moflo 4.11.9 → 4.11.10-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.claude/guidance/shipped/moflo-core-guidance.md +1 -0
  2. package/.claude/guidance/shipped/moflo-epic-processing.md +1 -0
  3. package/.claude/guidance/shipped/moflo-sdd.md +97 -0
  4. package/.claude/guidance/shipped/moflo-session-continuity.md +71 -0
  5. package/.claude/helpers/gate.cjs +232 -16
  6. package/.claude/skills/commune/SKILL.md +6 -1
  7. package/.claude/skills/fl/SKILL.md +101 -8
  8. package/.claude/skills/fl/execution-modes.md +2 -0
  9. package/.claude/skills/fl/phases.md +49 -2
  10. package/.claude/skills/fl/sdd.md +71 -0
  11. package/.claude/skills/verify/SKILL.md +94 -0
  12. package/README.md +61 -13
  13. package/bin/gate.cjs +232 -16
  14. package/bin/index-guidance.mjs +55 -1
  15. package/bin/lib/yaml-upgrader.mjs +93 -1
  16. package/dist/src/cli/commands/doctor-checks-config.js +52 -0
  17. package/dist/src/cli/commands/doctor-checks-deep.js +3 -0
  18. package/dist/src/cli/commands/doctor-checks-sdd.js +191 -0
  19. package/dist/src/cli/commands/doctor-fixes.js +100 -0
  20. package/dist/src/cli/commands/doctor-registry.js +13 -1
  21. package/dist/src/cli/commands/index.js +2 -0
  22. package/dist/src/cli/commands/sdd.js +338 -0
  23. package/dist/src/cli/config/moflo-config.js +46 -0
  24. package/dist/src/cli/init/executor.js +1 -0
  25. package/dist/src/cli/init/helpers-generator.js +172 -16
  26. package/dist/src/cli/init/moflo-yaml-template.js +15 -0
  27. package/dist/src/cli/init/settings-generator.js +20 -3
  28. package/dist/src/cli/mcp-tools/agent-tools.js +11 -1
  29. package/dist/src/cli/mcp-tools/hooks-tools.js +1 -0
  30. package/dist/src/cli/movector/model-router.js +38 -0
  31. package/dist/src/cli/parser.js +14 -2
  32. package/dist/src/cli/sdd/artifacts.js +298 -0
  33. package/dist/src/cli/sdd/index.js +9 -0
  34. package/dist/src/cli/sdd/templates.js +59 -0
  35. package/dist/src/cli/services/claude-stats.js +103 -20
  36. package/dist/src/cli/services/claudemd-injection.js +27 -15
  37. package/dist/src/cli/services/daemon-dashboard.js +25 -7
  38. package/dist/src/cli/services/hook-block-hash.js +4 -2
  39. package/dist/src/cli/services/hook-wiring.js +10 -0
  40. package/dist/src/cli/services/spell-gate.js +41 -2
  41. package/dist/src/cli/spells/core/runner.js +2 -0
  42. package/dist/src/cli/version.js +1 -1
  43. package/package.json +4 -3
@@ -218,6 +218,7 @@ See `.claude/guidance/moflo-memory-strategy.md` for memory-specific troubleshoot
218
218
  ## See Also
219
219
 
220
220
  - `.claude/guidance/moflo-skills-reference.md` — Slash-command skills catalog (`/flo`, `/commune`, `/meditate`, `/divine`, …) plus the automatic features (auto-meditate, session-continuity)
221
+ - `.claude/guidance/moflo-sdd.md` — Spec-Driven Development (`/flo -sd`/`-v`, `flo sdd`, verify-before-done gate, `sdd.default` / `gates.verify_before_done`)
221
222
  - `.claude/guidance/moflo-cli-reference.md` — CLI commands, agents, hooks, hive-mind, RuVector
222
223
  - `.claude/guidance/moflo-yaml-reference.md` — `moflo.yaml` schema, environment variables, `.mcp.json` setup
223
224
  - `.claude/guidance/moflo-subagents.md` — Subagents memory-first protocol and store patterns
@@ -57,6 +57,7 @@ The strategy is resolved with this precedence (highest first):
57
57
 
58
58
  ## See Also
59
59
 
60
+ - `.claude/guidance/moflo-session-continuity.md` — How `epic-state` + git reconciliation resume works vs. native Claude Code checkpoints (`/rewind`, agent-tree)
60
61
  - `src/cli/commands/epic.ts` — Epic command implementation
61
62
  - `src/cli/config/moflo-config.ts` — `MofloConfig.epic` interface and defaults
62
63
  - `.claude/skills/fl/SKILL.md` — `/flo` skill epic handling section
@@ -0,0 +1,97 @@
1
+ # MoFlo Spec-Driven Development (SDD) & Verify-Before-Done
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`/`--no-verify`, editing `.moflo/specs/`, or tuning `sdd.default` / `gates.verify_before_done`.
4
+
5
+ ---
6
+
7
+ ## When to Use `-sd` vs `-v` vs Neither
8
+
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
+
11
+ | Situation | Flag | Effect |
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`. 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
+
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
+
19
+ ---
20
+
21
+ ## The Artifact Convention
22
+
23
+ Specs and plans persist as Markdown, one directory per unit of work, under the configured specs directory (default `.moflo/specs`):
24
+
25
+ ```
26
+ <specs_dir>/<slug>/spec.md # the "what" + acceptance criteria
27
+ <specs_dir>/<slug>/plan.md # the "steps" + how each criterion is verified
28
+ ```
29
+
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.
40
+
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.
42
+
43
+ ---
44
+
45
+ ## Driving the Cycle with `flo sdd`
46
+
47
+ The `flo sdd` CLI is the single cross-platform entry point for every SDD artifact operation — scaffold, review, gate, and status. Drive the cycle through these commands; never hand-write a `.moflo/specs/...` path in a skill step.
48
+
49
+ | Command | Does |
50
+ |---------|------|
51
+ | `flo sdd spec "<title>"` | Scaffold `spec.md` (or show an existing one). `--from <file\|->` seeds the body. |
52
+ | `flo sdd review <slug>` | Mark the spec reviewed — unlocks the plan. |
53
+ | `flo sdd plan <slug>` | Scaffold `plan.md`; **requires the spec be reviewed**. |
54
+ | `flo sdd review <slug> plan` | Mark the plan reviewed — unlocks implementation. |
55
+ | `flo sdd check <slug> implement` | Review-checkpoint gate — exits non-zero until the plan is reviewed. |
56
+ | `flo sdd list` / `flo sdd status <slug>` | Enumerate specs / show one unit's spec+plan status. |
57
+
58
+ The two review checkpoints are the point: **a spec must be reviewed before its plan; a plan must be reviewed before implementation.** Pass `--force` only to deliberately skip a checkpoint.
59
+
60
+ ---
61
+
62
+ ## Verify-Before-Done Gate
63
+
64
+ When enforced, `gh pr create` is blocked until the change has been verified end-to-end since the last code edit.
65
+
66
+ - **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.
67
+ - **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).
68
+ - **A source edit invalidates a prior verification** — re-run `/verify` after editing. `/ward` and `/quicken` are targeted audits, not the completion gate.
69
+
70
+ ---
71
+
72
+ ## SDD & Verify moflo.yaml Defaults
73
+
74
+ `sdd.default` is off (opt-in); `verify_before_done` is **on** (#1294). Override per run with the flags.
75
+
76
+ ```yaml
77
+ sdd:
78
+ default: false # true → every /flo run uses the SDD cycle unless --no-sdd
79
+ gates:
80
+ verify_before_done: true # on by default (#1294); false → skip /verify unless -v. Per-run: --no-verify
81
+ ```
82
+
83
+ 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.
84
+
85
+ ---
86
+
87
+ ## From a Fuzzy Idea: `/commune` → Spec
88
+
89
+ When the unit of work is still fuzzy, start with `/commune`. It runs a short Socratic dialogue and synthesizes a spec, then can hand that spec straight into the SDD spine (`flo sdd spec "<title>" --from -`). Rename its **Success criteria** section to **`## Acceptance Criteria`** on the way in — the SDD validator requires it. `/commune` is the pre-execution counterpart to `/meditate`.
90
+
91
+ ---
92
+
93
+ ## See Also
94
+
95
+ - `.claude/skills/fl/sdd.md` — the `/flo` companion that drives `-sd`/`-v` end to end
96
+ - `.claude/guidance/moflo-core-guidance.md` — CLI, hooks, gates, and config hub
97
+ - `.claude/guidance/moflo-yaml-reference.md` — full `moflo.yaml` field reference, including `sdd` and `gates`
@@ -0,0 +1,71 @@
1
+ # Native Checkpoints vs. MoFlo Continuity — Who Owns Undo and Resume
2
+
3
+ **Purpose:** Decide which layer owns a given "undo" or "resume" — Claude Code's native checkpoints (`/rewind`, agent-tree checkpointing) or moflo's session-continuity + epic-state — so the two coexist by layering, never by competing. Read this before wiring any resume/rollback behavior into a moflo workflow.
4
+
5
+ ---
6
+
7
+ ## Ownership Decision Table
8
+
9
+ The two systems solve different problems. Never route a job to the wrong layer or build a moflo mechanism that duplicates a native one.
10
+
11
+ | You want to… | Owner | Mechanism |
12
+ |--------------|-------|-----------|
13
+ | Undo code edits from earlier THIS session | Native | `/rewind` code checkpoints |
14
+ | Pause/resume a multi-hour agent tree WITHIN a session | Native | Agent-tree checkpointing (beta) |
15
+ | Pick up context ACROSS sessions ("where you left off") | MoFlo | Session-continuity digest |
16
+ | Resume an interrupted `flo epic` from real progress | MoFlo | `epic-state` reconciled against git + the GitHub checklist |
17
+ | Fail over to another model when a tier is unavailable | Native | `fallbackModel` — moflo's router emits the chain |
18
+
19
+ **Rule:** native checkpoints govern *in-session* rollback of code and agent trees; moflo governs *cross-session* coordination and the durable learning record. They layer; they do not overlap.
20
+
21
+ ---
22
+
23
+ ## Native Checkpoints Are Runtime-Only — Never Couple MoFlo to Them
24
+
25
+ Native `/rewind`, code checkpoints, and agent-tree checkpoint state live entirely inside the Claude Code runtime. There is **no stable public file or API** for them, and moflo library code (the `flo` CLI, MCP server, hooks, launcher, daemon) is never handed a reference to that state.
26
+
27
+ **Never read or write native checkpoint state from moflo code.** Coupling to an undocumented runtime format violates the cross-platform rule and the consumer-blast-radius rule — it would break silently on any Claude Code update, in every consumer install. Treat native checkpoints as a black box the human drives; moflo reacts to committed results (git, GitHub), not to session snapshots.
28
+
29
+ ---
30
+
31
+ ## MoFlo Session-Continuity — The Cross-Session Digest
32
+
33
+ Session-continuity is moflo's narrative bridge between sessions, distinct from native checkpoints. At session end it captures a scrubbed digest — `{goal, decisions, gitState}` — to a rotated JSON store under `.moflo/`. At session start it scores digests by branch match, changed-file overlap, and recency, then injects the best one as a **"Where you left off"** lead.
34
+
35
+ **Treat the injected digest as a verifiable lead, not ground truth.** It reflects what was true when it was written — re-check the current branch, working tree, and files before acting on it. Configure via `session_continuity: {capture, inject, max_age_hours}` in `moflo.yaml`; both flags default on.
36
+
37
+ ---
38
+
39
+ ## Epic Resume Is Checkpoint-Agnostic — Git Is the Source of Truth
40
+
41
+ `flo epic` records progress in the `epic-state` memory namespace, but that namespace is an **in-session cache, hard-deleted on every session start** — never rely on it as the durable resume store. The durable sources of truth are the **git epic branch + commits** and the **GitHub issue checklist** (`[ ]` vs `[x]`).
42
+
43
+ On resume, `epic.ts` reconciles memory against git: it recomputes the `epic/{n}-{slug}` branch and, if the branch is missing or has no commits ahead of the base, discards the memory record and starts fresh. This makes epic resume **independent of native checkpoints** — it works identically whether or not the user used `/rewind`, because truth is re-derived from committed state, not a session snapshot. Do not add native-checkpoint detection to epic resume; it would be unverifiable noise.
44
+
45
+ ---
46
+
47
+ ## Swarm and Hive-Mind Are a Logical Layer Native Nesting Does Not Replace
48
+
49
+ MoFlo's `UnifiedSwarmCoordinator` is a **logical coordination layer** — an agent registry plus MessageBus routing, consensus voting, and workload-balanced task distribution. "Spawn" registers a tracked record; it never launches a process. Hive-mind is one level deep (queen → flat worker list; workers never spawn sub-workers).
50
+
51
+ Native 3-level nested subagents solve **process and context isolation for real, executing agents** — an orthogonal concern. Native nesting does not replace the registry, MessageBus, consensus, or task distribution.
52
+
53
+ **Never rewire swarm/hive-mind onto native nesting.** Reducing coordinator handlers to stubs to "map onto native primitives" is exactly the disconnection the protected-functionality rule forbids, for zero functional gain. The only seam between the two — the subagent bootstrap directive — is already carried in every spawn's metadata and response. Keep every handler wired to the coordinator.
54
+
55
+ ---
56
+
57
+ ## Model Routing Emits Native `fallbackModel` Chains
58
+
59
+ MoFlo's model router already tracks per-tier circuit-breaker state. Its routing result carries a `fallbackModel` chain: an ordered, circuit-breaker-aware failover list that excludes the primary, drops `inherit` and zero-score tiers, and demotes any open-circuit tier to the tail.
60
+
61
+ The chain is **advisory** — surfaced to the orchestrator via `hooks_model-route` and `agent_spawn`, then applied when spawning a real subagent. MoFlo never auto-launches a model itself; the orchestrator (or the native `Task` spawn) consumes the chain and maps it onto Claude Code's native `fallbackModel`. A healthy lower tier is always tried before a failing one.
62
+
63
+ ---
64
+
65
+ ## See Also
66
+
67
+ - `.claude/guidance/moflo-epic-processing.md` — Epic orchestration whose `epic-state` + git reconciliation this doc frames against native checkpoints
68
+ - `.claude/guidance/moflo-claude-swarm-cohesion.md` — Swarm/hive coordination model the "logical layer" note protects
69
+ - `.claude/guidance/moflo-core-guidance.md` — CLI, hooks, swarm, memory, and config hub
70
+ - `src/cli/movector/model-router.ts` — Router that emits the `fallbackModel` chain
71
+ - `src/cli/commands/epic.ts` — Epic resume + git-branch reconciliation
@@ -3,11 +3,12 @@
3
3
  var fs = require('fs');
4
4
  var path = require('path');
5
5
  var cp = require('child_process');
6
+ var os = require('os');
6
7
 
7
8
  var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
8
9
  var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
9
10
 
10
- var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, simplifySnapshotSha: null, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {}, flMode: null, swarmInitialized: false, hiveInitialized: false };
11
+ var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, simplifySnapshotSha: null, verifyRun: false, verifyOutcome: null, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {}, flMode: null, swarmInitialized: false, hiveInitialized: false, sddMode: false, activeSddSlug: null };
11
12
 
12
13
  // Per-actor memory-search tracking (#838). The legacy `memorySearched` boolean
13
14
  // is session-wide, so once the parent searches memory, every spawned subagent
@@ -60,27 +61,86 @@ function writeState(s) {
60
61
 
61
62
  // Load moflo.yaml gate config (defaults: all enabled)
62
63
  function loadGateConfig() {
63
- 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 };
64
- try {
65
- var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
66
- if (fs.existsSync(yamlPath)) {
67
- var content = fs.readFileSync(yamlPath, 'utf-8');
68
- if (/memory_first:\s*false/i.test(content)) defaults.memory_first = false;
69
- if (/task_create_first:\s*false/i.test(content)) defaults.task_create_first = false;
70
- if (/context_tracking:\s*false/i.test(content)) defaults.context_tracking = false;
71
- if (/testing_gate:\s*false/i.test(content)) defaults.testing_gate = false;
72
- if (/simplify_gate:\s*false/i.test(content)) defaults.simplify_gate = false;
73
- if (/learnings_gate:\s*false/i.test(content)) defaults.learnings_gate = false;
74
- if (/swarm_invocation_gate:\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
75
- }
76
- } catch (e) { /* use defaults */ }
64
+ // verify_before_done is opt-OUT (default true), like every other gate: #1294
65
+ // ships a real /verify skill and has /flo delegate to it, so leaving it off by
66
+ // default would make the default /flo run silently skip the acceptance check.
67
+ // Disable per-project with `verify_before_done: false` or per-run `--no-verify`.
68
+ 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, sdd_gate: true };
69
+ var content = MOFLO_YAML;
70
+ if (content) {
71
+ if (/memory_first:\s*false/i.test(content)) defaults.memory_first = false;
72
+ if (/task_create_first:\s*false/i.test(content)) defaults.task_create_first = false;
73
+ if (/context_tracking:\s*false/i.test(content)) defaults.context_tracking = false;
74
+ if (/testing_gate:\s*false/i.test(content)) defaults.testing_gate = false;
75
+ if (/simplify_gate:\s*false/i.test(content)) defaults.simplify_gate = false;
76
+ if (/learnings_gate:\s*false/i.test(content)) defaults.learnings_gate = false;
77
+ if (/swarm_invocation_gate:\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
78
+ // Opt-out: on by default; disable only when explicitly set false.
79
+ if (/verify_before_done:\s*false/i.test(content)) defaults.verify_before_done = false;
80
+ // sdd_gate is the check-before-implement backstop (#1297). Opt-out; the
81
+ // gate only fires when a run is actually armed for SDD (sddMode), so
82
+ // leaving it on costs non-SDD work nothing.
83
+ if (/sdd_gate:\s*false/i.test(content)) defaults.sdd_gate = false;
84
+ }
77
85
  return defaults;
78
86
  }
79
87
 
88
+ // Parse the top-level `sdd:` block from moflo.yaml (#1297). Scoped to the block
89
+ // body so we never match a `default:` key from another section (epic, merge).
90
+ // Cross-platform: tolerates CRLF; no path separators hardcoded.
91
+ function loadSddConfig() {
92
+ var out = { default: false, specsDir: '.moflo/specs' };
93
+ var content = MOFLO_YAML;
94
+ if (!content) return out;
95
+ var block = content.match(/^sdd:[ \t]*\r?\n((?:[ \t]+.*(?:\r?\n|$))*)/m);
96
+ if (!block) return out;
97
+ var body = block[1];
98
+ if (/^\s*default:\s*true\b/im.test(body)) out.default = true;
99
+ var sd = body.match(/^\s*specs_dir:\s*(.+?)\s*$/im);
100
+ if (sd) {
101
+ var v = sd[1].replace(/\s+#.*$/, '').replace(/^["']|["']$/g, '').trim();
102
+ if (v) out.specsDir = v;
103
+ }
104
+ return out;
105
+ }
106
+
107
+ // Read moflo.yaml exactly once per gate process (#1297 review): loadGateConfig
108
+ // and loadSddConfig both parse it, and the gate fires on every Write/Edit — a
109
+ // second read is wasted syscalls. Single readFileSync in try/catch (no existsSync
110
+ // double-stat); ENOENT/unreadable → '' → every parser falls back to defaults.
111
+ function readMofloYaml() {
112
+ try { return fs.readFileSync(path.join(PROJECT_DIR, 'moflo.yaml'), 'utf-8'); }
113
+ catch (e) { return ''; }
114
+ }
115
+ var MOFLO_YAML = readMofloYaml();
116
+
80
117
  var config = loadGateConfig();
118
+ var sddConf = loadSddConfig();
81
119
  var command = process.argv[2];
82
120
 
83
121
  var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules', 'moflo.yaml'];
122
+
123
+ // #1294 Finding 3 — reads/scans of EPHEMERAL files under the OS temp dir
124
+ // (background-task output/transcripts, agent scratchpads) are transient tool
125
+ // I/O and never carry indexable project knowledge, so they must not trip the
126
+ // memory-first gate. Cross-platform (Rule #1): os.tmpdir() is correct on every
127
+ // OS; we normalize a leading `/private` on both sides so macOS's
128
+ // /var/folders (os.tmpdir) vs /private/var/folders (realpath) symlink pair
129
+ // still matches (CLAUDE.md #1145). Never hardcode `/tmp`.
130
+ function stripPrivate(p) { return p.indexOf('/private/') === 0 ? p.slice('/private'.length) : p; }
131
+ function isEphemeralPath(fp) {
132
+ if (!fp) return false;
133
+ var tmp;
134
+ try { tmp = path.resolve(os.tmpdir()); } catch (e) { return false; }
135
+ var t = stripPrivate(tmp);
136
+ function under(p) { var n = stripPrivate(p); return n === t || n.indexOf(t + path.sep) === 0; }
137
+ var resolved = path.resolve(fp);
138
+ if (!under(resolved)) return false;
139
+ // Under tmp by literal path — confirm it isn't a symlink staged in tmp that
140
+ // dereferences to a real project file (realpath BOTH sides, CLAUDE.md Rule #2).
141
+ // On ENOENT (a not-yet-created tmp file) keep the verdict — still ephemeral.
142
+ try { return under(fs.realpathSync(resolved)); } catch (e) { return true; }
143
+ }
84
144
  // #1171 — DANGEROUS gained PowerShell additions to match the matcher widening
85
145
  // that now routes the dedicated `PowerShell` tool through check-dangerous-command.
86
146
  // POSIX entries still apply because PS will execute them when invoked. Substring
@@ -228,6 +288,59 @@ function detectFlMode(promptText) {
228
288
  return null;
229
289
  }
230
290
 
291
+ // #1297 — arm the SDD implement gate from the user prompt. Only /fl or /flo runs
292
+ // can arm it. Explicit -sd/--sdd wins; --no-sdd disarms; otherwise honor the
293
+ // sdd.default config. `-sd` is a distinct token from `-s` (swarm): the `d` sits
294
+ // on the word boundary so `-s\b` never matches `-sd`.
295
+ function detectSddMode(promptText) {
296
+ var p = promptText || '';
297
+ if (!/^\s*\/(?:fl|flo)\b/i.test(p)) return false;
298
+ if (/(?:^|\s)--no-sdd\b/.test(p)) return false;
299
+ if (/(?:^|\s)(?:-sd|--sdd)\b/.test(p)) return true;
300
+ return !!sddConf.default;
301
+ }
302
+
303
+ // Resolve the absolute specs root the same way TS specsRoot does (#1294): split
304
+ // the /-written config value on either separator, reject absolute/`..`-escaping
305
+ // values, and fall back to the gitignored default. Rule #1: no separator hardcoded.
306
+ function sddSpecsRootAbs() {
307
+ var configured = (sddConf.specsDir || '.moflo/specs');
308
+ var segments = configured.split(/[\\/]+/).filter(Boolean);
309
+ var escapes = segments.length === 0
310
+ || segments.indexOf('..') >= 0
311
+ || /^([a-zA-Z]:|~)$/.test(segments[0])
312
+ || configured.charAt(0) === '/'
313
+ || configured.charAt(0) === '\\';
314
+ if (escapes) return path.join(PROJECT_DIR, '.moflo', 'specs');
315
+ return path.join.apply(path, [PROJECT_DIR].concat(segments));
316
+ }
317
+
318
+ // Is the edited path inside the specs dir? Editing spec.md/plan.md themselves must
319
+ // never trip the implement gate. Compares resolved absolute paths (Rule #1: the
320
+ // edit path may be relative or absolute; normalize both before the prefix test).
321
+ function isInsideSpecsDir(filePath) {
322
+ try {
323
+ var root = sddSpecsRootAbs();
324
+ var abs = path.isAbsolute(filePath) ? filePath : path.resolve(PROJECT_DIR, filePath);
325
+ var rel = path.relative(root, abs);
326
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
327
+ } catch (e) { return false; }
328
+ }
329
+
330
+ // Read plan.md frontmatter for the active slug and report whether it is reviewed.
331
+ // Pure fs + regex (no spawn) so it stays cheap on every Write/Edit. Matches the
332
+ // double-quoted scalar serializeArtifact emits (`status: "reviewed"`).
333
+ function isPlanReviewed(slug) {
334
+ try {
335
+ var planPath = path.join(sddSpecsRootAbs(), slug, 'plan.md');
336
+ if (!fs.existsSync(planPath)) return false;
337
+ var content = fs.readFileSync(planPath, 'utf-8').replace(/\r\n/g, '\n');
338
+ var fm = content.match(/^---\n([\s\S]*?)\n---/);
339
+ if (!fm) return false;
340
+ return /^\s*status:\s*["']?reviewed["']?\s*$/im.test(fm[1]);
341
+ } catch (e) { return false; }
342
+ }
343
+
231
344
  function classifyNamespaceHint(promptText) {
232
345
  var lower = (promptText || '').toLowerCase();
233
346
  if (NS_TEST_RE.test(lower)) return 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
@@ -300,6 +413,10 @@ function applyPromptStateReset(state, promptText) {
300
413
  state.flMode = detectFlMode(promptText);
301
414
  state.swarmInitialized = false;
302
415
  state.hiveInitialized = false;
416
+ // #1297 — arm/disarm the SDD implement gate per prompt. A fresh /flo run starts
417
+ // with no active slug; `flo sdd spec` stamps activeSddSlug during the run.
418
+ state.sddMode = detectSddMode(promptText);
419
+ state.activeSddSlug = null;
303
420
  }
304
421
  // Match npm/yarn/pnpm/bun test, npx vitest|jest|..., bare runners at command-start only,
305
422
  // and language-native test commands. The bare-runner arm is anchored so that
@@ -525,6 +642,7 @@ switch (command) {
525
642
  var s = readState();
526
643
  if (!s.memoryRequired || isMemorySearchedFor(s)) break;
527
644
  var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
645
+ if (isEphemeralPath(process.env.TOOL_INPUT_path)) break;
528
646
  if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
529
647
  process.stderr.write('BLOCKED: Search memory before exploring files. Use mcp__moflo__memory_search. On chunk hits, traverse via mcp__moflo__memory_get_neighbors — see .claude/guidance/moflo-memory-protocol.md\n');
530
648
  process.exit(2);
@@ -534,6 +652,9 @@ switch (command) {
534
652
  var s = readState();
535
653
  if (!s.memoryRequired || isMemorySearchedFor(s)) break;
536
654
  var fp = process.env.TOOL_INPUT_file_path || '';
655
+ // Ephemeral tmp/scratch reads are exempt even when they look like guidance
656
+ // (a temp copy is still transient tool I/O, not the indexed source).
657
+ if (isEphemeralPath(fp)) break;
537
658
  var isGuidance = fp.indexOf('.claude/guidance/') >= 0 || fp.indexOf('.claude\\guidance\\') >= 0;
538
659
  if (!isGuidance && EXEMPT.some(function(p) { return fp.indexOf(p) >= 0; })) break;
539
660
  process.stderr.write('BLOCKED: Search memory before reading files. Use mcp__moflo__memory_search. On chunk hits, traverse via mcp__moflo__memory_get_neighbors — see .claude/guidance/moflo-memory-protocol.md\n');
@@ -654,6 +775,25 @@ switch (command) {
654
775
  }
655
776
  break;
656
777
  }
778
+ case 'record-verify-run': {
779
+ // Story #1274 (Epic #1269). Fires PostToolUse on ^Skill$ when the native
780
+ // /verify skill runs, satisfying the verify-before-done gate. Mirrors
781
+ // record-skill-run's fault-tolerant shape. The verification OUTCOME (what
782
+ // was checked, pass/fail) is written to memory by the verify flow itself
783
+ // via mcp__moflo__memory_store — same division of labour as testsRun vs the
784
+ // test output; this recorder only tracks that verification happened.
785
+ var vName = (process.env.TOOL_INPUT_skill || '');
786
+ // Only the native /verify skill satisfies verify-before-done. /ward and
787
+ // /quicken are targeted audits, NOT the completion gate (see fl/sdd.md) —
788
+ // crediting them would let the gate pass without an end-to-end verify.
789
+ if (vName === 'verify') {
790
+ var s = readState();
791
+ if (!s.verifyRun) { s.verifyRun = true; writeState(s); }
792
+ } else if (vName) {
793
+ process.stderr.write('gate: record-verify-run no-op — TOOL_INPUT_skill="' + vName + '" is not verify\n');
794
+ }
795
+ break;
796
+ }
657
797
  case 'reset-edit-gates': {
658
798
  var fp = process.env.TOOL_INPUT_file_path || '';
659
799
  // Inert files (markdown, lockfiles, CHANGELOG, .env.example) AND inert paths
@@ -664,10 +804,14 @@ switch (command) {
664
804
  // Test-only edits invalidate testsRun but preserve simplifyRun (#908).
665
805
  var isTestOnly = fp && EDIT_RESET_SKIP_SIMPLIFY_ONLY_RE.test(fp);
666
806
  var resetTests = s.testsRun;
807
+ // A code edit invalidates a prior verification (Story #1274) — same as tests,
808
+ // including test-only edits (the criteria being verified may have moved).
809
+ var resetVerify = s.verifyRun;
667
810
  var resetSimplify = s.simplifyRun && !isTestOnly;
668
- if (!resetTests && !resetSimplify) break;
811
+ if (!resetTests && !resetSimplify && !resetVerify) break;
669
812
  var gates = [];
670
813
  if (resetTests) { s.testsRun = false; gates.push('tests'); }
814
+ if (resetVerify) { s.verifyRun = false; gates.push('verify'); }
671
815
  if (resetSimplify) { s.simplifyRun = false; gates.push('simplify'); }
672
816
  if (fp) {
673
817
  s.lastResetBy = { file: fp, at: new Date().toISOString(), gates: gates };
@@ -675,6 +819,44 @@ switch (command) {
675
819
  writeState(s);
676
820
  break;
677
821
  }
822
+ case 'check-before-implement': {
823
+ // #1297 — the SDD front-half backstop. When a run is armed for SDD
824
+ // (sddMode, set from -sd/--sdd or sdd.default on a /flo run), block source
825
+ // Write/Edit until a spec exists and its plan is reviewed. Mirrors the
826
+ // memory_first gate shape. Disarmed runs (the default for non-SDD work)
827
+ // pass instantly. Opt out per-project with `gates: sdd_gate: false`.
828
+ if (!config.sdd_gate) break;
829
+ var si = readState();
830
+ if (!si.sddMode) break; // not an SDD run — no enforcement
831
+ var fpi = process.env.TOOL_INPUT_file_path || '';
832
+ if (!fpi) break;
833
+ // Only gate real source edits. Exempt the same inert files/paths the other
834
+ // gates skip, plus the spec/plan artifacts themselves.
835
+ if (EXEMPT.some(function (e) { return fpi.indexOf(e) >= 0; })) break;
836
+ if (!SOURCE_FILE_RE.test(fpi)) break;
837
+ if (EDIT_RESET_SKIP_PATH_RE.test(fpi)) break;
838
+ if (isInsideSpecsDir(fpi)) break;
839
+ if (!si.activeSddSlug) {
840
+ process.stderr.write(
841
+ 'BLOCKED: SDD mode is on — author a spec before editing source.\n' +
842
+ 'Run: flo sdd spec "<title>" (then review it, and plan)\n' +
843
+ 'This run is spec-gated (-sd / sdd.default). One-off skip: re-run with --no-sdd.\n' +
844
+ 'Disable per-project via moflo.yaml: gates: sdd_gate: false\n'
845
+ );
846
+ process.exit(2);
847
+ }
848
+ if (!isPlanReviewed(si.activeSddSlug)) {
849
+ process.stderr.write(
850
+ 'BLOCKED: SDD — the plan for "' + si.activeSddSlug + '" is not reviewed yet.\n' +
851
+ 'Author + review the plan first:\n' +
852
+ ' flo sdd plan ' + si.activeSddSlug + '\n' +
853
+ ' flo sdd review ' + si.activeSddSlug + ' plan\n' +
854
+ 'One-off skip: re-run with --no-sdd. Disable via moflo.yaml: gates: sdd_gate: false\n'
855
+ );
856
+ process.exit(2);
857
+ }
858
+ break;
859
+ }
678
860
  case 'check-before-pr': {
679
861
  // Anchored to command-start (or chained via && / || / ;) so heredoc bodies
680
862
  // and quoted strings that contain the literal "gh pr create" don't trip
@@ -734,6 +916,40 @@ switch (command) {
734
916
  process.stderr.write(' gates:\n testing_gate: false\n simplify_gate: false\n learnings_gate: false\n');
735
917
  process.exit(2);
736
918
  }
919
+ case 'check-before-done': {
920
+ // Story #1274 (Epic #1269) + #1294. Verify-before-done: block `gh pr create`
921
+ // until the change has been verified end-to-end (the /verify skill) against
922
+ // the plan's acceptance criteria. ON by default (#1294) — /flo delegates to
923
+ // /verify, so a default run does the acceptance check; disable per-project
924
+ // with `gates: verify_before_done: false` or per-run `--no-verify`. Same
925
+ // trigger + no-source exemption as check-before-pr, so they compose on one
926
+ // command (docs-only diffs are exempt, so this never blocks a docs PR).
927
+ if (!config.verify_before_done) break;
928
+ var cmd = process.env.TOOL_INPUT_command || '';
929
+ if (!/(?:^|&&\s*|\|\|\s*|;\s*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*gh\s+pr\s+create\b/.test(cmd)) break;
930
+ // No-source-files exemption — a docs-only / path-inert diff needs no verify.
931
+ var changedD = getChangedFilesVsBase();
932
+ if (changedD && changedD.length > 0) {
933
+ var hasSourceD = changedD.some(function(f) {
934
+ return SOURCE_FILE_RE.test(f) && !EDIT_RESET_SKIP_PATH_RE.test(f);
935
+ });
936
+ if (!hasSourceD) {
937
+ var reasonD = changedD.every(function(f) { return DOCS_ONLY_RE.test(f); }) ? 'Docs-only' : 'No source files in branch diff';
938
+ process.stdout.write(reasonD + ' (' + changedD.length + ' file' + (changedD.length === 1 ? '' : 's') + ') — skipping verify-before-done gate.\n');
939
+ break;
940
+ }
941
+ }
942
+ var sd = readState();
943
+ if (sd.verifyRun) break;
944
+ process.stderr.write('BLOCKED: gh pr create requires verification before done:\n');
945
+ process.stderr.write(' - the change has not been verified since the last code edit (run /verify)\n');
946
+ if (sd.lastResetBy && sd.lastResetBy.file && (sd.lastResetBy.gates || []).indexOf('verify') >= 0) {
947
+ process.stderr.write('Last gate reset: ' + sd.lastResetBy.file + ' (verify)\n');
948
+ }
949
+ process.stderr.write('Disable via moflo.yaml:\n');
950
+ process.stderr.write(' gates:\n verify_before_done: false\n');
951
+ process.exit(2);
952
+ }
737
953
  case 'check-dangerous-command': {
738
954
  // #1171 follow-up — strip quoted string bodies and heredoc bodies before
739
955
  // substring-matching DANGEROUS. Without this, `git commit -m "...remove-item
@@ -119,11 +119,16 @@ The whole point is to feed moflo's existing strengths, not start a parallel trac
119
119
  | Destination | When | How |
120
120
  |-------------|------|-----|
121
121
  | **`/flo` ticket** (Recommended) | The spec is a unit of work to build | Map the spec to Description / Acceptance Criteria / Suggested Test Cases, then run `/fl -t <title>` to create the GitHub issue (or `/fl <issue#>` to implement immediately). The spec's Success criteria become Acceptance Criteria; Scope+Approach become the Description. |
122
+ | **SDD spec artifact** | The user wants the spec to drive an `/flo --sdd` run (the spec→plan→implement→verify cycle) | Pipe the synthesized markdown into the SDD spine: `flo sdd spec "<title>" --from -` (writes `.moflo/specs/<slug>/spec.md`, git-tracked + memory-indexed). Rename the spec's **Success criteria** section to **`## Acceptance Criteria`** first — the SDD validator requires it. Then `flo sdd review <slug>` when signed off, and hand to `/fl --sdd <issue#>` (or `/fl -sd <issue#>`). This is the pre-execution counterpart to `/meditate`. |
122
123
  | **Spell** | The spec describes a repeatable, automatable pipeline | Hand the spec to `/spell-builder` as the design input. |
123
124
  | **Memory** | The spec is a decision/insight to retain, not build now | `mcp__moflo__memory_store { namespace: "learnings", key: "spec:<topic>", value: <spec> }` (use `patterns` for a reusable approach). |
124
125
  | **Just the file** | The user wants the artifact only | `Write` the markdown to a path the user names, or to a repo-relative `docs/specs/<kebab-title>.md`. Never hardcode an absolute or OS-specific path (e.g. `/tmp`); build the path from the project root. |
125
126
 
126
- Offer to do more than one (e.g. save to memory **and** open a ticket). Default to the `/flo` ticket path when the user is unsure.
127
+ Offer to do more than one (e.g. save to memory **and** open a ticket). Default to the `/flo` ticket path when the user is unsure. For the **SDD spec** path, always create the artifact through the `flo sdd` CLI — never hand-write the `.moflo/specs/...` path (cross-platform, Rule #1).
128
+
129
+ ## See Also — SDD
130
+
131
+ `.claude/skills/fl/sdd.md` — how `/flo --sdd` consumes the spec artifact this skill produces and drives it through plan → implement → verify.
127
132
 
128
133
  ## Guardrails
129
134