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
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { execFileSync, type ExecFileSyncOptions } from "node:child_process";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, utimesSync } from "node:fs";
|
|
3
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, utimesSync } from "node:fs";
|
|
4
4
|
import { mkdtempSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { gunzipSync, gzipSync } from "node:zlib";
|
|
8
|
-
import { assertCandidateOwnerParent, createCandidateOwner, prepareCandidateOwnerParent, removeCandidateOwner, samePath, sweepCandidateOwners, type CandidateViewOwner } from "./review-candidate-view-owner.ts";
|
|
8
|
+
import { assertCandidateOwnerParent, createCandidateOwner, PosixCandidateOwnerParentChmodIneffectiveError, PosixCandidateOwnerParentPrivacyError, prepareCandidateOwnerParent, removeCandidateOwner, samePath, sweepCandidateOwners, type CandidateViewOwner } from "./review-candidate-view-owner.ts";
|
|
9
9
|
|
|
10
10
|
const REVIEW_LENS = ["review-risk", "review-resilience", "review-readability", "review-reliability"] as const;
|
|
11
11
|
export type ReviewLens = (typeof REVIEW_LENS)[number];
|
|
@@ -108,6 +108,7 @@ interface CandidateViewRecord {
|
|
|
108
108
|
commonDir: string;
|
|
109
109
|
baseCommit: string;
|
|
110
110
|
baseTree: string;
|
|
111
|
+
providerBaseTree?: string;
|
|
111
112
|
candidateTree: string;
|
|
112
113
|
committedOnly: boolean;
|
|
113
114
|
intendedUntracked?: readonly string[];
|
|
@@ -125,6 +126,7 @@ export interface CandidateView {
|
|
|
125
126
|
contributorRoot: string;
|
|
126
127
|
baseCommit: string;
|
|
127
128
|
baseTree: string;
|
|
129
|
+
providerBaseTree?: string;
|
|
128
130
|
candidateTree: string;
|
|
129
131
|
committedOnly: boolean;
|
|
130
132
|
intendedUntracked?: readonly string[];
|
|
@@ -167,6 +169,7 @@ export interface FrozenCandidateProjection {
|
|
|
167
169
|
contributorRoot: string;
|
|
168
170
|
baseCommit: string;
|
|
169
171
|
baseTree: string;
|
|
172
|
+
providerBaseTree?: string;
|
|
170
173
|
candidateTree: string;
|
|
171
174
|
committedOnly: boolean;
|
|
172
175
|
intendedUntracked?: readonly string[];
|
|
@@ -179,6 +182,8 @@ export interface FrozenCandidateProjection {
|
|
|
179
182
|
export interface CreateCandidateViewRequest {
|
|
180
183
|
contributorRoot: string;
|
|
181
184
|
baseRef?: string;
|
|
185
|
+
/** Provider-validated tree, distinct from caller-supplied commit/ref baseRef. */
|
|
186
|
+
providerBaseTree?: string;
|
|
182
187
|
committedOnly?: boolean;
|
|
183
188
|
/** Undefined keeps legacy all-untracked capture; [] excludes untracked files. */
|
|
184
189
|
intendedUntracked?: readonly string[];
|
|
@@ -196,6 +201,7 @@ export interface AuthoritativeReviewingCandidateState {
|
|
|
196
201
|
contributorRoot: string;
|
|
197
202
|
baseCommit: string;
|
|
198
203
|
baseTree: string;
|
|
204
|
+
providerBaseTree?: string;
|
|
199
205
|
candidateTree: string;
|
|
200
206
|
committedOnly?: boolean;
|
|
201
207
|
intendedUntracked?: readonly string[];
|
|
@@ -226,6 +232,19 @@ export interface NativeCandidateProjectionDescriptor {
|
|
|
226
232
|
providerManifestHashVerified?: true;
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
export interface CandidateOwnerPrivacyDiagnostic {
|
|
236
|
+
code: "candidate-owner-parent-privacy";
|
|
237
|
+
message: typeof CANDIDATE_OWNER_PARENT_PRIVACY_MESSAGE;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export interface CandidateOwnerChmodIneffectiveDiagnostic {
|
|
241
|
+
code: "candidate-owner-parent-chmod-ineffective";
|
|
242
|
+
message: typeof CANDIDATE_OWNER_PARENT_CHMOD_INEFFECTIVE_MESSAGE;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const CANDIDATE_OWNER_PARENT_PRIVACY_MESSAGE = "candidate-views parent must be owned by the current user and inaccessible to group and others; inspect its ownership and permissions, then correct them out of band before retrying START" as const;
|
|
246
|
+
const CANDIDATE_OWNER_PARENT_CHMOD_INEFFECTIVE_MESSAGE = "candidate-views parent filesystem does not honor POSIX permission changes; move this repository's Git common directory to a POSIX-metadata filesystem or enable metadata support before retrying START" as const;
|
|
247
|
+
|
|
229
248
|
export interface CandidateViewDiagnostic {
|
|
230
249
|
phase: typeof CANDIDATE_VIEW_DIAGNOSTIC_PHASE;
|
|
231
250
|
category: CandidateViewGitFailureCategory;
|
|
@@ -237,8 +256,8 @@ export interface CandidateViewDiagnostic {
|
|
|
237
256
|
|
|
238
257
|
export class CandidateViewError extends Error {
|
|
239
258
|
readonly reason: string;
|
|
240
|
-
readonly diagnostics?: CandidateViewDiagnostic;
|
|
241
|
-
constructor(message: string, reason = "candidate-view-invalid", diagnostics?: CandidateViewDiagnostic, options?: ErrorOptions) {
|
|
259
|
+
readonly diagnostics?: CandidateViewDiagnostic | CandidateOwnerPrivacyDiagnostic | CandidateOwnerChmodIneffectiveDiagnostic;
|
|
260
|
+
constructor(message: string, reason = "candidate-view-invalid", diagnostics?: CandidateViewDiagnostic | CandidateOwnerPrivacyDiagnostic | CandidateOwnerChmodIneffectiveDiagnostic, options?: ErrorOptions) {
|
|
242
261
|
super(message, options);
|
|
243
262
|
this.name = "CandidateViewError";
|
|
244
263
|
this.reason = reason;
|
|
@@ -281,7 +300,12 @@ function candidateGitDiagnostic(category: CandidateViewGitFailureCategory, argum
|
|
|
281
300
|
});
|
|
282
301
|
}
|
|
283
302
|
|
|
284
|
-
function sanitizeCandidateViewDiagnostic(diagnostics: CandidateViewDiagnostic): CandidateViewDiagnostic | undefined {
|
|
303
|
+
function sanitizeCandidateViewDiagnostic(diagnostics: CandidateViewDiagnostic | CandidateOwnerPrivacyDiagnostic | CandidateOwnerChmodIneffectiveDiagnostic): CandidateViewDiagnostic | CandidateOwnerPrivacyDiagnostic | CandidateOwnerChmodIneffectiveDiagnostic | undefined {
|
|
304
|
+
if ("code" in diagnostics) {
|
|
305
|
+
if (diagnostics.code === "candidate-owner-parent-privacy" && diagnostics.message === CANDIDATE_OWNER_PARENT_PRIVACY_MESSAGE) return Object.freeze({ code: diagnostics.code, message: CANDIDATE_OWNER_PARENT_PRIVACY_MESSAGE });
|
|
306
|
+
if (diagnostics.code === "candidate-owner-parent-chmod-ineffective" && diagnostics.message === CANDIDATE_OWNER_PARENT_CHMOD_INEFFECTIVE_MESSAGE) return Object.freeze({ code: diagnostics.code, message: CANDIDATE_OWNER_PARENT_CHMOD_INEFFECTIVE_MESSAGE });
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
285
309
|
const { phase, category, git_subcommand, timeout_ms, max_buffer_bytes, message } = diagnostics;
|
|
286
310
|
if (
|
|
287
311
|
phase !== CANDIDATE_VIEW_DIAGNOSTIC_PHASE ||
|
|
@@ -362,6 +386,7 @@ function candidateRecordsShareIdentity(left: CandidateViewRecord, right: Candida
|
|
|
362
386
|
return left.contributorRoot === right.contributorRoot &&
|
|
363
387
|
left.baseCommit === right.baseCommit &&
|
|
364
388
|
left.baseTree === right.baseTree &&
|
|
389
|
+
left.providerBaseTree === right.providerBaseTree &&
|
|
365
390
|
left.candidateTree === right.candidateTree &&
|
|
366
391
|
left.committedOnly === right.committedOnly &&
|
|
367
392
|
JSON.stringify(left.intendedUntracked ?? null) === JSON.stringify(right.intendedUntracked ?? null) &&
|
|
@@ -593,7 +618,9 @@ function entryContentHash(root: string, entry: CandidateTreeEntry): string {
|
|
|
593
618
|
if (entry.mode === "120000") {
|
|
594
619
|
if (!item.isSymbolicLink()) throw new CandidateViewError("candidate view symlink does not match its frozen tree");
|
|
595
620
|
const target = readlinkSync(path, "buffer");
|
|
596
|
-
const
|
|
621
|
+
const nativeBytes = Buffer.isBuffer(target) ? target : Buffer.from(target);
|
|
622
|
+
// Windows readlink may spell a relative Git target with native separators.
|
|
623
|
+
const bytes = process.platform === "win32" ? Buffer.from(nativeBytes.toString("utf8").replaceAll("\\", "/")) : nativeBytes;
|
|
597
624
|
assertSafeSymlinkTarget(root, entry.path, bytes);
|
|
598
625
|
return createHash("sha256").update(bytes).digest("hex");
|
|
599
626
|
}
|
|
@@ -638,7 +665,13 @@ function candidateOwnerPreparationError(error: unknown): CandidateViewError {
|
|
|
638
665
|
// Surface the bounded errno of the underlying failure directly in the
|
|
639
666
|
// message so CI logs are self-diagnosing without TAP printing the cause.
|
|
640
667
|
const code = (error as NodeJS.ErrnoException | undefined)?.code;
|
|
641
|
-
|
|
668
|
+
const safeCode = typeof code === "string" && /^(?:EACCES|EPERM|ENOENT|EIO|ETIMEDOUT)$/.test(code) ? code : undefined;
|
|
669
|
+
const diagnostics = error instanceof PosixCandidateOwnerParentPrivacyError
|
|
670
|
+
? { code: "candidate-owner-parent-privacy" as const, message: CANDIDATE_OWNER_PARENT_PRIVACY_MESSAGE }
|
|
671
|
+
: error instanceof PosixCandidateOwnerParentChmodIneffectiveError
|
|
672
|
+
? { code: "candidate-owner-parent-chmod-ineffective" as const, message: CANDIDATE_OWNER_PARENT_CHMOD_INEFFECTIVE_MESSAGE }
|
|
673
|
+
: undefined;
|
|
674
|
+
return new CandidateViewError(`candidate view owner preparation failed${safeCode ? ` (${safeCode})` : ""}`, "candidate-owner-preparation-failed", diagnostics, { cause: error });
|
|
642
675
|
}
|
|
643
676
|
|
|
644
677
|
function candidateViewParent(commonDir: string, platform: NodeJS.Platform): string {
|
|
@@ -789,6 +822,36 @@ export function resolveCanonicalCandidateBase(contributorRoot: string, baseRef:
|
|
|
789
822
|
return resolveCandidateBase(realpathSync(contributorRoot), baseRef, process.env, defaultCandidateGitExecutor);
|
|
790
823
|
}
|
|
791
824
|
|
|
825
|
+
/** Classifies a full provider-owned object id without resolving it as a caller commit/ref. */
|
|
826
|
+
export function isProviderCandidateBaseTree(contributorRoot: string, objectId: string): boolean {
|
|
827
|
+
if (!isFullCommitId(objectId)) return false;
|
|
828
|
+
const cwd = realpathSync(contributorRoot);
|
|
829
|
+
const probe = probeCandidateGit(cwd, ["cat-file", "-t", objectId], process.env, defaultCandidateGitExecutor);
|
|
830
|
+
return probe.status === 0 && probe.stdout === "tree";
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function materializeSymlinkEntries(root: string, entries: readonly CandidateTreeEntry[], executor: CandidateGitExecutor): ReadonlyMap<string, string> {
|
|
834
|
+
// Validate every original blob before any symlink is created. In particular,
|
|
835
|
+
// never use Git's core.symlinks-dependent regular-file checkout for mode 120000.
|
|
836
|
+
const links = entries.filter((entry) => entry.mode === "120000").map((entry) => {
|
|
837
|
+
const bytes = candidateGit(root, ["cat-file", "blob", entry.objectId], process.env, "buffer", executor) as Buffer;
|
|
838
|
+
assertSafeSymlinkTarget(root, entry.path, bytes);
|
|
839
|
+
return { path: entry.path, target: bytes.toString("utf8") };
|
|
840
|
+
});
|
|
841
|
+
for (const link of links) {
|
|
842
|
+
const path = join(root, link.path);
|
|
843
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
844
|
+
try {
|
|
845
|
+
const targetPath = resolve(dirname(path), link.target);
|
|
846
|
+
const targetType = process.platform === "win32" && lstatSync(targetPath, { throwIfNoEntry: false })?.isDirectory() ? "dir" : "file";
|
|
847
|
+
symlinkSync(link.target, path, targetType);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
throw new CandidateViewError("candidate view cannot create native symlinks; enable symlink capability out of band before retrying START", "symlink-materialization-failed", undefined, { cause: error });
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return new Map(links.map((link) => [link.path, createHash("sha256").update(Buffer.from(link.target, "utf8")).digest("hex")]));
|
|
853
|
+
}
|
|
854
|
+
|
|
792
855
|
function checkoutMaterializedEntries(root: string, entries: readonly CandidateTreeEntry[], executor: CandidateGitExecutor): void {
|
|
793
856
|
let batch: string[] = [];
|
|
794
857
|
let bytes = 0;
|
|
@@ -798,6 +861,7 @@ function checkoutMaterializedEntries(root: string, entries: readonly CandidateTr
|
|
|
798
861
|
batch = []; bytes = 0;
|
|
799
862
|
};
|
|
800
863
|
for (const entry of entries) {
|
|
864
|
+
if (entry.mode === "120000") continue;
|
|
801
865
|
const size = Buffer.byteLength(entry.path, "utf8") + 1;
|
|
802
866
|
if (batch.length > 0 && bytes + size > 16_384) flush();
|
|
803
867
|
batch.push(entry.path); bytes += size;
|
|
@@ -885,10 +949,16 @@ function seedPrivateIndexFromLiveIndex(cwd: string, indexPath: string, executor:
|
|
|
885
949
|
function materializeCandidateView(request: CreateCandidateViewRequest, executor: CandidateGitExecutor, platform: NodeJS.Platform = process.platform): CandidateViewRecord {
|
|
886
950
|
const contributorRoot = realpathSync(request.contributorRoot);
|
|
887
951
|
if (!lstatSync(contributorRoot).isDirectory()) throw new CandidateViewError("contributor root is not a directory");
|
|
888
|
-
if (request.committedOnly === true && request.baseRef === undefined) throw new CandidateViewError("committed-only candidate views require an explicit base reference", "committed-only-base-required");
|
|
952
|
+
if (request.committedOnly === true && request.baseRef === undefined && request.providerBaseTree === undefined) throw new CandidateViewError("committed-only candidate views require an explicit base reference", "committed-only-base-required");
|
|
953
|
+
if (request.providerBaseTree !== undefined && (request.baseRef !== undefined || request.committedOnly !== true || !isFullCommitId(request.providerBaseTree))) throw new CandidateViewError("provider candidate base tree is invalid", "provider-base-tree-invalid");
|
|
889
954
|
const commonDir = resolve(contributorRoot, git(contributorRoot, ["rev-parse", "--git-common-dir"], process.env, executor));
|
|
890
955
|
const canonicalCommonDir = realpathSync(commonDir);
|
|
891
|
-
|
|
956
|
+
// A provider tree is a diff base, not a commit selector. Anchor the private
|
|
957
|
+
// worktree to HEAD without searching for a commit with a matching tree.
|
|
958
|
+
const anchor = resolveCandidateBase(contributorRoot, request.providerBaseTree === undefined ? request.baseRef : "HEAD", process.env, executor);
|
|
959
|
+
const providerBaseTree = request.providerBaseTree;
|
|
960
|
+
if (providerBaseTree !== undefined && (git(contributorRoot, ["cat-file", "-t", providerBaseTree], process.env, executor) !== "tree" || providerBaseTree.length !== anchor.tree.length)) throw new CandidateViewError("provider candidate base is not a tree", "provider-base-tree-invalid");
|
|
961
|
+
const base = providerBaseTree === undefined ? anchor : { commit: anchor.commit, tree: providerBaseTree };
|
|
892
962
|
const committedOnly = request.committedOnly === true;
|
|
893
963
|
const intendedUntracked = normalizeIntendedUntracked(request.intendedUntracked);
|
|
894
964
|
const candidateCommit = committedOnly
|
|
@@ -941,11 +1011,16 @@ function materializeCandidateView(request: CreateCandidateViewRequest, executor:
|
|
|
941
1011
|
git(root, ["read-tree", candidateTree], process.env, executor);
|
|
942
1012
|
const tree = parseTree(root, candidateTree, executor);
|
|
943
1013
|
checkoutMaterializedEntries(root, tree.entries, executor);
|
|
944
|
-
const
|
|
1014
|
+
const symlinkHashes = materializeSymlinkEntries(root, tree.entries, executor);
|
|
1015
|
+
const entries = tree.entries.map((entry) => {
|
|
1016
|
+
const contentHash = entryContentHash(root, entry);
|
|
1017
|
+
if (entry.mode === "120000" && contentHash !== symlinkHashes.get(entry.path)) throw new CandidateViewError("candidate view symlink does not match its frozen blob");
|
|
1018
|
+
return { ...entry, contentHash };
|
|
1019
|
+
});
|
|
945
1020
|
const scope = deriveChangedScope(contributorRoot, base.tree, candidateTree, [...tree.entries, ...tree.gitlinks], executor);
|
|
946
1021
|
for (const gitlink of tree.gitlinks) if (lstatSync(join(root, gitlink.path), { throwIfNoEntry: false })) throw new CandidateViewError("candidate view materialized a metadata-only gitlink");
|
|
947
1022
|
makeReadonly(root, entries);
|
|
948
|
-
return { owner, token: basename(root), root: realpathSync(root), parent, contributorRoot, commonDir: canonicalCommonDir, baseCommit, baseTree: base.tree, candidateTree, committedOnly, intendedUntracked, entries, gitlinks: tree.gitlinks, scope, gitExecutor: executor };
|
|
1023
|
+
return { owner, token: basename(root), root: realpathSync(root), parent, contributorRoot, commonDir: canonicalCommonDir, baseCommit, baseTree: base.tree, ...(providerBaseTree === undefined ? {} : { providerBaseTree }), candidateTree, committedOnly, intendedUntracked, entries, gitlinks: tree.gitlinks, scope, gitExecutor: executor };
|
|
949
1024
|
} catch (error) {
|
|
950
1025
|
try { removeCandidateOwner(owner, (args) => git(canonicalCommonDir, args, process.env, executor), makeWritableForCleanup, false, platform); } catch { /* Preserve the marker and any partial worktree for conservative recovery. */ }
|
|
951
1026
|
throw error;
|
|
@@ -1192,7 +1267,8 @@ export class CandidateViewRegistry {
|
|
|
1192
1267
|
if (this.current.has(root)) throw new CandidateViewError("candidate view already has a current lineage binding", "current-binding-already-established");
|
|
1193
1268
|
if (states.length === 0) throw new CandidateViewError("no authoritative reviewing lineage exactly matches the live candidate", "authoritative-current-match-missing");
|
|
1194
1269
|
if (states.length !== 1) throw new CandidateViewError("multiple authoritative reviewing lineages exactly match the live candidate", "authoritative-current-match-ambiguous");
|
|
1195
|
-
const
|
|
1270
|
+
const state = states[0]!;
|
|
1271
|
+
const live = materializeCandidateView({ contributorRoot: root, ...(state.providerBaseTree === undefined ? { baseRef: state.baseCommit } : { providerBaseTree: state.providerBaseTree }), committedOnly: state.committedOnly === true, ...(state.intendedUntracked === undefined ? {} : { intendedUntracked: state.intendedUntracked }) }, this.gitExecutor, this.platform);
|
|
1196
1272
|
try {
|
|
1197
1273
|
const matches = states.filter((state) => this.matchesAuthoritativeState(live, state));
|
|
1198
1274
|
if (matches.length === 0) throw new CandidateViewError("no authoritative reviewing lineage exactly matches the live candidate", "authoritative-current-match-missing");
|
|
@@ -1220,7 +1296,7 @@ export class CandidateViewRegistry {
|
|
|
1220
1296
|
assertRecordSafe(existing, this.platform);
|
|
1221
1297
|
return this.expose(existing);
|
|
1222
1298
|
}
|
|
1223
|
-
const record = materializeCandidateView({ contributorRoot: root, baseRef: projection.baseCommit, committedOnly: projection.committedOnly, ...(projection.intendedUntracked === undefined ? {} : { intendedUntracked: projection.intendedUntracked }) }, this.gitExecutor, this.platform);
|
|
1299
|
+
const record = materializeCandidateView({ contributorRoot: root, ...(projection.providerBaseTree === undefined ? { baseRef: projection.baseCommit } : { providerBaseTree: projection.providerBaseTree }), committedOnly: projection.committedOnly, ...(projection.intendedUntracked === undefined ? {} : { intendedUntracked: projection.intendedUntracked }) }, this.gitExecutor, this.platform);
|
|
1224
1300
|
try {
|
|
1225
1301
|
if (record.baseCommit !== projection.baseCommit || record.baseTree !== projection.baseTree) throw new CandidateViewError("corrected candidate base does not match the frozen genesis base");
|
|
1226
1302
|
if (!record.scope.paths.every((path) => projection.paths.includes(path))) throw new CandidateViewError("corrected candidate scope escapes the frozen genesis paths");
|
|
@@ -1253,6 +1329,7 @@ export class CandidateViewRegistry {
|
|
|
1253
1329
|
replacement.contributorRoot !== projection.contributorRoot ||
|
|
1254
1330
|
replacement.baseCommit !== projection.baseCommit ||
|
|
1255
1331
|
replacement.baseTree !== projection.baseTree ||
|
|
1332
|
+
replacement.providerBaseTree !== projection.providerBaseTree ||
|
|
1256
1333
|
replacement.committedOnly !== projection.committedOnly ||
|
|
1257
1334
|
JSON.stringify(replacement.intendedUntracked ?? null) !== JSON.stringify(projection.intendedUntracked ?? null) ||
|
|
1258
1335
|
!replacement.scope.paths.every((path) => projection.paths.includes(path))
|
|
@@ -1267,6 +1344,7 @@ export class CandidateViewRegistry {
|
|
|
1267
1344
|
contributorRoot: replacement.contributorRoot,
|
|
1268
1345
|
baseCommit: replacement.baseCommit,
|
|
1269
1346
|
baseTree: replacement.baseTree,
|
|
1347
|
+
...(replacement.providerBaseTree === undefined ? {} : { providerBaseTree: replacement.providerBaseTree }),
|
|
1270
1348
|
candidateTree: replacement.candidateTree,
|
|
1271
1349
|
committedOnly: replacement.committedOnly,
|
|
1272
1350
|
intendedUntracked: replacement.intendedUntracked,
|
|
@@ -1293,6 +1371,7 @@ export class CandidateViewRegistry {
|
|
|
1293
1371
|
return realpathSync(state.contributorRoot) === record.contributorRoot &&
|
|
1294
1372
|
state.baseCommit === record.baseCommit &&
|
|
1295
1373
|
state.baseTree === record.baseTree &&
|
|
1374
|
+
state.providerBaseTree === record.providerBaseTree &&
|
|
1296
1375
|
state.candidateTree === record.candidateTree &&
|
|
1297
1376
|
(state.committedOnly ?? false) === record.committedOnly &&
|
|
1298
1377
|
(state.intendedUntracked === undefined || JSON.stringify(state.intendedUntracked) === JSON.stringify(record.intendedUntracked)) &&
|
|
@@ -1317,6 +1396,7 @@ export class CandidateViewRegistry {
|
|
|
1317
1396
|
contributorRoot: record.contributorRoot,
|
|
1318
1397
|
baseCommit: record.baseCommit,
|
|
1319
1398
|
baseTree: record.baseTree,
|
|
1399
|
+
...(record.providerBaseTree === undefined ? {} : { providerBaseTree: record.providerBaseTree }),
|
|
1320
1400
|
candidateTree: record.candidateTree,
|
|
1321
1401
|
committedOnly: record.committedOnly,
|
|
1322
1402
|
intendedUntracked: record.intendedUntracked,
|
|
@@ -1341,15 +1421,17 @@ export class CandidateViewRegistry {
|
|
|
1341
1421
|
this.projections.set(key, { contributorRoot: root, baseCommit, baseTree, candidateTree, committedOnly: false, paths: [...paths], modes: {}, gitlinks: {}, deletedPaths: [] });
|
|
1342
1422
|
}
|
|
1343
1423
|
|
|
1344
|
-
restoreProjectionFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor): void {
|
|
1424
|
+
restoreProjectionFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor, providerBaseTree?: string): void {
|
|
1345
1425
|
const root = this.canonicalRoot(contributorRoot);
|
|
1346
1426
|
const key = this.lineageKey(root, lineageId);
|
|
1347
1427
|
if (!lineageId || this.projections.has(key) || !isFullCommitId(descriptor.baseTree) || !isFullCommitId(descriptor.currentCandidateTree)) throw new CandidateViewError("native frozen projection is invalid or already restored");
|
|
1348
1428
|
if (descriptor.paths.some((path) => !isSafeCandidatePath(path)) || new Set(descriptor.paths).size !== descriptor.paths.length) throw new CandidateViewError("native frozen projection paths are invalid");
|
|
1349
1429
|
if (descriptor.intendedUntracked.some((path) => !descriptor.paths.includes(path)) || new Set(descriptor.intendedUntracked).size !== descriptor.intendedUntracked.length) throw new CandidateViewError("native intended-untracked projection is invalid");
|
|
1350
1430
|
const head = resolveCandidateBase(root, "HEAD", process.env, this.gitExecutor);
|
|
1351
|
-
|
|
1431
|
+
if (providerBaseTree !== undefined && providerBaseTree !== descriptor.baseTree) throw new CandidateViewError("provider base tree does not match the native frozen projection", "provider-base-tree-invalid");
|
|
1432
|
+
const base = head.tree === descriptor.baseTree ? head : providerBaseTree === undefined ? resolveCandidateBaseTree(root, descriptor.baseTree, this.gitExecutor) : { commit: head.commit, tree: providerBaseTree };
|
|
1352
1433
|
const committedOnly = head.tree === descriptor.currentCandidateTree && base.tree !== head.tree;
|
|
1434
|
+
if (providerBaseTree !== undefined && !committedOnly) throw new CandidateViewError("provider base tree requires a committed frozen projection", "provider-base-tree-invalid");
|
|
1353
1435
|
if (!committedOnly && head.tree !== descriptor.baseTree) throw new CandidateViewError("native projection base no longer matches HEAD");
|
|
1354
1436
|
// Native `staged` covers both a committed HEAD range and the exact current
|
|
1355
1437
|
// index over HEAD. Re-derive the latter from Git instead of trusting the
|
|
@@ -1377,6 +1459,7 @@ export class CandidateViewRegistry {
|
|
|
1377
1459
|
contributorRoot: root,
|
|
1378
1460
|
baseCommit: base.commit,
|
|
1379
1461
|
baseTree: descriptor.baseTree,
|
|
1462
|
+
...(providerBaseTree === undefined ? {} : { providerBaseTree }),
|
|
1380
1463
|
candidateTree: descriptor.currentCandidateTree,
|
|
1381
1464
|
committedOnly,
|
|
1382
1465
|
intendedUntracked: Object.freeze([...descriptor.intendedUntracked]),
|
|
@@ -1407,15 +1490,15 @@ export class CandidateViewRegistry {
|
|
|
1407
1490
|
* here on purpose — the lenses that consumed it finished before the
|
|
1408
1491
|
* correction.
|
|
1409
1492
|
*/
|
|
1410
|
-
rebindForFinalizeFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor): CandidateView {
|
|
1411
|
-
return this.restoreForFinalizeFromNative(lineageId, contributorRoot, descriptor);
|
|
1493
|
+
rebindForFinalizeFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor, providerBaseTree?: string): CandidateView {
|
|
1494
|
+
return this.restoreForFinalizeFromNative(lineageId, contributorRoot, descriptor, providerBaseTree);
|
|
1412
1495
|
}
|
|
1413
1496
|
|
|
1414
1497
|
/**
|
|
1415
1498
|
* Restores a lineage's FINALIZE binding (gentle-pi #185): the stale entry
|
|
1416
1499
|
* is only detached, not destroyed, so a failed restore can undo it.
|
|
1417
1500
|
*/
|
|
1418
|
-
restoreForFinalizeFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor): CandidateView {
|
|
1501
|
+
restoreForFinalizeFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor, providerBaseTree?: string): CandidateView {
|
|
1419
1502
|
const root = this.canonicalRoot(contributorRoot);
|
|
1420
1503
|
const key = this.lineageKey(root, lineageId);
|
|
1421
1504
|
const staleToken = this.lineages.get(key), staleProjection = this.projections.get(key);
|
|
@@ -1423,16 +1506,16 @@ export class CandidateViewRegistry {
|
|
|
1423
1506
|
let projectionRestored = false;
|
|
1424
1507
|
let record: CandidateViewRecord | undefined;
|
|
1425
1508
|
try {
|
|
1426
|
-
this.restoreProjectionFromNative(lineageId, root, descriptor);
|
|
1509
|
+
this.restoreProjectionFromNative(lineageId, root, descriptor, providerBaseTree);
|
|
1427
1510
|
projectionRestored = true;
|
|
1428
1511
|
const projection = this.resolveProjection(lineageId, root);
|
|
1429
1512
|
const matchesProjection = (candidate: CandidateViewRecord): boolean => candidate.baseTree === projection.baseTree && candidate.candidateTree === projection.candidateTree && JSON.stringify(candidate.scope.paths) === JSON.stringify(projection.paths);
|
|
1430
1513
|
const emptyIntendedUntracked = projection.intendedUntracked?.length === 0;
|
|
1431
|
-
record = materializeCandidateView({ contributorRoot: root, baseRef: projection.baseCommit, committedOnly: projection.committedOnly, ...(!emptyIntendedUntracked && projection.intendedUntracked !== undefined ? { intendedUntracked: projection.intendedUntracked } : {}) }, this.gitExecutor, this.platform);
|
|
1514
|
+
record = materializeCandidateView({ contributorRoot: root, ...(projection.providerBaseTree === undefined ? { baseRef: projection.baseCommit } : { providerBaseTree: projection.providerBaseTree }), committedOnly: projection.committedOnly, ...(!emptyIntendedUntracked && projection.intendedUntracked !== undefined ? { intendedUntracked: projection.intendedUntracked } : {}) }, this.gitExecutor, this.platform);
|
|
1432
1515
|
if (!matchesProjection(record) && emptyIntendedUntracked) {
|
|
1433
1516
|
this.remove(record);
|
|
1434
1517
|
record = undefined;
|
|
1435
|
-
record = materializeCandidateView({ contributorRoot: root, baseRef: projection.baseCommit, committedOnly: projection.committedOnly, intendedUntracked: [] }, this.gitExecutor, this.platform);
|
|
1518
|
+
record = materializeCandidateView({ contributorRoot: root, ...(projection.providerBaseTree === undefined ? { baseRef: projection.baseCommit } : { providerBaseTree: projection.providerBaseTree }), committedOnly: projection.committedOnly, intendedUntracked: [] }, this.gitExecutor, this.platform);
|
|
1436
1519
|
}
|
|
1437
1520
|
if (!matchesProjection(record)) throw new CandidateViewError("live candidate does not match the native frozen projection");
|
|
1438
1521
|
this.records.set(record.token, record);
|
|
@@ -1461,7 +1544,7 @@ export class CandidateViewRegistry {
|
|
|
1461
1544
|
* dispatch-facing current binding is established with the provider-named
|
|
1462
1545
|
* pending lenses.
|
|
1463
1546
|
*/
|
|
1464
|
-
restoreCurrentForDispatchFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor, selectedLenses: readonly string[]): void {
|
|
1547
|
+
restoreCurrentForDispatchFromNative(lineageId: string, contributorRoot: string, descriptor: NativeCandidateProjectionDescriptor, selectedLenses: readonly string[], providerBaseTree?: string): void {
|
|
1465
1548
|
const root = this.canonicalRoot(contributorRoot);
|
|
1466
1549
|
const key = this.lineageKey(root, lineageId);
|
|
1467
1550
|
if (this.current.has(root)) throw new CandidateViewError("candidate view already has a current lineage binding", "current-binding-already-established");
|
|
@@ -1469,10 +1552,13 @@ export class CandidateViewRegistry {
|
|
|
1469
1552
|
let record: CandidateViewRecord | undefined;
|
|
1470
1553
|
try {
|
|
1471
1554
|
const lenses = this.validateSelectedLenses(selectedLenses);
|
|
1472
|
-
|
|
1555
|
+
// A fresh STATUS descriptor alone cannot assert provider-tree provenance.
|
|
1556
|
+
// Without a separately authenticated offer, restoreProjectionFromNative
|
|
1557
|
+
// resolves only its existing commit-backed or live-HEAD path.
|
|
1558
|
+
this.restoreProjectionFromNative(lineageId, root, descriptor, providerBaseTree);
|
|
1473
1559
|
projectionRestored = true;
|
|
1474
1560
|
const projection = this.resolveProjection(lineageId, root);
|
|
1475
|
-
record = materializeCandidateView({ contributorRoot: root, baseRef: projection.baseCommit, committedOnly: projection.committedOnly, ...(projection.intendedUntracked === undefined ? {} : { intendedUntracked: projection.intendedUntracked }) }, this.gitExecutor, this.platform);
|
|
1561
|
+
record = materializeCandidateView({ contributorRoot: root, ...(projection.providerBaseTree === undefined ? { baseRef: projection.baseCommit } : { providerBaseTree: projection.providerBaseTree }), committedOnly: projection.committedOnly, ...(projection.intendedUntracked === undefined ? {} : { intendedUntracked: projection.intendedUntracked }) }, this.gitExecutor, this.platform);
|
|
1476
1562
|
if (record.baseTree !== projection.baseTree || record.candidateTree !== projection.candidateTree || JSON.stringify(record.scope.paths) !== JSON.stringify(projection.paths)) {
|
|
1477
1563
|
throw new CandidateViewError("live candidate does not match the native frozen projection");
|
|
1478
1564
|
}
|
|
@@ -1566,7 +1652,7 @@ export class CandidateViewRegistry {
|
|
|
1566
1652
|
}
|
|
1567
1653
|
|
|
1568
1654
|
private assertCurrentBindingMatchesLiveCandidate(record: CandidateViewRecord): void {
|
|
1569
|
-
const live = materializeCandidateView({ contributorRoot: record.contributorRoot, baseRef: record.baseCommit, committedOnly: record.committedOnly, ...(record.intendedUntracked === undefined ? {} : { intendedUntracked: record.intendedUntracked }) }, this.gitExecutor, this.platform);
|
|
1655
|
+
const live = materializeCandidateView({ contributorRoot: record.contributorRoot, ...(record.providerBaseTree === undefined ? { baseRef: record.baseCommit } : { providerBaseTree: record.providerBaseTree }), committedOnly: record.committedOnly, ...(record.intendedUntracked === undefined ? {} : { intendedUntracked: record.intendedUntracked }) }, this.gitExecutor, this.platform);
|
|
1570
1656
|
try {
|
|
1571
1657
|
if (
|
|
1572
1658
|
live.baseCommit !== record.baseCommit ||
|
|
@@ -1634,6 +1720,7 @@ export class CandidateViewRegistry {
|
|
|
1634
1720
|
contributorRoot: record.contributorRoot,
|
|
1635
1721
|
baseCommit: record.baseCommit,
|
|
1636
1722
|
baseTree: record.baseTree,
|
|
1723
|
+
...(record.providerBaseTree === undefined ? {} : { providerBaseTree: record.providerBaseTree }),
|
|
1637
1724
|
candidateTree: record.candidateTree,
|
|
1638
1725
|
committedOnly: record.committedOnly,
|
|
1639
1726
|
intendedUntracked: record.intendedUntracked,
|
|
@@ -1774,7 +1861,7 @@ export function decodeCandidateContextManifest(encoded: string, sha256: string):
|
|
|
1774
1861
|
if (actualSha256 !== sha256) throw new CandidateViewError("candidate context manifest integrity check failed", "candidate-context-manifest-integrity");
|
|
1775
1862
|
const text = bytes.toString("utf8");
|
|
1776
1863
|
if (!Buffer.from(text, "utf8").equals(bytes)) return invalidCandidateContextManifest("candidate context manifest is not valid UTF-8");
|
|
1777
|
-
if (gzipSync(bytes
|
|
1864
|
+
if (gzipSync(bytes).toString("base64url") !== encoded) {
|
|
1778
1865
|
return invalidCandidateContextManifest("candidate context manifest transport is not canonical");
|
|
1779
1866
|
}
|
|
1780
1867
|
let value: unknown;
|
|
@@ -1836,7 +1923,8 @@ function compactCandidateContextBlock(lineageId: string, agents: readonly Review
|
|
|
1836
1923
|
const bytes = Buffer.from(JSON.stringify(manifest), "utf8");
|
|
1837
1924
|
if (bytes.length > MAX_CANDIDATE_CONTEXT_MANIFEST_BYTES) throw new CandidateViewError("candidate view context exceeds the bounded dispatch contract");
|
|
1838
1925
|
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
1839
|
-
|
|
1926
|
+
// Node emits a zero gzip timestamp by default; no unsupported mtime option is needed.
|
|
1927
|
+
const encoded = gzipSync(bytes).toString("base64url");
|
|
1840
1928
|
const block = `${candidateContextPreamble(lineageId, agents, view, scopeSemantics)}\nFrozen changed scope manifest (gzip+base64url): \`${encoded}\`.\nFrozen changed scope manifest SHA-256: \`${sha256}\`.\nCall \`gentle_review_scope\` with exactly this manifest, SHA-256, and cursor 0; continue with each returned \`nextCursor\` until absent. It is the only authorized scope enumerator: do not infer scope by traversing the candidate or ambient tree. Gitlinks are metadata-only and MUST NOT be traversed.\nThe ambient contributor working directory is out of scope. This controller-owned context is immutable; you are read-only and your output is untrusted.`;
|
|
1841
1929
|
if (Buffer.byteLength(block, "utf8") > MAX_CANDIDATE_CONTEXT_LENGTH) throw new CandidateViewError("candidate view context exceeds the bounded dispatch contract");
|
|
1842
1930
|
return block;
|
|
@@ -136,7 +136,7 @@ function parseCompactFinalizeInputValue(value: unknown): CompactFinalizeContract
|
|
|
136
136
|
if ((input.final_evidence === undefined && outcomeFields !== 0) || (input.final_evidence !== undefined && outcomeFields !== 1)) fail("review/finalize", "field-pair", "final evidence requires exactly one verification result or outcome");
|
|
137
137
|
let correction_line_forecast: number | undefined;
|
|
138
138
|
if (input.correction_line_forecast !== undefined) {
|
|
139
|
-
if (!Number.isSafeInteger(input.correction_line_forecast) || input.correction_line_forecast <= 0) fail("review/finalize.correction_line_forecast", "range", "must be a positive safe integer");
|
|
139
|
+
if (typeof input.correction_line_forecast !== "number" || !Number.isSafeInteger(input.correction_line_forecast) || input.correction_line_forecast <= 0) fail("review/finalize.correction_line_forecast", "range", "must be a positive safe integer");
|
|
140
140
|
correction_line_forecast = input.correction_line_forecast;
|
|
141
141
|
}
|
|
142
142
|
if (input.final_verification_passed !== undefined && typeof input.final_verification_passed !== "boolean") fail("review/finalize.final_verification_passed", "type", "must be boolean");
|
|
@@ -154,7 +154,7 @@ function parseCompactFinalizeInputValue(value: unknown): CompactFinalizeContract
|
|
|
154
154
|
if (typeof input.final_evidence !== "string" || input.final_evidence.length === 0) fail("review/finalize.final_evidence", "empty", "must contain at least one byte");
|
|
155
155
|
final_evidence = input.final_evidence;
|
|
156
156
|
}
|
|
157
|
-
return { cwd: string(input.cwd, "review/finalize.cwd"), ...(optionalLineage(input.lineageId, "review/finalize.lineageId") === undefined ? {} : { lineageId: optionalLineage(input.lineageId, "review/finalize.lineageId")! }), ...(correction_line_forecast === undefined ? {} : { correction_line_forecast }), ...(input.validation === undefined ? {} : { validation: parseValidation(input.validation, "review/finalize.validation") }), ...(final_evidence === undefined ? {} : { final_evidence }), ...(input.final_verification_passed
|
|
157
|
+
return { cwd: string(input.cwd, "review/finalize.cwd"), ...(optionalLineage(input.lineageId, "review/finalize.lineageId") === undefined ? {} : { lineageId: optionalLineage(input.lineageId, "review/finalize.lineageId")! }), ...(correction_line_forecast === undefined ? {} : { correction_line_forecast }), ...(input.validation === undefined ? {} : { validation: parseValidation(input.validation, "review/finalize.validation") }), ...(final_evidence === undefined ? {} : { final_evidence }), ...(typeof input.final_verification_passed !== "boolean" ? {} : { final_verification_passed: input.final_verification_passed }), ...(final_verification_outcome === undefined ? {} : { final_verification_outcome }), ...(typeof input.reviewer_run_acknowledged !== "boolean" ? {} : { reviewer_run_acknowledged: input.reviewer_run_acknowledged }) };
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
export function parseNativeCompactFinalizeInput(value: unknown): CompactFinalizeContractInput {
|
package/lib/review-host-relay.ts
CHANGED
|
@@ -34,13 +34,14 @@ import { spawn } from "node:child_process";
|
|
|
34
34
|
import { tmpdir } from "node:os";
|
|
35
35
|
import { isAbsolute, join } from "node:path";
|
|
36
36
|
import { completeSimple } from "@earendil-works/pi-ai/compat";
|
|
37
|
-
import { resolveGentleAiBinary } from "./gentle-ai-binary.ts";
|
|
37
|
+
import { gentleAiIsolationEnv, resolveGentleAiBinary } from "./gentle-ai-binary.ts";
|
|
38
38
|
import {
|
|
39
39
|
INPROCESS_REVIEWER_FAILURE,
|
|
40
40
|
runInProcessReviewer,
|
|
41
41
|
type InProcessReviewerFailureCode,
|
|
42
42
|
type InProcessReviewerOutcome,
|
|
43
43
|
type InProcessReviewerRegistry,
|
|
44
|
+
type InProcessReviewerRequest,
|
|
44
45
|
} from "./inprocess-reviewer.ts";
|
|
45
46
|
import { REVIEW_PROVIDER_ROLE_CAPTURE_OPERATION, REVIEW_PROVIDER_ROLE_CAPTURE_OPERATIONS, type ReviewCaptureSubmissionV1, type ReviewCollectInputV3 } from "./review-integration-v2.ts";
|
|
46
47
|
import { GENTLE_PI_REVIEW_RELAY_CONTRACT, GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV } from "./review-relay-contract.ts";
|
|
@@ -386,6 +387,8 @@ export interface ReviewHostRelayRequest {
|
|
|
386
387
|
readonly selection?: string;
|
|
387
388
|
/** The routing entry's thinking label, forwarded verbatim to the completion. */
|
|
388
389
|
readonly thinking?: string;
|
|
390
|
+
/** Effective Pi transport preference, forwarded without forcing a provider default. */
|
|
391
|
+
readonly reviewerTransport?: InProcessReviewerRequest["transport"];
|
|
389
392
|
/**
|
|
390
393
|
* The caller's live pi session id, forwarded into the in-process completion
|
|
391
394
|
* request so an OpenCode-routed reviewer model carries its
|
|
@@ -498,7 +501,8 @@ function collectGentleAiProcess(
|
|
|
498
501
|
const startedAt = Date.now();
|
|
499
502
|
const child = spawn(file, [...arguments_], {
|
|
500
503
|
cwd: options.cwd,
|
|
501
|
-
|
|
504
|
+
// S9: a bundled install keeps Gentle AI's state inside its prefix.
|
|
505
|
+
env: gentleAiIsolationEnv(options.env),
|
|
502
506
|
stdio: ["pipe", "pipe", "pipe"],
|
|
503
507
|
shell: false,
|
|
504
508
|
windowsHide: true,
|
|
@@ -737,6 +741,7 @@ export async function prepareReviewHostRelaySlot(
|
|
|
737
741
|
{
|
|
738
742
|
selection: preparedRequest.selection!,
|
|
739
743
|
...(preparedRequest.thinking === undefined ? {} : { thinking: preparedRequest.thinking }),
|
|
744
|
+
...(preparedRequest.reviewerTransport === undefined ? {} : { transport: preparedRequest.reviewerTransport }),
|
|
740
745
|
...(preparedRequest.reviewerSessionId === undefined ? {} : { sessionId: preparedRequest.reviewerSessionId }),
|
|
741
746
|
prompt: promptBytes,
|
|
742
747
|
timeoutMs: piTimeoutMs,
|