jinzd-ai-cli 0.4.12 → 0.4.13
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/{chunk-JPCRXQ2E.js → chunk-52BIUGQ2.js} +12 -3
- package/dist/{chunk-Z5IRVRGL.js → chunk-DP3M26PP.js} +1 -1
- package/dist/{chunk-3HJSWTKO.js → chunk-TLOCRYJC.js} +1 -1
- package/dist/{chunk-A3S7PUMM.js → chunk-TXCLZ72H.js} +23 -4
- package/dist/{hub-J5ZOVM4Q.js → hub-LURPCJ4Z.js} +1 -1
- package/dist/index.js +6 -6
- package/dist/{run-tests-EUOAU3RZ.js → run-tests-PVVT37LK.js} +1 -1
- package/dist/{run-tests-4WPBBNWG.js → run-tests-UPW6W4QE.js} +1 -1
- package/dist/{server-PWWT4P52.js → server-KPNMFMDU.js} +29 -5
- package/dist/{task-orchestrator-A5X2GUSH.js → task-orchestrator-XXOS7WHR.js} +33 -7
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
EnvLoader,
|
|
4
4
|
schemaToJsonSchema
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-TXCLZ72H.js";
|
|
6
6
|
import {
|
|
7
7
|
APP_NAME,
|
|
8
8
|
CONFIG_DIR_NAME,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
MCP_TOOL_PREFIX,
|
|
16
16
|
PLUGINS_DIR_NAME,
|
|
17
17
|
VERSION
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-DP3M26PP.js";
|
|
19
19
|
|
|
20
20
|
// src/config/config-manager.ts
|
|
21
21
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
@@ -277,7 +277,8 @@ ${err}`
|
|
|
277
277
|
if (rawValue === "true") value = true;
|
|
278
278
|
else if (rawValue === "false") value = false;
|
|
279
279
|
else if (rawValue !== "" && !isNaN(Number(rawValue))) value = Number(rawValue);
|
|
280
|
-
|
|
280
|
+
const draft = JSON.parse(JSON.stringify(this.config));
|
|
281
|
+
let current = draft;
|
|
281
282
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
282
283
|
const key = keys[i];
|
|
283
284
|
if (current[key] == null || typeof current[key] !== "object") {
|
|
@@ -286,6 +287,12 @@ ${err}`
|
|
|
286
287
|
current = current[key];
|
|
287
288
|
}
|
|
288
289
|
current[keys[keys.length - 1]] = value;
|
|
290
|
+
const result = ConfigSchema.safeParse(draft);
|
|
291
|
+
if (!result.success) {
|
|
292
|
+
const firstErr = result.error.errors[0];
|
|
293
|
+
throw new ConfigError(`Invalid config value for "${path}": ${firstErr?.message ?? "validation failed"}`);
|
|
294
|
+
}
|
|
295
|
+
this.config = result.data;
|
|
289
296
|
this.save();
|
|
290
297
|
}
|
|
291
298
|
/** 获取完整配置对象的格式化 JSON 字符串(用于 /config show 等展示) */
|
|
@@ -2535,6 +2542,7 @@ var McpClient = class {
|
|
|
2535
2542
|
config;
|
|
2536
2543
|
process = null;
|
|
2537
2544
|
nextId = 1;
|
|
2545
|
+
// M8: wraps at MAX_SAFE_INTEGER via getNextId()
|
|
2538
2546
|
connected = false;
|
|
2539
2547
|
serverInfo = null;
|
|
2540
2548
|
/** stderr 收集(最多保留最后 2KB,用于错误报告) */
|
|
@@ -2667,6 +2675,7 @@ var McpClient = class {
|
|
|
2667
2675
|
return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
|
|
2668
2676
|
}
|
|
2669
2677
|
const id = this.nextId++;
|
|
2678
|
+
if (this.nextId > Number.MAX_SAFE_INTEGER - 1) this.nextId = 1;
|
|
2670
2679
|
const request = {
|
|
2671
2680
|
jsonrpc: "2.0",
|
|
2672
2681
|
id,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
7
7
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
8
8
|
runTestsTool
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-DP3M26PP.js";
|
|
10
10
|
|
|
11
11
|
// src/tools/builtin/bash.ts
|
|
12
12
|
import { execSync } from "child_process";
|
|
@@ -534,6 +534,13 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
|
|
|
534
534
|
if (BINARY_EXTENSIONS.has(ext)) {
|
|
535
535
|
return `[Binary file: ${filePath} (${ext})]
|
|
536
536
|
This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
|
|
537
|
+
}
|
|
538
|
+
try {
|
|
539
|
+
const fstat = statSync2(normalizedPath);
|
|
540
|
+
if (fstat.size > MAX_FILE_BYTES) {
|
|
541
|
+
return `[File too large: ${filePath} | ${(fstat.size / 1024 / 1024).toFixed(1)} MB exceeds ${MAX_FILE_BYTES / 1024 / 1024} MB limit]`;
|
|
542
|
+
}
|
|
543
|
+
} catch {
|
|
537
544
|
}
|
|
538
545
|
const buf = readFileSync2(normalizedPath);
|
|
539
546
|
if (encoding === "base64") {
|
|
@@ -1003,6 +1010,10 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
|
|
|
1003
1010
|
const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
|
|
1004
1011
|
if (!pattern) throw new Error("pattern is required");
|
|
1005
1012
|
if (!existsSync6(rootPath)) throw new Error(`Path not found: ${rootPath}`);
|
|
1013
|
+
const MAX_PATTERN_LENGTH = 1e3;
|
|
1014
|
+
if (pattern.length > MAX_PATTERN_LENGTH) {
|
|
1015
|
+
throw new Error(`Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
|
|
1016
|
+
}
|
|
1006
1017
|
let regex;
|
|
1007
1018
|
try {
|
|
1008
1019
|
regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
|
|
@@ -1386,11 +1397,11 @@ ${stderr}`);
|
|
|
1386
1397
|
// src/tools/builtin/web-fetch.ts
|
|
1387
1398
|
import { promises as dnsPromises } from "dns";
|
|
1388
1399
|
function htmlToText(html) {
|
|
1400
|
+
let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
1389
1401
|
const HTML_REGEX_LIMIT = 2e5;
|
|
1390
|
-
if (
|
|
1391
|
-
|
|
1402
|
+
if (text.length > HTML_REGEX_LIMIT) {
|
|
1403
|
+
text = text.slice(0, HTML_REGEX_LIMIT);
|
|
1392
1404
|
}
|
|
1393
|
-
let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
1394
1405
|
text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, lvl, content) => {
|
|
1395
1406
|
const prefix = "#".repeat(Number(lvl));
|
|
1396
1407
|
return `
|
|
@@ -1848,6 +1859,14 @@ var EnvLoader = class {
|
|
|
1848
1859
|
if (val) return val;
|
|
1849
1860
|
}
|
|
1850
1861
|
const dynamicEnvVar = `AICLI_API_KEY_${providerId.toUpperCase().replace(/-/g, "_")}`;
|
|
1862
|
+
if (fixedEnvVar && fixedEnvVar !== dynamicEnvVar) {
|
|
1863
|
+
const fixedVal = process.env[fixedEnvVar];
|
|
1864
|
+
const dynVal = process.env[dynamicEnvVar];
|
|
1865
|
+
if (fixedVal && dynVal && fixedVal !== dynVal) {
|
|
1866
|
+
process.stderr.write(`[warn] env var collision: ${fixedEnvVar} and ${dynamicEnvVar} have different values for provider "${providerId}". Using ${fixedEnvVar}.
|
|
1867
|
+
`);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1851
1870
|
return process.env[dynamicEnvVar] || void 0;
|
|
1852
1871
|
}
|
|
1853
1872
|
static getDefaultProvider() {
|
|
@@ -381,7 +381,7 @@ ${content}`);
|
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
383
|
async function runTaskMode(config, providers, configManager, topic) {
|
|
384
|
-
const { TaskOrchestrator } = await import("./task-orchestrator-
|
|
384
|
+
const { TaskOrchestrator } = await import("./task-orchestrator-XXOS7WHR.js");
|
|
385
385
|
const orchestrator = new TaskOrchestrator(config, providers, configManager);
|
|
386
386
|
let interrupted = false;
|
|
387
387
|
const onSigint = () => {
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
saveDevState,
|
|
24
24
|
sessionHasMeaningfulContent,
|
|
25
25
|
setupProxy
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-52BIUGQ2.js";
|
|
27
27
|
import {
|
|
28
28
|
ToolRegistry,
|
|
29
29
|
askUserContext,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
theme,
|
|
39
39
|
truncateOutput,
|
|
40
40
|
undoStack
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-TXCLZ72H.js";
|
|
42
42
|
import {
|
|
43
43
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
44
44
|
AUTHOR,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
REPO_URL,
|
|
59
59
|
SKILLS_DIR_NAME,
|
|
60
60
|
VERSION
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-DP3M26PP.js";
|
|
62
62
|
|
|
63
63
|
// src/index.ts
|
|
64
64
|
import { program } from "commander";
|
|
@@ -1914,7 +1914,7 @@ ${hint}` : "")
|
|
|
1914
1914
|
description: "Run project tests and show structured report",
|
|
1915
1915
|
usage: "/test [command|filter]",
|
|
1916
1916
|
async execute(args, _ctx) {
|
|
1917
|
-
const { executeTests } = await import("./run-tests-
|
|
1917
|
+
const { executeTests } = await import("./run-tests-PVVT37LK.js");
|
|
1918
1918
|
const argStr = args.join(" ").trim();
|
|
1919
1919
|
let testArgs = {};
|
|
1920
1920
|
if (argStr) {
|
|
@@ -5528,7 +5528,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5528
5528
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5529
5529
|
process.exit(1);
|
|
5530
5530
|
}
|
|
5531
|
-
const { startWebServer } = await import("./server-
|
|
5531
|
+
const { startWebServer } = await import("./server-KPNMFMDU.js");
|
|
5532
5532
|
await startWebServer({ port, host: options.host });
|
|
5533
5533
|
});
|
|
5534
5534
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -5761,7 +5761,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5761
5761
|
}),
|
|
5762
5762
|
config.get("customProviders")
|
|
5763
5763
|
);
|
|
5764
|
-
const { startHub } = await import("./hub-
|
|
5764
|
+
const { startHub } = await import("./hub-LURPCJ4Z.js");
|
|
5765
5765
|
await startHub(
|
|
5766
5766
|
{
|
|
5767
5767
|
topic: topic ?? "",
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
renderDiff,
|
|
19
19
|
runHook,
|
|
20
20
|
setupProxy
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-52BIUGQ2.js";
|
|
22
22
|
import {
|
|
23
23
|
AuthManager
|
|
24
24
|
} from "./chunk-BYNY5JPB.js";
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
spawnAgentContext,
|
|
33
33
|
truncateOutput,
|
|
34
34
|
undoStack
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-TXCLZ72H.js";
|
|
36
36
|
import {
|
|
37
37
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
38
38
|
CONTEXT_FILE_CANDIDATES,
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
PLAN_MODE_SYSTEM_ADDON,
|
|
45
45
|
SKILLS_DIR_NAME,
|
|
46
46
|
VERSION
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-DP3M26PP.js";
|
|
48
48
|
|
|
49
49
|
// src/web/server.ts
|
|
50
50
|
import express from "express";
|
|
@@ -57,7 +57,7 @@ import { networkInterfaces } from "os";
|
|
|
57
57
|
// src/web/tool-executor-web.ts
|
|
58
58
|
import { randomUUID } from "crypto";
|
|
59
59
|
import { existsSync, readFileSync } from "fs";
|
|
60
|
-
var ToolExecutorWeb = class {
|
|
60
|
+
var ToolExecutorWeb = class _ToolExecutorWeb {
|
|
61
61
|
constructor(registry, ws) {
|
|
62
62
|
this.registry = registry;
|
|
63
63
|
this.ws = ws;
|
|
@@ -71,6 +71,10 @@ var ToolExecutorWeb = class {
|
|
|
71
71
|
pendingConfirms = /* @__PURE__ */ new Map();
|
|
72
72
|
/** Pending batch confirm promises */
|
|
73
73
|
pendingBatchConfirms = /* @__PURE__ */ new Map();
|
|
74
|
+
/** M7 fix: timers for confirm cleanup if client crashes */
|
|
75
|
+
pendingTimers = /* @__PURE__ */ new Map();
|
|
76
|
+
static CONFIRM_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
77
|
+
// 5 minutes
|
|
74
78
|
/** Publicly readable by SessionHandler to check if confirm is active */
|
|
75
79
|
confirming = false;
|
|
76
80
|
/** Session-level auto-approve toggle (/yolo command) */
|
|
@@ -86,10 +90,19 @@ var ToolExecutorWeb = class {
|
|
|
86
90
|
if (opts.permissionRules) this.permissionRules = opts.permissionRules;
|
|
87
91
|
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
88
92
|
}
|
|
93
|
+
/** Clear M7 timeout timer for a requestId */
|
|
94
|
+
clearPendingTimer(requestId) {
|
|
95
|
+
const timer = this.pendingTimers.get(requestId);
|
|
96
|
+
if (timer) {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
this.pendingTimers.delete(requestId);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
89
101
|
/** Resolve a pending confirm from client response */
|
|
90
102
|
resolveConfirm(requestId, approved) {
|
|
91
103
|
const resolve3 = this.pendingConfirms.get(requestId);
|
|
92
104
|
if (resolve3) {
|
|
105
|
+
this.clearPendingTimer(requestId);
|
|
93
106
|
this.pendingConfirms.delete(requestId);
|
|
94
107
|
this.confirming = false;
|
|
95
108
|
resolve3(approved);
|
|
@@ -99,6 +112,7 @@ var ToolExecutorWeb = class {
|
|
|
99
112
|
resolveBatchConfirm(requestId, decision) {
|
|
100
113
|
const resolve3 = this.pendingBatchConfirms.get(requestId);
|
|
101
114
|
if (resolve3) {
|
|
115
|
+
this.clearPendingTimer(requestId);
|
|
102
116
|
this.pendingBatchConfirms.delete(requestId);
|
|
103
117
|
this.confirming = false;
|
|
104
118
|
if (decision === "all" || decision === "none") {
|
|
@@ -190,6 +204,11 @@ var ToolExecutorWeb = class {
|
|
|
190
204
|
this.send(msg);
|
|
191
205
|
return new Promise((resolve3) => {
|
|
192
206
|
this.pendingConfirms.set(requestId, resolve3);
|
|
207
|
+
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
208
|
+
if (this.pendingConfirms.has(requestId)) {
|
|
209
|
+
this.resolveConfirm(requestId, false);
|
|
210
|
+
}
|
|
211
|
+
}, _ToolExecutorWeb.CONFIRM_TIMEOUT_MS));
|
|
193
212
|
});
|
|
194
213
|
}
|
|
195
214
|
/** WebSocket-based batch confirm */
|
|
@@ -210,6 +229,11 @@ var ToolExecutorWeb = class {
|
|
|
210
229
|
this.send(msg);
|
|
211
230
|
return new Promise((resolve3) => {
|
|
212
231
|
this.pendingBatchConfirms.set(requestId, resolve3);
|
|
232
|
+
this.pendingTimers.set(requestId, setTimeout(() => {
|
|
233
|
+
if (this.pendingBatchConfirms.has(requestId)) {
|
|
234
|
+
this.resolveBatchConfirm(requestId, "none");
|
|
235
|
+
}
|
|
236
|
+
}, _ToolExecutorWeb.CONFIRM_TIMEOUT_MS));
|
|
213
237
|
});
|
|
214
238
|
}
|
|
215
239
|
async execute(call) {
|
|
@@ -1448,7 +1472,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
|
|
|
1448
1472
|
case "test": {
|
|
1449
1473
|
this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
|
|
1450
1474
|
try {
|
|
1451
|
-
const { executeTests } = await import("./run-tests-
|
|
1475
|
+
const { executeTests } = await import("./run-tests-PVVT37LK.js");
|
|
1452
1476
|
const argStr = args.join(" ").trim();
|
|
1453
1477
|
let testArgs = {};
|
|
1454
1478
|
if (argStr) {
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
getDangerLevel,
|
|
5
5
|
googleSearchContext,
|
|
6
6
|
truncateOutput
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TXCLZ72H.js";
|
|
8
8
|
import {
|
|
9
9
|
SUBAGENT_ALLOWED_TOOLS
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-DP3M26PP.js";
|
|
11
11
|
|
|
12
12
|
// src/hub/task-orchestrator.ts
|
|
13
13
|
import { createInterface } from "readline";
|
|
14
|
-
import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
14
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "fs";
|
|
15
15
|
import { join } from "path";
|
|
16
16
|
import chalk from "chalk";
|
|
17
17
|
|
|
@@ -266,7 +266,9 @@ var TaskOrchestrator = class {
|
|
|
266
266
|
}
|
|
267
267
|
saveState(plan) {
|
|
268
268
|
try {
|
|
269
|
-
|
|
269
|
+
const tmpPath = this.stateFilePath + ".tmp";
|
|
270
|
+
writeFileSync(tmpPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
271
|
+
renameSync(tmpPath, this.stateFilePath);
|
|
270
272
|
} catch {
|
|
271
273
|
}
|
|
272
274
|
}
|
|
@@ -357,13 +359,29 @@ Example:
|
|
|
357
359
|
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
358
360
|
throw new Error("Empty task list");
|
|
359
361
|
}
|
|
362
|
+
const validRoleIds = new Set(roles.map((r) => r.id));
|
|
363
|
+
for (let i = 0; i < parsed.length; i++) {
|
|
364
|
+
const t = parsed[i];
|
|
365
|
+
if (typeof t !== "object" || t === null) {
|
|
366
|
+
throw new Error(`Task #${i + 1}: not an object`);
|
|
367
|
+
}
|
|
368
|
+
if (typeof t.description !== "string" || !t.description.trim()) {
|
|
369
|
+
throw new Error(`Task #${i + 1}: missing or empty "description"`);
|
|
370
|
+
}
|
|
371
|
+
if (typeof t.assignee !== "string" || !t.assignee.trim()) {
|
|
372
|
+
throw new Error(`Task #${i + 1}: missing or empty "assignee"`);
|
|
373
|
+
}
|
|
374
|
+
if (t.dependencies != null && !Array.isArray(t.dependencies)) {
|
|
375
|
+
throw new Error(`Task #${i + 1}: "dependencies" must be an array`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
360
378
|
const roleMap = new Map(roles.map((r) => [r.id, r.name]));
|
|
361
379
|
const tasks = parsed.map((t, i) => ({
|
|
362
|
-
id: t.id
|
|
380
|
+
id: typeof t.id === "number" ? t.id : i + 1,
|
|
363
381
|
description: t.description,
|
|
364
382
|
assignee: t.assignee,
|
|
365
383
|
assigneeName: roleMap.get(t.assignee) ?? t.assignee,
|
|
366
|
-
dependencies: t.dependencies
|
|
384
|
+
dependencies: Array.isArray(t.dependencies) ? t.dependencies : [],
|
|
367
385
|
status: "pending"
|
|
368
386
|
}));
|
|
369
387
|
return { goal, tasks };
|
|
@@ -428,7 +446,15 @@ Example:
|
|
|
428
446
|
if (!nextTask) {
|
|
429
447
|
const pending = plan.tasks.filter((t) => t.status === "pending");
|
|
430
448
|
if (pending.length === 0) break;
|
|
431
|
-
|
|
449
|
+
const failedIds = new Set(plan.tasks.filter((t) => t.status === "failed").map((t) => t.id));
|
|
450
|
+
const hasCycle = pending.every(
|
|
451
|
+
(t) => t.dependencies.some((d) => !completedIds.has(d) && !failedIds.has(d) && pending.some((p) => p.id === d))
|
|
452
|
+
);
|
|
453
|
+
if (hasCycle) {
|
|
454
|
+
console.log(chalk.red(` \u2717 Circular dependency detected among ${pending.length} task(s):`));
|
|
455
|
+
} else {
|
|
456
|
+
console.log(chalk.red(` \u2717 Cannot proceed: ${pending.length} task(s) have unmet dependencies.`));
|
|
457
|
+
}
|
|
432
458
|
for (const t of pending) {
|
|
433
459
|
const unmet = t.dependencies.filter((d) => !completedIds.has(d));
|
|
434
460
|
console.log(chalk.dim(` Task #${t.id}: waiting on [${unmet.join(", ")}]`));
|