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
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
type NativeReviewOutcome,
|
|
30
30
|
} from "../lib/review-risk-assessment.ts";
|
|
31
31
|
import { consumeReviewMutation, pendingReviewMutation, recordReviewMutation } from "../lib/review-reminder-receipt.ts";
|
|
32
|
+
import type { ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
32
33
|
|
|
33
34
|
// ---------------------------------------------------------------------------
|
|
34
35
|
// gentle-pi#662: decoder for the native `gentle-ai review assess` envelope
|
|
@@ -188,9 +189,9 @@ test("decodeReviewAssessmentV1 rejects malformed consumed, review_due, and revie
|
|
|
188
189
|
|
|
189
190
|
// gentle-pi#1175 (T2): the native schema fixes review_due by reason
|
|
190
191
|
// (true for high_risk and slice_budget_reached, false for passive,
|
|
191
|
-
// under_budget, and already_reviewed)
|
|
192
|
-
//
|
|
193
|
-
//
|
|
192
|
+
// under_budget, and already_reviewed). Consumed candidates must report
|
|
193
|
+
// already_reviewed, but an acknowledged predecessor plus a passive delta can
|
|
194
|
+
// also produce that reason without consuming this exact candidate (#1954).
|
|
194
195
|
interface ReviewDuePair {
|
|
195
196
|
reason: string;
|
|
196
197
|
due: boolean;
|
|
@@ -203,6 +204,7 @@ const CONSISTENT_REVIEW_DUE_PAIRS: readonly ReviewDuePair[] = [
|
|
|
203
204
|
{ reason: "passive", due: false, consumed: false },
|
|
204
205
|
{ reason: "under_budget", due: false, consumed: false },
|
|
205
206
|
{ reason: "already_reviewed", due: false, consumed: true },
|
|
207
|
+
{ reason: "already_reviewed", due: false, consumed: false },
|
|
206
208
|
];
|
|
207
209
|
|
|
208
210
|
function consistentEnvelope(pair: ReviewDuePair): Record<string, unknown> {
|
|
@@ -237,14 +239,38 @@ test("decodeReviewAssessmentV1 rejects a review_due boolean that contradicts rev
|
|
|
237
239
|
}
|
|
238
240
|
});
|
|
239
241
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
242
|
+
function reviewedPredecessorEnvelope(): Record<string, unknown> {
|
|
243
|
+
// The executable range still has medium risk; only the delta after the
|
|
244
|
+
// acknowledged committed predecessor is a passive ODD note.
|
|
245
|
+
return validEnvelope({
|
|
246
|
+
risk: "medium",
|
|
247
|
+
reasons: [{ code: "executable_changes" }],
|
|
248
|
+
changed_paths: 3,
|
|
249
|
+
changed_lines: 24,
|
|
250
|
+
candidate: { kind: "current-changes", consumed: false },
|
|
251
|
+
review_due: false,
|
|
252
|
+
review_due_reason: "already_reviewed",
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
test("decodeReviewAssessmentV1 accepts an already-reviewed predecessor without consuming the medium-risk candidate (#1954)", () => {
|
|
257
|
+
const decoded = decodeReviewAssessmentV1(reviewedPredecessorEnvelope());
|
|
258
|
+
assert.equal(decoded.risk, "medium");
|
|
259
|
+
assert.deepEqual(decoded.reasons, [{ code: "executable_changes" }]);
|
|
260
|
+
assert.equal(decoded.changedPaths, 3);
|
|
261
|
+
assert.equal(decoded.changedLines, 24);
|
|
262
|
+
assert.equal(decoded.reviewDue, false);
|
|
263
|
+
assert.equal(decoded.reviewDueReason, "already_reviewed");
|
|
264
|
+
assert.equal(decoded.candidate.consumed, false);
|
|
265
|
+
assert.equal(Object.hasOwn(decoded, "nextTransition"), false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("decodeReviewAssessmentV1 rejects already_reviewed with missing consumed evidence", () => {
|
|
269
|
+
// Native v2 requires consumed; older envelopes omit the entire due pair.
|
|
270
|
+
assert.throws(
|
|
271
|
+
() => decodeReviewAssessmentV1(newEnvelope({ candidate: { kind: "current-changes" }, review_due: false, review_due_reason: "already_reviewed", next_transition: undefined })),
|
|
272
|
+
TypeError,
|
|
273
|
+
);
|
|
248
274
|
});
|
|
249
275
|
|
|
250
276
|
test("decodeReviewAssessmentV1 rejects a consumed candidate reported with any reason other than already_reviewed", () => {
|
|
@@ -311,24 +337,22 @@ const RDD_LINES: readonly RddLine[] = [RDD_LINE.ON, RDD_LINE.OFF, RDD_LINE.UNKNO
|
|
|
311
337
|
const PROFILES: readonly WriterProfile[] = [WRITER_PROFILE.SMALL, WRITER_PROFILE.LARGE];
|
|
312
338
|
const NON_CLOSED_OUTCOMES: readonly NativeReviewOutcome[] = [NATIVE_REVIEW_OUTCOME.DECLINED, NATIVE_REVIEW_OUTCOME.UNAVAILABLE, NATIVE_REVIEW_OUTCOME.UNKNOWN];
|
|
313
339
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
assert.equal(plan.independentVerifier, false);
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
test("verificationPlan: rdd on + closed, medium/high/unassessable risk -> writer self-verification, no independent verifier", () => {
|
|
324
|
-
for (const risk of [VERIFICATION_TIER.MEDIUM, VERIFICATION_TIER.HIGH, VERIFICATION_TIER.UNASSESSABLE]) {
|
|
340
|
+
// verify-always-rdd-high S1: the native review is an additional outside view
|
|
341
|
+
// and never replaces verification, so a closed review under rdd on yields the
|
|
342
|
+
// exact risk-gated plan rdd off yields, for every risk and writer profile.
|
|
343
|
+
test("verificationPlan: rdd on + closed behaves exactly like rdd off for every risk/profile combination", () => {
|
|
344
|
+
for (const risk of RISKS) {
|
|
325
345
|
for (const writerProfile of PROFILES) {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
assert.equal(
|
|
329
|
-
assert.equal(
|
|
346
|
+
const off = verificationPlan({ rddLine: RDD_LINE.OFF, risk, writerProfile });
|
|
347
|
+
const closed = verificationPlan({ rddLine: RDD_LINE.ON, risk, writerProfile, nativeReviewOutcome: NATIVE_REVIEW_OUTCOME.CLOSED });
|
|
348
|
+
assert.equal(closed.writerSelfVerification, off.writerSelfVerification, `closed, risk ${risk}, profile ${writerProfile}`);
|
|
349
|
+
assert.equal(closed.structuralReadbackOnly, off.structuralReadbackOnly, `closed, risk ${risk}, profile ${writerProfile}`);
|
|
350
|
+
assert.equal(closed.independentVerifier, off.independentVerifier, `closed, risk ${risk}, profile ${writerProfile}`);
|
|
330
351
|
}
|
|
331
352
|
}
|
|
353
|
+
const high = verificationPlan({ rddLine: RDD_LINE.ON, risk: VERIFICATION_TIER.HIGH, writerProfile: WRITER_PROFILE.LARGE, nativeReviewOutcome: NATIVE_REVIEW_OUTCOME.CLOSED });
|
|
354
|
+
assert.equal(high.independentVerifier, true, "a closed review never removes the high-risk independent verifier");
|
|
355
|
+
assert.match(high.reason, /never replaces verification/);
|
|
332
356
|
});
|
|
333
357
|
|
|
334
358
|
// ---------------------------------------------------------------------------
|
|
@@ -365,9 +389,9 @@ test("verificationPlan: an omitted nativeReviewOutcome under rdd on defaults to
|
|
|
365
389
|
}
|
|
366
390
|
});
|
|
367
391
|
|
|
368
|
-
test("verificationPlan: on+closed+medium+
|
|
369
|
-
const plan = verificationPlan({ rddLine: RDD_LINE.ON, risk: VERIFICATION_TIER.MEDIUM, writerProfile: WRITER_PROFILE.
|
|
370
|
-
assert.equal(plan.independentVerifier,
|
|
392
|
+
test("verificationPlan: on+closed+medium+small -> verifier (the small-model bias still applies)", () => {
|
|
393
|
+
const plan = verificationPlan({ rddLine: RDD_LINE.ON, risk: VERIFICATION_TIER.MEDIUM, writerProfile: WRITER_PROFILE.SMALL, nativeReviewOutcome: NATIVE_REVIEW_OUTCOME.CLOSED });
|
|
394
|
+
assert.equal(plan.independentVerifier, true);
|
|
371
395
|
assert.equal(plan.writerSelfVerification, true);
|
|
372
396
|
});
|
|
373
397
|
|
|
@@ -712,7 +736,25 @@ function assessOnNativeCli(currentTargetIdentity: () => string): Partial<NativeR
|
|
|
712
736
|
return {
|
|
713
737
|
reviewMode: async () => ({ operation: "status", scope: "clone", status: { global: "on", cloneLocal: "", effective: "on", source: NATIVE_REVIEW_MODE_SOURCE.GLOBAL } }),
|
|
714
738
|
assess: async () => ({ schema: REVIEW_ASSESSMENT_SCHEMA, risk: "high", reasons: [], changedPaths: 1, changedLines: 5, candidate: { kind: "current-changes", baseRef: undefined } }),
|
|
715
|
-
targetStatus:
|
|
739
|
+
targetStatus: async (): Promise<ReviewStatusV3> => ({
|
|
740
|
+
contract: "gentle-ai.review-integration/v2", applicability: "current_target",
|
|
741
|
+
action: "stop", replayability: "manual_action_required", targetIdentity: currentTargetIdentity(),
|
|
742
|
+
projection: {
|
|
743
|
+
schema: "gentle-ai.review-integration.projection/v1",
|
|
744
|
+
kind: "current-changes", projection: "workspace", baseTree: "a".repeat(40),
|
|
745
|
+
initialReviewTree: "a".repeat(40), currentCandidateTree: "b".repeat(40),
|
|
746
|
+
pathsDigest: "c".repeat(64), paths: ["candidate.ts"], intendedUntracked: [],
|
|
747
|
+
intendedUntrackedProof: "d".repeat(64), initialSnapshotIdentity: "e".repeat(64),
|
|
748
|
+
currentSnapshotIdentity: currentTargetIdentity(),
|
|
749
|
+
},
|
|
750
|
+
repair: {
|
|
751
|
+
schema: "gentle-ai.review-authority-repair-assessment/v1", status: "unsupported",
|
|
752
|
+
counts: { lineages: 0, compactLineages: 0, legacyLineages: 0, events: 0, bytes: 0, eligibleCandidates: 0, unsupportedLineages: 0, conflicts: 0 },
|
|
753
|
+
supportedOperations: ["review/complete-fix", "review/validate-fix"],
|
|
754
|
+
authorizationSchema: "gentle-ai.review-repair-authorization/v1",
|
|
755
|
+
},
|
|
756
|
+
candidates: [], raw: {},
|
|
757
|
+
}),
|
|
716
758
|
};
|
|
717
759
|
}
|
|
718
760
|
|
|
@@ -806,12 +848,46 @@ async function assessWith(cli: Partial<NativeReviewCli>, input?: Record<string,
|
|
|
806
848
|
return (await reviewControllerTool(cli).execute("closure", params, undefined, undefined, context)).details as AssessDetails;
|
|
807
849
|
}
|
|
808
850
|
|
|
851
|
+
test("gentle_review assess: already-reviewed predecessor remains medium and never proves exact-candidate closure (#1954)", async (t) => {
|
|
852
|
+
t.after(() => __testing.clearNativeReviewOutcomeMemoForTesting());
|
|
853
|
+
__testing.clearNativeReviewOutcomeMemoForTesting();
|
|
854
|
+
for (const input of [undefined, { nativeReviewOutcome: "closed" }]) {
|
|
855
|
+
const queue = queuedAdapter([{ stdout: JSON.stringify(reviewedPredecessorEnvelope()) }]);
|
|
856
|
+
const client = nativeClient(queue.adapter);
|
|
857
|
+
const cli = {
|
|
858
|
+
...closureCli({ risk: "medium", consumed: false }),
|
|
859
|
+
assess: client.assess.bind(client),
|
|
860
|
+
};
|
|
861
|
+
const details = await assessWith(cli, input) as AssessDetails & {
|
|
862
|
+
candidate: { consumed: boolean };
|
|
863
|
+
reviewDue: boolean;
|
|
864
|
+
reviewDueReason: string;
|
|
865
|
+
};
|
|
866
|
+
assert.equal(queue.calls.length, 1, "assessment must pass through the native CLI decoder");
|
|
867
|
+
assert.equal(details.risk, "medium", "supported predecessor evidence must not become unassessable");
|
|
868
|
+
assert.equal(details.candidate.consumed, false);
|
|
869
|
+
assert.equal(details.reviewDue, false);
|
|
870
|
+
assert.equal(details.reviewDueReason, "already_reviewed");
|
|
871
|
+
assert.equal(Object.hasOwn(details, "nextTransition"), false);
|
|
872
|
+
assert.equal(details.nativeReviewOutcome, "unknown", "already_reviewed is not exact-candidate consumption");
|
|
873
|
+
assert.equal(details.outcome_source, "unknown");
|
|
874
|
+
assert.deepEqual(details.plan, verificationPlan({
|
|
875
|
+
rddLine: RDD_LINE.ON,
|
|
876
|
+
risk: VERIFICATION_TIER.MEDIUM,
|
|
877
|
+
writerProfile: details.writerProfile as WriterProfile,
|
|
878
|
+
nativeReviewOutcome: NATIVE_REVIEW_OUTCOME.UNKNOWN,
|
|
879
|
+
}));
|
|
880
|
+
assert.equal(details.plan.writerSelfVerification, true);
|
|
881
|
+
assert.equal(details.plan.structuralReadbackOnly, false);
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
|
|
809
885
|
test("gentle_review assess: native consumed true derives closed for this candidate", async () => {
|
|
810
886
|
const details = await assessWith(closureCli({ consumed: true }));
|
|
811
887
|
assert.equal(details.nativeReviewOutcome, "closed");
|
|
812
888
|
assert.equal(details.outcome_source, "derived");
|
|
813
889
|
assert.equal(details.plan.writerSelfVerification, true);
|
|
814
|
-
assert.equal(details.plan.independentVerifier,
|
|
890
|
+
assert.equal(details.plan.independentVerifier, true, "a natively closed candidate keeps the risk-tier verifier (verify-always-rdd-high S1)");
|
|
815
891
|
});
|
|
816
892
|
|
|
817
893
|
test("gentle_review assess: a caller-declared closed without native consumed evidence fails closed to unknown", async () => {
|
|
@@ -827,7 +903,7 @@ test("gentle_review assess: a caller-declared closed corroborated by native cons
|
|
|
827
903
|
const details = await assessWith(closureCli({ consumed: true }), { nativeReviewOutcome: "closed" });
|
|
828
904
|
assert.equal(details.nativeReviewOutcome, "closed");
|
|
829
905
|
assert.equal(details.outcome_source, "derived", "closure is attributed to the native evidence, not to the caller's claim");
|
|
830
|
-
assert.equal(details.plan.independentVerifier,
|
|
906
|
+
assert.equal(details.plan.independentVerifier, true, "closure never removes the risk-tier verifier");
|
|
831
907
|
});
|
|
832
908
|
|
|
833
909
|
test("gentle_review assess: explicit declined, unavailable, or unknown beats native consumed (only ever raises the bar)", async () => {
|
|
@@ -1154,7 +1230,7 @@ test("native assess: a non-zero exit (an older binary reporting an unknown comma
|
|
|
1154
1230
|
const textOnStdout = queuedAdapter([{ stdout: "unknown command \"assess\" for \"gentle-ai review\"", exitCode: 1 }]);
|
|
1155
1231
|
await assert.rejects(
|
|
1156
1232
|
() => nativeClient(textOnStdout.adapter).assess!({ cwd: process.cwd() }),
|
|
1157
|
-
(error: unknown) => error instanceof NativeReviewCliError &&
|
|
1233
|
+
(error: unknown) => error instanceof NativeReviewCliError && (error.code === NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON || error.code === NATIVE_REVIEW_ERROR_CODE.NON_ZERO),
|
|
1158
1234
|
);
|
|
1159
1235
|
});
|
|
1160
1236
|
|
|
@@ -42,7 +42,7 @@ function nonPiV3Consent() {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
function consentCustom(select: (title: string, options: string[]) => Promise<string | undefined>) {
|
|
45
|
-
return async (factory: (...args:
|
|
45
|
+
return async (factory: (...args: unknown[]) => unknown) => {
|
|
46
46
|
let result: unknown;
|
|
47
47
|
const component = factory(
|
|
48
48
|
{ terminal: { rows: 24 }, requestRender() {} },
|
|
@@ -241,15 +241,14 @@ function piConsent() {
|
|
|
241
241
|
const decoded = consent();
|
|
242
242
|
return {
|
|
243
243
|
...decoded,
|
|
244
|
-
choices:
|
|
245
|
-
...
|
|
246
|
-
invocation:
|
|
247
|
-
|
|
244
|
+
choices: [
|
|
245
|
+
{ ...decoded.choices[0], invocation: decoded.choices[0].invocation.replace(" --consent ", " --agent pi --consent ") },
|
|
246
|
+
{ ...decoded.choices[1], invocation: decoded.choices[1].invocation.replace(" --consent ", " --agent pi --consent ") },
|
|
247
|
+
] as const,
|
|
248
248
|
};
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
function controllerHarness(cwd: string, processEnv: NodeJS.ProcessEnv = {}, options: {
|
|
252
|
-
now?: () => number;
|
|
253
252
|
answerConsentError?: Error & { mutationOutcome?: "none" | "unknown" };
|
|
254
253
|
startAction?: "created" | "resumed" | "replayed" | "closed" | "blocked-scope-action";
|
|
255
254
|
childStandingReviewPermissionClient?: ChildStandingReviewPermissionClient;
|
|
@@ -284,7 +283,7 @@ function controllerHarness(cwd: string, processEnv: NodeJS.ProcessEnv = {}, opti
|
|
|
284
283
|
return { kind: "started", start: { lineageId: `lineage-${answers.length}`, state: action === "blocked-scope-action" ? "unreviewed" : "approved", riskLevel: "high", selectedLenses: [], changedFiles: 1, changedLines: 2, correctionBudget: 0, action, lensesRequired: false, riskReasons: [] } };
|
|
285
284
|
},
|
|
286
285
|
} as unknown as NativeReviewCli;
|
|
287
|
-
createGentleAiExtension({ nativeReviewCli: native, candidateViews: new CandidateViewRegistry(), processEnv,
|
|
286
|
+
createGentleAiExtension({ nativeReviewCli: native, candidateViews: new CandidateViewRegistry(), processEnv, childStandingReviewPermissionClient: options.childStandingReviewPermissionClient })({
|
|
288
287
|
on(name: string, handler: RegisteredEvent) { events.set(name, handler); },
|
|
289
288
|
registerCommand(name: string, definition: RegisteredCommand) { commands.set(name, definition); },
|
|
290
289
|
registerTool(definition: RegisteredTool & { name: string }) { tools.set(definition.name, definition); },
|
|
@@ -471,10 +470,11 @@ test("blocked-scope-action never persists host permission", async (t) => {
|
|
|
471
470
|
assert.deepEqual(runtime.answers, ["granted", "declined"]);
|
|
472
471
|
});
|
|
473
472
|
|
|
474
|
-
test("a
|
|
473
|
+
test("a late consent answer is accepted once and arms host permission without another prompt", async (t) => {
|
|
475
474
|
const cwd = reviewRepository(t);
|
|
476
475
|
let now = 0;
|
|
477
|
-
|
|
476
|
+
t.mock.method(Date, "now", () => now);
|
|
477
|
+
const runtime = controllerHarness(cwd);
|
|
478
478
|
const manager = {};
|
|
479
479
|
let prompts = 0;
|
|
480
480
|
const ctx = interactiveContext(cwd, manager, async (_title, options) => {
|
|
@@ -486,14 +486,13 @@ test("a stale consent result with contextual native status never arms host permi
|
|
|
486
486
|
return options[1];
|
|
487
487
|
});
|
|
488
488
|
const start = { operation: "start", input: JSON.stringify({ mode: "ordinary" }) };
|
|
489
|
-
const
|
|
490
|
-
assert.
|
|
491
|
-
assert.
|
|
492
|
-
assert.equal(typeof stale.result, "object", "native STATUS is contextual evidence, not a successful START result");
|
|
489
|
+
const accepted = (await runtime.controller.execute("late-host-choice", start, undefined, undefined, ctx)).details;
|
|
490
|
+
assert.notEqual(accepted.outcome, "consent-binding-stale");
|
|
491
|
+
assert.deepEqual(runtime.answers, ["granted"]);
|
|
493
492
|
writeFileSync(join(cwd, "app.ts"), "export const value = 3;\n");
|
|
494
|
-
await runtime.controller.execute("after-
|
|
495
|
-
assert.equal(prompts,
|
|
496
|
-
assert.deepEqual(runtime.answers, ["
|
|
493
|
+
await runtime.controller.execute("after-late-answer", start, undefined, undefined, ctx);
|
|
494
|
+
assert.equal(prompts, 1, "a late session grant must not cause a second prompt");
|
|
495
|
+
assert.deepEqual(runtime.answers, ["granted", "granted"]);
|
|
497
496
|
});
|
|
498
497
|
|
|
499
498
|
test("a failed consent invocation never arms host permission", async (t) => {
|
|
@@ -624,7 +623,7 @@ test("a package-owned child replays its exact pending ordinary grant from a sibl
|
|
|
624
623
|
const parentToChild = new PassThrough();
|
|
625
624
|
const broker = new ParentStandingReviewPermissionBroker(
|
|
626
625
|
{ readable: childToParent, writable: parentToChild },
|
|
627
|
-
(repositoryIdentity) => repositoryIdentity === parentIdentity.repositoryIdentity &&
|
|
626
|
+
(repositoryIdentity) => repositoryIdentity === parentIdentity.repositoryIdentity && parentIdentity.sessionManager.getSessionId() === "parent-session" && hasReviewSessionPermission(parentIdentity),
|
|
628
627
|
);
|
|
629
628
|
const childPermission = new ChildStandingReviewPermissionClient(
|
|
630
629
|
{ readable: parentToChild, writable: childToParent },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { PassThrough, Writable } from "node:stream";
|
|
3
|
+
import { Duplex, PassThrough, Writable } from "node:stream";
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import { AGENT_MODE, type AgentDefinition } from "../lib/agents-config.ts";
|
|
6
6
|
import { AgentRunner, type ChildLike, type TaskRequest } from "../lib/agents-runner.ts";
|
|
@@ -111,6 +111,7 @@ async function productionChild(requests: number, authorize: () => boolean, optio
|
|
|
111
111
|
stdio: options.withoutChannel ? ["ignore", "pipe", "pipe"] : ["ignore", "pipe", "pipe", options.stdioMode ?? productionFd3StdioMode],
|
|
112
112
|
});
|
|
113
113
|
const pipe = child.stdio[3];
|
|
114
|
+
assert.ok(pipe === null || pipe === undefined || pipe instanceof Duplex, "fd3 must be a bidirectional channel");
|
|
114
115
|
const trace = options.diagnosticTrace;
|
|
115
116
|
const onParentData = () => { if (trace !== undefined) trace.parentRawDataChunkCount += 1; };
|
|
116
117
|
if (trace !== undefined && pipe !== null && pipe !== undefined) pipe.on("data", onParentData);
|
|
@@ -120,9 +121,9 @@ async function productionChild(requests: number, authorize: () => boolean, optio
|
|
|
120
121
|
trace.parentAuthorizationCallbackCount += 1;
|
|
121
122
|
return authorize();
|
|
122
123
|
};
|
|
123
|
-
const broker = pipe
|
|
124
|
-
?
|
|
125
|
-
:
|
|
124
|
+
const broker = pipe instanceof Duplex
|
|
125
|
+
? new ParentStandingReviewPermissionBroker({ readable: pipe, writable: pipe }, parentAuthorize, options)
|
|
126
|
+
: undefined;
|
|
126
127
|
if (options.closeChannel && pipe !== null && pipe !== undefined) {
|
|
127
128
|
broker?.close();
|
|
128
129
|
pipe.destroy();
|
|
@@ -323,7 +324,7 @@ test("fresh Jiti moduleCache:false reloads share fd3 structurally and reject sta
|
|
|
323
324
|
stdio: ["ignore", "pipe", "pipe", productionFd3StdioMode],
|
|
324
325
|
});
|
|
325
326
|
const pipe = child.stdio[3];
|
|
326
|
-
assert.ok(pipe);
|
|
327
|
+
assert.ok(pipe instanceof Duplex, "fd3 must be a bidirectional channel");
|
|
327
328
|
const broker = new ParentStandingReviewPermissionBroker({ readable: pipe, writable: pipe }, () => true);
|
|
328
329
|
let stdout = "";
|
|
329
330
|
let stderr = "";
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ReviewMode,
|
|
8
8
|
} from "../lib/review-snapshot.ts";
|
|
9
9
|
import { existsSync, renameSync } from "node:fs";
|
|
10
|
+
import { correctionBudget, REVIEW_RISK_TIER } from "../lib/review-risk.ts";
|
|
10
11
|
import type { ReviewLockPlatformAdapterV1 } from "../lib/review-lock.ts";
|
|
11
12
|
import {
|
|
12
13
|
REVIEW_EVENT,
|
|
@@ -45,6 +46,7 @@ export function testSnapshot(options: TestSnapshotOptions): SnapshotV1 {
|
|
|
45
46
|
const lenses = options.lenses ?? [];
|
|
46
47
|
const initialTree = options.initialTree ?? options.completeTree;
|
|
47
48
|
const selected = lenses[0];
|
|
49
|
+
const changedLines = route === REVIEW_ROUTE.FULL_4R ? 401 : route === REVIEW_ROUTE.TRIVIAL ? 1 : 10;
|
|
48
50
|
return {
|
|
49
51
|
schema: "gentle-ai.review-snapshot/v1",
|
|
50
52
|
mode: options.mode ?? REVIEW_MODE.ORDINARY,
|
|
@@ -57,9 +59,13 @@ export function testSnapshot(options: TestSnapshotOptions): SnapshotV1 {
|
|
|
57
59
|
: { kind: REVIEW_PROJECTION.INTENDED_COMMIT, tree: initialTree },
|
|
58
60
|
initial_review_tree: initialTree,
|
|
59
61
|
genesis_paths: options.genesisPaths ?? ["app.ts", "src/auth.ts", "src/retry.ts", "src/review.ts"],
|
|
62
|
+
intended_untracked: [],
|
|
63
|
+
risk_tier: route === REVIEW_ROUTE.FULL_4R ? REVIEW_RISK_TIER.HIGH : route === REVIEW_ROUTE.TRIVIAL ? REVIEW_RISK_TIER.LOW : REVIEW_RISK_TIER.MEDIUM,
|
|
64
|
+
original_changed_lines: changedLines,
|
|
65
|
+
correction_budget: correctionBudget(changedLines),
|
|
60
66
|
diff_evidence: {
|
|
61
67
|
event: REVIEW_EVENT.ORDINARY_START,
|
|
62
|
-
changedLines
|
|
68
|
+
changedLines,
|
|
63
69
|
triviality:
|
|
64
70
|
route === REVIEW_ROUTE.TRIVIAL
|
|
65
71
|
? TRIVIALITY.PROVEN
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
createFrozenLedger,
|
|
34
34
|
createReceiptEnvelope,
|
|
35
35
|
createReviewState,
|
|
36
|
+
validateReviewGraphReplayV1,
|
|
36
37
|
evaluateGateTarget,
|
|
37
38
|
type CanonicalFrozenRowV1,
|
|
38
39
|
type ReceiptBodyV1,
|
|
@@ -41,8 +42,12 @@ import {
|
|
|
41
42
|
import { REVIEW_LENS, REVIEW_ROUTE } from "../lib/review-triggers.ts";
|
|
42
43
|
import {
|
|
43
44
|
ordinaryValidatorRequest,
|
|
45
|
+
recordOrdinaryDiscovery,
|
|
46
|
+
resolveOrdinaryEvidence,
|
|
47
|
+
recordOrdinaryFinalVerification,
|
|
44
48
|
recordOrdinaryValidation,
|
|
45
49
|
} from "../lib/review-policy-ordinary.ts";
|
|
50
|
+
import { createReviewEventV1 } from "../lib/review-graph-schema.ts";
|
|
46
51
|
import { qualifiedReviewLockPlatform, testSnapshot } from "./review-test-fixtures.ts";
|
|
47
52
|
|
|
48
53
|
const TREE = {
|
|
@@ -393,7 +398,19 @@ test("ordinary follow-ups are ID-sorted action-free validation evidence and do n
|
|
|
393
398
|
assert.equal(recorded.phase, REVIEW_PHASE.FINAL_VERIFICATION);
|
|
394
399
|
assert.equal(recorded.counters.validator_runs, fixed.counters.validator_runs + 1);
|
|
395
400
|
assert.equal(recorded.current_candidate_tree, fixed.current_candidate_tree);
|
|
396
|
-
assert.equal(recorded
|
|
401
|
+
assert.equal(Object.hasOwn(recorded, "follow_ups"), false);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("graph replay rejects a persisted non-boolean final verification instead of approving truthy input", () => {
|
|
405
|
+
const ready = resolveOrdinaryEvidence(recordOrdinaryDiscovery(state(), { rows: [] }), { deterministicResults: [] });
|
|
406
|
+
const approved = recordOrdinaryFinalVerification(ready, { passed: true });
|
|
407
|
+
const genesis = createReviewEventV1({ lineage_id: ready.lineage_id, sequence: 0, predecessor_event_id: null, kind: "lineage-created", reducer_transition: "start", reducer_input: ready, payload: { state: ready }, reduced_state_hash: canonicalHash(ready) });
|
|
408
|
+
for (const passed of ["false", "true", 0, 1, null]) {
|
|
409
|
+
const next = createReviewEventV1({ lineage_id: ready.lineage_id, sequence: 1, predecessor_event_id: genesis.event_id, kind: "operation-completed", reducer_transition: REVIEW_TRANSITION.ORDINARY_FINAL_VERIFICATION, reducer_input: { passed }, payload: { state: approved }, reduced_state_hash: canonicalHash(approved) });
|
|
410
|
+
assert.throws(() => validateReviewGraphReplayV1([genesis, next]), /reducer input.*passed/i);
|
|
411
|
+
}
|
|
412
|
+
const valid = createReviewEventV1({ lineage_id: ready.lineage_id, sequence: 1, predecessor_event_id: genesis.event_id, kind: "operation-completed", reducer_transition: REVIEW_TRANSITION.ORDINARY_FINAL_VERIFICATION, reducer_input: { passed: true }, payload: { state: approved }, reduced_state_hash: canonicalHash(approved) });
|
|
413
|
+
assert.deepEqual(validateReviewGraphReplayV1([genesis, valid]), approved);
|
|
397
414
|
});
|
|
398
415
|
|
|
399
416
|
test("new ordinary lineages fail closed when immutable genesis paths are absent", () => {
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import test from "node:test";
|
|
8
8
|
import { DEFAULT_STAGES, runTestSuite } from "../scripts/run-test-suite.mjs";
|
|
9
9
|
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// orchestration contract with fake stage implementations, so no real test
|
|
13
|
-
// process is spawned.
|
|
10
|
+
// #1285 requires that a stage-1 failure never suppresses later stages.
|
|
11
|
+
// Orchestration tests use fake stages; CLI tests exercise real child processes.
|
|
14
12
|
|
|
15
13
|
function fakeRunStage(codes) {
|
|
16
14
|
let index = 0;
|
|
@@ -113,6 +111,86 @@ test("direct invocation still runs stages when reached through a file symlink",
|
|
|
113
111
|
assert.match(result.stdout ?? "", /all stages passed/);
|
|
114
112
|
});
|
|
115
113
|
|
|
114
|
+
function probeStageEnvironment(t: test.TestContext, env: NodeJS.ProcessEnv) {
|
|
115
|
+
const stagesPath = writeStagesFile(t, []);
|
|
116
|
+
const probePath = join(dirname(stagesPath), "probe.mjs");
|
|
117
|
+
writeFileSync(probePath, `
|
|
118
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
119
|
+
const home = process.env.GENTLE_PI_CONFIG_HOME;
|
|
120
|
+
console.log("ENV_PROBE=" + JSON.stringify({
|
|
121
|
+
git: Object.fromEntries(Object.entries(process.env).filter(([key]) => key.toUpperCase().startsWith("GIT_"))),
|
|
122
|
+
agents: Object.fromEntries(Object.entries(process.env).filter(([key]) => key.toUpperCase().startsWith("GENTLE_PI_AGENTS_"))),
|
|
123
|
+
home,
|
|
124
|
+
files: home && existsSync(home) ? readdirSync(home) : null,
|
|
125
|
+
sentinel: process.env.TEST_ENV_SENTINEL,
|
|
126
|
+
}));
|
|
127
|
+
`);
|
|
128
|
+
writeFileSync(stagesPath, JSON.stringify([
|
|
129
|
+
{ name: "unit-tests", command: `node "${probePath}"` },
|
|
130
|
+
{ name: "provider-contract", command: `node "${probePath}"` },
|
|
131
|
+
]));
|
|
132
|
+
const result = spawnSync(process.execPath, [runnerPath, stagesPath], {
|
|
133
|
+
encoding: "utf8", env: { ...process.env, ...env, TEST_ENV_SENTINEL: "preserved" },
|
|
134
|
+
});
|
|
135
|
+
assert.equal(result.status, 0, result.stderr);
|
|
136
|
+
assert.equal(result.stderr, "");
|
|
137
|
+
assert.match(result.stdout, /PASS unit-tests/);
|
|
138
|
+
assert.match(result.stdout, /PASS provider-contract/);
|
|
139
|
+
const probes = result.stdout.split(/\r?\n/).filter((line) => line.startsWith("ENV_PROBE="))
|
|
140
|
+
.map((line) => JSON.parse(line.slice("ENV_PROBE=".length)));
|
|
141
|
+
assert.equal(probes.length, 2);
|
|
142
|
+
for (const probe of probes) assert.equal(probe.sentinel, "preserved");
|
|
143
|
+
return probes;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
test("unit stage strips inherited Git configuration without changing later stages", (t) => {
|
|
147
|
+
const env = {
|
|
148
|
+
GIT_CONFIG_COUNT: "2",
|
|
149
|
+
GIT_CONFIG_KEY_0: "credential.interactive", GIT_CONFIG_VALUE_0: "false",
|
|
150
|
+
GIT_CONFIG_KEY_1: "credential.guiPrompt", GIT_CONFIG_VALUE_1: "false",
|
|
151
|
+
GIT_DIR: "inherited-repository", GIT_AUTHOR_NAME: "inherited-author",
|
|
152
|
+
};
|
|
153
|
+
const [unit, provider] = probeStageEnvironment(t, env);
|
|
154
|
+
assert.deepEqual(unit.git, {});
|
|
155
|
+
for (const [key, value] of Object.entries(env)) assert.equal(provider.git[key], value);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("unit stage strips inherited subagent context without changing later stages", (t) => {
|
|
159
|
+
const env = { GENTLE_PI_AGENTS_CHILD: "1", GENTLE_PI_AGENTS_OWNED_IPC: "1" };
|
|
160
|
+
const [unit, provider] = probeStageEnvironment(t, env);
|
|
161
|
+
assert.deepEqual(unit.agents, {});
|
|
162
|
+
for (const [key, value] of Object.entries(env)) assert.equal(provider.agents[key], value);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("unit stage accepts an uncreated inherited config home without creating it", (t) => {
|
|
166
|
+
const dir = mkdtempSync(join(tmpdir(), "gentle-pi-caller-config-"));
|
|
167
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
168
|
+
const home = join(dir, "not-created");
|
|
169
|
+
assert.equal(existsSync(home), false);
|
|
170
|
+
const [unit, provider] = probeStageEnvironment(t, { GENTLE_PI_CONFIG_HOME: home });
|
|
171
|
+
assert.notEqual(unit.home, home);
|
|
172
|
+
assert.deepEqual(unit.files, []);
|
|
173
|
+
assert.equal(existsSync(unit.home), false);
|
|
174
|
+
assert.equal(provider.home, home);
|
|
175
|
+
assert.equal(provider.files, null);
|
|
176
|
+
assert.equal(existsSync(home), false);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("unit stage uses an empty temporary config home and removes it after exit", (t) => {
|
|
180
|
+
const home = mkdtempSync(join(tmpdir(), "gentle-pi-caller-config-"));
|
|
181
|
+
t.after(() => rmSync(home, { recursive: true, force: true }));
|
|
182
|
+
const configPath = join(home, "runtime-guardrails.json");
|
|
183
|
+
const config = JSON.stringify({ autonomousMode: true, guardedCommands: { gitPush: "allow", gitRebase: "allow", gitBranchDeleteForce: "allow" } });
|
|
184
|
+
writeFileSync(configPath, config);
|
|
185
|
+
const [unit, provider] = probeStageEnvironment(t, { GENTLE_PI_CONFIG_HOME: home });
|
|
186
|
+
assert.notEqual(unit.home, home);
|
|
187
|
+
assert.deepEqual(unit.files, []);
|
|
188
|
+
assert.equal(existsSync(unit.home), false);
|
|
189
|
+
assert.equal(provider.home, home);
|
|
190
|
+
assert.deepEqual(provider.files, ["runtime-guardrails.json"]);
|
|
191
|
+
assert.equal(readFileSync(configPath, "utf8"), config);
|
|
192
|
+
});
|
|
193
|
+
|
|
116
194
|
test("direct invocation rejects a malformed stages file with a non-zero exit", (t) => {
|
|
117
195
|
const dir = mkdtempSync(join(tmpdir(), "gentle-pi-run-test-suite-"));
|
|
118
196
|
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
@@ -390,7 +390,24 @@ async function run() {
|
|
|
390
390
|
assert.ok(flags.has("no-skill-registry"), "missing no-skill-registry flag");
|
|
391
391
|
assert.ok(hooks.has("session_start"), "missing session_start hook");
|
|
392
392
|
assert.ok(hooks.has("session_shutdown"), "missing session_shutdown hook");
|
|
393
|
-
assert.
|
|
393
|
+
assert.ok(hooks.has("input"), "missing live input provenance hook");
|
|
394
|
+
for (const text of [
|
|
395
|
+
"/sdd-init", "/sdd-continue", "/sdd-status", "/gentle-sdd-init",
|
|
396
|
+
"/gentle-sdd-continue", "/gentle-sdd-status", "/gentle:install-sdd",
|
|
397
|
+
"/gentle:sdd-preflight", "/gentle-ai:install-sdd", "/gentle-ai:sdd-preflight",
|
|
398
|
+
"/gentle-ai:sdd-status", "/gentle-ai:sdd-continue",
|
|
399
|
+
"Inspect this project without changing files.", "/ordinary-unregistered-command",
|
|
400
|
+
]) {
|
|
401
|
+
const event = { text, source: "interactive", images: [] };
|
|
402
|
+
const before = structuredClone(event);
|
|
403
|
+
const emittedBefore = emittedEvents.length;
|
|
404
|
+
for (const handler of hooks.get("input")) {
|
|
405
|
+
assert.deepEqual(await handler(event, createCtx(ROOT)), { action: "continue" },
|
|
406
|
+
`input must pass through without slash interception: ${text}`);
|
|
407
|
+
}
|
|
408
|
+
assert.deepEqual(event, before, "input text and attachments must remain unchanged");
|
|
409
|
+
assert.equal(emittedEvents.length, emittedBefore, "input must not dispatch slash logic");
|
|
410
|
+
}
|
|
394
411
|
assert.ok(hooks.has("before_agent_start"), "missing before_agent_start hook");
|
|
395
412
|
assert.ok(hooks.has("tool_call"), "missing tool_call hook");
|
|
396
413
|
for (const toolName of ["read", "grep", "find", "ls", "edit", "write"]) {
|
|
@@ -450,7 +467,8 @@ async function run() {
|
|
|
450
467
|
// rules) moved verbatim to assets/orchestrator-delegation.md; the
|
|
451
468
|
// always-on combined prompt now only carries a pointer to it. Union read
|
|
452
469
|
// so these assertions are repointed, not weakened.
|
|
453
|
-
const
|
|
470
|
+
const { DELEGATION_MODULES } = await import("./support/orchestrator-modules.ts");
|
|
471
|
+
const delegationDetail = (await Promise.all(DELEGATION_MODULES.map((file) => readFile(join(ROOT, "assets", file), "utf8")))).join("\n\n");
|
|
454
472
|
|
|
455
473
|
const promptCwd = await tempWorkspace();
|
|
456
474
|
try {
|
|
@@ -1018,8 +1036,8 @@ async function run() {
|
|
|
1018
1036
|
assert.equal(existsSync(installedExplorePath), true);
|
|
1019
1037
|
assert.deepEqual(
|
|
1020
1038
|
readAgentDefinition(await readFile(installedExplorePath, "utf8")),
|
|
1021
|
-
{ name: "gentle-ai-explore", tools: ["read", "grep", "find", "codegraph"] },
|
|
1022
|
-
"isolated package installation must
|
|
1039
|
+
{ name: "gentle-ai-explore", tools: ["read", "grep", "find", "codegraph", "web_enable", "web_search", "source_check", "fetch_content", "get_search_content"] },
|
|
1040
|
+
"isolated package installation must declare the explorer's local and direct web scope",
|
|
1023
1041
|
);
|
|
1024
1042
|
const installedRiskSource = await readFile(
|
|
1025
1043
|
join(globalAgentHome, "agents", "review-risk.md"),
|
|
@@ -1316,15 +1334,15 @@ async function run() {
|
|
|
1316
1334
|
);
|
|
1317
1335
|
await writeFile(globalModelsPath, JSON.stringify({ "sdd-apply": {} }, null, 2));
|
|
1318
1336
|
await hooks.get("session_start")[0]({ reason: "startup" }, legacyCtx);
|
|
1319
|
-
const
|
|
1337
|
+
const savedInheritPreservesAgent = await readFile(
|
|
1320
1338
|
join(legacyModelsCwd, ".pi", "agents", "sdd-apply.md"),
|
|
1321
1339
|
"utf8",
|
|
1322
1340
|
);
|
|
1323
|
-
assert.
|
|
1324
|
-
const
|
|
1341
|
+
assert.match(savedInheritPreservesAgent, /model: global\/provider-model/);
|
|
1342
|
+
const savedInheritPreservesProfiles = JSON.parse(
|
|
1325
1343
|
await readFile(join(legacyModelsCwd, ".pi", "subagents.json"), "utf8"),
|
|
1326
1344
|
);
|
|
1327
|
-
assert.equal(
|
|
1345
|
+
assert.equal(savedInheritPreservesProfiles.model_profiles["sdd-apply"].model, "global/provider-model");
|
|
1328
1346
|
} finally {
|
|
1329
1347
|
await rm(legacyModelsCwd, { recursive: true, force: true });
|
|
1330
1348
|
await rm(globalModelsPath, { force: true });
|
|
@@ -1346,10 +1364,12 @@ async function run() {
|
|
|
1346
1364
|
);
|
|
1347
1365
|
await writeFile(globalModelsPath, JSON.stringify({ worker: {} }, null, 2));
|
|
1348
1366
|
await hooks.get("session_start")[0]({ reason: "startup" }, createCtx(staleSettingsOnlyCwd, true));
|
|
1349
|
-
const
|
|
1367
|
+
const staleOnlyPreservedProfiles = JSON.parse(
|
|
1350
1368
|
await readFile(globalSubagentsPath, "utf8"),
|
|
1351
1369
|
);
|
|
1352
|
-
assert.
|
|
1370
|
+
assert.deepEqual(staleOnlyPreservedProfiles.model_profiles, {
|
|
1371
|
+
worker: { model: "stale/model", effort: "high" },
|
|
1372
|
+
});
|
|
1353
1373
|
} finally {
|
|
1354
1374
|
await rm(staleSettingsOnlyCwd, { recursive: true, force: true });
|
|
1355
1375
|
await rm(globalModelsPath, { force: true });
|
|
@@ -1565,22 +1585,29 @@ async function run() {
|
|
|
1565
1585
|
assert.match(preservedProjectSubagentWorker, /thinking: medium/);
|
|
1566
1586
|
await writeFile(globalModelsPath, JSON.stringify({ worker: {} }, null, 2));
|
|
1567
1587
|
await hooks.get("session_start")[0]({ reason: "startup" }, createCtx(modelsCwd, true));
|
|
1568
|
-
|
|
1588
|
+
// gentle-ai#4946: startup/reload never treats persisted inherit as
|
|
1589
|
+
// deletion approval, including for previously materialized routing.
|
|
1590
|
+
// Explicit panel saves and confirmed profile replacement still clear.
|
|
1591
|
+
const preservedClearProfiles = JSON.parse(
|
|
1569
1592
|
await readFile(join(modelsCwd, ".pi", "subagents.json"), "utf8"),
|
|
1570
1593
|
);
|
|
1571
|
-
assert.equal(
|
|
1594
|
+
assert.equal(
|
|
1595
|
+
preservedClearProfiles.model_profiles.worker.model,
|
|
1596
|
+
"existing/model",
|
|
1597
|
+
);
|
|
1598
|
+
assert.equal(preservedClearProfiles.model_profiles.worker.effort, "high");
|
|
1572
1599
|
const unchangedProjectWorker = await readFile(
|
|
1573
1600
|
join(modelsCwd, ".pi", "agents", "worker.md"),
|
|
1574
1601
|
"utf8",
|
|
1575
1602
|
);
|
|
1576
1603
|
assert.match(unchangedProjectWorker, /model: existing\/project-worker/);
|
|
1577
1604
|
assert.match(unchangedProjectWorker, /thinking: high/);
|
|
1578
|
-
const
|
|
1605
|
+
const preservedClearProjectSubagentWorker = await readFile(
|
|
1579
1606
|
join(modelsCwd, ".pi", "subagents", "worker.md"),
|
|
1580
1607
|
"utf8",
|
|
1581
1608
|
);
|
|
1582
|
-
assert.
|
|
1583
|
-
assert.
|
|
1609
|
+
assert.match(preservedClearProjectSubagentWorker, /model: existing\/project-subagent-worker/);
|
|
1610
|
+
assert.match(preservedClearProjectSubagentWorker, /thinking: medium/);
|
|
1584
1611
|
|
|
1585
1612
|
await writeFile(
|
|
1586
1613
|
globalModelsPath,
|