@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
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "massa-ai",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "massa-ai — semantic code search, memory, and context compression for Cursor"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/cursor-plugin",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "massa-ai plugin for Cursor — semantic code search, memory, and context compression",
5
5
  "files": [
6
6
  "agents",
@@ -38,7 +38,7 @@ Use this compact shape when closing docs or skill work:
38
38
  ✅ [Verified] Skill validation and stale-reference scans passed.
39
39
  🏁 [Finished] Updated massa-ai references. Memory outcome: durable decision stored. Remaining risk: none found.
40
40
 
41
- Evidence: `rtk python3 skills/skill-architect/scripts/validate_skill.py skills/massa-ai --format json` passed; stale-reference and local-link scans passed.
41
+ Evidence: `bun run scripts/generate-skill-artifacts.ts --check` passed; stale-reference and local-link scans passed.
42
42
  Changed artifacts: `skills/massa-ai/SKILL.md`, `workflows/spec-driven.md`, `references/spec-driven/`, and `references/memory-policy.md`.
43
43
  Memory outcome: wrote decision memory `dec_...`; no failed memory writes.
44
44
  Residual risk: none found.
@@ -44,7 +44,7 @@ Every hook exits 0 on bad stdin or non-critical error. Blocking hooks are
44
44
  | Hook | Event | Enforces |
45
45
  |---|---|---|
46
46
  | `stop_evidence_gate.py` | Stop | `references/evidence-gate.md` |
47
- | `continuous_learning_evaluate.py` | Stop | `references/lessons.md`, `scripts/lessons.py` |
47
+ | `continuous_learning_evaluate.py` | Stop | `references/lessons.md`, `scripts/lessons.ts` |
48
48
  | `precompact_save_state.py` | PreCompact | `workflows/long-session.md`, `references/context-firewall.md` |
49
49
  | `suggest_compact.py` | PreToolUse(Edit\|Write) | `references/context-firewall.md` (compaction boundary) |
50
50
  | `gateguard_fact_force.py` | PreToolUse(Edit) | `references/context-firewall.md` (investigate-before-edit) |
@@ -100,7 +100,7 @@ absent, it falls back to the flat evidence matrix (graceful degradation).
100
100
 
101
101
  The continuous-learning loop writes two stores, not one:
102
102
 
103
- - `lessons.py add` / `import` — deterministic grounded file store
103
+ - `lessons.ts add` / `import` — deterministic grounded file store
104
104
  (`.specs/lessons.json`); refuses ungrounded lessons; promotion/quarantine.
105
105
  - `remember` — durable memory, best-effort via REST (`MASSA_AI_API_URL`),
106
106
  file-only fallback when REST is unavailable.
@@ -22,8 +22,8 @@ human chose to merge it".
22
22
  | 1 | Isolate | `git fetch origin <base> && git worktree add -b <type>/<slug> <path> origin/<base>` | Branch name taken → suffix `-2`. Worktree path taken → reuse it only if its branch matches |
23
23
  | 2 | Implement | one task → gate → `git commit` | Gate red → fix before committing. Never commit through a failing gate |
24
24
  | 3 | Push | `git push -u origin <type>/<slug>` | Rejected non-fast-forward → `git fetch` + rebase, never force-push a shared branch |
25
- | 3.5 | Deliver specs | `python3 skills/massa-ai/scripts/check_specs_delivered.py <feature> [--root .]` | Non-zero → commit the missing `.specs/` updates (a `docs(specs):`-type commit is normal), push, re-run. Defensive fallback — should not fire when the close-out task already committed `.specs/` before the first push |
26
- | 4 | Propose — precondition: Stage 3.5 (`check_specs_delivered.py`) green | `gh pr create --base <base> --title <t> --body <b>` | `gh` absent/unauthenticated → degraded path |
25
+ | 3.5 | Deliver specs | `bun skills/massa-ai/scripts/check_specs_delivered.ts <feature> [--root .]` | Non-zero → commit the missing `.specs/` updates (a `docs(specs):`-type commit is normal), push, re-run. Defensive fallback — should not fire when the close-out task already committed `.specs/` before the first push |
26
+ | 4 | Propose — precondition: Stage 3.5 (`check_specs_delivered.ts`) green | `gh pr create --base <base> --title <t> --body <b>` | `gh` absent/unauthenticated → degraded path |
27
27
  | 5 | Watch | `gh pr checks --watch` | No checks configured → say so; do not claim CI passed |
28
28
  | 6 | Repair | fix on the branch, commit, return to stage 5 | Capped at 3 iterations, then stop as `Blocked` |
29
29
  | 7 | **Ask** | report the PR URL and the green check list, then **stop** | — |
@@ -76,7 +76,7 @@ One explicit delivery authorization per feature, obtained before implementation
76
76
 
77
77
  ### Stage 3.5 — deliver specs before PR (defensive fallback)
78
78
 
79
- Between Push and Propose, all feature `.specs/` artifacts (`spec/context/design/tasks/validation` as applicable), `.specs/project/STATE.md`, `.specs/HANDOFF.md`, and `.specs/project/FEATURES.json` must be updated and committed on the branch. **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. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
79
+ Between Push and Propose, all feature `.specs/` artifacts (`spec/context/design/tasks/validation` as applicable), `.specs/project/STATE.md`, `.specs/HANDOFF.md`, and `.specs/project/FEATURES.json` must be updated and committed on the branch. **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. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
80
80
 
81
81
  **Nominal path: this stage should never fire.** The feature's own close-out task (the last task before delivery — see `workflows/spec-driven.md` step 7) commits `.specs/` updates **before** the first push, so stage 3.5's remediation is a defensive fallback for the rare case something slipped through, not the normal place `.specs/` gets committed. On failure: commit the missing `.specs/` updates (a `docs(specs):`-type commit is normal), push, re-run this stage. No commits may land between the close-out commit and PR creation.
82
82
 
@@ -4,9 +4,8 @@ Use this reference when loading confirmed project lessons during startup, or rec
4
4
 
5
5
  ## Artifacts
6
6
 
7
- - `.specs/lessons.json` — canonical machine-owned lesson state. Read it; do not hand-edit.
8
- - `.specs/LESSONS.md` — rendered playbook generated by the script. Read it; do not hand-edit.
9
- - `skills/massa-ai/scripts/lessons.py` — deterministic bookkeeping script.
7
+ - `.specs/lessons.json` — canonical machine-owned lesson state, the single lessons store. Read it; do not hand-edit.
8
+ - `skills/massa-ai/scripts/lessons.ts` — deterministic bookkeeping script; `lessons list` is the on-demand view.
10
9
 
11
10
  ## Lesson Signal Table
12
11
 
@@ -23,7 +22,7 @@ Use this reference when loading confirmed project lessons during startup, or rec
23
22
  After a workflow's verification step finds a concrete reusable signal, record it:
24
23
 
25
24
  ```bash
26
- python3 skills/massa-ai/scripts/lessons.py --root . add \
25
+ bun skills/massa-ai/scripts/lessons.ts --root . add \
27
26
  --feature "<feature-slug>" \
28
27
  --signal "<ac_gap|surviving_mutant|spec_precision_gap|spec_deviation|gate_fail>" \
29
28
  --source "<validation.md source, AC id, file:line, mutant id, or SPEC_DEVIATION ref>" \
@@ -51,7 +50,7 @@ Run this self-check after verification: if a failed acceptance criterion, surviv
51
50
  During startup of any applicable workflow, load confirmed lessons when `.specs/lessons.json` exists:
52
51
 
53
52
  ```bash
54
- python3 skills/massa-ai/scripts/lessons.py --root . list --status confirmed [--scope <relevant>]
53
+ bun skills/massa-ai/scripts/lessons.ts --root . list --status confirmed [--scope <relevant>]
55
54
  ```
56
55
 
57
56
  - Use `--scope` or `--query` to keep the loaded set small.
@@ -69,7 +68,7 @@ python3 skills/massa-ai/scripts/lessons.py --root . list --status confirmed [--s
69
68
 
70
69
  ## No-Script Fallback
71
70
 
72
- If `lessons.py` is unavailable or cannot run, record `Lessons: skipped - script unavailable` in the validation report or evidence gate, keep the raw signal in the report, and do not hand-edit `lessons.json` or `LESSONS.md`. A future run with the script can import the validated signal.
71
+ If `lessons.ts` is unavailable or cannot run, record `Lessons: skipped - script unavailable` in the validation report or evidence gate, keep the raw signal in the report, and do not hand-edit `lessons.json`. A future run with the script can import the validated signal.
73
72
 
74
73
  ## Continuous-Learning Loop (hook-fed)
75
74
 
@@ -82,13 +81,13 @@ The lessons layer is a closed loop, not manual-only. Two runtime hooks
82
81
  2. **evaluate** — `continuous_learning_evaluate.py` (Stop) reads the active
83
82
  massa-ai context from `.specs/project/STATE.md` and the observations
84
83
  buffer. For each observation that already carries grounded fields
85
- (`signal`, `text`, `source`, `feature`), it calls `lessons.py add` with the
84
+ (`signal`, `text`, `source`, `feature`), it calls `lessons.ts add` with the
86
85
  `--project`/`--session`/`--workflow`/`--entity` context. Ungrounded
87
86
  observations are left in the buffer for agent input and logged as skipped.
88
87
 
89
88
  ### massa-ai Dual-Write
90
89
 
91
- `lessons.py add` and `import` best-effort write massa-ai memory so the file store
90
+ `lessons.ts add` and `import` best-effort write massa-ai memory so the file store
92
91
  and durable memory stay consistent:
93
92
 
94
93
  - **type** is always `pattern` (lessons are procedural knowledge). `procedural`
@@ -105,8 +104,8 @@ and durable memory stay consistent:
105
104
  ### Round-Trip
106
105
 
107
106
  ```bash
108
- python3 skills/massa-ai/scripts/lessons.py --root . export --out lessons.export.json
109
- python3 skills/massa-ai/scripts/lessons.py --root . import --in lessons.export.json
107
+ bun skills/massa-ai/scripts/lessons.ts --root . export --out lessons.export.json
108
+ bun skills/massa-ai/scripts/lessons.ts --root . import --in lessons.export.json
110
109
  ```
111
110
 
112
111
  `export`/`import` round-trip the file store; `import` re-emits massa-ai memory
@@ -285,7 +285,7 @@ delay. Preferred shell pattern:
285
285
  MASSA_AI_API_URL="${MASSA_AI_API_URL:-http://localhost:3333}"
286
286
  for i in $(seq 1 40); do
287
287
  result=$(rtk curl -s "$MASSA_AI_API_URL/api/v1/project/index/status/JOB_ID")
288
- status=$(printf '%s' "$result" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['status'])")
288
+ status=$(printf '%s' "$result" | bun -e "console.log(JSON.parse(await Bun.stdin.text()).data.status)")
289
289
  printf '[%s] status=%s\n' "$i" "$status"
290
290
  [ "$status" = "completed" ] || [ "$status" = "failed" ] && break
291
291
  sleep 15
@@ -23,7 +23,7 @@ continue only when the next decision still needs more.
23
23
  | 2 — Host config | `.claude/` (`settings.json`, `settings.local.json`, `commands/`, `agents/`, `skills/`, `hooks/`), `.cursor/` (`rules/`, `mcp.json`), `.github/copilot-instructions.md`, `.opencode/`, `.codex/` | Permissions, hooks, host-specific tooling, MCP registrations |
24
24
  | 3 — Product docs | `README.md`, `CONTRIBUTING.md`, `docs/`, `ARCHITECTURE.md` | Install/run/build surface, contribution protocol, architecture |
25
25
  | 4 — Delivery config | `.github/workflows/`, `CHANGELOG.md`, `package.json` / `Cargo.toml` / `build.gradle*` / `pyproject.toml`, `.tool-versions`, `mise.toml`, `Makefile` | CI gates, release rules, runtime and toolchain pins, task commands |
26
- | 5 — Live state | `.specs/project/STATE.md`, `.specs/project/FEATURES.json`, `.specs/HANDOFF.md`, `.specs/LESSONS.md` | In-flight work, decisions already made, confirmed lessons |
26
+ | 5 — Live state | `.specs/project/STATE.md`, `.specs/project/FEATURES.json`, `.specs/HANDOFF.md`, `.specs/lessons.json` (single store; `lessons list` is the on-demand view) | In-flight work, decisions already made, confirmed lessons |
27
27
 
28
28
  Tier 1 and Tier 3 (`README.md`) are mandatory in every workflow. Tiers 2, 4, and
29
29
  5 are read when the task touches host tooling, a gate/release surface, or
@@ -27,8 +27,7 @@ Use these logical paths:
27
27
  - `.specs/features/<slug>/validation.md` — feature validation report
28
28
  - `.specs/quick/NNN-slug/TASK.md` — quick-mode task (one-line intent + acceptance)
29
29
  - `.specs/quick/NNN-slug/SUMMARY.md` — quick-mode result (files changed + gate evidence)
30
- - `.specs/lessons.json` — machine-owned lesson state (managed by `lessons.py`)
31
- - `.specs/LESSONS.md` — rendered lesson playbook (read-only, generated by `lessons.py`)
30
+ - `.specs/lessons.json` — the single lessons store, machine-owned (managed by `lessons.ts`); `lessons list` is the on-demand view
32
31
 
33
32
  ## Quick Artifacts
34
33
 
@@ -61,21 +60,21 @@ Quick tasks are also listed in the STATE.md Quick Tasks table (see `references/s
61
60
 
62
61
  Load only what you need. Prefer metadata-only inspection before loading full content.
63
62
 
64
- - **Feature registry status:** `cat .specs/project/FEATURES.json | python3 -c "import json,sys; d=json.load(sys.stdin); print('active:', d.get('active_feature')); [print(f['id'], f['status']) for f in d.get('features',[])]"`
63
+ - **Feature registry status:** `bun -e "const d=await Bun.file('.specs/project/FEATURES.json').json(); console.log('active:', d.active_feature); for (const f of d.features??[]) console.log(f.id, f.status);"`
65
64
  - **Full feature registry:** `cat .specs/project/FEATURES.json`
66
65
  - **Project state:** `cat .specs/project/STATE.md`
67
66
  - **Handoff:** `cat .specs/HANDOFF.md`
68
67
  - **Feature spec:** `cat .specs/features/<slug>/spec.md`
69
- - **Confirmed lessons:** `python3 skills/massa-ai/scripts/lessons.py --root . list --status confirmed [--scope <relevant>]`
68
+ - **Confirmed lessons:** `bun skills/massa-ai/scripts/lessons.ts --root . list --status confirmed [--scope <relevant>]`
70
69
 
71
70
  ## Writing Artifacts
72
71
 
73
- Write artifacts directly to `.specs/` files. Use a here-doc or `printf` for Markdown, or `python3 -c` for JSON mutations.
72
+ Write artifacts directly to `.specs/` files. Use a here-doc or `printf` for Markdown, or `bun -e` for JSON mutations.
74
73
 
75
74
  - **Update state:** `printf '...' > .specs/project/STATE.md`
76
75
  - **Update handoff:** `printf '...' > .specs/HANDOFF.md`
77
76
  - **Write feature artifact:** `printf '...' > .specs/features/<slug>/design.md`
78
- - **Update feature registry:** use `python3 -c` to read-modify-write `FEATURES.json`
77
+ - **Update feature registry:** use `bun -e` to read-modify-write `FEATURES.json`
79
78
 
80
79
  Feature activate/complete flows update `.specs/project/FEATURES.json`, `.specs/project/STATE.md`, and `.specs/HANDOFF.md` through file writes.
81
80
 
@@ -83,7 +82,7 @@ Feature activate/complete flows update `.specs/project/FEATURES.json`, `.specs/p
83
82
 
84
83
  - Feature artifacts under `.specs/features/<slug>/` are versioned by design (one file per slug).
85
84
  - `.specs/project/FEATURES.json` and `.specs/project/STATE.md` are append-only versioned by their git history.
86
- - `lessons.json` versioning is managed automatically by `lessons.py`.
85
+ - `lessons.json` versioning is managed automatically by `lessons.ts`.
87
86
  - When content must be superseded (e.g., a decision), update the existing file and rely on git history for the prior version.
88
87
 
89
88
  ## Debug Exports
@@ -94,5 +93,5 @@ Exports under `.specs-exports/` are optional, untracked review aids. Use `cp -r
94
93
 
95
94
  - `.specs/` directory missing or not writable: block spec-driven state mutation; do not fall back to memory or chat.
96
95
  - Required artifact missing: create it on first write or block unless it is an approved initial creation.
97
- - `lessons.py` unavailable: skip lessons loading/recording, record skipped reason in validation report.
96
+ - `lessons.ts` unavailable: skip lessons loading/recording, record skipped reason in validation report.
98
97
  .specs/ files
@@ -35,7 +35,7 @@ Read `.specs/features/<slug>/spec.md` before designing. If `.specs/features/<slu
35
35
 
36
36
  Silently ignoring an active decision is not an option — it creates invisible inconsistency across features.
37
37
 
38
- **Also load confirmed lessons** relevant to this feature: `python3 skills/massa-ai/scripts/lessons.py list --status confirmed` (filter with `--scope`/`--query`). These are past verification failures distilled into guidance — apply them while designing. Load only `confirmed`. Skip silently if no store or no code tool. See [lessons.md](lessons.md).
38
+ **Also load confirmed lessons** relevant to this feature: `bun skills/massa-ai/scripts/lessons.ts list --status confirmed` (filter with `--scope`/`--query`). These are past verification failures distilled into guidance — apply them while designing. Load only `confirmed`. Skip silently if no store or no code tool. See [lessons.md](lessons.md).
39
39
 
40
40
  ### 1.5. Research (Optional but Recommended)
41
41
 
@@ -302,13 +302,13 @@ for reuse across multiple endpoints.
302
302
  - Never sneak in "while I'm here" changes.
303
303
  - If tests are part of the task, include them in the same commit.
304
304
 
305
- **Deterministic backing (run it, do not eyeball it):** `python3 skills/massa-ai/scripts/check_commit.py --message "<your message>"` before committing. A non-zero exit means fix the format first — this makes the format rule enforceable instead of memory-dependent. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
305
+ **Deterministic backing (run it, do not eyeball it):** `bun skills/massa-ai/scripts/check_commit.ts --message "<your message>"` before committing. A non-zero exit means fix the format first — this makes the format rule enforceable instead of memory-dependent. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
306
306
 
307
307
  **Optional git-level guard (git only, no agent dependency).** In a git repo the same check can run on every commit by wiring it as a `commit-msg` hook, so a malformed message is rejected regardless of who or what drives the commit:
308
308
 
309
309
  ```bash
310
310
  # from the repo root, one time:
311
- ln -sf skills/massa-ai/scripts/check_commit.py .git/hooks/commit-msg && chmod +x .git/hooks/commit-msg
311
+ ln -sf skills/massa-ai/scripts/check_commit.ts .git/hooks/commit-msg && chmod +x .git/hooks/commit-msg
312
312
  ```
313
313
 
314
314
  This is a plain git hook, not tied to any editor or assistant. Skip it if the project manages hooks its own way (for example a pre-commit framework); the manual check above still applies.
@@ -316,14 +316,14 @@ This is a plain git hook, not tied to any editor or assistant. Skip it if the pr
316
316
  **Distill a confirmed lesson** when a task produced a reusable signal (an unexpected failure mode, a confirmed pattern, a corrected assumption):
317
317
 
318
318
  ```
319
- python3 skills/massa-ai/scripts/lessons.py --root . add \
319
+ bun skills/massa-ai/scripts/lessons.ts --root . add \
320
320
  --feature <slug> --signal <S> --source <src> --text "<T>" --scope <O>
321
321
  ```
322
322
 
323
323
  `--source` is mandatory (grounding gate). Load applicable confirmed lessons before starting a task:
324
324
 
325
325
  ```
326
- python3 skills/massa-ai/scripts/lessons.py --root . list --status confirmed [--scope <relevant>]
326
+ bun skills/massa-ai/scripts/lessons.ts --root . list --status confirmed [--scope <relevant>]
327
327
  ```
328
328
 
329
329
  ### 8. Scope Guardrail / Scope Control
@@ -452,7 +452,7 @@ Then run `references/spec-driven/validate.md` as the final Execute gate. The ver
452
452
  **Status**: ✅ Complete | ❌ Blocked | ⚠️ Partial
453
453
  ```
454
454
 
455
- **After the LAST task:** dispatch the Verifier sub-agent (see step 9 and [sub-agents.md](sub-agents.md)) for independent feature-level validation, including the spec-anchored check and discrimination sensor. Validation always runs automatically — never prompted. Execute is not done until the Verifier reports PASS and the validation report is written, confirmed deterministically by `python3 skills/massa-ai/scripts/validate_state.py <feature> [--root .]` (exit non-zero = not done); see [validate.md](validate.md). If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
455
+ **After the LAST task:** dispatch the Verifier sub-agent (see step 9 and [sub-agents.md](sub-agents.md)) for independent feature-level validation, including the spec-anchored check and discrimination sensor. Validation always runs automatically — never prompted. Execute is not done until the Verifier reports PASS and the validation report is written, confirmed deterministically by `bun skills/massa-ai/scripts/validate_state.ts <feature> [--root .]` (exit non-zero = not done); see [validate.md](validate.md). If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
456
456
 
457
457
  ---
458
458
 
@@ -9,7 +9,7 @@ If the feature has ambiguous gray areas (multiple valid approaches for user-faci
9
9
  - First user request and any supplied requirement source.
10
10
  - Current repository source and docs needed to understand scope.
11
11
  - Existing `.specs/features/<slug>/` artifacts when resuming.
12
- - `.specs/LESSONS.md` only when lessons are enabled by existing lesson artifacts.
12
+ - `.specs/lessons.json` (via `lessons list`) only when lessons are enabled by existing lesson artifacts.
13
13
 
14
14
  ## Required Sections
15
15
 
@@ -65,7 +65,7 @@ The table is canonical; the prose is the applied sweep. **Large/Complex** work m
65
65
 
66
66
  ### 1. Clarify Requirements
67
67
 
68
- **Load confirmed lessons first:** Before clarifying, load the project's confirmed lessons so past verification failures shape this spec instead of repeating. Run `python3 skills/massa-ai/scripts/lessons.py --root . list --status confirmed` (optionally `--scope [area]` or `--query [term]` for the area this feature touches) and apply what comes back as guidance. Load only `confirmed` — never `candidate` or `quarantined`. If no store exists yet or no code tool is available, skip silently. See [lessons.md](../lessons.md).
68
+ **Load confirmed lessons first:** Before clarifying, load the project's confirmed lessons so past verification failures shape this spec instead of repeating. Run `bun skills/massa-ai/scripts/lessons.ts --root . list --status confirmed` (optionally `--scope [area]` or `--query [term]` for the area this feature touches) and apply what comes back as guidance. Load only `confirmed` — never `candidate` or `quarantined`. If no store exists yet or no code tool is available, skip silently. See [lessons.md](../lessons.md).
69
69
 
70
70
  **Lightweight context scan first (Knowledge Verification Chain Step 1):** Before asking questions, briefly scan existing code, patterns, and neighboring features relevant to this feature. Prefer massa-ai tooling first (`list_projects`, `search`, `project_map`, `optimized_context`) before `ast-grep`/`rg`/`grep`, honoring freshness and source-precedence (current source overrides stale index/memory). Use what you find to ground your clarifying questions in reality — not to constrain the spec to current implementation. Keep it lightweight (stay within the <40k token budget; reuse the chain, no new machinery). The spec captures WHAT is needed, not only what exists.
71
71
 
@@ -115,7 +115,7 @@ Write every acceptance criterion in **EARS** (Easy Approach to Requirements Synt
115
115
 
116
116
  **Why patterns beat one shape:** failure states, state transitions, and optional behavior become first-class criteria instead of footnotes squeezed into WHEN/THEN. The patterns map onto the implicit-requirement dimensions above: state-transition integrity to State-driven; failure and external-dependency failure to Unwanted-behavior; feature flags to Optional-feature.
117
117
 
118
- **Rules:** one requirement per criterion (never bundle two behaviors); use concrete values (a specific status code, a specific message, a bound) rather than "quickly" or "gracefully"; every criterion contains a SHALL and is measurable. `python3 skills/massa-ai/scripts/validate_spec.py` flags any criterion without a SHALL and any that matches no recognized pattern.
118
+ **Rules:** one requirement per criterion (never bundle two behaviors); use concrete values (a specific status code, a specific message, a bound) rather than "quickly" or "gracefully"; every criterion contains a SHALL and is measurable. `bun skills/massa-ai/scripts/validate_spec.ts` flags any criterion without a SHALL and any that matches no recognized pattern.
119
119
 
120
120
  ### 4. Requirement Closure Gate (before confirm)
121
121
 
@@ -135,7 +135,7 @@ Before Design, Tasks, or Execute — and before presenting the spec for confirma
135
135
 
136
136
  6. **Continue only when** the Open Questions table is empty or every row has an accepted assumption.
137
137
 
138
- 7. **Deterministic backing (run it, do not eyeball it):** `python3 skills/massa-ai/scripts/validate_spec.py <feature> [--root .]` checks that required sections exist, every AC is EARS-shaped (has a SHALL), no Assumptions row has an empty default or rationale, and requirement IDs are well-formed. A non-zero exit means fix before confirming — the script checks structure; the judgment calls (is the interpretation right, is the outcome precise) stay yours. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
138
+ 7. **Deterministic backing (run it, do not eyeball it):** `bun skills/massa-ai/scripts/validate_spec.ts <feature> [--root .]` checks that required sections exist, every AC is EARS-shaped (has a SHALL), no Assumptions row has an empty default or rationale, and requirement IDs are well-formed. A non-zero exit means fix before confirming — the script checks structure; the judgment calls (is the interpretation right, is the outcome precise) stay yours. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
139
139
 
140
140
  Fix inline. This gate is bounded to THIS feature's stated dimensions and actual behavior — never to "anything imaginable." The Out of Scope table and anti-scope-creep rules remain the counterweights: the gate clarifies existing requirements, it never invents new ones.
141
141
 
@@ -286,13 +286,13 @@ How we know the feature is successful:
286
286
  - **Edge cases matter** — What breaks? What's empty? What's huge?
287
287
  - **Out of Scope prevents creep** — If it's not here, it doesn't get built
288
288
  - **Closure gate before confirm** — Three checks: unambiguity + precision, open-questions/assumptions closure, declined gray areas logged; scope-tiered; bounded to stated dimensions; never invents requirements
289
- - **Confirm after the gate passes** — Present the spec for user confirmation only after the closure gate passes (no unresolved-and-unmarked items remain) and `validate_spec.py` exits clean; user approves spec before moving to the discuss phase
289
+ - **Confirm after the gate passes** — Present the spec for user confirmation only after the closure gate passes (no unresolved-and-unmarked items remain) and `validate_spec.ts` exits clean; user approves spec before moving to the discuss phase
290
290
 
291
291
  ---
292
292
 
293
293
  ## Done
294
294
 
295
- Specify is done when every requirement has an ID, acceptance criteria are testable EARS statements, edge cases are named, out-of-scope boundaries are explicit, implicit-requirement dimensions are resolved or marked `N/A because <reason>`, the Requirement Closure Gate is satisfied, and `validate_spec.py` exits clean (or the no-code-execution-tool fallback was applied).
295
+ Specify is done when every requirement has an ID, acceptance criteria are testable EARS statements, edge cases are named, out-of-scope boundaries are explicit, implicit-requirement dimensions are resolved or marked `N/A because <reason>`, the Requirement Closure Gate is satisfied, and `validate_spec.ts` exits clean (or the no-code-execution-tool fallback was applied).
296
296
 
297
297
  ## Massa-ai Integration
298
298
 
@@ -148,7 +148,7 @@ Delegated work returns through the compact summary contract above. Planning, tas
148
148
 
149
149
  ## Standalone Fallback
150
150
 
151
- When sub-agents are unavailable (a single agent executing the full feature), use the standalone fresh-eyes fallback: run `references/spec-driven/validate.md` as a standalone pass — clear implementation assumptions, re-read `spec.md` and the diff from scratch, apply evidence-or-zero, run the spec-anchored coverage check and discrimination sensor, and write `.specs/features/<slug>/validation.md`. **Deterministic backing (run it, do not eyeball it):** `python3 skills/massa-ai/scripts/validate_state.py <feature> [--root .]` confirms the report is a real PASS before the feature is marked done. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
151
+ When sub-agents are unavailable (a single agent executing the full feature), use the standalone fresh-eyes fallback: run `references/spec-driven/validate.md` as a standalone pass — clear implementation assumptions, re-read `spec.md` and the diff from scratch, apply evidence-or-zero, run the spec-anchored coverage check and discrimination sensor, and write `.specs/features/<slug>/validation.md`. **Deterministic backing (run it, do not eyeball it):** `bun skills/massa-ai/scripts/validate_state.ts <feature> [--root .]` confirms the report is a real PASS before the feature is marked done. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
152
152
 
153
153
  ---
154
154
 
@@ -157,7 +157,7 @@ This keeps phase boundaries meaningful while letting the packing hit its target
157
157
 
158
158
  Before showing tasks to the user, run ALL three pre-approval checks. These are NOT optional — they are gates. If any check fails, restructure the tasks and re-run until all pass.
159
159
 
160
- **Deterministic backing (run it, do not eyeball it):** `python3 skills/massa-ai/scripts/validate_tasks.py <feature> [--root .]` enforces the structural half of these checks so they cannot drift: it flags a `Where` that names multiple files (granularity smell, Check 1), a diagram edge with no matching `Depends on` within a phase and vice-versa (Check 2), a task missing its `Tests` or `Gate` field, a `Tests: none` to confirm against the matrix (Check 3), and any dependency pointing to a later phase. A non-zero exit means restructure before presenting. The script checks structure; the two tables below (the layer-to-test co-location judgment) are still yours. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
160
+ **Deterministic backing (run it, do not eyeball it):** `bun skills/massa-ai/scripts/validate_tasks.ts <feature> [--root .]` enforces the structural half of these checks so they cannot drift: it flags a `Where` that names multiple files (granularity smell, Check 1), a diagram edge with no matching `Depends on` within a phase and vice-versa (Check 2), a task missing its `Tests` or `Gate` field, a `Tests: none` to confirm against the matrix (Check 3), and any dependency pointing to a later phase. A non-zero exit means restructure before presenting. The script checks structure; the two tables below (the layer-to-test co-location judgment) are still yours. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
161
161
 
162
162
  **Check 1: Task Granularity** — verify each task is atomic (see Granularity Check section).
163
163
 
@@ -516,4 +516,4 @@ Before sampling tests manually, prefer massa-ai tooling to read the codebase:
516
516
 
517
517
  ## Done
518
518
 
519
- Tasks is done when every in-scope requirement maps to one or more executable tasks, dependencies are explicit, the Test Coverage Matrix and Gate Check Commands are present, project testing guideline scan and pre-approval checks are recorded, the three mandatory pre-approval tables (Granularity, Diagram-Definition Cross-Check, Test Co-location Validation) pass, `validate_tasks.py` exits clean (or the no-code-execution-tool fallback was applied), and each task has deterministic validation with artifact-store evidence recorded.
519
+ Tasks is done when every in-scope requirement maps to one or more executable tasks, dependencies are explicit, the Test Coverage Matrix and Gate Check Commands are present, project testing guideline scan and pre-approval checks are recorded, the three mandatory pre-approval tables (Granularity, Diagram-Definition Cross-Check, Test Co-location Validation) pass, `validate_tasks.ts` exits clean (or the no-code-execution-tool fallback was applied), and each task has deterministic validation with artifact-store evidence recorded.
@@ -204,14 +204,14 @@ After all checks complete, the Verifier MUST:
204
204
  1. **Write the persisted report** to `.specs/features/<slug>/validation.md` (see template below). This file is the evidence artifact — it survives the session and can be referenced by CI, reviewers, or future agents. Record in `.specs/project/STATE.md` (Decisions) that validation evidence is available at that path.
205
205
  2. **Return a compact summary in chat** to the orchestrator (see Compact Chat Summary section below). The orchestrator surfaces it to the user and routes any ranked gaps to fix tasks.
206
206
 
207
- **Deterministic backing (run it, do not eyeball it):** after writing the report, run `python3 skills/massa-ai/scripts/validate_state.py <feature> [--root .]`. It confirms the report is real — present, verdict filled to PASS, and backed by at least one `file:line` evidence citation — so a missing, hollow, placeholder, or FAIL report cannot slip through as done. A non-zero exit means the feature is NOT done: repair the report or route the FAIL gaps to fix tasks, then re-run. This is the closing gate of Execute and runs automatically, the same way the lessons layer runs at distillation — never a manual step. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
207
+ **Deterministic backing (run it, do not eyeball it):** after writing the report, run `bun skills/massa-ai/scripts/validate_state.ts <feature> [--root .]`. It confirms the report is real — present, verdict filled to PASS, and backed by at least one `file:line` evidence citation — so a missing, hollow, placeholder, or FAIL report cannot slip through as done. A non-zero exit means the feature is NOT done: repair the report or route the FAIL gaps to fix tasks, then re-run. This is the closing gate of Execute and runs automatically, the same way the lessons layer runs at distillation — never a manual step. If no code-execution tool is available, run the same checks by reading the artifact (graceful degradation preserved).
208
208
 
209
209
  ### 10. Distill Lessons (MANDATORY when validation.md has signal)
210
210
 
211
211
  This is the closing action of validation — not a separate phase. Immediately after the report is written, turn its grounded failures into reusable, project-local guidance by following [references/lessons.md](../lessons.md) and the stub at [references/spec-driven/lessons.md](lessons.md). In short: for each surviving mutant, spec-precision gap, failed/uncovered AC, or `// SPEC_DEVIATION`, record one terse general lesson via:
212
212
 
213
213
  ```bash
214
- python3 skills/massa-ai/scripts/lessons.py --root . add --feature <slug> --signal "<signal>" --source "<source>" --text "<lesson>" --scope "<scope>"
214
+ bun skills/massa-ai/scripts/lessons.ts --root . add --feature <slug> --signal "<signal>" --source "<source>" --text "<lesson>" --scope "<scope>"
215
215
  ```
216
216
 
217
217
  The script enforces grounding (mandatory `--source`) and owns all bookkeeping. A clean PASS with no signal → record nothing. Run the self-check: if there was signal but no lesson was recorded, say so in chat. See [references/lessons.md](../lessons.md) for the exact commands, phrasing rules, scope discipline, and the no-script fallback.
@@ -398,5 +398,5 @@ Update `.specs/features/<slug>/spec.md` requirement statuses and reflect verifie
398
398
  - **Max 3 diagnostic iterations** — Prevents infinite investigation loops
399
399
  - **Update traceability** — Every verified requirement updates spec.md status and the FEATURES.json registry
400
400
  - **Always write the report file** — `.specs/features/<slug>/validation.md` is the persisted evidence artifact
401
- - **Distill after writing** — turn grounded failures into lessons via `python3 skills/massa-ai/scripts/lessons.py` ([references/lessons.md](../lessons.md)); clean PASS → no lesson
401
+ - **Distill after writing** — turn grounded failures into lessons via `bun skills/massa-ai/scripts/lessons.ts` ([references/lessons.md](../lessons.md)); clean PASS → no lesson
402
402
  - **Independence first** — prefer a fresh read-only verifier; the author never verifies their own work when tooling allows
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * check_commit.ts - 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. Bun
7
+ * builtins only, 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.ts .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
+ * bun skills/massa-ai/scripts/check_commit.ts [msgfile]
27
+ * bun skills/massa-ai/scripts/check_commit.ts --message "feat(auth): add email validation"
28
+ * bun skills/massa-ai/scripts/check_commit.ts --message "[SA-142] feat(auth): reject expired tokens"
29
+ * echo "fix(cart): prevent negative quantity" | bun skills/massa-ai/scripts/check_commit.ts
30
+ *
31
+ * Exit codes: 0 pass, 1 violation, 2 usage error.
32
+ */
33
+
34
+ import { readFileSync } from "node:fs";
35
+
36
+ const TYPES = ["feat", "fix", "refactor", "docs", "test", "style", "perf", "build", "ci", "chore"];
37
+
38
+ // massa-ai patch (D1c): an optional leading Jira-style key prefix, e.g.
39
+ // "[SA-142] feat(auth): reject expired tokens" (workflows/commit.md §8). The
40
+ // key shape mirrors the branch-key regex there: [A-Z][A-Z0-9]{1,9}-\d+.
41
+ const PREFIX_RE = /^\[(?<key>[A-Z][A-Z0-9]{1,9}-\d+)\]\s+(?<rest>.+)$/;
42
+ const HEADER_RE = /^(?<type>\w+)(?:\((?<scope>[^)]+)\))?(?<bang>!)?: (?<desc>.+)$/;
43
+
44
+ const USAGE = "usage: check_commit.ts [-h] [--message MESSAGE] [msgfile]";
45
+ const HELP = `${USAGE}
46
+
47
+ Validate a Conventional Commits message.
48
+
49
+ positional arguments:
50
+ msgfile path to a commit message file (as git passes to commit-msg)
51
+
52
+ options:
53
+ -h, --help show this help message and exit
54
+ --message MESSAGE the commit message as a string`;
55
+
56
+ interface Args {
57
+ msgfile: string | null;
58
+ message: string | null;
59
+ }
60
+
61
+ /** Mirrors Python's str.splitlines(): universal newline split, no trailing empty element. */
62
+ function splitLines(text: string): string[] {
63
+ if (text === "") return [];
64
+ const result: string[] = [];
65
+ const lineBreakRe = /\r\n|\r|\n/g;
66
+ let start = 0;
67
+ let match: RegExpExecArray | null;
68
+ while ((match = lineBreakRe.exec(text)) !== null) {
69
+ result.push(text.slice(start, match.index));
70
+ start = match.index + match[0].length;
71
+ }
72
+ if (start < text.length) {
73
+ result.push(text.slice(start));
74
+ }
75
+ return result;
76
+ }
77
+
78
+ /** Mirrors Python's repr() for plain-text strings (single-quoted, backslash/quote/control escapes). */
79
+ function pyRepr(s: string): string {
80
+ const hasSingle = s.includes("'");
81
+ const hasDouble = s.includes('"');
82
+ const quote = hasSingle && !hasDouble ? '"' : "'";
83
+ let out = quote;
84
+ for (const ch of s) {
85
+ if (ch === "\\") out += "\\\\";
86
+ else if (ch === quote) out += "\\" + quote;
87
+ else if (ch === "\n") out += "\\n";
88
+ else if (ch === "\r") out += "\\r";
89
+ else if (ch === "\t") out += "\\t";
90
+ else {
91
+ const code = ch.codePointAt(0)!;
92
+ if (code < 0x20 || code === 0x7f) {
93
+ out += "\\x" + code.toString(16).padStart(2, "0");
94
+ } else {
95
+ out += ch;
96
+ }
97
+ }
98
+ }
99
+ out += quote;
100
+ return out;
101
+ }
102
+
103
+ function printUsageError(msg: string): void {
104
+ process.stderr.write(`${USAGE}\ncheck_commit.ts: error: ${msg}\n`);
105
+ }
106
+
107
+ function parseArgs(argv: string[]): Args | null {
108
+ let message: string | null = null;
109
+ const positionals: string[] = [];
110
+ for (let i = 0; i < argv.length; i++) {
111
+ const a = argv[i];
112
+ if (a === "--message") {
113
+ if (i + 1 >= argv.length) {
114
+ printUsageError("argument --message: expected one argument");
115
+ return null;
116
+ }
117
+ message = argv[++i];
118
+ } else if (a.startsWith("--message=")) {
119
+ message = a.slice("--message=".length);
120
+ } else if (a === "-h" || a === "--help") {
121
+ console.log(HELP);
122
+ process.exit(0);
123
+ } else if (a.startsWith("-") && a !== "-") {
124
+ printUsageError(`unrecognized arguments: ${a}`);
125
+ return null;
126
+ } else {
127
+ positionals.push(a);
128
+ }
129
+ }
130
+ if (positionals.length > 1) {
131
+ printUsageError(`unrecognized arguments: ${positionals.slice(1).join(" ")}`);
132
+ return null;
133
+ }
134
+ return { msgfile: positionals[0] ?? null, message };
135
+ }
136
+
137
+ function readMessage(args: Args): string {
138
+ if (args.message !== null) return args.message;
139
+ if (args.msgfile) {
140
+ return readFileSync(args.msgfile, "utf-8");
141
+ }
142
+ if (!process.stdin.isTTY) {
143
+ try {
144
+ return readFileSync(0, "utf-8");
145
+ } catch {
146
+ return "";
147
+ }
148
+ }
149
+ return "";
150
+ }
151
+
152
+ function check(message: string): { errors: string[]; warnings: string[] } {
153
+ const errors: string[] = [];
154
+ const warnings: string[] = [];
155
+ // Ignore comment lines (git puts '#' comments in the message file).
156
+ let lines = splitLines(message).filter((ln) => !ln.trimStart().startsWith("#"));
157
+ // Trim leading blank lines.
158
+ while (lines.length && !lines[0]!.trim()) {
159
+ lines.shift();
160
+ }
161
+ if (!lines.length) {
162
+ return { errors: ["empty commit message"], warnings };
163
+ }
164
+
165
+ const header = lines[0]!.replace(/\s+$/, "");
166
+ if (header.length > 72) {
167
+ warnings.push(`header is ${header.length} chars (>72): ${header.slice(0, 60)}...`);
168
+ }
169
+
170
+ // Strip an optional massa-ai Jira-style `[KEY] ` prefix before matching the
171
+ // Conventional Commits header shape.
172
+ const prefixed = PREFIX_RE.exec(header);
173
+ const headerBody = prefixed?.groups?.rest ?? header;
174
+
175
+ const m = HEADER_RE.exec(headerBody);
176
+ if (!m || !m.groups) {
177
+ errors.push(`header does not match 'type(scope): description': ${pyRepr(header)}`);
178
+ return { errors, warnings };
179
+ }
180
+
181
+ const ctype = m.groups.type!;
182
+ const desc = m.groups.desc!;
183
+ const bang = m.groups.bang;
184
+
185
+ if (!TYPES.includes(ctype)) {
186
+ errors.push(`type '${ctype}' is not one of: ${TYPES.join(", ")}`);
187
+ }
188
+ if (!desc.trim()) {
189
+ errors.push("description is empty");
190
+ } else {
191
+ if (/\p{Lu}/u.test(desc[0]!)) {
192
+ errors.push(`description should start lowercase: '${desc.slice(0, 30)}'`);
193
+ }
194
+ if (desc.replace(/\s+$/, "").endsWith(".")) {
195
+ errors.push("description should not end with a period");
196
+ }
197
+ }
198
+
199
+ const body = lines.slice(1).join("\n");
200
+ const breakingFooter = /^BREAKING CHANGE:/m.test(body);
201
+ if (bang && !breakingFooter) {
202
+ errors.push("'!' breaking marker present but no 'BREAKING CHANGE:' footer");
203
+ }
204
+
205
+ return { errors, warnings };
206
+ }
207
+
208
+ function main(argv: string[]): number {
209
+ const args = parseArgs(argv);
210
+ if (args === null) return 2;
211
+
212
+ const message = readMessage(args);
213
+ if (!message.trim()) {
214
+ console.error("check_commit: no message provided (pass a file, --message, or pipe via stdin).");
215
+ return 2;
216
+ }
217
+
218
+ const { errors, warnings } = check(message);
219
+ for (const w of warnings) console.log(` WARN ${w}`);
220
+ for (const e of errors) console.log(` ERROR ${e}`);
221
+ if (errors.length) {
222
+ console.log("\ncheck_commit: FAIL - see https://www.conventionalcommits.org/en/v1.0.0/");
223
+ return 1;
224
+ }
225
+ console.log("check_commit: OK");
226
+ return 0;
227
+ }
228
+
229
+ if (import.meta.main) {
230
+ process.exit(main(process.argv.slice(2)));
231
+ }