gentle-pi 2.1.2 → 2.2.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.
Files changed (171) hide show
  1. package/README.md +70 -12
  2. package/assets/agents/gentle-ai-worker.md +7 -3
  3. package/assets/agents/jd-fix-agent.md +1 -1
  4. package/assets/agents/jd-judge-a.md +3 -1
  5. package/assets/agents/jd-judge-b.md +3 -1
  6. package/assets/agents/review-readability.md +4 -1
  7. package/assets/agents/review-reliability.md +4 -1
  8. package/assets/agents/review-resilience.md +4 -1
  9. package/assets/agents/review-risk.md +4 -1
  10. package/assets/agents/sdd-apply.md +6 -1
  11. package/assets/agents/sdd-archive.md +6 -1
  12. package/assets/agents/sdd-design.md +6 -1
  13. package/assets/agents/sdd-explore.md +6 -2
  14. package/assets/agents/sdd-init.md +10 -2
  15. package/assets/agents/sdd-onboard.md +6 -1
  16. package/assets/agents/sdd-proposal.md +6 -1
  17. package/assets/agents/sdd-spec.md +6 -1
  18. package/assets/agents/sdd-status.md +6 -1
  19. package/assets/agents/sdd-sync.md +6 -1
  20. package/assets/agents/sdd-tasks.md +6 -1
  21. package/assets/agents/sdd-verify.md +6 -1
  22. package/assets/chains/4r-review.chain.md +2 -0
  23. package/assets/chains/sdd-full.chain.md +1 -1
  24. package/assets/chains/sdd-plan.chain.md +1 -1
  25. package/assets/chains/sdd-verify.chain.md +1 -1
  26. package/assets/orchestrator-delegation.md +246 -67
  27. package/assets/orchestrator.md +7 -14
  28. package/assets/sdd-orchestrator-workflow.md +154 -9
  29. package/assets/support/sdd-status-contract.md +19 -1
  30. package/contracts/review-integration/v1/fixtures/consent.fixture.json +3 -3
  31. package/contracts/review-integration/v1/fixtures/start-v2.fixture.json +19 -28
  32. package/contracts/review-integration/v1/fixtures/start.fixture.json +1 -10
  33. package/contracts/review-integration/v1/fixtures/status-v2.fixture.json +12 -21
  34. package/contracts/review-integration/v1/schemas/correction-plan-request.schema.json +49 -0
  35. package/contracts/review-integration/v1/schemas/operation.schema.json +76 -0
  36. package/contracts/review-integration/v1/schemas/repair.schema.json +39 -0
  37. package/contracts/review-integration/v1/schemas/status-v2.schema.json +4 -2
  38. package/contracts/review-integration/v1/schemas/status.schema.json +4 -2
  39. package/contracts/review-integration/v2/fixtures/consent.fixture.json +1 -1
  40. package/contracts/review-integration/v2/fixtures/start.fixture.json +1 -10
  41. package/contracts/review-integration/v2/fixtures/status.fixture.json +1 -10
  42. package/contracts/review-integration/v2/schemas/failure.schema.json +5 -1
  43. package/contracts/review-integration/v2/schemas/operation.schema.json +6 -1
  44. package/contracts/review-integration/v2/schemas/repair.schema.json +4 -2
  45. package/contracts/review-integration/v2/schemas/start.schema.json +5 -2
  46. package/contracts/review-integration/v2/schemas/status.schema.json +4 -2
  47. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +30 -0
  48. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/README.md +12 -0
  49. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/manifest.json +65 -0
  50. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/lens.schema.json +16 -0
  51. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/refuter.schema.json +1 -0
  52. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/schemas/targeted-validator.schema.json +1 -0
  53. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/lens.json +1 -0
  54. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/refuter.json +1 -0
  55. package/contracts/review-provider-contract-mirror/v1.1.0/bundle/vectors/targeted-validator.json +1 -0
  56. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-capabilities.baseline.json +15 -0
  57. package/contracts/review-provider-contract-mirror/v1.1.0/generated/provider-roles.baseline.json +42 -0
  58. package/docs/native-authority-architecture.md +5 -5
  59. package/docs/review-integration.md +22 -2
  60. package/extensions/gentle-ai.ts +1595 -201
  61. package/extensions/sdd-init.ts +19 -6
  62. package/extensions/skill-registry.ts +10 -2
  63. package/extensions/startup-banner.ts +10 -4
  64. package/lib/gentle-ai-binary.ts +173 -2
  65. package/lib/git-commit-transaction.ts +77 -17
  66. package/lib/native-review-cli.ts +528 -65
  67. package/lib/provider-contract-bundle.ts +704 -0
  68. package/lib/review-candidate-view.ts +527 -18
  69. package/lib/review-compact-contract.ts +59 -248
  70. package/lib/review-host-relay.ts +436 -0
  71. package/lib/review-integration-v2.ts +537 -36
  72. package/lib/review-relay-contract.ts +16 -0
  73. package/lib/sdd-preflight.ts +53 -1
  74. package/package.json +5 -2
  75. package/runtime/gentle-ai-binary.mjs +173 -2
  76. package/runtime/git-commit-transaction.mjs +75 -15
  77. package/runtime/native-review-cli.mjs +524 -61
  78. package/runtime/review-integration-v2.mjs +536 -35
  79. package/runtime/review-relay-contract.mjs +17 -0
  80. package/scripts/build-git-commit-transaction-runner.mjs +1 -0
  81. package/scripts/check-provider-contract.mjs +138 -0
  82. package/scripts/gentle-ai-installer.mjs +23 -13
  83. package/scripts/maintainer/provider-relay-matrix.mjs +219 -0
  84. package/scripts/mirror-provider-contract.mjs +143 -0
  85. package/scripts/test-packed-runner.mjs +16 -2
  86. package/scripts/verify-package-files.mjs +110 -33
  87. package/skills/_shared/review-ledger-contract.md +4 -6
  88. package/skills/gentle-ai/SKILL.md +4 -4
  89. package/skills/issue-creation/SKILL.md +94 -168
  90. package/skills/judgment-day/SKILL.md +7 -1
  91. package/skills/judgment-day/references/prompts-and-formats.md +2 -0
  92. package/skills/rdd-defect-workflow/SKILL.md +54 -0
  93. package/tests/background-subagents.test.ts +771 -0
  94. package/tests/crosslane/cross-lane.mjs +1169 -0
  95. package/tests/delegated-key-learnings-contract.test.ts +238 -0
  96. package/tests/fixtures/devbinary/capabilities-v2.1.derived.json +331 -0
  97. package/tests/fixtures/devbinary/capabilities-v2.2.captured.json +340 -0
  98. package/tests/fixtures/devbinary/consent-v3.captured.json +37 -0
  99. package/tests/fixtures/devbinary/failure-v2-capture-evidence.captured.json +16 -0
  100. package/tests/fixtures/devbinary/result-artifact-v2-path.captured.json +12 -0
  101. package/tests/fixtures/devbinary/result-artifact-v2.captured.json +12 -0
  102. package/tests/fixtures/devbinary/start-v3-consent-declined.captured.json +19 -0
  103. package/tests/fixtures/devbinary/start-v3-consent-granted.captured.json +109 -0
  104. package/tests/fixtures/devbinary/status-v5-capture-result-submission.captured.json +184 -0
  105. package/tests/fixtures/devbinary/status-v5-repository-context.captured.json +138 -0
  106. package/tests/fixtures/devbinary/status-v5.captured.json +88 -0
  107. package/tests/fixtures/provider-contract-bundle/v1.1.0/README.md +12 -0
  108. package/tests/fixtures/provider-contract-bundle/v1.1.0/manifest.json +65 -0
  109. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/lens.schema.json +16 -0
  110. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/refuter.schema.json +1 -0
  111. package/tests/fixtures/provider-contract-bundle/v1.1.0/schemas/targeted-validator.schema.json +1 -0
  112. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/lens.json +1 -0
  113. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/refuter.json +1 -0
  114. package/tests/fixtures/provider-contract-bundle/v1.1.0/vectors/targeted-validator.json +1 -0
  115. package/tests/gentle-ai-binary.test.ts +1 -1
  116. package/tests/gentle-ai-dev-binary-surfacing.test.ts +195 -0
  117. package/tests/gentle-ai-dev-binary.test.ts +336 -0
  118. package/tests/gentle-ai-installer.test.ts +46 -46
  119. package/tests/git-commit-transaction.test.ts +229 -1
  120. package/tests/maintainer/provider-relay.maintest.ts +265 -0
  121. package/tests/native-review-capability-contract.test.ts +48 -2
  122. package/tests/native-review-cli.test.ts +56 -0
  123. package/tests/native-review-consent.test.ts +164 -3
  124. package/tests/native-review-parity-runtime.test.ts +37 -0
  125. package/tests/native-review-parity.test.ts +218 -15
  126. package/tests/native-sdd-attempt-authority.test.ts +235 -0
  127. package/tests/orchestrator-budget.test.ts +30 -5
  128. package/tests/package-manifest.test.ts +98 -72
  129. package/tests/provider-contract-bundle.test.ts +385 -0
  130. package/tests/provider-contract-mirror.test.ts +206 -0
  131. package/tests/provider-defect-handoff.test.ts +355 -0
  132. package/tests/review-actor-tool-deny.test.ts +12 -13
  133. package/tests/review-candidate-view.test.ts +489 -9
  134. package/tests/review-compact-contract.test.ts +52 -119
  135. package/tests/review-controller-native-recovery.test.ts +643 -47
  136. package/tests/review-controller-native-routing.test.ts +1667 -222
  137. package/tests/review-controller-workspace-root.test.ts +17 -2
  138. package/tests/review-corrected-finalize-binding.test.ts +175 -0
  139. package/tests/review-dispatch-hydration-gap.test.ts +197 -0
  140. package/tests/review-host-relay-routing.test.ts +317 -0
  141. package/tests/review-host-relay.test.ts +520 -0
  142. package/tests/review-integration-v2-forward.test.ts +631 -0
  143. package/tests/review-integration-v2.test.ts +114 -0
  144. package/tests/review-ledger-contract.test.ts +12 -28
  145. package/tests/review-recovered-lineage-routing.test.ts +246 -0
  146. package/tests/review-relay-transport-agent.test.ts +249 -0
  147. package/tests/runtime-harness.mjs +242 -14
  148. package/tests/sdd-agent-tools.test.ts +18 -33
  149. package/tests/skill-collision-prefixes.test.ts +1 -0
  150. package/tests/skill-registry.test.ts +50 -1
  151. package/tests/verify-package-files.test.ts +62 -0
  152. package/assets/agents/review-refuter.md +0 -40
  153. package/assets/agents/review-validator.md +0 -23
  154. package/lib/native-review-remediation.ts +0 -49
  155. package/lib/review-compact.ts +0 -947
  156. package/lib/review-refuter-adapter.ts +0 -129
  157. package/lib/review-runtime-contract.ts +0 -68
  158. package/prompts/gcl.md +0 -54
  159. package/prompts/gis.md +0 -25
  160. package/prompts/gpr.md +0 -41
  161. package/prompts/gwr.md +0 -31
  162. package/tests/fixtures/native-review-cli/v2.1.2/bind-sdd.json +0 -25
  163. package/tests/fixtures/native-review-cli/v2.1.2/finalize.json +0 -8
  164. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status-engram.json +0 -139
  165. package/tests/fixtures/native-review-cli/v2.1.2/sdd-status.json +0 -200
  166. package/tests/fixtures/native-review-cli/v2.1.2/start.json +0 -12
  167. package/tests/fixtures/native-review-cli/v2.1.2/validate-allow.json +0 -24
  168. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny-empty-context.json +0 -20
  169. package/tests/fixtures/native-review-cli/v2.1.2/validate-deny.json +0 -28
  170. package/tests/review-compact.test.ts +0 -243
  171. package/tests/review-refuter-adapter.test.ts +0 -89
@@ -0,0 +1,238 @@
1
+ import assert from "node:assert/strict";
2
+ import { existsSync, mkdtempSync, mkdirSync, readdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
3
+ import { dirname, join, relative, sep } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import test from "node:test";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
9
+ const AGENTS = join(ROOT, "assets", "agents");
10
+ const ASSETS = join(ROOT, "assets");
11
+
12
+ // SDD phase executors installed to ~/.pi/agent/agents/ by installSddAssets.
13
+ // Each carries its own effective prompt; none inherits the parent workflow.
14
+ const SDD_PREFIX = "sdd-";
15
+
16
+ // Strict-output agents that must remain untouched by Key Learnings: the
17
+ // Judgment Day roles (`jd-*`) and the review lens roles (`review-*`). Derived
18
+ // from the agents actually on disk rather than hardcoded, so retiring one
19
+ // (as #320 did to `review-refuter.md` and `review-validator.md`) cannot break
20
+ // this guard with an ENOENT instead of a real assertion.
21
+ const STRICT_JSON_PREFIX = /^(jd-|review-)/;
22
+
23
+ // Canonical semantics every Key Learnings section must encode.
24
+ const KL_SEMANTICS: Array<[string, RegExp]> = [
25
+ ["heading `## Key Learnings`", /`## Key Learnings`/],
26
+ ["1–5 numbered items", /1[–-]5 numbered/],
27
+ ["standalone factual sentence", /standalone factual sentence/],
28
+ ["at least 20 characters", /at least 20 characters/],
29
+ ["at least 4 words", /at least 4 words/],
30
+ ["final report text only", /final (?:report|response) text only/],
31
+ ["Engram extracts and persists", /Engram[^\n]*automatically extracts[^\n]*persists/i],
32
+ ["executor does not parse", /do(?:es)? not parse/],
33
+ ["passive-capture tool wording", /passive.capture/i],
34
+ ["omit when no reusable learning", /[Oo]mit[^\n]*no reusable learning/],
35
+ ["separate from mem_save", /separate from[^\n]*mem_save/],
36
+ ];
37
+
38
+ function readSection(source: string, heading: string): string | null {
39
+ const lines = source.split(/\r?\n/);
40
+ const start = lines.findIndex((l) => /^#{1,6}\s+/.test(l) && l.replace(/^#{1,6}\s+/, "").trim() === heading);
41
+ if (start === -1) return null;
42
+ const level = lines[start].match(/^(#{1,6})/)![1].length;
43
+ let end = lines.length;
44
+ for (let i = start + 1; i < lines.length; i++) {
45
+ const m = lines[i].match(/^(#{1,6})\s+/);
46
+ if (m && m[1].length <= level) { end = i; break; }
47
+ }
48
+ return lines.slice(start, end).join("\n").trim();
49
+ }
50
+
51
+ function agentFiles(): string[] {
52
+ return readdirSync(AGENTS).filter((f) => f.endsWith(".md")).sort();
53
+ }
54
+
55
+ function sddAgents(): string[] {
56
+ return agentFiles().filter((f) => f.startsWith(SDD_PREFIX));
57
+ }
58
+
59
+ function strictJsonAgents(): string[] {
60
+ return agentFiles().filter((f) => STRICT_JSON_PREFIX.test(f));
61
+ }
62
+
63
+ test("every SDD phase executor carries an effective `## Key Learnings Closing` section with full semantics", () => {
64
+ const agents = sddAgents();
65
+ assert.ok(agents.length >= 12, `expected >=12 SDD agents, found ${agents.length}`);
66
+ const missing: string[] = [];
67
+ const failed: string[] = [];
68
+ for (const file of agents) {
69
+ const source = readFileSync(join(AGENTS, file), "utf8");
70
+ const section = readSection(source, "Key Learnings Closing");
71
+ if (section === null) { missing.push(file); continue; }
72
+ for (const [label, regex] of KL_SEMANTICS) {
73
+ if (!regex.test(section)) failed.push(`${file}: ${label}`);
74
+ }
75
+ }
76
+ assert.deepEqual(missing, [], "every SDD agent must carry a `## Key Learnings Closing` section");
77
+ assert.deepEqual(failed, [], "every section must encode all canonical semantics");
78
+ });
79
+
80
+ test("no SDD phase executor infers Key Learnings through `standard phase envelope` alone", () => {
81
+ for (const file of sddAgents()) {
82
+ const source = readFileSync(join(AGENTS, file), "utf8");
83
+ const section = readSection(source, "Key Learnings Closing");
84
+ assert.ok(section, `${file} must have a direct Key Learnings Closing section`);
85
+ }
86
+ });
87
+
88
+ test("SDD executor coverage is exhaustive against actual agent files", () => {
89
+ const actual = sddAgents();
90
+ // Allowlist: the 12 known phase executors. A new sdd-*.md without a
91
+ // Key Learnings Closing section fails the first test; this test proves
92
+ // the allowlist matches reality so coverage cannot silently drift.
93
+ const expected = [
94
+ "sdd-apply.md", "sdd-archive.md", "sdd-design.md", "sdd-explore.md",
95
+ "sdd-init.md", "sdd-onboard.md", "sdd-proposal.md", "sdd-spec.md",
96
+ "sdd-status.md", "sdd-sync.md", "sdd-tasks.md", "sdd-verify.md",
97
+ ];
98
+ assert.deepEqual(actual, expected, "SDD agent set must match the known allowlist");
99
+ });
100
+
101
+ test("generic delegation contract instructs the same `## Key Learnings` closing block", () => {
102
+ const delegation = readFileSync(join(ASSETS, "orchestrator-delegation.md"), "utf8");
103
+ const section = readSection(delegation, "Key Learnings closing block");
104
+ assert.ok(section, "orchestrator-delegation.md must have a Key Learnings closing block section");
105
+ for (const [label, regex] of KL_SEMANTICS) {
106
+ assert.match(section, regex, `delegation missing semantic: ${label}`);
107
+ }
108
+ assert.match(section, /native `Agent`/, "must cover native Agent fallback");
109
+ assert.match(section, /strict JSON/i, "must exclude strict-JSON agents");
110
+ assert.match(section, /layers on after/, "must state the block layers on after the envelope");
111
+ });
112
+
113
+ test("sdd-orchestrator-workflow documents routing, not executor authority", () => {
114
+ const workflow = readFileSync(join(ASSETS, "sdd-orchestrator-workflow.md"), "utf8");
115
+ const section = readSection(workflow, "Key Learnings closing block (routing)");
116
+ assert.ok(section, "workflow must document Key Learnings routing");
117
+ assert.match(section, /installed SDD phase executor agent.*carries the effective.*contract/i);
118
+ assert.match(section, /documents routing only and is not the executor authority/);
119
+ });
120
+
121
+ test("provider ownership: no Pi TypeScript runtime parses Key Learnings or invokes passive-capture tools", () => {
122
+ const roots = ["lib", "extensions", "scripts", "runtime"];
123
+ const forbidden: Array<[string, RegExp]> = [
124
+ ["Key Learnings parser", /Key Learnings/i],
125
+ ["key_learnings token", /key_learnings/i],
126
+ ["passive-capture tool invocation", /mem_capture_passive|capture_passive|passive_capture/i],
127
+ ];
128
+ const failures: string[] = [];
129
+ for (const root of roots) {
130
+ const absRoot = join(ROOT, root);
131
+ if (!existsSync(absRoot)) continue;
132
+ for (const file of listCodeFiles(absRoot)) {
133
+ const text = readFileSync(file, "utf8");
134
+ for (const [label, regex] of forbidden) {
135
+ if (regex.test(text)) failures.push(`${relative(ROOT, file)}: ${label}`);
136
+ }
137
+ }
138
+ }
139
+ assert.deepEqual(failures, [], "Pi must not parse Key Learnings or invoke passive-capture tools");
140
+ });
141
+
142
+ // Recursive walker over a provider root. Replaces the previous direct-children
143
+ // scan so nested `.ts`/`.mjs` runtime code cannot evade the ownership guard.
144
+ // Directories are recursed; only regular `.ts`/`.mjs` files are yielded;
145
+ // symlinks (file or directory) are never followed, preventing loops and
146
+ // external traversal. Output is sorted for deterministic failure messages.
147
+ // Fail-closed: traversal/read errors propagate so a forbidden file under an
148
+ // unreadable directory cannot silently evade the guard.
149
+ function listCodeFiles(rootDir: string): string[] {
150
+ const out: string[] = [];
151
+ const stack: string[] = [rootDir];
152
+ while (stack.length > 0) {
153
+ const dir = stack.pop()!;
154
+ for (const e of readdirSync(dir, { withFileTypes: true })) {
155
+ if (e.isSymbolicLink()) continue;
156
+ const full = join(dir, e.name);
157
+ if (e.isDirectory()) stack.push(full);
158
+ else if (e.isFile() && /\.(ts|mjs)$/.test(e.name)) out.push(full);
159
+ }
160
+ }
161
+ return out.sort();
162
+ }
163
+
164
+ test("listCodeFiles recurses nested code, ignores non-code files, and skips symlinks", () => {
165
+ const tmp = mkdtempSync(join(tmpdir(), "kl-walker-"));
166
+ try {
167
+ const nested = join(tmp, "a", "b");
168
+ mkdirSync(nested, { recursive: true });
169
+ writeFileSync(join(nested, "leak.ts"), "Key Learnings leak\n");
170
+ writeFileSync(join(tmp, "a", "leak.mjs"), "key_learnings\n");
171
+ writeFileSync(join(tmp, "a", "notes.md"), "Key Learnings in prose\n");
172
+ writeFileSync(join(tmp, "a", "data.json"), '{"k":"Key Learnings"}\n');
173
+ const files = listCodeFiles(tmp);
174
+ assert.ok(files.some((f) => f.endsWith("leak.ts")), "nested .ts must be discovered");
175
+ assert.ok(files.some((f) => f.endsWith("leak.mjs")), "shallow .mjs must be discovered");
176
+ assert.ok(!files.some((f) => f.endsWith(".md")), "non-code .md must be ignored");
177
+ assert.ok(!files.some((f) => f.endsWith(".json")), "non-code .json must be ignored");
178
+ // Symlink controls: skip when the platform rejects symlink creation.
179
+ const dirLink = join(tmp, "link-dir");
180
+ const fileLink = join(tmp, "link-file.ts");
181
+ let symlinksSupported = true;
182
+ try {
183
+ symlinkSync(join(tmp, "a"), dirLink, "dir");
184
+ symlinkSync(join(tmp, "a", "leak.mjs"), fileLink, "file");
185
+ } catch {
186
+ symlinksSupported = false;
187
+ }
188
+ if (symlinksSupported) {
189
+ const walked = listCodeFiles(tmp);
190
+ assert.ok(!walked.includes(fileLink), "symlinked .ts file must not be followed");
191
+ assert.ok(!walked.some((f) => f === dirLink || f.startsWith(dirLink + sep)), "symlinked directory must not be traversed");
192
+ }
193
+ } finally {
194
+ rmSync(tmp, { recursive: true, force: true });
195
+ }
196
+ });
197
+
198
+ test("strict review and Judgment Day agents do not gain Key Learnings or trailing-prose instruction", () => {
199
+ const strict = strictJsonAgents();
200
+ // A derived list can go empty and pass vacuously, which would assert
201
+ // nothing. Require both strict-output role families to still be covered
202
+ // without pinning an exact count that the next retirement would re-break.
203
+ assert.ok(strict.some((f) => f.startsWith("jd-")), "Judgment Day agents must be covered by this guard");
204
+ assert.ok(strict.some((f) => f.startsWith("review-")), "review lens agents must be covered by this guard");
205
+ const failures: string[] = [];
206
+ for (const file of strict) {
207
+ const source = readFileSync(join(AGENTS, file), "utf8");
208
+ for (const regex of [/Key Learnings/i, /key_learnings/i, /trailing prose/i]) {
209
+ if (regex.test(source)) failures.push(`${file}: ${regex.source}`);
210
+ }
211
+ }
212
+ assert.deepEqual(failures, [], "strict-JSON/ledger agents must remain untouched");
213
+ });
214
+
215
+ test("the canonical Key Learnings heading has no trailing colon in any asset", () => {
216
+ for (const file of sddAgents()) {
217
+ const source = readFileSync(join(AGENTS, file), "utf8");
218
+ assert.match(source, /`## Key Learnings`/, `${file} must reference the canonical heading`);
219
+ assert.doesNotMatch(source, /`## Key Learnings:`/, `${file} must not use a trailing colon`);
220
+ }
221
+ const delegation = readFileSync(join(ASSETS, "orchestrator-delegation.md"), "utf8");
222
+ assert.doesNotMatch(delegation, /`## Key Learnings:`/);
223
+ });
224
+
225
+ test("modified SDD agents are packaged and installed by the existing installer", () => {
226
+ const verifier = readFileSync(join(ROOT, "scripts", "verify-package-files.mjs"), "utf8");
227
+ const expected = [
228
+ "assets/agents/sdd-apply.md", "assets/agents/sdd-archive.md",
229
+ "assets/agents/sdd-design.md", "assets/agents/sdd-explore.md",
230
+ "assets/agents/sdd-init.md", "assets/agents/sdd-onboard.md",
231
+ "assets/agents/sdd-proposal.md", "assets/agents/sdd-spec.md",
232
+ "assets/agents/sdd-status.md", "assets/agents/sdd-sync.md",
233
+ "assets/agents/sdd-tasks.md", "assets/agents/sdd-verify.md",
234
+ ];
235
+ for (const path of expected) {
236
+ assert.match(verifier, new RegExp(path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")), `${path} must be in the package verifier`);
237
+ }
238
+ });
@@ -0,0 +1,331 @@
1
+ {
2
+ "schema": "gentle-ai.review-integration.capabilities/v2.1",
3
+ "contract": "gentle-ai.review-integration/v2",
4
+ "protocol": {
5
+ "major": 2,
6
+ "minor": 1
7
+ },
8
+ "package": {
9
+ "name": "gentle-ai",
10
+ "version": "2.4.0-rc.8+fix.verify-attestation-recovery",
11
+ "release_channel": "prerelease"
12
+ },
13
+ "build": {
14
+ "id": "sha256:4db874cd6a31fc1bc0deb2b94c22ad4668e878ffc5c0bc9acc2c81bcbe03e054",
15
+ "go_version": "go1.26.5",
16
+ "module_version": "(devel)",
17
+ "vcs": "git",
18
+ "vcs_revision": "",
19
+ "vcs_time": "",
20
+ "vcs_modified": "true"
21
+ },
22
+ "executable": {
23
+ "sha256": "sha256:ffc91d8fa79c869aba9aa3d1ec80edebb5b1744e5a06fef75d4c8b73c0e46bc1",
24
+ "evidence": "self-reported",
25
+ "verification": "compare-with-published-manifest"
26
+ },
27
+ "operations": [
28
+ "review.bind_sdd",
29
+ "review.capabilities",
30
+ "review.finalize",
31
+ "review.repair",
32
+ "review.retry_final_verification",
33
+ "review.start",
34
+ "review.status",
35
+ "review.validate"
36
+ ],
37
+ "gates": [
38
+ "post-apply",
39
+ "pre-commit",
40
+ "pre-push",
41
+ "pre-pr",
42
+ "release"
43
+ ],
44
+ "projections": [
45
+ "staged",
46
+ "workspace"
47
+ ],
48
+ "schemas": [
49
+ "gentle-ai.review-admitted-result/v2",
50
+ "gentle-ai.review-artifact-subject/v2",
51
+ "gentle-ai.review-authority-repair-assessment/v1",
52
+ "gentle-ai.review-authority-status/v1",
53
+ "gentle-ai.review-gate-request/v1",
54
+ "gentle-ai.review-integration.capabilities/v2.1",
55
+ "gentle-ai.review-integration.failure/v2",
56
+ "gentle-ai.review-final-verification-incident/v1",
57
+ "gentle-ai.review-integration.operation/v2",
58
+ "gentle-ai.review-integration.projection/v1",
59
+ "gentle-ai.review-integration.repair/v2",
60
+ "gentle-ai.review-integration.start/v3",
61
+ "gentle-ai.review-integration.status/v3",
62
+ "gentle-ai.review-receipt/v1",
63
+ "gentle-ai.review-receipt/v2",
64
+ "gentle-ai.review-result-artifact/v2",
65
+ "gentle-ai.review-targeted-validation-request/v1",
66
+ "gentle-ai.review-verification-evidence/v2",
67
+ "https://gentle-ai.dev/schema/review/refuter/v1",
68
+ "https://gentle-ai.dev/schema/review/reviewer/v1",
69
+ "https://gentle-ai.dev/schema/review/validator/v1",
70
+ "gentle-ai.review-integration.consent/v3"
71
+ ],
72
+ "features": {
73
+ "mandatory": [
74
+ {
75
+ "name": "compact_v2_authority",
76
+ "supported": true,
77
+ "requires": []
78
+ },
79
+ {
80
+ "name": "exact_receipt_replay",
81
+ "supported": true,
82
+ "requires": [
83
+ "compact_v2_authority"
84
+ ]
85
+ },
86
+ {
87
+ "name": "five_delivery_gates",
88
+ "supported": true,
89
+ "requires": [
90
+ "compact_v2_authority"
91
+ ]
92
+ },
93
+ {
94
+ "name": "immutable_snapshot",
95
+ "supported": true,
96
+ "requires": []
97
+ },
98
+ {
99
+ "name": "legacy_v1_target_scoped_read_only",
100
+ "supported": true,
101
+ "requires": [
102
+ "target_scoped_status"
103
+ ]
104
+ },
105
+ {
106
+ "name": "repository_independent_capabilities",
107
+ "supported": true,
108
+ "requires": []
109
+ },
110
+ {
111
+ "name": "restart_safe_projection",
112
+ "supported": true,
113
+ "requires": [
114
+ "target_scoped_status"
115
+ ]
116
+ },
117
+ {
118
+ "name": "sdd_receipt_binding",
119
+ "supported": true,
120
+ "requires": [
121
+ "compact_v2_authority"
122
+ ]
123
+ },
124
+ {
125
+ "name": "target_scoped_status",
126
+ "supported": true,
127
+ "requires": [
128
+ "repository_independent_capabilities"
129
+ ]
130
+ },
131
+ {
132
+ "name": "uniform_failure_envelope",
133
+ "supported": true,
134
+ "requires": [
135
+ "repository_independent_capabilities"
136
+ ]
137
+ }
138
+ ],
139
+ "optional": [
140
+ {
141
+ "name": "base_ref_workspace_overlay",
142
+ "supported": true,
143
+ "requires": [
144
+ "immutable_snapshot",
145
+ "restart_safe_projection"
146
+ ]
147
+ },
148
+ {
149
+ "name": "bounded_process_waits",
150
+ "supported": true,
151
+ "requires": [
152
+ "uniform_failure_envelope"
153
+ ]
154
+ },
155
+ {
156
+ "name": "classified_authority_repair",
157
+ "supported": true,
158
+ "requires": [
159
+ "native_next_transition",
160
+ "uniform_failure_envelope"
161
+ ]
162
+ },
163
+ {
164
+ "name": "exact_gate_receipt_discovery",
165
+ "supported": true,
166
+ "requires": [
167
+ "five_delivery_gates"
168
+ ]
169
+ },
170
+ {
171
+ "name": "native_frozen_candidate_context",
172
+ "supported": true,
173
+ "requires": [
174
+ "immutable_snapshot"
175
+ ]
176
+ },
177
+ {
178
+ "name": "native_low_risk_verification",
179
+ "supported": true,
180
+ "requires": [
181
+ "compact_v2_authority"
182
+ ]
183
+ },
184
+ {
185
+ "name": "native_next_transition",
186
+ "supported": true,
187
+ "requires": [
188
+ "target_scoped_status"
189
+ ]
190
+ },
191
+ {
192
+ "name": "one_shot_final_verification_retry",
193
+ "supported": true,
194
+ "requires": [
195
+ "compact_v2_authority",
196
+ "exact_receipt_replay",
197
+ "native_next_transition"
198
+ ]
199
+ },
200
+ {
201
+ "name": "opaque_repository_context",
202
+ "supported": true,
203
+ "requires": [
204
+ "compact_v2_authority",
205
+ "native_next_transition"
206
+ ]
207
+ },
208
+ {
209
+ "name": "outcome_bound_verification_evidence",
210
+ "supported": true,
211
+ "requires": [
212
+ "compact_v2_authority",
213
+ "native_next_transition"
214
+ ]
215
+ },
216
+ {
217
+ "name": "provider_artifact_admission",
218
+ "supported": true,
219
+ "requires": [
220
+ "compact_v2_authority",
221
+ "native_frozen_candidate_context",
222
+ "opaque_repository_context"
223
+ ]
224
+ },
225
+ {
226
+ "name": "provider_targeted_validation_request",
227
+ "supported": true,
228
+ "requires": [
229
+ "compact_v2_authority",
230
+ "native_next_transition"
231
+ ]
232
+ },
233
+ {
234
+ "name": "recovered_correction_evidence",
235
+ "supported": true,
236
+ "requires": [
237
+ "compact_v2_authority",
238
+ "provider_targeted_validation_request"
239
+ ]
240
+ },
241
+ {
242
+ "name": "risk_reasons",
243
+ "supported": true,
244
+ "requires": [
245
+ "repository_independent_capabilities"
246
+ ]
247
+ },
248
+ {
249
+ "name": "scope_change_diagnostics",
250
+ "supported": true,
251
+ "requires": [
252
+ "uniform_failure_envelope"
253
+ ]
254
+ },
255
+ {
256
+ "name": "validating_result_reopen",
257
+ "supported": true,
258
+ "requires": [
259
+ "compact_v2_authority",
260
+ "provider_artifact_admission"
261
+ ]
262
+ },
263
+ {
264
+ "name": "provider_bound_native_git_context",
265
+ "supported": true,
266
+ "requires": [
267
+ "native_frozen_candidate_context",
268
+ "opaque_repository_context",
269
+ "provider_artifact_admission"
270
+ ]
271
+ }
272
+ ]
273
+ },
274
+ "bootstrap": {
275
+ "command": "gentle-ai review status --cwd <repo> --contract gentle-ai.review-integration/v2 --next-transition",
276
+ "target_selector_variants": [
277
+ {
278
+ "target_type": "staged",
279
+ "arguments": [
280
+ "--projection",
281
+ "staged"
282
+ ]
283
+ },
284
+ {
285
+ "target_type": "base_ref",
286
+ "arguments": [
287
+ "--base-ref",
288
+ "<ref>"
289
+ ]
290
+ },
291
+ {
292
+ "target_type": "workspace_overlay_base_ref",
293
+ "arguments": [
294
+ "--workspace-overlay",
295
+ "--base-ref",
296
+ "<ref>"
297
+ ]
298
+ },
299
+ {
300
+ "target_type": "workspace_overlay_base_tree",
301
+ "arguments": [
302
+ "--workspace-overlay",
303
+ "--base-tree",
304
+ "<tree>"
305
+ ]
306
+ }
307
+ ],
308
+ "required_feature": "native_next_transition",
309
+ "unsupported_outcome": "unsupported-capability",
310
+ "parent_only": true
311
+ },
312
+ "compatibility": {
313
+ "minimum_protocol_major": 2,
314
+ "maximum_protocol_major": 2,
315
+ "additive_minor_policy": "optional-fields-only",
316
+ "unknown_mandatory": "reject",
317
+ "unknown_optional": "ignore",
318
+ "modes": [
319
+ "compact-v2",
320
+ "legacy-v1"
321
+ ],
322
+ "legacy_window": {
323
+ "mode": "legacy-v1",
324
+ "state": "active",
325
+ "read_only": true,
326
+ "deprecation_started": true,
327
+ "removal": "not-scheduled",
328
+ "minimum_compatibility_releases": 1
329
+ }
330
+ }
331
+ }