gentle-pi 2.4.0 → 2.6.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 +292 -25
- package/assets/agents/gentle-ai-worker.md +13 -0
- package/assets/agents/jd-fix-agent.md +18 -0
- package/assets/agents/jd-judge-a.md +1 -1
- package/assets/agents/jd-judge-b.md +1 -1
- package/assets/agents/sdd-apply.md +7 -5
- package/assets/agents/sdd-archive.md +5 -3
- package/assets/agents/sdd-design.md +4 -0
- package/assets/agents/sdd-explore.md +4 -0
- package/assets/agents/sdd-init.md +4 -0
- package/assets/agents/sdd-onboard.md +4 -0
- package/assets/agents/sdd-proposal.md +4 -0
- package/assets/agents/sdd-remediate.md +37 -0
- package/assets/agents/sdd-research.md +26 -3
- package/assets/agents/sdd-spec.md +4 -0
- package/assets/agents/sdd-status.md +9 -75
- package/assets/agents/sdd-sync.md +4 -0
- package/assets/agents/sdd-tasks.md +4 -0
- package/assets/agents/sdd-verify.md +5 -3
- package/assets/chains/sdd-full.chain.md +4 -0
- package/assets/chains/sdd-plan.chain.md +4 -0
- package/assets/chains/sdd-verify.chain.md +4 -0
- package/assets/migrations/managed-assets-v2.5.0.json +7 -0
- package/assets/orchestrator-delegation.md +39 -11
- package/assets/orchestrator.md +5 -5
- package/assets/sdd-orchestrator-workflow.md +54 -21
- package/assets/support/sdd-status-contract.md +34 -90
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
- package/docs/delegated-verification.md +25 -0
- package/docs/telemetry.md +94 -0
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +159 -25
- package/extensions/codegraph-tools.ts +95 -5
- package/extensions/gentle-agents.ts +1337 -0
- package/extensions/gentle-ai.ts +2916 -386
- package/extensions/gentle-shell.ts +650 -0
- package/extensions/gentle-todo.ts +234 -0
- package/extensions/quiet-tools.ts +2 -1
- package/extensions/runtime-metrics.ts +130 -0
- package/extensions/sdd-init.ts +2 -2
- package/extensions/startup-banner.ts +52 -75
- package/lib/agent-profiles.ts +550 -0
- package/lib/agents-completion-delivery.ts +72 -0
- package/lib/agents-config.ts +315 -0
- package/lib/agents-history.ts +88 -0
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +501 -0
- package/lib/agents-runner.ts +1012 -0
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +914 -0
- package/lib/agents-widget.ts +241 -0
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +143 -25
- package/lib/native-choice-list.ts +194 -0
- package/lib/native-fullscreen-interaction.ts +47 -0
- package/lib/native-pointer-region.ts +164 -0
- package/lib/native-review-cli.ts +371 -13
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +427 -0
- package/lib/review-candidate-view.ts +150 -48
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +110 -0
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +243 -11
- package/lib/review-last-event-controller.ts +8 -4
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-repository.ts +2 -2
- package/lib/review-risk-assessment.ts +339 -0
- package/lib/review-session-standing-permission-ipc.ts +309 -0
- package/lib/review-session-standing-permission.ts +240 -0
- package/lib/runtime-metrics-children.ts +199 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +166 -0
- package/lib/runtime-metrics-pi-identity.ts +113 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +255 -0
- package/lib/sdd-preflight.ts +362 -81
- package/lib/sdd-research-capabilities.ts +228 -0
- package/lib/sdd-status.ts +29 -7
- package/lib/session-worktree-registry.ts +118 -0
- package/lib/shell-bar.ts +184 -0
- package/lib/shell-card.ts +133 -0
- package/lib/shell-changes-view.ts +530 -0
- package/lib/shell-changes.ts +290 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +115 -0
- package/lib/shell-sidebar-banner.ts +11 -0
- package/lib/shell-sidebar-layout.ts +213 -0
- package/lib/shell-sidebar.ts +41 -0
- package/lib/shell-todo.ts +297 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +153 -0
- package/package.json +8 -5
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +370 -12
- package/runtime/review-integration-v2.mjs +243 -11
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +154 -0
- package/scripts/build-runtime-modules.mjs +11 -1
- package/scripts/check-types.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-gentle-ai.mjs +12 -0
- package/scripts/install-tui-mode-setting.mjs +114 -0
- package/scripts/test-packed-runner.mjs +38 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +8 -2
- package/skills/_shared/review-ledger-contract.md +20 -2
- package/skills/issue-creation/SKILL.md +3 -3
- package/skills/judgment-day/SKILL.md +17 -3
- package/skills/judgment-day/references/prompts-and-formats.md +14 -3
- package/tests/agent-profiles.test.ts +722 -0
- package/tests/agents-completion-delivery.test.ts +94 -0
- package/tests/agents-config.test.ts +205 -0
- package/tests/agents-fake-child.ts +66 -0
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +198 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +959 -0
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +685 -0
- package/tests/agents-widget.test.ts +141 -0
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +325 -5
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +112 -2
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +110 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +2168 -0
- package/tests/gentle-ai-binary.test.ts +7 -2
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +103 -0
- package/tests/gentle-ai.test.ts +971 -15
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +818 -0
- package/tests/gentle-todo.test.ts +226 -0
- package/tests/install-tui-mode-setting.test.ts +324 -0
- package/tests/issue-creation-skill.test.ts +22 -0
- package/tests/model-routing-authority.test.ts +12 -0
- package/tests/native-choice-list.test.ts +202 -0
- package/tests/native-fullscreen-interaction.test.ts +125 -0
- package/tests/native-pointer-region.test.ts +245 -0
- package/tests/native-review-capability-contract.test.ts +27 -1
- package/tests/native-review-cli.test.ts +317 -3
- package/tests/native-review-consent.test.ts +91 -0
- package/tests/native-review-parity-runtime.test.ts +8 -2
- package/tests/native-review-parity.test.ts +43 -29
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-budget.test.ts +69 -0
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/orchestrator-rdd-ownership.test.ts +9 -0
- package/tests/package-manifest.test.ts +243 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +97 -37
- package/tests/rdd-aware-verification-contract.test.ts +226 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +751 -7
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +17 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +884 -7
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +384 -8
- package/tests/review-host-relay.test.ts +29 -0
- package/tests/review-integration-v2-forward.test.ts +44 -0
- package/tests/review-integration-v2.test.ts +276 -0
- package/tests/review-last-event-closure.test.ts +112 -3
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-repository.test.ts +28 -1
- package/tests/review-risk-assessment.test.ts +626 -0
- package/tests/review-session-standing-permission-controller.test.ts +656 -0
- package/tests/review-session-standing-permission-ipc.test.ts +233 -0
- package/tests/review-session-standing-permission-runtime.test.ts +212 -0
- package/tests/review-session-standing-permission.test.ts +156 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +206 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +187 -0
- package/tests/runtime-metrics-native.test.ts +209 -0
- package/tests/runtime-metrics-pi-identity.test.ts +113 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +184 -0
- package/tests/sdd-agent-tools.test.ts +10 -1
- package/tests/sdd-execution-routing-contract.test.ts +28 -0
- package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
- package/tests/sdd-native-managed-uptake.test.ts +253 -0
- package/tests/sdd-planning-routing-contract.test.ts +45 -0
- package/tests/sdd-preflight.test.ts +252 -8
- package/tests/sdd-research-capabilities.test.ts +256 -0
- package/tests/sdd-research-live.test.ts +241 -0
- package/tests/sdd-selection-transport.test.ts +504 -0
- package/tests/sdd-status.test.ts +51 -0
- package/tests/session-worktree-registry.test.ts +135 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +139 -0
- package/tests/shell-changes-view.test.ts +609 -0
- package/tests/shell-changes.test.ts +350 -0
- package/tests/shell-prompt.test.ts +140 -0
- package/tests/shell-sidebar-banner.test.ts +23 -0
- package/tests/shell-sidebar-layout.test.ts +387 -0
- package/tests/shell-sidebar.test.ts +50 -0
- package/tests/shell-todo.test.ts +259 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +351 -0
package/lib/native-review-cli.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, isAbsolute, join, posix, win32 } from "node:path";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { PackageLocalGentleAiBinaryMissingError, resolveGentleAiBinary } from "./gentle-ai-binary.ts";
|
|
7
7
|
import { GENTLE_PI_REVIEW_RELAY_CONTRACT, GENTLE_PI_REVIEW_RELAY_CONTRACT_ENV } from "./review-relay-contract.ts";
|
|
8
|
+
import { decodeReviewAssessmentV1, type ReviewAssessmentV1 } from "./review-risk-assessment.ts";
|
|
8
9
|
import {
|
|
9
10
|
REVIEW_INTEGRATION_CONTRACT,
|
|
10
11
|
decodeReviewAcknowledgedV1,
|
|
@@ -59,11 +60,16 @@ export const NATIVE_REVIEW_OPERATION = {
|
|
|
59
60
|
RECONCILE_AUTHORITY: "review/reconcile-authority",
|
|
60
61
|
REPAIR_LEGACY_ALIAS: "review/repair-legacy-alias",
|
|
61
62
|
MODE: "review/mode",
|
|
63
|
+
ASSESS: "review/assess",
|
|
62
64
|
REPAIR: "review/repair",
|
|
63
65
|
CAPTURE_RESULT: "review/capture-result",
|
|
64
66
|
CAPTURE_CORRECTION_PLAN: "review/capture-correction-plan",
|
|
65
67
|
CAPTURE_PROVIDER_ROLE: "review/capture-provider-role",
|
|
68
|
+
CAPTURE_UNACHIEVABLE: "review/capture-unachievable",
|
|
66
69
|
ACKNOWLEDGE_APPROVED: "review/acknowledge-approved",
|
|
70
|
+
SDD_STATUS: "sdd-status",
|
|
71
|
+
SDD_ATTEMPT: "sdd-attempt",
|
|
72
|
+
SDD_CONTINUE: "sdd-continue",
|
|
67
73
|
} as const;
|
|
68
74
|
export type NativeReviewOperation = (typeof NATIVE_REVIEW_OPERATION)[keyof typeof NATIVE_REVIEW_OPERATION];
|
|
69
75
|
|
|
@@ -104,11 +110,56 @@ export interface NativeReviewCli {
|
|
|
104
110
|
captureResult?(request: NativeReviewCaptureResultRequest): Promise<NativeReviewCaptureResultOutcome>;
|
|
105
111
|
captureCorrectionPlan?(request: NativeReviewCorrectionPlanCaptureRequest): Promise<ReviewLastEventClosureV1>;
|
|
106
112
|
captureProviderRole?(request: NativeReviewProviderRoleCaptureRequest): Promise<NativeReviewProviderRoleCaptureOutcome>;
|
|
113
|
+
// gentle-pi#638: records the host relay's declaration that one bound selected lens slot cannot be completed under current conditions. Younger than every binary pinned in NATIVE_CLI_CONTRACTS, so the capability is gated invocation-adjacent (see isNativeReviewUnachievableVerbRefused) instead of by a contract row.
|
|
114
|
+
captureUnachievableLens?(request: NativeReviewUnachievableLensCaptureRequest): Promise<NativeReviewUnachievableLensCaptureArtifact>;
|
|
107
115
|
// Dark until a negotiated version reports the `mode` capability true
|
|
108
116
|
// (Design Decision #7, organic-rdd-parity). Plain versioned CLI operation,
|
|
109
117
|
// outside the negotiated review-integration protocol — same shape as
|
|
110
118
|
// reviewStatus/reclaim above.
|
|
111
119
|
reviewMode?(request: NativeReviewModeRequest): Promise<NativeReviewModeResult>;
|
|
120
|
+
// Read-only risk assessment (gentle-ai#4295, landing in parallel with
|
|
121
|
+
// gentle-pi#662). Same plain-versioned shape as reviewMode/reviewStatus:
|
|
122
|
+
// an older binary without the verb, or any other process/decode failure,
|
|
123
|
+
// rejects the returned promise -- callers fail closed to `high` risk.
|
|
124
|
+
assess?(request: NativeReviewAssessRequest): Promise<ReviewAssessmentV1>;
|
|
125
|
+
// Exact native SDD readiness authority for an explicitly selected phase.
|
|
126
|
+
// This is intentionally separate from review/RDD gates and carries no
|
|
127
|
+
// delivery or review-transaction authority.
|
|
128
|
+
sddStatus?(request: NativeSddStatusRequest): Promise<NativeSddStatusV2>;
|
|
129
|
+
sddContinue?(request: NativeSddStatusRequest): Promise<NativeSddStatusV2>;
|
|
130
|
+
sddAttemptAcquire?(request: NativeSddAcquireRequest): Promise<NativeSddAttemptResult>;
|
|
131
|
+
sddAttemptSettle?(request: NativeSddSettleRequest): Promise<NativeSddAttemptResult>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface NativeSddAttemptRequest extends Pick<NativeUntrackedSelectionRequest, "untrackedScope" | "expectedUntrackedInventory" | "intendedUntracked"> {
|
|
135
|
+
workspaceRoot: string;
|
|
136
|
+
changeName: string;
|
|
137
|
+
requestId: string;
|
|
138
|
+
remediatesEvidenceRevision?: string;
|
|
139
|
+
}
|
|
140
|
+
export interface NativeSddAcquireRequest extends NativeSddAttemptRequest {
|
|
141
|
+
workUnit: string;
|
|
142
|
+
evidenceGoal: string;
|
|
143
|
+
maxAttempts?: number;
|
|
144
|
+
maxChangedLines?: number;
|
|
145
|
+
expectedRevision?: string;
|
|
146
|
+
token?: string;
|
|
147
|
+
}
|
|
148
|
+
export const SDD_ATTEMPT_OUTCOME = { PASSED: "passed", FAILED: "failed", INTERRUPTED: "interrupted" } as const;
|
|
149
|
+
export interface NativeSddSettleRequest extends NativeSddAttemptRequest {
|
|
150
|
+
token: string;
|
|
151
|
+
outcome: (typeof SDD_ATTEMPT_OUTCOME)[keyof typeof SDD_ATTEMPT_OUTCOME];
|
|
152
|
+
evidenceRevision?: string;
|
|
153
|
+
remediationEvidence?: string;
|
|
154
|
+
diagnosis: string;
|
|
155
|
+
harnessDisposition: "reused" | "invalidated";
|
|
156
|
+
cleanupEvidence: string;
|
|
157
|
+
processEvidence: string;
|
|
158
|
+
}
|
|
159
|
+
export interface NativeSddAttemptResult {
|
|
160
|
+
state: "proceed" | "blocked" | "complete";
|
|
161
|
+
token?: string;
|
|
162
|
+
reason?: string;
|
|
112
163
|
}
|
|
113
164
|
|
|
114
165
|
export const NATIVE_REVIEW_MODE_OPERATION = {
|
|
@@ -145,12 +196,52 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
|
|
|
145
196
|
} as const;
|
|
146
197
|
export type NativeReviewModeScope = (typeof NATIVE_REVIEW_MODE_SCOPE)[keyof typeof NATIVE_REVIEW_MODE_SCOPE];
|
|
147
198
|
|
|
199
|
+
export interface NativeSddStatusRequest {
|
|
200
|
+
changeName?: string;
|
|
201
|
+
workspaceRoot: string;
|
|
202
|
+
signal?: AbortSignal;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export type NativeSddPhase = "apply" | "verify" | "archive";
|
|
206
|
+
export type NativeSddDependencyState = "blocked" | "ready" | "all_done";
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The native CLI owns this complete v2 record. The decoder validates the fields
|
|
210
|
+
* Pi relies on and returns the original object without adding, omitting, or
|
|
211
|
+
* reconciling local SDD state.
|
|
212
|
+
*/
|
|
213
|
+
export interface NativeSddStatusV2 extends Readonly<Record<string, unknown>> {
|
|
214
|
+
schemaName: "gentle-ai.sdd-status";
|
|
215
|
+
schemaVersion: 2;
|
|
216
|
+
changeName: string | null;
|
|
217
|
+
artifactStore: "openspec" | "engram" | "hybrid" | "none";
|
|
218
|
+
planningHome: Readonly<Record<string, unknown>> & { mode: "repo-local"; path: string };
|
|
219
|
+
changeRoot: string | null;
|
|
220
|
+
actionContext: Readonly<Record<string, unknown>> & { mode: "repo-local"; workspaceRoot: string; allowedEditRoots: readonly string[] };
|
|
221
|
+
dependencies: Readonly<Record<(typeof NATIVE_SDD_DEPENDENCIES)[number], NativeSddDependencyState>>;
|
|
222
|
+
phaseInstructions?: Readonly<Record<(typeof NATIVE_SDD_INSTRUCTION_PHASES)[number], readonly string[]>>;
|
|
223
|
+
blockedReasons: readonly string[];
|
|
224
|
+
nextRecommended: string;
|
|
225
|
+
remediationState?: { required: boolean; complete: boolean; failedEvidenceRevision: string };
|
|
226
|
+
}
|
|
227
|
+
|
|
148
228
|
export interface NativeReviewModeRequest {
|
|
149
229
|
cwd: string;
|
|
150
230
|
operation: NativeReviewModeOperation;
|
|
151
231
|
signal?: AbortSignal;
|
|
152
232
|
}
|
|
153
233
|
|
|
234
|
+
// Read-only risk assessment request (gentle-pi#662). `baseRef` requires
|
|
235
|
+
// explicit `committedOnly` acknowledgement, exactly like Native START's
|
|
236
|
+
// baseRef/committedOnly pairing, because both select a committed range
|
|
237
|
+
// instead of the ambient working tree.
|
|
238
|
+
export interface NativeReviewAssessRequest {
|
|
239
|
+
cwd: string;
|
|
240
|
+
baseRef?: string;
|
|
241
|
+
committedOnly?: boolean;
|
|
242
|
+
signal?: AbortSignal;
|
|
243
|
+
}
|
|
244
|
+
|
|
154
245
|
export interface NativeReviewModeStatus {
|
|
155
246
|
global: NativeReviewModeValue;
|
|
156
247
|
cloneLocal: NativeReviewModeValue;
|
|
@@ -261,7 +352,6 @@ export interface NativeReviewAbandonRequest {
|
|
|
261
352
|
snapshotIdentity: string;
|
|
262
353
|
capturedLensResults: readonly string[];
|
|
263
354
|
findingsPresent: boolean;
|
|
264
|
-
evidenceRecordsPresent: boolean;
|
|
265
355
|
actor: string;
|
|
266
356
|
reason: string;
|
|
267
357
|
maintainerAuthorization: string;
|
|
@@ -414,6 +504,49 @@ export interface NativeReviewProviderRoleCaptureArtifact {
|
|
|
414
504
|
readonly captured: true;
|
|
415
505
|
}
|
|
416
506
|
|
|
507
|
+
// gentle-pi#638: the typed declaration that one bound selected lens slot cannot be completed under current conditions. Mirrored from Go's reviewUnachievableLensCaptureArtifact (internal/cli/review_capture_unachievable.go): same schema identity, same closed field set, and the same 512-byte detail bound Go enforces, so a declaration this client refuses locally can never reach a binary that would accept it, and vice versa.
|
|
508
|
+
export const NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA = "gentle-ai.review-capture-unachievable/v1";
|
|
509
|
+
export const NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT = 512;
|
|
510
|
+
|
|
511
|
+
export interface NativeReviewUnachievableLensCaptureRequest {
|
|
512
|
+
/** Exact frozen review lineage identifier of the offered slot. */
|
|
513
|
+
readonly lineageId: string;
|
|
514
|
+
/** Exact frozen target identity of the offered slot. */
|
|
515
|
+
readonly targetIdentity: string;
|
|
516
|
+
/** Exact reviewing-authority revision the collect transition offered. */
|
|
517
|
+
readonly expectedRevision: string;
|
|
518
|
+
/** The offered slot's subject-hash; Go resolves the lens from it. */
|
|
519
|
+
readonly requestHash: string;
|
|
520
|
+
/** Short machine-readable cause, e.g. relay_transport_bound_exceeded. */
|
|
521
|
+
readonly reason: string;
|
|
522
|
+
/** Optional bounded evidence; refused above the Go-side byte limit. */
|
|
523
|
+
readonly detail?: string;
|
|
524
|
+
/** Opaque provider-issued repository context the slot carried, if any. */
|
|
525
|
+
readonly repositoryContext?: string;
|
|
526
|
+
/** Process working directory only; never rendered into the invocation. */
|
|
527
|
+
readonly cwd: string;
|
|
528
|
+
readonly signal?: AbortSignal;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export interface NativeReviewUnachievableLensCaptureArtifact {
|
|
532
|
+
readonly schema: typeof NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA;
|
|
533
|
+
readonly lineageId: string;
|
|
534
|
+
readonly targetIdentity: string;
|
|
535
|
+
readonly lens: string;
|
|
536
|
+
readonly selectedOrder: number;
|
|
537
|
+
readonly reason: string;
|
|
538
|
+
readonly recorded: true;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// gentle-pi#638 fail-open capability gate: `review capture-unachievable` is younger than every released binary pinned in NATIVE_CLI_CONTRACTS, so the verb is gated invocation-adjacent instead of by a capability row. An older binary renders Go's exact `unknown review command "capture-unachievable"` refusal (internal/cli/review_facade.go) on stderr with no stdout, so the invocation rejects before any decode and the captured diagnostics are the only place that text survives. Every other failure -- a typed binding-mismatch refusal, a timeout, a decode failure -- is a real outcome the caller must surface, never a capability signal. Duck-typed on purpose: the classifier must survive a duplicated module instance exactly like the error it inspects.
|
|
542
|
+
const NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL = /unknown review command "capture-unachievable"/;
|
|
543
|
+
|
|
544
|
+
export function isNativeReviewUnachievableVerbRefused(error: unknown): boolean {
|
|
545
|
+
if (typeof error !== "object" || error === null) return false;
|
|
546
|
+
const stderr = (error as { diagnostics?: { stderr?: unknown } }).diagnostics?.stderr;
|
|
547
|
+
return typeof stderr === "string" && NATIVE_REVIEW_UNKNOWN_UNACHIEVABLE_VERB_REFUSAL.test(stderr);
|
|
548
|
+
}
|
|
549
|
+
|
|
417
550
|
/** Refuter and validator captures close only when they are the native last event. */
|
|
418
551
|
export type NativeReviewProviderRoleCaptureOutcome = NativeReviewProviderRoleCaptureArtifact | ReviewLastEventClosureV1;
|
|
419
552
|
|
|
@@ -498,8 +631,13 @@ export const NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION = {
|
|
|
498
631
|
} as const;
|
|
499
632
|
export type NativeReviewAuthorityEntryVersion = (typeof NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION)[keyof typeof NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION];
|
|
500
633
|
|
|
501
|
-
export const NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS =
|
|
502
|
-
|
|
634
|
+
export const NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS = {
|
|
635
|
+
...NATIVE_REVIEW_AUTHORITY_STATUS,
|
|
636
|
+
INCOMPLETE_STORE_ENTRY: "incomplete-store-entry",
|
|
637
|
+
HISTORICAL_PRE_RECEIPT: "historical-pre-receipt",
|
|
638
|
+
INVALIDATED: "invalidated",
|
|
639
|
+
} as const;
|
|
640
|
+
export type NativeReviewAuthorityEntryStatus = (typeof NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS)[keyof typeof NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS];
|
|
503
641
|
|
|
504
642
|
export const NATIVE_REVIEW_LOCK_STATUS = {
|
|
505
643
|
OWNED: "owned",
|
|
@@ -541,6 +679,10 @@ export interface NativeReviewRecovery {
|
|
|
541
679
|
recoveredAt: string;
|
|
542
680
|
maintainerAuthorization?: string;
|
|
543
681
|
}
|
|
682
|
+
export interface NativeReviewDiscardedWorkSummary {
|
|
683
|
+
capturedLensResults: readonly string[];
|
|
684
|
+
findingsPresent: boolean;
|
|
685
|
+
}
|
|
544
686
|
export interface NativeReviewAuthorityEntry {
|
|
545
687
|
version: NativeReviewAuthorityEntryVersion;
|
|
546
688
|
lineageId?: string;
|
|
@@ -551,6 +693,7 @@ export interface NativeReviewAuthorityEntry {
|
|
|
551
693
|
snapshotIdentity?: string;
|
|
552
694
|
chainIdentity?: string;
|
|
553
695
|
recovery?: NativeReviewRecovery;
|
|
696
|
+
discardedWork?: NativeReviewDiscardedWorkSummary;
|
|
554
697
|
problems: readonly string[];
|
|
555
698
|
}
|
|
556
699
|
export interface NativeReviewAuthorityLock {
|
|
@@ -804,6 +947,25 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
804
947
|
// consumes did not change between 2.5.0 and 2.6.0, so the columns match
|
|
805
948
|
// the 2.5.0 row.
|
|
806
949
|
"2.6.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 }),
|
|
950
|
+
// Ground-truthed against the published v2.7.0 binary installed from its
|
|
951
|
+
// signed archive: `review capabilities` on the v2 lane still advertises
|
|
952
|
+
// capabilities/v2.5 (protocol minor 5) and answers status/v7, consent/v3,
|
|
953
|
+
// and the `start/v4` continuation, all of which the decoders already read.
|
|
954
|
+
// `review mode status --json` still answers `gentle-ai.rdd-mode-status/v1`
|
|
955
|
+
// and `review validate --gate pre-commit` still answers
|
|
956
|
+
// `gentle-ai.review-gate-result/v1` carrying `delivery`, both verified
|
|
957
|
+
// against the binary. riskEvidence and hint stay dark for the same reason
|
|
958
|
+
// they have on every row since 2.2.0: still not proven to reach the
|
|
959
|
+
// negotiated START path Pi reads. The closed fields Pi consumes did not
|
|
960
|
+
// change between 2.6.0 and 2.7.0, so the columns match the 2.6.0 row.
|
|
961
|
+
"2.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 }),
|
|
962
|
+
// Ground-truthed against the published v2.8.0 linux/amd64 binary from its
|
|
963
|
+
// signed release archive. The v2 lane still advertises capabilities/v2.5
|
|
964
|
+
// (protocol minor 5) with status/v7, consent/v3, and start/v4 schemas.
|
|
965
|
+
// The closed fields Pi consumes did not change between 2.7.0 and 2.8.0,
|
|
966
|
+
// so this row repeats 2.7.0. riskEvidence and hint remain dark because
|
|
967
|
+
// neither is proven to reach the negotiated START path Pi consumes.
|
|
968
|
+
"2.8.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 }),
|
|
807
969
|
});
|
|
808
970
|
|
|
809
971
|
export interface NativeReviewProcessDiagnostics {
|
|
@@ -835,7 +997,7 @@ export class NativeReviewCliError extends Error {
|
|
|
835
997
|
this.launchAttempted = launchAttempted;
|
|
836
998
|
this.mutating = mutating;
|
|
837
999
|
this.mutationOutcome = launchAttempted && mutating ? "unknown" : "none";
|
|
838
|
-
this.nextAction = this.mutationOutcome === "unknown" ? "review.status" : undefined;
|
|
1000
|
+
this.nextAction = this.mutationOutcome === "unknown" && operation !== NATIVE_REVIEW_OPERATION.SDD_ATTEMPT ? "review.status" : undefined;
|
|
839
1001
|
this.diagnostics = diagnostics ?? { operation, error_code: code, timed_out: false, output_limit_exceeded: false };
|
|
840
1002
|
this.auditRecord = auditRecord;
|
|
841
1003
|
}
|
|
@@ -1046,8 +1208,15 @@ function decodeNativeReviewRecovery(value: unknown): NativeReviewRecovery {
|
|
|
1046
1208
|
...(recovery.maintainer_authorization === undefined ? {} : { maintainerAuthorization: requiredString(recovery.maintainer_authorization) }),
|
|
1047
1209
|
};
|
|
1048
1210
|
}
|
|
1211
|
+
function decodeNativeReviewDiscardedWorkSummary(value: unknown): NativeReviewDiscardedWorkSummary {
|
|
1212
|
+
const discardedWork = exactObject(value, ["captured_lens_results", "findings_present"]);
|
|
1213
|
+
return {
|
|
1214
|
+
capturedLensResults: stringArray(discardedWork.captured_lens_results),
|
|
1215
|
+
findingsPresent: booleanValue(discardedWork.findings_present),
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1049
1218
|
function decodeNativeReviewStatusEntry(value: unknown): NativeReviewAuthorityEntry {
|
|
1050
|
-
const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery"]);
|
|
1219
|
+
const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery", "discarded_work"]);
|
|
1051
1220
|
return {
|
|
1052
1221
|
version: enumString(entry.version, Object.values(NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION)) as NativeReviewAuthorityEntryVersion,
|
|
1053
1222
|
...(entry.lineage_id === undefined ? {} : { lineageId: requiredString(entry.lineage_id) }),
|
|
@@ -1058,6 +1227,7 @@ function decodeNativeReviewStatusEntry(value: unknown): NativeReviewAuthorityEnt
|
|
|
1058
1227
|
...(entry.snapshot_identity === undefined ? {} : { snapshotIdentity: sha256Identity(entry.snapshot_identity) }),
|
|
1059
1228
|
...(entry.chain_identity === undefined ? {} : { chainIdentity: requiredString(entry.chain_identity) }),
|
|
1060
1229
|
...(entry.recovery === undefined ? {} : { recovery: decodeNativeReviewRecovery(entry.recovery) }),
|
|
1230
|
+
...(entry.discarded_work === undefined ? {} : { discardedWork: decodeNativeReviewDiscardedWorkSummary(entry.discarded_work) }),
|
|
1061
1231
|
problems: stringArray(entry.problems),
|
|
1062
1232
|
};
|
|
1063
1233
|
}
|
|
@@ -1252,6 +1422,46 @@ interface NativeJsonExecution {
|
|
|
1252
1422
|
process: ExecFileResult;
|
|
1253
1423
|
}
|
|
1254
1424
|
|
|
1425
|
+
const NATIVE_SDD_DEPENDENCIES = ["proposal", "specs", "design", "tasks", "apply", "verify", "archive"] as const;
|
|
1426
|
+
const NATIVE_SDD_INSTRUCTION_PHASES = ["apply", "verify", "remediate", "archive"] as const;
|
|
1427
|
+
const NATIVE_SDD_NEXT_RECOMMENDATIONS = ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"] as const;
|
|
1428
|
+
const NATIVE_SDD_DEPENDENCY_STATES = ["blocked", "ready", "all_done"] as const;
|
|
1429
|
+
|
|
1430
|
+
/** Strictly validates the native v2 contract while preserving its whole record. */
|
|
1431
|
+
export function decodeNativeSddStatusV2(value: unknown, request: Pick<NativeSddStatusRequest, "changeName" | "workspaceRoot">): NativeSddStatusV2 {
|
|
1432
|
+
const status = object(value);
|
|
1433
|
+
if (status.schemaName !== "gentle-ai.sdd-status" || status.schemaVersion !== 2) throw new Error("wrong native SDD status schema");
|
|
1434
|
+
if ((request.changeName !== undefined && status.changeName !== request.changeName) || (status.changeName !== null && !isCanonicalProcessString(status.changeName))) throw new Error("native SDD status change identity mismatch");
|
|
1435
|
+
const artifactStore = enumString(status.artifactStore, ["openspec", "engram", "hybrid", "none"]);
|
|
1436
|
+
const planningHome = object(status.planningHome);
|
|
1437
|
+
if (planningHome.mode !== "repo-local" || !isCanonicalProcessString(planningHome.path)) throw new Error("invalid native SDD planning home");
|
|
1438
|
+
const expectedOpenSpecHome = join(request.workspaceRoot, "openspec");
|
|
1439
|
+
if (planningHome.path !== expectedOpenSpecHome && !((artifactStore === "engram" || artifactStore === "hybrid") && planningHome.path === "engram:sdd")) throw new Error("native SDD planning home escaped its workspace");
|
|
1440
|
+
if (status.changeRoot !== null && !isCanonicalProcessString(status.changeRoot)) throw new Error("invalid native SDD change root");
|
|
1441
|
+
const actionContext = object(status.actionContext);
|
|
1442
|
+
if (actionContext.mode !== "repo-local" || actionContext.workspaceRoot !== request.workspaceRoot || !isCanonicalProcessString(actionContext.workspaceRoot)) throw new Error("native SDD status workspace root mismatch");
|
|
1443
|
+
const allowedEditRoots = stringArray(actionContext.allowedEditRoots);
|
|
1444
|
+
if (!allowedEditRoots.includes(request.workspaceRoot) || allowedEditRoots.some((root) => !isAbsolute(root) || root !== join(root))) throw new Error("invalid native SDD allowed edit roots");
|
|
1445
|
+
const dependencies = object(status.dependencies);
|
|
1446
|
+
for (const phase of NATIVE_SDD_DEPENDENCIES) {
|
|
1447
|
+
if (enumString(dependencies[phase], NATIVE_SDD_DEPENDENCY_STATES) !== dependencies[phase]) throw new Error("invalid native SDD dependency");
|
|
1448
|
+
}
|
|
1449
|
+
if (Object.keys(dependencies).length !== NATIVE_SDD_DEPENDENCIES.length) throw new Error("native SDD dependencies have an unsupported shape");
|
|
1450
|
+
if (status.instructions !== undefined) throw new Error("native SDD status uses phaseInstructions, not instructions");
|
|
1451
|
+
if (status.phaseInstructions !== undefined) {
|
|
1452
|
+
const instructions = object(status.phaseInstructions);
|
|
1453
|
+
for (const phase of NATIVE_SDD_INSTRUCTION_PHASES) stringArray(instructions[phase]);
|
|
1454
|
+
if (Object.keys(instructions).length !== NATIVE_SDD_INSTRUCTION_PHASES.length) throw new Error("native SDD instructions have an unsupported shape");
|
|
1455
|
+
}
|
|
1456
|
+
if (status.nextRecommended === "remediate" || status.remediationState !== undefined) {
|
|
1457
|
+
const remediation = object(status.remediationState);
|
|
1458
|
+
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");
|
|
1459
|
+
}
|
|
1460
|
+
stringArray(status.blockedReasons);
|
|
1461
|
+
enumString(status.nextRecommended, NATIVE_SDD_NEXT_RECOMMENDATIONS);
|
|
1462
|
+
return status as NativeSddStatusV2;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1255
1465
|
class NativeReviewPlainCli {
|
|
1256
1466
|
private readonly adapter: ExecFileAdapter;
|
|
1257
1467
|
private readonly executable: string | (() => string);
|
|
@@ -1372,9 +1582,7 @@ class NativeReviewPlainCli {
|
|
|
1372
1582
|
if (!isCanonicalProcessString(value)) throw new TypeError(`Native ABANDON ${name} must be a non-empty, trimmed, NUL-free string`);
|
|
1373
1583
|
}
|
|
1374
1584
|
if (!Array.isArray(request.capturedLensResults) || request.capturedLensResults.some((entry) => !isCanonicalProcessString(entry))) throw new TypeError("Native ABANDON capturedLensResults must be an array of non-empty, trimmed, NUL-free strings");
|
|
1375
|
-
|
|
1376
|
-
if (typeof value !== "boolean") throw new TypeError(`Native ABANDON ${name} must be a boolean`);
|
|
1377
|
-
}
|
|
1585
|
+
if (typeof request.findingsPresent !== "boolean") throw new TypeError("Native ABANDON findingsPresent must be a boolean");
|
|
1378
1586
|
if (request.maintainerAuthorization !== nativeReviewAbandonAuthorization(request)) throw new TypeError("Native ABANDON maintainerAuthorization must match the exact lineage, revision, snapshot, reason, discarded-work, and actor binding");
|
|
1379
1587
|
const execution = await this.execute(NATIVE_REVIEW_OPERATION.ABANDON, request.cwd, [
|
|
1380
1588
|
"review", "abandon", "--cwd", request.cwd,
|
|
@@ -1463,7 +1671,11 @@ class NativeReviewPlainCli {
|
|
|
1463
1671
|
}
|
|
1464
1672
|
}
|
|
1465
1673
|
|
|
1466
|
-
export function nativeReviewAbandonAuthorization(request: Pick<NativeReviewAbandonRequest, "lineage" | "expectedRevision" | "snapshotIdentity" | "capturedLensResults" | "findingsPresent" | "
|
|
1674
|
+
export function nativeReviewAbandonAuthorization(request: Pick<NativeReviewAbandonRequest, "lineage" | "expectedRevision" | "snapshotIdentity" | "capturedLensResults" | "findingsPresent" | "actor" | "reason">): string {
|
|
1675
|
+
// gentle-ai 0ed9225f removed evidence records from the discarded-work summary,
|
|
1676
|
+
// so the native v2 gate verifies an exact eight-line binding (schema, lineage,
|
|
1677
|
+
// revision, snapshot_identity, reason, captured_lens_results, findings_present,
|
|
1678
|
+
// actor) — there is no evidence_records_present line to derive or relay.
|
|
1467
1679
|
return [
|
|
1468
1680
|
"gentle-ai.review-abandon-authorization/v2",
|
|
1469
1681
|
`lineage=${request.lineage}`,
|
|
@@ -1472,7 +1684,6 @@ export function nativeReviewAbandonAuthorization(request: Pick<NativeReviewAband
|
|
|
1472
1684
|
`reason=${request.reason}`,
|
|
1473
1685
|
`captured_lens_results=${request.capturedLensResults.join(",")}`,
|
|
1474
1686
|
`findings_present=${request.findingsPresent}`,
|
|
1475
|
-
`evidence_records_present=${request.evidenceRecordsPresent}`,
|
|
1476
1687
|
`actor=${request.actor.trim()}`,
|
|
1477
1688
|
].join("\n");
|
|
1478
1689
|
}
|
|
@@ -1595,14 +1806,20 @@ function splitNativeConsentInvocation(invocation: string): readonly string[] {
|
|
|
1595
1806
|
let quote: "'" | '"' | undefined;
|
|
1596
1807
|
let escaping = false;
|
|
1597
1808
|
let started = false;
|
|
1598
|
-
|
|
1809
|
+
const source = invocation.trim();
|
|
1810
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1811
|
+
const character = source[index]!;
|
|
1599
1812
|
if (escaping) {
|
|
1600
1813
|
current += character;
|
|
1601
1814
|
escaping = false;
|
|
1602
1815
|
started = true;
|
|
1603
1816
|
continue;
|
|
1604
1817
|
}
|
|
1605
|
-
|
|
1818
|
+
// Provider invocations are not shell commands. Keep every path backslash
|
|
1819
|
+
// verbatim, including quoted UNC and drive-root paths. Outside quotes,
|
|
1820
|
+
// only a backslash before whitespace joins a space-containing token.
|
|
1821
|
+
const next = source[index + 1];
|
|
1822
|
+
if (character === "\\" && quote === undefined && next !== undefined && /\s/.test(next)) {
|
|
1606
1823
|
escaping = true;
|
|
1607
1824
|
started = true;
|
|
1608
1825
|
continue;
|
|
@@ -1693,7 +1910,9 @@ function consentInvocationArguments(request: NativeReviewConsentAnswerRequest):
|
|
|
1693
1910
|
if (words[0] !== "gentle-ai" || words[1] !== "review" || words[2] !== "start") throw new NativeReviewConsentBindingError("consent-invocation-not-start", "Native consent invocation is not a provider review START");
|
|
1694
1911
|
const arguments_ = words.slice(1);
|
|
1695
1912
|
if (exactConsentOption(arguments_, "--contract") !== REVIEW_INTEGRATION_CONTRACT) throw new NativeReviewConsentBindingError("consent-invocation-contract-changed", "Native consent invocation contract changed");
|
|
1696
|
-
|
|
1913
|
+
const providerCwd = exactConsentOption(arguments_, "--cwd");
|
|
1914
|
+
const requestedCwd = normalizeNativeReviewCwd(request.cwd);
|
|
1915
|
+
if (normalizeNativeReviewCwd(providerCwd) !== requestedCwd) throw new NativeReviewConsentBindingError("consent-invocation-cwd-changed", "Native consent invocation repository binding changed");
|
|
1697
1916
|
if (exactConsentOption(arguments_, "--target") !== request.consent.targetIdentity) throw new NativeReviewConsentBindingError("consent-invocation-target-changed", "Native consent invocation target binding changed");
|
|
1698
1917
|
if (exactConsentOption(arguments_, "--projection") !== request.consent.projection) throw new NativeReviewConsentBindingError("consent-invocation-projection-changed", "Native consent invocation projection binding changed");
|
|
1699
1918
|
const lineageId = optionalConsentLineageOption(arguments_);
|
|
@@ -1773,6 +1992,23 @@ function decodeNativeProviderRoleCaptureArtifact(value: unknown): NativeReviewPr
|
|
|
1773
1992
|
});
|
|
1774
1993
|
}
|
|
1775
1994
|
|
|
1995
|
+
// gentle-pi#638: the recorded declaration Go prints for one unachievable slot. Closed key set, exact schema identity, and the lens Go itself resolved from the request hash -- the host never asserts which lens failed, only reads back the one the provider bound.
|
|
1996
|
+
function decodeNativeUnachievableLensCaptureArtifact(value: unknown): NativeReviewUnachievableLensCaptureArtifact {
|
|
1997
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("native unachievable lens capture artifact must be an object");
|
|
1998
|
+
const body = value as Record<string, unknown>;
|
|
1999
|
+
const allowed = new Set(["schema", "lineage_id", "target_identity", "lens", "selected_order", "reason", "recorded"]);
|
|
2000
|
+
for (const key of Object.keys(body)) if (!allowed.has(key)) throw new TypeError(`native unachievable lens capture artifact carries unexpected key ${key}`);
|
|
2001
|
+
const text = (key: string): string => {
|
|
2002
|
+
const found = body[key];
|
|
2003
|
+
if (typeof found !== "string" || found.trim() !== found || found.length === 0) throw new TypeError(`native unachievable lens capture artifact ${key} must be a non-empty trimmed string`);
|
|
2004
|
+
return found;
|
|
2005
|
+
};
|
|
2006
|
+
if (text("schema") !== NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA) throw new TypeError(`native unachievable lens capture artifact schema must be ${NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA}`);
|
|
2007
|
+
if (typeof body.selected_order !== "number" || !Number.isSafeInteger(body.selected_order) || body.selected_order < 0) throw new TypeError("native unachievable lens capture artifact selected_order must be a non-negative integer");
|
|
2008
|
+
if (body.recorded !== true) throw new TypeError("native unachievable lens capture artifact must report recorded: true");
|
|
2009
|
+
return Object.freeze({ schema: NATIVE_REVIEW_UNACHIEVABLE_LENS_CAPTURE_SCHEMA, lineageId: text("lineage_id"), targetIdentity: text("target_identity"), lens: text("lens"), selectedOrder: body.selected_order, reason: text("reason"), recorded: true });
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1776
2012
|
export class NativeReviewCliV216 implements NativeReviewCli {
|
|
1777
2013
|
private readonly plain: NativeReviewPlainCli;
|
|
1778
2014
|
private readonly adapter: ExecFileAdapter;
|
|
@@ -1864,6 +2100,89 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
1864
2100
|
return this.invoke(operation, cwd, arguments_, mutating, signal, this.executablePath(operation, mutating), toleratedStderr);
|
|
1865
2101
|
}
|
|
1866
2102
|
|
|
2103
|
+
async sddAttemptAcquire(request: NativeSddAcquireRequest): Promise<NativeSddAttemptResult> {
|
|
2104
|
+
return this.sddAttempt("acquire", request);
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
async sddAttemptSettle(request: NativeSddSettleRequest): Promise<NativeSddAttemptResult> {
|
|
2108
|
+
return this.sddAttempt("settle", request);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
private async sddAttempt(verb: "acquire" | "settle", request: NativeSddAcquireRequest | NativeSddSettleRequest): Promise<NativeSddAttemptResult> {
|
|
2112
|
+
const args = ["sdd-attempt", verb, "--cwd", request.workspaceRoot, "--change", request.changeName, "--request-id", request.requestId];
|
|
2113
|
+
if (!isAbsolute(request.workspaceRoot) || !isCanonicalProcessString(request.workspaceRoot) || !isCanonicalProcessString(request.changeName) || !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(request.requestId)) throw new TypeError("Invalid SDD attempt identity");
|
|
2114
|
+
const text = (flag: string, value: string, max: number) => {
|
|
2115
|
+
if (!isCanonicalProcessString(value) || /[\r\n]/.test(value) || Buffer.byteLength(value) > max) throw new TypeError(`Invalid ${flag}`);
|
|
2116
|
+
args.push(flag, value);
|
|
2117
|
+
};
|
|
2118
|
+
const revision = (flag: string, value: string | undefined) => {
|
|
2119
|
+
if (value === undefined) return;
|
|
2120
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Invalid ${flag}`);
|
|
2121
|
+
args.push(flag, value);
|
|
2122
|
+
};
|
|
2123
|
+
if (verb === "acquire") {
|
|
2124
|
+
const acquire = request as NativeSddAcquireRequest;
|
|
2125
|
+
text("--work-unit", acquire.workUnit, 160);
|
|
2126
|
+
text("--evidence-goal", acquire.evidenceGoal, 240);
|
|
2127
|
+
for (const [flag, value, max] of [["--max-attempts", acquire.maxAttempts, 100], ["--max-changed-lines", acquire.maxChangedLines, 1_000_000]] as const) {
|
|
2128
|
+
if (value === undefined) continue;
|
|
2129
|
+
if (!Number.isInteger(value) || value < 1 || value > max) throw new TypeError(`Invalid ${flag}`);
|
|
2130
|
+
args.push(flag, String(value));
|
|
2131
|
+
}
|
|
2132
|
+
if (acquire.expectedRevision === "") args.push("--expected-revision", "");
|
|
2133
|
+
else revision("--expected-revision", acquire.expectedRevision);
|
|
2134
|
+
if (acquire.token !== undefined) text("--token", acquire.token, 500);
|
|
2135
|
+
} else {
|
|
2136
|
+
const settle = request as NativeSddSettleRequest;
|
|
2137
|
+
text("--token", settle.token, 500);
|
|
2138
|
+
if (!Object.values(SDD_ATTEMPT_OUTCOME).includes(settle.outcome)) throw new TypeError("Invalid outcome");
|
|
2139
|
+
args.push("--outcome", settle.outcome);
|
|
2140
|
+
if (settle.outcome === "interrupted" ? settle.evidenceRevision !== undefined || settle.remediationEvidence !== undefined : !settle.evidenceRevision && !(settle.outcome === "passed" && settle.remediationEvidence)) throw new TypeError("Invalid terminal evidence");
|
|
2141
|
+
revision("--evidence-revision", settle.evidenceRevision);
|
|
2142
|
+
text("--diagnosis", settle.diagnosis, 500);
|
|
2143
|
+
if (!["reused", "invalidated"].includes(settle.harnessDisposition)) throw new TypeError("Invalid harness disposition");
|
|
2144
|
+
args.push("--harness-disposition", settle.harnessDisposition);
|
|
2145
|
+
text("--cleanup-evidence", settle.cleanupEvidence, 500);
|
|
2146
|
+
text("--process-evidence", settle.processEvidence, 500);
|
|
2147
|
+
if (settle.remediationEvidence !== undefined) args.push("--remediation-evidence", settle.remediationEvidence);
|
|
2148
|
+
}
|
|
2149
|
+
revision("--remediates-evidence-revision", request.remediatesEvidenceRevision);
|
|
2150
|
+
args.push(...nativeUntrackedSelectionArguments(nativeUntrackedSelection(request)));
|
|
2151
|
+
const operation = NATIVE_REVIEW_OPERATION.SDD_ATTEMPT;
|
|
2152
|
+
const { body } = await this.negotiated(operation, request.workspaceRoot, args, true);
|
|
2153
|
+
return decode(operation, true, () => {
|
|
2154
|
+
const result = body as unknown as NativeSddAttemptResult;
|
|
2155
|
+
if (!result || !["proceed", "blocked", "complete"].includes(result.state)) throw new TypeError("Invalid compact state");
|
|
2156
|
+
if (verb === "acquire" && result.state === "proceed" && !isCanonicalProcessString(result.token)) throw new TypeError("Missing compact token");
|
|
2157
|
+
if (result.reason !== undefined && typeof result.reason !== "string") throw new TypeError("Invalid compact reason");
|
|
2158
|
+
return { state: result.state, ...(result.token === undefined ? {} : { token: result.token }), ...(result.reason === undefined ? {} : { reason: result.reason }) };
|
|
2159
|
+
});
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
async sddStatus(request: NativeSddStatusRequest): Promise<NativeSddStatusV2> {
|
|
2163
|
+
return this.sddProjection(request, false);
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
async sddContinue(request: NativeSddStatusRequest): Promise<NativeSddStatusV2> {
|
|
2167
|
+
if (!isCanonicalProcessString(request.changeName)) throw new TypeError("Native SDD continuation requires an exact selected change");
|
|
2168
|
+
return this.sddProjection(request, true);
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
private async sddProjection(request: NativeSddStatusRequest, mutating: boolean): Promise<NativeSddStatusV2> {
|
|
2172
|
+
if ((request.changeName !== undefined && !isCanonicalProcessString(request.changeName)) || !isCanonicalProcessString(request.workspaceRoot) || !isAbsolute(request.workspaceRoot)) {
|
|
2173
|
+
throw new TypeError("Native SDD status requires a canonical change and absolute workspace root");
|
|
2174
|
+
}
|
|
2175
|
+
const operation = mutating ? NATIVE_REVIEW_OPERATION.SDD_CONTINUE : NATIVE_REVIEW_OPERATION.SDD_STATUS;
|
|
2176
|
+
const execution = await this.negotiated(
|
|
2177
|
+
operation,
|
|
2178
|
+
request.workspaceRoot,
|
|
2179
|
+
[operation, ...(request.changeName === undefined ? [] : [request.changeName]), "--cwd", request.workspaceRoot, "--json", "--instructions"],
|
|
2180
|
+
mutating,
|
|
2181
|
+
request.signal,
|
|
2182
|
+
);
|
|
2183
|
+
return decode(operation, mutating, () => decodeNativeSddStatusV2(execution.body, request));
|
|
2184
|
+
}
|
|
2185
|
+
|
|
1867
2186
|
async start(request: NativeStartRequest): Promise<NativeStartResult> {
|
|
1868
2187
|
if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native START baseRef must be a non-empty, trimmed, NUL-free string");
|
|
1869
2188
|
if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native START baseRef requires explicit committedOnly acknowledgement");
|
|
@@ -2138,6 +2457,25 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2138
2457
|
});
|
|
2139
2458
|
}
|
|
2140
2459
|
|
|
2460
|
+
// gentle-pi#638: `gentle-ai review capture-unachievable` records one bound declaration that the exact slot the collect transition offered cannot be completed under current conditions. Unlike every other capture verb the host runs, this one reports a failure instead of a result, so it never takes the provider-issued tokens verbatim: the declaration's binding values are named fields re-derived from the slot, and Go verifies them against the frozen lineage, revision, target, and subject hash before recording anything (internal/cli/review_capture_unachievable.go). A binding that no longer matches is a typed refusal the caller surfaces, never a host-side reconstruction.
|
|
2461
|
+
async captureUnachievableLens(request: NativeReviewUnachievableLensCaptureRequest): Promise<NativeReviewUnachievableLensCaptureArtifact> {
|
|
2462
|
+
for (const [field, value] of (["lineageId", "reason"] as const).map((field) => [field, request[field]] as const)) {
|
|
2463
|
+
if (!isCanonicalProcessString(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a non-empty, trimmed, NUL-free string`);
|
|
2464
|
+
}
|
|
2465
|
+
// The three identity fields are digests on every slot the provider offers, so a non-sha value means the declaration binding was parsed wrong; refuse it locally instead of invoking a doomed process.
|
|
2466
|
+
for (const [field, value] of (["targetIdentity", "expectedRevision", "requestHash"] as const).map((field) => [field, request[field]] as const)) {
|
|
2467
|
+
if (!/^sha256:[0-9a-f]{64}$/.test(value)) throw new TypeError(`Native CAPTURE_UNACHIEVABLE ${field} must be a canonical SHA-256 identity`);
|
|
2468
|
+
}
|
|
2469
|
+
const detail = request.detail === undefined ? "" : request.detail.trim();
|
|
2470
|
+
// gentle-pi#822: the native limit is 512 UTF-8 bytes, not 512 UTF-16 code units — the Go side measures the encoded payload, so a multibyte detail needs Buffer.byteLength; a .length check would let 300 two-byte characters through.
|
|
2471
|
+
if (Buffer.byteLength(detail, "utf8") > NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT) throw new TypeError(`Native CAPTURE_UNACHIEVABLE detail exceeds ${NATIVE_REVIEW_UNACHIEVABLE_LENS_DETAIL_LIMIT} bytes`);
|
|
2472
|
+
if (request.repositoryContext !== undefined && !isCanonicalProcessString(request.repositoryContext)) throw new TypeError("Native CAPTURE_UNACHIEVABLE repositoryContext must be a non-empty, trimmed, NUL-free string");
|
|
2473
|
+
const executable = this.executablePath(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true);
|
|
2474
|
+
// gentle-pi#822: --repository-context is authoritative and mutually exclusive with a path (the captureProviderRole discipline), so --cwd rides the invocation only when no repository context names it; the process working directory stays request.cwd either way.
|
|
2475
|
+
const execution = await this.invoke(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, request.cwd, ["review", "capture-unachievable", "--lineage", request.lineageId, "--target", request.targetIdentity, "--expected-revision", request.expectedRevision, "--request-hash", request.requestHash, "--reason", request.reason, ...(detail === "" ? [] : ["--detail", detail]), ...(request.repositoryContext === undefined ? ["--cwd", request.cwd] : ["--repository-context", request.repositoryContext])], true, request.signal, executable);
|
|
2476
|
+
return decode(NATIVE_REVIEW_OPERATION.CAPTURE_UNACHIEVABLE, true, () => decodeNativeUnachievableLensCaptureArtifact(execution.body));
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2141
2479
|
// gentle-pi#311 P5: executes one provider-rendered `review.finalize`
|
|
2142
2480
|
// execute transition exactly as rendered. The tokens come verbatim from
|
|
2143
2481
|
// the negotiated next_transition (e.g. `--lineage=<id>
|
|
@@ -2193,6 +2531,26 @@ export class NativeReviewCliV216 implements NativeReviewCli {
|
|
|
2193
2531
|
return decode(NATIVE_REVIEW_OPERATION.MODE, mutating, () => decodeNativeReviewMode(execution.body, request.operation));
|
|
2194
2532
|
}
|
|
2195
2533
|
|
|
2534
|
+
// Read-only risk assessment (gentle-ai#4295, gentle-pi#662). Never mutates;
|
|
2535
|
+
// an older binary without the verb, or any other process/decode failure,
|
|
2536
|
+
// rejects -- callers (the `gentle_review` tool's `assess` operation) fail
|
|
2537
|
+
// closed to `high`.
|
|
2538
|
+
async assess(request: NativeReviewAssessRequest): Promise<ReviewAssessmentV1> {
|
|
2539
|
+
if (request.baseRef !== undefined && !isCanonicalProcessString(request.baseRef)) throw new TypeError("Native ASSESS baseRef must be a non-empty, trimmed, NUL-free string");
|
|
2540
|
+
if (request.baseRef !== undefined && request.committedOnly !== true) throw new TypeError("Native ASSESS baseRef requires explicit committedOnly acknowledgement");
|
|
2541
|
+
if (request.baseRef === undefined && request.committedOnly !== undefined) throw new TypeError("Native ASSESS committedOnly requires an explicit baseRef");
|
|
2542
|
+
const cwd = await canonicalNativeReviewCwd(request.cwd);
|
|
2543
|
+
const execution = await this.invoke(
|
|
2544
|
+
NATIVE_REVIEW_OPERATION.ASSESS,
|
|
2545
|
+
cwd,
|
|
2546
|
+
["review", "assess", "--cwd", cwd, ...(request.baseRef === undefined ? [] : ["--base-ref", request.baseRef, "--committed-only"]), "--json"],
|
|
2547
|
+
false,
|
|
2548
|
+
request.signal,
|
|
2549
|
+
this.executablePath(NATIVE_REVIEW_OPERATION.ASSESS, false),
|
|
2550
|
+
);
|
|
2551
|
+
return decode(NATIVE_REVIEW_OPERATION.ASSESS, false, () => decodeReviewAssessmentV1(execution.body));
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2196
2554
|
// Recovery commands are version-gated plain CLI operations outside the
|
|
2197
2555
|
// negotiated integration-v1 contract, exactly like reviewStatus.
|
|
2198
2556
|
reclaim(request: NativeReviewReclaimRequest): Promise<NativeReviewRecoveryResult> {
|