moflo 4.9.17 → 4.9.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/gate.cjs +40 -0
- package/bin/gate.cjs +40 -0
- package/dist/src/cli/commands/doctor-fixes.js +62 -16
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
package/.claude/helpers/gate.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
var fs = require('fs');
|
|
4
4
|
var path = require('path');
|
|
5
|
+
var cp = require('child_process');
|
|
5
6
|
|
|
6
7
|
var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
|
|
7
8
|
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
@@ -93,6 +94,36 @@ var EDIT_RESET_SKIP_BOTH_RE = /\.(md|markdown|txt|rst|adoc|lock|gitignore)$|(?:^
|
|
|
93
94
|
// but NOT the simplify gate — /simplify already reviewed the production code; touching
|
|
94
95
|
// a test file or fixture doesn't expose new untested surface for code review (#908).
|
|
95
96
|
var EDIT_RESET_SKIP_SIMPLIFY_ONLY_RE = /(?:^|[\\\/])(__tests__|__mocks__|tests?|spec|specs|cypress|e2e|fixtures?)[\\\/]|\.(test|spec)\.[mc]?[jt]sx?$|\.fixture\.[mc]?[jt]sx?$/i;
|
|
97
|
+
// Docs-only PR exemption: text/markup/image extensions that cannot change runtime behaviour.
|
|
98
|
+
// If EVERY file in the PR diff matches this, skip testing/simplify/learnings gates.
|
|
99
|
+
// Anchored to end-of-path so e.g. `foo.md.js` does not match. Excludes lock files / configs
|
|
100
|
+
// on purpose — those are inert for edit-reset (above) but not "documentation".
|
|
101
|
+
var DOCS_ONLY_RE = /\.(md|markdown|txt|rst|adoc|html?|pdf|png|jpe?g|gif|svg|webp|ico|bmp)$/i;
|
|
102
|
+
|
|
103
|
+
// Get the file list changed on the current branch vs the merge-base with origin/main
|
|
104
|
+
// (falling back to local main). Returns an array of repo-relative paths, or null on
|
|
105
|
+
// failure — in which case callers MUST fall through to the standard gate (fail-safe).
|
|
106
|
+
function getChangedFilesVsBase() {
|
|
107
|
+
var bases = ['origin/main', 'main', 'origin/master', 'master'];
|
|
108
|
+
var base = null;
|
|
109
|
+
for (var i = 0; i < bases.length; i++) {
|
|
110
|
+
try {
|
|
111
|
+
base = cp.execFileSync('git', ['merge-base', 'HEAD', bases[i]], {
|
|
112
|
+
cwd: PROJECT_DIR, encoding: 'utf-8', timeout: 2000, windowsHide: true,
|
|
113
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
114
|
+
}).trim();
|
|
115
|
+
if (base) break;
|
|
116
|
+
} catch (e) { /* try next */ }
|
|
117
|
+
}
|
|
118
|
+
if (!base) return null;
|
|
119
|
+
try {
|
|
120
|
+
var out = cp.execFileSync('git', ['diff', '--name-only', base + '...HEAD'], {
|
|
121
|
+
cwd: PROJECT_DIR, encoding: 'utf-8', timeout: 2000, windowsHide: true,
|
|
122
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
123
|
+
});
|
|
124
|
+
return out.split('\n').map(function(l) { return l.trim(); }).filter(Boolean);
|
|
125
|
+
} catch (e) { return null; }
|
|
126
|
+
}
|
|
96
127
|
|
|
97
128
|
switch (command) {
|
|
98
129
|
case 'check-before-agent': {
|
|
@@ -208,6 +239,15 @@ switch (command) {
|
|
|
208
239
|
// optional ENV=val prefix segment catches `GH_TOKEN=x gh pr create`.
|
|
209
240
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
210
241
|
if (!/(?:^|&&\s*|\|\|\s*|;\s*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*gh\s+pr\s+create\b/.test(cmd)) break;
|
|
242
|
+
// Docs-only exemption: if every file changed vs the merge-base is a docs/image
|
|
243
|
+
// file (no runtime-behaviour surface), skip the testing/simplify/learnings gates
|
|
244
|
+
// and surface a one-line transparency note. Falls through to the standard gate
|
|
245
|
+
// on any failure (no base, no diff, exec error) — fail-safe by design.
|
|
246
|
+
var changed = getChangedFilesVsBase();
|
|
247
|
+
if (changed && changed.length > 0 && changed.every(function(f) { return DOCS_ONLY_RE.test(f); })) {
|
|
248
|
+
process.stdout.write('Docs-only PR (' + changed.length + ' file' + (changed.length === 1 ? '' : 's') + ') — skipping testing/simplify/learnings gates.\n');
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
211
251
|
var s = readState();
|
|
212
252
|
var missing = [];
|
|
213
253
|
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)');
|
package/bin/gate.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
var fs = require('fs');
|
|
4
4
|
var path = require('path');
|
|
5
|
+
var cp = require('child_process');
|
|
5
6
|
|
|
6
7
|
var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
|
|
7
8
|
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
@@ -93,6 +94,36 @@ var EDIT_RESET_SKIP_BOTH_RE = /\.(md|markdown|txt|rst|adoc|lock|gitignore)$|(?:^
|
|
|
93
94
|
// but NOT the simplify gate — /simplify already reviewed the production code; touching
|
|
94
95
|
// a test file or fixture doesn't expose new untested surface for code review (#908).
|
|
95
96
|
var EDIT_RESET_SKIP_SIMPLIFY_ONLY_RE = /(?:^|[\\\/])(__tests__|__mocks__|tests?|spec|specs|cypress|e2e|fixtures?)[\\\/]|\.(test|spec)\.[mc]?[jt]sx?$|\.fixture\.[mc]?[jt]sx?$/i;
|
|
97
|
+
// Docs-only PR exemption: text/markup/image extensions that cannot change runtime behaviour.
|
|
98
|
+
// If EVERY file in the PR diff matches this, skip testing/simplify/learnings gates.
|
|
99
|
+
// Anchored to end-of-path so e.g. `foo.md.js` does not match. Excludes lock files / configs
|
|
100
|
+
// on purpose — those are inert for edit-reset (above) but not "documentation".
|
|
101
|
+
var DOCS_ONLY_RE = /\.(md|markdown|txt|rst|adoc|html?|pdf|png|jpe?g|gif|svg|webp|ico|bmp)$/i;
|
|
102
|
+
|
|
103
|
+
// Get the file list changed on the current branch vs the merge-base with origin/main
|
|
104
|
+
// (falling back to local main). Returns an array of repo-relative paths, or null on
|
|
105
|
+
// failure — in which case callers MUST fall through to the standard gate (fail-safe).
|
|
106
|
+
function getChangedFilesVsBase() {
|
|
107
|
+
var bases = ['origin/main', 'main', 'origin/master', 'master'];
|
|
108
|
+
var base = null;
|
|
109
|
+
for (var i = 0; i < bases.length; i++) {
|
|
110
|
+
try {
|
|
111
|
+
base = cp.execFileSync('git', ['merge-base', 'HEAD', bases[i]], {
|
|
112
|
+
cwd: PROJECT_DIR, encoding: 'utf-8', timeout: 2000, windowsHide: true,
|
|
113
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
114
|
+
}).trim();
|
|
115
|
+
if (base) break;
|
|
116
|
+
} catch (e) { /* try next */ }
|
|
117
|
+
}
|
|
118
|
+
if (!base) return null;
|
|
119
|
+
try {
|
|
120
|
+
var out = cp.execFileSync('git', ['diff', '--name-only', base + '...HEAD'], {
|
|
121
|
+
cwd: PROJECT_DIR, encoding: 'utf-8', timeout: 2000, windowsHide: true,
|
|
122
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
123
|
+
});
|
|
124
|
+
return out.split('\n').map(function(l) { return l.trim(); }).filter(Boolean);
|
|
125
|
+
} catch (e) { return null; }
|
|
126
|
+
}
|
|
96
127
|
|
|
97
128
|
switch (command) {
|
|
98
129
|
case 'check-before-agent': {
|
|
@@ -208,6 +239,15 @@ switch (command) {
|
|
|
208
239
|
// optional ENV=val prefix segment catches `GH_TOKEN=x gh pr create`.
|
|
209
240
|
var cmd = process.env.TOOL_INPUT_command || '';
|
|
210
241
|
if (!/(?:^|&&\s*|\|\|\s*|;\s*)\s*(?:[A-Z_][A-Z0-9_]*=\S+\s+)*gh\s+pr\s+create\b/.test(cmd)) break;
|
|
242
|
+
// Docs-only exemption: if every file changed vs the merge-base is a docs/image
|
|
243
|
+
// file (no runtime-behaviour surface), skip the testing/simplify/learnings gates
|
|
244
|
+
// and surface a one-line transparency note. Falls through to the standard gate
|
|
245
|
+
// on any failure (no base, no diff, exec error) — fail-safe by design.
|
|
246
|
+
var changed = getChangedFilesVsBase();
|
|
247
|
+
if (changed && changed.length > 0 && changed.every(function(f) { return DOCS_ONLY_RE.test(f); })) {
|
|
248
|
+
process.stdout.write('Docs-only PR (' + changed.length + ' file' + (changed.length === 1 ? '' : 's') + ') — skipping testing/simplify/learnings gates.\n');
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
211
251
|
var s = readState();
|
|
212
252
|
var missing = [];
|
|
213
253
|
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)');
|
|
@@ -23,26 +23,72 @@ async function runFixCommand(cmd) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* Fix
|
|
27
|
-
*
|
|
28
|
-
*
|
|
26
|
+
* Fix Gate Health failures: bin/.claude-helpers gate.cjs drift AND missing
|
|
27
|
+
* settings.json hook wiring. The check has three independent failure modes
|
|
28
|
+
* and the prior fix only handled hook wiring — leaving bin/helper drift
|
|
29
|
+
* unresolved while still claiming success (the "Auto-fixed 1 issue" lie that
|
|
30
|
+
* surfaced when #920 mirrored the docs-only PR exemption into only one of
|
|
31
|
+
* the two gate.cjs files).
|
|
32
|
+
*
|
|
33
|
+
* Sync direction is decided by which source file is "ahead" of its installed
|
|
34
|
+
* counterpart in `node_modules/moflo/`:
|
|
35
|
+
* - If only source `bin/gate.cjs` differs from installed bin → mirror bin → helper.
|
|
36
|
+
* - If only source `.claude/helpers/gate.cjs` differs from installed helper → mirror helper → bin.
|
|
37
|
+
* - If both are ahead with different content (genuine ambiguity) → bail
|
|
38
|
+
* and let the caller report failure; refuse to silently pick a side.
|
|
39
|
+
* - If `node_modules/moflo/` is missing entirely (consumer never installed,
|
|
40
|
+
* unusual layout) → bail.
|
|
29
41
|
*/
|
|
30
42
|
async function fixGateHealthHooks() {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
const cwd = process.cwd();
|
|
44
|
+
let driftFixed = true; // true means "no drift to fix or drift resolved"
|
|
45
|
+
const binGate = join(cwd, 'bin', 'gate.cjs');
|
|
46
|
+
const helperGate = join(cwd, '.claude', 'helpers', 'gate.cjs');
|
|
47
|
+
const installedBin = join(cwd, 'node_modules', 'moflo', 'bin', 'gate.cjs');
|
|
48
|
+
const installedHelper = join(cwd, 'node_modules', 'moflo', '.claude', 'helpers', 'gate.cjs');
|
|
49
|
+
if (existsSync(binGate) && existsSync(helperGate)) {
|
|
50
|
+
try {
|
|
51
|
+
const binContent = readFileSync(binGate, 'utf8');
|
|
52
|
+
const helperContent = readFileSync(helperGate, 'utf8');
|
|
53
|
+
if (binContent !== helperContent) {
|
|
54
|
+
const installedBinContent = existsSync(installedBin) ? readFileSync(installedBin, 'utf8') : null;
|
|
55
|
+
const installedHelperContent = existsSync(installedHelper) ? readFileSync(installedHelper, 'utf8') : null;
|
|
56
|
+
const binAhead = installedBinContent !== null && binContent !== installedBinContent;
|
|
57
|
+
const helperAhead = installedHelperContent !== null && helperContent !== installedHelperContent;
|
|
58
|
+
if (binAhead && !helperAhead) {
|
|
59
|
+
writeFileSync(helperGate, binContent, 'utf-8');
|
|
60
|
+
}
|
|
61
|
+
else if (helperAhead && !binAhead) {
|
|
62
|
+
writeFileSync(binGate, helperContent, 'utf-8');
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Both ahead with different content, OR neither ahead (no install
|
|
66
|
+
// to anchor on). Refuse to pick a side — surface the failure.
|
|
67
|
+
driftFixed = false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
driftFixed = false;
|
|
73
|
+
}
|
|
42
74
|
}
|
|
43
|
-
|
|
44
|
-
|
|
75
|
+
// Hook-wiring repair (separate failure mode that this fixer also owns).
|
|
76
|
+
const settingsPath = join(cwd, '.claude', 'settings.json');
|
|
77
|
+
let wiringFixed = true;
|
|
78
|
+
if (existsSync(settingsPath)) {
|
|
79
|
+
try {
|
|
80
|
+
const raw = readFileSync(settingsPath, 'utf8');
|
|
81
|
+
const settings = JSON.parse(raw);
|
|
82
|
+
const { repaired } = repairHookWiring(settings);
|
|
83
|
+
if (repaired.length > 0) {
|
|
84
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
wiringFixed = false;
|
|
89
|
+
}
|
|
45
90
|
}
|
|
91
|
+
return driftFixed && wiringFixed;
|
|
46
92
|
}
|
|
47
93
|
/**
|
|
48
94
|
* Execute the fix for a failed/warned health check.
|
package/dist/src/cli/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.9.
|
|
3
|
+
"version": "4.9.18",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. A standalone, opinionated toolkit with semantic memory, learned routing, gates, spells, and the /flo issue-execution skill.",
|
|
5
5
|
"main": "dist/src/cli/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
82
82
|
"@typescript-eslint/parser": "^7.18.0",
|
|
83
83
|
"eslint": "^8.0.0",
|
|
84
|
-
"moflo": "^4.9.
|
|
84
|
+
"moflo": "^4.9.17",
|
|
85
85
|
"tsx": "^4.21.0",
|
|
86
86
|
"typescript": "^5.9.3",
|
|
87
87
|
"vitest": "^4.0.0"
|