moflo 4.10.27 → 4.10.28
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.
|
@@ -229,9 +229,15 @@ Confirm the published version matches what we just built.
|
|
|
229
229
|
|
|
230
230
|
```bash
|
|
231
231
|
npm install moflo@<new-version> --save-dev
|
|
232
|
+
npm install --package-lock-only --force
|
|
233
|
+
npm ci --dry-run --legacy-peer-deps
|
|
232
234
|
```
|
|
233
235
|
|
|
234
|
-
|
|
236
|
+
The first command updates `package.json` and `package-lock.json` to use the newly published version as a devDependency.
|
|
237
|
+
|
|
238
|
+
The second command (`--package-lock-only --force`) backfills cross-platform optional-dependency entries that npm omits from the lockfile when `npm install` runs on a single host platform. Without it, transitive optional deps like the nested `@rolldown/binding-wasm32-wasi/node_modules/@emnapi/*` entries get dropped when publishing from Windows, and the resulting lockfile fails `npm ci` on Ubuntu/macOS CI legs. This produced the green-`release-smoke` → red-`ci.yml` divergence in run `26487580745` (moflo@4.10.27 install commit).
|
|
239
|
+
|
|
240
|
+
The final `npm ci --dry-run --legacy-peer-deps` proves the resulting lockfile is in sync with `package.json` before we commit it. If this exits non-zero, stop and investigate — do not commit a broken lockfile.
|
|
235
241
|
|
|
236
242
|
### Step 13: Final Commit
|
|
237
243
|
|
|
@@ -673,6 +673,58 @@ export async function checkMofloYamlCompliance(cwd = process.cwd()) {
|
|
|
673
673
|
fix: 'Restart Claude Code (yaml-upgrader auto-appends) or `npx moflo init --force`',
|
|
674
674
|
};
|
|
675
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* #1229 — standing tripwire for a silently-disabled memory_first gate.
|
|
678
|
+
*
|
|
679
|
+
* `gates.memory_first` is the only enforcement of the memory-search-first
|
|
680
|
+
* protocol. When it is `false` in moflo.yaml the protocol is off repo-wide
|
|
681
|
+
* with no per-prompt signal — and because the disabled gate is itself what
|
|
682
|
+
* surfaces the stored "never disable memory_first" learning, the situation
|
|
683
|
+
* self-conceals. Historically a daemon-spawned headless analysis worker could
|
|
684
|
+
* write this value unprompted to unblock itself (the `optimize` worker editing
|
|
685
|
+
* `moflo.yaml` to `memory_first: false # Temporarily disabled for performance
|
|
686
|
+
* analysis`); that root cause is fixed by making those workers read-only, but
|
|
687
|
+
* this check is the loud, standing detector so the state never goes unnoticed
|
|
688
|
+
* again — whatever writes it (worker, agent, or a deliberate consumer edit).
|
|
689
|
+
*
|
|
690
|
+
* Warn rather than fail: disabling the gate is a legitimate (if rare) choice;
|
|
691
|
+
* the point is that it can never be silent. Matches only an *active* (un-
|
|
692
|
+
* commented) `memory_first: false` line and is EOL-agnostic so it behaves
|
|
693
|
+
* identically on CRLF (Windows) and LF (POSIX) checkouts.
|
|
694
|
+
*
|
|
695
|
+
* Exported with a cwd param so tests can target a temp root without touching
|
|
696
|
+
* process.cwd().
|
|
697
|
+
*/
|
|
698
|
+
export async function checkMemoryFirstGate(cwd = process.cwd()) {
|
|
699
|
+
const yamlPath = join(cwd, 'moflo.yaml');
|
|
700
|
+
// Absence is covered by checkMofloYamlCompliance; with no file the gate
|
|
701
|
+
// defaults to enabled, so there is nothing to warn about here.
|
|
702
|
+
if (!existsSync(yamlPath)) {
|
|
703
|
+
return { name: 'Memory-First Gate', status: 'pass', message: 'Enabled (default — no moflo.yaml override)' };
|
|
704
|
+
}
|
|
705
|
+
let content;
|
|
706
|
+
try {
|
|
707
|
+
content = readFileSync(yamlPath, 'utf-8');
|
|
708
|
+
}
|
|
709
|
+
catch (e) {
|
|
710
|
+
return { name: 'Memory-First Gate', status: 'warn', message: `Unable to read moflo.yaml: ${errorDetail(e)}` };
|
|
711
|
+
}
|
|
712
|
+
// Active value only: a line whose first non-whitespace token is
|
|
713
|
+
// `memory_first: false`. A commented `# memory_first: false` is ignored
|
|
714
|
+
// because the leading `#` defeats the `^\s*memory_first` anchor.
|
|
715
|
+
const disabled = content
|
|
716
|
+
.split(/\r?\n/)
|
|
717
|
+
.some((line) => /^\s*memory_first:\s*false\b/i.test(line));
|
|
718
|
+
if (disabled) {
|
|
719
|
+
return {
|
|
720
|
+
name: 'Memory-First Gate',
|
|
721
|
+
status: 'warn',
|
|
722
|
+
message: 'DISABLED in moflo.yaml — memory-search-first protocol is off repo-wide and silent per-prompt',
|
|
723
|
+
fix: 'Restore `gates.memory_first: true` (e.g. `git checkout -- moflo.yaml`) unless you disabled it deliberately',
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
return { name: 'Memory-First Gate', status: 'pass', message: 'Enabled' };
|
|
727
|
+
}
|
|
676
728
|
/**
|
|
677
729
|
* #981 / #987 — surfaces the single-writer-architecture safety net.
|
|
678
730
|
*
|
|
@@ -12,7 +12,7 @@ import { checkWritersAudit } from './doctor-checks-writers-audit.js';
|
|
|
12
12
|
import { checkSwarmFunctional, checkHiveMindFunctional, } from './doctor-checks-swarm.js';
|
|
13
13
|
import { checkMemoryAccessFunctional } from './doctor-checks-memory-access.js';
|
|
14
14
|
import { checkBuildTools, checkClaudeCode, checkDiskSpace, checkGit, checkGitRepo, checkNodeVersion, checkNpmVersion, } from './doctor-checks-runtime.js';
|
|
15
|
-
import { checkConfigFile, checkDaemonIdentity, checkDaemonOrphan, checkDaemonStatus, checkDaemonWriteRouting, checkMcpServers, checkMemoryDatabase, checkMemoryDbIntegrity, checkMofloYamlCompliance, checkNestedMofloIslands, checkStatusLine, checkSwarmResidue, checkTestDirs, } from './doctor-checks-config.js';
|
|
15
|
+
import { checkConfigFile, checkDaemonIdentity, checkDaemonOrphan, checkDaemonStatus, checkDaemonWriteRouting, checkMcpServers, checkMemoryDatabase, checkMemoryDbIntegrity, checkMemoryFirstGate, checkMofloYamlCompliance, checkNestedMofloIslands, checkStatusLine, checkSwarmResidue, checkTestDirs, } from './doctor-checks-config.js';
|
|
16
16
|
import { checkSpellEngine, checkSandboxTier } from './doctor-checks-platform.js';
|
|
17
17
|
import { checkEmbeddings, checkSemanticQuality, } from './doctor-checks-memory.js';
|
|
18
18
|
import { checkIntelligence } from './doctor-checks-intelligence.js';
|
|
@@ -34,6 +34,8 @@ export const allChecks = [
|
|
|
34
34
|
checkGitRepo,
|
|
35
35
|
checkConfigFile,
|
|
36
36
|
checkMofloYamlCompliance,
|
|
37
|
+
// #1229 — loud tripwire for a silently-disabled memory_first gate.
|
|
38
|
+
checkMemoryFirstGate,
|
|
37
39
|
checkStatusLine,
|
|
38
40
|
checkDaemonStatus,
|
|
39
41
|
checkDaemonVersionSkew,
|
|
@@ -97,6 +99,8 @@ export const componentMap = {
|
|
|
97
99
|
'config': checkConfigFile,
|
|
98
100
|
'yaml': checkMofloYamlCompliance,
|
|
99
101
|
'moflo-yaml': checkMofloYamlCompliance,
|
|
102
|
+
'memory-first': checkMemoryFirstGate,
|
|
103
|
+
'memory-gate': checkMemoryFirstGate,
|
|
100
104
|
'statusline': checkStatusLine,
|
|
101
105
|
'status-line': checkStatusLine,
|
|
102
106
|
'daemon': checkDaemonStatus,
|
|
@@ -46,6 +46,28 @@ const MODEL_IDS = {
|
|
|
46
46
|
opus: 'claude-opus-4-6',
|
|
47
47
|
haiku: 'claude-haiku-4-5-20251001',
|
|
48
48
|
};
|
|
49
|
+
/**
|
|
50
|
+
* Tool allowlist for every headless worker. These workers are READ-ONLY by
|
|
51
|
+
* design: they analyse the codebase and produce a report. Moflo persists their
|
|
52
|
+
* stdout to `.moflo/reports/<type>.<ext>` itself (see `executeInternal`), so
|
|
53
|
+
* they never need write capability to deliver their output.
|
|
54
|
+
*
|
|
55
|
+
* Restricting tools here is a GATE-INTEGRITY guarantee, not a tidy-up. The
|
|
56
|
+
* spawn previously ran `claude --print <prompt>` with no tool restriction, so
|
|
57
|
+
* a daemon-spawned worker inherited the consumer's permissions and could edit
|
|
58
|
+
* any file in their tree. The `optimize` ("Performance optimization") worker
|
|
59
|
+
* is `enabled: true` by default and runs unattended; given write access and a
|
|
60
|
+
* "analyze this codebase for performance optimizations" prompt, it reasons its
|
|
61
|
+
* way into editing `moflo.yaml` to `memory_first: false`
|
|
62
|
+
* (`# Temporarily disabled for performance analysis`) to unblock its own
|
|
63
|
+
* non-memory-first tool calls against the gate — silently disabling the
|
|
64
|
+
* memory-first protocol repo-wide and dirtying the working tree every run.
|
|
65
|
+
* Worse, the gate it disables is what surfaces the "never disable memory_first"
|
|
66
|
+
* learning, so the loop self-conceals. A read-only allowlist makes that edit
|
|
67
|
+
* physically impossible while leaving full analysis capability (Read/Glob/Grep)
|
|
68
|
+
* intact. See issue #1229.
|
|
69
|
+
*/
|
|
70
|
+
const HEADLESS_WORKER_ALLOWED_TOOLS = 'Read,Glob,Grep';
|
|
49
71
|
/**
|
|
50
72
|
* Default headless worker configurations based on ADR-020 (the
|
|
51
73
|
* `audit`/`document`/`predict` entries from the original ADR were dropped
|
|
@@ -811,7 +833,7 @@ export class HeadlessWorkerExecutor extends EventEmitter {
|
|
|
811
833
|
buildPrompt(template, context, reportPath) {
|
|
812
834
|
const ioInstructions = `## Output
|
|
813
835
|
|
|
814
|
-
Save the full report to \`${reportPath}\`
|
|
836
|
+
Save the full report to \`${reportPath}\` by emitting it as your response — moflo writes your output to that path after you finish, so that location is authoritative. You are a READ-ONLY analysis worker and have no write tools: do not attempt to create or modify any file in the project, and in particular never edit \`moflo.yaml\`, anything under \`.claude/\`, or any gate/config (the memory-first gate stays on — comply with it, never disable it). Include any test skeletons or code samples inline in the report as fenced code blocks.`;
|
|
815
837
|
if (!context) {
|
|
816
838
|
return `${template}
|
|
817
839
|
|
|
@@ -845,8 +867,11 @@ ${ioInstructions}`;
|
|
|
845
867
|
};
|
|
846
868
|
// Set model
|
|
847
869
|
env.ANTHROPIC_MODEL = MODEL_IDS[options.model];
|
|
848
|
-
// Spawn claude CLI process
|
|
849
|
-
|
|
870
|
+
// Spawn claude CLI process. Keep `--print` first and the prompt second
|
|
871
|
+
// (callers/tests rely on argv[0]/argv[1]); the read-only allowlist is
|
|
872
|
+
// appended so these analysis workers cannot mutate the consumer's tree
|
|
873
|
+
// (e.g. disable a gate in moflo.yaml). See HEADLESS_WORKER_ALLOWED_TOOLS.
|
|
874
|
+
const child = spawn('claude', ['--print', prompt, '--allowedTools', HEADLESS_WORKER_ALLOWED_TOOLS], {
|
|
850
875
|
cwd: this.projectRoot,
|
|
851
876
|
env,
|
|
852
877
|
stdio: ['pipe', 'pipe', 'pipe'],
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.10.
|
|
3
|
+
"version": "4.10.28",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
|
|
5
5
|
"main": "dist/src/cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
96
96
|
"@typescript-eslint/parser": "^7.18.0",
|
|
97
97
|
"eslint": "^8.0.0",
|
|
98
|
-
"moflo": "^4.10.
|
|
98
|
+
"moflo": "^4.10.27",
|
|
99
99
|
"tsx": "^4.21.0",
|
|
100
100
|
"typescript": "^5.9.3",
|
|
101
101
|
"vitest": "^4.0.0"
|