@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.
- package/.cursor-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/massa-ai/references/evidence-gate.md +1 -1
- package/skills/massa-ai/references/hook-enforcement.md +2 -2
- package/skills/massa-ai/references/implementation-delivery.md +3 -3
- package/skills/massa-ai/references/lessons.md +9 -10
- package/skills/massa-ai/references/mcp-tools.md +1 -1
- package/skills/massa-ai/references/project-context.md +1 -1
- package/skills/massa-ai/references/spec-driven/artifact-store.md +7 -8
- package/skills/massa-ai/references/spec-driven/design.md +1 -1
- package/skills/massa-ai/references/spec-driven/execute.md +5 -5
- package/skills/massa-ai/references/spec-driven/specify.md +6 -6
- package/skills/massa-ai/references/spec-driven/sub-agents.md +1 -1
- package/skills/massa-ai/references/spec-driven/tasks.md +2 -2
- package/skills/massa-ai/references/spec-driven/validate.md +3 -3
- package/skills/massa-ai/scripts/check_commit.ts +231 -0
- package/skills/massa-ai/scripts/check_specs_delivered.ts +209 -0
- package/skills/massa-ai/scripts/lessons.ts +907 -0
- package/skills/massa-ai/scripts/validate_spec.ts +413 -0
- package/skills/massa-ai/scripts/validate_state.ts +276 -0
- package/skills/massa-ai/scripts/validate_tasks.ts +498 -0
- package/skills/massa-ai/workflows/architecture/architecture-fix.md +1 -1
- package/skills/massa-ai/workflows/bugs/bugs-fix.md +1 -1
- package/skills/massa-ai/workflows/code-quality/code-quality-fix.md +1 -1
- package/skills/massa-ai/workflows/debug.md +1 -1
- package/skills/massa-ai/workflows/feature.md +1 -1
- package/skills/massa-ai/workflows/general.md +2 -2
- package/skills/massa-ai/workflows/implementation/implementation-fix.md +1 -1
- package/skills/massa-ai/workflows/maestro/maestro-fix.md +1 -1
- package/skills/massa-ai/workflows/mobile-figma/mobile-figma-fix.md +1 -1
- package/skills/massa-ai/workflows/refactor.md +1 -1
- package/skills/massa-ai/workflows/requirements/requirements-fix.md +1 -1
- package/skills/massa-ai/workflows/security/security-fix.md +1 -1
- package/skills/massa-ai/workflows/spec-driven.md +3 -3
- package/skills/massa-ai/workflows/tests/tests-fix.md +1 -1
- package/skills/massa-ai/scripts/check_commit.py +0 -128
- package/skills/massa-ai/scripts/check_specs_delivered.py +0 -137
- package/skills/massa-ai/scripts/lessons.py +0 -630
- package/skills/massa-ai/scripts/validate_spec.py +0 -272
- package/skills/massa-ai/scripts/validate_state.py +0 -183
- 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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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
|
-
`
|
|
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.
|