gentle-pi 2.2.0 → 2.4.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 +76 -79
- package/assets/agents/review-readability.md +0 -2
- package/assets/agents/review-reliability.md +0 -2
- package/assets/agents/review-resilience.md +0 -2
- package/assets/agents/review-risk.md +0 -2
- package/assets/agents/sdd-apply.md +5 -3
- package/assets/agents/sdd-explore.md +1 -0
- package/assets/agents/sdd-proposal.md +2 -0
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-status.md +4 -4
- package/assets/agents/sdd-tasks.md +2 -3
- package/assets/agents/sdd-verify.md +21 -1
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +56 -249
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +20 -40
- package/assets/sdd-orchestrator-workflow.md +30 -37
- package/assets/support/sdd-status-contract.md +7 -7
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
- package/docs/native-authority-architecture.md +9 -11
- package/docs/review-integration.md +27 -373
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +2349 -3785
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/agent-home.ts +8 -0
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +387 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +88 -6
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +324 -96
- package/lib/review-integration-v2.ts +868 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +179 -68
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -82
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +337 -890
- package/runtime/review-integration-v2.mjs +816 -255
- package/runtime/review-relay-contract.mjs +1 -1
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
- package/scripts/gentle-ai-installer.mjs +75 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
- package/scripts/test-packed-runner.mjs +4 -7
- package/scripts/verify-package-files.mjs +23 -23
- package/skills/_shared/review-ledger-contract.md +14 -14
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/issue-creation/SKILL.md +53 -93
- package/skills/judgment-day/SKILL.md +5 -7
- package/skills/rdd-defect-workflow/SKILL.md +3 -3
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +15 -1168
- package/tests/delegated-key-learnings-contract.test.ts +8 -6
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
- package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
- package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary.test.ts +51 -228
- package/tests/gentle-ai-installer.test.ts +137 -47
- package/tests/gentle-ai.test.ts +821 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/issue-creation-skill.test.ts +103 -0
- package/tests/maintainer/provider-relay.maintest.ts +340 -4
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +45 -1
- package/tests/native-review-cli.test.ts +586 -962
- package/tests/native-review-consent.test.ts +150 -66
- package/tests/native-review-parity-runtime.test.ts +80 -359
- package/tests/native-review-parity.test.ts +674 -925
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +132 -88
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +152 -36
- package/tests/provider-contract-bundle.test.ts +76 -0
- package/tests/provider-contract-mirror.test.ts +19 -0
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-agent-end-preflight.test.ts +408 -0
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +409 -3
- package/tests/review-compact-contract.test.ts +4 -30
- package/tests/review-contract-prompt.test.ts +139 -0
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +363 -1450
- package/tests/review-controller-native-routing.test.ts +1251 -5642
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +222 -71
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +112 -153
- package/tests/review-dispatch-hydration-gap.test.ts +1 -53
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +328 -91
- package/tests/review-host-relay.test.ts +436 -14
- package/tests/review-integration-v2-forward.test.ts +623 -553
- package/tests/review-integration-v2.test.ts +239 -165
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +97 -35
- package/tests/review-recovered-lineage-routing.test.ts +0 -47
- package/tests/review-relay-transport-agent.test.ts +119 -46
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +362 -196
- package/tests/sdd-agent-tools.test.ts +36 -0
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +5 -8
- package/tests/writer-edit-surface-scope.test.ts +366 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/lib/git-commit-transaction.ts +0 -861
- package/runtime/git-commit-transaction.mjs +0 -862
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- package/tests/git-commit-transaction.test.ts +0 -530
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
- /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
|
@@ -3,7 +3,7 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
|
-
import { mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { chmod, mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
8
|
import { dirname, join } from "node:path";
|
|
9
9
|
import { discoverAndLoadExtensions } from "@earendil-works/pi-coding-agent";
|
|
@@ -11,8 +11,6 @@ import { matchesKey } from "@earendil-works/pi-tui";
|
|
|
11
11
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
12
|
import { stripAnsi } from "../lib/terminal-theme.ts";
|
|
13
13
|
import { domainHashV1 } from "../lib/review-canonical.ts";
|
|
14
|
-
import { resolveGentleAiBinary } from "../lib/gentle-ai-binary.ts";
|
|
15
|
-
import { NativeReviewCliV216, NATIVE_REVIEW_ERROR_CODE, NativeReviewCliError } from "../lib/native-review-cli.ts";
|
|
16
14
|
|
|
17
15
|
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
18
16
|
const EXTENSIONS = [
|
|
@@ -33,18 +31,23 @@ const EXPECTED_BANNER_COMMANDS = [
|
|
|
33
31
|
const EXPECTED_COMMANDS = [
|
|
34
32
|
"gentle:install-sdd",
|
|
35
33
|
"gentle:sdd-preflight",
|
|
36
|
-
"sdd-status",
|
|
37
|
-
"sdd-continue",
|
|
34
|
+
"gentle-sdd-status",
|
|
35
|
+
"gentle-sdd-continue",
|
|
38
36
|
"gentle:models",
|
|
39
37
|
"gentle:persona",
|
|
40
38
|
"gentle:status",
|
|
41
39
|
"gentle:doctor",
|
|
42
|
-
"sdd-init",
|
|
40
|
+
"gentle-sdd-init",
|
|
43
41
|
"skill-registry:refresh",
|
|
44
42
|
...EXPECTED_BANNER_COMMANDS,
|
|
45
43
|
];
|
|
46
44
|
|
|
47
45
|
const FORBIDDEN_COMPAT_COMMANDS = [
|
|
46
|
+
// The SDD entry points carry the gentle- prefix so they read identically in
|
|
47
|
+
// Claude Code and Pi. The bare names are retired without an alias.
|
|
48
|
+
"sdd-init",
|
|
49
|
+
"sdd-continue",
|
|
50
|
+
"sdd-status",
|
|
48
51
|
"gentle-ai:install-sdd",
|
|
49
52
|
"gentle-ai:sdd-preflight",
|
|
50
53
|
"gentle-ai:sdd-status",
|
|
@@ -66,10 +69,28 @@ function createPi() {
|
|
|
66
69
|
const commands = new Map();
|
|
67
70
|
const flags = new Map();
|
|
68
71
|
const tools = new Map();
|
|
72
|
+
const eventHandlers = new Map();
|
|
73
|
+
const emittedEvents = [];
|
|
69
74
|
const flagValues = new Map([["no-skill-registry", true]]);
|
|
75
|
+
const events = {
|
|
76
|
+
emit(channel, data) {
|
|
77
|
+
emittedEvents.push({ channel, data });
|
|
78
|
+
for (const handler of eventHandlers.get(channel) ?? []) handler(data);
|
|
79
|
+
},
|
|
80
|
+
on(channel, handler) {
|
|
81
|
+
const handlers = eventHandlers.get(channel) ?? new Set();
|
|
82
|
+
handlers.add(handler);
|
|
83
|
+
eventHandlers.set(channel, handlers);
|
|
84
|
+
return () => {
|
|
85
|
+
handlers.delete(handler);
|
|
86
|
+
if (handlers.size === 0) eventHandlers.delete(channel);
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
70
90
|
let activeTools = ["read", "bash", "edit", "write"];
|
|
71
91
|
|
|
72
92
|
const pi = {
|
|
93
|
+
events,
|
|
73
94
|
on(name, handler) {
|
|
74
95
|
const list = hooks.get(name) ?? [];
|
|
75
96
|
list.push(handler);
|
|
@@ -110,7 +131,7 @@ function createPi() {
|
|
|
110
131
|
},
|
|
111
132
|
};
|
|
112
133
|
|
|
113
|
-
return { pi, hooks, commands, flags, tools };
|
|
134
|
+
return { pi, hooks, commands, flags, tools, emittedEvents };
|
|
114
135
|
}
|
|
115
136
|
|
|
116
137
|
function createUi() {
|
|
@@ -182,6 +203,15 @@ async function tempWorkspace() {
|
|
|
182
203
|
return mkdtemp(join(tmpdir(), "gentle-pi-runtime-"));
|
|
183
204
|
}
|
|
184
205
|
|
|
206
|
+
function restoreWorkspaceWritePermissions(cwd) {
|
|
207
|
+
if (process.platform === "win32") return;
|
|
208
|
+
try {
|
|
209
|
+
execFileSync("chmod", ["-R", "u+w", cwd], { stdio: "ignore" });
|
|
210
|
+
} catch {
|
|
211
|
+
// A prior candidate-view cleanup may already have removed the workspace.
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
185
215
|
async function loadExtensions(pi) {
|
|
186
216
|
for (const [index, rel] of EXTENSIONS.entries()) {
|
|
187
217
|
const mod = await import(`${pathToFileURL(join(ROOT, rel)).href}?runtime-harness=${index}`);
|
|
@@ -197,28 +227,125 @@ async function run() {
|
|
|
197
227
|
process.env.GENTLE_PI_CONFIG_HOME = globalConfigHome;
|
|
198
228
|
process.env.GENTLE_PI_AGENT_HOME = globalAgentHome;
|
|
199
229
|
process.env.GENTLE_PI_TEST_ASSETS_DIR = ambientTestAssetsDir;
|
|
200
|
-
// The harness already sandboxes its config and agent homes; the review kill
|
|
201
|
-
// switch is the one piece of machine state it was still inheriting. Several
|
|
202
|
-
// lifecycle-gate assertions below only reach their subject while
|
|
203
|
-
// receipt-driven development is on — with it off, gateLifecycleCommand
|
|
204
|
-
// returns undefined before deriving any target, which is correct behavior
|
|
205
|
-
// but not what those assertions are about.
|
|
206
|
-
//
|
|
207
|
-
// gentle-ai v2.4.0 made the switch opt-in, so an unconfigured machine now
|
|
208
|
-
// resolves to off. Until then the harness passed or failed on whatever the
|
|
209
|
-
// operator happened to have set, which made it green locally and red on a
|
|
210
|
-
// fresh CI runner from identical bytes. It now owns a sandbox HOME and opts
|
|
211
|
-
// in explicitly, the same way a user does, so the gates are what is under
|
|
212
|
-
// test rather than the machine.
|
|
213
|
-
const reviewHome = await tempWorkspace();
|
|
214
|
-
process.env.HOME = reviewHome;
|
|
215
|
-
const reviewModeEnable = execFileSync(resolveGentleAiBinary(ROOT, process.platform), ["review", "mode", "enable", "--scope", "global", "--json"], { cwd: ROOT, encoding: "utf8", env: { ...process.env, HOME: reviewHome } });
|
|
216
|
-
assert.match(reviewModeEnable, /"effective": "on"/, "the harness sandbox HOME must have receipt-driven development explicitly enabled");
|
|
217
230
|
const globalModelsPath = join(globalConfigHome, "models.json");
|
|
218
231
|
const globalSubagentsPath = join(globalAgentHome, "subagents.json");
|
|
219
|
-
const { pi, hooks, commands, flags, tools } = createPi();
|
|
232
|
+
const { pi, hooks, commands, flags, tools, emittedEvents } = createPi();
|
|
220
233
|
await loadExtensions(pi);
|
|
221
234
|
|
|
235
|
+
// gentle-pi#404: a collect binding that returns the native last-event
|
|
236
|
+
// closure must terminate after one capture. It must not re-enter a public
|
|
237
|
+
// lifecycle mutation or synthesize a follow-up transition.
|
|
238
|
+
{
|
|
239
|
+
const lineageId = "runtime-last-event";
|
|
240
|
+
const sha = `sha256:${"a".repeat(64)}`;
|
|
241
|
+
const tree = "b".repeat(40);
|
|
242
|
+
const repositoryContext = `rctx1_${"c".repeat(64)}`;
|
|
243
|
+
const calls = [];
|
|
244
|
+
const arguments_ = [
|
|
245
|
+
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
246
|
+
{ name: "expected-revision", value: sha, token: `--expected-revision=${sha}` },
|
|
247
|
+
{ name: "target", value: sha, token: `--target=${sha}` },
|
|
248
|
+
{ name: "repository-context", value: repositoryContext, token: `--repository-context=${repositoryContext}` },
|
|
249
|
+
{ name: "lens", value: "review-risk", token: "--lens=review-risk" },
|
|
250
|
+
{ name: "order", value: "0", token: "--order=0" },
|
|
251
|
+
{ name: "subject-hash", value: sha, token: `--subject-hash=${sha}` },
|
|
252
|
+
];
|
|
253
|
+
const input = {
|
|
254
|
+
name: "correction_plan",
|
|
255
|
+
schema: "gentle-ai.review-correction-plan/v1",
|
|
256
|
+
captureOperation: "review.capture-correction-plan",
|
|
257
|
+
arguments: arguments_,
|
|
258
|
+
submission: {
|
|
259
|
+
operationToken: "capture-correction-plan",
|
|
260
|
+
argumentTokens: [...arguments_.map((argument) => argument.token), "--correction-lines={{value}}"],
|
|
261
|
+
values: [{ slot: "correction_lines", domain: "positive_integer", substitutionLocation: 7, minimum: 1, maximum: 1 }],
|
|
262
|
+
},
|
|
263
|
+
};
|
|
264
|
+
const status = {
|
|
265
|
+
contract: "gentle-ai.review-integration/v2",
|
|
266
|
+
applicability: "current_target",
|
|
267
|
+
authority: { version: "compact-v2", lineageId, state: "correction_required", generation: 1, revision: sha },
|
|
268
|
+
receipt: { status: "expected_missing" },
|
|
269
|
+
action: "stop",
|
|
270
|
+
replayability: "not_replayable",
|
|
271
|
+
targetIdentity: sha,
|
|
272
|
+
projection: {
|
|
273
|
+
schema: "gentle-ai.review-candidate-projection/v1",
|
|
274
|
+
kind: "current-changes",
|
|
275
|
+
projection: "workspace",
|
|
276
|
+
baseTree: tree,
|
|
277
|
+
initialReviewTree: tree,
|
|
278
|
+
currentCandidateTree: tree,
|
|
279
|
+
pathsDigest: sha,
|
|
280
|
+
paths: ["app.ts"],
|
|
281
|
+
intendedUntracked: [],
|
|
282
|
+
intendedUntrackedProof: sha,
|
|
283
|
+
initialSnapshotIdentity: sha,
|
|
284
|
+
currentSnapshotIdentity: sha,
|
|
285
|
+
},
|
|
286
|
+
repair: { schema: "gentle-ai.review-authority-repair-assessment/v1", status: "unsupported", counts: { lineages: 0, compactLineages: 0, legacyLineages: 0, events: 0, bytes: 0, eligibleCandidates: 0, unsupportedLineages: 0, conflicts: 0 }, supportedOperations: ["review/complete-fix", "review/validate-fix"], authorizationSchema: "gentle-ai.review-repair-authorization/v1" },
|
|
287
|
+
candidates: [],
|
|
288
|
+
nextTransition: { kind: "collect", reasonCode: "correction_plan_required", collect: { inputs: [input] } },
|
|
289
|
+
raw: { schema: "gentle-ai.review-integration.status/v5" },
|
|
290
|
+
};
|
|
291
|
+
const nativeReviewCli = {
|
|
292
|
+
async targetStatus(request) {
|
|
293
|
+
calls.push({ operation: "status", request });
|
|
294
|
+
return status;
|
|
295
|
+
},
|
|
296
|
+
async captureCorrectionPlan(request) {
|
|
297
|
+
calls.push({ operation: "capture-correction-plan", request });
|
|
298
|
+
return {
|
|
299
|
+
schema: "gentle-ai.review-last-event-closure/v1",
|
|
300
|
+
operation: "review.capture-correction-plan",
|
|
301
|
+
lineageId,
|
|
302
|
+
state: "correction_required",
|
|
303
|
+
targetIdentity: sha,
|
|
304
|
+
requestHash: sha,
|
|
305
|
+
correctionLines: 1,
|
|
306
|
+
storeRevision: sha,
|
|
307
|
+
};
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
const lastEventPi = createPi();
|
|
311
|
+
const { createGentleAiExtension } = await import(pathToFileURL(join(ROOT, "extensions/gentle-ai.ts")).href);
|
|
312
|
+
createGentleAiExtension({ nativeReviewCli })(lastEventPi.pi);
|
|
313
|
+
const controller = lastEventPi.tools.get("gentle_review");
|
|
314
|
+
const capture = lastEventPi.tools.get("gentle_review_capture");
|
|
315
|
+
assert.ok(controller, "runtime must register the public status controller");
|
|
316
|
+
assert.ok(capture, "runtime must register the one-slot capture tool");
|
|
317
|
+
assert.equal(lastEventPi.tools.get("gentle_review_capture_group")?.executionMode, "sequential", "runtime must register grouped capture with bounded foreground concurrency");
|
|
318
|
+
assert.equal(controller.parameters.properties.operation.enum.includes("finalize"), false);
|
|
319
|
+
assert.equal(controller.parameters.properties.operation.enum.includes("validate"), false);
|
|
320
|
+
|
|
321
|
+
const publicStatus = await controller.execute(
|
|
322
|
+
"runtime-status",
|
|
323
|
+
{ operation: "status", lineageId },
|
|
324
|
+
undefined,
|
|
325
|
+
undefined,
|
|
326
|
+
createCtx(ROOT, false, lineageId),
|
|
327
|
+
);
|
|
328
|
+
const collectBindings = publicStatus.details.collectBindings;
|
|
329
|
+
assert.equal(publicStatus.details.status, "blocked");
|
|
330
|
+
assert.equal(collectBindings.length, 1);
|
|
331
|
+
|
|
332
|
+
const captured = await capture.execute(
|
|
333
|
+
"runtime-capture",
|
|
334
|
+
{ lineageId, collectBinding: collectBindings[0].collectBinding, correctionLines: 1 },
|
|
335
|
+
undefined,
|
|
336
|
+
undefined,
|
|
337
|
+
createCtx(ROOT, false, lineageId),
|
|
338
|
+
);
|
|
339
|
+
assert.equal(captured.details.status, "closed");
|
|
340
|
+
assert.equal(captured.details.outcome, "native-last-event-closure");
|
|
341
|
+
assert.equal(captured.details.closure.operation, "review.capture-correction-plan");
|
|
342
|
+
assert.deepEqual(
|
|
343
|
+
calls.map(({ operation }) => operation),
|
|
344
|
+
["status", "status", "capture-correction-plan"],
|
|
345
|
+
"last-event closure must make no follow-up lifecycle mutation",
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
222
349
|
for (const name of EXPECTED_COMMANDS) {
|
|
223
350
|
assert.ok(commands.has(name), `missing command ${name}`);
|
|
224
351
|
}
|
|
@@ -281,10 +408,13 @@ async function run() {
|
|
|
281
408
|
assert.doesNotMatch(promptResult.systemPrompt, /default\s*\|\s*sonnet\s*\|\s*Non-SDD general delegation/);
|
|
282
409
|
assert.match(promptResult.systemPrompt, /openspec\/config\.yaml.*not session preflight/s);
|
|
283
410
|
assert.match(promptResult.systemPrompt, /Do not mark SDD preflight complete/);
|
|
284
|
-
assert.
|
|
285
|
-
promptResult.systemPrompt
|
|
286
|
-
|
|
411
|
+
assert.ok(
|
|
412
|
+
promptResult.systemPrompt.includes(
|
|
413
|
+
`Package assets root: \`${join(ROOT, "assets")}\`. Lazy asset paths below are relative to this root.`,
|
|
414
|
+
),
|
|
415
|
+
"parent prompt must declare the one absolute root for relative lazy asset paths",
|
|
287
416
|
);
|
|
417
|
+
assert.match(promptResult.systemPrompt, /`sdd-orchestrator-workflow\.md`/);
|
|
288
418
|
assert.doesNotMatch(
|
|
289
419
|
promptResult.systemPrompt,
|
|
290
420
|
new RegExp(ambientTestAssetsDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
|
|
@@ -358,10 +488,10 @@ async function run() {
|
|
|
358
488
|
assert.match(applyPromptResult.systemPrompt, /"changeName": "status-demo"/);
|
|
359
489
|
assert.match(applyPromptResult.systemPrompt, /### apply instructions/);
|
|
360
490
|
const statusCtx = createCtx(promptCwd, true);
|
|
361
|
-
await commands.get("sdd-status").handler("status-demo --json", statusCtx);
|
|
491
|
+
await commands.get("gentle-sdd-status").handler("status-demo --json", statusCtx);
|
|
362
492
|
assert.match(statusCtx.ui.notifications.at(-1).message, /"schemaName": "gentle-pi\.sdd-status"/);
|
|
363
493
|
const continueCtx = createCtx(promptCwd, true);
|
|
364
|
-
await commands.get("sdd-continue").handler("status-demo", continueCtx);
|
|
494
|
+
await commands.get("gentle-sdd-continue").handler("status-demo", continueCtx);
|
|
365
495
|
assert.match(continueCtx.ui.notifications.at(-1).message, /Native SDD Dispatcher/);
|
|
366
496
|
assert.match(continueCtx.ui.notifications.at(-1).message, /nextPhase: sdd-apply/);
|
|
367
497
|
const { execFileSync } = await import("node:child_process");
|
|
@@ -373,7 +503,7 @@ async function run() {
|
|
|
373
503
|
'{"schema":"gentle-ai.recovery-required/v1","change_name":"status-demo"}',
|
|
374
504
|
);
|
|
375
505
|
const blockedContinueCtx = createCtx(promptCwd, true);
|
|
376
|
-
await commands.get("sdd-continue").handler("status-demo", blockedContinueCtx);
|
|
506
|
+
await commands.get("gentle-sdd-continue").handler("status-demo", blockedContinueCtx);
|
|
377
507
|
assert.doesNotMatch(blockedContinueCtx.ui.notifications.at(-1).message, /resolve-review:/);
|
|
378
508
|
assert.match(blockedContinueCtx.ui.notifications.at(-1).message, /nextPhase: sdd-apply/);
|
|
379
509
|
} finally {
|
|
@@ -386,12 +516,11 @@ async function run() {
|
|
|
386
516
|
const ghPrCwd = await tempWorkspace();
|
|
387
517
|
try {
|
|
388
518
|
execFileSync("git", ["init"], { cwd: ghPrCwd, stdio: "ignore" });
|
|
389
|
-
const
|
|
519
|
+
const deliveryResult = await toolHook(
|
|
390
520
|
{ toolName: "bash", input: { command: "gh pr create --draft" } },
|
|
391
|
-
createCtx(ghPrCwd, true, "
|
|
521
|
+
createCtx(ghPrCwd, true, "ordinary-delivery-session"),
|
|
392
522
|
);
|
|
393
|
-
assert.equal(
|
|
394
|
-
assert.match(receiptGate.reason, /exactly derive.*--base/i);
|
|
523
|
+
assert.equal(deliveryResult, undefined, "ordinary delivery policy, not review authority, governs pull-request creation");
|
|
395
524
|
} finally {
|
|
396
525
|
await rm(ghPrCwd, { recursive: true, force: true });
|
|
397
526
|
}
|
|
@@ -404,6 +533,110 @@ async function run() {
|
|
|
404
533
|
assert.equal(missingReviewView.block, true);
|
|
405
534
|
assert.match(missingReviewView.reason, /candidate view/i);
|
|
406
535
|
assert.equal(reviewDispatch.task, "review", "blocked review dispatch must not mutate child input");
|
|
536
|
+
|
|
537
|
+
for (const [agent, label, task] of [
|
|
538
|
+
["gentle-ai-worker", "missing", "Implement the requested change."],
|
|
539
|
+
["gentle-ai-worker", "absolute", "## Allowed edit surfaces\n/tmp/outside.ts"],
|
|
540
|
+
["gentle-ai-worker", "Windows absolute", "## Allowed edit surfaces\nC:\\outside.ts"],
|
|
541
|
+
["gentle-ai-worker", "prose instead of paths", "## Allowed edit surfaces\nThe parent will determine the paths."],
|
|
542
|
+
["gentle-ai-worker", "repository root", "## Allowed edit surfaces\n."],
|
|
543
|
+
["gentle-ai-worker", "bare repository root", "## Allowed edit surfaces\n./"],
|
|
544
|
+
["gentle-ai-worker", "normalized bare repository root", "## Allowed edit surfaces\n.//"],
|
|
545
|
+
["gentle-ai-worker", "equivalent normalized bare repository root", "## Allowed edit surfaces\n././/"],
|
|
546
|
+
["worker", "generic writer missing", "Implement the requested change."],
|
|
547
|
+
]) {
|
|
548
|
+
const writerDispatch = { agent, task, mode: "task" };
|
|
549
|
+
const writerResult = await toolHook(
|
|
550
|
+
{ toolName: "subagent_run", input: writerDispatch },
|
|
551
|
+
createCtx(toolCwd),
|
|
552
|
+
);
|
|
553
|
+
assert.equal(writerResult?.block, true, `${label} writer scope must be blocked before dispatch`);
|
|
554
|
+
assert.match(writerResult?.reason ?? "", /derive|map/i);
|
|
555
|
+
assert.match(writerResult?.reason ?? "", /relaunch/i);
|
|
556
|
+
assert.match(writerResult?.reason ?? "", /do not ask.*human.*paths or globs/i);
|
|
557
|
+
assert.equal(writerDispatch.task, task, "writer guard must not mutate child input");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const scopedWriterDispatch = {
|
|
561
|
+
agent: "gentle-ai-worker",
|
|
562
|
+
task: "Implement the requested change.\n\n## Allowed edit surfaces\nextensions/gentle-ai.ts\ntests/runtime-harness.mjs",
|
|
563
|
+
mode: "task",
|
|
564
|
+
};
|
|
565
|
+
assert.equal(
|
|
566
|
+
await toolHook({ toolName: "subagent_run", input: scopedWriterDispatch }, createCtx(toolCwd)),
|
|
567
|
+
undefined,
|
|
568
|
+
"a writer may dispatch with narrow task-scoped repository-relative paths",
|
|
569
|
+
);
|
|
570
|
+
assert.equal(
|
|
571
|
+
await toolHook(
|
|
572
|
+
{
|
|
573
|
+
toolName: "subagent_run",
|
|
574
|
+
input: {
|
|
575
|
+
agent: "worker",
|
|
576
|
+
task: "Implement the requested change.",
|
|
577
|
+
context: "## Allowed edit surfaces\n- assets/orchestrator.md",
|
|
578
|
+
mode: "task",
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
createCtx(toolCwd),
|
|
582
|
+
),
|
|
583
|
+
undefined,
|
|
584
|
+
"a writer may dispatch when context carries narrow task-scoped repository-relative paths",
|
|
585
|
+
);
|
|
586
|
+
for (const [label, input] of [
|
|
587
|
+
[
|
|
588
|
+
"valid scope followed by a repository-root scope",
|
|
589
|
+
{
|
|
590
|
+
agent: "gentle-ai-worker",
|
|
591
|
+
task: "## Allowed edit surfaces\nextensions/gentle-ai.ts\n\n## Allowed edit surfaces\n.",
|
|
592
|
+
mode: "task",
|
|
593
|
+
},
|
|
594
|
+
],
|
|
595
|
+
[
|
|
596
|
+
"valid task scope plus invalid context scope",
|
|
597
|
+
{
|
|
598
|
+
agent: "gentle-ai-worker",
|
|
599
|
+
task: "## Allowed edit surfaces\nextensions/gentle-ai.ts",
|
|
600
|
+
context: "## Allowed edit surfaces\n.",
|
|
601
|
+
mode: "task",
|
|
602
|
+
},
|
|
603
|
+
],
|
|
604
|
+
[
|
|
605
|
+
"conflicting valid task and context scopes",
|
|
606
|
+
{
|
|
607
|
+
agent: "gentle-ai-worker",
|
|
608
|
+
task: "## Allowed edit surfaces\nextensions/gentle-ai.ts",
|
|
609
|
+
context: "## Allowed edit surfaces\ntests/runtime-harness.mjs",
|
|
610
|
+
mode: "task",
|
|
611
|
+
},
|
|
612
|
+
],
|
|
613
|
+
]) {
|
|
614
|
+
const writerResult = await toolHook({ toolName: "subagent_run", input }, createCtx(toolCwd));
|
|
615
|
+
assert.equal(writerResult?.block, true, `${label} must block writer dispatch`);
|
|
616
|
+
}
|
|
617
|
+
assert.equal(
|
|
618
|
+
await toolHook(
|
|
619
|
+
{
|
|
620
|
+
toolName: "subagent_run",
|
|
621
|
+
input: {
|
|
622
|
+
agent: "gentle-ai-worker",
|
|
623
|
+
task: "## Allowed edit surfaces\nextensions/gentle-ai.ts\ntests/runtime-harness.mjs\n\n## Allowed edit surfaces\n- `tests/runtime-harness.mjs`\n- `extensions/gentle-ai.ts`",
|
|
624
|
+
mode: "task",
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
createCtx(toolCwd),
|
|
628
|
+
),
|
|
629
|
+
undefined,
|
|
630
|
+
"a writer may dispatch when multiple allowed edit surface sections are compatible",
|
|
631
|
+
);
|
|
632
|
+
assert.equal(
|
|
633
|
+
await toolHook(
|
|
634
|
+
{ toolName: "subagent_run", input: { agent: "scout", task: "Map the repository.", mode: "task" } },
|
|
635
|
+
createCtx(toolCwd),
|
|
636
|
+
),
|
|
637
|
+
undefined,
|
|
638
|
+
"non-writer subagents must remain unaffected",
|
|
639
|
+
);
|
|
407
640
|
const sensitiveRead = await toolHook({ toolName: "read", input: { path: join(toolCwd, ".env.local") } }, createCtx(toolCwd));
|
|
408
641
|
assert.equal(sensitiveRead.block, true);
|
|
409
642
|
assert.match(sensitiveRead.reason, /sensitive path/);
|
|
@@ -419,20 +652,74 @@ async function run() {
|
|
|
419
652
|
);
|
|
420
653
|
assert.equal(needsConfirm.block, true);
|
|
421
654
|
assert.match(needsConfirm.reason, /not confirmed/);
|
|
655
|
+
assert.deepEqual(
|
|
656
|
+
emittedEvents.map(({ channel, data }) => ({
|
|
657
|
+
channel,
|
|
658
|
+
state: data.state,
|
|
659
|
+
active: data.active,
|
|
660
|
+
label: data.label,
|
|
661
|
+
})),
|
|
662
|
+
[
|
|
663
|
+
{
|
|
664
|
+
channel: "pi-permission-system:permission-request",
|
|
665
|
+
state: "waiting",
|
|
666
|
+
active: undefined,
|
|
667
|
+
label: undefined,
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
channel: "herdr:blocked",
|
|
671
|
+
state: undefined,
|
|
672
|
+
active: true,
|
|
673
|
+
label: "Guarded command confirmation",
|
|
674
|
+
},
|
|
675
|
+
{
|
|
676
|
+
channel: "pi-permission-system:permission-request",
|
|
677
|
+
state: "denied",
|
|
678
|
+
active: undefined,
|
|
679
|
+
label: undefined,
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
channel: "herdr:blocked",
|
|
683
|
+
state: undefined,
|
|
684
|
+
active: false,
|
|
685
|
+
label: undefined,
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
"guarded confirmation emits both lifecycle channels in order",
|
|
689
|
+
);
|
|
690
|
+
assert.equal(emittedEvents[0].data.requestId, emittedEvents[2].data.requestId);
|
|
691
|
+
emittedEvents.length = 0;
|
|
692
|
+
pi.events.emit("rpiv:ask-user:blocked", {
|
|
693
|
+
active: true,
|
|
694
|
+
question: "private runtime questionnaire",
|
|
695
|
+
answer: "private runtime answer",
|
|
696
|
+
path: "/private/runtime-path",
|
|
697
|
+
command: "private runtime command",
|
|
698
|
+
});
|
|
699
|
+
pi.events.emit("rpiv:ask-user:blocked", { active: true, duplicate: true });
|
|
700
|
+
pi.events.emit("rpiv:ask-user:blocked", { active: "true" });
|
|
701
|
+
pi.events.emit("rpiv:ask-user:other", { active: false });
|
|
702
|
+
pi.events.emit("rpiv:ask-user:blocked", { active: false });
|
|
703
|
+
const rpivHerdrEvents = emittedEvents.filter(({ channel }) => channel === "herdr:blocked");
|
|
704
|
+
assert.deepEqual(rpivHerdrEvents, [
|
|
705
|
+
{ channel: "herdr:blocked", data: { active: true, label: "Questionnaire awaiting input" } },
|
|
706
|
+
{ channel: "herdr:blocked", data: { active: false } },
|
|
707
|
+
]);
|
|
708
|
+
assert.doesNotMatch(JSON.stringify(rpivHerdrEvents), /private runtime/i);
|
|
709
|
+
emittedEvents.length = 0;
|
|
422
710
|
assert.equal(
|
|
423
711
|
dangerousReviewCtx.ui.notifications.length,
|
|
424
712
|
0,
|
|
425
|
-
"
|
|
713
|
+
"dangerous-command confirmation must not launch or announce review actors",
|
|
426
714
|
);
|
|
427
715
|
const commitCwd = await tempWorkspace();
|
|
428
716
|
try {
|
|
429
717
|
execFileSync("git", ["init"], { cwd: commitCwd, stdio: "ignore" });
|
|
430
|
-
const
|
|
718
|
+
const deliveryResult = await toolHook(
|
|
431
719
|
{ toolName: "bash", input: { command: "git commit -m bounded tracked.txt" } },
|
|
432
720
|
createCtx(commitCwd),
|
|
433
721
|
);
|
|
434
|
-
assert.equal(
|
|
435
|
-
assert.match(commitGate.reason, /exactly derive/i);
|
|
722
|
+
assert.equal(deliveryResult, undefined, "ordinary delivery policy, not review authority, governs commits");
|
|
436
723
|
} finally {
|
|
437
724
|
await rm(commitCwd, { recursive: true, force: true });
|
|
438
725
|
}
|
|
@@ -496,131 +783,13 @@ async function run() {
|
|
|
496
783
|
/Controller-owned review lineage/,
|
|
497
784
|
"a failed-closed dispatch must never inject a substituted candidate view into the lens sub-agent's task",
|
|
498
785
|
);
|
|
786
|
+
await chmod(view.root, 0o700);
|
|
499
787
|
registry.cleanup(view.token);
|
|
500
788
|
} finally {
|
|
789
|
+
restoreWorkspaceWritePermissions(candidateDriftCwd);
|
|
501
790
|
await rm(candidateDriftCwd, { recursive: true, force: true });
|
|
502
791
|
}
|
|
503
792
|
|
|
504
|
-
// Corrective v2 lifecycle settling test: the real controller must call the
|
|
505
|
-
// native evidence surface before it accepts a targeted-validation document.
|
|
506
|
-
// This is intentionally controller-level rather than a pure resolver test;
|
|
507
|
-
// the ordered call trace proves production wiring and the fail-closed gate.
|
|
508
|
-
const correctionCwd = await tempWorkspace();
|
|
509
|
-
try {
|
|
510
|
-
const { createGentleAiExtension } = await import(pathToFileURL(join(ROOT, "extensions/gentle-ai.ts")).href);
|
|
511
|
-
const { CandidateViewRegistry } = await import(pathToFileURL(join(ROOT, "lib/review-candidate-view.ts")).href);
|
|
512
|
-
gitSync(correctionCwd, "init", "-b", "main");
|
|
513
|
-
await writeFile(join(correctionCwd, "app.ts"), "export const value = 1;\n");
|
|
514
|
-
gitSync(correctionCwd, "add", "app.ts");
|
|
515
|
-
gitSync(correctionCwd, "-c", "user.name=Runtime Harness", "-c", "user.email=runtime-harness@example.invalid", "commit", "-m", "base");
|
|
516
|
-
await writeFile(join(correctionCwd, "app.ts"), "export const value = 2;\n");
|
|
517
|
-
const frozen = new CandidateViewRegistry().create({ contributorRoot: correctionCwd });
|
|
518
|
-
const sha = (digit) => `sha256:${digit.repeat(64)}`;
|
|
519
|
-
const repair = { schema: "gentle-ai.review-authority-repair-assessment/v1", status: "unsupported", counts: { lineages: 0, compactLineages: 0, legacyLineages: 0, events: 0, bytes: 0, eligibleCandidates: 0, unsupportedLineages: 0, conflicts: 0 }, supportedOperations: ["review/complete-fix", "review/validate-fix"], authorizationSchema: "gentle-ai.review-repair-authorization/v1" };
|
|
520
|
-
const projection = { schema: "gentle-ai.review-integration.projection/v1", kind: "current-changes", projection: "workspace", baseTree: frozen.baseTree, initialReviewTree: frozen.candidateTree, currentCandidateTree: frozen.candidateTree, pathsDigest: sha("a"), paths: frozen.paths, intendedUntracked: [], intendedUntrackedProof: sha("b"), initialSnapshotIdentity: sha("c"), currentSnapshotIdentity: sha("c") };
|
|
521
|
-
const status = { contract: "gentle-ai.review-integration/v2", applicability: "current_target", authority: { version: "compact-v2", lineageId: "runtime-correction", state: "correction_required", generation: 1, revision: sha("d") }, receipt: { status: "expected_missing" }, action: "finalize", replayability: "not_replayable", frozen: { tier: "medium", originalChangedLines: 1, correctionBudget: 1 }, targetIdentity: sha("e"), projection, repair, candidates: [], nextTransition: { kind: "collect", reasonCode: "verification_evidence_required", collect: { inputs: [{ name: "verification_evidence", schema: "gentle-ai.review-verification-evidence/v2", captureOperation: "review.capture-evidence", arguments: [{ name: "lineage", value: "runtime-correction" }] }] } }, raw: {} };
|
|
522
|
-
const validationRequest = { schema: "gentle-ai.review-targeted-validation-request/v1", requestHash: sha("f"), lineageId: "runtime-correction", expectedRevision: sha("d"), targetIdentity: sha("e"), fixFindingIds: [], projection: "workspace", correctionCandidateTree: frozen.candidateTree, correctionTargetIdentity: sha("e"), correctionPaths: frozen.paths, correctionPathsDigest: sha("a") };
|
|
523
|
-
const afterCapture = { ...status, authority: { ...status.authority, state: "validating" }, validationRequest, nextTransition: { kind: "collect", reasonCode: "targeted_validation_required", collect: { inputs: [{ name: "targeted_validation", schema: validationRequest.schema, captureOperation: "external.run_targeted_validation", arguments: [{ name: "lineage", value: "runtime-correction" }], validationRequest }] } } };
|
|
524
|
-
frozen.cleanup();
|
|
525
|
-
const calls = [];
|
|
526
|
-
let statuses = 0;
|
|
527
|
-
const nativeReviewCli = {
|
|
528
|
-
async targetStatus() { calls.push("status"); statuses += 1; return statuses === 1 ? status : afterCapture; },
|
|
529
|
-
async captureEvidence(request) { calls.push("capture-evidence"); return { schema: "gentle-ai.review-verification-evidence/v2", version: 2, lineageId: "runtime-correction", authorityRevision: sha("d"), targetIdentity: sha("e"), candidateTree: frozen.candidateTree, pathsDigest: sha("a"), paths: frozen.paths, ledgerIds: [], rawPayloadSha256: sha("1"), rawPayloadBytes: request.evidenceDocument.length, outcome: "passed", recordDigest: sha("2") }; },
|
|
530
|
-
async finalize() { calls.push("finalize"); return { lineageId: "runtime-correction", state: "approved", action: "approved", storeRevision: sha("3") }; },
|
|
531
|
-
async start() { throw new Error("not expected"); }, async validate() { throw new Error("not expected"); }, async bindSdd() { throw new Error("not expected"); }, async sddStatus() { return { ready: false }; }, async reviewStatus() { throw new Error("not expected"); },
|
|
532
|
-
};
|
|
533
|
-
const correctionPi = createPi();
|
|
534
|
-
createGentleAiExtension({ nativeReviewCli, candidateViews: new CandidateViewRegistry() })(correctionPi.pi);
|
|
535
|
-
const controller = correctionPi.tools.get("gentle_review");
|
|
536
|
-
const response = await controller.execute("runtime-correction", { operation: "finalize", lineageId: "runtime-correction", input: JSON.stringify({ final_evidence: "focused verification passed", final_verification_outcome: "passed", validation: { request_hash: "f".repeat(64), correction_ids: [], original_criteria: { passed: true, evidence: ["acceptance passes"] }, correction_regression: { passed: true, evidence: ["regression passes"] }, fix_caused_findings: [], follow_ups: [] } }) }, undefined, undefined, createCtx(correctionCwd));
|
|
537
|
-
assert.deepEqual(calls, ["status", "capture-evidence", "status", "finalize"]);
|
|
538
|
-
assert.equal(response.details.result.state, "approved");
|
|
539
|
-
} finally {
|
|
540
|
-
await rm(correctionCwd, { recursive: true, force: true });
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// Task 11.2 (migrate-review-integration-v2): an unimplemented
|
|
544
|
-
// next_transition.execute.operation (e.g. a future "dispose-result") must
|
|
545
|
-
// raise a typed, named unsupported-transition-operation refusal and stop —
|
|
546
|
-
// never fall through to a generic schema-incompatible error, and never
|
|
547
|
-
// synthesize an invocation for it. Drives the real negotiated STATUS decode
|
|
548
|
-
// path (NativeReviewCliV216.targetStatus) against a stubbed adapter, so no
|
|
549
|
-
// live binary needs to advertise the unimplemented operation to prove this.
|
|
550
|
-
{
|
|
551
|
-
const capabilitiesFixturePath = join(
|
|
552
|
-
ROOT, "contracts", "review-integration", "v2", "fixtures", "capabilities.fixture.json",
|
|
553
|
-
);
|
|
554
|
-
const capabilitiesFixture = JSON.parse(await readFile(capabilitiesFixturePath, "utf8"));
|
|
555
|
-
const executableDigest = "dcc846103b16d365eaeeb9d7f289c23fc4f2897f23def1cb3fe7f05557b64705";
|
|
556
|
-
const capabilitiesBody = {
|
|
557
|
-
...capabilitiesFixture,
|
|
558
|
-
package: { ...capabilitiesFixture.package, version: "2.4.0" },
|
|
559
|
-
};
|
|
560
|
-
const statusBody = {
|
|
561
|
-
schema: "gentle-ai.review-integration.status/v3",
|
|
562
|
-
contract: "gentle-ai.review-integration/v2",
|
|
563
|
-
operation: "review.status",
|
|
564
|
-
applicability: "unrelated",
|
|
565
|
-
receipt: { status: "not_applicable" },
|
|
566
|
-
action: "start",
|
|
567
|
-
replayability: "not_replayable",
|
|
568
|
-
target_identity: `sha256:${"a".repeat(64)}`,
|
|
569
|
-
repair: {
|
|
570
|
-
schema: "gentle-ai.review-authority-repair-assessment/v1",
|
|
571
|
-
status: "unsupported",
|
|
572
|
-
counts: { lineages: 0, compact_lineages: 0, legacy_lineages: 0, events: 0, bytes: 0, eligible_candidates: 0, unsupported_lineages: 0, conflicts: 0 },
|
|
573
|
-
supported_operations: ["review/complete-fix", "review/validate-fix"],
|
|
574
|
-
authorization_schema: "gentle-ai.review-repair-authorization/v1",
|
|
575
|
-
},
|
|
576
|
-
projection: {
|
|
577
|
-
schema: "gentle-ai.review-integration.projection/v1",
|
|
578
|
-
kind: "current-changes",
|
|
579
|
-
projection: "workspace",
|
|
580
|
-
base_tree: "b".repeat(40),
|
|
581
|
-
initial_review_tree: "b".repeat(40),
|
|
582
|
-
current_candidate_tree: "b".repeat(40),
|
|
583
|
-
paths_digest: `sha256:${"a".repeat(64)}`,
|
|
584
|
-
paths: [],
|
|
585
|
-
intended_untracked: [],
|
|
586
|
-
intended_untracked_proof: `sha256:${"a".repeat(64)}`,
|
|
587
|
-
initial_snapshot_identity: `sha256:${"a".repeat(64)}`,
|
|
588
|
-
current_snapshot_identity: `sha256:${"a".repeat(64)}`,
|
|
589
|
-
},
|
|
590
|
-
candidates: [],
|
|
591
|
-
next_transition: {
|
|
592
|
-
kind: "execute",
|
|
593
|
-
reason_code: "disposable_result_pending",
|
|
594
|
-
execute: {
|
|
595
|
-
operation: "review.dispose-result",
|
|
596
|
-
arguments: [],
|
|
597
|
-
preconditions: [],
|
|
598
|
-
binding: { target_identity: `sha256:${"a".repeat(64)}` },
|
|
599
|
-
},
|
|
600
|
-
},
|
|
601
|
-
};
|
|
602
|
-
const calls = [];
|
|
603
|
-
const adapter = async (request) => {
|
|
604
|
-
calls.push(request.arguments);
|
|
605
|
-
const body = calls.length === 1 ? capabilitiesBody : statusBody;
|
|
606
|
-
return { stdout: JSON.stringify(body), stderr: "", exitCode: 0, signal: null, timedOut: false, outputLimitExceeded: false };
|
|
607
|
-
};
|
|
608
|
-
const client = new NativeReviewCliV216(
|
|
609
|
-
adapter, "/package/.gentle-ai/gentle-ai", 30_000, 1024 * 1024,
|
|
610
|
-
async () => undefined, () => executableDigest,
|
|
611
|
-
);
|
|
612
|
-
let thrown;
|
|
613
|
-
try {
|
|
614
|
-
await client.targetStatus({ cwd: "/repo" });
|
|
615
|
-
} catch (error) {
|
|
616
|
-
thrown = error;
|
|
617
|
-
}
|
|
618
|
-
assert.ok(thrown instanceof NativeReviewCliError, "an unimplemented next_transition.execute.operation must raise a typed NativeReviewCliError");
|
|
619
|
-
assert.equal(thrown.code, NATIVE_REVIEW_ERROR_CODE.UNSUPPORTED_TRANSITION_OPERATION);
|
|
620
|
-
assert.match(thrown.message, /review\.dispose-result/, "the refusal must name the specific unimplemented operation");
|
|
621
|
-
assert.equal(calls.length, 2, "capabilities negotiation plus one STATUS call — never a third call synthesizing the unimplemented operation");
|
|
622
|
-
}
|
|
623
|
-
|
|
624
793
|
const bannerCwd = await tempWorkspace();
|
|
625
794
|
try {
|
|
626
795
|
const ctx = createCtx(bannerCwd, true);
|
|
@@ -925,17 +1094,13 @@ async function run() {
|
|
|
925
1094
|
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), true);
|
|
926
1095
|
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
927
1096
|
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
928
|
-
assert.equal(ctx.ui.selections.length,
|
|
929
|
-
assert.
|
|
930
|
-
assert.equal(ctx.ui.selections[1].label, "SDD artifact store");
|
|
931
|
-
assert.deepEqual(ctx.ui.selections[1].options, ["openspec"]);
|
|
932
|
-
assert.equal(ctx.ui.selections[2].label, "SDD PR chaining");
|
|
933
|
-
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: user prompt/);
|
|
1097
|
+
assert.equal(ctx.ui.selections.length, 0, "automatic SDD triggers must not render confirmation-only selectors");
|
|
1098
|
+
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: canonical default or persisted preference/);
|
|
934
1099
|
assert.deepEqual(
|
|
935
1100
|
await inputHook({ text: "please use sdd for this change", source: "interactive" }, ctx),
|
|
936
1101
|
{ action: "continue" },
|
|
937
1102
|
);
|
|
938
|
-
assert.equal(ctx.ui.selections.length,
|
|
1103
|
+
assert.equal(ctx.ui.selections.length, 0, "natural SDD triggers reuse preferences without prompts");
|
|
939
1104
|
assert.deepEqual(
|
|
940
1105
|
await inputHook({ text: "/sdd", source: "interactive" }, ctx),
|
|
941
1106
|
{ action: "continue" },
|
|
@@ -948,7 +1113,7 @@ async function run() {
|
|
|
948
1113
|
await inputHook({ text: "/sdd:plan", source: "interactive" }, ctx),
|
|
949
1114
|
{ action: "continue" },
|
|
950
1115
|
);
|
|
951
|
-
assert.equal(ctx.ui.selections.length,
|
|
1116
|
+
assert.equal(ctx.ui.selections.length, 0, "slash SDD triggers use automatic defaults without prompts");
|
|
952
1117
|
|
|
953
1118
|
assert.deepEqual(
|
|
954
1119
|
await inputHook({ text: "/sdd-plan this change", source: "interactive" }, ctx),
|
|
@@ -976,19 +1141,18 @@ async function run() {
|
|
|
976
1141
|
"global SDD model routing must be materialized in agent frontmatter, not project settings overrides",
|
|
977
1142
|
);
|
|
978
1143
|
}
|
|
979
|
-
assert.equal(ctx.ui.selections.length,
|
|
980
|
-
assert.
|
|
981
|
-
assert.match(ctx.ui.notifications.at(-1).message, /SDD preflight complete/);
|
|
1144
|
+
assert.equal(ctx.ui.selections.length, 0, "automatic SDD routing must not render confirmation-only selectors");
|
|
1145
|
+
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: canonical default or persisted preference/);
|
|
982
1146
|
await commands.get("gentle:status").handler("", ctx);
|
|
983
1147
|
assert.match(ctx.ui.notifications.at(-1).message, /Global SDD assets stale: 0 file\(s\)/);
|
|
984
1148
|
assert.doesNotMatch(ctx.ui.notifications.at(-1).message, /install-sdd --force/);
|
|
985
1149
|
|
|
986
1150
|
await inputHook({ text: "/sdd-plan another change", source: "interactive" }, ctx);
|
|
987
|
-
assert.equal(ctx.ui.selections.length,
|
|
1151
|
+
assert.equal(ctx.ui.selections.length, 0, "automatic preflight should remain prompt-free for the session");
|
|
988
1152
|
const promptHook = hooks.get("before_agent_start")[0];
|
|
989
1153
|
const promptResult = await promptHook({ systemPrompt: "base" }, ctx);
|
|
990
1154
|
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
991
|
-
assert.match(promptResult.systemPrompt, /Execution mode:
|
|
1155
|
+
assert.match(promptResult.systemPrompt, /Execution mode: auto/);
|
|
992
1156
|
const workerPromptResult = await promptHook(
|
|
993
1157
|
{ agentName: "worker", systemPrompt: "worker base" },
|
|
994
1158
|
ctx,
|
|
@@ -1003,7 +1167,7 @@ async function run() {
|
|
|
1003
1167
|
await rm(globalModelsPath, { force: true });
|
|
1004
1168
|
}
|
|
1005
1169
|
|
|
1006
|
-
for (const [index, text] of ["/sdd", "/sdd plan", "/sdd:plan", "/sdd-plan this change"].entries()) {
|
|
1170
|
+
for (const [index, text] of ["/sdd", "/sdd plan", "/sdd:plan", "/sdd-plan this change", "/gentle-sdd-continue", "/gentle-sdd-status fix-rose --json", "/gentle-sdd-init"].entries()) {
|
|
1007
1171
|
const slashSddCwd = await tempWorkspace();
|
|
1008
1172
|
try {
|
|
1009
1173
|
const ctx = createCtx(slashSddCwd, true, `slash-sdd-session-${index}`);
|
|
@@ -1013,7 +1177,7 @@ async function run() {
|
|
|
1013
1177
|
});
|
|
1014
1178
|
assert.equal(existsSync(join(slashSddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1015
1179
|
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1016
|
-
assert.equal(ctx.ui.selections.length,
|
|
1180
|
+
assert.equal(ctx.ui.selections.length, 0, `${text} should use automatic preflight without selectors`);
|
|
1017
1181
|
} finally {
|
|
1018
1182
|
await rm(slashSddCwd, { recursive: true, force: true });
|
|
1019
1183
|
}
|
|
@@ -1025,9 +1189,11 @@ async function run() {
|
|
|
1025
1189
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1026
1190
|
assert.equal(existsSync(join(commandSddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1027
1191
|
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1028
|
-
assert.equal(ctx.ui.selections.length,
|
|
1192
|
+
assert.equal(ctx.ui.selections.length, 2, "explicit preflight prompts intentional choice fields");
|
|
1193
|
+
assert.equal(ctx.ui.selections.some(({ label }) => label === "SDD artifact store"), false, "one-option artifact store must be elided");
|
|
1194
|
+
assert.match(ctx.ui.notifications.at(-1).message, /Preference source: explicit session choice/);
|
|
1029
1195
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1030
|
-
assert.equal(ctx.ui.selections.length,
|
|
1196
|
+
assert.equal(ctx.ui.selections.length, 4, "explicit preflight remains an intentional re-prompt");
|
|
1031
1197
|
} finally {
|
|
1032
1198
|
await rm(commandSddCwd, { recursive: true, force: true });
|
|
1033
1199
|
}
|
|
@@ -1046,7 +1212,7 @@ async function run() {
|
|
|
1046
1212
|
assert.equal(existsSync(join(sddAgentGuardCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1047
1213
|
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
1048
1214
|
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1049
|
-
assert.equal(ctx.ui.selections.length,
|
|
1215
|
+
assert.equal(ctx.ui.selections.length, 0, "automatic SDD-agent startup must not render selectors");
|
|
1050
1216
|
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
1051
1217
|
assert.doesNotMatch(
|
|
1052
1218
|
promptResult.systemPrompt,
|
|
@@ -1067,7 +1233,7 @@ async function run() {
|
|
|
1067
1233
|
},
|
|
1068
1234
|
ctx,
|
|
1069
1235
|
);
|
|
1070
|
-
assert.equal(ctx.ui.selections.length,
|
|
1236
|
+
assert.equal(ctx.ui.selections.length, 0, "SDD-agent startup should reuse automatic preferences");
|
|
1071
1237
|
assert.doesNotMatch(
|
|
1072
1238
|
reusedPromptResult.systemPrompt,
|
|
1073
1239
|
/el Gentleman Identity and Harness/,
|
|
@@ -1089,7 +1255,7 @@ async function run() {
|
|
|
1089
1255
|
ctx,
|
|
1090
1256
|
);
|
|
1091
1257
|
assert.match(promptResult.systemPrompt, /SDD Session Preflight/);
|
|
1092
|
-
assert.match(promptResult.systemPrompt, /
|
|
1258
|
+
assert.match(promptResult.systemPrompt, /canonical defaults or persisted choices/);
|
|
1093
1259
|
assert.equal(ctx.ui.selections.length, 0);
|
|
1094
1260
|
assert.equal(existsSync(join(noUiSddAgentCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1095
1261
|
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
@@ -1115,14 +1281,14 @@ async function run() {
|
|
|
1115
1281
|
pi.setActiveTools(["read", "bash", "edit", "write", "mem_save"]);
|
|
1116
1282
|
const ctx = createCtx(engramSddCwd, true, "engram-session");
|
|
1117
1283
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1118
|
-
assert.deepEqual(ctx.ui.selections[1].options, ["openspec", "engram", "
|
|
1284
|
+
assert.deepEqual(ctx.ui.selections[1].options, ["openspec", "engram", "hybrid"]);
|
|
1119
1285
|
} finally {
|
|
1120
1286
|
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1121
1287
|
await rm(engramSddCwd, { recursive: true, force: true });
|
|
1122
1288
|
}
|
|
1123
1289
|
|
|
1124
1290
|
// Issue #64: selecting engram as the artifact store must not cause
|
|
1125
|
-
// /sdd-init to create openspec/ or openspec/config.yaml.
|
|
1291
|
+
// /gentle-sdd-init to create openspec/ or openspec/config.yaml.
|
|
1126
1292
|
const engramSddInitCwd = await tempWorkspace();
|
|
1127
1293
|
try {
|
|
1128
1294
|
pi.setActiveTools(["read", "bash", "edit", "write", "mem_save"]);
|
|
@@ -1132,21 +1298,21 @@ async function run() {
|
|
|
1132
1298
|
return options[0];
|
|
1133
1299
|
};
|
|
1134
1300
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1135
|
-
await commands.get("sdd-init").handler("", ctx);
|
|
1301
|
+
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1136
1302
|
assert.equal(
|
|
1137
1303
|
existsSync(join(engramSddInitCwd, "openspec")),
|
|
1138
1304
|
false,
|
|
1139
|
-
"/sdd-init must not create openspec/ when artifactStore is engram",
|
|
1305
|
+
"/gentle-sdd-init must not create openspec/ when artifactStore is engram",
|
|
1140
1306
|
);
|
|
1141
1307
|
assert.equal(
|
|
1142
1308
|
existsSync(join(engramSddInitCwd, "openspec", "config.yaml")),
|
|
1143
1309
|
false,
|
|
1144
|
-
"/sdd-init must not write openspec/config.yaml when artifactStore is engram",
|
|
1310
|
+
"/gentle-sdd-init must not write openspec/config.yaml when artifactStore is engram",
|
|
1145
1311
|
);
|
|
1146
1312
|
assert.doesNotMatch(
|
|
1147
1313
|
ctx.ui.notifications.at(-1).message,
|
|
1148
1314
|
/Wrote openspec\/config\.yaml/,
|
|
1149
|
-
"/sdd-init must not announce openspec/config.yaml when artifactStore is engram",
|
|
1315
|
+
"/gentle-sdd-init must not announce openspec/config.yaml when artifactStore is engram",
|
|
1150
1316
|
);
|
|
1151
1317
|
assert.match(
|
|
1152
1318
|
ctx.ui.notifications.at(-1).message,
|
|
@@ -1166,30 +1332,30 @@ async function run() {
|
|
|
1166
1332
|
pi.setActiveTools(["read", "bash", "edit", "write", "mem_save"]);
|
|
1167
1333
|
const ctx = createCtx(bothSddInitCwd, true, "both-sdd-init-session");
|
|
1168
1334
|
ctx.ui.select = async (label, options) => {
|
|
1169
|
-
if (label === "SDD artifact store") return "
|
|
1335
|
+
if (label === "SDD artifact store") return "hybrid";
|
|
1170
1336
|
return options[0];
|
|
1171
1337
|
};
|
|
1172
1338
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1173
|
-
await commands.get("sdd-init").handler("", ctx);
|
|
1339
|
+
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1174
1340
|
assert.equal(
|
|
1175
1341
|
existsSync(join(bothSddInitCwd, "openspec", "specs")),
|
|
1176
1342
|
true,
|
|
1177
|
-
"/sdd-init must create openspec/specs when artifactStore is both",
|
|
1343
|
+
"/gentle-sdd-init must create openspec/specs when artifactStore is both",
|
|
1178
1344
|
);
|
|
1179
1345
|
assert.equal(
|
|
1180
1346
|
existsSync(join(bothSddInitCwd, "openspec", "changes", "archive")),
|
|
1181
1347
|
true,
|
|
1182
|
-
"/sdd-init must create openspec/changes/archive when artifactStore is both",
|
|
1348
|
+
"/gentle-sdd-init must create openspec/changes/archive when artifactStore is both",
|
|
1183
1349
|
);
|
|
1184
1350
|
assert.equal(
|
|
1185
1351
|
existsSync(join(bothSddInitCwd, "openspec", "config.yaml")),
|
|
1186
1352
|
true,
|
|
1187
|
-
"/sdd-init must write openspec/config.yaml when artifactStore is both",
|
|
1353
|
+
"/gentle-sdd-init must write openspec/config.yaml when artifactStore is both",
|
|
1188
1354
|
);
|
|
1189
1355
|
assert.match(
|
|
1190
1356
|
ctx.ui.notifications.at(-1).message,
|
|
1191
1357
|
/Wrote openspec\/config\.yaml/,
|
|
1192
|
-
"/sdd-init must announce openspec/config.yaml when artifactStore is both",
|
|
1358
|
+
"/gentle-sdd-init must announce openspec/config.yaml when artifactStore is both",
|
|
1193
1359
|
);
|
|
1194
1360
|
assert.equal(ctx.ui.notifications.at(-1).level, "info");
|
|
1195
1361
|
} finally {
|
|
@@ -1202,7 +1368,7 @@ async function run() {
|
|
|
1202
1368
|
pi.setActiveTools(["read", "bash", "edit", "write", "engram_mem_save"]);
|
|
1203
1369
|
const ctx = createCtx(directEngramToolCwd, true, "direct-engram-session");
|
|
1204
1370
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1205
|
-
assert.
|
|
1371
|
+
assert.equal(ctx.ui.selections.some(({ label }) => label === "SDD artifact store"), false, "unrecognized Engram capability must elide the artifact selector");
|
|
1206
1372
|
} finally {
|
|
1207
1373
|
pi.setActiveTools(["read", "bash", "edit", "write"]);
|
|
1208
1374
|
await rm(directEngramToolCwd, { recursive: true, force: true });
|
|
@@ -1252,7 +1418,7 @@ async function run() {
|
|
|
1252
1418
|
const sddCwd = await tempWorkspace();
|
|
1253
1419
|
try {
|
|
1254
1420
|
const ctx = createCtx(sddCwd, true);
|
|
1255
|
-
await commands.get("sdd-init").handler("", ctx);
|
|
1421
|
+
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1256
1422
|
assert.equal(existsSync(join(sddCwd, ".pi", "agents", "sdd-apply.md")), false);
|
|
1257
1423
|
assert.equal(existsSync(join(sddCwd, ".pi", "chains", "sdd-full.chain.md")), false);
|
|
1258
1424
|
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-apply.md")), true);
|
|
@@ -1260,12 +1426,12 @@ async function run() {
|
|
|
1260
1426
|
assert.equal(existsSync(join(globalAgentHome, "agents", "sdd-sync.md")), true);
|
|
1261
1427
|
assert.equal(existsSync(join(globalAgentHome, "gentle-ai", "support", "sdd-status-contract.md")), true);
|
|
1262
1428
|
assert.equal(existsSync(join(globalAgentHome, "chains", "sdd-full.chain.md")), true);
|
|
1263
|
-
assert.equal(ctx.ui.selections.length,
|
|
1429
|
+
assert.equal(ctx.ui.selections.length, 0, "sdd-init uses automatic preflight defaults");
|
|
1264
1430
|
assert.match(ctx.ui.notifications[0].message, /SDD preflight complete/);
|
|
1265
1431
|
assert.match(ctx.ui.notifications.at(-1).message, /Wrote openspec\/config\.yaml/);
|
|
1266
1432
|
|
|
1267
1433
|
await commands.get("gentle:sdd-preflight").handler("", ctx);
|
|
1268
|
-
assert.equal(ctx.ui.selections.length,
|
|
1434
|
+
assert.equal(ctx.ui.selections.length, 2, "explicit preflight prompts after automatic sdd-init");
|
|
1269
1435
|
} finally {
|
|
1270
1436
|
await rm(sddCwd, { recursive: true, force: true });
|
|
1271
1437
|
}
|
|
@@ -1279,7 +1445,7 @@ async function run() {
|
|
|
1279
1445
|
);
|
|
1280
1446
|
await writeFile(globalModelsPath, "{ invalid json");
|
|
1281
1447
|
const ctx = createCtx(invalidSddInitCwd, true, "invalid-sdd-init-session");
|
|
1282
|
-
await commands.get("sdd-init").handler("", ctx);
|
|
1448
|
+
await commands.get("gentle-sdd-init").handler("", ctx);
|
|
1283
1449
|
assert.equal(ctx.ui.notifications[0].level, "warning");
|
|
1284
1450
|
assert.match(ctx.ui.notifications[0].message, /Model routing skipped:/);
|
|
1285
1451
|
assert.match(ctx.ui.notifications[0].message, /models\.json/);
|