gentle-pi 3.7.0 → 4.1.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 +155 -10
- package/assets/agents/gentle-ai-explore.md +13 -6
- package/assets/agents/gentle-ai-verify.md +27 -6
- package/assets/agents/gentle-ai-worker.md +13 -15
- 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 +75 -169
- package/assets/orchestrator-memory.md +12 -26
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-skills.md +1 -1
- 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 +30 -38
- 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/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +956 -50
- 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/bundled-install.md +397 -0
- package/docs/delegated-verification.md +27 -4
- package/docs/gentle-agents-activity.md +537 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +165 -51
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +3 -3
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +364 -231
- package/docs/render-cache-performance.md +28 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +35 -0
- package/extensions/child-safety.ts +25 -0
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +1093 -326
- package/extensions/gentle-ai.ts +1636 -890
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +1619 -136
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +126 -20
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1424 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +254 -92
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +191 -58
- package/extensions/startup-banner.ts +71 -33
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +99 -0
- package/lib/agents-runner.ts +180 -107
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/auto-permission-classifier.ts +280 -0
- package/lib/auto-permission-runtime.ts +268 -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 +220 -0
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +195 -0
- package/lib/command-palette-catalog.ts +5 -10
- package/lib/command-palette.ts +25 -14
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +89 -27
- package/lib/gentle-shell-resume-hint.ts +176 -0
- 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/history-capture-policy.ts +95 -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 +5 -1
- package/lib/nan-provider.ts +249 -0
- package/lib/native-review-cli.ts +242 -180
- 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-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- 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/overlay-repaint.ts +26 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +73 -21
- package/lib/review-candidate-view.ts +115 -27
- 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-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +227 -56
- package/lib/review-sidebar-state.ts +224 -0
- 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 +527 -0
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +266 -0
- package/lib/session-worktree-registry.ts +87 -9
- package/lib/shell-bar.ts +215 -30
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +42 -14
- package/lib/shell-changes.ts +86 -71
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +83 -15
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +84 -31
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +70 -13
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +383 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +119 -0
- package/lib/visual-customize-view.ts +481 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +317 -0
- package/package.json +20 -8
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +518 -0
- package/runtime/gentle-shell-launcher.mjs +87 -25
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- 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 +239 -177
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +225 -54
- 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 +8 -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 +83 -35
- 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/mirror-odd-routing.mjs +2 -2
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +90 -0
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +54 -19
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +91 -38
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +16 -21
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +30 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +97 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +467 -93
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/auto-mode-invariants.test.ts +258 -0
- package/tests/auto-mode-sdk.test.ts +57 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-classifier.test.ts +283 -0
- package/tests/auto-permission-runtime.test.ts +196 -0
- package/tests/autonomous-guard.test.ts +69 -1
- 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 +146 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- 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 +87 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-context-files.test.ts +255 -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 +94 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +596 -0
- package/tests/command-palette.test.ts +43 -7
- 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 -78
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +171 -3
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +288 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +69 -0
- package/tests/gentle-agents.test.ts +2804 -346
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +21 -4
- 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-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +566 -62
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +493 -14
- package/tests/gentle-ai.test.ts +1427 -236
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +896 -115
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +333 -65
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- 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 +4216 -119
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +333 -10
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +49 -19
- 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 +2158 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +389 -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-authority.test.ts +16 -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 +481 -0
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +239 -118
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +101 -4
- 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 +76 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
- package/tests/odd-routing-contract.test.ts +158 -50
- package/tests/orchestrator-budget.test.ts +43 -45
- 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 +8 -7
- 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/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +344 -123
- package/tests/packed-runner-owned-path.test.ts +80 -0
- 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 +10 -18
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/provider-defect-handoff.test.ts +7 -14
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +569 -187
- package/tests/rdd-aware-verification-contract.test.ts +103 -40
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +237 -21
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +399 -12
- package/tests/review-contract-prompt.test.ts +138 -44
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +866 -6
- package/tests/review-controller-workspace-root.test.ts +206 -6
- 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 +342 -4
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +221 -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 +14 -37
- 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-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +594 -26
- 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-sidebar-state.test.ts +402 -0
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +202 -0
- package/tests/runtime-harness.mjs +186 -800
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +451 -0
- 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-messaging-grants.test.ts +255 -0
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +381 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +134 -2
- package/tests/shell-bar.test.ts +422 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +135 -32
- package/tests/shell-changes.test.ts +61 -62
- package/tests/shell-prompt.test.ts +94 -2
- 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 +351 -22
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +207 -3
- package/tests/shell-usage-view.test.ts +28 -1
- package/tests/shell-usage.test.ts +108 -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 +357 -2
- package/tests/startup-banner.test.ts +246 -4
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -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 +107 -20
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/theme-customization.test.ts +72 -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-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +139 -0
- package/tests/visual-customize-view.test.ts +696 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +271 -0
- package/tests/yolo-mode-runtime.test.ts +188 -0
- package/tests/yolo-mode.test.ts +289 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
- /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
|
@@ -17,9 +17,9 @@ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
17
17
|
const { createGentleAiExtension } = await import(pathToFileURL(join(ROOT, "extensions/gentle-ai.ts")).href);
|
|
18
18
|
const EXTENSIONS = [
|
|
19
19
|
"extensions/gentle-ai.ts",
|
|
20
|
+
"extensions/nan-provider.ts",
|
|
20
21
|
"extensions/quiet-tools.ts",
|
|
21
22
|
"extensions/skill-registry.ts",
|
|
22
|
-
"extensions/sdd-init.ts",
|
|
23
23
|
"extensions/startup-banner.ts",
|
|
24
24
|
];
|
|
25
25
|
|
|
@@ -33,30 +33,16 @@ const EXPECTED_BANNER_COMMANDS = [
|
|
|
33
33
|
const EXPECTED_COMMANDS = [
|
|
34
34
|
"gentle:install-delegation",
|
|
35
35
|
"gentle:install-review",
|
|
36
|
-
"gentle:install-sdd",
|
|
37
|
-
"gentle:sdd-preflight",
|
|
38
|
-
"gentle-sdd-status",
|
|
39
|
-
"gentle-sdd-continue",
|
|
40
36
|
"gentle:models",
|
|
41
37
|
"gentle:persona",
|
|
42
38
|
"gentle:status",
|
|
43
39
|
"gentle:doctor",
|
|
44
|
-
"gentle-sdd-init",
|
|
45
40
|
"skill-registry:refresh",
|
|
46
41
|
...EXPECTED_BANNER_COMMANDS,
|
|
47
42
|
];
|
|
48
43
|
|
|
49
44
|
const FORBIDDEN_COMPAT_COMMANDS = [
|
|
50
45
|
"gentle:install-assets",
|
|
51
|
-
// The SDD entry points carry the gentle- prefix so they read identically in
|
|
52
|
-
// Claude Code and Pi. The bare names are retired without an alias.
|
|
53
|
-
"sdd-init",
|
|
54
|
-
"sdd-continue",
|
|
55
|
-
"sdd-status",
|
|
56
|
-
"gentle-ai:install-sdd",
|
|
57
|
-
"gentle-ai:sdd-preflight",
|
|
58
|
-
"gentle-ai:sdd-status",
|
|
59
|
-
"gentle-ai:sdd-continue",
|
|
60
46
|
"gentle-ai:models",
|
|
61
47
|
"gentleman:models",
|
|
62
48
|
"gentle-ai:persona",
|
|
@@ -72,6 +58,7 @@ const FORBIDDEN_COMPAT_COMMANDS = [
|
|
|
72
58
|
function createPi() {
|
|
73
59
|
const hooks = new Map();
|
|
74
60
|
const commands = new Map();
|
|
61
|
+
const providers = new Map();
|
|
75
62
|
const flags = new Map();
|
|
76
63
|
const tools = new Map();
|
|
77
64
|
const eventHandlers = new Map();
|
|
@@ -104,6 +91,10 @@ function createPi() {
|
|
|
104
91
|
registerCommand(name, definition) {
|
|
105
92
|
commands.set(name, definition);
|
|
106
93
|
},
|
|
94
|
+
registerProvider(name, config) {
|
|
95
|
+
if (typeof name === "object") providers.set(name.id, name);
|
|
96
|
+
else providers.set(name, config);
|
|
97
|
+
},
|
|
107
98
|
registerFlag(name, definition) {
|
|
108
99
|
flags.set(name, definition);
|
|
109
100
|
},
|
|
@@ -136,7 +127,7 @@ function createPi() {
|
|
|
136
127
|
},
|
|
137
128
|
};
|
|
138
129
|
|
|
139
|
-
return { pi, hooks, commands, flags, tools, emittedEvents };
|
|
130
|
+
return { pi, hooks, commands, providers, flags, tools, emittedEvents };
|
|
140
131
|
}
|
|
141
132
|
|
|
142
133
|
function createUi() {
|
|
@@ -217,25 +208,56 @@ function restoreWorkspaceWritePermissions(cwd) {
|
|
|
217
208
|
}
|
|
218
209
|
}
|
|
219
210
|
|
|
220
|
-
|
|
211
|
+
const ownedFixtureRoots = [];
|
|
212
|
+
const previousFixtureEnvironment = new Map([
|
|
213
|
+
"HOME", "USERPROFILE", "XDG_CONFIG_HOME", "PI_CODING_AGENT_DIR",
|
|
214
|
+
"GENTLE_PI_CONFIG_HOME", "GENTLE_PI_AGENT_HOME", "GENTLE_PI_TEST_ASSETS_DIR",
|
|
215
|
+
"GENTLE_PI_AGENTS_PARENT_PERMISSION_FD",
|
|
216
|
+
].map((key) => [key, process.env[key]]));
|
|
217
|
+
|
|
218
|
+
async function loadExtensions(pi, fixtureDependencies) {
|
|
221
219
|
for (const [index, rel] of EXTENSIONS.entries()) {
|
|
222
220
|
const mod = await import(`${pathToFileURL(join(ROOT, rel)).href}?runtime-harness=${index}`);
|
|
223
221
|
assert.equal(typeof mod.default, "function", `${rel} must export a default function`);
|
|
224
|
-
|
|
222
|
+
if (rel === "extensions/gentle-ai.ts") createGentleAiExtension(fixtureDependencies)(pi);
|
|
223
|
+
else mod.default(pi);
|
|
225
224
|
}
|
|
226
225
|
}
|
|
227
226
|
|
|
228
227
|
async function run() {
|
|
229
228
|
const globalConfigHome = await tempWorkspace();
|
|
229
|
+
ownedFixtureRoots.push(globalConfigHome);
|
|
230
230
|
const globalAgentHome = await tempWorkspace();
|
|
231
|
+
ownedFixtureRoots.push(globalAgentHome);
|
|
231
232
|
const ambientTestAssetsDir = await tempWorkspace();
|
|
233
|
+
ownedFixtureRoots.push(ambientTestAssetsDir);
|
|
232
234
|
process.env.GENTLE_PI_CONFIG_HOME = globalConfigHome;
|
|
233
235
|
process.env.GENTLE_PI_AGENT_HOME = globalAgentHome;
|
|
234
236
|
process.env.GENTLE_PI_TEST_ASSETS_DIR = ambientTestAssetsDir;
|
|
237
|
+
process.env.HOME = globalAgentHome;
|
|
238
|
+
process.env.USERPROFILE = globalAgentHome;
|
|
239
|
+
process.env.XDG_CONFIG_HOME = join(globalAgentHome, "xdg");
|
|
240
|
+
process.env.PI_CODING_AGENT_DIR = join(globalAgentHome, "pi");
|
|
241
|
+
// Discovery still exercises the default exports under the actual worker
|
|
242
|
+
// role, but must never acquire the worker's parent-permission channel.
|
|
243
|
+
delete process.env.GENTLE_PI_AGENTS_PARENT_PERMISSION_FD;
|
|
244
|
+
const fixtureDependencies = {
|
|
245
|
+
nativeReviewCli: {},
|
|
246
|
+
processEnv: {
|
|
247
|
+
HOME: globalAgentHome, USERPROFILE: globalAgentHome,
|
|
248
|
+
GENTLE_PI_CONFIG_HOME: globalConfigHome, GENTLE_PI_AGENT_HOME: globalAgentHome,
|
|
249
|
+
PI_CODING_AGENT_DIR: process.env.PI_CODING_AGENT_DIR,
|
|
250
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
|
|
251
|
+
GENTLE_PI_AGENTS_CHILD: "0", GENTLE_AI_TELEMETRY: "0",
|
|
252
|
+
},
|
|
253
|
+
resolveTelemetryTriggerBinary: () => join(globalAgentHome, "never-executed"),
|
|
254
|
+
telemetryTriggerSpawn: () => assert.fail("Runtime fixtures must not spawn telemetry"),
|
|
255
|
+
};
|
|
235
256
|
const globalModelsPath = join(globalConfigHome, "models.json");
|
|
236
257
|
const globalSubagentsPath = join(globalAgentHome, "subagents.json");
|
|
237
|
-
const { pi, hooks, commands, flags, tools, emittedEvents } = createPi();
|
|
238
|
-
await loadExtensions(pi);
|
|
258
|
+
const { pi, hooks, commands, providers, flags, tools, emittedEvents } = createPi();
|
|
259
|
+
await loadExtensions(pi, fixtureDependencies);
|
|
260
|
+
assert.equal(providers.get("nan")?.getModels()[0]?.api, "openai-completions", "runtime extension loading registers the NaN provider");
|
|
239
261
|
|
|
240
262
|
// gentle-pi#404: a collect binding that returns the native last-event
|
|
241
263
|
// closure must terminate after one capture. It must not re-enter a public
|
|
@@ -313,7 +335,7 @@ async function run() {
|
|
|
313
335
|
},
|
|
314
336
|
};
|
|
315
337
|
const lastEventPi = createPi();
|
|
316
|
-
createGentleAiExtension({ nativeReviewCli })(lastEventPi.pi);
|
|
338
|
+
createGentleAiExtension({ ...fixtureDependencies, nativeReviewCli })(lastEventPi.pi);
|
|
317
339
|
const controller = lastEventPi.tools.get("gentle_review");
|
|
318
340
|
const capture = lastEventPi.tools.get("gentle_review_capture");
|
|
319
341
|
assert.ok(controller, "runtime must register the public status controller");
|
|
@@ -356,15 +378,49 @@ async function run() {
|
|
|
356
378
|
for (const name of FORBIDDEN_COMPAT_COMMANDS) {
|
|
357
379
|
assert.equal(commands.has(name), false, `compat command should not be registered: ${name}`);
|
|
358
380
|
}
|
|
381
|
+
// Retired SDD command entry points must not register, regardless of unrelated prompt text.
|
|
382
|
+
for (const name of [
|
|
383
|
+
"gentle:install-sdd", "gentle:sdd-preflight", "gentle-sdd-status",
|
|
384
|
+
"gentle-sdd-continue", "gentle-sdd-init", "sdd-init", "sdd-continue",
|
|
385
|
+
"sdd-status", "gentle-ai:install-sdd", "gentle-ai:sdd-preflight",
|
|
386
|
+
"gentle-ai:sdd-status", "gentle-ai:sdd-continue",
|
|
387
|
+
]) {
|
|
388
|
+
assert.equal(commands.has(name), false, `retired SDD command registered: ${name}`);
|
|
389
|
+
}
|
|
359
390
|
assert.ok(flags.has("no-skill-registry"), "missing no-skill-registry flag");
|
|
360
391
|
assert.ok(hooks.has("session_start"), "missing session_start hook");
|
|
361
392
|
assert.ok(hooks.has("session_shutdown"), "missing session_shutdown hook");
|
|
362
|
-
assert.ok(hooks.has("input"), "missing input hook");
|
|
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
|
+
}
|
|
363
411
|
assert.ok(hooks.has("before_agent_start"), "missing before_agent_start hook");
|
|
364
412
|
assert.ok(hooks.has("tool_call"), "missing tool_call hook");
|
|
365
|
-
for (const toolName of ["read", "
|
|
413
|
+
for (const toolName of ["read", "grep", "find", "ls", "edit", "write"]) {
|
|
366
414
|
assert.ok(tools.has(toolName), `missing quiet built-in tool renderer ${toolName}`);
|
|
367
415
|
}
|
|
416
|
+
const codemode = tools.get("codemode");
|
|
417
|
+
assert.ok(codemode, "quiet-tools must decorate the upstream codemode registration");
|
|
418
|
+
assert.equal(codemode.defaultActive, false, "rendering must not activate codemode");
|
|
419
|
+
assert.equal(codemode.renderShell, "self");
|
|
420
|
+
assert.equal(typeof codemode.renderCall, "function");
|
|
421
|
+
assert.equal(typeof codemode.renderResult, "function");
|
|
422
|
+
assert.equal(typeof codemode.prepareLoadout, "function", "upstream loadout policy must survive decoration");
|
|
423
|
+
assert.deepEqual(pi.getActiveTools(), ["read", "bash", "edit", "write"], "card registration must not change tool activation");
|
|
368
424
|
assert.ok(tools.has("gentle_review"), "missing registered bounded review controller tool");
|
|
369
425
|
assert.ok(tools.has("gentle_review_scope"), "missing registered bounded review scope tool");
|
|
370
426
|
assert.deepEqual(
|
|
@@ -389,73 +445,90 @@ async function run() {
|
|
|
389
445
|
[],
|
|
390
446
|
"declared extension directory must load without invalid helper modules",
|
|
391
447
|
);
|
|
448
|
+
assert.ok(
|
|
449
|
+
discovered.extensions.some((extension) => extension.resolvedPath.endsWith(join("extensions", "nan-provider.ts"))),
|
|
450
|
+
"declared extension directory must discover the NaN provider",
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
const nativeNan = discovered.runtime.pendingNativeProviderRegistrations
|
|
454
|
+
.find((entry) => entry.provider.id === "nan")?.provider;
|
|
455
|
+
assert.ok(nativeNan, "actual Pi loader must queue native NaN registration");
|
|
456
|
+
assert.ok(!discovered.runtime.pendingProviderRegistrations.some((entry) => entry.name === "nan"),
|
|
457
|
+
"NaN must not fall back to the legacy empty-key login route");
|
|
458
|
+
await assert.rejects(nativeNan.auth.apiKey.login({
|
|
459
|
+
signal: new AbortController().signal, prompt: async () => "", notify() {},
|
|
460
|
+
}), /non-empty/);
|
|
461
|
+
assert.deepEqual(await nativeNan.auth.apiKey.login({
|
|
462
|
+
signal: new AbortController().signal, prompt: async () => " synthetic-loader-key ", notify() {},
|
|
463
|
+
}), { type: "api_key", key: "synthetic-loader-key" });
|
|
392
464
|
|
|
393
465
|
// orchestrator-lazy-diet: Pi Subagent Model Routing detail (the "do not
|
|
394
466
|
// pass the `model` parameter by default" / SDD-model-assignment-scoping
|
|
395
467
|
// rules) moved verbatim to assets/orchestrator-delegation.md; the
|
|
396
468
|
// always-on combined prompt now only carries a pointer to it. Union read
|
|
397
469
|
// so these assertions are repointed, not weakened.
|
|
398
|
-
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");
|
|
399
472
|
|
|
400
473
|
const promptCwd = await tempWorkspace();
|
|
401
474
|
try {
|
|
402
475
|
const promptHook = hooks.get("before_agent_start")[0];
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
assert.
|
|
412
|
-
assert.
|
|
413
|
-
assert.
|
|
414
|
-
assert.match(
|
|
476
|
+
// gentle-shell#1485: pi-claude-bridge drops a handler-returned systemPrompt
|
|
477
|
+
// and forwards only structured systemPromptOptions, so the harness must
|
|
478
|
+
// land in appendSystemPrompt and the hook must never return a replacement.
|
|
479
|
+
const promptEvent = { systemPrompt: "base", systemPromptOptions: { appendSystemPrompt: "" } };
|
|
480
|
+
const promptResult = await promptHook(promptEvent, createCtx(promptCwd));
|
|
481
|
+
assert.equal(promptResult, undefined, "before_agent_start must not return a replacement systemPrompt");
|
|
482
|
+
assert.equal(promptEvent.systemPrompt, "base", "the original systemPrompt field must be left untouched");
|
|
483
|
+
const promptAppended = promptEvent.systemPromptOptions.appendSystemPrompt;
|
|
484
|
+
assert.match(promptAppended, /el Gentleman/);
|
|
485
|
+
assert.match(promptAppended, /Organic Driven Development/);
|
|
486
|
+
assert.doesNotMatch(promptAppended, /## SDD Research Capabilities/);
|
|
487
|
+
assert.match(promptAppended, /review execution contract/);
|
|
488
|
+
assert.doesNotMatch(await readFile(join(ROOT, "extensions", "gentle-ai.ts"), "utf8"), /readCommandSddStatus/);
|
|
489
|
+
assert.match(promptAppended + delegationDetail, /do not pass the `model` parameter by default/);
|
|
490
|
+
assert.doesNotMatch(promptAppended, /Every Agent tool call MUST include `model`/);
|
|
415
491
|
assert.ok(
|
|
416
|
-
|
|
492
|
+
promptAppended.includes(
|
|
417
493
|
`Package assets root: \`${join(ROOT, "assets")}\`. Lazy asset paths below are relative to this root.`,
|
|
418
494
|
),
|
|
419
495
|
"parent prompt must declare the one absolute root for relative lazy asset paths",
|
|
420
496
|
);
|
|
421
|
-
assert.match(promptResult.systemPrompt, /`sdd-orchestrator-workflow\.md`/);
|
|
422
497
|
assert.doesNotMatch(
|
|
423
|
-
|
|
498
|
+
promptAppended,
|
|
424
499
|
new RegExp(ambientTestAssetsDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
|
|
425
500
|
"normal runtime must ignore ambient GENTLE_PI_TEST_ASSETS_DIR",
|
|
426
501
|
);
|
|
427
|
-
assert.doesNotMatch(promptResult.systemPrompt, /\{\{GENTLE_PI_SDD_WORKFLOW_PATH\}\}/);
|
|
428
502
|
delete process.env.GENTLE_PI_TEST_ASSETS_DIR;
|
|
429
503
|
await rm(ambientTestAssetsDir, { recursive: true, force: true });
|
|
430
504
|
await writeFile(
|
|
431
505
|
join(globalConfigHome, "persona.json"),
|
|
432
506
|
'{"mode":"neutral"}\n',
|
|
433
507
|
);
|
|
434
|
-
const
|
|
435
|
-
|
|
508
|
+
const neutralPromptEvent = { systemPrompt: "base", systemPromptOptions: { appendSystemPrompt: "" } };
|
|
509
|
+
const neutralPromptResult = await promptHook(neutralPromptEvent, createCtx(promptCwd));
|
|
510
|
+
assert.equal(neutralPromptResult, undefined, "before_agent_start must not return a replacement systemPrompt");
|
|
511
|
+
const neutralAppended = neutralPromptEvent.systemPromptOptions.appendSystemPrompt;
|
|
512
|
+
assert.match(neutralAppended, /Do not use slang or regional expressions/);
|
|
436
513
|
assert.doesNotMatch(
|
|
437
|
-
|
|
514
|
+
neutralAppended,
|
|
438
515
|
/When the user writes Spanish, answer in natural Rioplatense Spanish with voseo/,
|
|
439
516
|
"neutral persona prompt must not include unconditional voseo instructions after reload",
|
|
440
517
|
);
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
);
|
|
445
|
-
assert.equal(subagentPromptResult.systemPrompt, "worker base");
|
|
446
|
-
assert.equal(
|
|
447
|
-
existsSync(join(promptCwd, ".pi", "agents", "sdd-apply.md")),
|
|
448
|
-
false,
|
|
449
|
-
"normal agent startup must not run SDD preflight",
|
|
450
|
-
);
|
|
518
|
+
const subagentEvent = { agentName: "worker", systemPrompt: "worker base", systemPromptOptions: { appendSystemPrompt: "" } };
|
|
519
|
+
const subagentPromptResult = await promptHook(subagentEvent, createCtx(promptCwd));
|
|
520
|
+
assert.equal(subagentPromptResult, undefined, "before_agent_start must not return a replacement systemPrompt");
|
|
521
|
+
assert.equal(subagentEvent.systemPromptOptions.appendSystemPrompt, "", "a named agent gets nothing appended");
|
|
451
522
|
await mkdir(join(promptCwd, ".pi", "gentle-ai"), { recursive: true });
|
|
452
523
|
await writeFile(
|
|
453
524
|
join(promptCwd, ".pi", "gentle-ai", "persona.json"),
|
|
454
525
|
'{"mode":"gentleman"}\n',
|
|
455
526
|
);
|
|
456
|
-
const
|
|
527
|
+
const localOverrideEvent = { systemPrompt: "base", systemPromptOptions: { appendSystemPrompt: "" } };
|
|
528
|
+
const localOverridePromptResult = await promptHook(localOverrideEvent, createCtx(promptCwd));
|
|
529
|
+
assert.equal(localOverridePromptResult, undefined, "before_agent_start must not return a replacement systemPrompt");
|
|
457
530
|
assert.match(
|
|
458
|
-
|
|
531
|
+
localOverrideEvent.systemPromptOptions.appendSystemPrompt,
|
|
459
532
|
/When the user writes Spanish, answer in natural Rioplatense Spanish with voseo/,
|
|
460
533
|
);
|
|
461
534
|
const personaCtx = createCtx(promptCwd, true);
|
|
@@ -470,54 +543,6 @@ async function run() {
|
|
|
470
543
|
'{\n "mode": "neutral"\n}\n',
|
|
471
544
|
);
|
|
472
545
|
assert.match(personaCtx.ui.notifications.at(-1).message, /Global config:/);
|
|
473
|
-
const onboardCtx = createCtx(promptCwd, true, "sdd-onboard-session");
|
|
474
|
-
onboardCtx.ui.select = async (_label, options) => options[0];
|
|
475
|
-
const onboardPromptResult = await promptHook(
|
|
476
|
-
{ agentName: "sdd-onboard", systemPrompt: "onboard base" },
|
|
477
|
-
onboardCtx,
|
|
478
|
-
);
|
|
479
|
-
assert.match(onboardPromptResult.systemPrompt, /onboard base/);
|
|
480
|
-
assert.match(onboardPromptResult.systemPrompt, /## SDD Session Preflight/);
|
|
481
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-onboard.md")), true);
|
|
482
|
-
await mkdir(join(promptCwd, "openspec", "changes", "status-demo", "specs", "demo"), { recursive: true });
|
|
483
|
-
await writeFile(join(promptCwd, "openspec", "changes", "status-demo", "proposal.md"), "# Proposal\n");
|
|
484
|
-
await writeFile(join(promptCwd, "openspec", "changes", "status-demo", "specs", "demo", "spec.md"), "# Spec\n");
|
|
485
|
-
await writeFile(join(promptCwd, "openspec", "changes", "status-demo", "design.md"), "# Design\n");
|
|
486
|
-
await writeFile(join(promptCwd, "openspec", "changes", "status-demo", "tasks.md"), "# Tasks\n\n- [ ] 1.1 Implement demo\n");
|
|
487
|
-
const applyPromptResult = await promptHook(
|
|
488
|
-
{ agentName: "sdd-apply", systemPrompt: "apply base" },
|
|
489
|
-
createCtx(promptCwd, true, "sdd-apply-session"),
|
|
490
|
-
);
|
|
491
|
-
assert.match(applyPromptResult.systemPrompt, /## Native SDD Status Engine/);
|
|
492
|
-
assert.match(applyPromptResult.systemPrompt, /"changeName": "status-demo"/);
|
|
493
|
-
assert.match(applyPromptResult.systemPrompt, /### apply instructions/);
|
|
494
|
-
const statusCtx = createCtx(promptCwd, true);
|
|
495
|
-
await commands.get("gentle-sdd-status").handler("status-demo --json", statusCtx);
|
|
496
|
-
assert.match(statusCtx.ui.notifications.at(-1).message, /"schemaName": "gentle-ai\.sdd-status"/);
|
|
497
|
-
const continueCtx = createCtx(promptCwd, true);
|
|
498
|
-
let markerConfirmations = 0;
|
|
499
|
-
continueCtx.ui.confirm = async (_title, message) => {
|
|
500
|
-
assert.ok(message.includes(join(promptCwd, "openspec/changes/status-demo/.gentle-ai-instance")));
|
|
501
|
-
assert.match(message, /no source roots and no persistent authority/);
|
|
502
|
-
markerConfirmations += 1;
|
|
503
|
-
return true;
|
|
504
|
-
};
|
|
505
|
-
await commands.get("gentle-sdd-continue").handler("status-demo", continueCtx);
|
|
506
|
-
assert.equal(markerConfirmations, 1);
|
|
507
|
-
assert.match(continueCtx.ui.notifications.at(-1).message, /Native SDD Status Engine/);
|
|
508
|
-
assert.match(continueCtx.ui.notifications.at(-1).message, /"nextRecommended": "apply"/);
|
|
509
|
-
const { execFileSync } = await import("node:child_process");
|
|
510
|
-
execFileSync("git", ["init"], { cwd: promptCwd, stdio: "ignore" });
|
|
511
|
-
const recoveryRequiredDirectory = join(promptCwd, ".git", "gentle-ai", "reviews", "control", "recovery-required-v1");
|
|
512
|
-
await mkdir(recoveryRequiredDirectory, { recursive: true });
|
|
513
|
-
await writeFile(
|
|
514
|
-
join(recoveryRequiredDirectory, `${domainHashV1("openspec-change-name", "status-demo")}.json`),
|
|
515
|
-
'{"schema":"gentle-ai.recovery-required/v1","change_name":"status-demo"}',
|
|
516
|
-
);
|
|
517
|
-
const blockedContinueCtx = createCtx(promptCwd, true);
|
|
518
|
-
await commands.get("gentle-sdd-continue").handler("status-demo", blockedContinueCtx);
|
|
519
|
-
assert.doesNotMatch(blockedContinueCtx.ui.notifications.at(-1).message, /resolve-review:/);
|
|
520
|
-
assert.match(blockedContinueCtx.ui.notifications.at(-1).message, /"nextRecommended": "apply"/);
|
|
521
546
|
} finally {
|
|
522
547
|
await rm(promptCwd, { recursive: true, force: true });
|
|
523
548
|
}
|
|
@@ -606,42 +631,6 @@ async function run() {
|
|
|
606
631
|
"a writer may dispatch when context carries narrow task-scoped repository-relative paths",
|
|
607
632
|
);
|
|
608
633
|
|
|
609
|
-
const interactiveSddDispatch = {
|
|
610
|
-
agent: "sdd-remediate",
|
|
611
|
-
task: "Correct the bound failed verification evidence.",
|
|
612
|
-
context: "Retain the parent-supplied remediation scope.",
|
|
613
|
-
mode: "task",
|
|
614
|
-
};
|
|
615
|
-
assert.equal(
|
|
616
|
-
await toolHook({ toolName: "subagent_run", input: interactiveSddDispatch }, createCtx(toolCwd, true, "interactive-sdd-parent")),
|
|
617
|
-
undefined,
|
|
618
|
-
"an interactive parent may dispatch an SDD child only after preflight resolves",
|
|
619
|
-
);
|
|
620
|
-
assert.match(
|
|
621
|
-
interactiveSddDispatch.context,
|
|
622
|
-
/^## SDD Session Preflight\nThese SDD preferences are explicit current-session choices\./,
|
|
623
|
-
"the parent-confirmed rendered preflight must be transported through the RPC child's context",
|
|
624
|
-
);
|
|
625
|
-
const rpcChildCwd = await tempWorkspace();
|
|
626
|
-
try {
|
|
627
|
-
const rpcChild = createPi();
|
|
628
|
-
createGentleAiExtension({ processEnv: { GENTLE_PI_AGENTS_CHILD: "1" } })(rpcChild.pi);
|
|
629
|
-
const rpcChildCtx = createCtx(rpcChildCwd, false, "delegated-rpc-sdd-child");
|
|
630
|
-
rpcChildCtx.mode = "rpc";
|
|
631
|
-
const rpcChildPrompt = await rpcChild.hooks.get("before_agent_start")[0](
|
|
632
|
-
{ agentName: "sdd-remediate", systemPrompt: "You are the SDD remediate executor for Gentle AI." },
|
|
633
|
-
rpcChildCtx,
|
|
634
|
-
);
|
|
635
|
-
assert.doesNotMatch(rpcChildPrompt.systemPrompt, /## SDD Session Preflight/);
|
|
636
|
-
assert.equal(
|
|
637
|
-
existsSync(join(rpcChildCwd, ".pi", "gentle-ai", "sdd-preflight.json")),
|
|
638
|
-
false,
|
|
639
|
-
"a delegated RPC child must not silently originate or persist defaults",
|
|
640
|
-
);
|
|
641
|
-
} finally {
|
|
642
|
-
await rm(rpcChildCwd, { recursive: true, force: true });
|
|
643
|
-
}
|
|
644
|
-
|
|
645
634
|
const canonicalFrozenFindingRow = '{"id":"JD-A-001","lens":"judgment-day","location":"extensions/gentle-ai.ts:1","severity":"CRITICAL","status_at_freeze":"open","evidence_class":"deterministic","evidence_claim":"The frozen finding has concrete user impact."}';
|
|
646
635
|
const canonicalFrozenFindingRows = [JSON.parse(canonicalFrozenFindingRow)];
|
|
647
636
|
const canonicalFrozenLedgerHash = canonicalHash(canonicalFrozenFindingRows);
|
|
@@ -899,7 +888,7 @@ async function run() {
|
|
|
899
888
|
registry.bindCurrent({ token: view.token, lineageId: "harness-candidate-drift", selectedLenses: ["review-risk"] });
|
|
900
889
|
|
|
901
890
|
const dispatchPi = createPi();
|
|
902
|
-
createGentleAiExtension({ candidateViews: registry })(dispatchPi.pi);
|
|
891
|
+
createGentleAiExtension({ ...fixtureDependencies, candidateViews: registry })(dispatchPi.pi);
|
|
903
892
|
const dispatchToolHook = dispatchPi.hooks.get("tool_call")[0];
|
|
904
893
|
|
|
905
894
|
// The contributor edits the tracked file strictly after the candidate
|
|
@@ -1030,24 +1019,9 @@ async function run() {
|
|
|
1030
1019
|
for (const handler of hooks.get("session_start")) {
|
|
1031
1020
|
await handler({ reason: "startup" }, createCtx(noUiCwd, false));
|
|
1032
1021
|
}
|
|
1033
|
-
assert.equal(
|
|
1034
|
-
existsSync(join(noUiCwd, ".pi", "agents", "sdd-apply.md")),
|
|
1035
|
-
false,
|
|
1036
|
-
"session_start must not install project-local SDD agents",
|
|
1037
|
-
);
|
|
1038
|
-
assert.equal(
|
|
1039
|
-
existsSync(join(noUiCwd, ".pi", "chains", "sdd-full.chain.md")),
|
|
1040
|
-
false,
|
|
1041
|
-
"session_start must not install project-local SDD chains",
|
|
1042
|
-
);
|
|
1043
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), false);
|
|
1044
|
-
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), false);
|
|
1045
|
-
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support")), false);
|
|
1046
1022
|
for (const diagnostic of ["gentle:status", "gentle:doctor"]) {
|
|
1047
1023
|
const ctx = createCtx(noUiCwd, true);
|
|
1048
1024
|
await commands.get(diagnostic).handler("", ctx);
|
|
1049
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Global SDD assets: on demand/);
|
|
1050
|
-
assert.doesNotMatch(ctx.ui.notifications.at(-1).message, /install-sdd --force/);
|
|
1051
1025
|
}
|
|
1052
1026
|
// gentle-pi#311 P5: the Pi-authored adversarial role agents are retired;
|
|
1053
1027
|
// installation must not (re)create them.
|
|
@@ -1062,8 +1036,8 @@ async function run() {
|
|
|
1062
1036
|
assert.equal(existsSync(installedExplorePath), true);
|
|
1063
1037
|
assert.deepEqual(
|
|
1064
1038
|
readAgentDefinition(await readFile(installedExplorePath, "utf8")),
|
|
1065
|
-
{ name: "gentle-ai-explore", tools: ["read", "grep", "find", "codegraph"] },
|
|
1066
|
-
"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",
|
|
1067
1041
|
);
|
|
1068
1042
|
const installedRiskSource = await readFile(
|
|
1069
1043
|
join(globalAgentHome, "agents", "review-risk.md"),
|
|
@@ -1071,19 +1045,10 @@ async function run() {
|
|
|
1071
1045
|
);
|
|
1072
1046
|
assert.match(installedRiskSource, /exactly once against the supplied `initial_review_tree`/);
|
|
1073
1047
|
assert.match(installedRiskSource, /cannot authorize transitions, fixes, receipts, gates, or delivery/);
|
|
1074
|
-
await commands.get("gentle:sdd-preflight").handler("", createCtx(noUiCwd, false, "startup-sdd-install"));
|
|
1075
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1076
|
-
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1077
|
-
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1078
1048
|
const managedAssetsManifestPath = join(globalAgentHome, "gentle-ai", "managed-assets.json");
|
|
1079
1049
|
const managedAssetsManifest = JSON.parse(
|
|
1080
1050
|
await readFile(managedAssetsManifestPath, "utf8"),
|
|
1081
1051
|
);
|
|
1082
|
-
const previousManagedApply = "stale global apply\n";
|
|
1083
|
-
const previousManagedChain = "stale global chain\n";
|
|
1084
|
-
const previousManagedSupport = "stale global status contract\n";
|
|
1085
|
-
await writeFile(join(globalAgentHome, "agents", "sdd-apply.md"), previousManagedApply);
|
|
1086
|
-
managedAssetsManifest.assets["agents/sdd-apply.md"] = sha256(previousManagedApply);
|
|
1087
1052
|
const samePathUserRefuter = [
|
|
1088
1053
|
"---",
|
|
1089
1054
|
"name: review-refuter",
|
|
@@ -1106,60 +1071,25 @@ async function run() {
|
|
|
1106
1071
|
await mkdir(join(globalAgentHome, "subagents"), { recursive: true });
|
|
1107
1072
|
const userRefuterOverride = join(globalAgentHome, "subagents", "review-refuter.md");
|
|
1108
1073
|
await writeFile(userRefuterOverride, "user refuter override must stay\n");
|
|
1109
|
-
await writeFile(join(globalAgentHome, "chains", "sdd-full.chain.md"), previousManagedChain);
|
|
1110
|
-
managedAssetsManifest.assets["chains/sdd-full.chain.md"] = sha256(previousManagedChain);
|
|
1111
|
-
await writeFile(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md"), previousManagedSupport);
|
|
1112
|
-
managedAssetsManifest.assets["gentle-ai/support/sdd-status-contract.md"] = sha256(previousManagedSupport);
|
|
1113
1074
|
await writeFile(
|
|
1114
1075
|
managedAssetsManifestPath,
|
|
1115
1076
|
JSON.stringify(managedAssetsManifest, null, 2),
|
|
1116
1077
|
);
|
|
1117
|
-
await mkdir(join(noUiCwd, ".pi", "agents"), { recursive: true });
|
|
1118
|
-
await writeFile(join(noUiCwd, ".pi", "agents", "sdd-apply.md"), "project override must stay\n");
|
|
1119
1078
|
const projectRefuterOverride = join(noUiCwd, ".pi", "agents", "review-refuter.md");
|
|
1079
|
+
await mkdir(dirname(projectRefuterOverride), { recursive: true });
|
|
1120
1080
|
await writeFile(projectRefuterOverride, "project refuter override must stay\n");
|
|
1121
1081
|
for (const handler of hooks.get("session_start")) {
|
|
1122
1082
|
await handler({ reason: "startup" }, createCtx(noUiCwd, false));
|
|
1123
1083
|
}
|
|
1124
|
-
for (const [key, stale] of [
|
|
1125
|
-
["agents/sdd-apply.md", previousManagedApply],
|
|
1126
|
-
["chains/sdd-full.chain.md", previousManagedChain],
|
|
1127
|
-
["gentle-ai/support/sdd-status-contract.md", previousManagedSupport],
|
|
1128
|
-
]) {
|
|
1129
|
-
assert.equal(await readFile(join(globalAgentHome, key), "utf8"), stale,
|
|
1130
|
-
"startup must preserve existing SDD package content without explicit routing");
|
|
1131
|
-
}
|
|
1132
1084
|
assert.equal(existsSync(retiredManagedValidatorPath), false, "review retirement still runs at startup");
|
|
1133
1085
|
const driftCtx = createCtx(noUiCwd, true);
|
|
1134
1086
|
await commands.get("gentle:status").handler("", driftCtx);
|
|
1135
|
-
assert.match(driftCtx.ui.notifications.at(-1).message, /Global SDD assets stale: 3 file\(s\).*install-sdd --force/);
|
|
1136
1087
|
assert.doesNotMatch(driftCtx.ui.notifications.at(-1).message, /install-(delegation|review) --force/);
|
|
1137
|
-
await commands.get("gentle:sdd-preflight").handler("", createCtx(noUiCwd, false, "startup-sdd-refresh"));
|
|
1138
|
-
assert.notEqual(
|
|
1139
|
-
await readFile(join(globalAgentHome, "agents", "sdd-apply.md"), "utf8"),
|
|
1140
|
-
"stale global apply\n",
|
|
1141
|
-
"SDD preflight must refresh stale global SDD agents",
|
|
1142
|
-
);
|
|
1143
1088
|
assert.equal(
|
|
1144
1089
|
await readFile(installedRefuterPath, "utf8"),
|
|
1145
1090
|
samePathUserRefuter,
|
|
1146
1091
|
"session refresh must preserve a same-path user-authored agent byte-for-byte",
|
|
1147
1092
|
);
|
|
1148
|
-
assert.notEqual(
|
|
1149
|
-
await readFile(join(globalAgentHome, "chains", "sdd-full.chain.md"), "utf8"),
|
|
1150
|
-
"stale global chain\n",
|
|
1151
|
-
"SDD preflight must refresh stale global SDD chains",
|
|
1152
|
-
);
|
|
1153
|
-
assert.notEqual(
|
|
1154
|
-
await readFile(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md"), "utf8"),
|
|
1155
|
-
"stale global status contract\n",
|
|
1156
|
-
"SDD preflight must refresh stale global SDD support files",
|
|
1157
|
-
);
|
|
1158
|
-
assert.equal(
|
|
1159
|
-
await readFile(join(noUiCwd, ".pi", "agents", "sdd-apply.md"), "utf8"),
|
|
1160
|
-
"project override must stay\n",
|
|
1161
|
-
"session_start must not overwrite project-local SDD overrides",
|
|
1162
|
-
);
|
|
1163
1093
|
assert.equal(
|
|
1164
1094
|
await readFile(projectRefuterOverride, "utf8"),
|
|
1165
1095
|
"project refuter override must stay\n",
|
|
@@ -1193,423 +1123,10 @@ async function run() {
|
|
|
1193
1123
|
await rm(noUiCwd, { recursive: true, force: true });
|
|
1194
1124
|
}
|
|
1195
1125
|
|
|
1196
|
-
const
|
|
1197
|
-
const lazyAgentHome = join(lazySddCwd, "agent-home");
|
|
1198
|
-
process.env.GENTLE_PI_AGENT_HOME = lazyAgentHome;
|
|
1199
|
-
try {
|
|
1200
|
-
// This scenario must not inherit SDD definitions from earlier startup tests.
|
|
1201
|
-
const globalAgentHome = lazyAgentHome;
|
|
1202
|
-
await writeFile(
|
|
1203
|
-
globalModelsPath,
|
|
1204
|
-
JSON.stringify({ "sdd-apply": { model: "openai/gpt-5", thinking: "high" } }, null, 2),
|
|
1205
|
-
);
|
|
1206
|
-
const ctx = createCtx(lazySddCwd, true);
|
|
1207
|
-
const inputHook = hooks.get("input")[0];
|
|
1208
|
-
assert.deepEqual(
|
|
1209
|
-
await inputHook({ text: "hola, solo mirando", source: "interactive" }, ctx),
|
|
1210
|
-
{ action: "continue" },
|
|
1211
|
-
);
|
|
1212
|
-
assert.deepEqual(
|
|
1213
|
-
await inputHook({ text: "what is SDD?", source: "interactive" }, ctx),
|
|
1214
|
-
{ action: "continue" },
|
|
1215
|
-
);
|
|
1216
|
-
assert.deepEqual(
|
|
1217
|
-
await inputHook({ text: "what can I do with SDD?", source: "interactive" }, ctx),
|
|
1218
|
-
{ action: "continue" },
|
|
1219
|
-
);
|
|
1220
|
-
assert.deepEqual(
|
|
1221
|
-
await inputHook({ text: "how do I use SDD?", source: "interactive" }, ctx),
|
|
1222
|
-
{ action: "continue" },
|
|
1223
|
-
);
|
|
1224
|
-
assert.deepEqual(
|
|
1225
|
-
await inputHook({ text: "Can I use SDD?", source: "interactive" }, ctx),
|
|
1226
|
-
{ action: "continue" },
|
|
1227
|
-
);
|
|
1228
|
-
assert.deepEqual(
|
|
1229
|
-
await inputHook({ text: "don't use sdd for this", source: "interactive" }, ctx),
|
|
1230
|
-
{ action: "continue" },
|
|
1231
|
-
);
|
|
1232
|
-
assert.deepEqual(
|
|
1233
|
-
await inputHook({ text: "sin usar SDD por ahora", source: "interactive" }, ctx),
|
|
1234
|
-
{ action: "continue" },
|
|
1235
|
-
);
|
|
1236
|
-
assert.deepEqual(
|
|
1237
|
-
await inputHook({ text: "let's not use SDD for this", source: "interactive" }, ctx),
|
|
1238
|
-
{ action: "continue" },
|
|
1239
|
-
);
|
|
1240
|
-
assert.deepEqual(
|
|
1241
|
-
await inputHook({ text: "never use SDD here", source: "interactive" }, ctx),
|
|
1242
|
-
{ action: "continue" },
|
|
1243
|
-
);
|
|
1244
|
-
assert.deepEqual(
|
|
1245
|
-
await inputHook({ text: "no quiero usar SDD por ahora", source: "interactive" }, ctx),
|
|
1246
|
-
{ action: "continue" },
|
|
1247
|
-
);
|
|
1248
|
-
assert.deepEqual(
|
|
1249
|
-
await inputHook({ text: "I use SDD sometimes", source: "interactive" }, ctx),
|
|
1250
|
-
{ action: "continue" },
|
|
1251
|
-
);
|
|
1252
|
-
assert.deepEqual(
|
|
1253
|
-
await inputHook({ text: "I'm using SDD in another repo", source: "interactive" }, ctx),
|
|
1254
|
-
{ action: "continue" },
|
|
1255
|
-
);
|
|
1256
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1257
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), false);
|
|
1258
|
-
|
|
1259
|
-
assert.deepEqual(
|
|
1260
|
-
await inputHook({ text: "vamos con sdd", source: "interactive" }, ctx),
|
|
1261
|
-
{ action: "continue" },
|
|
1262
|
-
);
|
|
1263
|
-
assert.deepEqual(
|
|
1264
|
-
await inputHook({ text: "please use sdd for this change", source: "interactive" }, ctx),
|
|
1265
|
-
{ action: "continue" },
|
|
1266
|
-
);
|
|
1267
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1268
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1269
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), false);
|
|
1270
|
-
assert.equal(ctx.ui.selections.length, 0, "natural-language SDD text has no input-hook side effect");
|
|
1271
|
-
|
|
1272
|
-
assert.deepEqual(
|
|
1273
|
-
await inputHook({ text: "/sdd", source: "interactive" }, ctx),
|
|
1274
|
-
{ action: "continue" },
|
|
1275
|
-
);
|
|
1276
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1277
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-status.md")), true);
|
|
1278
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), false);
|
|
1279
|
-
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1280
|
-
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1281
|
-
assert.equal(ctx.ui.selections.length, 1, "first slash SDD trigger confirms session suggestions");
|
|
1282
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: explicit session choice/);
|
|
1283
|
-
assert.deepEqual(
|
|
1284
|
-
await inputHook({ text: "/sdd plan", source: "interactive" }, ctx),
|
|
1285
|
-
{ action: "continue" },
|
|
1286
|
-
);
|
|
1287
|
-
assert.deepEqual(
|
|
1288
|
-
await inputHook({ text: "/sdd:plan", source: "interactive" }, ctx),
|
|
1289
|
-
{ action: "continue" },
|
|
1290
|
-
);
|
|
1291
|
-
assert.equal(ctx.ui.selections.length, 1, "slash SDD triggers reuse confirmed session choices");
|
|
1292
|
-
|
|
1293
|
-
assert.deepEqual(
|
|
1294
|
-
await inputHook({ text: "/sdd-plan this change", source: "interactive" }, ctx),
|
|
1295
|
-
{ action: "continue" },
|
|
1296
|
-
);
|
|
1297
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1298
|
-
assert.equal(existsSync(join(lazySddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1299
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1300
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-status.md")), true);
|
|
1301
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), false);
|
|
1302
|
-
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1303
|
-
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1304
|
-
const globalSddApply = await readFile(
|
|
1305
|
-
join(globalAgentHome, "agents", "sdd-apply.md"),
|
|
1306
|
-
"utf8",
|
|
1307
|
-
);
|
|
1308
|
-
assert.match(globalSddApply, /model: openai\/gpt-5/);
|
|
1309
|
-
assert.match(globalSddApply, /thinking: high/);
|
|
1310
|
-
const lazySettingsPath = join(lazySddCwd, ".pi", "settings.json");
|
|
1311
|
-
if (existsSync(lazySettingsPath)) {
|
|
1312
|
-
const lazySettings = JSON.parse(await readFile(lazySettingsPath, "utf8"));
|
|
1313
|
-
assert.equal(
|
|
1314
|
-
lazySettings.subagents?.agentOverrides?.["sdd-apply"],
|
|
1315
|
-
undefined,
|
|
1316
|
-
"global SDD model routing must be materialized in agent frontmatter, not project settings overrides",
|
|
1317
|
-
);
|
|
1318
|
-
}
|
|
1319
|
-
assert.equal(ctx.ui.selections.length, 1, "automatic SDD routing reuses session confirmation");
|
|
1320
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: explicit session choice/);
|
|
1321
|
-
await commands.get("gentle:status").handler("", ctx);
|
|
1322
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Global SDD assets stale: 0 file\(s\)/);
|
|
1323
|
-
assert.doesNotMatch(ctx.ui.notifications.at(-1).message, /install-sdd --force/);
|
|
1324
|
-
|
|
1325
|
-
await inputHook({ text: "/sdd-plan another change", source: "interactive" }, ctx);
|
|
1326
|
-
assert.equal(ctx.ui.selections.length, 1, "resolved preflight must not prompt again in this session");
|
|
1327
|
-
const promptHook = hooks.get("before_agent_start")[0];
|
|
1328
|
-
const promptResult = await promptHook({ systemPrompt: "base" }, ctx);
|
|
1329
|
-
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
1330
|
-
assert.match(promptResult.systemPrompt, /Execution mode: auto/);
|
|
1331
|
-
const workerPromptResult = await promptHook(
|
|
1332
|
-
{ agentName: "worker", systemPrompt: "worker base" },
|
|
1333
|
-
ctx,
|
|
1334
|
-
);
|
|
1335
|
-
assert.equal(
|
|
1336
|
-
workerPromptResult.systemPrompt,
|
|
1337
|
-
"worker base",
|
|
1338
|
-
"non-SDD subagents must not receive parent harness or SDD preflight prompts",
|
|
1339
|
-
);
|
|
1340
|
-
} finally {
|
|
1341
|
-
process.env.GENTLE_PI_AGENT_HOME = globalAgentHome;
|
|
1342
|
-
await rm(lazySddCwd, { recursive: true, force: true });
|
|
1343
|
-
await rm(globalModelsPath, { force: true });
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
for (const [index, text] of ["/sdd", "/sdd plan", "/sdd:plan", "/sdd-plan this change", "/gentle-sdd-continue", "/gentle-sdd-status fix-rose --json", "/gentle-sdd-init"].entries()) {
|
|
1347
|
-
const slashSddCwd = await tempWorkspace();
|
|
1348
|
-
try {
|
|
1349
|
-
const ctx = createCtx(slashSddCwd, true, `slash-sdd-session-${index}`);
|
|
1350
|
-
const inputHook = hooks.get("input")[0];
|
|
1351
|
-
assert.deepEqual(await inputHook({ text, source: "interactive" }, ctx), {
|
|
1352
|
-
action: "continue",
|
|
1353
|
-
});
|
|
1354
|
-
assert.equal(existsSync(join(slashSddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1355
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1356
|
-
assert.equal(ctx.ui.selections.length, 1, `${text} must confirm the fresh interactive session`);
|
|
1357
|
-
} finally {
|
|
1358
|
-
await rm(slashSddCwd, { recursive: true, force: true });
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
const commandPreflightCwd = await tempWorkspace();
|
|
1363
|
-
try {
|
|
1364
|
-
const command = commands.get("gentle:sdd-preflight");
|
|
1365
|
-
const interactive = createCtx(commandPreflightCwd, true, "explicit-command-regression");
|
|
1366
|
-
await command.handler("", interactive);
|
|
1367
|
-
const first = interactive.ui.selections.length;
|
|
1368
|
-
await command.handler("", interactive);
|
|
1369
|
-
const reused = interactive.ui.selections.length;
|
|
1370
|
-
const select = interactive.ui.select;
|
|
1371
|
-
interactive.ui.select = async (label, options) => {
|
|
1372
|
-
const value = await select(label, options);
|
|
1373
|
-
return label === "SDD execution mode" ? "interactive" : value;
|
|
1374
|
-
};
|
|
1375
|
-
interactive.ui.input = async () => "650";
|
|
1376
|
-
await command.handler("--edit", interactive);
|
|
1377
|
-
const edited = interactive.ui.selections.length;
|
|
1378
|
-
await command.handler("", interactive);
|
|
1379
|
-
assert.equal(interactive.ui.selections.length, edited, "edited choices also reuse without prompting");
|
|
1380
|
-
const saved = JSON.parse(await readFile(join(commandPreflightCwd, ".pi", "gentle-ai", "sdd-preflight.json"), "utf8"));
|
|
1381
|
-
assert.equal(saved.executionMode, "interactive");
|
|
1382
|
-
assert.equal(saved.reviewBudgetLines, 650);
|
|
1383
|
-
const rpc = createCtx(commandPreflightCwd, true, "explicit-command-rpc-regression");
|
|
1384
|
-
rpc.mode = "rpc";
|
|
1385
|
-
let rpcInputs = 0;
|
|
1386
|
-
rpc.ui.input = async (_label, placeholder) => { rpcInputs += 1; return placeholder; };
|
|
1387
|
-
await command.handler("", rpc);
|
|
1388
|
-
await command.handler("--edit", rpc);
|
|
1389
|
-
const noUi = createCtx(commandPreflightCwd, false, "explicit-command-no-ui-regression");
|
|
1390
|
-
let noUiInputs = 0;
|
|
1391
|
-
noUi.ui.input = async (_label, placeholder) => { noUiInputs += 1; return placeholder; };
|
|
1392
|
-
await command.handler("", noUi);
|
|
1393
|
-
await command.handler("--edit", noUi);
|
|
1394
|
-
assert.deepEqual({ first, reused, edited, rpc: rpc.ui.selections.length, noUi: noUi.ui.selections.length },
|
|
1395
|
-
{ first: 1, reused: 1, edited: 3, rpc: 0, noUi: 0 },
|
|
1396
|
-
"explicit command confirms once, reuses unless --edit, and never prompts headless callers");
|
|
1397
|
-
assert.deepEqual({ rpcInputs, noUiInputs }, { rpcInputs: 0, noUiInputs: 0 });
|
|
1398
|
-
} finally {
|
|
1399
|
-
await rm(commandPreflightCwd, { recursive: true, force: true });
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
const commandSddCwd = await tempWorkspace();
|
|
1403
|
-
try {
|
|
1404
|
-
const ctx = createCtx(commandSddCwd, true, "command-session");
|
|
1405
|
-
await commands.get("gentle:sdd-preflight").handler("--edit", ctx);
|
|
1406
|
-
assert.equal(existsSync(join(commandSddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1407
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1408
|
-
assert.equal(ctx.ui.selections.length, 2, "explicit preflight prompts intentional choice fields");
|
|
1409
|
-
assert.equal(ctx.ui.selections.some(({ label }) => label === "SDD artifact store"), false, "one-option artifact store must be elided");
|
|
1410
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: explicit session choice/);
|
|
1411
|
-
await commands.get("gentle:sdd-preflight").handler("--edit", ctx);
|
|
1412
|
-
assert.equal(ctx.ui.selections.length, 4, "--edit remains an intentional re-prompt");
|
|
1413
|
-
} finally {
|
|
1414
|
-
await rm(commandSddCwd, { recursive: true, force: true });
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
const sddAgentGuardCwd = await tempWorkspace();
|
|
1418
|
-
try {
|
|
1419
|
-
const ctx = createCtx(sddAgentGuardCwd, true, "sdd-agent-guard-session");
|
|
1420
|
-
const promptHook = hooks.get("before_agent_start")[0];
|
|
1421
|
-
const promptResult = await promptHook(
|
|
1422
|
-
{
|
|
1423
|
-
systemPrompt: "You are the SDD proposal executor for Gentle AI.",
|
|
1424
|
-
},
|
|
1425
|
-
ctx,
|
|
1426
|
-
);
|
|
1427
|
-
assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1428
|
-
assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1429
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1430
|
-
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1431
|
-
assert.equal(ctx.ui.selections.length, 1, "fresh interactive SDD-agent startup requires confirmation");
|
|
1432
|
-
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
1433
|
-
assert.doesNotMatch(
|
|
1434
|
-
promptResult.systemPrompt,
|
|
1435
|
-
/el Gentleman Identity and Harness/,
|
|
1436
|
-
"SDD executor startup must not receive the parent orchestrator prompt",
|
|
1437
|
-
);
|
|
1438
|
-
assert.doesNotMatch(
|
|
1439
|
-
promptResult.systemPrompt,
|
|
1440
|
-
/Work Routing Ladder/,
|
|
1441
|
-
"SDD executor startup must not receive parent routing instructions",
|
|
1442
|
-
);
|
|
1443
|
-
assert.match(ctx.ui.notifications.at(-1).message, /SDD preflight complete/);
|
|
1444
|
-
|
|
1445
|
-
const reusedPromptResult = await promptHook(
|
|
1446
|
-
{
|
|
1447
|
-
agentName: "sdd-tasks",
|
|
1448
|
-
systemPrompt: "You are the SDD tasks executor for Gentle AI.",
|
|
1449
|
-
},
|
|
1450
|
-
ctx,
|
|
1451
|
-
);
|
|
1452
|
-
assert.equal(ctx.ui.selections.length, 1, "SDD-agent startup reuses confirmed session preferences");
|
|
1453
|
-
assert.doesNotMatch(
|
|
1454
|
-
reusedPromptResult.systemPrompt,
|
|
1455
|
-
/el Gentleman Identity and Harness/,
|
|
1456
|
-
"named SDD executor startup must not receive the parent orchestrator prompt",
|
|
1457
|
-
);
|
|
1458
|
-
} finally {
|
|
1459
|
-
await rm(sddAgentGuardCwd, { recursive: true, force: true });
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
const unresolvedSddCwd = await tempWorkspace();
|
|
1463
|
-
try {
|
|
1464
|
-
const ctx = createCtx(unresolvedSddCwd, true, "unresolved-preflight-session");
|
|
1465
|
-
ctx.ui.select = async () => undefined;
|
|
1466
|
-
assert.deepEqual(await hooks.get("input")[0]({ text: "/sdd", source: "interactive" }, ctx), { action: "handled" });
|
|
1467
|
-
const result = await hooks.get("before_agent_start")[0]({ agentName: "sdd-explore", systemPrompt: "SDD executor" }, ctx);
|
|
1468
|
-
assert.match(result.systemPrompt, /SDD preflight unresolved/);
|
|
1469
|
-
assert.match(result.systemPrompt, /STOP: Do not initialize/);
|
|
1470
|
-
assert.doesNotMatch(result.systemPrompt, /## SDD Session Preflight/);
|
|
1471
|
-
assert.equal(existsSync(join(unresolvedSddCwd, "openspec", "config.yaml")), false);
|
|
1472
|
-
assert.equal(existsSync(join(unresolvedSddCwd, ".pi", "gentle-ai", "sdd-preflight.json")), false);
|
|
1473
|
-
} finally {
|
|
1474
|
-
await rm(unresolvedSddCwd, { recursive: true, force: true });
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
const noUiSddAgentCwd = await tempWorkspace();
|
|
1478
|
-
try {
|
|
1479
|
-
const ctx = createCtx(noUiSddAgentCwd, false, "no-ui-sdd-agent-session");
|
|
1480
|
-
const promptHook = hooks.get("before_agent_start")[0];
|
|
1481
|
-
const promptResult = await promptHook(
|
|
1482
|
-
{
|
|
1483
|
-
agentName: "sdd-proposal",
|
|
1484
|
-
systemPrompt: "You are the SDD proposal executor for Gentle AI.",
|
|
1485
|
-
},
|
|
1486
|
-
ctx,
|
|
1487
|
-
);
|
|
1488
|
-
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
1489
|
-
assert.match(promptResult.systemPrompt, /canonical defaults or persisted choices/);
|
|
1490
|
-
assert.equal(ctx.ui.selections.length, 0);
|
|
1491
|
-
assert.equal(existsSync(join(noUiSddAgentCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1492
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1493
|
-
} finally {
|
|
1494
|
-
await rm(noUiSddAgentCwd, { recursive: true, force: true });
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
const invalidPreflightCwd = await tempWorkspace();
|
|
1498
|
-
try {
|
|
1499
|
-
await writeFile(globalModelsPath, "{ invalid json");
|
|
1500
|
-
const ctx = createCtx(invalidPreflightCwd, true, "invalid-preflight-session");
|
|
1501
|
-
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1502
|
-
assert.equal(ctx.ui.notifications.at(-1).level, "warning");
|
|
1503
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Model routing skipped:/);
|
|
1504
|
-
assert.match(ctx.ui.notifications.at(-1).message, /invalid JSON or not an object/);
|
|
1505
|
-
} finally {
|
|
1506
|
-
await rm(invalidPreflightCwd, { recursive: true, force: true });
|
|
1507
|
-
await rm(globalModelsPath, { force: true });
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
const engramSddCwd = await tempWorkspace();
|
|
1511
|
-
try {
|
|
1512
|
-
pi.setActiveTools(["read", "bash", "edit", "write", "mem_save"]);
|
|
1513
|
-
const ctx = createCtx(engramSddCwd, true, "engram-session");
|
|
1514
|
-
await commands.get("gentle:sdd-preflight").handler("--edit", ctx);
|
|
1515
|
-
assert.deepEqual(ctx.ui.selections[1].options, ["openspec", "engram", "hybrid"]);
|
|
1516
|
-
} finally {
|
|
1517
|
-
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1518
|
-
await rm(engramSddCwd, { recursive: true, force: true });
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
// Issue #64: selecting engram as the artifact store must not cause
|
|
1522
|
-
// /gentle-sdd-init to create openspec/ or openspec/config.yaml.
|
|
1523
|
-
const engramSddInitCwd = await tempWorkspace();
|
|
1524
|
-
try {
|
|
1525
|
-
pi.setActiveTools(["read", "bash", "edit", "write", "mem_save"]);
|
|
1526
|
-
const ctx = createCtx(engramSddInitCwd, true, "engram-sdd-init-session");
|
|
1527
|
-
ctx.ui.select = async (label, options) => {
|
|
1528
|
-
if (label === "SDD artifact store") return "engram";
|
|
1529
|
-
return options[0];
|
|
1530
|
-
};
|
|
1531
|
-
await commands.get("gentle:sdd-preflight").handler("--edit", ctx);
|
|
1532
|
-
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1533
|
-
assert.equal(
|
|
1534
|
-
existsSync(join(engramSddInitCwd, "openspec")),
|
|
1535
|
-
false,
|
|
1536
|
-
"/gentle-sdd-init must not create openspec/ when artifactStore is engram",
|
|
1537
|
-
);
|
|
1538
|
-
assert.equal(
|
|
1539
|
-
existsSync(join(engramSddInitCwd, "openspec", "config.yaml")),
|
|
1540
|
-
false,
|
|
1541
|
-
"/gentle-sdd-init must not write openspec/config.yaml when artifactStore is engram",
|
|
1542
|
-
);
|
|
1543
|
-
assert.doesNotMatch(
|
|
1544
|
-
ctx.ui.notifications.at(-1).message,
|
|
1545
|
-
/Wrote openspec\/config\.yaml/,
|
|
1546
|
-
"/gentle-sdd-init must not announce openspec/config.yaml when artifactStore is engram",
|
|
1547
|
-
);
|
|
1548
|
-
assert.match(
|
|
1549
|
-
ctx.ui.notifications.at(-1).message,
|
|
1550
|
-
/SDD initialized for engram:/,
|
|
1551
|
-
);
|
|
1552
|
-
assert.equal(ctx.ui.notifications.at(-1).level, "info");
|
|
1553
|
-
} finally {
|
|
1554
|
-
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1555
|
-
await rm(engramSddInitCwd, { recursive: true, force: true });
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
// Issue #64 counterpart: the engram skip must stay narrow. Selecting both
|
|
1559
|
-
// still has to create the full openspec/ scaffold and write config.yaml,
|
|
1560
|
-
// so an over-broad skip is caught here instead of in the field.
|
|
1561
|
-
const bothSddInitCwd = await tempWorkspace();
|
|
1562
|
-
try {
|
|
1563
|
-
pi.setActiveTools(["read", "bash", "edit", "write", "mem_save"]);
|
|
1564
|
-
const ctx = createCtx(bothSddInitCwd, true, "both-sdd-init-session");
|
|
1565
|
-
ctx.ui.select = async (label, options) => {
|
|
1566
|
-
if (label === "SDD artifact store") return "hybrid";
|
|
1567
|
-
return options[0];
|
|
1568
|
-
};
|
|
1569
|
-
await commands.get("gentle:sdd-preflight").handler("--edit", ctx);
|
|
1570
|
-
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1571
|
-
assert.equal(
|
|
1572
|
-
existsSync(join(bothSddInitCwd, "openspec", "specs")),
|
|
1573
|
-
true,
|
|
1574
|
-
"/gentle-sdd-init must create openspec/specs when artifactStore is both",
|
|
1575
|
-
);
|
|
1576
|
-
assert.equal(
|
|
1577
|
-
existsSync(join(bothSddInitCwd, "openspec", "changes", "archive")),
|
|
1578
|
-
true,
|
|
1579
|
-
"/gentle-sdd-init must create openspec/changes/archive when artifactStore is both",
|
|
1580
|
-
);
|
|
1581
|
-
assert.equal(
|
|
1582
|
-
existsSync(join(bothSddInitCwd, "openspec", "config.yaml")),
|
|
1583
|
-
true,
|
|
1584
|
-
"/gentle-sdd-init must write openspec/config.yaml when artifactStore is both",
|
|
1585
|
-
);
|
|
1586
|
-
assert.match(
|
|
1587
|
-
ctx.ui.notifications.at(-1).message,
|
|
1588
|
-
/Wrote openspec\/config\.yaml/,
|
|
1589
|
-
"/gentle-sdd-init must announce openspec/config.yaml when artifactStore is both",
|
|
1590
|
-
);
|
|
1591
|
-
assert.equal(ctx.ui.notifications.at(-1).level, "info");
|
|
1592
|
-
} finally {
|
|
1593
|
-
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1594
|
-
await rm(bothSddInitCwd, { recursive: true, force: true });
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
const directEngramToolCwd = await tempWorkspace();
|
|
1598
|
-
try {
|
|
1599
|
-
pi.setActiveTools(["read", "bash", "edit", "write", "engram_mem_save"]);
|
|
1600
|
-
const ctx = createCtx(directEngramToolCwd, true, "direct-engram-session");
|
|
1601
|
-
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1602
|
-
assert.equal(ctx.ui.selections.some(({ label }) => label === "SDD artifact store"), false, "unrecognized Engram capability must elide the artifact selector");
|
|
1603
|
-
} finally {
|
|
1604
|
-
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1605
|
-
await rm(directEngramToolCwd, { recursive: true, force: true });
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
for (const owner of ["sdd", "delegation", "review"]) {
|
|
1126
|
+
for (const owner of ["delegation", "review"]) {
|
|
1609
1127
|
const fixture = await tempWorkspace();
|
|
1610
1128
|
const agentHome = join(fixture, "agent-home");
|
|
1611
1129
|
const representatives = {
|
|
1612
|
-
sdd: "sdd-apply.md",
|
|
1613
1130
|
delegation: "gentle-ai-worker.md",
|
|
1614
1131
|
review: "review-risk.md",
|
|
1615
1132
|
};
|
|
@@ -1658,7 +1175,7 @@ async function run() {
|
|
|
1658
1175
|
assert.equal(existsSync(join(agentHome, "subagents.json")), false,
|
|
1659
1176
|
"owner installers must not apply model routing");
|
|
1660
1177
|
await commands.get("gentle:status").handler("", ctx);
|
|
1661
|
-
const label = owner
|
|
1178
|
+
const label = owner;
|
|
1662
1179
|
assert.ok(ctx.ui.notifications.at(-1).message.includes(`Global ${label} user overrides: 1 file(s)`));
|
|
1663
1180
|
} finally {
|
|
1664
1181
|
process.env.GENTLE_PI_CONFIG_HOME = globalConfigHome;
|
|
@@ -1671,153 +1188,60 @@ async function run() {
|
|
|
1671
1188
|
try {
|
|
1672
1189
|
process.env.GENTLE_PI_AGENT_HOME = join(repairFixture, "agent-home");
|
|
1673
1190
|
const ctx = createCtx(repairFixture, true);
|
|
1674
|
-
await commands.get("gentle:install-sdd").handler("", ctx);
|
|
1675
1191
|
for (const diagnostic of ["gentle:status", "gentle:doctor"]) {
|
|
1676
1192
|
await commands.get(diagnostic).handler("", ctx);
|
|
1677
1193
|
const message = ctx.ui.notifications.at(-1).message;
|
|
1678
|
-
assert.match(message,
|
|
1194
|
+
assert.match(message, /Global delegation assets stale: [1-9]\d* file\(s\).*\/gentle:install-delegation --force/,
|
|
1679
1195
|
`${diagnostic} must provide a repair for missing delegation assets`);
|
|
1680
|
-
assert.match(message,
|
|
1196
|
+
assert.match(message, /Global review assets stale: [1-9]\d* file\(s\).*\/gentle:install-review --force/,
|
|
1681
1197
|
`${diagnostic} must provide a repair for missing review assets`);
|
|
1682
1198
|
assert.doesNotMatch(message, /install-sdd --force/);
|
|
1683
1199
|
}
|
|
1200
|
+
await commands.get("gentle:install-delegation").handler("", ctx);
|
|
1201
|
+
await commands.get("gentle:install-review").handler("", ctx);
|
|
1684
1202
|
const manifest = JSON.parse(await readFile(join(process.env.GENTLE_PI_AGENT_HOME, "gentle-ai", "managed-assets.json"), "utf8"));
|
|
1685
1203
|
for (const key of Object.keys(manifest.assets)) {
|
|
1686
1204
|
await rm(join(process.env.GENTLE_PI_AGENT_HOME, key));
|
|
1687
1205
|
}
|
|
1688
1206
|
for (const diagnostic of ["gentle:status", "gentle:doctor"]) {
|
|
1689
1207
|
await commands.get(diagnostic).handler("", ctx);
|
|
1690
|
-
|
|
1691
|
-
assert.
|
|
1692
|
-
|
|
1208
|
+
const message = ctx.ui.notifications.at(-1).message;
|
|
1209
|
+
assert.match(message, /Global delegation assets stale: [1-9]\d* file\(s\).*\/gentle:install-delegation --force/);
|
|
1210
|
+
assert.match(message, /Global review assets stale: [1-9]\d* file\(s\).*\/gentle:install-review --force/);
|
|
1211
|
+
assert.doesNotMatch(message, /install-sdd --force|on demand/,
|
|
1212
|
+
"managed installation evidence must survive missing delegation and review files");
|
|
1693
1213
|
}
|
|
1694
1214
|
} finally {
|
|
1695
1215
|
process.env.GENTLE_PI_AGENT_HOME = globalAgentHome;
|
|
1696
1216
|
await rm(repairFixture, { recursive: true, force: true });
|
|
1697
1217
|
}
|
|
1698
1218
|
|
|
1699
|
-
for (const trigger of ["gentle:sdd-preflight", "gentle-sdd-init"]) {
|
|
1700
|
-
const fixture = await tempWorkspace();
|
|
1701
|
-
const agentHome = join(fixture, "agent-home");
|
|
1702
|
-
try {
|
|
1703
|
-
process.env.GENTLE_PI_AGENT_HOME = agentHome;
|
|
1704
|
-
const command = commands.get(trigger);
|
|
1705
|
-
await command.handler("", createCtx(fixture, true, `${trigger}-install`));
|
|
1706
|
-
assert.equal(existsSync(join(agentHome, "agents", "gentle-ai-worker.md")), false,
|
|
1707
|
-
`${trigger} must not install delegation assets`);
|
|
1708
|
-
assert.equal(existsSync(join(agentHome, "agents", "review-risk.md")), false,
|
|
1709
|
-
`${trigger} must not install review assets`);
|
|
1710
|
-
const manifestPath = join(agentHome, "gentle-ai", "managed-assets.json");
|
|
1711
|
-
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
1712
|
-
const managedKeys = [
|
|
1713
|
-
"agents/sdd-apply.md",
|
|
1714
|
-
"chains/sdd-full.chain.md",
|
|
1715
|
-
"gentle-ai/support/sdd-status-contract.md",
|
|
1716
|
-
];
|
|
1717
|
-
const originals = new Map();
|
|
1718
|
-
for (const key of managedKeys) {
|
|
1719
|
-
originals.set(key, await readFile(join(agentHome, key), "utf8"));
|
|
1720
|
-
}
|
|
1721
|
-
const untouchedKeys = ["agents/gentle-ai-worker.md", "agents/review-risk.md"];
|
|
1722
|
-
for (const key of [...managedKeys, ...untouchedKeys]) {
|
|
1723
|
-
await writeFile(join(agentHome, key), `stale ${key}\n`);
|
|
1724
|
-
manifest.assets[key] = sha256(`stale ${key}\n`);
|
|
1725
|
-
}
|
|
1726
|
-
await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
|
1727
|
-
const userPath = join(agentHome, "agents", "sdd-spec.md");
|
|
1728
|
-
const userEdit = `${await readFile(userPath, "utf8")}\nUser-owned instructions.\n`;
|
|
1729
|
-
await writeFile(userPath, userEdit);
|
|
1730
|
-
const projectOverride = join(fixture, ".pi", "agents", "sdd-apply.md");
|
|
1731
|
-
await mkdir(dirname(projectOverride), { recursive: true });
|
|
1732
|
-
await writeFile(projectOverride, "Project override must survive.\n");
|
|
1733
|
-
await command.handler("", createCtx(fixture, true, `${trigger}-refresh`));
|
|
1734
|
-
for (const key of managedKeys) {
|
|
1735
|
-
assert.equal(await readFile(join(agentHome, key), "utf8"), originals.get(key),
|
|
1736
|
-
`${trigger} must refresh managed ${key} on demand`);
|
|
1737
|
-
}
|
|
1738
|
-
const refreshed = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
1739
|
-
for (const key of untouchedKeys) {
|
|
1740
|
-
assert.equal(await readFile(join(agentHome, key), "utf8"), `stale ${key}\n`);
|
|
1741
|
-
assert.equal(refreshed.assets[key], manifest.assets[key]);
|
|
1742
|
-
}
|
|
1743
|
-
assert.equal(await readFile(userPath, "utf8"), userEdit);
|
|
1744
|
-
assert.equal(await readFile(projectOverride, "utf8"), "Project override must survive.\n");
|
|
1745
|
-
} finally {
|
|
1746
|
-
process.env.GENTLE_PI_AGENT_HOME = globalAgentHome;
|
|
1747
|
-
await rm(fixture, { recursive: true, force: true });
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
const installCwd = await tempWorkspace();
|
|
1752
|
-
try {
|
|
1753
|
-
const ctx = createCtx(installCwd, true);
|
|
1754
|
-
await commands.get("gentle:install-sdd").handler("", ctx);
|
|
1755
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Global Gentle AI SDD assets installed/);
|
|
1756
|
-
assert.equal(existsSync(join(installCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1757
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1758
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-status.md")), true);
|
|
1759
|
-
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1760
|
-
} finally {
|
|
1761
|
-
await rm(installCwd, { recursive: true, force: true });
|
|
1762
|
-
}
|
|
1763
|
-
|
|
1764
1219
|
const staleAssetsCwd = await tempWorkspace();
|
|
1765
1220
|
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
1766
1221
|
const previousHome = process.env.HOME;
|
|
1767
1222
|
const previousUserProfile = process.env.USERPROFILE;
|
|
1768
1223
|
try {
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
process.env.
|
|
1772
|
-
process.env.HOME = diagnosticsHome;
|
|
1773
|
-
process.env.USERPROFILE = diagnosticsHome;
|
|
1774
|
-
for (const [dir, name] of [
|
|
1775
|
-
[join(diagnosticsAgentHome, "subagents"), "sdd-apply.md"],
|
|
1776
|
-
[join(diagnosticsHome, ".agents"), "sdd-archive.md"],
|
|
1777
|
-
[join(staleAssetsCwd, ".agents"), "sdd-design.md"],
|
|
1778
|
-
[join(staleAssetsCwd, ".pi", "agents"), "sdd-spec.md"],
|
|
1779
|
-
[join(staleAssetsCwd, ".pi", "subagents"), "sdd-sync.md"],
|
|
1780
|
-
]) {
|
|
1781
|
-
await mkdir(dir, { recursive: true });
|
|
1782
|
-
await writeFile(
|
|
1783
|
-
join(dir, name),
|
|
1784
|
-
`---\nname: ${name.replace(/\.md$/, "")}\n---\nintentional SDD override\n`,
|
|
1785
|
-
);
|
|
1786
|
-
}
|
|
1787
|
-
await mkdir(join(diagnosticsAgentHome, "subagents", "nested"), { recursive: true });
|
|
1788
|
-
await mkdir(join(diagnosticsAgentHome, "agents", "nested"), { recursive: true });
|
|
1789
|
-
await mkdir(join(staleAssetsCwd, ".agents", "skills", "ignored"), { recursive: true });
|
|
1790
|
-
await writeFile(
|
|
1791
|
-
join(diagnosticsAgentHome, "subagents", "nested", "renamed-agent.md"),
|
|
1792
|
-
"---\nname: sdd-apply\n---\nintentional nested SDD override\n",
|
|
1793
|
-
);
|
|
1794
|
-
await writeFile(
|
|
1795
|
-
join(diagnosticsAgentHome, "agents", "nested", "managed-copy.md"),
|
|
1796
|
-
"---\nname: sdd-apply\n---\npackage-managed root is excluded\n",
|
|
1797
|
-
);
|
|
1798
|
-
await writeFile(
|
|
1799
|
-
join(staleAssetsCwd, ".agents", "skills", "ignored", "SKILL.md"),
|
|
1800
|
-
"---\nname: sdd-apply\n---\nskills are not agent definitions\n",
|
|
1801
|
-
);
|
|
1802
|
-
await mkdir(join(staleAssetsCwd, ".pi", "chains"), { recursive: true });
|
|
1803
|
-
await mkdir(join(staleAssetsCwd, ".pi", "gentle-ai", "support"), { recursive: true });
|
|
1804
|
-
await writeFile(join(staleAssetsCwd, ".pi", "chains", "sdd-full.chain.md"), "stale chain\n");
|
|
1805
|
-
await writeFile(join(staleAssetsCwd, ".pi", "gentle-ai", "support", "sdd-status-contract.md"), "stale status contract\n");
|
|
1224
|
+
process.env.GENTLE_PI_AGENT_HOME = join(staleAssetsCwd, "agent-home");
|
|
1225
|
+
process.env.HOME = staleAssetsCwd;
|
|
1226
|
+
process.env.USERPROFILE = staleAssetsCwd;
|
|
1806
1227
|
const ctx = createCtx(staleAssetsCwd, true);
|
|
1807
|
-
await commands.get("gentle:status").handler("", ctx);
|
|
1808
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Active SDD agent overrides: 5 file\(s\)/);
|
|
1809
|
-
assert.match(ctx.ui.notifications.at(-1).message, /active non-builtin SDD agents shadow package assets/);
|
|
1810
1228
|
await commands.get("gentle:doctor").handler("", ctx);
|
|
1811
1229
|
assert.match(ctx.ui.notifications.at(-1).message, /el Gentleman doctor/);
|
|
1812
1230
|
assert.match(ctx.ui.notifications.at(-1).message, /Sensitive-path guard active/);
|
|
1231
|
+
for (const diagnostic of ["gentle:status", "gentle:doctor"]) {
|
|
1232
|
+
await commands.get(diagnostic).handler("", ctx);
|
|
1233
|
+
const message = ctx.ui.notifications.at(-1).message;
|
|
1234
|
+
assert.match(message, /Organic Driven Development \(ODD\): active/);
|
|
1235
|
+
assert.doesNotMatch(message, /OpenSpec|openspec\/config\.yaml|SDD/);
|
|
1236
|
+
}
|
|
1813
1237
|
pi.setActiveTools([{ name: "engram.mem_save" }]);
|
|
1814
1238
|
await commands.get("gentle:doctor").handler("", ctx);
|
|
1815
1239
|
assert.match(ctx.ui.notifications.at(-1).message, /Engram memory tools active/);
|
|
1816
1240
|
pi.setActiveTools([{ name: "engram_mem_save" }]);
|
|
1817
1241
|
await commands.get("gentle:doctor").handler("", ctx);
|
|
1818
1242
|
assert.match(ctx.ui.notifications.at(-1).message, /Engram memory tools not active in this session/);
|
|
1819
|
-
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1820
1243
|
} finally {
|
|
1244
|
+
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1821
1245
|
if (previousAgentHome === undefined) delete process.env.GENTLE_PI_AGENT_HOME;
|
|
1822
1246
|
else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
|
|
1823
1247
|
if (previousHome === undefined) delete process.env.HOME;
|
|
@@ -1827,59 +1251,6 @@ async function run() {
|
|
|
1827
1251
|
await rm(staleAssetsCwd, { recursive: true, force: true });
|
|
1828
1252
|
}
|
|
1829
1253
|
|
|
1830
|
-
const sddCwd = await tempWorkspace();
|
|
1831
|
-
try {
|
|
1832
|
-
const ctx = createCtx(sddCwd, true);
|
|
1833
|
-
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1834
|
-
assert.equal(existsSync(join(sddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1835
|
-
assert.equal(existsSync(join(sddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1836
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1837
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-status.md")), true);
|
|
1838
|
-
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), false);
|
|
1839
|
-
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1840
|
-
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1841
|
-
assert.equal(ctx.ui.selections.length, 1, "sdd-init confirms session preflight before project initialization");
|
|
1842
|
-
assert.match(ctx.ui.notifications[1].message, /SDD preflight complete/);
|
|
1843
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Wrote openspec\/config\.yaml/);
|
|
1844
|
-
const initializedConfig = await readFile(join(sddCwd, "openspec", "config.yaml"), "utf8");
|
|
1845
|
-
const explore = await hooks.get("before_agent_start")[0]({ agentName: "sdd-explore", systemPrompt: "You are the SDD explore executor for Gentle AI." }, ctx);
|
|
1846
|
-
assert.match(explore.systemPrompt, /explicit current-session choices/);
|
|
1847
|
-
assert.equal(ctx.ui.selections.length, 1, "confirmation -> sdd-init -> explore must reuse the resolved preflight");
|
|
1848
|
-
const nextSession = createCtx(sddCwd, true, "cold-start-with-saved-preferences");
|
|
1849
|
-
await hooks.get("input")[0]({ text: "/sdd", source: "interactive" }, nextSession);
|
|
1850
|
-
assert.equal(nextSession.ui.selections.length, 1, "saved preferences still require confirmation in a new session");
|
|
1851
|
-
assert.equal(await readFile(join(sddCwd, "openspec", "config.yaml"), "utf8"), initializedConfig, "new session confirmation must not reset project initialization");
|
|
1852
|
-
|
|
1853
|
-
await commands.get("gentle:sdd-preflight").handler("--edit", ctx);
|
|
1854
|
-
assert.equal(ctx.ui.selections.length, 3, "--edit permits changes after confirmed sdd-init");
|
|
1855
|
-
} finally {
|
|
1856
|
-
await rm(sddCwd, { recursive: true, force: true });
|
|
1857
|
-
}
|
|
1858
|
-
|
|
1859
|
-
const invalidSddInitCwd = await tempWorkspace();
|
|
1860
|
-
try {
|
|
1861
|
-
await mkdir(join(invalidSddInitCwd, ".pi", "agents"), { recursive: true });
|
|
1862
|
-
await writeFile(
|
|
1863
|
-
join(invalidSddInitCwd, ".pi", "agents", "sdd-apply.md"),
|
|
1864
|
-
`---\nname: sdd-apply\ndescription: Apply phase\nmodel: keep/provider-model\n---\n\nbody\n`,
|
|
1865
|
-
);
|
|
1866
|
-
await writeFile(globalModelsPath, "{ invalid json");
|
|
1867
|
-
const ctx = createCtx(invalidSddInitCwd, true, "invalid-sdd-init-session");
|
|
1868
|
-
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1869
|
-
assert.equal(ctx.ui.notifications[1].level, "warning");
|
|
1870
|
-
assert.match(ctx.ui.notifications[1].message, /Model routing skipped:/);
|
|
1871
|
-
assert.match(ctx.ui.notifications[1].message, /models\.json/);
|
|
1872
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Wrote openspec\/config\.yaml/);
|
|
1873
|
-
const preservedAgent = await readFile(
|
|
1874
|
-
join(invalidSddInitCwd, ".pi", "agents", "sdd-apply.md"),
|
|
1875
|
-
"utf8",
|
|
1876
|
-
);
|
|
1877
|
-
assert.match(preservedAgent, /model: keep\/provider-model/);
|
|
1878
|
-
} finally {
|
|
1879
|
-
await rm(invalidSddInitCwd, { recursive: true, force: true });
|
|
1880
|
-
await rm(globalModelsPath, { force: true });
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
1254
|
const legacyModelsCwd = await tempWorkspace();
|
|
1884
1255
|
try {
|
|
1885
1256
|
await mkdir(join(legacyModelsCwd, ".pi", "agents"), { recursive: true });
|
|
@@ -1963,15 +1334,15 @@ async function run() {
|
|
|
1963
1334
|
);
|
|
1964
1335
|
await writeFile(globalModelsPath, JSON.stringify({ "sdd-apply": {} }, null, 2));
|
|
1965
1336
|
await hooks.get("session_start")[0]({ reason: "startup" }, legacyCtx);
|
|
1966
|
-
const
|
|
1337
|
+
const savedInheritPreservesAgent = await readFile(
|
|
1967
1338
|
join(legacyModelsCwd, ".pi", "agents", "sdd-apply.md"),
|
|
1968
1339
|
"utf8",
|
|
1969
1340
|
);
|
|
1970
|
-
assert.
|
|
1971
|
-
const
|
|
1341
|
+
assert.match(savedInheritPreservesAgent, /model: global\/provider-model/);
|
|
1342
|
+
const savedInheritPreservesProfiles = JSON.parse(
|
|
1972
1343
|
await readFile(join(legacyModelsCwd, ".pi", "subagents.json"), "utf8"),
|
|
1973
1344
|
);
|
|
1974
|
-
assert.equal(
|
|
1345
|
+
assert.equal(savedInheritPreservesProfiles.model_profiles["sdd-apply"].model, "global/provider-model");
|
|
1975
1346
|
} finally {
|
|
1976
1347
|
await rm(legacyModelsCwd, { recursive: true, force: true });
|
|
1977
1348
|
await rm(globalModelsPath, { force: true });
|
|
@@ -1993,10 +1364,12 @@ async function run() {
|
|
|
1993
1364
|
);
|
|
1994
1365
|
await writeFile(globalModelsPath, JSON.stringify({ worker: {} }, null, 2));
|
|
1995
1366
|
await hooks.get("session_start")[0]({ reason: "startup" }, createCtx(staleSettingsOnlyCwd, true));
|
|
1996
|
-
const
|
|
1367
|
+
const staleOnlyPreservedProfiles = JSON.parse(
|
|
1997
1368
|
await readFile(globalSubagentsPath, "utf8"),
|
|
1998
1369
|
);
|
|
1999
|
-
assert.
|
|
1370
|
+
assert.deepEqual(staleOnlyPreservedProfiles.model_profiles, {
|
|
1371
|
+
worker: { model: "stale/model", effort: "high" },
|
|
1372
|
+
});
|
|
2000
1373
|
} finally {
|
|
2001
1374
|
await rm(staleSettingsOnlyCwd, { recursive: true, force: true });
|
|
2002
1375
|
await rm(globalModelsPath, { force: true });
|
|
@@ -2212,22 +1585,29 @@ async function run() {
|
|
|
2212
1585
|
assert.match(preservedProjectSubagentWorker, /thinking: medium/);
|
|
2213
1586
|
await writeFile(globalModelsPath, JSON.stringify({ worker: {} }, null, 2));
|
|
2214
1587
|
await hooks.get("session_start")[0]({ reason: "startup" }, createCtx(modelsCwd, true));
|
|
2215
|
-
|
|
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(
|
|
2216
1592
|
await readFile(join(modelsCwd, ".pi", "subagents.json"), "utf8"),
|
|
2217
1593
|
);
|
|
2218
|
-
assert.equal(
|
|
1594
|
+
assert.equal(
|
|
1595
|
+
preservedClearProfiles.model_profiles.worker.model,
|
|
1596
|
+
"existing/model",
|
|
1597
|
+
);
|
|
1598
|
+
assert.equal(preservedClearProfiles.model_profiles.worker.effort, "high");
|
|
2219
1599
|
const unchangedProjectWorker = await readFile(
|
|
2220
1600
|
join(modelsCwd, ".pi", "agents", "worker.md"),
|
|
2221
1601
|
"utf8",
|
|
2222
1602
|
);
|
|
2223
1603
|
assert.match(unchangedProjectWorker, /model: existing\/project-worker/);
|
|
2224
1604
|
assert.match(unchangedProjectWorker, /thinking: high/);
|
|
2225
|
-
const
|
|
1605
|
+
const preservedClearProjectSubagentWorker = await readFile(
|
|
2226
1606
|
join(modelsCwd, ".pi", "subagents", "worker.md"),
|
|
2227
1607
|
"utf8",
|
|
2228
1608
|
);
|
|
2229
|
-
assert.
|
|
2230
|
-
assert.
|
|
1609
|
+
assert.match(preservedClearProjectSubagentWorker, /model: existing\/project-subagent-worker/);
|
|
1610
|
+
assert.match(preservedClearProjectSubagentWorker, /thinking: medium/);
|
|
2231
1611
|
|
|
2232
1612
|
await writeFile(
|
|
2233
1613
|
globalModelsPath,
|
|
@@ -2240,7 +1620,7 @@ async function run() {
|
|
|
2240
1620
|
{ provider: "evil\u001b]52;c;Zm9v\u0007", id: "model" },
|
|
2241
1621
|
];
|
|
2242
1622
|
ctx.ui.custom = (factory) => {
|
|
2243
|
-
const panel = factory(
|
|
1623
|
+
const panel = factory({ terminal: { rows: 24 } }, null, null, () => undefined);
|
|
2244
1624
|
const initialLines = panel.render(120);
|
|
2245
1625
|
const plainInitialLines = initialLines.map(stripAnsi);
|
|
2246
1626
|
assert.ok(
|
|
@@ -2248,8 +1628,8 @@ async function run() {
|
|
|
2248
1628
|
"model panel should render inside a bordered card",
|
|
2249
1629
|
);
|
|
2250
1630
|
assert.ok(
|
|
2251
|
-
initialLines.length
|
|
2252
|
-
"
|
|
1631
|
+
initialLines.length === 24,
|
|
1632
|
+
"model panel should fill the 24-row terminal like /gentle:profiles",
|
|
2253
1633
|
);
|
|
2254
1634
|
assert.ok(
|
|
2255
1635
|
plainInitialLines.some((line) => /↓ \d+ more agent\(s\)/.test(line)),
|
|
@@ -2273,8 +1653,8 @@ async function run() {
|
|
|
2273
1653
|
const scrolledLines = panel.render(120);
|
|
2274
1654
|
const plainScrolledLines = scrolledLines.map(stripAnsi);
|
|
2275
1655
|
assert.ok(
|
|
2276
|
-
scrolledLines.length
|
|
2277
|
-
"scrolled model agent list should
|
|
1656
|
+
scrolledLines.length === 24,
|
|
1657
|
+
"scrolled model agent list should keep filling the terminal",
|
|
2278
1658
|
);
|
|
2279
1659
|
assert.ok(
|
|
2280
1660
|
plainScrolledLines.some((line) => /↑ \d+ more agent\(s\)/.test(line)),
|
|
@@ -2284,8 +1664,8 @@ async function run() {
|
|
|
2284
1664
|
const bottomLines = panel.render(120);
|
|
2285
1665
|
const plainBottomLines = bottomLines.map(stripAnsi);
|
|
2286
1666
|
assert.ok(
|
|
2287
|
-
bottomLines.length
|
|
2288
|
-
"bottom model agent list should
|
|
1667
|
+
bottomLines.length === 24,
|
|
1668
|
+
"bottom model agent list should keep filling the terminal",
|
|
2289
1669
|
);
|
|
2290
1670
|
assert.ok(
|
|
2291
1671
|
plainBottomLines.some((line) => line.includes("▸ ← Back")),
|
|
@@ -2532,4 +1912,10 @@ async function run() {
|
|
|
2532
1912
|
run().catch((error) => {
|
|
2533
1913
|
console.error(error);
|
|
2534
1914
|
process.exitCode = 1;
|
|
1915
|
+
}).finally(async () => {
|
|
1916
|
+
for (const [key, value] of previousFixtureEnvironment) {
|
|
1917
|
+
if (value === undefined) delete process.env[key];
|
|
1918
|
+
else process.env[key] = value;
|
|
1919
|
+
}
|
|
1920
|
+
for (const root of ownedFixtureRoots) await rm(root, { recursive: true, force: true });
|
|
2535
1921
|
});
|