gentle-pi 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +29 -25
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase.ts +1 -1
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +7 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +79 -32
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +52 -1
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +6 -5
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +5 -4
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-risk-assessment.test.ts +109 -33
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +27 -1
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -7,7 +7,18 @@ import { tmpdir } from "node:os";
|
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import test, { after, before } from "node:test";
|
|
9
9
|
import type { ExtensionAPI, ExtensionContext, ExtensionToolContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
10
|
+
import { __testing, createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
11
|
+
|
|
12
|
+
// S5 (startup-home-repo-and-changes): startup review negotiation runs after
|
|
13
|
+
// session_start returns and passes its deadline signal to native STATUS.
|
|
14
|
+
// These tests observe negotiation outcomes, so they wait for it, and they
|
|
15
|
+
// compare request contracts without the per-call signal.
|
|
16
|
+
const settleStartup = () => __testing.startupNegotiationSettled();
|
|
17
|
+
function withoutSignal<T>(request: T): T {
|
|
18
|
+
if (request === null || typeof request !== "object" || !("signal" in request)) return request;
|
|
19
|
+
const { signal: _signal, ...rest } = request as Record<string, unknown>;
|
|
20
|
+
return rest as T;
|
|
21
|
+
}
|
|
11
22
|
import { NATIVE_REVIEW_ERROR_CODE, NativeReviewCliError, type NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
12
23
|
import type { ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
13
24
|
|
|
@@ -235,7 +246,7 @@ for (const scenario of ["same", "changed", "sibling-root", "nested-root", "faile
|
|
|
235
246
|
const native = {
|
|
236
247
|
reviewMode: onMode("on"),
|
|
237
248
|
targetStatus: async (request: unknown) => {
|
|
238
|
-
statusRequests.push(request);
|
|
249
|
+
statusRequests.push(withoutSignal(request));
|
|
239
250
|
return burned ? executeStartStatus(nextTarget) : approved;
|
|
240
251
|
},
|
|
241
252
|
acknowledgeApproved: async (request: unknown) => {
|
|
@@ -260,6 +271,7 @@ for (const scenario of ["same", "changed", "sibling-root", "nested-root", "faile
|
|
|
260
271
|
await directWrite(handlers, session);
|
|
261
272
|
const result = await review.execute("post-ack", { operation: "acknowledge-approved", lineageId }, undefined, undefined, toolContext(session));
|
|
262
273
|
if (unsuccessful) {
|
|
274
|
+
assert.ok(typeof result.details === "object" && result.details !== null && "outcome" in result.details && "mutation_outcome" in result.details);
|
|
263
275
|
assert.equal(result.details.outcome, scenario === "failed" ? "native-operation-failed" : "native-mutation-status-reconciled");
|
|
264
276
|
assert.equal(result.details.mutation_outcome, scenario === "failed" ? "none" : "unknown");
|
|
265
277
|
} else assert.deepEqual(result.details, {
|
|
@@ -300,6 +312,7 @@ test("passive session events outside Git with RDD off never invoke native STATUS
|
|
|
300
312
|
const { handlers } = harness(native);
|
|
301
313
|
const session = ctx("outside-git", true, cwd);
|
|
302
314
|
await handlers.get("session_start")!({ type: "session_start" }, session);
|
|
315
|
+
await settleStartup();
|
|
303
316
|
await handlers.get("agent_end")!(agentEndEvent, session);
|
|
304
317
|
assert.deepEqual(calls, [], "passive non-Git events need no native entry");
|
|
305
318
|
assert.equal(existsSync(join(cwd, ".git")), false, "passive events must not initialize Git");
|
|
@@ -311,7 +324,7 @@ test("agent_end performs no STATUS call and sends nothing when RDD is off", asyn
|
|
|
311
324
|
const native = {
|
|
312
325
|
reviewMode: onMode("off"),
|
|
313
326
|
targetStatus: async (request: unknown) => {
|
|
314
|
-
statusRequests.push(request);
|
|
327
|
+
statusRequests.push(withoutSignal(request));
|
|
315
328
|
throw new Error("targetStatus must not be called when RDD is off");
|
|
316
329
|
},
|
|
317
330
|
} as unknown as NativeReviewCli;
|
|
@@ -329,7 +342,7 @@ test("agent_end nudges exactly once when RDD is on and STATUS offers review.star
|
|
|
329
342
|
const native = {
|
|
330
343
|
reviewMode: onMode("on"),
|
|
331
344
|
targetStatus: async (request: { agent?: string }) => {
|
|
332
|
-
statusRequests.push(request);
|
|
345
|
+
statusRequests.push(withoutSignal(request));
|
|
333
346
|
return executeStartStatus(targetIdentity);
|
|
334
347
|
},
|
|
335
348
|
} as unknown as NativeReviewCli;
|
|
@@ -351,6 +364,30 @@ test("agent_end nudges exactly once when RDD is on and STATUS offers review.star
|
|
|
351
364
|
assert.equal(statusRequests[0]?.agent, "pi");
|
|
352
365
|
});
|
|
353
366
|
|
|
367
|
+
// verify-always-rdd-high S2: automatic review is for high risk only. When the
|
|
368
|
+
// native assessment reports review_due false (a medium or passive candidate),
|
|
369
|
+
// agent_end never nudges; review_due true still nudges. An assessment that
|
|
370
|
+
// cannot answer keeps the nudge, so a reminder is never lost to an error.
|
|
371
|
+
test("agent_end nudges only when the native assessment reports review_due", async () => {
|
|
372
|
+
const targetIdentity = `sha256:${"b".repeat(64)}`;
|
|
373
|
+
for (const [label, assess, wantNudges] of [
|
|
374
|
+
["medium, not due", async () => ({ risk: "medium", reviewDue: false, reviewDueReason: "under_budget" }), 0],
|
|
375
|
+
["high, due", async () => ({ risk: "high", reviewDue: true, reviewDueReason: "high_risk" }), 1],
|
|
376
|
+
["assessment rejects", async () => { throw new Error("assess failed"); }, 1],
|
|
377
|
+
] as const) {
|
|
378
|
+
const native = {
|
|
379
|
+
reviewMode: onMode("on"),
|
|
380
|
+
targetStatus: async () => executeStartStatus(targetIdentity),
|
|
381
|
+
assess,
|
|
382
|
+
} as unknown as NativeReviewCli;
|
|
383
|
+
const { handlers, sent } = harness(native);
|
|
384
|
+
const session = ctx(`agent-end-review-due-${label.replace(/\W+/g, "-")}`);
|
|
385
|
+
await directWrite(handlers, session);
|
|
386
|
+
await handlers.get("agent_end")!(agentEndEvent, session);
|
|
387
|
+
assert.equal(sent.length, wantNudges, label);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
354
391
|
test("agent_end nudges once per target identity and again for a fresh identity", async () => {
|
|
355
392
|
let targetIdentity = `sha256:${"b".repeat(64)}`;
|
|
356
393
|
const native = {
|
|
@@ -431,7 +468,7 @@ test("gentle-ai-worker agent_end never queries native review", async () => {
|
|
|
431
468
|
const native = {
|
|
432
469
|
reviewMode: onMode("on"),
|
|
433
470
|
targetStatus: async (request: unknown) => {
|
|
434
|
-
statusRequests.push(request);
|
|
471
|
+
statusRequests.push(withoutSignal(request));
|
|
435
472
|
throw new Error("a bounded worker must not own native review");
|
|
436
473
|
},
|
|
437
474
|
} as unknown as NativeReviewCli;
|
|
@@ -452,7 +489,7 @@ test("agent_end sends nothing and performs no STATUS call when GENTLE_PI_AGENTS_
|
|
|
452
489
|
const native = {
|
|
453
490
|
reviewMode: onMode("on"),
|
|
454
491
|
targetStatus: async (request: unknown) => {
|
|
455
|
-
statusRequests.push(request);
|
|
492
|
+
statusRequests.push(withoutSignal(request));
|
|
456
493
|
throw new Error("targetStatus must not be called in a child session");
|
|
457
494
|
},
|
|
458
495
|
} as unknown as NativeReviewCli;
|
|
@@ -538,12 +575,14 @@ for (const scenario of ["reload", "fork", "new", "off-branch"] as const) {
|
|
|
538
575
|
const first = harness(native, entries);
|
|
539
576
|
const session = ctx(`receipt-${scenario}`, true, cwd);
|
|
540
577
|
await first.handlers.get("session_start")!({}, session);
|
|
578
|
+
await settleStartup();
|
|
541
579
|
await directWrite(first.handlers, session);
|
|
542
580
|
await first.handlers.get("session_shutdown")!({ reason: "reload" }, session);
|
|
543
581
|
const resumed = harness(native, entries);
|
|
544
582
|
const next = ctx(scenario === "reload" || scenario === "off-branch" ? `receipt-${scenario}` : `replacement-${scenario}`, true, cwd);
|
|
545
583
|
if (scenario === "off-branch") next.sessionManager.getBranch = () => [];
|
|
546
584
|
await resumed.handlers.get("session_start")!({ reason: scenario }, next);
|
|
585
|
+
await settleStartup();
|
|
547
586
|
const beforeEnd = statusCalls;
|
|
548
587
|
await resumed.handlers.get("agent_end")!(agentEndEvent, next);
|
|
549
588
|
assert.equal(statusCalls - beforeEnd, scenario === "reload" ? 1 : 0);
|
|
@@ -624,6 +663,7 @@ for (const mode of ["on", "off", "unknown", "missing", "malformed", "incompatibl
|
|
|
624
663
|
const { handlers, sent } = harness(native);
|
|
625
664
|
const session = ctx(`non-git-${mode}`, true, cwd);
|
|
626
665
|
await handlers.get("session_start")!({}, session);
|
|
666
|
+
await settleStartup();
|
|
627
667
|
assert.equal(requests.length, 0, "passive startup cannot bootstrap");
|
|
628
668
|
assert.equal(existsSync(join(cwd, ".git")), false);
|
|
629
669
|
await handlers.get("agent_end")!(agentEndEvent, session);
|
|
@@ -643,6 +683,7 @@ for (const path of ["odd/tasks/feature.md", "README.md", ".pi/settings.json", "m
|
|
|
643
683
|
const h = harness({ reviewMode: onMode("on"), targetStatus: async () => { calls++; return stopStatus("unused"); } } as unknown as NativeReviewCli);
|
|
644
684
|
const session = ctx("non-source", false, cwd);
|
|
645
685
|
await h.handlers.get("session_start")!({}, session);
|
|
686
|
+
await settleStartup();
|
|
646
687
|
await h.handlers.get("tool_result")!({ toolName: "write", toolCallId: "bookkeeping", input: { path }, isError: false }, session);
|
|
647
688
|
assert.equal(calls, 0);
|
|
648
689
|
assert.equal(existsSync(join(cwd, ".git")), false);
|
|
@@ -659,6 +700,7 @@ for (const initiallyGit of [true, false]) {
|
|
|
659
700
|
} } as unknown as NativeReviewCli);
|
|
660
701
|
const session = ctx("established-source", false, cwd);
|
|
661
702
|
await h.handlers.get("session_start")!({}, session);
|
|
703
|
+
await settleStartup();
|
|
662
704
|
if (!initiallyGit) { await directWrite(h.handlers, session); assert.equal(calls, 1); }
|
|
663
705
|
await rename(join(cwd, ".git"), join(cwd, "saved-git"));
|
|
664
706
|
calls = 0;
|
|
@@ -677,6 +719,7 @@ test("headless successful source write prepares after binding without a reminder
|
|
|
677
719
|
} } as unknown as NativeReviewCli);
|
|
678
720
|
const session = ctx("headless-source", false, cwd);
|
|
679
721
|
await h.handlers.get("session_start")!({}, session);
|
|
722
|
+
await settleStartup();
|
|
680
723
|
await directWrite(h.handlers, session);
|
|
681
724
|
assert.equal(calls, 1);
|
|
682
725
|
assert.equal(existsSync(join(cwd, ".git")), true);
|
|
@@ -690,7 +733,7 @@ test("session_start negotiates the current target identity when RDD is on", asyn
|
|
|
690
733
|
const native = {
|
|
691
734
|
reviewMode: onMode("on"),
|
|
692
735
|
targetStatus: async (request: { agent?: string }) => {
|
|
693
|
-
statusRequests.push(request);
|
|
736
|
+
statusRequests.push(withoutSignal(request));
|
|
694
737
|
return executeStartStatus(targetIdentity);
|
|
695
738
|
},
|
|
696
739
|
} as unknown as NativeReviewCli;
|
|
@@ -701,9 +744,10 @@ test("session_start negotiates the current target identity when RDD is on", asyn
|
|
|
701
744
|
const notifications: Array<{ message: string; severity: string }> = [];
|
|
702
745
|
const session = {
|
|
703
746
|
...ctx("session-baseline-record", true, cwd),
|
|
704
|
-
ui: { notify: (message: string, severity: string) => notifications.push({ message, severity }) },
|
|
747
|
+
ui: { ...ctx("session-baseline-record", true, cwd).ui, notify: (message: string, severity: string) => { notifications.push({ message, severity }); } },
|
|
705
748
|
};
|
|
706
749
|
await sessionStart!({}, session);
|
|
750
|
+
await settleStartup();
|
|
707
751
|
assert.equal(statusRequests[0]?.agent, "pi");
|
|
708
752
|
assert.deepEqual(sent, []);
|
|
709
753
|
assert.deepEqual(notifications, []);
|
|
@@ -723,6 +767,8 @@ test("agent_end skips pre-session work, then nudges after an own mutation", asyn
|
|
|
723
767
|
const session = ctx("session-baseline-skip", true, cwd);
|
|
724
768
|
|
|
725
769
|
await sessionStart!({}, session);
|
|
770
|
+
|
|
771
|
+
await settleStartup();
|
|
726
772
|
await agentEnd!(agentEndEvent, session);
|
|
727
773
|
assert.deepEqual(sent, [], "the baseline candidate predates the session and is not nudged");
|
|
728
774
|
|
|
@@ -746,7 +792,7 @@ for (const ownsMutation of [false, true]) {
|
|
|
746
792
|
const native = {
|
|
747
793
|
reviewMode: onMode("on"),
|
|
748
794
|
targetStatus: async (request: unknown) => {
|
|
749
|
-
statusRequests.push(request);
|
|
795
|
+
statusRequests.push(withoutSignal(request));
|
|
750
796
|
return executeStartStatus(targetIdentity);
|
|
751
797
|
},
|
|
752
798
|
} as unknown as NativeReviewCli;
|
|
@@ -760,6 +806,8 @@ for (const ownsMutation of [false, true]) {
|
|
|
760
806
|
assert.equal(typeof agentEnd, "function");
|
|
761
807
|
|
|
762
808
|
await sessionStart!({}, session);
|
|
809
|
+
|
|
810
|
+
await settleStartup();
|
|
763
811
|
assert.deepEqual(statusRequests, [{ cwd, agent: "pi" }], "startup records targetA separately");
|
|
764
812
|
assert.deepEqual(sent, []);
|
|
765
813
|
// Like host event delivery, an event without a subscriber is a no-op.
|
|
@@ -810,7 +858,7 @@ test("agent_end reminds for an own write after startup with RDD off", async () =
|
|
|
810
858
|
status: { global: "", cloneLocal: mode === "on" ? "on" : "", effective: mode, source: "clone_local" },
|
|
811
859
|
}),
|
|
812
860
|
targetStatus: async (request: unknown) => {
|
|
813
|
-
statusRequests.push(request);
|
|
861
|
+
statusRequests.push(withoutSignal(request));
|
|
814
862
|
if (mode === "off") throw new Error("targetStatus must not be called when RDD is off");
|
|
815
863
|
return executeStartStatus(targetIdentity);
|
|
816
864
|
},
|
|
@@ -822,6 +870,8 @@ test("agent_end reminds for an own write after startup with RDD off", async () =
|
|
|
822
870
|
const session = ctx("session-baseline-rdd-off", true, cwd);
|
|
823
871
|
|
|
824
872
|
await sessionStart!({}, session);
|
|
873
|
+
|
|
874
|
+
await settleStartup();
|
|
825
875
|
assert.deepEqual(statusRequests, []);
|
|
826
876
|
|
|
827
877
|
mode = "on";
|
|
@@ -870,6 +920,8 @@ test("session_shutdown does not turn the baseline into an own mutation", async (
|
|
|
870
920
|
const session = ctx("session-baseline-shutdown", true, cwd);
|
|
871
921
|
|
|
872
922
|
await sessionStart!({}, session);
|
|
923
|
+
|
|
924
|
+
await settleStartup();
|
|
873
925
|
await agentEnd!(agentEndEvent, session);
|
|
874
926
|
assert.deepEqual(sent, [], "the baseline candidate is skipped");
|
|
875
927
|
|
|
@@ -364,6 +364,55 @@ test("candidate owner publication never removes a replaced marker", (t) => {
|
|
|
364
364
|
assert.equal(readdirSync(parent).some((name) => name.endsWith(".reaper-lock")), false);
|
|
365
365
|
});
|
|
366
366
|
|
|
367
|
+
test("candidate owner publication preserves a replaced marker with 64-bit identity precision loss", (t) => {
|
|
368
|
+
mockWindowsAcl(t);
|
|
369
|
+
// The registry canonicalizes the contributor root (e.g. /var -> /private/var on macOS).
|
|
370
|
+
const cwd = repository(t), parent = join(realpathSync(cwd), ".git", "gentle-ai", "candidate-views");
|
|
371
|
+
const originalLstat = fs.lstatSync, fsync = fs.fsyncSync, write = fs.writeFileSync;
|
|
372
|
+
// Modeled identities, not measured NTFS values: distinct 64-bit indices round to one Number.
|
|
373
|
+
const high = 2n ** 53n;
|
|
374
|
+
assert.notEqual(high, high + 1n);
|
|
375
|
+
assert.equal(Number(high), Number(high + 1n));
|
|
376
|
+
let marker: string | undefined, replaced = false, faults = 0, adds = 0;
|
|
377
|
+
let savedContent: string | undefined;
|
|
378
|
+
t.mock.method(fs, "lstatSync", (path: Parameters<typeof fs.lstatSync>[0], ...args: unknown[]) => {
|
|
379
|
+
const stat = (originalLstat as (...arguments_: unknown[]) => ReturnType<typeof fs.lstatSync>)(path, ...args);
|
|
380
|
+
if (marker === undefined && typeof path === "string" && path.endsWith(".owner.json") && path === join(parent, readdirSync(parent).find((name) => name.endsWith(".owner.json")) ?? "")) marker = path;
|
|
381
|
+
if (path !== marker) return stat;
|
|
382
|
+
const bigint = (args[0] as { bigint?: boolean } | undefined)?.bigint === true;
|
|
383
|
+
const ino = replaced ? high + 1n : high;
|
|
384
|
+
// Retain real metadata and Stats methods; only the identity pair is synthetic.
|
|
385
|
+
return Object.assign(Object.create(Object.getPrototypeOf(stat)), stat, {
|
|
386
|
+
dev: bigint ? high : Number(high), ino: bigint ? ino : Number(ino),
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
t.mock.method(fs, "fsyncSync", (fd: number) => {
|
|
390
|
+
// The reaper lock is synced before marker publication; it is not this fault's boundary.
|
|
391
|
+
if (marker === undefined || replaced) return fsync(fd);
|
|
392
|
+
assert.ok(marker, "owned marker identity must be captured before the fault");
|
|
393
|
+
faults++;
|
|
394
|
+
savedContent = readFileSync(marker, "utf8");
|
|
395
|
+
renameSync(marker, `${marker}.saved`);
|
|
396
|
+
write(marker, "replacement", { mode: 0o600 });
|
|
397
|
+
replaced = true;
|
|
398
|
+
throw new Error("fixture marker fsync failure after 64-bit identity replacement");
|
|
399
|
+
});
|
|
400
|
+
syncBuiltinESMExports();
|
|
401
|
+
t.after(() => { t.mock.restoreAll(); syncBuiltinESMExports(); });
|
|
402
|
+
const registry = new CandidateViewRegistry((file, args, options) => {
|
|
403
|
+
if (args[0] === "worktree" && args[1] === "add") adds++;
|
|
404
|
+
return execFileSync(file, args, options);
|
|
405
|
+
}, "win32");
|
|
406
|
+
assert.throws(() => registry.create({ contributorRoot: cwd }), (error: unknown) => error instanceof CandidateViewError && error.reason === "candidate-owner-preparation-failed");
|
|
407
|
+
assert.equal(faults, 1, "replacement fault must be reached exactly once");
|
|
408
|
+
assert.equal(adds, 0);
|
|
409
|
+
assert.ok(marker);
|
|
410
|
+
assert.equal(readFileSync(`${marker}.saved`, "utf8"), savedContent);
|
|
411
|
+
assert.equal(readdirSync(parent).some((name) => name.endsWith(".reaper-lock")), false);
|
|
412
|
+
assert.equal(existsSync(marker), true, "publication rollback deleted the replacement after distinct 64-bit identities rounded to the same Number");
|
|
413
|
+
assert.equal(readFileSync(marker, "utf8"), "replacement");
|
|
414
|
+
});
|
|
415
|
+
|
|
367
416
|
test("public POSIX candidate-views parent reports bounded privacy guidance without changing permissions or adding a worktree", { skip: process.platform === "win32" }, (t) => {
|
|
368
417
|
const cwd = repository(t);
|
|
369
418
|
const parent = join(cwd, ".git", "gentle-ai", "candidate-views");
|
|
@@ -721,6 +770,47 @@ for (const race of ["root", "registration", "lock"] as const) {
|
|
|
721
770
|
});
|
|
722
771
|
}
|
|
723
772
|
|
|
773
|
+
test("ordinary cleanup preserves a replaced root with 64-bit identity precision loss", (t) => {
|
|
774
|
+
mockWindowsAcl(t);
|
|
775
|
+
const cwd = repository(t), high = 2n ** 53n;
|
|
776
|
+
const originalLstat = fs.lstatSync;
|
|
777
|
+
let root = "", replaced = false, lists = 0, removes = 0;
|
|
778
|
+
const registry = new CandidateViewRegistry((file, args, options) => {
|
|
779
|
+
// Never allow Git to delete the replacement, even on the failing implementation.
|
|
780
|
+
if (args[0] === "worktree" && args[1] === "remove") { removes++; return ""; }
|
|
781
|
+
const result = execFileSync(file, args, options);
|
|
782
|
+
if (root && args[0] === "worktree" && args[1] === "list" && ++lists === 2) {
|
|
783
|
+
const pointer = readFileSync(join(root, ".git"));
|
|
784
|
+
renameSync(root, `${root}.saved`);
|
|
785
|
+
mkdirSync(root);
|
|
786
|
+
writeFileSync(join(root, ".git"), pointer);
|
|
787
|
+
writeFileSync(join(root, "replacement.txt"), "replacement");
|
|
788
|
+
replaced = true;
|
|
789
|
+
}
|
|
790
|
+
return result;
|
|
791
|
+
});
|
|
792
|
+
const view = registry.create({ contributorRoot: cwd });
|
|
793
|
+
root = view.root;
|
|
794
|
+
t.mock.method(fs, "lstatSync", (path: Parameters<typeof fs.lstatSync>[0], ...args: unknown[]) => {
|
|
795
|
+
const stat = (originalLstat as (...arguments_: unknown[]) => ReturnType<typeof fs.lstatSync>)(path, ...args);
|
|
796
|
+
if (path !== root) return stat;
|
|
797
|
+
const bigint = (args[0] as { bigint?: boolean } | undefined)?.bigint === true;
|
|
798
|
+
const ino = replaced ? high + 1n : high;
|
|
799
|
+
return Object.assign(Object.create(Object.getPrototypeOf(stat)), stat, {
|
|
800
|
+
dev: bigint ? high : Number(high), ino: bigint ? ino : Number(ino),
|
|
801
|
+
});
|
|
802
|
+
});
|
|
803
|
+
syncBuiltinESMExports();
|
|
804
|
+
t.after(() => { t.mock.restoreAll(); syncBuiltinESMExports(); });
|
|
805
|
+
assert.throws(() => view.cleanup());
|
|
806
|
+
assert.equal(replaced, true);
|
|
807
|
+
assert.equal(readFileSync(join(root, "replacement.txt"), "utf8"), "replacement");
|
|
808
|
+
assert.equal(existsSync(`${root}.saved`), true);
|
|
809
|
+
assert.equal(existsSync(ownerMarker(root)), true);
|
|
810
|
+
assert.equal(existsSync(`${root}.reaper-lock`), false);
|
|
811
|
+
assert.equal(removes, 0, "distinct 64-bit directory identities must stop cleanup before Git removal");
|
|
812
|
+
});
|
|
813
|
+
|
|
724
814
|
test("ordinary cleanup never unlinks a replaced sidecar after Git removal", (t) => {
|
|
725
815
|
const cwd = repository(t);
|
|
726
816
|
let marker = "";
|
|
@@ -1193,22 +1283,21 @@ test("candidate view skips a shared index that disappears during stat or copy",
|
|
|
1193
1283
|
for (const phase of ["stat", "copy"] as const) {
|
|
1194
1284
|
const originalLstatSync = fs.lstatSync;
|
|
1195
1285
|
const originalCopyFileSync = fs.copyFileSync;
|
|
1196
|
-
fs
|
|
1286
|
+
t.mock.method(fs, "lstatSync", (path: string | Buffer, options?: Parameters<typeof lstatSync>[1]) => {
|
|
1197
1287
|
if (phase === "stat" && path === sharedIndexPath) throw Object.assign(new Error("shared index disappeared"), { code: "ENOENT" });
|
|
1198
1288
|
return originalLstatSync(path, options);
|
|
1199
|
-
})
|
|
1200
|
-
fs
|
|
1289
|
+
});
|
|
1290
|
+
t.mock.method(fs, "copyFileSync", (source: string | Buffer, destination: string | Buffer) => {
|
|
1201
1291
|
if (phase === "copy" && source === sharedIndexPath) throw Object.assign(new Error("shared index disappeared"), { code: "ENOENT" });
|
|
1202
1292
|
return originalCopyFileSync(source, destination);
|
|
1203
|
-
})
|
|
1293
|
+
});
|
|
1204
1294
|
syncBuiltinESMExports();
|
|
1205
1295
|
let view: ReturnType<typeof createCandidateView> | undefined;
|
|
1206
1296
|
try {
|
|
1207
1297
|
view = createCandidateView({ contributorRoot, intendedUntracked: [] });
|
|
1208
1298
|
assert.equal(view.paths.includes("staged-addition.txt"), true, phase);
|
|
1209
1299
|
} finally {
|
|
1210
|
-
|
|
1211
|
-
fs.copyFileSync = originalCopyFileSync;
|
|
1300
|
+
t.mock.restoreAll();
|
|
1212
1301
|
syncBuiltinESMExports();
|
|
1213
1302
|
view?.cleanup();
|
|
1214
1303
|
}
|
|
@@ -1435,13 +1524,13 @@ test("corrected views stay within frozen scope and replace projections only when
|
|
|
1435
1524
|
const registry = new CandidateViewRegistry();
|
|
1436
1525
|
const initial = registry.create({ contributorRoot }); registry.bind({ token: initial.token, lineageId: "correction", selectedLenses: ["review-risk"] });
|
|
1437
1526
|
writeFileSync(join(contributorRoot, "tracked.txt"), "corrected\n");
|
|
1438
|
-
const corrected = registry.createCorrected("correction", contributorRoot);
|
|
1527
|
+
const corrected = registry.createCorrected("correction", contributorRoot, "corrected-1");
|
|
1439
1528
|
assert.notEqual(corrected.candidateTree, initial.candidateTree);
|
|
1440
1529
|
assert.equal(registry.resolveProjection("correction", contributorRoot).candidateTree, initial.candidateTree);
|
|
1441
1530
|
registry.promoteCorrected("correction", corrected.token);
|
|
1442
1531
|
assert.equal(registry.resolveProjection("correction", contributorRoot).candidateTree, corrected.candidateTree);
|
|
1443
1532
|
writeFileSync(join(contributorRoot, "escaped.txt"), "outside scope\n");
|
|
1444
|
-
assert.throws(() => registry.createCorrected("correction", contributorRoot), /escapes the frozen genesis paths/);
|
|
1533
|
+
assert.throws(() => registry.createCorrected("correction", contributorRoot, "escaped-2"), /escapes the frozen genesis paths/);
|
|
1445
1534
|
registry.cleanupTerminal("correction", "approved");
|
|
1446
1535
|
});
|
|
1447
1536
|
|
|
@@ -1664,7 +1753,7 @@ test("candidate view compacts an oversized non-ASCII scope losslessly and determ
|
|
|
1664
1753
|
assert.throws(() => readCandidateContextManifestPage(compact.encoded, compact.sha256, actorEntries.length + 1), /cursor/);
|
|
1665
1754
|
const nonCanonicalBytes = Buffer.from(JSON.stringify({ gitlinks: decoded.manifest.gitlinks, scopeByMode: decoded.manifest.scopeByMode, version: 1 }), "utf8");
|
|
1666
1755
|
assert.throws(
|
|
1667
|
-
() => decodeCandidateContextManifest(gzipSync(nonCanonicalBytes
|
|
1756
|
+
() => decodeCandidateContextManifest(gzipSync(nonCanonicalBytes).toString("base64url"), createHash("sha256").update(nonCanonicalBytes).digest("hex")),
|
|
1668
1757
|
/canonical/,
|
|
1669
1758
|
);
|
|
1670
1759
|
} finally {
|
|
@@ -1682,7 +1771,7 @@ test("candidate context manifest decoder accepts canonical numeric-looking gitli
|
|
|
1682
1771
|
scopeByMode: { "160000": ["10", "2"] },
|
|
1683
1772
|
gitlinks,
|
|
1684
1773
|
}), "utf8");
|
|
1685
|
-
const encoded = gzipSync(bytes
|
|
1774
|
+
const encoded = gzipSync(bytes).toString("base64url");
|
|
1686
1775
|
assert.deepEqual(decodeCandidateContextManifest(encoded, createHash("sha256").update(bytes).digest("hex")).manifest, {
|
|
1687
1776
|
version: 1,
|
|
1688
1777
|
scopeByMode: { "160000": ["10", "2"] },
|
|
@@ -1701,7 +1790,7 @@ test("candidate context manifest decoder rejects noncanonical nonnumeric gitlink
|
|
|
1701
1790
|
gitlinks,
|
|
1702
1791
|
}), "utf8");
|
|
1703
1792
|
assert.throws(
|
|
1704
|
-
() => decodeCandidateContextManifest(gzipSync(bytes
|
|
1793
|
+
() => decodeCandidateContextManifest(gzipSync(bytes).toString("base64url"), createHash("sha256").update(bytes).digest("hex")),
|
|
1705
1794
|
/canonical/,
|
|
1706
1795
|
);
|
|
1707
1796
|
});
|
|
@@ -1709,7 +1798,7 @@ test("candidate context manifest decoder rejects noncanonical nonnumeric gitlink
|
|
|
1709
1798
|
test("candidate context manifest decoder rejects noncanonical gzip transport for verified bytes", () => {
|
|
1710
1799
|
const bytes = Buffer.from(JSON.stringify({ version: 1, scopeByMode: { "100644": ["file.ts"] }, gitlinks: {} }), "utf8");
|
|
1711
1800
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
1712
|
-
const canonical = gzipSync(bytes
|
|
1801
|
+
const canonical = gzipSync(bytes);
|
|
1713
1802
|
const noncanonical = Buffer.from(canonical);
|
|
1714
1803
|
noncanonical[4] = (noncanonical[4]! + 1) & 0xff;
|
|
1715
1804
|
assert.throws(() => decodeCandidateContextManifest(noncanonical.toString("base64url"), sha256), /canonical/);
|
|
@@ -6,7 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
import test, { after, before } from "node:test";
|
|
7
7
|
import { pathToFileURL } from "node:url";
|
|
8
8
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
9
|
+
import { createGentleAiExtension, __testing } from "../extensions/gentle-ai.ts";
|
|
10
10
|
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
11
11
|
|
|
12
12
|
// gentle-pi#560 / gentle-ai#4056, #4057: since 2026-08-01 Gentle AI stopped
|
|
@@ -103,7 +103,7 @@ test("before_agent_start injects the mirrored review execution contract for the
|
|
|
103
103
|
assert.equal(result, undefined, "the handler must not return a replacement systemPrompt");
|
|
104
104
|
const appended = event.systemPromptOptions.appendSystemPrompt;
|
|
105
105
|
const expected = mirroredPiOrchestrationText();
|
|
106
|
-
assert.match(appended, /
|
|
106
|
+
assert.match(appended, /large tasks get ODD tracking and workers/);
|
|
107
107
|
assert.match(appended, /For behavior changes with applicable runnable deterministic tests and a clear expected outcome, use test-first by default: observe RED, GREEN, then refactor with focused checks/);
|
|
108
108
|
assert.match(appended, /Test presence alone does not establish applicability; no chat or TUI toggle activates it/);
|
|
109
109
|
assert.match(appended, /no meaningful RED, explain why and run proportionate ordinary functional or structural verification/);
|
|
@@ -111,7 +111,7 @@ test("before_agent_start injects the mirrored review execution contract for the
|
|
|
111
111
|
assert.doesNotMatch(appended, /If tests exist, use strict TDD/);
|
|
112
112
|
assert.match(appended, /ODD \(Default Workflow, harness section above\) is mandatory on every request/);
|
|
113
113
|
assert.doesNotMatch(appended, /Prefer SDD\/OpenSpec artifacts/);
|
|
114
|
-
assert.match(appended, /## Gentle AI review execution contract \(mirrored provider bundle 1\.
|
|
114
|
+
assert.match(appended, /## Gentle AI review execution contract \(mirrored provider bundle 1\.3\.0\)/);
|
|
115
115
|
assert.ok(appended.includes(expected), "the mirrored orchestration/pi.md text must appear verbatim");
|
|
116
116
|
assert.match(appended, /call `gentle_review` with {"operation":"inspect"}/);
|
|
117
117
|
assert.match(appended, /call `gentle_review` with operation `status`, the exact retained `lineageId`, and `workspaceRoot`/);
|
|
@@ -162,7 +162,7 @@ test("before_agent_start does not let legacy prompt text bypass primary ODD and
|
|
|
162
162
|
const event = primaryEvent({ systemPrompt: "SDD apply executor body" });
|
|
163
163
|
await beforeAgentStart(event, ctx());
|
|
164
164
|
const appended = event.systemPromptOptions.appendSystemPrompt;
|
|
165
|
-
assert.match(appended, /
|
|
165
|
+
assert.match(appended, /large tasks get ODD tracking and workers/);
|
|
166
166
|
assert.match(appended, /Gentle AI review execution contract/);
|
|
167
167
|
assert.doesNotMatch(appended, /### 3\. SDD \(optional\)/);
|
|
168
168
|
});
|
|
@@ -228,3 +228,32 @@ test("rejects a tampered mirror, accepts a matching one, and warns once", async
|
|
|
228
228
|
rmSync(matching, { recursive: true, force: true });
|
|
229
229
|
}
|
|
230
230
|
});
|
|
231
|
+
|
|
232
|
+
// gentle-shell#1494 F2: when receipt-driven development is off, the review
|
|
233
|
+
// execution contract is irrelevant to the session, so it is not loaded. On and
|
|
234
|
+
// unknown keep it (unknown fails safe toward the reviewed path).
|
|
235
|
+
function rddCli(effective: "on" | "off" | "throws"): NativeReviewCli {
|
|
236
|
+
return {
|
|
237
|
+
reviewMode: async () => {
|
|
238
|
+
if (effective === "throws") throw new Error("native review mode is unavailable");
|
|
239
|
+
return { operation: "status", scope: "clone", status: { global: effective, cloneLocal: "", effective, source: "global" } };
|
|
240
|
+
},
|
|
241
|
+
} as unknown as NativeReviewCli;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const [effective, injected] of [["off", false], ["on", true], ["throws", true]] as const) {
|
|
245
|
+
test(`before_agent_start ${injected ? "injects" : "skips"} the review execution contract when RDD is ${effective === "throws" ? "unknown" : effective}`, async () => {
|
|
246
|
+
__testing.clearRddStatusMemoForTesting();
|
|
247
|
+
try {
|
|
248
|
+
const { beforeAgentStart } = harness(rddCli(effective));
|
|
249
|
+
const event = primaryEvent();
|
|
250
|
+
await beforeAgentStart(event, ctx());
|
|
251
|
+
const appended = event.systemPromptOptions.appendSystemPrompt;
|
|
252
|
+
assert.match(appended, /# el Gentleman Orchestrator/, "the harness itself is always injected");
|
|
253
|
+
if (injected) assert.match(appended, /Gentle AI review execution contract/);
|
|
254
|
+
else assert.doesNotMatch(appended, /Gentle AI review execution contract/);
|
|
255
|
+
} finally {
|
|
256
|
+
__testing.clearRddStatusMemoForTesting();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|