moflo 4.11.10-rc.2 → 4.11.10-rc.4
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/.claude/guidance/shipped/moflo-sdd.md +23 -14
- package/.claude/helpers/gate.cjs +14 -12
- package/.claude/skills/fl/SKILL.md +18 -15
- package/.claude/skills/fl/phases.md +4 -2
- package/.claude/skills/fl/sdd.md +6 -11
- package/.claude/skills/verify/SKILL.md +94 -0
- package/README.md +14 -9
- package/bin/gate.cjs +14 -12
- package/bin/index-guidance.mjs +40 -5
- package/bin/lib/yaml-upgrader.mjs +74 -1
- package/dist/src/cli/config/moflo-config.js +13 -2
- package/dist/src/cli/init/executor.js +1 -0
- package/dist/src/cli/init/helpers-generator.js +6 -5
- package/dist/src/cli/init/moflo-yaml-template.js +1 -1
- package/dist/src/cli/init/settings-generator.js +3 -3
- package/dist/src/cli/sdd/artifacts.js +23 -2
- package/dist/src/cli/services/daemon-dashboard.js +1 -0
- package/dist/src/cli/services/hook-wiring.js +3 -3
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
|
@@ -1,33 +1,42 @@
|
|
|
1
1
|
# MoFlo Spec-Driven Development (SDD) & Verify-Before-Done
|
|
2
2
|
|
|
3
|
-
**Purpose:** How to run and reason about the `spec → plan → implement → verify` cycle in `/flo`, the `flo sdd` artifact CLI, and the verify-before-done gate. Read this before using `-sd`/`-v`, editing `.moflo/specs/`, or
|
|
3
|
+
**Purpose:** How to run and reason about the `spec → plan → implement → verify` cycle in `/flo`, the `flo sdd` artifact CLI, and the verify-before-done gate. Read this before using `-sd`/`-v`/`--no-verify`, editing `.moflo/specs/`, or tuning `sdd.default` / `gates.verify_before_done`.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
## When to Use `-sd` vs `-v` vs Neither
|
|
8
8
|
|
|
9
|
-
Two independent
|
|
9
|
+
Two independent modifiers on `/flo`, orthogonal to execution mode (`-n`/`-s`/`-h`) and `--worktree`. The **spec/plan ceremony (`-sd`) is opt-in**; **verify-before-done (`-v`) runs by default** (#1294) — separable from SDD by design.
|
|
10
10
|
|
|
11
11
|
| Situation | Flag | Effect |
|
|
12
12
|
|-----------|------|--------|
|
|
13
|
-
| Fuzzy or large unit of work; you want a reviewed spec/plan before code | `-sd` / `--sdd` | Full spec → plan → (review) → implement → verify. Implies `--verify`. |
|
|
14
|
-
|
|
|
15
|
-
|
|
|
13
|
+
| Fuzzy or large unit of work; you want a reviewed spec/plan before code | `-sd` / `--sdd` | Full spec → plan → (review) → implement → verify. Implies `--verify`. Opt-in. |
|
|
14
|
+
| A default run already verifies before done | (none) | Verify-before-done runs automatically via the `/verify` skill. |
|
|
15
|
+
| Skip verification for one run | `--no-verify` | Drops the (default-on) verify step. |
|
|
16
16
|
|
|
17
|
-
`--sdd` **always implies** `--verify` — a spec/plan without an enforced verify step drifts.
|
|
17
|
+
`--sdd` **always implies** `--verify` — a spec/plan without an enforced verify step drifts. `--no-sdd` / `--no-verify` opt a single run out; `-v` only matters to force verify back on where a project set `verify_before_done: false`.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
21
|
## The Artifact Convention
|
|
22
22
|
|
|
23
|
-
Specs and plans persist as
|
|
23
|
+
Specs and plans persist as Markdown, one directory per unit of work, under the configured specs directory (default `.moflo/specs`):
|
|
24
24
|
|
|
25
25
|
```
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
<specs_dir>/<slug>/spec.md # the "what" + acceptance criteria
|
|
27
|
+
<specs_dir>/<slug>/plan.md # the "steps" + how each criterion is verified
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
They are
|
|
30
|
+
They are indexed into memory on session start, so `mcp__moflo__memory_search` surfaces prior specs across sessions. **Always create and mutate them through the `flo sdd` CLI** — never hand-write the path in a skill step (cross-platform, Rule #1: the CLI builds every path with `path.join`).
|
|
31
|
+
|
|
32
|
+
**Where they live is configurable (`sdd.specs_dir`, #1294).** The default `.moflo/specs` is **gitignored** by `flo init` — specs stay local and do not bloat source control, but they also do **not** appear in PRs. To make specs reviewable, point `sdd.specs_dir` at a **tracked** path and commit them:
|
|
33
|
+
|
|
34
|
+
| `sdd.specs_dir` | Committed? | Use when |
|
|
35
|
+
|-----------------|------------|----------|
|
|
36
|
+
| `.moflo/specs` (default) | No (gitignored) | You want the SDD workflow but not spec artifacts in history |
|
|
37
|
+
| `docs/specs`, `.specs`, … (tracked) | Yes | You want specs reviewed in the PR alongside the code |
|
|
38
|
+
|
|
39
|
+
Set it once in `moflo.yaml`; the `flo sdd` CLI and the session-start indexer both honor it. If the path sits inside a `guidance.directories` entry, specs are indexed once (as guidance), not twice.
|
|
31
40
|
|
|
32
41
|
Each artifact carries a `status` of `draft` or `reviewed` in its frontmatter. The constitution layer (`CLAUDE.md` + `.claude/guidance/`) is referenced by every stage — never restate its invariants inside a spec.
|
|
33
42
|
|
|
@@ -52,21 +61,21 @@ The two review checkpoints are the point: **a spec must be reviewed before its p
|
|
|
52
61
|
|
|
53
62
|
When enforced, `gh pr create` is blocked until the change has been verified end-to-end since the last code edit.
|
|
54
63
|
|
|
55
|
-
- **
|
|
56
|
-
- **Satisfy it by running the
|
|
64
|
+
- **On by default (#1294).** Enforced for every `/flo` run; disable per-project with `gates.verify_before_done: false` or per-run with `--no-verify`. On upgrade, consumers with no `verify_before_done` key start enforcing; an explicit value is preserved. Docs-only diffs are exempt, so a pure-docs PR is never blocked.
|
|
65
|
+
- **Satisfy it by running the `/verify` skill** — `/flo` delegates to it. It exercises the change against the plan's (or ticket's) acceptance criteria and records its own outcome to memory (`namespace: learnings, key: verify:<slug>`). It reuses the Tests-phase run rather than repeating it (no double verify).
|
|
57
66
|
- **A source edit invalidates a prior verification** — re-run `/verify` after editing. `/ward` and `/quicken` are targeted audits, not the completion gate.
|
|
58
67
|
|
|
59
68
|
---
|
|
60
69
|
|
|
61
70
|
## Configuration Defaults
|
|
62
71
|
|
|
63
|
-
|
|
72
|
+
`sdd.default` is off (opt-in); `verify_before_done` is **on** (#1294). Override per run with the flags.
|
|
64
73
|
|
|
65
74
|
```yaml
|
|
66
75
|
sdd:
|
|
67
76
|
default: false # true → every /flo run uses the SDD cycle unless --no-sdd
|
|
68
77
|
gates:
|
|
69
|
-
verify_before_done:
|
|
78
|
+
verify_before_done: true # on by default (#1294); false → skip /verify unless -v. Per-run: --no-verify
|
|
70
79
|
```
|
|
71
80
|
|
|
72
81
|
Check wiring status with `/healer` (or `/eldar`) — the `SDD + Verify Wiring` check reports whether the gate cases and hooks are present and which toggles are on.
|
package/.claude/helpers/gate.cjs
CHANGED
|
@@ -60,10 +60,11 @@ function writeState(s) {
|
|
|
60
60
|
|
|
61
61
|
// Load moflo.yaml gate config (defaults: all enabled)
|
|
62
62
|
function loadGateConfig() {
|
|
63
|
-
//
|
|
64
|
-
// ships
|
|
65
|
-
//
|
|
66
|
-
|
|
63
|
+
// verify_before_done is opt-OUT (default true), like every other gate: #1294
|
|
64
|
+
// ships a real /verify skill and has /flo delegate to it, so leaving it off by
|
|
65
|
+
// default would make the default /flo run silently skip the acceptance check.
|
|
66
|
+
// Disable per-project with `verify_before_done: false` or per-run `--no-verify`.
|
|
67
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true, swarm_invocation_gate: true, verify_before_done: true };
|
|
67
68
|
try {
|
|
68
69
|
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
69
70
|
if (fs.existsSync(yamlPath)) {
|
|
@@ -75,8 +76,8 @@ function loadGateConfig() {
|
|
|
75
76
|
if (/simplify_gate:\s*false/i.test(content)) defaults.simplify_gate = false;
|
|
76
77
|
if (/learnings_gate:\s*false/i.test(content)) defaults.learnings_gate = false;
|
|
77
78
|
if (/swarm_invocation_gate:\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
|
|
78
|
-
// Opt-
|
|
79
|
-
if (/verify_before_done:\s*
|
|
79
|
+
// Opt-out: on by default; disable only when explicitly set false.
|
|
80
|
+
if (/verify_before_done:\s*false/i.test(content)) defaults.verify_before_done = false;
|
|
80
81
|
}
|
|
81
82
|
} catch (e) { /* use defaults */ }
|
|
82
83
|
return defaults;
|
|
@@ -763,12 +764,13 @@ switch (command) {
|
|
|
763
764
|
process.exit(2);
|
|
764
765
|
}
|
|
765
766
|
case 'check-before-done': {
|
|
766
|
-
// Story #1274 (Epic #1269). Verify-before-done: block `gh pr create`
|
|
767
|
-
// the change has been verified end-to-end (
|
|
768
|
-
// plan's acceptance criteria.
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
// exemption as check-before-pr, so
|
|
767
|
+
// Story #1274 (Epic #1269) + #1294. Verify-before-done: block `gh pr create`
|
|
768
|
+
// until the change has been verified end-to-end (the /verify skill) against
|
|
769
|
+
// the plan's acceptance criteria. ON by default (#1294) — /flo delegates to
|
|
770
|
+
// /verify, so a default run does the acceptance check; disable per-project
|
|
771
|
+
// with `gates: verify_before_done: false` or per-run `--no-verify`. Same
|
|
772
|
+
// trigger + no-source exemption as check-before-pr, so they compose on one
|
|
773
|
+
// command (docs-only diffs are exempt, so this never blocks a docs PR).
|
|
772
774
|
if (!config.verify_before_done) break;
|
|
773
775
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
774
776
|
if (!/(?:^|&&\s*|\|\|\s*|;\s*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*gh\s+pr\s+create\b/.test(cmd)) break;
|
|
@@ -49,10 +49,10 @@ Two **independent** modifiers, orthogonal to execution mode (`-n`/`-s`/`-h`) and
|
|
|
49
49
|
| Flag | Long | Effect |
|
|
50
50
|
|------|------|--------|
|
|
51
51
|
| `-sd` | `--sdd` | Run the full **spec → plan → (review) → implement → verify** cycle. Short is `-sd`, **not** `-s` (swarm) — follows the two-letter convention (`-wf`, `-wt`). Implies `--verify`. |
|
|
52
|
-
| `-v` | `--verify` | **Verify-before-done
|
|
53
|
-
| `--no-sdd`, `--no-verify` | | Opt a single run out
|
|
52
|
+
| `-v` | `--verify` | **Verify-before-done** — a normal run plus the `/verify` skill (the completion gate), no spec/plan front-half. **On by default** (#1294) — this flag only forces it back on for a project that set `gates.verify_before_done: false`. |
|
|
53
|
+
| `--no-sdd`, `--no-verify` | | Opt a single run out. `--no-verify` skips the (default-on) verify step. |
|
|
54
54
|
|
|
55
|
-
Defaults seed from `moflo.yaml` — `sdd.default` and `gates.verify_before_done`
|
|
55
|
+
Defaults seed from `moflo.yaml` — `sdd.default` (default **off**) and `gates.verify_before_done` (default **on** since #1294). So the SDD spec/plan ceremony is opt-in, but **verify-before-done runs by default**; per-run flags override (`--no-verify` to skip). `--sdd` implies `--verify` (a spec/plan without an enforced verify step drifts). In `-t`/`-r` modes (no implementation) verify is a no-op — cleared silently, with the one-line ignored note only when the user explicitly passed `-v`/`--verify`; `--sdd` in `-t` writes the spec/plan **into the ticket** rather than scaffolding artifacts. Full mechanics in `./sdd.md`.
|
|
56
56
|
|
|
57
57
|
## Auto-merge (#1285)
|
|
58
58
|
|
|
@@ -115,13 +115,14 @@ let epicBranch = null;
|
|
|
115
115
|
let issueNumber = null;
|
|
116
116
|
let titleWords = [];
|
|
117
117
|
|
|
118
|
-
// SDD/verify modifiers (Epic #1269). Seed from moflo.yaml BEFORE parsing
|
|
119
|
-
// project default applies unless a per-run flag overrides it. Read the two
|
|
120
|
-
// from moflo.yaml at the project root
|
|
121
|
-
// sddMode ← `sdd.default
|
|
122
|
-
// verifyMode ← `gates.verify_before_done
|
|
118
|
+
// SDD/verify modifiers (Epic #1269, #1294). Seed from moflo.yaml BEFORE parsing
|
|
119
|
+
// so a project default applies unless a per-run flag overrides it. Read the two
|
|
120
|
+
// keys from moflo.yaml at the project root — NOTE the different absent-defaults:
|
|
121
|
+
// sddMode ← `sdd.default` (absent ⇒ false — spec/plan is opt-in)
|
|
122
|
+
// verifyMode ← `gates.verify_before_done` (absent ⇒ TRUE — verify is on by default, #1294)
|
|
123
123
|
let sddMode = false; // -sd / --sdd (full spec→plan→implement→verify)
|
|
124
|
-
let verifyMode =
|
|
124
|
+
let verifyMode = true; // -v / --verify (on by default; --no-verify opts out)
|
|
125
|
+
let verifyExplicit = false; // did the user actually type -v/--verify? (drives the -t/-r note only, so it doesn't fire on the default)
|
|
125
126
|
|
|
126
127
|
// Auto-merge modifier (#1285). Seed from moflo.yaml BEFORE parsing, same as
|
|
127
128
|
// sddMode/verifyMode: read `merge.auto` at the project root (absent ⇒ false).
|
|
@@ -167,7 +168,7 @@ for (let i = 0; i < args.length; i++) {
|
|
|
167
168
|
// (swarm) + `d`; the swarm case above only matches the exact string "-s".
|
|
168
169
|
else if (arg === "-sd" || arg === "--sdd") { sddMode = true; verifyMode = true; }
|
|
169
170
|
else if (arg === "--no-sdd") sddMode = false;
|
|
170
|
-
else if (arg === "-v" || arg === "--verify") verifyMode = true;
|
|
171
|
+
else if (arg === "-v" || arg === "--verify") { verifyMode = true; verifyExplicit = true; }
|
|
171
172
|
else if (arg === "--no-verify") verifyMode = false;
|
|
172
173
|
// Auto-merge modifier. `-m` is free (`-h` is hive, `-s` is swarm) — no collision.
|
|
173
174
|
else if (arg === "-m" || arg === "--merge") mergeMode = true;
|
|
@@ -187,10 +188,12 @@ if (useWorktree && (epicBranch || workflowMode !== "full")) {
|
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
// SDD/verify are implementation-time modifiers. -t (ticket) and -r (research)
|
|
190
|
-
// never implement, so
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
191
|
+
// never implement, so verify is a no-op there — clear it. Verify is on by
|
|
192
|
+
// default now (#1294), so only surface the "ignored" note when the user
|
|
193
|
+
// EXPLICITLY passed -v/--verify — otherwise clearing the default is silent.
|
|
194
|
+
// --sdd in -t writes the spec/plan INTO the ticket (see ./sdd.md); in -r ignored.
|
|
195
|
+
if (workflowMode === "ticket" || workflowMode === "research") {
|
|
196
|
+
if (verifyExplicit) console.log("Note: --verify ignored — " + workflowMode + " mode does not implement.");
|
|
194
197
|
verifyMode = false;
|
|
195
198
|
}
|
|
196
199
|
if (sddMode && workflowMode === "research") {
|
|
@@ -225,7 +228,7 @@ Full mode runs end-to-end without further prompts.
|
|
|
225
228
|
5. Create branch, implement, write tests — `./phases.md` Phases 3–4
|
|
226
229
|
6. Run `/flo-simplify` on changed code; rerun tests if it edits — `./phases.md` Phase 4.5
|
|
227
230
|
7. Commit — `./phases.md` Phase 5.1
|
|
228
|
-
8. **
|
|
231
|
+
8. **Verify — default, unless `--no-verify`** (`verifyMode`, always on under `sddMode`): delegate to the `/verify` skill — `Skill({ skill: "verify" })`. It checks the change against the acceptance criteria, reusing Phase 4's tests (no double verify) and recording its own outcome; invoking it satisfies the verify-before-done gate. Mechanics live in `.claude/skills/verify/SKILL.md`; trigger/flow in `./phases.md` Phase 5.1b.
|
|
229
232
|
9. Store learnings via `mcp__moflo__memory_store` — `./phases.md` Phase 5.2
|
|
230
233
|
10. Open PR, update issue status — `./phases.md` Phases 5.3–5.4
|
|
231
234
|
11. **If `mergeMode`:** await the PR's merge preconditions and merge it (native `--auto` preferred, else poll-then-merge) — `./phases.md` Phase 5.3b
|
|
@@ -183,8 +183,10 @@ Closes #<issue-number>
|
|
|
183
183
|
Co-Authored-By: moflo <noreply@motailz.com>"
|
|
184
184
|
```
|
|
185
185
|
|
|
186
|
-
### 5.1b Verify-before-done (
|
|
187
|
-
|
|
186
|
+
### 5.1b Verify-before-done (default; skipped only with `--no-verify`)
|
|
187
|
+
**Delegate to the `/verify` skill** — `Skill({ skill: "verify" })`, passing the issue number or spec slug. That skill owns the mechanics (locate acceptance criteria → reuse Phase 4's already-green tests, no double verify → map each criterion → run only uncovered checks → record the outcome). Don't restate them here or verify in prose — *invoking* `/verify` is what records the run and satisfies the `check-before-done` gate.
|
|
188
|
+
|
|
189
|
+
**When it runs:** by default (`verify_before_done` now defaults true, #1294) and always under `--sdd`; `--no-verify` skips it for one run. See `./sdd.md` for triggers and `.claude/skills/verify/SKILL.md` for how verification is performed.
|
|
188
190
|
|
|
189
191
|
### 5.2 Store learnings
|
|
190
192
|
Before opening the PR, call `mcp__moflo__memory_store` with what was learned. The `check-before-pr` gate blocks `gh pr create` until this has run.
|
package/.claude/skills/fl/sdd.md
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
Spec-Driven Development for `/flo` — Epic #1269. Two independent modifiers:
|
|
4
4
|
|
|
5
|
-
- **`-sd` / `--sdd`** — run the full **spec → plan → (review) → implement → verify** cycle. Implies
|
|
6
|
-
- **`-v` / `--verify`** — verify-before-done
|
|
5
|
+
- **`-sd` / `--sdd`** — run the full **spec → plan → (review) → implement → verify** cycle. Opt-in (`sdd.default` defaults false). Implies verify.
|
|
6
|
+
- **`-v` / `--verify`** — verify-before-done: a normal run plus the completion gate, no spec/plan front-half. **On by default** (`gates.verify_before_done` defaults true, #1294) — `-v` is explicit, `--no-verify` opts out for one run.
|
|
7
7
|
|
|
8
|
-
Defaults seed from `moflo.yaml` (`sdd.default
|
|
8
|
+
Defaults seed from `moflo.yaml` (`sdd.default` = false, `gates.verify_before_done` = true); per-run flags and `--no-sdd` / `--no-verify` override. Both are orthogonal to execution mode (`-n`/`-s`/`-h`) and `--worktree`, so `--sdd -s -wt 42` runs the SDD cycle in a swarm inside a worktree.
|
|
9
9
|
|
|
10
10
|
The artifact model, paths, and CLI live in `src/cli/sdd/` (`flo sdd …`). The constitution layer (CLAUDE.md + `.claude/guidance/`) is referenced by every stage — never restated in a spec.
|
|
11
11
|
|
|
12
12
|
## The `--sdd` cycle
|
|
13
13
|
|
|
14
|
-
Artifacts live at
|
|
14
|
+
Artifacts live at `<specs_dir>/<slug>/{spec,plan}.md` — default `.moflo/specs`, which is **gitignored** (local, not in PRs). Set `moflo.yaml sdd.specs_dir` to a tracked path (e.g. `docs/specs`) to make them reviewable (#1294). Drive them with the `flo sdd` CLI; never hand-write the paths.
|
|
15
15
|
|
|
16
16
|
1. **Spec** — capture the *what* + acceptance criteria:
|
|
17
17
|
```bash
|
|
@@ -38,14 +38,9 @@ Specs/plans are indexed into memory on session start, so `mcp__moflo__memory_sea
|
|
|
38
38
|
|
|
39
39
|
## The `--verify` step (verify-before-done)
|
|
40
40
|
|
|
41
|
-
Runs at step 8 of the full-mode flow, before the PR
|
|
41
|
+
Runs at step 8 of the full-mode flow, before the PR — **by default** and always under `--sdd`; `--no-verify` skips it for one run.
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
2. Store the outcome to memory so it feeds routing/learning:
|
|
45
|
-
```
|
|
46
|
-
mcp__moflo__memory_store { namespace: "learnings", key: "verify:<slug-or-issue>", value: "<what was verified, pass/fail>" }
|
|
47
|
-
```
|
|
48
|
-
3. The `/verify` run trips `record-verify-run`, satisfying the `check-before-done` gate — `gh pr create` unblocks. When `gates.verify_before_done: true`, this gate is enforced for every run whether or not `-v` was passed; `-v` makes the skill *do* the verification so the gate passes cleanly. A source edit after verifying invalidates it — re-run `/verify`.
|
|
43
|
+
**Delegate to the `/verify` skill** — `Skill({ skill: "verify" })`, passing the issue number or spec slug. It owns the mechanics (single source of truth — don't restate them here): locate the acceptance criteria (plan, else ticket) → reuse the Tests-phase run (no double verify) → map each criterion to evidence → run only uncovered checks → record its own outcome to memory (`learnings`, `verify:<slug-or-issue>`) → return a per-criterion PASS/FAIL. *Invoking* it is the point — it trips `record-verify-run` and satisfies the `check-before-done` gate (describing verification in prose does not). A source edit after verifying invalidates it — re-run `/verify`. Full how-to: `.claude/skills/verify/SKILL.md`.
|
|
49
44
|
|
|
50
45
|
`/ward` and `/quicken` stay targeted audits, not the completion gate.
|
|
51
46
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify
|
|
3
|
+
description: Verify-before-done — exercise the current change end-to-end against its acceptance criteria (the SDD plan's, or the ticket's) and report a per-criterion PASS/FAIL verdict, then record the outcome to memory. This is the concrete action that satisfies moflo's verify-before-done gate (`gates.verify_before_done` / `/flo -v` / `/flo -sd`). Use before `gh pr create` when the change must be proven to work, not just tested in passing. Verifies only — it does not fix; on FAIL it reports the gaps and stops.
|
|
4
|
+
arguments: "[issue-number | spec-slug] (defaults to the current branch's issue/spec)"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /verify — Verify Before Done
|
|
8
|
+
|
|
9
|
+
Prove the current change **actually does what it was supposed to** before it ships. `/verify` is the end-to-end completion check for moflo's SDD cycle: it takes the change's **acceptance criteria**, exercises the change against each one with concrete evidence, and returns a per-criterion verdict. It is the action that makes the [verify-before-done gate](../../guidance/moflo-sdd.md) mean something — running it is what unblocks `gh pr create` when `gates.verify_before_done` is on.
|
|
10
|
+
|
|
11
|
+
**Arguments:** $ARGUMENTS
|
|
12
|
+
|
|
13
|
+
`/verify` **verifies, it does not fix.** If a criterion fails, it reports the gap and stops — the caller (you, or the `/flo` flow) decides what to do. This separation is deliberate: a checker that also patches can rationalise its way to green.
|
|
14
|
+
|
|
15
|
+
## What satisfies the gate
|
|
16
|
+
|
|
17
|
+
Invoking this skill (name `verify`) trips the `record-verify-run` hook, which flips the `verifyRun` state the `check-before-done` gate reads. **Only `/verify` satisfies it** — `/ward` and `/quicken` are targeted audits, not an end-to-end verification. A source edit *after* verifying invalidates the result (the edit gate resets `verifyRun`), so run `/verify` as the last step before the PR.
|
|
18
|
+
|
|
19
|
+
## Step 0 — Memory first
|
|
20
|
+
|
|
21
|
+
Search memory before reading files, same as any task (satisfies `memory_first`, surfaces prior verify runs for this area):
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
mcp__moflo__memory_search { query: "verify <feature keywords>", namespace: "learnings" }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Step 1 — Locate the acceptance criteria
|
|
28
|
+
|
|
29
|
+
Resolve the criteria to check against, in priority order — **stop at the first that exists**:
|
|
30
|
+
|
|
31
|
+
| Source | How to find it | When |
|
|
32
|
+
|--------|----------------|------|
|
|
33
|
+
| SDD **plan** | `flo sdd list` → the slug for this branch → read its `plan.md` "Acceptance Criteria" | `--sdd` runs |
|
|
34
|
+
| SDD **spec** | same slug → `spec.md` "Acceptance Criteria" | spec authored, no plan criteria |
|
|
35
|
+
| **Ticket** | `gh issue view <n>` → the `## Acceptance Criteria` section | plain `-v` run, no spec |
|
|
36
|
+
| **Inferred** | derive 2–4 checkable criteria from the diff + PR intent, and say so | nothing above exists |
|
|
37
|
+
|
|
38
|
+
Never hand-write the spec path — get it from `flo sdd` (Rule #1: the CLI builds every path with `path.join`). If an explicit `<issue-number | spec-slug>` argument was passed, use it directly.
|
|
39
|
+
|
|
40
|
+
## Step 2 — Map each criterion to a concrete check
|
|
41
|
+
|
|
42
|
+
For every acceptance criterion, pick the **cheapest evidence that actually proves it**:
|
|
43
|
+
|
|
44
|
+
| Criterion shape | Verification action |
|
|
45
|
+
|-----------------|---------------------|
|
|
46
|
+
| Logic / behavior / bug-fix | A passing test that **specifically** exercises it — reuse the suite run already done this session (see below); run only a missing/targeted test |
|
|
47
|
+
| Build / types / packaging | The project's build or typecheck command — reuse if already run since the last edit |
|
|
48
|
+
| Integration / e2e | The project's e2e or integration runner |
|
|
49
|
+
| User-facing / UI / CLI output | Run the real thing — use `/run` to launch the app/CLI and observe the actual output (a screenshot or captured stdout is the evidence) |
|
|
50
|
+
| Config / docs / non-code | Inspect the artifact directly and confirm it says what the criterion requires |
|
|
51
|
+
|
|
52
|
+
**Reuse fresh evidence — do not re-run what this session already ran.** This is the key to avoiding a **double verify**. If the tests/build already ran green this session with no source edit since — which is exactly the case when `/flo` invokes `/verify` right after its Tests phase — that run **is** the evidence: cite it, don't execute it again. `/verify`'s job is the *mapping* (which criterion is proven by which existing result) and *gap-finding* (criteria no check covers), **not** re-running the suite. Only execute a check whose evidence doesn't already exist this session.
|
|
53
|
+
|
|
54
|
+
**Discover the commands from the project — never assume.** When you *do* need to run something (a criterion nothing covered yet), read `package.json` scripts / `Makefile` / language toolchain and use what the repo already uses (`npm/yarn/pnpm test`, `vitest`, `jest`, `pytest`, `cargo test`, `go test`, …). Cross-platform (Rule #1): invoke the project's own scripts, don't shell out to `bash`-only constructs.
|
|
55
|
+
|
|
56
|
+
## Step 3 — Execute the gaps and gather evidence
|
|
57
|
+
|
|
58
|
+
Run **only the checks whose evidence doesn't already exist** this session; for everything else, cite the run that already happened. Capture, per criterion, the **specific** evidence: the passing test name, the build exit, the observed output. "The suite passed" is not evidence for a criterion unless a test actually exercises *that* criterion — if none does, that is a **gap**, not a pass (consider `/ward` to fill it, in a separate step). The distinction from a plain test run is the whole point: tests prove the code works; `/verify` proves the change did **what the ticket asked**.
|
|
59
|
+
|
|
60
|
+
## Step 4 — Verdict
|
|
61
|
+
|
|
62
|
+
Emit a per-criterion table. **PASS only when every criterion has affirmative evidence.** Any criterion with no covering check is `UNVERIFIED`, which counts as **not passing**.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
| # | Acceptance criterion | Evidence | Verdict |
|
|
66
|
+
|---|----------------------|----------|---------|
|
|
67
|
+
| 1 | <criterion> | <test/output/observation> | ✅ PASS / ❌ FAIL / ⚠️ UNVERIFIED |
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Overall verdict is PASS iff no row is FAIL or UNVERIFIED.
|
|
71
|
+
|
|
72
|
+
## Step 5 — Record the outcome to memory
|
|
73
|
+
|
|
74
|
+
Always store the result (feeds routing/learning and the SDD trail):
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
mcp__moflo__memory_store {
|
|
78
|
+
namespace: "learnings",
|
|
79
|
+
key: "verify:<slug-or-issue>",
|
|
80
|
+
value: "<overall PASS/FAIL> — per-criterion: <criterion → evidence → verdict>; commit <sha>",
|
|
81
|
+
tags: ["verify", "sdd"]
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Step 6 — Report
|
|
86
|
+
|
|
87
|
+
One concise summary: overall verdict, the per-criterion table, and — on FAIL/UNVERIFIED — exactly which criteria are unproven and what evidence is missing. Do **not** open the PR or edit code from here; hand the verdict back to the caller.
|
|
88
|
+
|
|
89
|
+
## See Also
|
|
90
|
+
|
|
91
|
+
- `.claude/guidance/moflo-sdd.md` — the SDD cycle and the verify-before-done gate this skill satisfies
|
|
92
|
+
- `.claude/skills/fl/sdd.md` — how `/flo` invokes `/verify` at the verify step
|
|
93
|
+
- `.claude/skills/ward/SKILL.md` — fills coverage gaps `/verify` surfaces (audit, not a completion gate)
|
|
94
|
+
- `.claude/skills/run/SKILL.md` — launch the real app/CLI for user-facing criteria
|
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ MoFlo makes deliberate choices so you don't have to:
|
|
|
76
76
|
| **Learned Routing** | Routes tasks to the right agent type. Learns from outcomes — gets better over time. |
|
|
77
77
|
| **Spell Engine** | Define multi-step automations as YAML — shell commands, agent spawns, conditionals, loops, memory ops. [Full documentation →](docs/SPELLS.md) |
|
|
78
78
|
| **`/flo` Skill** | Execute GitHub issues through a full process: research → enhance → implement → test → simplify → PR. (Also available as `/fl`.) |
|
|
79
|
-
| **Spec-Driven Development** | `/flo -sd` runs a spec → plan → implement → verify cycle with
|
|
79
|
+
| **Spec-Driven Development** | `/flo -sd` runs a spec → plan → implement → verify cycle with memory-indexed artifacts (local by default; set `sdd.specs_dir` to a tracked path to review them in PRs). The spec/plan ceremony is opt-in; verify-before-done runs by **default** (opt out with `--no-verify`). [Details →](#spec-driven-development-sdd) |
|
|
80
80
|
| **Context Tracking** | Monitors context window usage (FRESH → MODERATE → DEPLETED → CRITICAL) and advises accordingly. |
|
|
81
81
|
| **Cross-Platform** | Works on macOS, Linux, and Windows. |
|
|
82
82
|
|
|
@@ -293,7 +293,7 @@ MoFlo installs Claude Code hooks that run on every tool call. Together, these ga
|
|
|
293
293
|
| **TaskCreate-first** | Claude must call TaskCreate before spawning sub-agents via the Task tool | Before every Task (agent spawn) call | Ensures every piece of delegated work is tracked. Prevents runaway agent proliferation where Claude spawns agents without a clear plan. |
|
|
294
294
|
| **Context tracking** | Tracks conversation length and warns about context depletion | On every user prompt (UserPromptSubmit hook) | As conversations grow, AI quality degrades. MoFlo tracks interaction count and assigns a bracket (FRESH → MODERATE → DEPLETED → CRITICAL), advising Claude to checkpoint progress or start a fresh session before quality drops. |
|
|
295
295
|
| **Routing** | Analyzes each prompt and recommends the optimal agent type and model tier | On every user prompt (UserPromptSubmit hook) | Saves cost by suggesting haiku for simple tasks, sonnet for moderate ones, opus for complex reasoning — without you having to think about model selection. |
|
|
296
|
-
| **Verify-before-done** *(
|
|
296
|
+
| **Verify-before-done** *(on by default)* | Claude must verify the change end-to-end (the `/verify` skill) before `gh pr create` | Before `gh pr create` (docs-only diffs exempt) | Enforces "prove it works before done." On by default since #1294 (`/flo` delegates to `/verify` and reuses its test run — no double verify). Opt out with `verify_before_done: false` or `--no-verify`. Pairs with the [Spec-Driven Development](#spec-driven-development-sdd) cycle, which gives verification its acceptance criteria. |
|
|
297
297
|
|
|
298
298
|
### Smart classification
|
|
299
299
|
|
|
@@ -322,7 +322,7 @@ gates:
|
|
|
322
322
|
memory_first: true # Set to false to disable memory-first enforcement
|
|
323
323
|
task_create_first: true # Set to false to disable TaskCreate enforcement
|
|
324
324
|
context_tracking: true # Set to false to disable context bracket warnings
|
|
325
|
-
verify_before_done:
|
|
325
|
+
verify_before_done: true # On by default (#1294); set false to skip /verify before `gh pr create`
|
|
326
326
|
```
|
|
327
327
|
|
|
328
328
|
You can also disable individual hooks in `.claude/settings.json` by removing the corresponding hook entries.
|
|
@@ -344,24 +344,29 @@ Inside Claude Code, the `/flo` (or `/fl`) slash command drives GitHub issue exec
|
|
|
344
344
|
/flo -m <issue> # Auto-merge the PR once required checks pass
|
|
345
345
|
```
|
|
346
346
|
|
|
347
|
-
Flags compose: e.g. `/flo -sd -m <issue>` runs the SDD cycle and auto-merges. Each
|
|
347
|
+
Flags compose: e.g. `/flo -sd -m <issue>` runs the SDD cycle and auto-merges. Each modifier has a `--no-*` form (`--no-sdd`, `--no-verify`, `--no-merge`) to override a `moflo.yaml` default for a single run — including `--no-verify`, since verify-before-done is on by default. For full options and details, type `/flo` with no arguments — Claude Code will display the complete skill documentation. Also available as `/fl`.
|
|
348
348
|
|
|
349
349
|
### Spec-Driven Development (SDD)
|
|
350
350
|
|
|
351
|
-
`/flo` can run the full **spec → plan → (review) → implement → verify** cycle — the 2026 agentic-coding pattern — with two independent
|
|
351
|
+
`/flo` can run the full **spec → plan → (review) → implement → verify** cycle — the 2026 agentic-coding pattern — with two independent modifiers (the spec/plan front-half is opt-in; the verify back-half is on by default):
|
|
352
352
|
|
|
353
|
-
- **`-sd` / `--sdd`** — author a **spec** (the *what* + acceptance criteria) and a **plan** (the *steps*), with a review checkpoint between each stage, before implementing. Artifacts persist as
|
|
354
|
-
- **`-v` / `--verify`** — the verify half
|
|
353
|
+
- **`-sd` / `--sdd`** *(opt-in)* — author a **spec** (the *what* + acceptance criteria) and a **plan** (the *steps*), with a review checkpoint between each stage, before implementing. Artifacts persist as Markdown at `<specs_dir>/<slug>/{spec,plan}.md` (default `.moflo/specs`) and are indexed into memory, so prior specs are searchable across sessions. Implies `--verify`.
|
|
354
|
+
- **`-v` / `--verify`** *(on by default)* — the verify half: a normal run plus the [verify-before-done gate](#the-gate-system). It runs the **`/verify`** skill, which exercises the change end-to-end against its acceptance criteria and reports a per-criterion PASS/FAIL, reusing the run's own tests (no double verify). Runs by default; `--no-verify` skips it. Separable from SDD, so you get "prove it works" without the spec ceremony.
|
|
355
355
|
|
|
356
356
|
Both compose with execution mode (`-n`/`-s`/`-h`) and `--worktree`, and default from `moflo.yaml`:
|
|
357
357
|
|
|
358
358
|
```yaml
|
|
359
359
|
sdd:
|
|
360
360
|
default: false # true → every /flo run uses the SDD cycle unless --no-sdd
|
|
361
|
+
specs_dir: .moflo/specs # where spec/plan artifacts are written (see below)
|
|
361
362
|
gates:
|
|
362
|
-
verify_before_done:
|
|
363
|
+
verify_before_done: true # on by default (#1294); false → skip /verify unless -v. Per-run: --no-verify
|
|
363
364
|
```
|
|
364
365
|
|
|
366
|
+
On upgrade, consumers with no `verify_before_done` key start enforcing verify-before-done; an explicit value is preserved. Docs-only diffs are exempt, so a pure-docs PR is never blocked.
|
|
367
|
+
|
|
368
|
+
**Where specs live — and making them reviewable.** By default spec/plan artifacts are written under `.moflo/specs`, which `flo init` gitignores — so they stay **local and never bloat source control**. If you want specs to appear in PRs and be reviewed like any other file, point `sdd.specs_dir` at a **tracked** path (e.g. `docs/specs` or `.specs`) and commit them normally; no gitignore surgery required. The path is written with `/` and resolved cross-platform. If you set it to a directory already covered by `guidance.directories`, moflo indexes the specs once (as guidance) rather than twice.
|
|
369
|
+
|
|
365
370
|
`--sdd` implies `--verify` (a spec/plan without an enforced verify step drifts). Manage artifacts directly with `flo sdd` (`spec`, `plan`, `review`, `check`, `list`), and start from a fuzzy idea with **`/commune`**, which can hand its synthesized spec straight into the SDD spine. Wiring status is reported by **`/healer`** (and `/eldar`).
|
|
366
371
|
|
|
367
372
|
### Auto-merge
|
|
@@ -907,7 +912,7 @@ gates:
|
|
|
907
912
|
memory_first: true # Must search memory before file exploration
|
|
908
913
|
task_create_first: true # Must TaskCreate before Agent tool
|
|
909
914
|
context_tracking: true # Track context window depletion
|
|
910
|
-
verify_before_done:
|
|
915
|
+
verify_before_done: true # On by default (#1294); /verify before `gh pr create` (unless --no-verify). false to disable
|
|
911
916
|
|
|
912
917
|
sdd:
|
|
913
918
|
default: false # true → every /flo run uses the spec→plan→implement→verify cycle unless --no-sdd
|
package/bin/gate.cjs
CHANGED
|
@@ -60,10 +60,11 @@ function writeState(s) {
|
|
|
60
60
|
|
|
61
61
|
// Load moflo.yaml gate config (defaults: all enabled)
|
|
62
62
|
function loadGateConfig() {
|
|
63
|
-
//
|
|
64
|
-
// ships
|
|
65
|
-
//
|
|
66
|
-
|
|
63
|
+
// verify_before_done is opt-OUT (default true), like every other gate: #1294
|
|
64
|
+
// ships a real /verify skill and has /flo delegate to it, so leaving it off by
|
|
65
|
+
// default would make the default /flo run silently skip the acceptance check.
|
|
66
|
+
// Disable per-project with `verify_before_done: false` or per-run `--no-verify`.
|
|
67
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true, swarm_invocation_gate: true, verify_before_done: true };
|
|
67
68
|
try {
|
|
68
69
|
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
69
70
|
if (fs.existsSync(yamlPath)) {
|
|
@@ -75,8 +76,8 @@ function loadGateConfig() {
|
|
|
75
76
|
if (/simplify_gate:\s*false/i.test(content)) defaults.simplify_gate = false;
|
|
76
77
|
if (/learnings_gate:\s*false/i.test(content)) defaults.learnings_gate = false;
|
|
77
78
|
if (/swarm_invocation_gate:\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
|
|
78
|
-
// Opt-
|
|
79
|
-
if (/verify_before_done:\s*
|
|
79
|
+
// Opt-out: on by default; disable only when explicitly set false.
|
|
80
|
+
if (/verify_before_done:\s*false/i.test(content)) defaults.verify_before_done = false;
|
|
80
81
|
}
|
|
81
82
|
} catch (e) { /* use defaults */ }
|
|
82
83
|
return defaults;
|
|
@@ -763,12 +764,13 @@ switch (command) {
|
|
|
763
764
|
process.exit(2);
|
|
764
765
|
}
|
|
765
766
|
case 'check-before-done': {
|
|
766
|
-
// Story #1274 (Epic #1269). Verify-before-done: block `gh pr create`
|
|
767
|
-
// the change has been verified end-to-end (
|
|
768
|
-
// plan's acceptance criteria.
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
// exemption as check-before-pr, so
|
|
767
|
+
// Story #1274 (Epic #1269) + #1294. Verify-before-done: block `gh pr create`
|
|
768
|
+
// until the change has been verified end-to-end (the /verify skill) against
|
|
769
|
+
// the plan's acceptance criteria. ON by default (#1294) — /flo delegates to
|
|
770
|
+
// /verify, so a default run does the acceptance check; disable per-project
|
|
771
|
+
// with `gates: verify_before_done: false` or per-run `--no-verify`. Same
|
|
772
|
+
// trigger + no-source exemption as check-before-pr, so they compose on one
|
|
773
|
+
// command (docs-only diffs are exempt, so this never blocks a docs PR).
|
|
772
774
|
if (!config.verify_before_done) break;
|
|
773
775
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
774
776
|
if (!/(?:^|&&\s*|\|\|\s*|;\s*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*gh\s+pr\s+create\b/.test(cmd)) break;
|
package/bin/index-guidance.mjs
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { existsSync, readdirSync, readFileSync, statSync, mkdirSync, writeFileSync } from 'fs';
|
|
26
|
-
import { resolve, relative, dirname, basename, extname } from 'path';
|
|
26
|
+
import { resolve, relative, dirname, basename, extname, sep } from 'path';
|
|
27
27
|
import { fileURLToPath } from 'url';
|
|
28
28
|
import { memoryDbPath, findProjectRoot } from './lib/moflo-paths.mjs';
|
|
29
29
|
import { openBackend } from './lib/get-backend.mjs';
|
|
@@ -51,12 +51,25 @@ function loadGuidanceDirs() {
|
|
|
51
51
|
|
|
52
52
|
// 1. Read moflo.yaml / moflo.config.json for user-configured directories
|
|
53
53
|
let configDirs = null;
|
|
54
|
+
// #1294 — configurable SDD spec/plan location (default .moflo/specs). Parsed
|
|
55
|
+
// here alongside the guidance dirs so step 6 can honor it and skip a
|
|
56
|
+
// double-index when it sits inside a guidance dir.
|
|
57
|
+
let specsDirConfig = null;
|
|
54
58
|
const yamlPath = resolve(projectRoot, 'moflo.yaml');
|
|
55
59
|
const jsonPath = resolve(projectRoot, 'moflo.config.json');
|
|
56
60
|
|
|
57
61
|
if (existsSync(yamlPath)) {
|
|
58
62
|
try {
|
|
59
63
|
const content = readFileSync(yamlPath, 'utf-8');
|
|
64
|
+
// sdd.specs_dir (snake_case or camelCase, quoted or bare). Two-step:
|
|
65
|
+
// isolate the top-level `sdd:` block (up to the next column-0 key, blank
|
|
66
|
+
// lines included — a naive contiguous-line regex would break on natural
|
|
67
|
+
// whitespace between keys, #1294 review), then find specs_dir within it.
|
|
68
|
+
const sddBlock = content.match(/(?:^|\n)[ \t]*sdd:[ \t]*\n([\s\S]*?)(?=\n\S|$)/);
|
|
69
|
+
if (sddBlock) {
|
|
70
|
+
const m = sddBlock[1].match(/(?:^|\n)[ \t]+specs_?[dD]ir:[ \t]*["']?([^"'\n#]+)/);
|
|
71
|
+
if (m && m[1].trim()) specsDirConfig = m[1].trim();
|
|
72
|
+
}
|
|
60
73
|
// Simple YAML array extraction — avoids needing js-yaml at runtime
|
|
61
74
|
// Matches: guidance:\n directories:\n - .claude/guidance\n - docs/guides
|
|
62
75
|
const guidanceBlock = content.match(/guidance:\s*\n\s+directories:\s*\n((?:\s+-\s+.+\n?)+)/);
|
|
@@ -73,6 +86,8 @@ function loadGuidanceDirs() {
|
|
|
73
86
|
if (raw.guidance?.directories && Array.isArray(raw.guidance.directories)) {
|
|
74
87
|
configDirs = raw.guidance.directories;
|
|
75
88
|
}
|
|
89
|
+
const sd = raw.sdd?.specs_dir ?? raw.sdd?.specsDir;
|
|
90
|
+
if (typeof sd === 'string' && sd.trim()) specsDirConfig = sd.trim();
|
|
76
91
|
} catch { /* ignore parse errors */ }
|
|
77
92
|
}
|
|
78
93
|
|
|
@@ -133,12 +148,32 @@ function loadGuidanceDirs() {
|
|
|
133
148
|
dirs.push({ path: bundledSkillsDir, prefix: 'skill-bundled', fileFilter: ['SKILL.md'], kind: 'skill', absolute: true });
|
|
134
149
|
}
|
|
135
150
|
|
|
136
|
-
// 6. SDD spec/plan artifacts (Epic #1269) — index
|
|
151
|
+
// 6. SDD spec/plan artifacts (Epic #1269) — index <specs_dir>/<slug>/{spec,plan}.md
|
|
137
152
|
// so prior specs/plans are searchable across sessions. kind: 'spec' keys each
|
|
138
153
|
// file by <slug>-<spec|plan> to avoid collisions between per-slug spec.md files.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
154
|
+
// #1294 — the location is configurable (default .moflo/specs). Cross-platform
|
|
155
|
+
// (Rule #1): split the /-written value and re-join, never hardcode a separator.
|
|
156
|
+
// Validation MUST match specsRoot() in src/cli/sdd/artifacts.ts exactly, or
|
|
157
|
+
// the indexer and the CLI would disagree on where specs live: reject
|
|
158
|
+
// absolute / drive-letter / parent-escape values and fall back to the default.
|
|
159
|
+
const rawSpecs = specsDirConfig || '.moflo/specs';
|
|
160
|
+
let specsRel = rawSpecs.split(/[\\/]+/).filter(Boolean);
|
|
161
|
+
const specsEscapes = specsRel.length === 0
|
|
162
|
+
|| specsRel.includes('..')
|
|
163
|
+
|| /^([a-zA-Z]:|~)$/.test(specsRel[0])
|
|
164
|
+
|| rawSpecs.startsWith('/')
|
|
165
|
+
|| rawSpecs.startsWith('\\');
|
|
166
|
+
if (specsEscapes) specsRel = ['.moflo', 'specs'];
|
|
167
|
+
const projectSpecsDir = resolve(projectRoot, ...specsRel);
|
|
168
|
+
// Double-index guard: if specs_dir sits inside a guidance dir, the guidance
|
|
169
|
+
// scan (step 1) already indexes those .md files — skip the 'spec' entry so
|
|
170
|
+
// they aren't indexed twice under two prefixes.
|
|
171
|
+
const insideGuidance = userDirs.some(d => {
|
|
172
|
+
const gd = resolve(projectRoot, ...d.split(/[\\/]+/).filter(Boolean));
|
|
173
|
+
return projectSpecsDir === gd || projectSpecsDir.startsWith(gd + sep);
|
|
174
|
+
});
|
|
175
|
+
if (existsSync(projectSpecsDir) && !insideGuidance) {
|
|
176
|
+
dirs.push({ path: specsRel.join('/'), prefix: 'spec', fileFilter: ['spec.md', 'plan.md'], kind: 'spec' });
|
|
142
177
|
}
|
|
143
178
|
|
|
144
179
|
return dirs;
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
14
|
+
import { dirname } from 'path';
|
|
15
|
+
import { hasMigrationRun, markMigrationDone } from './migrations.mjs';
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Registry of top-level config sections that moflo ships with default blocks.
|
|
@@ -93,6 +95,70 @@ export const RENAMED_SECTIONS = [
|
|
|
93
95
|
{ from: 'auto_reflect', to: 'auto_meditate' },
|
|
94
96
|
];
|
|
95
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Registry of one-time in-place VALUE migrations — for when the polarity of a
|
|
100
|
+
* shipped default changes and we want existing users carried onto the new
|
|
101
|
+
* default. Unlike a rename or an appended block, these rewrite an existing
|
|
102
|
+
* value line, so each entry is scoped to a narrowly-matched, AUTO-GENERATED
|
|
103
|
+
* default and never a value the user deliberately hand-set. Each is idempotent:
|
|
104
|
+
* once applied, the line no longer matches its own `match`.
|
|
105
|
+
*/
|
|
106
|
+
export const VALUE_MIGRATIONS = [
|
|
107
|
+
{
|
|
108
|
+
// #1294 — verify-before-done flipped to default-ON. `flo init` briefly (from
|
|
109
|
+
// Epic #1269 until #1294) wrote `verify_before_done: false` into the template,
|
|
110
|
+
// so a project created in that window is pinned OFF and would silently skip
|
|
111
|
+
// verification on upgrade. Flip that auto-written default to true. We match it
|
|
112
|
+
// by its generated "opt-in" comment: a hand-typed bare `false` (no such
|
|
113
|
+
// comment) is a deliberate opt-out and is LEFT UNTOUCHED. Erring toward more
|
|
114
|
+
// verification is safe (a spurious extra gate is a mild `--no-verify` away);
|
|
115
|
+
// dropping verification silently is the dangerous direction.
|
|
116
|
+
id: 'verify_before_done-default-on-1294',
|
|
117
|
+
// Cross-platform (Rule #1, checklist #4): match line CONTENT only with
|
|
118
|
+
// [^\r\n] and do NOT anchor on `$` — so on a CRLF `moflo.yaml` the trailing
|
|
119
|
+
// \r\n is left intact by the replace and we never mix line endings.
|
|
120
|
+
match: /^([ \t]*)verify_before_done:[ \t]*false\b[^\r\n]*\bopt-in\b[^\r\n]*/m,
|
|
121
|
+
replace: (_m, indent) =>
|
|
122
|
+
`${indent}verify_before_done: true # Epic #1269/#1294: on by default; opt out with false or per-run --no-verify`,
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Apply registered one-time VALUE migrations to the yaml file in place.
|
|
128
|
+
*
|
|
129
|
+
* **Runs each migration at most once, ever** — gated on the `.moflo/migrations.json`
|
|
130
|
+
* ledger (`hasMigrationRun`/`markMigrationDone`). This is the load-bearing
|
|
131
|
+
* guarantee: after the first attempt the id is recorded permanently, so if the
|
|
132
|
+
* user later flips the value back (e.g. turns verify off again), a future upgrade
|
|
133
|
+
* will **not** silently re-apply the migration and overwrite their choice. The
|
|
134
|
+
* ledger is marked whether or not the line actually matched, so a project that
|
|
135
|
+
* was already on the new value — or that had a deliberate hand-set value we chose
|
|
136
|
+
* not to touch — is likewise never reconsidered.
|
|
137
|
+
*
|
|
138
|
+
* Returns the ids that actually rewrote a line (empty if nothing changed).
|
|
139
|
+
*/
|
|
140
|
+
export function applyValueMigrations(yamlPath, registry = VALUE_MIGRATIONS) {
|
|
141
|
+
if (!existsSync(yamlPath)) return [];
|
|
142
|
+
const projectRoot = dirname(yamlPath);
|
|
143
|
+
let text = readFileSync(yamlPath, 'utf-8');
|
|
144
|
+
let changed = false;
|
|
145
|
+
const applied = [];
|
|
146
|
+
for (const mig of registry) {
|
|
147
|
+
if (hasMigrationRun(projectRoot, mig.id)) continue; // already attempted once — never again
|
|
148
|
+
const flipped = mig.match.test(text);
|
|
149
|
+
if (flipped) {
|
|
150
|
+
text = text.replace(mig.match, mig.replace);
|
|
151
|
+
changed = true;
|
|
152
|
+
applied.push(mig.id);
|
|
153
|
+
}
|
|
154
|
+
// Record the attempt regardless of whether it matched, so this migration is
|
|
155
|
+
// considered exactly once for this project and can't fire on a later opt-out.
|
|
156
|
+
markMigrationDone(projectRoot, mig.id, { flipped });
|
|
157
|
+
}
|
|
158
|
+
if (changed) writeFileSync(yamlPath, text, 'utf-8');
|
|
159
|
+
return applied;
|
|
160
|
+
}
|
|
161
|
+
|
|
96
162
|
/**
|
|
97
163
|
* Rename any registered top-level sections present under their OLD key to the
|
|
98
164
|
* NEW key, in place. Only the top-level key line is rewritten; the block body
|
|
@@ -136,7 +202,10 @@ export function missingSections(yamlText, registry = REQUIRED_SECTIONS) {
|
|
|
136
202
|
* Append any missing registered sections to the yaml file at `yamlPath`.
|
|
137
203
|
*
|
|
138
204
|
* - Idempotent: sections already present are left alone.
|
|
139
|
-
* -
|
|
205
|
+
* - Appended values are never re-touched once present. The only in-place edits
|
|
206
|
+
* are the registered key renames (RENAMED_SECTIONS) and one-time value
|
|
207
|
+
* migrations (VALUE_MIGRATIONS), each narrowly scoped so a user's deliberate
|
|
208
|
+
* settings are preserved.
|
|
140
209
|
* - Returns the list of section keys that were appended (empty if no change).
|
|
141
210
|
*/
|
|
142
211
|
export function ensureYamlSections(yamlPath, registry = REQUIRED_SECTIONS) {
|
|
@@ -147,6 +216,10 @@ export function ensureYamlSections(yamlPath, registry = REQUIRED_SECTIONS) {
|
|
|
147
216
|
// not re-appended as a fresh default block alongside the stale old one.
|
|
148
217
|
renameYamlSections(yamlPath);
|
|
149
218
|
|
|
219
|
+
// One-time value migrations (e.g. #1294 verify-before-done default flip). Also
|
|
220
|
+
// in place, also idempotent; scoped to auto-written defaults, never user values.
|
|
221
|
+
applyValueMigrations(yamlPath);
|
|
222
|
+
|
|
150
223
|
const original = readFileSync(yamlPath, 'utf-8');
|
|
151
224
|
const toAppend = registry.filter((entry) => !hasTopLevelSection(original, entry.key));
|
|
152
225
|
if (toAppend.length === 0) return [];
|
|
@@ -35,7 +35,7 @@ const DEFAULT_CONFIG = {
|
|
|
35
35
|
memory_first: true,
|
|
36
36
|
task_create_first: true,
|
|
37
37
|
context_tracking: true,
|
|
38
|
-
verify_before_done:
|
|
38
|
+
verify_before_done: true,
|
|
39
39
|
},
|
|
40
40
|
auto_index: {
|
|
41
41
|
guidance: true,
|
|
@@ -104,6 +104,7 @@ const DEFAULT_CONFIG = {
|
|
|
104
104
|
},
|
|
105
105
|
sdd: {
|
|
106
106
|
default: false,
|
|
107
|
+
specs_dir: '.moflo/specs',
|
|
107
108
|
},
|
|
108
109
|
merge: {
|
|
109
110
|
auto: false,
|
|
@@ -321,6 +322,13 @@ function mergeConfig(raw, root) {
|
|
|
321
322
|
},
|
|
322
323
|
sdd: {
|
|
323
324
|
default: raw.sdd?.default ?? DEFAULT_CONFIG.sdd.default,
|
|
325
|
+
// Accept snake_case (`specs_dir`) or camelCase (`specsDir`). A blank/empty
|
|
326
|
+
// value falls back to the default so a consumer can't accidentally point
|
|
327
|
+
// specs at the project root with `specs_dir: ""`.
|
|
328
|
+
specs_dir: (typeof (raw.sdd?.specs_dir ?? raw.sdd?.specsDir) === 'string'
|
|
329
|
+
&& (raw.sdd?.specs_dir ?? raw.sdd?.specsDir).trim())
|
|
330
|
+
? (raw.sdd?.specs_dir ?? raw.sdd?.specsDir).trim()
|
|
331
|
+
: DEFAULT_CONFIG.sdd.specs_dir,
|
|
324
332
|
},
|
|
325
333
|
merge: {
|
|
326
334
|
auto: raw.merge?.auto ?? DEFAULT_CONFIG.merge.auto,
|
|
@@ -460,7 +468,7 @@ gates:
|
|
|
460
468
|
memory_first: true # Search memory before Glob/Grep
|
|
461
469
|
task_create_first: true # TaskCreate before Agent tool
|
|
462
470
|
context_tracking: true # Track context bracket (FRESH/MODERATE/DEPLETED/CRITICAL)
|
|
463
|
-
verify_before_done:
|
|
471
|
+
verify_before_done: true # Epic #1269/#1294: run /verify before 'gh pr create'. On by default; opt out with false or per-run --no-verify
|
|
464
472
|
|
|
465
473
|
# Auto-index on session start
|
|
466
474
|
auto_index:
|
|
@@ -564,6 +572,9 @@ epic:
|
|
|
564
572
|
# Spec-Driven Development (Epic #1269)
|
|
565
573
|
sdd:
|
|
566
574
|
default: false # true = every /flo run uses spec->plan->implement->verify unless --no-sdd
|
|
575
|
+
specs_dir: .moflo/specs # where flo sdd writes spec/plan artifacts. Default is local + gitignored
|
|
576
|
+
# (no source-control bloat). Set a tracked path (e.g. docs/specs) to make
|
|
577
|
+
# specs reviewable in PRs. Written with / — resolved cross-platform (#1294).
|
|
567
578
|
|
|
568
579
|
# Auto-merge the PR at the end of a full /flo run once preconditions are met (#1285).
|
|
569
580
|
# Opt-in; default false. Override per-run with --merge / --no-merge.
|
|
@@ -47,6 +47,7 @@ export const SKILLS_MAP = {
|
|
|
47
47
|
'perf-audit', // alias for quicken (pointer skill); installed alongside it
|
|
48
48
|
'ward', // ad-hoc test-gap audit (replaced the always-on `testgaps` daemon worker)
|
|
49
49
|
'test-gaps', // alias for ward (pointer skill); installed alongside it
|
|
50
|
+
'verify', // SDD verify-before-done: exercises the change against acceptance criteria (#1294)
|
|
50
51
|
],
|
|
51
52
|
memory: [
|
|
52
53
|
'memory-patterns',
|
|
@@ -255,7 +255,7 @@ function writeState(s) {
|
|
|
255
255
|
|
|
256
256
|
// Load moflo.yaml gate config (defaults: all enabled)
|
|
257
257
|
function loadGateConfig() {
|
|
258
|
-
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true, swarm_invocation_gate: true, verify_before_done:
|
|
258
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true, swarm_invocation_gate: true, verify_before_done: true };
|
|
259
259
|
try {
|
|
260
260
|
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
261
261
|
if (fs.existsSync(yamlPath)) {
|
|
@@ -267,7 +267,8 @@ function loadGateConfig() {
|
|
|
267
267
|
if (/simplify_gate:\\s*false/i.test(content)) defaults.simplify_gate = false;
|
|
268
268
|
if (/learnings_gate:\\s*false/i.test(content)) defaults.learnings_gate = false;
|
|
269
269
|
if (/swarm_invocation_gate:\\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
|
|
270
|
-
|
|
270
|
+
// Opt-out: on by default (#1294); disable only when explicitly set false.
|
|
271
|
+
if (/verify_before_done:\\s*false/i.test(content)) defaults.verify_before_done = false;
|
|
271
272
|
}
|
|
272
273
|
} catch (e) { /* use defaults */ }
|
|
273
274
|
return defaults;
|
|
@@ -618,9 +619,9 @@ switch (command) {
|
|
|
618
619
|
process.exit(2);
|
|
619
620
|
}
|
|
620
621
|
case 'check-before-done': {
|
|
621
|
-
// Story #1274 (Epic #1269) — verify-before-done.
|
|
622
|
-
//
|
|
623
|
-
//
|
|
622
|
+
// Story #1274 (Epic #1269) + #1294 — verify-before-done. ON by default
|
|
623
|
+
// (#1294); disable via moflo.yaml gates.verify_before_done: false or per-run
|
|
624
|
+
// --no-verify. Same 'gh pr create' trigger as check-before-pr. This
|
|
624
625
|
// template variant intentionally omits the no-source (docs-only) exemption to
|
|
625
626
|
// stay consistent with THIS file's simpler check-before-pr; the full exemption
|
|
626
627
|
// lives in the source bin/gate.cjs that the launcher syncs over this fallback.
|
|
@@ -206,7 +206,7 @@ gates:
|
|
|
206
206
|
memory_first: ${gates}
|
|
207
207
|
task_create_first: ${gates}
|
|
208
208
|
context_tracking: ${gates}
|
|
209
|
-
verify_before_done:
|
|
209
|
+
verify_before_done: true # Epic #1269/#1294: run /verify before 'gh pr create'. On by default; opt out with false or per-run --no-verify
|
|
210
210
|
|
|
211
211
|
# Auto-index on session start
|
|
212
212
|
auto_index:
|
|
@@ -250,9 +250,9 @@ function generateHooksConfig(config) {
|
|
|
250
250
|
hooks: [
|
|
251
251
|
{ type: 'command', command: gateHookCmd('check-dangerous-command'), timeout: 2000 },
|
|
252
252
|
{ type: 'command', command: gateHookCmd('check-before-pr'), timeout: 2000 },
|
|
253
|
-
// Story #1274 (Epic #1269) — verify-before-done.
|
|
254
|
-
//
|
|
255
|
-
//
|
|
253
|
+
// Story #1274 (Epic #1269) + #1294 — verify-before-done. On by default
|
|
254
|
+
// (#1294); disable with `gates: verify_before_done: false`. Gates
|
|
255
|
+
// `gh pr create` like check-before-pr, so both live on this Bash/PowerShell block.
|
|
256
256
|
{ type: 'command', command: gateHookCmd('check-before-done'), timeout: 2000 },
|
|
257
257
|
// #1132 — moved from PostToolUse so process.exit(2) actually blocks
|
|
258
258
|
// read-like shell commands that bypass the Read/Glob/Grep gates.
|
|
@@ -20,13 +20,34 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { join } from 'node:path';
|
|
22
22
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, } from 'node:fs';
|
|
23
|
+
import { loadMofloConfig } from '../config/moflo-config.js';
|
|
23
24
|
export const SDD_STATUSES = ['draft', 'reviewed'];
|
|
24
25
|
// ============================================================================
|
|
25
26
|
// Paths — all via path.join (Rule #1)
|
|
26
27
|
// ============================================================================
|
|
27
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* Root directory holding every spec slug. Defaults to `<projectRoot>/.moflo/specs`
|
|
30
|
+
* but is configurable via `moflo.yaml` `sdd.specs_dir` (#1294) — point it at a
|
|
31
|
+
* tracked path (e.g. `docs/specs`) to make specs reviewable in PRs.
|
|
32
|
+
*
|
|
33
|
+
* Cross-platform (Rule #1): the configured value is a `/`-written relative path;
|
|
34
|
+
* we split on either separator and `path.join` each segment, so a Windows repo
|
|
35
|
+
* with `docs/specs` in yaml still resolves correctly and no separator is ever
|
|
36
|
+
* hardcoded. An absolute or `..`-escaping value falls back to the default.
|
|
37
|
+
*/
|
|
28
38
|
export function specsRoot(projectRoot) {
|
|
29
|
-
|
|
39
|
+
const configured = loadMofloConfig(projectRoot).sdd.specs_dir;
|
|
40
|
+
const segments = configured.split(/[\\/]+/).filter(Boolean);
|
|
41
|
+
// Reject absolute paths and parent-directory escapes — specs live under the
|
|
42
|
+
// project root. Fall back to the default rather than resolving outside it.
|
|
43
|
+
const escapes = segments.length === 0
|
|
44
|
+
|| segments.includes('..')
|
|
45
|
+
|| /^([a-zA-Z]:|~)$/.test(segments[0])
|
|
46
|
+
|| configured.startsWith('/')
|
|
47
|
+
|| configured.startsWith('\\');
|
|
48
|
+
if (escapes)
|
|
49
|
+
return join(projectRoot, '.moflo', 'specs');
|
|
50
|
+
return join(projectRoot, ...segments);
|
|
30
51
|
}
|
|
31
52
|
/** Per-unit directory: `<projectRoot>/.moflo/specs/<slug>`. */
|
|
32
53
|
export function specDir(projectRoot, slug) {
|
|
@@ -1347,6 +1347,7 @@ const DASHBOARD_HTML = `<!DOCTYPE html>
|
|
|
1347
1347
|
const cards =
|
|
1348
1348
|
'<div class="grid">' +
|
|
1349
1349
|
'<div class="stat-card"><div class="label">Total Sessions</div><div class="value">' + fmtCount(cs.totalSessions) + '</div></div>' +
|
|
1350
|
+
'<div class="stat-card"><div class="label">Total Tokens</div><div class="value">' + fmtCount(lifeTotal) + '</div></div>' +
|
|
1350
1351
|
'<div class="stat-card"><div class="label">Subagent Tokens</div><div class="value">' + fmtCount(subTotal) + '</div></div>' +
|
|
1351
1352
|
'<div class="stat-card"><div class="label">Sessions w/ Errors</div><div class="value">' + fmtCount(cs.errorSessions) + '</div></div>' +
|
|
1352
1353
|
'<div class="stat-card"><div class="label">Median Duration</div><div class="value">' + fmtDuration(cs.sessionDurationMs.median) + '</div></div>' +
|
|
@@ -18,9 +18,9 @@ export const REQUIRED_HOOK_WIRING = [
|
|
|
18
18
|
{ event: 'PreToolUse', pattern: 'check-before-read' },
|
|
19
19
|
{ event: 'PreToolUse', pattern: 'check-dangerous-command' },
|
|
20
20
|
{ event: 'PreToolUse', pattern: 'check-before-pr' },
|
|
21
|
-
// Story #1274 (Epic #1269) — verify-before-done gate on `gh pr create`.
|
|
22
|
-
// for every consumer so
|
|
23
|
-
//
|
|
21
|
+
// Story #1274 (Epic #1269) + #1294 — verify-before-done gate on `gh pr create`.
|
|
22
|
+
// Wired for every consumer; on by default (#1294) so a default /flo run does the
|
|
23
|
+
// acceptance check. Disable with `gates: verify_before_done: false`.
|
|
24
24
|
{ event: 'PreToolUse', pattern: 'check-before-done' },
|
|
25
25
|
// #931 — TaskCreate REMINDER + namespace hint emit only at Agent spawn now,
|
|
26
26
|
// not on every prompt. Saves ~90 tokens × every prompt × every consumer.
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.11.10-rc.
|
|
3
|
+
"version": "4.11.10-rc.4",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
|
|
5
5
|
"main": "dist/src/cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
96
96
|
"@typescript-eslint/parser": "^7.18.0",
|
|
97
97
|
"eslint": "^8.0.0",
|
|
98
|
-
"moflo": "^4.11.10-rc.
|
|
98
|
+
"moflo": "^4.11.10-rc.3",
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
101
|
"vitest": "^4.0.0"
|