moflo 4.9.19 → 4.9.21
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/commands/{simplify.md → flo-simplify.md} +4 -4
- package/.claude/guidance/shipped/moflo-agent-rules.md +172 -0
- package/.claude/guidance/shipped/moflo-claude-swarm-cohesion.md +73 -265
- package/.claude/guidance/shipped/moflo-cli-reference.md +6 -6
- package/.claude/guidance/shipped/moflo-core-guidance.md +66 -184
- package/.claude/guidance/shipped/moflo-cross-platform.md +1 -1
- package/.claude/guidance/shipped/moflo-error-handling.md +3 -3
- package/.claude/guidance/shipped/moflo-guidance-rules.md +17 -7
- package/.claude/guidance/shipped/moflo-memory-strategy.md +76 -182
- package/.claude/guidance/shipped/moflo-memorydb-maintenance.md +6 -8
- package/.claude/guidance/shipped/moflo-settings-injection.md +7 -9
- package/.claude/guidance/shipped/moflo-source-hygiene.md +5 -5
- package/.claude/guidance/shipped/moflo-spell-connectors.md +3 -4
- package/.claude/guidance/shipped/moflo-spell-custom-steps.md +3 -4
- package/.claude/guidance/shipped/moflo-spell-engine.md +40 -162
- package/.claude/guidance/shipped/moflo-spell-runner.md +134 -0
- package/.claude/guidance/shipped/moflo-spell-sandboxing.md +10 -57
- package/.claude/guidance/shipped/moflo-spell-troubleshooting.md +149 -0
- package/.claude/guidance/shipped/moflo-subagents.md +43 -114
- package/.claude/guidance/shipped/moflo-task-icons.md +4 -4
- package/.claude/guidance/shipped/moflo-user-facing-language.md +3 -3
- package/.claude/guidance/shipped/moflo-verbose-command-filtering.md +3 -3
- package/.claude/guidance/shipped/moflo-yaml-reference.md +4 -5
- package/.claude/helpers/gate.cjs +124 -14
- package/.claude/helpers/prompt-hook.mjs +4 -38
- package/.claude/helpers/simplify-classify.cjs +32 -11
- package/.claude/helpers/subagent-bootstrap.json +1 -1
- package/.claude/helpers/subagent-start.cjs +1 -1
- package/.claude/skills/connector-builder/SKILL.md +42 -429
- package/.claude/skills/connector-builder/templates/connector.md +189 -0
- package/.claude/skills/connector-builder/templates/step-command.md +176 -0
- package/.claude/skills/eldar/SKILL.md +7 -7
- package/.claude/skills/fl/SKILL.md +3 -3
- package/.claude/skills/fl/execution-modes.md +3 -3
- package/.claude/skills/fl/phases.md +3 -3
- package/.claude/skills/{simplify → flo-simplify}/SKILL.md +11 -11
- package/.claude/skills/guidance/SKILL.md +17 -9
- package/.claude/skills/memory-patterns/SKILL.md +1 -1
- package/.claude/skills/publish/SKILL.md +121 -36
- package/.claude/skills/reset-epic/SKILL.md +2 -2
- package/.claude/skills/spell-builder/SKILL.md +39 -226
- package/.claude/skills/spell-builder/architecture.md +1 -1
- package/.claude/skills/spell-builder/permissions.md +107 -0
- package/.claude/skills/spell-builder/preflight.md +101 -0
- package/.claude/skills/spell-schedule/SKILL.md +2 -3
- package/bin/gate.cjs +124 -14
- package/bin/prompt-hook.mjs +4 -38
- package/bin/session-start-launcher.mjs +66 -1
- package/bin/setup-project.mjs +63 -69
- package/bin/simplify-classify.cjs +32 -11
- package/dist/src/cli/commands/doctor-checks-deep.js +4 -0
- package/dist/src/cli/init/claudemd-generator.js +30 -33
- package/dist/src/cli/init/executor.js +28 -16
- package/dist/src/cli/init/helpers-generator.js +101 -51
- package/dist/src/cli/init/moflo-init.js +41 -114
- package/dist/src/cli/init/settings-generator.js +32 -14
- package/dist/src/cli/services/hook-block-hash.js +7 -2
- package/dist/src/cli/services/hook-wiring.js +86 -3
- package/dist/src/cli/services/subagent-bootstrap.js +1 -1
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
- package/scripts/post-install-bootstrap.mjs +19 -0
- package/.claude/guidance/shipped/moflo-session-start.md +0 -154
- package/.claude/guidance/shipped/moflo-spell-engine-architecture.md +0 -145
- package/.claude/skills/browser/SKILL.md +0 -204
- package/.claude/skills/github-code-review/SKILL.md +0 -1140
- package/.claude/skills/github-multi-repo/SKILL.md +0 -866
- package/.claude/skills/github-project-management/SKILL.md +0 -1272
- package/.claude/skills/github-release-management/SKILL.md +0 -1074
- package/.claude/skills/github-workflow-automation/SKILL.md +0 -1060
- package/.claude/skills/hive-mind-advanced/SKILL.md +0 -712
- package/.claude/skills/hooks-automation/SKILL.md +0 -1193
- package/.claude/skills/pair-programming/SKILL.md +0 -1202
- package/.claude/skills/performance-analysis/SKILL.md +0 -563
- package/.claude/skills/skill-builder/SKILL.md +0 -910
- package/.claude/skills/sparc-methodology/SKILL.md +0 -904
- package/.claude/skills/stream-chain/SKILL.md +0 -563
- package/.claude/skills/swarm-advanced/SKILL.md +0 -811
- package/.claude/skills/swarm-orchestration/SKILL.md +0 -179
- package/.claude/skills/verification-quality/SKILL.md +0 -649
- package/.claude/skills/worker-benchmarks/skill.md +0 -135
- package/.claude/skills/worker-integration/skill.md +0 -154
package/bin/gate.cjs
CHANGED
|
@@ -7,7 +7,7 @@ var cp = require('child_process');
|
|
|
7
7
|
var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
|
|
8
8
|
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
9
9
|
|
|
10
|
-
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, simplifySnapshotSha: null, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
|
|
10
|
+
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, simplifySnapshotSha: null, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {} };
|
|
11
11
|
|
|
12
12
|
// Per-actor memory-search tracking (#838). The legacy `memorySearched` boolean
|
|
13
13
|
// is session-wide, so once the parent searches memory, every spawned subagent
|
|
@@ -83,6 +83,78 @@ var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state
|
|
|
83
83
|
var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
|
|
84
84
|
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
|
|
85
85
|
var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
|
|
86
|
+
|
|
87
|
+
// Namespace classification (#931). The hint used to be emitted on every prompt
|
|
88
|
+
// by prompt-hook.mjs which cost ~40 tokens × every prompt × every consumer.
|
|
89
|
+
// Now we classify here, store on workflow-state, and let check-before-agent
|
|
90
|
+
// emit it once when Claude is actually about to spawn an agent.
|
|
91
|
+
//
|
|
92
|
+
// SYNC: these regexes + classifyNamespaceHint + applyPromptStateReset are
|
|
93
|
+
// duplicated verbatim in src/cli/init/helpers-generator.ts (the embedded
|
|
94
|
+
// gate.cjs fallback used by `flo init` when source helpers can't be located).
|
|
95
|
+
// Any edit to either copy MUST be applied to both — there is no shared module
|
|
96
|
+
// because helpers-generator emits a self-contained string template.
|
|
97
|
+
var NS_LEARNINGS_RE = /\b(remember|recall|insight|lesson learned|gotcha|post.?mortem)\b|we (decid|agree|chose|said)/;
|
|
98
|
+
var NS_TEST_RE = /\b(test|spec|coverage|tested|test case|test cases|tests for|spec for)\b/;
|
|
99
|
+
var NS_EXPLICIT = [
|
|
100
|
+
{ pattern: /\b(pattern|convention|best practice|style|coding rule)\b/, ns: 'patterns', label: 'code patterns and conventions' },
|
|
101
|
+
{ pattern: /\b(code.?map|file structure|project structure|directory)\b/, ns: 'code-map', label: 'codebase navigation' },
|
|
102
|
+
];
|
|
103
|
+
var NS_PATTERN_RES = [/\b(template|example|similar to|how do we|how should)\b/];
|
|
104
|
+
var NS_DOMAIN_RES = [
|
|
105
|
+
/\b(guidance|guide|docs|documentation|rules|how-to)\b/,
|
|
106
|
+
/\b(architecture|design|domain|tenant|migrat|schema|deploy)/,
|
|
107
|
+
/\b(rule|requirement|constraint|compliance)\b/,
|
|
108
|
+
];
|
|
109
|
+
var NS_NAV_RES = [
|
|
110
|
+
/\b(find|where|which file|look up|locate|endpoint|route|url|path)\b/,
|
|
111
|
+
/\b(class|function|method|component|service|entity|module)\b/,
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
function classifyNamespaceHint(promptText) {
|
|
115
|
+
var lower = (promptText || '').toLowerCase();
|
|
116
|
+
if (NS_TEST_RE.test(lower)) return 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
|
|
117
|
+
if (NS_LEARNINGS_RE.test(lower)) return 'Memory namespace hint: use "learnings" for user-directed decisions and distilled insights.';
|
|
118
|
+
for (var i = 0; i < NS_EXPLICIT.length; i++) {
|
|
119
|
+
if (NS_EXPLICIT[i].pattern.test(lower)) return 'Memory namespace hint: use "' + NS_EXPLICIT[i].ns + '" for ' + NS_EXPLICIT[i].label + '.';
|
|
120
|
+
}
|
|
121
|
+
for (var j = 0; j < NS_DOMAIN_RES.length; j++) {
|
|
122
|
+
if (NS_DOMAIN_RES[j].test(lower)) return 'Memory namespace hint: search "guidance" and "learnings" for domain rules and project decisions.';
|
|
123
|
+
}
|
|
124
|
+
for (var k = 0; k < NS_PATTERN_RES.length; k++) {
|
|
125
|
+
if (NS_PATTERN_RES[k].test(lower)) return 'Memory namespace hint: use "patterns" for code patterns and conventions.';
|
|
126
|
+
}
|
|
127
|
+
for (var m = 0; m < NS_NAV_RES.length; m++) {
|
|
128
|
+
if (NS_NAV_RES[m].test(lower)) return 'Memory namespace hint: use "code-map" for codebase navigation.';
|
|
129
|
+
}
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Apply per-prompt state reset shared by `prompt-reminder` (full) and
|
|
134
|
+
// `prompt-state-reset` (defensive safety-net, no emission). Idempotent — both
|
|
135
|
+
// UserPromptSubmit hooks can run it without compounding any field. Caller
|
|
136
|
+
// owns interactionCount and the user-visible REMINDER/Context emissions, so
|
|
137
|
+
// this helper stays silent.
|
|
138
|
+
function applyPromptStateReset(state, promptText) {
|
|
139
|
+
state.memorySearched = false;
|
|
140
|
+
// Wipe per-actor memory tracking too — a new user prompt is a fresh window
|
|
141
|
+
// for both parent AND any subagents the parent may spawn during this turn.
|
|
142
|
+
state.memorySearchedBy = {};
|
|
143
|
+
// learningsStored is session-scoped — once stored, it stays true until session reset.
|
|
144
|
+
// Resetting per-prompt caused false blocks when PR creation was on a later prompt.
|
|
145
|
+
var DIRECTIVE_MAX_LEN = 20;
|
|
146
|
+
var escaped = /^@@\s*/.test(promptText || '');
|
|
147
|
+
state.memoryRequired = !escaped && (promptText || '').length >= 4 && (TASK_RE.test(promptText || '') || (promptText || '').length > DIRECTIVE_MAX_LEN);
|
|
148
|
+
// Stash namespace hint for check-before-agent to emit when Claude actually
|
|
149
|
+
// spawns an Agent (#931). Empty string when nothing matched — overwriting
|
|
150
|
+
// any stale value from the previous prompt.
|
|
151
|
+
state.lastNamespaceHint = classifyNamespaceHint(promptText);
|
|
152
|
+
// Per-actor emission tracking — each subagent's session gets the hint at
|
|
153
|
+
// most once per prompt, but a fresh prompt resets every actor's window so
|
|
154
|
+
// subsequent agents (parent + subagents that spawn their own agents) all
|
|
155
|
+
// see the new classification on their first check-before-agent.
|
|
156
|
+
state.lastNamespaceHintEmittedBy = {};
|
|
157
|
+
}
|
|
86
158
|
// Match npm/yarn/pnpm/bun test, npx vitest|jest|..., bare runners at command-start only,
|
|
87
159
|
// and language-native test commands. The bare-runner arm is anchored so that
|
|
88
160
|
// `npm install jest`, `grep -r vitest src/`, and similar don't false-positive.
|
|
@@ -203,6 +275,11 @@ switch (command) {
|
|
|
203
275
|
// Advisory only — agent spawning is never blocked.
|
|
204
276
|
// Memory-first enforcement happens at the scan/read gate layer.
|
|
205
277
|
// SubagentStart hook injects guidance directive into subagent context.
|
|
278
|
+
//
|
|
279
|
+
// #931 — TaskCreate REMINDER and the namespace hint moved here from
|
|
280
|
+
// prompt-reminder. They only matter when Claude is actually about to spawn
|
|
281
|
+
// an Agent; emitting per-prompt cost ~90 tokens × every prompt × every
|
|
282
|
+
// consumer.
|
|
206
283
|
var s = readState();
|
|
207
284
|
if (config.task_create_first && !s.tasksCreated) {
|
|
208
285
|
process.stdout.write('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.\n');
|
|
@@ -210,6 +287,24 @@ switch (command) {
|
|
|
210
287
|
if (config.memory_first && s.memoryRequired && !s.memorySearched) {
|
|
211
288
|
process.stdout.write('REMINDER: Search memory (mcp__moflo__memory_search) before spawning agents.\n');
|
|
212
289
|
}
|
|
290
|
+
if (s.lastNamespaceHint) {
|
|
291
|
+
// Per-actor single-shot. Each session_id gets the hint at most once per
|
|
292
|
+
// prompt, but the hint itself stays available for other actors (e.g.
|
|
293
|
+
// a subagent that spawns its own agent has its own session_id and is
|
|
294
|
+
// entitled to a fresh emission). Falls back to a `_legacy_` bucket when
|
|
295
|
+
// Claude Code didn't forward a session_id (older host or direct CLI
|
|
296
|
+
// invocation), preserving the old "emit once globally" behavior. The
|
|
297
|
+
// map is wiped by applyPromptStateReset on every new prompt.
|
|
298
|
+
var sid = process.env.HOOK_SESSION_ID || '';
|
|
299
|
+
var emittedBy = s.lastNamespaceHintEmittedBy || {};
|
|
300
|
+
var bucket = sid || '_legacy_';
|
|
301
|
+
if (!emittedBy[bucket]) {
|
|
302
|
+
process.stdout.write(s.lastNamespaceHint + '\n');
|
|
303
|
+
emittedBy[bucket] = true;
|
|
304
|
+
s.lastNamespaceHintEmittedBy = emittedBy;
|
|
305
|
+
writeState(s);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
213
308
|
break;
|
|
214
309
|
}
|
|
215
310
|
case 'check-before-scan': {
|
|
@@ -277,7 +372,8 @@ switch (command) {
|
|
|
277
372
|
break;
|
|
278
373
|
}
|
|
279
374
|
case 'record-skill-run': {
|
|
280
|
-
|
|
375
|
+
var skName = (process.env.TOOL_INPUT_skill || '');
|
|
376
|
+
if (skName === 'simplify' || skName === 'flo-simplify') {
|
|
281
377
|
var s = readState();
|
|
282
378
|
var changed = false;
|
|
283
379
|
if (!s.simplifyRun) { s.simplifyRun = true; changed = true; }
|
|
@@ -346,7 +442,7 @@ switch (command) {
|
|
|
346
442
|
}
|
|
347
443
|
var missing = [];
|
|
348
444
|
if (config.testing_gate && !s.testsRun) missing.push('tests have not run since the last code edit (run npm test, vitest, jest, pytest, or similar)');
|
|
349
|
-
if (config.simplify_gate && !s.simplifyRun) missing.push('/simplify has not run since the last code edit');
|
|
445
|
+
if (config.simplify_gate && !s.simplifyRun) missing.push('/flo-simplify has not run since the last code edit');
|
|
350
446
|
if (config.learnings_gate && !s.learningsStored) missing.push('learnings have not been stored (call mcp__moflo__memory_store)');
|
|
351
447
|
if (missing.length === 0) break;
|
|
352
448
|
process.stderr.write('BLOCKED: gh pr create requires the following before opening a PR:\n');
|
|
@@ -371,20 +467,16 @@ switch (command) {
|
|
|
371
467
|
break;
|
|
372
468
|
}
|
|
373
469
|
case 'prompt-reminder': {
|
|
470
|
+
// Full per-prompt reset. Wired as the first UserPromptSubmit hook (via
|
|
471
|
+
// prompt-hook.mjs). Owns interactionCount + Context warnings; the
|
|
472
|
+
// TaskCreate REMINDER and namespace hint moved to check-before-agent
|
|
473
|
+
// (#931) so they only fire when Claude is actually about to spawn an
|
|
474
|
+
// Agent.
|
|
374
475
|
var s = readState();
|
|
375
|
-
s.memorySearched = false;
|
|
376
|
-
// Wipe per-actor memory tracking too — a new user prompt is a fresh window
|
|
377
|
-
// for both parent AND any subagents the parent may spawn during this turn.
|
|
378
|
-
s.memorySearchedBy = {};
|
|
379
|
-
// learningsStored is session-scoped — once stored, it stays true until session reset.
|
|
380
|
-
// Resetting per-prompt caused false blocks when PR creation was on a later prompt.
|
|
381
476
|
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
382
|
-
|
|
383
|
-
var escaped = /^@@\s*/.test(prompt);
|
|
384
|
-
s.memoryRequired = !escaped && prompt.length >= 4 && (TASK_RE.test(prompt) || prompt.length > DIRECTIVE_MAX_LEN);
|
|
477
|
+
applyPromptStateReset(s, prompt);
|
|
385
478
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
386
479
|
writeState(s);
|
|
387
|
-
if (!s.tasksCreated) console.log('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.');
|
|
388
480
|
if (config.context_tracking) {
|
|
389
481
|
var ic = s.interactionCount;
|
|
390
482
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
@@ -393,12 +485,30 @@ switch (command) {
|
|
|
393
485
|
}
|
|
394
486
|
break;
|
|
395
487
|
}
|
|
488
|
+
case 'prompt-state-reset': {
|
|
489
|
+
// Defensive safety-net hook (#931 dedupe). Wired as the second
|
|
490
|
+
// UserPromptSubmit hook so an exception in prompt-hook.mjs doesn't skip
|
|
491
|
+
// the per-prompt state reset. Idempotent — applyPromptStateReset only
|
|
492
|
+
// sets fields to derived values, and we deliberately do NOT increment
|
|
493
|
+
// interactionCount or emit anything (that's prompt-reminder's job).
|
|
494
|
+
//
|
|
495
|
+
// Skip the disk write on the normal path: prompt-reminder runs first and
|
|
496
|
+
// already wrote the byte-identical post-reset state. Only writeState when
|
|
497
|
+
// the reset actually changed something (i.e., prompt-reminder was skipped
|
|
498
|
+
// because prompt-hook.mjs threw before invoking it).
|
|
499
|
+
var s = readState();
|
|
500
|
+
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
501
|
+
var before = JSON.stringify(s);
|
|
502
|
+
applyPromptStateReset(s, prompt);
|
|
503
|
+
if (JSON.stringify(s) !== before) writeState(s);
|
|
504
|
+
break;
|
|
505
|
+
}
|
|
396
506
|
case 'compact-guidance': {
|
|
397
507
|
console.log('Pre-Compact: Check CLAUDE.md for rules. Use memory search to recover context after compact.');
|
|
398
508
|
break;
|
|
399
509
|
}
|
|
400
510
|
case 'session-reset': {
|
|
401
|
-
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
|
|
511
|
+
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {} });
|
|
402
512
|
break;
|
|
403
513
|
}
|
|
404
514
|
default:
|
package/bin/prompt-hook.mjs
CHANGED
|
@@ -33,43 +33,9 @@ try {
|
|
|
33
33
|
});
|
|
34
34
|
} catch (err) { output = (err && err.stdout) || ''; }
|
|
35
35
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
var LEARNINGS_HINTS = /\b(remember|recall|insight|lesson learned|gotcha|post.?mortem)\b|we (decid|agree|chose|said)/;
|
|
40
|
-
var TEST_HINTS = /\b(test|spec|coverage|tested|test case|test cases|tests for|spec for)\b/;
|
|
41
|
-
var EXPLICIT_NS = [
|
|
42
|
-
{ pattern: /\b(pattern|convention|best practice|style|coding rule)\b/, ns: 'patterns', label: 'code patterns and conventions' },
|
|
43
|
-
{ pattern: /\b(code.?map|file structure|project structure|directory)\b/, ns: 'code-map', label: 'codebase navigation' },
|
|
44
|
-
];
|
|
45
|
-
var PATTERN_HINTS = [/\b(template|example|similar to|how do we|how should)\b/];
|
|
46
|
-
var DOMAIN_HINTS = [
|
|
47
|
-
/\b(guidance|guide|docs|documentation|rules|how-to)\b/,
|
|
48
|
-
/\b(architecture|design|domain|tenant|migrat|schema|deploy)/,
|
|
49
|
-
/\b(rule|requirement|constraint|compliance)\b/,
|
|
50
|
-
];
|
|
51
|
-
var NAV_PATTERNS = [
|
|
52
|
-
/\b(find|where|which file|look up|locate|endpoint|route|url|path)\b/,
|
|
53
|
-
/\b(class|function|method|component|service|entity|module)\b/,
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
var nsHint = '';
|
|
57
|
-
if (TEST_HINTS.test(lower)) {
|
|
58
|
-
nsHint = 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
|
|
59
|
-
} else if (LEARNINGS_HINTS.test(lower)) {
|
|
60
|
-
nsHint = 'Memory namespace hint: use "learnings" for user-directed decisions and distilled insights.';
|
|
61
|
-
} else {
|
|
62
|
-
var found = EXPLICIT_NS.find(function(e) { return e.pattern.test(lower); });
|
|
63
|
-
if (found) {
|
|
64
|
-
nsHint = 'Memory namespace hint: use "' + found.ns + '" for ' + found.label + '.';
|
|
65
|
-
} else if (DOMAIN_HINTS.some(function(p) { return p.test(lower); })) {
|
|
66
|
-
nsHint = 'Memory namespace hint: search "guidance" and "learnings" for domain rules and project decisions.';
|
|
67
|
-
} else if (PATTERN_HINTS.some(function(p) { return p.test(lower); })) {
|
|
68
|
-
nsHint = 'Memory namespace hint: use "patterns" for code patterns and conventions.';
|
|
69
|
-
} else if (NAV_PATTERNS.some(function(p) { return p.test(lower); })) {
|
|
70
|
-
nsHint = 'Memory namespace hint: use "code-map" for codebase navigation.';
|
|
71
|
-
}
|
|
72
|
-
}
|
|
36
|
+
// #931 — Namespace hint classification moved into gate.cjs (computed by
|
|
37
|
+
// prompt-reminder, stored on workflow-state.json, emitted once by
|
|
38
|
+
// check-before-agent at Agent-spawn time). No per-prompt token leak now.
|
|
73
39
|
|
|
74
40
|
// #867 — surface post-install restart notice. File is written by
|
|
75
41
|
// scripts/post-install-notice.mjs and cleared by the SessionStart launcher
|
|
@@ -84,6 +50,6 @@ try {
|
|
|
84
50
|
}
|
|
85
51
|
} catch (e) { /* ENOENT or malformed — silent fast-path */ }
|
|
86
52
|
|
|
87
|
-
var parts = [restartNotice, output.trim()
|
|
53
|
+
var parts = [restartNotice, output.trim()].filter(Boolean);
|
|
88
54
|
if (parts.length) process.stdout.write(parts.join('\n\n') + '\n');
|
|
89
55
|
process.exit(0);
|
|
@@ -53,6 +53,36 @@ function findProjectRoot() {
|
|
|
53
53
|
|
|
54
54
|
const projectRoot = findProjectRoot();
|
|
55
55
|
|
|
56
|
+
// Dogfood guard (#928). When this launcher runs inside the moflo repo itself,
|
|
57
|
+
// .claude/scripts/, .claude/helpers/, and .claude/guidance/ are committed git
|
|
58
|
+
// files — they ARE moflo's source of truth, not destinations to be re-synced
|
|
59
|
+
// from node_modules. Drift heal would silently revert any post-publish edit
|
|
60
|
+
// to one of those files (e.g. story #927's gate.cjs got reverted overnight
|
|
61
|
+
// because manifest.size still pointed at the previously-published version).
|
|
62
|
+
// Detection is `package.json#name === "moflo"` — the project's own package.json,
|
|
63
|
+
// NOT node_modules/moflo/package.json. Defaults to false on any read/parse
|
|
64
|
+
// error so a corrupt package.json never silently disables drift heal in a
|
|
65
|
+
// real consumer.
|
|
66
|
+
//
|
|
67
|
+
// Workspace caveat: findProjectRoot() walks up to the nearest package.json,
|
|
68
|
+
// so in a workspace child (packages/foo/) the read sees the child package
|
|
69
|
+
// (name !== "moflo") and the guard stays false. moflo isn't a workspace
|
|
70
|
+
// today; if it ever becomes one, run sessions from the repo root or extend
|
|
71
|
+
// this check to walk further up.
|
|
72
|
+
let isMofloDogfood = false;
|
|
73
|
+
try {
|
|
74
|
+
const projectPkgPath = resolve(projectRoot, 'package.json');
|
|
75
|
+
if (existsSync(projectPkgPath)) {
|
|
76
|
+
const projectPkg = JSON.parse(readFileSync(projectPkgPath, 'utf-8'));
|
|
77
|
+
isMofloDogfood = projectPkg?.name === 'moflo';
|
|
78
|
+
}
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// Defaults to false — safer than accidentally disabling drift heal in a
|
|
81
|
+
// real consumer. Surface the failure so a corrupt project package.json
|
|
82
|
+
// doesn't silently change launcher behavior (per feedback_no_silent_failures).
|
|
83
|
+
process.stderr.write(`[moflo] dogfood-guard package.json read failed: ${err && err.message ? err.message : String(err)}\n`);
|
|
84
|
+
}
|
|
85
|
+
|
|
56
86
|
// Visible mutation reporter (#716). Claude Code's SessionStart hook captures
|
|
57
87
|
// stdout as `additionalContext`, so each line here surfaces to Claude — and
|
|
58
88
|
// through it to the user — explaining what the launcher just changed. Keep
|
|
@@ -360,6 +390,8 @@ try {
|
|
|
360
390
|
}
|
|
361
391
|
}
|
|
362
392
|
}
|
|
393
|
+
// Dogfood (#928): never drift-heal moflo's own committed copies.
|
|
394
|
+
if (isMofloDogfood) manifestDrifted = false;
|
|
363
395
|
|
|
364
396
|
if (installedVersion !== cachedVersion || manifestDrifted) {
|
|
365
397
|
if (installedVersion !== cachedVersion) {
|
|
@@ -444,6 +476,20 @@ try {
|
|
|
444
476
|
|
|
445
477
|
const binDir = resolve(projectRoot, 'node_modules/moflo/bin');
|
|
446
478
|
|
|
479
|
+
// Dogfood (#928): in moflo's own repo, the destinations under
|
|
480
|
+
// .claude/scripts/, .claude/helpers/, .claude/guidance/ are committed
|
|
481
|
+
// git files — copying node_modules/moflo content over them clobbers
|
|
482
|
+
// in-flight work (the same bug that silently reverted #927 between
|
|
483
|
+
// commit and publish). Skip the sync, cleanup, and manifest write
|
|
484
|
+
// entirely; queue the version-stamp write so we don't re-enter this
|
|
485
|
+
// branch on every subsequent session. Daemon recycle still happens
|
|
486
|
+
// (the stopDaemon call earlier handled this) and 3a-pre will spawn a
|
|
487
|
+
// fresh daemon under the new code.
|
|
488
|
+
if (isMofloDogfood) {
|
|
489
|
+
pendingVersionStampWrite = { path: versionStampPath, version: installedVersion };
|
|
490
|
+
emitMutation('skipped file-sync', 'moflo dogfood — committed dogfood copies preserved');
|
|
491
|
+
} else {
|
|
492
|
+
|
|
447
493
|
// ── Manifest-based auto-update ──────────────────────────────────────
|
|
448
494
|
//
|
|
449
495
|
// IMPORTANT: Every file moflo installs into the destination project
|
|
@@ -709,6 +755,7 @@ try {
|
|
|
709
755
|
// queued for 3g.
|
|
710
756
|
emitWarning(`manifest write failed (${errMessage(err)})`);
|
|
711
757
|
}
|
|
758
|
+
} // end !isMofloDogfood file-sync branch (#928)
|
|
712
759
|
}
|
|
713
760
|
}
|
|
714
761
|
} catch (err) {
|
|
@@ -1046,7 +1093,7 @@ try {
|
|
|
1046
1093
|
// Also prunes top-level mirrors whose source no longer exists in shipped/
|
|
1047
1094
|
// (#839): pre-manifest-tracking mirrors never appeared in installed-files.json
|
|
1048
1095
|
// so the section-3 manifest-diff cleanup can't reach them. The marker gate
|
|
1049
|
-
// protects user files
|
|
1096
|
+
// protects user-authored files; pre-#939 moflo-bootstrap.md is handled by 3c-939.
|
|
1050
1097
|
try {
|
|
1051
1098
|
const guidanceDir = resolve(projectRoot, '.claude/guidance');
|
|
1052
1099
|
const shippedDir = resolve(projectRoot, 'node_modules/moflo/.claude/guidance/shipped');
|
|
@@ -1122,6 +1169,24 @@ try {
|
|
|
1122
1169
|
}
|
|
1123
1170
|
} catch { /* non-fatal */ }
|
|
1124
1171
|
|
|
1172
|
+
// ── 3c-939. Remove pre-#939 moflo-bootstrap.md duplicate ────────────────────
|
|
1173
|
+
// Before #939, `flo init` Step 7 (and `flo-setup`) renamed moflo-subagents.md
|
|
1174
|
+
// to moflo-bootstrap.md while the launcher's stage-3 ALSO synced the original
|
|
1175
|
+
// name — consumers ended up with two copies of the same content on disk. The
|
|
1176
|
+
// rename is gone; this prunes any leftover from older installs. Marker-gated
|
|
1177
|
+
// so we never delete a homonymous user-authored file.
|
|
1178
|
+
try {
|
|
1179
|
+
const legacyBootstrap = resolve(projectRoot, '.claude/guidance/moflo-bootstrap.md');
|
|
1180
|
+
if (existsSync(legacyBootstrap)) {
|
|
1181
|
+
const head = readFileSync(legacyBootstrap, 'utf-8').slice(0, 200);
|
|
1182
|
+
const ours = head.includes('AUTO-GENERATED by moflo init') || head.includes('AUTO-GENERATED by flo-setup');
|
|
1183
|
+
if (ours) {
|
|
1184
|
+
unlinkSync(legacyBootstrap);
|
|
1185
|
+
emitMutation('removed legacy moflo-bootstrap.md', 'duplicate of moflo-subagents.md (#939)');
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
} catch { /* non-fatal */ }
|
|
1189
|
+
|
|
1125
1190
|
// ── 3d-yaml-create. Create moflo.yaml if missing (#895) ────────────────────
|
|
1126
1191
|
// Sibling self-heal to 3d-yaml: that block APPENDS new sections to existing
|
|
1127
1192
|
// yaml; this block CREATES the file from the canonical template when no yaml
|
package/bin/setup-project.mjs
CHANGED
|
@@ -11,78 +11,50 @@
|
|
|
11
11
|
* flo-setup --check # Check if setup is current
|
|
12
12
|
*
|
|
13
13
|
* What it does:
|
|
14
|
-
* 1. Copies
|
|
14
|
+
* 1. Copies moflo/.claude/guidance/shipped/moflo-subagents.md → project's .claude/guidance/moflo-subagents.md
|
|
15
15
|
* 2. Appends a subagent protocol section to CLAUDE.md (idempotent, with markers)
|
|
16
16
|
*
|
|
17
|
+
* Historical note (#939): pre-fix builds renamed the destination to
|
|
18
|
+
* moflo-bootstrap.md, but the launcher's stage-3 also synced the original name
|
|
19
|
+
* — consumers ended up with two copies of the same content. The launcher now
|
|
20
|
+
* prunes any legacy moflo-bootstrap.md on first session-start after upgrade.
|
|
21
|
+
*
|
|
17
22
|
* The project can layer its own guidance files on top for
|
|
18
23
|
* project-specific rules (companyId, entity templates, etc.).
|
|
24
|
+
*
|
|
25
|
+
* The CLAUDE.md content is owned by `src/cli/init/claudemd-generator.ts` (compiled to
|
|
26
|
+
* `dist/src/cli/init/claudemd-generator.js`). This script is a thin wrapper so consumers
|
|
27
|
+
* always get byte-identical output regardless of which entry point they use
|
|
28
|
+
* (`flo init` vs `flo-setup`).
|
|
19
29
|
*/
|
|
20
30
|
|
|
21
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'node:fs';
|
|
31
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, unlinkSync } from 'node:fs';
|
|
22
32
|
import { dirname, resolve, join } from 'node:path';
|
|
23
33
|
import { fileURLToPath } from 'node:url';
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
34
|
+
import { mofloInternalURL } from './lib/moflo-resolve.mjs';
|
|
35
|
+
|
|
36
|
+
// Resolve moflo's installed package root via Node module resolution so the script
|
|
37
|
+
// works identically from bin/ (canonical) or from .claude/scripts/ (synced copy).
|
|
38
|
+
const mofloRoot = dirname(fileURLToPath(mofloInternalURL('package.json')));
|
|
39
|
+
|
|
40
|
+
// Single source of truth: claudemd-generator.ts owns the section content.
|
|
41
|
+
// Use the shared mofloInternalURL helper so the script works identically when
|
|
42
|
+
// invoked from bin/ (canonical) or from .claude/scripts/ (synced copy).
|
|
43
|
+
const {
|
|
44
|
+
generateClaudeMd,
|
|
45
|
+
MARKER_START,
|
|
46
|
+
MARKER_END,
|
|
47
|
+
LEGACY_MARKER_STARTS,
|
|
48
|
+
LEGACY_MARKER_ENDS,
|
|
49
|
+
} = await import(mofloInternalURL('dist/src/cli/init/claudemd-generator.js'));
|
|
27
50
|
|
|
28
51
|
const args = process.argv.slice(2);
|
|
29
52
|
const updateOnly = args.includes('--update');
|
|
30
53
|
const checkOnly = args.includes('--check');
|
|
31
54
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
// Legacy markers to detect and replace
|
|
36
|
-
const LEGACY_STARTS = ['<!-- MOFLO:SUBAGENT-PROTOCOL:START -->', '<!-- MOFLO:START -->'];
|
|
37
|
-
const LEGACY_ENDS = ['<!-- MOFLO:SUBAGENT-PROTOCOL:END -->', '<!-- MOFLO:END -->'];
|
|
38
|
-
|
|
39
|
-
// Minimal injection — just enough for Claude to work with moflo.
|
|
40
|
-
// All detailed docs live in .claude/guidance/shipped/moflo-core-guidance.md.
|
|
41
|
-
const CLAUDE_MD_SECTION = `${MARKER_START}
|
|
42
|
-
## MoFlo — AI Agent Orchestration
|
|
43
|
-
|
|
44
|
-
This project uses [MoFlo](https://github.com/eric-cielo/moflo) for AI-assisted development workflows.
|
|
45
|
-
|
|
46
|
-
### FIRST ACTION ON EVERY PROMPT: Search Memory
|
|
47
|
-
|
|
48
|
-
Your first tool call for every new user prompt MUST be a memory search. Do this BEFORE Glob, Grep, Read, or any file exploration.
|
|
49
|
-
|
|
50
|
-
\`\`\`
|
|
51
|
-
mcp__moflo__memory_search — query: "<task description>", namespace: "guidance" or "patterns" or "learnings" or "code-map" or "tests"
|
|
52
|
-
\`\`\`
|
|
53
|
-
|
|
54
|
-
Search \`guidance\`, \`patterns\`, and \`learnings\` namespaces on every prompt. Search \`code-map\` when navigating the codebase, \`tests\` when looking for test inventory or coverage.
|
|
55
|
-
When the user asks you to remember something: \`mcp__moflo__memory_store\` with namespace \`learnings\`.
|
|
56
|
-
|
|
57
|
-
### Workflow Gates (enforced automatically)
|
|
58
|
-
|
|
59
|
-
- **Memory-first**: Must search memory before Glob/Grep/Read
|
|
60
|
-
- **TaskCreate-first**: Must call TaskCreate before spawning Agent tool
|
|
61
|
-
|
|
62
|
-
- **Task Icons**: \`TaskCreate\` MUST use ICON+[Role] format — see \`.claude/guidance/moflo-task-icons.md\`
|
|
63
|
-
|
|
64
|
-
### MCP Tools (preferred over CLI)
|
|
65
|
-
|
|
66
|
-
| Tool | Purpose |
|
|
67
|
-
|------|---------|
|
|
68
|
-
| \`mcp__moflo__memory_search\` | Semantic search across indexed knowledge |
|
|
69
|
-
| \`mcp__moflo__memory_store\` | Store patterns and decisions |
|
|
70
|
-
| \`mcp__moflo__hooks_route\` | Route task to optimal agent type |
|
|
71
|
-
| \`mcp__moflo__hooks_pre-task\` | Record task start |
|
|
72
|
-
| \`mcp__moflo__hooks_post-task\` | Record task completion for learning |
|
|
73
|
-
|
|
74
|
-
### CLI Fallback
|
|
75
|
-
|
|
76
|
-
\`\`\`bash
|
|
77
|
-
flo-search "[query]" --namespace guidance # Semantic search
|
|
78
|
-
flo doctor --fix # Health check
|
|
79
|
-
\`\`\`
|
|
80
|
-
|
|
81
|
-
### Full Reference
|
|
82
|
-
|
|
83
|
-
For CLI commands, hooks, agents, swarm config, memory commands, and moflo.yaml options, see:
|
|
84
|
-
\`.claude/guidance/shipped/moflo-core-guidance.md\`
|
|
85
|
-
${MARKER_END}`;
|
|
55
|
+
// Canonical section content (owned by claudemd-generator.ts). Trim the trailing newline
|
|
56
|
+
// that generateClaudeMd appends so the marker-replace logic below stays exact.
|
|
57
|
+
const CLAUDE_MD_SECTION = generateClaudeMd({}).trimEnd();
|
|
86
58
|
|
|
87
59
|
function log(msg) {
|
|
88
60
|
console.log(`[flo-setup] ${msg}`);
|
|
@@ -115,16 +87,16 @@ function findProjectRoot() {
|
|
|
115
87
|
return null;
|
|
116
88
|
}
|
|
117
89
|
|
|
118
|
-
function
|
|
90
|
+
function copySubagentsGuidance(projectRoot) {
|
|
119
91
|
const shippedSource = join(mofloRoot, '.claude', 'guidance', 'shipped', 'moflo-subagents.md');
|
|
120
92
|
const source = existsSync(shippedSource)
|
|
121
93
|
? shippedSource
|
|
122
94
|
: join(mofloRoot, '.claude', 'guidance', 'moflo-subagents.md');
|
|
123
95
|
const targetDir = join(projectRoot, '.claude', 'guidance');
|
|
124
|
-
const target = join(targetDir, 'moflo-
|
|
96
|
+
const target = join(targetDir, 'moflo-subagents.md');
|
|
125
97
|
|
|
126
98
|
if (!existsSync(source)) {
|
|
127
|
-
log('❌ Source
|
|
99
|
+
log('❌ Source subagents guidance not found in moflo package');
|
|
128
100
|
return false;
|
|
129
101
|
}
|
|
130
102
|
|
|
@@ -143,12 +115,12 @@ function copyBootstrap(projectRoot) {
|
|
|
143
115
|
const existing = readFileSync(target, 'utf-8');
|
|
144
116
|
const newContent = header + content;
|
|
145
117
|
if (existing === newContent) {
|
|
146
|
-
log('✅ moflo-
|
|
118
|
+
log('✅ moflo-subagents.md is current');
|
|
147
119
|
return true;
|
|
148
120
|
}
|
|
149
|
-
log('📝 Updating moflo-
|
|
121
|
+
log('📝 Updating moflo-subagents.md');
|
|
150
122
|
} else {
|
|
151
|
-
log('📝 Creating .claude/guidance/moflo-
|
|
123
|
+
log('📝 Creating .claude/guidance/moflo-subagents.md');
|
|
152
124
|
}
|
|
153
125
|
|
|
154
126
|
if (!checkOnly) {
|
|
@@ -157,6 +129,25 @@ function copyBootstrap(projectRoot) {
|
|
|
157
129
|
return true;
|
|
158
130
|
}
|
|
159
131
|
|
|
132
|
+
// Best-effort cleanup of pre-#939 moflo-bootstrap.md left over from prior
|
|
133
|
+
// installs. Only removes the file if it carries one of our auto-generated
|
|
134
|
+
// headers — never deletes a homonymous user-authored file. Idempotent.
|
|
135
|
+
function cleanupLegacyBootstrap(projectRoot) {
|
|
136
|
+
const legacy = join(projectRoot, '.claude', 'guidance', 'moflo-bootstrap.md');
|
|
137
|
+
if (!existsSync(legacy)) return;
|
|
138
|
+
try {
|
|
139
|
+
const head = readFileSync(legacy, 'utf-8').slice(0, 200);
|
|
140
|
+
const ours = head.includes('AUTO-GENERATED by moflo init') || head.includes('AUTO-GENERATED by flo-setup');
|
|
141
|
+
if (!ours) return;
|
|
142
|
+
if (!checkOnly) {
|
|
143
|
+
unlinkSync(legacy);
|
|
144
|
+
log('🧹 Removed legacy moflo-bootstrap.md (pre-#939 duplicate of moflo-subagents.md)');
|
|
145
|
+
} else {
|
|
146
|
+
log('⚠️ Legacy moflo-bootstrap.md present — will be removed on next non-check run');
|
|
147
|
+
}
|
|
148
|
+
} catch { /* non-fatal */ }
|
|
149
|
+
}
|
|
150
|
+
|
|
160
151
|
function updateClaudeMd(projectRoot) {
|
|
161
152
|
const claudeMdPath = join(projectRoot, 'CLAUDE.md');
|
|
162
153
|
|
|
@@ -173,8 +164,8 @@ function updateClaudeMd(projectRoot) {
|
|
|
173
164
|
const content = readFileSync(claudeMdPath, 'utf-8');
|
|
174
165
|
|
|
175
166
|
// Check for current or legacy markers and replace
|
|
176
|
-
const allStarts = [MARKER_START, ...
|
|
177
|
-
const allEnds = [MARKER_END, ...
|
|
167
|
+
const allStarts = [MARKER_START, ...LEGACY_MARKER_STARTS];
|
|
168
|
+
const allEnds = [MARKER_END, ...LEGACY_MARKER_ENDS];
|
|
178
169
|
|
|
179
170
|
for (let i = 0; i < allStarts.length; i++) {
|
|
180
171
|
if (content.includes(allStarts[i])) {
|
|
@@ -235,8 +226,11 @@ function main() {
|
|
|
235
226
|
log(`Project: ${projectRoot}`);
|
|
236
227
|
console.log('');
|
|
237
228
|
|
|
238
|
-
// Step 1: Copy bootstrap
|
|
239
|
-
const bootstrapOk =
|
|
229
|
+
// Step 1: Copy subagents guidance under its real name (#939 — was moflo-bootstrap.md)
|
|
230
|
+
const bootstrapOk = copySubagentsGuidance(projectRoot);
|
|
231
|
+
|
|
232
|
+
// Step 1b: Remove pre-#939 moflo-bootstrap.md if it still exists from an older install
|
|
233
|
+
cleanupLegacyBootstrap(projectRoot);
|
|
240
234
|
|
|
241
235
|
// Step 2: Update CLAUDE.md (skip on --update, only refresh the file)
|
|
242
236
|
const claudeOk = updateOnly ? true : updateClaudeMd(projectRoot);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* /simplify diff classifier
|
|
3
|
+
* /flo-simplify diff classifier.
|
|
4
4
|
*
|
|
5
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
|
|
6
|
+
* dispatch decision. The /flo-simplify skill MUST call this first so routing is
|
|
7
7
|
* deterministic and unit-testable instead of a prose decision Claude makes
|
|
8
8
|
* over and over per run.
|
|
9
9
|
*
|
|
10
|
-
* Rule
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Rule: default to single-agent Sonnet review. Only escalate to a 3-agent
|
|
11
|
+
* fan-out when diff signals genuinely warrant it. Opus is never selected —
|
|
12
|
+
* the existing skill already documents that.
|
|
13
13
|
*
|
|
14
14
|
* Outputs JSON:
|
|
15
15
|
* {
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
* }
|
|
22
22
|
*
|
|
23
23
|
* Usage:
|
|
24
|
-
* node bin/simplify-classify.cjs
|
|
24
|
+
* node bin/simplify-classify.cjs # auto-detects default branch
|
|
25
|
+
* node bin/simplify-classify.cjs --base develop # explicit override
|
|
25
26
|
* node bin/simplify-classify.cjs --diff <unified-diff-on-stdin>
|
|
26
27
|
*
|
|
27
28
|
* The --diff stdin form exists so unit tests can drive the classifier
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
|
|
32
33
|
const { execSync } = require('child_process');
|
|
33
34
|
|
|
34
|
-
// Paths where new logic warrants the 3-agent fan-out
|
|
35
|
+
// Paths where new logic warrants the 3-agent fan-out.
|
|
35
36
|
// Mechanical edits inside these paths are still SMALL; only adding/removing
|
|
36
37
|
// declarations triggers escalation.
|
|
37
38
|
const SECURITY_PATHS = [
|
|
@@ -49,6 +50,25 @@ function safeExec(cmd) {
|
|
|
49
50
|
catch { return ''; }
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
// Detect the consumer's default branch. Hardcoding 'main' silently miscalibrates
|
|
54
|
+
// classification on repos that use 'master', 'develop', etc. — empty diff →
|
|
55
|
+
// TRIVIAL → gate stamps clean without any real review.
|
|
56
|
+
let _cachedDefaultBranch = null;
|
|
57
|
+
function detectDefaultBranch() {
|
|
58
|
+
if (_cachedDefaultBranch !== null) return _cachedDefaultBranch;
|
|
59
|
+
|
|
60
|
+
// Preferred: origin/HEAD points to whatever the remote considers default.
|
|
61
|
+
const symbolic = safeExec('git symbolic-ref --short refs/remotes/origin/HEAD').trim();
|
|
62
|
+
if (symbolic.startsWith('origin/')) return (_cachedDefaultBranch = symbolic.slice('origin/'.length));
|
|
63
|
+
|
|
64
|
+
// Fallback: local init.defaultBranch (set by `git init -b <name>` or config).
|
|
65
|
+
const configured = safeExec('git config --get init.defaultBranch').trim();
|
|
66
|
+
if (configured) return (_cachedDefaultBranch = configured);
|
|
67
|
+
|
|
68
|
+
// Last resort: 'main' (most common modern default).
|
|
69
|
+
return (_cachedDefaultBranch = 'main');
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
function readDiffFromGit(base) {
|
|
53
73
|
// Combined diff: committed-since-base + working-tree
|
|
54
74
|
const committed = safeExec(`git diff ${base}...HEAD`);
|
|
@@ -186,14 +206,15 @@ function classifyDiff(diffText) {
|
|
|
186
206
|
return decide(parseDiff(diffText));
|
|
187
207
|
}
|
|
188
208
|
|
|
189
|
-
function classifyFromGit(base
|
|
190
|
-
|
|
209
|
+
function classifyFromGit(base) {
|
|
210
|
+
const resolved = base || detectDefaultBranch();
|
|
211
|
+
return classifyDiff(readDiffFromGit(resolved));
|
|
191
212
|
}
|
|
192
213
|
|
|
193
214
|
if (require.main === module) {
|
|
194
215
|
const args = process.argv.slice(2);
|
|
195
216
|
const baseIdx = args.indexOf('--base');
|
|
196
|
-
const base = baseIdx >= 0 ? args[baseIdx + 1] :
|
|
217
|
+
const base = baseIdx >= 0 ? args[baseIdx + 1] : detectDefaultBranch();
|
|
197
218
|
const stdinDiff = args.includes('--diff') || args.includes('--stdin');
|
|
198
219
|
|
|
199
220
|
let result;
|
|
@@ -211,4 +232,4 @@ if (require.main === module) {
|
|
|
211
232
|
}
|
|
212
233
|
}
|
|
213
234
|
|
|
214
|
-
module.exports = { parseDiff, decide, classifyDiff, classifyFromGit };
|
|
235
|
+
module.exports = { parseDiff, decide, classifyDiff, classifyFromGit, detectDefaultBranch };
|
|
@@ -425,6 +425,10 @@ const REQUIRED_GATE_CASES = [
|
|
|
425
425
|
'check-before-pr',
|
|
426
426
|
'check-dangerous-command',
|
|
427
427
|
'prompt-reminder',
|
|
428
|
+
// #931 — Defensive safety-net for the second UserPromptSubmit hook. State
|
|
429
|
+
// reset only, no emission. doctor warns if a consumer's gate.cjs is too old
|
|
430
|
+
// to handle it.
|
|
431
|
+
'prompt-state-reset',
|
|
428
432
|
'session-reset',
|
|
429
433
|
];
|
|
430
434
|
// Import + re-export from the self-contained hook-wiring module (single source of truth).
|