moflo 4.9.20 → 4.9.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/analysis/analyze-code-quality.md +0 -121
- package/.claude/agents/analysis/code-analyzer.md +5 -26
- package/.claude/agents/architecture/system-design/arch-system-design.md +0 -119
- package/.claude/agents/base-template-generator.md +0 -1
- package/.claude/agents/core/coder.md +0 -22
- package/.claude/agents/core/planner.md +0 -16
- package/.claude/agents/core/researcher.md +0 -16
- package/.claude/agents/core/reviewer.md +0 -17
- package/.claude/agents/core/tester.md +0 -19
- package/.claude/agents/custom/test-long-runner.md +0 -2
- package/.claude/agents/development/dev-backend-api.md +0 -167
- package/.claude/agents/development/dev-database.md +43 -0
- package/.claude/agents/development/dev-frontend.md +42 -0
- package/.claude/agents/devops/ci-cd/ops-cicd-github.md +0 -112
- package/.claude/agents/documentation/api-docs/docs-api-openapi.md +0 -111
- package/.claude/agents/security/security-auditor.md +45 -0
- 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 +192 -15
- 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 +39 -16
- 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 +192 -15
- package/bin/lib/retired-files.mjs +146 -0
- package/bin/prompt-hook.mjs +4 -38
- package/bin/session-start-launcher.mjs +120 -1
- package/bin/setup-project.mjs +63 -69
- package/bin/simplify-classify.cjs +32 -11
- package/dist/src/cli/appliance/rvfa-builder.js +1 -1
- package/dist/src/cli/commands/agent.js +3 -9
- package/dist/src/cli/commands/doctor-checks-deep.js +4 -0
- package/dist/src/cli/commands/hooks.js +1 -3
- package/dist/src/cli/commands/index.js +2 -0
- package/dist/src/cli/commands/retire.js +111 -0
- package/dist/src/cli/hooks/reasoningbank/index.js +7 -7
- package/dist/src/cli/init/claudemd-generator.js +30 -33
- package/dist/src/cli/init/executor.js +53 -69
- package/dist/src/cli/init/helpers-generator.js +165 -52
- package/dist/src/cli/init/moflo-init.js +41 -114
- package/dist/src/cli/init/settings-generator.js +44 -14
- package/dist/src/cli/mcp-tools/agent-tools.js +9 -27
- package/dist/src/cli/mcp-tools/hooks-tools.js +23 -21
- package/dist/src/cli/memory/controllers/semantic-router.js +18 -12
- package/dist/src/cli/memory/sona-optimizer.js +6 -6
- package/dist/src/cli/neural/domain/services/learning-service.js +3 -3
- package/dist/src/cli/services/agent-router.js +2 -5
- package/dist/src/cli/services/hook-block-hash.js +11 -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/shared/events/example-usage.js +6 -6
- package/dist/src/cli/shared/hooks/task-hooks.js +8 -8
- package/dist/src/cli/version.js +1 -1
- package/package.json +3 -2
- package/retired-files.json +1989 -0
- package/scripts/post-install-bootstrap.mjs +19 -0
- package/src/cli/data/model-registry.json +2 -2
- package/.claude/agents/consensus/byzantine-coordinator.md +0 -63
- package/.claude/agents/consensus/crdt-synchronizer.md +0 -997
- package/.claude/agents/consensus/gossip-coordinator.md +0 -63
- package/.claude/agents/consensus/performance-benchmarker.md +0 -851
- package/.claude/agents/consensus/quorum-manager.md +0 -823
- package/.claude/agents/consensus/raft-manager.md +0 -63
- package/.claude/agents/consensus/security-manager.md +0 -622
- package/.claude/agents/data/ml/data-ml-model.md +0 -193
- package/.claude/agents/github/code-review-swarm.md +0 -538
- package/.claude/agents/github/github-modes.md +0 -172
- package/.claude/agents/github/issue-tracker.md +0 -311
- package/.claude/agents/github/multi-repo-swarm.md +0 -551
- package/.claude/agents/github/pr-manager.md +0 -183
- package/.claude/agents/github/project-board-sync.md +0 -508
- package/.claude/agents/github/release-manager.md +0 -360
- package/.claude/agents/github/release-swarm.md +0 -580
- package/.claude/agents/github/repo-architect.md +0 -391
- package/.claude/agents/github/swarm-issue.md +0 -566
- package/.claude/agents/github/swarm-pr.md +0 -414
- package/.claude/agents/github/sync-coordinator.md +0 -426
- package/.claude/agents/github/workflow-automation.md +0 -606
- package/.claude/agents/goal/code-goal-planner.md +0 -440
- package/.claude/agents/goal/goal-planner.md +0 -168
- package/.claude/agents/hive-mind/collective-intelligence-coordinator.md +0 -127
- package/.claude/agents/hive-mind/queen-coordinator.md +0 -198
- package/.claude/agents/hive-mind/scout-explorer.md +0 -233
- package/.claude/agents/hive-mind/swarm-memory-manager.md +0 -184
- package/.claude/agents/hive-mind/worker-specialist.md +0 -208
- package/.claude/agents/neural/safla-neural.md +0 -73
- package/.claude/agents/optimization/benchmark-suite.md +0 -665
- package/.claude/agents/optimization/load-balancer.md +0 -431
- package/.claude/agents/optimization/performance-monitor.md +0 -672
- package/.claude/agents/optimization/resource-allocator.md +0 -674
- package/.claude/agents/optimization/topology-optimizer.md +0 -808
- package/.claude/agents/reasoning/goal-planner.md +0 -67
- package/.claude/agents/sona/sona-learning-optimizer.md +0 -74
- package/.claude/agents/sparc/architecture.md +0 -472
- package/.claude/agents/sparc/pseudocode.md +0 -318
- package/.claude/agents/sparc/refinement.md +0 -525
- package/.claude/agents/sparc/specification.md +0 -276
- package/.claude/agents/specialized/mobile/spec-mobile-react-native.md +0 -225
- package/.claude/agents/swarm/adaptive-coordinator.md +0 -391
- package/.claude/agents/swarm/hierarchical-coordinator.md +0 -321
- package/.claude/agents/swarm/mesh-coordinator.md +0 -383
- package/.claude/agents/testing/production-validator.md +0 -395
- package/.claude/agents/testing/tdd-london-swarm.md +0 -244
- package/.claude/agents/v3/adr-architect.md +0 -184
- package/.claude/agents/v3/aidefence-guardian.md +0 -277
- package/.claude/agents/v3/claims-authorizer.md +0 -208
- package/.claude/agents/v3/collective-intelligence-coordinator.md +0 -988
- package/.claude/agents/v3/ddd-domain-expert.md +0 -220
- package/.claude/agents/v3/injection-analyst.md +0 -232
- package/.claude/agents/v3/memory-specialist.md +0 -987
- package/.claude/agents/v3/performance-engineer.md +0 -1225
- package/.claude/agents/v3/pii-detector.md +0 -146
- package/.claude/agents/v3/reasoningbank-learner.md +0 -213
- package/.claude/agents/v3/security-architect-aidefence.md +0 -405
- package/.claude/agents/v3/security-architect.md +0 -865
- package/.claude/agents/v3/security-auditor.md +0 -771
- package/.claude/agents/v3/sparc-orchestrator.md +0 -182
- package/.claude/agents/v3/swarm-memory-manager.md +0 -142
- package/.claude/agents/v3/v3-integration-architect.md +0 -205
- package/.claude/commands/claude-flow-help.md +0 -103
- package/.claude/commands/claude-flow-memory.md +0 -107
- package/.claude/commands/claude-flow-swarm.md +0 -205
- package/.claude/commands/github/README.md +0 -11
- package/.claude/commands/github/code-review-swarm.md +0 -514
- package/.claude/commands/github/code-review.md +0 -25
- package/.claude/commands/github/github-modes.md +0 -146
- package/.claude/commands/github/github-swarm.md +0 -113
- package/.claude/commands/github/issue-tracker.md +0 -284
- package/.claude/commands/github/issue-triage.md +0 -25
- package/.claude/commands/github/multi-repo-swarm.md +0 -519
- package/.claude/commands/github/pr-enhance.md +0 -26
- package/.claude/commands/github/pr-manager.md +0 -164
- package/.claude/commands/github/project-board-sync.md +0 -471
- package/.claude/commands/github/release-manager.md +0 -332
- package/.claude/commands/github/release-swarm.md +0 -544
- package/.claude/commands/github/repo-analyze.md +0 -25
- package/.claude/commands/github/repo-architect.md +0 -361
- package/.claude/commands/github/swarm-issue.md +0 -482
- package/.claude/commands/github/swarm-pr.md +0 -285
- package/.claude/commands/github/sync-coordinator.md +0 -294
- package/.claude/commands/github/workflow-automation.md +0 -442
- package/.claude/commands/hooks/README.md +0 -11
- package/.claude/commands/hooks/overview.md +0 -58
- package/.claude/commands/hooks/post-edit.md +0 -117
- package/.claude/commands/hooks/post-task.md +0 -112
- package/.claude/commands/hooks/pre-edit.md +0 -113
- package/.claude/commands/hooks/pre-task.md +0 -111
- package/.claude/commands/hooks/session-end.md +0 -118
- package/.claude/commands/hooks/setup.md +0 -103
- package/.claude/commands/simplify.md +0 -101
- package/.claude/commands/sparc/analyzer.md +0 -42
- package/.claude/commands/sparc/architect.md +0 -43
- package/.claude/commands/sparc/ask.md +0 -86
- package/.claude/commands/sparc/batch-executor.md +0 -44
- package/.claude/commands/sparc/code.md +0 -78
- package/.claude/commands/sparc/coder.md +0 -44
- package/.claude/commands/sparc/debug.md +0 -72
- package/.claude/commands/sparc/debugger.md +0 -44
- package/.claude/commands/sparc/designer.md +0 -43
- package/.claude/commands/sparc/devops.md +0 -98
- package/.claude/commands/sparc/docs-writer.md +0 -69
- package/.claude/commands/sparc/documenter.md +0 -44
- package/.claude/commands/sparc/innovator.md +0 -44
- package/.claude/commands/sparc/integration.md +0 -72
- package/.claude/commands/sparc/mcp.md +0 -106
- package/.claude/commands/sparc/memory-manager.md +0 -44
- package/.claude/commands/sparc/optimizer.md +0 -44
- package/.claude/commands/sparc/orchestrator.md +0 -116
- package/.claude/commands/sparc/post-deployment-monitoring-mode.md +0 -72
- package/.claude/commands/sparc/refinement-optimization-mode.md +0 -72
- package/.claude/commands/sparc/researcher.md +0 -44
- package/.claude/commands/sparc/reviewer.md +0 -44
- package/.claude/commands/sparc/security-review.md +0 -69
- package/.claude/commands/sparc/sparc-modes.md +0 -139
- package/.claude/commands/sparc/sparc.md +0 -99
- package/.claude/commands/sparc/spec-pseudocode.md +0 -69
- package/.claude/commands/sparc/spell-manager.md +0 -44
- package/.claude/commands/sparc/supabase-admin.md +0 -337
- package/.claude/commands/sparc/swarm-coordinator.md +0 -44
- package/.claude/commands/sparc/tdd.md +0 -44
- package/.claude/commands/sparc/tester.md +0 -44
- package/.claude/commands/sparc/tutorial.md +0 -68
- package/.claude/commands/sparc.md +0 -151
- 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: {}, flMode: null, swarmInitialized: false, hiveInitialized: false };
|
|
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
|
|
@@ -60,7 +60,7 @@ function writeState(s) {
|
|
|
60
60
|
|
|
61
61
|
// Load moflo.yaml gate config (defaults: all enabled)
|
|
62
62
|
function loadGateConfig() {
|
|
63
|
-
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true };
|
|
63
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true, swarm_invocation_gate: true };
|
|
64
64
|
try {
|
|
65
65
|
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
66
66
|
if (fs.existsSync(yamlPath)) {
|
|
@@ -71,6 +71,7 @@ function loadGateConfig() {
|
|
|
71
71
|
if (/testing_gate:\s*false/i.test(content)) defaults.testing_gate = false;
|
|
72
72
|
if (/simplify_gate:\s*false/i.test(content)) defaults.simplify_gate = false;
|
|
73
73
|
if (/learnings_gate:\s*false/i.test(content)) defaults.learnings_gate = false;
|
|
74
|
+
if (/swarm_invocation_gate:\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
|
|
74
75
|
}
|
|
75
76
|
} catch (e) { /* use defaults */ }
|
|
76
77
|
return defaults;
|
|
@@ -83,6 +84,99 @@ var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state
|
|
|
83
84
|
var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
|
|
84
85
|
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
|
|
85
86
|
var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
|
|
87
|
+
|
|
88
|
+
// Namespace classification (#931). The hint used to be emitted on every prompt
|
|
89
|
+
// by prompt-hook.mjs which cost ~40 tokens × every prompt × every consumer.
|
|
90
|
+
// Now we classify here, store on workflow-state, and let check-before-agent
|
|
91
|
+
// emit it once when Claude is actually about to spawn an agent.
|
|
92
|
+
//
|
|
93
|
+
// SYNC: these regexes + classifyNamespaceHint + applyPromptStateReset are
|
|
94
|
+
// duplicated verbatim in src/cli/init/helpers-generator.ts (the embedded
|
|
95
|
+
// gate.cjs fallback used by `flo init` when source helpers can't be located).
|
|
96
|
+
// Any edit to either copy MUST be applied to both — there is no shared module
|
|
97
|
+
// because helpers-generator emits a self-contained string template.
|
|
98
|
+
var NS_LEARNINGS_RE = /\b(remember|recall|insight|lesson learned|gotcha|post.?mortem)\b|we (decid|agree|chose|said)/;
|
|
99
|
+
var NS_TEST_RE = /\b(test|spec|coverage|tested|test case|test cases|tests for|spec for)\b/;
|
|
100
|
+
var NS_EXPLICIT = [
|
|
101
|
+
{ pattern: /\b(pattern|convention|best practice|style|coding rule)\b/, ns: 'patterns', label: 'code patterns and conventions' },
|
|
102
|
+
{ pattern: /\b(code.?map|file structure|project structure|directory)\b/, ns: 'code-map', label: 'codebase navigation' },
|
|
103
|
+
];
|
|
104
|
+
var NS_PATTERN_RES = [/\b(template|example|similar to|how do we|how should)\b/];
|
|
105
|
+
var NS_DOMAIN_RES = [
|
|
106
|
+
/\b(guidance|guide|docs|documentation|rules|how-to)\b/,
|
|
107
|
+
/\b(architecture|design|domain|tenant|migrat|schema|deploy)/,
|
|
108
|
+
/\b(rule|requirement|constraint|compliance)\b/,
|
|
109
|
+
];
|
|
110
|
+
var NS_NAV_RES = [
|
|
111
|
+
/\b(find|where|which file|look up|locate|endpoint|route|url|path)\b/,
|
|
112
|
+
/\b(class|function|method|component|service|entity|module)\b/,
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
// Detect whether the current prompt invoked /fl or /flo with a swarm/hive flag (#952).
|
|
116
|
+
// When set, check-before-agent BLOCKS the Agent spawn until the matching MCP init
|
|
117
|
+
// (mcp__moflo__swarm_init or mcp__moflo__hive-mind_init) has been recorded — the user
|
|
118
|
+
// explicitly opted in to the protected coordination surface, so falling back to
|
|
119
|
+
// raw Agent dispatch silently regresses headline moflo product capability.
|
|
120
|
+
//
|
|
121
|
+
// SYNC: duplicated verbatim in src/cli/init/helpers-generator.ts.
|
|
122
|
+
function detectFlMode(promptText) {
|
|
123
|
+
var p = promptText || '';
|
|
124
|
+
if (!/^\s*\/(?:fl|flo)\b/i.test(p)) return null;
|
|
125
|
+
if (/(?:^|\s)(?:-s|--swarm)\b/.test(p)) return 'swarm';
|
|
126
|
+
if (/(?:^|\s)(?:-h|--hive)\b/.test(p)) return 'hive';
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function classifyNamespaceHint(promptText) {
|
|
131
|
+
var lower = (promptText || '').toLowerCase();
|
|
132
|
+
if (NS_TEST_RE.test(lower)) return 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
|
|
133
|
+
if (NS_LEARNINGS_RE.test(lower)) return 'Memory namespace hint: use "learnings" for user-directed decisions and distilled insights.';
|
|
134
|
+
for (var i = 0; i < NS_EXPLICIT.length; i++) {
|
|
135
|
+
if (NS_EXPLICIT[i].pattern.test(lower)) return 'Memory namespace hint: use "' + NS_EXPLICIT[i].ns + '" for ' + NS_EXPLICIT[i].label + '.';
|
|
136
|
+
}
|
|
137
|
+
for (var j = 0; j < NS_DOMAIN_RES.length; j++) {
|
|
138
|
+
if (NS_DOMAIN_RES[j].test(lower)) return 'Memory namespace hint: search "guidance" and "learnings" for domain rules and project decisions.';
|
|
139
|
+
}
|
|
140
|
+
for (var k = 0; k < NS_PATTERN_RES.length; k++) {
|
|
141
|
+
if (NS_PATTERN_RES[k].test(lower)) return 'Memory namespace hint: use "patterns" for code patterns and conventions.';
|
|
142
|
+
}
|
|
143
|
+
for (var m = 0; m < NS_NAV_RES.length; m++) {
|
|
144
|
+
if (NS_NAV_RES[m].test(lower)) return 'Memory namespace hint: use "code-map" for codebase navigation.';
|
|
145
|
+
}
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Apply per-prompt state reset shared by `prompt-reminder` (full) and
|
|
150
|
+
// `prompt-state-reset` (defensive safety-net, no emission). Idempotent — both
|
|
151
|
+
// UserPromptSubmit hooks can run it without compounding any field. Caller
|
|
152
|
+
// owns interactionCount and the user-visible REMINDER/Context emissions, so
|
|
153
|
+
// this helper stays silent.
|
|
154
|
+
function applyPromptStateReset(state, promptText) {
|
|
155
|
+
state.memorySearched = false;
|
|
156
|
+
// Wipe per-actor memory tracking too — a new user prompt is a fresh window
|
|
157
|
+
// for both parent AND any subagents the parent may spawn during this turn.
|
|
158
|
+
state.memorySearchedBy = {};
|
|
159
|
+
// learningsStored is session-scoped — once stored, it stays true until session reset.
|
|
160
|
+
// Resetting per-prompt caused false blocks when PR creation was on a later prompt.
|
|
161
|
+
var DIRECTIVE_MAX_LEN = 20;
|
|
162
|
+
var escaped = /^@@\s*/.test(promptText || '');
|
|
163
|
+
state.memoryRequired = !escaped && (promptText || '').length >= 4 && (TASK_RE.test(promptText || '') || (promptText || '').length > DIRECTIVE_MAX_LEN);
|
|
164
|
+
// Stash namespace hint for check-before-agent to emit when Claude actually
|
|
165
|
+
// spawns an Agent (#931). Empty string when nothing matched — overwriting
|
|
166
|
+
// any stale value from the previous prompt.
|
|
167
|
+
state.lastNamespaceHint = classifyNamespaceHint(promptText);
|
|
168
|
+
// Per-actor emission tracking — each subagent's session gets the hint at
|
|
169
|
+
// most once per prompt, but a fresh prompt resets every actor's window so
|
|
170
|
+
// subsequent agents (parent + subagents that spawn their own agents) all
|
|
171
|
+
// see the new classification on their first check-before-agent.
|
|
172
|
+
state.lastNamespaceHintEmittedBy = {};
|
|
173
|
+
// #952 — derive flMode from the user prompt, and reset the matching init
|
|
174
|
+
// flag. Each /fl invocation must call its protected MCP init; the previous
|
|
175
|
+
// prompt's swarm/hive registration does not satisfy this prompt's gate.
|
|
176
|
+
state.flMode = detectFlMode(promptText);
|
|
177
|
+
state.swarmInitialized = false;
|
|
178
|
+
state.hiveInitialized = false;
|
|
179
|
+
}
|
|
86
180
|
// Match npm/yarn/pnpm/bun test, npx vitest|jest|..., bare runners at command-start only,
|
|
87
181
|
// and language-native test commands. The bare-runner arm is anchored so that
|
|
88
182
|
// `npm install jest`, `grep -r vitest src/`, and similar don't false-positive.
|
|
@@ -203,6 +297,11 @@ switch (command) {
|
|
|
203
297
|
// Advisory only — agent spawning is never blocked.
|
|
204
298
|
// Memory-first enforcement happens at the scan/read gate layer.
|
|
205
299
|
// SubagentStart hook injects guidance directive into subagent context.
|
|
300
|
+
//
|
|
301
|
+
// #931 — TaskCreate REMINDER and the namespace hint moved here from
|
|
302
|
+
// prompt-reminder. They only matter when Claude is actually about to spawn
|
|
303
|
+
// an Agent; emitting per-prompt cost ~90 tokens × every prompt × every
|
|
304
|
+
// consumer.
|
|
206
305
|
var s = readState();
|
|
207
306
|
if (config.task_create_first && !s.tasksCreated) {
|
|
208
307
|
process.stdout.write('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.\n');
|
|
@@ -210,6 +309,65 @@ switch (command) {
|
|
|
210
309
|
if (config.memory_first && s.memoryRequired && !s.memorySearched) {
|
|
211
310
|
process.stdout.write('REMINDER: Search memory (mcp__moflo__memory_search) before spawning agents.\n');
|
|
212
311
|
}
|
|
312
|
+
if (s.lastNamespaceHint) {
|
|
313
|
+
// Per-actor single-shot. Each session_id gets the hint at most once per
|
|
314
|
+
// prompt, but the hint itself stays available for other actors (e.g.
|
|
315
|
+
// a subagent that spawns its own agent has its own session_id and is
|
|
316
|
+
// entitled to a fresh emission). Falls back to a `_legacy_` bucket when
|
|
317
|
+
// Claude Code didn't forward a session_id (older host or direct CLI
|
|
318
|
+
// invocation), preserving the old "emit once globally" behavior. The
|
|
319
|
+
// map is wiped by applyPromptStateReset on every new prompt.
|
|
320
|
+
var sid = process.env.HOOK_SESSION_ID || '';
|
|
321
|
+
var emittedBy = s.lastNamespaceHintEmittedBy || {};
|
|
322
|
+
var bucket = sid || '_legacy_';
|
|
323
|
+
if (!emittedBy[bucket]) {
|
|
324
|
+
process.stdout.write(s.lastNamespaceHint + '\n');
|
|
325
|
+
emittedBy[bucket] = true;
|
|
326
|
+
s.lastNamespaceHintEmittedBy = emittedBy;
|
|
327
|
+
writeState(s);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// #952 — when /fl was invoked with -s/-h, the protected MCP init must run
|
|
331
|
+
// BEFORE any Agent spawn. Hard block: the user explicitly opted in to
|
|
332
|
+
// moflo's coordination surface, so silently dispatching `Agent` calls
|
|
333
|
+
// without `mcp__moflo__swarm_init` / `mcp__moflo__hive-mind_init` is the
|
|
334
|
+
// failure mode this gate exists to prevent (CLAUDE.md "⛔ Protected
|
|
335
|
+
// functionality — swarm + hive-mind"). Other Agent uses remain advisory.
|
|
336
|
+
if (config.swarm_invocation_gate) {
|
|
337
|
+
if (s.flMode === 'swarm' && !s.swarmInitialized) {
|
|
338
|
+
process.stderr.write('BLOCKED: /fl was invoked with -s/--swarm but mcp__moflo__swarm_init has not been called.\n');
|
|
339
|
+
process.stderr.write('Run mcp__moflo__swarm_init first, then mcp__moflo__agent_spawn for each role, then dispatch Agent.\n');
|
|
340
|
+
process.stderr.write('See .claude/skills/fl/execution-modes.md "SWARM mode" and CLAUDE.md "⛔ Protected functionality".\n');
|
|
341
|
+
process.stderr.write('Disable via moflo.yaml: gates: swarm_invocation_gate: false\n');
|
|
342
|
+
process.exit(2);
|
|
343
|
+
}
|
|
344
|
+
if (s.flMode === 'hive' && !s.hiveInitialized) {
|
|
345
|
+
process.stderr.write('BLOCKED: /fl was invoked with -h/--hive but mcp__moflo__hive-mind_init has not been called.\n');
|
|
346
|
+
process.stderr.write('Run mcp__moflo__hive-mind_init first, then dispatch Agent or hive-mind workers.\n');
|
|
347
|
+
process.stderr.write('See .claude/skills/fl/execution-modes.md "HIVE-MIND mode" and CLAUDE.md "⛔ Protected functionality".\n');
|
|
348
|
+
process.stderr.write('Disable via moflo.yaml: gates: swarm_invocation_gate: false\n');
|
|
349
|
+
process.exit(2);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case 'record-swarm-init': {
|
|
355
|
+
// #952 — wired to mcp__moflo__swarm_init PostToolUse. Marks the gate
|
|
356
|
+
// satisfied so subsequent Agent spawns under /fl -s pass.
|
|
357
|
+
var s = readState();
|
|
358
|
+
if (!s.swarmInitialized) {
|
|
359
|
+
s.swarmInitialized = true;
|
|
360
|
+
writeState(s);
|
|
361
|
+
}
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
case 'record-hive-init': {
|
|
365
|
+
// #952 — wired to mcp__moflo__hive-mind_init PostToolUse.
|
|
366
|
+
var s = readState();
|
|
367
|
+
if (!s.hiveInitialized) {
|
|
368
|
+
s.hiveInitialized = true;
|
|
369
|
+
writeState(s);
|
|
370
|
+
}
|
|
213
371
|
break;
|
|
214
372
|
}
|
|
215
373
|
case 'check-before-scan': {
|
|
@@ -277,7 +435,8 @@ switch (command) {
|
|
|
277
435
|
break;
|
|
278
436
|
}
|
|
279
437
|
case 'record-skill-run': {
|
|
280
|
-
|
|
438
|
+
var skName = (process.env.TOOL_INPUT_skill || '');
|
|
439
|
+
if (skName === 'simplify' || skName === 'flo-simplify') {
|
|
281
440
|
var s = readState();
|
|
282
441
|
var changed = false;
|
|
283
442
|
if (!s.simplifyRun) { s.simplifyRun = true; changed = true; }
|
|
@@ -346,7 +505,7 @@ switch (command) {
|
|
|
346
505
|
}
|
|
347
506
|
var missing = [];
|
|
348
507
|
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');
|
|
508
|
+
if (config.simplify_gate && !s.simplifyRun) missing.push('/flo-simplify has not run since the last code edit');
|
|
350
509
|
if (config.learnings_gate && !s.learningsStored) missing.push('learnings have not been stored (call mcp__moflo__memory_store)');
|
|
351
510
|
if (missing.length === 0) break;
|
|
352
511
|
process.stderr.write('BLOCKED: gh pr create requires the following before opening a PR:\n');
|
|
@@ -371,20 +530,16 @@ switch (command) {
|
|
|
371
530
|
break;
|
|
372
531
|
}
|
|
373
532
|
case 'prompt-reminder': {
|
|
533
|
+
// Full per-prompt reset. Wired as the first UserPromptSubmit hook (via
|
|
534
|
+
// prompt-hook.mjs). Owns interactionCount + Context warnings; the
|
|
535
|
+
// TaskCreate REMINDER and namespace hint moved to check-before-agent
|
|
536
|
+
// (#931) so they only fire when Claude is actually about to spawn an
|
|
537
|
+
// Agent.
|
|
374
538
|
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
539
|
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);
|
|
540
|
+
applyPromptStateReset(s, prompt);
|
|
385
541
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
386
542
|
writeState(s);
|
|
387
|
-
if (!s.tasksCreated) console.log('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.');
|
|
388
543
|
if (config.context_tracking) {
|
|
389
544
|
var ic = s.interactionCount;
|
|
390
545
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
@@ -393,12 +548,34 @@ switch (command) {
|
|
|
393
548
|
}
|
|
394
549
|
break;
|
|
395
550
|
}
|
|
551
|
+
case 'prompt-state-reset': {
|
|
552
|
+
// Defensive safety-net hook (#931 dedupe). Wired as the second
|
|
553
|
+
// UserPromptSubmit hook so an exception in prompt-hook.mjs doesn't skip
|
|
554
|
+
// the per-prompt state reset. Idempotent — applyPromptStateReset only
|
|
555
|
+
// sets fields to derived values, and we deliberately do NOT increment
|
|
556
|
+
// interactionCount or emit anything (that's prompt-reminder's job).
|
|
557
|
+
//
|
|
558
|
+
// Skip the disk write on the normal path: prompt-reminder runs first and
|
|
559
|
+
// already wrote the byte-identical post-reset state. Only writeState when
|
|
560
|
+
// the reset actually changed something (i.e., prompt-reminder was skipped
|
|
561
|
+
// because prompt-hook.mjs threw before invoking it).
|
|
562
|
+
var s = readState();
|
|
563
|
+
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
564
|
+
var before = JSON.stringify(s);
|
|
565
|
+
applyPromptStateReset(s, prompt);
|
|
566
|
+
if (JSON.stringify(s) !== before) writeState(s);
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
396
569
|
case 'compact-guidance': {
|
|
397
570
|
console.log('Pre-Compact: Check CLAUDE.md for rules. Use memory search to recover context after compact.');
|
|
398
571
|
break;
|
|
399
572
|
}
|
|
400
573
|
case 'session-reset': {
|
|
401
|
-
|
|
574
|
+
// Derive from STATE_DEFAULTS so adding a new state field requires only one
|
|
575
|
+
// edit (the defaults object) — the literal that used to live here drifted
|
|
576
|
+
// every time a field was added and is what motivated #952's audit of state
|
|
577
|
+
// shape consistency.
|
|
578
|
+
writeState(Object.assign({}, STATE_DEFAULTS, { sessionStart: new Date().toISOString() }));
|
|
402
579
|
break;
|
|
403
580
|
}
|
|
404
581
|
default:
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retired-shipped-files prune helper (#948).
|
|
3
|
+
*
|
|
4
|
+
* Closes the gap that retired agents (#932) and retired skills (#945) leave
|
|
5
|
+
* in consumer projects: the manifest cleanup at section 3 of the launcher
|
|
6
|
+
* only knows about files moflo previously synced, but `.claude/agents/` and
|
|
7
|
+
* `.claude/skills/` were not in the manifest before #948. So files retired
|
|
8
|
+
* before #948 lands stay on disk forever — Claude Code keeps loading them as
|
|
9
|
+
* subagents on every prompt, paying the per-prompt roster tokens we just
|
|
10
|
+
* spent #932 fixing.
|
|
11
|
+
*
|
|
12
|
+
* This module reads `retired-files.json` (shipped at the moflo package
|
|
13
|
+
* root) and, for each entry, only prunes the consumer-side path when the
|
|
14
|
+
* file's sha256 matches one of `knownContentHashes` — i.e. the file matches
|
|
15
|
+
* a version moflo actually shipped, so the consumer didn't customize it.
|
|
16
|
+
* Customized files are preserved and a one-line notice is emitted so the
|
|
17
|
+
* user can act.
|
|
18
|
+
*
|
|
19
|
+
* Manifest format:
|
|
20
|
+
*
|
|
21
|
+
* {
|
|
22
|
+
* "version": 1,
|
|
23
|
+
* "retired": [
|
|
24
|
+
* {
|
|
25
|
+
* "path": ".claude/agents/v3/performance-engineer.md",
|
|
26
|
+
* "retiredIn": "4.9.22",
|
|
27
|
+
* "retiredBy": "#932",
|
|
28
|
+
* "knownContentHashes": ["sha256:abc...", "sha256:def..."]
|
|
29
|
+
* }
|
|
30
|
+
* ]
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* @module bin/lib/retired-files
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
37
|
+
import { resolve } from 'path';
|
|
38
|
+
import { createHash } from 'crypto';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Compute sha256 of file content. Returns null on read errors so the caller
|
|
42
|
+
* can decide what to do — we never want a transient stat/read failure to
|
|
43
|
+
* trigger a prune of a file we couldn't actually verify.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} absPath
|
|
46
|
+
* @returns {string|null} `sha256:<hex>` or null
|
|
47
|
+
*/
|
|
48
|
+
export function fileSha256(absPath) {
|
|
49
|
+
try {
|
|
50
|
+
const buf = readFileSync(absPath);
|
|
51
|
+
return 'sha256:' + createHash('sha256').update(buf).digest('hex');
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Load + validate the retired-files manifest. Returns `{ entries: [] }` for
|
|
59
|
+
* any failure mode (missing file, invalid JSON, wrong shape) — the launcher
|
|
60
|
+
* must never block on a corrupt manifest.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} manifestPath - absolute path to retired-files.json
|
|
63
|
+
* @returns {{ entries: Array<{path:string, retiredIn?:string, retiredBy?:string, knownContentHashes:string[]}> }}
|
|
64
|
+
*/
|
|
65
|
+
export function loadRetiredManifest(manifestPath) {
|
|
66
|
+
if (!existsSync(manifestPath)) return { entries: [] };
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
70
|
+
} catch { return { entries: [] }; }
|
|
71
|
+
if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.retired)) {
|
|
72
|
+
return { entries: [] };
|
|
73
|
+
}
|
|
74
|
+
// Filter out malformed entries — guards against a hand-edited file
|
|
75
|
+
// dropping the wrong shape into a launcher that runs on N consumer
|
|
76
|
+
// machines. A skipped entry never auto-prunes (safest default).
|
|
77
|
+
const entries = parsed.retired.filter((entry) =>
|
|
78
|
+
entry &&
|
|
79
|
+
typeof entry.path === 'string' &&
|
|
80
|
+
entry.path.length > 0 &&
|
|
81
|
+
Array.isArray(entry.knownContentHashes) &&
|
|
82
|
+
entry.knownContentHashes.length > 0 &&
|
|
83
|
+
entry.knownContentHashes.every((h) => typeof h === 'string' && h.startsWith('sha256:'))
|
|
84
|
+
);
|
|
85
|
+
return { entries };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Decide what to do with a consumer-side path against a retirement entry:
|
|
90
|
+
*
|
|
91
|
+
* - 'absent' — path doesn't exist on disk; nothing to do
|
|
92
|
+
* - 'prune' — file exists and content hash matches a known-shipped
|
|
93
|
+
* value; safe to delete (consumer didn't customize)
|
|
94
|
+
* - 'preserve' — file exists but content differs from every known-shipped
|
|
95
|
+
* hash; consumer customized, leave alone
|
|
96
|
+
* - 'unknown' — file exists but its hash couldn't be read (transient
|
|
97
|
+
* error); leave alone, retry next session
|
|
98
|
+
*
|
|
99
|
+
* @param {string} projectRoot
|
|
100
|
+
* @param {{path:string, knownContentHashes:string[]}} entry
|
|
101
|
+
* @returns {{ action: 'absent'|'prune'|'preserve'|'unknown', actualHash: string|null }}
|
|
102
|
+
*/
|
|
103
|
+
export function classifyRetiredFile(projectRoot, entry) {
|
|
104
|
+
const abs = resolve(projectRoot, entry.path);
|
|
105
|
+
if (!existsSync(abs)) return { action: 'absent', actualHash: null };
|
|
106
|
+
const actualHash = fileSha256(abs);
|
|
107
|
+
if (!actualHash) return { action: 'unknown', actualHash: null };
|
|
108
|
+
if (entry.knownContentHashes.includes(actualHash)) {
|
|
109
|
+
return { action: 'prune', actualHash };
|
|
110
|
+
}
|
|
111
|
+
return { action: 'preserve', actualHash };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Walk the manifest and apply prune decisions. Returns a small report so
|
|
116
|
+
* the caller can emit one summary line (the launcher pattern) instead of
|
|
117
|
+
* forcing it to track per-entry state.
|
|
118
|
+
*
|
|
119
|
+
* Failures are non-fatal — a single un-deletable file (Windows AV hold,
|
|
120
|
+
* EBUSY) must not stop pruning the rest. The caller surfaces the report.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} projectRoot
|
|
123
|
+
* @param {string} manifestPath
|
|
124
|
+
* @returns {{ pruned: string[], preserved: string[], unknown: string[], failed: Array<{path:string, message:string}> }}
|
|
125
|
+
*/
|
|
126
|
+
export function applyRetiredPrune(projectRoot, manifestPath) {
|
|
127
|
+
const { entries } = loadRetiredManifest(manifestPath);
|
|
128
|
+
const report = { pruned: [], preserved: [], unknown: [], failed: [] };
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const { action } = classifyRetiredFile(projectRoot, entry);
|
|
131
|
+
if (action === 'absent') continue;
|
|
132
|
+
if (action === 'preserve') { report.preserved.push(entry.path); continue; }
|
|
133
|
+
if (action === 'unknown') { report.unknown.push(entry.path); continue; }
|
|
134
|
+
// action === 'prune'
|
|
135
|
+
try {
|
|
136
|
+
unlinkSync(resolve(projectRoot, entry.path));
|
|
137
|
+
report.pruned.push(entry.path);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
report.failed.push({
|
|
140
|
+
path: entry.path,
|
|
141
|
+
message: err && err.message ? err.message : String(err),
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return report;
|
|
146
|
+
}
|
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);
|
|
@@ -14,6 +14,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
|
|
|
14
14
|
import { mofloDir } from './lib/moflo-paths.mjs';
|
|
15
15
|
import { repairMemoryDbIfCorrupt } from './lib/db-repair.mjs';
|
|
16
16
|
import { resolveMofloBin } from './lib/resolve-bin.mjs';
|
|
17
|
+
import { applyRetiredPrune } from './lib/retired-files.mjs';
|
|
17
18
|
|
|
18
19
|
// Headless skip (#860). The daemon's headless workers spawn `claude --print`
|
|
19
20
|
// with CLAUDE_CODE_HEADLESS=true (see src/cli/services/headless-worker-
|
|
@@ -681,6 +682,56 @@ try {
|
|
|
681
682
|
}
|
|
682
683
|
}
|
|
683
684
|
|
|
685
|
+
// ── Sync .claude/agents/ + .claude/skills/ recursively (#948) ──────
|
|
686
|
+
// Pre-#948, agents and skills weren't manifest-tracked at all, so any
|
|
687
|
+
// file moflo retired (e.g. the 49 ruflo-aspirational agents in #932 or
|
|
688
|
+
// skill-builder in #945) would linger forever in consumer projects —
|
|
689
|
+
// Claude Code kept loading them on every prompt, paying the per-prompt
|
|
690
|
+
// roster tokens we just spent #932 fixing. Walking these dirs through
|
|
691
|
+
// syncFile() puts every shipped file in `currentManifest`, which lets
|
|
692
|
+
// the cleanup loop below auto-prune retired files going forward.
|
|
693
|
+
//
|
|
694
|
+
// Limitation: this only catches retirements that happen AFTER #948.
|
|
695
|
+
// For files retired BEFORE #948 (#932 + #945), see the retired-files.json
|
|
696
|
+
// hash-gated prune block further down — it ships a static list with
|
|
697
|
+
// content hashes so we only delete files the consumer didn't customize.
|
|
698
|
+
//
|
|
699
|
+
// User-authored files at custom paths (.claude/agents/custom/foo.md,
|
|
700
|
+
// .claude/skills/<custom>/SKILL.md) are NEVER passed through syncFile()
|
|
701
|
+
// because they don't exist in node_modules/moflo/, so they never enter
|
|
702
|
+
// the manifest and never get pruned — same proven safety story as
|
|
703
|
+
// scripts/helpers above.
|
|
704
|
+
async function syncDirRecursive(srcDir, destPrefix) {
|
|
705
|
+
if (!existsSync(srcDir)) return;
|
|
706
|
+
let entries;
|
|
707
|
+
try {
|
|
708
|
+
entries = readdirSync(srcDir, { recursive: true, withFileTypes: true });
|
|
709
|
+
} catch (err) {
|
|
710
|
+
emitWarning(`${destPrefix} readdir failed (${errMessage(err)})`);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
for (const entry of entries) {
|
|
714
|
+
if (!entry.isFile()) continue;
|
|
715
|
+
if (!entry.name.toLowerCase().endsWith('.md')) continue;
|
|
716
|
+
const parent = entry.parentPath || entry.path || srcDir;
|
|
717
|
+
const absSrc = resolve(parent, entry.name);
|
|
718
|
+
const rel = absSrc.slice(srcDir.length + 1).split(/[\\/]/).join('/');
|
|
719
|
+
const absDest = resolve(projectRoot, destPrefix, rel);
|
|
720
|
+
try { mkdirSync(dirname(absDest), { recursive: true }); } catch (err) {
|
|
721
|
+
emitWarning(`${destPrefix} subdir mkdir failed for ${rel} (${errMessage(err)})`);
|
|
722
|
+
}
|
|
723
|
+
await syncFile(absSrc, absDest, `${destPrefix}/${rel}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
await syncDirRecursive(
|
|
727
|
+
resolve(projectRoot, 'node_modules/moflo/.claude/agents'),
|
|
728
|
+
'.claude/agents',
|
|
729
|
+
);
|
|
730
|
+
await syncDirRecursive(
|
|
731
|
+
resolve(projectRoot, 'node_modules/moflo/.claude/skills'),
|
|
732
|
+
'.claude/skills',
|
|
733
|
+
);
|
|
734
|
+
|
|
684
735
|
// Sync all shipped guidance files from node_modules/moflo/.claude/guidance/shipped/
|
|
685
736
|
const guidanceDir = resolve(projectRoot, '.claude/guidance');
|
|
686
737
|
const shippedDir = resolve(projectRoot, 'node_modules/moflo/.claude/guidance/shipped');
|
|
@@ -725,6 +776,56 @@ try {
|
|
|
725
776
|
emitMutation('cleaned up retired files', `${removedFiles} removed`);
|
|
726
777
|
}
|
|
727
778
|
|
|
779
|
+
// ── Hash-gated prune of pre-#948 retirements (Mechanism B) ─────────
|
|
780
|
+
// The manifest cleanup above only knows about files moflo previously
|
|
781
|
+
// synced. Agents and skills retired BEFORE #948 (#932's 49 agents and
|
|
782
|
+
// #945's skill-builder, plus older retirements) were never in any
|
|
783
|
+
// manifest — they exist on disk in consumer projects only because
|
|
784
|
+
// earlier moflo versions shipped them via the npm tarball + flo init.
|
|
785
|
+
// `retired-files.json` is the explicit, reviewable static list that
|
|
786
|
+
// closes that gap. Each entry pairs a path with the sha256 hashes of
|
|
787
|
+
// every content version moflo previously shipped, so we only auto-
|
|
788
|
+
// prune when the consumer's file matches a known-shipped hash —
|
|
789
|
+
// customized files (different hash) get preserved with a one-line
|
|
790
|
+
// notice the user can act on.
|
|
791
|
+
try {
|
|
792
|
+
const retiredManifestPath = resolve(
|
|
793
|
+
projectRoot,
|
|
794
|
+
'node_modules/moflo/retired-files.json',
|
|
795
|
+
);
|
|
796
|
+
const report = applyRetiredPrune(projectRoot, retiredManifestPath);
|
|
797
|
+
if (report.pruned.length > 0) {
|
|
798
|
+
emitMutation(
|
|
799
|
+
'pruned retired shipped files',
|
|
800
|
+
`${plural(report.pruned.length, 'file')} matching known-shipped content removed`,
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (report.preserved.length > 0) {
|
|
804
|
+
// stdout (not stderr) so Claude sees this in `additionalContext`
|
|
805
|
+
// and can surface to the user — these aren't failures, just
|
|
806
|
+
// consumer-customized files we deliberately left alone.
|
|
807
|
+
const sample = report.preserved.slice(0, 5).map((p) => ` - ${p}`).join('\n');
|
|
808
|
+
const more = report.preserved.length > 5
|
|
809
|
+
? `\n …and ${report.preserved.length - 5} more`
|
|
810
|
+
: '';
|
|
811
|
+
try {
|
|
812
|
+
process.stdout.write(
|
|
813
|
+
`moflo: retained ${plural(report.preserved.length, 'customized retired file')} (delete manually if unwanted):\n${sample}${more}\n`,
|
|
814
|
+
);
|
|
815
|
+
} catch { /* non-fatal */ }
|
|
816
|
+
}
|
|
817
|
+
if (report.failed.length > 0) {
|
|
818
|
+
const sample = report.failed.slice(0, 3)
|
|
819
|
+
.map((f) => ` - ${f.path}: ${f.message}`).join('\n');
|
|
820
|
+
emitWarning(`${plural(report.failed.length, 'retired file')} failed to prune:\n${sample}`);
|
|
821
|
+
}
|
|
822
|
+
} catch (err) {
|
|
823
|
+
// Non-fatal — the consumer just doesn't get the prune this session.
|
|
824
|
+
// Next session retries; manifest-cleanup above still works for
|
|
825
|
+
// future retirements regardless.
|
|
826
|
+
emitWarning(`retired-files prune skipped (${errMessage(err)})`);
|
|
827
|
+
}
|
|
828
|
+
|
|
728
829
|
// The daemon was already stopped above so the lock file is gone and
|
|
729
830
|
// there's no live PID to recycle here. Section 4's `hooks.mjs
|
|
730
831
|
// session-start` will spawn a fresh daemon under the current moflo
|
|
@@ -1093,7 +1194,7 @@ try {
|
|
|
1093
1194
|
// Also prunes top-level mirrors whose source no longer exists in shipped/
|
|
1094
1195
|
// (#839): pre-manifest-tracking mirrors never appeared in installed-files.json
|
|
1095
1196
|
// so the section-3 manifest-diff cleanup can't reach them. The marker gate
|
|
1096
|
-
// protects user files
|
|
1197
|
+
// protects user-authored files; pre-#939 moflo-bootstrap.md is handled by 3c-939.
|
|
1097
1198
|
try {
|
|
1098
1199
|
const guidanceDir = resolve(projectRoot, '.claude/guidance');
|
|
1099
1200
|
const shippedDir = resolve(projectRoot, 'node_modules/moflo/.claude/guidance/shipped');
|
|
@@ -1169,6 +1270,24 @@ try {
|
|
|
1169
1270
|
}
|
|
1170
1271
|
} catch { /* non-fatal */ }
|
|
1171
1272
|
|
|
1273
|
+
// ── 3c-939. Remove pre-#939 moflo-bootstrap.md duplicate ────────────────────
|
|
1274
|
+
// Before #939, `flo init` Step 7 (and `flo-setup`) renamed moflo-subagents.md
|
|
1275
|
+
// to moflo-bootstrap.md while the launcher's stage-3 ALSO synced the original
|
|
1276
|
+
// name — consumers ended up with two copies of the same content on disk. The
|
|
1277
|
+
// rename is gone; this prunes any leftover from older installs. Marker-gated
|
|
1278
|
+
// so we never delete a homonymous user-authored file.
|
|
1279
|
+
try {
|
|
1280
|
+
const legacyBootstrap = resolve(projectRoot, '.claude/guidance/moflo-bootstrap.md');
|
|
1281
|
+
if (existsSync(legacyBootstrap)) {
|
|
1282
|
+
const head = readFileSync(legacyBootstrap, 'utf-8').slice(0, 200);
|
|
1283
|
+
const ours = head.includes('AUTO-GENERATED by moflo init') || head.includes('AUTO-GENERATED by flo-setup');
|
|
1284
|
+
if (ours) {
|
|
1285
|
+
unlinkSync(legacyBootstrap);
|
|
1286
|
+
emitMutation('removed legacy moflo-bootstrap.md', 'duplicate of moflo-subagents.md (#939)');
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
} catch { /* non-fatal */ }
|
|
1290
|
+
|
|
1172
1291
|
// ── 3d-yaml-create. Create moflo.yaml if missing (#895) ────────────────────
|
|
1173
1292
|
// Sibling self-heal to 3d-yaml: that block APPENDS new sections to existing
|
|
1174
1293
|
// yaml; this block CREATES the file from the canonical template when no yaml
|