@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
@@ -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.
@@ -40,7 +40,7 @@ Complexity determines depth, not a fixed pipeline. Assess scope first, apply onl
40
40
  | Large | >10 tasks OR multi-component feature | Full spec + requirement IDs | Architecture + components | Full breakdown + deps | Implement + verify per task |
41
41
  | Complex | Ambiguity or new domain (unfamiliar vocabulary, no prior pattern) | Full spec + discuss gray areas | Research + architecture | Breakdown + phase plan | Implement + interactive UAT |
42
42
 
43
- A "phase" is a group of tasks sharing a dependency boundary or a checkpoint commit — it is distinct from a single task or atomic step. The sub-agent offer fires when a formal `tasks.md` has more than 3 tasks — packing itself still uses ~7-task batches; a 4–8-task feature is offered as a single batch worker.
43
+ A "Phase" is an ordered group of Tasks sharing a dependency boundary or a checkpoint commit — it is distinct from a single Task or atomic step. Report sizing in the uniform vocabulary: `1 Phase = X Tasks`; the whole plan is `Y Phases = Z Tasks`. The sub-agent offer fires when a formal `tasks.md` has more than 3 Tasks — packing itself still uses ~7-Task Phase groups; a 4–8-Task feature is offered as a single Phase-group worker.
44
44
 
45
45
  - Specify and Execute are always required.
46
46
  - Design is skipped when straightforward (no architectural decisions, no new patterns).
@@ -81,11 +81,11 @@ Quick artifacts live under `.specs/quick/NNN-slug/` with a `TASK.md` (one-line i
81
81
  - Load current canonical artifacts from `.specs/` files: `.specs/project/FEATURES.json`, `.specs/project/STATE.md`, `.specs/HANDOFF.md`, existing `.specs/features/<slug>/` phase artifacts, lessons, and project source. Current repository source and approved `.specs/` artifacts override stale memory, old handoff notes, external summaries, debug exports, or unapproved generated plans.
82
82
  - `search` only counts as codebase evidence when it is fresh for the current repository path and commit/worktree state, or when the result is confirmed against source files read in this session.
83
83
  - Load confirmed project lessons through `references/lessons.md` when `.specs/lessons.json` exists:
84
- `python3 skills/massa-ai/scripts/lessons.py --root . list --status confirmed`
84
+ `bun skills/massa-ai/scripts/lessons.ts --root . list --status confirmed`
85
85
  3. Run `Specify` with `references/spec-driven/specify.md`.
86
86
  - Capture stable requirement IDs, testable acceptance criteria, edge cases, and explicit out-of-scope items.
87
87
  - Run `references/spec-driven/discuss.md` inside Specify when gray areas, implicit requirements, persistence/state, external calls, auth, payments, concurrency, or state transitions affect behavior.
88
- - For Android, iOS, or KMP Compose Multiplatform UI work, load `references/mobile-context.md` and run the optional design-source gate: ask for one or more Figma links, node IDs, a readable desktop selection, supplied screenshots, or explicit `none`; record `Figma Source: none by user choice` when declined. Screenshots are context-only unless paired with structured Figma evidence. For unsupported targets, record the source as outside mobile Figma scope and do not run mobile Figma.
88
+ - For Android, iOS, or KMP Compose Multiplatform UI work, load `references/mobile-context.md` and run the optional design-source gate: ask for one or more Figma links, node IDs, a readable desktop selection, supplied screenshots, or explicit `none`; record `Figma Source: none by user choice` when declined. When Figma links or node IDs are supplied, load `references/figma-pre-analysis.md` and run its two-stage sequential retrieval protocol before consuming the design evidence. Screenshots are context-only unless paired with structured Figma evidence. For unsupported targets, record the source as outside mobile Figma scope and do not run mobile Figma.
89
89
  - Apply the Requirement Closure Gate: every open requirement question is resolved with the user or recorded as an accepted assumption before execution begins.
90
90
  4. Decide whether `Design` is required. If yes, run `references/spec-driven/design.md`; if no, record why the skip is valid. When Design is skipped and a design concern appears later, stop and create `design.md` before continuing.
91
91
  5. Decide whether `Tasks` is required. If yes, run `references/spec-driven/tasks.md`; if no, list the inline atomic execution steps before editing. If the inline list reveals more than 5 steps or complex dependencies, stop and create `tasks.md` — the Tasks phase was wrongly skipped (safety valve).
@@ -94,7 +94,7 @@ Quick artifacts live under `.specs/quick/NNN-slug/` with a `TASK.md` (one-line i
94
94
  - Run repo-rules discovery from `references/repo-rules-discovery.md` before the first repository mutation: record the harness sources loaded (or `repo-rules: none present`), and implement so every new or changed file conforms to the target repo's module layout, unit-test location, and testing-area conventions. A repo rule wins over a skill default for placement and gate commands; record any deviation with an explicit reason. Never fabricate rules or create `.claude/`/`.cursor/` directories the repo lacks.
95
95
  - Use the Test Coverage Matrix and Gate Check Commands from `tasks.md`, or state their inline equivalents when Tasks was skipped.
96
96
  - Ask the MCP and skill question in Tasks or inline Execute when tool choice can change correctness or verification.
97
- - If a formal `tasks.md` has more than 3 tasks, present the sub-agent offer from `references/spec-driven/sub-agents.md` before starting Execute — even when packing yields a single batch (a 4–8-task feature is offered as one batch worker). Offer-then-confirm — never auto-spawn; the user must accept before any sub-agent is dispatched. One worker per batch (~7 tasks, whole phases): each batch worker executes all its tasks in order (implement → gate → atomic commit), then reports a compact summary (tasks done, commit hashes, test counts, deviations). Workers never spawn further sub-agents.
97
+ - If a formal `tasks.md` has more than 3 Tasks, present the sub-agent offer from `references/spec-driven/sub-agents.md` before starting Execute — even when packing yields a single Phase group (a 4–8-Task feature is offered as one Phase-group worker). Offer-then-confirm — never auto-spawn; the user must accept before any sub-agent is dispatched. One worker per Phase group (~7 Tasks, whole Phases): each worker executes all its Tasks in order (implement → gate → atomic commit), then reports a compact summary (Tasks done, commit hashes, test counts, deviations). Workers never spawn further sub-agents.
98
98
  - Implement one atomic step or approved task at a time.
99
99
  - For long-running task sequences, create a checkpoint via `create_checkpoint` at task boundaries with `taskId`, `description`, `progressPercent`, `currentStep`, `nextAction`, `fileChanges`, and `checkpointType: "manual"` so progress is resumable after interruption.
100
100
  - If resuming after interruption, call `list_checkpoints` with the `taskId` and `restore_checkpoint` to recover task state before continuing. If `create_checkpoint` is unavailable (e.g. `task_checkpoints` table missing), continue with `.specs/` artifact state as the fallback.
@@ -117,7 +117,7 @@ Quick artifacts live under `.specs/quick/NNN-slug/` with a `TASK.md` (one-line i
117
117
  - The verification-agent re-derives coverage independently using evidence-or-zero and does not inherit the author's mental model.
118
118
  - The fix → re-verify loop is capped at 3 iterations before escalating to `Blocked`.
119
119
  - Distill lesson signals through `references/lessons.md` when validation produces grounded reusable failures.
120
- 7. Before the delivery chain's Propose stage (PR creation), write and commit `.specs/project/STATE.md`, `.specs/HANDOFF.md`, and `.specs/project/FEATURES.json` on the branch — not merely "after meaningful progress" during Execute, but committed before `gh pr create`. **Deterministic backing (run it, do not eyeball it):** `python3 skills/massa-ai/scripts/check_specs_delivered.py <feature> [--root .]` — a non-zero exit blocks Propose (see `references/implementation-delivery.md` stage 3.5 and GATE-02). If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved). Record `references/spec-driven/memory.md` decisions, blockers, handoff, and completion evidence per that reference's write triggers.
120
+ 7. Before the delivery chain's Propose stage (PR creation), write and commit `.specs/project/STATE.md`, `.specs/HANDOFF.md`, and `.specs/project/FEATURES.json` on the branch — not merely "after meaningful progress" during Execute, but committed before `gh pr create`. **Deterministic backing (run it, do not eyeball it):** `bun skills/massa-ai/scripts/check_specs_delivered.ts <feature> [--root .]` — a non-zero exit blocks Propose (see `references/implementation-delivery.md` stage 3.5 and GATE-02). If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved). Record `references/spec-driven/memory.md` decisions, blockers, handoff, and completion evidence per that reference's write triggers.
121
121
  8. When the user splits planning and implementation across clean chats, resume from the canonical `.specs/` artifacts — `.specs/project/STATE.md`, `.specs/project/FEATURES.json`, `.specs/HANDOFF.md`, and the feature's phase files. This workflow owns the spec phase contracts on both sides of the split; there is no separate save/load procedure.
122
122
  9. Complete the configured Plan Challenge Gate for non-trivial plans and complete `references/evidence-gate.md` before claiming completion.
123
123
 
@@ -128,7 +128,7 @@ Quick artifacts live under `.specs/quick/NNN-slug/` with a `TASK.md` (one-line i
128
128
  - Feature registry is `.specs/project/FEATURES.json`.
129
129
  - Handoff is `.specs/HANDOFF.md`.
130
130
  - `.specs/` files are canonical and git-tracked; there is no secondary store.
131
- - `.specs/lessons.json` is the machine-owned canonical lessons state; `.specs/LESSONS.md` is its rendered view, regenerated by `scripts/lessons.py` on every write — do not hand-edit.
131
+ - `.specs/lessons.json` is the machine-owned canonical lessons state, the single lessons store — do not hand-edit; `lessons list` is the on-demand view.
132
132
  - Optional `.specs-exports/` projections are untracked debug aids only.
133
133
  - `remember` remains canonical for durable cross-session decisions, rejected approaches, reusable patterns, and verification recipes.
134
134
 
@@ -52,7 +52,7 @@ Do not use this workflow for findings-only test coverage, assertion quality, fix
52
52
  10. Use strict harness sensors:
53
53
  - Never rely on AI subjective evaluation.
54
54
  - If verification found a reusable signal (`ac_gap`, `surviving_mutant`, `spec_precision_gap`, `spec_deviation`, `gate_fail`), record it via `references/lessons.md`:
55
- `python3 skills/massa-ai/scripts/lessons.py --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
55
+ `bun skills/massa-ai/scripts/lessons.ts --root . add --feature "<slug>" --signal "<signal>" --source "<ref>" --text "<one terse lesson>"`
56
56
  - 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.
57
57
  - Run the actual focused test command first, then broader relevant suites when feasible, such as `rtk yarn test`, `rtk npm test`, `rtk pytest`, or `rtk cargo test`.
58
58
  - Continue only when the execution harness returns a clean exit code, or report the exact skipped-check reason.
@@ -1,128 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- check_commit.py - deterministic Conventional Commits validation.
4
-
5
- The per-task atomic-commit rule mandates Conventional Commits 1.0.0. This makes
6
- that rule checkable instead of trusting the model to remember the format. Pure
7
- standard library, zero dependencies, agent-agnostic.
8
-
9
- It reads the message from (in priority order): a positional file path, --message,
10
- or stdin. The file-path form matches how git passes the message file to a
11
- `commit-msg` hook, so this doubles as an optional git-level guard WITHOUT
12
- coupling the skill to any AI agent:
13
-
14
- ln -s skills/massa-ai/scripts/check_commit.py .git/hooks/commit-msg && chmod +x .git/hooks/commit-msg
15
-
16
- What it checks:
17
- ERROR - header does not match type(scope)!: description
18
- (an optional leading `[KEY] ` Jira-style prefix is stripped first -
19
- see massa-ai's `workflows/commit.md` §8, e.g. `[SA-142] feat(x): y`)
20
- ERROR - type is not one of the allowed Conventional Commits types
21
- ERROR - description is empty, starts uppercase, or ends with a period
22
- ERROR - `!` breaking marker present but no `BREAKING CHANGE:` footer
23
- WARN - header longer than 72 characters
24
-
25
- Usage:
26
- python3 skills/massa-ai/scripts/check_commit.py [msgfile]
27
- python3 skills/massa-ai/scripts/check_commit.py --message "feat(auth): add email validation"
28
- python3 skills/massa-ai/scripts/check_commit.py --message "[SA-142] feat(auth): reject expired tokens"
29
- echo "fix(cart): prevent negative quantity" | python3 skills/massa-ai/scripts/check_commit.py
30
-
31
- Exit codes: 0 pass, 1 violation, 2 usage error.
32
- """
33
-
34
- import argparse
35
- import re
36
- import sys
37
-
38
- TYPES = ["feat", "fix", "refactor", "docs", "test", "style", "perf", "build", "ci", "chore"]
39
-
40
- # massa-ai patch (D1c): an optional leading Jira-style key prefix, e.g.
41
- # "[SA-142] feat(auth): reject expired tokens" (workflows/commit.md §8). The
42
- # key shape mirrors the branch-key regex there: [A-Z][A-Z0-9]{1,9}-\d+.
43
- PREFIX_RE = re.compile(r"^\[(?P<key>[A-Z][A-Z0-9]{1,9}-\d+)\]\s+(?P<rest>.+)$")
44
- HEADER_RE = re.compile(r"^(?P<type>\w+)(?:\((?P<scope>[^)]+)\))?(?P<bang>!)?: (?P<desc>.+)$")
45
-
46
-
47
- def read_message(args):
48
- if args.message is not None:
49
- return args.message
50
- if args.msgfile:
51
- with open(args.msgfile, "r", encoding="utf-8") as f:
52
- return f.read()
53
- if not sys.stdin.isatty():
54
- return sys.stdin.read()
55
- return ""
56
-
57
-
58
- def check(message):
59
- errors, warnings = [], []
60
- # Ignore comment lines (git puts '#' comments in the message file).
61
- lines = [ln for ln in message.splitlines() if not ln.lstrip().startswith("#")]
62
- # Trim leading blank lines.
63
- while lines and not lines[0].strip():
64
- lines.pop(0)
65
- if not lines:
66
- return (["empty commit message"], warnings)
67
-
68
- header = lines[0].rstrip()
69
- if len(header) > 72:
70
- warnings.append(f"header is {len(header)} chars (>72): {header[:60]}...")
71
-
72
- # Strip an optional massa-ai Jira-style `[KEY] ` prefix before matching the
73
- # Conventional Commits header shape.
74
- prefixed = PREFIX_RE.match(header)
75
- header_body = prefixed.group("rest") if prefixed else header
76
-
77
- m = HEADER_RE.match(header_body)
78
- if not m:
79
- errors.append(f"header does not match 'type(scope): description': {header!r}")
80
- return (errors, warnings)
81
-
82
- ctype = m.group("type")
83
- desc = m.group("desc")
84
- bang = m.group("bang")
85
-
86
- if ctype not in TYPES:
87
- errors.append(f"type '{ctype}' is not one of: {', '.join(TYPES)}")
88
- if not desc.strip():
89
- errors.append("description is empty")
90
- else:
91
- if desc[:1].isupper():
92
- errors.append(f"description should start lowercase: '{desc[:30]}'")
93
- if desc.rstrip().endswith("."):
94
- errors.append("description should not end with a period")
95
-
96
- body = "\n".join(lines[1:])
97
- breaking_footer = bool(re.search(r"^BREAKING CHANGE:", body, re.MULTILINE))
98
- if bang and not breaking_footer:
99
- errors.append("'!' breaking marker present but no 'BREAKING CHANGE:' footer")
100
-
101
- return (errors, warnings)
102
-
103
-
104
- def main(argv=None):
105
- p = argparse.ArgumentParser(prog="check_commit.py", description="Validate a Conventional Commits message.")
106
- p.add_argument("msgfile", nargs="?", default=None, help="path to a commit message file (as git passes to commit-msg)")
107
- p.add_argument("--message", default=None, help="the commit message as a string")
108
- args = p.parse_args(argv)
109
-
110
- message = read_message(args)
111
- if not message.strip():
112
- print("check_commit: no message provided (pass a file, --message, or pipe via stdin).", file=sys.stderr)
113
- return 2
114
-
115
- errors, warnings = check(message)
116
- for w in warnings:
117
- print(f" WARN {w}")
118
- for e in errors:
119
- print(f" ERROR {e}")
120
- if errors:
121
- print("\ncheck_commit: FAIL - see https://www.conventionalcommits.org/en/v1.0.0/")
122
- return 1
123
- print("check_commit: OK")
124
- return 0
125
-
126
-
127
- if __name__ == "__main__":
128
- raise SystemExit(main())
@@ -1,137 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- check_specs_delivered.py - deterministic gate: .specs/ artifacts are
4
- committed on the branch before a PR is opened (GATE-02).
5
-
6
- New script, not a TLC port - massa-ai's `implementation-delivery.md` chain has
7
- no gate requiring `.specs/` (spec/context/design/tasks/validation, project
8
- state, handoff, features registry) to be committed before `gh pr create`. This
9
- turns that gap into two conjunctive, deterministic checks:
10
-
11
- 1. `git status --porcelain -- .specs/` is empty - nothing under .specs/ is
12
- modified-but-uncommitted or untracked.
13
- 2. The feature's `spec.md` (always required) plus any of
14
- `{context,design,tasks,validation}.md` that exist on disk, plus
15
- `.specs/project/STATE.md`, `.specs/HANDOFF.md`, and
16
- `.specs/project/FEATURES.json`, are tracked on HEAD
17
- (`git ls-tree -r --name-only HEAD`).
18
-
19
- Check 2 exists because check 1 alone is not sufficient: a feature whose
20
- `.specs/` artifacts were simply never written is porcelain-clean (nothing to
21
- be dirty about) while still failing the actual requirement. Absence must fail,
22
- not pass.
23
-
24
- Pure `git` + standard library. No dependencies. Run from the project root (the
25
- dir that contains .specs/), or pass --root.
26
-
27
- Usage:
28
- python3 skills/massa-ai/scripts/check_specs_delivered.py <feature> [--root DIR]
29
-
30
- Exit codes: 0 all required paths clean + tracked, 1 a required path is dirty,
31
- untracked, or not tracked on HEAD (paths named), 2 usage/git error.
32
- """
33
-
34
- import argparse
35
- import os
36
- import subprocess
37
- import sys
38
-
39
- # Always required for the named feature.
40
- FEATURE_REQUIRED = ["spec.md"]
41
- # Required only when present on disk (not every feature reaches every phase).
42
- FEATURE_OPTIONAL = ["context.md", "design.md", "tasks.md", "validation.md"]
43
-
44
- STATE_FILES = [
45
- os.path.join(".specs", "project", "STATE.md"),
46
- os.path.join(".specs", "HANDOFF.md"),
47
- os.path.join(".specs", "project", "FEATURES.json"),
48
- ]
49
-
50
-
51
- def _run_git(args, root):
52
- try:
53
- proc = subprocess.run(
54
- ["git"] + args, cwd=root, capture_output=True, text=True, check=False
55
- )
56
- except FileNotFoundError:
57
- print("check_specs_delivered: git not found on PATH", file=sys.stderr)
58
- raise SystemExit(2)
59
- if proc.returncode != 0:
60
- print(
61
- f"check_specs_delivered: git {' '.join(args)} failed: {proc.stderr.strip()}",
62
- file=sys.stderr,
63
- )
64
- raise SystemExit(2)
65
- return proc.stdout
66
-
67
-
68
- def _porcelain_dirty_paths(root):
69
- """Lines from `git status --porcelain -- .specs/` (empty = clean)."""
70
- out = _run_git(["status", "--porcelain", "--", ".specs/"], root)
71
- return [ln for ln in out.splitlines() if ln.strip()]
72
-
73
-
74
- def _tracked_on_head(root):
75
- """Set of every path tracked on HEAD, repo-root-relative, '/'-separated."""
76
- out = _run_git(["ls-tree", "-r", "--name-only", "HEAD"], root)
77
- return set(out.splitlines())
78
-
79
-
80
- def _resolve_feature_dir(root, feature):
81
- if os.path.isabs(feature) or os.sep in feature:
82
- return os.path.normpath(feature)
83
- return os.path.join(root, ".specs", "features", feature)
84
-
85
-
86
- def required_paths(root, feature):
87
- """Repo-root-relative, '/'-separated paths this feature must have tracked."""
88
- fdir = _resolve_feature_dir(root, feature)
89
- fdir_rel = os.path.relpath(fdir, root)
90
- paths = [os.path.join(fdir_rel, name) for name in FEATURE_REQUIRED]
91
- if os.path.isdir(fdir):
92
- for name in FEATURE_OPTIONAL:
93
- if os.path.isfile(os.path.join(fdir, name)):
94
- paths.append(os.path.join(fdir_rel, name))
95
- paths.extend(STATE_FILES)
96
- return [p.replace(os.sep, "/") for p in paths]
97
-
98
-
99
- def check(root, feature):
100
- """Return (errors, checked_paths). errors empty = pass."""
101
- errors = []
102
-
103
- for ln in _porcelain_dirty_paths(root):
104
- errors.append(f"uncommitted/untracked under .specs/: {ln.strip()}")
105
-
106
- paths = required_paths(root, feature)
107
- tracked = _tracked_on_head(root)
108
- for p in paths:
109
- if p not in tracked:
110
- errors.append(f"not tracked on HEAD: {p}")
111
-
112
- return errors, paths
113
-
114
-
115
- def main(argv=None):
116
- p = argparse.ArgumentParser(
117
- prog="check_specs_delivered.py",
118
- description="Gate: .specs/ artifacts committed on the branch before PR (GATE-02).",
119
- )
120
- p.add_argument("feature", help="Feature slug under <root>/.specs/features/, or a direct path")
121
- p.add_argument("--root", default=".", help="Project root containing .specs/ (default: current dir)")
122
- args = p.parse_args(argv)
123
- root = os.path.abspath(args.root)
124
-
125
- errors, checked = check(root, args.feature)
126
-
127
- for e in errors:
128
- print(f" ERROR {e}")
129
- print(f"\ncheck_specs_delivered: checked {len(checked)} path(s):")
130
- for c in checked:
131
- print(f" - {c}")
132
- print(f"check_specs_delivered: {len(errors)} error(s)")
133
- return 1 if errors else 0
134
-
135
-
136
- if __name__ == "__main__":
137
- raise SystemExit(main())