@massa-ai/cursor-plugin 1.21.0 → 1.22.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 (41) hide show
  1. package/.cursor-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/skills/massa-ai/references/evidence-gate.md +1 -1
  4. package/skills/massa-ai/references/hook-enforcement.md +2 -2
  5. package/skills/massa-ai/references/implementation-delivery.md +3 -3
  6. package/skills/massa-ai/references/lessons.md +9 -10
  7. package/skills/massa-ai/references/mcp-tools.md +1 -1
  8. package/skills/massa-ai/references/project-context.md +1 -1
  9. package/skills/massa-ai/references/spec-driven/artifact-store.md +7 -8
  10. package/skills/massa-ai/references/spec-driven/design.md +1 -1
  11. package/skills/massa-ai/references/spec-driven/execute.md +5 -5
  12. package/skills/massa-ai/references/spec-driven/specify.md +6 -6
  13. package/skills/massa-ai/references/spec-driven/sub-agents.md +1 -1
  14. package/skills/massa-ai/references/spec-driven/tasks.md +2 -2
  15. package/skills/massa-ai/references/spec-driven/validate.md +3 -3
  16. package/skills/massa-ai/scripts/check_commit.ts +231 -0
  17. package/skills/massa-ai/scripts/check_specs_delivered.ts +209 -0
  18. package/skills/massa-ai/scripts/lessons.ts +907 -0
  19. package/skills/massa-ai/scripts/validate_spec.ts +413 -0
  20. package/skills/massa-ai/scripts/validate_state.ts +276 -0
  21. package/skills/massa-ai/scripts/validate_tasks.ts +498 -0
  22. package/skills/massa-ai/workflows/architecture/architecture-fix.md +1 -1
  23. package/skills/massa-ai/workflows/bugs/bugs-fix.md +1 -1
  24. package/skills/massa-ai/workflows/code-quality/code-quality-fix.md +1 -1
  25. package/skills/massa-ai/workflows/debug.md +1 -1
  26. package/skills/massa-ai/workflows/feature.md +1 -1
  27. package/skills/massa-ai/workflows/general.md +2 -2
  28. package/skills/massa-ai/workflows/implementation/implementation-fix.md +1 -1
  29. package/skills/massa-ai/workflows/maestro/maestro-fix.md +1 -1
  30. package/skills/massa-ai/workflows/mobile-figma/mobile-figma-fix.md +1 -1
  31. package/skills/massa-ai/workflows/refactor.md +1 -1
  32. package/skills/massa-ai/workflows/requirements/requirements-fix.md +1 -1
  33. package/skills/massa-ai/workflows/security/security-fix.md +1 -1
  34. package/skills/massa-ai/workflows/spec-driven.md +3 -3
  35. package/skills/massa-ai/workflows/tests/tests-fix.md +1 -1
  36. package/skills/massa-ai/scripts/check_commit.py +0 -128
  37. package/skills/massa-ai/scripts/check_specs_delivered.py +0 -137
  38. package/skills/massa-ai/scripts/lessons.py +0 -630
  39. package/skills/massa-ai/scripts/validate_spec.py +0 -272
  40. package/skills/massa-ai/scripts/validate_state.py +0 -183
  41. package/skills/massa-ai/scripts/validate_tasks.py +0 -302
@@ -0,0 +1,498 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * validate_tasks.ts - deterministic pre-approval checks for a feature tasks.md.
4
+ *
5
+ * Turns the three pre-approval checks (task granularity, diagram-vs-definition
6
+ * cross-check, test co-location) into a checkable pass/fail run BEFORE tasks are
7
+ * presented for approval, instead of trusting the model to build the tables by
8
+ * hand. Bun builtins only, zero dependencies. Operates only on the tasks.md
9
+ * markdown artifact, so it is stack-agnostic and tool-agnostic.
10
+ *
11
+ * What it checks (heuristic markdown inspection, not a full parser):
12
+ * ERROR - a required section is missing
13
+ * ERROR - a task is missing its `Tests` or `Gate` field
14
+ * ERROR - a task depends on a task in a LATER phase (dependencies point back only)
15
+ * ERROR - a dependency edge shown in the diagram has no matching `Depends on`
16
+ * (and vice-versa) when both sides are parseable
17
+ * WARN - a task's `Where` names multiple files (granularity smell -> split it)
18
+ * WARN - a task says `Tests: none` (confirm the coverage matrix agrees)
19
+ * WARN - the diagram could not be parsed confidently (cross-check skipped)
20
+ *
21
+ * Usage:
22
+ * bun skills/massa-ai/scripts/validate_tasks.ts [target] [--root DIR] [--strict]
23
+ *
24
+ * Invoke with the repo-root-relative script path shown above (matches
25
+ * lessons.ts's convention), not a project-local copy.
26
+ * target Path to a tasks.md, a feature directory, or a project root.
27
+ * Omitted -> auto-detect the single feature under <root>/.specs/features/.
28
+ * --root Project root that contains .specs/ (default: current dir).
29
+ * --strict Treat warnings as errors.
30
+ *
31
+ * Exit codes: 0 pass, 1 errors found (or warnings under --strict), 2 usage error.
32
+ */
33
+
34
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
35
+
36
+ const REQUIRED_SECTIONS = ["Test Coverage Matrix", "Gate Check Commands", "Execution Plan", "Task Breakdown"];
37
+ // Task ids are `T<n>` plus optional letter prefix (`FT3` for fix tasks): an
38
+ // unrecognized `### FT3:` header would fold its fields into the previous task's
39
+ // record and misreport e.g. a self-dependency (IT2-01).
40
+ const TASK_RE = /^#{2,4}\s+([A-Z]*T\d+)\s*:/i;
41
+ const FILE_HINT_RE_SRC = "[\\w./-]+\\.\\w{1,6}\\b";
42
+ const TASK_BREAKDOWN_RE = /^#{1,4}\s+Task Breakdown\b/i;
43
+ const PHASE_HEADING_RE = /^#{2,4}\s+Phase\s+(\d+)/i;
44
+ const DEPENDS_RE = /^\*{0,2}Depends on\*{0,2}\s*:\s*(.*)$/i;
45
+ const WHERE_RE = /^\*{0,2}Where\*{0,2}\s*:\s*(.*)$/i;
46
+ const TESTS_RE = /^\*{0,2}Tests\*{0,2}\s*:\s*(.*)$/i;
47
+ const GATE_RE = /^\*{0,2}Gate\*{0,2}\s*:\s*(.*)$/i;
48
+
49
+ function isFile(p: string): boolean {
50
+ return existsSync(p) && statSync(p).isFile();
51
+ }
52
+
53
+ function isDir(p: string): boolean {
54
+ return existsSync(p) && statSync(p).isDirectory();
55
+ }
56
+
57
+ /**
58
+ * Mirrors Python's os.path.join(): unlike node:path's join(), it does NOT
59
+ * normalize away a leading "." segment (os.path.join(".", "a") === "./a",
60
+ * node's join(".", "a") === "a") - divergence risk since this script's
61
+ * default --root "." is never abspath()'d.
62
+ */
63
+ function pyJoin(...parts: string[]): string {
64
+ let result = parts[0] ?? "";
65
+ for (let i = 1; i < parts.length; i++) {
66
+ const part = parts[i]!;
67
+ if (part.startsWith("/")) {
68
+ result = part;
69
+ } else if (result === "" || result.endsWith("/")) {
70
+ result += part;
71
+ } else {
72
+ result += `/${part}`;
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+
78
+ /** Mirrors Python's str.splitlines(): universal newline split, no trailing empty element. */
79
+ function splitLines(text: string): string[] {
80
+ if (text === "") return [];
81
+ const result: string[] = [];
82
+ const lineBreakRe = /\r\n|\r|\n/g;
83
+ let start = 0;
84
+ let match: RegExpExecArray | null;
85
+ while ((match = lineBreakRe.exec(text)) !== null) {
86
+ result.push(text.slice(start, match.index));
87
+ start = match.index + match[0].length;
88
+ }
89
+ if (start < text.length) {
90
+ result.push(text.slice(start));
91
+ }
92
+ return result;
93
+ }
94
+
95
+ function escapeRegExp(s: string): string {
96
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
97
+ }
98
+
99
+ /** Mirrors Python's repr() for plain-text strings (single-quoted, backslash/quote/control escapes). */
100
+ function pyRepr(s: string): string {
101
+ const hasSingle = s.includes("'");
102
+ const hasDouble = s.includes('"');
103
+ const quote = hasSingle && !hasDouble ? '"' : "'";
104
+ let out = quote;
105
+ for (const ch of s) {
106
+ if (ch === "\\") out += "\\\\";
107
+ else if (ch === quote) out += "\\" + quote;
108
+ else if (ch === "\n") out += "\\n";
109
+ else if (ch === "\r") out += "\\r";
110
+ else if (ch === "\t") out += "\\t";
111
+ else {
112
+ const code = ch.codePointAt(0)!;
113
+ if (code < 0x20 || code === 0x7f) {
114
+ out += "\\x" + code.toString(16).padStart(2, "0");
115
+ } else {
116
+ out += ch;
117
+ }
118
+ }
119
+ }
120
+ out += quote;
121
+ return out;
122
+ }
123
+
124
+ /** Mirrors Python's `str(sorted(set(list)))` (a list repr: ['a', 'b']). */
125
+ function pyListRepr(items: string[]): string {
126
+ return `[${items.map(pyRepr).join(", ")}]`;
127
+ }
128
+
129
+ function findAllEdges(text: string): string[] {
130
+ const re = /\b[A-Z]*T\d+\b/gi;
131
+ const out: string[] = [];
132
+ let m: RegExpExecArray | null;
133
+ while ((m = re.exec(text)) !== null) {
134
+ out.push(m[0]);
135
+ }
136
+ return out;
137
+ }
138
+
139
+ function findAllFileHints(text: string): string[] {
140
+ const re = new RegExp(FILE_HINT_RE_SRC, "g");
141
+ const out: string[] = [];
142
+ let m: RegExpExecArray | null;
143
+ while ((m = re.exec(text)) !== null) {
144
+ out.push(m[0]);
145
+ }
146
+ return out;
147
+ }
148
+
149
+ function autodetect(root: string): string | null {
150
+ const base = pyJoin(root, ".specs", "features");
151
+ if (!isDir(base)) return null;
152
+ const features = readdirSync(base)
153
+ .filter((d) => isFile(pyJoin(base, d, "tasks.md")))
154
+ .sort();
155
+ if (features.length === 1) return pyJoin(base, features[0]!, "tasks.md");
156
+ if (features.length === 0) return null;
157
+ const joined = features.map((f) => pyJoin(base, f, "tasks.md")).join("\n ");
158
+ console.error(`validate_tasks: multiple features found; pass one explicitly:\n ${joined}`);
159
+ process.exit(1);
160
+ }
161
+
162
+ function resolveTasks(target: string | null, root: string): string | null {
163
+ if (target) {
164
+ if (isFile(target)) return target;
165
+ if (isDir(target)) {
166
+ const cand = pyJoin(target, "tasks.md");
167
+ if (isFile(cand)) return cand;
168
+ return autodetect(target);
169
+ }
170
+ // Not a path: treat as a feature name under <root>/.specs/features/<name>/
171
+ const cand = pyJoin(root, ".specs", "features", target, "tasks.md");
172
+ if (isFile(cand)) return cand;
173
+ return null;
174
+ }
175
+ return autodetect(root);
176
+ }
177
+
178
+ function sectionPresent(lines: string[], name: string): boolean {
179
+ const re = new RegExp(`^#{1,4}\\s+${escapeRegExp(name)}\\b`);
180
+ return lines.some((ln) => re.test(ln.trim()));
181
+ }
182
+
183
+ interface TaskRecord {
184
+ deps: Set<string>;
185
+ tests: string | null;
186
+ gate: string | null;
187
+ where: string;
188
+ }
189
+
190
+ /** Return a map: task_id -> {deps, tests, gate, where}. */
191
+ function parseTasks(lines: string[]): Map<string, TaskRecord> {
192
+ const tasks = new Map<string, TaskRecord>();
193
+ let current: string | null = null;
194
+ for (const ln of lines) {
195
+ const stripped = ln.trim();
196
+ const m = TASK_RE.exec(stripped);
197
+ if (m) {
198
+ current = m[1]!.toUpperCase();
199
+ tasks.set(current, { deps: new Set(), tests: null, gate: null, where: "" });
200
+ continue;
201
+ }
202
+ if (current === null) continue;
203
+ const record = tasks.get(current)!;
204
+ const dm = DEPENDS_RE.exec(stripped);
205
+ if (dm) {
206
+ const body = dm[1]!;
207
+ if (!body.toLowerCase().includes("none")) {
208
+ for (const e of findAllEdges(body.toUpperCase())) {
209
+ record.deps.add(e);
210
+ }
211
+ }
212
+ }
213
+ const wm = WHERE_RE.exec(stripped);
214
+ if (wm) {
215
+ record.where = wm[1]!;
216
+ }
217
+ const tm = TESTS_RE.exec(stripped);
218
+ if (tm) {
219
+ record.tests = tm[1]!.trim();
220
+ }
221
+ const gm = GATE_RE.exec(stripped);
222
+ if (gm) {
223
+ record.gate = gm[1]!.trim();
224
+ }
225
+ }
226
+ return tasks;
227
+ }
228
+
229
+ /**
230
+ * Map task_id -> phase index, read from '### Phase N' headers.
231
+ *
232
+ * massa-ai patch (beyond D1): the reference tasks.md template (and TLC's own)
233
+ * puts the Execution Plan (phase headers + a diagram/list of the task IDs in
234
+ * each phase) BEFORE the separate Task Breakdown section (a flat list of
235
+ * "### Tn:" task headers with no phase sub-headers). Upstream mapped a task
236
+ * to a phase only by which "### Tn:" HEADER line followed the most-recently-
237
+ * seen "### Phase N" header while scanning the WHOLE file - so with that
238
+ * template shape every task header in Task Breakdown inherits the LAST phase
239
+ * index left over from Execution Plan, and the forward-phase-dependency
240
+ * check (SYNC-01 AC2) never fires. Confirmed against this feature's own
241
+ * tasks.md (whose Phase headers happen to sit directly inside Task
242
+ * Breakdown, immediately before their tasks) still passing, and a
243
+ * template-shaped fixture then found 0/18 forward-phase violations
244
+ * detectable when it should catch a deliberately-introduced one.
245
+ *
246
+ * Fix: read membership from the Execution Plan's diagram/plain-list content
247
+ * (bare `Tn` tokens under a `### Phase N` heading) as the authoritative
248
+ * signal there, and fall back to the header-based signal (setdefault-style,
249
+ * never overwriting) once "## Task Breakdown" is reached - which is also
250
+ * exactly what the ORIGINAL algorithm already got right for tasks.md files
251
+ * (like this feature's own) that interleave phase headers directly inside
252
+ * Task Breakdown. Diagram-style scanning is deliberately NOT applied inside
253
+ * Task Breakdown, preserving the original comment's concern: a `Depends on:`
254
+ * line inside one task's block often names a task from an EARLIER phase and
255
+ * must never be misattributed to the current phase.
256
+ */
257
+ function parsePhaseMembership(lines: string[]): Map<string, number> {
258
+ const membership = new Map<string, number>();
259
+ let phaseIdx = 0;
260
+ let inPhase = false;
261
+ let inTaskBreakdown = false;
262
+ for (const ln of lines) {
263
+ const stripped = ln.trim();
264
+ if (TASK_BREAKDOWN_RE.test(stripped)) {
265
+ inTaskBreakdown = true;
266
+ }
267
+ const pm = PHASE_HEADING_RE.exec(stripped);
268
+ if (pm) {
269
+ phaseIdx = parseInt(pm[1]!, 10);
270
+ inPhase = true;
271
+ continue;
272
+ }
273
+ if (!inPhase) continue;
274
+ const hm = TASK_RE.exec(stripped);
275
+ if (hm) {
276
+ const tid = hm[1]!.toUpperCase();
277
+ if (!membership.has(tid)) membership.set(tid, phaseIdx);
278
+ continue;
279
+ }
280
+ if (!inTaskBreakdown) {
281
+ for (const tid of findAllEdges(stripped.toUpperCase())) {
282
+ membership.set(tid, phaseIdx);
283
+ }
284
+ }
285
+ }
286
+ return membership;
287
+ }
288
+
289
+ /**
290
+ * Best-effort: parse 'Tx -> Ty -> Tz' arrow chains from fenced blocks into
291
+ * an ordering position per task (position increases along each chain).
292
+ *
293
+ * massa-ai patch (beyond D1): upstream compared the diagram against `Depends
294
+ * on` as an exact edge set (`Tx -> Ty` in the diagram requires literally
295
+ * `Depends on: Tx` on Ty and vice-versa). Both massa-ai's tasks.md reference
296
+ * template AND TLC's own upstream template violate that under a real fill-in
297
+ * - e.g. the upstream example diagrams `T1 -> T2 -> T3` while T3's `Depends
298
+ * on` is `T1`, not `T2`, because the diagram documents *execution order*
299
+ * ("tasks execute sequentially within a phase"), not a literal dependency
300
+ * graph; the real graph lives in each task's `Depends on` field. Re-running
301
+ * the strict edge check against this feature's own live tasks.md as its T2
302
+ * fixture (as this task requires) failed with 16 false positives, confirming
303
+ * the defect is not cosmetic. This function instead returns each task's
304
+ * position in its diagram chain; check() below verifies the weaker, correct
305
+ * invariant: every `Depends on` edge inside one phase must point to a task
306
+ * that appears no later in that phase's diagram order.
307
+ */
308
+ function parseDiagramOrder(lines: string[]): { positions: Map<string, number>; parsed: boolean } {
309
+ const positions = new Map<string, number>();
310
+ let inFence = false;
311
+ let foundAnyArrow = false;
312
+ for (const ln of lines) {
313
+ if (ln.trim().startsWith("```")) {
314
+ inFence = !inFence;
315
+ continue;
316
+ }
317
+ if (!inFence) continue;
318
+ // normalize arrow glyphs
319
+ const norm = ln.replaceAll("→", "->").replaceAll("──", "-");
320
+ if (!norm.includes("->")) continue;
321
+ // only treat as a chain if arrows connect them left-to-right
322
+ const segments = norm.split("->");
323
+ const seqRaw: (string | null)[] = [];
324
+ for (const seg of segments) {
325
+ const ids = findAllEdges(seg.toUpperCase());
326
+ seqRaw.push(ids.length ? ids[ids.length - 1]! : null);
327
+ }
328
+ const seq = seqRaw.filter((s): s is string => s !== null);
329
+ if (seq.length >= 2) {
330
+ foundAnyArrow = true;
331
+ }
332
+ seq.forEach((tid, idx) => {
333
+ positions.set(tid, idx);
334
+ });
335
+ }
336
+ return { positions, parsed: foundAnyArrow };
337
+ }
338
+
339
+ function check(tasksPath: string): { errors: string[]; warnings: string[] } {
340
+ const text = readFileSync(tasksPath, "utf-8");
341
+ const lines = splitLines(text);
342
+ const errors: string[] = [];
343
+ const warnings: string[] = [];
344
+
345
+ for (const name of REQUIRED_SECTIONS) {
346
+ if (!sectionPresent(lines, name)) {
347
+ errors.push(`missing required section: ## ${name}`);
348
+ }
349
+ }
350
+
351
+ const tasks = parseTasks(lines);
352
+ if (tasks.size === 0) {
353
+ warnings.push("no tasks (### T1: ...) parsed - is this file filled in?");
354
+ return { errors, warnings };
355
+ }
356
+
357
+ // Field presence + granularity smell.
358
+ for (const [tid, t] of tasks) {
359
+ if (t.tests === null) {
360
+ errors.push(`${tid}: missing \`Tests\` field`);
361
+ } else if (t.tests.toLowerCase().startsWith("none")) {
362
+ warnings.push(`${tid}: Tests: none - confirm the Test Coverage Matrix says 'none' for this layer`);
363
+ }
364
+ if (t.gate === null) {
365
+ errors.push(`${tid}: missing \`Gate\` field`);
366
+ }
367
+ const files = findAllFileHints(t.where);
368
+ const uniqueSorted = Array.from(new Set(files)).sort();
369
+ if (uniqueSorted.length > 1) {
370
+ warnings.push(
371
+ `${tid}: \`Where\` names multiple files ${pyListRepr(uniqueSorted)} - granularity smell, consider splitting`,
372
+ );
373
+ }
374
+ }
375
+
376
+ // Forward-phase dependency. Iterates each task's deps Set in JS insertion
377
+ // order - note Python's own iteration here is UNSORTED (`for dep in
378
+ // t["deps"]`, a set) and its per-process order is hash-randomized, so a
379
+ // fixture must never trigger more than one forward-phase violation per
380
+ // task or byte-parity with the Python original would be unachievable by
381
+ // construction (both languages would be non-deterministic against each
382
+ // other, and Python would even be non-deterministic against itself).
383
+ const membership = parsePhaseMembership(lines);
384
+ for (const [tid, t] of tasks) {
385
+ const pHere = membership.get(tid);
386
+ if (pHere === undefined) continue;
387
+ for (const dep of t.deps) {
388
+ const pDep = membership.get(dep);
389
+ if (pDep !== undefined && pDep > pHere) {
390
+ errors.push(
391
+ `${tid} (phase ${pHere}) depends on ${dep} (phase ${pDep}) - dependencies must point backward or within the same phase`,
392
+ );
393
+ }
394
+ }
395
+ }
396
+
397
+ // Diagram vs definition cross-check (best effort, order-consistency - see
398
+ // parseDiagramOrder's docstring for why this is not exact-edge equality).
399
+ const { positions, parsed } = parseDiagramOrder(lines);
400
+ if (!parsed) {
401
+ warnings.push("diagram arrows not parsed confidently - diagram/definition cross-check skipped (verify by hand)");
402
+ } else {
403
+ for (const [tid, t] of tasks) {
404
+ const pHere = membership.get(tid);
405
+ if (!positions.has(tid)) continue;
406
+ for (const dep of Array.from(t.deps).sort()) {
407
+ if (!positions.has(dep)) continue;
408
+ const pDep = membership.get(dep);
409
+ if (pDep === undefined || pHere === undefined || pDep !== pHere) continue; // cross-phase; forward-phase check above already covers ordering
410
+ if (positions.get(dep)! >= positions.get(tid)!) {
411
+ errors.push(
412
+ `${tid} declares \`Depends on: ${dep}\` but the phase diagram shows ${dep} at or after ${tid}, not before it`,
413
+ );
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ return { errors, warnings };
420
+ }
421
+
422
+ const USAGE = "usage: validate_tasks.ts [-h] [--root ROOT] [--strict] [target]";
423
+ const HELP = `${USAGE}
424
+
425
+ Pre-approval checks for a feature tasks.md.
426
+
427
+ positional arguments:
428
+ target Path to a tasks.md, a feature directory, or a project root
429
+
430
+ options:
431
+ -h, --help show this help message and exit
432
+ --root ROOT
433
+ --strict`;
434
+
435
+ interface Args {
436
+ target: string | null;
437
+ root: string;
438
+ strict: boolean;
439
+ }
440
+
441
+ function printUsageError(msg: string): void {
442
+ process.stderr.write(`${USAGE}\nvalidate_tasks.ts: error: ${msg}\n`);
443
+ }
444
+
445
+ function parseArgs(argv: string[]): Args | null {
446
+ let root = ".";
447
+ let strict = false;
448
+ const positionals: string[] = [];
449
+ for (let i = 0; i < argv.length; i++) {
450
+ const a = argv[i]!;
451
+ if (a === "--root") {
452
+ if (i + 1 >= argv.length) {
453
+ printUsageError("argument --root: expected one argument");
454
+ return null;
455
+ }
456
+ root = argv[++i]!;
457
+ } else if (a.startsWith("--root=")) {
458
+ root = a.slice("--root=".length);
459
+ } else if (a === "--strict") {
460
+ strict = true;
461
+ } else if (a === "-h" || a === "--help") {
462
+ console.log(HELP);
463
+ process.exit(0);
464
+ } else if (a.startsWith("-") && a !== "-") {
465
+ printUsageError(`unrecognized arguments: ${a}`);
466
+ return null;
467
+ } else {
468
+ positionals.push(a);
469
+ }
470
+ }
471
+ if (positionals.length > 1) {
472
+ printUsageError(`unrecognized arguments: ${positionals.slice(1).join(" ")}`);
473
+ return null;
474
+ }
475
+ return { target: positionals[0] ?? null, root, strict };
476
+ }
477
+
478
+ function main(argv: string[]): number {
479
+ const args = parseArgs(argv);
480
+ if (args === null) return 2;
481
+
482
+ const tasksPath = resolveTasks(args.target, args.root);
483
+ if (!tasksPath) {
484
+ console.error("validate_tasks: could not locate a tasks.md. Pass a path or run from the project root.");
485
+ return 2;
486
+ }
487
+
488
+ const { errors, warnings } = check(tasksPath);
489
+ for (const w of warnings) console.log(` WARN ${w}`);
490
+ for (const e of errors) console.log(` ERROR ${e}`);
491
+ const fail = errors.length > 0 || (warnings.length > 0 && args.strict);
492
+ console.log(`\nvalidate_tasks: ${errors.length} error(s), ${warnings.length} warning(s) in ${tasksPath}`);
493
+ return fail ? 1 : 0;
494
+ }
495
+
496
+ if (import.meta.main) {
497
+ process.exit(main(process.argv.slice(2)));
498
+ }
@@ -80,7 +80,7 @@ Do not use this workflow for findings-only architecture review; route that to `w
80
80
  > - persona: optional — the active route's cataloged id only, never the persona prompt, passed as advisory framing only — it never overrides the agent's charter Restrictions, scope, or permissions; omit when no persona is routed
81
81
  11. Verify each completed finding:
82
82
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
83
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
83
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
84
84
  - Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected finding or coherent group.
85
85
  - A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
86
86
  - Run the report's verification suggestion when available.
@@ -70,7 +70,7 @@ Do not use this workflow for findings-only bug discovery; route that to `workflo
70
70
  - Main agent owns report parsing, prioritization, memory writes, final synthesis, and Evidence Gate.
71
71
  10. Verify each completed finding:
72
72
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
73
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
73
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
74
74
  - Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected finding or coherent group.
75
75
  - A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
76
76
  - Run the report's verification suggestion when available.
@@ -77,7 +77,7 @@ Do not use this workflow for findings-only SOLID, Clean Code, KISS, YAGNI, DRY,
77
77
  - Main agent owns report parsing, prioritization, memory writes, final synthesis, and Evidence Gate.
78
78
  11. Verify each completed finding:
79
79
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
80
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
80
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
81
81
  - Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected finding or coherent group.
82
82
  - A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
83
83
  - Run the report's verification suggestion when available.
@@ -50,7 +50,7 @@ Before the first repository mutation, load `references/implementation-delivery.m
50
50
  12. Fix the divergence point closest to the root cause
51
51
  13. Add regression coverage at the correct seam, or document why no valid regression seam exists
52
52
  14. If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
53
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
53
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
54
54
  Rerun the original feedback loop, run the verification recipe, and remove temporary instrumentation unless intentionally retained as observability
55
55
  15. Use `references/agent-orchestration.md` only for independent verification or isolated investigation branches
56
56
  16. IF fix found:
@@ -44,7 +44,7 @@ Before the first repository mutation, load `references/implementation-delivery.m
44
44
  - Invoke `workflows/commit.md` for each verified group; do not duplicate commit staging, message, audit-exclusion, or Jira-prefix rules in this workflow.
45
45
  - When every group has a confirmed Jira key, follow the reference's optional stacked branch flow: ask whether to create stacked task branches, ask for the base branch and a branch pattern containing `<jira-task-key>` if accepted, create each next branch from the previous task branch, never push, and report branches and commits in push order.
46
46
  12. Run the verification recipe and report skipped checks explicitly. If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
47
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
47
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
48
48
  13. At completion, persist (run the scoring rubric from `references/decision-engine.md` for each):
49
49
  - Design decisions made via `remember` as scored `decision` memories
50
50
  - New patterns introduced via `remember` as scored `pattern` memories
@@ -11,7 +11,7 @@ Before the first repository mutation, load `references/implementation-delivery.m
11
11
  3. Recall relevant durable context with `recall`. Treat recalled memory as a lead until current source confirms it. Confirm recalled memory against current source before relying on it only when the change touches the enumerated risk-domain set: public API, data loss, auth/PII, migrations, or cross-service contracts. Otherwise trust recalled memory and cite it with a one-line source note.
12
12
  4. Create a Synapse session when planned related `search` calls >=2, following `references/synapse-policy.md`.
13
13
  5. Load confirmed project lessons through `references/lessons.md` when `.specs/lessons.json` exists:
14
- `python3 skills/massa-ai/scripts/lessons.py --root . list --status confirmed`
14
+ `bun skills/massa-ai/scripts/lessons.ts --root . list --status confirmed`
15
15
  Retrieve only the context required for the goal:
16
16
  - begin with focused local inspection or the shared summary-search sequence
17
17
  - deepen into enriched search, symbols, or exact files only when needed
@@ -21,7 +21,7 @@ Before the first repository mutation, load `references/implementation-delivery.m
21
21
  - For analysis that benefits from running code (derived values, data inspection, bulk transforms), call `execute` with `language` and `code` or `batch_execute` with `commands`[] instead of loading raw data into context. Respect the local-dev-only trust model (no untrusted-client exposure).
22
22
  7. Use `compress` only when accumulated source or conversation context is reducing execution quality; preserve decisions, constraints, current state, and next steps rather than raw history.
23
23
  8. Before completion, if verification found a reusable signal, record it via `references/lessons.md`. Score potential memories using `references/decision-engine.md` when that guidance is not already loaded:
24
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
24
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
25
25
  - remember verified decisions, reusable discoveries, recurring blockers, accepted constraints, and completed outcomes that will save future work
26
26
  - if a recalled memory is stale or needs correction, call `memory_update` with `id` and the new `content` (re-embeds automatically); if a memory is obsolete, call `memory_delete` with `id` (hard-delete, severs graph edges)
27
27
  - for usage insights (search/cache patterns, recent activity), call `analytics` with `type` and `projectId`
@@ -73,7 +73,7 @@ Do not execute from chat summaries, inline review comments, remembered findings,
73
73
  11. Verify each completed finding with the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check, then run focused tests, build, lint, type, static, or runtime checks relevant to the source lens. Reinspect tests, fixtures, snapshots, types, specs, public contracts, and touched identifiers so validation assets were not weakened and names follow `references/naming-standards.md`. A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
74
74
  12. Produce a closure matrix with finding ID, source lens, status (`fixed`, `deferred`, `blocked`, `skipped`), changed files, command/artifact, result, skipped reason or `none`, highest Verification Ladder level reached, validation assets protected, residual risk, and exact next step for deferred or blocked findings.
75
75
  13. If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
76
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
76
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
77
77
  14. Persist only reusable root-cause patterns, approved remediation exceptions, durable architecture/security/requirements decisions, or project-specific verification recipes after Importance Calibration. Use `workflow:implementation-fix` and required project/session/entity/memory tags.
78
78
  15. Complete `references/evidence-gate.md`.
79
79
 
@@ -56,7 +56,7 @@ Reject direct use without a saved `audits/maestro/<YYYY-MM-DD maestro-audit.md>`
56
56
  - Protect existing flows, subflows, fixtures, snapshots, baselines, report consumers, and CI commands unless the audit finding explicitly scopes them.
57
57
  9. Use strict harness sensors:
58
58
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
59
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
59
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
60
60
  - Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected `MST-*` finding or coherent group.
61
61
  - Prefer repository-specific Maestro commands; otherwise run `maestro test --format junit --output <report.xml> --test-output-dir <artifact-dir> <flow-or-directory>`.
62
62
  - A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted.
@@ -42,7 +42,7 @@ Do not execute from chat summaries, screenshots alone, remembered findings, or a
42
42
  - Modify tracked Maestro flows only when the selected finding explicitly identifies the flow as incorrect or missing and the user-approved scope includes that change.
43
43
  9. Verify after each coherent finding group:
44
44
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
45
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
45
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
46
46
  - Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected `MFM-*` finding or coherent group.
47
47
  - A finding cannot be marked `fixed` when a target-relevant command, render sensor, comparison artifact, or Maestro reproduction exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
48
48
  - Re-resolve every affected comparison row, not only the previous mismatch.
@@ -39,7 +39,7 @@ Before the first repository mutation, load `references/implementation-delivery.m
39
39
  - Invoke `workflows/commit.md` for each verified group; do not duplicate commit staging, message, audit-exclusion, or Jira-prefix rules in this workflow.
40
40
  - When every group has a confirmed Jira key, follow the reference's optional stacked branch flow: ask whether to create stacked task branches, ask for the base branch and a branch pattern containing `<jira-task-key>` if accepted, create each next branch from the previous task branch, never push, and report branches and commits in push order.
41
41
  10. Include file-integrity checks when tests, specs, benchmarks, fixtures, or snapshots are validation assets. If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
42
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
42
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
43
43
  11. Use `references/agent-orchestration.md` only for isolated implementation slices or independent verification
44
44
  12. At completion, persist (run the scoring rubric from `references/decision-engine.md`):
45
45
  - Refactored architectural decisions via `remember` as scored `decision` memories
@@ -72,7 +72,7 @@ Do not use this workflow for findings-only requirements review; route that to `w
72
72
  - Main agent owns report parsing, traceability matrix, memory writes, final synthesis, and Evidence Gate.
73
73
  11. Verify each completed finding:
74
74
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
75
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
75
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
76
76
  - Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected finding or coherent group.
77
77
  - A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
78
78
  - Run the report's verification suggestion when available.
@@ -76,7 +76,7 @@ Do not use this workflow for findings-only security review; route that to `workf
76
76
  - Main agent owns report parsing, prioritization, memory writes, final synthesis, and Evidence Gate.
77
77
  11. Verify each completed finding:
78
78
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
79
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
79
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
80
80
  - Apply the Mandatory Verification Fix Gate from `references/verification-ladder.md`: run the report's Verification Suggestion or an equivalent deterministic command/artifact check for each selected finding or coherent group.
81
81
  - A finding cannot be marked `fixed` when a target-relevant command or artifact check exists but was not attempted; if verification cannot run, mark it `blocked`, `deferred`, or `skipped` with an allowed skipped-check reason.
82
82
  - Run the report's verification suggestion when available.