moflo 4.12.0 → 4.12.1
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/helpers/gate.cjs +105 -9
- package/.claude/skills/fl/SKILL.md +35 -14
- package/.claude/skills/fl/sdd.md +4 -2
- package/bin/gate.cjs +105 -9
- package/dist/src/cli/commands/sdd.js +119 -1
- package/dist/src/cli/init/helpers-generator.js +88 -6
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
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,6 +129,7 @@ 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'];
|
|
@@ -288,16 +302,71 @@ function detectFlMode(promptText) {
|
|
|
288
302
|
return null;
|
|
289
303
|
}
|
|
290
304
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
|
|
305
|
+
// Resolve the FULL set of /flo run modifiers from the prompt + moflo.yaml — the
|
|
306
|
+
// single source of truth for both gate arming (#1297) and the authoritative
|
|
307
|
+
// announcement emitted by prompt-reminder. Two consumers, one resolver: a second
|
|
308
|
+
// implementation is exactly how `sdd.default: true` got silently ignored (the
|
|
309
|
+
// skill's prompt carried `let sddMode = false` and the model executed the literal).
|
|
310
|
+
//
|
|
311
|
+
// Precedence per key: explicit --no-X > explicit -x/--X > moflo.yaml > built-in.
|
|
312
|
+
// NOTE the differing built-in defaults: sdd is opt-IN (false), verify is opt-OUT
|
|
313
|
+
// (true, #1294), merge is opt-IN (false, #1285).
|
|
314
|
+
//
|
|
315
|
+
// `-sd` is a distinct token from `-s` (swarm): the `d` sits on the word boundary
|
|
316
|
+
// so `-s\b` never matches `-sd`.
|
|
317
|
+
function resolveFloRun(promptText) {
|
|
296
318
|
var p = promptText || '';
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
|
|
319
|
+
var out = { isFlo: false, workflow: 'full', sdd: false, verify: false, merge: false,
|
|
320
|
+
sddSrc: 'default', verifySrc: 'default', mergeSrc: 'default' };
|
|
321
|
+
if (!/^\s*\/(?:fl|flo)\b/i.test(p)) return out;
|
|
322
|
+
out.isFlo = true;
|
|
323
|
+
|
|
324
|
+
// Workflow mode — decides which modifiers are even applicable.
|
|
325
|
+
if (/(?:^|\s)(?:-wf|--workflow)\b/.test(p)) out.workflow = 'spell-engine';
|
|
326
|
+
else if (/(?:^|\s)(?:-r|--research)\b/.test(p)) out.workflow = 'research';
|
|
327
|
+
else if (/(?:^|\s)(?:-t|--ticket)\b/.test(p)) out.workflow = 'ticket';
|
|
328
|
+
var epicBranch = /(?:^|\s)--epic-branch\b/.test(p);
|
|
329
|
+
|
|
330
|
+
if (/(?:^|\s)--no-sdd\b/.test(p)) { out.sdd = false; out.sddSrc = 'flag'; }
|
|
331
|
+
else if (/(?:^|\s)(?:-sd|--sdd)\b/.test(p)) { out.sdd = true; out.sddSrc = 'flag'; }
|
|
332
|
+
else if (sddConf.default) { out.sdd = true; out.sddSrc = 'moflo.yaml sdd.default'; }
|
|
333
|
+
|
|
334
|
+
if (/(?:^|\s)--no-verify\b/.test(p)) { out.verify = false; out.verifySrc = 'flag'; }
|
|
335
|
+
else if (/(?:^|\s)(?:-v|--verify)\b/.test(p)) { out.verify = true; out.verifySrc = 'flag'; }
|
|
336
|
+
else if (!config.verify_before_done) { out.verify = false; out.verifySrc = 'moflo.yaml gates.verify_before_done'; }
|
|
337
|
+
else { out.verify = true; out.verifySrc = 'default'; }
|
|
338
|
+
// --sdd implies --verify: a spec/plan without an enforced verify step drifts.
|
|
339
|
+
if (out.sdd && !out.verify && out.verifySrc !== 'flag') out.verify = true;
|
|
340
|
+
|
|
341
|
+
if (/(?:^|\s)--no-merge\b/.test(p)) { out.merge = false; out.mergeSrc = 'flag'; }
|
|
342
|
+
else if (/(?:^|\s)(?:-m|--merge)\b/.test(p)) { out.merge = true; out.mergeSrc = 'flag'; }
|
|
343
|
+
else if (mergeConf.auto) { out.merge = true; out.mergeSrc = 'moflo.yaml merge.auto'; }
|
|
344
|
+
|
|
345
|
+
// Applicability: -t/-r never implement, so verify is a no-op there; -r produces
|
|
346
|
+
// no artifacts, so sdd is a no-op too (in -t the spec/plan goes INTO the ticket,
|
|
347
|
+
// so sdd stays on). Only a full non-epic-branch run opens a PR to merge.
|
|
348
|
+
// Re-attribute anything applicability turned off, so a false never carries the
|
|
349
|
+
// source of the value it no longer has. This whole change exists to stop modes
|
|
350
|
+
// being reported inaccurately — the attribution has to be honest too.
|
|
351
|
+
if (out.workflow === 'ticket' || out.workflow === 'research') {
|
|
352
|
+
if (out.verify) out.verifySrc = out.workflow + ' mode does not implement';
|
|
353
|
+
out.verify = false;
|
|
354
|
+
}
|
|
355
|
+
if (out.workflow === 'research' || out.workflow === 'spell-engine') {
|
|
356
|
+
if (out.sdd) out.sddSrc = out.workflow + ' mode produces no spec artifacts';
|
|
357
|
+
out.sdd = false;
|
|
358
|
+
}
|
|
359
|
+
if (out.workflow !== 'full' || epicBranch) {
|
|
360
|
+
if (out.merge) out.mergeSrc = epicBranch ? '--epic-branch owns merging' : out.workflow + ' mode opens no PR';
|
|
361
|
+
out.merge = false;
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// #1297 — arm the SDD implement gate from the user prompt. Thin wrapper so the
|
|
367
|
+
// arming decision and the announced decision can never disagree.
|
|
368
|
+
function detectSddMode(promptText) {
|
|
369
|
+
return resolveFloRun(promptText).sdd;
|
|
301
370
|
}
|
|
302
371
|
|
|
303
372
|
// Resolve the absolute specs root the same way TS specsRoot does (#1294): split
|
|
@@ -981,6 +1050,33 @@ switch (command) {
|
|
|
981
1050
|
applyPromptStateReset(s, prompt);
|
|
982
1051
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
983
1052
|
writeState(s);
|
|
1053
|
+
// Announce the resolved /flo run modifiers. The gate already parsed
|
|
1054
|
+
// moflo.yaml in THIS process (fresh per prompt — a git pull or a mid-session
|
|
1055
|
+
// yaml edit is picked up automatically, no cache to invalidate), so this
|
|
1056
|
+
// costs no extra read. Emitting it is what closes the loop: the skill used
|
|
1057
|
+
// to carry `let sddMode = false` as a literal and the model executed it,
|
|
1058
|
+
// silently ignoring `sdd.default: true`. Only fires on /fl|/flo prompts.
|
|
1059
|
+
var floRun = resolveFloRun(prompt);
|
|
1060
|
+
if (floRun.isFlo) {
|
|
1061
|
+
console.log(
|
|
1062
|
+
'[moflo] /flo run modes (AUTHORITATIVE — use verbatim; do NOT re-derive from the skill defaults): ' +
|
|
1063
|
+
'sdd=' + (floRun.sdd ? 'ON' : 'off') +
|
|
1064
|
+
' verify=' + (floRun.verify ? 'ON' : 'off') +
|
|
1065
|
+
' merge=' + (floRun.merge ? 'ON' : 'off') +
|
|
1066
|
+
' [workflow=' + floRun.workflow + ']'
|
|
1067
|
+
);
|
|
1068
|
+
// Spell out the surprising case: a project default the user did not type.
|
|
1069
|
+
if (floRun.sdd && floRun.sddSrc !== 'flag') {
|
|
1070
|
+
console.log(
|
|
1071
|
+
'[moflo] sdd is ON via ' + floRun.sddSrc + ' — the spec→plan→implement→verify cycle is ' +
|
|
1072
|
+
'MANDATORY this run. Author the spec before editing source (the sdd_gate blocks source ' +
|
|
1073
|
+
'Write/Edit until a reviewed plan exists). One-off opt out: re-run with --no-sdd.'
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
if (floRun.merge && floRun.mergeSrc !== 'flag') {
|
|
1077
|
+
console.log('[moflo] merge is ON via ' + floRun.mergeSrc + ' — the PR will be auto-merged. Opt out: --no-merge.');
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
984
1080
|
if (config.context_tracking) {
|
|
985
1081
|
var ic = s.interactionCount;
|
|
986
1082
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
@@ -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
|
|
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,6 +129,7 @@ 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'];
|
|
@@ -288,16 +302,71 @@ function detectFlMode(promptText) {
|
|
|
288
302
|
return null;
|
|
289
303
|
}
|
|
290
304
|
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
|
|
305
|
+
// Resolve the FULL set of /flo run modifiers from the prompt + moflo.yaml — the
|
|
306
|
+
// single source of truth for both gate arming (#1297) and the authoritative
|
|
307
|
+
// announcement emitted by prompt-reminder. Two consumers, one resolver: a second
|
|
308
|
+
// implementation is exactly how `sdd.default: true` got silently ignored (the
|
|
309
|
+
// skill's prompt carried `let sddMode = false` and the model executed the literal).
|
|
310
|
+
//
|
|
311
|
+
// Precedence per key: explicit --no-X > explicit -x/--X > moflo.yaml > built-in.
|
|
312
|
+
// NOTE the differing built-in defaults: sdd is opt-IN (false), verify is opt-OUT
|
|
313
|
+
// (true, #1294), merge is opt-IN (false, #1285).
|
|
314
|
+
//
|
|
315
|
+
// `-sd` is a distinct token from `-s` (swarm): the `d` sits on the word boundary
|
|
316
|
+
// so `-s\b` never matches `-sd`.
|
|
317
|
+
function resolveFloRun(promptText) {
|
|
296
318
|
var p = promptText || '';
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (
|
|
300
|
-
|
|
319
|
+
var out = { isFlo: false, workflow: 'full', sdd: false, verify: false, merge: false,
|
|
320
|
+
sddSrc: 'default', verifySrc: 'default', mergeSrc: 'default' };
|
|
321
|
+
if (!/^\s*\/(?:fl|flo)\b/i.test(p)) return out;
|
|
322
|
+
out.isFlo = true;
|
|
323
|
+
|
|
324
|
+
// Workflow mode — decides which modifiers are even applicable.
|
|
325
|
+
if (/(?:^|\s)(?:-wf|--workflow)\b/.test(p)) out.workflow = 'spell-engine';
|
|
326
|
+
else if (/(?:^|\s)(?:-r|--research)\b/.test(p)) out.workflow = 'research';
|
|
327
|
+
else if (/(?:^|\s)(?:-t|--ticket)\b/.test(p)) out.workflow = 'ticket';
|
|
328
|
+
var epicBranch = /(?:^|\s)--epic-branch\b/.test(p);
|
|
329
|
+
|
|
330
|
+
if (/(?:^|\s)--no-sdd\b/.test(p)) { out.sdd = false; out.sddSrc = 'flag'; }
|
|
331
|
+
else if (/(?:^|\s)(?:-sd|--sdd)\b/.test(p)) { out.sdd = true; out.sddSrc = 'flag'; }
|
|
332
|
+
else if (sddConf.default) { out.sdd = true; out.sddSrc = 'moflo.yaml sdd.default'; }
|
|
333
|
+
|
|
334
|
+
if (/(?:^|\s)--no-verify\b/.test(p)) { out.verify = false; out.verifySrc = 'flag'; }
|
|
335
|
+
else if (/(?:^|\s)(?:-v|--verify)\b/.test(p)) { out.verify = true; out.verifySrc = 'flag'; }
|
|
336
|
+
else if (!config.verify_before_done) { out.verify = false; out.verifySrc = 'moflo.yaml gates.verify_before_done'; }
|
|
337
|
+
else { out.verify = true; out.verifySrc = 'default'; }
|
|
338
|
+
// --sdd implies --verify: a spec/plan without an enforced verify step drifts.
|
|
339
|
+
if (out.sdd && !out.verify && out.verifySrc !== 'flag') out.verify = true;
|
|
340
|
+
|
|
341
|
+
if (/(?:^|\s)--no-merge\b/.test(p)) { out.merge = false; out.mergeSrc = 'flag'; }
|
|
342
|
+
else if (/(?:^|\s)(?:-m|--merge)\b/.test(p)) { out.merge = true; out.mergeSrc = 'flag'; }
|
|
343
|
+
else if (mergeConf.auto) { out.merge = true; out.mergeSrc = 'moflo.yaml merge.auto'; }
|
|
344
|
+
|
|
345
|
+
// Applicability: -t/-r never implement, so verify is a no-op there; -r produces
|
|
346
|
+
// no artifacts, so sdd is a no-op too (in -t the spec/plan goes INTO the ticket,
|
|
347
|
+
// so sdd stays on). Only a full non-epic-branch run opens a PR to merge.
|
|
348
|
+
// Re-attribute anything applicability turned off, so a false never carries the
|
|
349
|
+
// source of the value it no longer has. This whole change exists to stop modes
|
|
350
|
+
// being reported inaccurately — the attribution has to be honest too.
|
|
351
|
+
if (out.workflow === 'ticket' || out.workflow === 'research') {
|
|
352
|
+
if (out.verify) out.verifySrc = out.workflow + ' mode does not implement';
|
|
353
|
+
out.verify = false;
|
|
354
|
+
}
|
|
355
|
+
if (out.workflow === 'research' || out.workflow === 'spell-engine') {
|
|
356
|
+
if (out.sdd) out.sddSrc = out.workflow + ' mode produces no spec artifacts';
|
|
357
|
+
out.sdd = false;
|
|
358
|
+
}
|
|
359
|
+
if (out.workflow !== 'full' || epicBranch) {
|
|
360
|
+
if (out.merge) out.mergeSrc = epicBranch ? '--epic-branch owns merging' : out.workflow + ' mode opens no PR';
|
|
361
|
+
out.merge = false;
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// #1297 — arm the SDD implement gate from the user prompt. Thin wrapper so the
|
|
367
|
+
// arming decision and the announced decision can never disagree.
|
|
368
|
+
function detectSddMode(promptText) {
|
|
369
|
+
return resolveFloRun(promptText).sdd;
|
|
301
370
|
}
|
|
302
371
|
|
|
303
372
|
// Resolve the absolute specs root the same way TS specsRoot does (#1294): split
|
|
@@ -981,6 +1050,33 @@ switch (command) {
|
|
|
981
1050
|
applyPromptStateReset(s, prompt);
|
|
982
1051
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
983
1052
|
writeState(s);
|
|
1053
|
+
// Announce the resolved /flo run modifiers. The gate already parsed
|
|
1054
|
+
// moflo.yaml in THIS process (fresh per prompt — a git pull or a mid-session
|
|
1055
|
+
// yaml edit is picked up automatically, no cache to invalidate), so this
|
|
1056
|
+
// costs no extra read. Emitting it is what closes the loop: the skill used
|
|
1057
|
+
// to carry `let sddMode = false` as a literal and the model executed it,
|
|
1058
|
+
// silently ignoring `sdd.default: true`. Only fires on /fl|/flo prompts.
|
|
1059
|
+
var floRun = resolveFloRun(prompt);
|
|
1060
|
+
if (floRun.isFlo) {
|
|
1061
|
+
console.log(
|
|
1062
|
+
'[moflo] /flo run modes (AUTHORITATIVE — use verbatim; do NOT re-derive from the skill defaults): ' +
|
|
1063
|
+
'sdd=' + (floRun.sdd ? 'ON' : 'off') +
|
|
1064
|
+
' verify=' + (floRun.verify ? 'ON' : 'off') +
|
|
1065
|
+
' merge=' + (floRun.merge ? 'ON' : 'off') +
|
|
1066
|
+
' [workflow=' + floRun.workflow + ']'
|
|
1067
|
+
);
|
|
1068
|
+
// Spell out the surprising case: a project default the user did not type.
|
|
1069
|
+
if (floRun.sdd && floRun.sddSrc !== 'flag') {
|
|
1070
|
+
console.log(
|
|
1071
|
+
'[moflo] sdd is ON via ' + floRun.sddSrc + ' — the spec→plan→implement→verify cycle is ' +
|
|
1072
|
+
'MANDATORY this run. Author the spec before editing source (the sdd_gate blocks source ' +
|
|
1073
|
+
'Write/Edit until a reviewed plan exists). One-off opt out: re-run with --no-sdd.'
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
if (floRun.merge && floRun.mergeSrc !== 'flag') {
|
|
1077
|
+
console.log('[moflo] merge is ON via ' + floRun.mergeSrc + ' — the PR will be auto-merged. Opt out: --no-merge.');
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
984
1080
|
if (config.context_tracking) {
|
|
985
1081
|
var ic = s.interactionCount;
|
|
986
1082
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
@@ -16,12 +16,14 @@
|
|
|
16
16
|
* flo sdd path <slug> [spec|plan] Print the artifact path (for skill shell-out)
|
|
17
17
|
* flo sdd embed <slug> Print spec+plan as a PR-body block (#1297)
|
|
18
18
|
* flo sdd index Re-index specs into memory now (also runs at session start)
|
|
19
|
+
* flo sdd mode --args "<flo args>" Resolve sdd/verify/merge for a /flo run as JSON
|
|
19
20
|
*/
|
|
20
21
|
import { spawnSync } from 'node:child_process';
|
|
21
22
|
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
22
23
|
import { dirname, join } from 'node:path';
|
|
23
24
|
import { atomicWriteFileSync } from '../shared/utils/atomic-file-write.js';
|
|
24
25
|
import { findProjectRoot } from '../services/project-root.js';
|
|
26
|
+
import { loadMofloConfig } from '../config/moflo-config.js';
|
|
25
27
|
import { locateMofloRootPath } from '../services/moflo-require.js';
|
|
26
28
|
import { artifactPath, assertReviewed, listSpecs, newArtifact, readArtifact, slugify, specExists, validateArtifact, writeArtifact, } from '../sdd/index.js';
|
|
27
29
|
import { defaultPlanBody, defaultSpecBody } from '../sdd/templates.js';
|
|
@@ -226,6 +228,119 @@ function cmdList(ctx) {
|
|
|
226
228
|
}
|
|
227
229
|
return { success: true, data: specs };
|
|
228
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Resolve the effective `/flo` run modifiers from an argument string + moflo.yaml
|
|
233
|
+
* and print them as JSON. This is the FALLBACK path for the authoritative
|
|
234
|
+
* announcement that `bin/gate.cjs prompt-reminder` normally injects on every
|
|
235
|
+
* `/flo` prompt — consumers with hooks disabled (or a gate.cjs predating this
|
|
236
|
+
* change) still get a deterministic answer by shelling out to this.
|
|
237
|
+
*
|
|
238
|
+
* Exists because the `/flo` skill used to carry `let sddMode = false` as a
|
|
239
|
+
* literal beside a comment saying "seed from moflo.yaml"; the model executed the
|
|
240
|
+
* literal and `sdd.default: true` was silently ignored. Config-derived run modes
|
|
241
|
+
* must be COMPUTED, never described in prose next to a contradicting default.
|
|
242
|
+
*
|
|
243
|
+
* Precedence per key: `--no-X` > `-x`/`--X` > moflo.yaml > built-in. Built-in
|
|
244
|
+
* defaults differ deliberately: sdd is opt-in (false), verify is opt-out (true,
|
|
245
|
+
* #1294), merge is opt-in (false, #1285).
|
|
246
|
+
*
|
|
247
|
+
* SYNC: mirrors `resolveFloRun` in bin/gate.cjs (and its copy in
|
|
248
|
+
* init/helpers-generator.ts). Any precedence change MUST land in all three.
|
|
249
|
+
*/
|
|
250
|
+
function cmdMode(ctx) {
|
|
251
|
+
// MUST be passed as `--args=<value>`, not `--args <value>`: the flag parser
|
|
252
|
+
// only consumes a following token as a value when it does not start with `-`,
|
|
253
|
+
// so `--args "-sd 42"` would silently degrade to `args=true` and resolve every
|
|
254
|
+
// run to the bare config default. The `=` form bypasses that lookahead.
|
|
255
|
+
// parseValue may coerce a bare numeric ("42") to a number — coerce back.
|
|
256
|
+
const flagArgs = ctx.flags?.args;
|
|
257
|
+
const raw = (typeof flagArgs === 'string' || typeof flagArgs === 'number')
|
|
258
|
+
? String(flagArgs)
|
|
259
|
+
: (ctx.args ?? []).slice(1).join(' ');
|
|
260
|
+
// Tokenize on whitespace so `-sd` is matched as a whole token — it is NOT
|
|
261
|
+
// `-s` (swarm) + `d`.
|
|
262
|
+
const tokens = raw.trim().split(/\s+/).filter(Boolean);
|
|
263
|
+
const has = (...names) => names.some((n) => tokens.includes(n));
|
|
264
|
+
const cfg = loadMofloConfig(projectRoot(ctx));
|
|
265
|
+
let workflow = 'full';
|
|
266
|
+
if (has('-wf', '--workflow'))
|
|
267
|
+
workflow = 'spell-engine';
|
|
268
|
+
else if (has('-r', '--research'))
|
|
269
|
+
workflow = 'research';
|
|
270
|
+
else if (has('-t', '--ticket'))
|
|
271
|
+
workflow = 'ticket';
|
|
272
|
+
const epicBranch = has('--epic-branch');
|
|
273
|
+
let sdd = false;
|
|
274
|
+
let sddSrc = 'default';
|
|
275
|
+
if (has('--no-sdd')) {
|
|
276
|
+
sdd = false;
|
|
277
|
+
sddSrc = 'flag';
|
|
278
|
+
}
|
|
279
|
+
else if (has('-sd', '--sdd')) {
|
|
280
|
+
sdd = true;
|
|
281
|
+
sddSrc = 'flag';
|
|
282
|
+
}
|
|
283
|
+
else if (cfg.sdd.default) {
|
|
284
|
+
sdd = true;
|
|
285
|
+
sddSrc = 'moflo.yaml sdd.default';
|
|
286
|
+
}
|
|
287
|
+
let verify;
|
|
288
|
+
let verifySrc = 'default';
|
|
289
|
+
if (has('--no-verify')) {
|
|
290
|
+
verify = false;
|
|
291
|
+
verifySrc = 'flag';
|
|
292
|
+
}
|
|
293
|
+
else if (has('-v', '--verify')) {
|
|
294
|
+
verify = true;
|
|
295
|
+
verifySrc = 'flag';
|
|
296
|
+
}
|
|
297
|
+
else if (!cfg.gates.verify_before_done) {
|
|
298
|
+
verify = false;
|
|
299
|
+
verifySrc = 'moflo.yaml gates.verify_before_done';
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
verify = true;
|
|
303
|
+
}
|
|
304
|
+
// --sdd implies --verify: a spec/plan without an enforced verify step drifts.
|
|
305
|
+
if (sdd && !verify && verifySrc !== 'flag')
|
|
306
|
+
verify = true;
|
|
307
|
+
let merge = false;
|
|
308
|
+
let mergeSrc = 'default';
|
|
309
|
+
if (has('--no-merge')) {
|
|
310
|
+
merge = false;
|
|
311
|
+
mergeSrc = 'flag';
|
|
312
|
+
}
|
|
313
|
+
else if (has('-m', '--merge')) {
|
|
314
|
+
merge = true;
|
|
315
|
+
mergeSrc = 'flag';
|
|
316
|
+
}
|
|
317
|
+
else if (cfg.merge.auto) {
|
|
318
|
+
merge = true;
|
|
319
|
+
mergeSrc = 'moflo.yaml merge.auto';
|
|
320
|
+
}
|
|
321
|
+
// Applicability: -t/-r never implement, so verify is a no-op there; -r and -wf
|
|
322
|
+
// produce no spec artifacts (in -t the spec/plan goes INTO the ticket, so sdd
|
|
323
|
+
// stays on). Only a full, non-epic-branch run opens a PR to merge.
|
|
324
|
+
// Re-attribute anything applicability turned off — a false must never carry the
|
|
325
|
+
// source of the value it no longer has. SYNC: mirrors bin/gate.cjs.
|
|
326
|
+
if (workflow === 'ticket' || workflow === 'research') {
|
|
327
|
+
if (verify)
|
|
328
|
+
verifySrc = `${workflow} mode does not implement`;
|
|
329
|
+
verify = false;
|
|
330
|
+
}
|
|
331
|
+
if (workflow === 'research' || workflow === 'spell-engine') {
|
|
332
|
+
if (sdd)
|
|
333
|
+
sddSrc = `${workflow} mode produces no spec artifacts`;
|
|
334
|
+
sdd = false;
|
|
335
|
+
}
|
|
336
|
+
if (workflow !== 'full' || epicBranch) {
|
|
337
|
+
if (merge)
|
|
338
|
+
mergeSrc = epicBranch ? '--epic-branch owns merging' : `${workflow} mode opens no PR`;
|
|
339
|
+
merge = false;
|
|
340
|
+
}
|
|
341
|
+
console.log(JSON.stringify({ workflow, sdd, verify, merge, sddSrc, verifySrc, mergeSrc }, null, 2));
|
|
342
|
+
return { success: true };
|
|
343
|
+
}
|
|
229
344
|
function cmdPath(ctx) {
|
|
230
345
|
const root = projectRoot(ctx);
|
|
231
346
|
const slug = slugify(ctx.args[1] || '');
|
|
@@ -294,7 +409,8 @@ Spec-Driven Development artifacts (.moflo/specs/<slug>/{spec,plan}.md):
|
|
|
294
409
|
list List every spec slug
|
|
295
410
|
path <slug> [spec|plan] Print the artifact path
|
|
296
411
|
embed <slug> Print spec+plan as a collapsible block for the PR body
|
|
297
|
-
index Re-index specs into memory now
|
|
412
|
+
index Re-index specs into memory now
|
|
413
|
+
mode --args "<flo args>" Resolve sdd/verify/merge for a /flo run as JSON (moflo.yaml + flags)`;
|
|
298
414
|
const sddCommand = {
|
|
299
415
|
name: 'sdd',
|
|
300
416
|
description: 'Spec-Driven Development artifacts (spec → plan → implement → verify)',
|
|
@@ -322,6 +438,8 @@ const sddCommand = {
|
|
|
322
438
|
return cmdStatus(ctx);
|
|
323
439
|
case 'list':
|
|
324
440
|
return cmdList(ctx);
|
|
441
|
+
case 'mode':
|
|
442
|
+
return cmdMode(ctx);
|
|
325
443
|
case 'path':
|
|
326
444
|
return cmdPath(ctx);
|
|
327
445
|
case 'embed':
|
|
@@ -293,6 +293,18 @@ function loadSddConfig() {
|
|
|
293
293
|
return out;
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
// #1285 — parse the top-level merge: block. Block-scoped like loadSddConfig.
|
|
297
|
+
// SYNC: mirrors bin/gate.cjs loadMergeConfig.
|
|
298
|
+
function loadMergeConfig() {
|
|
299
|
+
var out = { auto: false };
|
|
300
|
+
var content = MOFLO_YAML;
|
|
301
|
+
if (!content) return out;
|
|
302
|
+
var block = content.match(/^merge:[ \\t]*\\r?\\n((?:[ \\t]+.*(?:\\r?\\n|$))*)/m);
|
|
303
|
+
if (!block) return out;
|
|
304
|
+
if (/^\\s*auto:\\s*true\\b/im.test(block[1])) out.auto = true;
|
|
305
|
+
return out;
|
|
306
|
+
}
|
|
307
|
+
|
|
296
308
|
// #1297 — read moflo.yaml once (both loaders parse it; gate fires on every
|
|
297
309
|
// Write/Edit). SYNC: mirrors bin/gate.cjs readMofloYaml.
|
|
298
310
|
function readMofloYaml() {
|
|
@@ -303,6 +315,7 @@ var MOFLO_YAML = readMofloYaml();
|
|
|
303
315
|
|
|
304
316
|
var config = loadGateConfig();
|
|
305
317
|
var sddConf = loadSddConfig();
|
|
318
|
+
var mergeConf = loadMergeConfig();
|
|
306
319
|
var command = process.argv[2];
|
|
307
320
|
|
|
308
321
|
var EXEMPT = ['.claude/', '.claude\\\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules', 'moflo.yaml'];
|
|
@@ -388,13 +401,58 @@ function detectFlMode(promptText) {
|
|
|
388
401
|
return null;
|
|
389
402
|
}
|
|
390
403
|
|
|
391
|
-
//
|
|
392
|
-
|
|
404
|
+
// Resolve ALL /flo run modifiers from the prompt + moflo.yaml. Single source of
|
|
405
|
+
// truth for gate arming AND the authoritative announcement below — a second
|
|
406
|
+
// implementation is how sdd.default got silently ignored. Precedence per key:
|
|
407
|
+
// --no-X > -x/--X > moflo.yaml > built-in (sdd opt-in, verify opt-out, merge
|
|
408
|
+
// opt-in). SYNC: mirrors bin/gate.cjs resolveFloRun.
|
|
409
|
+
function resolveFloRun(promptText) {
|
|
393
410
|
var p = promptText || '';
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (
|
|
397
|
-
|
|
411
|
+
var out = { isFlo: false, workflow: 'full', sdd: false, verify: false, merge: false,
|
|
412
|
+
sddSrc: 'default', verifySrc: 'default', mergeSrc: 'default' };
|
|
413
|
+
if (!/^\\s*\\/(?:fl|flo)\\b/i.test(p)) return out;
|
|
414
|
+
out.isFlo = true;
|
|
415
|
+
|
|
416
|
+
if (/(?:^|\\s)(?:-wf|--workflow)\\b/.test(p)) out.workflow = 'spell-engine';
|
|
417
|
+
else if (/(?:^|\\s)(?:-r|--research)\\b/.test(p)) out.workflow = 'research';
|
|
418
|
+
else if (/(?:^|\\s)(?:-t|--ticket)\\b/.test(p)) out.workflow = 'ticket';
|
|
419
|
+
var epicBranch = /(?:^|\\s)--epic-branch\\b/.test(p);
|
|
420
|
+
|
|
421
|
+
if (/(?:^|\\s)--no-sdd\\b/.test(p)) { out.sdd = false; out.sddSrc = 'flag'; }
|
|
422
|
+
else if (/(?:^|\\s)(?:-sd|--sdd)\\b/.test(p)) { out.sdd = true; out.sddSrc = 'flag'; }
|
|
423
|
+
else if (sddConf.default) { out.sdd = true; out.sddSrc = 'moflo.yaml sdd.default'; }
|
|
424
|
+
|
|
425
|
+
if (/(?:^|\\s)--no-verify\\b/.test(p)) { out.verify = false; out.verifySrc = 'flag'; }
|
|
426
|
+
else if (/(?:^|\\s)(?:-v|--verify)\\b/.test(p)) { out.verify = true; out.verifySrc = 'flag'; }
|
|
427
|
+
else if (!config.verify_before_done) { out.verify = false; out.verifySrc = 'moflo.yaml gates.verify_before_done'; }
|
|
428
|
+
else { out.verify = true; out.verifySrc = 'default'; }
|
|
429
|
+
if (out.sdd && !out.verify && out.verifySrc !== 'flag') out.verify = true;
|
|
430
|
+
|
|
431
|
+
if (/(?:^|\\s)--no-merge\\b/.test(p)) { out.merge = false; out.mergeSrc = 'flag'; }
|
|
432
|
+
else if (/(?:^|\\s)(?:-m|--merge)\\b/.test(p)) { out.merge = true; out.mergeSrc = 'flag'; }
|
|
433
|
+
else if (mergeConf.auto) { out.merge = true; out.mergeSrc = 'moflo.yaml merge.auto'; }
|
|
434
|
+
|
|
435
|
+
// Re-attribute anything applicability turned off — a false must never carry
|
|
436
|
+
// the source of the value it no longer has. SYNC: mirrors bin/gate.cjs.
|
|
437
|
+
if (out.workflow === 'ticket' || out.workflow === 'research') {
|
|
438
|
+
if (out.verify) out.verifySrc = out.workflow + ' mode does not implement';
|
|
439
|
+
out.verify = false;
|
|
440
|
+
}
|
|
441
|
+
if (out.workflow === 'research' || out.workflow === 'spell-engine') {
|
|
442
|
+
if (out.sdd) out.sddSrc = out.workflow + ' mode produces no spec artifacts';
|
|
443
|
+
out.sdd = false;
|
|
444
|
+
}
|
|
445
|
+
if (out.workflow !== 'full' || epicBranch) {
|
|
446
|
+
if (out.merge) out.mergeSrc = epicBranch ? '--epic-branch owns merging' : out.workflow + ' mode opens no PR';
|
|
447
|
+
out.merge = false;
|
|
448
|
+
}
|
|
449
|
+
return out;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// #1297 — arm the SDD implement gate from a /flo prompt. Thin wrapper so the
|
|
453
|
+
// armed decision and the announced decision can never disagree.
|
|
454
|
+
function detectSddMode(promptText) {
|
|
455
|
+
return resolveFloRun(promptText).sdd;
|
|
398
456
|
}
|
|
399
457
|
|
|
400
458
|
// SDD specs-root resolution + artifact helpers for check-before-implement.
|
|
@@ -779,6 +837,30 @@ switch (command) {
|
|
|
779
837
|
applyPromptStateReset(s, prompt);
|
|
780
838
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
781
839
|
writeState(s);
|
|
840
|
+
// Announce the resolved /flo run modifiers. moflo.yaml was already parsed in
|
|
841
|
+
// THIS process (fresh per prompt — a git pull or mid-session yaml edit is
|
|
842
|
+
// picked up automatically, no cache to invalidate), so this costs no extra
|
|
843
|
+
// read. SYNC: mirrors bin/gate.cjs prompt-reminder.
|
|
844
|
+
var floRun = resolveFloRun(prompt);
|
|
845
|
+
if (floRun.isFlo) {
|
|
846
|
+
console.log(
|
|
847
|
+
'[moflo] /flo run modes (AUTHORITATIVE — use verbatim; do NOT re-derive from the skill defaults): ' +
|
|
848
|
+
'sdd=' + (floRun.sdd ? 'ON' : 'off') +
|
|
849
|
+
' verify=' + (floRun.verify ? 'ON' : 'off') +
|
|
850
|
+
' merge=' + (floRun.merge ? 'ON' : 'off') +
|
|
851
|
+
' [workflow=' + floRun.workflow + ']'
|
|
852
|
+
);
|
|
853
|
+
if (floRun.sdd && floRun.sddSrc !== 'flag') {
|
|
854
|
+
console.log(
|
|
855
|
+
'[moflo] sdd is ON via ' + floRun.sddSrc + ' — the spec→plan→implement→verify cycle is ' +
|
|
856
|
+
'MANDATORY this run. Author the spec before editing source (the sdd_gate blocks source ' +
|
|
857
|
+
'Write/Edit until a reviewed plan exists). One-off opt out: re-run with --no-sdd.'
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
if (floRun.merge && floRun.mergeSrc !== 'flag') {
|
|
861
|
+
console.log('[moflo] merge is ON via ' + floRun.mergeSrc + ' — the PR will be auto-merged. Opt out: --no-merge.');
|
|
862
|
+
}
|
|
863
|
+
}
|
|
782
864
|
if (config.context_tracking) {
|
|
783
865
|
var ic = s.interactionCount;
|
|
784
866
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.12.
|
|
3
|
+
"version": "4.12.1",
|
|
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.
|
|
98
|
+
"moflo": "^4.12.0",
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
101
|
"vitest": "^4.0.0"
|