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
package/lib/sdd-preflight.ts
DELETED
|
@@ -1,1066 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { resolveGentlePiAgentHome } from "./agent-home.ts";
|
|
7
|
-
import type { SddArtifactStore } from "./sdd-status.ts";
|
|
8
|
-
|
|
9
|
-
export type { SddArtifactStore };
|
|
10
|
-
|
|
11
|
-
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
12
|
-
const ASSETS_DIR = join(PACKAGE_ROOT, "assets");
|
|
13
|
-
const MANAGED_ASSETS_MANIFEST = "managed-assets.json";
|
|
14
|
-
const MANAGED_ASSETS_LOCK = "managed-assets.lock";
|
|
15
|
-
const MANAGED_ASSETS_SCHEMA_VERSION = 1;
|
|
16
|
-
const MANAGED_ASSETS_LOCK_TIMEOUT_MS = 5_000;
|
|
17
|
-
const MANAGED_ASSETS_LOCK_RETRY_MS = 25;
|
|
18
|
-
const LEGACY_MANAGED_ASSET_MANIFESTS = Object.freeze([
|
|
19
|
-
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.10.7.json"), version: "0.10.7" },
|
|
20
|
-
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.13.json"), version: "0.13.0" },
|
|
21
|
-
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v0.14.json"), version: "0.14.0" },
|
|
22
|
-
{ path: join(ASSETS_DIR, "migrations", "managed-assets-v2.5.0.json"), version: "2.5.0" },
|
|
23
|
-
]);
|
|
24
|
-
|
|
25
|
-
const ASSET_OWNER_BY_KEY = Object.freeze({
|
|
26
|
-
"agents/gentle-ai-explore.md": "delegation",
|
|
27
|
-
"agents/gentle-ai-verify.md": "delegation",
|
|
28
|
-
"agents/gentle-ai-worker.md": "delegation",
|
|
29
|
-
"agents/jd-fix-agent.md": "review",
|
|
30
|
-
"agents/jd-judge-a.md": "review",
|
|
31
|
-
"agents/jd-judge-b.md": "review",
|
|
32
|
-
"agents/review-readability.md": "review",
|
|
33
|
-
"agents/review-reliability.md": "review",
|
|
34
|
-
"agents/review-resilience.md": "review",
|
|
35
|
-
"agents/review-risk.md": "review",
|
|
36
|
-
"agents/review-refuter.md": "review",
|
|
37
|
-
"agents/review-validator.md": "review",
|
|
38
|
-
"chains/4r-review.chain.md": "review",
|
|
39
|
-
"agents/sdd-apply.md": "sdd",
|
|
40
|
-
"agents/sdd-archive.md": "sdd",
|
|
41
|
-
"agents/sdd-design.md": "sdd",
|
|
42
|
-
"agents/sdd-explore.md": "sdd",
|
|
43
|
-
"agents/sdd-init.md": "sdd",
|
|
44
|
-
"agents/sdd-onboard.md": "sdd",
|
|
45
|
-
"agents/sdd-proposal.md": "sdd",
|
|
46
|
-
"agents/sdd-research.md": "sdd",
|
|
47
|
-
"agents/sdd-remediate.md": "sdd",
|
|
48
|
-
"agents/sdd-spec.md": "sdd",
|
|
49
|
-
"agents/sdd-status.md": "sdd",
|
|
50
|
-
"agents/sdd-sync.md": "sdd",
|
|
51
|
-
"agents/sdd-tasks.md": "sdd",
|
|
52
|
-
"agents/sdd-verify.md": "sdd",
|
|
53
|
-
"chains/sdd-full.chain.md": "sdd",
|
|
54
|
-
"chains/sdd-plan.chain.md": "sdd",
|
|
55
|
-
"chains/sdd-verify.chain.md": "sdd",
|
|
56
|
-
"gentle-ai/support/sdd-status-contract.md": "sdd",
|
|
57
|
-
"gentle-ai/support/strict-tdd.md": "sdd",
|
|
58
|
-
"gentle-ai/support/strict-tdd-verify.md": "sdd",
|
|
59
|
-
} as const);
|
|
60
|
-
|
|
61
|
-
export type PackageAssetOwner = (typeof ASSET_OWNER_BY_KEY)[keyof typeof ASSET_OWNER_BY_KEY];
|
|
62
|
-
|
|
63
|
-
export function getPackageAssetOwner(ownershipKey: string): PackageAssetOwner | undefined {
|
|
64
|
-
return Object.hasOwn(ASSET_OWNER_BY_KEY, ownershipKey)
|
|
65
|
-
? ASSET_OWNER_BY_KEY[ownershipKey as keyof typeof ASSET_OWNER_BY_KEY]
|
|
66
|
-
: undefined;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function gentlePiAgentHome(): string {
|
|
70
|
-
return resolveGentlePiAgentHome();
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export type SddExecutionMode = "interactive" | "auto";
|
|
74
|
-
export type SddDeliveryStrategy =
|
|
75
|
-
| "ask-on-risk"
|
|
76
|
-
| "auto-chain"
|
|
77
|
-
| "single-pr"
|
|
78
|
-
| "exception-ok";
|
|
79
|
-
/** @deprecated Use SddDeliveryStrategy; legacy values normalize at persistence boundaries. */
|
|
80
|
-
export type SddChainedPrStrategy =
|
|
81
|
-
| SddDeliveryStrategy
|
|
82
|
-
| "auto-forecast"
|
|
83
|
-
| "ask-always"
|
|
84
|
-
| "single-pr-default"
|
|
85
|
-
| "force-chained";
|
|
86
|
-
export type SddPreflightField = "executionMode" | "artifactStore" | "chainedPrStrategy" | "reviewBudgetLines";
|
|
87
|
-
export const SDD_PREFLIGHT_FIELDS = ["executionMode", "artifactStore", "chainedPrStrategy", "reviewBudgetLines"] as const;
|
|
88
|
-
// The parent dispatch and the process-spawn boundary share this exact shipped
|
|
89
|
-
// inventory so a newly packaged SDD actor cannot bypass preflight transport.
|
|
90
|
-
export const SHIPPED_SDD_AGENT_NAMES = Object.freeze([
|
|
91
|
-
"sdd-init",
|
|
92
|
-
"sdd-onboard",
|
|
93
|
-
"sdd-explore",
|
|
94
|
-
"sdd-research",
|
|
95
|
-
"sdd-proposal",
|
|
96
|
-
"sdd-spec",
|
|
97
|
-
"sdd-design",
|
|
98
|
-
"sdd-tasks",
|
|
99
|
-
"sdd-status",
|
|
100
|
-
"sdd-apply",
|
|
101
|
-
"sdd-verify",
|
|
102
|
-
"sdd-archive",
|
|
103
|
-
"sdd-remediate",
|
|
104
|
-
]);
|
|
105
|
-
|
|
106
|
-
export interface SddPreflightPreferences {
|
|
107
|
-
executionMode: SddExecutionMode;
|
|
108
|
-
artifactStore: SddArtifactStore;
|
|
109
|
-
/** Runtime values are canonical; legacy assignments normalize at persistence/render boundaries. */
|
|
110
|
-
chainedPrStrategy: SddChainedPrStrategy;
|
|
111
|
-
reviewBudgetLines: number;
|
|
112
|
-
engramAvailable: boolean;
|
|
113
|
-
prompted: boolean;
|
|
114
|
-
sizeExceptionAccepted?: true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export interface SddPreflightResolutionOptions {
|
|
118
|
-
persisted?: Partial<SddPreflightPreferences>;
|
|
119
|
-
promptFields?: readonly SddPreflightField[];
|
|
120
|
-
acceptSizeException?: true;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
interface SddPreflightCallbacks {
|
|
124
|
-
pi: ExtensionAPI;
|
|
125
|
-
installAssets?: (cwd: string) =>
|
|
126
|
-
| {
|
|
127
|
-
agents: number;
|
|
128
|
-
chains: number;
|
|
129
|
-
support: number;
|
|
130
|
-
skipped: number;
|
|
131
|
-
}
|
|
132
|
-
| Promise<{
|
|
133
|
-
agents: number;
|
|
134
|
-
chains: number;
|
|
135
|
-
support: number;
|
|
136
|
-
skipped: number;
|
|
137
|
-
}>;
|
|
138
|
-
applyModelConfig?: (
|
|
139
|
-
cwd: string,
|
|
140
|
-
) =>
|
|
141
|
-
| { updated: number; skipped: number; invalidPath?: string }
|
|
142
|
-
| Promise<{ updated: number; skipped: number; invalidPath?: string }>;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
interface ManagedAssetsManifest {
|
|
146
|
-
schemaVersion: number;
|
|
147
|
-
assets: Record<string, string>;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
interface LegacyManagedAssetsManifest extends ManagedAssetsManifest {
|
|
151
|
-
packageVersion: string;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
interface ManagedAssetsLockOwner {
|
|
155
|
-
schemaVersion: 1;
|
|
156
|
-
token: string;
|
|
157
|
-
pid: number;
|
|
158
|
-
createdAtMs: number;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** @internal The hold option exists only to make process-lock regression tests deterministic. */
|
|
162
|
-
interface PackageAssetInstallLockOptions {
|
|
163
|
-
timeoutMs?: number;
|
|
164
|
-
retryMs?: number;
|
|
165
|
-
holdLockMs?: number;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export const DEFAULT_SDD_PREFLIGHT: SddPreflightPreferences = Object.freeze({
|
|
169
|
-
executionMode: "auto",
|
|
170
|
-
artifactStore: "openspec",
|
|
171
|
-
chainedPrStrategy: "ask-on-risk",
|
|
172
|
-
reviewBudgetLines: 400,
|
|
173
|
-
engramAvailable: false,
|
|
174
|
-
prompted: false,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const sddPreflightBySession = new Map<string, SddPreflightPreferences>();
|
|
178
|
-
const sddPreflightInFlight = new Map<string, Promise<SddPreflightPreferences>>();
|
|
179
|
-
|
|
180
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
181
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function isSddExecutionMode(value: unknown): value is SddExecutionMode {
|
|
185
|
-
return value === "interactive" || value === "auto";
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function isSddArtifactStore(value: unknown): value is SddArtifactStore {
|
|
189
|
-
return value === "openspec" || value === "engram" || value === "hybrid" || value === "none";
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// normalizeSddArtifactStore accepts the canonical names and the legacy "both"
|
|
193
|
-
// spelling of the dual-store mode. Operator preflight files written before the
|
|
194
|
-
// rename carry "both" on disk; rejecting it would silently drop the operator's
|
|
195
|
-
// choice back to the default, so it maps forward instead.
|
|
196
|
-
export function normalizeSddArtifactStore(value: unknown): SddArtifactStore | undefined {
|
|
197
|
-
if (value === "both") return "hybrid";
|
|
198
|
-
return isSddArtifactStore(value) ? value : undefined;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function normalizeReviewBudgetValue(value: unknown): number | undefined {
|
|
202
|
-
const parsed = typeof value === "number"
|
|
203
|
-
? value
|
|
204
|
-
: typeof value === "string"
|
|
205
|
-
? Number.parseInt(value.trim(), 10)
|
|
206
|
-
: Number.NaN;
|
|
207
|
-
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : undefined;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function normalizeSddStrategySelection(
|
|
211
|
-
value: unknown,
|
|
212
|
-
allowExceptionOk: boolean,
|
|
213
|
-
): SddDeliveryStrategy | undefined {
|
|
214
|
-
if (value === "exception-ok") return allowExceptionOk ? value : undefined;
|
|
215
|
-
if (value === "auto-forecast" || value === "ask-always") return "ask-on-risk";
|
|
216
|
-
if (value === "single-pr-default") return "single-pr";
|
|
217
|
-
if (value === "force-chained") return "auto-chain";
|
|
218
|
-
return value === "ask-on-risk" || value === "auto-chain" || value === "single-pr"
|
|
219
|
-
? value
|
|
220
|
-
: undefined;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function normalizeSddChainedPrStrategy(
|
|
224
|
-
value: unknown,
|
|
225
|
-
allowExceptionOk = false,
|
|
226
|
-
): SddDeliveryStrategy {
|
|
227
|
-
return normalizeSddStrategySelection(value, allowExceptionOk) ?? "ask-on-risk";
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function normalizedSelections(
|
|
231
|
-
value: Partial<Record<SddPreflightField, unknown>> | undefined,
|
|
232
|
-
engramAvailable: boolean,
|
|
233
|
-
allowExceptionOk: boolean,
|
|
234
|
-
): Partial<Record<SddPreflightField, unknown>> {
|
|
235
|
-
if (!value) return {};
|
|
236
|
-
const result: Partial<Record<SddPreflightField, unknown>> = {};
|
|
237
|
-
if (isSddExecutionMode(value.executionMode)) result.executionMode = value.executionMode;
|
|
238
|
-
const artifactStore = normalizeSddArtifactStore(value.artifactStore);
|
|
239
|
-
if (artifactStore !== undefined && (engramAvailable || artifactStore === "openspec" || artifactStore === "none")) result.artifactStore = artifactStore;
|
|
240
|
-
const strategy = normalizeSddStrategySelection(value.chainedPrStrategy, allowExceptionOk);
|
|
241
|
-
if (strategy) result.chainedPrStrategy = strategy;
|
|
242
|
-
const reviewBudgetLines = normalizeReviewBudgetValue(value.reviewBudgetLines);
|
|
243
|
-
if (reviewBudgetLines !== undefined) result.reviewBudgetLines = reviewBudgetLines;
|
|
244
|
-
return result;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function emptyManagedAssetsManifest(): ManagedAssetsManifest {
|
|
248
|
-
return {
|
|
249
|
-
schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION,
|
|
250
|
-
assets: {},
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function readManagedAssetsManifest(path: string): ManagedAssetsManifest {
|
|
255
|
-
if (!existsSync(path)) return emptyManagedAssetsManifest();
|
|
256
|
-
try {
|
|
257
|
-
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
258
|
-
if (
|
|
259
|
-
!isRecord(parsed) ||
|
|
260
|
-
parsed.schemaVersion !== MANAGED_ASSETS_SCHEMA_VERSION ||
|
|
261
|
-
!isRecord(parsed.assets)
|
|
262
|
-
) {
|
|
263
|
-
return emptyManagedAssetsManifest();
|
|
264
|
-
}
|
|
265
|
-
const assets = Object.fromEntries(
|
|
266
|
-
Object.entries(parsed.assets).filter(
|
|
267
|
-
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
268
|
-
),
|
|
269
|
-
);
|
|
270
|
-
return { schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION, assets };
|
|
271
|
-
} catch {
|
|
272
|
-
return emptyManagedAssetsManifest();
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function managedAssetHash(content: string): string {
|
|
277
|
-
return createHash("sha256").update(content).digest("hex");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function readManagedAssetsLockOwner(lockPath: string): ManagedAssetsLockOwner | undefined {
|
|
281
|
-
try {
|
|
282
|
-
if (!lstatSync(lockPath).isFile()) return undefined;
|
|
283
|
-
const parsed: unknown = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
284
|
-
if (!isRecord(parsed) || parsed.schemaVersion !== 1 || typeof parsed.token !== "string" || parsed.token.length === 0 || !Number.isInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.createdAtMs !== "number" || !Number.isFinite(parsed.createdAtMs)) return undefined;
|
|
285
|
-
return parsed as ManagedAssetsLockOwner;
|
|
286
|
-
} catch {
|
|
287
|
-
return undefined;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function waitForManagedAssetsLock(milliseconds: number): void {
|
|
292
|
-
if (milliseconds <= 0) return;
|
|
293
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function normalizedLockDuration(value: number | undefined, fallback: number): number {
|
|
297
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0
|
|
298
|
-
? Math.floor(value)
|
|
299
|
-
: fallback;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function acquireManagedAssetsLock(
|
|
303
|
-
agentHome: string,
|
|
304
|
-
options: PackageAssetInstallLockOptions = {},
|
|
305
|
-
): { path: string; owner: ManagedAssetsLockOwner } {
|
|
306
|
-
const lockParent = join(agentHome, "gentle-ai");
|
|
307
|
-
const lockPath = join(lockParent, MANAGED_ASSETS_LOCK);
|
|
308
|
-
const timeoutMs = normalizedLockDuration(options.timeoutMs, MANAGED_ASSETS_LOCK_TIMEOUT_MS);
|
|
309
|
-
const retryMs = Math.max(1, normalizedLockDuration(options.retryMs, MANAGED_ASSETS_LOCK_RETRY_MS));
|
|
310
|
-
const deadline = Date.now() + timeoutMs;
|
|
311
|
-
mkdirSync(lockParent, { recursive: true });
|
|
312
|
-
for (;;) {
|
|
313
|
-
const owner: ManagedAssetsLockOwner = {
|
|
314
|
-
schemaVersion: 1,
|
|
315
|
-
token: randomUUID(),
|
|
316
|
-
pid: process.pid,
|
|
317
|
-
createdAtMs: Date.now(),
|
|
318
|
-
};
|
|
319
|
-
try {
|
|
320
|
-
writeFileSync(lockPath, JSON.stringify(owner), { encoding: "utf8", flag: "wx" });
|
|
321
|
-
return { path: lockPath, owner };
|
|
322
|
-
} catch (error) {
|
|
323
|
-
if (!isRecord(error) || error.code !== "EEXIST") throw error;
|
|
324
|
-
try {
|
|
325
|
-
if (!lstatSync(lockPath).isFile()) {
|
|
326
|
-
throw new Error(`Managed-assets lock path is unsafe and must be a regular file: ${lockPath}`);
|
|
327
|
-
}
|
|
328
|
-
} catch (inspectionError) {
|
|
329
|
-
if (isRecord(inspectionError) && inspectionError.code === "ENOENT") continue;
|
|
330
|
-
throw inspectionError;
|
|
331
|
-
}
|
|
332
|
-
if (Date.now() >= deadline) {
|
|
333
|
-
throw new Error(`Timed out acquiring managed-assets lock file ${lockPath}. Verify no installer is active before removing this exact lock file.`);
|
|
334
|
-
}
|
|
335
|
-
waitForManagedAssetsLock(retryMs);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function releaseManagedAssetsLock(lock: { path: string; owner: ManagedAssetsLockOwner }): void {
|
|
341
|
-
if (readManagedAssetsLockOwner(lock.path)?.token !== lock.owner.token) return;
|
|
342
|
-
try {
|
|
343
|
-
unlinkSync(lock.path);
|
|
344
|
-
} catch {
|
|
345
|
-
// An unreadable or replaced lock remains for an operator to inspect.
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function withManagedAssetsLock<T>(
|
|
350
|
-
agentHome: string,
|
|
351
|
-
action: () => T,
|
|
352
|
-
options: PackageAssetInstallLockOptions | undefined,
|
|
353
|
-
): T {
|
|
354
|
-
const lock = acquireManagedAssetsLock(agentHome, options);
|
|
355
|
-
try {
|
|
356
|
-
waitForManagedAssetsLock(normalizedLockDuration(options?.holdLockMs, 0));
|
|
357
|
-
return action();
|
|
358
|
-
} finally {
|
|
359
|
-
releaseManagedAssetsLock(lock);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
function readLegacyManagedAssets(
|
|
364
|
-
path: string,
|
|
365
|
-
version: string,
|
|
366
|
-
): LegacyManagedAssetsManifest | undefined {
|
|
367
|
-
try {
|
|
368
|
-
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
369
|
-
if (
|
|
370
|
-
!isRecord(parsed) ||
|
|
371
|
-
parsed.schemaVersion !== MANAGED_ASSETS_SCHEMA_VERSION ||
|
|
372
|
-
parsed.packageVersion !== version ||
|
|
373
|
-
!isRecord(parsed.assets)
|
|
374
|
-
) {
|
|
375
|
-
return undefined;
|
|
376
|
-
}
|
|
377
|
-
const assets = Object.fromEntries(
|
|
378
|
-
Object.entries(parsed.assets).filter(
|
|
379
|
-
(entry): entry is [string, string] => typeof entry[1] === "string",
|
|
380
|
-
),
|
|
381
|
-
);
|
|
382
|
-
return {
|
|
383
|
-
schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION,
|
|
384
|
-
packageVersion: version,
|
|
385
|
-
assets,
|
|
386
|
-
};
|
|
387
|
-
} catch {
|
|
388
|
-
return undefined;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function readLegacyManagedAssetHashes(): Record<string, readonly string[]> {
|
|
393
|
-
const hashes: Record<string, string[]> = {};
|
|
394
|
-
for (const manifest of LEGACY_MANAGED_ASSET_MANIFESTS) {
|
|
395
|
-
const assets = readLegacyManagedAssets(manifest.path, manifest.version)?.assets;
|
|
396
|
-
if (!assets) continue;
|
|
397
|
-
for (const [ownershipKey, hash] of Object.entries(assets)) {
|
|
398
|
-
const known = hashes[ownershipKey] ?? [];
|
|
399
|
-
if (!known.includes(hash)) known.push(hash);
|
|
400
|
-
hashes[ownershipKey] = known;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
return hashes;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function updateAgentFrontmatterRouting(
|
|
407
|
-
content: string,
|
|
408
|
-
routingLines: readonly string[],
|
|
409
|
-
): string {
|
|
410
|
-
if (!content.startsWith("---\n")) return content;
|
|
411
|
-
const endIndex = content.indexOf("\n---", 4);
|
|
412
|
-
if (endIndex === -1) return content;
|
|
413
|
-
const frontmatter = content.slice(4, endIndex);
|
|
414
|
-
const body = content.slice(endIndex);
|
|
415
|
-
const lines = frontmatter
|
|
416
|
-
.split("\n")
|
|
417
|
-
.filter((line) => !/^(?:model|thinking):/.test(line));
|
|
418
|
-
if (routingLines.length > 0) {
|
|
419
|
-
const descriptionIndex = lines.findIndex((line) =>
|
|
420
|
-
line.startsWith("description:"),
|
|
421
|
-
);
|
|
422
|
-
const insertIndex =
|
|
423
|
-
descriptionIndex >= 0 ? descriptionIndex + 1 : Math.min(1, lines.length);
|
|
424
|
-
lines.splice(insertIndex, 0, ...routingLines);
|
|
425
|
-
}
|
|
426
|
-
return `---\n${lines.join("\n")}${body}`;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function legacyComparableAssetContent(
|
|
430
|
-
ownershipKey: string,
|
|
431
|
-
content: string,
|
|
432
|
-
): string {
|
|
433
|
-
return ownershipKey.startsWith("agents/")
|
|
434
|
-
? updateAgentFrontmatterRouting(content, [])
|
|
435
|
-
: content;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function migrateLegacyAssetContent(
|
|
439
|
-
ownershipKey: string,
|
|
440
|
-
installedContent: string,
|
|
441
|
-
packagedContent: string,
|
|
442
|
-
): string {
|
|
443
|
-
if (!ownershipKey.startsWith("agents/")) return packagedContent;
|
|
444
|
-
if (!installedContent.startsWith("---\n")) return packagedContent;
|
|
445
|
-
const endIndex = installedContent.indexOf("\n---", 4);
|
|
446
|
-
if (endIndex === -1) return packagedContent;
|
|
447
|
-
const routingLines = installedContent
|
|
448
|
-
.slice(4, endIndex)
|
|
449
|
-
.split("\n")
|
|
450
|
-
.filter((line) => /^(?:model|thinking):/.test(line));
|
|
451
|
-
return updateAgentFrontmatterRouting(packagedContent, routingLines);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function replaceManagedAssetFileAtomically(path: string, content: string): void {
|
|
455
|
-
const temporaryPath = join(dirname(path), `.${randomUUID()}.tmp`);
|
|
456
|
-
let mode: number | undefined;
|
|
457
|
-
try {
|
|
458
|
-
const stat = lstatSync(path);
|
|
459
|
-
if (stat.isFile()) mode = stat.mode & 0o777;
|
|
460
|
-
} catch (error) {
|
|
461
|
-
if (!isRecord(error) || error.code !== "ENOENT") throw error;
|
|
462
|
-
}
|
|
463
|
-
try {
|
|
464
|
-
writeFileSync(temporaryPath, content, {
|
|
465
|
-
encoding: "utf8",
|
|
466
|
-
flag: "wx",
|
|
467
|
-
...(mode === undefined ? {} : { mode }),
|
|
468
|
-
});
|
|
469
|
-
renameSync(temporaryPath, path);
|
|
470
|
-
} finally {
|
|
471
|
-
try {
|
|
472
|
-
unlinkSync(temporaryPath);
|
|
473
|
-
} catch {
|
|
474
|
-
// A renamed or otherwise inaccessible temporary file needs no further action.
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
export function updatePackageManagedSddAgentOwnership(
|
|
480
|
-
installedPath: string,
|
|
481
|
-
previousContent: string,
|
|
482
|
-
nextContent: string,
|
|
483
|
-
lockOptions?: PackageAssetInstallLockOptions,
|
|
484
|
-
): boolean {
|
|
485
|
-
const agentHome = gentlePiAgentHome();
|
|
486
|
-
const relativePath = relative(join(agentHome, "agents"), installedPath);
|
|
487
|
-
if (
|
|
488
|
-
relativePath.length === 0 ||
|
|
489
|
-
relativePath === ".." ||
|
|
490
|
-
relativePath.startsWith(`..${sep}`) ||
|
|
491
|
-
isAbsolute(relativePath)
|
|
492
|
-
) {
|
|
493
|
-
return false;
|
|
494
|
-
}
|
|
495
|
-
const ownershipKey = `agents/${relativePath.split(sep).join("/")}`;
|
|
496
|
-
return withManagedAssetsLock(agentHome, () => {
|
|
497
|
-
const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
|
|
498
|
-
const manifest = readManagedAssetsManifest(manifestPath);
|
|
499
|
-
if (manifest.assets[ownershipKey] !== managedAssetHash(previousContent)) {
|
|
500
|
-
return false;
|
|
501
|
-
}
|
|
502
|
-
const installedContent = readFileSync(installedPath, "utf8");
|
|
503
|
-
// The next-content branch preserves the prior internal caller contract:
|
|
504
|
-
// callers that wrote the file before this function still receive a managed
|
|
505
|
-
// manifest update. New callers take the previous-content branch below so
|
|
506
|
-
// the file and manifest update share this lock.
|
|
507
|
-
if (installedContent !== nextContent && installedContent !== previousContent) {
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
try {
|
|
511
|
-
if (installedContent === previousContent) {
|
|
512
|
-
replaceManagedAssetFileAtomically(installedPath, nextContent);
|
|
513
|
-
}
|
|
514
|
-
manifest.assets[ownershipKey] = managedAssetHash(nextContent);
|
|
515
|
-
replaceManagedAssetFileAtomically(
|
|
516
|
-
manifestPath,
|
|
517
|
-
JSON.stringify(manifest, null, 2),
|
|
518
|
-
);
|
|
519
|
-
} catch (error) {
|
|
520
|
-
try {
|
|
521
|
-
replaceManagedAssetFileAtomically(installedPath, previousContent);
|
|
522
|
-
} catch (rollbackError) {
|
|
523
|
-
throw new Error(
|
|
524
|
-
`Managed routing update failed and could not restore ${installedPath}: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`,
|
|
525
|
-
{ cause: error },
|
|
526
|
-
);
|
|
527
|
-
}
|
|
528
|
-
throw error;
|
|
529
|
-
}
|
|
530
|
-
return true;
|
|
531
|
-
}, lockOptions);
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
export function hasPackageAssetOwnerInstallation(owner: PackageAssetOwner): boolean {
|
|
535
|
-
const agentHome = gentlePiAgentHome();
|
|
536
|
-
const manifest = readManagedAssetsManifest(join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST));
|
|
537
|
-
return Object.keys(manifest.assets).some((key) => getPackageAssetOwner(key) === owner) ||
|
|
538
|
-
Object.entries(ASSET_OWNER_BY_KEY).some(([key, candidate]) =>
|
|
539
|
-
candidate === owner && existsSync(join(agentHome, key)),
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
export function isPackageManagedSddAsset(
|
|
544
|
-
installedPath: string,
|
|
545
|
-
ownershipKey: string,
|
|
546
|
-
): boolean {
|
|
547
|
-
const manifest = readManagedAssetsManifest(
|
|
548
|
-
join(gentlePiAgentHome(), "gentle-ai", MANAGED_ASSETS_MANIFEST),
|
|
549
|
-
);
|
|
550
|
-
const expectedHash = manifest.assets[ownershipKey];
|
|
551
|
-
if (expectedHash === undefined || !existsSync(installedPath)) return false;
|
|
552
|
-
try {
|
|
553
|
-
return (
|
|
554
|
-
managedAssetHash(readFileSync(installedPath, "utf8")) ===
|
|
555
|
-
expectedHash
|
|
556
|
-
);
|
|
557
|
-
} catch {
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// ---------------------------------------------------------------------------
|
|
563
|
-
// Durable store — survives restarts, resumed sessions, and non-SDD agent starts
|
|
564
|
-
// ---------------------------------------------------------------------------
|
|
565
|
-
|
|
566
|
-
export function sddPreflightDiskPath(cwd: string): string {
|
|
567
|
-
return join(cwd, ".pi", "gentle-ai", "sdd-preflight.json");
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
export function readSddPreflightFromDisk(cwd: string): SddPreflightPreferences | undefined {
|
|
571
|
-
const path = sddPreflightDiskPath(cwd);
|
|
572
|
-
if (!existsSync(path)) return undefined;
|
|
573
|
-
try {
|
|
574
|
-
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
575
|
-
if (!isRecord(parsed)) return undefined;
|
|
576
|
-
// Validate required fields to guard against stale/corrupt writes.
|
|
577
|
-
const { executionMode, artifactStore, chainedPrStrategy, reviewBudgetLines, engramAvailable, prompted } = parsed;
|
|
578
|
-
// Normalize before validating: a preflight file written before the
|
|
579
|
-
// dual-store rename carries "both", and discarding it would silently
|
|
580
|
-
// drop the operator's choice back to the default.
|
|
581
|
-
const canonicalArtifactStore = normalizeSddArtifactStore(artifactStore);
|
|
582
|
-
if (
|
|
583
|
-
!isSddExecutionMode(executionMode) ||
|
|
584
|
-
canonicalArtifactStore === undefined ||
|
|
585
|
-
typeof reviewBudgetLines !== "number" ||
|
|
586
|
-
!Number.isFinite(reviewBudgetLines) ||
|
|
587
|
-
reviewBudgetLines <= 0 ||
|
|
588
|
-
typeof engramAvailable !== "boolean" ||
|
|
589
|
-
typeof prompted !== "boolean"
|
|
590
|
-
) {
|
|
591
|
-
return undefined;
|
|
592
|
-
}
|
|
593
|
-
return {
|
|
594
|
-
executionMode,
|
|
595
|
-
artifactStore: canonicalArtifactStore,
|
|
596
|
-
chainedPrStrategy: normalizeSddChainedPrStrategy(chainedPrStrategy),
|
|
597
|
-
reviewBudgetLines: normalizeReviewBudgetValue(reviewBudgetLines) ?? DEFAULT_SDD_PREFLIGHT.reviewBudgetLines,
|
|
598
|
-
engramAvailable,
|
|
599
|
-
prompted,
|
|
600
|
-
};
|
|
601
|
-
} catch {
|
|
602
|
-
return undefined;
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
export function writeSddPreflightToDisk(cwd: string, prefs: SddPreflightPreferences): void {
|
|
607
|
-
try {
|
|
608
|
-
const path = sddPreflightDiskPath(cwd);
|
|
609
|
-
const prompted = prefs.prompted === true;
|
|
610
|
-
const canonical: SddPreflightPreferences = {
|
|
611
|
-
executionMode: isSddExecutionMode(prefs.executionMode) ? prefs.executionMode : DEFAULT_SDD_PREFLIGHT.executionMode,
|
|
612
|
-
artifactStore: normalizeSddArtifactStore(prefs.artifactStore) ?? DEFAULT_SDD_PREFLIGHT.artifactStore,
|
|
613
|
-
chainedPrStrategy: normalizeSddChainedPrStrategy(prefs.chainedPrStrategy),
|
|
614
|
-
reviewBudgetLines:
|
|
615
|
-
normalizeReviewBudgetValue(prefs.reviewBudgetLines) ??
|
|
616
|
-
DEFAULT_SDD_PREFLIGHT.reviewBudgetLines,
|
|
617
|
-
engramAvailable: prefs.engramAvailable === true,
|
|
618
|
-
prompted,
|
|
619
|
-
};
|
|
620
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
621
|
-
writeFileSync(path, JSON.stringify(canonical, null, 2));
|
|
622
|
-
} catch {
|
|
623
|
-
// Disk write failures are non-fatal; in-memory cache is the primary store
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
function copyDirectoryFiles(
|
|
628
|
-
sourceDir: string,
|
|
629
|
-
targetDir: string,
|
|
630
|
-
ownershipPrefix: string,
|
|
631
|
-
force: boolean,
|
|
632
|
-
manifest: ManagedAssetsManifest,
|
|
633
|
-
legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined,
|
|
634
|
-
selected?: ReadonlySet<string>,
|
|
635
|
-
): { copied: number; skipped: number } {
|
|
636
|
-
if (!existsSync(sourceDir)) return { copied: 0, skipped: 0 };
|
|
637
|
-
if (selected && ![...selected].some(key => key.startsWith(`${ownershipPrefix}/`))) {
|
|
638
|
-
return { copied: 0, skipped: 0 };
|
|
639
|
-
}
|
|
640
|
-
mkdirSync(targetDir, { recursive: true });
|
|
641
|
-
let copied = 0;
|
|
642
|
-
let skipped = 0;
|
|
643
|
-
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
644
|
-
const sourcePath = join(sourceDir, entry.name);
|
|
645
|
-
const targetPath = join(targetDir, entry.name);
|
|
646
|
-
const ownershipKey = `${ownershipPrefix}/${entry.name}`;
|
|
647
|
-
if (entry.isDirectory()) {
|
|
648
|
-
const child = copyDirectoryFiles(
|
|
649
|
-
sourcePath,
|
|
650
|
-
targetPath,
|
|
651
|
-
ownershipKey,
|
|
652
|
-
force,
|
|
653
|
-
manifest,
|
|
654
|
-
legacyAssetHashes,
|
|
655
|
-
selected,
|
|
656
|
-
);
|
|
657
|
-
copied += child.copied;
|
|
658
|
-
skipped += child.skipped;
|
|
659
|
-
continue;
|
|
660
|
-
}
|
|
661
|
-
if (!entry.isFile() || (selected && !selected.has(ownershipKey))) continue;
|
|
662
|
-
const source = readFileSync(sourcePath, "utf8");
|
|
663
|
-
let nextSource = source;
|
|
664
|
-
if (existsSync(targetPath)) {
|
|
665
|
-
if (!force) {
|
|
666
|
-
skipped += 1;
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
const managedHash = manifest.assets[ownershipKey];
|
|
670
|
-
let installedContent: string | undefined;
|
|
671
|
-
try {
|
|
672
|
-
installedContent = readFileSync(targetPath, "utf8");
|
|
673
|
-
} catch {
|
|
674
|
-
installedContent = undefined;
|
|
675
|
-
}
|
|
676
|
-
const installedHash = installedContent === undefined
|
|
677
|
-
? undefined
|
|
678
|
-
: managedAssetHash(installedContent);
|
|
679
|
-
if (managedHash === undefined) {
|
|
680
|
-
const legacyHashes = legacyAssetHashes?.()[ownershipKey];
|
|
681
|
-
const comparableLegacyHash = installedContent === undefined
|
|
682
|
-
? undefined
|
|
683
|
-
: managedAssetHash(
|
|
684
|
-
legacyComparableAssetContent(ownershipKey, installedContent),
|
|
685
|
-
);
|
|
686
|
-
if (
|
|
687
|
-
legacyHashes === undefined ||
|
|
688
|
-
comparableLegacyHash === undefined ||
|
|
689
|
-
!legacyHashes.includes(comparableLegacyHash)
|
|
690
|
-
) {
|
|
691
|
-
delete manifest.assets[ownershipKey];
|
|
692
|
-
skipped += 1;
|
|
693
|
-
continue;
|
|
694
|
-
}
|
|
695
|
-
nextSource = migrateLegacyAssetContent(
|
|
696
|
-
ownershipKey,
|
|
697
|
-
installedContent,
|
|
698
|
-
source,
|
|
699
|
-
);
|
|
700
|
-
} else if (installedHash !== managedHash) {
|
|
701
|
-
delete manifest.assets[ownershipKey];
|
|
702
|
-
skipped += 1;
|
|
703
|
-
continue;
|
|
704
|
-
} else if (ownershipKey === "agents/sdd-research.md" && installedContent !== undefined) {
|
|
705
|
-
// Keep routing adopted by the legacy migration on subsequent refreshes.
|
|
706
|
-
nextSource = migrateLegacyAssetContent(ownershipKey, installedContent, source);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
writeFileSync(targetPath, nextSource);
|
|
710
|
-
manifest.assets[ownershipKey] = managedAssetHash(nextSource);
|
|
711
|
-
copied += 1;
|
|
712
|
-
}
|
|
713
|
-
return { copied, skipped };
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
// Assets retired by gentle-pi#311 P5: the Pi-owned adversarial review actors.
|
|
717
|
-
// The refuter and validator verdicts now execute through Go-owned pi
|
|
718
|
-
// processes via provider-rendered self-contained vectors, so these agent
|
|
719
|
-
// definitions have no runtime consumer. The migration manifests under
|
|
720
|
-
// assets/migrations are append-only legacy-hash HISTORY (adoption evidence
|
|
721
|
-
// for force-installs) with no removal semantics, so history stays untouched
|
|
722
|
-
// and retirement happens here: an installed copy is deleted only when its
|
|
723
|
-
// content hash proves package ownership (current manifest or legacy
|
|
724
|
-
// history); user-modified copies are left in place and only lose managed
|
|
725
|
-
// ownership.
|
|
726
|
-
const RETIRED_MANAGED_ASSETS = Object.freeze([
|
|
727
|
-
// Canonical spec composition now belongs to archive (gentle-pi#1051).
|
|
728
|
-
"agents/sdd-sync.md",
|
|
729
|
-
"agents/review-refuter.md",
|
|
730
|
-
"agents/review-validator.md",
|
|
731
|
-
]);
|
|
732
|
-
|
|
733
|
-
function removeRetiredManagedAssets(
|
|
734
|
-
agentHome: string,
|
|
735
|
-
manifest: ManagedAssetsManifest,
|
|
736
|
-
selected?: ReadonlySet<string>,
|
|
737
|
-
): void {
|
|
738
|
-
let legacyHashes: Record<string, readonly string[]> | undefined;
|
|
739
|
-
for (const ownershipKey of RETIRED_MANAGED_ASSETS) {
|
|
740
|
-
if (selected && !selected.has(ownershipKey)) continue;
|
|
741
|
-
const installedPath = join(agentHome, ...ownershipKey.split("/"));
|
|
742
|
-
if (!existsSync(installedPath)) {
|
|
743
|
-
delete manifest.assets[ownershipKey];
|
|
744
|
-
continue;
|
|
745
|
-
}
|
|
746
|
-
let installedContent: string | undefined;
|
|
747
|
-
try {
|
|
748
|
-
installedContent = readFileSync(installedPath, "utf8");
|
|
749
|
-
} catch {
|
|
750
|
-
installedContent = undefined;
|
|
751
|
-
}
|
|
752
|
-
if (installedContent === undefined) continue;
|
|
753
|
-
const installedHash = managedAssetHash(installedContent);
|
|
754
|
-
const managed = manifest.assets[ownershipKey] === installedHash;
|
|
755
|
-
const legacy = (legacyHashes ??= readLegacyManagedAssetHashes())[ownershipKey]?.includes(
|
|
756
|
-
managedAssetHash(legacyComparableAssetContent(ownershipKey, installedContent)),
|
|
757
|
-
) === true;
|
|
758
|
-
if (managed || legacy) {
|
|
759
|
-
try {
|
|
760
|
-
rmSync(installedPath);
|
|
761
|
-
} catch {
|
|
762
|
-
continue;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
// Managed copies are gone; user-modified copies stay but stop being
|
|
766
|
-
// package-managed either way.
|
|
767
|
-
delete manifest.assets[ownershipKey];
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Legacy all-owner entry point retained for compatibility.
|
|
772
|
-
// Owner-specific commands and SDD preflight use installPackageAssets directly.
|
|
773
|
-
export function installSddAssets(
|
|
774
|
-
cwd: string,
|
|
775
|
-
force: boolean,
|
|
776
|
-
): { agents: number; chains: number; support: number; skipped: number } {
|
|
777
|
-
return installPackageAssets(cwd, force);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
export function installPackageAssets(
|
|
781
|
-
_cwd: string,
|
|
782
|
-
force: boolean,
|
|
783
|
-
owners?: readonly PackageAssetOwner[],
|
|
784
|
-
lockOptions?: PackageAssetInstallLockOptions,
|
|
785
|
-
): { agents: number; chains: number; support: number; skipped: number } {
|
|
786
|
-
const agentHome = gentlePiAgentHome();
|
|
787
|
-
return withManagedAssetsLock(agentHome, () => {
|
|
788
|
-
const selected = owners === undefined ? undefined : new Set(
|
|
789
|
-
Object.entries(ASSET_OWNER_BY_KEY).filter(([, owner]) => owners.includes(owner)).map(([key]) => key),
|
|
790
|
-
);
|
|
791
|
-
const manifestPath = join(agentHome, "gentle-ai", MANAGED_ASSETS_MANIFEST);
|
|
792
|
-
let legacyAssetHashes: (() => Readonly<Record<string, readonly string[]>>) | undefined;
|
|
793
|
-
if (force) {
|
|
794
|
-
let cachedLegacyAssetHashes: Record<string, readonly string[]> | undefined;
|
|
795
|
-
legacyAssetHashes = () =>
|
|
796
|
-
(cachedLegacyAssetHashes ??= readLegacyManagedAssetHashes());
|
|
797
|
-
}
|
|
798
|
-
const manifest = readManagedAssetsManifest(manifestPath);
|
|
799
|
-
removeRetiredManagedAssets(agentHome, manifest, selected);
|
|
800
|
-
const agents = copyDirectoryFiles(
|
|
801
|
-
join(ASSETS_DIR, "agents"),
|
|
802
|
-
join(agentHome, "agents"),
|
|
803
|
-
"agents",
|
|
804
|
-
force,
|
|
805
|
-
manifest,
|
|
806
|
-
legacyAssetHashes,
|
|
807
|
-
selected,
|
|
808
|
-
);
|
|
809
|
-
const chains = copyDirectoryFiles(
|
|
810
|
-
join(ASSETS_DIR, "chains"),
|
|
811
|
-
join(agentHome, "chains"),
|
|
812
|
-
"chains",
|
|
813
|
-
force,
|
|
814
|
-
manifest,
|
|
815
|
-
legacyAssetHashes,
|
|
816
|
-
selected,
|
|
817
|
-
);
|
|
818
|
-
const support = copyDirectoryFiles(
|
|
819
|
-
join(ASSETS_DIR, "support"),
|
|
820
|
-
join(agentHome, "gentle-ai", "support"),
|
|
821
|
-
"gentle-ai/support",
|
|
822
|
-
force,
|
|
823
|
-
manifest,
|
|
824
|
-
legacyAssetHashes,
|
|
825
|
-
selected,
|
|
826
|
-
);
|
|
827
|
-
mkdirSync(dirname(manifestPath), { recursive: true });
|
|
828
|
-
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
829
|
-
return {
|
|
830
|
-
agents: agents.copied,
|
|
831
|
-
chains: chains.copied,
|
|
832
|
-
support: support.copied,
|
|
833
|
-
skipped: agents.skipped + chains.skipped + support.skipped,
|
|
834
|
-
};
|
|
835
|
-
}, lockOptions);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Input interception is intentionally syntax-only. Natural-language SDD intent
|
|
839
|
-
// belongs to the parent/orchestrator; dispatch and before_agent_start gates run
|
|
840
|
-
// or reuse preflight when an SDD action is actually attempted. This keeps mere
|
|
841
|
-
// mentions side-effect free without trying to encode language in a regex.
|
|
842
|
-
export function isSddPreflightTrigger(text: string): boolean {
|
|
843
|
-
return /^\/(?:gentle-)?sdd(?:[-:][^\s]*)?(?:\s|$)/i.test(text.trim());
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
export function sddPreflightSessionKey(ctx: ExtensionContext): string {
|
|
847
|
-
const manager = (ctx as unknown as { sessionManager?: unknown }).sessionManager;
|
|
848
|
-
if (isRecord(manager)) {
|
|
849
|
-
const getSessionFile = manager.getSessionFile;
|
|
850
|
-
if (typeof getSessionFile === "function") {
|
|
851
|
-
const value = getSessionFile.call(manager);
|
|
852
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
853
|
-
}
|
|
854
|
-
const getSessionId = manager.getSessionId;
|
|
855
|
-
if (typeof getSessionId === "function") {
|
|
856
|
-
const value = getSessionId.call(manager);
|
|
857
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
return ctx.cwd;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
function hasWritableEngramTool(pi: ExtensionAPI): boolean {
|
|
864
|
-
try {
|
|
865
|
-
const getActiveTools = (pi as unknown as { getActiveTools?: () => unknown[] })
|
|
866
|
-
.getActiveTools;
|
|
867
|
-
if (typeof getActiveTools !== "function") return false;
|
|
868
|
-
const tools = getActiveTools.call(pi);
|
|
869
|
-
return tools.some((tool) => {
|
|
870
|
-
const name =
|
|
871
|
-
typeof tool === "string"
|
|
872
|
-
? tool
|
|
873
|
-
: isRecord(tool) && typeof tool.name === "string"
|
|
874
|
-
? tool.name
|
|
875
|
-
: "";
|
|
876
|
-
return name === "mem_save" || name.endsWith(".mem_save");
|
|
877
|
-
});
|
|
878
|
-
} catch {
|
|
879
|
-
return false;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
export async function collectSddPreflightPreferences(
|
|
884
|
-
ctx: ExtensionContext,
|
|
885
|
-
engramAvailable: boolean,
|
|
886
|
-
options: SddPreflightResolutionOptions = {},
|
|
887
|
-
): Promise<SddPreflightPreferences> {
|
|
888
|
-
// Disk preferences suggest values; they never carry current-session consent.
|
|
889
|
-
const allowExceptionOk = options.acceptSizeException === true;
|
|
890
|
-
const persisted = normalizedSelections(options.persisted, engramAvailable, allowExceptionOk);
|
|
891
|
-
const resolved: Partial<Record<SddPreflightField, unknown>> = { ...persisted };
|
|
892
|
-
let prompted = false;
|
|
893
|
-
let sizeExceptionAccepted = allowExceptionOk && persisted.chainedPrStrategy === "exception-ok";
|
|
894
|
-
const promptFields = new Set(options.promptFields ?? []);
|
|
895
|
-
// RPC is headless even though Pi exposes functional dialog methods there.
|
|
896
|
-
if (ctx.hasUI && ctx.mode !== "rpc" && promptFields.size === 0) {
|
|
897
|
-
const suggestions = { ...DEFAULT_SDD_PREFLIGHT, ...persisted };
|
|
898
|
-
ctx.ui.notify(`SDD session suggestions: mode=${suggestions.executionMode}; artifacts=${suggestions.artifactStore}; delivery=${suggestions.chainedPrStrategy}; budget=${suggestions.reviewBudgetLines}. Saved preferences are not session consent.`, "info");
|
|
899
|
-
if (typeof ctx.ui.select !== "function") throw new Error("SDD preflight confirmation UI unavailable; no session consent recorded.");
|
|
900
|
-
const answer = await ctx.ui.select("Confirm SDD session preflight", ["Confirm", "Change choices"]);
|
|
901
|
-
if (answer === "Confirm") prompted = true;
|
|
902
|
-
else if (answer === "Change choices") for (const field of SDD_PREFLIGHT_FIELDS) promptFields.add(field);
|
|
903
|
-
else throw new Error("SDD preflight cancelled; no session consent recorded.");
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
const usePromptedValue = (
|
|
907
|
-
field: SddPreflightField,
|
|
908
|
-
value: unknown,
|
|
909
|
-
): void => {
|
|
910
|
-
const candidate = normalizedSelections({ [field]: value }, engramAvailable, allowExceptionOk);
|
|
911
|
-
if (candidate[field] !== undefined) {
|
|
912
|
-
resolved[field] = candidate[field];
|
|
913
|
-
if (field === "chainedPrStrategy" && candidate[field] === "exception-ok") sizeExceptionAccepted = true;
|
|
914
|
-
prompted = true;
|
|
915
|
-
}
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
const promptField = async (
|
|
919
|
-
field: SddPreflightField,
|
|
920
|
-
read: () => Promise<unknown>,
|
|
921
|
-
enabled = true,
|
|
922
|
-
): Promise<void> => {
|
|
923
|
-
if (ctx.hasUI && ctx.mode !== "rpc" && enabled && promptFields.has(field)) {
|
|
924
|
-
const value = await read();
|
|
925
|
-
if (normalizedSelections({ [field]: value }, engramAvailable, allowExceptionOk)[field] === undefined) {
|
|
926
|
-
throw new Error("SDD preflight cancelled or invalid; no session consent recorded.");
|
|
927
|
-
}
|
|
928
|
-
usePromptedValue(field, value);
|
|
929
|
-
}
|
|
930
|
-
};
|
|
931
|
-
const artifactOptions = engramAvailable ? ["openspec", "engram", "hybrid"] : ["openspec"];
|
|
932
|
-
const suggestedFirst = (field: SddPreflightField, values: string[]): string[] => {
|
|
933
|
-
const suggested = String(resolved[field] ?? DEFAULT_SDD_PREFLIGHT[field]);
|
|
934
|
-
return values.includes(suggested) ? [suggested, ...values.filter(value => value !== suggested)] : values;
|
|
935
|
-
};
|
|
936
|
-
await promptField("executionMode", () => ctx.ui.select("SDD execution mode", suggestedFirst("executionMode", ["interactive", "auto"])));
|
|
937
|
-
await promptField("artifactStore", () => ctx.ui.select("SDD artifact store", suggestedFirst("artifactStore", artifactOptions)), artifactOptions.length > 1);
|
|
938
|
-
await promptField("chainedPrStrategy", () => ctx.ui.select("SDD delivery strategy", suggestedFirst("chainedPrStrategy", ["ask-on-risk", "auto-chain", "single-pr"])));
|
|
939
|
-
await promptField("reviewBudgetLines", () => ctx.ui.input("SDD review budget lines", String(resolved.reviewBudgetLines ?? DEFAULT_SDD_PREFLIGHT.reviewBudgetLines)));
|
|
940
|
-
|
|
941
|
-
const resolvedValue = <T>(field: SddPreflightField, fallback: T): T =>
|
|
942
|
-
(resolved[field] as T | undefined) ?? fallback;
|
|
943
|
-
return {
|
|
944
|
-
executionMode: resolvedValue("executionMode", DEFAULT_SDD_PREFLIGHT.executionMode),
|
|
945
|
-
artifactStore: resolvedValue("artifactStore", DEFAULT_SDD_PREFLIGHT.artifactStore),
|
|
946
|
-
chainedPrStrategy: resolvedValue("chainedPrStrategy", DEFAULT_SDD_PREFLIGHT.chainedPrStrategy),
|
|
947
|
-
reviewBudgetLines: resolvedValue("reviewBudgetLines", DEFAULT_SDD_PREFLIGHT.reviewBudgetLines),
|
|
948
|
-
engramAvailable,
|
|
949
|
-
prompted,
|
|
950
|
-
...(sizeExceptionAccepted ? { sizeExceptionAccepted: true as const } : {}),
|
|
951
|
-
};
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
export function isParentConfirmedSddPreflightContext(context: unknown): context is string {
|
|
955
|
-
if (typeof context !== "string") return false;
|
|
956
|
-
return /^## SDD Session Preflight\n(?:These SDD preferences are explicit current-session choices\. Reuse them unless the user explicitly changes them\.|These SDD preferences are canonical defaults or persisted choices\. Treat them as authoritative; do not revisit dependent decisions unless a genuine human-control gate is reached\.)\n- Execution mode: (?:interactive|auto)\n- Artifact store: (?:openspec|engram|hybrid|none)(?: \(Engram unavailable in this session\))?\n- Delivery strategy: (?:ask-on-risk|auto-chain|single-pr|exception-ok)\n- Delivery strategy domain: `ask-on-risk` \| `auto-chain` \| `single-pr` \| `exception-ok`\n- Review budget: [1-9]\d* changed lines \(400 is the canonical threshold unless explicitly changed\)\n- Chain strategy: deferred until chaining is selected\./.test(context);
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
export function extractParentConfirmedSddPreflightContext(context: unknown): string | undefined {
|
|
960
|
-
if (!isParentConfirmedSddPreflightContext(context)) return undefined;
|
|
961
|
-
return context.split("\n\n", 1)[0];
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
export function renderSddPreflightPrompt(prefs: SddPreflightPreferences): string {
|
|
965
|
-
const deliveryStrategy = normalizeSddChainedPrStrategy(
|
|
966
|
-
prefs.chainedPrStrategy,
|
|
967
|
-
prefs.sizeExceptionAccepted === true,
|
|
968
|
-
);
|
|
969
|
-
const sourceLine = prefs.prompted
|
|
970
|
-
? "These SDD preferences are explicit current-session choices. Reuse them unless the user explicitly changes them."
|
|
971
|
-
: "These SDD preferences are canonical defaults or persisted choices. Treat them as authoritative; do not revisit dependent decisions unless a genuine human-control gate is reached.";
|
|
972
|
-
const interactiveRules =
|
|
973
|
-
prefs.executionMode === "interactive"
|
|
974
|
-
? [
|
|
975
|
-
"- Interactive phase gate: complete only the current SDD phase. Do not start the next SDD phase unless the current user turn explicitly approves that next phase.",
|
|
976
|
-
"- In interactive mode, words like `continue`, `dale`, or `go on` approve only the immediate next phase, not all remaining phases.",
|
|
977
|
-
"- Before writing an SDD proposal in interactive mode, offer the user a proposal question round to improve the PRD/proposal by uncovering business rules, implications, impact, edge cases, product tradeoffs, and decision gaps. Prefer 3–5 concrete product questions per round, then summarize assumptions and ask whether the user wants corrections or a second question round. Do not ask about test commands, PR shape, changed-line budget, or other harness mechanics at proposal time unless the user explicitly asks to discuss delivery.",
|
|
978
|
-
]
|
|
979
|
-
: [
|
|
980
|
-
"- Auto mode: phases may run back-to-back only because the user chose speed and trusts the flow.",
|
|
981
|
-
];
|
|
982
|
-
return [
|
|
983
|
-
"## SDD Session Preflight",
|
|
984
|
-
sourceLine,
|
|
985
|
-
`- Execution mode: ${prefs.executionMode}`,
|
|
986
|
-
`- Artifact store: ${prefs.artifactStore}${prefs.engramAvailable ? "" : " (Engram unavailable in this session)"}`,
|
|
987
|
-
`- Delivery strategy: ${deliveryStrategy}`,
|
|
988
|
-
"- Delivery strategy domain: `ask-on-risk` | `auto-chain` | `single-pr` | `exception-ok`",
|
|
989
|
-
`- Review budget: ${prefs.reviewBudgetLines} changed lines (400 is the canonical threshold unless explicitly changed)`,
|
|
990
|
-
"- Chain strategy: deferred until chaining is selected.",
|
|
991
|
-
"- `exception-ok` is never inferred; it requires explicit acceptance of `size:exception`.",
|
|
992
|
-
...interactiveRules,
|
|
993
|
-
"- Preserve human-controlled consent, authorization, security, destructive/publishing, ambiguous-scope, and `size:exception` gates.",
|
|
994
|
-
"- When review-budget risk requires a delivery decision, use `ask-on-risk` to pause and ask; do not invent a chain strategy or an exception.",
|
|
995
|
-
].join("\n");
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
export async function ensureSddPreflight(
|
|
999
|
-
ctx: ExtensionContext,
|
|
1000
|
-
callbacks: SddPreflightCallbacks,
|
|
1001
|
-
resolutionOptions: SddPreflightResolutionOptions = {},
|
|
1002
|
-
): Promise<SddPreflightPreferences> {
|
|
1003
|
-
// `collectSddPreflightPreferences` remains a pure suggestion resolver for
|
|
1004
|
-
// callers that need to render options. Persisting or promoting those options
|
|
1005
|
-
// is parent-only: an RPC child must consume the transported rendered block.
|
|
1006
|
-
if (ctx.mode === "rpc") {
|
|
1007
|
-
throw new Error("SDD preflight must be resolved by the parent; an RPC child cannot originate or persist defaults.");
|
|
1008
|
-
}
|
|
1009
|
-
const sessionKey = sddPreflightSessionKey(ctx);
|
|
1010
|
-
const existing = sddPreflightBySession.get(sessionKey);
|
|
1011
|
-
if (existing && !(resolutionOptions.promptFields?.length ?? 0)) return existing;
|
|
1012
|
-
const inFlight = sddPreflightInFlight.get(sessionKey);
|
|
1013
|
-
if (inFlight && !(resolutionOptions.promptFields?.length ?? 0)) return inFlight;
|
|
1014
|
-
const promise = (async () => {
|
|
1015
|
-
const engramAvailable = hasWritableEngramTool(callbacks.pi);
|
|
1016
|
-
const persisted = resolutionOptions.persisted ?? readSddPreflightFromDisk(ctx.cwd);
|
|
1017
|
-
const prefs = await collectSddPreflightPreferences(ctx, engramAvailable, {
|
|
1018
|
-
...resolutionOptions,
|
|
1019
|
-
persisted,
|
|
1020
|
-
});
|
|
1021
|
-
const result =
|
|
1022
|
-
(await callbacks.installAssets?.(ctx.cwd)) ??
|
|
1023
|
-
installPackageAssets(ctx.cwd, false, ["sdd"]);
|
|
1024
|
-
const modelResult = (await callbacks.applyModelConfig?.(ctx.cwd)) ?? {
|
|
1025
|
-
updated: 0,
|
|
1026
|
-
skipped: 0,
|
|
1027
|
-
};
|
|
1028
|
-
if (ctx.hasUI) {
|
|
1029
|
-
const modelRoutingLine = modelResult.invalidPath
|
|
1030
|
-
? `Model routing skipped: ${modelResult.invalidPath} is invalid JSON or not an object.`
|
|
1031
|
-
: `Model-routed agents updated: ${modelResult.updated}`;
|
|
1032
|
-
ctx.ui.notify(
|
|
1033
|
-
[
|
|
1034
|
-
"Gentle AI SDD preflight complete.",
|
|
1035
|
-
`Mode: ${prefs.executionMode}`,
|
|
1036
|
-
`Artifacts: ${prefs.artifactStore}`,
|
|
1037
|
-
`Delivery strategy: ${prefs.chainedPrStrategy}`,
|
|
1038
|
-
`Review budget: ${prefs.reviewBudgetLines} changed lines`,
|
|
1039
|
-
`Preference source: ${prefs.prompted ? "explicit session choice" : "canonical default or persisted preference"}`,
|
|
1040
|
-
`Global SDD assets ready: ${result.agents} agent(s), ${result.chains} chain(s), ${result.support} support file(s), ${result.skipped} already present.`,
|
|
1041
|
-
modelRoutingLine,
|
|
1042
|
-
].join("\n"),
|
|
1043
|
-
modelResult.invalidPath ? "warning" : "info",
|
|
1044
|
-
);
|
|
1045
|
-
}
|
|
1046
|
-
sddPreflightBySession.set(sessionKey, prefs);
|
|
1047
|
-
writeSddPreflightToDisk(ctx.cwd, prefs);
|
|
1048
|
-
return prefs;
|
|
1049
|
-
})();
|
|
1050
|
-
sddPreflightInFlight.set(sessionKey, promise);
|
|
1051
|
-
try {
|
|
1052
|
-
return await promise;
|
|
1053
|
-
} finally {
|
|
1054
|
-
sddPreflightInFlight.delete(sessionKey);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
export function getSddPreflightPreferences(
|
|
1059
|
-
ctx: ExtensionContext,
|
|
1060
|
-
): SddPreflightPreferences | undefined {
|
|
1061
|
-
const sessionKey = sddPreflightSessionKey(ctx);
|
|
1062
|
-
const cached = sddPreflightBySession.get(sessionKey);
|
|
1063
|
-
if (cached) return cached;
|
|
1064
|
-
// Only ensureSddPreflight may promote disk suggestions to resolved session choices.
|
|
1065
|
-
return undefined;
|
|
1066
|
-
}
|