@wrongstack/cli 0.7.5 → 0.7.6
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/index.js +123 -32
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import * as os6 from 'os';
|
|
|
9
9
|
import os6__default from 'os';
|
|
10
10
|
import * as crypto2 from 'crypto';
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
|
-
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
12
|
+
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, isSecretField, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
13
13
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
14
14
|
import { MCPRegistry } from '@wrongstack/mcp';
|
|
15
15
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
@@ -64,8 +64,10 @@ __export(sdd_exports, {
|
|
|
64
64
|
getActiveSDDPhase: () => getActiveSDDPhase,
|
|
65
65
|
getCurrentExecutingContext: () => getCurrentExecutingContext,
|
|
66
66
|
getCurrentTask: () => getCurrentTask,
|
|
67
|
+
getTaskGraphId: () => getTaskGraphId,
|
|
67
68
|
getTaskListText: () => getTaskListText,
|
|
68
69
|
getTaskProgress: () => getTaskProgress,
|
|
70
|
+
getTaskTracker: () => getTaskTracker,
|
|
69
71
|
markTaskCompleted: () => markTaskCompleted,
|
|
70
72
|
renderTaskListWithProgress: () => renderTaskListWithProgress,
|
|
71
73
|
trySaveImplementationPlan: () => trySaveImplementationPlan,
|
|
@@ -380,10 +382,9 @@ function buildSddCommand(opts) {
|
|
|
380
382
|
name: "sdd",
|
|
381
383
|
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
382
384
|
async run(args) {
|
|
383
|
-
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
const graphsDir = path23.join(projectRoot, ".wrongstack", "task-graphs");
|
|
385
|
+
opts.context;
|
|
386
|
+
const specsDir = opts.paths.projectSpecs;
|
|
387
|
+
const graphsDir = opts.paths.projectTaskGraphs;
|
|
387
388
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
388
389
|
new TaskGraphStore({ baseDir: graphsDir });
|
|
389
390
|
const versioning = sddState.getVersioning();
|
|
@@ -399,10 +400,10 @@ function buildSddCommand(opts) {
|
|
|
399
400
|
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
400
401
|
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
401
402
|
if (!sessionState.getBuilder() && !forceFlag) {
|
|
402
|
-
const sessionPath =
|
|
403
|
+
const sessionPath = opts.paths.projectSddSession;
|
|
403
404
|
try {
|
|
404
405
|
await fsp2.access(sessionPath);
|
|
405
|
-
const projectContext2 = await gatherProjectContext(projectRoot);
|
|
406
|
+
const projectContext2 = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
|
|
406
407
|
const tempBuilder = new AISpecBuilder({
|
|
407
408
|
store: specStore,
|
|
408
409
|
projectContext: projectContext2,
|
|
@@ -428,13 +429,13 @@ function buildSddCommand(opts) {
|
|
|
428
429
|
}
|
|
429
430
|
}
|
|
430
431
|
sddState.clearTaskState();
|
|
431
|
-
const projectContext = await gatherProjectContext(projectRoot);
|
|
432
|
+
const projectContext = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
|
|
432
433
|
sddState.setBuilder(new AISpecBuilder({
|
|
433
434
|
store: specStore,
|
|
434
435
|
projectContext,
|
|
435
436
|
minQuestions: 2,
|
|
436
437
|
maxQuestions: 10,
|
|
437
|
-
sessionPath:
|
|
438
|
+
sessionPath: opts.paths.projectSddSession
|
|
438
439
|
}));
|
|
439
440
|
sddState.setSessionStartTime(Date.now());
|
|
440
441
|
sddState.setPhaseStartTime(Date.now());
|
|
@@ -547,8 +548,16 @@ Start executing the tasks one by one.`
|
|
|
547
548
|
};
|
|
548
549
|
}
|
|
549
550
|
// ── Task Execution ─────────────────────────────────────────────────
|
|
550
|
-
case "
|
|
551
|
-
case "
|
|
551
|
+
case "run":
|
|
552
|
+
case "execute": {
|
|
553
|
+
if (opts.onSddParallelRun) {
|
|
554
|
+
const slotsArg = restJoined.trim();
|
|
555
|
+
const slots = slotsArg ? Number.parseInt(slotsArg, 10) : void 0;
|
|
556
|
+
const message = await opts.onSddParallelRun(
|
|
557
|
+
slots && Number.isFinite(slots) ? { parallelSlots: Math.min(16, Math.max(1, slots)) } : {}
|
|
558
|
+
);
|
|
559
|
+
return { message };
|
|
560
|
+
}
|
|
552
561
|
const runBuilder = sddState.getBuilder();
|
|
553
562
|
if (!runBuilder) {
|
|
554
563
|
return {
|
|
@@ -572,6 +581,21 @@ User message:
|
|
|
572
581
|
Start executing the tasks one by one.`
|
|
573
582
|
};
|
|
574
583
|
}
|
|
584
|
+
case "parallel": {
|
|
585
|
+
if (!opts.onSddParallelRun) {
|
|
586
|
+
return { message: "SDD parallel run is not available in this session." };
|
|
587
|
+
}
|
|
588
|
+
const slotsArg = restJoined.trim();
|
|
589
|
+
const slots = slotsArg ? Number.parseInt(slotsArg, 10) : void 0;
|
|
590
|
+
const message = await opts.onSddParallelRun(
|
|
591
|
+
slots && Number.isFinite(slots) ? { parallelSlots: Math.min(16, Math.max(1, slots)) } : {}
|
|
592
|
+
);
|
|
593
|
+
return { message };
|
|
594
|
+
}
|
|
595
|
+
case "stop": {
|
|
596
|
+
opts.onSddParallelStop?.();
|
|
597
|
+
return { message: "SDD parallel run stopped." };
|
|
598
|
+
}
|
|
575
599
|
case "plan":
|
|
576
600
|
case "impl": {
|
|
577
601
|
const planBuilder = sddState.getBuilder();
|
|
@@ -1001,7 +1025,7 @@ Start executing the tasks one by one.`
|
|
|
1001
1025
|
return { message: lines2.join("\n") };
|
|
1002
1026
|
}
|
|
1003
1027
|
try {
|
|
1004
|
-
const graphStore2 = new TaskGraphStore({ baseDir:
|
|
1028
|
+
const graphStore2 = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
1005
1029
|
const stored = await graphStore2.load(graphId);
|
|
1006
1030
|
if (stored) {
|
|
1007
1031
|
return { message: renderTaskGraph(stored, { compact: false }) };
|
|
@@ -1026,7 +1050,7 @@ Start executing the tasks one by one.`
|
|
|
1026
1050
|
return { message: lines.join("\n") };
|
|
1027
1051
|
}
|
|
1028
1052
|
case "cancel": {
|
|
1029
|
-
const sessionPath =
|
|
1053
|
+
const sessionPath = opts.paths.projectSddSession;
|
|
1030
1054
|
let deletedFromDisk = false;
|
|
1031
1055
|
try {
|
|
1032
1056
|
await fsp2.unlink(sessionPath);
|
|
@@ -1034,11 +1058,11 @@ Start executing the tasks one by one.`
|
|
|
1034
1058
|
} catch {
|
|
1035
1059
|
}
|
|
1036
1060
|
try {
|
|
1037
|
-
await fsp2.rm(
|
|
1061
|
+
await fsp2.rm(opts.paths.projectSpecs, { recursive: true, force: true });
|
|
1038
1062
|
} catch {
|
|
1039
1063
|
}
|
|
1040
1064
|
try {
|
|
1041
|
-
await fsp2.rm(
|
|
1065
|
+
await fsp2.rm(opts.paths.projectTaskGraphs, { recursive: true, force: true });
|
|
1042
1066
|
} catch {
|
|
1043
1067
|
}
|
|
1044
1068
|
const cancelBuilder = sddState.getBuilder();
|
|
@@ -1058,8 +1082,8 @@ Start executing the tasks one by one.`
|
|
|
1058
1082
|
if (sddState.getBuilder()) {
|
|
1059
1083
|
return { message: "An SDD session is already active. Use /sdd cancel first." };
|
|
1060
1084
|
}
|
|
1061
|
-
const sessionPath =
|
|
1062
|
-
const projectContext = await gatherProjectContext(projectRoot);
|
|
1085
|
+
const sessionPath = opts.paths.projectSddSession;
|
|
1086
|
+
const projectContext = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
|
|
1063
1087
|
sddState.setBuilder(new AISpecBuilder({
|
|
1064
1088
|
store: specStore,
|
|
1065
1089
|
projectContext,
|
|
@@ -1222,7 +1246,7 @@ ${lines.join("\n")}`
|
|
|
1222
1246
|
return { message: "No task graph found. Generate tasks first." };
|
|
1223
1247
|
}
|
|
1224
1248
|
try {
|
|
1225
|
-
const graphStore2 = new TaskGraphStore({ baseDir:
|
|
1249
|
+
const graphStore2 = new TaskGraphStore({ baseDir: opts.paths.projectTaskGraphs });
|
|
1226
1250
|
const graph = await graphStore2.load(graphId);
|
|
1227
1251
|
if (!graph) {
|
|
1228
1252
|
return { message: "Could not load task graph." };
|
|
@@ -1416,6 +1440,12 @@ async function findSpec(store, idOrTitle) {
|
|
|
1416
1440
|
if (match) return store.load(match.id);
|
|
1417
1441
|
return null;
|
|
1418
1442
|
}
|
|
1443
|
+
function getTaskGraphId() {
|
|
1444
|
+
return sddState.getTaskGraphId();
|
|
1445
|
+
}
|
|
1446
|
+
function getTaskTracker() {
|
|
1447
|
+
return sddState.getTaskTracker();
|
|
1448
|
+
}
|
|
1419
1449
|
var SDD_META_KEY, SDDState, sddState;
|
|
1420
1450
|
var init_sdd = __esm({
|
|
1421
1451
|
"src/slash-commands/sdd.ts"() {
|
|
@@ -1821,7 +1851,21 @@ async function runWebUI(opts) {
|
|
|
1821
1851
|
const client = { ws, sessionId: opts.session.id };
|
|
1822
1852
|
clients.set(ws, client);
|
|
1823
1853
|
console.log("[WebUI] Client connected");
|
|
1854
|
+
let msgCount = 0;
|
|
1855
|
+
let windowResetAt = Date.now() + 6e4;
|
|
1824
1856
|
ws.on("message", async (data) => {
|
|
1857
|
+
const now = Date.now();
|
|
1858
|
+
if (now > windowResetAt) {
|
|
1859
|
+
msgCount = 0;
|
|
1860
|
+
windowResetAt = now + 6e4;
|
|
1861
|
+
}
|
|
1862
|
+
if (++msgCount > 60) {
|
|
1863
|
+
send(ws, {
|
|
1864
|
+
type: "error",
|
|
1865
|
+
payload: { phase: "rate_limit", message: "Too many messages. Please wait." }
|
|
1866
|
+
});
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1825
1869
|
try {
|
|
1826
1870
|
const msg = JSON.parse(data.toString());
|
|
1827
1871
|
await handleMessage(ws, client, msg);
|
|
@@ -2743,7 +2787,7 @@ function maskConfigSecrets(cfg) {
|
|
|
2743
2787
|
if (typeof cfg !== "object" || cfg === null) return {};
|
|
2744
2788
|
const out = {};
|
|
2745
2789
|
for (const [k, v] of Object.entries(cfg)) {
|
|
2746
|
-
if (k
|
|
2790
|
+
if (isSecretField(k)) {
|
|
2747
2791
|
out[k] = "[REDACTED]";
|
|
2748
2792
|
} else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
2749
2793
|
out[k] = maskConfigSecrets(v);
|
|
@@ -2760,7 +2804,7 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
2760
2804
|
const o = JSON.stringify(oldCfg[k]);
|
|
2761
2805
|
const n = JSON.stringify(newCfg[k]);
|
|
2762
2806
|
if (o !== n) {
|
|
2763
|
-
if (k
|
|
2807
|
+
if (isSecretField(k)) {
|
|
2764
2808
|
changes.push(`${k}: [CHANGED]`);
|
|
2765
2809
|
} else if (typeof newCfg[k] !== "object") {
|
|
2766
2810
|
changes.push(`${k}: ${oldCfg[k] ?? "(unset)"} \u2192 ${newCfg[k]}`);
|
|
@@ -4583,7 +4627,7 @@ function buildPlanCommand(opts) {
|
|
|
4583
4627
|
name: "plan",
|
|
4584
4628
|
description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
|
|
4585
4629
|
async run(args) {
|
|
4586
|
-
const planPath = opts.planPath;
|
|
4630
|
+
const planPath = opts.paths?.projectPlan ?? opts.planPath;
|
|
4587
4631
|
if (!planPath) return { message: "Plan storage is not configured for this session." };
|
|
4588
4632
|
const ctx = opts.context;
|
|
4589
4633
|
const sessionId = ctx?.session.id ?? "unknown";
|
|
@@ -5180,7 +5224,7 @@ function buildGoalCommand(opts) {
|
|
|
5180
5224
|
"Stage flow: decide \u2192 execute \u2192 reflect \u2192 sleep | paused | stopped",
|
|
5181
5225
|
"Pausing stops after current iteration completes. Resume continues from next iteration.",
|
|
5182
5226
|
"",
|
|
5183
|
-
"Goals live in
|
|
5227
|
+
"Goals live in ~/.wrongstack/projects/<hash>/goal.json and persist across sessions.",
|
|
5184
5228
|
"A goal is the prerequisite for /autonomy eternal \u2014 the engine consults it on",
|
|
5185
5229
|
"every iteration to decide what to do next."
|
|
5186
5230
|
].join("\n"),
|
|
@@ -5189,7 +5233,7 @@ function buildGoalCommand(opts) {
|
|
|
5189
5233
|
const [verbRaw, ...rest] = trimmed.split(/\s+/);
|
|
5190
5234
|
const verb = (verbRaw ?? "").toLowerCase();
|
|
5191
5235
|
const restJoined = rest.join(" ").trim();
|
|
5192
|
-
const goalPath =
|
|
5236
|
+
const goalPath = opts.paths.projectGoal;
|
|
5193
5237
|
const verbForDispatch = verb && !KNOWN_VERBS.has(verb) ? "set" : verb;
|
|
5194
5238
|
const setText = verbForDispatch === "set" && !KNOWN_VERBS.has(verb) ? trimmed : restJoined;
|
|
5195
5239
|
switch (verbForDispatch) {
|
|
@@ -8445,8 +8489,7 @@ var initCmd = async (_args, deps) => {
|
|
|
8445
8489
|
await fsp2.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
8446
8490
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
8447
8491
|
if (apiKey) config.apiKey = apiKey;
|
|
8448
|
-
const
|
|
8449
|
-
const vault = new DefaultSecretVault$1({ keyFile });
|
|
8492
|
+
const vault = new DefaultSecretVault$1({ keyFile: deps.paths.secretsKey });
|
|
8450
8493
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
8451
8494
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
8452
8495
|
await fsp2.mkdir(path23.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
@@ -8896,14 +8939,12 @@ Cache age: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never fetched"}. Run
|
|
|
8896
8939
|
);
|
|
8897
8940
|
return 0;
|
|
8898
8941
|
};
|
|
8899
|
-
|
|
8900
|
-
// src/subcommands/handlers/helpers.ts
|
|
8901
8942
|
function redactKeys(obj) {
|
|
8902
8943
|
if (!obj || typeof obj !== "object") return obj;
|
|
8903
8944
|
if (Array.isArray(obj)) return obj.map(redactKeys);
|
|
8904
8945
|
const out = {};
|
|
8905
8946
|
for (const [k, v] of Object.entries(obj)) {
|
|
8906
|
-
if (
|
|
8947
|
+
if (isSecretField(k) && typeof v === "string" && v.length > 0)
|
|
8907
8948
|
out[k] = "[REDACTED]";
|
|
8908
8949
|
else out[k] = redactKeys(v);
|
|
8909
8950
|
}
|
|
@@ -11963,7 +12004,7 @@ async function main(argv) {
|
|
|
11963
12004
|
const skillLoader = container.resolve(TOKENS.SkillLoader);
|
|
11964
12005
|
const sessionRef = {};
|
|
11965
12006
|
const autonomyModeRef = { current: "off" };
|
|
11966
|
-
const goalPathForPrompt =
|
|
12007
|
+
const goalPathForPrompt = wpaths.projectGoal;
|
|
11967
12008
|
container.bind(
|
|
11968
12009
|
TOKENS.SystemPromptBuilder,
|
|
11969
12010
|
() => new DefaultSystemPromptBuilder({
|
|
@@ -12269,6 +12310,7 @@ async function main(argv) {
|
|
|
12269
12310
|
const slashCmds = buildBuiltinSlashCommands({
|
|
12270
12311
|
registry: slashRegistry,
|
|
12271
12312
|
toolRegistry,
|
|
12313
|
+
paths: wpaths,
|
|
12272
12314
|
compactor: container.resolve(TOKENS.Compactor),
|
|
12273
12315
|
sessionStore,
|
|
12274
12316
|
skillLoader,
|
|
@@ -12828,13 +12870,62 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
12828
12870
|
onDispatchClassify: makeProviderClassifier(
|
|
12829
12871
|
context.provider,
|
|
12830
12872
|
context.model
|
|
12831
|
-
)
|
|
12873
|
+
),
|
|
12874
|
+
onSddParallelRun: async (opts) => {
|
|
12875
|
+
const { SddParallelRun } = await import('@wrongstack/core');
|
|
12876
|
+
const sdd = await Promise.resolve().then(() => (init_sdd(), sdd_exports));
|
|
12877
|
+
const tracker = sdd.getTaskTracker();
|
|
12878
|
+
const builder = sdd.getActiveBuilder();
|
|
12879
|
+
if (!tracker || !builder) {
|
|
12880
|
+
return "No active SDD session with tasks. Use /sdd new to start one.";
|
|
12881
|
+
}
|
|
12882
|
+
const session2 = builder.getSession();
|
|
12883
|
+
if (session2.phase !== "executing" && session2.phase !== "task_review") {
|
|
12884
|
+
return `Cannot run parallel in phase "${session2.phase}". Use /sdd approve first.`;
|
|
12885
|
+
}
|
|
12886
|
+
const graphId = sdd.getTaskGraphId();
|
|
12887
|
+
const graphStore = new (await import('@wrongstack/core')).TaskGraphStore({
|
|
12888
|
+
baseDir: wpaths.projectTaskGraphs
|
|
12889
|
+
});
|
|
12890
|
+
const graph = graphId ? await graphStore.load(graphId) : null;
|
|
12891
|
+
if (!graph) {
|
|
12892
|
+
return "No task graph found for the current SDD session.";
|
|
12893
|
+
}
|
|
12894
|
+
const subagentFactory = multiAgentHost.makeSubagentFactory(config);
|
|
12895
|
+
const run = new SddParallelRun({
|
|
12896
|
+
tracker,
|
|
12897
|
+
graph,
|
|
12898
|
+
agent,
|
|
12899
|
+
projectRoot,
|
|
12900
|
+
parallelSlots: opts?.parallelSlots,
|
|
12901
|
+
subagentFactory,
|
|
12902
|
+
onProgress: (p) => {
|
|
12903
|
+
renderer.write(` \u2591 wave ${p.wave + 1} \xB7 ${p.completed}/${p.total} tasks \xB7 ${p.percent}% done
|
|
12904
|
+
`);
|
|
12905
|
+
}
|
|
12906
|
+
});
|
|
12907
|
+
globalThis.__sddParallelRun = run;
|
|
12908
|
+
const result = await run.run();
|
|
12909
|
+
delete globalThis.__sddParallelRun;
|
|
12910
|
+
const lines = [
|
|
12911
|
+
`SDD parallel run complete:`,
|
|
12912
|
+
` ${result.totalWaves} waves \xB7 ${result.totalCompleted} done \xB7 ${result.totalFailed} failed`,
|
|
12913
|
+
` ${(result.totalDurationMs / 1e3).toFixed(1)}s total`
|
|
12914
|
+
];
|
|
12915
|
+
if (result.deadlocked) lines.push(color.red(" \u26A0 deadlock \u2014 tasks blocked by failed tasks."));
|
|
12916
|
+
if (result.stopRequested) lines.push(color.yellow(" \u26A1 stopped by user."));
|
|
12917
|
+
return lines.join("\n");
|
|
12918
|
+
},
|
|
12919
|
+
onSddParallelStop: () => {
|
|
12920
|
+
const run = globalThis.__sddParallelRun;
|
|
12921
|
+
run?.stop();
|
|
12922
|
+
}
|
|
12832
12923
|
});
|
|
12833
12924
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
12834
12925
|
const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";
|
|
12835
12926
|
if (eternalFlag.length > 0) {
|
|
12836
|
-
const { saveGoal: saveGoal2, emptyGoal: emptyGoal2, goalFilePath:
|
|
12837
|
-
const goalPath =
|
|
12927
|
+
const { saveGoal: saveGoal2, emptyGoal: emptyGoal2, goalFilePath: goalFilePath3, loadGoal: loadGoal4 } = await import('@wrongstack/core');
|
|
12928
|
+
const goalPath = goalFilePath3(projectRoot);
|
|
12838
12929
|
const prior = await loadGoal4(goalPath);
|
|
12839
12930
|
const next = prior ? { ...prior, goal: eternalFlag, setAt: (/* @__PURE__ */ new Date()).toISOString(), lastActivityAt: (/* @__PURE__ */ new Date()).toISOString() } : emptyGoal2(eternalFlag);
|
|
12840
12931
|
await saveGoal2(goalPath, next);
|