gentle-pi 3.7.0 → 4.0.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 +37 -6
- package/assets/agents/gentle-ai-explore.md +4 -4
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -9
- package/assets/orchestrator-delegation.md +40 -41
- package/assets/orchestrator-memory.md +1 -22
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator.md +12 -24
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/gentle-shell.mjs +254 -29
- package/docs/delegated-verification.md +26 -1
- package/docs/gentle-agents-activity.md +24 -0
- package/docs/gentle-shell.md +92 -28
- package/docs/native-authority-architecture.md +2 -2
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +112 -206
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +26 -0
- package/extensions/child-safety.ts +23 -0
- package/extensions/gentle-agents.ts +429 -262
- package/extensions/gentle-ai.ts +781 -683
- package/extensions/gentle-shell.ts +1375 -88
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +18 -7
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1419 -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/quiet-tools.ts +179 -87
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +16 -12
- package/extensions/startup-banner.ts +60 -31
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-protocol.ts +60 -0
- package/lib/agents-runner.ts +96 -98
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/bounded-writer-admission.ts +147 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/codemode-renderer.ts +185 -0
- package/lib/command-palette-catalog.ts +3 -9
- package/lib/command-palette.ts +25 -14
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-launcher.ts +24 -13
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +227 -0
- package/lib/native-review-cli.ts +49 -108
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +67 -17
- package/lib/review-candidate-view.ts +112 -25
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +156 -11
- package/lib/review-sidebar-state.ts +223 -0
- package/lib/selection-engine.ts +515 -0
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-worktree-registry.ts +14 -2
- package/lib/shell-bar.ts +179 -24
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +2 -1
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +78 -14
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +23 -13
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +66 -10
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +379 -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 +108 -0
- package/lib/visual-customize-view.ts +330 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/yolo-session-policy.ts +240 -0
- package/package.json +20 -8
- package/runtime/gentle-shell-launcher.mjs +23 -12
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/native-review-cli.mjs +49 -108
- package/runtime/review-risk-assessment.mjs +154 -9
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/gentle-ai-installer.mjs +14 -13
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/run-test-suite.mjs +76 -0
- package/scripts/test-packed-runner.mjs +31 -14
- package/scripts/verify-package-files.mjs +11 -22
- 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 +9 -15
- 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-profiles.test.ts +18 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +40 -0
- package/tests/agents-runner.test.ts +378 -89
- 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/autonomous-guard.test.ts +69 -1
- package/tests/bounded-writer-admission.test.ts +95 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/card-style-policy.test.ts +55 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-safety.test.ts +82 -0
- package/tests/codemode-rendering.test.ts +491 -0
- package/tests/command-palette.test.ts +39 -3
- package/tests/delegated-key-learnings-contract.test.ts +0 -76
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +170 -2
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -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 +54 -0
- package/tests/gentle-agents.test.ts +1047 -252
- package/tests/gentle-ai-binary.test.ts +3 -3
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +68 -54
- package/tests/gentle-ai-renderer.test.ts +487 -8
- package/tests/gentle-ai.test.ts +288 -65
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-shell-bin.test.ts +651 -114
- package/tests/gentle-shell-launcher.test.ts +99 -52
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell.test.ts +3839 -187
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +80 -8
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -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 +29 -19
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/nan-provider.test.ts +471 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +6 -120
- package/tests/native-review-parity-runtime.test.ts +100 -3
- package/tests/odd-integration.test.ts +67 -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 +11 -6
- package/tests/odd-routing-contract.test.ts +86 -35
- package/tests/orchestrator-budget.test.ts +14 -39
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +252 -115
- package/tests/packed-runner-owned-path.test.ts +46 -0
- package/tests/persona-single-channel.test.ts +6 -6
- package/tests/provider-defect-handoff.test.ts +3 -11
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +409 -184
- package/tests/rdd-aware-verification-contract.test.ts +76 -1
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +176 -12
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +298 -0
- package/tests/review-contract-prompt.test.ts +108 -43
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-routing.test.ts +611 -5
- package/tests/review-controller-workspace-root.test.ts +163 -4
- package/tests/review-host-relay-routing.test.ts +338 -2
- package/tests/review-integration-v2-forward.test.ts +200 -0
- package/tests/review-ledger-contract.test.ts +10 -34
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +498 -6
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/run-test-suite.test.ts +124 -0
- package/tests/runtime-harness.mjs +145 -786
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/selection-engine.test.ts +421 -0
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-worktree-registry.test.ts +77 -0
- package/tests/shell-bar.test.ts +382 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +52 -0
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-sidebar-layout.test.ts +325 -21
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +87 -1
- package/tests/shell-usage-view.test.ts +27 -0
- package/tests/shell-usage.test.ts +73 -0
- package/tests/skill-registry.test.ts +50 -1
- package/tests/startup-banner.test.ts +130 -2
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/telemetry-trigger.test.ts +81 -20
- package/tests/theme-customization.test.ts +72 -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 +110 -0
- package/tests/visual-customize-view.test.ts +418 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/yolo-customize.test.ts +256 -0
- package/tests/yolo-mode-runtime.test.ts +161 -0
- package/tests/yolo-mode.test.ts +261 -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
|
@@ -68,8 +68,6 @@ export const NATIVE_REVIEW_OPERATION = {
|
|
|
68
68
|
CAPTURE_PROVIDER_ROLE: "review/capture-provider-role",
|
|
69
69
|
CAPTURE_UNACHIEVABLE: "review/capture-unachievable",
|
|
70
70
|
ACKNOWLEDGE_APPROVED: "review/acknowledge-approved",
|
|
71
|
-
SDD_STATUS: "sdd-status",
|
|
72
|
-
SDD_CONTINUE: "sdd-continue",
|
|
73
71
|
} ;
|
|
74
72
|
|
|
75
73
|
|
|
@@ -124,11 +122,6 @@ export const NATIVE_REVIEW_ERROR_CODE = {
|
|
|
124
122
|
|
|
125
123
|
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
125
|
export const NATIVE_REVIEW_MODE_OPERATION = {
|
|
133
126
|
STATUS: "status",
|
|
134
127
|
ENABLE: "enable",
|
|
@@ -168,35 +161,6 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
|
|
|
168
161
|
|
|
169
162
|
|
|
170
163
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* The native CLI owns this complete v2 record. The decoder validates the fields
|
|
177
|
-
* Pi relies on and returns the original object without adding, omitting, or
|
|
178
|
-
* reconciling local SDD state.
|
|
179
|
-
*/
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
164
|
|
|
201
165
|
// Read-only risk assessment request (gentle-pi#662). `baseRef` requires
|
|
202
166
|
// explicit `committedOnly` acknowledgement, exactly like Native START's
|
|
@@ -748,6 +712,21 @@ function nativeUntrackedSelectionArguments(selection )
|
|
|
748
712
|
];
|
|
749
713
|
}
|
|
750
714
|
|
|
715
|
+
// Fail closed on malformed or duplicate provider selectors before invoking STATUS.
|
|
716
|
+
function providerFlagValue(tokens , flag ) {
|
|
717
|
+
let found ;
|
|
718
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
719
|
+
const token = tokens[index] ;
|
|
720
|
+
if (token !== `--${flag}` && !token.startsWith(`--${flag}=`)) continue;
|
|
721
|
+
if (found !== undefined) throw new TypeError(`Native intended-untracked selection repeats --${flag}`);
|
|
722
|
+
const value = token === `--${flag}` ? tokens[index + 1] : token.slice(`--${flag}=`.length);
|
|
723
|
+
if (value === undefined || value === "" || value.startsWith("--")) throw new TypeError(`Native intended-untracked selection has an invalid --${flag} value`);
|
|
724
|
+
found = value;
|
|
725
|
+
if (token === `--${flag}`) index++;
|
|
726
|
+
}
|
|
727
|
+
return found;
|
|
728
|
+
}
|
|
729
|
+
|
|
751
730
|
const NATIVE_RISK_LEVEL = ["low", "medium", "high"] ;
|
|
752
731
|
|
|
753
732
|
// gentle-ai's negotiated `start/v2` envelope is a closed schema
|
|
@@ -1049,6 +1028,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
1049
1028
|
// 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c
|
|
1050
1029
|
// at contract 1.2.0. No new negotiated capability is asserted.
|
|
1051
1030
|
"3.7.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
1031
|
+
// v4.0.0 repeats 3.7.0: the published provider-contract tar remains SHA-256
|
|
1032
|
+
// 547b68e172cc87aa297309d61624e5fc2c24d407a494b53eeb5a2b053904352c
|
|
1033
|
+
// at contract 1.2.0, and the published binary still advertises
|
|
1034
|
+
// capabilities/v2.6 under review-integration/v2. The Go module path moved
|
|
1035
|
+
// to /v4 without a review-integration/v2 change. No new negotiated
|
|
1036
|
+
// capability is asserted.
|
|
1037
|
+
"4.0.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
1052
1038
|
});
|
|
1053
1039
|
|
|
1054
1040
|
|
|
@@ -1516,52 +1502,6 @@ function nativeError(code , operation
|
|
|
1516
1502
|
|
|
1517
1503
|
|
|
1518
1504
|
|
|
1519
|
-
const NATIVE_SDD_DEPENDENCIES = ["proposal", "specs", "design", "tasks", "apply", "verify", "archive"] ;
|
|
1520
|
-
const NATIVE_SDD_INSTRUCTION_PHASES = ["apply", "verify", "archive"] ;
|
|
1521
|
-
const NATIVE_SDD_NEXT_RECOMMENDATIONS = ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"] ;
|
|
1522
|
-
const NATIVE_SDD_DEPENDENCY_STATES = ["blocked", "ready", "all_done"] ;
|
|
1523
|
-
|
|
1524
|
-
/** Strictly validates the native v2 contract while preserving its whole record. */
|
|
1525
|
-
export function decodeNativeSddStatusV2(value , request ) {
|
|
1526
|
-
const status = object(value);
|
|
1527
|
-
if (status.schemaName !== "gentle-ai.sdd-status" || status.schemaVersion !== 2) throw new Error("wrong native SDD status schema");
|
|
1528
|
-
if ((request.changeName !== undefined && status.changeName !== request.changeName) || (status.changeName !== null && !isCanonicalProcessString(status.changeName))) throw new Error("native SDD status change identity mismatch");
|
|
1529
|
-
const artifactStore = enumString(status.artifactStore, ["openspec", "engram", "hybrid", "none"]);
|
|
1530
|
-
const planningHome = object(status.planningHome);
|
|
1531
|
-
if (planningHome.mode !== "repo-local" || !isCanonicalProcessString(planningHome.path)) throw new Error("invalid native SDD planning home");
|
|
1532
|
-
const expectedOpenSpecHome = join(request.workspaceRoot, "openspec");
|
|
1533
|
-
if (planningHome.path !== expectedOpenSpecHome && !((artifactStore === "engram" || artifactStore === "hybrid") && planningHome.path === "engram:sdd")) throw new Error("native SDD planning home escaped its workspace");
|
|
1534
|
-
if (status.changeRoot !== null && !isCanonicalProcessString(status.changeRoot)) throw new Error("invalid native SDD change root");
|
|
1535
|
-
const actionContext = object(status.actionContext);
|
|
1536
|
-
if (actionContext.mode !== "repo-local" || actionContext.workspaceRoot !== request.workspaceRoot || !isCanonicalProcessString(actionContext.workspaceRoot)) throw new Error("native SDD status workspace root mismatch");
|
|
1537
|
-
const allowedEditRoots = stringArray(actionContext.allowedEditRoots);
|
|
1538
|
-
if (!allowedEditRoots.includes(request.workspaceRoot) || allowedEditRoots.some((root) => !isAbsolute(root) || root !== join(root))) throw new Error("invalid native SDD allowed edit roots");
|
|
1539
|
-
const dependencies = object(status.dependencies);
|
|
1540
|
-
for (const phase of NATIVE_SDD_DEPENDENCIES) {
|
|
1541
|
-
if (enumString(dependencies[phase], NATIVE_SDD_DEPENDENCY_STATES) !== dependencies[phase]) throw new Error("invalid native SDD dependency");
|
|
1542
|
-
}
|
|
1543
|
-
if (Object.keys(dependencies).length !== NATIVE_SDD_DEPENDENCIES.length) throw new Error("native SDD dependencies have an unsupported shape");
|
|
1544
|
-
if (status.instructions !== undefined) throw new Error("native SDD status uses phaseInstructions, not instructions");
|
|
1545
|
-
if (status.phaseInstructions !== undefined) {
|
|
1546
|
-
const instructions = object(status.phaseInstructions);
|
|
1547
|
-
for (const phase of NATIVE_SDD_INSTRUCTION_PHASES) stringArray(instructions[phase]);
|
|
1548
|
-
// Classical SDD no longer emits a remediation phase. Keep the published
|
|
1549
|
-
// producer's optional legacy instructions intact without inventing them
|
|
1550
|
-
// for a newer producer or accepting unknown phase keys.
|
|
1551
|
-
const hasRemediation = Object.hasOwn(instructions, "remediate");
|
|
1552
|
-
if (hasRemediation) stringArray(instructions.remediate);
|
|
1553
|
-
if (Object.keys(instructions).length !== NATIVE_SDD_INSTRUCTION_PHASES.length + Number(hasRemediation)) throw new Error("native SDD instructions have an unsupported shape");
|
|
1554
|
-
if (status.nextRecommended === "remediate" && !hasRemediation) throw new Error("native SDD remediation instructions are missing");
|
|
1555
|
-
}
|
|
1556
|
-
if (status.nextRecommended === "remediate" || status.remediationState !== undefined) {
|
|
1557
|
-
const remediation = object(status.remediationState);
|
|
1558
|
-
if (typeof remediation.required !== "boolean" || typeof remediation.complete !== "boolean" || typeof remediation.failedEvidenceRevision !== "string" || (remediation.failedEvidenceRevision !== "" && !/^sha256:[0-9a-f]{64}$/.test(remediation.failedEvidenceRevision)) || (status.nextRecommended === "remediate" && (!remediation.required || remediation.complete || !remediation.failedEvidenceRevision))) throw new Error("Invalid native remediation state");
|
|
1559
|
-
}
|
|
1560
|
-
stringArray(status.blockedReasons);
|
|
1561
|
-
enumString(status.nextRecommended, NATIVE_SDD_NEXT_RECOMMENDATIONS);
|
|
1562
|
-
return status ;
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
1505
|
class NativeReviewPlainCli {
|
|
1566
1506
|
adapter ;
|
|
1567
1507
|
executable ;
|
|
@@ -2200,30 +2140,6 @@ export class NativeReviewCliV216 {
|
|
|
2200
2140
|
return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
|
|
2201
2141
|
}
|
|
2202
2142
|
|
|
2203
|
-
async sddStatus(request ) {
|
|
2204
|
-
return this.sddProjection(request, false);
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
async sddContinue(request ) {
|
|
2208
|
-
if (!isCanonicalProcessString(request.changeName)) throw new TypeError("Native SDD continuation requires an exact selected change");
|
|
2209
|
-
return this.sddProjection(request, true);
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
|
-
async sddProjection(request , mutating ) {
|
|
2213
|
-
if ((request.changeName !== undefined && !isCanonicalProcessString(request.changeName)) || !isCanonicalProcessString(request.workspaceRoot) || !isAbsolute(request.workspaceRoot)) {
|
|
2214
|
-
throw new TypeError("Native SDD status requires a canonical change and absolute workspace root");
|
|
2215
|
-
}
|
|
2216
|
-
const operation = mutating ? NATIVE_REVIEW_OPERATION.SDD_CONTINUE : NATIVE_REVIEW_OPERATION.SDD_STATUS;
|
|
2217
|
-
const execution = await this.negotiated(
|
|
2218
|
-
operation,
|
|
2219
|
-
request.workspaceRoot,
|
|
2220
|
-
[operation, ...(request.changeName === undefined ? [] : [request.changeName]), "--cwd", request.workspaceRoot, "--json", "--instructions"],
|
|
2221
|
-
mutating,
|
|
2222
|
-
request.signal,
|
|
2223
|
-
);
|
|
2224
|
-
return decode(operation, mutating, () => decodeNativeSddStatusV2(execution.body, request));
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
2143
|
async start(request ) {
|
|
2228
2144
|
if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native START baseRef must be a non-empty, trimmed, NUL-free string");
|
|
2229
2145
|
if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native START baseRef requires explicit committedOnly acknowledgement");
|
|
@@ -2346,7 +2262,32 @@ export class NativeReviewCliV216 {
|
|
|
2346
2262
|
const selection = nativeUntrackedSelection(request);
|
|
2347
2263
|
const submitted = request.intendedUntrackedSelection;
|
|
2348
2264
|
if (submitted !== undefined && submitted.argumentTokens.reduce((count, token) => count + token.split("{{value}}").length - 1, 0) !== 1) throw new TypeError("Native intended-untracked selection requires exactly one provider-issued {{value}} token");
|
|
2349
|
-
const
|
|
2265
|
+
const submittedTokens = submitted === undefined ? undefined : submitted.argumentTokens.map((token) => token.replaceAll("{{value}}", submitted.value));
|
|
2266
|
+
// gentle-ai 3.7.1 submission argument_tokens never carry --base-ref /
|
|
2267
|
+
// --committed-only / --lineage, so a forwarded selector must be appended
|
|
2268
|
+
// after them; only bail if the provider already staked out a conflicting
|
|
2269
|
+
// value for the same flag. providerFlagValue also fails closed on a
|
|
2270
|
+
// dangling `--flag` (last token, no value) or an empty `--flag=`.
|
|
2271
|
+
const submittedBaseRefValue = submittedTokens === undefined ? undefined : providerFlagValue(submittedTokens, "base-ref");
|
|
2272
|
+
if (request.baseRef !== undefined && submittedBaseRefValue !== undefined && submittedBaseRefValue !== request.baseRef) throw new TypeError("Native intended-untracked selection already carries a conflicting base-ref");
|
|
2273
|
+
const submittedLineageValue = submittedTokens === undefined ? undefined : providerFlagValue(submittedTokens, "lineage");
|
|
2274
|
+
if (request.lineageId !== undefined && submittedLineageValue !== undefined && submittedLineageValue !== request.lineageId) throw new TypeError("Native intended-untracked selection already carries a conflicting lineage");
|
|
2275
|
+
let submittedHasCommittedOnly = false;
|
|
2276
|
+
for (const token of submittedTokens ?? []) {
|
|
2277
|
+
if (token !== "--committed-only" && !token.startsWith("--committed-only=")) continue;
|
|
2278
|
+
if (token !== "--committed-only" && token !== "--committed-only=true") throw new TypeError("Native intended-untracked selection has an invalid --committed-only value");
|
|
2279
|
+
if (submittedHasCommittedOnly) throw new TypeError("Native intended-untracked selection repeats --committed-only");
|
|
2280
|
+
submittedHasCommittedOnly = true;
|
|
2281
|
+
}
|
|
2282
|
+
// Append only the half of the base-ref/committed-only pair the provider
|
|
2283
|
+
// did not already carry (a present base-ref already equals
|
|
2284
|
+
// request.baseRef; the throw above ruled out the conflicting case).
|
|
2285
|
+
const forwardedBaseRef = request.baseRef === undefined ? [] : [
|
|
2286
|
+
...(submittedBaseRefValue === undefined ? ["--base-ref", request.baseRef] : []),
|
|
2287
|
+
...(submittedHasCommittedOnly ? [] : ["--committed-only"]),
|
|
2288
|
+
];
|
|
2289
|
+
const forwardedLineage = request.lineageId === undefined || submittedLineageValue !== undefined ? [] : ["--lineage", request.lineageId];
|
|
2290
|
+
const statusArguments = submittedTokens === undefined ? [
|
|
2350
2291
|
"review", "status", "--contract", REVIEW_INTEGRATION_CONTRACT, "--cwd", request.cwd,
|
|
2351
2292
|
"--projection", request.projection ?? "workspace",
|
|
2352
2293
|
...nativeUntrackedSelectionArguments(selection),
|
|
@@ -2354,7 +2295,7 @@ export class NativeReviewCliV216 {
|
|
|
2354
2295
|
...(request.lineageId === undefined ? [] : ["--lineage", request.lineageId]),
|
|
2355
2296
|
...(request.agent === undefined ? [] : ["--agent", request.agent]),
|
|
2356
2297
|
"--next-transition",
|
|
2357
|
-
] : ["review", "status", "--cwd", request.cwd, ...
|
|
2298
|
+
] : ["review", "status", "--cwd", request.cwd, ...submittedTokens, ...forwardedBaseRef, ...forwardedLineage];
|
|
2358
2299
|
const execution = await this.negotiated(NATIVE_REVIEW_OPERATION.STATUS, request.cwd, statusArguments, false, request.signal);
|
|
2359
2300
|
assertSupportedNextTransitionOperation(execution.body);
|
|
2360
2301
|
return decode(NATIVE_REVIEW_OPERATION.STATUS, false, () => decodeReviewStatusV3(execution.body));
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
// prints `{"schema":"gentle-ai.review-assessment/v1","risk":"passive|medium|high",
|
|
12
12
|
// "reasons":[{"code":"…","path":"…","detail":"…"}],"changed_paths":1,
|
|
13
13
|
// "changed_lines":1,"candidate":{"kind":"current-changes|base-diff","base_ref":"…"}}`.
|
|
14
|
+
// gentle-pi#1175: the native v2 `assess.schema.json` requires only `code` on a
|
|
15
|
+
// reason (`path`/`detail` are optional) and adds `candidate.consumed`,
|
|
16
|
+
// `review_due`, `review_due_reason`, and an opaque `next_transition`. Older
|
|
17
|
+
// binaries (for example gentle-ai v3.7.0) predate those fields, so
|
|
18
|
+
// they decode as optional and stay absent rather than being defaulted.
|
|
14
19
|
// A non-zero exit or a failure envelope means the candidate could not be
|
|
15
20
|
// assessed; hosts treat that as `high`. Older binaries without the verb (or
|
|
16
21
|
// any other decode/process failure) fail closed the same way -- see
|
|
@@ -31,6 +36,41 @@ export const REVIEW_ASSESSMENT_CANDIDATE_KIND = {
|
|
|
31
36
|
} ;
|
|
32
37
|
|
|
33
38
|
|
|
39
|
+
export const REVIEW_DUE_REASON = {
|
|
40
|
+
HIGH_RISK: "high_risk",
|
|
41
|
+
SLICE_BUDGET_REACHED: "slice_budget_reached",
|
|
42
|
+
PASSIVE: "passive",
|
|
43
|
+
UNDER_BUDGET: "under_budget",
|
|
44
|
+
ALREADY_REVIEWED: "already_reviewed",
|
|
45
|
+
} ;
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
export const REVIEW_ASSESSMENT_NEXT_TRANSITION_OPERATION = "review.status" ;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The native, literally runnable `review status ... --next-transition`
|
|
71
|
+
* preflight continuation. Carried verbatim as an opaque value: hosts never
|
|
72
|
+
* rebuild, reorder, or synthesize it.
|
|
73
|
+
*/
|
|
34
74
|
|
|
35
75
|
|
|
36
76
|
|
|
@@ -55,25 +95,99 @@ function isRecord(value ) {
|
|
|
55
95
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
56
96
|
}
|
|
57
97
|
|
|
98
|
+
function isNonEmptyString(value ) {
|
|
99
|
+
return typeof value === "string" && value.length > 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
58
102
|
function decodeReason(value ) {
|
|
59
103
|
if (!isRecord(value)) throw new TypeError("review assessment reason must be an object");
|
|
60
104
|
const { code, path, detail } = value;
|
|
61
|
-
if (
|
|
62
|
-
if (
|
|
63
|
-
if (
|
|
64
|
-
|
|
105
|
+
if (!isNonEmptyString(code)) throw new TypeError("review assessment reason.code must be a non-empty string");
|
|
106
|
+
if (path !== undefined && !isNonEmptyString(path)) throw new TypeError("review assessment reason.path must be a non-empty string when present");
|
|
107
|
+
if (detail !== undefined && !isNonEmptyString(detail)) throw new TypeError("review assessment reason.detail must be a non-empty string when present");
|
|
108
|
+
// Absent path/detail stay absent: never synthesized (gentle-pi#1175).
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
code,
|
|
111
|
+
...(path === undefined ? {} : { path: path }),
|
|
112
|
+
...(detail === undefined ? {} : { detail: detail }),
|
|
113
|
+
});
|
|
65
114
|
}
|
|
66
115
|
|
|
67
116
|
function decodeCandidate(value ) {
|
|
68
117
|
if (!isRecord(value)) throw new TypeError("review assessment candidate must be an object");
|
|
69
|
-
const { kind, base_ref: baseRef } = value;
|
|
118
|
+
const { kind, base_ref: baseRef, consumed } = value;
|
|
70
119
|
if (kind !== REVIEW_ASSESSMENT_CANDIDATE_KIND.CURRENT_CHANGES && kind !== REVIEW_ASSESSMENT_CANDIDATE_KIND.BASE_DIFF) {
|
|
71
120
|
throw new TypeError("review assessment candidate.kind must be current-changes or base-diff");
|
|
72
121
|
}
|
|
73
|
-
if (baseRef !== undefined && (
|
|
122
|
+
if (baseRef !== undefined && !isNonEmptyString(baseRef)) {
|
|
74
123
|
throw new TypeError("review assessment candidate.base_ref must be a non-empty string when present");
|
|
75
124
|
}
|
|
76
|
-
|
|
125
|
+
if (consumed !== undefined && typeof consumed !== "boolean") {
|
|
126
|
+
throw new TypeError("review assessment candidate.consumed must be a boolean when present");
|
|
127
|
+
}
|
|
128
|
+
// An older binary omits consumed; it stays absent, never defaulted.
|
|
129
|
+
return Object.freeze({ kind, baseRef: baseRef , ...(consumed === undefined ? {} : { consumed: consumed }) });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function isReviewDueReason(value ) {
|
|
133
|
+
return Object.values(REVIEW_DUE_REASON).includes(value );
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The native schema fixes review_due by reason: due for high_risk and
|
|
137
|
+
// slice_budget_reached, not due for passive, under_budget, and
|
|
138
|
+
// already_reviewed (gentle-pi#1175).
|
|
139
|
+
const DUE_REVIEW_REASONS = [REVIEW_DUE_REASON.HIGH_RISK, REVIEW_DUE_REASON.SLICE_BUDGET_REACHED];
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Rejects a review_due/review_due_reason/consumed combination the native
|
|
143
|
+
* schema can never produce. `already_reviewed` takes precedence exactly when
|
|
144
|
+
* the candidate is consumed, so the two must agree in both directions. A
|
|
145
|
+
* contradictory envelope is never trusted, least of all as closure evidence.
|
|
146
|
+
* Envelopes without the pair (older binaries) are not checked here.
|
|
147
|
+
*/
|
|
148
|
+
function validateReviewDueConsistency(reviewDue , reviewDueReason , candidate ) {
|
|
149
|
+
if (reviewDue !== DUE_REVIEW_REASONS.includes(reviewDueReason)) {
|
|
150
|
+
throw new TypeError(`review assessment review_due ${reviewDue} contradicts review_due_reason ${reviewDueReason}`);
|
|
151
|
+
}
|
|
152
|
+
const alreadyReviewed = reviewDueReason === REVIEW_DUE_REASON.ALREADY_REVIEWED;
|
|
153
|
+
if (alreadyReviewed !== (candidate.consumed === true)) {
|
|
154
|
+
throw new TypeError("review assessment review_due_reason already_reviewed must be reported exactly when candidate.consumed is true");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function hasOnlyKeys(value , allowed ) {
|
|
159
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function validateNextTransitionArgument(value ) {
|
|
163
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["name", "value", "token"])) {
|
|
164
|
+
throw new TypeError("review assessment next_transition argument must be an object with only name, value, and token");
|
|
165
|
+
}
|
|
166
|
+
if (!isNonEmptyString(value.name)) throw new TypeError("review assessment next_transition argument name must be a non-empty string");
|
|
167
|
+
if (typeof value.value !== "string") throw new TypeError("review assessment next_transition argument value must be a string");
|
|
168
|
+
if (value.token !== undefined && !isNonEmptyString(value.token)) {
|
|
169
|
+
throw new TypeError("review assessment next_transition argument token must be a non-empty string when present");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Validates `next_transition` exactly as the native schema declares it and
|
|
175
|
+
* returns the native value itself, frozen -- validated, never rebuilt,
|
|
176
|
+
* reordered, or synthesized (gentle-pi#1175).
|
|
177
|
+
*/
|
|
178
|
+
function decodeNextTransition(value ) {
|
|
179
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["operation", "command", "arguments"])) {
|
|
180
|
+
throw new TypeError("review assessment next_transition must be an object with only operation, command, and arguments");
|
|
181
|
+
}
|
|
182
|
+
if (value.operation !== REVIEW_ASSESSMENT_NEXT_TRANSITION_OPERATION) {
|
|
183
|
+
throw new TypeError(`review assessment next_transition.operation must be ${REVIEW_ASSESSMENT_NEXT_TRANSITION_OPERATION}`);
|
|
184
|
+
}
|
|
185
|
+
if (!isNonEmptyString(value.command)) throw new TypeError("review assessment next_transition.command must be a non-empty string");
|
|
186
|
+
if (!Array.isArray(value.arguments)) throw new TypeError("review assessment next_transition.arguments must be an array");
|
|
187
|
+
for (const argument of value.arguments) validateNextTransitionArgument(argument);
|
|
188
|
+
for (const argument of value.arguments) Object.freeze(argument);
|
|
189
|
+
Object.freeze(value.arguments);
|
|
190
|
+
return Object.freeze(value) ;
|
|
77
191
|
}
|
|
78
192
|
|
|
79
193
|
/**
|
|
@@ -82,24 +196,55 @@ function decodeCandidate(value ) {
|
|
|
82
196
|
* native review CLI wrapper turns a thrown error here into the same
|
|
83
197
|
* schema-incompatible failure it already produces for every other decoded
|
|
84
198
|
* native response.
|
|
199
|
+
*
|
|
200
|
+
* `review_due` and `review_due_reason` were added together natively: both
|
|
201
|
+
* absent (an older binary) or both present are accepted; only one of them is
|
|
202
|
+
* rejected. When present, the pair must agree with each other and with
|
|
203
|
+
* `candidate.consumed` (see `validateReviewDueConsistency`).
|
|
204
|
+
* `next_transition` is accepted only alongside `review_due: true`.
|
|
205
|
+
* Unknown extra top-level fields are ignored and never projected, so a newer
|
|
206
|
+
* binary stays decodable (gentle-pi#1175).
|
|
85
207
|
*/
|
|
86
208
|
export function decodeReviewAssessmentV1(value ) {
|
|
87
209
|
if (!isRecord(value)) throw new TypeError("review assessment must be an object");
|
|
88
210
|
if (value.schema !== REVIEW_ASSESSMENT_SCHEMA) throw new TypeError(`review assessment schema must be ${REVIEW_ASSESSMENT_SCHEMA}`);
|
|
89
|
-
const {
|
|
211
|
+
const {
|
|
212
|
+
risk,
|
|
213
|
+
reasons,
|
|
214
|
+
changed_paths: changedPaths,
|
|
215
|
+
changed_lines: changedLines,
|
|
216
|
+
candidate,
|
|
217
|
+
review_due: reviewDue,
|
|
218
|
+
review_due_reason: reviewDueReason,
|
|
219
|
+
next_transition: nextTransition,
|
|
220
|
+
} = value;
|
|
90
221
|
if (risk !== REVIEW_ASSESSMENT_RISK.PASSIVE && risk !== REVIEW_ASSESSMENT_RISK.MEDIUM && risk !== REVIEW_ASSESSMENT_RISK.HIGH) {
|
|
91
222
|
throw new TypeError("review assessment risk must be passive, medium, or high");
|
|
92
223
|
}
|
|
93
224
|
if (!Array.isArray(reasons)) throw new TypeError("review assessment reasons must be an array");
|
|
94
225
|
if (!Number.isSafeInteger(changedPaths) || (changedPaths ) < 0) throw new TypeError("review assessment changed_paths must be a non-negative integer");
|
|
95
226
|
if (!Number.isSafeInteger(changedLines) || (changedLines ) < 0) throw new TypeError("review assessment changed_lines must be a non-negative integer");
|
|
227
|
+
if ((reviewDue === undefined) !== (reviewDueReason === undefined)) {
|
|
228
|
+
throw new TypeError("review assessment review_due and review_due_reason must be both present or both absent");
|
|
229
|
+
}
|
|
230
|
+
if (reviewDue !== undefined && typeof reviewDue !== "boolean") throw new TypeError("review assessment review_due must be a boolean when present");
|
|
231
|
+
if (reviewDueReason !== undefined && !isReviewDueReason(reviewDueReason)) {
|
|
232
|
+
throw new TypeError(`review assessment review_due_reason must be one of ${Object.values(REVIEW_DUE_REASON).join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
if (nextTransition !== undefined && reviewDue !== true) {
|
|
235
|
+
throw new TypeError("review assessment next_transition is only valid when review_due is true");
|
|
236
|
+
}
|
|
237
|
+
const decodedCandidate = decodeCandidate(candidate);
|
|
238
|
+
if (reviewDue !== undefined) validateReviewDueConsistency(reviewDue , reviewDueReason , decodedCandidate);
|
|
96
239
|
return Object.freeze({
|
|
97
240
|
schema: REVIEW_ASSESSMENT_SCHEMA,
|
|
98
241
|
risk,
|
|
99
242
|
reasons: Object.freeze(reasons.map(decodeReason)),
|
|
100
243
|
changedPaths: changedPaths ,
|
|
101
244
|
changedLines: changedLines ,
|
|
102
|
-
candidate:
|
|
245
|
+
candidate: decodedCandidate,
|
|
246
|
+
...(reviewDue === undefined ? {} : { reviewDue: reviewDue , reviewDueReason: reviewDueReason }),
|
|
247
|
+
...(nextTransition === undefined ? {} : { nextTransition: decodeNextTransition(nextTransition) }),
|
|
103
248
|
});
|
|
104
249
|
}
|
|
105
250
|
|
|
@@ -36,19 +36,20 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
|
|
|
36
36
|
// version check below) derives from this constant instead of repeating the
|
|
37
37
|
// literal, so a pin bump cannot leave a stale copy behind. See
|
|
38
38
|
// scripts/install-gentle-ai.mjs for the incident that motivated this.
|
|
39
|
-
export const INSTALLER_VERSION = "
|
|
39
|
+
export const INSTALLER_VERSION = "4.0.0";
|
|
40
40
|
export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
|
|
41
41
|
export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
42
42
|
SIGNED_RELEASE_ASSET: "signed-release-asset",
|
|
43
43
|
GO_SUMDB_SOURCE_BUILD: "go-sumdb-source-build",
|
|
44
44
|
});
|
|
45
|
-
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/
|
|
46
|
-
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/
|
|
45
|
+
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai";
|
|
46
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v4";
|
|
47
47
|
export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
|
|
48
|
-
// `go mod download -json github.com/gentleman-programming/gentle-ai/
|
|
48
|
+
// `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.0.0`
|
|
49
49
|
// with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
|
|
50
|
-
// tag resolves to commit
|
|
51
|
-
|
|
50
|
+
// tag resolves to commit ff77164d4f56f1665b22fb6fac51c2ccbb769400, the published v4.0.0 release head.
|
|
51
|
+
// v4.0.0 moved the Go module path to the /v4 major-version suffix.
|
|
52
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:pZ/XZ2Pk3U9lgXigOTY62zlxxFOHnc9CjQhLgaV/Hfc=";
|
|
52
53
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
|
|
53
54
|
export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
|
|
54
55
|
export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
|
|
@@ -67,7 +68,7 @@ export class GentleAiInstallerError extends Error {
|
|
|
67
68
|
// Sentinel used while a re-pinned gentle-ai release is not yet published. A
|
|
68
69
|
// sentinel digest can never match a real SHA-256, so installation fails closed,
|
|
69
70
|
// and verify-package-files.mjs refuses to pack/publish while any digest below
|
|
70
|
-
// still holds it. The
|
|
71
|
+
// still holds it. The v4.0.0 digests are pinned from the published release:
|
|
71
72
|
// archive sha256 values verified against the minisign-signed checksums.txt and
|
|
72
73
|
// freshly computed hashes; binary sha256 values computed from the extracted
|
|
73
74
|
// executables.
|
|
@@ -109,15 +110,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
|
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
// Windows is absent from signed release archives on purpose. gentle-ai stopped
|
|
112
|
-
// distributing unsigned Windows builds in c4b764d0, so
|
|
113
|
+
// distributing unsigned Windows builds in c4b764d0, so v4.0.0 publishes signed
|
|
113
114
|
// Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
|
|
114
115
|
// exact-tag Go SumDB source-build path below; restore archive rows only when
|
|
115
116
|
// upstream ships signed Windows assets.
|
|
116
117
|
export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
|
|
117
|
-
"darwin/amd64": asset("gentle-
|
|
118
|
-
"darwin/arm64": asset("gentle-
|
|
119
|
-
"linux/amd64": asset("gentle-
|
|
120
|
-
"linux/arm64": asset("gentle-
|
|
118
|
+
"darwin/amd64": asset("gentle-ai_4.0.0_darwin_amd64.tar.gz", "b5b74f22b38ec3339b38e8c68f797dc76ff12ed6580826a6e425d5c718da80c1", "d4a5b16ff70e65331e17a62356941bb0c75ecb9dbe3a0d98a6b54cfbd76cd6b0", "gentle-ai"),
|
|
119
|
+
"darwin/arm64": asset("gentle-ai_4.0.0_darwin_arm64.tar.gz", "d2159caf6d68f367b18830ece6af71ef26963d5f5320d7df6a794773f45cc7e9", "18a9f7fae55d85c95684b6d512a4a148d0cb24a856325f72573c34caf65159eb", "gentle-ai"),
|
|
120
|
+
"linux/amd64": asset("gentle-ai_4.0.0_linux_amd64.tar.gz", "5f4417cf29c969c86da4799942fd673368840901be1bb09c779a12d7ed6096ea", "50ba217b5138c1a9c7d5bf2f79931b1bb89b89c4cf650dcd7ee037657c88158d", "gentle-ai"),
|
|
121
|
+
"linux/arm64": asset("gentle-ai_4.0.0_linux_arm64.tar.gz", "1383b040c95cfc69206660d73c21907b14ad70ab913f410917c54f43d3147e57", "6703704f0c4a5b70c36fbdc44db641e810871cab16bc28040d06aa1d10704ad3", "gentle-ai"),
|
|
121
122
|
});
|
|
122
123
|
|
|
123
124
|
// A pinned asset is either a signed archive or, for a prerelease pin only,
|
|
@@ -402,7 +403,7 @@ async function safeRemoveDirectory(path) {
|
|
|
402
403
|
try {
|
|
403
404
|
const details = await lstat(path);
|
|
404
405
|
if (!details.isDirectory() || details.isSymbolicLink()) throw new Error("not a real directory");
|
|
405
|
-
await rm(path, { recursive: true, force: true });
|
|
406
|
+
await rm(path, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
406
407
|
} catch (error) { if (!(error && typeof error === "object" && error.code === "ENOENT")) throw error; }
|
|
407
408
|
}
|
|
408
409
|
|
|
@@ -38,8 +38,8 @@ import (
|
|
|
38
38
|
"fmt"
|
|
39
39
|
"os"
|
|
40
40
|
|
|
41
|
-
"github.com/gentleman-programming/gentle-ai/
|
|
42
|
-
"github.com/gentleman-programming/gentle-ai/
|
|
41
|
+
"github.com/gentleman-programming/gentle-ai/v4/internal/components/agentguidance"
|
|
42
|
+
"github.com/gentleman-programming/gentle-ai/v4/internal/model"
|
|
43
43
|
)
|
|
44
44
|
|
|
45
45
|
func main() {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Sequential test-suite runner for `pnpm test` (gentle-pi#1285).
|
|
3
|
+
//
|
|
4
|
+
// `pnpm test` used to chain its three stages with `&&`, so any stage-1
|
|
5
|
+
// failure — including a known-flaky unit test — silently suppressed the
|
|
6
|
+
// provider-contract check and the runtime harness while reporting a plain
|
|
7
|
+
// "test failed". This runner executes every stage unconditionally, prints a
|
|
8
|
+
// per-stage header and a final summary, and exits non-zero when any stage
|
|
9
|
+
// fails. Stages are plain shell commands, so behavior stays identical on
|
|
10
|
+
// POSIX and Windows CI.
|
|
11
|
+
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_STAGES = Object.freeze([
|
|
17
|
+
{ name: "unit-tests", command: "node --experimental-strip-types --test tests/*.test.ts" },
|
|
18
|
+
{ name: "provider-contract", command: "pnpm run check:provider-contract" },
|
|
19
|
+
{ name: "runtime-harness", command: "pnpm run test:harness" },
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
export async function runStage(stage) {
|
|
23
|
+
return await new Promise((resolve) => {
|
|
24
|
+
const child = spawn(stage.command, { shell: true, stdio: "inherit" });
|
|
25
|
+
child.on("close", (code) => resolve({ name: stage.name, code: code ?? 1 }));
|
|
26
|
+
child.on("error", (error) => {
|
|
27
|
+
console.error(`\n[${stage.name}] spawn failed: ${error.message}`);
|
|
28
|
+
resolve({ name: stage.name, code: 1 });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runTestSuite(stages = DEFAULT_STAGES, { runStageImpl = runStage, write = (line) => console.log(line) } = {}) {
|
|
34
|
+
const results = [];
|
|
35
|
+
for (const stage of stages) {
|
|
36
|
+
write(`\n=== [${stage.name}] ${stage.command} ===`);
|
|
37
|
+
results.push(await runStageImpl(stage));
|
|
38
|
+
}
|
|
39
|
+
write("\n=== test suite summary ===");
|
|
40
|
+
for (const result of results) write(`${result.code === 0 ? "PASS" : "FAIL"} ${result.name}`);
|
|
41
|
+
const failures = results.filter((result) => result.code !== 0);
|
|
42
|
+
write(failures.length === 0 ? "all stages passed" : `${failures.length} stage(s) failed: ${failures.map((f) => f.name).join(", ")}`);
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readStagesFromJsonPath(jsonPath) {
|
|
47
|
+
const stages = JSON.parse(readFileSync(jsonPath, "utf8"));
|
|
48
|
+
if (!Array.isArray(stages) || stages.some((stage) => typeof stage?.name !== "string" || typeof stage?.command !== "string")) {
|
|
49
|
+
throw new Error(`stages file must be an array of { name, command } objects: ${jsonPath}`);
|
|
50
|
+
}
|
|
51
|
+
return stages;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Direct-invocation detection must survive symlinks: Node real-paths
|
|
55
|
+
// `import.meta.url` but keeps `process.argv[1]` logical, so a plain equality
|
|
56
|
+
// check silently no-ops the whole runner (zero stages run, exit 0) when the
|
|
57
|
+
// script is reached through a file symlink.
|
|
58
|
+
function isDirectRun(argv1) {
|
|
59
|
+
if (!argv1) return false;
|
|
60
|
+
try {
|
|
61
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(import.meta.url));
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (isDirectRun(process.argv[1])) {
|
|
68
|
+
try {
|
|
69
|
+
const stages = process.argv[2] ? readStagesFromJsonPath(process.argv[2]) : DEFAULT_STAGES;
|
|
70
|
+
const results = await runTestSuite(stages);
|
|
71
|
+
process.exitCode = results.some((result) => result.code !== 0) ? 1 : 0;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(error instanceof Error ? error.message : error);
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
}
|
|
76
|
+
}
|