gentle-pi 2.5.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 +139 -30
- package/assets/agents/gentle-ai-worker.md +4 -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 +21 -3
- 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/telemetry.md +57 -1
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +143 -15
- package/extensions/codegraph-tools.ts +1 -0
- package/extensions/gentle-agents.ts +799 -50
- package/extensions/gentle-ai.ts +2033 -322
- package/extensions/gentle-shell.ts +145 -42
- package/extensions/gentle-todo.ts +47 -12
- package/extensions/quiet-tools.ts +1 -0
- 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 +7 -10
- package/lib/agents-history.ts +9 -1
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +77 -5
- package/lib/agents-runner.ts +548 -26
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +548 -191
- package/lib/agents-widget.ts +33 -14
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +8 -6
- package/lib/native-review-cli.ts +283 -1
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +296 -46
- package/lib/review-candidate-view.ts +30 -20
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +53 -8
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +187 -5
- package/lib/review-last-event-controller.ts +7 -4
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-session-standing-permission.ts +27 -6
- 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 +47 -1
- package/lib/shell-card.ts +1 -4
- package/lib/shell-changes-view.ts +362 -37
- package/lib/shell-changes.ts +81 -1
- package/lib/shell-prompt.ts +11 -15
- 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 +28 -11
- package/lib/telemetry-trigger.ts +2 -0
- package/package.json +6 -3
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +283 -1
- package/runtime/review-integration-v2.mjs +187 -5
- package/runtime/telemetry-trigger.mjs +2 -0
- package/scripts/build-runtime-modules.mjs +9 -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 +16 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +4 -2
- package/skills/_shared/review-ledger-contract.md +17 -1
- 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 +62 -0
- package/tests/agents-fake-child.ts +15 -1
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +45 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner.test.ts +571 -14
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-view.test.ts +476 -65
- package/tests/agents-widget.test.ts +31 -1
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +169 -3
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +2 -1
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +2 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +1460 -33
- 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 +38 -0
- package/tests/gentle-ai.test.ts +944 -4
- package/tests/gentle-shell.test.ts +303 -12
- package/tests/gentle-todo.test.ts +54 -10
- 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-review-capability-contract.test.ts +12 -1
- package/tests/native-review-cli.test.ts +277 -3
- package/tests/native-review-parity.test.ts +14 -7
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/package-manifest.test.ts +232 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +1 -0
- package/tests/rdd-aware-verification-contract.test.ts +10 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +304 -6
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +14 -0
- package/tests/review-controller-native-routing.test.ts +563 -3
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +364 -4
- 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 +164 -0
- package/tests/review-last-event-closure.test.ts +105 -1
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-session-standing-permission-controller.test.ts +52 -4
- package/tests/review-session-standing-permission.test.ts +30 -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-card.test.ts +24 -3
- package/tests/shell-changes-view.test.ts +471 -8
- package/tests/shell-changes.test.ts +168 -0
- package/tests/shell-prompt.test.ts +28 -6
- 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 +100 -11
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +3 -1
|
@@ -7,12 +7,16 @@ import {
|
|
|
7
7
|
decodeReviewConsentV3,
|
|
8
8
|
} from "../lib/review-integration-v2.ts";
|
|
9
9
|
import {
|
|
10
|
+
decodeNativeSddStatusV2,
|
|
10
11
|
NATIVE_REVIEW_DEFAULT_MAX_BUFFER_BYTES,
|
|
11
12
|
NATIVE_REVIEW_ERROR_CODE,
|
|
12
13
|
NativeReviewCliError,
|
|
13
14
|
NativeReviewCliV216,
|
|
15
|
+
NativeReviewIntegrationError,
|
|
14
16
|
createNodeExecFileAdapter,
|
|
17
|
+
isNativeReviewUnachievableVerbRefused,
|
|
15
18
|
type ExecFileAdapter,
|
|
19
|
+
type NativeTargetStatusRequest,
|
|
16
20
|
} from "../lib/native-review-cli.ts";
|
|
17
21
|
|
|
18
22
|
const fixture = (name: string): Record<string, unknown> => JSON.parse(
|
|
@@ -56,6 +60,100 @@ function client(adapter: ExecFileAdapter): NativeReviewCliV216 {
|
|
|
56
60
|
return new NativeReviewCliV216(adapter, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024);
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
// Structural test data follows the 2026-09-11 native 2.7.1-0.20260911070137-350f31554a4b
|
|
64
|
+
// capture: seven dependencies and four phaseInstructions groups, not a legacy alias.
|
|
65
|
+
function nativeSddStatus(changeName = "complete-native-review-lifecycle", workspaceRoot = "/repo"): Record<string, unknown> {
|
|
66
|
+
return {
|
|
67
|
+
schemaName: "gentle-ai.sdd-status",
|
|
68
|
+
schemaVersion: 2,
|
|
69
|
+
changeName,
|
|
70
|
+
artifactStore: "openspec",
|
|
71
|
+
planningHome: { mode: "repo-local", path: `${workspaceRoot}/openspec` },
|
|
72
|
+
changeRoot: `${workspaceRoot}/openspec/changes/${changeName}`,
|
|
73
|
+
actionContext: { mode: "repo-local", workspaceRoot, allowedEditRoots: [workspaceRoot] },
|
|
74
|
+
dependencies: { proposal: "all_done", specs: "all_done", design: "all_done", tasks: "all_done", apply: "all_done", verify: "all_done", archive: "ready" },
|
|
75
|
+
phaseInstructions: {
|
|
76
|
+
apply: ["Apply is complete."],
|
|
77
|
+
verify: ["Verification is complete."],
|
|
78
|
+
remediate: ["Bind remediation to failed evidence."],
|
|
79
|
+
archive: ["Archive the selected change."],
|
|
80
|
+
},
|
|
81
|
+
blockedReasons: [],
|
|
82
|
+
nextRecommended: "archive",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
test("native SDD status executes its exact selected-root argv and returns only a validated v2 authority", async () => {
|
|
87
|
+
const body = nativeSddStatus();
|
|
88
|
+
const queue = queuedAdapter([{ stdout: JSON.stringify(body) }]);
|
|
89
|
+
const status = await (client(queue.adapter) as unknown as {
|
|
90
|
+
sddStatus(request: { changeName: string; workspaceRoot: string }): Promise<Record<string, unknown>>;
|
|
91
|
+
}).sddStatus({ changeName: "complete-native-review-lifecycle", workspaceRoot: "/repo" });
|
|
92
|
+
|
|
93
|
+
assert.deepEqual(status, body);
|
|
94
|
+
assert.deepEqual(queue.calls, [{
|
|
95
|
+
file: "/package/.gentle-ai/gentle-ai",
|
|
96
|
+
arguments: ["sdd-status", "complete-native-review-lifecycle", "--cwd", "/repo", "--json", "--instructions"],
|
|
97
|
+
cwd: "/repo",
|
|
98
|
+
timeoutMs: 30_000,
|
|
99
|
+
}]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("native SDD status rejects malformed v2 identities, dependencies, instructions, and blockers", async () => {
|
|
103
|
+
const invalidInstructions = { apply: ["ok"], verify: ["ok"], remediate: ["ok"], archive: [42] };
|
|
104
|
+
assert.doesNotThrow(() => decodeNativeSddStatusV2(
|
|
105
|
+
{ ...nativeSddStatus(), phaseInstructions: { ...invalidInstructions, archive: ["ok"] } },
|
|
106
|
+
{ changeName: "complete-native-review-lifecycle", workspaceRoot: "/repo" },
|
|
107
|
+
));
|
|
108
|
+
const malformed = [
|
|
109
|
+
{ ...nativeSddStatus(), schemaName: "gentle-pi.sdd-status" },
|
|
110
|
+
{ ...nativeSddStatus(), schemaVersion: 1 },
|
|
111
|
+
{ ...nativeSddStatus(), changeName: "other-change" },
|
|
112
|
+
{ ...nativeSddStatus(), artifactStore: "future-store" },
|
|
113
|
+
{ ...nativeSddStatus(), planningHome: { mode: "future-mode", path: "/repo/openspec" } },
|
|
114
|
+
{ ...nativeSddStatus(), planningHome: { mode: "repo-local", path: "/outside" } },
|
|
115
|
+
{ ...nativeSddStatus(), changeRoot: "bad\nroot" },
|
|
116
|
+
{ ...nativeSddStatus(), actionContext: { mode: "repo-local", workspaceRoot: "/other", allowedEditRoots: ["/other"] } },
|
|
117
|
+
{ ...nativeSddStatus(), actionContext: { mode: "future-mode", workspaceRoot: "/repo", allowedEditRoots: ["/repo"] } },
|
|
118
|
+
{ ...nativeSddStatus(), actionContext: { mode: "repo-local", workspaceRoot: "/repo", allowedEditRoots: ["/outside"] } },
|
|
119
|
+
{ ...nativeSddStatus(), dependencies: { apply: "all_done", verify: "all_done", archive: "future" } },
|
|
120
|
+
{ ...nativeSddStatus(), phaseInstructions: invalidInstructions },
|
|
121
|
+
{ ...nativeSddStatus(), blockedReasons: "not-an-array" },
|
|
122
|
+
{ ...nativeSddStatus(), nextRecommended: "unknown" },
|
|
123
|
+
{ ...nativeSddStatus(), instructions: {}, phaseInstructions: undefined },
|
|
124
|
+
{ ...nativeSddStatus(), phaseInstructions: null },
|
|
125
|
+
];
|
|
126
|
+
for (const body of malformed) {
|
|
127
|
+
await assert.rejects(
|
|
128
|
+
() => (client(queuedAdapter([{ stdout: JSON.stringify(body) }]).adapter) as unknown as {
|
|
129
|
+
sddStatus(request: { changeName: string; workspaceRoot: string }): Promise<Record<string, unknown>>;
|
|
130
|
+
}).sddStatus({ changeName: "complete-native-review-lifecycle", workspaceRoot: "/repo" }),
|
|
131
|
+
(error: unknown) => error instanceof NativeReviewCliError && error.code === NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("native v2 read-only decoding preserves omitted optional phaseInstructions", () => {
|
|
137
|
+
const body = nativeSddStatus();
|
|
138
|
+
delete body.phaseInstructions;
|
|
139
|
+
assert.equal(decodeNativeSddStatusV2(body, { changeName: "complete-native-review-lifecycle", workspaceRoot: "/repo" }), body);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("native SDD status keeps malformed JSON, timeout, and nonzero execution fail-closed", async () => {
|
|
143
|
+
for (const [result, code] of [
|
|
144
|
+
[{ stdout: "not-json" }, NATIVE_REVIEW_ERROR_CODE.MALFORMED_JSON],
|
|
145
|
+
[{ stdout: "", timedOut: true }, NATIVE_REVIEW_ERROR_CODE.TIMEOUT],
|
|
146
|
+
[{ stdout: "{}", exitCode: 1 }, NATIVE_REVIEW_ERROR_CODE.NON_ZERO],
|
|
147
|
+
] as const) {
|
|
148
|
+
await assert.rejects(
|
|
149
|
+
() => (client(queuedAdapter([result]).adapter) as unknown as {
|
|
150
|
+
sddStatus(request: { changeName: string; workspaceRoot: string }): Promise<Record<string, unknown>>;
|
|
151
|
+
}).sddStatus({ changeName: "complete-native-review-lifecycle", workspaceRoot: "/repo" }),
|
|
152
|
+
(error: unknown) => error instanceof NativeReviewCliError && error.code === code && error.mutationOutcome === "none",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
59
157
|
test("negotiated STATUS accepts the pinned v5 receipt before routing its transition", async () => {
|
|
60
158
|
const status = fixture("status-v5.captured.json");
|
|
61
159
|
const queue = queuedAdapter([{ stdout: JSON.stringify(status) }]);
|
|
@@ -133,6 +231,125 @@ test("provider-owned refuter and targeted-validator vectors accept only their ma
|
|
|
133
231
|
);
|
|
134
232
|
});
|
|
135
233
|
|
|
234
|
+
// gentle-pi#638: the host relay's deterministic-failure exit declares the
|
|
235
|
+
// bound slot unachievable through the native verb instead of leaving the
|
|
236
|
+
// operator to re-spend an identical reviewer run. The invocation names the
|
|
237
|
+
// slot's frozen binding fields (--lineage/--target/--expected-revision/
|
|
238
|
+
// --request-hash), the machine-readable --reason, an optional bounded
|
|
239
|
+
// --detail, and the opaque --repository-context the slot carried; Go
|
|
240
|
+
// verifies all of them against the frozen authority before recording
|
|
241
|
+
// anything (internal/cli/review_capture_unachievable.go).
|
|
242
|
+
const UNACHIEVABLE_TARGET = `sha256:${"1".repeat(64)}`;
|
|
243
|
+
const UNACHIEVABLE_REVISION = `sha256:${"3".repeat(64)}`;
|
|
244
|
+
const UNACHIEVABLE_REQUEST_HASH = `sha256:${"0".repeat(64)}`;
|
|
245
|
+
const UNACHIEVABLE_ARTIFACT = { schema: "gentle-ai.review-capture-unachievable/v1", lineage_id: "relay-lineage", target_identity: UNACHIEVABLE_TARGET, lens: "review-reliability", selected_order: 0, reason: "relay_transport_bound_exceeded", recorded: true };
|
|
246
|
+
function unachievableRequest(cwd = "/repo") {
|
|
247
|
+
return { cwd, lineageId: "relay-lineage", targetIdentity: UNACHIEVABLE_TARGET, expectedRevision: UNACHIEVABLE_REVISION, requestHash: UNACHIEVABLE_REQUEST_HASH, reason: "relay_transport_bound_exceeded" };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
test("capture-unachievable declares the exact slot binding and decodes its recorded artifact", async () => {
|
|
251
|
+
const queue = queuedAdapter([{ stdout: JSON.stringify(UNACHIEVABLE_ARTIFACT) }]);
|
|
252
|
+
const result = await client(queue.adapter).captureUnachievableLens({ ...unachievableRequest(), detail: "killed after 2256004ms against a 2256000ms relay bound", repositoryContext: "rctx1_" + "e".repeat(64) });
|
|
253
|
+
assert.deepEqual(result, { schema: "gentle-ai.review-capture-unachievable/v1", lineageId: "relay-lineage", targetIdentity: UNACHIEVABLE_TARGET, lens: "review-reliability", selectedOrder: 0, reason: "relay_transport_bound_exceeded", recorded: true });
|
|
254
|
+
// gentle-pi#822: --repository-context is authoritative and mutually exclusive with a path, so a declaration carrying one names no --cwd; the process still runs from request.cwd.
|
|
255
|
+
assert.deepEqual(queue.calls[0]?.arguments, ["review", "capture-unachievable", "--lineage", "relay-lineage", "--target", UNACHIEVABLE_TARGET, "--expected-revision", UNACHIEVABLE_REVISION, "--request-hash", UNACHIEVABLE_REQUEST_HASH, "--reason", "relay_transport_bound_exceeded", "--detail", "killed after 2256004ms against a 2256000ms relay bound", "--repository-context", "rctx1_" + "e".repeat(64)]);
|
|
256
|
+
assert.equal(queue.calls[0]?.cwd, "/repo", "the process working directory stays request.cwd even when --cwd is not passed as an argument");
|
|
257
|
+
assert.equal(queue.calls[0]?.timeoutMs, undefined, "the mutating declaration runs without the read-only negotiation timeout");
|
|
258
|
+
|
|
259
|
+
// the optional detail and repository context are genuinely optional
|
|
260
|
+
const bare = queuedAdapter([{ stdout: JSON.stringify(UNACHIEVABLE_ARTIFACT) }]);
|
|
261
|
+
await client(bare.adapter).captureUnachievableLens(unachievableRequest());
|
|
262
|
+
assert.deepEqual(bare.calls[0]?.arguments, ["review", "capture-unachievable", "--lineage", "relay-lineage", "--target", UNACHIEVABLE_TARGET, "--expected-revision", UNACHIEVABLE_REVISION, "--request-hash", UNACHIEVABLE_REQUEST_HASH, "--reason", "relay_transport_bound_exceeded", "--cwd", "/repo"]);
|
|
263
|
+
assert.equal(bare.calls[0]?.cwd, "/repo");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("capture-unachievable measures detail in UTF-8 bytes, not UTF-16 code units", async () => {
|
|
267
|
+
const atLimit = queuedAdapter([{ stdout: JSON.stringify(UNACHIEVABLE_ARTIFACT) }]);
|
|
268
|
+
await client(atLimit.adapter).captureUnachievableLens({ ...unachievableRequest(), detail: "é".repeat(256) });
|
|
269
|
+
assert.equal(atLimit.calls.length, 1, "256 two-byte characters are exactly the 512-byte limit and must pass");
|
|
270
|
+
|
|
271
|
+
const oneCharBeyond = queuedAdapter([]);
|
|
272
|
+
await assert.rejects(() => client(oneCharBeyond.adapter).captureUnachievableLens({ ...unachievableRequest(), detail: "é".repeat(257) }), TypeError);
|
|
273
|
+
assert.equal(oneCharBeyond.calls.length, 0, "257 two-byte characters are 514 bytes and are refused before any process launches");
|
|
274
|
+
|
|
275
|
+
const shortButHeavy = queuedAdapter([]);
|
|
276
|
+
await assert.rejects(() => client(shortButHeavy.adapter).captureUnachievableLens({ ...unachievableRequest(), detail: "é".repeat(300) }), TypeError);
|
|
277
|
+
assert.equal(shortButHeavy.calls.length, 0, "300 code units pass a .length check but encode to 600 bytes and must throw");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test("capture-unachievable validates its request and artifact shape before and after the invocation", async () => {
|
|
281
|
+
for (const request of [
|
|
282
|
+
{ ...unachievableRequest(), lineageId: "" },
|
|
283
|
+
{ ...unachievableRequest(), targetIdentity: "not-a-sha" },
|
|
284
|
+
{ ...unachievableRequest(), expectedRevision: " " },
|
|
285
|
+
{ ...unachievableRequest(), requestHash: "sha256:short" },
|
|
286
|
+
{ ...unachievableRequest(), reason: "" },
|
|
287
|
+
{ ...unachievableRequest(), detail: "x".repeat(513) },
|
|
288
|
+
{ ...unachievableRequest(), repositoryContext: "no\u0000context" },
|
|
289
|
+
]) {
|
|
290
|
+
const queue = queuedAdapter([]);
|
|
291
|
+
await assert.rejects(() => client(queue.adapter).captureUnachievableLens(request), TypeError);
|
|
292
|
+
assert.equal(queue.calls.length, 0, "a malformed declaration is refused before any process launches");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (const artifact of [
|
|
296
|
+
{ ...UNACHIEVABLE_ARTIFACT, schema: "gentle-ai.review-capture-unachievable/v2" },
|
|
297
|
+
{ ...UNACHIEVABLE_ARTIFACT, recorded: false },
|
|
298
|
+
{ ...UNACHIEVABLE_ARTIFACT, selected_order: -1 },
|
|
299
|
+
]) {
|
|
300
|
+
const queue = queuedAdapter([{ stdout: JSON.stringify(artifact) }]);
|
|
301
|
+
await assert.rejects(() => client(queue.adapter).captureUnachievableLens(unachievableRequest()));
|
|
302
|
+
assert.equal(queue.calls.length, 1);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test("an older binary's unknown-verb refusal classifies as a capability signal, not a capture failure", async () => {
|
|
307
|
+
const queue = queuedAdapter([{ stdout: "", stderr: 'Error: unknown review command "capture-unachievable"\n', exitCode: 1 }]);
|
|
308
|
+
const error = await client(queue.adapter).captureUnachievableLens(unachievableRequest()).then(() => undefined, (caught: unknown) => caught);
|
|
309
|
+
assert.ok(error instanceof NativeReviewCliError, "an empty-stdout refusal rejects with the typed CLI error carrying stderr diagnostics");
|
|
310
|
+
assert.equal(isNativeReviewUnachievableVerbRefused(error), true);
|
|
311
|
+
|
|
312
|
+
// Every other stderr — a typed binding-mismatch refusal, a timeout, or a
|
|
313
|
+
// clean run — is a real outcome, never a capability signal.
|
|
314
|
+
assert.equal(isNativeReviewUnachievableVerbRefused(new Error('unknown review command "capture-unachievable"')), false, "only the captured native stderr classifies");
|
|
315
|
+
const typedRefusal = new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.NON_ZERO, "review/capture-unachievable", true, true, "native capture-unachievable refused", { operation: "review/capture-unachievable", error_code: NATIVE_REVIEW_ERROR_CODE.NON_ZERO, exit_code: 1, timed_out: false, output_limit_exceeded: false, stderr: "Error: review capture-unachievable binding does not match the current reviewing authority [invalid_request]" });
|
|
316
|
+
assert.equal(isNativeReviewUnachievableVerbRefused(typedRefusal), false);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("nonzero targeted-validator capture preserves its dot-form typed failure", async () => {
|
|
320
|
+
const failure = {
|
|
321
|
+
schema: "gentle-ai.review-integration.failure/v2",
|
|
322
|
+
contract: "gentle-ai.review-integration/v2",
|
|
323
|
+
operation: "review.capture-validation",
|
|
324
|
+
phase: "native_running",
|
|
325
|
+
code: "targeted_validation_failed",
|
|
326
|
+
message: "the targeted validator rejected the candidate",
|
|
327
|
+
mutation_outcome: "unknown",
|
|
328
|
+
authority_applicability: "current_target",
|
|
329
|
+
retry_safe: false,
|
|
330
|
+
replayability: "status_required",
|
|
331
|
+
required_inputs: [],
|
|
332
|
+
next_action: "review.status",
|
|
333
|
+
};
|
|
334
|
+
const queue = queuedAdapter([{ stdout: JSON.stringify(failure), exitCode: 1 }]);
|
|
335
|
+
await assert.rejects(
|
|
336
|
+
() => client(queue.adapter).captureProviderRole({
|
|
337
|
+
captureOperation: "review.capture-validation",
|
|
338
|
+
argumentTokens: ["--repository-context=rctx1_" + "a".repeat(64), "--agent=pi", "--execute=true"],
|
|
339
|
+
cwd: "/repo",
|
|
340
|
+
}),
|
|
341
|
+
(error: unknown) => {
|
|
342
|
+
if (!(error instanceof NativeReviewIntegrationError)) return false;
|
|
343
|
+
assert.equal(error.failureEnvelope.operation, "review.capture-validation");
|
|
344
|
+
assert.equal(error.mutationOutcome, "unknown");
|
|
345
|
+
assert.equal(error.nextAction, "review.status");
|
|
346
|
+
assert.deepEqual(error.failureEnvelope.raw, failure);
|
|
347
|
+
return true;
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
assert.deepEqual(queue.calls[0]?.arguments, ["review", "capture-validation", "--repository-context=rctx1_" + "a".repeat(64), "--agent=pi", "--execute=true"]);
|
|
351
|
+
});
|
|
352
|
+
|
|
136
353
|
test("malformed closure output remains a typed schema failure and never authorizes a retry", async () => {
|
|
137
354
|
const queue = queuedAdapter([{ stdout: JSON.stringify({ schema: "gentle-ai.review-last-event-closure/v1", operation: "review/capture-result" }) }]);
|
|
138
355
|
await assert.rejects(
|
|
@@ -247,7 +464,8 @@ test("negotiated STATUS rejects malformed committed selectors before launching a
|
|
|
247
464
|
{ cwd: "/repo", baseRef: "refs/heads/main", committedOnly: "true" },
|
|
248
465
|
]) {
|
|
249
466
|
const queue = queuedAdapter([]);
|
|
250
|
-
|
|
467
|
+
// gentle-pi#822: these fixtures are intentionally mistyped (a numeric baseRef, a string committedOnly) — the repo's `as unknown as` idiom keeps the invalid shapes reaching the validator instead of widening the request type.
|
|
468
|
+
await assert.rejects(() => client(queue.adapter).targetStatus(request as unknown as NativeTargetStatusRequest), TypeError);
|
|
251
469
|
assert.equal(queue.calls.length, 0);
|
|
252
470
|
}
|
|
253
471
|
});
|
|
@@ -568,8 +786,8 @@ test("capture-result receives the controller AbortSignal without an automatic mu
|
|
|
568
786
|
assert.equal(receivedTimeout, undefined);
|
|
569
787
|
});
|
|
570
788
|
|
|
571
|
-
test("native review client
|
|
572
|
-
assert.equal(
|
|
789
|
+
test("native review client exposes native v2 SDD status without adding review lifecycle behavior", () => {
|
|
790
|
+
assert.equal(typeof (client(queuedAdapter([]).adapter) as unknown as { sddStatus?: unknown }).sddStatus, "function");
|
|
573
791
|
});
|
|
574
792
|
|
|
575
793
|
test("read-only authority inventory rejects a repository identity mismatch after decoding", async () => {
|
|
@@ -782,3 +1000,59 @@ test("acknowledge-approved keeps its fail-closed stderr and typed refusal discip
|
|
|
782
1000
|
(error: unknown) => error instanceof NativeReviewCliError && error.code === NATIVE_REVIEW_ERROR_CODE.EMPTY_OUTPUT,
|
|
783
1001
|
);
|
|
784
1002
|
});
|
|
1003
|
+
|
|
1004
|
+
test("native continuation uses a distinct mutating operation and the same v2 decoder", async () => {
|
|
1005
|
+
const queue = queuedAdapter([{ stdout: JSON.stringify(nativeSddStatus()) }, { stdout: "{}" }]);
|
|
1006
|
+
const cli = client(queue.adapter);
|
|
1007
|
+
const request = { changeName: "complete-native-review-lifecycle", workspaceRoot: "/repo" };
|
|
1008
|
+
assert.equal((await cli.sddContinue(request)).nextRecommended, nativeSddStatus().nextRecommended);
|
|
1009
|
+
assert.deepEqual(queue.calls[0]!.arguments, ["sdd-continue", request.changeName, "--cwd", "/repo", "--json", "--instructions"]);
|
|
1010
|
+
assert.equal(queue.calls[0]!.timeoutMs, undefined);
|
|
1011
|
+
await assert.rejects(() => cli.sddContinue(request), (error: unknown) => error instanceof NativeReviewCliError && error.mutating === true);
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
test("native discovery preserves nullable selection and rejects an invented selected identity", () => {
|
|
1015
|
+
const discovery = { ...nativeSddStatus(), changeName: null, changeRoot: null, nextRecommended: "select-change" };
|
|
1016
|
+
assert.equal(decodeNativeSddStatusV2(discovery, { workspaceRoot: "/repo" }).changeName, null);
|
|
1017
|
+
assert.throws(() => decodeNativeSddStatusV2(discovery, { changeName: "alpha", workspaceRoot: "/repo" }), /identity/);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
test("all twelve native actions retain their exact tokens without prose routing", () => {
|
|
1021
|
+
for (const nextRecommended of ["apply", "verify", "remediate", "archive", "archived", "resolve-blockers", "sdd-new", "select-change", "propose", "spec", "design", "tasks"]) {
|
|
1022
|
+
const status = { ...nativeSddStatus(), nextRecommended, ...(nextRecommended === "remediate" ? { remediationState: { required: true, complete: false, failedEvidenceRevision: `sha256:${"a".repeat(64)}` } } : {}) };
|
|
1023
|
+
assert.equal(decodeNativeSddStatusV2(status, { workspaceRoot: "/repo" }).nextRecommended, nextRecommended);
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
test("compact SDD bracket transports exact selection and remediation binding", async () => {
|
|
1029
|
+
const revision = `sha256:${"a".repeat(64)}`;
|
|
1030
|
+
const queued = queuedAdapter([{ stdout: JSON.stringify({ state: "proceed", token: revision }) }, { stdout: '{"state":"complete"}' }]);
|
|
1031
|
+
const cli = client(queued.adapter);
|
|
1032
|
+
const request = { workspaceRoot: "/repo", changeName: "fix", requestId: "acquire-one", workUnit: "correction", evidenceGoal: "Observed correction", maxAttempts: 1, maxChangedLines: 300, remediatesEvidenceRevision: revision };
|
|
1033
|
+
assert.deepEqual(await cli.sddAttemptAcquire(request), { state: "proceed", token: revision });
|
|
1034
|
+
assert.deepEqual(queued.calls[0].arguments, ["sdd-attempt", "acquire", "--cwd", "/repo", "--change", "fix", "--request-id", "acquire-one", "--work-unit", "correction", "--evidence-goal", "Observed correction", "--max-attempts", "1", "--max-changed-lines", "300", "--remediates-evidence-revision", revision]);
|
|
1035
|
+
assert.deepEqual(await cli.sddAttemptSettle({ workspaceRoot: "/repo", changeName: "fix", token: revision, requestId: "settle-one", outcome: "interrupted", diagnosis: "Child could not spawn", harnessDisposition: "invalidated", cleanupEvidence: "No process created", processEvidence: "Spawn rejected", remediatesEvidenceRevision: revision }), { state: "complete" });
|
|
1036
|
+
assert.equal(queued.calls[1].arguments.includes("--evidence-revision"), false);
|
|
1037
|
+
assert.equal(queued.calls[1].timeoutMs, undefined);
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
test("compact SDD refuses malformed result and invalid terminal evidence before execution", async () => {
|
|
1041
|
+
const queued = queuedAdapter([{ stdout: '{"state":"proceed"}' }, { stdout: '{"state":"blocked","reason":"budget"}' }]);
|
|
1042
|
+
const cli = client(queued.adapter);
|
|
1043
|
+
const request = { workspaceRoot: "/repo", changeName: "fix", requestId: "one", workUnit: "correction", evidenceGoal: "Observed correction" };
|
|
1044
|
+
await assert.rejects(cli.sddAttemptAcquire(request), /schema incompatible/);
|
|
1045
|
+
assert.deepEqual(await cli.sddAttemptAcquire(request), { state: "blocked", reason: "budget" });
|
|
1046
|
+
await assert.rejects(cli.sddAttemptSettle({ ...request, token: `sha256:${"a".repeat(64)}`, outcome: "failed", diagnosis: "Test failed", harnessDisposition: "reused", cleanupEvidence: "Process exited", processEvidence: "Exit one" }), /evidence/i);
|
|
1047
|
+
assert.equal(queued.calls.length, 2);
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
test("compact admission accepts native empty CAS and never redirects uncertainty into review", async () => {
|
|
1052
|
+
const queued = queuedAdapter([{ stdout: '{"state":"blocked","reason":"budget"}' }, { stdout: "", timedOut: true }]);
|
|
1053
|
+
const cli = client(queued.adapter);
|
|
1054
|
+
const request = { workspaceRoot: "/repo", changeName: "fix", requestId: "one", workUnit: "fix", evidenceGoal: "Observed correction", expectedRevision: "" };
|
|
1055
|
+
await cli.sddAttemptAcquire(request);
|
|
1056
|
+
assert.equal(queued.calls[0].arguments[queued.calls[0].arguments.indexOf("--expected-revision") + 1], "");
|
|
1057
|
+
await assert.rejects(cli.sddAttemptAcquire({ ...request, expectedRevision: undefined }), error => error instanceof NativeReviewCliError && error.mutationOutcome === "unknown" && error.nextAction === undefined);
|
|
1058
|
+
});
|
|
@@ -511,13 +511,18 @@ test("launched answer-consent failures remain blocked after fresh-target STATUS
|
|
|
511
511
|
let answerCalls = 0;
|
|
512
512
|
fixture.native.answerConsent = async () => {
|
|
513
513
|
answerCalls += 1;
|
|
514
|
-
throw
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
514
|
+
throw {
|
|
515
|
+
name: "NativeReviewCliError",
|
|
516
|
+
code: NATIVE_REVIEW_ERROR_CODE.NON_ZERO,
|
|
517
|
+
mutationOutcome: "unknown",
|
|
518
|
+
nextAction: "review.status",
|
|
519
|
+
diagnostics: {
|
|
520
|
+
operation: NATIVE_REVIEW_OPERATION.START,
|
|
521
|
+
error_code: NATIVE_REVIEW_ERROR_CODE.NON_ZERO,
|
|
522
|
+
timed_out: false,
|
|
523
|
+
output_limit_exceeded: false,
|
|
524
|
+
},
|
|
525
|
+
};
|
|
521
526
|
};
|
|
522
527
|
const runtime = parityRuntime(fixture.native);
|
|
523
528
|
const blocked = await beginConsent(runtime, cwd);
|
|
@@ -533,6 +538,8 @@ test("launched answer-consent failures remain blocked after fresh-target STATUS
|
|
|
533
538
|
output_limit_exceeded: false,
|
|
534
539
|
});
|
|
535
540
|
assert.deepEqual(first.reconciliation, freshTargetStatus.raw);
|
|
541
|
+
assert.match(String(first.required_status_action), /Run target-scoped review\.status/);
|
|
542
|
+
assert.equal(first.next_action, "start");
|
|
536
543
|
assert.equal(statusRequests.length, 2, "the launched failure reconciles exactly once after the initial START status");
|
|
537
544
|
assert.equal(answerCalls, 1, "reconciliation must not replay answer-consent");
|
|
538
545
|
|
|
@@ -147,7 +147,6 @@ test("workflow settle command line binds mandatory arguments and routing invaria
|
|
|
147
147
|
"--token <token>",
|
|
148
148
|
"--request-id <id>",
|
|
149
149
|
"--outcome <failed|interrupted|passed>",
|
|
150
|
-
"--evidence-revision <sha256:...>",
|
|
151
150
|
"--diagnosis <text>",
|
|
152
151
|
"--harness-disposition <reused|invalidated>",
|
|
153
152
|
"--cleanup-evidence <text>",
|
|
@@ -155,7 +154,13 @@ test("workflow settle command line binds mandatory arguments and routing invaria
|
|
|
155
154
|
]) {
|
|
156
155
|
assert.ok(settle.includes(arg), `settle command is missing ${arg}; command: ${settle}`);
|
|
157
156
|
}
|
|
158
|
-
assert.
|
|
157
|
+
assert.ok(settle.includes("[--evidence-revision <sha256:...>]"));
|
|
158
|
+
assert.match(section, /Every settle field except `evidence-revision` is required/);
|
|
159
|
+
assert.match(
|
|
160
|
+
section,
|
|
161
|
+
/For `failed` or `passed`, include `--evidence-revision` with the `sha256:\.\.\.` evidence hash\./,
|
|
162
|
+
);
|
|
163
|
+
assert.match(section, /For `interrupted`, omit the entire `--evidence-revision` flag\./);
|
|
159
164
|
assert.match(section, /proceed\|blocked\|complete/);
|
|
160
165
|
assert.match(section, /--successor-lineage/);
|
|
161
166
|
assert.match(section, /--remediates-evidence-revision/);
|