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.
@@ -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 turning on `sdd.default` / `gates.verify_before_done`.
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, opt-in modifiers on `/flo` orthogonal to execution mode (`-n`/`-s`/`-h`) and `--worktree`. Verify is separable from SDD by design.
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
- | Well-scoped work, but you want "prove it works before done" enforced | `-v` / `--verify` | Normal run plus the verify-before-done gate; no spec/plan front-half. |
15
- | Small, obvious change | (none) | Standard `/flo` run. |
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. Use `--no-sdd` / `--no-verify` to opt a single run out of a `moflo.yaml` default.
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 reviewable Markdown, one directory per unit of work:
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
- .moflo/specs/<slug>/spec.md # the "what" + acceptance criteria
27
- .moflo/specs/<slug>/plan.md # the "steps" + how each criterion is verified
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 git-tracked in consumer repos (so they are reviewable) and 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 `.moflo/specs/...` path in a skill step (cross-platform, Rule #1: the CLI builds every path with `path.join`).
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
- - **Enforcement is opt-in.** Off by default; enable with `gates.verify_before_done: true` (or per-run `-v`). Existing installs see zero change on upgrade.
56
- - **Satisfy it by running the native `/verify` skill** — it exercises the change against the plan's (or ticket's) acceptance criteria. Store the outcome to memory (`namespace: learnings, key: verify:<slug>`) so it feeds routing/learning.
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
- Both defaults are off; turn either on to make it the project standard, then override per run.
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: false # truerequire /verify before `gh pr create` unless --no-verify
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.
@@ -60,10 +60,11 @@ function writeState(s) {
60
60
 
61
61
  // Load moflo.yaml gate config (defaults: all enabled)
62
62
  function loadGateConfig() {
63
- // Note: verify_before_done defaults to FALSE (opt-in) the only gate that
64
- // ships off, so existing consumers see zero behavior change on upgrade
65
- // (Story #1274, Epic #1269). Every other gate is opt-out (default true).
66
- 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: false };
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-in: enable only when explicitly set true.
79
- if (/verify_before_done:\s*true/i.test(content)) defaults.verify_before_done = true;
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` until
767
- // the change has been verified end-to-end (native /verify skill) against the
768
- // plan's acceptance criteria. OFF by default — only enforced when the
769
- // consumer opts in via moflo.yaml `gates: verify_before_done: true`, so
770
- // existing installs see zero change on upgrade. Same trigger + no-source
771
- // exemption as check-before-pr, so the two gates compose on one command.
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 only** — a normal run plus the completion gate, no spec/plan front-half. |
53
- | `--no-sdd`, `--no-verify` | | Opt a single run out when a `moflo.yaml` default turned it on. |
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` so a project can make either the default; per-run flags override. `--sdd` implies `--verify` (a spec/plan without an enforced verify step drifts). In `-t`/`-r` modes (no implementation) `--verify` is a no-op — emit the one-line ignored note; `--sdd` in `-t` writes the spec/plan **into the ticket** rather than scaffolding artifacts. Full mechanics in `./sdd.md`.
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 so a
119
- // project default applies unless a per-run flag overrides it. Read the two keys
120
- // from moflo.yaml at the project root (absent false):
121
- // sddMode ← `sdd.default: true`
122
- // verifyMode ← `gates.verify_before_done: true`
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 = false; // -v / --verify (verify-before-done only)
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 --verify is a no-op there — note and clear it. --sdd in
191
- // -t writes the spec/plan INTO the ticket (see ./sdd.md); in -r it's ignored.
192
- if (verifyMode && (workflowMode === "ticket" || workflowMode === "research")) {
193
- console.log("Note: --verify ignored " + workflowMode + " mode does not implement.");
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. **If `verifyMode`** (always on under `sddMode`): verify the change end-to-end with `/verify` against the plan's acceptance criteria, and store the outcome to memory `./sdd.md`. This satisfies the verify-before-done gate.
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 (when `verifyMode` / `--sdd` / `gates.verify_before_done`)
187
- Run the native `/verify` skill to exercise the change end-to-end against the plan's (or ticket's) acceptance criteria, and store the outcome to memory. Under `--sdd` this always runs; independently, `-v`/`--verify` or `moflo.yaml gates.verify_before_done: true` triggers it. The `check-before-done` gate blocks `gh pr create` until a `/verify` run is recorded (a source edit invalidates a prior verification). Full mechanics: `./sdd.md`.
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.
@@ -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 `--verify`.
6
- - **`-v` / `--verify`** — verify-before-done only: a normal run plus the completion gate, no spec/plan front-half.
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`, `gates.verify_before_done`); 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.
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 `.moflo/specs/<slug>/{spec,plan}.md` (git-tracked in consumer repos reviewable). Drive them with the `flo sdd` CLI; never hand-write the paths.
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
- 1. Run the native **`/verify`** skill to exercise the change end-to-end. Under `--sdd`, verify against the plan's acceptance criteria; without a plan, verify against the ticket's Acceptance Criteria.
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 reviewable, memory-indexed artifacts; `/flo -v` enforces verify-before-done on its own. Both opt-in. [Details →](#spec-driven-development-sdd) |
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** *(opt-in)* | Claude must verify the change end-to-end (native `/verify`) before `gh pr create` | Before `gh pr create`, only when `gates.verify_before_done: true` | Enforces "prove it works before done." Off by default, so it never surprises an existing install. Pairs with the [Spec-Driven Development](#spec-driven-development-sdd) cycle, which gives verification its acceptance criteria. |
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: false # Set to true to require /verify before `gh pr create`
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 opt-in modifier has a `--no-*` form (`--no-sdd`, `--no-verify`, `--no-merge`) to override a `moflo.yaml` default for a single run. For full options and details, type `/flo` with no arguments — Claude Code will display the complete skill documentation. Also available as `/fl`.
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, opt-in modifiers:
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 reviewable Markdown at `.moflo/specs/<slug>/{spec,plan}.md` and are indexed into memory, so prior specs are searchable across sessions. Implies `--verify`.
354
- - **`-v` / `--verify`** — the verify half on its own: a normal run plus the [verify-before-done gate](#the-gate-system). Separable from SDD so you can enforce "prove it works" without the spec ceremony.
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: false # truerequire /verify before `gh pr create` unless --no-verify
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: false # Require /verify before `gh pr create` (unless --no-verify)
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
- // Note: verify_before_done defaults to FALSE (opt-in) the only gate that
64
- // ships off, so existing consumers see zero behavior change on upgrade
65
- // (Story #1274, Epic #1269). Every other gate is opt-out (default true).
66
- 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: false };
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-in: enable only when explicitly set true.
79
- if (/verify_before_done:\s*true/i.test(content)) defaults.verify_before_done = true;
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` until
767
- // the change has been verified end-to-end (native /verify skill) against the
768
- // plan's acceptance criteria. OFF by default — only enforced when the
769
- // consumer opts in via moflo.yaml `gates: verify_before_done: true`, so
770
- // existing installs see zero change on upgrade. Same trigger + no-source
771
- // exemption as check-before-pr, so the two gates compose on one command.
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;
@@ -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 .moflo/specs/<slug>/{spec,plan}.md
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
- const projectSpecsDir = resolve(projectRoot, '.moflo/specs');
140
- if (existsSync(projectSpecsDir)) {
141
- dirs.push({ path: '.moflo/specs', prefix: 'spec', fileFilter: ['spec.md', 'plan.md'], kind: 'spec' });
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
- * - Non-destructive: user values are never read, parsed, or rewritten.
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: false,
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: false # Epic #1269: require /verify before 'gh pr create' (opt-in)
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: false };
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
- if (/verify_before_done:\\s*true/i.test(content)) defaults.verify_before_done = true;
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. OFF unless the consumer
622
- // opts in via moflo.yaml gates.verify_before_done: true, so existing installs
623
- // see zero change. Same 'gh pr create' trigger as check-before-pr. This
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: false # Epic #1269: require /verify before 'gh pr create' (opt-in; independent of the toggle above)
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. Inert unless the
254
- // consumer sets `gates: verify_before_done: true`; gates `gh pr create`
255
- // the same as check-before-pr, so both live on this Bash/PowerShell block.
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
- /** Root directory holding every spec slug: `<projectRoot>/.moflo/specs`. */
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
- return join(projectRoot, '.moflo', 'specs');
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`. Wired
22
- // for every consumer so opting in via `gates: verify_before_done: true` takes
23
- // effect without a re-init; inert (breaks immediately) while the toggle is off.
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.
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.11.10-rc.2';
5
+ export const VERSION = '4.11.10-rc.4';
6
6
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "moflo",
3
- "version": "4.11.10-rc.2",
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.1",
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"