gentle-pi 2.6.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -7
- package/assets/agents/gentle-ai-worker.md +5 -1
- package/assets/agents/sdd-apply.md +9 -7
- package/assets/agents/sdd-archive.md +42 -23
- package/assets/agents/sdd-proposal.md +2 -2
- package/assets/agents/sdd-remediate.md +4 -4
- package/assets/agents/sdd-research.md +20 -48
- package/assets/agents/sdd-tasks.md +5 -5
- package/assets/agents/sdd-verify.md +6 -28
- package/assets/chains/sdd-full.chain.md +4 -22
- package/assets/chains/sdd-verify.chain.md +3 -12
- package/assets/orchestrator-delegation.md +33 -3
- package/assets/orchestrator-memory.md +20 -7
- package/assets/orchestrator.md +5 -3
- package/assets/sdd-orchestrator-workflow.md +25 -58
- package/assets/support/sdd-status-contract.md +9 -12
- package/docs/gentle-shell.md +41 -19
- package/docs/readme-reference.md +175 -36
- package/extensions/codegraph-tools.ts +2 -0
- package/extensions/gentle-agents.ts +289 -361
- package/extensions/gentle-ai.ts +588 -117
- package/extensions/gentle-shell.ts +123 -97
- package/extensions/pi-pretty.ts +63 -14
- package/extensions/quiet-tools.ts +1 -2
- package/extensions/startup-banner.ts +10 -9
- package/lib/agent-home.ts +8 -0
- package/lib/agent-profile-pin.ts +336 -0
- package/lib/agent-profiles.ts +28 -8
- package/lib/agents-config.ts +24 -2
- package/lib/agents-history.ts +3 -97
- package/lib/agents-keys.ts +27 -0
- package/lib/agents-protocol.ts +2 -15
- package/lib/agents-runner.ts +74 -113
- package/lib/agents-session-transport.ts +691 -0
- package/lib/command-palette-catalog.ts +87 -0
- package/lib/command-palette.ts +346 -0
- package/lib/native-choice-list.ts +5 -0
- package/lib/native-review-cli.ts +19 -97
- package/lib/review-publication-gate.ts +11 -1
- package/lib/review-repository.ts +1 -1
- package/lib/review-snapshot.ts +1 -0
- package/lib/review-transaction.ts +4 -2
- package/lib/sdd-preflight.ts +2 -1
- package/lib/sdd-research-capabilities.ts +18 -152
- package/lib/sdd-status.ts +7 -779
- package/lib/session-change-capture.ts +88 -0
- package/lib/session-changes.ts +147 -0
- package/lib/shell-bar.ts +24 -10
- package/lib/shell-card.ts +8 -12
- package/lib/shell-changes-view.ts +2 -1
- package/lib/shell-changes.ts +5 -2
- package/lib/shell-prompt.ts +25 -8
- package/lib/shell-sidebar-banner.ts +2 -2
- package/lib/shell-sidebar-layout.ts +5 -2
- package/lib/windows-session-transport.ts +877 -0
- package/package.json +3 -3
- package/runtime/native-review-cli.mjs +18 -96
- package/runtime/windows-session-transport.ps1 +791 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/test-packed-runner.mjs +1668 -20
- package/scripts/verify-package-files.mjs +2 -3
- package/tests/agent-home.test.ts +52 -0
- package/tests/agent-profiles.test.ts +30 -1
- package/tests/agents-config.test.ts +44 -0
- package/tests/agents-history.test.ts +12 -24
- package/tests/agents-runner.test.ts +321 -58
- package/tests/agents-session-transport-process.test.ts +249 -0
- package/tests/agents-session-transport.test.ts +823 -0
- package/tests/artifact-language.test.ts +10 -7
- package/tests/command-palette.test.ts +378 -0
- package/tests/delegated-key-learnings-contract.test.ts +2 -2
- package/tests/fixtures/agents-session-transport-process.mjs +108 -0
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +54 -0
- package/tests/fixtures/windows-session-bootstrap.ps1 +129 -0
- package/tests/fixtures/windows-session-compile.ps1 +110 -0
- package/tests/gentle-agents.test.ts +883 -356
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai.test.ts +472 -4
- package/tests/gentle-shell.test.ts +338 -205
- package/tests/native-choice-list.test.ts +13 -0
- package/tests/native-review-capability-contract.test.ts +15 -1
- package/tests/native-review-cli.test.ts +0 -33
- package/tests/odd-routing-contract.test.ts +208 -0
- package/tests/orchestrator-budget.test.ts +17 -2
- package/tests/package-manifest.test.ts +119 -31
- package/tests/persona-single-channel.test.ts +3 -3
- package/tests/pi-pretty.test.ts +45 -0
- package/tests/profile-pin.test.ts +370 -0
- package/tests/quiet-tool-rendering.test.ts +32 -5
- package/tests/review-contract-prompt.test.ts +9 -0
- package/tests/review-controller.test.ts +0 -44
- package/tests/review-session-standing-permission-ipc.test.ts +427 -13
- package/tests/runtime-harness.mjs +4 -4
- package/tests/sdd-agent-tools.test.ts +15 -36
- package/tests/sdd-archive-replay.test.ts +82 -0
- package/tests/sdd-classical-continuation.test.ts +74 -0
- package/tests/sdd-execution-routing-contract.test.ts +18 -2
- package/tests/sdd-managed-runtime-settlement.test.ts +42 -330
- package/tests/sdd-native-managed-uptake.test.ts +11 -21
- package/tests/sdd-no-attempts-contract.test.ts +15 -0
- package/tests/sdd-odd-integration.test.ts +33 -0
- package/tests/sdd-optional-research.test.ts +124 -0
- package/tests/sdd-planning-routing-contract.test.ts +1 -1
- package/tests/sdd-preflight-rpc-input.test.ts +125 -0
- package/tests/sdd-preflight.test.ts +1 -1
- package/tests/sdd-research-capabilities.test.ts +20 -162
- package/tests/sdd-selection-transport.test.ts +180 -88
- package/tests/sdd-status.test.ts +5 -778
- package/tests/sdd-task-truth.test.ts +43 -0
- package/tests/session-change-capture.test.ts +86 -0
- package/tests/session-changes-shell.test.ts +38 -0
- package/tests/session-changes.test.ts +114 -0
- package/tests/shell-bar.test.ts +35 -0
- package/tests/shell-card.test.ts +8 -6
- package/tests/shell-changes.test.ts +8 -0
- package/tests/shell-prompt.test.ts +41 -7
- package/tests/shell-sidebar-banner.test.ts +4 -4
- package/tests/shell-sidebar-layout.test.ts +97 -13
- package/tests/startup-banner.test.ts +55 -2
- package/tests/windows-hidden-processes.test.ts +303 -0
- package/tests/windows-session-bootstrap.test.ts +1772 -0
- package/tests/windows-session-compile.test.ts +170 -0
- package/tests/windows-session-transport.test.ts +754 -0
- package/assets/agents/sdd-sync.md +0 -146
- package/lib/openspec-guardrails.ts +0 -99
- package/tests/native-sdd-attempt-authority.test.ts +0 -240
- package/tests/openspec-guardrails.test.ts +0 -71
|
@@ -85,6 +85,19 @@ test("native choice list presents one hovered wrapped option without changing se
|
|
|
85
85
|
assert.doesNotMatch(list.render(56).join("\n"), new RegExp(hoverBackground.replace(/[\[\]]/g, "\\$&")));
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
+
test("native choice list refreshes existing item content without changing selection", () => {
|
|
89
|
+
const items = [
|
|
90
|
+
{ id: "first", label: "First", description: "Initial detail." },
|
|
91
|
+
{ id: "second", label: "Second", description: "Second detail." },
|
|
92
|
+
];
|
|
93
|
+
const list = new NativeChoiceList(items, theme);
|
|
94
|
+
list.setSelectedIndex(1);
|
|
95
|
+
items[1]!.description = "Updated detail.";
|
|
96
|
+
list.refreshItems();
|
|
97
|
+
assert.equal(list.getSelectedItem()?.id, "second");
|
|
98
|
+
assert.match(stripTerminalSequences(list.render(40).join("\n")), /Updated detail\./);
|
|
99
|
+
});
|
|
100
|
+
|
|
88
101
|
test("native choice list ignores Kitty key releases", () => {
|
|
89
102
|
const list = new NativeChoiceList(
|
|
90
103
|
[{ id: "first", label: "First" }, { id: "second", label: "Second" }],
|
|
@@ -232,11 +232,25 @@ test("2.9.0 repeats 2.8.2 because the negotiated lane Pi consumes is unchanged",
|
|
|
232
232
|
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.8.2"] as Record<string, boolean>);
|
|
233
233
|
});
|
|
234
234
|
|
|
235
|
+
test("2.9.1 repeats 2.9.0 because the negotiated lane Pi consumes is unchanged", () => {
|
|
236
|
+
// v2.9.1 shipped restoring compatible OpenCode review consent (#4584) and
|
|
237
|
+
// deriving Claude Code SDD dispatch authority from the session transcript
|
|
238
|
+
// (#4575, #4551). Diffing contracts/review-integration/v2 and
|
|
239
|
+
// contracts/review-provider-contract between the v2.9.0 and v2.9.1 tags
|
|
240
|
+
// in the gentle-ai source tree showed zero byte changes, so this row
|
|
241
|
+
// repeats 2.9.0. riskEvidence and hint remain dark because neither is
|
|
242
|
+
// proven to reach Pi's negotiated START path.
|
|
243
|
+
const contract = NATIVE_CLI_CONTRACTS["2.9.1"] as Record<string, boolean>;
|
|
244
|
+
assert.equal(contract.riskEvidence, false);
|
|
245
|
+
assert.equal(contract.hint, false);
|
|
246
|
+
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["2.9.0"] as Record<string, boolean>);
|
|
247
|
+
});
|
|
248
|
+
|
|
235
249
|
test("no shipped version key was added beyond the pin bump", () => {
|
|
236
250
|
// Rows are promises to consumers, so a new key only ever appears in a
|
|
237
251
|
// dedicated commit alongside a pin bump, never as a side effect. v2.2.4 and
|
|
238
252
|
// v2.3.0 shipped upstream while Pi stayed on 2.2.3 and were never pinned,
|
|
239
253
|
// so they get no row: a row asserts ground truth measured against a binary
|
|
240
254
|
// Pi actually ran, and the table only has to be ascending, not gapless.
|
|
241
|
-
assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0"]);
|
|
255
|
+
assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.1"]);
|
|
242
256
|
});
|
|
@@ -1023,36 +1023,3 @@ test("all twelve native actions retain their exact tokens without prose routing"
|
|
|
1023
1023
|
assert.equal(decodeNativeSddStatusV2(status, { workspaceRoot: "/repo" }).nextRecommended, nextRecommended);
|
|
1024
1024
|
}
|
|
1025
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
|
-
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { __testing } from "../extensions/gentle-ai.ts";
|
|
6
|
+
|
|
7
|
+
// These are instruction-delivery contracts, not proof of autonomous model adherence.
|
|
8
|
+
const read = (path: string) => readFileSync(join(import.meta.dirname, "..", path), "utf8");
|
|
9
|
+
const core = read("assets/orchestrator.md");
|
|
10
|
+
const delegation = read("assets/orchestrator-delegation.md");
|
|
11
|
+
const memory = read("assets/orchestrator-memory.md");
|
|
12
|
+
const wrapper = read("extensions/gentle-ai.ts");
|
|
13
|
+
|
|
14
|
+
function containsAll(text: string, clauses: readonly string[]): void {
|
|
15
|
+
for (const clause of clauses) assert.ok(text.includes(clause), `missing contract: ${clause}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test("organic entry stays read-only without authorization and loads detail before work", () => {
|
|
19
|
+
containsAll(core, [
|
|
20
|
+
"Substantial authorized work: use ODD",
|
|
21
|
+
"ODD (Default Workflow, harness section above) is mandatory on every request",
|
|
22
|
+
"orchestrator-delegation.md",
|
|
23
|
+
"orchestrator-memory.md",
|
|
24
|
+
]);
|
|
25
|
+
containsAll(delegation, [
|
|
26
|
+
"Investigation, explanation, review, comparison, and proposal-only requests remain read-only",
|
|
27
|
+
"without a task or storage permission prompt",
|
|
28
|
+
"Small, understood work creates no durable task artifacts",
|
|
29
|
+
]);
|
|
30
|
+
assert.doesNotMatch(core + wrapper, /Prefer SDD\/OpenSpec artifacts|Substantial feature: suggest SDD organically/);
|
|
31
|
+
assert.doesNotMatch(core + delegation, /Suggest it when proposal\/spec\/design\/tasks|propose SDD only when durable proposal\/spec\/design\/tasks/);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("research uses adaptive evidence gathering and existing general workers only", () => {
|
|
35
|
+
containsAll(delegation, [
|
|
36
|
+
"problem, intended outcome, constraints, and current evidence",
|
|
37
|
+
"no fixed questionnaire or mandatory rounds",
|
|
38
|
+
"one focused user question",
|
|
39
|
+
"stop and wait",
|
|
40
|
+
"workers return gaps to the parent",
|
|
41
|
+
"available authorized documentation/web tools",
|
|
42
|
+
"prefer primary sources",
|
|
43
|
+
"URLs or code locations",
|
|
44
|
+
"verified facts, assumptions, contradictions, freshness, and gaps",
|
|
45
|
+
"recommendation, tradeoffs, open questions, and implementation implications",
|
|
46
|
+
"Forward these research instructions",
|
|
47
|
+
"existing fresh general exploration/research worker",
|
|
48
|
+
"do not create a specialized agent or invoke `sdd-research`",
|
|
49
|
+
"no new persistence or readiness machinery",
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("task sizing is explicitly advisory and forwarded without cosmetic savings", () => {
|
|
54
|
+
containsAll(delegation, [
|
|
55
|
+
"about 400 authored changed lines",
|
|
56
|
+
"additions plus deletions",
|
|
57
|
+
"not a task acceptance criterion, hard cap, counter-trigger, automatic stop, forced split, or RDD trigger",
|
|
58
|
+
"Forward this same advisory-only instruction",
|
|
59
|
+
"Never delete spaces, blank lines, or comments",
|
|
60
|
+
"never omit tests, minify, add gratuitous abstractions, or split artificially",
|
|
61
|
+
"Existing PR size gates remain unchanged",
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("organic progress preserves both complete feature copies and reconciles actual evidence", () => {
|
|
66
|
+
containsAll(memory, [
|
|
67
|
+
"odd/tasks/<feature-name>.md",
|
|
68
|
+
"odd/<feature-name>/tasks",
|
|
69
|
+
"stable task IDs",
|
|
70
|
+
"full current document",
|
|
71
|
+
"repository-relative file locator",
|
|
72
|
+
"preserve valid completed and unrelated work",
|
|
73
|
+
"reopen invalidated items",
|
|
74
|
+
"Check off only observed outcomes",
|
|
75
|
+
"Read back both writes",
|
|
76
|
+
"not atomic",
|
|
77
|
+
"mirror pending",
|
|
78
|
+
"Preserve both versions",
|
|
79
|
+
"mem_context",
|
|
80
|
+
"mem_search",
|
|
81
|
+
"mem_get_observation",
|
|
82
|
+
"read the actual task file",
|
|
83
|
+
"not a third authority",
|
|
84
|
+
]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("assumption challenge and task checks do not activate or duplicate native review", () => {
|
|
88
|
+
containsAll(delegation, [
|
|
89
|
+
"at most one scoped independent read-only assumption challenge",
|
|
90
|
+
"high-consequence unproven premise",
|
|
91
|
+
"Deterministic failures need fixes",
|
|
92
|
+
"native RDD refuter",
|
|
93
|
+
"functional checks per task, not an RDD cycle per TODO",
|
|
94
|
+
"deliverable candidate boundary",
|
|
95
|
+
"native candidate risk assessment",
|
|
96
|
+
"gentle_review` with `{\"operation\":\"assess\"}",
|
|
97
|
+
"Passive/low",
|
|
98
|
+
"no reviewer or consent ceremony",
|
|
99
|
+
"only on grant",
|
|
100
|
+
"decline continues under ordinary policy",
|
|
101
|
+
"never infer low risk from a failed assessment",
|
|
102
|
+
"When RDD is disabled, do not start or prompt for RDD",
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("user documentation shows recovery and candidate-level consent without claiming model proof", () => {
|
|
107
|
+
const docs = read("docs/readme-reference.md");
|
|
108
|
+
containsAll(docs, [
|
|
109
|
+
"## Organic Driven Development",
|
|
110
|
+
"```mermaid",
|
|
111
|
+
"Full feature memory and actual task file",
|
|
112
|
+
"Native candidate risk",
|
|
113
|
+
"advisory",
|
|
114
|
+
"Static prompt tests",
|
|
115
|
+
"autonomous",
|
|
116
|
+
]);
|
|
117
|
+
assert.ok(read("README.md").includes("#organic-driven-development"));
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
test("one feature document carries intent, accepted rationale and worker context", () => {
|
|
122
|
+
containsAll(memory, [
|
|
123
|
+
"one feature document, not a separate plan file or topic",
|
|
124
|
+
"objective, problem, why, scope, constraints",
|
|
125
|
+
"progress, verification evidence, and next step",
|
|
126
|
+
"concise rationale for meaningful accepted changes",
|
|
127
|
+
"Routine corrections stay with their tasks; no exhaustive decision journal",
|
|
128
|
+
"Accepted user, review, or verification changes",
|
|
129
|
+
"automatically update affected intent and TODOs",
|
|
130
|
+
"add genuinely new tasks or reopen invalidated items with a reason",
|
|
131
|
+
"Findings alone never authorize scope expansion or automatic acceptance",
|
|
132
|
+
"Before implementation or resume, the parent reads both the actual file and full observation",
|
|
133
|
+
"passes the locator and relevant context; workers read the document before edits",
|
|
134
|
+
]);
|
|
135
|
+
containsAll(read("assets/agents/gentle-ai-worker.md"), [
|
|
136
|
+
"Read the parent's ODD feature document locator before edits",
|
|
137
|
+
"Preserve valid completed work; return proposed intent/task changes and their reasons",
|
|
138
|
+
]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("ODD forwards configured TDD without equating test presence with enablement", () => {
|
|
142
|
+
containsAll(delegation, [
|
|
143
|
+
"Resolve effective TDD on/off from existing project/session configuration or explicit user choice",
|
|
144
|
+
"retain its source and exact test runner",
|
|
145
|
+
"Record resolved mode, source, and runner in the feature document when present",
|
|
146
|
+
"Tests or frameworks being present does not enable TDD",
|
|
147
|
+
"Forward mode, source, and runner on every implementation delegation; refresh on resume",
|
|
148
|
+
"When enabled, require observed RED before implementation, GREEN, then REFACTOR",
|
|
149
|
+
"When disabled, run ordinary functional checks, not no checks",
|
|
150
|
+
"If mode is unknown/conflicting or the runner is missing",
|
|
151
|
+
"resolve only the ambiguity affecting the next action",
|
|
152
|
+
"never invent precedence or a command, and never invoke sdd-init to determine ODD TDD",
|
|
153
|
+
]);
|
|
154
|
+
containsAll(read("assets/agents/gentle-ai-worker.md"), [
|
|
155
|
+
"Consume the parent's effective TDD mode, configuration/choice source, and exact runner",
|
|
156
|
+
"Missing or conflicting mode/source/runner is not disabled TDD",
|
|
157
|
+
]);
|
|
158
|
+
assert.doesNotMatch(wrapper, /If tests exist, use strict TDD/);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("ODD protocol is always-on in the rendered system prompt and runs by default", () => {
|
|
162
|
+
const orderedClauses = [
|
|
163
|
+
"Default workflow: Organic Driven Development (MANDATORY)",
|
|
164
|
+
"predefined workflow of this orchestrator",
|
|
165
|
+
"SDD is a branch inside ODD",
|
|
166
|
+
"Never describe this workflow only when asked about it: run it.",
|
|
167
|
+
"1. **Authorize.**",
|
|
168
|
+
"2. **Explore.**",
|
|
169
|
+
"3. **Resolve uncertainty.**",
|
|
170
|
+
"4. **Classify.**",
|
|
171
|
+
"two or more meaningful implementation steps",
|
|
172
|
+
"5. **Track before the first write.**",
|
|
173
|
+
"Tell the user in one line which feature document was created and how many tasks it holds",
|
|
174
|
+
"6. **Implement task by task.**",
|
|
175
|
+
"7. **Close.**",
|
|
176
|
+
"Harness principles:",
|
|
177
|
+
"# el Gentleman Orchestrator",
|
|
178
|
+
];
|
|
179
|
+
for (const persona of ["gentleman", "neutral"] as const) {
|
|
180
|
+
const prompt = __testing.buildGentlePrompt(persona);
|
|
181
|
+
let cursor = -1;
|
|
182
|
+
for (const clause of orderedClauses) {
|
|
183
|
+
const index = prompt.indexOf(clause);
|
|
184
|
+
assert.ok(index !== -1, `[${persona}] missing contract: ${clause}`);
|
|
185
|
+
assert.ok(
|
|
186
|
+
index > cursor,
|
|
187
|
+
`[${persona}] clause out of order (must appear after the previous one): ${clause}`,
|
|
188
|
+
);
|
|
189
|
+
cursor = index;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
assert.ok(
|
|
194
|
+
wrapper.includes("Organic Driven Development (ODD) is the predefined workflow for every request"),
|
|
195
|
+
"missing contract: extensions/gentle-ai.ts harness principle",
|
|
196
|
+
);
|
|
197
|
+
assert.ok(
|
|
198
|
+
wrapper.includes(
|
|
199
|
+
"I run Organic Driven Development by default and SDD/OpenSpec when explicitly selected",
|
|
200
|
+
),
|
|
201
|
+
"missing contract: extensions/gentle-ai.ts identity sentence",
|
|
202
|
+
);
|
|
203
|
+
assert.ok(
|
|
204
|
+
core.includes("ODD (Default Workflow, harness section above) is mandatory on every request"),
|
|
205
|
+
"missing contract: assets/orchestrator.md pointer sentence",
|
|
206
|
+
);
|
|
207
|
+
containsAll(core, ["orchestrator-delegation.md", "orchestrator-memory.md"]);
|
|
208
|
+
});
|
|
@@ -281,8 +281,8 @@ function isNormativeLine(line: string): boolean {
|
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
const fixtureLines = readFileSync(FIXTURE_PATH, "utf8").split("\n");
|
|
284
|
-
// Fixture
|
|
285
|
-
// canonical-authority resolution. Keep
|
|
284
|
+
// Fixture line 36 is superseded by ODD (#1035); 187 and 191 predate
|
|
285
|
+
// root-relative lazy assets and canonical-authority resolution. Keep coverage by asserting the
|
|
286
286
|
// intentionally updated production wording instead of weakening the range.
|
|
287
287
|
const CURRENT_SDD_WORKFLOW_PATH = "`sdd-orchestrator-workflow.md`";
|
|
288
288
|
const CURRENT_HARD_PREFLIGHT_INVARIANT = "Hard preflight invariant: `openspec/config.yaml`, existing SDD changes, installed `.pi`/global SDD assets, or a todo named \"preflight\" are not session preflight. Do not mark SDD preflight complete, start `sdd-init`, launch SDD subagents/chains, or move to explore/proposal/spec/design/tasks until this session has an injected `## SDD Session Preflight` block or a canonical-authority resolution. Defaults and capability constraints may resolve fields without confirmation prompts; preserve unresolved-choice and safety gates.";
|
|
@@ -320,11 +320,26 @@ for (const range of DISPOSITION_MAP) {
|
|
|
320
320
|
if (raw === undefined || !isNormativeLine(raw)) continue;
|
|
321
321
|
const trimmed = raw.trim();
|
|
322
322
|
const expected =
|
|
323
|
+
ln === 36 ? "- Substantial authorized work: use ODD; track feature progress automatically." :
|
|
324
|
+
// #1051 keeps the selected store when memory is unavailable.
|
|
325
|
+
ln === 221 ? "do not switch the selected store" :
|
|
326
|
+
// Research returns findings; other phases keep direct backend ownership.
|
|
327
|
+
ln === 205 ? trimmed.replace("Each SDD phase", "Except for output-only `sdd-research`, each SDD phase") :
|
|
328
|
+
ln === 185 ? trimmed.replace("apply/verify/sync/archive", "apply/verify/archive") :
|
|
323
329
|
ln === 187
|
|
324
330
|
? CURRENT_SDD_WORKFLOW_PATH
|
|
325
331
|
: ln === 191
|
|
326
332
|
? CURRENT_HARD_PREFLIGHT_INVARIANT
|
|
327
333
|
: trimmed;
|
|
334
|
+
// #1051 retires the standalone sync row and its artifact key, not
|
|
335
|
+
// the surrounding memory/recovery contract or historical fixture.
|
|
336
|
+
if (ln === 216 || ln === 220) {
|
|
337
|
+
assert.ok(!targetContent.includes(trimmed), `retired sync contract remains at fixture:${ln}`);
|
|
338
|
+
assert.match(targetContent, /sdd\/<change>\/archive-report/);
|
|
339
|
+
assert.match(targetContent, /sdd\/<change>\/verify-report/);
|
|
340
|
+
assert.doesNotMatch(targetContent, /sdd\/<change>\/sync-report|\| `sdd-sync`/);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
328
343
|
if (SUPERSEDED_LIFECYCLE_REVIEW_LINES.has(ln)) {
|
|
329
344
|
assert.ok(
|
|
330
345
|
!targetContent.includes(trimmed),
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { homedir, tmpdir } from "node:os";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import test from "node:test";
|
|
15
|
+
import ts from "typescript";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
16
17
|
import { applyModelConfig } from "../extensions/gentle-ai.ts";
|
|
17
18
|
import { resolveGentlePiAgentHome } from "../lib/agent-home.ts";
|
|
@@ -182,7 +183,7 @@ test("npm publication is bound to the exact package tag and triggering commit",
|
|
|
182
183
|
packageJson.repository,
|
|
183
184
|
{
|
|
184
185
|
type: "git",
|
|
185
|
-
url: "git+https://github.com/Gentleman-Programming/gentle-
|
|
186
|
+
url: "git+https://github.com/Gentleman-Programming/gentle-shell.git",
|
|
186
187
|
},
|
|
187
188
|
"trusted publishing requires the exact case-sensitive npm repository identity",
|
|
188
189
|
);
|
|
@@ -218,11 +219,29 @@ test("generated runtime modules and packed-package checks are deterministic", ()
|
|
|
218
219
|
assert.match(ci, /pnpm run check:runtime-modules/);
|
|
219
220
|
assert.match(ci, /pnpm run test:packed-package/);
|
|
220
221
|
assert.match(generator, /Generated by scripts\/build-runtime-modules\.mjs/);
|
|
221
|
-
|
|
222
|
-
assert.
|
|
223
|
-
assert.
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
const ordinaryPostinstall = readNamedFunction(packedRunner, "testHookedPackedRunner");
|
|
223
|
+
assert.match(ordinaryPostinstall, /\["install"[^\]]*"--ignore-scripts=false"/s, "ordinary packed install must explicitly enable postinstall");
|
|
224
|
+
assert.doesNotMatch(ordinaryPostinstall, /\["install"[^\]]*"--ignore-scripts"(?!\=false)/s);
|
|
225
|
+
for (const name of [
|
|
226
|
+
"testSdkLifecyclePackedSession",
|
|
227
|
+
"testWindowsStartupTimingPackedHelper",
|
|
228
|
+
"testWindowsStartupTimingEnvironmentExperiment",
|
|
229
|
+
"testUnhookedPackedImports",
|
|
230
|
+
]) {
|
|
231
|
+
const isolatedInstall = readNamedFunction(packedRunner, name);
|
|
232
|
+
assert.match(isolatedInstall, /\["install"[^\]]*"--ignore-scripts"(?!\=false)/s, `${name} must isolate installation scripts`);
|
|
233
|
+
assert.doesNotMatch(isolatedInstall, /\["install"[^\]]*"--ignore-scripts=false"/s, `${name} must not enable postinstall`);
|
|
234
|
+
}
|
|
235
|
+
const windowsNpmInvocation = readNamedFunction(packedRunner, "windowsNpmInvocation");
|
|
236
|
+
const runNpmWithEnv = readNamedFunction(packedRunner, "runNpmWithEnv");
|
|
237
|
+
assert.match(windowsNpmInvocation, /execFileSync\("where\.exe", \["npm"\]/);
|
|
238
|
+
assert.match(windowsNpmInvocation, /return \{ file: process\.execPath, prefix: \[installedCli\] \}/);
|
|
239
|
+
assert.match(windowsNpmInvocation, /return \{ file: path, prefix: \[\] \}/);
|
|
240
|
+
assert.match(windowsNpmInvocation, /could not resolve npm-cli\.js without a command shell/);
|
|
241
|
+
assert.doesNotMatch(windowsNpmInvocation, /ComSpec|cmd\.exe/);
|
|
242
|
+
assert.match(runNpmWithEnv, /execFileSync\(invocation\.file, \[\.\.\.invocation\.prefix, \.\.\.arguments_\]/);
|
|
243
|
+
assert.match(runNpmWithEnv, /\.\.\.options, env/);
|
|
244
|
+
assert.doesNotMatch(packedRunner, /shell\s*:\s*true/);
|
|
226
245
|
assert.match(packedRunner, /review", "capabilities", "--contract", "gentle-ai\.review-integration\/v2"/);
|
|
227
246
|
assert.doesNotMatch(packedRunner, /git-commit-transaction|transaction runner/i);
|
|
228
247
|
});
|
|
@@ -247,8 +266,8 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
247
266
|
|
|
248
267
|
assert.equal(
|
|
249
268
|
packageJson.dependencies?.["@heyhuynhgiabuu/pi-pretty"],
|
|
250
|
-
"0.6.
|
|
251
|
-
"gentle-pi must install the tested pi-pretty version
|
|
269
|
+
"0.6.27",
|
|
270
|
+
"gentle-pi must install the tested pi-pretty version containing the model-visible result integrity fix",
|
|
252
271
|
);
|
|
253
272
|
assert.ok(
|
|
254
273
|
packageJson.pi?.extensions?.includes("./extensions"),
|
|
@@ -260,10 +279,22 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
260
279
|
),
|
|
261
280
|
"gentle-pi must not reference pnpm-unportable nested node_modules paths",
|
|
262
281
|
);
|
|
282
|
+
const piPrettyWrapperPath = join(PACKAGE_ROOT, "extensions", "pi-pretty.ts");
|
|
263
283
|
assert.ok(
|
|
264
|
-
existsSync(
|
|
284
|
+
existsSync(piPrettyWrapperPath),
|
|
265
285
|
"gentle-pi must expose pi-pretty through a packaged wrapper extension",
|
|
266
286
|
);
|
|
287
|
+
const piPrettyWrapper = readFileSync(piPrettyWrapperPath, "utf8");
|
|
288
|
+
assert.match(
|
|
289
|
+
piPrettyWrapper,
|
|
290
|
+
/import\("@heyhuynhgiabuu\/pi-pretty"\)/,
|
|
291
|
+
"the wrapper must use the compiled-runtime-safe ESM loader",
|
|
292
|
+
);
|
|
293
|
+
assert.doesNotMatch(
|
|
294
|
+
piPrettyWrapper,
|
|
295
|
+
/requireFromRealPackage\("@heyhuynhgiabuu\/pi-pretty"\)/,
|
|
296
|
+
"the wrapper must not resolve the pi-pretty package root through createRequire",
|
|
297
|
+
);
|
|
267
298
|
assert.ok(
|
|
268
299
|
existsSync(join(PACKAGE_ROOT, "extensions", "quiet-tools.ts")),
|
|
269
300
|
"gentle-pi must expose quiet built-in tool rendering through a packaged extension",
|
|
@@ -278,20 +309,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
278
309
|
);
|
|
279
310
|
});
|
|
280
311
|
|
|
281
|
-
test("package verification binds the published Gentle AI v2.9.
|
|
312
|
+
test("package verification binds the published Gentle AI v2.9.1 runtime pin", () => {
|
|
282
313
|
const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
|
|
283
314
|
const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
|
|
284
315
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
285
316
|
|
|
286
|
-
assert.match(installer, /INSTALLER_VERSION = "2\.9\.
|
|
317
|
+
assert.match(installer, /INSTALLER_VERSION = "2\.9\.1"/);
|
|
287
318
|
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
|
|
288
|
-
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
319
|
+
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:roCuxlF\+L4YOb1X0los4RpjcLfxAmwmHxteSQMcfThY="/);
|
|
289
320
|
assert.match(installer, /GOTOOLCHAIN: "local"/);
|
|
290
321
|
assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
|
|
291
322
|
assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
|
|
292
323
|
assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
|
|
293
324
|
assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
|
|
294
|
-
assert.match(verifier, /v2\.9\.
|
|
325
|
+
assert.match(verifier, /v2\.9\.1/);
|
|
295
326
|
});
|
|
296
327
|
|
|
297
328
|
|
|
@@ -355,6 +386,46 @@ function nestedContractFields(contract: string, parent: string): string[] {
|
|
|
355
386
|
return contractFields(nestedBlock.join("\n"), 2);
|
|
356
387
|
}
|
|
357
388
|
|
|
389
|
+
function readNamedFunction(source: string, name: string): string {
|
|
390
|
+
const fileName = "test-packed-runner.mjs";
|
|
391
|
+
const options: ts.CompilerOptions = {
|
|
392
|
+
allowJs: true,
|
|
393
|
+
noLib: true,
|
|
394
|
+
noResolve: true,
|
|
395
|
+
target: ts.ScriptTarget.Latest,
|
|
396
|
+
};
|
|
397
|
+
const host = ts.createCompilerHost(options, true);
|
|
398
|
+
host.getSourceFile = (requested, languageVersion) =>
|
|
399
|
+
requested === fileName
|
|
400
|
+
? ts.createSourceFile(requested, source, languageVersion, true, ts.ScriptKind.JS)
|
|
401
|
+
: undefined;
|
|
402
|
+
host.fileExists = (requested) => requested === fileName;
|
|
403
|
+
host.readFile = (requested) => requested === fileName ? source : undefined;
|
|
404
|
+
host.getCurrentDirectory = () => ".";
|
|
405
|
+
host.writeFile = () => {};
|
|
406
|
+
const program = ts.createProgram([fileName], options, host);
|
|
407
|
+
const sourceFile = program.getSourceFile(fileName);
|
|
408
|
+
assert.ok(sourceFile, "packed runner source must be available to the in-memory compiler host");
|
|
409
|
+
assert.equal(program.getSyntacticDiagnostics(sourceFile).length, 0, "packed runner source must have no syntactic diagnostics");
|
|
410
|
+
const declarations = sourceFile.statements.filter(
|
|
411
|
+
(statement): statement is ts.FunctionDeclaration =>
|
|
412
|
+
ts.isFunctionDeclaration(statement) && statement.name?.text === name,
|
|
413
|
+
);
|
|
414
|
+
assert.equal(declarations.length, 1, `packed runner must define exactly one top-level ${name}`);
|
|
415
|
+
const declaration = declarations[0];
|
|
416
|
+
assert.ok(declaration.body, `${name} must have a complete function body`);
|
|
417
|
+
assert.ok(declaration.body.end > declaration.body.pos, `${name} must have a non-empty function body`);
|
|
418
|
+
return source.slice(declaration.getStart(sourceFile), declaration.end);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
test("readNamedFunction fails closed for missing, duplicate, and incomplete declarations", () => {
|
|
422
|
+
assert.equal(readNamedFunction("function target() { return 1; }\nfunction neighbor() { return 2; }\n", "target"), "function target() { return 1; }");
|
|
423
|
+
assert.throws(() => readNamedFunction("const target = () => {};\n", "target"), /exactly one top-level target/);
|
|
424
|
+
assert.throws(() => readNamedFunction("function target() {}\nfunction target() {}\n", "target"), /exactly one top-level target/);
|
|
425
|
+
assert.throws(() => readNamedFunction("function target() {\n", "target"), /packed runner source must have no syntactic diagnostics/);
|
|
426
|
+
assert.throws(() => readNamedFunction("function target() {}\nfunction neighbor( {}\n", "target"), /packed runner source must have no syntactic diagnostics/);
|
|
427
|
+
});
|
|
428
|
+
|
|
358
429
|
function readMarkdownSection(source: string, heading: string): string {
|
|
359
430
|
const lines = source.split(/\r?\n/);
|
|
360
431
|
const matches = lines.flatMap((line, index) => {
|
|
@@ -548,7 +619,7 @@ const EXPECTED_OWNER_ASSETS: Record<PackageAssetOwner, readonly string[]> = {
|
|
|
548
619
|
"agents/sdd-apply.md", "agents/sdd-archive.md", "agents/sdd-design.md",
|
|
549
620
|
"agents/sdd-explore.md", "agents/sdd-init.md", "agents/sdd-onboard.md",
|
|
550
621
|
"agents/sdd-proposal.md", "agents/sdd-remediate.md", "agents/sdd-research.md", "agents/sdd-spec.md",
|
|
551
|
-
"agents/sdd-status.md", "agents/sdd-
|
|
622
|
+
"agents/sdd-status.md", "agents/sdd-tasks.md", "agents/sdd-verify.md",
|
|
552
623
|
"chains/sdd-full.chain.md", "chains/sdd-plan.chain.md", "chains/sdd-verify.chain.md",
|
|
553
624
|
"gentle-ai/support/sdd-status-contract.md", "gentle-ai/support/strict-tdd-verify.md",
|
|
554
625
|
"gentle-ai/support/strict-tdd.md",
|
|
@@ -591,10 +662,10 @@ test("legacy all-assets installation covers every packaged file with explicit ow
|
|
|
591
662
|
assert.equal(getPackageAssetOwner(key), undefined, "unknown assets must not default to SDD");
|
|
592
663
|
}
|
|
593
664
|
withIsolatedAssetHome((agentHome) => {
|
|
594
|
-
assert.deepEqual(installSddAssets(agentHome, false), { agents:
|
|
665
|
+
assert.deepEqual(installSddAssets(agentHome, false), { agents: 23, chains: 4, support: 3, skipped: 0 });
|
|
595
666
|
assert.deepEqual(Object.keys(installedAssetManifest(agentHome).assets).sort(), packaged);
|
|
596
|
-
assert.deepEqual(installSddAssets(agentHome, false), { agents: 0, chains: 0, support: 0, skipped:
|
|
597
|
-
assert.deepEqual(installSddAssets(agentHome, true), { agents:
|
|
667
|
+
assert.deepEqual(installSddAssets(agentHome, false), { agents: 0, chains: 0, support: 0, skipped: 30 });
|
|
668
|
+
assert.deepEqual(installSddAssets(agentHome, true), { agents: 23, chains: 4, support: 3, skipped: 0 });
|
|
598
669
|
});
|
|
599
670
|
});
|
|
600
671
|
|
|
@@ -651,14 +722,8 @@ test("selective review migration adopts only untouched legacy copies and preserv
|
|
|
651
722
|
});
|
|
652
723
|
|
|
653
724
|
test("unowned legacy research migrates by exact normalized hash, preserving routing and user edits", () => {
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
const legacy = packaged
|
|
657
|
-
.replace(/## Parent Preflight Transport\n[\s\S]*?(?=## Skill Resolution Contract)/, "")
|
|
658
|
-
.replace(/## Bounded artifact handoff\n[\s\S]*?(?=## Memory Contract)/, "")
|
|
659
|
-
.replace(/ - fetch_content\n - web_search\n - source_check\n - get_search_content\n/, "")
|
|
660
|
-
.replace(/- Use the injected `## SDD Research Capabilities`[\s\S]*?(?=- Admission denial)/, oldAdmission)
|
|
661
|
-
.replace(/Use `done` only when all selected questions[\s\S]*?product decisions remain separately confirmed by the parent\./i, "For this runtime the outcome is `blocked` with an admission denial and no claims.");
|
|
725
|
+
// Historical bytes must not be reconstructed from the evolving current agent.
|
|
726
|
+
const legacy = readFileSync(join(PACKAGE_ROOT, "tests/fixtures/legacy/sdd-research-v2.5.0.md"), "utf8");
|
|
662
727
|
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "assets", "migrations", "managed-assets-v2.5.0.json"), "utf8"));
|
|
663
728
|
assert.equal(sha256(legacy), manifest.assets["agents/sdd-research.md"], "fixture reconstruction must match observed old package bytes");
|
|
664
729
|
const temporary = mkdtempSync(join(tmpdir(), "gentle-research-migration-"));
|
|
@@ -1496,22 +1561,22 @@ test("orchestrator routes generic roles without static RDD lens routing", () =>
|
|
|
1496
1561
|
assert.match(core, /this package invents no lifecycle instructions/);
|
|
1497
1562
|
});
|
|
1498
1563
|
|
|
1499
|
-
test("pi-pretty wrapper uses
|
|
1564
|
+
test("pi-pretty wrapper uses cached ESM loading for compiled and pnpm symlink installs", () => {
|
|
1500
1565
|
const wrapper = readFileSync(
|
|
1501
1566
|
join(PACKAGE_ROOT, "extensions", "pi-pretty.ts"),
|
|
1502
1567
|
"utf8",
|
|
1503
1568
|
);
|
|
1504
1569
|
|
|
1505
|
-
assert.
|
|
1506
|
-
assert.match(wrapper, /
|
|
1507
|
-
assert.match(wrapper,
|
|
1570
|
+
assert.doesNotMatch(wrapper, /realpathSync|createRequire/);
|
|
1571
|
+
assert.match(wrapper, /piPrettyExtensionPromise/);
|
|
1572
|
+
assert.match(wrapper, /import\("@heyhuynhgiabuu\/pi-pretty"\)/);
|
|
1508
1573
|
assert.match(wrapper, /PI_PRETTY_SUPPRESSED_TOOL_NAMES/);
|
|
1509
1574
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1510
1575
|
});
|
|
1511
1576
|
|
|
1512
|
-
test("
|
|
1577
|
+
test("v3.0.0 release package and runtime stop before publication", () => {
|
|
1513
1578
|
const packageJson = readPackageJson();
|
|
1514
|
-
assert.equal(packageJson.version, "
|
|
1579
|
+
assert.equal(packageJson.version, "3.0.0", "the release manifest must remain explicitly pinned to v3.0.0");
|
|
1515
1580
|
assert.equal(
|
|
1516
1581
|
packageJson.scripts?.test,
|
|
1517
1582
|
"node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
|
|
@@ -1559,3 +1624,26 @@ test("technical reference documents dynamic Gentle AI RDD ownership and the inst
|
|
|
1559
1624
|
test("package verification explicitly requires the managed remediation actor", () => {
|
|
1560
1625
|
assert.match(readFileSync(join(PACKAGE_ROOT, "scripts/verify-package-files.mjs"), "utf8"), /assets\/agents\/sdd-remediate\.md/);
|
|
1561
1626
|
});
|
|
1627
|
+
|
|
1628
|
+
test("SDD installation retires owned sync but preserves modified copies and unrelated owners", () => {
|
|
1629
|
+
withIsolatedAssetHome((agentHome) => {
|
|
1630
|
+
installSddAssets(agentHome, false);
|
|
1631
|
+
const path = join(agentHome, "agents/sdd-sync.md");
|
|
1632
|
+
const manifestPath = join(agentHome, "gentle-ai/managed-assets.json");
|
|
1633
|
+
const legacy = "Previously managed sync executor\n";
|
|
1634
|
+
const manifest = installedAssetManifest(agentHome);
|
|
1635
|
+
manifest.assets["agents/sdd-sync.md"] = sha256(legacy);
|
|
1636
|
+
writeFileSync(path, legacy);
|
|
1637
|
+
writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
1638
|
+
installPackageAssets(agentHome, true, ["delegation"]);
|
|
1639
|
+
assert.equal(readFileSync(path, "utf8"), legacy);
|
|
1640
|
+
installPackageAssets(agentHome, true, ["sdd"]);
|
|
1641
|
+
assert.equal(existsSync(path), false);
|
|
1642
|
+
assert.equal(installedAssetManifest(agentHome).assets["agents/sdd-sync.md"], undefined);
|
|
1643
|
+
writeFileSync(path, "User-modified sync instructions\n");
|
|
1644
|
+
writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
1645
|
+
installPackageAssets(agentHome, true, ["sdd"]);
|
|
1646
|
+
assert.equal(readFileSync(path, "utf8"), "User-modified sync instructions\n");
|
|
1647
|
+
assert.equal(installedAssetManifest(agentHome).assets["agents/sdd-sync.md"], undefined);
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
@@ -262,7 +262,7 @@ test("Table A rule: wrapper :180/:181 + orchestrator :9,:12 self-description MER
|
|
|
262
262
|
);
|
|
263
263
|
assert.match(
|
|
264
264
|
prompt,
|
|
265
|
-
/I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona\. I
|
|
265
|
+
/I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona\. I run Organic Driven Development by default and SDD\/OpenSpec when explicitly selected, coordinate subagents, use phase artifacts, run commands, and edit files\. I am not a generic chatbot\./,
|
|
266
266
|
`[${persona}] the richer translated self-description paragraph (orchestrator :9,:12) must survive in the wrapper`,
|
|
267
267
|
);
|
|
268
268
|
}
|
|
@@ -295,7 +295,7 @@ test("Table A rule: SDD/OpenSpec artifacts + subagents core-capabilities bullet
|
|
|
295
295
|
const prompt = __testing.buildGentlePrompt(persona);
|
|
296
296
|
assert.match(
|
|
297
297
|
prompt,
|
|
298
|
-
/Mention SDD\/OpenSpec phase artifacts and subagents as core capabilities\./,
|
|
298
|
+
/Mention ODD as the default workflow, SDD\/OpenSpec phase artifacts, and subagents as core capabilities\./,
|
|
299
299
|
`[${persona}] wrapper :182 rule must survive`,
|
|
300
300
|
);
|
|
301
301
|
}
|
|
@@ -439,7 +439,7 @@ test("dup guard (exact-string): 'Do not claim portability outside the Pi runtime
|
|
|
439
439
|
|
|
440
440
|
test("dup guard (exact-string): identity self-description sentence occurs exactly once", () => {
|
|
441
441
|
const selfDescription =
|
|
442
|
-
"I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona. I
|
|
442
|
+
"I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona. I run Organic Driven Development by default and SDD/OpenSpec when explicitly selected, coordinate subagents, use phase artifacts, run commands, and edit files. I am not a generic chatbot.";
|
|
443
443
|
for (const persona of ["gentleman", "neutral"] as const) {
|
|
444
444
|
const prompt = __testing.buildGentlePrompt(persona);
|
|
445
445
|
assert.equal(
|