@alecsibilia/luca 13.0.0-alpha.10 → 13.0.0-alpha.13

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 (75) hide show
  1. package/dist/chunks/branch.mjs +11 -7
  2. package/dist/chunks/checks.mjs +11 -7
  3. package/dist/chunks/claim-verify.mjs +13 -408
  4. package/dist/chunks/classify.mjs +1 -1
  5. package/dist/chunks/confidence.mjs +19 -11
  6. package/dist/chunks/hook.mjs +13 -10
  7. package/dist/chunks/init.mjs +315 -61
  8. package/dist/chunks/luca-gitignore.mjs +4 -4
  9. package/dist/chunks/muninn-mcp.mjs +13 -10
  10. package/dist/chunks/phase.mjs +15 -9
  11. package/dist/chunks/plan.mjs +51 -0
  12. package/dist/chunks/pr-review.mjs +15 -9
  13. package/dist/chunks/preferences.mjs +13 -8
  14. package/dist/chunks/repair.mjs +1 -1
  15. package/dist/chunks/repo.mjs +11 -7
  16. package/dist/chunks/retro.mjs +6 -5
  17. package/dist/chunks/roadmap.mjs +13 -8
  18. package/dist/chunks/rules.mjs +6 -5
  19. package/dist/chunks/state.mjs +17 -10
  20. package/dist/chunks/stray-local-install.mjs +4 -4
  21. package/dist/chunks/telemetry.mjs +1 -1
  22. package/dist/chunks/todo.mjs +54 -15
  23. package/dist/chunks/vault-init.mjs +7 -228
  24. package/dist/chunks/verification.mjs +10 -6
  25. package/dist/chunks/workflow.mjs +11 -7
  26. package/dist/claude/.claude/agents/architect.md +68 -21
  27. package/dist/claude/.claude/agents/build.md +8 -2
  28. package/dist/claude/.claude/agents/debater.md +6 -0
  29. package/dist/claude/.claude/agents/discuss.md +6 -0
  30. package/dist/claude/.claude/agents/discussion.md +6 -0
  31. package/dist/claude/.claude/agents/execute.md +28 -14
  32. package/dist/claude/.claude/agents/executor.md +38 -1
  33. package/dist/claude/.claude/agents/fast.md +6 -0
  34. package/dist/claude/.claude/agents/finalize.md +61 -35
  35. package/dist/claude/.claude/agents/learner.md +29 -6
  36. package/dist/claude/.claude/agents/plan-reviewer.md +11 -0
  37. package/dist/claude/.claude/agents/plan.md +6 -0
  38. package/dist/claude/.claude/agents/research.md +8 -1
  39. package/dist/claude/.claude/agents/researcher.md +6 -0
  40. package/dist/claude/.claude/agents/review.md +28 -9
  41. package/dist/claude/.claude/agents/reviewer.md +20 -10
  42. package/dist/claude/.claude/agents/shadow-scanner.md +6 -0
  43. package/dist/claude/.claude/agents/test-writer.md +6 -0
  44. package/dist/claude/.claude/agents/triage.md +13 -18
  45. package/dist/claude/.claude/agents/verifier.md +72 -12
  46. package/dist/claude/.claude/commands/phase-plan.md +5 -1
  47. package/dist/claude/.claude/commands/todo-check.md +2 -2
  48. package/dist/claude/skills/gh-issue-triage/SKILL.md +6 -6
  49. package/dist/claude/skills/lu/SKILL.md +90 -1
  50. package/dist/claude/skills/luca-telemetry-report/SKILL.md +8 -1
  51. package/dist/claude/skills/phase-execute/SKILL.md +64 -1
  52. package/dist/claude/skills/phase-plan/SKILL.md +16 -2
  53. package/dist/claude/skills/quick/SKILL.md +1 -1
  54. package/dist/claude/skills/session-plan/SKILL.md +3 -2
  55. package/dist/claude/skills/session-resume/SKILL.md +41 -5
  56. package/dist/claude/skills/todo-add/SKILL.md +0 -23
  57. package/dist/index.mjs +4 -1
  58. package/dist/shared/luca.3OdFfCDq.mjs +114 -0
  59. package/dist/shared/{luca.BHov6l1O.mjs → luca.41dcdyd6.mjs} +33 -19
  60. package/dist/shared/luca.B-u10ROM.mjs +249 -0
  61. package/dist/shared/luca.B15qErL2.mjs +118 -0
  62. package/dist/shared/luca.BAqmbI4n.mjs +8 -0
  63. package/dist/shared/{luca.CI5IvdwC.mjs → luca.BDFQnOdW.mjs} +545 -5
  64. package/dist/shared/{luca.DbrDoFFH.mjs → luca.BKflEntw.mjs} +1 -1
  65. package/dist/shared/luca.BKsihSAK.mjs +21 -0
  66. package/dist/shared/{luca.ChU85KSu.mjs → luca.BZc5veeE.mjs} +4 -0
  67. package/dist/shared/{luca.BtwZ3m3l.mjs → luca.CbIDM-ci.mjs} +274 -94
  68. package/dist/shared/{luca.cizL1tYz.mjs → luca.DYSBzNqG.mjs} +1 -1
  69. package/dist/shared/{luca.Xq97ytct.mjs → luca.D_sXMXRZ.mjs} +4 -3
  70. package/dist/shared/luca.Dmpy9SlE.mjs +52 -0
  71. package/dist/shared/{luca.ig-qt2eC.mjs → luca.DpeRJDkV.mjs} +2 -59
  72. package/dist/shared/{luca.LySXBNNL.mjs → luca.YEUgoPFK.mjs} +2 -2
  73. package/package.json +1 -1
  74. package/dist/shared/luca.DGgeB5U5.mjs +0 -56
  75. package/dist/shared/luca.GSXJf0Tv.mjs +0 -57
@@ -1,6 +1,6 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import 'zod';
3
- import '../shared/luca.CI5IvdwC.mjs';
3
+ import '../shared/luca.BDFQnOdW.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:fs/promises';
6
6
  import 'node:path';
@@ -8,13 +8,16 @@ import 'node:crypto';
8
8
  import 'node:module';
9
9
  import 'node:url';
10
10
  import 'node:child_process';
11
- import { r as runWriteHandler, u as lucaBranchGuardTool } from '../shared/luca.BtwZ3m3l.mjs';
12
- import '../shared/luca.ig-qt2eC.mjs';
11
+ import { t as lucaBranchGuardTool } from '../shared/luca.CbIDM-ci.mjs';
12
+ import '../shared/luca.DpeRJDkV.mjs';
13
+ import { r as rejectUnknownFlags, a as runWriteHandler } from '../shared/luca.3OdFfCDq.mjs';
13
14
  import 'node:os';
14
- import '../shared/luca.GSXJf0Tv.mjs';
15
- import '../shared/luca.LySXBNNL.mjs';
16
- import '../shared/luca.DbrDoFFH.mjs';
15
+ import '../shared/luca.B15qErL2.mjs';
16
+ import '../shared/luca.YEUgoPFK.mjs';
17
+ import '../shared/luca.BKflEntw.mjs';
18
+ import '../shared/luca.BAqmbI4n.mjs';
17
19
  import '../shared/luca.CQ3g1xrD.mjs';
20
+ import '../shared/luca.Dmpy9SlE.mjs';
18
21
 
19
22
  const guardCommand = defineCommand({
20
23
  meta: {
@@ -28,7 +31,8 @@ const guardCommand = defineCommand({
28
31
  description: 'Branch name that must NOT equal the current branch (typically the repository default branch, default "main").'
29
32
  }
30
33
  },
31
- async run({ args }) {
34
+ async run({ args, rawArgs, cmd }) {
35
+ rejectUnknownFlags("branch guard", cmd, rawArgs);
32
36
  await runWriteHandler("branch guard", lucaBranchGuardTool, {
33
37
  default_branch: args["default-branch"]
34
38
  });
@@ -1,6 +1,6 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import 'zod';
3
- import '../shared/luca.CI5IvdwC.mjs';
3
+ import '../shared/luca.BDFQnOdW.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:fs/promises';
6
6
  import 'node:path';
@@ -8,13 +8,16 @@ import 'node:crypto';
8
8
  import 'node:module';
9
9
  import 'node:url';
10
10
  import 'node:child_process';
11
- import { f as readJsonPayload, r as runWriteHandler, t as lucaChecksRunTool } from '../shared/luca.BtwZ3m3l.mjs';
12
- import '../shared/luca.ig-qt2eC.mjs';
11
+ import { s as lucaChecksRunTool } from '../shared/luca.CbIDM-ci.mjs';
12
+ import '../shared/luca.DpeRJDkV.mjs';
13
+ import { r as rejectUnknownFlags, b as readJsonPayload, a as runWriteHandler } from '../shared/luca.3OdFfCDq.mjs';
13
14
  import 'node:os';
14
- import '../shared/luca.GSXJf0Tv.mjs';
15
- import '../shared/luca.LySXBNNL.mjs';
16
- import '../shared/luca.DbrDoFFH.mjs';
15
+ import '../shared/luca.B15qErL2.mjs';
16
+ import '../shared/luca.YEUgoPFK.mjs';
17
+ import '../shared/luca.BKflEntw.mjs';
18
+ import '../shared/luca.BAqmbI4n.mjs';
17
19
  import '../shared/luca.CQ3g1xrD.mjs';
20
+ import '../shared/luca.Dmpy9SlE.mjs';
18
21
 
19
22
  const runCommand = defineCommand({
20
23
  meta: {
@@ -32,7 +35,8 @@ const runCommand = defineCommand({
32
35
  description: "Per-command timeout in milliseconds (range 100-600000, default 90000). On timeout the process is killed."
33
36
  }
34
37
  },
35
- async run({ args }) {
38
+ async run({ args, rawArgs, cmd }) {
39
+ rejectUnknownFlags("checks run", cmd, rawArgs);
36
40
  const commands = await readJsonPayload("checks run", args.file);
37
41
  await runWriteHandler("checks run", lucaChecksRunTool, {
38
42
  commands,
@@ -1,420 +1,19 @@
1
1
  import { defineCommand } from 'citty';
2
2
  import { resolve } from 'pathe';
3
- import '../shared/luca.CI5IvdwC.mjs';
4
- import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
3
+ import { v as verifyFile } from '../shared/luca.BDFQnOdW.mjs';
4
+ import 'node:fs';
5
5
  import 'node:fs/promises';
6
- import { join } from 'node:path';
6
+ import 'node:path';
7
7
  import 'node:crypto';
8
8
  import 'node:module';
9
9
  import 'node:url';
10
- import { spawnSync } from 'node:child_process';
10
+ import 'node:child_process';
11
11
  import { l as logger } from '../shared/luca.dM-MKlNE.mjs';
12
+ import { s as sanitizeControlChars } from '../shared/luca.BAqmbI4n.mjs';
12
13
  import 'zod';
13
14
  import 'node:os';
14
15
  import 'consola';
15
16
 
16
- const SYMBOL_STOPWORDS = /* @__PURE__ */ new Set([
17
- "true",
18
- "false",
19
- "null",
20
- "undefined",
21
- "void",
22
- "any",
23
- "unknown",
24
- "never",
25
- "string",
26
- "number",
27
- "boolean",
28
- "object",
29
- "array",
30
- "bun",
31
- "npm",
32
- "pnpm",
33
- "yarn",
34
- "git",
35
- "gh",
36
- "pr",
37
- "ci",
38
- "cd",
39
- "repo",
40
- "main",
41
- "master",
42
- "dev",
43
- "build",
44
- "test",
45
- "lint",
46
- "tsc",
47
- "eslint",
48
- "todo",
49
- "fixme",
50
- "note",
51
- "warn",
52
- "info",
53
- "log",
54
- "http",
55
- "https",
56
- "json",
57
- "yaml",
58
- "toml",
59
- "env",
60
- "src",
61
- "dist",
62
- "lib",
63
- "app",
64
- "apps"
65
- ]);
66
- const COUNTABLE_NOUNS = /* @__PURE__ */ new Set([
67
- "file",
68
- "files",
69
- "test",
70
- "tests",
71
- "table",
72
- "tables",
73
- "index",
74
- "indexes",
75
- "indices",
76
- "function",
77
- "functions",
78
- "endpoint",
79
- "endpoints",
80
- "route",
81
- "routes",
82
- "mutation",
83
- "mutations",
84
- "query",
85
- "queries",
86
- "action",
87
- "actions",
88
- "tool",
89
- "tools",
90
- "mode",
91
- "modes",
92
- "phase",
93
- "phases",
94
- "wave",
95
- "waves",
96
- "commit",
97
- "commits",
98
- "package",
99
- "packages",
100
- "module",
101
- "modules",
102
- "class",
103
- "classes",
104
- "method",
105
- "methods",
106
- "component",
107
- "components",
108
- "hook",
109
- "hooks"
110
- ]);
111
- function singularize(noun) {
112
- const lower = noun.toLowerCase();
113
- if (lower === "indices") return "index";
114
- if (lower === "queries") return "query";
115
- if (lower === "classes") return "class";
116
- if (lower === "indexes") return "index";
117
- if (lower.endsWith("ies") && lower.length > 3) {
118
- return `${lower.slice(0, -3)}y`;
119
- }
120
- if (lower.endsWith("s") && !lower.endsWith("ss")) return lower.slice(0, -1);
121
- return lower;
122
- }
123
- const BACKTICK_RE = /`([^`\n]+)`/g;
124
- const FILE_PATH_RE = /(?:^|[\s(`'"])((?:packages|src|apps|lib|tests?|docs?|\.planning|\.luca|\.changeset|\.github)\/[\w./-]+\.[\w]+)(?=[\s)`'".,;!?]|$)/g;
125
- const QUANTITATIVE_RE = /\b(\d+)\s+([a-zA-Z]+)\b/g;
126
- const IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
127
- function extractClaims(text) {
128
- if (!text) return [];
129
- const lines = text.split("\n");
130
- const seen = /* @__PURE__ */ new Set();
131
- const claims = [];
132
- const pushIfNew = (key, claim) => {
133
- if (seen.has(key)) return;
134
- seen.add(key);
135
- claims.push(claim);
136
- };
137
- for (let i = 0; i < lines.length; i++) {
138
- const line = lines[i] ?? "";
139
- const sourceLine = i + 1;
140
- const sourceContext = line.trim().slice(0, 240);
141
- for (const match of line.matchAll(BACKTICK_RE)) {
142
- const inner = (match[1] ?? "").trim();
143
- if (inner.includes("/") && /\.\w+(?:[#?:]|$)/.test(inner)) continue;
144
- if (!IDENTIFIER_RE.test(inner)) continue;
145
- if (inner.length < 3) continue;
146
- if (SYMBOL_STOPWORDS.has(inner.toLowerCase())) continue;
147
- pushIfNew(`symbol:${inner}`, {
148
- type: "symbol",
149
- text: match[0],
150
- identifier: inner,
151
- sourceLine,
152
- sourceContext
153
- });
154
- }
155
- for (const match of line.matchAll(FILE_PATH_RE)) {
156
- const path = match[1];
157
- if (!path) continue;
158
- pushIfNew(`path:${path}`, {
159
- type: "file-path",
160
- text: path,
161
- path,
162
- sourceLine,
163
- sourceContext
164
- });
165
- }
166
- for (const match of line.matchAll(QUANTITATIVE_RE)) {
167
- const numStr = match[1];
168
- const noun = match[2];
169
- if (!numStr || !noun) continue;
170
- if (numStr.length === 4) {
171
- const n = Number(numStr);
172
- if (n >= 1900 && n <= 2200) continue;
173
- }
174
- const lowerNoun = noun.toLowerCase();
175
- if (!COUNTABLE_NOUNS.has(lowerNoun)) continue;
176
- const num = Number(numStr);
177
- if (!Number.isFinite(num)) continue;
178
- pushIfNew(`count:${num}:${lowerNoun}`, {
179
- type: "quantitative",
180
- text: `${numStr} ${noun}`,
181
- number: num,
182
- noun: singularize(lowerNoun),
183
- sourceLine,
184
- sourceContext
185
- });
186
- }
187
- }
188
- return claims;
189
- }
190
- function gitAvailable(repoRoot) {
191
- const r = spawnSync(
192
- "git",
193
- ["-C", repoRoot, "rev-parse", "--is-inside-work-tree"],
194
- { encoding: "utf-8" }
195
- );
196
- return r.status === 0;
197
- }
198
- function gitGrepFiles(repoRoot, needle, timeoutMs) {
199
- const r = spawnSync(
200
- "git",
201
- ["-C", repoRoot, "grep", "--untracked", "-l", "--fixed-strings", needle],
202
- { encoding: "utf-8", timeout: timeoutMs }
203
- );
204
- if (r.signal === "SIGTERM" || r.error?.message?.includes("ETIMEDOUT")) {
205
- return { ok: false, matchedFiles: [], timedOut: true };
206
- }
207
- if (r.status === 1) return { ok: true, matchedFiles: [], timedOut: false };
208
- if (r.status !== 0) return { ok: false, matchedFiles: [], timedOut: false };
209
- const matchedFiles = (r.stdout ?? "").split("\n").map((s) => s.trim()).filter(Boolean);
210
- return { ok: true, matchedFiles, timedOut: false };
211
- }
212
- const FS_SKIP_DIRS = /* @__PURE__ */ new Set([
213
- "node_modules",
214
- ".git",
215
- "dist",
216
- "build",
217
- ".next",
218
- ".turbo",
219
- "coverage",
220
- ".cache"
221
- ]);
222
- const FS_SKIP_EXTENSIONS = /* @__PURE__ */ new Set([
223
- ".png",
224
- ".jpg",
225
- ".jpeg",
226
- ".gif",
227
- ".webp",
228
- ".ico",
229
- ".pdf",
230
- ".zip",
231
- ".tar",
232
- ".gz",
233
- ".lock",
234
- ".lockb"
235
- ]);
236
- const FS_MAX_FILE_BYTES = 5 * 1024 * 1024;
237
- function fsGrepFiles(repoRoot, needle, timeoutMs) {
238
- const start = Date.now();
239
- const matchedFiles = [];
240
- const stack = [repoRoot];
241
- while (stack.length > 0) {
242
- if (Date.now() - start > timeoutMs) {
243
- return { ok: false, matchedFiles, timedOut: true };
244
- }
245
- const dir = stack.pop();
246
- if (dir === void 0) break;
247
- let entries;
248
- try {
249
- entries = readdirSync(dir);
250
- } catch {
251
- continue;
252
- }
253
- for (const name of entries) {
254
- const full = join(dir, name);
255
- let info;
256
- try {
257
- info = statSync(full);
258
- } catch {
259
- continue;
260
- }
261
- if (info.isDirectory()) {
262
- if (FS_SKIP_DIRS.has(name)) continue;
263
- stack.push(full);
264
- continue;
265
- }
266
- if (!info.isFile()) continue;
267
- const dotIdx = name.lastIndexOf(".");
268
- if (dotIdx >= 0) {
269
- const ext = name.slice(dotIdx).toLowerCase();
270
- if (FS_SKIP_EXTENSIONS.has(ext)) continue;
271
- }
272
- if (info.size > FS_MAX_FILE_BYTES) continue;
273
- try {
274
- if (readFileSync(full).includes(needle)) {
275
- matchedFiles.push(full);
276
- }
277
- } catch {
278
- }
279
- }
280
- }
281
- return { ok: true, matchedFiles, timedOut: false };
282
- }
283
- function searchFiles(repoRoot, needle, timeoutMs, useGit) {
284
- return useGit ? gitGrepFiles(repoRoot, needle, timeoutMs) : fsGrepFiles(repoRoot, needle, timeoutMs);
285
- }
286
- function verifyClaims(claims, opts) {
287
- const totalBudgetMs = opts.totalBudgetMs ?? 3e4;
288
- const perClaimBudgetMs = opts.perClaimBudgetMs ?? 5e3;
289
- const useGit = gitAvailable(opts.repoRoot);
290
- const start = Date.now();
291
- const failures = [];
292
- let timedOut = false;
293
- const breakdown = { symbols: 0, filePaths: 0, quantitative: 0 };
294
- for (const claim of claims) {
295
- if (claim.type === "symbol") breakdown.symbols++;
296
- else if (claim.type === "file-path") breakdown.filePaths++;
297
- else breakdown.quantitative++;
298
- if (Date.now() - start > totalBudgetMs) {
299
- timedOut = true;
300
- failures.push({
301
- claim,
302
- reason: "timeout",
303
- evidence: `Total budget ${totalBudgetMs}ms exhausted before this claim was checked.`
304
- });
305
- continue;
306
- }
307
- if (claim.type === "symbol" && claim.identifier) {
308
- const r = searchFiles(
309
- opts.repoRoot,
310
- claim.identifier,
311
- perClaimBudgetMs,
312
- useGit
313
- );
314
- if (r.timedOut) {
315
- timedOut = true;
316
- failures.push({
317
- claim,
318
- reason: "timeout",
319
- evidence: `Search for symbol "${claim.identifier}" exceeded ${perClaimBudgetMs}ms.`
320
- });
321
- continue;
322
- }
323
- if (!r.ok || r.matchedFiles.length === 0) {
324
- failures.push({
325
- claim,
326
- reason: "symbol-not-found",
327
- evidence: `git grep for "${claim.identifier}" returned 0 hits.`
328
- });
329
- }
330
- continue;
331
- }
332
- if (claim.type === "file-path" && claim.path) {
333
- const abs = join(opts.repoRoot, claim.path);
334
- if (!existsSync(abs)) {
335
- failures.push({
336
- claim,
337
- reason: "file-not-found",
338
- evidence: `Path "${claim.path}" does not exist (resolved to ${abs}).`
339
- });
340
- }
341
- continue;
342
- }
343
- if (claim.type === "quantitative" && claim.noun && typeof claim.number === "number") {
344
- const r = searchFiles(
345
- opts.repoRoot,
346
- claim.noun,
347
- perClaimBudgetMs,
348
- useGit
349
- );
350
- if (r.timedOut) {
351
- timedOut = true;
352
- failures.push({
353
- claim,
354
- reason: "timeout",
355
- evidence: `Search for noun "${claim.noun}" exceeded ${perClaimBudgetMs}ms.`
356
- });
357
- continue;
358
- }
359
- if (!r.ok) {
360
- failures.push({
361
- claim,
362
- reason: "count-mismatch",
363
- evidence: `grep for "${claim.noun}" failed; cannot verify "${claim.text}".`
364
- });
365
- continue;
366
- }
367
- const found = r.matchedFiles.length;
368
- if (Math.abs(found - claim.number) > 1) {
369
- failures.push({
370
- claim,
371
- reason: "count-mismatch",
372
- evidence: `Claim says "${claim.text}", repo has ${found} file(s) mentioning "${claim.noun}" (tolerance \xB11).`
373
- });
374
- }
375
- continue;
376
- }
377
- }
378
- return {
379
- passed: failures.length === 0,
380
- totalClaims: claims.length,
381
- failures,
382
- extractedBreakdown: breakdown,
383
- timedOut
384
- };
385
- }
386
- function verifyTextArtifact(text, opts) {
387
- return verifyClaims(extractClaims(text), opts);
388
- }
389
- function verifyFile(filePath, opts) {
390
- let text;
391
- try {
392
- text = readFileSync(filePath, "utf-8");
393
- } catch (err) {
394
- const msg = err instanceof Error ? err.message : String(err);
395
- return {
396
- passed: false,
397
- totalClaims: 0,
398
- failures: [
399
- {
400
- claim: {
401
- type: "file-path",
402
- text: filePath,
403
- path: filePath,
404
- sourceLine: 0,
405
- sourceContext: ""
406
- },
407
- reason: "artifact-unreadable",
408
- evidence: `Could not read "${filePath}": ${msg}`
409
- }
410
- ],
411
- extractedBreakdown: { symbols: 0, filePaths: 0, quantitative: 0 },
412
- timedOut: false
413
- };
414
- }
415
- return verifyTextArtifact(text, opts);
416
- }
417
-
418
17
  const claimVerifyCommand = defineCommand({
419
18
  meta: {
420
19
  name: "claim-verify",
@@ -446,8 +45,14 @@ const claimVerifyCommand = defineCommand({
446
45
  for (const failure of report.failures) {
447
46
  const loc = failure.claim.sourceLine > 0 ? ` (line ${failure.claim.sourceLine})` : "";
448
47
  logger.error(
449
- `${failure.reason}: ${failure.claim.text}${loc}
450
- ${failure.evidence}`
48
+ `${failure.reason}: ${sanitizeControlChars(failure.claim.text)}${loc}
49
+ ${sanitizeControlChars(failure.evidence)}`
50
+ );
51
+ }
52
+ for (const warning of report.forbiddenLanguage) {
53
+ logger.warn(
54
+ `forbidden-language: "${warning.phrase}" (line ${warning.sourceLine}) has no nearby probe evidence
55
+ ${sanitizeControlChars(warning.sourceContext)}`
451
56
  );
452
57
  }
453
58
  if (report.passed) {
@@ -1,5 +1,5 @@
1
1
  import { defineCommand } from 'citty';
2
- import { C as ClassifyComplexityInputSchema } from '../shared/luca.CI5IvdwC.mjs';
2
+ import { C as ClassifyComplexityInputSchema } from '../shared/luca.BDFQnOdW.mjs';
3
3
  import 'node:fs';
4
4
  import 'node:fs/promises';
5
5
  import 'node:path';
@@ -1,6 +1,6 @@
1
1
  import { defineCommand } from 'citty';
2
- import '../shared/luca.CI5IvdwC.mjs';
3
- import { l as loadCurrentState, r as resolveActiveSlug } from '../shared/luca.GSXJf0Tv.mjs';
2
+ import '../shared/luca.BDFQnOdW.mjs';
3
+ import { l as loadCurrentState, r as resolveActiveSlug } from '../shared/luca.B15qErL2.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:fs/promises';
6
6
  import 'node:path';
@@ -8,15 +8,18 @@ import 'node:crypto';
8
8
  import 'node:module';
9
9
  import 'node:url';
10
10
  import 'node:child_process';
11
- import { r as readConfidenceJournal, b as renderConfidenceJournalMarkdown, g as getConfidenceSummary } from '../shared/luca.LySXBNNL.mjs';
11
+ import { r as readConfidenceJournal, b as renderConfidenceJournalMarkdown, g as getConfidenceSummary } from '../shared/luca.YEUgoPFK.mjs';
12
12
  import { l as logger } from '../shared/luca.dM-MKlNE.mjs';
13
13
  import 'zod';
14
- import { f as readJsonPayload, r as runWriteHandler, w as lucaConfidenceLogTool } from '../shared/luca.BtwZ3m3l.mjs';
15
- import '../shared/luca.ig-qt2eC.mjs';
14
+ import { v as lucaConfidenceLogTool } from '../shared/luca.CbIDM-ci.mjs';
15
+ import '../shared/luca.DpeRJDkV.mjs';
16
+ import { r as rejectUnknownFlags, b as readJsonPayload, a as runWriteHandler } from '../shared/luca.3OdFfCDq.mjs';
16
17
  import 'node:os';
17
- import '../shared/luca.DbrDoFFH.mjs';
18
+ import '../shared/luca.BKflEntw.mjs';
18
19
  import 'consola';
20
+ import '../shared/luca.BAqmbI4n.mjs';
19
21
  import '../shared/luca.CQ3g1xrD.mjs';
22
+ import '../shared/luca.Dmpy9SlE.mjs';
20
23
 
21
24
  function selectConfidenceGateActions(entries) {
22
25
  const auto = [];
@@ -128,7 +131,8 @@ const logCommand = defineCommand({
128
131
  description: "Optional. Explicit gate routing override: auto | research | ask. Overrides confidence-derived bucketing when set."
129
132
  }
130
133
  },
131
- async run({ args }) {
134
+ async run({ args, rawArgs, cmd }) {
135
+ rejectUnknownFlags("confidence log", cmd, rawArgs);
132
136
  let payload;
133
137
  if (args.file) {
134
138
  const fromFile = await readJsonPayload("confidence log", args.file);
@@ -181,7 +185,8 @@ const readCommand = defineCommand({
181
185
  description: "Phase slug to read (default: the active phase)."
182
186
  }
183
187
  },
184
- async run({ args }) {
188
+ async run({ args, rawArgs, cmd }) {
189
+ rejectUnknownFlags("confidence read", cmd, rawArgs);
185
190
  const cwd = process.cwd();
186
191
  const slug = await resolveSlug({ explicit: args.slug, cwd });
187
192
  const entries = readConfidenceJournal({ cwd, slug });
@@ -200,7 +205,8 @@ const summaryCommand = defineCommand({
200
205
  description: "Phase slug to summarize (default: the active phase)."
201
206
  }
202
207
  },
203
- async run({ args }) {
208
+ async run({ args, rawArgs, cmd }) {
209
+ rejectUnknownFlags("confidence summary", cmd, rawArgs);
204
210
  const cwd = process.cwd();
205
211
  const slug = await resolveSlug({ explicit: args.slug, cwd });
206
212
  const summary = getConfidenceSummary(
@@ -221,7 +227,8 @@ const renderCommand = defineCommand({
221
227
  description: "Phase slug to render (default: the active phase)."
222
228
  }
223
229
  },
224
- async run({ args }) {
230
+ async run({ args, rawArgs, cmd }) {
231
+ rejectUnknownFlags("confidence render", cmd, rawArgs);
225
232
  const cwd = process.cwd();
226
233
  const slug = await resolveSlug({ explicit: args.slug, cwd });
227
234
  const md = renderConfidenceJournalMarkdown(
@@ -242,7 +249,8 @@ const gateCommand = defineCommand({
242
249
  description: "Phase slug (defaults to active phase)."
243
250
  }
244
251
  },
245
- async run({ args }) {
252
+ async run({ args, rawArgs, cmd }) {
253
+ rejectUnknownFlags("confidence gate", cmd, rawArgs);
246
254
  const cwd = process.cwd();
247
255
  const slug = await resolveSlug({ explicit: args.slug, cwd });
248
256
  const actions = selectConfidenceGateActions(
@@ -1,16 +1,16 @@
1
1
  import { defineCommand } from 'citty';
2
- import { c as coarsePhaseOf, t as toLucaRelative, a as classifyWritePath, b as TMP_PATH_PATTERN, A as AUDIT_PATH_PATTERN, W as WAVE_FILE_RE } from '../shared/luca.CI5IvdwC.mjs';
3
- import { l as loadCurrentState, r as resolveActiveSlug } from '../shared/luca.GSXJf0Tv.mjs';
2
+ import { c as coarsePhaseOf, t as toLucaRelative, a as classifyWritePath, b as TMP_PATH_PATTERN, A as AUDIT_PATH_PATTERN, W as WAVE_FILE_RE } from '../shared/luca.BDFQnOdW.mjs';
3
+ import { l as loadCurrentState, S as STEP_ARTIFACTS, r as resolveActiveSlug } from '../shared/luca.B15qErL2.mjs';
4
4
  import 'node:fs';
5
5
  import 'node:fs/promises';
6
6
  import 'node:path';
7
- import { l as lucaStateClaimOwnerTool, S as STEP_ARTIFACTS } from '../shared/luca.ig-qt2eC.mjs';
8
- import { p as phasePathFor } from '../shared/luca.DbrDoFFH.mjs';
7
+ import { p as phasePathFor } from '../shared/luca.BKflEntw.mjs';
9
8
  import 'node:crypto';
10
9
  import 'node:module';
11
10
  import 'node:url';
12
11
  import 'node:child_process';
13
12
  import { parse } from 'shell-quote';
13
+ import { l as lucaStateClaimOwnerTool } from '../shared/luca.DpeRJDkV.mjs';
14
14
  import 'zod';
15
15
  import 'node:os';
16
16
 
@@ -234,7 +234,8 @@ const LUCA_READ_VERBS = /* @__PURE__ */ new Set([
234
234
  "aggregate",
235
235
  "filter-stale",
236
236
  "detect-convergence",
237
- "regression-check"
237
+ "regression-check",
238
+ "lint"
238
239
  ]);
239
240
  const LUCA_NOUN_VERBS = {
240
241
  state: /* @__PURE__ */ new Set(["read", "advance", "claim-owner", "set-current-phase"]),
@@ -253,7 +254,9 @@ const LUCA_NOUN_VERBS = {
253
254
  workflow: /* @__PURE__ */ new Set(["reset"]),
254
255
  confidence: /* @__PURE__ */ new Set(["log"]),
255
256
  // Read-side surfaces over the per-phase verify.json files.
256
- verification: /* @__PURE__ */ new Set(["read", "aggregate"])
257
+ verification: /* @__PURE__ */ new Set(["read", "aggregate"]),
258
+ // Read-only plan-quality linter over a plan.md file.
259
+ plan: /* @__PURE__ */ new Set(["lint"])
257
260
  };
258
261
  function classifyLucaCommand(rest) {
259
262
  if (rest.some((t) => ["--help", "-h", "--version"].includes(t))) {
@@ -590,10 +593,10 @@ async function handleStageGateHook(opts) {
590
593
  }
591
594
  let category;
592
595
  let pathBlockReason;
593
- if (toolName === "Edit" || toolName === "Write" || toolName === "NotebookEdit") {
594
- const targetPath = toolInput?.file_path;
596
+ if (toolName === "Edit" || toolName === "Write" || toolName === "NotebookEdit" || toolName === "replace" || toolName === "write_file") {
597
+ const targetPath = toolInput?.file_path ?? toolInput?.path;
595
598
  if (!targetPath) {
596
- log(`stage-gate: ${toolName} without file_path \u2014 allowing`);
599
+ log(`stage-gate: ${toolName} without file_path/path \u2014 allowing`);
597
600
  return { exitCode: 0, toolName, toolInput, decision: "allow" };
598
601
  }
599
602
  const relTarget = toLucaRelative(targetPath, cwd);
@@ -620,7 +623,7 @@ async function handleStageGateHook(opts) {
620
623
  } else {
621
624
  category = pathClassToToolCategory(pc.class);
622
625
  }
623
- } else if (toolName === "Bash") {
626
+ } else if (toolName === "Bash" || toolName === "run_shell_command" || toolName === "run_command") {
624
627
  const command = toolInput?.command ?? "";
625
628
  const bashResult = classifyBashCommand(command);
626
629
  if (bashResult.category === "denied") {