@massa-ai/cursor-plugin 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/skills/agents/audit-specialist/SKILL.md +1 -1
- package/skills/agents/context-curator/SKILL.md +1 -1
- package/skills/agents/furps-analyst/SKILL.md +1 -1
- package/skills/agents/investigator/SKILL.md +1 -1
- package/skills/agents/mobile-specialist/SKILL.md +1 -1
- package/skills/agents/navigator/SKILL.md +1 -1
- package/skills/agents/requirements-analyst/SKILL.md +1 -1
- package/skills/agents/reviewer/SKILL.md +1 -1
- package/skills/agents/verification-agent/SKILL.md +1 -1
- package/skills/massa-ai/SKILL.md +10 -0
- package/skills/massa-ai/references/evidence-gate.md +1 -1
- package/skills/massa-ai/references/hook-enforcement.md +2 -2
- package/skills/massa-ai/references/implementation-delivery.md +12 -1
- package/skills/massa-ai/references/lessons.md +9 -10
- package/skills/massa-ai/references/mcp-tools.md +1 -1
- package/skills/massa-ai/references/project-context.md +1 -1
- package/skills/massa-ai/references/spec-driven/artifact-store.md +7 -8
- package/skills/massa-ai/references/spec-driven/coding-principles.md +16 -0
- package/skills/massa-ai/references/spec-driven/design.md +3 -3
- package/skills/massa-ai/references/spec-driven/discuss.md +35 -12
- package/skills/massa-ai/references/spec-driven/execute.md +42 -25
- package/skills/massa-ai/references/spec-driven/memory.md +12 -2
- package/skills/massa-ai/references/spec-driven/specify.md +32 -14
- package/skills/massa-ai/references/spec-driven/sub-agents.md +33 -6
- package/skills/massa-ai/references/spec-driven/tasks.md +8 -6
- package/skills/massa-ai/references/spec-driven/validate.md +17 -12
- package/skills/massa-ai/scripts/check_commit.ts +231 -0
- package/skills/massa-ai/scripts/check_specs_delivered.ts +209 -0
- package/skills/massa-ai/scripts/lessons.ts +907 -0
- package/skills/massa-ai/scripts/validate_spec.ts +413 -0
- package/skills/massa-ai/scripts/validate_state.ts +276 -0
- package/skills/massa-ai/scripts/validate_tasks.ts +498 -0
- package/skills/massa-ai/workflows/architecture/architecture-fix.md +1 -1
- package/skills/massa-ai/workflows/bugs/bugs-fix.md +1 -1
- package/skills/massa-ai/workflows/code-quality/code-quality-fix.md +1 -1
- package/skills/massa-ai/workflows/debug.md +1 -1
- package/skills/massa-ai/workflows/exploration.md +1 -1
- package/skills/massa-ai/workflows/feature.md +1 -1
- package/skills/massa-ai/workflows/general.md +2 -2
- package/skills/massa-ai/workflows/implementation/implementation-fix.md +1 -1
- package/skills/massa-ai/workflows/maestro/maestro-fix.md +1 -1
- package/skills/massa-ai/workflows/mobile-figma/mobile-figma-fix.md +1 -1
- package/skills/massa-ai/workflows/refactor.md +1 -1
- package/skills/massa-ai/workflows/requirements/requirements-fix.md +1 -1
- package/skills/massa-ai/workflows/security/security-fix.md +1 -1
- package/skills/massa-ai/workflows/spec-driven.md +7 -6
- package/skills/massa-ai/workflows/tests/tests-fix.md +1 -1
- package/skills/massa-ai/scripts/lessons.py +0 -590
|
@@ -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/
|
|
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 `
|
|
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
|
|
|
@@ -82,6 +82,8 @@ If needed:
|
|
|
82
82
|
- "What are the constraints (time, tech, resources)?"
|
|
83
83
|
- "What is explicitly out of scope?"
|
|
84
84
|
|
|
85
|
+
**Facts you look up; decisions you ask.** Anything discoverable by reading the environment (the codebase, config, docs, existing conventions) you resolve yourself through the Knowledge Verification Chain — do not spend the user's attention asking for it. Reserve questions for genuine decisions that are the user's to make: scope, priorities, product behavior, trade-offs. A question you could have answered by reading the code erodes trust and wastes a turn.
|
|
86
|
+
|
|
85
87
|
**Challenge vagueness.** Never accept fuzzy answers. "Good" means what? "Users" means who? "Simple" means how? Make the abstract concrete: "Walk me through using this." "What does that actually look like?"
|
|
86
88
|
|
|
87
89
|
**Know when to stop — then run the dimensions sweep.** When you understand what they're building, why, who it's for, and what done looks like, run a closing **implicit-requirement dimensions sweep** before offering to proceed:
|
|
@@ -98,11 +100,22 @@ The `N/A because...` escape is mandatory — it prevents inventing requirements
|
|
|
98
100
|
|
|
99
101
|
Each story MUST be **independently testable** — you can implement and demo just that story.
|
|
100
102
|
|
|
101
|
-
### 3. Write Acceptance Criteria
|
|
103
|
+
### 3. Write Acceptance Criteria (EARS notation)
|
|
104
|
+
|
|
105
|
+
Write every acceptance criterion in **EARS** (Easy Approach to Requirements Syntax). Each criterion resolves to exactly one pattern, which keeps it unambiguous and directly testable. Choose the pattern that fits the requirement instead of forcing everything into a single shape:
|
|
106
|
+
|
|
107
|
+
| Pattern | Keyword | Template | Use for |
|
|
108
|
+
| ------- | ------- | -------- | ------- |
|
|
109
|
+
| Ubiquitous | (none) | The [system] SHALL [response] | Always-on invariants and constraints |
|
|
110
|
+
| Event-driven | WHEN | WHEN [trigger] THEN the [system] SHALL [response] | A response to a discrete trigger |
|
|
111
|
+
| State-driven | WHILE | WHILE [state] the [system] SHALL [response] | Behavior that holds during a state |
|
|
112
|
+
| Optional-feature | WHERE | WHERE [feature is present] the [system] SHALL [response] | Behavior gated behind an optional capability or flag |
|
|
113
|
+
| Unwanted-behavior | IF / THEN | IF [undesired condition] THEN the [system] SHALL [response] | Errors, failures, invalid input, timeouts |
|
|
114
|
+
| Complex | combination | WHILE [state], WHEN [trigger] the [system] SHALL [response] | Richer behavior combining the above |
|
|
102
115
|
|
|
103
|
-
|
|
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.
|
|
104
117
|
|
|
105
|
-
|
|
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.
|
|
106
119
|
|
|
107
120
|
### 4. Requirement Closure Gate (before confirm)
|
|
108
121
|
|
|
@@ -122,6 +135,8 @@ Before Design, Tasks, or Execute — and before presenting the spec for confirma
|
|
|
122
135
|
|
|
123
136
|
6. **Continue only when** the Open Questions table is empty or every row has an accepted assumption.
|
|
124
137
|
|
|
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
|
+
|
|
125
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.
|
|
126
141
|
|
|
127
142
|
---
|
|
@@ -187,11 +202,12 @@ Every ambiguity is resolved or recorded here — nothing is left silently unclea
|
|
|
187
202
|
|
|
188
203
|
**Why P1**: [Why this is critical for MVP]
|
|
189
204
|
|
|
190
|
-
**Acceptance Criteria
|
|
205
|
+
**Acceptance Criteria** (each line is one EARS pattern):
|
|
191
206
|
|
|
192
|
-
1. WHEN [user action/event] THEN system SHALL [expected behavior]
|
|
193
|
-
2.
|
|
194
|
-
3.
|
|
207
|
+
1. WHEN [user action/event] THEN system SHALL [expected behavior] <!-- event-driven -->
|
|
208
|
+
2. IF [invalid input / failure] THEN system SHALL [graceful handling] <!-- unwanted-behavior -->
|
|
209
|
+
3. WHILE [state holds] system SHALL [behavior during that state] <!-- state-driven -->
|
|
210
|
+
4. The system SHALL [always-on invariant] <!-- ubiquitous -->
|
|
195
211
|
|
|
196
212
|
**Independent Test**: [How to verify this story works alone - e.g., "Can demo by doing X and seeing Y"]
|
|
197
213
|
|
|
@@ -226,9 +242,11 @@ Every ambiguity is resolved or recorded here — nothing is left silently unclea
|
|
|
226
242
|
|
|
227
243
|
## Edge Cases
|
|
228
244
|
|
|
245
|
+
Edge cases are usually unwanted-behavior (IF/THEN) or boundary (WHEN) criteria:
|
|
246
|
+
|
|
247
|
+
- IF [error scenario] THEN system SHALL [graceful handling]
|
|
248
|
+
- IF [unexpected input] THEN system SHALL [validation response]
|
|
229
249
|
- WHEN [boundary condition] THEN system SHALL [behavior]
|
|
230
|
-
- WHEN [error scenario] THEN system SHALL [graceful handling]
|
|
231
|
-
- WHEN [unexpected input] THEN system SHALL [validation response]
|
|
232
250
|
|
|
233
251
|
---
|
|
234
252
|
|
|
@@ -263,18 +281,18 @@ How we know the feature is successful:
|
|
|
263
281
|
## Tips
|
|
264
282
|
|
|
265
283
|
- **P1 = Vertical Slice** — A complete, demo-able feature, not just backend or frontend
|
|
266
|
-
- **
|
|
284
|
+
- **EARS is code** — If you can't write a criterion as a test, rewrite it; pick the pattern (WHEN / WHILE / WHERE / IF / ubiquitous) that fits
|
|
267
285
|
- **Requirement IDs are mandatory** — Every story maps to trackable IDs
|
|
268
286
|
- **Edge cases matter** — What breaks? What's empty? What's huge?
|
|
269
287
|
- **Out of Scope prevents creep** — If it's not here, it doesn't get built
|
|
270
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
|
|
271
|
-
- **Confirm after the gate passes** — Present the spec for user confirmation only after the closure gate passes (no unresolved-and-unmarked items remain); 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
|
|
272
290
|
|
|
273
291
|
---
|
|
274
292
|
|
|
275
293
|
## Done
|
|
276
294
|
|
|
277
|
-
Specify is done when every requirement has an ID, acceptance criteria are testable, edge cases are named, out-of-scope boundaries are explicit, implicit-requirement dimensions are resolved or marked `N/A because <reason>`,
|
|
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).
|
|
278
296
|
|
|
279
297
|
## Massa-ai Integration
|
|
280
298
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sub-Agent Delegation
|
|
2
2
|
|
|
3
|
-
Use this reference during Execute when formal task planning
|
|
3
|
+
Use this reference during Execute when formal task planning has more than 3 tasks, when the user explicitly asks for delegation, or when final validation needs an independent verifier. Full mechanics for phase-batch workers and the Verifier sub-agent used during Execute.
|
|
4
4
|
|
|
5
5
|
## Phase-Batch Workers
|
|
6
6
|
|
|
@@ -11,15 +11,15 @@ Use this reference during Execute when formal task planning packs into more than
|
|
|
11
11
|
|
|
12
12
|
Conflating the two (one worker per phase) is what fragments execution: a feature's dependency-layer count has nothing to do with the ideal per-worker workload. Batching by task budget separates the two concerns without breaking phases.
|
|
13
13
|
|
|
14
|
-
**Trigger:** Count total tasks across all phases. If the feature
|
|
14
|
+
**Trigger:** Count total tasks across all phases. If the feature has **more than 3 tasks**, offer the user phase-batch sub-agents before starting Execute — even when packing yields a single batch (a 4–8-task feature is offered as one batch worker). If the feature has 3 or fewer tasks, execute inline in the main window — no sub-agents spawned, no offer made.
|
|
15
15
|
|
|
16
16
|
**Batching algorithm (task budget ≈ 7 tasks/worker, phase-aligned):**
|
|
17
17
|
|
|
18
18
|
The benchmarked sweet spot is ~7 tasks of context per worker (~20 tasks → 3 workers). Pack whole phases into that budget:
|
|
19
19
|
|
|
20
20
|
1. Count total tasks `T`.
|
|
21
|
-
2. If `T ≤
|
|
22
|
-
3. Otherwise walk phases **in order**, accumulating whole phases into the current batch. When the batch's running task count reaches ~7 **and** phases remain, close the batch and start the next.
|
|
21
|
+
2. If `T ≤ 3` → inline, no sub-agents, no offer.
|
|
22
|
+
3. Otherwise (even for `T` as low as 4) offer sub-agents and walk phases **in order**, accumulating whole phases into the current batch. When the batch's running task count reaches ~7 **and** phases remain, close the batch and start the next.
|
|
23
23
|
4. **Never split a phase** across workers — the cut only ever lands on a phase boundary. This preserves dependency ordering and keeps a phase's tasks + shared context in one worker.
|
|
24
24
|
5. If the final batch is a lone tail (1–2 tasks), fold it into the previous batch.
|
|
25
25
|
|
|
@@ -121,7 +121,7 @@ Delegated work returns through the compact summary contract above. Planning, tas
|
|
|
121
121
|
**What the Verifier does (full process in `validate.md`):**
|
|
122
122
|
|
|
123
123
|
1. **Spec-anchored coverage check** — re-derives coverage evidence-or-zero: every AC traced to `file:line` + assertion expression. For each covered criterion, confirms the test's asserted value matches the **spec-defined expected outcome** (not just that an assertion exists). Where the spec does not define a precise outcome, flags a **spec-precision gap** rather than passing silently.
|
|
124
|
-
2. **Discrimination sensor** — injects a small behavior-level fault (flip a condition, change a return value, off-by-one, remove a required side effect) in
|
|
124
|
+
2. **Discrimination sensor** — injects a small behavior-level fault (flip a condition, change a return value, off-by-one, remove a required side effect) in an **isolated scratch** (temporary `git worktree` or temp file copies — never `git stash`), runs the relevant tests there, confirms they FAIL (kill the mutant), discards the scratch, and verifies the real worktree's `git status --porcelain` matches the pre-sensor baseline. Tiered by risk: lightweight (1–3 mutations) for standard features; expanded (≥5 mutations or full mutation tooling) for P0/critical paths. Surviving mutants become fix tasks.
|
|
125
125
|
3. Applies the **payload/conjunction rule**: checks payload fields are asserted on value/state, not just that the call occurred.
|
|
126
126
|
4. **Writes the persisted report** to `.specs/features/<slug>/validation.md` — PASS/FAIL, per-AC evidence (`file:line` + assertion + spec outcome), sensor result (killed/survived per mutation), gate exit results, diff/commit range.
|
|
127
127
|
5. **Returns a compact verdict in chat** to the orchestrator.
|
|
@@ -148,4 +148,31 @@ 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, write `.specs/features/<slug>/validation.md
|
|
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
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Model Tier per Role
|
|
156
|
+
|
|
157
|
+
**Applies only if the harness can assign a model per sub-agent.** If it cannot, ignore this section and run everything on the default model — the workflow is correct either way. The point is to spend high-reasoning capacity where ambiguity and consequence are high, and a faster tier where the work is mechanical, instead of paying top-tier cost uniformly.
|
|
158
|
+
|
|
159
|
+
massa-ai resolves the actual model per agent through `metadata.model_tier` (`light` / `standard` / `deep`) in each sub-agent's charter (`skills/agents/<name>/SKILL.md`), combined with the host and the active profile in `skills/model-profiles.json` (see `CLAUDE.md` § Agent-harness surface). This section maps role/work characteristics onto that mechanism — it is not a separate free-floating table.
|
|
160
|
+
|
|
161
|
+
Judge the tier by the work in front of the role, not by the role's title:
|
|
162
|
+
|
|
163
|
+
| Role / work | Characteristic | Suggested tier |
|
|
164
|
+
| ----------- | -------------- | -------------- |
|
|
165
|
+
| Design phase | High ambiguity, hard-to-reverse structural decisions | `deep` |
|
|
166
|
+
| Batch worker — core-domain or high-ambiguity phase | Non-obvious logic, tricky edge cases, novel integration | `deep` |
|
|
167
|
+
| Batch worker — mechanical phase | Entities, DTOs, config, wiring, straightforward CRUD against a settled pattern | `light` / `standard` |
|
|
168
|
+
| Verifier | Adversarial reasoning: designs mutations, re-derives coverage, judges outcome precision | `deep` (always — see below) |
|
|
169
|
+
| Specify / Tasks authoring | Structured but judgment-heavy | `standard` / `deep` |
|
|
170
|
+
| Read-only specialist (audit-specialist, context-curator, furps-analyst, investigator, mobile-specialist, navigator, requirements-analyst, reviewer) | No write access — findings, investigation, or review quality is the entire deliverable, with no implementation pass downstream to catch a missed nuance | `deep` (always — see below) |
|
|
171
|
+
|
|
172
|
+
**Rules of thumb:**
|
|
173
|
+
|
|
174
|
+
- When unsure, size up, not down. An under-powered worker on ambiguous logic produces gaps the Verifier then has to catch — more expensive than paying for reasoning once.
|
|
175
|
+
- **The Verifier always runs on the deepest tier** — per project rule, `skills/agents/verification-agent/SKILL.md` pins `metadata.model_tier: deep`, structurally, not just as advisory guidance here. A weak Verifier defeats the author ≠ verifier gate.
|
|
176
|
+
- **Read-only specialists always run on the deepest tier** — this generalizes the Verifier rule: every findings-only or investigation-only charter (`permission: read-only`) pins `metadata.model_tier: deep` structurally, because there is no later implementation pass to catch what a weaker read-only pass missed.
|
|
177
|
+
- Set the tier per batch, from that batch's phases. A feature can mix tiers across batches.
|
|
178
|
+
- Outside the Verifier's and read-only specialists' structural pins, this table is advisory metadata only — no gate, commit, or verification step depends on it.
|
|
@@ -58,7 +58,7 @@ This step ALWAYS runs — there is no precondition. Decide which of two paths to
|
|
|
58
58
|
|
|
59
59
|
Before sampling tests or inferring anything, scan the project for documented quality and testing standards. Stack-agnostic sources to check (illustrative, not exhaustive):
|
|
60
60
|
|
|
61
|
-
- Agent/AI
|
|
61
|
+
- Agent/AI convention files, if the repo has any: `AGENTS.md` (the vendor-neutral standard) and any tool-specific rules file or rules directory the project happens to use
|
|
62
62
|
- Contributor guides: `CONTRIBUTING.md`, `docs/` (testing, quality, or standards subdocs), README testing section
|
|
63
63
|
- Tool configuration: coverage thresholds in the test runner config (e.g., `jest.config.*`, `vitest.config.*`, `pytest.ini`, `.nycrc`, `Makefile` coverage targets, CI coverage gates)
|
|
64
64
|
|
|
@@ -74,7 +74,7 @@ Before sampling tests or inferring anything, scan the project for documented qua
|
|
|
74
74
|
**How to infer (path 1 — existing tests):**
|
|
75
75
|
|
|
76
76
|
1. **Sample test files.** Locate 5–10 existing test files. Map each file's location relative to its source file to identify which code layers are exercised and at what level (unit, integration, e2e). Use these samples for style, location patterns, framework, and test type — and as a **floor** (never produce tests less thorough than existing ones for the same layer). Existing tests are NOT a ceiling on thoroughness; the thoroughness target comes from the spec ACs, listed edge cases, and guidelines (or strong default). The Coverage Expectation column captures the target per layer.
|
|
77
|
-
2. **Discover commands from the repo.** Do NOT invent commands and do NOT assume an ecosystem. Read the project's own build/task manifests, test config, and CI workflows to extract the actual commands — for example: `package.json` / `project.json` (JS/TS), `Makefile`, `pyproject.toml` / `tox.ini` / `pytest` (Python), `Cargo.toml` (Rust), `go test` invocations (Go), `pom.xml` / `build.gradle` (Java/Kotlin), `Gemfile` / `Rakefile` (Ruby), `composer.json` (PHP), `.github/workflows` / `.gitlab-ci.yml`. The list is illustrative; detect what this repo actually uses.
|
|
77
|
+
2. **Discover commands from the repo.** Do NOT invent commands and do NOT assume an ecosystem. Read the project's own build/task manifests, test config, and CI workflows to extract the actual commands — for example: `package.json` / `project.json` (JS/TS), `Makefile`, `pyproject.toml` / `tox.ini` / `pytest` (Python), `Cargo.toml` (Rust), `go test` invocations (Go), `pom.xml` / `build.gradle` (Java/Kotlin), `Gemfile` / `Rakefile` (Ruby), `composer.json` (PHP), `.github/workflows` / `.gitlab-ci.yml`. The list is illustrative; detect what this repo actually uses. Capture the **linter/formatter** command too (e.g. the configured `lint`/`format`/`typecheck` script, or a `.pre-commit-config`, `.golangci.yml`, `ruff`/`eslint`/`biome` config) — the Build gate runs it alongside the tests.
|
|
78
78
|
|
|
79
79
|
**Output contract — render these two sections verbatim into `tasks.md`** (the exact headings downstream phases reference):
|
|
80
80
|
|
|
@@ -157,6 +157,8 @@ 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):** `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
|
+
|
|
160
162
|
**Check 1: Task Granularity** — verify each task is atomic (see Granularity Check section).
|
|
161
163
|
|
|
162
164
|
**Check 2: Diagram-Definition Cross-Check** — verify the execution diagram matches every task's `Depends on` field (see Diagram-Definition Cross-Check section). Build the cross-check table and include it in the output.
|
|
@@ -350,12 +352,12 @@ Execution is strictly sequential — there is no intra-phase parallelism. A sing
|
|
|
350
352
|
|
|
351
353
|
**How phase-based execution works:**
|
|
352
354
|
|
|
353
|
-
At Execute, the agent counts total tasks and packs phases into **task-budgeted batches** (~7 tasks per worker, whole phases — the benchmarked sweet spot is ~20 tasks → ~3 workers). A **phase** is the semantic/dependency unit; a **batch** is one or more *consecutive whole phases* assigned to one worker. The cut only ever lands on a phase boundary — a phase is never split across workers.
|
|
355
|
+
At Execute, the agent counts total tasks and packs phases into **task-budgeted batches** (~7 tasks per worker, whole phases — the benchmarked sweet spot is ~20 tasks → ~3 workers). A **phase** is the semantic/dependency unit; a **batch** is one or more *consecutive whole phases* assigned to one worker. The cut only ever lands on a phase boundary — a phase is never split across workers. **The sub-agent offer fires whenever the feature has more than 3 tasks** — a 4–8-task feature still packs into a single batch and is offered as one batch worker; only a feature with 3 or fewer tasks executes inline with no offer. Batches run sequentially: each worker executes ALL its tasks in order, then reports a compact summary before the next batch starts. This right-sizes the worker count by workload instead of by phase count (one-per-phase is too fragmented; expensive and slow). See `references/spec-driven/sub-agents.md` for the full model — packing algorithm, offer-then-confirm, worker payload, compact summary contract, failure handling, and context sizing guidance.
|
|
354
356
|
|
|
355
|
-
When the whole feature
|
|
357
|
+
When the whole feature has 3 or fewer tasks, execution happens inline in the main window with no sub-agents spawned and no offer made.
|
|
356
358
|
|
|
357
359
|
**The orchestrating agent's role during Execute:**
|
|
358
|
-
1. Count total tasks
|
|
360
|
+
1. Count total tasks — if more than 3, pack phases into ~7-task batches and offer batch sub-agents (even a single resulting batch is offered); wait for the user's choice
|
|
359
361
|
2. Dispatch the next batch (to a worker, or execute inline)
|
|
360
362
|
3. Receive the compact batch summary
|
|
361
363
|
4. Update tasks.md with results
|
|
@@ -514,4 +516,4 @@ Before sampling tests manually, prefer massa-ai tooling to read the codebase:
|
|
|
514
516
|
|
|
515
517
|
## Done
|
|
516
518
|
|
|
517
|
-
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, 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.
|
|
@@ -27,7 +27,7 @@ Use this reference for the mandatory final Execute validation gate. This is not
|
|
|
27
27
|
|
|
28
28
|
Prefer a fresh read-only verifier agent or tool. The author must not verify their own work when independent verification tooling is available. When subagents are unavailable, run the standalone fresh-eyes fallback: re-read `spec.md`, changed files, tests, and diff from scratch before making a verdict.
|
|
29
29
|
|
|
30
|
-
The verifier is read-only against the real worktree. Discrimination sensor mutations run only in scratch state
|
|
30
|
+
The verifier is read-only against the real worktree. Discrimination sensor mutations run only in an isolated scratch state — a temporary git worktree (preferred) or temp file copies (fallback), never `git stash` — and must be reverted or discarded before verdict.
|
|
31
31
|
|
|
32
32
|
## Inputs
|
|
33
33
|
|
|
@@ -100,18 +100,20 @@ The sensor provides the empirical guarantee that the tests can actually detect r
|
|
|
100
100
|
|
|
101
101
|
**How it works:**
|
|
102
102
|
|
|
103
|
-
1. **Prepare
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
- A
|
|
107
|
-
2. **
|
|
103
|
+
1. **Prepare an isolated scratch.** Never mutate the real worktree. Choose one:
|
|
104
|
+
- Preferred: a temporary git worktree (`git worktree add <scratch-path> HEAD`), mutate and run tests there, then `git worktree remove --force <scratch-path>`.
|
|
105
|
+
- Fallback (no git / worktree unavailable): copy only the affected file(s) to a temp directory, mutate the copies, point the test runner at those copies (or restore originals from the copies' backups), then delete the temp directory.
|
|
106
|
+
- **Forbidden:** `git stash` / `git stash pop`. A stash records state *before* the mutation; popping it does not reverse a mutation applied afterward, and on a clean tree `git stash` creates no entry at all — so the fault is left in the real worktree.
|
|
107
|
+
2. **Capture a baseline.** Record `git status --porcelain` (or equivalent) of the real worktree *before* any sensor work. It must be unchanged after cleanup.
|
|
108
|
+
3. **Inject a behavior-level fault** into the scratch copy of the new code introduced by this feature. Choose a mutation proportional to the code's risk:
|
|
108
109
|
- Flip a boolean condition (`if (x)` → `if (!x)`, `>` → `>=`)
|
|
109
110
|
- Change a return value (return a wrong status code, wrong field, zero instead of a computed value)
|
|
110
111
|
- Off-by-one (shift a loop bound, change a slice index)
|
|
111
112
|
- Remove a required side effect (delete a method call that the spec requires)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
4. **Run the tests** that cover the mutated code (against the scratch). Use the Quick or Full gate command from tasks.md.
|
|
114
|
+
5. **Confirm the mutant is killed** (tests FAIL). Discard the scratch (remove worktree or delete temp copies).
|
|
115
|
+
6. **Verify isolation.** Re-run `git status --porcelain` on the real worktree and confirm it matches the baseline from step 2. If it differs, STOP — restore the real tree before continuing, and treat the sensor run as invalid.
|
|
116
|
+
7. **If a mutant survives** (tests still pass after the fault), the tests are not discriminating for that behavior — add a fix task to strengthen the assertion.
|
|
115
117
|
|
|
116
118
|
**Tiering (proportional, not optional):**
|
|
117
119
|
|
|
@@ -202,12 +204,14 @@ After all checks complete, the Verifier MUST:
|
|
|
202
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.
|
|
203
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.
|
|
204
206
|
|
|
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
|
+
|
|
205
209
|
### 10. Distill Lessons (MANDATORY when validation.md has signal)
|
|
206
210
|
|
|
207
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:
|
|
208
212
|
|
|
209
213
|
```bash
|
|
210
|
-
|
|
214
|
+
bun skills/massa-ai/scripts/lessons.ts --root . add --feature <slug> --signal "<signal>" --source "<source>" --text "<lesson>" --scope "<scope>"
|
|
211
215
|
```
|
|
212
216
|
|
|
213
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.
|
|
@@ -364,6 +368,7 @@ Update `.specs/features/<slug>/spec.md` requirement statuses and reflect verifie
|
|
|
364
368
|
## Summary
|
|
365
369
|
|
|
366
370
|
**Overall**: ✅ Ready | ⚠️ Issues | ❌ Not Ready
|
|
371
|
+
**Result**: PASS | FAIL
|
|
367
372
|
|
|
368
373
|
**Spec-anchored check**: [N/N ACs matched spec outcome | M spec-precision gaps]
|
|
369
374
|
**Sensor**: [N/N mutations killed]
|
|
@@ -382,7 +387,7 @@ Update `.specs/features/<slug>/spec.md` requirement statuses and reflect verifie
|
|
|
382
387
|
|
|
383
388
|
- **Validation is never prompted** — it always runs after the last task; do not ask the user whether to run it
|
|
384
389
|
- **Spec-anchored, not just covered** — "there is an assertion" is not enough; the assertion must target the spec-defined outcome
|
|
385
|
-
- **Sensor in scratch only** — never mutate the real tree;
|
|
390
|
+
- **Sensor in scratch only** — never mutate the real tree; use a temp worktree or file copies (never `git stash`), run, discard, then confirm porcelain matches the pre-sensor baseline
|
|
386
391
|
- **Surviving mutants are fix tasks** — do not mark the feature done if the sensor found weak tests
|
|
387
392
|
- **P1 first** — MVP must work before P2/P3
|
|
388
393
|
- **WHEN/THEN = Test** — Each criterion is a test case
|
|
@@ -393,5 +398,5 @@ Update `.specs/features/<slug>/spec.md` requirement statuses and reflect verifie
|
|
|
393
398
|
- **Max 3 diagnostic iterations** — Prevents infinite investigation loops
|
|
394
399
|
- **Update traceability** — Every verified requirement updates spec.md status and the FEATURES.json registry
|
|
395
400
|
- **Always write the report file** — `.specs/features/<slug>/validation.md` is the persisted evidence artifact
|
|
396
|
-
- **Distill after writing** — turn grounded failures into lessons via `
|
|
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
|
|
397
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
|
+
}
|