claude-flow 3.50.0 → 3.51.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/.claude/settings.json +11 -2
- package/.claude-plugin/marketplace.json +5 -0
- package/package.json +1 -1
- package/v3/@claude-flow/cli/README.md +0 -2
- package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
- package/v3/@claude-flow/cli/dist/src/commands/catalog.d.ts +18 -0
- package/v3/@claude-flow/cli/dist/src/commands/catalog.js +89 -0
- package/v3/@claude-flow/cli/dist/src/commands/doctor.js +8 -3
- package/v3/@claude-flow/cli/dist/src/commands/index.js +3 -0
- package/v3/@claude-flow/cli/dist/src/commands/init.js +50 -11
- package/v3/@claude-flow/cli/dist/src/commands/mission.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/commands/mission.js +116 -0
- package/v3/@claude-flow/cli/dist/src/commands/mods.d.ts +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/mods.js +67 -23
- package/v3/@claude-flow/cli/dist/src/mcp-client.js +3 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +1 -1
- 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/mission-tools.d.ts +22 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/mission-tools.js +75 -0
- package/v3/@claude-flow/cli/dist/src/missions/fold.d.ts +19 -0
- package/v3/@claude-flow/cli/dist/src/missions/fold.js +238 -0
- package/v3/@claude-flow/cli/dist/src/missions/index.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/missions/index.js +10 -0
- package/v3/@claude-flow/cli/dist/src/missions/observation.d.ts +70 -0
- package/v3/@claude-flow/cli/dist/src/missions/observation.js +77 -0
- package/v3/@claude-flow/cli/dist/src/missions/schemas.d.ts +735 -0
- package/v3/@claude-flow/cli/dist/src/missions/schemas.js +150 -0
- package/v3/@claude-flow/cli/dist/src/missions/service.d.ts +88 -0
- package/v3/@claude-flow/cli/dist/src/missions/service.js +278 -0
- package/v3/@claude-flow/cli/dist/src/missions/store.d.ts +68 -0
- package/v3/@claude-flow/cli/dist/src/missions/store.js +209 -0
- package/v3/@claude-flow/cli/dist/src/missions/transitions.d.ts +81 -0
- package/v3/@claude-flow/cli/dist/src/missions/transitions.js +178 -0
- package/v3/@claude-flow/cli/dist/src/mods/apply.d.ts +50 -0
- package/v3/@claude-flow/cli/dist/src/mods/apply.js +186 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.d.ts +45 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/catalog.js +234 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.d.ts +66 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/engine-report.js +119 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/generate.js +166 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.d.ts +9 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/index.js +9 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/inventory.js +230 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.d.ts +836 -0
- package/v3/@claude-flow/cli/dist/src/mods/command-registry/schema.js +137 -0
- package/v3/@claude-flow/cli/dist/src/mods/install.d.ts +60 -22
- package/v3/@claude-flow/cli/dist/src/mods/install.js +91 -41
- package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.d.ts +81 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-repair.js +120 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/mods/plugin-resolve.js +182 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/mods/probe.js +28 -3
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.d.ts +5 -0
- package/v3/@claude-flow/cli/dist/src/services/policy-runtime.js +5 -1
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-406 P0/P1 — gather inputs (git history, packed npm contents, engine
|
|
3
|
+
* probes) and produce the inventory report, engine report and catalog.
|
|
4
|
+
*
|
|
5
|
+
* Reads command sources; writes only the catalog and report files it is given.
|
|
6
|
+
* External programs run through fixed argv with no shell (`execFileSync`).
|
|
7
|
+
*/
|
|
8
|
+
import { type EngineReport, type Runner } from './engine-report.js';
|
|
9
|
+
import { type RawCommand } from './inventory.js';
|
|
10
|
+
import type { CommandCatalog } from './schema.js';
|
|
11
|
+
export declare const CATALOG_RELATIVE_PATH = "v3/@claude-flow/cli/src/mods/command-registry/catalog.generated.json";
|
|
12
|
+
export declare const ENGINE_REPORT_RELATIVE_PATH = "v3/@claude-flow/cli/src/mods/command-registry/engine-report.generated.json";
|
|
13
|
+
export declare const PROPOSED_NAMES: readonly ["ruflo", "ruflo-workbench", "ruflo-console"];
|
|
14
|
+
export declare const defaultRunner: Runner;
|
|
15
|
+
/**
|
|
16
|
+
* Last commit per repository-relative path, in one `git log` pass; dirty paths
|
|
17
|
+
* are dropped (they read as `uncommitted`). `order` lists commits newest first.
|
|
18
|
+
*/
|
|
19
|
+
export declare function lastCommits(repoRoot: string, roots: readonly string[], run?: Runner): {
|
|
20
|
+
byPath: Record<string, string>;
|
|
21
|
+
order: string[];
|
|
22
|
+
dirty: Set<string>;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The catalog's anchor: the newest commit that touched a file the catalog
|
|
26
|
+
* actually inventories, so unrelated plugin commits do not make it stale.
|
|
27
|
+
*/
|
|
28
|
+
export declare function anchorCommit(paths: readonly string[], commits: ReturnType<typeof lastCommits>): string;
|
|
29
|
+
/** `npm pack --dry-run --json` file list for a package directory, made repository-relative. */
|
|
30
|
+
export declare function packedFiles(repoRoot: string, packageDir: string, run?: Runner): {
|
|
31
|
+
name: string;
|
|
32
|
+
files: string[];
|
|
33
|
+
} | null;
|
|
34
|
+
export interface GenerateInput {
|
|
35
|
+
readonly repoRoot: string;
|
|
36
|
+
readonly engineExecutable: string | null;
|
|
37
|
+
readonly run?: Runner;
|
|
38
|
+
/** Skip `npm pack` (tests); shipped status is then not asserted. */
|
|
39
|
+
readonly inspectPackages?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface GenerateResult {
|
|
42
|
+
readonly inventory: readonly RawCommand[];
|
|
43
|
+
readonly catalog: CommandCatalog;
|
|
44
|
+
readonly engine: EngineReport;
|
|
45
|
+
readonly modHookSource: 'engine-validate' | 'source-scan';
|
|
46
|
+
readonly packages: readonly string[];
|
|
47
|
+
}
|
|
48
|
+
export declare function generate(input: GenerateInput): GenerateResult;
|
|
49
|
+
/** Counts for the P0 exit gate: every inventoried command is classified. */
|
|
50
|
+
export declare function summarize(result: GenerateResult): Record<string, number | string>;
|
|
51
|
+
//# sourceMappingURL=generate.d.ts.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-406 P0/P1 — gather inputs (git history, packed npm contents, engine
|
|
3
|
+
* probes) and produce the inventory report, engine report and catalog.
|
|
4
|
+
*
|
|
5
|
+
* Reads command sources; writes only the catalog and report files it is given.
|
|
6
|
+
* External programs run through fixed argv with no shell (`execFileSync`).
|
|
7
|
+
*/
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { existsSync, mkdtempSync, rmSync } from 'node:fs';
|
|
10
|
+
import { tmpdir } from 'node:os';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { buildCatalog } from './catalog.js';
|
|
13
|
+
import { probeEngine, hooksFromValidations } from './engine-report.js';
|
|
14
|
+
import { CLI_TEMPLATE_COMMANDS_DIR, PROJECT_COMMANDS_DIR, collectInventory, scanModHooksFromSource, } from './inventory.js';
|
|
15
|
+
export const CATALOG_RELATIVE_PATH = 'v3/@claude-flow/cli/src/mods/command-registry/catalog.generated.json';
|
|
16
|
+
export const ENGINE_REPORT_RELATIVE_PATH = 'v3/@claude-flow/cli/src/mods/command-registry/engine-report.generated.json';
|
|
17
|
+
export const PROPOSED_NAMES = ['ruflo', 'ruflo-workbench', 'ruflo-console'];
|
|
18
|
+
export const defaultRunner = (file, args, env) => {
|
|
19
|
+
try {
|
|
20
|
+
const stdout = execFileSync(file, [...args], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 120_000, maxBuffer: 64 * 1024 * 1024 });
|
|
21
|
+
return { status: 0, stdout, stderr: '' };
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const e = error;
|
|
25
|
+
return { status: e.status ?? null, stdout: String(e.stdout ?? ''), stderr: String(e.stderr ?? '') };
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Last commit per repository-relative path, in one `git log` pass; dirty paths
|
|
30
|
+
* are dropped (they read as `uncommitted`). `order` lists commits newest first.
|
|
31
|
+
*/
|
|
32
|
+
export function lastCommits(repoRoot, roots, run = defaultRunner) {
|
|
33
|
+
const log = run('git', ['-C', repoRoot, 'log', '--format=%x00%H', '--name-only', '--', ...roots], process.env);
|
|
34
|
+
const byPath = {};
|
|
35
|
+
const order = [];
|
|
36
|
+
let current = '';
|
|
37
|
+
for (const line of log.stdout.split('\n')) {
|
|
38
|
+
if (line.startsWith('\u0000')) {
|
|
39
|
+
current = line.slice(1).trim();
|
|
40
|
+
order.push(current);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const path = line.trim();
|
|
44
|
+
if (path && current && !(path in byPath))
|
|
45
|
+
byPath[path] = current;
|
|
46
|
+
}
|
|
47
|
+
const status = run('git', ['-C', repoRoot, 'status', '--porcelain', '--untracked-files=all', '--', ...roots], process.env);
|
|
48
|
+
const dirty = new Set();
|
|
49
|
+
for (const line of status.stdout.split('\n')) {
|
|
50
|
+
const path = line.slice(3).trim();
|
|
51
|
+
if (path) {
|
|
52
|
+
delete byPath[path];
|
|
53
|
+
dirty.add(path);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { byPath, order, dirty };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The catalog's anchor: the newest commit that touched a file the catalog
|
|
60
|
+
* actually inventories, so unrelated plugin commits do not make it stale.
|
|
61
|
+
*/
|
|
62
|
+
export function anchorCommit(paths, commits) {
|
|
63
|
+
if (paths.some((p) => commits.dirty.has(p) || !commits.byPath[p]))
|
|
64
|
+
return 'uncommitted';
|
|
65
|
+
const used = new Set(paths.map((p) => commits.byPath[p]));
|
|
66
|
+
return commits.order.find((c) => used.has(c)) ?? 'uncommitted';
|
|
67
|
+
}
|
|
68
|
+
/** `npm pack --dry-run --json` file list for a package directory, made repository-relative. */
|
|
69
|
+
export function packedFiles(repoRoot, packageDir, run = defaultRunner) {
|
|
70
|
+
// The runner has no cwd; `npm pack <folder>` packs that folder's package.
|
|
71
|
+
const out = run('npm', ['pack', '--dry-run', '--json', '--ignore-scripts', join(repoRoot, packageDir)], process.env);
|
|
72
|
+
if (out.status !== 0)
|
|
73
|
+
return null;
|
|
74
|
+
try {
|
|
75
|
+
const parsed = JSON.parse(out.stdout.slice(out.stdout.indexOf('[')));
|
|
76
|
+
const prefix = packageDir === '.' ? '' : `${packageDir.replace(/\/$/, '')}/`;
|
|
77
|
+
return { name: parsed[0].name, files: parsed[0].files.map((f) => `${prefix}${f.path}`) };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function generate(input) {
|
|
84
|
+
const run = input.run ?? defaultRunner;
|
|
85
|
+
const configDir = mkdtempSync(join(tmpdir(), 'ruflo-engine-probe-'));
|
|
86
|
+
try {
|
|
87
|
+
const roots = [PROJECT_COMMANDS_DIR, CLI_TEMPLATE_COMMANDS_DIR, 'plugins'].filter((r) => existsSync(join(input.repoRoot, r)));
|
|
88
|
+
const packed = {};
|
|
89
|
+
if (input.inspectPackages !== false) {
|
|
90
|
+
for (const dir of ['v3/@claude-flow/cli', '.', 'ruflo']) {
|
|
91
|
+
if (!existsSync(join(input.repoRoot, dir, 'package.json')))
|
|
92
|
+
continue;
|
|
93
|
+
const p = packedFiles(input.repoRoot, dir, run);
|
|
94
|
+
if (p)
|
|
95
|
+
packed[p.name] = p.files;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// First pass without hooks to know every existing name for collisions.
|
|
99
|
+
const sourceHooks = scanModHooksFromSource(input.repoRoot);
|
|
100
|
+
const preliminary = collectInventory({ repoRoot: input.repoRoot, modHooks: sourceHooks });
|
|
101
|
+
const engine = probeEngine({
|
|
102
|
+
repoRoot: input.repoRoot,
|
|
103
|
+
executable: input.engineExecutable,
|
|
104
|
+
isolatedConfigDir: configDir,
|
|
105
|
+
run,
|
|
106
|
+
proposedNames: PROPOSED_NAMES,
|
|
107
|
+
existingNames: new Set(preliminary.map((r) => r.key)),
|
|
108
|
+
});
|
|
109
|
+
// The engine confirms which command hooks exist; the source scan knows which
|
|
110
|
+
// file declares each. Prefer the declaring file so the catalog is the same
|
|
111
|
+
// whether or not an engine was available to confirm it.
|
|
112
|
+
const engineHooks = hooksFromValidations(engine.validations)
|
|
113
|
+
.map((h) => sourceHooks.find((s) => s.plugin === h.plugin && s.command === h.command) ?? h);
|
|
114
|
+
// A plugin whose matcher the engine reports as `?` names its commands only in source: keep those.
|
|
115
|
+
const dynamicPlugins = new Set(engine.validations.filter((v) => v.dynamicCommandHook).map((v) => v.plugin));
|
|
116
|
+
for (const s of sourceHooks) {
|
|
117
|
+
if (dynamicPlugins.has(s.plugin) && !engineHooks.some((h) => h.plugin === s.plugin && h.command === s.command))
|
|
118
|
+
engineHooks.push(s);
|
|
119
|
+
}
|
|
120
|
+
const useEngine = engine.validations.length > 0 && engine.validations.every((v) => v.passed === true);
|
|
121
|
+
const inventory = collectInventory({
|
|
122
|
+
repoRoot: input.repoRoot,
|
|
123
|
+
packedFiles: Object.keys(packed).length ? packed : undefined,
|
|
124
|
+
modHooks: useEngine ? engineHooks : sourceHooks,
|
|
125
|
+
});
|
|
126
|
+
const commits = lastCommits(input.repoRoot, roots, run);
|
|
127
|
+
const options = {
|
|
128
|
+
commits: commits.byPath,
|
|
129
|
+
packageContentsInspected: Object.keys(packed).length > 0,
|
|
130
|
+
sourceCommit: anchorCommit([...new Set(inventory.map((r) => r.source.sourcePath))], commits),
|
|
131
|
+
};
|
|
132
|
+
return {
|
|
133
|
+
inventory,
|
|
134
|
+
catalog: buildCatalog(inventory, options),
|
|
135
|
+
engine,
|
|
136
|
+
modHookSource: useEngine ? 'engine-validate' : 'source-scan',
|
|
137
|
+
packages: Object.keys(packed).sort(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
rmSync(configDir, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/** Counts for the P0 exit gate: every inventoried command is classified. */
|
|
145
|
+
export function summarize(result) {
|
|
146
|
+
const byStatus = {};
|
|
147
|
+
const byDisposition = {};
|
|
148
|
+
const byChannel = {};
|
|
149
|
+
for (const e of result.catalog.entries) {
|
|
150
|
+
byStatus[e.status] = (byStatus[e.status] ?? 0) + 1;
|
|
151
|
+
byDisposition[e.definition.disposition] = (byDisposition[e.definition.disposition] ?? 0) + 1;
|
|
152
|
+
for (const s of e.sources)
|
|
153
|
+
byChannel[s.channel] = (byChannel[s.channel] ?? 0) + 1;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
occurrences: result.inventory.length,
|
|
157
|
+
entries: result.catalog.entries.length,
|
|
158
|
+
divergent: result.catalog.entries.filter((e) => e.divergent).length,
|
|
159
|
+
modHookSource: result.modHookSource,
|
|
160
|
+
packages: result.packages.join(',') || 'none',
|
|
161
|
+
...Object.fromEntries(Object.entries(byStatus).map(([k, v]) => [`status.${k}`, v])),
|
|
162
|
+
...Object.fromEntries(Object.entries(byDisposition).map(([k, v]) => [`disposition.${k}`, v])),
|
|
163
|
+
...Object.fromEntries(Object.entries(byChannel).map(([k, v]) => [`channel.${k}`, v])),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=generate.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-406 canonical command registry (P0 inventory, P1 registry + catalog).
|
|
3
|
+
*/
|
|
4
|
+
export * from './schema.js';
|
|
5
|
+
export * from './inventory.js';
|
|
6
|
+
export * from './catalog.js';
|
|
7
|
+
export * from './engine-report.js';
|
|
8
|
+
export * from './generate.js';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-406 canonical command registry (P0 inventory, P1 registry + catalog).
|
|
3
|
+
*/
|
|
4
|
+
export * from './schema.js';
|
|
5
|
+
export * from './inventory.js';
|
|
6
|
+
export * from './catalog.js';
|
|
7
|
+
export * from './engine-report.js';
|
|
8
|
+
export * from './generate.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-406 P0 — command inventory.
|
|
3
|
+
*
|
|
4
|
+
* Scans every place a Ruflo slash command can come from and records, per
|
|
5
|
+
* occurrence, its source path, delivery channel, package, digest, the
|
|
6
|
+
* invocation spelling the engine exposes, and its runtime binding. It reads
|
|
7
|
+
* only; nothing here writes a command file.
|
|
8
|
+
*
|
|
9
|
+
* Spelling rules recorded here were checked against Claude Code 2.1.287 in a
|
|
10
|
+
* live session (see the engine report): project commands in a subfolder are
|
|
11
|
+
* `<dir>:<name>` (`sparc:architect`, `agents:README`), plugin commands are
|
|
12
|
+
* `<plugin>:<name>` (`ruflo-swarm:swarm`), and a mod's `$.command.register`
|
|
13
|
+
* name is used verbatim (`ruflo-swarm-status`).
|
|
14
|
+
*
|
|
15
|
+
* The loader exposes every `.md` file in a commands folder, including READMEs
|
|
16
|
+
* and compliance reports (`agents:README`, `analysis:COMMAND_COMPLIANCE_REPORT`
|
|
17
|
+
* appear in the session command list). Those are inventoried as documentation,
|
|
18
|
+
* not hidden and not counted as executable workflows.
|
|
19
|
+
*/
|
|
20
|
+
import type { InventorySource } from './schema.js';
|
|
21
|
+
/** Mirror of `COMMANDS_MAP` in `src/init/executor.ts`; a parity test keeps them equal. */
|
|
22
|
+
export declare const INIT_COMMAND_CATEGORIES: Readonly<Record<string, readonly string[]>>;
|
|
23
|
+
export declare const PROJECT_COMMANDS_DIR = ".claude/commands";
|
|
24
|
+
export declare const CLI_TEMPLATE_COMMANDS_DIR = "v3/@claude-flow/cli/.claude/commands";
|
|
25
|
+
export declare const MARKETPLACE_MANIFEST = ".claude-plugin/marketplace.json";
|
|
26
|
+
/** One occurrence of a command in one delivery channel. */
|
|
27
|
+
export interface RawCommand {
|
|
28
|
+
readonly key: string;
|
|
29
|
+
readonly ownerPlugin: string;
|
|
30
|
+
readonly content?: string;
|
|
31
|
+
readonly source: InventorySource;
|
|
32
|
+
}
|
|
33
|
+
/** A mod `command.run` hook as the engine's static scan reports it. */
|
|
34
|
+
export interface ModCommandHook {
|
|
35
|
+
readonly plugin: string;
|
|
36
|
+
readonly command: string;
|
|
37
|
+
readonly sourcePath: string;
|
|
38
|
+
}
|
|
39
|
+
export interface InventoryInput {
|
|
40
|
+
readonly repoRoot: string;
|
|
41
|
+
/** Repository-relative packed file lists per npm package (from `npm pack --dry-run --json`). */
|
|
42
|
+
readonly packedFiles?: Readonly<Record<string, readonly string[]>>;
|
|
43
|
+
/** Mod command hooks; from `claude plugin validate` when available, else a source scan. */
|
|
44
|
+
readonly modHooks?: readonly ModCommandHook[];
|
|
45
|
+
}
|
|
46
|
+
export declare function sha256Hex(data: string | Buffer): string;
|
|
47
|
+
/** `sparc/architect.md` -> `sparc:architect`. */
|
|
48
|
+
export declare function projectInvocation(relativeToCommandsDir: string): string;
|
|
49
|
+
interface MarketplacePlugin {
|
|
50
|
+
name: string;
|
|
51
|
+
source: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function readMarketplace(repoRoot: string): MarketplacePlugin[];
|
|
54
|
+
/**
|
|
55
|
+
* Static fallback for mod hooks when the engine is not available: the
|
|
56
|
+
* `command.run` matchers written in each plugin's hooks sources. The engine
|
|
57
|
+
* report replaces this with `claude plugin validate` output when it can run.
|
|
58
|
+
*/
|
|
59
|
+
export declare function scanModHooksFromSource(repoRoot: string): ModCommandHook[];
|
|
60
|
+
/** Everything the repository and its packages expose as a slash command. */
|
|
61
|
+
export declare function collectInventory(input: InventoryInput): RawCommand[];
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=inventory.d.ts.map
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-406 P0 — command inventory.
|
|
3
|
+
*
|
|
4
|
+
* Scans every place a Ruflo slash command can come from and records, per
|
|
5
|
+
* occurrence, its source path, delivery channel, package, digest, the
|
|
6
|
+
* invocation spelling the engine exposes, and its runtime binding. It reads
|
|
7
|
+
* only; nothing here writes a command file.
|
|
8
|
+
*
|
|
9
|
+
* Spelling rules recorded here were checked against Claude Code 2.1.287 in a
|
|
10
|
+
* live session (see the engine report): project commands in a subfolder are
|
|
11
|
+
* `<dir>:<name>` (`sparc:architect`, `agents:README`), plugin commands are
|
|
12
|
+
* `<plugin>:<name>` (`ruflo-swarm:swarm`), and a mod's `$.command.register`
|
|
13
|
+
* name is used verbatim (`ruflo-swarm-status`).
|
|
14
|
+
*
|
|
15
|
+
* The loader exposes every `.md` file in a commands folder, including READMEs
|
|
16
|
+
* and compliance reports (`agents:README`, `analysis:COMMAND_COMPLIANCE_REPORT`
|
|
17
|
+
* appear in the session command list). Those are inventoried as documentation,
|
|
18
|
+
* not hidden and not counted as executable workflows.
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from 'node:crypto';
|
|
21
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
22
|
+
import { join, relative, sep } from 'node:path';
|
|
23
|
+
/** Mirror of `COMMANDS_MAP` in `src/init/executor.ts`; a parity test keeps them equal. */
|
|
24
|
+
export const INIT_COMMAND_CATEGORIES = Object.freeze({
|
|
25
|
+
core: ['claude-flow-help.md', 'claude-flow-swarm.md', 'claude-flow-memory.md'],
|
|
26
|
+
analysis: ['analysis'],
|
|
27
|
+
automation: ['automation'],
|
|
28
|
+
github: ['github'],
|
|
29
|
+
hooks: ['hooks'],
|
|
30
|
+
monitoring: ['monitoring'],
|
|
31
|
+
optimization: ['optimization'],
|
|
32
|
+
sparc: ['sparc'],
|
|
33
|
+
agents: ['agents'],
|
|
34
|
+
coordination: ['coordination'],
|
|
35
|
+
hiveMind: ['hive-mind'],
|
|
36
|
+
memory: ['memory'],
|
|
37
|
+
swarm: ['swarm'],
|
|
38
|
+
workflows: ['workflows'],
|
|
39
|
+
pair: ['pair'],
|
|
40
|
+
training: ['training'],
|
|
41
|
+
streamChain: ['stream-chain'],
|
|
42
|
+
truth: ['truth'],
|
|
43
|
+
verify: ['verify'],
|
|
44
|
+
});
|
|
45
|
+
export const PROJECT_COMMANDS_DIR = '.claude/commands';
|
|
46
|
+
export const CLI_TEMPLATE_COMMANDS_DIR = 'v3/@claude-flow/cli/.claude/commands';
|
|
47
|
+
export const MARKETPLACE_MANIFEST = '.claude-plugin/marketplace.json';
|
|
48
|
+
export function sha256Hex(data) {
|
|
49
|
+
return createHash('sha256').update(data).digest('hex');
|
|
50
|
+
}
|
|
51
|
+
function toPosix(path) {
|
|
52
|
+
return path.split(sep).join('/');
|
|
53
|
+
}
|
|
54
|
+
function listMarkdown(dir) {
|
|
55
|
+
if (!existsSync(dir))
|
|
56
|
+
return [];
|
|
57
|
+
const out = [];
|
|
58
|
+
const walk = (current) => {
|
|
59
|
+
for (const name of readdirSync(current).sort()) {
|
|
60
|
+
const full = join(current, name);
|
|
61
|
+
const stat = statSync(full);
|
|
62
|
+
if (stat.isDirectory())
|
|
63
|
+
walk(full);
|
|
64
|
+
else if (stat.isFile() && name.endsWith('.md'))
|
|
65
|
+
out.push(full);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
walk(dir);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** `sparc/architect.md` -> `sparc:architect`. */
|
|
72
|
+
export function projectInvocation(relativeToCommandsDir) {
|
|
73
|
+
return toPosix(relativeToCommandsDir).replace(/\.md$/, '').split('/').join(':');
|
|
74
|
+
}
|
|
75
|
+
function initCategoryFor(relativeToCommandsDir) {
|
|
76
|
+
const top = toPosix(relativeToCommandsDir).split('/')[0];
|
|
77
|
+
for (const [category, entries] of Object.entries(INIT_COMMAND_CATEGORIES)) {
|
|
78
|
+
if (entries.includes(top))
|
|
79
|
+
return category;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
function shippedIn(repoPath, packedFiles) {
|
|
84
|
+
if (!packedFiles)
|
|
85
|
+
return [];
|
|
86
|
+
return Object.entries(packedFiles)
|
|
87
|
+
.filter(([, files]) => files.includes(repoPath))
|
|
88
|
+
.map(([name]) => name)
|
|
89
|
+
.sort();
|
|
90
|
+
}
|
|
91
|
+
function scanCommandsDir(input, commandsDir, channel, pkg, ownerPlugin, prefix) {
|
|
92
|
+
const absolute = join(input.repoRoot, commandsDir);
|
|
93
|
+
return listMarkdown(absolute).map((file) => {
|
|
94
|
+
const relToDir = toPosix(relative(absolute, file));
|
|
95
|
+
const repoPath = toPosix(relative(input.repoRoot, file));
|
|
96
|
+
const content = readFileSync(file, 'utf8');
|
|
97
|
+
const invocation = prefix ? `${prefix}:${projectInvocation(relToDir)}` : projectInvocation(relToDir);
|
|
98
|
+
return {
|
|
99
|
+
key: invocation,
|
|
100
|
+
ownerPlugin,
|
|
101
|
+
content,
|
|
102
|
+
source: {
|
|
103
|
+
channel,
|
|
104
|
+
package: pkg,
|
|
105
|
+
sourcePath: repoPath,
|
|
106
|
+
digest: `sha256:${sha256Hex(content)}`,
|
|
107
|
+
observedInvocation: `/${invocation}`,
|
|
108
|
+
binding: 'markdown-loader',
|
|
109
|
+
...(channel === 'cli-init-template' && initCategoryFor(relToDir)
|
|
110
|
+
? { initCategory: initCategoryFor(relToDir) }
|
|
111
|
+
: {}),
|
|
112
|
+
shippedIn: shippedIn(repoPath, input.packedFiles),
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
export function readMarketplace(repoRoot) {
|
|
118
|
+
const file = join(repoRoot, MARKETPLACE_MANIFEST);
|
|
119
|
+
if (!existsSync(file))
|
|
120
|
+
return [];
|
|
121
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
122
|
+
if (!Array.isArray(parsed.plugins))
|
|
123
|
+
return [];
|
|
124
|
+
return parsed.plugins
|
|
125
|
+
.filter((p) => !!p && typeof p.name === 'string' && typeof p.source === 'string')
|
|
126
|
+
.map((p) => ({ name: p.name, source: toPosix(p.source).replace(/^\.\//, '').replace(/\/$/, '') }));
|
|
127
|
+
}
|
|
128
|
+
/** Plugin command folders: marketplace-listed sources, then any other `plugins/<p>/commands`. */
|
|
129
|
+
function scanPlugins(input) {
|
|
130
|
+
const out = [];
|
|
131
|
+
const listed = new Set();
|
|
132
|
+
for (const plugin of readMarketplace(input.repoRoot)) {
|
|
133
|
+
const dir = `${plugin.source}/commands`;
|
|
134
|
+
listed.add(dir);
|
|
135
|
+
out.push(...scanCommandsDir(input, dir, 'marketplace-plugin', `${plugin.name}@ruflo`, plugin.name, plugin.name));
|
|
136
|
+
}
|
|
137
|
+
const pluginsRoot = join(input.repoRoot, 'plugins');
|
|
138
|
+
if (!existsSync(pluginsRoot))
|
|
139
|
+
return out;
|
|
140
|
+
for (const name of readdirSync(pluginsRoot).sort()) {
|
|
141
|
+
const dir = `plugins/${name}/commands`;
|
|
142
|
+
if (listed.has(dir) || !existsSync(join(input.repoRoot, dir)))
|
|
143
|
+
continue;
|
|
144
|
+
out.push(...scanCommandsDir(input, dir, 'source-plugin', `plugins/${name}`, name, name));
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Mod hooks. A `command.run` hook on a name that a Markdown command of the
|
|
150
|
+
* same plugin already exposes is middleware on that workflow, not a new
|
|
151
|
+
* command; any other name is a mod registration.
|
|
152
|
+
*/
|
|
153
|
+
function scanModHooks(input, markdown) {
|
|
154
|
+
const markdownKeys = new Set(markdown.map((m) => m.key));
|
|
155
|
+
return (input.modHooks ?? []).map((hook) => {
|
|
156
|
+
const middleware = markdownKeys.has(hook.command);
|
|
157
|
+
const file = join(input.repoRoot, hook.sourcePath);
|
|
158
|
+
const digest = existsSync(file) ? sha256Hex(readFileSync(file)) : sha256Hex(hook.command);
|
|
159
|
+
return {
|
|
160
|
+
key: hook.command,
|
|
161
|
+
ownerPlugin: hook.plugin,
|
|
162
|
+
source: {
|
|
163
|
+
channel: 'mod-registration',
|
|
164
|
+
package: `${hook.plugin}@ruflo`,
|
|
165
|
+
sourcePath: hook.sourcePath,
|
|
166
|
+
digest: `sha256:${digest}`,
|
|
167
|
+
observedInvocation: `/${hook.command}`,
|
|
168
|
+
binding: middleware ? 'mod-middleware-on-markdown' : 'mod-register',
|
|
169
|
+
shippedIn: [],
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Static fallback for mod hooks when the engine is not available: the
|
|
176
|
+
* `command.run` matchers written in each plugin's hooks sources. The engine
|
|
177
|
+
* report replaces this with `claude plugin validate` output when it can run.
|
|
178
|
+
*/
|
|
179
|
+
export function scanModHooksFromSource(repoRoot) {
|
|
180
|
+
const pluginsRoot = join(repoRoot, 'plugins');
|
|
181
|
+
if (!existsSync(pluginsRoot))
|
|
182
|
+
return [];
|
|
183
|
+
const hooks = [];
|
|
184
|
+
for (const plugin of readdirSync(pluginsRoot).sort()) {
|
|
185
|
+
const hooksDir = join(pluginsRoot, plugin, 'hooks');
|
|
186
|
+
if (!existsSync(join(hooksDir, 'register.ts')))
|
|
187
|
+
continue;
|
|
188
|
+
const walk = (dir) => {
|
|
189
|
+
for (const name of readdirSync(dir).sort()) {
|
|
190
|
+
const full = join(dir, name);
|
|
191
|
+
if (statSync(full).isDirectory()) {
|
|
192
|
+
walk(full);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (!name.endsWith('.ts'))
|
|
196
|
+
continue;
|
|
197
|
+
const text = readFileSync(full, 'utf8');
|
|
198
|
+
const seen = new Set();
|
|
199
|
+
for (const m of text.matchAll(/command\.run'\s*,\s*\{\s*command:\s*'([a-z0-9._:-]+)'/gi))
|
|
200
|
+
seen.add(m[1]);
|
|
201
|
+
for (const m of text.matchAll(/\.register(?:Command)?\(\{\s*name:\s*'([a-z0-9._:-]+)'/gi))
|
|
202
|
+
seen.add(m[1]);
|
|
203
|
+
for (const m of text.matchAll(/\{\s*name:\s*'(ruflo-[a-z0-9-]+)'\s*,\s*description:/gi))
|
|
204
|
+
seen.add(m[1]);
|
|
205
|
+
for (const command of [...seen].sort()) {
|
|
206
|
+
hooks.push({ plugin, command, sourcePath: toPosix(relative(repoRoot, full)) });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
walk(hooksDir);
|
|
211
|
+
}
|
|
212
|
+
// One hook per (plugin, command): a name both registered and matched is one command.
|
|
213
|
+
const unique = new Map();
|
|
214
|
+
for (const hook of hooks) {
|
|
215
|
+
const key = `${hook.plugin}\u0000${hook.command}`;
|
|
216
|
+
if (!unique.has(key))
|
|
217
|
+
unique.set(key, hook);
|
|
218
|
+
}
|
|
219
|
+
return [...unique.values()];
|
|
220
|
+
}
|
|
221
|
+
/** Everything the repository and its packages expose as a slash command. */
|
|
222
|
+
export function collectInventory(input) {
|
|
223
|
+
const markdown = [
|
|
224
|
+
...scanCommandsDir(input, PROJECT_COMMANDS_DIR, 'repo-project-commands', 'claude-flow', 'project'),
|
|
225
|
+
...scanCommandsDir(input, CLI_TEMPLATE_COMMANDS_DIR, 'cli-init-template', '@claude-flow/cli', 'project'),
|
|
226
|
+
...scanPlugins(input),
|
|
227
|
+
];
|
|
228
|
+
return [...markdown, ...scanModHooks(input, markdown)];
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=inventory.js.map
|