moflo 4.12.0 → 4.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/base-template-generator.md +21 -1
- package/.claude/guidance/shipped/moflo-yaml-reference.md +40 -0
- package/.claude/helpers/gate.cjs +117 -13
- package/.claude/skills/distill/SKILL.md +1 -1
- package/.claude/skills/fl/SKILL.md +35 -14
- package/.claude/skills/fl/sdd.md +4 -2
- package/.claude/skills/perf-audit/SKILL.md +1 -1
- package/.claude/skills/test-gaps/SKILL.md +1 -1
- package/bin/gate.cjs +117 -13
- package/bin/lib/retired-files.mjs +125 -5
- package/bin/lib/skill-categories.mjs +158 -0
- package/bin/session-start-launcher.mjs +79 -3
- package/dist/src/cli/commands/init.js +32 -11
- package/dist/src/cli/commands/sdd.js +119 -1
- package/dist/src/cli/index.js +15 -0
- package/dist/src/cli/init/executor.js +11 -4
- package/dist/src/cli/init/helpers-generator.js +99 -10
- package/dist/src/cli/init/index.js +1 -1
- package/dist/src/cli/init/types.js +27 -6
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: base-template-generator
|
|
3
|
-
description:
|
|
3
|
+
description: Create foundational templates, boilerplate code, and starter configurations for new projects, components, or features. Use for scaffolding a new component, API endpoint, model, config, or test suite from scratch.
|
|
4
4
|
color: orange
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -19,6 +19,26 @@ On chunk hits where `navigation` is non-null, traverse via `mcp__moflo__memory_g
|
|
|
19
19
|
|
|
20
20
|
You are a Base Template Generator, an expert architect specializing in creating clean, well-structured foundational templates and boilerplate code. Your expertise lies in establishing solid starting points that follow industry best practices, maintain consistency, and provide clear extension paths.
|
|
21
21
|
|
|
22
|
+
## When you get dispatched
|
|
23
|
+
|
|
24
|
+
Worked examples of the requests that route here — these live in the body, not the
|
|
25
|
+
frontmatter `description`, because the description is always-resident routing
|
|
26
|
+
context while this body only loads on dispatch.
|
|
27
|
+
|
|
28
|
+
<example>
|
|
29
|
+
Context: User needs to start a new React component and wants a solid foundation.
|
|
30
|
+
user: 'I need to create a new user profile component'
|
|
31
|
+
assistant: 'I'll use the base-template-generator agent to create a comprehensive React component template with proper structure, TypeScript definitions, and styling setup.'
|
|
32
|
+
<commentary>Since the user needs a foundational template for a new component, use the base-template-generator agent to create a well-structured starting point.</commentary>
|
|
33
|
+
</example>
|
|
34
|
+
|
|
35
|
+
<example>
|
|
36
|
+
Context: User is setting up a new API endpoint and needs a template.
|
|
37
|
+
user: 'Can you help me set up a new REST API endpoint for user management?'
|
|
38
|
+
assistant: 'I'll use the base-template-generator agent to create a complete API endpoint template with proper error handling, validation, and documentation structure.'
|
|
39
|
+
<commentary>The user needs a foundational template for an API endpoint, so use the base-template-generator agent to provide a comprehensive starting point.</commentary>
|
|
40
|
+
</example>
|
|
41
|
+
|
|
22
42
|
Your core responsibilities:
|
|
23
43
|
- Generate comprehensive base templates for components, modules, APIs, configurations, and project structures
|
|
24
44
|
- Ensure all templates follow established coding standards and best practices from the project's CLAUDE.md guidelines
|
|
@@ -123,14 +123,54 @@ auto_update:
|
|
|
123
123
|
helpers: true # Sync .claude/helpers/ from moflo source
|
|
124
124
|
hook_block_drift: regenerate # warn | regenerate | off (default: regenerate since #1227)
|
|
125
125
|
claudemd_injection_drift: regenerate # warn | regenerate | off
|
|
126
|
+
|
|
127
|
+
# Which skill categories to install (OPTIONAL — omit to get every skill)
|
|
128
|
+
skills:
|
|
129
|
+
categories: [core, memory, spells] # core | memory | spells
|
|
126
130
|
```
|
|
127
131
|
|
|
128
132
|
If your `moflo.yaml` predates the `sandbox:` or `auto_update:` blocks, they are auto-appended on the next session start — you never need to re-run `moflo init` after a version bump.
|
|
129
133
|
|
|
134
|
+
### Narrowing Installed Skills with `skills.categories`
|
|
135
|
+
|
|
136
|
+
**Omit the `skills:` block entirely to receive every moflo skill — that is the default and the recommended setting.** Add `skills.categories` only when you want a smaller always-resident skill listing, and expect to lose the commands in the categories you drop.
|
|
137
|
+
|
|
138
|
+
Every installed skill's name and description sit in Claude's context in every session, so a project that will never author a spell can reclaim that space. The cost is real but small — the full set is roughly 2,000 tokens.
|
|
139
|
+
|
|
140
|
+
| Category | Skills | Drop it when |
|
|
141
|
+
|----------|--------|--------------|
|
|
142
|
+
| `core` | `/fl`, `/healer`, `/verify`, `/flo-simplify`, `/eldar`, `/quicken`, `/ward`, `/divine`, `/meditate`, `/guidance`, `/commune`, `/luminarium` | Never — these are the everyday commands |
|
|
143
|
+
| `memory` | `/memory-patterns`, `/vector-search`, `/memory-optimization`, `/memory-team`, `/memory-worktree` | You are not building on moflo's memory stack and have finished any team/worktree sharing setup |
|
|
144
|
+
| `spells` | `/spell-builder`, `/spell-schedule`, `/connector-builder` | You do not author or schedule spells in this project |
|
|
145
|
+
|
|
146
|
+
Both YAML list styles work:
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
skills:
|
|
150
|
+
categories: [core, memory]
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
skills:
|
|
155
|
+
categories:
|
|
156
|
+
- core
|
|
157
|
+
- memory
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Key behaviors:**
|
|
161
|
+
|
|
162
|
+
- **Omitting `skills:` means no restriction.** Every shipped skill syncs on every session start, exactly as before this option existed.
|
|
163
|
+
- **`/flo` and `/fl` are never gated.** The ticket spell is the primary entry point and installs regardless of selection.
|
|
164
|
+
- **An unknown category name is ignored, not fatal.** A typo narrows nothing rather than silently stripping your skills.
|
|
165
|
+
- **Narrowing stops future syncing; it does not delete.** Skills already on disk stay until you remove them — moflo will not re-add them once excluded, so delete the directories under `.claude/skills/` to reclaim the context.
|
|
166
|
+
- **Re-widening is instant.** Add the category back and the next session start restores its skills.
|
|
167
|
+
|
|
130
168
|
### Key Behaviors
|
|
131
169
|
|
|
132
170
|
| Config | Effect |
|
|
133
171
|
|--------|--------|
|
|
172
|
+
| `skills.categories` omitted | Install every skill (default) |
|
|
173
|
+
| `skills.categories: [core]` | Install only core skills; `memory` + `spells` stop syncing |
|
|
134
174
|
| `auto_index.guidance: false` | Skip guidance indexing on session start |
|
|
135
175
|
| `auto_index.code_map: false` | Skip code map generation on session start |
|
|
136
176
|
| `auto_meditate.enabled: false` | Opt out of automatic session-lesson distillation (`/meditate` still works manually) |
|
package/.claude/helpers/gate.cjs
CHANGED
|
@@ -104,6 +104,19 @@ function loadSddConfig() {
|
|
|
104
104
|
return out;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Parse the top-level `merge:` block from moflo.yaml (#1285). Block-scoped for
|
|
108
|
+
// the same reason as loadSddConfig: a bare `auto:` key can appear under other
|
|
109
|
+
// sections. Cross-platform: tolerates CRLF.
|
|
110
|
+
function loadMergeConfig() {
|
|
111
|
+
var out = { auto: false };
|
|
112
|
+
var content = MOFLO_YAML;
|
|
113
|
+
if (!content) return out;
|
|
114
|
+
var block = content.match(/^merge:[ \t]*\r?\n((?:[ \t]+.*(?:\r?\n|$))*)/m);
|
|
115
|
+
if (!block) return out;
|
|
116
|
+
if (/^\s*auto:\s*true\b/im.test(block[1])) out.auto = true;
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
// Read moflo.yaml exactly once per gate process (#1297 review): loadGateConfig
|
|
108
121
|
// and loadSddConfig both parse it, and the gate fires on every Write/Edit — a
|
|
109
122
|
// second read is wasted syscalls. Single readFileSync in try/catch (no existsSync
|
|
@@ -116,10 +129,18 @@ var MOFLO_YAML = readMofloYaml();
|
|
|
116
129
|
|
|
117
130
|
var config = loadGateConfig();
|
|
118
131
|
var sddConf = loadSddConfig();
|
|
132
|
+
var mergeConf = loadMergeConfig();
|
|
119
133
|
var command = process.argv[2];
|
|
120
134
|
|
|
121
135
|
var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules', 'moflo.yaml'];
|
|
122
136
|
|
|
137
|
+
// Appended to every memory-first denial. Without it, a context audit of Claude
|
|
138
|
+
// Code's denial telemetry reads moflo's gate enforcements as permission-setup
|
|
139
|
+
// failures and "fixes" them with allow rules — which cannot override a hook
|
|
140
|
+
// block at all (#1307 finding 5). Naming the gate makes the cause self-evident.
|
|
141
|
+
var GATE_ORIGIN_NOTE = 'This is a moflo hook, not a Claude Code permission rule — allow-rules cannot override it.';
|
|
142
|
+
var GATE_DISABLE_NOTE = 'Disable per-gate via moflo.yaml: gates: memory_first: false';
|
|
143
|
+
|
|
123
144
|
// #1294 Finding 3 — reads/scans of EPHEMERAL files under the OS temp dir
|
|
124
145
|
// (background-task output/transcripts, agent scratchpads) are transient tool
|
|
125
146
|
// I/O and never carry indexable project knowledge, so they must not trip the
|
|
@@ -288,16 +309,71 @@ function detectFlMode(promptText) {
|
|
|
288
309
|
return null;
|
|
289
310
|
}
|
|
290
311
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
|
|
312
|
+
// Resolve the FULL set of /flo run modifiers from the prompt + moflo.yaml — the
|
|
313
|
+
// single source of truth for both gate arming (#1297) and the authoritative
|
|
314
|
+
// announcement emitted by prompt-reminder. Two consumers, one resolver: a second
|
|
315
|
+
// implementation is exactly how `sdd.default: true` got silently ignored (the
|
|
316
|
+
// skill's prompt carried `let sddMode = false` and the model executed the literal).
|
|
317
|
+
//
|
|
318
|
+
// Precedence per key: explicit --no-X > explicit -x/--X > moflo.yaml > built-in.
|
|
319
|
+
// NOTE the differing built-in defaults: sdd is opt-IN (false), verify is opt-OUT
|
|
320
|
+
// (true, #1294), merge is opt-IN (false, #1285).
|
|
321
|
+
//
|
|
322
|
+
// `-sd` is a distinct token from `-s` (swarm): the `d` sits on the word boundary
|
|
323
|
+
// so `-s\b` never matches `-sd`.
|
|
324
|
+
function resolveFloRun(promptText) {
|
|
296
325
|
var p = promptText || '';
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
|
|
326
|
+
var out = { isFlo: false, workflow: 'full', sdd: false, verify: false, merge: false,
|
|
327
|
+
sddSrc: 'default', verifySrc: 'default', mergeSrc: 'default' };
|
|
328
|
+
if (!/^\s*\/(?:fl|flo)\b/i.test(p)) return out;
|
|
329
|
+
out.isFlo = true;
|
|
330
|
+
|
|
331
|
+
// Workflow mode — decides which modifiers are even applicable.
|
|
332
|
+
if (/(?:^|\s)(?:-wf|--workflow)\b/.test(p)) out.workflow = 'spell-engine';
|
|
333
|
+
else if (/(?:^|\s)(?:-r|--research)\b/.test(p)) out.workflow = 'research';
|
|
334
|
+
else if (/(?:^|\s)(?:-t|--ticket)\b/.test(p)) out.workflow = 'ticket';
|
|
335
|
+
var epicBranch = /(?:^|\s)--epic-branch\b/.test(p);
|
|
336
|
+
|
|
337
|
+
if (/(?:^|\s)--no-sdd\b/.test(p)) { out.sdd = false; out.sddSrc = 'flag'; }
|
|
338
|
+
else if (/(?:^|\s)(?:-sd|--sdd)\b/.test(p)) { out.sdd = true; out.sddSrc = 'flag'; }
|
|
339
|
+
else if (sddConf.default) { out.sdd = true; out.sddSrc = 'moflo.yaml sdd.default'; }
|
|
340
|
+
|
|
341
|
+
if (/(?:^|\s)--no-verify\b/.test(p)) { out.verify = false; out.verifySrc = 'flag'; }
|
|
342
|
+
else if (/(?:^|\s)(?:-v|--verify)\b/.test(p)) { out.verify = true; out.verifySrc = 'flag'; }
|
|
343
|
+
else if (!config.verify_before_done) { out.verify = false; out.verifySrc = 'moflo.yaml gates.verify_before_done'; }
|
|
344
|
+
else { out.verify = true; out.verifySrc = 'default'; }
|
|
345
|
+
// --sdd implies --verify: a spec/plan without an enforced verify step drifts.
|
|
346
|
+
if (out.sdd && !out.verify && out.verifySrc !== 'flag') out.verify = true;
|
|
347
|
+
|
|
348
|
+
if (/(?:^|\s)--no-merge\b/.test(p)) { out.merge = false; out.mergeSrc = 'flag'; }
|
|
349
|
+
else if (/(?:^|\s)(?:-m|--merge)\b/.test(p)) { out.merge = true; out.mergeSrc = 'flag'; }
|
|
350
|
+
else if (mergeConf.auto) { out.merge = true; out.mergeSrc = 'moflo.yaml merge.auto'; }
|
|
351
|
+
|
|
352
|
+
// Applicability: -t/-r never implement, so verify is a no-op there; -r produces
|
|
353
|
+
// no artifacts, so sdd is a no-op too (in -t the spec/plan goes INTO the ticket,
|
|
354
|
+
// so sdd stays on). Only a full non-epic-branch run opens a PR to merge.
|
|
355
|
+
// Re-attribute anything applicability turned off, so a false never carries the
|
|
356
|
+
// source of the value it no longer has. This whole change exists to stop modes
|
|
357
|
+
// being reported inaccurately — the attribution has to be honest too.
|
|
358
|
+
if (out.workflow === 'ticket' || out.workflow === 'research') {
|
|
359
|
+
if (out.verify) out.verifySrc = out.workflow + ' mode does not implement';
|
|
360
|
+
out.verify = false;
|
|
361
|
+
}
|
|
362
|
+
if (out.workflow === 'research' || out.workflow === 'spell-engine') {
|
|
363
|
+
if (out.sdd) out.sddSrc = out.workflow + ' mode produces no spec artifacts';
|
|
364
|
+
out.sdd = false;
|
|
365
|
+
}
|
|
366
|
+
if (out.workflow !== 'full' || epicBranch) {
|
|
367
|
+
if (out.merge) out.mergeSrc = epicBranch ? '--epic-branch owns merging' : out.workflow + ' mode opens no PR';
|
|
368
|
+
out.merge = false;
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// #1297 — arm the SDD implement gate from the user prompt. Thin wrapper so the
|
|
374
|
+
// arming decision and the announced decision can never disagree.
|
|
375
|
+
function detectSddMode(promptText) {
|
|
376
|
+
return resolveFloRun(promptText).sdd;
|
|
301
377
|
}
|
|
302
378
|
|
|
303
379
|
// Resolve the absolute specs root the same way TS specsRoot does (#1294): split
|
|
@@ -644,7 +720,7 @@ switch (command) {
|
|
|
644
720
|
var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
|
|
645
721
|
if (isEphemeralPath(process.env.TOOL_INPUT_path)) break;
|
|
646
722
|
if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
|
|
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');
|
|
723
|
+
process.stderr.write('BLOCKED [moflo memory_first gate]: 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' + GATE_ORIGIN_NOTE + '\n' + GATE_DISABLE_NOTE + '\n');
|
|
648
724
|
process.exit(2);
|
|
649
725
|
}
|
|
650
726
|
case 'check-before-read': {
|
|
@@ -657,7 +733,7 @@ switch (command) {
|
|
|
657
733
|
if (isEphemeralPath(fp)) break;
|
|
658
734
|
var isGuidance = fp.indexOf('.claude/guidance/') >= 0 || fp.indexOf('.claude\\guidance\\') >= 0;
|
|
659
735
|
if (!isGuidance && EXEMPT.some(function(p) { return fp.indexOf(p) >= 0; })) break;
|
|
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');
|
|
736
|
+
process.stderr.write('BLOCKED [moflo memory_first gate]: 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' + GATE_ORIGIN_NOTE + '\n' + GATE_DISABLE_NOTE + '\n');
|
|
661
737
|
process.exit(2);
|
|
662
738
|
}
|
|
663
739
|
case 'record-task-created': {
|
|
@@ -706,11 +782,12 @@ switch (command) {
|
|
|
706
782
|
// "Memory namespace hint: ..." sentence so the BLOCK message stays uniform.
|
|
707
783
|
var hint = s2.lastNamespaceHint || classifyBashNamespaceHint(cmd) || '';
|
|
708
784
|
process.stderr.write(
|
|
709
|
-
'BLOCKED: Search memory before reading files via Bash.\n' +
|
|
785
|
+
'BLOCKED [moflo memory_first gate]: Search memory before reading files via Bash.\n' +
|
|
710
786
|
'Example: mcp__moflo__memory_search { query: "<topic>", namespace: "<one of: guidance | code-map | patterns | learnings | tests>" }\n' +
|
|
711
787
|
(hint ? hint + '\n' : '') +
|
|
712
788
|
'On chunk hits, traverse via mcp__moflo__memory_get_neighbors — see .claude/guidance/moflo-memory-protocol.md\n' +
|
|
713
|
-
|
|
789
|
+
GATE_ORIGIN_NOTE + '\n' +
|
|
790
|
+
GATE_DISABLE_NOTE + '\n'
|
|
714
791
|
);
|
|
715
792
|
process.exit(2);
|
|
716
793
|
break;
|
|
@@ -981,6 +1058,33 @@ switch (command) {
|
|
|
981
1058
|
applyPromptStateReset(s, prompt);
|
|
982
1059
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
983
1060
|
writeState(s);
|
|
1061
|
+
// Announce the resolved /flo run modifiers. The gate already parsed
|
|
1062
|
+
// moflo.yaml in THIS process (fresh per prompt — a git pull or a mid-session
|
|
1063
|
+
// yaml edit is picked up automatically, no cache to invalidate), so this
|
|
1064
|
+
// costs no extra read. Emitting it is what closes the loop: the skill used
|
|
1065
|
+
// to carry `let sddMode = false` as a literal and the model executed it,
|
|
1066
|
+
// silently ignoring `sdd.default: true`. Only fires on /fl|/flo prompts.
|
|
1067
|
+
var floRun = resolveFloRun(prompt);
|
|
1068
|
+
if (floRun.isFlo) {
|
|
1069
|
+
console.log(
|
|
1070
|
+
'[moflo] /flo run modes (AUTHORITATIVE — use verbatim; do NOT re-derive from the skill defaults): ' +
|
|
1071
|
+
'sdd=' + (floRun.sdd ? 'ON' : 'off') +
|
|
1072
|
+
' verify=' + (floRun.verify ? 'ON' : 'off') +
|
|
1073
|
+
' merge=' + (floRun.merge ? 'ON' : 'off') +
|
|
1074
|
+
' [workflow=' + floRun.workflow + ']'
|
|
1075
|
+
);
|
|
1076
|
+
// Spell out the surprising case: a project default the user did not type.
|
|
1077
|
+
if (floRun.sdd && floRun.sddSrc !== 'flag') {
|
|
1078
|
+
console.log(
|
|
1079
|
+
'[moflo] sdd is ON via ' + floRun.sddSrc + ' — the spec→plan→implement→verify cycle is ' +
|
|
1080
|
+
'MANDATORY this run. Author the spec before editing source (the sdd_gate blocks source ' +
|
|
1081
|
+
'Write/Edit until a reviewed plan exists). One-off opt out: re-run with --no-sdd.'
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
if (floRun.merge && floRun.mergeSrc !== 'flag') {
|
|
1085
|
+
console.log('[moflo] merge is ON via ' + floRun.mergeSrc + ' — the PR will be auto-merged. Opt out: --no-merge.');
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
984
1088
|
if (config.context_tracking) {
|
|
985
1089
|
var ic = s.interactionCount;
|
|
986
1090
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: distill
|
|
3
|
-
description: Alias for /flo-simplify
|
|
3
|
+
description: Alias for /flo-simplify — see that skill's description.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# /distill is an alias for /flo-simplify
|
|
@@ -72,7 +72,7 @@ Two **independent** modifiers, orthogonal to execution mode (`-n`/`-s`/`-h`) and
|
|
|
72
72
|
| `-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`. |
|
|
73
73
|
| `--no-sdd`, `--no-verify` | | Opt a single run out. `--no-verify` skips the (default-on) verify step. |
|
|
74
74
|
|
|
75
|
-
Defaults seed from `moflo.yaml` — `sdd.default` (
|
|
75
|
+
Defaults seed from `moflo.yaml` — `sdd.default` (built-in **off**) and `gates.verify_before_done` (built-in **on** since #1294). So the SDD spec/plan ceremony is opt-in *by default*, but **a project can turn it on for every run** — never assume it is off; resolve it per the "Resolved run modes" section below. 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`.
|
|
76
76
|
|
|
77
77
|
## Auto-merge (#1285)
|
|
78
78
|
|
|
@@ -124,6 +124,35 @@ Read the relevant file before executing that part of the run.
|
|
|
124
124
|
| `./spell-engine.md` | `-wf` invocations (list, info, execute) |
|
|
125
125
|
| `./sdd.md` | `-sd`/`--sdd` and `-v`/`--verify` — the spec→plan→implement→verify cycle |
|
|
126
126
|
|
|
127
|
+
## Resolved run modes — read this BEFORE parsing arguments
|
|
128
|
+
|
|
129
|
+
`sddMode`, `verifyMode`, and `mergeMode` are **config-derived**. Their project defaults live in `moflo.yaml` (`sdd.default`, `gates.verify_before_done`, `merge.auto`), so they cannot be known from this document — you MUST obtain them, never assume them. A run that assumes `sdd=off` on a project with `sdd.default: true` silently skips the entire spec→plan cycle, which is the exact failure this step exists to prevent.
|
|
130
|
+
|
|
131
|
+
**Primary source — the injected resolution line.** moflo's `prompt-reminder` hook resolves all three from `moflo.yaml` on every `/flo` prompt (fresh process per prompt, so a `git pull` or a mid-session edit to `moflo.yaml` is picked up automatically) and emits them into your context immediately above the user's message:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
[moflo] /flo run modes (AUTHORITATIVE — use verbatim; do NOT re-derive from the skill defaults): sdd=ON verify=ON merge=off [workflow=full]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Scan for that `[moflo] /flo run modes` line and assign `sddMode` / `verifyMode` / `mergeMode` from it **verbatim**. It already accounts for flags the user typed, `moflo.yaml` defaults, `--sdd` implying `--verify`, and mode applicability — do not second-guess it.
|
|
138
|
+
|
|
139
|
+
**Fallback — no such line in context** (hooks disabled, or a consumer on a gate.cjs predating this). Shell out once; the CLI resolves from the same precedence rules:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
flo sdd mode --args="$ARGUMENTS"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Use `--args=` (with the `=`), **not** `--args <value>` — a value starting with `-` would otherwise be parsed as flags and you would silently get the bare config default back.
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{ "workflow": "full", "sdd": true, "verify": true, "merge": false,
|
|
149
|
+
"sddSrc": "moflo.yaml sdd.default", "verifySrc": "default", "mergeSrc": "default" }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Never skip both.** If the injected line is absent and the CLI call fails, say so explicitly to the user and stop rather than proceeding on a guessed mode — a wrongly-assumed `sdd=off` is invisible to the user until the PR lands without a spec.
|
|
153
|
+
|
|
154
|
+
When `sdd` resolved ON from config rather than a typed flag, state that in your first message (e.g. *"SDD is on via `moflo.yaml sdd.default` — running the spec→plan→implement→verify cycle."*) so the user can `--no-sdd` out before work starts.
|
|
155
|
+
|
|
127
156
|
## Argument parsing
|
|
128
157
|
|
|
129
158
|
```javascript
|
|
@@ -135,21 +164,13 @@ let epicBranch = null;
|
|
|
135
164
|
let issueNumber = null;
|
|
136
165
|
let titleWords = [];
|
|
137
166
|
|
|
138
|
-
// SDD/verify modifiers (Epic #1269, #1294)
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
let sddMode = false; // -sd / --sdd (full spec→plan→implement→verify)
|
|
144
|
-
let verifyMode = true; // -v / --verify (on by default; --no-verify opts out)
|
|
167
|
+
// SDD / verify / merge modifiers (Epic #1269, #1294, #1285) — DO NOT resolve
|
|
168
|
+
// these here. They are config-derived, and the values below are placeholders,
|
|
169
|
+
// NOT defaults. Take them from the "Resolved run modes" step above; the loop
|
|
170
|
+
// below only applies what the user typed on top of that resolution.
|
|
171
|
+
let sddMode, verifyMode, mergeMode; // ← assigned from the resolution, never guessed
|
|
145
172
|
let verifyExplicit = false; // did the user actually type -v/--verify? (drives the -t/-r note only, so it doesn't fire on the default)
|
|
146
173
|
|
|
147
|
-
// Auto-merge modifier (#1285). Seed from moflo.yaml BEFORE parsing, same as
|
|
148
|
-
// sddMode/verifyMode: read `merge.auto` at the project root (absent ⇒ false).
|
|
149
|
-
// When true, a full run awaits the PR's merge preconditions and merges it
|
|
150
|
-
// (Phase 5.3b) instead of stopping at "PR opened".
|
|
151
|
-
let mergeMode = false; // -m / --merge ← moflo.yaml merge.auto
|
|
152
|
-
|
|
153
174
|
let wfName = null, wfSubcommand = null;
|
|
154
175
|
let wfArgs = [], wfNamedArgs = {};
|
|
155
176
|
|
package/.claude/skills/fl/sdd.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
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. Opt-in (`sdd.default`
|
|
5
|
+
- **`-sd` / `--sdd`** — run the full **spec → plan → (review) → implement → verify** cycle. Opt-in *by built-in default* (`sdd.default` = false), but **a project can set `sdd.default: true` and turn it on for every run**. Implies verify.
|
|
6
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
|
-
|
|
8
|
+
⚠ **Never infer whether SDD is on from these built-in defaults.** They are project-configurable, so the effective value is only knowable at run time. Resolve it as `SKILL.md` § "Resolved run modes" describes — the `[moflo] /flo run modes` line injected into context, or `flo sdd mode --args="$ARGUMENTS"`. Assuming `sdd=off` on a project with `sdd.default: true` silently skips the whole spec/plan cycle and is invisible to the user until the PR lands without a spec.
|
|
9
|
+
|
|
10
|
+
Precedence: per-run flags and `--no-sdd` / `--no-verify` override `moflo.yaml`, which overrides the built-ins. 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
11
|
|
|
10
12
|
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
13
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: perf-audit
|
|
3
|
-
description: Alias for /quicken
|
|
3
|
+
description: Alias for /quicken — see that skill's description.
|
|
4
4
|
arguments: "[path or area | --all]"
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: test-gaps
|
|
3
|
-
description: Alias for /ward
|
|
3
|
+
description: Alias for /ward — see that skill's description.
|
|
4
4
|
arguments: "[path or area | --all]"
|
|
5
5
|
---
|
|
6
6
|
|
package/bin/gate.cjs
CHANGED
|
@@ -104,6 +104,19 @@ function loadSddConfig() {
|
|
|
104
104
|
return out;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Parse the top-level `merge:` block from moflo.yaml (#1285). Block-scoped for
|
|
108
|
+
// the same reason as loadSddConfig: a bare `auto:` key can appear under other
|
|
109
|
+
// sections. Cross-platform: tolerates CRLF.
|
|
110
|
+
function loadMergeConfig() {
|
|
111
|
+
var out = { auto: false };
|
|
112
|
+
var content = MOFLO_YAML;
|
|
113
|
+
if (!content) return out;
|
|
114
|
+
var block = content.match(/^merge:[ \t]*\r?\n((?:[ \t]+.*(?:\r?\n|$))*)/m);
|
|
115
|
+
if (!block) return out;
|
|
116
|
+
if (/^\s*auto:\s*true\b/im.test(block[1])) out.auto = true;
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
|
|
107
120
|
// Read moflo.yaml exactly once per gate process (#1297 review): loadGateConfig
|
|
108
121
|
// and loadSddConfig both parse it, and the gate fires on every Write/Edit — a
|
|
109
122
|
// second read is wasted syscalls. Single readFileSync in try/catch (no existsSync
|
|
@@ -116,10 +129,18 @@ var MOFLO_YAML = readMofloYaml();
|
|
|
116
129
|
|
|
117
130
|
var config = loadGateConfig();
|
|
118
131
|
var sddConf = loadSddConfig();
|
|
132
|
+
var mergeConf = loadMergeConfig();
|
|
119
133
|
var command = process.argv[2];
|
|
120
134
|
|
|
121
135
|
var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules', 'moflo.yaml'];
|
|
122
136
|
|
|
137
|
+
// Appended to every memory-first denial. Without it, a context audit of Claude
|
|
138
|
+
// Code's denial telemetry reads moflo's gate enforcements as permission-setup
|
|
139
|
+
// failures and "fixes" them with allow rules — which cannot override a hook
|
|
140
|
+
// block at all (#1307 finding 5). Naming the gate makes the cause self-evident.
|
|
141
|
+
var GATE_ORIGIN_NOTE = 'This is a moflo hook, not a Claude Code permission rule — allow-rules cannot override it.';
|
|
142
|
+
var GATE_DISABLE_NOTE = 'Disable per-gate via moflo.yaml: gates: memory_first: false';
|
|
143
|
+
|
|
123
144
|
// #1294 Finding 3 — reads/scans of EPHEMERAL files under the OS temp dir
|
|
124
145
|
// (background-task output/transcripts, agent scratchpads) are transient tool
|
|
125
146
|
// I/O and never carry indexable project knowledge, so they must not trip the
|
|
@@ -288,16 +309,71 @@ function detectFlMode(promptText) {
|
|
|
288
309
|
return null;
|
|
289
310
|
}
|
|
290
311
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
|
|
312
|
+
// Resolve the FULL set of /flo run modifiers from the prompt + moflo.yaml — the
|
|
313
|
+
// single source of truth for both gate arming (#1297) and the authoritative
|
|
314
|
+
// announcement emitted by prompt-reminder. Two consumers, one resolver: a second
|
|
315
|
+
// implementation is exactly how `sdd.default: true` got silently ignored (the
|
|
316
|
+
// skill's prompt carried `let sddMode = false` and the model executed the literal).
|
|
317
|
+
//
|
|
318
|
+
// Precedence per key: explicit --no-X > explicit -x/--X > moflo.yaml > built-in.
|
|
319
|
+
// NOTE the differing built-in defaults: sdd is opt-IN (false), verify is opt-OUT
|
|
320
|
+
// (true, #1294), merge is opt-IN (false, #1285).
|
|
321
|
+
//
|
|
322
|
+
// `-sd` is a distinct token from `-s` (swarm): the `d` sits on the word boundary
|
|
323
|
+
// so `-s\b` never matches `-sd`.
|
|
324
|
+
function resolveFloRun(promptText) {
|
|
296
325
|
var p = promptText || '';
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
|
|
326
|
+
var out = { isFlo: false, workflow: 'full', sdd: false, verify: false, merge: false,
|
|
327
|
+
sddSrc: 'default', verifySrc: 'default', mergeSrc: 'default' };
|
|
328
|
+
if (!/^\s*\/(?:fl|flo)\b/i.test(p)) return out;
|
|
329
|
+
out.isFlo = true;
|
|
330
|
+
|
|
331
|
+
// Workflow mode — decides which modifiers are even applicable.
|
|
332
|
+
if (/(?:^|\s)(?:-wf|--workflow)\b/.test(p)) out.workflow = 'spell-engine';
|
|
333
|
+
else if (/(?:^|\s)(?:-r|--research)\b/.test(p)) out.workflow = 'research';
|
|
334
|
+
else if (/(?:^|\s)(?:-t|--ticket)\b/.test(p)) out.workflow = 'ticket';
|
|
335
|
+
var epicBranch = /(?:^|\s)--epic-branch\b/.test(p);
|
|
336
|
+
|
|
337
|
+
if (/(?:^|\s)--no-sdd\b/.test(p)) { out.sdd = false; out.sddSrc = 'flag'; }
|
|
338
|
+
else if (/(?:^|\s)(?:-sd|--sdd)\b/.test(p)) { out.sdd = true; out.sddSrc = 'flag'; }
|
|
339
|
+
else if (sddConf.default) { out.sdd = true; out.sddSrc = 'moflo.yaml sdd.default'; }
|
|
340
|
+
|
|
341
|
+
if (/(?:^|\s)--no-verify\b/.test(p)) { out.verify = false; out.verifySrc = 'flag'; }
|
|
342
|
+
else if (/(?:^|\s)(?:-v|--verify)\b/.test(p)) { out.verify = true; out.verifySrc = 'flag'; }
|
|
343
|
+
else if (!config.verify_before_done) { out.verify = false; out.verifySrc = 'moflo.yaml gates.verify_before_done'; }
|
|
344
|
+
else { out.verify = true; out.verifySrc = 'default'; }
|
|
345
|
+
// --sdd implies --verify: a spec/plan without an enforced verify step drifts.
|
|
346
|
+
if (out.sdd && !out.verify && out.verifySrc !== 'flag') out.verify = true;
|
|
347
|
+
|
|
348
|
+
if (/(?:^|\s)--no-merge\b/.test(p)) { out.merge = false; out.mergeSrc = 'flag'; }
|
|
349
|
+
else if (/(?:^|\s)(?:-m|--merge)\b/.test(p)) { out.merge = true; out.mergeSrc = 'flag'; }
|
|
350
|
+
else if (mergeConf.auto) { out.merge = true; out.mergeSrc = 'moflo.yaml merge.auto'; }
|
|
351
|
+
|
|
352
|
+
// Applicability: -t/-r never implement, so verify is a no-op there; -r produces
|
|
353
|
+
// no artifacts, so sdd is a no-op too (in -t the spec/plan goes INTO the ticket,
|
|
354
|
+
// so sdd stays on). Only a full non-epic-branch run opens a PR to merge.
|
|
355
|
+
// Re-attribute anything applicability turned off, so a false never carries the
|
|
356
|
+
// source of the value it no longer has. This whole change exists to stop modes
|
|
357
|
+
// being reported inaccurately — the attribution has to be honest too.
|
|
358
|
+
if (out.workflow === 'ticket' || out.workflow === 'research') {
|
|
359
|
+
if (out.verify) out.verifySrc = out.workflow + ' mode does not implement';
|
|
360
|
+
out.verify = false;
|
|
361
|
+
}
|
|
362
|
+
if (out.workflow === 'research' || out.workflow === 'spell-engine') {
|
|
363
|
+
if (out.sdd) out.sddSrc = out.workflow + ' mode produces no spec artifacts';
|
|
364
|
+
out.sdd = false;
|
|
365
|
+
}
|
|
366
|
+
if (out.workflow !== 'full' || epicBranch) {
|
|
367
|
+
if (out.merge) out.mergeSrc = epicBranch ? '--epic-branch owns merging' : out.workflow + ' mode opens no PR';
|
|
368
|
+
out.merge = false;
|
|
369
|
+
}
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// #1297 — arm the SDD implement gate from the user prompt. Thin wrapper so the
|
|
374
|
+
// arming decision and the announced decision can never disagree.
|
|
375
|
+
function detectSddMode(promptText) {
|
|
376
|
+
return resolveFloRun(promptText).sdd;
|
|
301
377
|
}
|
|
302
378
|
|
|
303
379
|
// Resolve the absolute specs root the same way TS specsRoot does (#1294): split
|
|
@@ -644,7 +720,7 @@ switch (command) {
|
|
|
644
720
|
var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
|
|
645
721
|
if (isEphemeralPath(process.env.TOOL_INPUT_path)) break;
|
|
646
722
|
if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
|
|
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');
|
|
723
|
+
process.stderr.write('BLOCKED [moflo memory_first gate]: 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' + GATE_ORIGIN_NOTE + '\n' + GATE_DISABLE_NOTE + '\n');
|
|
648
724
|
process.exit(2);
|
|
649
725
|
}
|
|
650
726
|
case 'check-before-read': {
|
|
@@ -657,7 +733,7 @@ switch (command) {
|
|
|
657
733
|
if (isEphemeralPath(fp)) break;
|
|
658
734
|
var isGuidance = fp.indexOf('.claude/guidance/') >= 0 || fp.indexOf('.claude\\guidance\\') >= 0;
|
|
659
735
|
if (!isGuidance && EXEMPT.some(function(p) { return fp.indexOf(p) >= 0; })) break;
|
|
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');
|
|
736
|
+
process.stderr.write('BLOCKED [moflo memory_first gate]: 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' + GATE_ORIGIN_NOTE + '\n' + GATE_DISABLE_NOTE + '\n');
|
|
661
737
|
process.exit(2);
|
|
662
738
|
}
|
|
663
739
|
case 'record-task-created': {
|
|
@@ -706,11 +782,12 @@ switch (command) {
|
|
|
706
782
|
// "Memory namespace hint: ..." sentence so the BLOCK message stays uniform.
|
|
707
783
|
var hint = s2.lastNamespaceHint || classifyBashNamespaceHint(cmd) || '';
|
|
708
784
|
process.stderr.write(
|
|
709
|
-
'BLOCKED: Search memory before reading files via Bash.\n' +
|
|
785
|
+
'BLOCKED [moflo memory_first gate]: Search memory before reading files via Bash.\n' +
|
|
710
786
|
'Example: mcp__moflo__memory_search { query: "<topic>", namespace: "<one of: guidance | code-map | patterns | learnings | tests>" }\n' +
|
|
711
787
|
(hint ? hint + '\n' : '') +
|
|
712
788
|
'On chunk hits, traverse via mcp__moflo__memory_get_neighbors — see .claude/guidance/moflo-memory-protocol.md\n' +
|
|
713
|
-
|
|
789
|
+
GATE_ORIGIN_NOTE + '\n' +
|
|
790
|
+
GATE_DISABLE_NOTE + '\n'
|
|
714
791
|
);
|
|
715
792
|
process.exit(2);
|
|
716
793
|
break;
|
|
@@ -981,6 +1058,33 @@ switch (command) {
|
|
|
981
1058
|
applyPromptStateReset(s, prompt);
|
|
982
1059
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
983
1060
|
writeState(s);
|
|
1061
|
+
// Announce the resolved /flo run modifiers. The gate already parsed
|
|
1062
|
+
// moflo.yaml in THIS process (fresh per prompt — a git pull or a mid-session
|
|
1063
|
+
// yaml edit is picked up automatically, no cache to invalidate), so this
|
|
1064
|
+
// costs no extra read. Emitting it is what closes the loop: the skill used
|
|
1065
|
+
// to carry `let sddMode = false` as a literal and the model executed it,
|
|
1066
|
+
// silently ignoring `sdd.default: true`. Only fires on /fl|/flo prompts.
|
|
1067
|
+
var floRun = resolveFloRun(prompt);
|
|
1068
|
+
if (floRun.isFlo) {
|
|
1069
|
+
console.log(
|
|
1070
|
+
'[moflo] /flo run modes (AUTHORITATIVE — use verbatim; do NOT re-derive from the skill defaults): ' +
|
|
1071
|
+
'sdd=' + (floRun.sdd ? 'ON' : 'off') +
|
|
1072
|
+
' verify=' + (floRun.verify ? 'ON' : 'off') +
|
|
1073
|
+
' merge=' + (floRun.merge ? 'ON' : 'off') +
|
|
1074
|
+
' [workflow=' + floRun.workflow + ']'
|
|
1075
|
+
);
|
|
1076
|
+
// Spell out the surprising case: a project default the user did not type.
|
|
1077
|
+
if (floRun.sdd && floRun.sddSrc !== 'flag') {
|
|
1078
|
+
console.log(
|
|
1079
|
+
'[moflo] sdd is ON via ' + floRun.sddSrc + ' — the spec→plan→implement→verify cycle is ' +
|
|
1080
|
+
'MANDATORY this run. Author the spec before editing source (the sdd_gate blocks source ' +
|
|
1081
|
+
'Write/Edit until a reviewed plan exists). One-off opt out: re-run with --no-sdd.'
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
if (floRun.merge && floRun.mergeSrc !== 'flag') {
|
|
1085
|
+
console.log('[moflo] merge is ON via ' + floRun.mergeSrc + ' — the PR will be auto-merged. Opt out: --no-merge.');
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
984
1088
|
if (config.context_tracking) {
|
|
985
1089
|
var ic = s.interactionCount;
|
|
986
1090
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|