gentle-pi 3.7.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +155 -10
- package/assets/agents/gentle-ai-explore.md +13 -6
- package/assets/agents/gentle-ai-verify.md +27 -6
- package/assets/agents/gentle-ai-worker.md +13 -15
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +75 -169
- package/assets/orchestrator-memory.md +12 -26
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +30 -38
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +956 -50
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/bundled-install.md +397 -0
- package/docs/delegated-verification.md +27 -4
- package/docs/gentle-agents-activity.md +537 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +165 -51
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +3 -3
- package/docs/permission-modes.md +82 -0
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +364 -231
- package/docs/render-cache-performance.md +28 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +35 -0
- package/extensions/child-safety.ts +25 -0
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +1093 -326
- package/extensions/gentle-ai.ts +1636 -890
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +1619 -136
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +126 -20
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1424 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +254 -92
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +191 -58
- package/extensions/startup-banner.ts +71 -33
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +99 -0
- package/lib/agents-runner.ts +180 -107
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/auto-permission-classifier.ts +280 -0
- package/lib/auto-permission-runtime.ts +268 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +220 -0
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +195 -0
- package/lib/command-palette-catalog.ts +5 -10
- package/lib/command-palette.ts +25 -14
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-bundled.ts +517 -0
- package/lib/gentle-shell-launcher.ts +89 -27
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +249 -0
- package/lib/native-review-cli.ts +242 -180
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +73 -21
- package/lib/review-candidate-view.ts +115 -27
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +227 -56
- package/lib/review-sidebar-state.ts +224 -0
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +527 -0
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +266 -0
- package/lib/session-worktree-registry.ts +87 -9
- package/lib/shell-bar.ts +215 -30
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +42 -14
- package/lib/shell-changes.ts +86 -71
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +83 -15
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +84 -31
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +70 -13
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +383 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +119 -0
- package/lib/visual-customize-view.ts +481 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +317 -0
- package/package.json +20 -8
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +518 -0
- package/runtime/gentle-shell-launcher.mjs +87 -25
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +239 -177
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +225 -54
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +8 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +83 -35
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +90 -0
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +54 -19
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +91 -38
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +16 -21
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +30 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +97 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +467 -93
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/auto-mode-invariants.test.ts +258 -0
- package/tests/auto-mode-sdk.test.ts +57 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-classifier.test.ts +283 -0
- package/tests/auto-permission-runtime.test.ts +196 -0
- package/tests/autonomous-guard.test.ts +69 -1
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +146 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +87 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +94 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +596 -0
- package/tests/command-palette.test.ts +43 -7
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -78
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +171 -3
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +288 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +69 -0
- package/tests/gentle-agents.test.ts +2804 -346
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +21 -4
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +566 -62
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +493 -14
- package/tests/gentle-ai.test.ts +1427 -236
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +896 -115
- package/tests/gentle-shell-bundled.test.ts +1002 -0
- package/tests/gentle-shell-launcher.test.ts +333 -65
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +4216 -119
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +333 -10
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +49 -19
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2158 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +389 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +481 -0
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +13 -1
- package/tests/native-review-cli.test.ts +239 -118
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +101 -4
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +76 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +60 -14
- package/tests/odd-routing-contract.test.ts +158 -50
- package/tests/orchestrator-budget.test.ts +43 -45
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +8 -7
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +344 -123
- package/tests/packed-runner-owned-path.test.ts +80 -0
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +10 -18
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/provider-defect-handoff.test.ts +7 -14
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +569 -187
- package/tests/rdd-aware-verification-contract.test.ts +103 -40
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +237 -21
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +399 -12
- package/tests/review-contract-prompt.test.ts +138 -44
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +866 -6
- package/tests/review-controller-workspace-root.test.ts +206 -6
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +342 -4
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +221 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +14 -37
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +594 -26
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +202 -0
- package/tests/runtime-harness.mjs +186 -800
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +451 -0
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +381 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +134 -2
- package/tests/shell-bar.test.ts +422 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +135 -32
- package/tests/shell-changes.test.ts +61 -62
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +351 -22
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +207 -3
- package/tests/shell-usage-view.test.ts +28 -1
- package/tests/shell-usage.test.ts +108 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +357 -2
- package/tests/startup-banner.test.ts +246 -4
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +107 -20
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/theme-customization.test.ts +72 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +139 -0
- package/tests/visual-customize-view.test.ts +696 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +271 -0
- package/tests/yolo-mode-runtime.test.ts +188 -0
- package/tests/yolo-mode.test.ts +289 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
decodeReviewStartV4,
|
|
18
18
|
decodeReviewStatusV3,
|
|
19
19
|
} from "../lib/review-integration-v2.ts";
|
|
20
|
+
import { NativeReviewCliV216, type ExecFileAdapter } from "../lib/native-review-cli.ts";
|
|
20
21
|
|
|
21
22
|
const DEV_FIXTURES = join(process.cwd(), "tests", "fixtures", "devbinary");
|
|
22
23
|
const V2_FIXTURES = join(process.cwd(), "contracts", "review-integration", "v2", "fixtures");
|
|
@@ -556,6 +557,205 @@ test("status/v6 decodes and enforces the intended-untracked selection submission
|
|
|
556
557
|
}
|
|
557
558
|
});
|
|
558
559
|
|
|
560
|
+
function queuedTargetStatusAdapter(): { adapter: ExecFileAdapter; calls: string[][] } {
|
|
561
|
+
const calls: string[][] = [];
|
|
562
|
+
return {
|
|
563
|
+
calls,
|
|
564
|
+
adapter: async (request) => {
|
|
565
|
+
calls.push([...request.arguments]);
|
|
566
|
+
return { stdout: JSON.stringify(currentStatusFixture("status-v5.captured.json")), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function targetStatusClient(adapter: ExecFileAdapter): NativeReviewCliV216 {
|
|
572
|
+
return new NativeReviewCliV216(adapter, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const SUBMITTED_PROVIDER_TOKENS = ["--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", "--intended-untracked-selection={{value}}"];
|
|
576
|
+
|
|
577
|
+
test("targetStatus appends the forwarded base-ref/committed-only pair after intended-untracked submission tokens", async () => {
|
|
578
|
+
const queue = queuedTargetStatusAdapter();
|
|
579
|
+
await targetStatusClient(queue.adapter).targetStatus({
|
|
580
|
+
cwd: "/repo",
|
|
581
|
+
baseRef: "deadbeef",
|
|
582
|
+
committedOnly: true,
|
|
583
|
+
intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
|
|
584
|
+
});
|
|
585
|
+
assert.deepEqual(queue.calls[0], [
|
|
586
|
+
"review", "status", "--cwd", "/repo",
|
|
587
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
588
|
+
"--base-ref", "deadbeef", "--committed-only",
|
|
589
|
+
]);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
test("targetStatus leaves the intended-untracked submission argv unchanged when no baseRef is forwarded", async () => {
|
|
593
|
+
const queue = queuedTargetStatusAdapter();
|
|
594
|
+
await targetStatusClient(queue.adapter).targetStatus({
|
|
595
|
+
cwd: "/repo",
|
|
596
|
+
intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
|
|
597
|
+
});
|
|
598
|
+
assert.deepEqual(queue.calls[0], [
|
|
599
|
+
"review", "status", "--cwd", "/repo",
|
|
600
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
601
|
+
]);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test("targetStatus rejects a forwarded baseRef that conflicts with a base-ref the submission already carries", async () => {
|
|
605
|
+
const queue = queuedTargetStatusAdapter();
|
|
606
|
+
const tokensWithBaseRef = [...SUBMITTED_PROVIDER_TOKENS, "--base-ref=cafebabe"];
|
|
607
|
+
await assert.rejects(
|
|
608
|
+
() => targetStatusClient(queue.adapter).targetStatus({
|
|
609
|
+
cwd: "/repo", baseRef: "deadbeef", committedOnly: true,
|
|
610
|
+
intendedUntrackedSelection: { argumentTokens: tokensWithBaseRef, value: '{"selection":true}' },
|
|
611
|
+
}),
|
|
612
|
+
TypeError,
|
|
613
|
+
);
|
|
614
|
+
assert.equal(queue.calls.length, 0);
|
|
615
|
+
|
|
616
|
+
// Same value on both sides is not a conflict, but the submission still lacks
|
|
617
|
+
// --committed-only: T3 appends it so the selector stays complete.
|
|
618
|
+
const sameValueQueue = queuedTargetStatusAdapter();
|
|
619
|
+
await targetStatusClient(sameValueQueue.adapter).targetStatus({
|
|
620
|
+
cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
|
|
621
|
+
intendedUntrackedSelection: { argumentTokens: tokensWithBaseRef, value: '{"selection":true}' },
|
|
622
|
+
});
|
|
623
|
+
assert.deepEqual(sameValueQueue.calls[0], [
|
|
624
|
+
"review", "status", "--cwd", "/repo",
|
|
625
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
626
|
+
"--base-ref=cafebabe",
|
|
627
|
+
"--committed-only",
|
|
628
|
+
]);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
test("targetStatus appends --committed-only when the submission's same base-ref is missing it, and skips it when already present", async () => {
|
|
632
|
+
const missingQueue = queuedTargetStatusAdapter();
|
|
633
|
+
const tokensWithBaseRefOnly = [...SUBMITTED_PROVIDER_TOKENS, "--base-ref", "cafebabe"];
|
|
634
|
+
await targetStatusClient(missingQueue.adapter).targetStatus({
|
|
635
|
+
cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
|
|
636
|
+
intendedUntrackedSelection: { argumentTokens: tokensWithBaseRefOnly, value: '{"selection":true}' },
|
|
637
|
+
});
|
|
638
|
+
assert.deepEqual(missingQueue.calls[0], [
|
|
639
|
+
"review", "status", "--cwd", "/repo",
|
|
640
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
641
|
+
"--base-ref", "cafebabe",
|
|
642
|
+
"--committed-only",
|
|
643
|
+
]);
|
|
644
|
+
|
|
645
|
+
const presentQueue = queuedTargetStatusAdapter();
|
|
646
|
+
const tokensWithBoth = [...SUBMITTED_PROVIDER_TOKENS, "--base-ref=cafebabe", "--committed-only=true"];
|
|
647
|
+
await targetStatusClient(presentQueue.adapter).targetStatus({
|
|
648
|
+
cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
|
|
649
|
+
intendedUntrackedSelection: { argumentTokens: tokensWithBoth, value: '{"selection":true}' },
|
|
650
|
+
});
|
|
651
|
+
assert.deepEqual(presentQueue.calls[0], [
|
|
652
|
+
"review", "status", "--cwd", "/repo",
|
|
653
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
654
|
+
"--base-ref=cafebabe", "--committed-only=true",
|
|
655
|
+
]);
|
|
656
|
+
|
|
657
|
+
const committedOnlyQueue = queuedTargetStatusAdapter();
|
|
658
|
+
const tokensWithCommittedOnly = [...SUBMITTED_PROVIDER_TOKENS, "--committed-only"];
|
|
659
|
+
await targetStatusClient(committedOnlyQueue.adapter).targetStatus({
|
|
660
|
+
cwd: "/repo", baseRef: "cafebabe", committedOnly: true,
|
|
661
|
+
intendedUntrackedSelection: { argumentTokens: tokensWithCommittedOnly, value: '{"selection":true}' },
|
|
662
|
+
});
|
|
663
|
+
assert.deepEqual(committedOnlyQueue.calls[0], [
|
|
664
|
+
"review", "status", "--cwd", "/repo",
|
|
665
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
666
|
+
"--committed-only",
|
|
667
|
+
"--base-ref", "cafebabe",
|
|
668
|
+
]);
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
test("targetStatus forwards --lineage on the submitted branch, skips a matching value, and rejects a conflicting one", async () => {
|
|
672
|
+
const forwardedQueue = queuedTargetStatusAdapter();
|
|
673
|
+
await targetStatusClient(forwardedQueue.adapter).targetStatus({
|
|
674
|
+
cwd: "/repo", lineageId: "review-abc123",
|
|
675
|
+
intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
|
|
676
|
+
});
|
|
677
|
+
assert.deepEqual(forwardedQueue.calls[0], [
|
|
678
|
+
"review", "status", "--cwd", "/repo",
|
|
679
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
680
|
+
"--lineage", "review-abc123",
|
|
681
|
+
]);
|
|
682
|
+
|
|
683
|
+
const sameValueQueue = queuedTargetStatusAdapter();
|
|
684
|
+
const tokensWithLineage = [...SUBMITTED_PROVIDER_TOKENS, "--lineage=review-abc123"];
|
|
685
|
+
await targetStatusClient(sameValueQueue.adapter).targetStatus({
|
|
686
|
+
cwd: "/repo", lineageId: "review-abc123",
|
|
687
|
+
intendedUntrackedSelection: { argumentTokens: tokensWithLineage, value: '{"selection":true}' },
|
|
688
|
+
});
|
|
689
|
+
assert.deepEqual(sameValueQueue.calls[0], [
|
|
690
|
+
"review", "status", "--cwd", "/repo",
|
|
691
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
692
|
+
"--lineage=review-abc123",
|
|
693
|
+
]);
|
|
694
|
+
|
|
695
|
+
const conflictQueue = queuedTargetStatusAdapter();
|
|
696
|
+
await assert.rejects(
|
|
697
|
+
() => targetStatusClient(conflictQueue.adapter).targetStatus({
|
|
698
|
+
cwd: "/repo", lineageId: "review-other",
|
|
699
|
+
intendedUntrackedSelection: { argumentTokens: tokensWithLineage, value: '{"selection":true}' },
|
|
700
|
+
}),
|
|
701
|
+
TypeError,
|
|
702
|
+
);
|
|
703
|
+
assert.equal(conflictQueue.calls.length, 0);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
test("targetStatus forwards base-ref and lineage together on the submitted branch, base-ref first", async () => {
|
|
707
|
+
const queue = queuedTargetStatusAdapter();
|
|
708
|
+
await targetStatusClient(queue.adapter).targetStatus({
|
|
709
|
+
cwd: "/repo", baseRef: "deadbeef", committedOnly: true, lineageId: "review-abc123",
|
|
710
|
+
intendedUntrackedSelection: { argumentTokens: SUBMITTED_PROVIDER_TOKENS, value: '{"selection":true}' },
|
|
711
|
+
});
|
|
712
|
+
assert.deepEqual(queue.calls[0], [
|
|
713
|
+
"review", "status", "--cwd", "/repo",
|
|
714
|
+
"--contract=gentle-ai.review-integration/v2", "--next-transition=true", "--agent=pi", "--projection=workspace", '--intended-untracked-selection={"selection":true}',
|
|
715
|
+
"--base-ref", "deadbeef", "--committed-only",
|
|
716
|
+
"--lineage", "review-abc123",
|
|
717
|
+
]);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test("targetStatus rejects a dangling or empty --base-ref/--lineage in the submitted tokens", async () => {
|
|
721
|
+
for (const tokens of [
|
|
722
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--base-ref"],
|
|
723
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--base-ref="],
|
|
724
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--lineage"],
|
|
725
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--lineage="],
|
|
726
|
+
]) {
|
|
727
|
+
const queue = queuedTargetStatusAdapter();
|
|
728
|
+
await assert.rejects(
|
|
729
|
+
() => targetStatusClient(queue.adapter).targetStatus({
|
|
730
|
+
cwd: "/repo",
|
|
731
|
+
intendedUntrackedSelection: { argumentTokens: tokens, value: '{"selection":true}' },
|
|
732
|
+
}),
|
|
733
|
+
TypeError,
|
|
734
|
+
JSON.stringify(tokens),
|
|
735
|
+
);
|
|
736
|
+
assert.equal(queue.calls.length, 0, JSON.stringify(tokens));
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
test("targetStatus rejects ambiguous or malformed provider selectors before executing STATUS", async () => {
|
|
741
|
+
for (const tokens of [
|
|
742
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--committed-only=false"],
|
|
743
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--committed-only=garbage"],
|
|
744
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--committed-only=true", "--committed-only=true"],
|
|
745
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--committed-only", "--committed-only=true"],
|
|
746
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--base-ref=deadbeef", "--base-ref", "deadbeef"],
|
|
747
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--lineage=review-a", "--lineage", "review-a"],
|
|
748
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--base-ref", "--lineage=review-a"],
|
|
749
|
+
[...SUBMITTED_PROVIDER_TOKENS, "--lineage", "--base-ref=deadbeef"],
|
|
750
|
+
]) {
|
|
751
|
+
const queue = queuedTargetStatusAdapter();
|
|
752
|
+
await assert.rejects(() => targetStatusClient(queue.adapter).targetStatus({
|
|
753
|
+
cwd: "/repo", intendedUntrackedSelection: { argumentTokens: tokens, value: '{"selection":true}' },
|
|
754
|
+
}), TypeError, JSON.stringify(tokens));
|
|
755
|
+
assert.equal(queue.calls.length, 0, JSON.stringify(tokens));
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
|
|
559
759
|
test("START/v4 accepts only its reviewing status continuation and preserves v3 strictness", () => {
|
|
560
760
|
for (const action of ["created", "replayed"] as const) {
|
|
561
761
|
const start = reviewingStartV4(action);
|
|
@@ -747,7 +947,7 @@ test("review-acknowledged/v1 is disjoint from every prior captured identity in b
|
|
|
747
947
|
["status/v3", decodeReviewStatusV3],
|
|
748
948
|
["start/v3", decodeReviewStartV3],
|
|
749
949
|
["start/v4", decodeReviewStartV4],
|
|
750
|
-
["capabilities/v2", decodeReviewCapabilitiesV2],
|
|
950
|
+
["capabilities/v2", (value) => decodeReviewCapabilitiesV2(value, CAPTURED_DIGEST)],
|
|
751
951
|
["consent/v2", decodeReviewConsentV2],
|
|
752
952
|
["consent/v3", decodeReviewConsentV3],
|
|
753
953
|
["last-event-closure/v1", decodeReviewLastEventClosureV1],
|
|
@@ -804,3 +1004,23 @@ test("v7 escalation strictly decodes all causes and optional refuter evidence",
|
|
|
804
1004
|
assert.throws(() => decodeReviewStatusV3({ ...escalatedStatusV7(), schema: `gentle-ai.review-integration.status/v${version}` }), /not allowed/);
|
|
805
1005
|
}
|
|
806
1006
|
});
|
|
1007
|
+
|
|
1008
|
+
test("START/v4 accepts every risk reason code and signal the published start schema allows", () => {
|
|
1009
|
+
// gentle-ai publishes dangerous_sink (code and signal) and agent_escalation
|
|
1010
|
+
// (code and signal) in start-v4.schema.json; a high-risk START that names
|
|
1011
|
+
// them must decode instead of failing as schema-incompatible (#5374).
|
|
1012
|
+
const start = reviewingStartV4();
|
|
1013
|
+
start.risk_reasons = [
|
|
1014
|
+
{ code: "dangerous_sink", signal: "dangerous_sink", path: "auth/session.py" },
|
|
1015
|
+
{ code: "hot_path", signal: "auth", path: "auth/session.py" },
|
|
1016
|
+
{ code: "process_boundary", signal: "shell_process", path: "auth/session.py" },
|
|
1017
|
+
{ code: "agent_escalation", signal: "agent_escalation" },
|
|
1018
|
+
];
|
|
1019
|
+
const decoded = decodeReviewStartV4(start);
|
|
1020
|
+
assert.deepEqual(decoded.riskReasons.map((reason) => reason.code), ["dangerous_sink", "hot_path", "process_boundary", "agent_escalation"]);
|
|
1021
|
+
assert.deepEqual(decoded.riskReasons.map((reason) => reason.signal), ["dangerous_sink", "auth", "shell_process", "agent_escalation"]);
|
|
1022
|
+
|
|
1023
|
+
const unknown = reviewingStartV4();
|
|
1024
|
+
unknown.risk_reasons = [{ code: "code_from_a_newer_cli" }];
|
|
1025
|
+
assert.throws(() => decodeReviewStartV4(unknown), /risk_reasons\[0\]\.code is unsupported/);
|
|
1026
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { stream as streamAnthropic } from "@earendil-works/pi-ai/api/anthropic-messages";
|
|
4
|
+
import { normalizeContext, validateToolArguments, type Model, type ToolCall } from "@earendil-works/pi-ai";
|
|
5
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
7
|
+
import { __testing, createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
8
|
+
|
|
9
|
+
function registeredTools(): Map<string, any> {
|
|
10
|
+
const tools = new Map<string, any>();
|
|
11
|
+
createGentleAiExtension({ nativeReviewCli: null })({
|
|
12
|
+
on() {}, registerCommand() {}, registerTool(tool: any) { tools.set(tool.name, tool); },
|
|
13
|
+
} as unknown as ExtensionAPI);
|
|
14
|
+
return tools;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const tools = registeredTools();
|
|
18
|
+
function validate(name: string, args: Record<string, unknown>): any {
|
|
19
|
+
return validateToolArguments(tools.get(name), { type: "toolCall", id: "json-arguments", name, arguments: args as ToolCall["arguments"] });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("registered review schemas have plain object parameters with no root-level union combinators (#1698)", () => {
|
|
23
|
+
for (const [name, tool] of tools) {
|
|
24
|
+
assert.equal(tool.parameters.type, "object", `${name} parameters must be an object`);
|
|
25
|
+
assert.equal(tool.parameters.anyOf, undefined, `${name} parameters must not have root-level anyOf`);
|
|
26
|
+
assert.equal(tool.parameters.oneOf, undefined, `${name} parameters must not have root-level oneOf`);
|
|
27
|
+
assert.equal(tool.parameters.allOf, undefined, `${name} parameters must not have root-level allOf`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("public Anthropic non-strict adapter preserves the complete review tool declarations", async () => {
|
|
32
|
+
const model: Model<"anthropic-messages"> = {
|
|
33
|
+
id: "claude-sonnet-4-20250514", name: "Payload-only model", api: "anthropic-messages", provider: "anthropic",
|
|
34
|
+
baseUrl: "https://example.invalid", input: ["text"], reasoning: false,
|
|
35
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, maxTokens: 1024,
|
|
36
|
+
compat: { supportsStrictTools: false },
|
|
37
|
+
};
|
|
38
|
+
let payload: any;
|
|
39
|
+
let transportCalls = 0;
|
|
40
|
+
const sentinel = "PAYLOAD_CAPTURED_BEFORE_TRANSPORT";
|
|
41
|
+
const result = await streamAnthropic(model, normalizeContext({ messages: [{ role: "user", content: "payload only", timestamp: 0 }], tools: [...tools.values()] }), {
|
|
42
|
+
apiKey: "dummy-test-key", env: {},
|
|
43
|
+
onPayload(value) { payload = value; throw new Error(sentinel); },
|
|
44
|
+
fetch: async () => { transportCalls++; throw new Error("TRANSPORT_MUST_NOT_RUN"); },
|
|
45
|
+
}).result();
|
|
46
|
+
assert.equal(transportCalls, 0);
|
|
47
|
+
assert.equal(result.stopReason, "error");
|
|
48
|
+
assert.match(result.errorMessage ?? "", new RegExp(sentinel));
|
|
49
|
+
assert.ok(payload, "adapter reached payload callback");
|
|
50
|
+
const emitted = new Map<string, any>(payload.tools.map((tool: any) => [tool.name, tool.input_schema]));
|
|
51
|
+
const controller = emitted.get("gentle_review");
|
|
52
|
+
assert.equal(controller.type, "object");
|
|
53
|
+
assert.deepEqual(controller.required, ["operation"]);
|
|
54
|
+
assert.deepEqual(Object.keys(controller.properties).sort(), ["operation", "lineageId", "selectionBinding", "intendedUntracked", "untrackedScope", "changeName", "idempotencyKey", "transition", "input", "outputPath", "inputPath", "operationId", "lineageIds", "workspaceRoot"].sort());
|
|
55
|
+
assert.ok(controller.properties.input.anyOf.some((schema: any) => schema.type === "object"));
|
|
56
|
+
assert.match(controller.properties.input.description, /START\/ASSESS only/);
|
|
57
|
+
assert.deepEqual(controller.properties.operation.enum, tools.get("gentle_review").parameters.properties.operation.enum);
|
|
58
|
+
for (const name of ["gentle_review_capture", "gentle_review_capture_group"]) {
|
|
59
|
+
const schema = emitted.get(name);
|
|
60
|
+
assert.deepEqual(schema.required, tools.get(name).parameters.required);
|
|
61
|
+
assert.deepEqual(schema.properties, tools.get(name).parameters.properties);
|
|
62
|
+
}
|
|
63
|
+
assert.ok(emitted.get("gentle_review_capture_group").properties.collectBindings.items.anyOf.some((schema: any) => schema.type === "object"));
|
|
64
|
+
// Anthropic omits the root operation constraints. Even its broader nullable
|
|
65
|
+
// declaration cannot grant consent, maintenance, or native execution.
|
|
66
|
+
let nativeCalls = 0;
|
|
67
|
+
const native = new Proxy({}, { get() { return async () => { nativeCalls++; }; } }) as NativeReviewCli;
|
|
68
|
+
for (const operation of ["start", "assess", "answer-consent", "inspect", "reset", "recover"]) {
|
|
69
|
+
const inputs = operation === "start" || operation === "assess" ? [null] : [null, { consentBinding: "opaque", answer: "granted" }];
|
|
70
|
+
for (const input of inputs) {
|
|
71
|
+
const args = validateToolArguments({ ...tools.get("gentle_review"), parameters: controller }, { type: "toolCall", id: "provider-shell", name: "gentle_review", arguments: { operation, input } });
|
|
72
|
+
assert.deepEqual(args.input, input, "provider-shell validation accepts and retains this input");
|
|
73
|
+
await assert.rejects(__testing.executeReviewControllerOperation(args, process.cwd(), native));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
assert.equal(nativeCalls, 0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const binding = { name: "reviewer_result", arguments: [] };
|
|
80
|
+
test("registered review schemas admit START/ASSESS objects and ordered capture objects", () => {
|
|
81
|
+
for (const operation of ["start", "assess"]) {
|
|
82
|
+
const input = operation === "start" ? { mode: "ordinary" } : { committedOnly: true, baseRef: "HEAD" };
|
|
83
|
+
assert.deepEqual(validate("gentle_review", { operation, input }).input, input);
|
|
84
|
+
assert.equal(__testing.parseReviewControllerParameters(validate("gentle_review", { operation, input })).input, JSON.stringify(input));
|
|
85
|
+
}
|
|
86
|
+
assert.deepEqual(validate("gentle_review_capture", { lineageId: "l", collectBinding: binding }).collectBinding, binding);
|
|
87
|
+
const ordered = [binding, ' { "name": "second", "arguments": [] } '];
|
|
88
|
+
const args = validate("gentle_review_capture_group", { lineageId: "l", collectBindings: ordered });
|
|
89
|
+
assert.deepEqual(__testing.parseReviewCaptureGroupParameters(args).collectBindings, [JSON.stringify(binding), ordered[1]]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("validated ASSESS objects reach the mocked native facade with parsed fields", async () => {
|
|
93
|
+
const requests: unknown[] = [];
|
|
94
|
+
const native = { assess: async (request: unknown) => { requests.push(request); throw new Error("mock assessment unavailable"); } } as unknown as NativeReviewCli;
|
|
95
|
+
for (const input of [{ baseRef: "HEAD", committedOnly: true, nativeReviewOutcome: "unknown" }, ' { "baseRef": "HEAD", "committedOnly": true, "nativeReviewOutcome": "unknown" } ']) {
|
|
96
|
+
await __testing.executeReviewControllerOperation(validate("gentle_review", { operation: "assess", input }), process.cwd(), native);
|
|
97
|
+
}
|
|
98
|
+
assert.equal(requests.length, 2);
|
|
99
|
+
assert.deepEqual(requests[0], { cwd: process.cwd(), baseRef: "HEAD", committedOnly: true });
|
|
100
|
+
assert.deepEqual(requests[1], requests[0]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("serialized inputs remain byte-for-byte unchanged at validation and facade parsing", () => {
|
|
104
|
+
const input = ' \n{ "mode" : "ordinary" }\t';
|
|
105
|
+
for (const operation of ["start", "assess", "answer-consent", "reset", "recover"]) {
|
|
106
|
+
assert.equal(__testing.parseReviewControllerParameters(validate("gentle_review", { operation, input })).input, input);
|
|
107
|
+
}
|
|
108
|
+
const collectBinding = ' { "name": "reviewer_result", "arguments": [] }\n';
|
|
109
|
+
assert.equal(__testing.parseReviewCaptureParameters(validate("gentle_review_capture", { lineageId: "l", collectBinding })).collectBinding, collectBinding);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("actual validation rejects arrays, null and coerced primitives rather than admitting native calls", async () => {
|
|
113
|
+
for (const value of [[], 123, true, "123", "null", "[]"]) {
|
|
114
|
+
for (const operation of ["start", "assess", "answer-consent", "inspect", "reset", "recover"]) {
|
|
115
|
+
assert.throws(() => validate("gentle_review", { operation, input: value }), /Validation failed/);
|
|
116
|
+
}
|
|
117
|
+
assert.throws(() => validate("gentle_review_capture", { lineageId: "l", collectBinding: value }), /Validation failed/);
|
|
118
|
+
assert.throws(() => validate("gentle_review_capture_group", { lineageId: "l", collectBindings: [binding, value] }), /Validation failed/);
|
|
119
|
+
}
|
|
120
|
+
let calls = 0;
|
|
121
|
+
const native = new Proxy({}, { get() { return async () => { calls++; throw new Error("unexpected native execution"); }; } }) as NativeReviewCli;
|
|
122
|
+
for (const value of [null, false]) {
|
|
123
|
+
for (const operation of ["start", "assess", "answer-consent", "inspect", "reset", "recover"]) {
|
|
124
|
+
const validated = validate("gentle_review", { operation, input: value });
|
|
125
|
+
assert.equal(validated.input, null, "nullable shell preserves null instead of stripping it to omitted");
|
|
126
|
+
await assert.rejects(__testing.executeReviewControllerOperation(validated, process.cwd(), native));
|
|
127
|
+
}
|
|
128
|
+
assert.throws(() => validate("gentle_review_capture", { lineageId: "l", collectBinding: value }), /Validation failed/);
|
|
129
|
+
assert.throws(() => validate("gentle_review_capture_group", { lineageId: "l", collectBindings: [binding, value] }), /Validation failed/);
|
|
130
|
+
}
|
|
131
|
+
assert.equal(calls, 0);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("all other controller operations keep object input fail-closed at the facade boundary (#1698)", async () => {
|
|
135
|
+
const operations = (tools.get("gentle_review").parameters.properties.operation.enum as string[]).filter((operation) => operation !== "start" && operation !== "assess" && operation !== "select-intended-untracked");
|
|
136
|
+
let calls = 0;
|
|
137
|
+
const native = new Proxy({}, { get() { return async () => { calls++; throw new Error("unexpected native execution"); }; } }) as NativeReviewCli;
|
|
138
|
+
for (const operation of operations) {
|
|
139
|
+
const args = { operation, lineageId: "l", input: { consentBinding: "opaque", answer: "granted" } };
|
|
140
|
+
await assert.rejects(
|
|
141
|
+
__testing.executeReviewControllerOperation(args, process.cwd(), native),
|
|
142
|
+
/Review controller input must be a string/,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
assert.equal(calls, 0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("facade rejects non-JSON runtime objects without lossy serialization or native execution", async () => {
|
|
149
|
+
const cyclic: any = {}; cyclic.self = cyclic;
|
|
150
|
+
const accessor = Object.defineProperty({}, "mode", { enumerable: true, get() { throw new Error("getter must not execute"); } });
|
|
151
|
+
const hidden = Object.defineProperty({}, "hidden", { value: 1 });
|
|
152
|
+
const extended: any[] = [1]; Object.assign(extended, { extra: 1 });
|
|
153
|
+
const invalid = [null, [], 1, true, undefined, new Date(), cyclic, accessor, hidden, new Proxy({}, {}), { x: new Proxy({}, {}) },
|
|
154
|
+
{ x: undefined }, { x: NaN }, { x: Infinity }, { x: -0 }, { x: 1n }, { x() {} },
|
|
155
|
+
{ [Symbol("x")]: 1 }, { x: new Array(2) }, { x: extended }, { toJSON() { return {}; } }];
|
|
156
|
+
let calls = 0;
|
|
157
|
+
const native = new Proxy({}, { get() { return async () => { calls++; }; } }) as NativeReviewCli;
|
|
158
|
+
for (const input of invalid) {
|
|
159
|
+
for (const operation of ["start", "assess"]) {
|
|
160
|
+
await assert.rejects(__testing.executeReviewControllerOperation({ operation, input }, process.cwd(), native), /Review JSON argument/);
|
|
161
|
+
}
|
|
162
|
+
assert.throws(() => __testing.parseReviewCaptureParameters({ lineageId: "l", collectBinding: input }), /Review JSON argument/);
|
|
163
|
+
assert.throws(() => __testing.parseReviewCaptureGroupParameters({ lineageId: "l", collectBindings: [binding, input] }), /Review JSON argument/);
|
|
164
|
+
}
|
|
165
|
+
assert.equal(calls, 0);
|
|
166
|
+
});
|
|
167
|
+
|
|
@@ -34,21 +34,28 @@ function closure(operation: string, lineageId: string): Record<string, unknown>
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function
|
|
37
|
+
function fixtureLens(suffix: string) {
|
|
38
|
+
const lenses = ["review-risk", "review-reliability", "review-resilience", "review-readability"] as const;
|
|
39
|
+
const lens = lenses[Number(suffix)];
|
|
40
|
+
assert.ok(lens, "fixture order must select an authoritative lens");
|
|
41
|
+
return lens;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function bindingArguments(lineageId: string, suffix = "0", subjectHash = SHA): ReviewCollectInputV3["arguments"] {
|
|
38
45
|
return [
|
|
39
46
|
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
40
47
|
{ name: "expected-revision", value: SHA, token: `--expected-revision=${SHA}` },
|
|
41
48
|
{ name: "target", value: SHA, token: `--target=${SHA}` },
|
|
42
49
|
{ name: "repository-context", value: `rctx1_${"c".repeat(64)}`, token: `--repository-context=rctx1_${"c".repeat(64)}` },
|
|
43
|
-
{ name: "lens", value:
|
|
50
|
+
{ name: "lens", value: fixtureLens(suffix), token: `--lens=${fixtureLens(suffix)}` },
|
|
44
51
|
{ name: "order", value: suffix, token: `--order=${suffix}` },
|
|
45
|
-
{ name: "subject-hash", value:
|
|
52
|
+
{ name: "subject-hash", value: subjectHash, token: `--subject-hash=${subjectHash}` },
|
|
46
53
|
];
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
function materializeInput(lineageId: string, suffix = "0"): ReviewCollectInputV3 {
|
|
56
|
+
function materializeInput(lineageId: string, suffix = "0", subjectHash = SHA): ReviewCollectInputV3 {
|
|
50
57
|
const arguments_ = [
|
|
51
|
-
...bindingArguments(lineageId, suffix),
|
|
58
|
+
...bindingArguments(lineageId, suffix, subjectHash),
|
|
52
59
|
{ name: "agent", value: "pi", token: "--agent=pi" },
|
|
53
60
|
{ name: "materialize", value: "true", token: "--materialize=true" },
|
|
54
61
|
];
|
|
@@ -59,19 +66,19 @@ function materializeInput(lineageId: string, suffix = "0"): ReviewCollectInputV3
|
|
|
59
66
|
arguments: arguments_,
|
|
60
67
|
artifactSubject: {
|
|
61
68
|
schema: "gentle-ai.review-artifact-subject/v2",
|
|
62
|
-
subjectHash
|
|
69
|
+
subjectHash,
|
|
63
70
|
lineageId,
|
|
64
71
|
authorityRevision: SHA,
|
|
65
72
|
targetIdentity: SHA,
|
|
66
73
|
baseTree: TREE,
|
|
67
74
|
candidateTree: TREE,
|
|
68
75
|
changedPathManifestSha256: SHA,
|
|
69
|
-
lens:
|
|
76
|
+
lens: fixtureLens(suffix),
|
|
70
77
|
selectedOrder: Number(suffix),
|
|
71
78
|
},
|
|
72
79
|
submission: {
|
|
73
80
|
operationToken: "capture-result",
|
|
74
|
-
argumentTokens: [...bindingArguments(lineageId, suffix).map((argument) => argument.token!), "--input={{value}}"],
|
|
81
|
+
argumentTokens: [...bindingArguments(lineageId, suffix, subjectHash).map((argument) => argument.token!), "--input={{value}}"],
|
|
75
82
|
values: [{ slot: "reviewer_result", domain: "artifact_path_or_stdin", substitutionLocation: 7 }],
|
|
76
83
|
},
|
|
77
84
|
};
|
|
@@ -195,6 +202,148 @@ test("approved closure preserves complete admitted reviewer results before ackno
|
|
|
195
202
|
assert.throws(() => decodeReviewLastEventClosureV1(correctionPlan), /reviewer_results requires approved state/);
|
|
196
203
|
});
|
|
197
204
|
|
|
205
|
+
test("escalated closures preserve the native diagnostic evidence without granting a continuation", () => {
|
|
206
|
+
const evidence = { cause: "unknown_causality", finding_ids: ["R3-001"], refuter_outcomes: [{ finding_id: "R3-001", outcome: "inconclusive", proof: "causal origin could not be established" }] };
|
|
207
|
+
for (const operation of ["review/capture-result", "review.capture-refuter", "review/capture-validation"]) {
|
|
208
|
+
const payload = { ...closure(operation, "escalated-evidence"), state: "escalated", escalation: evidence };
|
|
209
|
+
const decoded = decodeReviewLastEventClosureV1(payload);
|
|
210
|
+
assert.equal(decoded.state, "escalated");
|
|
211
|
+
assert.deepEqual(decoded.escalation, {
|
|
212
|
+
cause: "unknown_causality", findingIds: ["R3-001"],
|
|
213
|
+
refuterOutcomes: [{ findingId: "R3-001", outcome: "inconclusive", proof: "causal origin could not be established" }],
|
|
214
|
+
});
|
|
215
|
+
assert.equal(decoded.acknowledgement, undefined);
|
|
216
|
+
assert.equal(decoded.statusContinuation, undefined);
|
|
217
|
+
const historical = { ...payload } as Record<string, unknown>;
|
|
218
|
+
delete historical.escalation;
|
|
219
|
+
assert.equal(decodeReviewLastEventClosureV1(historical).state, "escalated", "older provider closures remain readable");
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("closure escalation is strict diagnostic data restricted to escalated state", () => {
|
|
224
|
+
const evidence = { cause: "unknown_causality", finding_ids: ["R3-001"] };
|
|
225
|
+
const payload = { ...closure("review/capture-result", "strict-escalation"), state: "escalated", escalation: evidence };
|
|
226
|
+
for (const invalid of [
|
|
227
|
+
null, undefined, {}, { ...evidence, cause: "unknown-new-cause" },
|
|
228
|
+
{ cause: evidence.cause }, { ...evidence, finding_ids: [""] },
|
|
229
|
+
{ ...evidence, unexpected: true }, { ...evidence, refuter_outcomes: null },
|
|
230
|
+
{ ...evidence, refuter_outcomes: [{ finding_id: "R3-001", outcome: "approved", proof: "unsupported outcome" }] },
|
|
231
|
+
{ ...evidence, refuter_outcomes: [{ finding_id: "R3-001", outcome: "inconclusive", proof: "proof", unexpected: true }] },
|
|
232
|
+
]) assert.throws(() => decodeReviewLastEventClosureV1({ ...payload, escalation: invalid }), TypeError);
|
|
233
|
+
for (const operation of ["review/capture-result", "review.capture-correction-plan"]) {
|
|
234
|
+
assert.throws(() => decodeReviewLastEventClosureV1({ ...closure(operation, "wrong-state"), escalation: evidence }), /escalation requires escalated state/);
|
|
235
|
+
}
|
|
236
|
+
assert.throws(() => decodeReviewLastEventClosureV1({ ...payload, unexpected: true }), /is not allowed/);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
function targetedValidationClosure(): Record<string, unknown> {
|
|
240
|
+
return {
|
|
241
|
+
...closure("review/capture-validation", "validation-evidence"), state: "escalated",
|
|
242
|
+
escalation: { cause: "targeted_validator_rejected", finding_ids: ["R3-001"] },
|
|
243
|
+
targeted_validator_evidence: {
|
|
244
|
+
targeted_validation_request_hash: SHA, correction_target_identity: SHA,
|
|
245
|
+
original_criteria: { passed: false, evidence: ["original criterion still fails"] },
|
|
246
|
+
correction_regression: { passed: false, evidence: ["correction regressed a second path"], regressions: [{ location: "lib/session.ts:4", claim: "changed behavior regressed", proof_refs: ["observed result"] }] },
|
|
247
|
+
follow_ups: [{ observation: "inspect the remaining behavior", proof_refs: ["observed result"] }],
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
test("TS and runtime preserve targeted-validator diagnostics without granting authority", async () => {
|
|
253
|
+
const runtime = await import(new URL("../runtime/review-integration-v2.mjs", import.meta.url).href);
|
|
254
|
+
for (const decode of [decodeReviewLastEventClosureV1, runtime.decodeReviewLastEventClosureV1]) {
|
|
255
|
+
const payload = targetedValidationClosure();
|
|
256
|
+
const decoded = decode(payload);
|
|
257
|
+
assert.deepEqual(decoded.targetedValidatorEvidence.raw, payload.targeted_validator_evidence);
|
|
258
|
+
assert.equal(decoded.targetedValidatorEvidence.targetedValidationRequestHash, SHA);
|
|
259
|
+
assert.equal(decoded.targetedValidatorEvidence.correctionTargetIdentity, SHA);
|
|
260
|
+
assert.equal(decoded.targetedValidatorEvidence.originalCriteria.passed, false);
|
|
261
|
+
assert.deepEqual(decoded.targetedValidatorEvidence.correctionRegression.regressions[0].proofRefs, ["observed result"]);
|
|
262
|
+
assert.deepEqual(decoded.targetedValidatorEvidence.followUps[0].proofRefs, ["observed result"]);
|
|
263
|
+
assert.equal(decoded.acknowledgement, undefined);
|
|
264
|
+
assert.equal(decoded.statusContinuation, undefined);
|
|
265
|
+
const historical = { ...payload };
|
|
266
|
+
delete historical.targeted_validator_evidence;
|
|
267
|
+
assert.equal(decode(historical).targetedValidatorEvidence, undefined);
|
|
268
|
+
assert.throws(() => decode({ ...payload, acknowledgement: {} }), /acknowledgement requires approved state/);
|
|
269
|
+
assert.throws(() => decode({ ...payload, status_continuation: {} }), /status_continuation/);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("targeted-validator evidence is strict and exclusive to escalated validation closures", async () => {
|
|
274
|
+
const runtime = await import(new URL("../runtime/review-integration-v2.mjs", import.meta.url).href);
|
|
275
|
+
const payload = targetedValidationClosure();
|
|
276
|
+
const evidence = payload.targeted_validator_evidence as Record<string, unknown>;
|
|
277
|
+
for (const decode of [decodeReviewLastEventClosureV1, runtime.decodeReviewLastEventClosureV1]) {
|
|
278
|
+
for (const operation of ["review/capture-result", "review.capture-refuter", "review.capture-correction-plan", "review/capture-validation"]) {
|
|
279
|
+
for (const state of ["approved", "correction_required", "escalated"]) {
|
|
280
|
+
if (operation === "review/capture-validation" && state === "escalated") continue;
|
|
281
|
+
assert.throws(() => decode({ ...payload, operation, state, escalation: undefined }), /targeted_validator_evidence requires escalated review\/capture-validation/);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const invalid of [
|
|
285
|
+
null, undefined, {}, { ...evidence, extra: true },
|
|
286
|
+
{ ...evidence, targeted_validation_request_hash: "bad-hash" }, { ...evidence, correction_target_identity: "bad-hash" },
|
|
287
|
+
{ ...evidence, original_criteria: null }, { ...evidence, original_criteria: { passed: "false", evidence: ["proof"] } },
|
|
288
|
+
{ ...evidence, original_criteria: { passed: false, evidence: [] } }, { ...evidence, original_criteria: { passed: false, evidence: [""] } },
|
|
289
|
+
{ ...evidence, original_criteria: { passed: false, evidence: [" \t\n"] } }, { ...evidence, correction_regression: { passed: true, evidence: ["\u0085\u00a0"] } },
|
|
290
|
+
{ ...evidence, correction_regression: { passed: true, evidence: ["proof"], extra: true } },
|
|
291
|
+
{ ...evidence, correction_regression: { passed: false, evidence: ["proof"], regressions: [{ location: "path:1", claim: "claim", proof_refs: [] }] } },
|
|
292
|
+
{ ...evidence, correction_regression: { passed: false, evidence: ["proof"], regressions: [{ location: "path:1", claim: "claim", proof_refs: ["proof"], extra: true }] } },
|
|
293
|
+
{ ...evidence, follow_ups: null }, { ...evidence, follow_ups: [{ observation: "note", proof_refs: [] }] },
|
|
294
|
+
{ ...evidence, follow_ups: [{ observation: "note", proof_refs: ["proof"], extra: true }] },
|
|
295
|
+
]) assert.throws(() => decode({ ...payload, targeted_validator_evidence: invalid }), TypeError);
|
|
296
|
+
const minimal = { ...evidence, original_criteria: { passed: false, evidence: [" original observation "] }, correction_regression: { passed: true, evidence: ["no regression"] }, follow_ups: [] };
|
|
297
|
+
assert.deepEqual(decode({ ...payload, targeted_validator_evidence: minimal }).targetedValidatorEvidence.raw, minimal);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("single-lens terminal escalation returns its evidence directly without reconciliation or replay", async (t) => {
|
|
302
|
+
t.after(() => __testing.setReviewHostRelayRunnerForTesting());
|
|
303
|
+
const lineageId = "single-escalation", input = materializeInput(lineageId);
|
|
304
|
+
const escalation = { cause: "unknown_causality", finding_ids: ["R3-001"] };
|
|
305
|
+
let statusCalls = 0, captures = 0;
|
|
306
|
+
const native = { targetStatus: async () => { statusCalls += 1; return status(lineageId, [input]); } } as unknown as NativeReviewCli;
|
|
307
|
+
__testing.setReviewHostRelayRunnerForTesting(async () => {
|
|
308
|
+
captures += 1;
|
|
309
|
+
return { promptByteLength: 1, resultByteLength: 1, submission: JSON.stringify({ ...closure("review/capture-result", lineageId), state: "escalated", escalation }) };
|
|
310
|
+
});
|
|
311
|
+
const result = await capture(lineageId, input, native, { reviewerRunAcknowledged: true });
|
|
312
|
+
assert.equal(result.status, "closed");
|
|
313
|
+
assert.equal(result.state, "escalated");
|
|
314
|
+
assert.deepEqual((result.closure as Record<string, unknown>).escalation, escalation);
|
|
315
|
+
assert.equal(result.next_action, undefined);
|
|
316
|
+
assert.equal(statusCalls, 1);
|
|
317
|
+
assert.equal(captures, 1);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("one- and four-lens groups count the final escalated submission and preserve its native evidence", async (t) => {
|
|
321
|
+
t.after(() => __testing.setReviewHostRelayGroupRunnersForTesting());
|
|
322
|
+
for (const count of [1, 4]) {
|
|
323
|
+
const lineageId = `group-escalation-${count}`;
|
|
324
|
+
const inputs = Array.from({ length: count }, (_, index) => materializeInput(lineageId, String(index), `sha256:${String(index + 1).repeat(64)}`));
|
|
325
|
+
const escalation = { cause: "unknown_causality", finding_ids: ["R3-001"], refuter_outcomes: [{ finding_id: "R3-001", outcome: "inconclusive", proof: "causal origin could not be established" }] };
|
|
326
|
+
let submitted = 0, statusCalls = 0;
|
|
327
|
+
const native = { targetStatus: async () => {
|
|
328
|
+
statusCalls += 1;
|
|
329
|
+
return { ...status(lineageId, inputs.slice(submitted)), repositoryContext: { capability: "review.opaque_repository_context", handle: `rctx1_${"c".repeat(64)}`, revision: SHA, targetIdentity: SHA } };
|
|
330
|
+
} } as unknown as NativeReviewCli;
|
|
331
|
+
__testing.setReviewHostRelayGroupRunnersForTesting(async (requests) => requests.map((request) => ({ request, promptByteLength: 1, resultByteLength: 1 })), async () => {
|
|
332
|
+
submitted += 1;
|
|
333
|
+
return { promptByteLength: 1, resultByteLength: 1, submission: submitted === count ? JSON.stringify({ ...closure("review/capture-result", lineageId), state: "escalated", escalation }) : '{"admission_decision":"completed"}' };
|
|
334
|
+
});
|
|
335
|
+
const result = await __testing.executeReviewCaptureGroupOperation({ lineageId, collectBindings: inputs, reviewerRunAcknowledged: true }, process.cwd(), native);
|
|
336
|
+
assert.equal(result.status, "closed", JSON.stringify(result));
|
|
337
|
+
assert.equal(result.state, "escalated");
|
|
338
|
+
assert.equal(result.submitted_reviewers, count);
|
|
339
|
+
assert.equal(result.mutation_outcome, "completed");
|
|
340
|
+
assert.deepEqual((result.closure as Record<string, unknown>).escalation, escalation);
|
|
341
|
+
assert.equal(result.next_action, undefined);
|
|
342
|
+
assert.equal(submitted, count);
|
|
343
|
+
assert.equal(statusCalls, count + 1, "closure triggers no post-success STATUS");
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
|
|
198
347
|
test("retired legacy client no longer exposes a FINALIZE route", () => {
|
|
199
348
|
assert.equal("NativeReviewCliV214" in nativeReviewCliModule, false);
|
|
200
349
|
assert.equal("NativeReviewCliV213" in nativeReviewCliModule, false);
|