@massa-ai/cursor-plugin 1.21.0 → 1.23.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 (47) hide show
  1. package/.cursor-plugin/plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/skills/massa-ai/SKILL.md +9 -0
  4. package/skills/massa-ai/references/evidence-gate.md +1 -1
  5. package/skills/massa-ai/references/figma-pre-analysis.md +69 -0
  6. package/skills/massa-ai/references/hook-enforcement.md +2 -2
  7. package/skills/massa-ai/references/implementation-delivery.md +17 -3
  8. package/skills/massa-ai/references/lessons.md +9 -10
  9. package/skills/massa-ai/references/mcp-tools.md +1 -1
  10. package/skills/massa-ai/references/mobile-context.md +19 -0
  11. package/skills/massa-ai/references/mobile-diagnosis.md +1 -1
  12. package/skills/massa-ai/references/project-context.md +1 -1
  13. package/skills/massa-ai/references/spec-driven/artifact-store.md +7 -8
  14. package/skills/massa-ai/references/spec-driven/design.md +1 -1
  15. package/skills/massa-ai/references/spec-driven/execute.md +5 -5
  16. package/skills/massa-ai/references/spec-driven/specify.md +6 -6
  17. package/skills/massa-ai/references/spec-driven/sub-agents.md +1 -1
  18. package/skills/massa-ai/references/spec-driven/tasks.md +2 -2
  19. package/skills/massa-ai/references/spec-driven/validate.md +3 -3
  20. package/skills/massa-ai/scripts/check_commit.ts +231 -0
  21. package/skills/massa-ai/scripts/check_specs_delivered.ts +209 -0
  22. package/skills/massa-ai/scripts/lessons.ts +907 -0
  23. package/skills/massa-ai/scripts/validate_spec.ts +413 -0
  24. package/skills/massa-ai/scripts/validate_state.ts +276 -0
  25. package/skills/massa-ai/scripts/validate_tasks.ts +498 -0
  26. package/skills/massa-ai/workflows/architecture/architecture-fix.md +1 -1
  27. package/skills/massa-ai/workflows/bugs/bugs-fix.md +1 -1
  28. package/skills/massa-ai/workflows/code-quality/code-quality-fix.md +1 -1
  29. package/skills/massa-ai/workflows/debug.md +1 -1
  30. package/skills/massa-ai/workflows/design.md +1 -1
  31. package/skills/massa-ai/workflows/feature.md +2 -2
  32. package/skills/massa-ai/workflows/general.md +2 -2
  33. package/skills/massa-ai/workflows/implementation/implementation-fix.md +1 -1
  34. package/skills/massa-ai/workflows/maestro/maestro-fix.md +1 -1
  35. package/skills/massa-ai/workflows/mobile-figma/mobile-figma-audit.md +1 -0
  36. package/skills/massa-ai/workflows/mobile-figma/mobile-figma-fix.md +2 -1
  37. package/skills/massa-ai/workflows/refactor.md +1 -1
  38. package/skills/massa-ai/workflows/requirements/requirements-fix.md +1 -1
  39. package/skills/massa-ai/workflows/security/security-fix.md +1 -1
  40. package/skills/massa-ai/workflows/spec-driven.md +6 -6
  41. package/skills/massa-ai/workflows/tests/tests-fix.md +1 -1
  42. package/skills/massa-ai/scripts/check_commit.py +0 -128
  43. package/skills/massa-ai/scripts/check_specs_delivered.py +0 -137
  44. package/skills/massa-ai/scripts/lessons.py +0 -630
  45. package/skills/massa-ai/scripts/validate_spec.py +0 -272
  46. package/skills/massa-ai/scripts/validate_state.py +0 -183
  47. package/skills/massa-ai/scripts/validate_tasks.py +0 -302
@@ -1,302 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- validate_tasks.py - 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. Pure standard library, 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
- python3 skills/massa-ai/scripts/validate_tasks.py [target] [--root DIR] [--strict]
23
-
24
- Invoke with the repo-root-relative script path shown above (matches
25
- lessons.py'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 argparse
35
- import os
36
- import re
37
- import sys
38
-
39
- REQUIRED_SECTIONS = ["Test Coverage Matrix", "Gate Check Commands", "Execution Plan", "Task Breakdown"]
40
- # Task ids are `T<n>` plus optional letter prefix (`FT3` for fix tasks): an
41
- # unrecognized `### FT3:` header would fold its fields into the previous task's
42
- # record and misreport e.g. a self-dependency (IT2-01).
43
- TASK_RE = re.compile(r"^#{2,4}\s+([A-Z]*T\d+)\s*:", re.IGNORECASE)
44
- EDGE_RE = re.compile(r"\b[A-Z]*T\d+\b", re.IGNORECASE)
45
- FILE_HINT_RE = re.compile(r"[\w./-]+\.\w{1,6}\b")
46
-
47
-
48
- def resolve_tasks(target, root):
49
- if target:
50
- if os.path.isfile(target):
51
- return target
52
- if os.path.isdir(target):
53
- cand = os.path.join(target, "tasks.md")
54
- if os.path.isfile(cand):
55
- return cand
56
- return _autodetect(target)
57
- # Not a path: treat as a feature name under <root>/.specs/features/<name>/
58
- cand = os.path.join(root, ".specs", "features", target, "tasks.md")
59
- if os.path.isfile(cand):
60
- return cand
61
- return None
62
- return _autodetect(root)
63
-
64
-
65
- def _autodetect(root):
66
- base = os.path.join(root, ".specs", "features")
67
- if not os.path.isdir(base):
68
- return None
69
- features = [d for d in sorted(os.listdir(base)) if os.path.isfile(os.path.join(base, d, "tasks.md"))]
70
- if len(features) == 1:
71
- return os.path.join(base, features[0], "tasks.md")
72
- if len(features) == 0:
73
- return None
74
- raise SystemExit(
75
- "validate_tasks: multiple features found; pass one explicitly:\n "
76
- + "\n ".join(os.path.join(base, f, "tasks.md") for f in features)
77
- )
78
-
79
-
80
- def section_present(lines, name):
81
- return any(re.match(r"^#{1,4}\s+" + re.escape(name) + r"\b", ln.strip()) for ln in lines)
82
-
83
-
84
- def parse_tasks(lines):
85
- """Return a dict: task_id -> {'deps': set, 'tests': str|None, 'gate': str|None, 'where': str}."""
86
- tasks = {}
87
- current = None
88
- for ln in lines:
89
- m = TASK_RE.match(ln.strip())
90
- if m:
91
- current = m.group(1).upper()
92
- tasks[current] = {"deps": set(), "tests": None, "gate": None, "where": ""}
93
- continue
94
- if current is None:
95
- continue
96
- stripped = ln.strip()
97
- dm = re.match(r"^\*{0,2}Depends on\*{0,2}\s*:\s*(.*)$", stripped, re.IGNORECASE)
98
- if dm:
99
- body = dm.group(1)
100
- if "none" not in body.lower():
101
- for e in EDGE_RE.findall(body.upper()):
102
- tasks[current]["deps"].add(e)
103
- wm = re.match(r"^\*{0,2}Where\*{0,2}\s*:\s*(.*)$", stripped, re.IGNORECASE)
104
- if wm:
105
- tasks[current]["where"] = wm.group(1)
106
- tm = re.match(r"^\*{0,2}Tests\*{0,2}\s*:\s*(.*)$", stripped, re.IGNORECASE)
107
- if tm:
108
- tasks[current]["tests"] = tm.group(1).strip()
109
- gm = re.match(r"^\*{0,2}Gate\*{0,2}\s*:\s*(.*)$", stripped, re.IGNORECASE)
110
- if gm:
111
- tasks[current]["gate"] = gm.group(1).strip()
112
- return tasks
113
-
114
-
115
- TASK_BREAKDOWN_RE = re.compile(r"^#{1,4}\s+Task Breakdown\b", re.IGNORECASE)
116
-
117
-
118
- def parse_phase_membership(lines):
119
- """Map task_id -> phase index, read from '### Phase N' headers.
120
-
121
- massa-ai patch (beyond D1): the reference tasks.md template (and TLC's own)
122
- puts the Execution Plan (phase headers + a diagram/list of the task IDs in
123
- each phase) BEFORE the separate Task Breakdown section (a flat list of
124
- "### Tn:" task headers with no phase sub-headers). Upstream mapped a task
125
- to a phase only by which "### Tn:" HEADER line followed the most-recently-
126
- seen "### Phase N" header while scanning the WHOLE file - so with that
127
- template shape every task header in Task Breakdown inherits the LAST phase
128
- index left over from Execution Plan, and the forward-phase-dependency
129
- check (SYNC-01 AC2) never fires. Confirmed against this feature's own
130
- tasks.md (whose Phase headers happen to sit directly inside Task
131
- Breakdown, immediately before their tasks) still passing, and a
132
- template-shaped fixture then found 0/18 forward-phase violations
133
- detectable when it should catch a deliberately-introduced one.
134
-
135
- Fix: read membership from the Execution Plan's diagram/plain-list content
136
- (bare `Tn` tokens under a `### Phase N` heading) as the authoritative
137
- signal there, and fall back to the header-based signal (`setdefault`,
138
- never overwriting) once "## Task Breakdown" is reached - which is also
139
- exactly what the ORIGINAL algorithm already got right for tasks.md files
140
- (like this feature's own) that interleave phase headers directly inside
141
- Task Breakdown. Diagram-style scanning is deliberately NOT applied inside
142
- Task Breakdown, preserving the original comment's concern: a `Depends on:`
143
- line inside one task's block often names a task from an EARLIER phase and
144
- must never be misattributed to the current phase.
145
- """
146
- membership = {}
147
- phase_idx = 0
148
- in_phase = False
149
- in_task_breakdown = False
150
- for ln in lines:
151
- stripped = ln.strip()
152
- if TASK_BREAKDOWN_RE.match(stripped):
153
- in_task_breakdown = True
154
- pm = re.match(r"^#{2,4}\s+Phase\s+(\d+)", stripped, re.IGNORECASE)
155
- if pm:
156
- phase_idx = int(pm.group(1))
157
- in_phase = True
158
- continue
159
- if not in_phase:
160
- continue
161
- hm = TASK_RE.match(stripped)
162
- if hm:
163
- membership.setdefault(hm.group(1).upper(), phase_idx)
164
- continue
165
- if not in_task_breakdown:
166
- for tid in EDGE_RE.findall(stripped.upper()):
167
- membership[tid] = phase_idx
168
- return membership
169
-
170
-
171
- def parse_diagram_order(lines):
172
- """Best-effort: parse 'Tx -> Ty -> Tz' arrow chains from fenced blocks into
173
- an ordering position per task (position increases along each chain).
174
-
175
- massa-ai patch (beyond D1): upstream compared the diagram against `Depends
176
- on` as an exact edge set (`Tx -> Ty` in the diagram requires literally
177
- `Depends on: Tx` on Ty and vice-versa). Both massa-ai's tasks.md reference
178
- template AND TLC's own upstream template violate that under a real fill-in
179
- - e.g. the upstream example diagrams `T1 -> T2 -> T3` while T3's `Depends
180
- on` is `T1`, not `T2`, because the diagram documents *execution order*
181
- ("tasks execute sequentially within a phase"), not a literal dependency
182
- graph; the real graph lives in each task's `Depends on` field. Re-running
183
- the strict edge check against this feature's own live tasks.md as its T2
184
- fixture (as this task requires) failed with 16 false positives, confirming
185
- the defect is not cosmetic. This function instead returns each task's
186
- position in its diagram chain; `check()` below verifies the weaker, correct
187
- invariant: every `Depends on` edge inside one phase must point to a task
188
- that appears no later in that phase's diagram order.
189
- Returns (positions: dict[str,int], parsed: bool)."""
190
- positions = {}
191
- in_fence = False
192
- found_any_arrow = False
193
- for ln in lines:
194
- if ln.strip().startswith("```"):
195
- in_fence = not in_fence
196
- continue
197
- if not in_fence:
198
- continue
199
- # normalize arrow glyphs
200
- norm = ln.replace("→", "->").replace("──", "-").replace("-", "-")
201
- if "->" not in norm:
202
- continue
203
- # only treat as a chain if arrows connect them left-to-right
204
- segments = [s for s in re.split(r"->", norm)]
205
- seq = []
206
- for seg in segments:
207
- ids = EDGE_RE.findall(seg.upper())
208
- seq.append(ids[-1] if ids else None)
209
- seq = [s for s in seq if s]
210
- if len(seq) >= 2:
211
- found_any_arrow = True
212
- for idx, tid in enumerate(seq):
213
- positions[tid] = idx
214
- return positions, found_any_arrow
215
-
216
-
217
- def check(tasks_path):
218
- with open(tasks_path, "r", encoding="utf-8") as f:
219
- lines = f.read().splitlines()
220
- errors, warnings = [], []
221
-
222
- for name in REQUIRED_SECTIONS:
223
- if not section_present(lines, name):
224
- errors.append(f"missing required section: ## {name}")
225
-
226
- tasks = parse_tasks(lines)
227
- if not tasks:
228
- warnings.append("no tasks (### T1: ...) parsed - is this file filled in?")
229
- return errors, warnings
230
-
231
- # Field presence + granularity smell.
232
- for tid, t in tasks.items():
233
- if t["tests"] is None:
234
- errors.append(f"{tid}: missing `Tests` field")
235
- elif t["tests"].lower().startswith("none"):
236
- warnings.append(f"{tid}: Tests: none - confirm the Test Coverage Matrix says 'none' for this layer")
237
- if t["gate"] is None:
238
- errors.append(f"{tid}: missing `Gate` field")
239
- files = FILE_HINT_RE.findall(t["where"])
240
- if len(set(files)) > 1:
241
- warnings.append(f"{tid}: `Where` names multiple files {sorted(set(files))} - granularity smell, consider splitting")
242
-
243
- # Forward-phase dependency.
244
- membership = parse_phase_membership(lines)
245
- for tid, t in tasks.items():
246
- p_here = membership.get(tid)
247
- if p_here is None:
248
- continue
249
- for dep in t["deps"]:
250
- p_dep = membership.get(dep)
251
- if p_dep is not None and p_dep > p_here:
252
- errors.append(f"{tid} (phase {p_here}) depends on {dep} (phase {p_dep}) - dependencies must point backward or within the same phase")
253
-
254
- # Diagram vs definition cross-check (best effort, order-consistency - see
255
- # parse_diagram_order's docstring for why this is not exact-edge equality).
256
- positions, parsed = parse_diagram_order(lines)
257
- if not parsed:
258
- warnings.append("diagram arrows not parsed confidently - diagram/definition cross-check skipped (verify by hand)")
259
- else:
260
- for tid, t in tasks.items():
261
- p_here = membership.get(tid)
262
- if tid not in positions:
263
- continue
264
- for dep in sorted(t["deps"]):
265
- if dep not in positions:
266
- continue
267
- p_dep = membership.get(dep)
268
- if p_dep is None or p_here is None or p_dep != p_here:
269
- continue # cross-phase; forward-phase check above already covers ordering
270
- if positions[dep] >= positions[tid]:
271
- errors.append(
272
- f"{tid} declares `Depends on: {dep}` but the phase diagram shows {dep} "
273
- f"at or after {tid}, not before it"
274
- )
275
-
276
- return errors, warnings
277
-
278
-
279
- def main(argv=None):
280
- p = argparse.ArgumentParser(prog="validate_tasks.py", description="Pre-approval checks for a feature tasks.md.")
281
- p.add_argument("target", nargs="?", default=None)
282
- p.add_argument("--root", default=".")
283
- p.add_argument("--strict", action="store_true")
284
- args = p.parse_args(argv)
285
-
286
- tasks_path = resolve_tasks(args.target, args.root)
287
- if not tasks_path:
288
- print("validate_tasks: could not locate a tasks.md. Pass a path or run from the project root.", file=sys.stderr)
289
- return 2
290
-
291
- errors, warnings = check(tasks_path)
292
- for w in warnings:
293
- print(f" WARN {w}")
294
- for e in errors:
295
- print(f" ERROR {e}")
296
- fail = errors or (warnings and args.strict)
297
- print(f"\nvalidate_tasks: {len(errors)} error(s), {len(warnings)} warning(s) in {tasks_path}")
298
- return 1 if fail else 0
299
-
300
-
301
- if __name__ == "__main__":
302
- raise SystemExit(main())