@doquflow/cli 1.3.0 → 1.4.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/dist/commands/ui.js +12 -0
- package/dist/commands/watch-stop.js +25 -0
- package/dist/commands/watch.js +73 -26
- package/package.json +2 -2
- package/ui-dist/assets/index-B5BRczn1.js +44 -0
- package/ui-dist/assets/index-BQawiJiJ.js +44 -0
- package/ui-dist/assets/index-C67aw_2M.css +1 -0
- package/ui-dist/assets/index-C78tFlFj.css +1 -0
- package/ui-dist/assets/index-C98_cjC3.js +44 -0
- package/ui-dist/assets/index-ChWtqBFK.js +44 -0
- package/ui-dist/assets/index-CvIjJv9o.js +44 -0
- package/ui-dist/assets/index-Da40DmTZ.js +44 -0
- package/ui-dist/assets/index-T8ZPx9-_.css +1 -0
- package/ui-dist/index.html +2 -2
package/dist/commands/ui.js
CHANGED
|
@@ -202,6 +202,7 @@ async function run(opts = {}) {
|
|
|
202
202
|
const lintWikiTool = loadTool('lint-wiki', 'lintWiki');
|
|
203
203
|
const queryWikiTool = loadTool('query-wiki', 'queryWiki');
|
|
204
204
|
const wikiSearchTool = loadTool('wiki-search', 'wikiSearch');
|
|
205
|
+
const buildGraphTool = loadTool('build-graph', 'buildGraph');
|
|
205
206
|
// ── 3. Express app ──────────────────────────────────────────────────────
|
|
206
207
|
const app = (0, express_1.default)();
|
|
207
208
|
app.use((0, cors_1.default)());
|
|
@@ -296,6 +297,17 @@ async function run(opts = {}) {
|
|
|
296
297
|
}
|
|
297
298
|
return res.status(404).json({ error: `Page not found: ${pageId}` });
|
|
298
299
|
});
|
|
300
|
+
app.get('/api/graph', async (req, res) => {
|
|
301
|
+
const projectPath = req.query.path;
|
|
302
|
+
if (!projectPath)
|
|
303
|
+
return res.status(400).json({ error: 'path required' });
|
|
304
|
+
try {
|
|
305
|
+
return res.json(await buildGraphTool({ project_path: projectPath }));
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
return res.status(500).json({ error: e.message });
|
|
309
|
+
}
|
|
310
|
+
});
|
|
299
311
|
app.get('/api/health', async (req, res) => {
|
|
300
312
|
const projectPath = req.query.path;
|
|
301
313
|
if (!projectPath)
|
|
@@ -137,6 +137,31 @@ async function runStatus(projectPath) {
|
|
|
137
137
|
console.log(` Uptime: ${formatUptime(data.started_at)}`);
|
|
138
138
|
console.log(` Started: ${new Date(data.started_at).toLocaleString()}`);
|
|
139
139
|
console.log(` Bridge: ${bridgeLabel}`);
|
|
140
|
+
// Active bridge — only shown when it differs from startup bridge (failover occurred)
|
|
141
|
+
const activeBridge = data.active_bridge ?? data.bridge;
|
|
142
|
+
if (activeBridge !== data.bridge) {
|
|
143
|
+
const activeBridgeLabel = activeBridge === "none" ? c.dim("sources-only (no AI)") :
|
|
144
|
+
activeBridge === "copilot" ? c.green("copilot — direct MCP ⚡") :
|
|
145
|
+
activeBridge === "claude" ? c.green("claude — direct MCP ⚡") :
|
|
146
|
+
activeBridge === "codex" ? c.yellow("codex — doc-gen mode") :
|
|
147
|
+
activeBridge === "api" ? c.yellow("api — doc-gen mode") : activeBridge;
|
|
148
|
+
console.log(` Active bridge: ${activeBridgeLabel} ${c.yellow("(failed over)")}`);
|
|
149
|
+
}
|
|
150
|
+
// Failover stats
|
|
151
|
+
const failover = data.failover ?? { count: 0, last_at: null, from: null, to: null, reason: null };
|
|
152
|
+
if (failover.count > 0) {
|
|
153
|
+
const lastAt = failover.last_at
|
|
154
|
+
? new Date(failover.last_at).toLocaleTimeString()
|
|
155
|
+
: "unknown";
|
|
156
|
+
console.log(` Failovers: ${c.yellow(String(failover.count))} total`);
|
|
157
|
+
console.log(` Last failover: ${lastAt} — ${failover.from} → ${failover.to}`);
|
|
158
|
+
if (failover.reason) {
|
|
159
|
+
console.log(` Reason: ${c.dim(failover.reason)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log(` Failovers: ${c.dim("none")}`);
|
|
164
|
+
}
|
|
140
165
|
console.log(` Project: ${data.project_path}`);
|
|
141
166
|
if (data.options.lintIntervalHours) {
|
|
142
167
|
console.log(` Lint: every ${data.options.lintIntervalHours}h`);
|
package/dist/commands/watch.js
CHANGED
|
@@ -39,6 +39,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
39
39
|
};
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
exports.detectBridge = detectBridge;
|
|
42
|
+
exports.getNextBridge = getNextBridge;
|
|
43
|
+
exports.recordFailover = recordFailover;
|
|
42
44
|
exports.getPidFilePath = getPidFilePath;
|
|
43
45
|
exports.writePidFile = writePidFile;
|
|
44
46
|
exports.removePidFile = removePidFile;
|
|
@@ -282,7 +284,11 @@ function buildDocGenPrompt(projectPath, changedFiles) {
|
|
|
282
284
|
].join("\n");
|
|
283
285
|
}
|
|
284
286
|
// ─── Core sync dispatcher ─────────────────────────────────────────────────────
|
|
285
|
-
async function syncWithAI(projectPath, changedFiles, bridge) {
|
|
287
|
+
async function syncWithAI(projectPath, changedFiles, bridge, depth = 0) {
|
|
288
|
+
if (bridge === "none" || depth >= 4) {
|
|
289
|
+
await directIngestAll(projectPath);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
286
292
|
const bridgeLabel = bridge === "copilot" ? "Copilot" : bridge === "claude" ? "Claude" : bridge === "codex" ? "Codex" : "API";
|
|
287
293
|
log("🤖", `${changedFiles.length} file(s) changed — asking ${c.cyan(bridgeLabel)} to update wiki...`);
|
|
288
294
|
// Copilot and Claude: DIRECT MCP tool calling
|
|
@@ -294,8 +300,10 @@ async function syncWithAI(projectPath, changedFiles, bridge) {
|
|
|
294
300
|
console.log(c.dim(` ${result.replace(/\n/g, "\n ")}`));
|
|
295
301
|
}
|
|
296
302
|
else {
|
|
297
|
-
|
|
298
|
-
await
|
|
303
|
+
const next = getNextBridge(bridge);
|
|
304
|
+
await recordFailover(projectPath, bridge, next, "no output");
|
|
305
|
+
log("⚠️ ", c.yellow(`Copilot failed — falling over to ${next}`));
|
|
306
|
+
await syncWithAI(projectPath, changedFiles, next, depth + 1);
|
|
299
307
|
}
|
|
300
308
|
return;
|
|
301
309
|
}
|
|
@@ -307,8 +315,10 @@ async function syncWithAI(projectPath, changedFiles, bridge) {
|
|
|
307
315
|
console.log(c.dim(` ${result.replace(/\n/g, "\n ")}`));
|
|
308
316
|
}
|
|
309
317
|
else {
|
|
310
|
-
|
|
311
|
-
await
|
|
318
|
+
const next = getNextBridge(bridge);
|
|
319
|
+
await recordFailover(projectPath, bridge, next, "no output");
|
|
320
|
+
log("⚠️ ", c.yellow(`Claude failed — falling over to ${next}`));
|
|
321
|
+
await syncWithAI(projectPath, changedFiles, next, depth + 1);
|
|
312
322
|
}
|
|
313
323
|
return;
|
|
314
324
|
}
|
|
@@ -322,8 +332,10 @@ async function syncWithAI(projectPath, changedFiles, bridge) {
|
|
|
322
332
|
docContent = await callAnthropicAPI(docPrompt);
|
|
323
333
|
}
|
|
324
334
|
if (!docContent) {
|
|
325
|
-
|
|
326
|
-
await
|
|
335
|
+
const next = getNextBridge(bridge);
|
|
336
|
+
await recordFailover(projectPath, bridge, next, "no output");
|
|
337
|
+
log("⚠️ ", c.yellow(`${bridgeLabel} failed — falling over to ${next}`));
|
|
338
|
+
await syncWithAI(projectPath, changedFiles, next, depth + 1);
|
|
327
339
|
return;
|
|
328
340
|
}
|
|
329
341
|
// Save AI-generated doc to sources/
|
|
@@ -335,13 +347,23 @@ async function syncWithAI(projectPath, changedFiles, bridge) {
|
|
|
335
347
|
log("💾", `AI doc saved → ${c.cyan(autoFilename)}`);
|
|
336
348
|
await directIngest(projectPath, autoFilename);
|
|
337
349
|
}
|
|
338
|
-
async function scheduledLintWithAI(projectPath, bridge) {
|
|
350
|
+
async function scheduledLintWithAI(projectPath, bridge, depth = 0) {
|
|
351
|
+
if (bridge === "none" || depth >= 4) {
|
|
352
|
+
// Lint is best-effort — silent return at terminal level (no directIngestAll)
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
339
355
|
if (bridge === "copilot") {
|
|
340
356
|
log("🔍", `Running scheduled lint via ${c.cyan("Copilot")} (direct MCP call)...`);
|
|
341
357
|
const result = runCopilotCLI(buildCopilotLintPrompt(projectPath));
|
|
342
358
|
if (result) {
|
|
343
359
|
console.log(c.dim(` ${result.replace(/\n/g, "\n ")}`));
|
|
344
360
|
}
|
|
361
|
+
else {
|
|
362
|
+
const next = getNextBridge(bridge);
|
|
363
|
+
await recordFailover(projectPath, bridge, next, "no output during lint");
|
|
364
|
+
log("⚠️ ", c.yellow(`Copilot lint failed — falling over to ${next}`));
|
|
365
|
+
await scheduledLintWithAI(projectPath, next, depth + 1);
|
|
366
|
+
}
|
|
345
367
|
return;
|
|
346
368
|
}
|
|
347
369
|
if (bridge === "claude") {
|
|
@@ -350,10 +372,17 @@ async function scheduledLintWithAI(projectPath, bridge) {
|
|
|
350
372
|
if (result) {
|
|
351
373
|
console.log(c.dim(` ${result.replace(/\n/g, "\n ")}`));
|
|
352
374
|
}
|
|
375
|
+
else {
|
|
376
|
+
const next = getNextBridge(bridge);
|
|
377
|
+
await recordFailover(projectPath, bridge, next, "no output during lint");
|
|
378
|
+
log("⚠️ ", c.yellow(`Claude lint failed — falling over to ${next}`));
|
|
379
|
+
await scheduledLintWithAI(projectPath, next, depth + 1);
|
|
380
|
+
}
|
|
353
381
|
return;
|
|
354
382
|
}
|
|
355
|
-
|
|
356
|
-
|
|
383
|
+
const next = getNextBridge(bridge);
|
|
384
|
+
log("ℹ️ ", c.dim(`${bridge} cannot perform MCP lint — escalating to ${next}`));
|
|
385
|
+
await scheduledLintWithAI(projectPath, next, depth + 1);
|
|
357
386
|
}
|
|
358
387
|
// ─── Direct tool calls (no AI) ───────────────────────────────────────────────
|
|
359
388
|
async function directIngest(projectPath, filename) {
|
|
@@ -378,22 +407,6 @@ async function directIngestAll(projectPath) {
|
|
|
378
407
|
}
|
|
379
408
|
catch { }
|
|
380
409
|
}
|
|
381
|
-
async function directLint(projectPath) {
|
|
382
|
-
const { lintWiki } = loadServerTool("lint-wiki");
|
|
383
|
-
log("🔍", "Running scheduled lint check...");
|
|
384
|
-
const result = await lintWiki({ project_path: projectPath, check_type: "all" });
|
|
385
|
-
const score = result.health_score ?? 0;
|
|
386
|
-
const scoreLabel = score >= 90 ? c.green(`${score}/100`) : score >= 70 ? c.yellow(`${score}/100`) : c.red(`${score}/100`);
|
|
387
|
-
log("📊", `Health score: ${scoreLabel}`);
|
|
388
|
-
if (result.issues_found?.length > 0) {
|
|
389
|
-
const high = result.issues_found.filter((i) => i.severity === "high").length;
|
|
390
|
-
const med = result.issues_found.filter((i) => i.severity === "medium").length;
|
|
391
|
-
log("⚠️ ", `Issues: 🔴 ${high} high 🟡 ${med} medium`);
|
|
392
|
-
for (const rec of result.recommendations?.slice(0, 3) ?? []) {
|
|
393
|
-
console.log(c.dim(` → ${rec}`));
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
410
|
// ─── Debounce helper ─────────────────────────────────────────────────────────
|
|
398
411
|
function debounce(fn, ms) {
|
|
399
412
|
let timer;
|
|
@@ -408,6 +421,32 @@ const DEFAULT_CODE_EXTS = new Set([
|
|
|
408
421
|
".py", ".go", ".rb", ".java", ".cs",
|
|
409
422
|
".php", ".rs", ".kt", ".swift", ".vue",
|
|
410
423
|
]);
|
|
424
|
+
function getNextBridge(current) {
|
|
425
|
+
const chain = ["copilot", "claude", "codex", "api", "none"];
|
|
426
|
+
const idx = chain.indexOf(current);
|
|
427
|
+
return (idx !== -1 && idx + 1 < chain.length) ? chain[idx + 1] : "none";
|
|
428
|
+
}
|
|
429
|
+
async function recordFailover(projectPath, from, to, reason) {
|
|
430
|
+
try {
|
|
431
|
+
const data = await readPidFile(projectPath);
|
|
432
|
+
if (!data)
|
|
433
|
+
return;
|
|
434
|
+
if (!data.failover) {
|
|
435
|
+
data.failover = { count: 0, last_at: null, from: null, to: null, reason: null };
|
|
436
|
+
}
|
|
437
|
+
data.failover.count += 1;
|
|
438
|
+
data.failover.last_at = new Date().toISOString();
|
|
439
|
+
data.failover.from = from;
|
|
440
|
+
data.failover.to = to;
|
|
441
|
+
data.failover.reason = reason.slice(0, 120);
|
|
442
|
+
data.active_bridge = to;
|
|
443
|
+
try {
|
|
444
|
+
await writePidFile(projectPath, data);
|
|
445
|
+
}
|
|
446
|
+
catch { }
|
|
447
|
+
}
|
|
448
|
+
catch { }
|
|
449
|
+
}
|
|
411
450
|
function getPidFilePath(projectPath) {
|
|
412
451
|
return node_path_1.default.join(projectPath, ".docuflow", "watch.pid");
|
|
413
452
|
}
|
|
@@ -501,6 +540,7 @@ async function run(options = {}) {
|
|
|
501
540
|
pid: process.pid,
|
|
502
541
|
started_at: new Date().toISOString(),
|
|
503
542
|
bridge,
|
|
543
|
+
active_bridge: bridge,
|
|
504
544
|
project_path: projectPath,
|
|
505
545
|
options: {
|
|
506
546
|
ai: !!options.ai,
|
|
@@ -510,6 +550,13 @@ async function run(options = {}) {
|
|
|
510
550
|
lintIntervalHours: options.lintIntervalHours ?? 24,
|
|
511
551
|
codeExtensions: options.codeExtensions,
|
|
512
552
|
},
|
|
553
|
+
failover: {
|
|
554
|
+
count: 0,
|
|
555
|
+
last_at: null,
|
|
556
|
+
from: null,
|
|
557
|
+
to: null,
|
|
558
|
+
reason: null,
|
|
559
|
+
},
|
|
513
560
|
});
|
|
514
561
|
log("💾", `PID ${process.pid} written to ${c.dim(".docuflow/watch.pid")}`);
|
|
515
562
|
// ── Watch 1: .docuflow/sources/ ─────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doquflow/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "CLI for setting up Docuflow in your project",
|
|
5
5
|
"author": "Docuflow <hello@doquflows.dev>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"build": "tsc && node -e \"const fs=require('fs'),p=require('path'),src=p.join(process.cwd(),'../ui/dist'),dst=p.join(process.cwd(),'ui-dist');if(!fs.existsSync(src)){console.log('Warning: packages/ui/dist not found — run npm run build:ui first');process.exit(0)}fs.mkdirSync(dst,{recursive:true});fs.cpSync(src,dst,{recursive:true,force:true});console.log(' ✓ ui-dist synced from packages/ui/dist ('+(fs.readdirSync(dst).length)+' files at root)')\""
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@doquflow/server": "1.
|
|
34
|
+
"@doquflow/server": "1.4.0",
|
|
35
35
|
"cors": "^2.8.5",
|
|
36
36
|
"express": "^4.19.2"
|
|
37
37
|
},
|