maestro-flow-one 0.1.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/LICENSE +21 -0
- package/README.md +173 -0
- package/bin/maestro-flow.js +730 -0
- package/claude/maestro-flow/SKILL.md +239 -0
- package/claude/maestro-flow/chains/templates.json +256 -0
- package/claude/maestro-flow/commands/learn/decompose.md +176 -0
- package/claude/maestro-flow/commands/learn/follow.md +167 -0
- package/claude/maestro-flow/commands/learn/investigate.md +221 -0
- package/claude/maestro-flow/commands/learn/retro.md +303 -0
- package/claude/maestro-flow/commands/learn/second-opinion.md +167 -0
- package/claude/maestro-flow/commands/lifecycle/amend.md +300 -0
- package/claude/maestro-flow/commands/lifecycle/analyze.md +126 -0
- package/claude/maestro-flow/commands/lifecycle/brainstorm.md +100 -0
- package/claude/maestro-flow/commands/lifecycle/composer.md +354 -0
- package/claude/maestro-flow/commands/lifecycle/execute.md +114 -0
- package/claude/maestro-flow/commands/lifecycle/fork.md +86 -0
- package/claude/maestro-flow/commands/lifecycle/init.md +78 -0
- package/claude/maestro-flow/commands/lifecycle/learn.md +140 -0
- package/claude/maestro-flow/commands/lifecycle/link-coordinate.md +71 -0
- package/claude/maestro-flow/commands/lifecycle/merge.md +61 -0
- package/claude/maestro-flow/commands/lifecycle/overlay.md +178 -0
- package/claude/maestro-flow/commands/lifecycle/plan.md +138 -0
- package/claude/maestro-flow/commands/lifecycle/player.md +404 -0
- package/claude/maestro-flow/commands/lifecycle/quick.md +56 -0
- package/claude/maestro-flow/commands/lifecycle/roadmap.md +164 -0
- package/claude/maestro-flow/commands/lifecycle/ui-design.md +93 -0
- package/claude/maestro-flow/commands/lifecycle/update.md +176 -0
- package/claude/maestro-flow/commands/lifecycle/verify.md +90 -0
- package/claude/maestro-flow/commands/manage/codebase-rebuild.md +75 -0
- package/claude/maestro-flow/commands/manage/codebase-refresh.md +57 -0
- package/claude/maestro-flow/commands/manage/harvest.md +94 -0
- package/claude/maestro-flow/commands/manage/issue-discover.md +77 -0
- package/claude/maestro-flow/commands/manage/issue.md +73 -0
- package/claude/maestro-flow/commands/manage/knowhow-capture.md +193 -0
- package/claude/maestro-flow/commands/manage/knowhow.md +77 -0
- package/claude/maestro-flow/commands/manage/learn.md +67 -0
- package/claude/maestro-flow/commands/manage/status.md +51 -0
- package/claude/maestro-flow/commands/manage/wiki.md +62 -0
- package/claude/maestro-flow/commands/milestone/audit.md +68 -0
- package/claude/maestro-flow/commands/milestone/complete.md +75 -0
- package/claude/maestro-flow/commands/milestone/release.md +96 -0
- package/claude/maestro-flow/commands/quality/auto-test.md +124 -0
- package/claude/maestro-flow/commands/quality/debug.md +115 -0
- package/claude/maestro-flow/commands/quality/refactor.md +55 -0
- package/claude/maestro-flow/commands/quality/retrospective.md +78 -0
- package/claude/maestro-flow/commands/quality/review.md +108 -0
- package/claude/maestro-flow/commands/quality/sync.md +51 -0
- package/claude/maestro-flow/commands/quality/test.md +103 -0
- package/claude/maestro-flow/commands/spec/add.md +49 -0
- package/claude/maestro-flow/commands/spec/load.md +51 -0
- package/claude/maestro-flow/commands/spec/remove.md +51 -0
- package/claude/maestro-flow/commands/spec/setup.md +51 -0
- package/claude/maestro-flow/commands/wiki/connect.md +62 -0
- package/claude/maestro-flow/commands/wiki/digest.md +69 -0
- package/codex/maestro-flow/SKILL.md +505 -0
- package/codex/maestro-flow/chains/templates.json +256 -0
- package/codex/maestro-flow/commands/learn/decompose.md +113 -0
- package/codex/maestro-flow/commands/learn/follow.md +83 -0
- package/codex/maestro-flow/commands/learn/investigate.md +83 -0
- package/codex/maestro-flow/commands/learn/retro.md +83 -0
- package/codex/maestro-flow/commands/learn/second-opinion.md +86 -0
- package/codex/maestro-flow/commands/lifecycle/amend.md +300 -0
- package/codex/maestro-flow/commands/lifecycle/analyze.md +483 -0
- package/codex/maestro-flow/commands/lifecycle/brainstorm.md +397 -0
- package/codex/maestro-flow/commands/lifecycle/composer.md +213 -0
- package/codex/maestro-flow/commands/lifecycle/execute.md +318 -0
- package/codex/maestro-flow/commands/lifecycle/fork.md +98 -0
- package/codex/maestro-flow/commands/lifecycle/init.md +134 -0
- package/codex/maestro-flow/commands/lifecycle/learn.md +80 -0
- package/codex/maestro-flow/commands/lifecycle/link-coordinate.md +257 -0
- package/codex/maestro-flow/commands/lifecycle/merge.md +69 -0
- package/codex/maestro-flow/commands/lifecycle/overlay.md +119 -0
- package/codex/maestro-flow/commands/lifecycle/plan.md +460 -0
- package/codex/maestro-flow/commands/lifecycle/player.md +323 -0
- package/codex/maestro-flow/commands/lifecycle/quick.md +124 -0
- package/codex/maestro-flow/commands/lifecycle/roadmap.md +468 -0
- package/codex/maestro-flow/commands/lifecycle/ui-design.md +135 -0
- package/codex/maestro-flow/commands/lifecycle/update.md +176 -0
- package/codex/maestro-flow/commands/lifecycle/verify.md +468 -0
- package/codex/maestro-flow/commands/manage/codebase-rebuild.md +347 -0
- package/codex/maestro-flow/commands/manage/codebase-refresh.md +66 -0
- package/codex/maestro-flow/commands/manage/harvest.md +91 -0
- package/codex/maestro-flow/commands/manage/issue-discover.md +431 -0
- package/codex/maestro-flow/commands/manage/issue.md +75 -0
- package/codex/maestro-flow/commands/manage/knowhow-capture.md +110 -0
- package/codex/maestro-flow/commands/manage/knowhow.md +95 -0
- package/codex/maestro-flow/commands/manage/learn.md +137 -0
- package/codex/maestro-flow/commands/manage/status.md +76 -0
- package/codex/maestro-flow/commands/manage/wiki.md +55 -0
- package/codex/maestro-flow/commands/milestone/audit.md +87 -0
- package/codex/maestro-flow/commands/milestone/complete.md +91 -0
- package/codex/maestro-flow/commands/milestone/release.md +70 -0
- package/codex/maestro-flow/commands/quality/auto-test.md +547 -0
- package/codex/maestro-flow/commands/quality/debug.md +334 -0
- package/codex/maestro-flow/commands/quality/refactor.md +151 -0
- package/codex/maestro-flow/commands/quality/retrospective.md +292 -0
- package/codex/maestro-flow/commands/quality/review.md +364 -0
- package/codex/maestro-flow/commands/quality/sync.md +111 -0
- package/codex/maestro-flow/commands/quality/test.md +498 -0
- package/codex/maestro-flow/commands/spec/add.md +101 -0
- package/codex/maestro-flow/commands/spec/load.md +77 -0
- package/codex/maestro-flow/commands/spec/remove.md +69 -0
- package/codex/maestro-flow/commands/spec/setup.md +75 -0
- package/codex/maestro-flow/commands/wiki/connect.md +73 -0
- package/codex/maestro-flow/commands/wiki/digest.md +87 -0
- package/package.json +24 -0
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
// --- Path resolution ---
|
|
8
|
+
// Package root: ../ relative to this script
|
|
9
|
+
const PKG_ROOT = path.join(__dirname, "..");
|
|
10
|
+
const VARIANTS = {
|
|
11
|
+
codex: path.join(PKG_ROOT, "codex", "maestro-flow"),
|
|
12
|
+
claude: path.join(PKG_ROOT, "claude", "maestro-flow"),
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_VARIANT = "codex";
|
|
15
|
+
|
|
16
|
+
// Resolve active variant: check installed first (project -> global), then package
|
|
17
|
+
function getSkillRoot(variant) {
|
|
18
|
+
if (variant && VARIANTS[variant]) return VARIANTS[variant];
|
|
19
|
+
|
|
20
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
21
|
+
// Check installed locations: project first, then global
|
|
22
|
+
const candidates = [
|
|
23
|
+
path.join(process.cwd(), ".codex", "skills", "maestro-flow"),
|
|
24
|
+
path.join(process.cwd(), ".claude", "skills", "maestro-flow"),
|
|
25
|
+
path.join(home, ".codex", "skills", "maestro-flow"),
|
|
26
|
+
path.join(home, ".claude", "skills", "maestro-flow"),
|
|
27
|
+
];
|
|
28
|
+
for (const c of candidates) {
|
|
29
|
+
if (fs.existsSync(path.join(c, "SKILL.md"))) return c;
|
|
30
|
+
}
|
|
31
|
+
return VARIANTS[DEFAULT_VARIANT];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let SKILL_ROOT = getSkillRoot();
|
|
35
|
+
let COMMANDS_DIR = path.join(SKILL_ROOT, "commands");
|
|
36
|
+
let CHAINS_FILE = path.join(SKILL_ROOT, "chains", "templates.json");
|
|
37
|
+
|
|
38
|
+
function setVariant(variant) {
|
|
39
|
+
SKILL_ROOT = getSkillRoot(variant);
|
|
40
|
+
COMMANDS_DIR = path.join(SKILL_ROOT, "commands");
|
|
41
|
+
CHAINS_FILE = path.join(SKILL_ROOT, "chains", "templates.json");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- Name mapping ---
|
|
45
|
+
const PREFIX_CATEGORY = [
|
|
46
|
+
["maestro-milestone-", "milestone"],
|
|
47
|
+
["maestro-ralph-", "lifecycle"],
|
|
48
|
+
["maestro-", "lifecycle"],
|
|
49
|
+
["quality-", "quality"],
|
|
50
|
+
["manage-", "manage"],
|
|
51
|
+
["learn-", "learn"],
|
|
52
|
+
["spec-", "spec"],
|
|
53
|
+
["wiki-", "wiki"],
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// --- Helpers ---
|
|
57
|
+
|
|
58
|
+
function parseFrontmatter(filepath) {
|
|
59
|
+
const text = fs.readFileSync(filepath, "utf-8");
|
|
60
|
+
const match = text.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
61
|
+
if (!match) return {};
|
|
62
|
+
const fm = {};
|
|
63
|
+
for (const line of match[1].split("\n")) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (trimmed.startsWith("-") || trimmed.startsWith("#")) continue;
|
|
66
|
+
const idx = trimmed.indexOf(":");
|
|
67
|
+
if (idx > 0) {
|
|
68
|
+
const key = trimmed.slice(0, idx).trim();
|
|
69
|
+
const val = trimmed.slice(idx + 1).trim().replace(/^["']|["']$/g, "");
|
|
70
|
+
fm[key] = val;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return fm;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function extractPurpose(filepath) {
|
|
77
|
+
const text = fs.readFileSync(filepath, "utf-8");
|
|
78
|
+
const match = text.match(/<purpose>\s*\n([\s\S]*?)<\/purpose>/);
|
|
79
|
+
return match ? match[1].trim().slice(0, 200) : "";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveCommand(name) {
|
|
83
|
+
for (const [prefix, category] of PREFIX_CATEGORY) {
|
|
84
|
+
if (name.startsWith(prefix)) {
|
|
85
|
+
let filename = name.slice(prefix.length) + ".md";
|
|
86
|
+
if (!filename || filename === ".md") filename = name + ".md";
|
|
87
|
+
const p = path.join(COMMANDS_DIR, category, filename);
|
|
88
|
+
if (fs.existsSync(p)) return p;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Fallback: scan all categories
|
|
92
|
+
if (fs.existsSync(COMMANDS_DIR)) {
|
|
93
|
+
for (const cat of fs.readdirSync(COMMANDS_DIR)) {
|
|
94
|
+
const catDir = path.join(COMMANDS_DIR, cat);
|
|
95
|
+
if (!fs.statSync(catDir).isDirectory()) continue;
|
|
96
|
+
for (const f of fs.readdirSync(catDir).filter((x) => x.endsWith(".md"))) {
|
|
97
|
+
const fm = parseFrontmatter(path.join(catDir, f));
|
|
98
|
+
if (fm.name === name) return path.join(catDir, f);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function loadChains() {
|
|
106
|
+
if (!fs.existsSync(CHAINS_FILE)) return {};
|
|
107
|
+
return JSON.parse(fs.readFileSync(CHAINS_FILE, "utf-8"));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function findSessions(workDir, all) {
|
|
111
|
+
const maestroDir = path.join(workDir, ".workflow", ".maestro");
|
|
112
|
+
if (!fs.existsSync(maestroDir)) return [];
|
|
113
|
+
const sessions = [];
|
|
114
|
+
for (const d of fs.readdirSync(maestroDir).sort().reverse()) {
|
|
115
|
+
const statusFile = path.join(maestroDir, d, "status.json");
|
|
116
|
+
if (!fs.existsSync(statusFile)) continue;
|
|
117
|
+
const data = JSON.parse(fs.readFileSync(statusFile, "utf-8"));
|
|
118
|
+
data._path = statusFile;
|
|
119
|
+
if (data.source === "flow" || all) sessions.push(data);
|
|
120
|
+
}
|
|
121
|
+
return sessions;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function writeSession(session) {
|
|
125
|
+
const p = session._path;
|
|
126
|
+
const data = { ...session };
|
|
127
|
+
delete data._path;
|
|
128
|
+
data.updated_at = new Date().toISOString();
|
|
129
|
+
fs.writeFileSync(p, JSON.stringify(data, null, 2), "utf-8");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// --- Commands ---
|
|
133
|
+
|
|
134
|
+
function cmdList(args) {
|
|
135
|
+
const categoryFilter = args.category;
|
|
136
|
+
if (!fs.existsSync(COMMANDS_DIR)) {
|
|
137
|
+
console.log("Commands directory not found:", COMMANDS_DIR);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const categories = fs
|
|
141
|
+
.readdirSync(COMMANDS_DIR)
|
|
142
|
+
.filter((d) => fs.statSync(path.join(COMMANDS_DIR, d)).isDirectory())
|
|
143
|
+
.sort()
|
|
144
|
+
.filter((c) => !categoryFilter || c === categoryFilter);
|
|
145
|
+
|
|
146
|
+
let total = 0;
|
|
147
|
+
console.log(
|
|
148
|
+
`${"Category".padEnd(14)} ${"Command".padEnd(30)} Description`
|
|
149
|
+
);
|
|
150
|
+
console.log(`${"-".repeat(13)} ${"-".repeat(29)} ${"-".repeat(40)}`);
|
|
151
|
+
|
|
152
|
+
for (const cat of categories) {
|
|
153
|
+
const catDir = path.join(COMMANDS_DIR, cat);
|
|
154
|
+
const files = fs.readdirSync(catDir).filter((f) => f.endsWith(".md")).sort();
|
|
155
|
+
for (const f of files) {
|
|
156
|
+
const fm = parseFrontmatter(path.join(catDir, f));
|
|
157
|
+
const name = fm.name || f.replace(".md", "");
|
|
158
|
+
const desc = (fm.description || "").slice(0, 50);
|
|
159
|
+
console.log(`${cat.padEnd(14)} ${name.padEnd(30)} ${desc}`);
|
|
160
|
+
total++;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
console.log(`\nTotal: ${total} commands`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function cmdShow(args) {
|
|
167
|
+
const cmdPath = resolveCommand(args.name);
|
|
168
|
+
if (!cmdPath) {
|
|
169
|
+
console.log(`Command not found: ${args.name}`);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
const fm = parseFrontmatter(cmdPath);
|
|
173
|
+
const purpose = extractPurpose(cmdPath);
|
|
174
|
+
const relPath = path.relative(SKILL_ROOT, cmdPath);
|
|
175
|
+
|
|
176
|
+
console.log(`+${"-".repeat(50)}+`);
|
|
177
|
+
console.log(`| ${(fm.name || args.name).padEnd(48)} |`);
|
|
178
|
+
console.log(`| ${(fm.description || "").slice(0, 48).padEnd(48)} |`);
|
|
179
|
+
console.log(`+${"-".repeat(50)}+`);
|
|
180
|
+
if (fm["argument-hint"]) {
|
|
181
|
+
console.log(`| Args: ${fm["argument-hint"].slice(0, 42).padEnd(42)} |`);
|
|
182
|
+
}
|
|
183
|
+
console.log(`| File: ${relPath.slice(0, 42).padEnd(42)} |`);
|
|
184
|
+
if (purpose) {
|
|
185
|
+
console.log(`+${"-".repeat(50)}+`);
|
|
186
|
+
// Simple word wrap
|
|
187
|
+
const words = purpose.split(/\s+/);
|
|
188
|
+
let line = "";
|
|
189
|
+
for (const w of words) {
|
|
190
|
+
if (line.length + w.length + 1 > 48) {
|
|
191
|
+
console.log(`| ${line.padEnd(48)} |`);
|
|
192
|
+
line = w;
|
|
193
|
+
} else {
|
|
194
|
+
line = line ? line + " " + w : w;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (line) console.log(`| ${line.padEnd(48)} |`);
|
|
198
|
+
}
|
|
199
|
+
console.log(`+${"-".repeat(50)}+`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function cmdChains(args) {
|
|
203
|
+
const data = loadChains();
|
|
204
|
+
let templates = data.templates || {};
|
|
205
|
+
if (args.category) {
|
|
206
|
+
templates = Object.fromEntries(
|
|
207
|
+
Object.entries(templates).filter(([, v]) => v.category === args.category)
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
console.log(
|
|
211
|
+
`${"Template".padEnd(25)} ${"Category".padEnd(16)} ${"Steps".padStart(5)} Description`
|
|
212
|
+
);
|
|
213
|
+
console.log(
|
|
214
|
+
`${"-".repeat(24)} ${"-".repeat(15)} ${"-".repeat(5)} ${"-".repeat(35)}`
|
|
215
|
+
);
|
|
216
|
+
for (const [name, tmpl] of Object.entries(templates)) {
|
|
217
|
+
const steps = (tmpl.steps || []).length;
|
|
218
|
+
const desc = (tmpl.description || "").slice(0, 35);
|
|
219
|
+
const cat = tmpl.category || "";
|
|
220
|
+
console.log(
|
|
221
|
+
`${name.padEnd(25)} ${cat.padEnd(16)} ${String(steps).padStart(5)} ${desc}`
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
console.log(`\nTotal: ${Object.keys(templates).length} templates`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function cmdChain(args) {
|
|
228
|
+
const data = loadChains();
|
|
229
|
+
const tmpl = (data.templates || {})[args.name];
|
|
230
|
+
if (!tmpl) {
|
|
231
|
+
console.log(`Chain template not found: ${args.name}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
console.log(`\n Chain: ${args.name}`);
|
|
235
|
+
console.log(` Category: ${tmpl.category || ""}`);
|
|
236
|
+
console.log(` Description: ${tmpl.description || ""}`);
|
|
237
|
+
console.log(` Triggers: ${(tmpl.triggers || []).join(", ")}`);
|
|
238
|
+
console.log(`\n Steps:`);
|
|
239
|
+
for (let i = 0; i < (tmpl.steps || []).length; i++) {
|
|
240
|
+
const s = tmpl.steps[i];
|
|
241
|
+
const badge =
|
|
242
|
+
s.type === "external" ? "*" : s.type === "decision" ? ">" : " ";
|
|
243
|
+
console.log(
|
|
244
|
+
` ${i}. ${badge} ${s.cmd} ${s.args || ""} [${s.type}]`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
console.log();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function cmdSuggest(args) {
|
|
251
|
+
const intent = args.intent.toLowerCase();
|
|
252
|
+
const data = loadChains();
|
|
253
|
+
const matches = [];
|
|
254
|
+
|
|
255
|
+
for (const [name, tmpl] of Object.entries(data.templates || {})) {
|
|
256
|
+
let score = 0;
|
|
257
|
+
const matched = [];
|
|
258
|
+
for (const trigger of tmpl.triggers || []) {
|
|
259
|
+
if (intent.includes(trigger.toLowerCase())) {
|
|
260
|
+
score += trigger.length;
|
|
261
|
+
matched.push(trigger);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (score > 0) matches.push({ score, name, tmpl, matched });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
matches.sort((a, b) => b.score - a.score);
|
|
268
|
+
|
|
269
|
+
if (!matches.length) {
|
|
270
|
+
// Check single_commands
|
|
271
|
+
for (const [key, cmd] of Object.entries(data.single_commands || {})) {
|
|
272
|
+
if (intent.includes(key)) {
|
|
273
|
+
console.log(`\nSuggested single command:`);
|
|
274
|
+
console.log(` ${cmd} (match: ${key})`);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
console.log("No matching chain found. Try: maestro-flow chains");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
console.log(`\nSuggested chains for: "${args.intent}"`);
|
|
283
|
+
for (let i = 0; i < Math.min(3, matches.length); i++) {
|
|
284
|
+
const m = matches[i];
|
|
285
|
+
const steps = (m.tmpl.steps || []).length;
|
|
286
|
+
console.log(
|
|
287
|
+
` ${i + 1}. ${m.name.padEnd(25)} ${(m.tmpl.description || "").padEnd(35)} (${steps} steps, match: ${m.matched.join(", ")})`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function cmdResolve(args) {
|
|
293
|
+
const p = resolveCommand(args.name);
|
|
294
|
+
if (p) {
|
|
295
|
+
console.log(p);
|
|
296
|
+
} else {
|
|
297
|
+
console.error(`NOT_FOUND: ${args.name}`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function cmdStatus(args) {
|
|
303
|
+
const sessions = findSessions(process.cwd(), true);
|
|
304
|
+
const filtered = args.sessionId
|
|
305
|
+
? sessions.filter((s) => s.session_id === args.sessionId)
|
|
306
|
+
: sessions.filter((s) => s.status === "running");
|
|
307
|
+
const list = filtered.length ? filtered : sessions;
|
|
308
|
+
|
|
309
|
+
if (!list.length) {
|
|
310
|
+
console.log("No flow sessions found.");
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const session = list[0];
|
|
315
|
+
const steps = session.steps || [];
|
|
316
|
+
const total = steps.length;
|
|
317
|
+
const completed = steps.filter((s) => s.status === "completed").length;
|
|
318
|
+
|
|
319
|
+
console.log(`\n Session: ${session.session_id || "?"}`);
|
|
320
|
+
console.log(` Status: ${session.status || "?"}`);
|
|
321
|
+
console.log(` Source: ${session.source || "?"}`);
|
|
322
|
+
console.log(` Chain: ${session.chain_name || "?"} (${total} steps)`);
|
|
323
|
+
console.log(` Intent: ${(session.intent || "").slice(0, 60)}`);
|
|
324
|
+
if (session.phase) console.log(` Phase: ${session.phase}`);
|
|
325
|
+
console.log(
|
|
326
|
+
` Progress: ${completed}/${total} (${total ? Math.round((completed / total) * 100) : 0}%)`
|
|
327
|
+
);
|
|
328
|
+
console.log(`\n Steps:`);
|
|
329
|
+
|
|
330
|
+
for (const step of steps) {
|
|
331
|
+
const icon = { completed: "done", running: " >> ", failed: "FAIL", skipped: "skip", pending: " " }[step.status] || " ";
|
|
332
|
+
const badge = step.type === "external" ? "*" : step.type === "decision" ? ">" : " ";
|
|
333
|
+
console.log(
|
|
334
|
+
` [${icon}] ${step.index}. ${badge} ${step.skill} ${step.args || ""} [${step.type}]`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
console.log();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function cmdSessions(args) {
|
|
341
|
+
const sessions = findSessions(process.cwd(), !!args.all);
|
|
342
|
+
if (!sessions.length) {
|
|
343
|
+
console.log("No sessions found.");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
console.log(
|
|
347
|
+
`${"Session ID".padEnd(30)} ${"Status".padEnd(12)} ${"Source".padEnd(8)} ${"Chain".padEnd(20)} Intent`
|
|
348
|
+
);
|
|
349
|
+
console.log(
|
|
350
|
+
`${"-".repeat(29)} ${"-".repeat(11)} ${"-".repeat(7)} ${"-".repeat(19)} ${"-".repeat(30)}`
|
|
351
|
+
);
|
|
352
|
+
for (const s of sessions.slice(0, 20)) {
|
|
353
|
+
console.log(
|
|
354
|
+
`${(s.session_id || "?").padEnd(30)} ${(s.status || "?").padEnd(12)} ${(s.source || "?").padEnd(8)} ${(s.chain_name || "?").slice(0, 19).padEnd(20)} ${(s.intent || "").slice(0, 30)}`
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function cmdStep(args) {
|
|
360
|
+
const sessions = findSessions(process.cwd(), true);
|
|
361
|
+
const session = sessions.find((s) => s.session_id === args.sessionId);
|
|
362
|
+
if (!session) {
|
|
363
|
+
console.log(`Session not found: ${args.sessionId}`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
const idx = parseInt(args.index);
|
|
367
|
+
const steps = session.steps || [];
|
|
368
|
+
if (idx < 0 || idx >= steps.length) {
|
|
369
|
+
console.log(`Invalid step index: ${idx} (0-${steps.length - 1})`);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
const valid = ["pending", "running", "completed", "failed", "skipped"];
|
|
373
|
+
if (!valid.includes(args.status)) {
|
|
374
|
+
console.log(`Invalid status: ${args.status}. Use: ${valid.join(", ")}`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
}
|
|
377
|
+
steps[idx].status = args.status;
|
|
378
|
+
const now = new Date().toISOString();
|
|
379
|
+
if (args.status === "running") steps[idx].started_at = now;
|
|
380
|
+
else if (["completed", "failed", "skipped"].includes(args.status))
|
|
381
|
+
steps[idx].completed_at = now;
|
|
382
|
+
|
|
383
|
+
writeSession(session);
|
|
384
|
+
console.log(`Step ${idx} -> ${args.status}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function cmdNext(args) {
|
|
388
|
+
const sessions = findSessions(process.cwd(), true);
|
|
389
|
+
const filtered = args.sessionId
|
|
390
|
+
? sessions.filter((s) => s.session_id === args.sessionId)
|
|
391
|
+
: sessions.filter((s) => s.status === "running");
|
|
392
|
+
|
|
393
|
+
if (!filtered.length) {
|
|
394
|
+
console.log("NO_SESSION");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const session = filtered[0];
|
|
399
|
+
const steps = session.steps || [];
|
|
400
|
+
const total = steps.length;
|
|
401
|
+
|
|
402
|
+
const pending = steps.find((s) => s.status === "pending");
|
|
403
|
+
if (!pending) {
|
|
404
|
+
session.status = "completed";
|
|
405
|
+
writeSession(session);
|
|
406
|
+
console.log("SESSION_COMPLETE");
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const idx = pending.index;
|
|
411
|
+
pending.status = "running";
|
|
412
|
+
pending.started_at = new Date().toISOString();
|
|
413
|
+
session.current_step = idx;
|
|
414
|
+
writeSession(session);
|
|
415
|
+
|
|
416
|
+
console.log(`STEP: ${idx}/${total - 1}`);
|
|
417
|
+
console.log(`TYPE: ${pending.type}`);
|
|
418
|
+
console.log(`SKILL: ${pending.skill}`);
|
|
419
|
+
console.log(`ARGS: ${pending.args || ""}`);
|
|
420
|
+
|
|
421
|
+
if (pending.type === "decision") {
|
|
422
|
+
console.log(`DECISION: ${pending.decision || ""}`);
|
|
423
|
+
console.log(`RETRY: ${pending.retry_count || 0}/${pending.max_retries || 2}`);
|
|
424
|
+
console.log("---COMMAND---");
|
|
425
|
+
console.log("(decision node - evaluate via Agent)");
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const cmdPath = resolveCommand(pending.skill);
|
|
430
|
+
if (!cmdPath) {
|
|
431
|
+
console.log(`RESOLVE_FAILED: ${pending.skill}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
console.log(`PATH: ${cmdPath}`);
|
|
436
|
+
console.log("---COMMAND---");
|
|
437
|
+
console.log(fs.readFileSync(cmdPath, "utf-8"));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function cmdDone(args) {
|
|
441
|
+
const sessions = findSessions(process.cwd(), true);
|
|
442
|
+
const filtered = args.sessionId
|
|
443
|
+
? sessions.filter((s) => s.session_id === args.sessionId)
|
|
444
|
+
: sessions.filter((s) => s.status === "running");
|
|
445
|
+
|
|
446
|
+
if (!filtered.length) {
|
|
447
|
+
console.log("NO_SESSION");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const session = filtered[0];
|
|
452
|
+
const steps = session.steps || [];
|
|
453
|
+
|
|
454
|
+
const running = steps.find((s) => s.status === "running");
|
|
455
|
+
if (!running) {
|
|
456
|
+
console.log("NO_RUNNING_STEP");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
running.status = "completed";
|
|
461
|
+
running.completed_at = new Date().toISOString();
|
|
462
|
+
writeSession(session);
|
|
463
|
+
|
|
464
|
+
console.log(`COMPLETED: ${running.index} ${running.skill || "?"}`);
|
|
465
|
+
|
|
466
|
+
const nextPending = steps.find((s) => s.status === "pending");
|
|
467
|
+
if (!nextPending) {
|
|
468
|
+
// Reload and mark session complete
|
|
469
|
+
const data = JSON.parse(fs.readFileSync(session._path, "utf-8"));
|
|
470
|
+
data.status = "completed";
|
|
471
|
+
data.updated_at = new Date().toISOString();
|
|
472
|
+
fs.writeFileSync(session._path, JSON.stringify(data, null, 2), "utf-8");
|
|
473
|
+
console.log("SESSION_COMPLETE");
|
|
474
|
+
} else {
|
|
475
|
+
console.log(
|
|
476
|
+
`NEXT: ${nextPending.index} ${nextPending.skill || "?"} [${nextPending.type || "internal"}]`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function cmdReset(args) {
|
|
482
|
+
const sessions = findSessions(process.cwd(), true);
|
|
483
|
+
const session = sessions.find((s) => s.session_id === args.sessionId);
|
|
484
|
+
if (!session) {
|
|
485
|
+
console.log(`Session not found: ${args.sessionId}`);
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
let count = 0;
|
|
489
|
+
for (const step of session.steps || []) {
|
|
490
|
+
if (step.status === "failed" || step.status === "running") {
|
|
491
|
+
step.status = "pending";
|
|
492
|
+
delete step.error;
|
|
493
|
+
delete step.started_at;
|
|
494
|
+
delete step.completed_at;
|
|
495
|
+
count++;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
session.status = "running";
|
|
499
|
+
writeSession(session);
|
|
500
|
+
console.log(`Reset ${count} steps to pending. Session status -> running.`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function copyDir(src, dest) {
|
|
504
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
505
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
506
|
+
const srcPath = path.join(src, entry.name);
|
|
507
|
+
const destPath = path.join(dest, entry.name);
|
|
508
|
+
if (entry.isDirectory()) copyDir(srcPath, destPath);
|
|
509
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function countMd(dir) {
|
|
514
|
+
let n = 0;
|
|
515
|
+
if (!fs.existsSync(dir)) return 0;
|
|
516
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
517
|
+
if (entry.isDirectory()) n += countMd(path.join(dir, entry.name));
|
|
518
|
+
else if (entry.name.endsWith(".md")) n++;
|
|
519
|
+
}
|
|
520
|
+
return n;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function rmDir(dir) {
|
|
524
|
+
if (!fs.existsSync(dir)) return;
|
|
525
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
526
|
+
const p = path.join(dir, entry.name);
|
|
527
|
+
if (entry.isDirectory()) rmDir(p);
|
|
528
|
+
else fs.unlinkSync(p);
|
|
529
|
+
}
|
|
530
|
+
fs.rmdirSync(dir);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Resolve target skills directory per variant and scope
|
|
534
|
+
// codex -> .codex/skills/ claude -> .claude/skills/
|
|
535
|
+
function resolveSkillsDir(variant, args) {
|
|
536
|
+
const dotDir = variant === "codex" ? ".codex" : ".claude";
|
|
537
|
+
if (args.project) {
|
|
538
|
+
return path.join(path.resolve(args.project), dotDir, "skills");
|
|
539
|
+
}
|
|
540
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
541
|
+
return path.join(home, dotDir, "skills");
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function cmdInstall(args) {
|
|
545
|
+
const variant = args.variant || "all";
|
|
546
|
+
const validVariants = ["codex", "claude", "all"];
|
|
547
|
+
|
|
548
|
+
if (!validVariants.includes(variant)) {
|
|
549
|
+
console.log(`Invalid variant: ${variant}. Use: ${validVariants.join(", ")}`);
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const scope = args.project ? "project" : "global";
|
|
554
|
+
console.log(`Install scope: ${scope}`);
|
|
555
|
+
console.log();
|
|
556
|
+
|
|
557
|
+
const toInstall = variant === "all" ? ["codex", "claude"] : [variant];
|
|
558
|
+
|
|
559
|
+
for (const v of toInstall) {
|
|
560
|
+
const source = VARIANTS[v];
|
|
561
|
+
const skillsDir = resolveSkillsDir(v, args);
|
|
562
|
+
const skillName = "maestro-flow";
|
|
563
|
+
const skillTarget = path.join(skillsDir, skillName);
|
|
564
|
+
|
|
565
|
+
console.log(`Installing [${v}] -> ${skillName}`);
|
|
566
|
+
console.log(` Source: ${source}`);
|
|
567
|
+
console.log(` Target: ${skillTarget}`);
|
|
568
|
+
|
|
569
|
+
if (!fs.existsSync(path.join(source, "SKILL.md"))) {
|
|
570
|
+
console.error(` Error: SKILL.md not found in ${source}`);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
copyDir(source, skillTarget);
|
|
575
|
+
|
|
576
|
+
const cmdCount = countMd(path.join(skillTarget, "commands"));
|
|
577
|
+
console.log(` Commands: ${cmdCount}`);
|
|
578
|
+
console.log();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
console.log("Installation complete!");
|
|
582
|
+
if (variant === "all") {
|
|
583
|
+
console.log(" .codex/skills/maestro-flow/ -> codex (spawn_agents_on_csv)");
|
|
584
|
+
console.log(" .claude/skills/maestro-flow/ -> claude (Skill + delegate)");
|
|
585
|
+
} else {
|
|
586
|
+
const dotDir = variant === "codex" ? ".codex" : ".claude";
|
|
587
|
+
console.log(` ${dotDir}/skills/maestro-flow/ -> ${variant}`);
|
|
588
|
+
}
|
|
589
|
+
console.log();
|
|
590
|
+
console.log("Usage:");
|
|
591
|
+
console.log(' /maestro-flow "your intent"');
|
|
592
|
+
console.log(" maestro-flow list");
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function cmdUninstall(args) {
|
|
596
|
+
const variant = args.variant || "all";
|
|
597
|
+
const toUninstall = variant === "all" ? ["codex", "claude"] : [variant];
|
|
598
|
+
const scope = args.project ? "project" : "global";
|
|
599
|
+
|
|
600
|
+
let removed = 0;
|
|
601
|
+
for (const v of toUninstall) {
|
|
602
|
+
const skillsDir = resolveSkillsDir(v, args);
|
|
603
|
+
const skillTarget = path.join(skillsDir, "maestro-flow");
|
|
604
|
+
if (!fs.existsSync(skillTarget)) continue;
|
|
605
|
+
const cmdCount = countMd(path.join(skillTarget, "commands"));
|
|
606
|
+
const dotDir = v === "codex" ? ".codex" : ".claude";
|
|
607
|
+
rmDir(skillTarget);
|
|
608
|
+
console.log(`Uninstalled ${dotDir}/skills/maestro-flow (${cmdCount} commands) [${scope}]`);
|
|
609
|
+
removed++;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (!removed) {
|
|
613
|
+
console.log(`Maestro Flow not found [${scope}]`);
|
|
614
|
+
} else {
|
|
615
|
+
console.log();
|
|
616
|
+
console.log("Global CLI still available. Remove: npm uninstall -g maestro-flow-one");
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// --- CLI Parser ---
|
|
621
|
+
|
|
622
|
+
function parseArgs() {
|
|
623
|
+
const args = process.argv.slice(2);
|
|
624
|
+
if (!args.length) {
|
|
625
|
+
printHelp();
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const cmd = args[0];
|
|
630
|
+
const rest = args.slice(1);
|
|
631
|
+
|
|
632
|
+
// Parse flags
|
|
633
|
+
const flags = {};
|
|
634
|
+
const positional = [];
|
|
635
|
+
for (let i = 0; i < rest.length; i++) {
|
|
636
|
+
if (rest[i] === "--category" || rest[i] === "-c") {
|
|
637
|
+
flags.category = rest[++i];
|
|
638
|
+
} else if (rest[i] === "--all" || rest[i] === "-a") {
|
|
639
|
+
flags.all = true;
|
|
640
|
+
} else if (rest[i] === "--project" || rest[i] === "-p") {
|
|
641
|
+
flags.project = rest[++i];
|
|
642
|
+
} else if (rest[i] === "--variant" || rest[i] === "-v") {
|
|
643
|
+
flags.variant = rest[++i];
|
|
644
|
+
} else {
|
|
645
|
+
positional.push(rest[i]);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Apply variant for query commands
|
|
650
|
+
if (flags.variant) setVariant(flags.variant);
|
|
651
|
+
|
|
652
|
+
switch (cmd) {
|
|
653
|
+
case "list":
|
|
654
|
+
return cmdList({ category: flags.category });
|
|
655
|
+
case "show":
|
|
656
|
+
if (!positional[0]) { console.log("Usage: maestro-flow show <command-name>"); return; }
|
|
657
|
+
return cmdShow({ name: positional[0] });
|
|
658
|
+
case "chains":
|
|
659
|
+
return cmdChains({ category: flags.category });
|
|
660
|
+
case "chain":
|
|
661
|
+
if (!positional[0]) { console.log("Usage: maestro-flow chain <template-name>"); return; }
|
|
662
|
+
return cmdChain({ name: positional[0] });
|
|
663
|
+
case "suggest":
|
|
664
|
+
if (!positional[0]) { console.log("Usage: maestro-flow suggest <intent-text>"); return; }
|
|
665
|
+
return cmdSuggest({ intent: positional.join(" ") });
|
|
666
|
+
case "resolve":
|
|
667
|
+
if (!positional[0]) { console.log("Usage: maestro-flow resolve <command-name>"); return; }
|
|
668
|
+
return cmdResolve({ name: positional[0] });
|
|
669
|
+
case "status":
|
|
670
|
+
return cmdStatus({ sessionId: positional[0] });
|
|
671
|
+
case "sessions":
|
|
672
|
+
return cmdSessions({ all: flags.all });
|
|
673
|
+
case "step":
|
|
674
|
+
if (positional.length < 3) { console.log("Usage: maestro-flow step <session-id> <index> <status>"); return; }
|
|
675
|
+
return cmdStep({ sessionId: positional[0], index: positional[1], status: positional[2] });
|
|
676
|
+
case "next":
|
|
677
|
+
return cmdNext({ sessionId: positional[0] });
|
|
678
|
+
case "done":
|
|
679
|
+
return cmdDone({ sessionId: positional[0] });
|
|
680
|
+
case "reset":
|
|
681
|
+
if (!positional[0]) { console.log("Usage: maestro-flow reset <session-id>"); return; }
|
|
682
|
+
return cmdReset({ sessionId: positional[0] });
|
|
683
|
+
case "install":
|
|
684
|
+
return cmdInstall({ project: flags.project || positional[0], variant: flags.variant });
|
|
685
|
+
case "uninstall":
|
|
686
|
+
return cmdUninstall({ project: flags.project || positional[0], variant: flags.variant });
|
|
687
|
+
case "help":
|
|
688
|
+
case "--help":
|
|
689
|
+
case "-h":
|
|
690
|
+
return printHelp();
|
|
691
|
+
default:
|
|
692
|
+
console.log(`Unknown command: ${cmd}`);
|
|
693
|
+
printHelp();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function printHelp() {
|
|
698
|
+
console.log(`
|
|
699
|
+
Maestro Flow CLI - command discovery, chain management, session tracking
|
|
700
|
+
|
|
701
|
+
Usage: maestro-flow <command> [options]
|
|
702
|
+
|
|
703
|
+
Commands:
|
|
704
|
+
list [--category <cat>] List available commands
|
|
705
|
+
show <command-name> Display command details
|
|
706
|
+
chains [--category <cat>] List chain templates
|
|
707
|
+
chain <template-name> Show chain details
|
|
708
|
+
suggest <intent-text> Suggest chain for intent
|
|
709
|
+
resolve <command-name> Resolve command name to file path
|
|
710
|
+
status [session-id] Show session status
|
|
711
|
+
sessions [--all] List sessions
|
|
712
|
+
next [session-id] Load next pending step
|
|
713
|
+
done [session-id] Complete current step
|
|
714
|
+
step <session-id> <index> <status> Update step status
|
|
715
|
+
reset <session-id> Reset failed session
|
|
716
|
+
install [--variant codex|claude|all] Install skill (default: all)
|
|
717
|
+
uninstall [--variant codex|claude|all] Remove skill
|
|
718
|
+
|
|
719
|
+
Options:
|
|
720
|
+
--variant, -v <name> Select variant: codex, claude, all (default: all)
|
|
721
|
+
codex -> .codex/skills/maestro-flow/
|
|
722
|
+
claude -> .claude/skills/maestro-flow/
|
|
723
|
+
all -> both
|
|
724
|
+
--project, -p <dir> Install to project dir (default: global ~/.claude|.codex)
|
|
725
|
+
--category, -c <cat> Filter by category (list, chains)
|
|
726
|
+
--all, -a Show all sessions (sessions)
|
|
727
|
+
`);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
parseArgs();
|