claude-flow 3.58.0 → 3.59.0
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/proven-config.json +1 -1
- package/README.md +2 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/package.json +2 -1
- package/v3/@claude-flow/cli/README.md +62 -174
- package/v3/@claude-flow/cli/bin/cli.js +19 -1
- package/v3/@claude-flow/cli/catalog-manifest.json +5 -5
- package/v3/@claude-flow/cli/dist/src/commands/harness.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/commands/harness.js +142 -0
- package/v3/@claude-flow/cli/dist/src/commands/index.js +1 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +116 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +6 -1
- package/v3/@claude-flow/cli/dist/src/commands/status.js +2 -2
- package/v3/@claude-flow/cli/dist/src/index.d.ts +3 -0
- package/v3/@claude-flow/cli/dist/src/index.js +12 -2
- package/v3/@claude-flow/cli/dist/src/init/grok-generator.d.ts +96 -0
- package/v3/@claude-flow/cli/dist/src/init/grok-generator.js +371 -0
- package/v3/@claude-flow/cli/dist/src/init/grok-templates.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/init/grok-templates.js +28 -0
- package/v3/@claude-flow/cli/dist/src/init/index.d.ts +2 -0
- package/v3/@claude-flow/cli/dist/src/init/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +4 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/agent-execute-core.js +59 -12
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +5 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/embeddings-tools.js +11 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +13 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.d.ts +1 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/memory-tools.js +42 -4
- package/v3/@claude-flow/cli/dist/src/mcp-tools/task-tools.js +7 -3
- package/v3/@claude-flow/cli/dist/src/mcp-tools/team-tools.d.ts +15 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/team-tools.js +244 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +163 -3
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.d.ts +4 -0
- package/v3/@claude-flow/cli/dist/src/memory/memory-initializer.js +70 -37
- package/v3/@claude-flow/cli/dist/src/memory/rabitq-index.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/memory/rabitq-index.js +117 -1
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +113 -113
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +193 -193
- package/v3/@claude-flow/cli/dist/src/parser.js +3 -2
- package/v3/@claude-flow/cli/dist/src/services/live-harness.d.ts +111 -0
- package/v3/@claude-flow/cli/dist/src/services/live-harness.js +427 -0
- package/v3/@claude-flow/cli/package.json +4 -3
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-architect.md +35 -0
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-coder.md +34 -0
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-reviewer.md +30 -0
- package/v3/@claude-flow/cli/templates/grok/agents/ruflo-tester.md +26 -0
- package/v3/@claude-flow/cli/templates/grok/config.toml +63 -0
- package/v3/@claude-flow/cli/templates/grok/docs/README.md +186 -0
- package/v3/@claude-flow/cli/templates/grok/hooks/subagent-stop-team.json +15 -0
- package/v3/@claude-flow/cli/templates/grok/rules/ruflo-grok.md +132 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/grok-subagent-stop-hook.mjs +90 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/grok-team-bus.mjs +525 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/grok-team-store.mjs +287 -0
- package/v3/@claude-flow/cli/templates/grok/scripts/host-statusline.mjs +281 -0
- package/v3/@claude-flow/cli/templates/grok/skills/agent-teams-grok/SKILL.md +98 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Build host init (ADR-402).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `init --codex`: writes project-scoped `.grok/` surface + team bus scripts
|
|
5
|
+
* so any repo can run Ruflo Agent Teams under Grok without Claude SendMessage.
|
|
6
|
+
*
|
|
7
|
+
* Writes outside the project only with `statusLine: true` (`--grok-statusline`):
|
|
8
|
+
* the status row lives in the user's ~/.grok/config.toml, which is parsed and
|
|
9
|
+
* merged with a real TOML parser, never blindly appended to.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'node:fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import * as TOML from '@iarna/toml';
|
|
15
|
+
import { grokPackageRoot, grokTemplatesRoot } from './grok-templates.js';
|
|
16
|
+
export { grokTemplatesRoot };
|
|
17
|
+
function packageVersion(root) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf-8')).version || '0.0.0';
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return '0.0.0';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* How the project .grok/config.toml starts the Ruflo MCP server.
|
|
27
|
+
*
|
|
28
|
+
* The team_* tools ship in the same release as `init --grok`, so the server is
|
|
29
|
+
* pinned to the version that is running this init — never `@latest`, which can
|
|
30
|
+
* be an older release without them. Running from a source checkout (not under
|
|
31
|
+
* node_modules) points at that checkout's CLI, since its version may not be
|
|
32
|
+
* published yet.
|
|
33
|
+
*/
|
|
34
|
+
export function rufloMcpLaunch(root = grokPackageRoot()) {
|
|
35
|
+
const version = packageVersion(root);
|
|
36
|
+
const published = root.split(path.sep).includes('node_modules');
|
|
37
|
+
if (!published) {
|
|
38
|
+
return {
|
|
39
|
+
command: 'node',
|
|
40
|
+
args: [path.join(root, 'bin', 'cli.js'), 'mcp', 'start'],
|
|
41
|
+
source: 'checkout',
|
|
42
|
+
version,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const npxArgs = ['-y', `ruflo@${version}`, 'mcp', 'start'];
|
|
46
|
+
return process.platform === 'win32'
|
|
47
|
+
? { command: 'cmd', args: ['/c', 'npx', ...npxArgs], source: 'published', version }
|
|
48
|
+
: { command: 'npx', args: npxArgs, source: 'published', version };
|
|
49
|
+
}
|
|
50
|
+
/** A TOML basic string. JSON escaping is a valid subset (\\, \", \uXXXX). */
|
|
51
|
+
function tomlString(value) {
|
|
52
|
+
return JSON.stringify(value);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fill the template placeholders. Every inserted value is escaped as a TOML
|
|
56
|
+
* string, so a Windows path such as C:\Users\x stays valid TOML.
|
|
57
|
+
*/
|
|
58
|
+
export function materializeGrokConfig(template, launch, home) {
|
|
59
|
+
const homeInString = tomlString(home.replace(/[\\/]+$/, '')).slice(1, -1);
|
|
60
|
+
return template
|
|
61
|
+
.replaceAll('{{RUFLO_MCP_COMMAND}}', tomlString(launch.command))
|
|
62
|
+
.replaceAll('{{RUFLO_MCP_ARGS}}', `[${launch.args.map(tomlString).join(', ')}]`)
|
|
63
|
+
.replaceAll('{{HOME}}/', `${homeInString}/`);
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// User-level status row (~/.grok/config.toml [ui.status_line])
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/** Where the opt-in status row script is installed: a stable absolute path. */
|
|
69
|
+
export function grokStatusScriptPath(home) {
|
|
70
|
+
return path.join(home, '.grok', 'ruflo', 'host-statusline.mjs');
|
|
71
|
+
}
|
|
72
|
+
/** `node "<abs path>"` — no shell builtins, and never a repo-relative script. */
|
|
73
|
+
export function grokStatusLineCommand(home) {
|
|
74
|
+
return `node "${grokStatusScriptPath(home)}"`;
|
|
75
|
+
}
|
|
76
|
+
export function grokStatusLineSnippet(home) {
|
|
77
|
+
return TOML.stringify({
|
|
78
|
+
ui: { status_line: { type: 'command', command: grokStatusLineCommand(home) } },
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function stableJson(value) {
|
|
82
|
+
return JSON.stringify(value, (_k, v) => v && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date)
|
|
83
|
+
? Object.fromEntries(Object.keys(v).sort().map((k) => [k, v[k]]))
|
|
84
|
+
: v);
|
|
85
|
+
}
|
|
86
|
+
function statusLineOf(doc) {
|
|
87
|
+
const ui = doc.ui;
|
|
88
|
+
return ui && typeof ui === 'object' && !Array.isArray(ui) ? ui.status_line : undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Add the status row to an existing user config without disturbing it.
|
|
92
|
+
*
|
|
93
|
+
* Any existing `ui.status_line` — header, `[ ui.status_line ]`,
|
|
94
|
+
* `[ui."status_line"]`, dotted keys, or an inline table — is detected by
|
|
95
|
+
* parsing, and left alone. Otherwise the block is appended and the result is
|
|
96
|
+
* re-parsed: it is written only if it parses, carries exactly our status row,
|
|
97
|
+
* and every other value is unchanged. Anything else (unparseable file, `ui`
|
|
98
|
+
* as an inline table, …) falls back to printing the snippet.
|
|
99
|
+
*/
|
|
100
|
+
export function mergeGrokStatusLine(existing, home) {
|
|
101
|
+
let before;
|
|
102
|
+
try {
|
|
103
|
+
before = TOML.parse(existing);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
return { kind: 'unsafe', reason: `could not parse it as TOML (${e.message.split('\n')[0]})` };
|
|
107
|
+
}
|
|
108
|
+
const current = statusLineOf(before);
|
|
109
|
+
if (current !== undefined) {
|
|
110
|
+
const command = current && typeof current === 'object' ? current.command : undefined;
|
|
111
|
+
return { kind: 'already-set', ours: command === grokStatusLineCommand(home) };
|
|
112
|
+
}
|
|
113
|
+
const sep = existing.length === 0 || existing.endsWith('\n') ? '' : '\n';
|
|
114
|
+
const text = `${existing}${sep}\n# Ruflo loaded-status row (added by ruflo init --grok --grok-statusline)\n${grokStatusLineSnippet(home)}`;
|
|
115
|
+
let after;
|
|
116
|
+
try {
|
|
117
|
+
after = TOML.parse(text);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
return { kind: 'unsafe', reason: `appending [ui.status_line] would not parse (${e.message.split('\n')[0]})` };
|
|
121
|
+
}
|
|
122
|
+
const added = statusLineOf(after);
|
|
123
|
+
if (!added || added.type !== 'command' || added.command !== grokStatusLineCommand(home)) {
|
|
124
|
+
return { kind: 'unsafe', reason: 'the merged file did not contain the expected status row' };
|
|
125
|
+
}
|
|
126
|
+
const ui = after.ui;
|
|
127
|
+
delete ui.status_line;
|
|
128
|
+
if (before.ui === undefined && Object.keys(ui).length === 0)
|
|
129
|
+
delete after.ui;
|
|
130
|
+
if (stableJson(after) !== stableJson(before)) {
|
|
131
|
+
return { kind: 'unsafe', reason: 'the merge would change other settings' };
|
|
132
|
+
}
|
|
133
|
+
return { kind: 'write', text };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* If `file` is a symlink, resolve it to what it actually points at. A rename
|
|
137
|
+
* onto a symlink path replaces the symlink itself with a plain file — in a
|
|
138
|
+
* dotfiles-managed setup where `~/.grok/config.toml` is a symlink, that
|
|
139
|
+
* silently destroys the symlink instead of updating the file it points to
|
|
140
|
+
* (ADR-402 round-2 review, minor item 1). A broken symlink (target missing)
|
|
141
|
+
* falls back to writing `file` itself, same as a plain non-existent path.
|
|
142
|
+
*/
|
|
143
|
+
function resolveWriteTarget(file) {
|
|
144
|
+
try {
|
|
145
|
+
if (fs.lstatSync(file).isSymbolicLink()) {
|
|
146
|
+
return fs.realpathSync(file);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
/* missing, unreadable, or a broken symlink target: write `file` itself */
|
|
151
|
+
}
|
|
152
|
+
return file;
|
|
153
|
+
}
|
|
154
|
+
function writeFileAtomic(file, text, mode) {
|
|
155
|
+
const target = resolveWriteTarget(file);
|
|
156
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
157
|
+
const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
158
|
+
fs.writeFileSync(tmp, text, { encoding: 'utf-8', mode: mode ?? 0o600 });
|
|
159
|
+
fs.renameSync(tmp, target);
|
|
160
|
+
}
|
|
161
|
+
function wireGrokUserStatusLine(home, dryRun, created, errors) {
|
|
162
|
+
const dest = path.join(home, '.grok', 'config.toml');
|
|
163
|
+
const snippet = grokStatusLineSnippet(home);
|
|
164
|
+
const script = grokStatusScriptPath(home);
|
|
165
|
+
const result = (action, reason) => ({
|
|
166
|
+
action,
|
|
167
|
+
path: dest,
|
|
168
|
+
snippet,
|
|
169
|
+
reason,
|
|
170
|
+
});
|
|
171
|
+
let existing = null;
|
|
172
|
+
try {
|
|
173
|
+
existing = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf-8') : null;
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
return result('snippet', `could not read ${dest}: ${e.message}`);
|
|
177
|
+
}
|
|
178
|
+
const outcome = existing === null
|
|
179
|
+
? { kind: 'write', text: `# Ruflo loaded-status row (added by ruflo init --grok --grok-statusline)\n${snippet}` }
|
|
180
|
+
: mergeGrokStatusLine(existing, home);
|
|
181
|
+
if (outcome.kind === 'unsafe')
|
|
182
|
+
return result('snippet', `${dest}: ${outcome.reason}`);
|
|
183
|
+
if (outcome.kind === 'already-set' && !outcome.ours) {
|
|
184
|
+
return result('already-set', `${dest} already has its own [ui.status_line]; left it and did not install the Ruflo script`);
|
|
185
|
+
}
|
|
186
|
+
// Ours, or about to be ours: install/refresh the script it runs.
|
|
187
|
+
const needsConfig = outcome.kind === 'write';
|
|
188
|
+
if (dryRun) {
|
|
189
|
+
created.push(script);
|
|
190
|
+
if (needsConfig)
|
|
191
|
+
created.push(dest);
|
|
192
|
+
return result(needsConfig ? 'would-write' : 'already-set');
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
const src = path.join(grokTemplatesRoot(), 'scripts', 'host-statusline.mjs');
|
|
196
|
+
writeFileAtomic(script, fs.readFileSync(src, 'utf-8'), 0o755);
|
|
197
|
+
created.push(script);
|
|
198
|
+
if (!needsConfig)
|
|
199
|
+
return result('already-set');
|
|
200
|
+
writeFileAtomic(dest, outcome.text);
|
|
201
|
+
created.push(dest);
|
|
202
|
+
return result(existing === null ? 'created' : 'merged');
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
errors.push(`${dest}: ${e.message}`);
|
|
206
|
+
return result('snippet', e.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/** First `object.key` (or `[index]`) path to a BigInt value, or null. */
|
|
210
|
+
function findBigIntKeyPath(value, path = []) {
|
|
211
|
+
if (typeof value === 'bigint')
|
|
212
|
+
return path.length ? path.join('.') : '(root)';
|
|
213
|
+
if (Array.isArray(value)) {
|
|
214
|
+
for (let i = 0; i < value.length; i++) {
|
|
215
|
+
const found = findBigIntKeyPath(value[i], [...path, `[${i}]`]);
|
|
216
|
+
if (found)
|
|
217
|
+
return found;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (value && typeof value === 'object' && !(value instanceof Date)) {
|
|
222
|
+
for (const [k, v] of Object.entries(value)) {
|
|
223
|
+
const found = findBigIntKeyPath(v, [...path, k]);
|
|
224
|
+
if (found)
|
|
225
|
+
return found;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Whether an existing `~/.grok/config.toml` can be safely parsed and
|
|
232
|
+
* serialized before init touches anything. A TOML integer literal above
|
|
233
|
+
* 2^53 parses to a BigInt (@iarna/toml), and `JSON.stringify` — used by
|
|
234
|
+
* `mergeGrokStatusLine`'s stableJson comparison — throws "Do not know how
|
|
235
|
+
* to serialize a BigInt" on it. That merge used to run as the LAST step of
|
|
236
|
+
* `executeGrokInit`, so the crash left project files already written and
|
|
237
|
+
* nothing to show for the failure. Checked here, first, so init either
|
|
238
|
+
* fully succeeds or writes nothing at all (ADR-402 round-2 review, minor
|
|
239
|
+
* item 2). Returns a user-facing error message naming the offending key,
|
|
240
|
+
* or null if the file is fine (or absent, or already unparseable as
|
|
241
|
+
* TOML — mergeGrokStatusLine reports that case on its own, safely).
|
|
242
|
+
*/
|
|
243
|
+
function validateExistingUserConfig(dest) {
|
|
244
|
+
let existing;
|
|
245
|
+
try {
|
|
246
|
+
if (!fs.existsSync(dest))
|
|
247
|
+
return null;
|
|
248
|
+
existing = fs.readFileSync(dest, 'utf-8');
|
|
249
|
+
}
|
|
250
|
+
catch (e) {
|
|
251
|
+
return `could not read ${dest}: ${e.message}`;
|
|
252
|
+
}
|
|
253
|
+
let doc;
|
|
254
|
+
try {
|
|
255
|
+
doc = TOML.parse(existing);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
// An existing status row is left as-is, so nothing in this file is
|
|
261
|
+
// serialized. A BigInt elsewhere must not fail init in that case
|
|
262
|
+
// (ADR-402 round-3 review). The status step reports already-set.
|
|
263
|
+
if (statusLineOf(doc) !== undefined)
|
|
264
|
+
return null;
|
|
265
|
+
try {
|
|
266
|
+
JSON.stringify(doc);
|
|
267
|
+
}
|
|
268
|
+
catch (e) {
|
|
269
|
+
const offending = findBigIntKeyPath(doc);
|
|
270
|
+
const where = offending ? ` at "${offending}"` : '';
|
|
271
|
+
return `${dest} has a value ruflo cannot serialize${where} (${e.message}) — fix or remove it before running init --grok --grok-statusline again`;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
function writeProjectFile(ctx, dest, write) {
|
|
276
|
+
try {
|
|
277
|
+
if (fs.existsSync(dest) && !ctx.force) {
|
|
278
|
+
ctx.skipped.push(dest);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!ctx.dryRun) {
|
|
282
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
283
|
+
write();
|
|
284
|
+
}
|
|
285
|
+
ctx.created.push(dest);
|
|
286
|
+
}
|
|
287
|
+
catch (e) {
|
|
288
|
+
ctx.errors.push(`${dest}: ${e.message}`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function copyFile(ctx, src, dest) {
|
|
292
|
+
writeProjectFile(ctx, dest, () => {
|
|
293
|
+
fs.copyFileSync(src, dest);
|
|
294
|
+
fs.chmodSync(dest, fs.statSync(src).mode);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
function walkCopy(ctx, srcDir, destDir) {
|
|
298
|
+
if (!fs.existsSync(srcDir)) {
|
|
299
|
+
ctx.errors.push(`Template directory missing: ${srcDir}`);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
for (const ent of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
303
|
+
const src = path.join(srcDir, ent.name);
|
|
304
|
+
const dest = path.join(destDir, ent.name);
|
|
305
|
+
if (ent.isDirectory())
|
|
306
|
+
walkCopy(ctx, src, dest);
|
|
307
|
+
else
|
|
308
|
+
copyFile(ctx, src, dest);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Initialize Grok host surface in targetDir.
|
|
313
|
+
*/
|
|
314
|
+
export function executeGrokInit(options) {
|
|
315
|
+
const { targetDir, force = false, docs = true, statusLine = false, dryRun = false } = options;
|
|
316
|
+
const home = options.homeDir || os.homedir();
|
|
317
|
+
const ctx = { force, dryRun, created: [], skipped: [], errors: [] };
|
|
318
|
+
const tpl = grokTemplatesRoot();
|
|
319
|
+
const mcp = rufloMcpLaunch();
|
|
320
|
+
const userConfig = {
|
|
321
|
+
action: 'not-requested',
|
|
322
|
+
path: path.join(home, '.grok', 'config.toml'),
|
|
323
|
+
snippet: grokStatusLineSnippet(home),
|
|
324
|
+
};
|
|
325
|
+
const done = () => ({
|
|
326
|
+
success: ctx.errors.length === 0,
|
|
327
|
+
dryRun,
|
|
328
|
+
filesCreated: ctx.created,
|
|
329
|
+
filesSkipped: ctx.skipped,
|
|
330
|
+
errors: ctx.errors,
|
|
331
|
+
mcp,
|
|
332
|
+
userConfig,
|
|
333
|
+
});
|
|
334
|
+
if (!fs.existsSync(path.join(tpl, 'config.toml'))) {
|
|
335
|
+
ctx.errors.push(`Grok templates not found at ${tpl}. Reinstall @claude-flow/cli or run from a monorepo checkout that includes templates/grok.`);
|
|
336
|
+
return done();
|
|
337
|
+
}
|
|
338
|
+
// Validate any existing user-level config before writing a single project
|
|
339
|
+
// file — see validateExistingUserConfig for why (BigInt/serialization
|
|
340
|
+
// failures must not leave a half-initialized project).
|
|
341
|
+
if (statusLine) {
|
|
342
|
+
const problem = validateExistingUserConfig(userConfig.path);
|
|
343
|
+
if (problem) {
|
|
344
|
+
ctx.errors.push(problem);
|
|
345
|
+
return done();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// .grok/config.toml — placeholders filled, then checked with a TOML parser
|
|
349
|
+
const config = materializeGrokConfig(fs.readFileSync(path.join(tpl, 'config.toml'), 'utf-8'), mcp, home);
|
|
350
|
+
try {
|
|
351
|
+
TOML.parse(config);
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
ctx.errors.push(`.grok/config.toml would not be valid TOML: ${e.message.split('\n')[0]}`);
|
|
355
|
+
return done();
|
|
356
|
+
}
|
|
357
|
+
writeProjectFile(ctx, path.join(targetDir, '.grok', 'config.toml'), () => fs.writeFileSync(path.join(targetDir, '.grok', 'config.toml'), config, 'utf-8'));
|
|
358
|
+
for (const dir of ['rules', 'agents', 'hooks', 'skills']) {
|
|
359
|
+
walkCopy(ctx, path.join(tpl, dir), path.join(targetDir, '.grok', dir));
|
|
360
|
+
}
|
|
361
|
+
// scripts/ team bus (project-local CLI + SubagentStop adapter + status row)
|
|
362
|
+
walkCopy(ctx, path.join(tpl, 'scripts'), path.join(targetDir, 'scripts'));
|
|
363
|
+
if (docs) {
|
|
364
|
+
walkCopy(ctx, path.join(tpl, 'docs'), path.join(targetDir, 'docs', 'grok'));
|
|
365
|
+
}
|
|
366
|
+
if (statusLine) {
|
|
367
|
+
Object.assign(userConfig, wireGrokUserStatusLine(home, dryRun, ctx.created, ctx.errors));
|
|
368
|
+
}
|
|
369
|
+
return done();
|
|
370
|
+
}
|
|
371
|
+
//# sourceMappingURL=grok-generator.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the Grok host templates live (ADR-402).
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from grok-generator.ts so the team_* MCP tools can find the team
|
|
5
|
+
* bus scripts without loading the TOML parser on every MCP client import.
|
|
6
|
+
*/
|
|
7
|
+
/** Root of the installed @claude-flow/cli package (the one holding templates/grok). */
|
|
8
|
+
export declare function grokPackageRoot(): string;
|
|
9
|
+
/** templates/grok inside the installed @claude-flow/cli package. */
|
|
10
|
+
export declare function grokTemplatesRoot(): string;
|
|
11
|
+
//# sourceMappingURL=grok-templates.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the Grok host templates live (ADR-402).
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from grok-generator.ts so the team_* MCP tools can find the team
|
|
5
|
+
* bus scripts without loading the TOML parser on every MCP client import.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
/** Root of the installed @claude-flow/cli package (the one holding templates/grok). */
|
|
11
|
+
export function grokPackageRoot() {
|
|
12
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const candidates = [
|
|
14
|
+
path.resolve(here, '../../..'), // dist/src/init → package root
|
|
15
|
+
path.resolve(here, '../..'), // src/init → package root
|
|
16
|
+
];
|
|
17
|
+
for (const c of candidates) {
|
|
18
|
+
if (fs.existsSync(path.join(c, 'templates', 'grok', 'config.toml'))) {
|
|
19
|
+
return c;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return candidates[0];
|
|
23
|
+
}
|
|
24
|
+
/** templates/grok inside the installed @claude-flow/cli package. */
|
|
25
|
+
export function grokTemplatesRoot() {
|
|
26
|
+
return path.join(grokPackageRoot(), 'templates', 'grok');
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=grok-templates.js.map
|
|
@@ -10,4 +10,6 @@ export { generatePreCommitHook, generatePostCommitHook, generateSessionManager,
|
|
|
10
10
|
export { generateClaudeMd, generateMinimalClaudeMd, CLAUDE_MD_TEMPLATES, } from './claudemd-generator.js';
|
|
11
11
|
export { executeInit, executeUpgrade, executeUpgradeWithMissing, default } from './executor.js';
|
|
12
12
|
export type { UpgradeResult } from './executor.js';
|
|
13
|
+
export { executeGrokInit } from './grok-generator.js';
|
|
14
|
+
export type { GrokInitOptions, GrokInitResult } from './grok-generator.js';
|
|
13
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -12,4 +12,6 @@ export { generatePreCommitHook, generatePostCommitHook, generateSessionManager,
|
|
|
12
12
|
export { generateClaudeMd, generateMinimalClaudeMd, CLAUDE_MD_TEMPLATES, } from './claudemd-generator.js';
|
|
13
13
|
// Main executor
|
|
14
14
|
export { executeInit, executeUpgrade, executeUpgradeWithMissing, default } from './executor.js';
|
|
15
|
+
// Grok Build host (ADR-402)
|
|
16
|
+
export { executeGrokInit } from './grok-generator.js';
|
|
15
17
|
//# sourceMappingURL=index.js.map
|
|
@@ -73,6 +73,8 @@ import { businessPodTools } from './mcp-tools/business-pod-tools.js';
|
|
|
73
73
|
// + auth headers; opt-in via CLAUDE_FLOW_HTTP_FETCH_ALLOW_PRIVATE / _AUTH=1.
|
|
74
74
|
import { httpFetchTools } from './mcp-tools/http-fetch-tools.js';
|
|
75
75
|
import { missionTools } from './mcp-tools/mission-tools.js';
|
|
76
|
+
// ADR-402 — host-agnostic Agent Teams bus (team_create/send/inbox/spawn/…)
|
|
77
|
+
import { teamTools } from './mcp-tools/team-tools.js';
|
|
76
78
|
// #1916: coverage-aware routing tools — defined in ruvector/coverage-tools.ts
|
|
77
79
|
// but were never registered, so the `ruflo hooks coverage-*` CLI subcommands
|
|
78
80
|
// failed with `Tool not found: hooks_coverage-route`.
|
|
@@ -183,6 +185,8 @@ registerTools([
|
|
|
183
185
|
...httpFetchTools,
|
|
184
186
|
// ADR-406 — mission semantic operations (create/plan/get/events/request_action)
|
|
185
187
|
...missionTools,
|
|
188
|
+
// ADR-402 — host-agnostic Agent Teams (9 tools)
|
|
189
|
+
...teamTools,
|
|
186
190
|
]);
|
|
187
191
|
// The capability brain consumes the completed live registry. This is injected
|
|
188
192
|
// after registration to keep guidance honest and avoid a registry import cycle.
|
|
@@ -152,7 +152,11 @@ export async function callAnthropicMessages(input) {
|
|
|
152
152
|
...input,
|
|
153
153
|
apiKey: ollamaKey || persistedOllama?.apiKey || 'local',
|
|
154
154
|
baseUrl: resolvedBaseUrl,
|
|
155
|
-
model: input.model
|
|
155
|
+
model: input.model,
|
|
156
|
+
// Local default for tier aliases / Anthropic ids (see
|
|
157
|
+
// resolveOllamaModel): OLLAMA_DEFAULT_MODEL, then the model saved by
|
|
158
|
+
// `providers configure -p ollama -m <tag>`.
|
|
159
|
+
defaultModel: process.env.OLLAMA_DEFAULT_MODEL || persistedOllama?.model,
|
|
156
160
|
});
|
|
157
161
|
}
|
|
158
162
|
return { success: false, error: 'Ollama was selected but no API key or self-hosted endpoint is configured.' };
|
|
@@ -242,13 +246,15 @@ export async function callAnthropicMessages(input) {
|
|
|
242
246
|
*
|
|
243
247
|
* Translates the Anthropic-flavored input shape onto OpenAI chat-completions
|
|
244
248
|
* and translates the response back so callers never see provider-specific
|
|
245
|
-
* fields. Logical model names
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
+
* fields. Logical model names ('haiku' / 'sonnet' / 'opus' / a tier-routed
|
|
250
|
+
* `claude-*` id) resolve to the configured OLLAMA_DEFAULT_MODEL / persisted
|
|
251
|
+
* default; only when the endpoint is genuinely Ollama's cloud service AND no
|
|
252
|
+
* default is configured does this fall back to 'gpt-oss:120b-cloud' — a
|
|
253
|
+
* self-hosted/local endpoint with no default configured fails loudly instead
|
|
254
|
+
* (see resolveOllamaModel). An explicit 'ollama:<model>' or bare
|
|
255
|
+
* provider-native name always passes through unchanged.
|
|
249
256
|
*/
|
|
250
257
|
async function callOllamaCompat(input) {
|
|
251
|
-
const model = resolveOllamaModel(input.model);
|
|
252
258
|
const startedAt = Date.now();
|
|
253
259
|
// #2962 — input.baseUrl (resolved by the caller from persisted
|
|
254
260
|
// `providers configure` config, then OLLAMA_BASE_URL) takes precedence
|
|
@@ -257,6 +263,20 @@ async function callOllamaCompat(input) {
|
|
|
257
263
|
// Cloud. Falls back to the original OLLAMA_BASE_URL-or-cloud behavior
|
|
258
264
|
// for any direct caller that doesn't pass baseUrl.
|
|
259
265
|
const base = (input.baseUrl || process.env.OLLAMA_BASE_URL || 'https://ollama.com').replace(/\/+$/, '');
|
|
266
|
+
// Only a base URL actually pointed at Ollama's real cloud endpoint may
|
|
267
|
+
// fall back to a `-cloud`-suffixed default model — see resolveOllamaModel.
|
|
268
|
+
const isCloudEndpoint = /^https:\/\/ollama\.com\/?$/i.test(base);
|
|
269
|
+
let model;
|
|
270
|
+
try {
|
|
271
|
+
model = resolveOllamaModel(input.model, input.defaultModel, isCloudEndpoint);
|
|
272
|
+
}
|
|
273
|
+
catch (err) {
|
|
274
|
+
return {
|
|
275
|
+
success: false,
|
|
276
|
+
error: err instanceof Error ? err.message : String(err),
|
|
277
|
+
durationMs: Date.now() - startedAt,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
260
280
|
const url = `${base}/v1/chat/completions`;
|
|
261
281
|
// Self-hosted endpoints typically don't need an Authorization header
|
|
262
282
|
// (the daemon binds to 11434 with no auth by default), but Ollama Cloud
|
|
@@ -403,18 +423,45 @@ function resolveOpenAICompatModel(input, fallback) {
|
|
|
403
423
|
return 'anthropic/claude-opus-4-8';
|
|
404
424
|
return input;
|
|
405
425
|
}
|
|
406
|
-
|
|
407
|
-
|
|
426
|
+
/**
|
|
427
|
+
* Resolve the model name to send to an Ollama-compatible endpoint.
|
|
428
|
+
*
|
|
429
|
+
* Self-hosted setups name their default via OLLAMA_DEFAULT_MODEL or
|
|
430
|
+
* `providers configure -p ollama -m <tag>` (`defaultModel`, resolved by the
|
|
431
|
+
* caller). When no default is configured AND the endpoint is not actually
|
|
432
|
+
* Ollama's real cloud service, this must NOT silently rewrite a tier-routed
|
|
433
|
+
* `claude-*` id (or a bare tier alias) to a `-cloud`-suffixed model name: a
|
|
434
|
+
* signed-in local Ollama daemon forwards any `-cloud`-suffixed model to
|
|
435
|
+
* Ollama's actual cloud service, so a silent rewrite would leak a prompt
|
|
436
|
+
* that was supposed to stay local. Fail loudly instead and tell the caller
|
|
437
|
+
* how to fix it — a loud, local failure beats silent cloud egress.
|
|
438
|
+
*/
|
|
439
|
+
function resolveOllamaModel(input, defaultModel, isCloudEndpoint = false) {
|
|
440
|
+
const cloudDefault = 'gpt-oss:120b-cloud';
|
|
441
|
+
const resolveDefault = () => {
|
|
442
|
+
if (defaultModel)
|
|
443
|
+
return defaultModel;
|
|
444
|
+
if (isCloudEndpoint)
|
|
445
|
+
return cloudDefault;
|
|
446
|
+
throw new Error('No Ollama model configured for this self-hosted/local endpoint — refusing to fall back to '
|
|
447
|
+
+ `a cloud-suffixed default ("${cloudDefault}") a local Ollama daemon would forward to Ollama `
|
|
448
|
+
+ 'Cloud. Set OLLAMA_DEFAULT_MODEL, or run `providers configure -p ollama -m <tag>`, to pin a '
|
|
449
|
+
+ 'real local model.');
|
|
450
|
+
};
|
|
408
451
|
if (!input)
|
|
409
|
-
return
|
|
410
|
-
// Logical →
|
|
452
|
+
return resolveDefault();
|
|
453
|
+
// Logical tier alias → default
|
|
411
454
|
if (input === 'haiku' || input === 'sonnet' || input === 'opus' || input === 'inherit') {
|
|
412
|
-
return
|
|
455
|
+
return resolveDefault();
|
|
413
456
|
}
|
|
414
457
|
// Explicit provider prefix
|
|
415
458
|
if (input.startsWith('ollama:'))
|
|
416
459
|
return input.slice('ollama:'.length);
|
|
417
|
-
//
|
|
460
|
+
// Anthropic model ids (what executeAgentTask sends for a tier-routed agent
|
|
461
|
+
// via MODEL_MAP) never exist on an Ollama endpoint → default.
|
|
462
|
+
if (/^claude-/i.test(input))
|
|
463
|
+
return resolveDefault();
|
|
464
|
+
// Bare name (e.g. 'qwen3-coder:30b', 'llama3:70b-cloud') passes through
|
|
418
465
|
return input;
|
|
419
466
|
}
|
|
420
467
|
/**
|
|
@@ -97,7 +97,7 @@ export interface CapabilityBrain {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
/** Top-level commands from the CLI command loader. Kept as a checked manifest. */
|
|
100
|
-
export declare const RUFLO_CLI_COMMANDS: readonly ["init", "start", "status", "task", "session", "agent", "swarm", "memory", "mcp", "config", "migrate", "hooks", "workflow", "hive-mind", "process", "daemon", "version", "neural", "security", "performance", "providers", "plugins", "deployment", "claims", "policy", "embeddings", "completions", "doctor", "verify", "analyze", "route", "progress", "issues", "update", "ruvector", "benchmark", "guidance", "appliance", "appliance-advanced", "transfer-store", "cleanup", "autopilot", "gaia-bench", "metaharness", "eject", "funnel", "settings", "auth", "proxy", "advisor", "spinner", "announcements", "tui"];
|
|
100
|
+
export declare const RUFLO_CLI_COMMANDS: readonly ["init", "start", "status", "task", "session", "agent", "swarm", "memory", "mcp", "config", "migrate", "hooks", "workflow", "hive-mind", "process", "daemon", "version", "neural", "security", "performance", "providers", "plugins", "deployment", "claims", "policy", "embeddings", "completions", "doctor", "verify", "analyze", "route", "progress", "issues", "update", "ruvector", "benchmark", "guidance", "appliance", "appliance-advanced", "transfer-store", "cleanup", "autopilot", "gaia-bench", "metaharness", "eject", "funnel", "settings", "auth", "proxy", "advisor", "spinner", "announcements", "tui", "harness"];
|
|
101
101
|
export interface ImplementationLoopStep {
|
|
102
102
|
id: string;
|
|
103
103
|
name: string;
|
|
@@ -16,7 +16,7 @@ export const RUFLO_CLI_COMMANDS = [
|
|
|
16
16
|
'ruvector', 'benchmark', 'guidance', 'appliance', 'appliance-advanced',
|
|
17
17
|
'transfer-store', 'cleanup', 'autopilot', 'gaia-bench', 'metaharness',
|
|
18
18
|
'eject', 'funnel', 'settings', 'auth', 'proxy', 'advisor', 'spinner',
|
|
19
|
-
'announcements', 'tui',
|
|
19
|
+
'announcements', 'tui', 'harness',
|
|
20
20
|
];
|
|
21
21
|
export const IMPLEMENTATION_LOOP = [
|
|
22
22
|
{
|
|
@@ -284,9 +284,10 @@ export const CAPABILITY_DOMAINS = [
|
|
|
284
284
|
{
|
|
285
285
|
id: 'swarm-coordination',
|
|
286
286
|
name: 'Swarm & Coordination',
|
|
287
|
-
|
|
287
|
+
// team_: host-agnostic Agent Teams bus (ADR-402)
|
|
288
|
+
prefixes: ['swarm_', 'coordination_', 'daa_', 'team_'],
|
|
288
289
|
description: 'Topology-aware multi-agent coordination, task ownership, and collective scheduling.',
|
|
289
|
-
taskSignals: ['swarm', 'concurrent', 'parallel', 'coordinate', 'multi-agent'],
|
|
290
|
+
taskSignals: ['swarm', 'concurrent', 'parallel', 'coordinate', 'multi-agent', 'agent team'],
|
|
290
291
|
commands: ['swarm init', 'swarm status', 'swarm coordinate'],
|
|
291
292
|
skills: ['swarm-orchestration', 'swarm-advanced'],
|
|
292
293
|
agents: ['coordinator', 'coder', 'tester', 'reviewer'],
|
|
@@ -553,6 +554,7 @@ const TOOL_OWNERSHIP = [
|
|
|
553
554
|
{ prefixes: ['guidance_'], packageOwner: '@claude-flow/guidance', pluginOwner: 'ruflo-core' },
|
|
554
555
|
{ prefixes: ['seraphina_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-core' },
|
|
555
556
|
{ prefixes: ['x_federation_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-x-gateway' },
|
|
557
|
+
{ prefixes: ['team_'], packageOwner: '@claude-flow/cli' },
|
|
556
558
|
];
|
|
557
559
|
function ownershipForTool(toolName, definition) {
|
|
558
560
|
const explicit = TOOL_OWNERSHIP.find((entry) => entry.prefixes.some((prefix) => toolName.startsWith(prefix)));
|
|
@@ -928,15 +928,22 @@ export const embeddingsTools = [
|
|
|
928
928
|
const v = vt(params.query, 'query');
|
|
929
929
|
if (!v.valid)
|
|
930
930
|
return { success: false, error: v.error };
|
|
931
|
-
const { searchRabitq } = await import('../memory/rabitq-index.js');
|
|
931
|
+
const { ensureRabitqIndex, searchRabitq } = await import('../memory/rabitq-index.js');
|
|
932
932
|
const { generateEmbedding } = await import('../memory/memory-initializer.js');
|
|
933
933
|
const queryEmb = await generateEmbedding(params.query);
|
|
934
|
-
const
|
|
934
|
+
const searchOptions = {
|
|
935
935
|
k: params.k || 10,
|
|
936
936
|
namespace: params.namespace,
|
|
937
|
-
}
|
|
937
|
+
};
|
|
938
|
+
// #4203: the index is built on demand, so a search no longer requires a
|
|
939
|
+
// separate `embeddings_rabitq_build` call first.
|
|
940
|
+
let results = await searchRabitq(queryEmb.embedding, searchOptions);
|
|
941
|
+
if (!results) {
|
|
942
|
+
await ensureRabitqIndex({ dimensions: queryEmb.embedding.length });
|
|
943
|
+
results = await searchRabitq(queryEmb.embedding, searchOptions);
|
|
944
|
+
}
|
|
938
945
|
if (!results) {
|
|
939
|
-
return { success: false, error: 'RaBitQ index not built. Call embeddings_rabitq_build first.' };
|
|
946
|
+
return { success: false, error: 'RaBitQ index not built (needs at least 10 embedded entries). Call embeddings_rabitq_build first.' };
|
|
940
947
|
}
|
|
941
948
|
return {
|
|
942
949
|
success: true,
|
|
@@ -5,7 +5,7 @@ import { feedbackPatternsSchema, validateFeedbackPatterns } from '../memory/feed
|
|
|
5
5
|
*/
|
|
6
6
|
import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync, unlinkSync, readdirSync } from 'fs';
|
|
7
7
|
import * as nodeFs from 'fs';
|
|
8
|
-
import { dirname, join, resolve } from 'path';
|
|
8
|
+
import { dirname, join, resolve, sep } from 'path';
|
|
9
9
|
import { getProjectCwd } from './types.js';
|
|
10
10
|
import { validateIdentifier, validateText, validatePath } from './validate-input.js';
|
|
11
11
|
import { commandRecordingDisabled, redactCommandForStorage } from '../services/command-recording.js';
|
|
@@ -5136,7 +5136,17 @@ export const hooksCodemod = {
|
|
|
5136
5136
|
if (!v.valid)
|
|
5137
5137
|
return v.error;
|
|
5138
5138
|
const abs = resolve(cwd, v.sanitized);
|
|
5139
|
-
|
|
5139
|
+
// Anchor containment at a path-separator boundary — an unanchored
|
|
5140
|
+
// `abs.startsWith(cwd)` string-prefix check would let an absolute
|
|
5141
|
+
// path like `${cwd}-staging/x.ts` pass as "inside" `cwd`. Matches
|
|
5142
|
+
// the pattern already used correctly elsewhere in this codebase
|
|
5143
|
+
// (@claude-flow/security's `isWithinPrefix`, hooks/workers, plugins/security).
|
|
5144
|
+
// KNOWN LIMITATION (disclosed 2026-10-20, not fixed here): this
|
|
5145
|
+
// compares the resolved-but-not-realpath'd path, so a symlink
|
|
5146
|
+
// inside cwd pointing outside it still escapes containment
|
|
5147
|
+
// (CWE-59, a distinct/broader gap than the CWE-22 prefix bug this
|
|
5148
|
+
// anchors). Needs fs.realpathSync-based containment as a follow-up.
|
|
5149
|
+
if (abs !== cwd && !abs.startsWith(cwd + sep))
|
|
5140
5150
|
return `path escapes project root: ${p}`;
|
|
5141
5151
|
out.add(abs);
|
|
5142
5152
|
return undefined;
|
|
@@ -5169,7 +5179,7 @@ export const hooksCodemod = {
|
|
|
5169
5179
|
}
|
|
5170
5180
|
for (const m of matches) {
|
|
5171
5181
|
const abs = resolve(cwd, m);
|
|
5172
|
-
if (abs.startsWith(cwd) && CODEMOD_EXTENSIONS.has(abs.slice(abs.lastIndexOf('.')).toLowerCase())) {
|
|
5182
|
+
if ((abs === cwd || abs.startsWith(cwd + sep)) && CODEMOD_EXTENSIONS.has(abs.slice(abs.lastIndexOf('.')).toLowerCase())) {
|
|
5173
5183
|
out.add(abs);
|
|
5174
5184
|
}
|
|
5175
5185
|
}
|