moflo 4.9.14 → 4.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/guidance/shipped/moflo-core-guidance.md +1 -0
- package/.claude/guidance/shipped/moflo-verbose-command-filtering.md +45 -0
- package/.claude/helpers/simplify-classify.cjs +211 -0
- package/.claude/skills/eldar/SKILL.md +23 -21
- package/.claude/skills/guidance/SKILL.md +48 -6
- package/.claude/skills/spell-schedule/SKILL.md +1 -1
- package/dist/src/cli/commands/doctor-checks-deep.js +40 -2
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
|
@@ -307,3 +307,4 @@ See `moflo-memory-strategy.md` for memory-specific troubleshooting.
|
|
|
307
307
|
- `.claude/guidance/shipped/moflo-session-start.md` — Complete session-start lifecycle (DB heal, sync, migrations, daemon)
|
|
308
308
|
- `.claude/guidance/shipped/moflo-settings-injection.md` — What moflo writes into `.claude/` and how surgical self-heal works
|
|
309
309
|
- `.claude/guidance/shipped/moflo-cross-platform.md` — Windows/macOS/Linux portability rules for any code change
|
|
310
|
+
- `.claude/guidance/shipped/moflo-verbose-command-filtering.md` — Filter long verbose commands at the source; never tee-then-grep
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Verbose Command Filtering — Filter at Source, Never Tee-Then-Read
|
|
2
|
+
|
|
3
|
+
**Purpose:** Pipe long verbose commands (smoke runs, full test suites, builds with `--verbose`) through a filter at execution time so only relevant lines reach the model context. Never tee output to disk and `tail`/`grep` it later — every follow-up read re-loads the full file into context (~5K tokens per round-trip).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## The Rule
|
|
8
|
+
|
|
9
|
+
| Pattern | Verdict |
|
|
10
|
+
|---------|---------|
|
|
11
|
+
| `cmd 2>&1 \| grep -E "FAIL\|Summary"` (run_in_background) | ✅ Filter at source |
|
|
12
|
+
| `cmd 2>&1 \| tee .tmp.log` then later `tail`/`grep` `.tmp.log` | ❌ Tee-then-read |
|
|
13
|
+
|
|
14
|
+
Tee-then-read is the silent context killer. The Bash tool surfaces stdout into context, so each follow-up `grep`/`tail` of a tee'd file re-reads the file fresh on every call. Three follow-ups burn 15K+ tokens before any decision lands. Filtering at source emits the matching lines once.
|
|
15
|
+
|
|
16
|
+
## When to Apply
|
|
17
|
+
|
|
18
|
+
Smoke harness runs, full `vitest`/`jest` suites, builds with `--verbose`, anything passing `--trace`, any `node ... --verbose` invocation. If you genuinely need the full log for post-mortem, write it to disk but inspect it OUTSIDE the model loop (have the user open it, attach it to an issue) — do NOT pipe a tee'd file back through Bash.
|
|
19
|
+
|
|
20
|
+
## Concrete Examples
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# ✅ Smoke harness
|
|
24
|
+
node harness/consumer-smoke/run.mjs 2>&1 | grep -E "FAIL|Summary|Zombie"
|
|
25
|
+
# ❌ node harness/consumer-smoke/run.mjs 2>&1 | tee .tmp.log; tail .tmp.log
|
|
26
|
+
|
|
27
|
+
# ✅ Vitest full suite
|
|
28
|
+
npm test -- --reporter=verbose 2>&1 | grep -E "FAIL|✗|Error:"
|
|
29
|
+
# ❌ npm test 2>&1 | tee .test.log; grep FAIL .test.log
|
|
30
|
+
|
|
31
|
+
# ✅ Build with verbose
|
|
32
|
+
npm run build -- --verbose 2>&1 | grep -E "error TS|Failed|Cannot find"
|
|
33
|
+
# ❌ npm run build 2>&1 | tee .build.log; grep "error TS" .build.log
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Why It Matters
|
|
37
|
+
|
|
38
|
+
Case study: issue #903 burned ~25K tokens across 5 tee-then-grep round-trips where a single grep-at-source would have surfaced the same signal once. Filtering at source is not an optimization — it is the default shape for any verbose command whose full output you do not need in your context.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## See Also
|
|
43
|
+
|
|
44
|
+
- `.claude/guidance/shipped/moflo-core-guidance.md` — Hub for moflo's CLI/MCP surface and runtime conventions
|
|
45
|
+
- `.claude/guidance/shipped/moflo-memory-strategy.md` — Companion rules on RAG indexing and context discipline
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* /simplify diff classifier — issue #908.
|
|
4
|
+
*
|
|
5
|
+
* Decides which review tier the current diff warrants and returns a JSON
|
|
6
|
+
* dispatch decision. The /simplify skill MUST call this first so routing is
|
|
7
|
+
* deterministic and unit-testable instead of a prose decision Claude makes
|
|
8
|
+
* over and over per run.
|
|
9
|
+
*
|
|
10
|
+
* Rule (per user direction): default to single-agent Sonnet review. Only
|
|
11
|
+
* escalate to a 3-agent fan-out when diff signals genuinely warrant it.
|
|
12
|
+
* Opus is never selected — the existing skill already documents that.
|
|
13
|
+
*
|
|
14
|
+
* Outputs JSON:
|
|
15
|
+
* {
|
|
16
|
+
* "tier": "TRIVIAL" | "SMALL" | "NORMAL",
|
|
17
|
+
* "model": "sonnet",
|
|
18
|
+
* "agentCount": 0 | 1 | 3,
|
|
19
|
+
* "reasoning": [string, ...],
|
|
20
|
+
* "stats": { added, deleted, fileCount, declAdded, declRemoved, ... }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* node bin/simplify-classify.cjs [--base main]
|
|
25
|
+
* node bin/simplify-classify.cjs --diff <unified-diff-on-stdin>
|
|
26
|
+
*
|
|
27
|
+
* The --diff stdin form exists so unit tests can drive the classifier
|
|
28
|
+
* with synthetic diffs (no git repo required).
|
|
29
|
+
*/
|
|
30
|
+
'use strict';
|
|
31
|
+
|
|
32
|
+
const { execSync } = require('child_process');
|
|
33
|
+
|
|
34
|
+
// Paths where new logic warrants the 3-agent fan-out (issue #908).
|
|
35
|
+
// Mechanical edits inside these paths are still SMALL; only adding/removing
|
|
36
|
+
// declarations triggers escalation.
|
|
37
|
+
const SECURITY_PATHS = [
|
|
38
|
+
/(?:^|[\\\/])aidefence[\\\/]/i,
|
|
39
|
+
/(?:^|[\\\/])swarm[\\\/]consensus[\\\/]/i,
|
|
40
|
+
/(?:^|[\\\/])hooks?[\\\/](?:handlers?|gate|wiring)/i,
|
|
41
|
+
/(?:^|[\\\/])services[\\\/]daemon-lock\.ts$/i,
|
|
42
|
+
/(?:^|[\\\/])bin[\\\/]gate\./i,
|
|
43
|
+
/(?:^|[\\\/])bin[\\\/]session-start-launcher\./i,
|
|
44
|
+
/(?:^|[\\\/])\.claude[\\\/]helpers[\\\/]gate/i,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
function safeExec(cmd) {
|
|
48
|
+
try { return execSync(cmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }); }
|
|
49
|
+
catch { return ''; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readDiffFromGit(base) {
|
|
53
|
+
// Combined diff: committed-since-base + working-tree
|
|
54
|
+
const committed = safeExec(`git diff ${base}...HEAD`);
|
|
55
|
+
const working = safeExec('git diff HEAD');
|
|
56
|
+
return committed + (working ? '\n' + working : '');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse a unified-diff string into per-file stats and aggregate signals.
|
|
61
|
+
* No git/I/O — pure function over the diff text. Test-friendly.
|
|
62
|
+
*/
|
|
63
|
+
function parseDiff(diff) {
|
|
64
|
+
const lines = diff.split('\n');
|
|
65
|
+
const files = new Map(); // filename → { added, deleted, declAdded, declRemoved, isNew, isRenamed }
|
|
66
|
+
let current = null;
|
|
67
|
+
|
|
68
|
+
// Match function/class/export-const-arrow/method declarations being
|
|
69
|
+
// added or removed. Conservative — biased toward false negatives so we
|
|
70
|
+
// don't over-escalate.
|
|
71
|
+
const DECL_RE = /^(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type)\s+\w/;
|
|
72
|
+
const ARROW_DECL_RE = /^(?:export\s+)?(?:const|let|var)\s+\w+\s*[:=].*=>\s*\{?$/;
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
const ln = lines[i];
|
|
76
|
+
|
|
77
|
+
// File header: `diff --git a/path b/path`
|
|
78
|
+
let m = ln.match(/^diff --git (?:a\/)?(.+?) (?:b\/)?(.+)$/);
|
|
79
|
+
if (m) {
|
|
80
|
+
const filename = m[2];
|
|
81
|
+
current = { filename, added: 0, deleted: 0, declAdded: 0, declRemoved: 0, isNew: false, isRenamed: false };
|
|
82
|
+
files.set(filename, current);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!current) continue;
|
|
86
|
+
|
|
87
|
+
if (ln.startsWith('new file mode')) current.isNew = true;
|
|
88
|
+
if (ln.startsWith('rename from') || ln.startsWith('rename to') || ln.startsWith('similarity index')) current.isRenamed = true;
|
|
89
|
+
|
|
90
|
+
// Skip diff headers
|
|
91
|
+
if (ln.startsWith('+++') || ln.startsWith('---') || ln.startsWith('@@') || ln.startsWith('index ')) continue;
|
|
92
|
+
|
|
93
|
+
if (ln.startsWith('+') && !ln.startsWith('+++')) {
|
|
94
|
+
current.added++;
|
|
95
|
+
const body = ln.slice(1).trim();
|
|
96
|
+
if (DECL_RE.test(body) || ARROW_DECL_RE.test(body)) current.declAdded++;
|
|
97
|
+
} else if (ln.startsWith('-') && !ln.startsWith('---')) {
|
|
98
|
+
current.deleted++;
|
|
99
|
+
const body = ln.slice(1).trim();
|
|
100
|
+
if (DECL_RE.test(body) || ARROW_DECL_RE.test(body)) current.declRemoved++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Aggregate
|
|
105
|
+
let added = 0, deleted = 0, declAdded = 0, declRemoved = 0;
|
|
106
|
+
let newFiles = 0, renamedFiles = 0;
|
|
107
|
+
let securityHit = false;
|
|
108
|
+
for (const f of files.values()) {
|
|
109
|
+
added += f.added;
|
|
110
|
+
deleted += f.deleted;
|
|
111
|
+
declAdded += f.declAdded;
|
|
112
|
+
declRemoved += f.declRemoved;
|
|
113
|
+
if (f.isNew) newFiles++;
|
|
114
|
+
if (f.isRenamed) renamedFiles++;
|
|
115
|
+
if (SECURITY_PATHS.some(rx => rx.test(f.filename))) securityHit = true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
added, deleted, declAdded, declRemoved,
|
|
120
|
+
netDecls: declAdded - declRemoved,
|
|
121
|
+
fileCount: files.size,
|
|
122
|
+
newFiles, renamedFiles,
|
|
123
|
+
securityHit,
|
|
124
|
+
files: [...files.keys()],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Pure decision function. Takes parsed stats, returns dispatch decision.
|
|
130
|
+
* No I/O. Easy to unit-test with synthetic stats.
|
|
131
|
+
*/
|
|
132
|
+
function decide(stats) {
|
|
133
|
+
const reasoning = [];
|
|
134
|
+
const totalChange = stats.added + stats.deleted;
|
|
135
|
+
|
|
136
|
+
if (totalChange === 0) {
|
|
137
|
+
return { tier: 'TRIVIAL', model: 'sonnet', agentCount: 0, reasoning: ['empty diff — nothing to review'], stats };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// TRIVIAL: tiny diff, no declarations changed
|
|
141
|
+
if (totalChange <= 10 && stats.fileCount <= 1 && stats.netDecls === 0 && stats.declAdded === 0 && stats.declRemoved === 0) {
|
|
142
|
+
reasoning.push(`≤10 LOC in 1 file with no declaration changes`);
|
|
143
|
+
return { tier: 'TRIVIAL', model: 'sonnet', agentCount: 0, reasoning, stats };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Mechanical relocation detection — the #906 case.
|
|
147
|
+
// If declarations were both ADDED and REMOVED at roughly matching rates,
|
|
148
|
+
// it's a structural move, not net-new logic. Judge by declaration balance,
|
|
149
|
+
// not raw LOC balance — formatting/blank-line differences between source
|
|
150
|
+
// and destination files easily push raw LOC out of balance even when the
|
|
151
|
+
// semantic change is purely "moved 5 functions across 5 new files".
|
|
152
|
+
// Mechanical relocations are SMALL even when many files / many lines.
|
|
153
|
+
const declTouched = stats.declAdded + stats.declRemoved;
|
|
154
|
+
const isMostlyRelocation = stats.declAdded >= 2
|
|
155
|
+
&& stats.declRemoved >= 2
|
|
156
|
+
&& Math.abs(stats.netDecls) <= Math.max(2, Math.floor(declTouched * 0.30));
|
|
157
|
+
|
|
158
|
+
if (isMostlyRelocation) {
|
|
159
|
+
reasoning.push(
|
|
160
|
+
`mostly relocation: ${stats.declAdded} decls added, ${stats.declRemoved} removed, net ${stats.netDecls >= 0 ? '+' : ''}${stats.netDecls}`,
|
|
161
|
+
);
|
|
162
|
+
return { tier: 'SMALL', model: 'sonnet', agentCount: 1, reasoning, stats };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Escalation triggers — any one trips NORMAL (3 agents).
|
|
166
|
+
// Always Sonnet — Opus is never the right model for /simplify per skill rule.
|
|
167
|
+
const triggers = [];
|
|
168
|
+
if (totalChange > 500) triggers.push(`>500 LOC changed (${totalChange})`);
|
|
169
|
+
if (stats.fileCount >= 5 && stats.netDecls >= 3) triggers.push(`${stats.fileCount} files with ${stats.netDecls} net new declarations`);
|
|
170
|
+
if (stats.securityHit && stats.netDecls > 0) triggers.push('security-sensitive path with new logic');
|
|
171
|
+
if (stats.newFiles >= 3 && stats.declAdded >= 5) triggers.push(`${stats.newFiles} new files with ${stats.declAdded} new declarations`);
|
|
172
|
+
|
|
173
|
+
if (triggers.length > 0) {
|
|
174
|
+
return { tier: 'NORMAL', model: 'sonnet', agentCount: 3, reasoning: triggers, stats };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Default: SMALL — single sonnet agent
|
|
178
|
+
reasoning.push(`small/medium diff: ${totalChange} LOC across ${stats.fileCount} file(s), +${stats.declAdded}/-${stats.declRemoved} decls`);
|
|
179
|
+
return { tier: 'SMALL', model: 'sonnet', agentCount: 1, reasoning, stats };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function classifyDiff(diffText) {
|
|
183
|
+
return decide(parseDiff(diffText));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function classifyFromGit(base = 'main') {
|
|
187
|
+
return classifyDiff(readDiffFromGit(base));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (require.main === module) {
|
|
191
|
+
const args = process.argv.slice(2);
|
|
192
|
+
const baseIdx = args.indexOf('--base');
|
|
193
|
+
const base = baseIdx >= 0 ? args[baseIdx + 1] : 'main';
|
|
194
|
+
const stdinDiff = args.includes('--diff') || args.includes('--stdin');
|
|
195
|
+
|
|
196
|
+
let result;
|
|
197
|
+
if (stdinDiff) {
|
|
198
|
+
let buf = '';
|
|
199
|
+
process.stdin.setEncoding('utf-8');
|
|
200
|
+
process.stdin.on('data', (d) => { buf += d; });
|
|
201
|
+
process.stdin.on('end', () => {
|
|
202
|
+
result = classifyDiff(buf);
|
|
203
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
result = classifyFromGit(base);
|
|
207
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = { parseDiff, decide, classifyDiff, classifyFromGit };
|
|
@@ -90,17 +90,24 @@ Count `.md` files under `.claude/guidance/` (recursive). Severity table:
|
|
|
90
90
|
| 3–10 | info |
|
|
91
91
|
| 11+ | info |
|
|
92
92
|
|
|
93
|
-
### 1g. Guidance Structure
|
|
93
|
+
### 1g. Guidance Structure — MANDATORY when guidance docs exist
|
|
94
94
|
|
|
95
|
-
|
|
95
|
+
**This step is not optional.** If 1f found ≥1 guidance file, you MUST invoke `/guidance -a` via the `Skill` tool *inline, during this audit run, before rendering the report.* Do not defer it ("rerun separately if you want"), do not skip it because the corpus is large, do not substitute a hand-rolled grep pass — that defeats the single-source-of-truth contract.
|
|
96
96
|
|
|
97
|
-
-
|
|
98
|
-
- Has `## See Also` at end
|
|
99
|
-
- Under 500 lines
|
|
100
|
-
- H2 headings are specific (not "Overview", "Configuration", "Examples")
|
|
101
|
-
- No hedged language in rule contexts (`should`, `might`, `consider`)
|
|
97
|
+
The /guidance skill enforces the universal rules from `.claude/guidance/shipped/moflo-guidance-rules.md` (Purpose lines, See Also, generic H2s, hedged language, 500-line cap, RAG chunking) and is the single source of truth for those checks — never re-implement them here.
|
|
102
98
|
|
|
103
|
-
|
|
99
|
+
If `/guidance -a` is genuinely too expensive (50+ files AND user explicitly asks for a fast read), skip it only after asking and surface the skip explicitly in the report (`Guidance structure | skipped at user request | warn`). Default behaviour is always to run it.
|
|
100
|
+
|
|
101
|
+
Fold the result into the Eldar report under the "Guidance structure" row:
|
|
102
|
+
|
|
103
|
+
| Outcome of `/guidance -a` | Eldar row severity |
|
|
104
|
+
|---------------------------|--------------------|
|
|
105
|
+
| 0 files with issues | ok |
|
|
106
|
+
| 1–2 files with issues | info |
|
|
107
|
+
| 3+ files with issues | warn |
|
|
108
|
+
| `/guidance` itself errors | warn — quote the error verbatim so the user can fix the offending file before re-running |
|
|
109
|
+
|
|
110
|
+
When the user is in `--fix` mode and chooses guidance fixes from the triage menu (3b), the same /guidance skill is the handoff target — so the audit and the fix flow share one implementation.
|
|
104
111
|
|
|
105
112
|
### 1h. Memory Health
|
|
106
113
|
|
|
@@ -150,21 +157,13 @@ Glob — { pattern: ".claude/agents/**/*.md" }
|
|
|
150
157
|
|
|
151
158
|
Count the result. `info` if 0 (no project-specific subagents — user is relying entirely on built-ins).
|
|
152
159
|
|
|
153
|
-
### 1m. Stack → Guidance Cross-Reference (
|
|
154
|
-
|
|
155
|
-
Detect the project's stack from manifests:
|
|
160
|
+
### 1m. Stack → Guidance Cross-Reference (delegated to /guidance)
|
|
156
161
|
|
|
157
|
-
|
|
158
|
-
|----------|----------------|
|
|
159
|
-
| `package.json` deps | Node — inspect for React, Next, Drizzle, Prisma, Express, NestJS, Vite, etc. |
|
|
160
|
-
| `pyproject.toml` / `requirements.txt` | Python — Django, FastAPI, SQLAlchemy, etc. |
|
|
161
|
-
| `Cargo.toml` | Rust — axum, tokio, sqlx, etc. |
|
|
162
|
-
| `go.mod` | Go — gin, sqlc, gorm, etc. |
|
|
163
|
-
| `Gemfile` | Ruby — Rails, Sidekiq, etc. |
|
|
162
|
+
Gap analysis (which codebase concerns lack a guidance doc) is the **/guidance skill's** responsibility — step 3b of `/guidance -a`. Since 1g already runs `/guidance -a` inline, do **not** re-implement gap detection here. Instead, fold the gap findings from /guidance's output into the Eldar report under the same "Stack → guidance" category.
|
|
164
163
|
|
|
165
|
-
|
|
164
|
+
Each gap finding from /guidance becomes one row in the Eldar report. Severity carries through from /guidance (warn/info per its severity table).
|
|
166
165
|
|
|
167
|
-
|
|
166
|
+
**Why delegated:** keeping a single source of truth — /guidance owns both the structural rule audit and the gap analysis, /eldar surfaces the results in its broader project audit. If /eldar duplicated gap detection, the two would drift.
|
|
168
167
|
|
|
169
168
|
### 1n. Anti-Pattern from History (best-effort, optional)
|
|
170
169
|
|
|
@@ -204,7 +203,9 @@ TOP 3 RECOMMENDATIONS
|
|
|
204
203
|
2. Add Drizzle conventions guidance (info — high leverage)
|
|
205
204
|
You use Drizzle ORM but have no DB-conventions doc. This is the
|
|
206
205
|
single highest-leverage gap for getting Claude to write idiomatic
|
|
207
|
-
queries and migrations in your codebase.
|
|
206
|
+
queries and migrations in your codebase. /guidance -a (run inline
|
|
207
|
+
in step 1g) flagged 3 existing docs with structural issues; pick
|
|
208
|
+
one to fix alongside this new one.
|
|
208
209
|
See: .claude/guidance/shipped/moflo-guidance-rules.md
|
|
209
210
|
|
|
210
211
|
3. Run `flo healer --fix` (warn)
|
|
@@ -295,6 +296,7 @@ Never leave the user without a clear next step.
|
|
|
295
296
|
- **Portable only.** This skill ships to consumers via `.claude/skills/**/*.md` in the package files array. Never assume moflo source paths or moflo-internal state.
|
|
296
297
|
- **No kitchen sink.** The audit checklist is locked at the categories above. New checks require a specific portable benefit and an issue to discuss them.
|
|
297
298
|
- **Read-only by default.** `/eldar` (no flag) never writes. Only `--fix` writes, and only with per-finding confirmation.
|
|
299
|
+
- **Step 1g is mandatory, not optional.** Whenever `.claude/guidance/` has at least one file, `/guidance -a` runs inline as part of every audit. Saying "rerun /guidance -a separately if you want a structural pass" is a defect, not a feature — the user already asked for the structural pass by typing /eldar.
|
|
298
300
|
- **Hand off to specialists.** `/guidance` for guidance authoring, `flo healer --fix` for setup repair, `flo init --upgrade` for wiring. The Eldar route, they don't reimplement.
|
|
299
301
|
|
|
300
302
|
## See Also
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: guidance
|
|
3
3
|
description: Add, edit, or audit guidance docs in this project's .claude/guidance/ directory following moflo's universal guidance rules. Default mode walks the user through one doc (creating or improving it); the -a flag audits every doc in the directory and offers per-file improvements.
|
|
4
|
-
arguments: "[-a]
|
|
4
|
+
arguments: "[-a] <topic-or-path>"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# /guidance — Author and audit project guidance
|
|
@@ -15,7 +15,7 @@ Help the user write, edit, or audit guidance files in their `.claude/guidance/`
|
|
|
15
15
|
| Mode | Trigger | What it does |
|
|
16
16
|
|------|---------|--------------|
|
|
17
17
|
| Single-doc | no flag, optional `<topic-or-path>` arg | Walk the user through creating one new doc OR improving one existing doc |
|
|
18
|
-
| Audit | `-a` flag |
|
|
18
|
+
| Audit | `-a` flag | Two passes over `.claude/guidance/`: (1) **structural audit** scoring each existing doc against the universal rules, (2) **gap analysis** scanning the codebase to identify high-leverage topics that *should* have a guidance doc but don't. Both feed one combined triage report. |
|
|
19
19
|
|
|
20
20
|
## Step 0 — Memory First
|
|
21
21
|
|
|
@@ -101,7 +101,11 @@ Then propose edits as concrete diffs — never rewrite the whole file unless the
|
|
|
101
101
|
|
|
102
102
|
## Step 3 — Audit Mode (`-a`)
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
Audit mode runs **two passes**, then merges them into one triage report. Both passes are mandatory — never skip one to save tokens. The user typed `-a` because they want both signals.
|
|
105
|
+
|
|
106
|
+
### 3a. Structural audit (existing-doc rule conformance)
|
|
107
|
+
|
|
108
|
+
Scan the guidance directory and score each `.md` against the universal rules. Walk the directory yourself with `Glob` and `Read`; do not delegate to a subagent for the audit itself unless the user has 30+ files.
|
|
105
109
|
|
|
106
110
|
For each `.md` file:
|
|
107
111
|
|
|
@@ -112,11 +116,49 @@ For each `.md` file:
|
|
|
112
116
|
5. Look for hedged language: `\b(should|might|consider|may want to)\b` in rule contexts
|
|
113
117
|
6. Detect prose preambles (>3 paragraphs between H1 and first H2 rule)
|
|
114
118
|
|
|
115
|
-
Render
|
|
119
|
+
Render a sortable table with one row per file, columns: file, lines, has-purpose, has-see-also, generic-headings count, hedged count. Highlight the worst offenders first.
|
|
120
|
+
|
|
121
|
+
**Note on `**Purpose:**` and `## See Also` regex:** ripgrep / Grep tool treats `**` as a glob escape and may return zero matches even when the markers are present. Use a plain string check (`Select-String` on Windows, `grep -F` elsewhere) or read the file and string-match in JS — never trust a zero count from a wildcard-ambiguous pattern without spot-checking one file.
|
|
122
|
+
|
|
123
|
+
### 3b. Gap analysis (what topics are missing)
|
|
124
|
+
|
|
125
|
+
Now look at *what isn't there*. Scan the codebase for high-leverage areas that lack a corresponding guidance doc, so Claude has nothing to follow when working in those areas.
|
|
126
|
+
|
|
127
|
+
**Detection sources** (read each that exists):
|
|
128
|
+
|
|
129
|
+
| Signal | What to learn |
|
|
130
|
+
|--------|---------------|
|
|
131
|
+
| `package.json` deps + devDeps | Frameworks/libraries the project relies on (React, Drizzle, Vitest, etc.) |
|
|
132
|
+
| `pyproject.toml` / `requirements.txt` / `Cargo.toml` / `go.mod` / `Gemfile` | Same idea, other ecosystems |
|
|
133
|
+
| Top-level source layout (`src/**`, `bin/**`, `scripts/**`, etc.) | Architectural concerns (e.g. a `daemon/` directory implies daemon architecture is a concern) |
|
|
134
|
+
| `.claude/helpers/`, `.claude/scripts/`, `.claude/hooks/` | Hook + helper authoring is in scope |
|
|
135
|
+
| MCP tool source (`mcp-tools/**`, `mcp-server/**`) | MCP tool authoring |
|
|
136
|
+
| Test directories | Testing conventions (load-bearing if specific patterns exist — e.g. golden-file tests, snapshot conventions) |
|
|
137
|
+
| `.github/workflows/` | CI/CD conventions |
|
|
138
|
+
| Recent `git log` for files repeatedly edited together | Cross-cutting concerns that need explicit guidance |
|
|
139
|
+
|
|
140
|
+
**Cross-reference:** for each detected concern, grep the existing `.claude/guidance/` corpus for keyword coverage. A topic is a **gap** if (a) the concern shows up in code (not just transitive deps) and (b) no existing guidance doc names it in the title or first H2.
|
|
141
|
+
|
|
142
|
+
**Severity table:**
|
|
143
|
+
|
|
144
|
+
| Concern type | Severity if unmatched |
|
|
145
|
+
|--------------|------------------------|
|
|
146
|
+
| Direct dep used pervasively (>10 files import it) | warn |
|
|
147
|
+
| Architectural directory with >5 files | warn |
|
|
148
|
+
| Direct dep used in 1–10 files | info |
|
|
149
|
+
| Helper/hook/MCP authoring surface with custom code | warn |
|
|
150
|
+
| CI/CD workflows beyond the standard ones | info |
|
|
151
|
+
| Cross-cutting concern from git-history co-change | info |
|
|
152
|
+
|
|
153
|
+
**Render gaps as a separate table** with columns: detected concern, evidence (file count or representative path), suggested doc filename, severity. Lead with the warns.
|
|
154
|
+
|
|
155
|
+
**Don't auto-write any new doc.** Surface the gap, name the concern, propose a filename — then ask the user which gaps (if any) they want to fill. The single-doc mode (Step 2) handles authoring once they pick.
|
|
156
|
+
|
|
157
|
+
### 3c. Combined triage and fixes
|
|
116
158
|
|
|
117
|
-
After
|
|
159
|
+
After both 3a and 3b, list the **top 3–5 priority items** across both passes in plain English. Mix them — a structural fix to an existing doc and a missing-doc gap can both make the top list. For each, explain WHY (rule citation for structural; concern + evidence for gaps) and propose either a per-file fix (3a) or a per-doc authoring flow (3b).
|
|
118
160
|
|
|
119
|
-
Ask the user which to apply, then walk through
|
|
161
|
+
Ask the user which to apply, then walk through chosen items one at a time. **Never apply audit fixes without explicit per-file confirmation** — guidance is high-leverage; silent edits are dangerous. **Never auto-create gap docs** — every new doc starts as a single-doc mode session with the user.
|
|
120
162
|
|
|
121
163
|
## Step 4 — After Editing
|
|
122
164
|
|
|
@@ -5,7 +5,7 @@ description: |
|
|
|
5
5
|
Use when the user wants to schedule, automate, or recurringly run one of THEIR spells locally —
|
|
6
6
|
e.g. "schedule the oap spell every hour", "run my audit spell every weekday at 9am", "fire X once tomorrow morning".
|
|
7
7
|
This is the LOCAL daemon path. For remote Anthropic-cloud agents, use /schedule instead.
|
|
8
|
-
arguments: "
|
|
8
|
+
arguments: "<spell-name-or-alias>"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# /spell-schedule — Schedule a Local Spell
|
|
@@ -432,6 +432,31 @@ const REQUIRED_GATE_CASES = [
|
|
|
432
432
|
// session-start-launcher.mjs in consumer projects without transitive failures.
|
|
433
433
|
import { REQUIRED_HOOK_WIRING } from '../services/hook-wiring.js';
|
|
434
434
|
export { REQUIRED_HOOK_WIRING };
|
|
435
|
+
/**
|
|
436
|
+
* Detect "expected pre-publish drift" — source `bin/gate.cjs` is ahead of the
|
|
437
|
+
* installed `node_modules/moflo/bin/gate.cjs`, but the deployed
|
|
438
|
+
* `.claude/helpers/gate.cjs` still matches the installed version. This is the
|
|
439
|
+
* steady state in the moflo dogfood repo while a PR has landed but no
|
|
440
|
+
* `npm install moflo@<new>` has rotated the package.
|
|
441
|
+
*
|
|
442
|
+
* Returns true only when both are true:
|
|
443
|
+
* - helper content equals installed bin content (helper is correctly synced
|
|
444
|
+
* to what's installed)
|
|
445
|
+
* - installed bin content differs from source bin content (source is ahead)
|
|
446
|
+
*
|
|
447
|
+
* If `node_modules/moflo/bin/gate.cjs` is missing (consumer never installed
|
|
448
|
+
* moflo, or path is unusual) we conservatively return false so other drift
|
|
449
|
+
* detection still applies.
|
|
450
|
+
*/
|
|
451
|
+
export function isExpectedPrePublishDrift(installedBinGate, helperContent, sourceBinContent) {
|
|
452
|
+
try {
|
|
453
|
+
const installedContent = readFileSync(installedBinGate, 'utf8');
|
|
454
|
+
return installedContent === helperContent && installedContent !== sourceBinContent;
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
435
460
|
/**
|
|
436
461
|
* Verify gate infrastructure health:
|
|
437
462
|
* 1. gate.cjs exists and contains all required cases
|
|
@@ -474,14 +499,27 @@ export async function checkGateHealth() {
|
|
|
474
499
|
issues.push(`gate.cjs missing cases: ${missingCases.join(', ')}`);
|
|
475
500
|
}
|
|
476
501
|
// 2. Check bin/gate.cjs sync
|
|
502
|
+
//
|
|
503
|
+
// The launcher syncs `node_modules/moflo/bin/gate.cjs` → `.claude/helpers/gate.cjs`
|
|
504
|
+
// on version change. Source `bin/gate.cjs` is only present in the moflo dogfood
|
|
505
|
+
// repo. During the dogfood publish window — between a PR landing and the next
|
|
506
|
+
// `npm install moflo@<new>` — source bin/ legitimately moves ahead of the
|
|
507
|
+
// installed bin/, while the helper continues to mirror the installed version.
|
|
508
|
+
// That's the expected steady state, not a bug; downgrade it to `warn` and skip
|
|
509
|
+
// the `fix` field so `--fix` doesn't paint a false success (#913).
|
|
477
510
|
const binGate = join(projectDir, 'bin', 'gate.cjs');
|
|
511
|
+
const installedBinGate = join(projectDir, 'node_modules', 'moflo', 'bin', 'gate.cjs');
|
|
478
512
|
if (existsSync(binGate)) {
|
|
479
513
|
try {
|
|
480
514
|
const binContent = readFileSync(binGate, 'utf8');
|
|
481
515
|
if (binContent !== gateContent) {
|
|
482
|
-
// Check if it's a size difference (likely out of sync) vs whitespace
|
|
483
516
|
const sizeDiff = Math.abs(binContent.length - gateContent.length);
|
|
484
|
-
|
|
517
|
+
const prePublishDrift = isExpectedPrePublishDrift(installedBinGate, gateContent, binContent);
|
|
518
|
+
if (prePublishDrift) {
|
|
519
|
+
warnings.push(`source bin/gate.cjs is ${sizeDiff} chars ahead of node_modules/moflo/bin/gate.cjs ` +
|
|
520
|
+
'(expected pre-publish drift; resolves on next `npm install moflo@<new>`)');
|
|
521
|
+
}
|
|
522
|
+
else if (sizeDiff > 10) {
|
|
485
523
|
issues.push(`bin/gate.cjs out of sync with .claude/helpers/gate.cjs (${sizeDiff} chars differ)`);
|
|
486
524
|
}
|
|
487
525
|
else {
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.16",
|
|
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",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
82
82
|
"@typescript-eslint/parser": "^7.18.0",
|
|
83
83
|
"eslint": "^8.0.0",
|
|
84
|
-
"moflo": "^4.9.
|
|
84
|
+
"moflo": "^4.9.15",
|
|
85
85
|
"tsx": "^4.21.0",
|
|
86
86
|
"typescript": "^5.9.3",
|
|
87
87
|
"vitest": "^4.0.0"
|