@christang/keel 5.1.1 → 5.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 (33) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +114 -158
  3. package/README.zh-CN.md +118 -197
  4. package/assets/bootstrap/AGENTS.md +1 -1
  5. package/{plugins/keel/skills/keel-align-expectations/references → assets/lenses}/hardware-dsl.md +4 -2
  6. package/{plugins/keel/skills/keel-align-expectations/references → assets/lenses}/hardware.md +4 -2
  7. package/{plugins/keel/skills/keel-align-expectations/references → assets/lenses}/web.md +4 -2
  8. package/assets/openspec/schemas/keel-spec-driven/schema.yaml +172 -166
  9. package/assets/openspec/schemas/keel-spec-driven/templates/tasks.md +13 -4
  10. package/bin/keel.js +218 -15
  11. package/package.json +1 -1
  12. package/plugins/keel/.claude-plugin/plugin.json +1 -1
  13. package/plugins/keel/.codex-plugin/plugin.json +1 -1
  14. package/plugins/keel/hooks/hooks.json +30 -30
  15. package/plugins/keel/scripts/pretooluse-guard.js +156 -156
  16. package/plugins/keel/scripts/session-start.js +182 -182
  17. package/plugins/keel/skills/keel-align-expectations/SKILL.md +2 -6
  18. package/plugins/keel/skills/keel-debug-failure/SKILL.md +2 -2
  19. package/plugins/keel/skills/keel-review-checklist/SKILL.md +2 -2
  20. package/plugins/keel/skills/keel-tdd-or-test-first/SKILL.md +2 -2
  21. package/scripts/bump_version.js +140 -0
  22. package/scripts/install_to_repo.py +0 -70
  23. package/scripts/run_python.js +63 -63
  24. package/scripts/validate_plugin.py +408 -96
  25. package/src/core/capabilities.js +291 -291
  26. package/src/core/context.js +521 -514
  27. package/src/core/gates.js +664 -643
  28. package/src/core/goal.js +230 -230
  29. package/src/core/guard.js +295 -295
  30. package/src/core/helper.js +319 -319
  31. package/src/core/projection.js +195 -195
  32. package/src/core/task-contract.js +757 -736
  33. package/src/core/tasksview.js +123 -123
@@ -1,319 +1,319 @@
1
- "use strict";
2
-
3
- // Keel 4.1.0 bounded read-only helper contract.
4
- //
5
- // A helper is never a second writer. `keel project helper` compiles one
6
- // bounded read-only question or one exact repository-byte-stable verification
7
- // command into a `keel-helper-brief/v1` evidence contract, and verifies a
8
- // helper's return only after proving the repository bytes are unchanged. The
9
- // current agent stays the sole writer, owner of Acceptance, Review, gates, and
10
- // completion; helper absence never disables current-agent goal execution.
11
-
12
- const fs = require("fs");
13
- const os = require("os");
14
- const path = require("path");
15
- const crypto = require("crypto");
16
-
17
- const BRIEF_VERSION = "keel-helper-brief/v1";
18
- const BASELINE_VERSION = "keel-helper-baseline/v1";
19
- const VERIFY_VERSION = "keel-helper-verification/v1";
20
- const SUPPORTED_HELPER_TARGETS = new Set(["codex", "claude"]);
21
-
22
- // Any mutation, delegation, or ownership request disqualifies a read-only brief.
23
- const FORBIDDEN_INTENT = new RegExp(
24
- "\\b(implement|write|edit|modify|create|refactor|delete|remove|fix|apply|"
25
- + "generate|install|commit|push|sync|archive|mark|check off|rewrite|"
26
- + "update the task|change the acceptance|use the fallback|use a fallback)\\b",
27
- "i"
28
- );
29
- const DELEGATION_INTENT = new RegExp(
30
- "\\b(subagent|sub-agent|delegate|delegation|spawn|agent team|another agent|"
31
- + "helper)\\b",
32
- "i"
33
- );
34
- // Shell tokens that would create or move repository artifacts.
35
- const ARTIFACT_TOKENS = [
36
- ">", ">>", "|&", "tee ", "git commit", "git add", "git push", "git checkout",
37
- "git reset", "git restore", "git rm", "git mv", "npm install", "npm i ",
38
- "npm ci", "mkdir", "touch ", "rm ", "mv ", "cp ", "rmdir",
39
- ];
40
- const REPORT_SCHEMA = [
41
- "question-or-command",
42
- "reads-performed",
43
- "observed-evidence",
44
- "byte-stability: verified|rejected|unverifiable",
45
- "no-writes-no-delegation-no-completion-authority",
46
- ];
47
-
48
- function blockedBrief(target, reason, extra = {}) {
49
- return {
50
- version: BRIEF_VERSION,
51
- status: "blocked",
52
- target,
53
- brief: null,
54
- reasons: [reason],
55
- ...extra,
56
- };
57
- }
58
-
59
- function isExternal(repo, candidate) {
60
- const rel = path.relative(repo, path.resolve(candidate));
61
- return (
62
- rel === ".."
63
- || rel.startsWith(`..${path.sep}`)
64
- || path.isAbsolute(rel)
65
- );
66
- }
67
-
68
- function compileHelperBrief(repo, options) {
69
- const target = options.target;
70
- if (!SUPPORTED_HELPER_TARGETS.has(target)) {
71
- return blockedBrief(
72
- target,
73
- `Bounded read-only helpers support codex and claude only; `
74
- + `${target || "missing"} remains manual/compatibility-only.`
75
- );
76
- }
77
-
78
- const question = (options.helperQuestion || "").trim();
79
- const command = (options.helperCommand || "").trim();
80
- if ((question && command) || (!question && !command)) {
81
- return blockedBrief(
82
- target,
83
- "A helper brief needs exactly one bounded read-only question or one exact "
84
- + "repository-byte-stable verification command, not both and not neither."
85
- );
86
- }
87
-
88
- const external = options.helperExternal || [];
89
- for (const declared of external) {
90
- if (!isExternal(repo, declared)) {
91
- return blockedBrief(
92
- target,
93
- `Declared helper output path is inside the repository: ${declared}; `
94
- + "helper temporaries must live outside the worktree."
95
- );
96
- }
97
- }
98
-
99
- if (question) {
100
- if (/\?[\s\S]*\?/.test(question)) {
101
- return blockedBrief(
102
- target,
103
- "A helper brief carries one bounded question; multiple unrelated "
104
- + "questions must be split into separate read-only briefs."
105
- );
106
- }
107
- if (DELEGATION_INTENT.test(question)) {
108
- return blockedBrief(
109
- target,
110
- "A helper cannot delegate or spawn nested helpers; the brief must be a "
111
- + "single read-only question the helper answers itself."
112
- );
113
- }
114
- if (FORBIDDEN_INTENT.test(question)) {
115
- return blockedBrief(
116
- target,
117
- "A helper is read-only and owns no completion authority; it cannot be "
118
- + "asked to implement, write, mark, sync, archive, commit, push, or "
119
- + "change Acceptance."
120
- );
121
- }
122
- } else {
123
- if (DELEGATION_INTENT.test(command)) {
124
- return blockedBrief(
125
- target,
126
- "A helper verification command cannot delegate or nest further helpers."
127
- );
128
- }
129
- const lowered = command.toLowerCase();
130
- const artifact = ARTIFACT_TOKENS.find((token) => lowered.includes(token));
131
- if (artifact) {
132
- return blockedBrief(
133
- target,
134
- `Helper command would generate or move repository artifacts (${artifact.trim()}); `
135
- + "only repository-byte-stable verification commands are allowed."
136
- );
137
- }
138
- if (FORBIDDEN_INTENT.test(command)) {
139
- return blockedBrief(
140
- target,
141
- "A helper verification command cannot mark, sync, archive, commit, push, "
142
- + "or otherwise assume completion authority."
143
- );
144
- }
145
- }
146
-
147
- const brief = {
148
- version: BRIEF_VERSION,
149
- target,
150
- mode: question ? "question" : "verification-command",
151
- request: question || command,
152
- reads: options.helperReads || [],
153
- externalPaths: external,
154
- authority: "read-only-evidence-only",
155
- writesProducts: false,
156
- delegates: false,
157
- completionAuthority: false,
158
- reportSchema: REPORT_SCHEMA,
159
- };
160
- return {
161
- version: BRIEF_VERSION,
162
- status: "ready",
163
- target,
164
- brief,
165
- reasons: [],
166
- };
167
- }
168
-
169
- function snapshotRepo(repo) {
170
- const snapshot = {};
171
- const walk = (dir) => {
172
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
173
- if (entry.name === ".git") continue;
174
- const full = path.join(dir, entry.name);
175
- if (entry.isDirectory()) {
176
- walk(full);
177
- } else if (entry.isFile()) {
178
- const rel = path.relative(repo, full).replace(/\\/g, "/");
179
- const bytes = fs.readFileSync(full);
180
- const hash = crypto.createHash("sha256").update(bytes).digest("hex");
181
- const mode = fs.statSync(full).mode & 0o777;
182
- snapshot[rel] = `${hash}:${mode.toString(8)}`;
183
- }
184
- }
185
- };
186
- walk(repo);
187
- return snapshot;
188
- }
189
-
190
- function captureHelperBaseline(repo, options) {
191
- const outPath = options.helperBaseline;
192
- if (!outPath) {
193
- throw new Error("helper baseline capture requires --baseline");
194
- }
195
- if (!isExternal(repo, outPath)) {
196
- throw new Error(
197
- "helper baseline must be written outside the repository worktree"
198
- );
199
- }
200
- const snapshot = snapshotRepo(repo);
201
- fs.mkdirSync(path.dirname(path.resolve(outPath)), { recursive: true });
202
- fs.writeFileSync(
203
- path.resolve(outPath),
204
- `${JSON.stringify({ version: BASELINE_VERSION, snapshot }, null, 2)}\n`,
205
- "utf8"
206
- );
207
- return {
208
- version: BASELINE_VERSION,
209
- status: "captured",
210
- path: path.resolve(outPath),
211
- count: Object.keys(snapshot).length,
212
- };
213
- }
214
-
215
- function classifyChanges(before, after) {
216
- const changes = [];
217
- for (const [rel, value] of Object.entries(after)) {
218
- if (!(rel in before)) {
219
- changes.push({ path: rel, kind: "added" });
220
- } else if (before[rel] !== after[rel]) {
221
- const [beforeHash, beforeMode] = before[rel].split(":");
222
- const [afterHash] = value.split(":");
223
- changes.push({
224
- path: rel,
225
- kind: beforeHash === afterHash ? "permission-changed" : "modified",
226
- });
227
- void beforeMode;
228
- }
229
- }
230
- for (const rel of Object.keys(before)) {
231
- if (!(rel in after)) {
232
- changes.push({ path: rel, kind: "removed" });
233
- }
234
- }
235
- return changes.sort((a, b) => a.path.localeCompare(b.path));
236
- }
237
-
238
- function verifyHelperEvidence(repo, options) {
239
- const baselinePath = options.helperBaseline;
240
- if (!baselinePath || !fs.existsSync(path.resolve(baselinePath))) {
241
- return {
242
- version: VERIFY_VERSION,
243
- status: "unverifiable",
244
- target: options.target,
245
- changes: [],
246
- cleanup: "none",
247
- reasons: [
248
- "Byte stability cannot be established without a recorded baseline; the "
249
- + "verification remains current-agent work.",
250
- ],
251
- };
252
- }
253
- let baseline;
254
- try {
255
- baseline = JSON.parse(fs.readFileSync(path.resolve(baselinePath), "utf8"));
256
- } catch {
257
- return {
258
- version: VERIFY_VERSION,
259
- status: "unverifiable",
260
- target: options.target,
261
- changes: [],
262
- cleanup: "none",
263
- reasons: [
264
- "Recorded helper baseline is unreadable; byte stability cannot be "
265
- + "established and the verification remains current-agent work.",
266
- ],
267
- };
268
- }
269
- const before = baseline.snapshot || {};
270
- const after = snapshotRepo(repo);
271
- const changes = classifyChanges(before, after);
272
- if (changes.length === 0) {
273
- return {
274
- version: VERIFY_VERSION,
275
- status: "verified",
276
- target: options.target,
277
- changes: [],
278
- cleanup: "none",
279
- reasons: [],
280
- };
281
- }
282
- return {
283
- version: VERIFY_VERSION,
284
- status: "rejected",
285
- target: options.target,
286
- changes,
287
- // The helper never restores, deletes, or attributes; it reports exact paths.
288
- cleanup: "none",
289
- reasons: [
290
- "Helper evidence rejected: repository bytes changed at "
291
- + changes.map((item) => `${item.path} (${item.kind})`).join(", ")
292
- + "; the current agent owns these paths and no cleanup is performed.",
293
- ],
294
- };
295
- }
296
-
297
- function renderHelper(result) {
298
- const lines = [
299
- `Keel helper: ${result.status}`,
300
- `Version: ${result.version}`,
301
- ];
302
- if (result.target) lines.push(`Target: ${result.target}`);
303
- for (const change of result.changes || []) {
304
- lines.push(`Changed: ${change.path} (${change.kind})`);
305
- }
306
- for (const reason of result.reasons || []) lines.push(`Reason: ${reason}`);
307
- return `${lines.join("\n")}\n`;
308
- }
309
-
310
- module.exports = {
311
- BRIEF_VERSION,
312
- BASELINE_VERSION,
313
- VERIFY_VERSION,
314
- SUPPORTED_HELPER_TARGETS,
315
- compileHelperBrief,
316
- captureHelperBaseline,
317
- verifyHelperEvidence,
318
- renderHelper,
319
- };
1
+ "use strict";
2
+
3
+ // Keel 4.1.0 bounded read-only helper contract.
4
+ //
5
+ // A helper is never a second writer. `keel project helper` compiles one
6
+ // bounded read-only question or one exact repository-byte-stable verification
7
+ // command into a `keel-helper-brief/v1` evidence contract, and verifies a
8
+ // helper's return only after proving the repository bytes are unchanged. The
9
+ // current agent stays the sole writer, owner of Acceptance, Review, gates, and
10
+ // completion; helper absence never disables current-agent goal execution.
11
+
12
+ const fs = require("fs");
13
+ const os = require("os");
14
+ const path = require("path");
15
+ const crypto = require("crypto");
16
+
17
+ const BRIEF_VERSION = "keel-helper-brief/v1";
18
+ const BASELINE_VERSION = "keel-helper-baseline/v1";
19
+ const VERIFY_VERSION = "keel-helper-verification/v1";
20
+ const SUPPORTED_HELPER_TARGETS = new Set(["codex", "claude"]);
21
+
22
+ // Any mutation, delegation, or ownership request disqualifies a read-only brief.
23
+ const FORBIDDEN_INTENT = new RegExp(
24
+ "\\b(implement|write|edit|modify|create|refactor|delete|remove|fix|apply|"
25
+ + "generate|install|commit|push|sync|archive|mark|check off|rewrite|"
26
+ + "update the task|change the acceptance|use the fallback|use a fallback)\\b",
27
+ "i"
28
+ );
29
+ const DELEGATION_INTENT = new RegExp(
30
+ "\\b(subagent|sub-agent|delegate|delegation|spawn|agent team|another agent|"
31
+ + "helper)\\b",
32
+ "i"
33
+ );
34
+ // Shell tokens that would create or move repository artifacts.
35
+ const ARTIFACT_TOKENS = [
36
+ ">", ">>", "|&", "tee ", "git commit", "git add", "git push", "git checkout",
37
+ "git reset", "git restore", "git rm", "git mv", "npm install", "npm i ",
38
+ "npm ci", "mkdir", "touch ", "rm ", "mv ", "cp ", "rmdir",
39
+ ];
40
+ const REPORT_SCHEMA = [
41
+ "question-or-command",
42
+ "reads-performed",
43
+ "observed-evidence",
44
+ "byte-stability: verified|rejected|unverifiable",
45
+ "no-writes-no-delegation-no-completion-authority",
46
+ ];
47
+
48
+ function blockedBrief(target, reason, extra = {}) {
49
+ return {
50
+ version: BRIEF_VERSION,
51
+ status: "blocked",
52
+ target,
53
+ brief: null,
54
+ reasons: [reason],
55
+ ...extra,
56
+ };
57
+ }
58
+
59
+ function isExternal(repo, candidate) {
60
+ const rel = path.relative(repo, path.resolve(candidate));
61
+ return (
62
+ rel === ".."
63
+ || rel.startsWith(`..${path.sep}`)
64
+ || path.isAbsolute(rel)
65
+ );
66
+ }
67
+
68
+ function compileHelperBrief(repo, options) {
69
+ const target = options.target;
70
+ if (!SUPPORTED_HELPER_TARGETS.has(target)) {
71
+ return blockedBrief(
72
+ target,
73
+ `Bounded read-only helpers support codex and claude only; `
74
+ + `${target || "missing"} remains manual/compatibility-only.`
75
+ );
76
+ }
77
+
78
+ const question = (options.helperQuestion || "").trim();
79
+ const command = (options.helperCommand || "").trim();
80
+ if ((question && command) || (!question && !command)) {
81
+ return blockedBrief(
82
+ target,
83
+ "A helper brief needs exactly one bounded read-only question or one exact "
84
+ + "repository-byte-stable verification command, not both and not neither."
85
+ );
86
+ }
87
+
88
+ const external = options.helperExternal || [];
89
+ for (const declared of external) {
90
+ if (!isExternal(repo, declared)) {
91
+ return blockedBrief(
92
+ target,
93
+ `Declared helper output path is inside the repository: ${declared}; `
94
+ + "helper temporaries must live outside the worktree."
95
+ );
96
+ }
97
+ }
98
+
99
+ if (question) {
100
+ if (/\?[\s\S]*\?/.test(question)) {
101
+ return blockedBrief(
102
+ target,
103
+ "A helper brief carries one bounded question; multiple unrelated "
104
+ + "questions must be split into separate read-only briefs."
105
+ );
106
+ }
107
+ if (DELEGATION_INTENT.test(question)) {
108
+ return blockedBrief(
109
+ target,
110
+ "A helper cannot delegate or spawn nested helpers; the brief must be a "
111
+ + "single read-only question the helper answers itself."
112
+ );
113
+ }
114
+ if (FORBIDDEN_INTENT.test(question)) {
115
+ return blockedBrief(
116
+ target,
117
+ "A helper is read-only and owns no completion authority; it cannot be "
118
+ + "asked to implement, write, mark, sync, archive, commit, push, or "
119
+ + "change Acceptance."
120
+ );
121
+ }
122
+ } else {
123
+ if (DELEGATION_INTENT.test(command)) {
124
+ return blockedBrief(
125
+ target,
126
+ "A helper verification command cannot delegate or nest further helpers."
127
+ );
128
+ }
129
+ const lowered = command.toLowerCase();
130
+ const artifact = ARTIFACT_TOKENS.find((token) => lowered.includes(token));
131
+ if (artifact) {
132
+ return blockedBrief(
133
+ target,
134
+ `Helper command would generate or move repository artifacts (${artifact.trim()}); `
135
+ + "only repository-byte-stable verification commands are allowed."
136
+ );
137
+ }
138
+ if (FORBIDDEN_INTENT.test(command)) {
139
+ return blockedBrief(
140
+ target,
141
+ "A helper verification command cannot mark, sync, archive, commit, push, "
142
+ + "or otherwise assume completion authority."
143
+ );
144
+ }
145
+ }
146
+
147
+ const brief = {
148
+ version: BRIEF_VERSION,
149
+ target,
150
+ mode: question ? "question" : "verification-command",
151
+ request: question || command,
152
+ reads: options.helperReads || [],
153
+ externalPaths: external,
154
+ authority: "read-only-evidence-only",
155
+ writesProducts: false,
156
+ delegates: false,
157
+ completionAuthority: false,
158
+ reportSchema: REPORT_SCHEMA,
159
+ };
160
+ return {
161
+ version: BRIEF_VERSION,
162
+ status: "ready",
163
+ target,
164
+ brief,
165
+ reasons: [],
166
+ };
167
+ }
168
+
169
+ function snapshotRepo(repo) {
170
+ const snapshot = {};
171
+ const walk = (dir) => {
172
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
173
+ if (entry.name === ".git") continue;
174
+ const full = path.join(dir, entry.name);
175
+ if (entry.isDirectory()) {
176
+ walk(full);
177
+ } else if (entry.isFile()) {
178
+ const rel = path.relative(repo, full).replace(/\\/g, "/");
179
+ const bytes = fs.readFileSync(full);
180
+ const hash = crypto.createHash("sha256").update(bytes).digest("hex");
181
+ const mode = fs.statSync(full).mode & 0o777;
182
+ snapshot[rel] = `${hash}:${mode.toString(8)}`;
183
+ }
184
+ }
185
+ };
186
+ walk(repo);
187
+ return snapshot;
188
+ }
189
+
190
+ function captureHelperBaseline(repo, options) {
191
+ const outPath = options.helperBaseline;
192
+ if (!outPath) {
193
+ throw new Error("helper baseline capture requires --baseline");
194
+ }
195
+ if (!isExternal(repo, outPath)) {
196
+ throw new Error(
197
+ "helper baseline must be written outside the repository worktree"
198
+ );
199
+ }
200
+ const snapshot = snapshotRepo(repo);
201
+ fs.mkdirSync(path.dirname(path.resolve(outPath)), { recursive: true });
202
+ fs.writeFileSync(
203
+ path.resolve(outPath),
204
+ `${JSON.stringify({ version: BASELINE_VERSION, snapshot }, null, 2)}\n`,
205
+ "utf8"
206
+ );
207
+ return {
208
+ version: BASELINE_VERSION,
209
+ status: "captured",
210
+ path: path.resolve(outPath),
211
+ count: Object.keys(snapshot).length,
212
+ };
213
+ }
214
+
215
+ function classifyChanges(before, after) {
216
+ const changes = [];
217
+ for (const [rel, value] of Object.entries(after)) {
218
+ if (!(rel in before)) {
219
+ changes.push({ path: rel, kind: "added" });
220
+ } else if (before[rel] !== after[rel]) {
221
+ const [beforeHash, beforeMode] = before[rel].split(":");
222
+ const [afterHash] = value.split(":");
223
+ changes.push({
224
+ path: rel,
225
+ kind: beforeHash === afterHash ? "permission-changed" : "modified",
226
+ });
227
+ void beforeMode;
228
+ }
229
+ }
230
+ for (const rel of Object.keys(before)) {
231
+ if (!(rel in after)) {
232
+ changes.push({ path: rel, kind: "removed" });
233
+ }
234
+ }
235
+ return changes.sort((a, b) => a.path.localeCompare(b.path));
236
+ }
237
+
238
+ function verifyHelperEvidence(repo, options) {
239
+ const baselinePath = options.helperBaseline;
240
+ if (!baselinePath || !fs.existsSync(path.resolve(baselinePath))) {
241
+ return {
242
+ version: VERIFY_VERSION,
243
+ status: "unverifiable",
244
+ target: options.target,
245
+ changes: [],
246
+ cleanup: "none",
247
+ reasons: [
248
+ "Byte stability cannot be established without a recorded baseline; the "
249
+ + "verification remains current-agent work.",
250
+ ],
251
+ };
252
+ }
253
+ let baseline;
254
+ try {
255
+ baseline = JSON.parse(fs.readFileSync(path.resolve(baselinePath), "utf8"));
256
+ } catch {
257
+ return {
258
+ version: VERIFY_VERSION,
259
+ status: "unverifiable",
260
+ target: options.target,
261
+ changes: [],
262
+ cleanup: "none",
263
+ reasons: [
264
+ "Recorded helper baseline is unreadable; byte stability cannot be "
265
+ + "established and the verification remains current-agent work.",
266
+ ],
267
+ };
268
+ }
269
+ const before = baseline.snapshot || {};
270
+ const after = snapshotRepo(repo);
271
+ const changes = classifyChanges(before, after);
272
+ if (changes.length === 0) {
273
+ return {
274
+ version: VERIFY_VERSION,
275
+ status: "verified",
276
+ target: options.target,
277
+ changes: [],
278
+ cleanup: "none",
279
+ reasons: [],
280
+ };
281
+ }
282
+ return {
283
+ version: VERIFY_VERSION,
284
+ status: "rejected",
285
+ target: options.target,
286
+ changes,
287
+ // The helper never restores, deletes, or attributes; it reports exact paths.
288
+ cleanup: "none",
289
+ reasons: [
290
+ "Helper evidence rejected: repository bytes changed at "
291
+ + changes.map((item) => `${item.path} (${item.kind})`).join(", ")
292
+ + "; the current agent owns these paths and no cleanup is performed.",
293
+ ],
294
+ };
295
+ }
296
+
297
+ function renderHelper(result) {
298
+ const lines = [
299
+ `Keel helper: ${result.status}`,
300
+ `Version: ${result.version}`,
301
+ ];
302
+ if (result.target) lines.push(`Target: ${result.target}`);
303
+ for (const change of result.changes || []) {
304
+ lines.push(`Changed: ${change.path} (${change.kind})`);
305
+ }
306
+ for (const reason of result.reasons || []) lines.push(`Reason: ${reason}`);
307
+ return `${lines.join("\n")}\n`;
308
+ }
309
+
310
+ module.exports = {
311
+ BRIEF_VERSION,
312
+ BASELINE_VERSION,
313
+ VERIFY_VERSION,
314
+ SUPPORTED_HELPER_TARGETS,
315
+ compileHelperBrief,
316
+ captureHelperBaseline,
317
+ verifyHelperEvidence,
318
+ renderHelper,
319
+ };