@tech-leads-club/harness-toolkit 0.3.0 → 0.3.2

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 (44) hide show
  1. package/README.md +46 -11
  2. package/bin/tlc-build.mjs +35 -6
  3. package/config.example.json +1 -1
  4. package/dist/chunks/compact-before-1e4qg1qt.mjs +1185 -0
  5. package/dist/chunks/compact-before-2hpbfxm5.mjs +5782 -0
  6. package/dist/chunks/compact-before-49j320yp.mjs +1283 -0
  7. package/dist/chunks/compact-before-4jrq0sqs.mjs +61 -0
  8. package/dist/chunks/compact-before-6w8n1vh1.mjs +186 -0
  9. package/dist/chunks/compact-before-7sdmwswh.mjs +52 -0
  10. package/dist/chunks/compact-before-beqpmqrm.mjs +187 -0
  11. package/dist/chunks/compact-before-j9y4jgn4.mjs +845 -0
  12. package/dist/chunks/compact-before-pk86tqx2.mjs +118 -0
  13. package/dist/chunks/compact-before-pkqk5v29.mjs +137 -0
  14. package/dist/chunks/compact-before-w1293m4n.mjs +315 -0
  15. package/dist/chunks/compact-before-wnnds45y.mjs +26 -0
  16. package/dist/chunks/compact-before-wt2c3nh4.mjs +551 -0
  17. package/dist/compact-before.mjs +13 -7961
  18. package/dist/doctor.mjs +33 -8480
  19. package/dist/help-topic.mjs +6 -14
  20. package/dist/init-project.mjs +36 -793
  21. package/dist/install-runtime.mjs +18 -1039
  22. package/dist/lessons-cli.mjs +25 -7043
  23. package/dist/obs-cli.mjs +27 -7037
  24. package/dist/price-lookup.mjs +11 -201
  25. package/dist/prompt-submit.mjs +14 -7974
  26. package/dist/refresh-model-prices.mjs +26 -7061
  27. package/dist/response-after.mjs +11 -7961
  28. package/dist/run.mjs +10 -7960
  29. package/dist/session-end.mjs +14 -8029
  30. package/dist/session-start.mjs +20 -8075
  31. package/dist/shim.mjs +18 -7024
  32. package/dist/stop.mjs +29 -8042
  33. package/dist/subagent-start.mjs +13 -7983
  34. package/dist/subagent-stop.mjs +11 -7961
  35. package/dist/support.mjs +21 -7168
  36. package/dist/tlc-cli.mjs +65 -8200
  37. package/dist/tool-after.mjs +20 -8176
  38. package/dist/tool-before.mjs +15 -7990
  39. package/dist/tool-failure.mjs +14 -7961
  40. package/dist/uninstall-runtime.mjs +61 -1008
  41. package/docs/log.md +4 -0
  42. package/package.json +2 -2
  43. package/src/core/release/release.version.ts +10 -4
  44. package/tools/install-runtime.ts +21 -1
@@ -0,0 +1,1283 @@
1
+ // src/platform/fs-jsonl.ts
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ function appendRecord(path, value) {
5
+ mkdirSync(dirname(path), { recursive: true });
6
+ appendFileSync(path, `${JSON.stringify(value)}
7
+ `);
8
+ }
9
+ function readTail(path, n) {
10
+ if (!existsSync(path)) {
11
+ return [];
12
+ }
13
+ const records = [];
14
+ for (const line of readFileSync(path, "utf8").split(`
15
+ `)) {
16
+ const trimmed = line.trim();
17
+ if (trimmed.length === 0) {
18
+ continue;
19
+ }
20
+ try {
21
+ records.push(JSON.parse(trimmed));
22
+ } catch {}
23
+ }
24
+ return records.slice(-n);
25
+ }
26
+
27
+ // src/platform/process.ts
28
+ import { spawn } from "node:child_process";
29
+ var TIMEOUT_EXIT_CODE = 124;
30
+ async function readStdinText() {
31
+ const chunks = [];
32
+ for await (const chunk of process.stdin) {
33
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
34
+ }
35
+ return Buffer.concat(chunks).toString("utf8");
36
+ }
37
+ async function runProcess(args) {
38
+ const [file, ...argv] = args.command;
39
+ if (file === undefined) {
40
+ return { exitCode: 0, stdout: "", stderr: "" };
41
+ }
42
+ return await new Promise((resolve, reject) => {
43
+ const child = spawn(file, argv, {
44
+ cwd: args.cwd,
45
+ stdio: ["pipe", "pipe", "pipe"],
46
+ env: args.env ?? process.env
47
+ });
48
+ let stdout = "";
49
+ let stderr = "";
50
+ let timedOut = false;
51
+ const timer = args.timeoutMs === undefined ? undefined : setTimeout(() => {
52
+ timedOut = true;
53
+ child.kill("SIGKILL");
54
+ }, args.timeoutMs);
55
+ child.stdout.on("data", (c) => {
56
+ stdout += c.toString();
57
+ });
58
+ child.stderr.on("data", (c) => {
59
+ stderr += c.toString();
60
+ });
61
+ child.on("error", (error) => {
62
+ if (timer)
63
+ clearTimeout(timer);
64
+ reject(error);
65
+ });
66
+ child.on("close", (code) => {
67
+ if (timer)
68
+ clearTimeout(timer);
69
+ if (timedOut) {
70
+ resolve({ exitCode: TIMEOUT_EXIT_CODE, stdout, stderr: `${stderr}
71
+ (process timed out)` });
72
+ return;
73
+ }
74
+ resolve({ exitCode: code ?? 1, stdout, stderr });
75
+ });
76
+ if (args.input !== undefined) {
77
+ child.stdin.write(args.input);
78
+ }
79
+ child.stdin.end();
80
+ });
81
+ }
82
+
83
+ // src/platform/git.ts
84
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
85
+ import { join } from "node:path";
86
+
87
+ // src/platform/sanitize.ts
88
+ var ALLOWED = /[A-Za-z0-9._-]/;
89
+ var EMPTY_PLACEHOLDER = "_empty_";
90
+ function sanitizeSegment(input) {
91
+ if (input.length === 0) {
92
+ return EMPTY_PLACEHOLDER;
93
+ }
94
+ let out = "";
95
+ for (const ch of input) {
96
+ if (ALLOWED.test(ch)) {
97
+ out += ch;
98
+ continue;
99
+ }
100
+ for (const byte of Buffer.from(ch, "utf8")) {
101
+ out += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+ function normalizeSeparators(input) {
107
+ return input.replace(/\\/g, "/");
108
+ }
109
+
110
+ // src/platform/git.ts
111
+ async function gitLines(projectDir, args) {
112
+ const result = await runProcess({ command: ["git", ...args], cwd: projectDir });
113
+ if (result.exitCode !== 0) {
114
+ return [];
115
+ }
116
+ return result.stdout.split(`
117
+ `).map((line) => line.trim()).filter(Boolean);
118
+ }
119
+ async function listChangedRepoFiles(projectDir, base = "HEAD") {
120
+ if (!existsSync2(join(projectDir, ".git"))) {
121
+ return [];
122
+ }
123
+ const batches = await Promise.all([
124
+ gitLines(projectDir, ["diff", "--name-only", base]),
125
+ gitLines(projectDir, ["diff", "--name-only", "--cached"]),
126
+ gitLines(projectDir, ["ls-files", "--others", "--exclude-standard"])
127
+ ]);
128
+ const paths = new Set;
129
+ for (const batch of batches) {
130
+ for (const path of batch) {
131
+ paths.add(path);
132
+ }
133
+ }
134
+ return [...paths];
135
+ }
136
+ async function listAddedLines(projectDir, relativePaths, base = "HEAD") {
137
+ if (!existsSync2(join(projectDir, ".git")) || relativePaths.length === 0) {
138
+ return [];
139
+ }
140
+ const tracked = new Set(await gitLines(projectDir, ["ls-files", "--", ...relativePaths]));
141
+ const out = [];
142
+ for (const file of relativePaths) {
143
+ if (!tracked.has(file)) {
144
+ let raw = "";
145
+ try {
146
+ raw = readFileSync2(join(projectDir, file), "utf8");
147
+ } catch {
148
+ continue;
149
+ }
150
+ raw.split(/\r?\n/).forEach((text, index) => {
151
+ out.push({ file, line: index + 1, text });
152
+ });
153
+ continue;
154
+ }
155
+ const diff = await gitLines(projectDir, ["diff", "--unified=0", base, "--", file]);
156
+ let lineNo = 0;
157
+ for (const row of diff) {
158
+ const hunk = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(row);
159
+ if (hunk) {
160
+ lineNo = Number(hunk[1]);
161
+ continue;
162
+ }
163
+ if (row.startsWith("+++")) {
164
+ continue;
165
+ }
166
+ if (row.startsWith("+")) {
167
+ out.push({ file, line: lineNo, text: row.slice(1) });
168
+ lineNo += 1;
169
+ }
170
+ }
171
+ }
172
+ return out;
173
+ }
174
+ function isUnderPrefixes(relativePath, prefixes) {
175
+ const normalized = normalizeSeparators(relativePath);
176
+ return prefixes.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`));
177
+ }
178
+ function filterCodeTargets(relativePaths, codePaths) {
179
+ return relativePaths.filter((path) => {
180
+ if (!isUnderPrefixes(path, codePaths)) {
181
+ return false;
182
+ }
183
+ return /\.(ts|tsx|js|jsx|json|mjs|cjs|py|go|rs)$/.test(path);
184
+ });
185
+ }
186
+ function filterTestTargets(relativePaths) {
187
+ return relativePaths.filter((path) => /\.(spec|test)\.(ts|tsx|js|jsx)$/.test(path));
188
+ }
189
+ async function runCommand(projectDir, command, extraArgs = [], options = {}) {
190
+ if (command.length === 0) {
191
+ return { exitCode: 0, output: "", durationMs: 0 };
192
+ }
193
+ const started = Date.now();
194
+ const result = await runProcess({
195
+ command: [...command, ...extraArgs],
196
+ cwd: projectDir,
197
+ env: options.env ? { ...process.env, ...options.env } : process.env
198
+ });
199
+ const combined = (result.stdout + result.stderr).trim();
200
+ const maxChars = 8000;
201
+ const output = combined.length === 0 ? "(no output captured)" : combined.length <= maxChars ? combined : combined.slice(-maxChars);
202
+ return {
203
+ exitCode: result.exitCode,
204
+ output,
205
+ durationMs: Date.now() - started
206
+ };
207
+ }
208
+ async function listTrackedFiles(projectDir) {
209
+ const result = await runCommand(projectDir, ["git", "ls-files", "-z"]);
210
+ if (result.exitCode !== 0) {
211
+ return [];
212
+ }
213
+ return result.output.split("\x00").filter((path) => path !== "");
214
+ }
215
+
216
+ // src/platform/style.ts
217
+ var COLORS = {
218
+ structure: "#3d3a4a",
219
+ accent: "#a78bfa",
220
+ success: "#6ee7b7",
221
+ warning: "#d4a574",
222
+ error: "#f87171",
223
+ info: "#93c5fd",
224
+ textMain: "#f5f5f7",
225
+ textMuted: "#9ca3af",
226
+ textDim: "#6b7280"
227
+ };
228
+ var SYMBOLS = {
229
+ check: "✔",
230
+ cross: "✖",
231
+ warning: "⚠",
232
+ arrow: "→",
233
+ arrowRight: "▸",
234
+ dot: "•",
235
+ bar: "│",
236
+ rule: "══",
237
+ dash: "──"
238
+ };
239
+ function rgb(hex) {
240
+ const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex.trim());
241
+ if (!match) {
242
+ return "255;255;255";
243
+ }
244
+ return [match[1], match[2], match[3]].map((part) => Number.parseInt(part, 16)).join(";");
245
+ }
246
+ var ESC = String.fromCharCode(27);
247
+ var RESET = `${ESC}[0m`;
248
+ function colorEnabled(env = process.env, argv = process.argv, isTty = process.stdout.isTTY === true) {
249
+ if ("NO_COLOR" in env) {
250
+ return false;
251
+ }
252
+ if (argv.includes("--no-color")) {
253
+ return false;
254
+ }
255
+ return isTty;
256
+ }
257
+ var STATUS_COLOR = {
258
+ ok: "success",
259
+ warn: "warning",
260
+ fail: "error",
261
+ info: "info"
262
+ };
263
+ var STATUS_MARK = {
264
+ ok: SYMBOLS.check,
265
+ warn: SYMBOLS.warning,
266
+ fail: SYMBOLS.cross,
267
+ info: SYMBOLS.arrowRight
268
+ };
269
+ var KV_WIDTH = 16;
270
+ function createStyle(enabled = colorEnabled()) {
271
+ const wrap = (code, text) => enabled ? `${ESC}[${code}m${text}${RESET}` : text;
272
+ const paint = (name, text) => wrap(`38;2;${rgb(COLORS[name])}`, text);
273
+ return {
274
+ enabled,
275
+ paint,
276
+ bold: (text) => wrap("1", text),
277
+ dim: (text) => paint("textDim", text),
278
+ heading: (text) => paint("accent", `${SYMBOLS.rule} ${text} ${SYMBOLS.rule}`),
279
+ footer: (text) => paint("textDim", `${SYMBOLS.dash} ${text} ${SYMBOLS.dash}`),
280
+ kv: (label, value, width = KV_WIDTH) => ` ${paint("textMuted", `${label}:`.padEnd(width))} ${value}`,
281
+ status: (level, text) => `${paint(STATUS_COLOR[level], STATUS_MARK[level])} ${text}`
282
+ };
283
+ }
284
+ var PLAIN = createStyle(false);
285
+
286
+ // src/platform/screen.ts
287
+ function render(screen, style) {
288
+ const out = [style.heading(screen.title.toUpperCase())];
289
+ if (screen.summary && screen.summary.length > 0) {
290
+ out.push(` ${screen.summary.join(style.dim(` ${SYMBOLS.bar} `))}`);
291
+ }
292
+ const width = Math.max(KV_WIDTH, ...screen.sections.flatMap((section) => (section.rows ?? []).map((row) => row.label.length + 1)));
293
+ for (const section of screen.sections) {
294
+ out.push("");
295
+ if (section.title) {
296
+ out.push(style.paint("accent", section.title));
297
+ }
298
+ for (const row of section.rows ?? []) {
299
+ const value = row.level ? style.status(row.level, row.value) : row.value;
300
+ out.push(style.kv(row.label, value, width));
301
+ }
302
+ for (const line of section.lines ?? []) {
303
+ out.push(line === "" ? "" : ` ${line}`);
304
+ }
305
+ }
306
+ if (screen.footer) {
307
+ out.push("", style.footer(screen.footer));
308
+ }
309
+ return out.join(`
310
+ `);
311
+ }
312
+
313
+ // src/core/observability/observability.types.ts
314
+ var DEFAULT_OBS = {
315
+ enabled: true,
316
+ signalPath: "obs.jsonl",
317
+ debugPath: "debug.jsonl",
318
+ debugEnabled: false,
319
+ includePayloads: false,
320
+ maxAttrChars: 500,
321
+ sessionCostAlertUsd: 5,
322
+ retentionDays: 14,
323
+ maxSignalEvents: 50000,
324
+ globalSpool: false
325
+ };
326
+ var SIGNAL_KINDS = new Set([
327
+ "session.start",
328
+ "session.end",
329
+ "generation.end",
330
+ "tool.fail",
331
+ "subagent.start",
332
+ "subagent.end",
333
+ "prompt.submit",
334
+ "compact",
335
+ "gate.outcome",
336
+ "cost.turn",
337
+ "cost.session_alert",
338
+ "ship.claim",
339
+ "policy.deny",
340
+ "policy.observe"
341
+ ]);
342
+ var LIVE_ALLOWLIST = new Set([
343
+ "session.start",
344
+ "session.end",
345
+ "generation.end",
346
+ "tool.fail",
347
+ "shell.end",
348
+ "subagent.start",
349
+ "subagent.end",
350
+ "gate.outcome",
351
+ "cost.turn",
352
+ "cost.session_alert",
353
+ "ship.claim",
354
+ "policy.deny",
355
+ "compact",
356
+ "prompt.submit"
357
+ ]);
358
+ function resolveObsLevel(kind, attrs = {}, forceDebug = false) {
359
+ if (forceDebug) {
360
+ return "debug";
361
+ }
362
+ if (kind === "shell.end" || kind === "shell.start") {
363
+ const permission = String(attrs.permission ?? "allow");
364
+ return permission === "allow" ? "debug" : "signal";
365
+ }
366
+ if (kind === "mcp.end") {
367
+ const outcome = String(attrs.outcome ?? attrs.status ?? "success");
368
+ return outcome === "error" || outcome === "fail" || outcome === "denied" ? "signal" : "debug";
369
+ }
370
+ return SIGNAL_KINDS.has(kind) ? "signal" : "debug";
371
+ }
372
+ var EVENT_KIND_TO_OBS_KIND = {
373
+ "session.start": "session.start",
374
+ "session.end": "session.end",
375
+ "prompt.submit": "prompt.submit",
376
+ "tool.before": "tool.start",
377
+ "tool.after": "tool.end",
378
+ "tool.failure": "tool.fail",
379
+ "shell.before": "shell.start",
380
+ "shell.after": "shell.end",
381
+ "mcp.before": "mcp.start",
382
+ "mcp.after": "mcp.end",
383
+ "read.before": "file.read",
384
+ "edit.after": "file.edit",
385
+ "subagent.start": "subagent.start",
386
+ "subagent.stop": "subagent.end",
387
+ stop: "generation.end",
388
+ "compact.before": "compact",
389
+ "response.after": "agent.response",
390
+ "thought.after": "agent.thought"
391
+ };
392
+ var SECRET_KEY = /(token|secret|password|api[_-]?key|authorization|credential|private[_-]?key)/i;
393
+ var SECRET_VALUE = /\b(ghp_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9]{20,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g;
394
+ function redactDeep(value) {
395
+ if (typeof value === "string") {
396
+ return value.replace(SECRET_VALUE, "[REDACTED]");
397
+ }
398
+ if (Array.isArray(value)) {
399
+ return value.map(redactDeep);
400
+ }
401
+ if (value && typeof value === "object") {
402
+ const out = {};
403
+ for (const [key, nested] of Object.entries(value)) {
404
+ out[key] = SECRET_KEY.test(key) ? "[REDACTED]" : redactDeep(nested);
405
+ }
406
+ return out;
407
+ }
408
+ return value;
409
+ }
410
+
411
+ // src/core/comment-policy/comment-policy.service.ts
412
+ import { readFileSync as readFileSync3 } from "node:fs";
413
+ import { join as join2 } from "node:path";
414
+
415
+ // src/core/comment-policy/comment-resolvability.ts
416
+ var LEAK_RULES = [
417
+ {
418
+ kind: "change-narration",
419
+ pattern: /\b(?:used to|previously)\b/i,
420
+ says: "narrates the change instead of the state"
421
+ },
422
+ {
423
+ kind: "change-narration",
424
+ pattern: /\bthis (?:was|used to)\b/i,
425
+ says: "narrates the change instead of the state"
426
+ },
427
+ {
428
+ kind: "change-narration",
429
+ pattern: /\bthe old (?:code|version|implementation|approach|way|behaviou?r)\b/i,
430
+ says: "refers to code that is no longer here"
431
+ },
432
+ {
433
+ kind: "change-narration",
434
+ pattern: /\bbefore (?:this|the) (?:change|commit|fix|patch|refactor)\b/i,
435
+ says: "refers to a state the repository no longer holds"
436
+ },
437
+ {
438
+ kind: "dead-citation",
439
+ pattern: /\((?:decision|item|step|phase|task|audit|option)\s*#?\d+\)/i,
440
+ says: "cites something only the authoring session could see"
441
+ },
442
+ {
443
+ kind: "dead-citation",
444
+ pattern: /§\s*\d/,
445
+ says: "cites a section of a document that is not in the repository"
446
+ },
447
+ {
448
+ kind: "dead-citation",
449
+ pattern: /\bas (?:decided|agreed|discussed|mentioned|described) (?:above|earlier|previously|before)\b/i,
450
+ says: "points at a conversation the reader cannot see"
451
+ },
452
+ {
453
+ kind: "dead-citation",
454
+ pattern: /\b(?:per|from|in) the plan\b|\bthe plan above\b/i,
455
+ says: "points at a plan that is not in the repository"
456
+ },
457
+ {
458
+ kind: "review-vantage",
459
+ pattern: /\bthis (?:PR|MR|commit|patch|diff|changeset)\b/i,
460
+ says: "speaks from the change rather than from the repository"
461
+ },
462
+ {
463
+ kind: "review-vantage",
464
+ pattern: /\ba (?:later|follow-?up|subsequent) (?:PR|MR|commit)\b/i,
465
+ says: "speaks from the change rather than from the repository"
466
+ },
467
+ {
468
+ kind: "reviewer-addressed",
469
+ pattern: /\bthis is (?:safe|correct|fine|ok|okay)\b/i,
470
+ says: "argues its own correctness to a reviewer instead of stating the invariant"
471
+ },
472
+ {
473
+ kind: "reviewer-addressed",
474
+ pattern: /\brejected in review\b|\bthe reviewer\b/i,
475
+ says: "records who said what, which the repository cannot confirm"
476
+ },
477
+ {
478
+ kind: "flow-narration",
479
+ pattern: /\bfirst (?:we|it|this)\b[\s\S]{0,80}\bthen (?:we|it|this)\b/i,
480
+ says: "restates the control flow the code already shows"
481
+ }
482
+ ];
483
+ function findLeaks(blockText) {
484
+ const leaks = [];
485
+ for (const rule of LEAK_RULES) {
486
+ const found = rule.pattern.exec(blockText);
487
+ if (found !== null) {
488
+ leaks.push({ kind: rule.kind, says: rule.says, match: found[0] });
489
+ }
490
+ }
491
+ return leaks;
492
+ }
493
+ function firstLeak(blockText) {
494
+ return findLeaks(blockText)[0] ?? null;
495
+ }
496
+ function leakReason(leak) {
497
+ return `unresolvable comment — ${leak.says} (\`${leak.match}\`)`;
498
+ }
499
+
500
+ // src/core/comment-policy/comment-syntax.catalog.ts
501
+ var COMMENT_SYNTAX = [
502
+ {
503
+ id: "typescript",
504
+ extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"],
505
+ line: ["//"],
506
+ block: [["/*", "*/"]],
507
+ middle: ["*"]
508
+ },
509
+ {
510
+ id: "python",
511
+ extensions: [".py", ".pyi", ".pyw"],
512
+ line: ["#"],
513
+ block: [
514
+ ['"""', '"""'],
515
+ ["'''", "'''"]
516
+ ],
517
+ middle: []
518
+ },
519
+ {
520
+ id: "ruby",
521
+ extensions: [".rb", ".rake", ".gemspec"],
522
+ line: ["#"],
523
+ block: [["=begin", "=end"]],
524
+ middle: []
525
+ },
526
+ {
527
+ id: "shell",
528
+ extensions: [".sh", ".bash", ".zsh", ".ksh", ".fish"],
529
+ line: ["#"],
530
+ block: [],
531
+ middle: []
532
+ },
533
+ {
534
+ id: "go",
535
+ extensions: [".go"],
536
+ line: ["//"],
537
+ block: [["/*", "*/"]],
538
+ middle: ["*"]
539
+ },
540
+ {
541
+ id: "rust",
542
+ extensions: [".rs"],
543
+ line: ["//"],
544
+ block: [["/*", "*/"]],
545
+ middle: ["*"]
546
+ },
547
+ {
548
+ id: "java",
549
+ extensions: [".java"],
550
+ line: ["//"],
551
+ block: [["/*", "*/"]],
552
+ middle: ["*"]
553
+ },
554
+ {
555
+ id: "kotlin",
556
+ extensions: [".kt", ".kts"],
557
+ line: ["//"],
558
+ block: [["/*", "*/"]],
559
+ middle: ["*"]
560
+ },
561
+ {
562
+ id: "swift",
563
+ extensions: [".swift"],
564
+ line: ["//"],
565
+ block: [["/*", "*/"]],
566
+ middle: ["*"]
567
+ },
568
+ {
569
+ id: "c",
570
+ extensions: [".c", ".h"],
571
+ line: ["//"],
572
+ block: [["/*", "*/"]],
573
+ middle: ["*"]
574
+ },
575
+ {
576
+ id: "cpp",
577
+ extensions: [".cc", ".cpp", ".cxx", ".hpp", ".hh", ".hxx"],
578
+ line: ["//"],
579
+ block: [["/*", "*/"]],
580
+ middle: ["*"]
581
+ },
582
+ {
583
+ id: "csharp",
584
+ extensions: [".cs"],
585
+ line: ["//"],
586
+ block: [["/*", "*/"]],
587
+ middle: ["*"]
588
+ },
589
+ {
590
+ id: "php",
591
+ extensions: [".php"],
592
+ line: ["//", "#"],
593
+ block: [["/*", "*/"]],
594
+ middle: ["*"]
595
+ },
596
+ {
597
+ id: "scala",
598
+ extensions: [".scala", ".sc"],
599
+ line: ["//"],
600
+ block: [["/*", "*/"]],
601
+ middle: ["*"]
602
+ },
603
+ {
604
+ id: "dart",
605
+ extensions: [".dart"],
606
+ line: ["//"],
607
+ block: [["/*", "*/"]],
608
+ middle: ["*"]
609
+ },
610
+ {
611
+ id: "elixir",
612
+ extensions: [".ex", ".exs"],
613
+ line: ["#"],
614
+ block: [],
615
+ middle: []
616
+ },
617
+ {
618
+ id: "erlang",
619
+ extensions: [".erl", ".hrl"],
620
+ line: ["%"],
621
+ block: [],
622
+ middle: []
623
+ },
624
+ {
625
+ id: "haskell",
626
+ extensions: [".hs"],
627
+ line: ["--"],
628
+ block: [["{-", "-}"]],
629
+ middle: []
630
+ },
631
+ {
632
+ id: "lua",
633
+ extensions: [".lua"],
634
+ line: ["--"],
635
+ block: [["--[[", "]]"]],
636
+ middle: []
637
+ },
638
+ {
639
+ id: "sql",
640
+ extensions: [".sql"],
641
+ line: ["--"],
642
+ block: [["/*", "*/"]],
643
+ middle: ["*"]
644
+ },
645
+ {
646
+ id: "css",
647
+ extensions: [".css", ".scss", ".sass", ".less"],
648
+ line: ["//"],
649
+ block: [["/*", "*/"]],
650
+ middle: ["*"]
651
+ },
652
+ {
653
+ id: "yaml",
654
+ extensions: [".yaml", ".yml"],
655
+ line: ["#"],
656
+ block: [],
657
+ middle: []
658
+ },
659
+ {
660
+ id: "toml",
661
+ extensions: [".toml"],
662
+ line: ["#"],
663
+ block: [],
664
+ middle: []
665
+ },
666
+ {
667
+ id: "ini",
668
+ extensions: [".ini", ".cfg", ".conf", ".properties"],
669
+ line: [";", "#"],
670
+ block: [],
671
+ middle: []
672
+ },
673
+ {
674
+ id: "dockerfile",
675
+ extensions: [".dockerfile", "dockerfile"],
676
+ line: ["#"],
677
+ block: [],
678
+ middle: []
679
+ },
680
+ {
681
+ id: "makefile",
682
+ extensions: [".mk", "makefile"],
683
+ line: ["#"],
684
+ block: [],
685
+ middle: []
686
+ },
687
+ {
688
+ id: "terraform",
689
+ extensions: [".tf", ".tfvars"],
690
+ line: ["#", "//"],
691
+ block: [["/*", "*/"]],
692
+ middle: ["*"]
693
+ },
694
+ {
695
+ id: "powershell",
696
+ extensions: [".ps1", ".psm1", ".psd1"],
697
+ line: ["#"],
698
+ block: [["<#", "#>"]],
699
+ middle: []
700
+ },
701
+ {
702
+ id: "perl",
703
+ extensions: [".pl", ".pm"],
704
+ line: ["#"],
705
+ block: [],
706
+ middle: []
707
+ },
708
+ {
709
+ id: "r",
710
+ extensions: [".r"],
711
+ line: ["#"],
712
+ block: [],
713
+ middle: []
714
+ },
715
+ {
716
+ id: "julia",
717
+ extensions: [".jl"],
718
+ line: ["#"],
719
+ block: [["#=", "=#"]],
720
+ middle: []
721
+ },
722
+ {
723
+ id: "vue",
724
+ extensions: [".vue", ".svelte"],
725
+ line: ["//"],
726
+ block: [
727
+ ["/*", "*/"],
728
+ ["<!--", "-->"]
729
+ ],
730
+ middle: ["*"]
731
+ },
732
+ {
733
+ id: "html",
734
+ extensions: [".html", ".htm", ".xml", ".xhtml"],
735
+ line: [],
736
+ block: [["<!--", "-->"]],
737
+ middle: []
738
+ },
739
+ {
740
+ id: "graphql",
741
+ extensions: [".graphql", ".gql"],
742
+ line: ["#"],
743
+ block: [],
744
+ middle: []
745
+ },
746
+ {
747
+ id: "protobuf",
748
+ extensions: [".proto"],
749
+ line: ["//"],
750
+ block: [["/*", "*/"]],
751
+ middle: ["*"]
752
+ },
753
+ {
754
+ id: "zig",
755
+ extensions: [".zig"],
756
+ line: ["//"],
757
+ block: [],
758
+ middle: []
759
+ },
760
+ {
761
+ id: "clojure",
762
+ extensions: [".clj", ".cljs", ".cljc", ".edn"],
763
+ line: [";"],
764
+ block: [],
765
+ middle: []
766
+ },
767
+ {
768
+ id: "ocaml",
769
+ extensions: [".ml", ".mli"],
770
+ line: [],
771
+ block: [["(*", "*)"]],
772
+ middle: ["*"]
773
+ },
774
+ {
775
+ id: "fsharp",
776
+ extensions: [".fs", ".fsi", ".fsx"],
777
+ line: ["//"],
778
+ block: [["(*", "*)"]],
779
+ middle: ["*"]
780
+ },
781
+ {
782
+ id: "vim",
783
+ extensions: [".vim"],
784
+ line: ['"'],
785
+ block: [],
786
+ middle: []
787
+ },
788
+ {
789
+ id: "tex",
790
+ extensions: [".tex", ".sty", ".cls"],
791
+ line: ["%"],
792
+ block: [],
793
+ middle: []
794
+ }
795
+ ];
796
+
797
+ // src/core/comment-policy/comment-syntax.store.ts
798
+ function buildIndex(entries) {
799
+ const byKey = new Map;
800
+ for (const entry of entries) {
801
+ const syntax = { line: entry.line, block: entry.block, middle: entry.middle };
802
+ for (const extension of entry.extensions) {
803
+ byKey.set(extension.toLowerCase(), syntax);
804
+ }
805
+ }
806
+ return byKey;
807
+ }
808
+ var INDEX = buildIndex(COMMENT_SYNTAX);
809
+ function lookupSyntax(file, index = INDEX) {
810
+ const lower = file.toLowerCase().replace(/\\/g, "/");
811
+ const name = lower.slice(lower.lastIndexOf("/") + 1);
812
+ const direct = index.get(name);
813
+ if (direct) {
814
+ return direct;
815
+ }
816
+ let best = null;
817
+ let bestLength = 0;
818
+ for (const [extension, syntax] of index) {
819
+ if (extension.startsWith(".") && name.endsWith(extension) && extension.length > bestLength) {
820
+ best = syntax;
821
+ bestLength = extension.length;
822
+ }
823
+ }
824
+ return best;
825
+ }
826
+ function syntaxFor(file) {
827
+ return lookupSyntax(file);
828
+ }
829
+ function unknownExtensions(files) {
830
+ const unknown = new Set;
831
+ for (const file of files) {
832
+ if (lookupSyntax(file) === null) {
833
+ const name = file.toLowerCase().replace(/\\/g, "/").split("/").pop() ?? file;
834
+ const dot = name.lastIndexOf(".");
835
+ unknown.add(dot > 0 ? name.slice(dot) : name);
836
+ }
837
+ }
838
+ return [...unknown].sort();
839
+ }
840
+ var KNOWN_EXTENSION_COUNT = INDEX.size;
841
+
842
+ // src/core/comment-policy/comment-policy.service.ts
843
+ function matchesSyntax(text, syntax) {
844
+ const trimmed = text.trimStart();
845
+ if (trimmed === "") {
846
+ return false;
847
+ }
848
+ if (syntax.line.some((prefix) => prefix !== "" && trimmed.startsWith(prefix))) {
849
+ return true;
850
+ }
851
+ for (const [open, close] of syntax.block) {
852
+ if (trimmed.startsWith(open)) {
853
+ return true;
854
+ }
855
+ if (open !== close && trimmed.startsWith(close)) {
856
+ return false;
857
+ }
858
+ }
859
+ return syntax.middle.some((middle) => trimmed.startsWith(middle) && !trimmed.startsWith(middle + middle));
860
+ }
861
+ var TOOL_DIRECTIVE = /^\s*(?:\/\/|\/\*|\*|#)\s*(?:biome-ignore|eslint|@ts-|prettier-ignore|noqa|type:|shellcheck|!)/;
862
+ var DECLARED_REASON = /^\s*(?:\/\/|\/\*|\*|#)\s*(?:why|hazard|invariant):\s*\S/i;
863
+ var CLOSER_OR_CONTINUATION = /^\s*(?:\*\/|\*|\/\/)/;
864
+ var COMMENT_MARKERS = ["why:", "hazard:", "invariant:"];
865
+ function isCommentLine(text, file = "") {
866
+ const syntax = file === "" ? null : syntaxFor(file);
867
+ if (syntax === null) {
868
+ return false;
869
+ }
870
+ return matchesSyntax(text, syntax) && !TOOL_DIRECTIVE.test(text);
871
+ }
872
+ function declaresReason(text) {
873
+ return DECLARED_REASON.test(text);
874
+ }
875
+ var DECLARATION = /^\s*(?:(?:export|declare|public|private|protected|readonly|static|async|abstract)\s+)*(?:class|function|const|let|var|type|interface|enum|namespace)\s+([A-Za-z_$][\w$]*)|^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\??\s*[:(<]/;
876
+ function attachedIdentifier(codeLine) {
877
+ const match = codeLine === undefined ? null : DECLARATION.exec(codeLine);
878
+ return match ? match[1] ?? match[2] ?? null : null;
879
+ }
880
+ var STOPWORDS = new Set([
881
+ "a",
882
+ "an",
883
+ "and",
884
+ "are",
885
+ "as",
886
+ "at",
887
+ "be",
888
+ "by",
889
+ "for",
890
+ "from",
891
+ "get",
892
+ "gets",
893
+ "has",
894
+ "in",
895
+ "into",
896
+ "is",
897
+ "it",
898
+ "its",
899
+ "of",
900
+ "on",
901
+ "or",
902
+ "return",
903
+ "returns",
904
+ "set",
905
+ "sets",
906
+ "that",
907
+ "the",
908
+ "then",
909
+ "this",
910
+ "to",
911
+ "true",
912
+ "when",
913
+ "which",
914
+ "with"
915
+ ]);
916
+ function words(text) {
917
+ return text.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length > 1);
918
+ }
919
+ var MIN_INFORMATIVE_WORDS = 3;
920
+ function isInformativeDoc(commentText, identifier) {
921
+ const named = new Set(words(identifier));
922
+ const remaining = words(commentText.replace(/^[\s/*]+|[\s*/]+$/g, "")).filter((word) => !named.has(word) && !STOPWORDS.has(word));
923
+ return new Set(remaining).size >= MIN_INFORMATIVE_WORDS;
924
+ }
925
+ function groupCommentBlocks(added) {
926
+ const blocks = [];
927
+ let block = [];
928
+ for (const line of added) {
929
+ if (!isCommentLine(line.text, line.file)) {
930
+ block = [];
931
+ continue;
932
+ }
933
+ const previous = block.at(-1);
934
+ if (previous && previous.file === line.file && previous.line === line.line - 1) {
935
+ block.push(line);
936
+ continue;
937
+ }
938
+ block = [line];
939
+ blocks.push(block);
940
+ }
941
+ return blocks;
942
+ }
943
+ var MAX_DECLARED_LINES = 4;
944
+ function declarationAfter(file, tailLine, nextCodeLine) {
945
+ for (let line = tailLine + 1;line <= tailLine + 4; line += 1) {
946
+ const text = nextCodeLine(file, line);
947
+ if (text === undefined) {
948
+ continue;
949
+ }
950
+ if (text.trim() === "" || CLOSER_OR_CONTINUATION.test(text)) {
951
+ continue;
952
+ }
953
+ return text;
954
+ }
955
+ return;
956
+ }
957
+ function judge(block, mode, nextCodeLine) {
958
+ const head = block[0];
959
+ const tail = block.at(-1);
960
+ if (head.text.trimStart().startsWith("/**") && nextCodeLine) {
961
+ const identifier = attachedIdentifier(declarationAfter(head.file, tail.line, nextCodeLine));
962
+ if (identifier !== null) {
963
+ const body = block.map((line) => line.text).join(" ");
964
+ if (mode === "strict") {
965
+ return { violates: true, reason: "comment added this turn" };
966
+ }
967
+ if (!isInformativeDoc(body, identifier)) {
968
+ return { violates: true, reason: `doc comment only restates ${identifier}` };
969
+ }
970
+ const docLeak = mode === "resolvable" ? firstLeak(body) : null;
971
+ return docLeak === null ? { violates: false, reason: "" } : { violates: true, reason: leakReason(docLeak) };
972
+ }
973
+ }
974
+ if (mode === "strict") {
975
+ return { violates: true, reason: "comment added this turn" };
976
+ }
977
+ if (!declaresReason(head.text)) {
978
+ return { violates: true, reason: "undeclared comment added this turn" };
979
+ }
980
+ if (block.length > MAX_DECLARED_LINES) {
981
+ return { violates: true, reason: `declared comment runs past ${MAX_DECLARED_LINES} lines` };
982
+ }
983
+ if (mode === "resolvable") {
984
+ const leak = firstLeak(block.map((line) => line.text).join(" "));
985
+ if (leak !== null) {
986
+ return { violates: true, reason: leakReason(leak) };
987
+ }
988
+ }
989
+ return { violates: false, reason: "" };
990
+ }
991
+ function findAddedComments(added, mode = "declared", nextCodeLine) {
992
+ const findings = [];
993
+ for (const block of groupCommentBlocks(added)) {
994
+ if (block[0] === undefined) {
995
+ continue;
996
+ }
997
+ const verdict = judge(block, mode, nextCodeLine);
998
+ if (verdict.violates) {
999
+ const head = block[0];
1000
+ findings.push({
1001
+ file: head.file,
1002
+ line: head.line,
1003
+ reason: verdict.reason,
1004
+ text: head.text.trim().slice(0, 120)
1005
+ });
1006
+ }
1007
+ }
1008
+ return findings;
1009
+ }
1010
+ function diskLineReader(projectDir) {
1011
+ const cache = new Map;
1012
+ return (file, line) => {
1013
+ let lines = cache.get(file);
1014
+ if (lines === undefined) {
1015
+ try {
1016
+ lines = readFileSync3(join2(projectDir, file), "utf8").split(`
1017
+ `);
1018
+ } catch {
1019
+ lines = [];
1020
+ }
1021
+ cache.set(file, lines);
1022
+ }
1023
+ return lines[line - 1];
1024
+ };
1025
+ }
1026
+ async function scanAddedComments(projectDir, relativePaths, mode = "declared", base = "HEAD") {
1027
+ const added = await listAddedLines(projectDir, relativePaths, base);
1028
+ return findAddedComments(added, mode, diskLineReader(projectDir));
1029
+ }
1030
+ function commentViolationMessage(hits, mode = "declared") {
1031
+ const need = mode === "resolvable" ? [
1032
+ "NEED: restate each line below so a reader at HEAD can check it without the transcript of",
1033
+ "this session — state the present behaviour, or state the counterfactual (`without X, Y`).",
1034
+ "Delete it when nothing survives that restatement."
1035
+ ] : mode === "strict" ? [
1036
+ "NEED: delete every line below. This project does not accept agent-added comments.",
1037
+ "If one is genuinely warranted, say so in your reply and let the operator write it."
1038
+ ] : [
1039
+ `NEED: delete each line below, or restate it as ${COMMENT_MARKERS.join(" / ")} when it`,
1040
+ "records a non-obvious why, a hazard, or an external constraint. Narrating what the code",
1041
+ "does is not a reason."
1042
+ ];
1043
+ return [
1044
+ `BLOCKED: this turn added ${hits.length} comment(s).`,
1045
+ "TRIED: compared the lines this turn added against the commit it started from; pre-existing",
1046
+ "comments are never counted.",
1047
+ "Each entry is one comment, reported at its first line.",
1048
+ ...need,
1049
+ "Tool directives (biome-ignore, @ts-, noqa, shellcheck, shebang) are exempt.",
1050
+ "",
1051
+ ...hits.slice(0, 20).map((h) => `${h.file}:${h.line} ${h.text}`)
1052
+ ].join(`
1053
+ `);
1054
+ }
1055
+
1056
+ // src/core/duplication/duplication.service.ts
1057
+ var MIN_RUN = 6;
1058
+ var MIN_LINE_CHARS = 8;
1059
+ function normaliseLine(text) {
1060
+ const collapsed = text.trim().replace(/\s+/g, " ").replace(/,$/, "");
1061
+ return collapsed.length < MIN_LINE_CHARS ? null : collapsed;
1062
+ }
1063
+ var DEPENDENCY_LINE = /^\s*(?:import|from|export|require|#include|use|using|package|namespace|open)\b/;
1064
+ function isCodeLine(text, file) {
1065
+ if (DEPENDENCY_LINE.test(text)) {
1066
+ return false;
1067
+ }
1068
+ const syntax = syntaxFor(file);
1069
+ return syntax === null ? true : !matchesSyntax(text, syntax);
1070
+ }
1071
+ var OPERATIONAL = /[(=]|\b(?:if|for|while|switch|return|throw|await|new|catch)\b/;
1072
+ var MIN_OPERATIONAL_RATIO = 0.5;
1073
+ function operationalEnough(window) {
1074
+ const operations = window.filter((entry) => OPERATIONAL.test(entry.key)).length;
1075
+ return operations >= Math.ceil(window.length * MIN_OPERATIONAL_RATIO);
1076
+ }
1077
+ var SITES_PER_RUN = 2;
1078
+ function runKey(window) {
1079
+ return window.join(`
1080
+ `);
1081
+ }
1082
+ function indexRuns(lines, minRun = MIN_RUN) {
1083
+ const index = new Map;
1084
+ const usable = lines.map((entry) => ({
1085
+ ...entry,
1086
+ key: isCodeLine(entry.text, entry.file) ? normaliseLine(entry.text) : null
1087
+ })).map((entry) => entry.key === null ? null : { file: entry.file, line: entry.line, key: entry.key });
1088
+ for (let start = 0;start + minRun <= usable.length; start += 1) {
1089
+ const window = usable.slice(start, start + minRun);
1090
+ if (window.some((entry) => entry === null)) {
1091
+ continue;
1092
+ }
1093
+ const solid = window;
1094
+ const head = solid[0];
1095
+ if (solid.some((entry, offset) => entry.file !== head.file || entry.line !== head.line + offset)) {
1096
+ continue;
1097
+ }
1098
+ if (!operationalEnough(solid)) {
1099
+ continue;
1100
+ }
1101
+ const key = runKey(solid.map((entry) => entry.key));
1102
+ const sites = index.get(key) ?? [];
1103
+ if (sites.length < SITES_PER_RUN) {
1104
+ index.set(key, [...sites, { file: head.file, line: head.line }]);
1105
+ }
1106
+ }
1107
+ return index;
1108
+ }
1109
+ function findDuplications(added, project, minRun = MIN_RUN) {
1110
+ const found = [];
1111
+ const reported = new Set;
1112
+ for (const [key, sites] of indexRuns(added, minRun)) {
1113
+ const where = sites[0];
1114
+ const prior = (project.get(key) ?? []).find((site) => site.file !== where.file || site.line !== where.line);
1115
+ if (prior === undefined) {
1116
+ continue;
1117
+ }
1118
+ const seen = `${where.file}:${where.line}`;
1119
+ if (reported.has(seen)) {
1120
+ continue;
1121
+ }
1122
+ reported.add(seen);
1123
+ found.push({
1124
+ file: where.file,
1125
+ line: where.line,
1126
+ matchFile: prior.file,
1127
+ matchLine: prior.line,
1128
+ runLength: minRun
1129
+ });
1130
+ }
1131
+ return found.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
1132
+ }
1133
+ function duplicationMessage(hits) {
1134
+ return [
1135
+ `BLOCKED: this turn added ${hits.length} run(s) of ${MIN_RUN}+ lines that already exist in this project.`,
1136
+ "TRIED: compared the lines this turn added against the rest of the repository, ignoring",
1137
+ "comments, blank lines and whitespace. A run already duplicated before this turn is not counted.",
1138
+ "NEED: call the existing code, or extract what both need. If the duplication is deliberate —",
1139
+ "the two will diverge, or the shared form would couple them — say which, in one line, and continue.",
1140
+ "",
1141
+ ...hits.slice(0, 10).map((hit) => `${hit.file}:${hit.line} already at ${hit.matchFile}:${hit.matchLine}`)
1142
+ ].join(`
1143
+ `);
1144
+ }
1145
+ var MAX_SCAN_FILES = 2000;
1146
+ var MAX_SCAN_BYTES = 8000000;
1147
+ function scanProject(files, readFile, minRun = MIN_RUN) {
1148
+ const lines = [];
1149
+ let bytes = 0;
1150
+ let filesRead = 0;
1151
+ let truncated = false;
1152
+ for (const file of files) {
1153
+ if (filesRead >= MAX_SCAN_FILES || bytes >= MAX_SCAN_BYTES) {
1154
+ truncated = true;
1155
+ break;
1156
+ }
1157
+ const text = readFile(file);
1158
+ if (text === null) {
1159
+ continue;
1160
+ }
1161
+ bytes += text.length;
1162
+ filesRead += 1;
1163
+ for (const [index, line] of text.split(`
1164
+ `).entries()) {
1165
+ lines.push({ file, line: index + 1, text: line });
1166
+ }
1167
+ }
1168
+ return { index: indexRuns(lines, minRun), filesRead, truncated };
1169
+ }
1170
+
1171
+ // src/core/policy/policy.defaults.ts
1172
+ var DEFAULT_LESSONS_POLICY = {
1173
+ enabled: false,
1174
+ maxInjectSession: 5,
1175
+ maxInjectRetry: 8,
1176
+ maxCharsSession: 900,
1177
+ maxCharsRetry: 1400,
1178
+ promoteHitCount: 2,
1179
+ decayLambda: 0.02,
1180
+ projectBoost: 1.5,
1181
+ syncRulesFile: "auto",
1182
+ gardenOnSessionEnd: true
1183
+ };
1184
+ var DEFAULTS = {
1185
+ version: 1,
1186
+ mode: "solo",
1187
+ codePaths: ["src", "apps", "libs", "packages"],
1188
+ grind: {
1189
+ enabled: false,
1190
+ maxLoops: 5,
1191
+ lintCommand: null,
1192
+ testCommand: null,
1193
+ appendFiles: "auto"
1194
+ },
1195
+ shipGate: {
1196
+ enabled: false,
1197
+ runtimePathPrefixes: ["src", "apps", "libs", "packages", "deploy", "scripts"],
1198
+ runtimePathExcludes: [".tlc/", "**/node_modules/", "**/.git/"],
1199
+ evidenceDir: null,
1200
+ evidenceMaxAgeHours: 48,
1201
+ emptyDiffAntiShip: false,
1202
+ claimWindowMinutes: 10
1203
+ },
1204
+ subagents: {
1205
+ enforceAllowlist: false,
1206
+ requireModel: false,
1207
+ allowedModels: [],
1208
+ blockedPatterns: ["-fast(?:$|[^a-z0-9])", "/fast(?:$|[^a-z0-9])"],
1209
+ minEffort: null,
1210
+ blockParentFast: false,
1211
+ blockMode: "deny",
1212
+ readOnlyTypes: ["explore"]
1213
+ },
1214
+ docs: {
1215
+ command: null,
1216
+ severity: "warn"
1217
+ },
1218
+ observe: {
1219
+ enabled: false,
1220
+ rails: []
1221
+ },
1222
+ comments: {
1223
+ enabled: false,
1224
+ onViolation: "followup",
1225
+ mode: "declared"
1226
+ },
1227
+ supplyChain: {
1228
+ enabled: false
1229
+ },
1230
+ duplication: {
1231
+ enabled: false,
1232
+ minRun: MIN_RUN
1233
+ },
1234
+ obs: {
1235
+ globalSpool: false,
1236
+ includePayloads: DEFAULT_OBS.includePayloads,
1237
+ maxAttrChars: DEFAULT_OBS.maxAttrChars,
1238
+ sessionCostAlertUsd: DEFAULT_OBS.sessionCostAlertUsd,
1239
+ retentionDays: DEFAULT_OBS.retentionDays
1240
+ },
1241
+ untrustedContent: {
1242
+ mode: "frame",
1243
+ enabled: false,
1244
+ extraTools: [],
1245
+ extraCommandPatterns: []
1246
+ },
1247
+ planGate: {
1248
+ enabled: false,
1249
+ windowMinutes: 120
1250
+ },
1251
+ shell: {
1252
+ catastrophicAsk: true,
1253
+ stallDetection: false,
1254
+ stallRepeatThreshold: 3
1255
+ },
1256
+ intelligence: {
1257
+ gapFeedback: true,
1258
+ failureClassification: true,
1259
+ progressiveHandoff: true,
1260
+ progressiveContext: true,
1261
+ autopilot: true,
1262
+ idleTurnGate: false,
1263
+ budgetContinue: false,
1264
+ budgetContinueAfterLoops: 3,
1265
+ lessons: { ...DEFAULT_LESSONS_POLICY }
1266
+ },
1267
+ mcpPrime: [],
1268
+ bootstrapExtra: []
1269
+ };
1270
+
1271
+ // src/contracts/effort.ts
1272
+ var EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
1273
+ function effortOrdinal(level) {
1274
+ return EFFORT_LEVELS.indexOf(level);
1275
+ }
1276
+ function compareEffort(a, b) {
1277
+ return effortOrdinal(a) - effortOrdinal(b);
1278
+ }
1279
+ function isEffortLevel(value) {
1280
+ return typeof value === "string" && EFFORT_LEVELS.includes(value);
1281
+ }
1282
+
1283
+ export { appendRecord, readTail, readStdinText, runProcess, sanitizeSegment, normalizeSeparators, listChangedRepoFiles, listAddedLines, filterCodeTargets, filterTestTargets, runCommand, listTrackedFiles, unknownExtensions, KNOWN_EXTENSION_COUNT, isCommentLine, declaresReason, findAddedComments, scanAddedComments, commentViolationMessage, MIN_RUN, findDuplications, duplicationMessage, scanProject, SYMBOLS, createStyle, PLAIN, render, DEFAULT_OBS, resolveObsLevel, EVENT_KIND_TO_OBS_KIND, redactDeep, DEFAULTS, compareEffort, isEffortLevel };