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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import { parseNpmPackResult } from "../scripts/npm-pack-result.mjs";
|
|
2
3
|
import { execFileSync } from "node:child_process";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
4
5
|
import {
|
|
@@ -60,7 +61,7 @@ const V014_MANAGED_ASSETS = join(
|
|
|
60
61
|
// gentle-ai-explore.md as their exemplar (packaged, absent from the v0.13
|
|
61
62
|
// manifest) after review-refuter.md was retired together with every
|
|
62
63
|
// Pi-authored adversarial review verdict.
|
|
63
|
-
const MANAGED_EXEMPLAR_TOOLS = ["read", "grep", "find", "codegraph"];
|
|
64
|
+
const MANAGED_EXEMPLAR_TOOLS = ["read", "grep", "find", "codegraph", "web_enable", "web_search", "source_check", "fetch_content", "get_search_content"];
|
|
64
65
|
const RETIRED_ADVERSARIAL_AGENTS = ["review-refuter.md", "review-validator.md"];
|
|
65
66
|
|
|
66
67
|
interface ManagedAssetsManifest {
|
|
@@ -132,13 +133,13 @@ test("public docs and metadata advertise ODD and review without retired phase wo
|
|
|
132
133
|
|
|
133
134
|
test("technical reference declares the tested Pi minimum required for agent_settled", () => {
|
|
134
135
|
const manifest = readPackageJson();
|
|
135
|
-
assert.equal(manifest.peerDependencies?.["@earendil-works/pi-coding-agent"], ">=
|
|
136
|
-
assert.equal(manifest.devDependencies?.["@earendil-works/pi-coding-agent"], ">=1.
|
|
136
|
+
assert.equal(manifest.peerDependencies?.["@earendil-works/pi-coding-agent"], ">=1.2.0");
|
|
137
|
+
assert.equal(manifest.devDependencies?.["@earendil-works/pi-coding-agent"], ">=1.2.0");
|
|
137
138
|
assert.equal(manifest.peerDependenciesMeta?.["@earendil-works/pi-coding-agent"]?.optional, true);
|
|
138
139
|
assert.equal(manifest.engines?.node, ">=22.19.0");
|
|
139
140
|
for (const path of ["docs/readme-reference.md", "docs/gentle-shell.md"]) {
|
|
140
141
|
const source = readFileSync(join(PACKAGE_ROOT, path), "utf8");
|
|
141
|
-
assert.match(source, /Pi
|
|
142
|
+
assert.match(source, /Pi 1\.2\.0 or newer/, path);
|
|
142
143
|
assert.match(source, /open `>=1\.0\.0` development range/, path);
|
|
143
144
|
assert.doesNotMatch(source, /development tests pin Pi/, path);
|
|
144
145
|
// Docs name exactly the audited Vim editor releases, never a future one.
|
|
@@ -161,7 +162,7 @@ test("packed runtime uses optional Pi host peers with one open development range
|
|
|
161
162
|
// one exact release shared by every Pi host package.
|
|
162
163
|
const installed = new Set<string>();
|
|
163
164
|
for (const name of ["@earendil-works/pi-coding-agent", "@earendil-works/pi-ai", "@earendil-works/pi-tui"]) {
|
|
164
|
-
assert.equal(manifest.devDependencies?.[name], ">=1.
|
|
165
|
+
assert.equal(manifest.devDependencies?.[name], ">=1.2.0", name);
|
|
165
166
|
const metadata = JSON.parse(readFileSync(join(PACKAGE_ROOT, "node_modules", name, "package.json"), "utf8")) as { name: string; version: string };
|
|
166
167
|
assert.equal(metadata.name, name);
|
|
167
168
|
installed.add(metadata.version);
|
|
@@ -253,6 +254,25 @@ test("package verification names the native review runtime boundary and packaged
|
|
|
253
254
|
);
|
|
254
255
|
});
|
|
255
256
|
|
|
257
|
+
test("double-click installers are attached to the release only after the verified publication", () => {
|
|
258
|
+
const workflow = readFileSync(join(PACKAGE_ROOT, ".github", "workflows", "publish.yml"), "utf8");
|
|
259
|
+
const job = workflow.match(/^ {2}installers:\n([\s\S]*?)(?=^ {2}[A-Za-z0-9_-]+:\n|(?![\s\S]))/m)?.[1];
|
|
260
|
+
assert.ok(job, "publish.yml has an installers job");
|
|
261
|
+
assert.match(job, /^ {4}needs: publish$/m, "installers wait for the verified npm publication");
|
|
262
|
+
assert.match(job, /^ {4}if: github\.repository == 'Gentleman-Programming\/gentle-shell'$/m);
|
|
263
|
+
assert.match(job, /^ {6}contents: write$/m, "only this job may write the release");
|
|
264
|
+
assert.match(job, /ref: \$\{\{ github\.sha \}\}/, "the installers come from the verified release commit");
|
|
265
|
+
assert.match(job, /persist-credentials: false/);
|
|
266
|
+
assert.match(job, /node scripts\/build-installer-bundles\.mjs --out "\$\{RUNNER_TEMP\}\/installers"/);
|
|
267
|
+
assert.match(job, /RELEASE_TAG: \$\{\{ needs\.publish\.outputs\.tag \}\}/, "the tag the publish job verified, not the raw input");
|
|
268
|
+
assert.match(job, /package-manager-cache: false/);
|
|
269
|
+
assert.match(workflow, /^ {4}outputs:\n {6}tag: \$\{\{ steps\.release\.outputs\.tag \}\}$/m, "publish exports its verified tag");
|
|
270
|
+
assert.match(job, /gh release upload "\$\{RELEASE_TAG\}" "\$\{RUNNER_TEMP\}"\/installers\/\* --repo "\$\{GITHUB_REPOSITORY\}" --clobber/);
|
|
271
|
+
const publish = workflow.match(/^ {2}publish:\n([\s\S]*?)(?=^ {2}installers:\n)/m)?.[1];
|
|
272
|
+
assert.ok(publish);
|
|
273
|
+
assert.doesNotMatch(publish, /contents: write/, "the npm publication job keeps read-only contents");
|
|
274
|
+
});
|
|
275
|
+
|
|
256
276
|
test("npm publication is bound to the exact package tag and triggering commit", () => {
|
|
257
277
|
const workflow = readFileSync(join(PACKAGE_ROOT, ".github", "workflows", "publish.yml"), "utf8");
|
|
258
278
|
const packageJson = readPackageJson();
|
|
@@ -411,20 +431,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
411
431
|
);
|
|
412
432
|
});
|
|
413
433
|
|
|
414
|
-
test("package verification binds the published Gentle AI v4.
|
|
434
|
+
test("package verification binds the published Gentle AI v4.1.1 runtime pin", () => {
|
|
415
435
|
const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
|
|
416
436
|
const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
|
|
417
437
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
418
438
|
|
|
419
|
-
assert.match(installer, /INSTALLER_VERSION = "4\.
|
|
439
|
+
assert.match(installer, /INSTALLER_VERSION = "4\.1\.1"/);
|
|
420
440
|
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
|
|
421
|
-
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1
|
|
441
|
+
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:\+WmZ2IvIOiJ8qtHa7wsjnYKNasem\+PByeOZ6HIjoAL8="/);
|
|
422
442
|
assert.match(installer, /GOTOOLCHAIN: "local"/);
|
|
423
443
|
assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
|
|
424
444
|
assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
|
|
425
445
|
assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
|
|
426
446
|
assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
|
|
427
|
-
assert.match(verifier, /v4\.
|
|
447
|
+
assert.match(verifier, /v4\.1\.1/);
|
|
428
448
|
});
|
|
429
449
|
|
|
430
450
|
|
|
@@ -550,7 +570,7 @@ function readMarkdownSection(source: string, heading: string): string {
|
|
|
550
570
|
|
|
551
571
|
function assertWorkerFallbackRouting(section: string, sectionName: string): void {
|
|
552
572
|
const boundedWriterPolicy = section.match(
|
|
553
|
-
/For bounded
|
|
573
|
+
/For a large task's bounded writes,[\s\S]*?(?=\n\n|\n\s*\d+\.|$)/,
|
|
554
574
|
)?.[0];
|
|
555
575
|
assert.ok(boundedWriterPolicy, `${sectionName} must define bounded writer routing`);
|
|
556
576
|
|
|
@@ -847,6 +867,42 @@ test("selective review migration adopts only untouched legacy copies and preserv
|
|
|
847
867
|
}
|
|
848
868
|
});
|
|
849
869
|
|
|
870
|
+
test("native pulse audio ships owned TypeScript sources without new dependencies or addons", () => {
|
|
871
|
+
const manifest = readPackageJson();
|
|
872
|
+
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
873
|
+
const sources = [
|
|
874
|
+
"lib/notification-pulse-protocol.ts", "lib/notification-pulse-client.ts",
|
|
875
|
+
"lib/notification-pulse-stream.ts", "lib/notification-audio-native.ts",
|
|
876
|
+
"lib/notification-pulse-worker.ts",
|
|
877
|
+
];
|
|
878
|
+
for (const source of sources) {
|
|
879
|
+
assert.ok(existsSync(join(PACKAGE_ROOT, source)), `${source} must exist`);
|
|
880
|
+
assert.ok(verifier.includes(`"${source}"`), `${source} must be a required package resource`);
|
|
881
|
+
}
|
|
882
|
+
assert.ok(!manifest.files?.includes("native/"), "no native addon directory may ship");
|
|
883
|
+
for (const name of Object.keys(manifest.dependencies ?? {})) assert.doesNotMatch(name, /pulse|audio|sound|native/i, name);
|
|
884
|
+
for (const name of Object.keys(manifest.scripts ?? {})) assert.doesNotMatch(name, /pulse|native:build|audio:install/i, name);
|
|
885
|
+
for (const path of ["lib/notification-pulse-worker.ts", "lib/notification-audio-native.ts"]) {
|
|
886
|
+
const source = readFileSync(join(PACKAGE_ROOT, path), "utf8");
|
|
887
|
+
assert.doesNotMatch(source, /@earendil-works|\.node["']|addon/i, path);
|
|
888
|
+
assert.doesNotMatch(source, /postinstall|installer|download/i, path);
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
test("native windows audio ships an owned encoded-command adapter without scripts or dependencies", () => {
|
|
893
|
+
const manifest = readPackageJson();
|
|
894
|
+
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
895
|
+
const path = "lib/notification-audio-windows.ts";
|
|
896
|
+
assert.ok(existsSync(join(PACKAGE_ROOT, path)), `${path} must exist`);
|
|
897
|
+
assert.ok(verifier.includes(`"${path}"`), `${path} must be a required package resource`);
|
|
898
|
+
for (const dependency of Object.keys(manifest.dependencies ?? {})) assert.doesNotMatch(dependency, /audio|sound|windows|powershell|native/i, dependency);
|
|
899
|
+
for (const script of Object.keys(manifest.scripts ?? {})) assert.doesNotMatch(script, /windows:build|powershell|audio:install/i, script);
|
|
900
|
+
const source = readFileSync(join(PACKAGE_ROOT, path), "utf8");
|
|
901
|
+
assert.match(source, /-EncodedCommand/, "the adapter must drive the trusted built-in PowerShell host with an encoded command");
|
|
902
|
+
assert.doesNotMatch(source, /\.ps1|ExecutionPolicy|\.node["']|addon/i, path);
|
|
903
|
+
assert.doesNotMatch(source, /postinstall|installer|download/i, path);
|
|
904
|
+
});
|
|
905
|
+
|
|
850
906
|
test("packed tarball excludes retired workflow paths while source retains legacy migration proof", () => {
|
|
851
907
|
const fixture = "tests/fixtures/legacy/sdd-research-v2.5.0.md";
|
|
852
908
|
assert.ok(existsSync(join(PACKAGE_ROOT, fixture)), "the historical source fixture must remain available to migration tests");
|
|
@@ -857,9 +913,22 @@ test("packed tarball excludes retired workflow paths while source retains legacy
|
|
|
857
913
|
encoding: "utf8",
|
|
858
914
|
maxBuffer: 8 * 1024 * 1024,
|
|
859
915
|
});
|
|
860
|
-
const [packed] =
|
|
916
|
+
const [packed] = parseNpmPackResult(output);
|
|
861
917
|
assert.ok(packed?.files?.length, "npm pack must return a nonempty tar manifest");
|
|
862
918
|
assert.ok(packed.files.some(file => file.path === "tests/package-manifest.test.ts"), "other tests remain packed");
|
|
919
|
+
for (const path of [
|
|
920
|
+
"extensions/gentle-notifications.ts",
|
|
921
|
+
"lib/notification-audio.ts", "lib/notification-customize.ts", "lib/notification-events.ts", "lib/notification-policy.ts",
|
|
922
|
+
"lib/notification-scheduler.ts", "lib/notification-service.ts", "lib/notification-ui.ts",
|
|
923
|
+
"assets/sounds/success.wav", "assets/sounds/error.wav", "assets/sounds/attention.wav",
|
|
924
|
+
"assets/sounds/LICENSE.md", "docs/sound-notifications.md", "docs/sound-notifications-proposal.md",
|
|
925
|
+
"lib/notification-pulse-protocol.ts", "lib/notification-pulse-client.ts", "lib/notification-pulse-stream.ts",
|
|
926
|
+
"lib/notification-audio-native.ts", "lib/notification-pulse-worker.ts",
|
|
927
|
+
"lib/notification-audio-windows.ts",
|
|
928
|
+
"scripts/npm-pack-result.mjs",
|
|
929
|
+
]) {
|
|
930
|
+
assert.ok(packed.files.some(file => file.path === path), `${path} must be packed`);
|
|
931
|
+
}
|
|
863
932
|
assert.deepEqual(packed.files.filter(file => /sdd|openspec/i.test(file.path)).map(file => file.path), []);
|
|
864
933
|
} finally {
|
|
865
934
|
rmSync(destination, { recursive: true, force: true });
|
|
@@ -1441,7 +1510,7 @@ test("gentle-ai-worker packages the exact scoped writer contract", () => {
|
|
|
1441
1510
|
const testDiscipline = readMarkdownSection(source, "Test discipline");
|
|
1442
1511
|
assert.match(testDiscipline, /Apply the ODD test-first policy by default for behavior changes with applicable runnable deterministic tests and a clear expected outcome/);
|
|
1443
1512
|
assert.match(testDiscipline, /Test presence alone does not establish applicability; no TUI toggle or per-task chat choice is needed/);
|
|
1444
|
-
assert.match(testDiscipline, /RED[\s\S]*GREEN[\s\S]*
|
|
1513
|
+
assert.match(testDiscipline, /RED[\s\S]*GREEN[\s\S]*PRESERVE[\s\S]*REFACTOR/);
|
|
1445
1514
|
assert.match(testDiscipline, /no meaningful RED[\s\S]*proportionate ordinary functional or structural verification/);
|
|
1446
1515
|
assert.match(testDiscipline, /Never claim RED\/GREEN evidence that was not observed/);
|
|
1447
1516
|
assert.match(
|
|
@@ -1604,7 +1673,7 @@ test("global model routing uses PI_CODING_AGENT_DIR for package-installed agents
|
|
|
1604
1673
|
test("normal and forced installation copy generic agents with complete role contracts", () => {
|
|
1605
1674
|
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
1606
1675
|
const expectedTools = {
|
|
1607
|
-
"gentle-ai-explore": ["read", "grep", "find", "codegraph"],
|
|
1676
|
+
"gentle-ai-explore": ["read", "grep", "find", "codegraph", "web_enable", "web_search", "source_check", "fetch_content", "get_search_content"],
|
|
1608
1677
|
"gentle-ai-verify": ["read", "grep", "find", "bash"],
|
|
1609
1678
|
} as const;
|
|
1610
1679
|
|
|
@@ -1636,7 +1705,7 @@ test("normal and forced installation copy generic agents with complete role cont
|
|
|
1636
1705
|
assert.match(source, /compressed (?:handoff|evidence handoff)/);
|
|
1637
1706
|
assert.match(source, /Do not use review lenses\. RDD review remains independent and parent-owned\./);
|
|
1638
1707
|
if (name === "gentle-ai-verify") {
|
|
1639
|
-
assert.match(source, /exact test, build, or
|
|
1708
|
+
assert.match(source, /exact test, build, lint, or spec example commands explicitly authorized by the parent/);
|
|
1640
1709
|
assert.match(source, /only outputs the parent explicitly identified as expected/);
|
|
1641
1710
|
assert.match(source, /unexpected mutation as a blocker/);
|
|
1642
1711
|
assert.match(source, /do not clean it up or fix it/);
|
|
@@ -1652,16 +1721,30 @@ test("normal and forced installation copy generic agents with complete role cont
|
|
|
1652
1721
|
}
|
|
1653
1722
|
});
|
|
1654
1723
|
|
|
1655
|
-
test("bounded implementation routing
|
|
1724
|
+
test("bounded implementation routing resolves the explicit canonical fallback reference", () => {
|
|
1656
1725
|
const routing = readFileSync(
|
|
1657
1726
|
join(PACKAGE_ROOT, "assets", "orchestrator-delegation.md"),
|
|
1658
1727
|
"utf8",
|
|
1659
1728
|
);
|
|
1660
|
-
const
|
|
1729
|
+
const reference = "For bounded writes, follow the canonical Writer rule under Mandatory Delegation Triggers.";
|
|
1730
|
+
const resolveSimpleDelegation = (source: string): string => {
|
|
1731
|
+
const simpleDelegation = readMarkdownSection(source, "2. Simple Delegation");
|
|
1732
|
+
assert.ok(simpleDelegation.split("\n").includes(reference), "Simple Delegation must name the exact canonical Writer rule");
|
|
1733
|
+
const canonical = readMarkdownSection(source, "Mandatory Delegation Triggers");
|
|
1734
|
+
assertWorkerFallbackRouting(canonical, "resolved Simple Delegation");
|
|
1735
|
+
return canonical;
|
|
1736
|
+
};
|
|
1661
1737
|
const mandatoryDelegation = readMarkdownSection(routing, "Mandatory Delegation Triggers");
|
|
1662
1738
|
|
|
1663
|
-
assertWorkerFallbackRouting(
|
|
1739
|
+
assertWorkerFallbackRouting(resolveSimpleDelegation(routing), "Simple Delegation");
|
|
1664
1740
|
assertWorkerFallbackRouting(mandatoryDelegation, "Mandatory Delegation Triggers");
|
|
1741
|
+
assert.throws(() => resolveSimpleDelegation(routing.replace(reference, "")), /must name the exact canonical Writer rule/);
|
|
1742
|
+
assert.throws(() => resolveSimpleDelegation(routing.replace(reference, reference.replace("Mandatory Delegation Triggers", "Other Rule"))),
|
|
1743
|
+
/must name the exact canonical Writer rule/);
|
|
1744
|
+
assert.throws(() => resolveSimpleDelegation(routing.replace("#### Mandatory Delegation Triggers", "#### Missing Canonical Rule")),
|
|
1745
|
+
/exactly one Mandatory Delegation Triggers section/);
|
|
1746
|
+
assert.throws(() => resolveSimpleDelegation(routing.replace("user-configured `worker`", "unspecified worker")),
|
|
1747
|
+
/must prefer the package-owned worker before a user-configured worker/);
|
|
1665
1748
|
assert.doesNotMatch(
|
|
1666
1749
|
routing,
|
|
1667
1750
|
/non-normative compatibility quotation|former wording is retained|no-runtime inline exception|superseded by the stop requirement/,
|
|
@@ -1680,8 +1763,9 @@ test("orchestrator routes generic roles without static RDD lens routing", () =>
|
|
|
1680
1763
|
assert.match(routing, /`gentle-ai-explore`/);
|
|
1681
1764
|
assert.match(routing, /`gentle-ai-worker`/);
|
|
1682
1765
|
assert.match(routing, /`gentle-ai-verify`/);
|
|
1683
|
-
assert.match(routing, /
|
|
1684
|
-
|
|
1766
|
+
assert.match(routing, /focused test and (?:the )?suite/);
|
|
1767
|
+
// The Verification rule line itself must route high risk to the verifier.
|
|
1768
|
+
assert.match(routing, /^\d\. \*\*Verification rule\*\*[^\n]*high[- ]risk[^\n]*`gentle-ai-verify`/m);
|
|
1685
1769
|
assert.match(routing, /missing(?: or |\/)unusable[\s\S]*native `Agent`[\s\S]*(?:the )?same read-only/);
|
|
1686
1770
|
assert.match(routing, /report (?:the )?fallback/);
|
|
1687
1771
|
assert.doesNotMatch(routing, /review lenses? (?:inside|only inside)|review lens routing/i);
|
|
@@ -1705,9 +1789,9 @@ test("pi-pretty wrapper uses cached ESM loading for compiled and pnpm symlink in
|
|
|
1705
1789
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1706
1790
|
});
|
|
1707
1791
|
|
|
1708
|
-
test("Gentle Shell v4.
|
|
1792
|
+
test("Gentle Shell v4.2.0 package manifest declares the release version", () => {
|
|
1709
1793
|
const packageJson = readPackageJson();
|
|
1710
|
-
assert.equal(packageJson.version, "4.
|
|
1794
|
+
assert.equal(packageJson.version, "4.2.0", "the release manifest must be explicitly pinned to v4.2.0");
|
|
1711
1795
|
assert.equal(packageJson.scripts?.test, "node scripts/run-test-suite.mjs");
|
|
1712
1796
|
assert.ok(packageJson.files?.includes("assets/"));
|
|
1713
1797
|
assert.ok(packageJson.files?.includes("contracts/"));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { join, resolve, sep } from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import { resolveInstalledJitiStaticEntry } from "../scripts/test-packed-runner.mjs";
|
|
7
|
+
import * as packedRunner from "../scripts/test-packed-runner.mjs";
|
|
7
8
|
|
|
8
9
|
function withAliasedConsumer(run: (consumer: string, sdkManifest: string, jitiRoot: string) => void): void {
|
|
9
10
|
const disposable = mkdtempSync(join(tmpdir(), "packed-owned-path-"));
|
|
@@ -24,6 +25,39 @@ function withAliasedConsumer(run: (consumer: string, sdkManifest: string, jitiRo
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
test("hooked consumer approves only the canonical local tarball file spec", () => {
|
|
29
|
+
const disposable = mkdtempSync(join(tmpdir(), "packed-approval-"));
|
|
30
|
+
try {
|
|
31
|
+
const consumer = join(disposable, "install");
|
|
32
|
+
const pack = join(disposable, "pack");
|
|
33
|
+
mkdirSync(consumer);
|
|
34
|
+
mkdirSync(pack);
|
|
35
|
+
const tarball = join(pack, "gentle-pi-4.0.0.tgz");
|
|
36
|
+
writeFileSync(tarball, "owned tarball fixture");
|
|
37
|
+
const manifest = packedRunner.createHookedPackedConsumerManifest(consumer, tarball);
|
|
38
|
+
assert.deepEqual(manifest, {
|
|
39
|
+
name: "gentle-pi-packed-runner-test", private: true,
|
|
40
|
+
allowScripts: {
|
|
41
|
+
"file:../pack/gentle-pi-4.0.0.tgz": true,
|
|
42
|
+
[`file:${realpathSync.native(tarball).split(sep).join("/")}`]: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
assert.equal(manifest.allowScripts["gentle-pi"], undefined);
|
|
46
|
+
assert.equal(manifest.allowScripts["*"], undefined);
|
|
47
|
+
for (const spec of Object.keys(manifest.allowScripts)) {
|
|
48
|
+
const filePath = spec.slice(5);
|
|
49
|
+
assert.equal(realpathSync.native(resolve(consumer, filePath)), realpathSync.native(tarball));
|
|
50
|
+
}
|
|
51
|
+
symlinkSync(consumer, join(disposable, "consumer-alias"), "dir");
|
|
52
|
+
assert.deepEqual(packedRunner.createHookedPackedConsumerManifest(join(disposable, "consumer-alias"), tarball), manifest);
|
|
53
|
+
symlinkSync("gentle-pi-4.0.0.tgz", join(pack, "alias.tgz"));
|
|
54
|
+
assert.throws(() => packedRunner.createHookedPackedConsumerManifest(consumer, join(pack, "alias.tgz")), /symbolic link/);
|
|
55
|
+
assert.throws(() => packedRunner.createHookedPackedConsumerManifest(consumer, pack), /regular file/);
|
|
56
|
+
} finally {
|
|
57
|
+
rmSync(disposable, { recursive: true, force: true });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
27
61
|
const checks = { sdkManifest: "sdk-manifest", sdkVersion: "sdk-version", jitiManifest: "jiti-manifest-owned", jitiStaticExport: "jiti-static-export", jitiEntry: "jiti-entry-owned", jitiVersion: "jiti-version" };
|
|
28
62
|
|
|
29
63
|
test("accepts a canonical Jiti manifest beneath an aliased ancestor", () => {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
// gentle-shell#1731 T29 (L66): in the targeted bench (B, pi --mode json,
|
|
7
|
+
// background policy off) the parallelism reason fired for two independent
|
|
8
|
+
// features, but the writers ran one after the other in task mode: no
|
|
9
|
+
// parallelism, only subagent overhead (x5 took 10.9 min vs 4.5 inline). The
|
|
10
|
+
// lazy writer module already voided serial launches, but it loads only after
|
|
11
|
+
// the core rule fires, so the condition must live in the core Writer rule.
|
|
12
|
+
const core = readFileSync(join(import.meta.dirname, "..", "assets/orchestrator.md"), "utf8");
|
|
13
|
+
|
|
14
|
+
test("T29: the core parallelism reason requires launching the units together in background", () => {
|
|
15
|
+
const rule = core.split("\n").find((line) => line.includes("5. **Writer rule**"));
|
|
16
|
+
assert.ok(rule, "core Writer rule is missing");
|
|
17
|
+
assert.ok(
|
|
18
|
+
rule.includes("launched together in background, else inline"),
|
|
19
|
+
"parallelism must require background launches in the same turn",
|
|
20
|
+
);
|
|
21
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { AGENT_MODE, type AgentDefinition } from "../lib/agents-config.ts";
|
|
4
|
+
import { TASK_STATUS, TaskStore } from "../lib/agents-protocol.ts";
|
|
5
|
+
import { AgentRunner, type TaskRequest } from "../lib/agents-runner.ts";
|
|
6
|
+
import { fakeChild, type FakeChild } from "./agents-fake-child.ts";
|
|
7
|
+
|
|
8
|
+
// gentle-shell#1731 T4 (S2, AC6): the runner admits a writer only while no
|
|
9
|
+
// queued or running writer in the same worktree root claims an overlapping
|
|
10
|
+
// `## Allowed edit surfaces` entry. Admission and registration happen in the
|
|
11
|
+
// same synchronous run() call, and every terminal path releases the claim.
|
|
12
|
+
|
|
13
|
+
const worker: AgentDefinition = { name: "gentle-ai-worker", description: "writes", filePath: "/a/worker.md", scope: "global", instructions: "You write.", model: undefined, thinking: undefined, mode: undefined, tools: ["read", "edit", "write"] };
|
|
14
|
+
const explorer: AgentDefinition = { ...worker, name: "explore", description: "maps", tools: ["read"] };
|
|
15
|
+
|
|
16
|
+
function writer(surfaces: string[], overrides: Partial<TaskRequest> = {}): TaskRequest {
|
|
17
|
+
return { agent: worker, prompt: `Write.\n\n## Allowed edit surfaces\n${surfaces.join("\n")}\n`, label: undefined, context: undefined, mode: AGENT_MODE.BACKGROUND, cwd: "/repo", parentSessionId: "s1", model: undefined, thinking: undefined, sessionDir: "/sessions", resumeSessionPath: undefined, env: {}, writerSurfaces: surfaces, ...overrides };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function harness(maxConcurrency = 5) {
|
|
21
|
+
const children: FakeChild[] = [];
|
|
22
|
+
const timers: Array<{ fn: () => void; cancelled: boolean }> = [];
|
|
23
|
+
const store = new TaskStore();
|
|
24
|
+
const runner = new AgentRunner(store, { maxConcurrency, stallTimeoutMs: 10_000 }, {
|
|
25
|
+
spawn: () => {
|
|
26
|
+
const fake = fakeChild();
|
|
27
|
+
children.push(fake);
|
|
28
|
+
return fake.child;
|
|
29
|
+
},
|
|
30
|
+
now: () => Date.now(),
|
|
31
|
+
schedule: (fn) => {
|
|
32
|
+
const timer = { fn, cancelled: false };
|
|
33
|
+
timers.push(timer);
|
|
34
|
+
return () => { timer.cancelled = true; };
|
|
35
|
+
},
|
|
36
|
+
pi: { command: "pi", args: [] },
|
|
37
|
+
}, { askUser: async () => ({ cancelled: true }) });
|
|
38
|
+
const fireTimers = () => { for (const timer of timers.splice(0)) if (!timer.cancelled) timer.fn(); };
|
|
39
|
+
return { store, runner, children, fireTimers };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const tick = () => new Promise((resolve) => setImmediate(resolve));
|
|
43
|
+
|
|
44
|
+
async function complete(h: ReturnType<typeof harness>, index: number, id: string): Promise<void> {
|
|
45
|
+
h.children[index].emit({ type: "agent_end", messages: [{ role: "assistant", content: [{ type: "text", text: "done" }] }] });
|
|
46
|
+
h.children[index].emit({ type: "agent_settled" });
|
|
47
|
+
await h.runner.waitFor(id);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test("writers with disjoint surfaces run concurrently in one worktree", async () => {
|
|
51
|
+
const h = harness();
|
|
52
|
+
const a = h.runner.run(writer(["lib/a.ts", "tests/a.test.ts"]));
|
|
53
|
+
const b = h.runner.run(writer(["lib/b.ts", "tests/b.test.ts"]));
|
|
54
|
+
await tick();
|
|
55
|
+
assert.equal(h.children.length, 2);
|
|
56
|
+
assert.equal(h.store.get(a.id)?.status, TASK_STATUS.RUNNING);
|
|
57
|
+
assert.equal(h.store.get(b.id)?.status, TASK_STATUS.RUNNING);
|
|
58
|
+
h.runner.cancelAll();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("an overlapping writer is rejected before any task, queue entry or spawn exists", async () => {
|
|
62
|
+
const h = harness();
|
|
63
|
+
const a = h.runner.run(writer(["lib/a.ts", "tests/a.test.ts"]));
|
|
64
|
+
await tick();
|
|
65
|
+
assert.throws(() => h.runner.run(writer(["lib/*.ts"])), (error: Error) => {
|
|
66
|
+
assert.match(error.message, new RegExp(`task ${a.id}`));
|
|
67
|
+
assert.match(error.message, /`lib\/\*\.ts` overlaps `lib\/a\.ts`/);
|
|
68
|
+
assert.match(error.message, /isolated worktree/);
|
|
69
|
+
return true;
|
|
70
|
+
});
|
|
71
|
+
assert.equal(h.store.list("s1").length, 1, "the rejected writer leaves no task record");
|
|
72
|
+
await tick();
|
|
73
|
+
assert.equal(h.children.length, 1, "the rejected writer never spawns");
|
|
74
|
+
h.runner.cancelAll();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("a queued writer already holds its surfaces", async () => {
|
|
78
|
+
const h = harness(1);
|
|
79
|
+
h.runner.run(writer(["lib/a.ts"]));
|
|
80
|
+
const queued = h.runner.run(writer(["lib/b.ts"]));
|
|
81
|
+
await tick();
|
|
82
|
+
assert.equal(h.store.get(queued.id)?.status, TASK_STATUS.QUEUED);
|
|
83
|
+
assert.throws(() => h.runner.run(writer(["lib/b.ts"])), new RegExp(`task ${queued.id}`));
|
|
84
|
+
h.runner.cancelAll();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("completion releases the claim", async () => {
|
|
88
|
+
const h = harness();
|
|
89
|
+
const a = h.runner.run(writer(["lib/a.ts"]));
|
|
90
|
+
await tick();
|
|
91
|
+
await complete(h, 0, a.id);
|
|
92
|
+
assert.equal(h.store.get(a.id)?.status, TASK_STATUS.COMPLETED);
|
|
93
|
+
const next = h.runner.run(writer(["lib/a.ts"]));
|
|
94
|
+
assert.ok(next.id);
|
|
95
|
+
h.runner.cancelAll();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("failure releases the claim", async () => {
|
|
99
|
+
const h = harness();
|
|
100
|
+
const a = h.runner.run(writer(["lib/a.ts"]));
|
|
101
|
+
await tick();
|
|
102
|
+
h.children[0].exit(1);
|
|
103
|
+
await h.runner.waitFor(a.id);
|
|
104
|
+
assert.equal(h.store.get(a.id)?.status, TASK_STATUS.FAILED);
|
|
105
|
+
assert.doesNotThrow(() => h.runner.run(writer(["lib/a.ts"])));
|
|
106
|
+
h.runner.cancelAll();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("a spawn failure releases the claim", async () => {
|
|
110
|
+
const h = harness();
|
|
111
|
+
const a = h.runner.run(writer(["lib/a.ts"], { beforeSpawn: () => { throw new Error("session changed"); } }));
|
|
112
|
+
await h.runner.waitFor(a.id);
|
|
113
|
+
assert.equal(h.store.get(a.id)?.status, TASK_STATUS.FAILED);
|
|
114
|
+
assert.doesNotThrow(() => h.runner.run(writer(["lib/a.ts"])));
|
|
115
|
+
h.runner.cancelAll();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("cancelling a running or queued writer releases its claim", async () => {
|
|
119
|
+
const h = harness(1);
|
|
120
|
+
const running = h.runner.run(writer(["lib/a.ts"]));
|
|
121
|
+
const queued = h.runner.run(writer(["lib/b.ts"]));
|
|
122
|
+
await tick();
|
|
123
|
+
assert.equal(h.runner.cancel(queued.id), true);
|
|
124
|
+
assert.equal(h.store.get(queued.id)?.status, TASK_STATUS.CANCELLED);
|
|
125
|
+
assert.doesNotThrow(() => h.runner.run(writer(["lib/b.ts"])));
|
|
126
|
+
assert.equal(h.runner.cancel(running.id), true);
|
|
127
|
+
await h.runner.waitFor(running.id);
|
|
128
|
+
assert.equal(h.store.get(running.id)?.status, TASK_STATUS.CANCELLED);
|
|
129
|
+
assert.doesNotThrow(() => h.runner.run(writer(["lib/a.ts"])));
|
|
130
|
+
h.runner.cancelAll();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("a stall timeout releases the claim", async () => {
|
|
134
|
+
const h = harness();
|
|
135
|
+
const a = h.runner.run(writer(["lib/a.ts"]));
|
|
136
|
+
await tick();
|
|
137
|
+
h.fireTimers();
|
|
138
|
+
await h.runner.waitFor(a.id);
|
|
139
|
+
assert.equal(h.store.get(a.id)?.status, TASK_STATUS.TIMED_OUT);
|
|
140
|
+
assert.doesNotThrow(() => h.runner.run(writer(["lib/a.ts"])));
|
|
141
|
+
h.runner.cancelAll();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Verify m1: a quarantined writer may still be alive, so its claim lives as
|
|
145
|
+
// long as its reserved concurrency slot and is released only on proven exit.
|
|
146
|
+
test("a quarantined writer keeps its claim until its exit is confirmed", async () => {
|
|
147
|
+
const store = new TaskStore();
|
|
148
|
+
const timers: Array<{ fn: () => void; ms: number; cancelled: boolean }> = [];
|
|
149
|
+
let now = 0;
|
|
150
|
+
let groupGone = false;
|
|
151
|
+
const child = fakeChild({ exitOnKill: false, pid: 71 });
|
|
152
|
+
const runner = new AgentRunner(store, { maxConcurrency: 5, stallTimeoutMs: 10_000 }, {
|
|
153
|
+
spawn: () => child.child,
|
|
154
|
+
now: () => now,
|
|
155
|
+
schedule: (fn, ms) => {
|
|
156
|
+
const timer = { fn, ms, cancelled: false };
|
|
157
|
+
timers.push(timer);
|
|
158
|
+
return () => { timer.cancelled = true; };
|
|
159
|
+
},
|
|
160
|
+
pi: { command: "pi", args: [] },
|
|
161
|
+
process: { platform: "linux", kill: (_pid, signal) => {
|
|
162
|
+
if (signal === 0) throw Object.assign(new Error("group probe"), { code: groupGone ? "ESRCH" : "EPERM" });
|
|
163
|
+
} },
|
|
164
|
+
}, { askUser: async () => ({ cancelled: true }) });
|
|
165
|
+
const first = runner.run(writer(["lib/a.ts"]));
|
|
166
|
+
await tick();
|
|
167
|
+
runner.cancel(first.id);
|
|
168
|
+
timers.find((timer) => timer.ms === 250)!.fn();
|
|
169
|
+
now = 2_000;
|
|
170
|
+
timers.filter((timer) => timer.ms === 25).at(-1)!.fn();
|
|
171
|
+
await tick();
|
|
172
|
+
assert.match(store.get(first.id)?.error ?? "", /capacity quarantined/);
|
|
173
|
+
assert.throws(() => runner.run(writer(["lib/a.ts"])), new RegExp(`task ${first.id}`), "the quarantined writer still holds its surfaces");
|
|
174
|
+
groupGone = true;
|
|
175
|
+
child.exit(0);
|
|
176
|
+
await tick();
|
|
177
|
+
assert.doesNotThrow(() => runner.run(writer(["lib/a.ts"])), "proven exit releases the claim");
|
|
178
|
+
runner.cancelAll();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Second verify A1: the claim key is the canonical worktree root supplied by
|
|
182
|
+
// the parent, not the spawn cwd, so a subdirectory cwd and the root conflict.
|
|
183
|
+
test("writers keyed to the same canonical root conflict whatever their cwd", async () => {
|
|
184
|
+
const h = harness();
|
|
185
|
+
const first = h.runner.run(writer(["lib/a.ts"], { cwd: "/repo/sub", writerRoot: "/repo" }));
|
|
186
|
+
assert.throws(() => h.runner.run(writer(["lib/a.ts"], { cwd: "/repo", writerRoot: "/repo" })), new RegExp(`task ${first.id}`));
|
|
187
|
+
assert.doesNotThrow(() => h.runner.run(writer(["lib/a.ts"], { cwd: "/repo-worktrees/feature", writerRoot: "/repo-worktrees/feature" })));
|
|
188
|
+
h.runner.cancelAll();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("writers in different worktree roots never conflict", async () => {
|
|
192
|
+
const h = harness();
|
|
193
|
+
h.runner.run(writer(["lib/a.ts"], { cwd: "/repo" }));
|
|
194
|
+
assert.doesNotThrow(() => h.runner.run(writer(["lib/a.ts"], { cwd: "/repo-worktrees/feature" })));
|
|
195
|
+
await tick();
|
|
196
|
+
assert.equal(h.children.length, 2);
|
|
197
|
+
h.runner.cancelAll();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("read-only agents are never registered and never blocked", async () => {
|
|
201
|
+
const h = harness();
|
|
202
|
+
h.runner.run(writer(["lib/**"]));
|
|
203
|
+
const reader = h.runner.run(writer([], { agent: explorer, prompt: "Map lib/", writerSurfaces: undefined }));
|
|
204
|
+
const second = h.runner.run(writer([], { agent: explorer, prompt: "Map lib/ again", writerSurfaces: undefined }));
|
|
205
|
+
await tick();
|
|
206
|
+
assert.equal(h.store.get(reader.id)?.status, TASK_STATUS.RUNNING);
|
|
207
|
+
assert.equal(h.store.get(second.id)?.status, TASK_STATUS.RUNNING);
|
|
208
|
+
assert.doesNotThrow(() => h.runner.run(writer(["tests/a.ts"])), "a reader holds no surfaces");
|
|
209
|
+
h.runner.cancelAll();
|
|
210
|
+
});
|