@wrongstack/cli 0.8.4 → 0.8.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 +563 -300
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as path25 from 'path';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import * as
|
|
4
|
+
import * as fsp3 from 'fs/promises';
|
|
5
5
|
import { readdir, readFile } from 'fs/promises';
|
|
6
|
-
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS,
|
|
6
|
+
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
8
|
+
import * as os6 from 'os';
|
|
9
|
+
import os6__default from 'os';
|
|
10
10
|
import * as crypto2 from 'crypto';
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, isSecretField, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
@@ -403,7 +403,7 @@ function buildSddCommand(opts) {
|
|
|
403
403
|
if (!sessionState.getBuilder() && !forceFlag) {
|
|
404
404
|
const sessionPath = opts.paths.projectSddSession;
|
|
405
405
|
try {
|
|
406
|
-
await
|
|
406
|
+
await fsp3.access(sessionPath);
|
|
407
407
|
const projectContext2 = await gatherProjectContext(opts.context?.projectRoot ?? process.cwd());
|
|
408
408
|
const tempBuilder = new AISpecBuilder({
|
|
409
409
|
store: specStore,
|
|
@@ -1054,16 +1054,16 @@ Start executing the tasks one by one.`
|
|
|
1054
1054
|
const sessionPath = opts.paths.projectSddSession;
|
|
1055
1055
|
let deletedFromDisk = false;
|
|
1056
1056
|
try {
|
|
1057
|
-
await
|
|
1057
|
+
await fsp3.unlink(sessionPath);
|
|
1058
1058
|
deletedFromDisk = true;
|
|
1059
1059
|
} catch {
|
|
1060
1060
|
}
|
|
1061
1061
|
try {
|
|
1062
|
-
await
|
|
1062
|
+
await fsp3.rm(opts.paths.projectSpecs, { recursive: true, force: true });
|
|
1063
1063
|
} catch {
|
|
1064
1064
|
}
|
|
1065
1065
|
try {
|
|
1066
|
-
await
|
|
1066
|
+
await fsp3.rm(opts.paths.projectTaskGraphs, { recursive: true, force: true });
|
|
1067
1067
|
} catch {
|
|
1068
1068
|
}
|
|
1069
1069
|
const cancelBuilder = sddState.getBuilder();
|
|
@@ -1399,7 +1399,7 @@ async function gatherProjectContext(projectRoot) {
|
|
|
1399
1399
|
const parts = [];
|
|
1400
1400
|
try {
|
|
1401
1401
|
const pkgPath = path25.join(projectRoot, "package.json");
|
|
1402
|
-
const pkgRaw = await
|
|
1402
|
+
const pkgRaw = await fsp3.readFile(pkgPath, "utf8");
|
|
1403
1403
|
const pkg = JSON.parse(pkgRaw);
|
|
1404
1404
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
1405
1405
|
parts.push(`Description: ${String(pkg.description ?? "none")}`);
|
|
@@ -1415,13 +1415,13 @@ async function gatherProjectContext(projectRoot) {
|
|
|
1415
1415
|
}
|
|
1416
1416
|
try {
|
|
1417
1417
|
const tsconfigPath = path25.join(projectRoot, "tsconfig.json");
|
|
1418
|
-
await
|
|
1418
|
+
await fsp3.access(tsconfigPath);
|
|
1419
1419
|
parts.push("Language: TypeScript");
|
|
1420
1420
|
} catch {
|
|
1421
1421
|
}
|
|
1422
1422
|
try {
|
|
1423
1423
|
const srcDir = path25.join(projectRoot, "src");
|
|
1424
|
-
const entries = await
|
|
1424
|
+
const entries = await fsp3.readdir(srcDir, { withFileTypes: true });
|
|
1425
1425
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1426
1426
|
if (dirs.length > 0) {
|
|
1427
1427
|
parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -1603,7 +1603,7 @@ function isNewer(a, b) {
|
|
|
1603
1603
|
}
|
|
1604
1604
|
async function readCache(homeFn = defaultHomeDir2) {
|
|
1605
1605
|
try {
|
|
1606
|
-
const raw = await
|
|
1606
|
+
const raw = await fsp3.readFile(cachePath(homeFn), "utf8");
|
|
1607
1607
|
const entry = JSON.parse(raw);
|
|
1608
1608
|
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
|
|
1609
1609
|
return entry;
|
|
@@ -1614,8 +1614,8 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1614
1614
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1615
1615
|
try {
|
|
1616
1616
|
const dir = path25.dirname(cachePath(homeFn));
|
|
1617
|
-
await
|
|
1618
|
-
await
|
|
1617
|
+
await fsp3.mkdir(dir, { recursive: true });
|
|
1618
|
+
await fsp3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1619
1619
|
} catch {
|
|
1620
1620
|
}
|
|
1621
1621
|
}
|
|
@@ -1686,7 +1686,7 @@ async function getUpdateNotification(signal, homeFn) {
|
|
|
1686
1686
|
var defaultHomeDir2, CACHE_TTL_MS;
|
|
1687
1687
|
var init_update_check = __esm({
|
|
1688
1688
|
"src/update-check.ts"() {
|
|
1689
|
-
defaultHomeDir2 = () =>
|
|
1689
|
+
defaultHomeDir2 = () => os6.homedir();
|
|
1690
1690
|
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1691
1691
|
}
|
|
1692
1692
|
});
|
|
@@ -1823,10 +1823,27 @@ async function runWebUI(opts) {
|
|
|
1823
1823
|
});
|
|
1824
1824
|
wss.on("connection", (ws, req2) => {
|
|
1825
1825
|
const isLoopback = (hostname) => hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
1826
|
+
const tokenMatches = (provided) => {
|
|
1827
|
+
if (!provided) return false;
|
|
1828
|
+
const a = Buffer.from(provided);
|
|
1829
|
+
const b = Buffer.from(authToken);
|
|
1830
|
+
return a.length === b.length && crypto2.timingSafeEqual(a, b);
|
|
1831
|
+
};
|
|
1826
1832
|
try {
|
|
1827
1833
|
const url = new URL(req2.url ?? "/", `http://localhost:${port}`);
|
|
1828
1834
|
const token = url.searchParams.get("token");
|
|
1829
|
-
const tokenOk = token
|
|
1835
|
+
const tokenOk = tokenMatches(token);
|
|
1836
|
+
const hostHeader = (req2.headers.host ?? "").trim();
|
|
1837
|
+
let hostOk = false;
|
|
1838
|
+
try {
|
|
1839
|
+
hostOk = !!hostHeader && isLoopback(new URL(`http://${hostHeader}`).hostname);
|
|
1840
|
+
} catch {
|
|
1841
|
+
hostOk = false;
|
|
1842
|
+
}
|
|
1843
|
+
if (!hostOk) {
|
|
1844
|
+
ws.close(4003, "Forbidden: non-loopback Host header");
|
|
1845
|
+
return;
|
|
1846
|
+
}
|
|
1830
1847
|
const origin = req2.headers.origin;
|
|
1831
1848
|
if (origin) {
|
|
1832
1849
|
try {
|
|
@@ -2206,7 +2223,7 @@ async function runWebUI(opts) {
|
|
|
2206
2223
|
if (!opts.globalConfigPath) return {};
|
|
2207
2224
|
let raw;
|
|
2208
2225
|
try {
|
|
2209
|
-
raw = await
|
|
2226
|
+
raw = await fsp3.readFile(opts.globalConfigPath, "utf8");
|
|
2210
2227
|
} catch {
|
|
2211
2228
|
return {};
|
|
2212
2229
|
}
|
|
@@ -2226,7 +2243,7 @@ async function runWebUI(opts) {
|
|
|
2226
2243
|
let raw;
|
|
2227
2244
|
let fileExists = true;
|
|
2228
2245
|
try {
|
|
2229
|
-
raw = await
|
|
2246
|
+
raw = await fsp3.readFile(opts.globalConfigPath, "utf8");
|
|
2230
2247
|
} catch (err) {
|
|
2231
2248
|
if (err.code !== "ENOENT") {
|
|
2232
2249
|
throw new Error(
|
|
@@ -2459,7 +2476,7 @@ function parseSpawnFlags(input) {
|
|
|
2459
2476
|
}
|
|
2460
2477
|
async function pathExists(file) {
|
|
2461
2478
|
try {
|
|
2462
|
-
await
|
|
2479
|
+
await fsp3.access(file);
|
|
2463
2480
|
return true;
|
|
2464
2481
|
} catch {
|
|
2465
2482
|
return false;
|
|
@@ -2494,7 +2511,7 @@ function parseMakeTargets(makefile) {
|
|
|
2494
2511
|
async function detectProjectFacts(root) {
|
|
2495
2512
|
const facts = { hints: [] };
|
|
2496
2513
|
try {
|
|
2497
|
-
const pkg = JSON.parse(await
|
|
2514
|
+
const pkg = JSON.parse(await fsp3.readFile(path25.join(root, "package.json"), "utf8"));
|
|
2498
2515
|
const scripts = pkg.scripts ?? {};
|
|
2499
2516
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2500
2517
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2532,7 +2549,7 @@ async function detectProjectFacts(root) {
|
|
|
2532
2549
|
} catch {
|
|
2533
2550
|
}
|
|
2534
2551
|
try {
|
|
2535
|
-
const makefile = await
|
|
2552
|
+
const makefile = await fsp3.readFile(path25.join(root, "Makefile"), "utf8");
|
|
2536
2553
|
const targets = parseMakeTargets(makefile);
|
|
2537
2554
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2538
2555
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -3428,8 +3445,8 @@ function buildInitCommand(opts) {
|
|
|
3428
3445
|
const file = path25.join(dir, "AGENTS.md");
|
|
3429
3446
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
3430
3447
|
const body = renderAgentsTemplate(detected);
|
|
3431
|
-
await
|
|
3432
|
-
await
|
|
3448
|
+
await fsp3.mkdir(dir, { recursive: true });
|
|
3449
|
+
await fsp3.writeFile(file, body, "utf8");
|
|
3433
3450
|
if (detected.hints.length > 0) {
|
|
3434
3451
|
const msg2 = `Wrote ${file}
|
|
3435
3452
|
Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
@@ -3618,7 +3635,7 @@ function stateBadge(state) {
|
|
|
3618
3635
|
}
|
|
3619
3636
|
async function readConfig(path26) {
|
|
3620
3637
|
try {
|
|
3621
|
-
return JSON.parse(await
|
|
3638
|
+
return JSON.parse(await fsp3.readFile(path26, "utf8"));
|
|
3622
3639
|
} catch {
|
|
3623
3640
|
return {};
|
|
3624
3641
|
}
|
|
@@ -3626,8 +3643,8 @@ async function readConfig(path26) {
|
|
|
3626
3643
|
async function writeConfig(path26, cfg) {
|
|
3627
3644
|
const raw = JSON.stringify(cfg, null, 2);
|
|
3628
3645
|
const tmp = path26 + ".tmp";
|
|
3629
|
-
await
|
|
3630
|
-
await
|
|
3646
|
+
await fsp3.writeFile(tmp, raw, "utf8");
|
|
3647
|
+
await fsp3.rename(tmp, path26);
|
|
3631
3648
|
}
|
|
3632
3649
|
|
|
3633
3650
|
// src/slash-commands/mcp.ts
|
|
@@ -4899,7 +4916,7 @@ function resolveConfigPath() {
|
|
|
4899
4916
|
async function loadStatuslineConfig() {
|
|
4900
4917
|
const p = resolveConfigPath();
|
|
4901
4918
|
try {
|
|
4902
|
-
const raw = await
|
|
4919
|
+
const raw = await fsp3.readFile(p, "utf8");
|
|
4903
4920
|
return { ...DEFAULTS, ...JSON.parse(raw) };
|
|
4904
4921
|
} catch {
|
|
4905
4922
|
return { ...DEFAULTS };
|
|
@@ -4908,7 +4925,7 @@ async function loadStatuslineConfig() {
|
|
|
4908
4925
|
async function saveStatuslineConfig(cfg) {
|
|
4909
4926
|
const p = resolveConfigPath();
|
|
4910
4927
|
try {
|
|
4911
|
-
await
|
|
4928
|
+
await fsp3.mkdir(path25.dirname(p), { recursive: true });
|
|
4912
4929
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
4913
4930
|
} catch (err) {
|
|
4914
4931
|
throw new FsError({
|
|
@@ -5807,7 +5824,7 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
|
5807
5824
|
};
|
|
5808
5825
|
}
|
|
5809
5826
|
function makeInstaller(opts, projectRoot, global) {
|
|
5810
|
-
const globalRoot = path25.join(
|
|
5827
|
+
const globalRoot = path25.join(os6.homedir(), ".wrongstack");
|
|
5811
5828
|
return new SkillInstaller({
|
|
5812
5829
|
manifestPath: path25.join(globalRoot, "installed-skills.json"),
|
|
5813
5830
|
projectSkillsDir: path25.join(projectRoot, ".wrongstack", "skills"),
|
|
@@ -5967,229 +5984,207 @@ function buildSkillUninstallCommand(opts) {
|
|
|
5967
5984
|
}
|
|
5968
5985
|
};
|
|
5969
5986
|
}
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
var DEFAULT_PHASES = [
|
|
5973
|
-
{
|
|
5974
|
-
name: "Discovery",
|
|
5975
|
-
description: "Requirements gathering and analysis",
|
|
5976
|
-
priority: "high",
|
|
5977
|
-
estimateHours: 2,
|
|
5978
|
-
parallelizable: false
|
|
5979
|
-
},
|
|
5980
|
-
{
|
|
5981
|
-
name: "Design",
|
|
5982
|
-
description: "Architecture and design decisions",
|
|
5983
|
-
priority: "critical",
|
|
5984
|
-
estimateHours: 4,
|
|
5985
|
-
parallelizable: false
|
|
5986
|
-
},
|
|
5987
|
-
{
|
|
5988
|
-
name: "Implementation",
|
|
5989
|
-
description: "Core feature development",
|
|
5990
|
-
priority: "critical",
|
|
5991
|
-
estimateHours: 12,
|
|
5992
|
-
parallelizable: false
|
|
5993
|
-
},
|
|
5994
|
-
{
|
|
5995
|
-
name: "Testing",
|
|
5996
|
-
description: "Unit, integration, and e2e tests",
|
|
5997
|
-
priority: "high",
|
|
5998
|
-
estimateHours: 6,
|
|
5999
|
-
parallelizable: true
|
|
6000
|
-
},
|
|
6001
|
-
{
|
|
6002
|
-
name: "Deployment",
|
|
6003
|
-
description: "Deploy to production",
|
|
6004
|
-
priority: "medium",
|
|
6005
|
-
estimateHours: 2,
|
|
6006
|
-
parallelizable: false
|
|
6007
|
-
}
|
|
6008
|
-
];
|
|
6009
|
-
function getStore() {
|
|
6010
|
-
const baseDir = path25.join(os7.homedir(), ".wrongstack", "autophase");
|
|
6011
|
-
return new PhaseStore({ baseDir });
|
|
5987
|
+
function getStore(opts) {
|
|
5988
|
+
return new PhaseStore({ baseDir: opts.paths.projectAutophase });
|
|
6012
5989
|
}
|
|
6013
5990
|
function formatProgress(p) {
|
|
6014
|
-
const
|
|
5991
|
+
const filled = Math.floor(p.percentComplete / 5);
|
|
5992
|
+
const bars = "\u2588".repeat(filled) + "\u2591".repeat(20 - filled);
|
|
6015
5993
|
return [
|
|
6016
5994
|
`
|
|
6017
5995
|
\u{1F4CA} Progress: ${bars} ${p.percentComplete}%`,
|
|
6018
5996
|
` \u{1F4CB} Phases: ${p.completed}/${p.totalPhases} done, ${p.running} running, ${p.pending} pending`,
|
|
6019
5997
|
` \u2705 Tasks: ${p.completedTasks}/${p.totalTasks} completed`,
|
|
6020
|
-
` \u23F1
|
|
5998
|
+
` \u23F1 Est: ${p.estimatedHours.toFixed(1)}h | Actual: ${p.actualHours.toFixed(1)}h`
|
|
6021
5999
|
].join("\n");
|
|
6022
6000
|
}
|
|
6001
|
+
var STATUS_EMOJI = {
|
|
6002
|
+
pending: "\u23F3",
|
|
6003
|
+
ready: "\u{1F51C}",
|
|
6004
|
+
running: "\u{1F504}",
|
|
6005
|
+
paused: "\u23F8",
|
|
6006
|
+
completed: "\u2705",
|
|
6007
|
+
failed: "\u274C",
|
|
6008
|
+
skipped: "\u23ED"
|
|
6009
|
+
};
|
|
6023
6010
|
function formatPhaseList(graph) {
|
|
6024
6011
|
const phases = Array.from(graph.phases.values());
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
const emoji = statusEmoji[p.status] ?? "\u26AA";
|
|
6036
|
-
const progress = p.taskGraph.nodes.size > 0 ? `${Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length}/${p.taskGraph.nodes.size}` : "0/0";
|
|
6037
|
-
return ` ${i + 1}. ${emoji} ${p.name} (${p.status}) \u2014 ${progress} tasks`;
|
|
6038
|
-
}).join("\n");
|
|
6012
|
+
return [
|
|
6013
|
+
"",
|
|
6014
|
+
"Phases:",
|
|
6015
|
+
...phases.map((p) => {
|
|
6016
|
+
const total = p.taskGraph.nodes.size;
|
|
6017
|
+
const done = Array.from(p.taskGraph.nodes.values()).filter((t) => t.status === "completed").length;
|
|
6018
|
+
const tasks = total > 0 ? ` (${done}/${total} todos)` : "";
|
|
6019
|
+
return ` ${STATUS_EMOJI[p.status] ?? "?"} ${p.name}: ${p.status}${tasks}`;
|
|
6020
|
+
})
|
|
6021
|
+
].join("\n");
|
|
6039
6022
|
}
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
}
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6023
|
+
async function gatherProjectContext2(projectRoot) {
|
|
6024
|
+
try {
|
|
6025
|
+
const raw = await fsp3.readFile(path25.join(projectRoot, "package.json"), "utf8");
|
|
6026
|
+
const pkg = JSON.parse(raw);
|
|
6027
|
+
const parts = [
|
|
6028
|
+
`Project: ${String(pkg.name ?? "unknown")}`,
|
|
6029
|
+
pkg.description ? `Description: ${String(pkg.description)}` : ""
|
|
6030
|
+
].filter(Boolean);
|
|
6031
|
+
return parts.join("\n") || void 0;
|
|
6032
|
+
} catch {
|
|
6033
|
+
return void 0;
|
|
6034
|
+
}
|
|
6035
|
+
}
|
|
6036
|
+
function buildAutoPhaseCommand(opts) {
|
|
6037
|
+
return {
|
|
6038
|
+
name: "autophase",
|
|
6039
|
+
description: "Autonomous phase-based workflow \u2014 plans a project into phases of todos and builds it with the LLM.",
|
|
6040
|
+
help: [
|
|
6041
|
+
"Usage:",
|
|
6042
|
+
" /autophase Show current status",
|
|
6043
|
+
" /autophase start <goal> Plan + start an autonomous phase build",
|
|
6044
|
+
" /autophase pause Pause (in-flight tasks finish, no new ones start)",
|
|
6045
|
+
" /autophase resume Resume a paused run",
|
|
6046
|
+
" /autophase stop Stop and abort in-flight tasks",
|
|
6047
|
+
" /autophase save Persist current graph to disk",
|
|
6048
|
+
" /autophase load [title] Load a persisted graph (display only)",
|
|
6049
|
+
" /autophase list List saved projects",
|
|
6050
|
+
""
|
|
6051
|
+
].join("\n"),
|
|
6052
|
+
async run(args) {
|
|
6053
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
6054
|
+
const sub = parts[0] ?? "status";
|
|
6055
|
+
const store = getStore(opts);
|
|
6056
|
+
switch (sub) {
|
|
6057
|
+
case "start": {
|
|
6058
|
+
const goal = parts.slice(1).join(" ").trim();
|
|
6059
|
+
if (!goal) {
|
|
6060
|
+
return { message: "Usage: /autophase start <goal> \u2014 describe what to build." };
|
|
6061
|
+
}
|
|
6062
|
+
if (!opts.onAutoPhaseStart) {
|
|
6063
|
+
return { message: "\u274C AutoPhase is not available in this session (no LLM host wired)." };
|
|
6064
|
+
}
|
|
6065
|
+
const projectContext = await gatherProjectContext2(opts.projectRoot);
|
|
6066
|
+
const result = await opts.onAutoPhaseStart({ goal, projectContext });
|
|
6067
|
+
if (!result.ok) {
|
|
6068
|
+
return { message: `\u274C ${result.error}` };
|
|
6069
|
+
}
|
|
6070
|
+
return {
|
|
6071
|
+
message: [
|
|
6072
|
+
`\u{1F680} AutoPhase started: **${result.graph.title}**`,
|
|
6073
|
+
formatPhaseList(result.graph),
|
|
6074
|
+
"",
|
|
6075
|
+
"Building autonomously in the background \u2014 one subagent per todo.",
|
|
6076
|
+
"Use `/autophase` for status, `/autophase pause` to hold, `/autophase stop` to abort."
|
|
6077
|
+
].join("\n"),
|
|
6078
|
+
metadata: { autoPhaseInit: { title: result.graph.title } }
|
|
6079
|
+
};
|
|
6097
6080
|
}
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
if (!currentRunner) {
|
|
6103
|
-
return { message: "\u274C Aktif AutoPhase yok. \xD6nce `/autophase start` \xE7al\u0131\u015Ft\u0131r\u0131n." };
|
|
6081
|
+
case "pause": {
|
|
6082
|
+
if (!opts.onAutoPhasePause) return { message: "\u274C AutoPhase host not available." };
|
|
6083
|
+
opts.onAutoPhasePause();
|
|
6084
|
+
return { message: "\u23F8\uFE0F AutoPhase paused \u2014 running tasks will finish; no new ones will start." };
|
|
6104
6085
|
}
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
if (!currentRunner) {
|
|
6110
|
-
return { message: "\u274C Aktif AutoPhase yok." };
|
|
6086
|
+
case "resume": {
|
|
6087
|
+
if (!opts.onAutoPhaseResume) return { message: "\u274C AutoPhase host not available." };
|
|
6088
|
+
opts.onAutoPhaseResume();
|
|
6089
|
+
return { message: "\u25B6 AutoPhase resuming." };
|
|
6111
6090
|
}
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6091
|
+
case "stop": {
|
|
6092
|
+
if (!opts.onAutoPhaseStop) return { message: "\u274C AutoPhase host not available." };
|
|
6093
|
+
opts.onAutoPhaseStop();
|
|
6094
|
+
return { message: "\u23F9 AutoPhase stopped \u2014 in-flight tasks aborted, progress saved." };
|
|
6095
|
+
}
|
|
6096
|
+
case "save": {
|
|
6097
|
+
const view = opts.getAutoPhaseRunner?.();
|
|
6098
|
+
if (!view) return { message: "\u274C No active AutoPhase to save." };
|
|
6099
|
+
await store.save(view.graph);
|
|
6100
|
+
return { message: `\u{1F4BE} AutoPhase saved: ${view.graph.title}` };
|
|
6101
|
+
}
|
|
6102
|
+
case "load": {
|
|
6103
|
+
const title = parts.slice(1).join(" ").trim();
|
|
6118
6104
|
const graphs = await store.list();
|
|
6119
|
-
if (graphs.length === 0) {
|
|
6120
|
-
|
|
6121
|
-
}
|
|
6122
|
-
const
|
|
6123
|
-
return { message:
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
const phaseList = formatPhaseList(currentGraph);
|
|
6128
|
-
return {
|
|
6129
|
-
message: [
|
|
6130
|
-
`**${currentGraph.title}**`,
|
|
6131
|
-
progress ? formatProgress(progress) : "",
|
|
6132
|
-
"",
|
|
6133
|
-
"**Fazlar:**",
|
|
6134
|
-
phaseList,
|
|
6135
|
-
"",
|
|
6136
|
-
currentRunner.isPaused() ? "\u23F8\uFE0F Duraklat\u0131ld\u0131" : currentRunner.isRunning() ? "\u{1F504} \xC7al\u0131\u015F\u0131yor" : "\u23F9\uFE0F Durdu"
|
|
6137
|
-
].join("\n")
|
|
6138
|
-
};
|
|
6139
|
-
}
|
|
6140
|
-
case "list": {
|
|
6141
|
-
const graphs = await store.list();
|
|
6142
|
-
if (graphs.length === 0) {
|
|
6143
|
-
return { message: "Kay\u0131tl\u0131 AutoPhase projesi yok." };
|
|
6105
|
+
if (graphs.length === 0) return { message: "\u274C No saved projects." };
|
|
6106
|
+
const entry = title ? graphs.find((g) => g.title.toLowerCase().includes(title.toLowerCase())) : graphs[0];
|
|
6107
|
+
if (!entry) return { message: `\u274C No saved project matching "${title}".` };
|
|
6108
|
+
const graph = await store.load(entry.id);
|
|
6109
|
+
if (!graph) return { message: `\u274C Could not load project "${entry.title}".` };
|
|
6110
|
+
return {
|
|
6111
|
+
message: [`\u{1F4C2} Loaded (display only): **${graph.title}**`, formatPhaseList(graph)].join("\n")
|
|
6112
|
+
};
|
|
6144
6113
|
}
|
|
6145
|
-
|
|
6146
|
-
const
|
|
6147
|
-
return
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
if (!graphId) {
|
|
6155
|
-
return { message: "\u274C Graph ID gerekli. Kullan\u0131m: `/autophase load <id>`" };
|
|
6114
|
+
case "list": {
|
|
6115
|
+
const graphs = await store.list();
|
|
6116
|
+
if (graphs.length === 0) return { message: "No saved projects." };
|
|
6117
|
+
return {
|
|
6118
|
+
message: [
|
|
6119
|
+
"Saved AutoPhase projects:",
|
|
6120
|
+
...graphs.map((g) => ` \xB7 ${g.title} \u2014 ${g.status} (updated ${new Date(g.updatedAt).toLocaleString()})`)
|
|
6121
|
+
].join("\n")
|
|
6122
|
+
};
|
|
6156
6123
|
}
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6124
|
+
case "default":
|
|
6125
|
+
case "status": {
|
|
6126
|
+
const view = opts.getAutoPhaseRunner?.();
|
|
6127
|
+
if (!view) {
|
|
6128
|
+
return { message: "No active AutoPhase. Run `/autophase start <goal>` to begin." };
|
|
6129
|
+
}
|
|
6130
|
+
const progress = view.getProgress();
|
|
6131
|
+
return {
|
|
6132
|
+
message: [
|
|
6133
|
+
`**${view.graph.title}** ${view.isRunning() ? "\u{1F504} running" : "\u23F8 idle"}`,
|
|
6134
|
+
formatPhaseList(view.graph),
|
|
6135
|
+
...progress ? [formatProgress(progress)] : []
|
|
6136
|
+
].join("\n")
|
|
6137
|
+
};
|
|
6160
6138
|
}
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
};
|
|
6142
|
+
}
|
|
6164
6143
|
|
|
6165
|
-
|
|
6166
|
-
|
|
6144
|
+
// src/slash-commands/worktree.ts
|
|
6145
|
+
function buildWorktreeCommand(opts) {
|
|
6146
|
+
return {
|
|
6147
|
+
name: "worktree",
|
|
6148
|
+
aliases: ["wt"],
|
|
6149
|
+
description: "Inspect/manage git worktrees used for AutoPhase per-phase isolation.",
|
|
6150
|
+
argsHint: "[list | merge <branch> | prune | clean]",
|
|
6151
|
+
help: [
|
|
6152
|
+
"Usage: /worktree [subcommand]",
|
|
6153
|
+
"",
|
|
6154
|
+
" list List active worktrees (default).",
|
|
6155
|
+
" merge <branch> Squash-merge <branch> into the current branch.",
|
|
6156
|
+
" prune Remove stale worktree administrative entries.",
|
|
6157
|
+
" clean Remove all wstack-managed worktrees and branches.",
|
|
6158
|
+
"",
|
|
6159
|
+
"AutoPhase allocates one worktree per phase under .wrongstack/worktrees/",
|
|
6160
|
+
"so parallelizable phases run isolated, then merge back sequentially."
|
|
6161
|
+
].join("\n"),
|
|
6162
|
+
async run(args) {
|
|
6163
|
+
if (!opts.onWorktree) {
|
|
6164
|
+
return { message: "\u26A0 No worktree manager active in this session." };
|
|
6167
6165
|
}
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6166
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
6167
|
+
const sub = (parts[0] ?? "list").toLowerCase();
|
|
6168
|
+
switch (sub) {
|
|
6169
|
+
case "list":
|
|
6170
|
+
return { message: await opts.onWorktree("list") };
|
|
6171
|
+
case "merge": {
|
|
6172
|
+
const branch = parts[1];
|
|
6173
|
+
if (!branch) return { message: "Usage: /worktree merge <branch>" };
|
|
6174
|
+
return { message: await opts.onWorktree("merge", branch) };
|
|
6175
|
+
}
|
|
6176
|
+
case "prune":
|
|
6177
|
+
return { message: await opts.onWorktree("prune") };
|
|
6178
|
+
case "clean":
|
|
6179
|
+
return { message: await opts.onWorktree("clean") };
|
|
6180
|
+
default:
|
|
6181
|
+
return {
|
|
6182
|
+
message: `Unknown subcommand "${sub}". Valid: list, merge <branch>, prune, clean.`
|
|
6183
|
+
};
|
|
6174
6184
|
}
|
|
6175
|
-
default:
|
|
6176
|
-
return {
|
|
6177
|
-
message: [
|
|
6178
|
-
"**AutoPhase Komutlar\u0131:**",
|
|
6179
|
-
"",
|
|
6180
|
-
"`/autophase start [title]` \u2014 Yeni proje ba\u015Flat",
|
|
6181
|
-
"`/autophase pause` \u2014 Duraklat",
|
|
6182
|
-
"`/autophase resume` \u2014 Devam et",
|
|
6183
|
-
"`/autophase stop` \u2014 Durdur",
|
|
6184
|
-
"`/autophase status` \u2014 Durum g\xF6ster",
|
|
6185
|
-
"`/autophase list` \u2014 Kay\u0131tl\u0131 projeleri listele",
|
|
6186
|
-
"`/autophase load <id>` \u2014 Projeyi y\xFCkle",
|
|
6187
|
-
"`/autophase save` \u2014 Aktif projeyi kaydet"
|
|
6188
|
-
].join("\n")
|
|
6189
|
-
};
|
|
6190
6185
|
}
|
|
6191
|
-
}
|
|
6192
|
-
}
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
6193
6188
|
|
|
6194
6189
|
// src/slash-commands/index.ts
|
|
6195
6190
|
function buildBuiltinSlashCommands(opts) {
|
|
@@ -6232,7 +6227,8 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
6232
6227
|
buildPushCommand(),
|
|
6233
6228
|
buildSecurityCommand(opts),
|
|
6234
6229
|
buildFixCommand(opts),
|
|
6235
|
-
|
|
6230
|
+
buildAutoPhaseCommand(opts),
|
|
6231
|
+
buildWorktreeCommand(opts),
|
|
6236
6232
|
buildStatuslineCommand({
|
|
6237
6233
|
cwd: opts.cwd,
|
|
6238
6234
|
hiddenItems: opts.statuslineHiddenItems ?? [],
|
|
@@ -6260,13 +6256,13 @@ var MANIFESTS = [
|
|
|
6260
6256
|
];
|
|
6261
6257
|
async function detectProjectKind(projectRoot) {
|
|
6262
6258
|
try {
|
|
6263
|
-
await
|
|
6259
|
+
await fsp3.access(path25.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
6264
6260
|
return "initialized";
|
|
6265
6261
|
} catch {
|
|
6266
6262
|
}
|
|
6267
6263
|
for (const m of MANIFESTS) {
|
|
6268
6264
|
try {
|
|
6269
|
-
await
|
|
6265
|
+
await fsp3.access(path25.join(projectRoot, m));
|
|
6270
6266
|
return "project";
|
|
6271
6267
|
} catch {
|
|
6272
6268
|
}
|
|
@@ -6278,8 +6274,8 @@ async function scaffoldAgentsMd(projectRoot) {
|
|
|
6278
6274
|
const file = path25.join(dir, "AGENTS.md");
|
|
6279
6275
|
const facts = await detectProjectFacts(projectRoot);
|
|
6280
6276
|
const body = renderAgentsTemplate(facts);
|
|
6281
|
-
await
|
|
6282
|
-
await
|
|
6277
|
+
await fsp3.mkdir(dir, { recursive: true });
|
|
6278
|
+
await fsp3.writeFile(file, body, "utf8");
|
|
6283
6279
|
return file;
|
|
6284
6280
|
}
|
|
6285
6281
|
async function runProjectCheck(opts) {
|
|
@@ -6322,7 +6318,7 @@ async function runProjectCheck(opts) {
|
|
|
6322
6318
|
const gitDir = path25.join(projectRoot, ".git");
|
|
6323
6319
|
let hasGit = false;
|
|
6324
6320
|
try {
|
|
6325
|
-
await
|
|
6321
|
+
await fsp3.access(gitDir);
|
|
6326
6322
|
hasGit = true;
|
|
6327
6323
|
} catch {
|
|
6328
6324
|
}
|
|
@@ -6341,9 +6337,9 @@ async function runProjectCheck(opts) {
|
|
|
6341
6337
|
}
|
|
6342
6338
|
if (answer2 === "y" || answer2 === "yes") {
|
|
6343
6339
|
try {
|
|
6344
|
-
const { spawn:
|
|
6340
|
+
const { spawn: spawn4 } = await import('child_process');
|
|
6345
6341
|
await new Promise((resolve4, reject) => {
|
|
6346
|
-
const child =
|
|
6342
|
+
const child = spawn4("git", ["init"], { cwd: projectRoot });
|
|
6347
6343
|
child.on("error", reject);
|
|
6348
6344
|
child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
|
|
6349
6345
|
});
|
|
@@ -6447,7 +6443,7 @@ async function bootConfig(flags) {
|
|
|
6447
6443
|
const cwd = typeof flags["cwd"] === "string" ? path25.resolve(flags["cwd"]) : process.cwd();
|
|
6448
6444
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
6449
6445
|
const projectRoot = pathResolver.projectRoot;
|
|
6450
|
-
const userHome =
|
|
6446
|
+
const userHome = os6.homedir();
|
|
6451
6447
|
const wpaths = resolveWstackPaths({ projectRoot, userHome });
|
|
6452
6448
|
await ensureProjectMeta(wpaths, projectRoot);
|
|
6453
6449
|
const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
|
|
@@ -6495,13 +6491,13 @@ function flagsToConfigPatch(flags) {
|
|
|
6495
6491
|
}
|
|
6496
6492
|
async function ensureProjectMeta(paths, projectRoot) {
|
|
6497
6493
|
try {
|
|
6498
|
-
await
|
|
6494
|
+
await fsp3.mkdir(paths.projectDir, { recursive: true });
|
|
6499
6495
|
const meta = {
|
|
6500
6496
|
hash: paths.projectHash,
|
|
6501
6497
|
root: projectRoot,
|
|
6502
6498
|
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
|
|
6503
6499
|
};
|
|
6504
|
-
await
|
|
6500
|
+
await fsp3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
|
|
6505
6501
|
} catch {
|
|
6506
6502
|
}
|
|
6507
6503
|
}
|
|
@@ -6511,11 +6507,11 @@ var ReadlineInputReader = class {
|
|
|
6511
6507
|
history = [];
|
|
6512
6508
|
pending = false;
|
|
6513
6509
|
constructor(opts = {}) {
|
|
6514
|
-
this.historyFile = opts.historyFile ?? path25.join(
|
|
6510
|
+
this.historyFile = opts.historyFile ?? path25.join(os6.homedir(), ".wrongstack", "history");
|
|
6515
6511
|
}
|
|
6516
6512
|
async loadHistory() {
|
|
6517
6513
|
try {
|
|
6518
|
-
const raw = await
|
|
6514
|
+
const raw = await fsp3.readFile(this.historyFile, "utf8");
|
|
6519
6515
|
this.history = raw.split("\n").filter(Boolean).slice(-1e3);
|
|
6520
6516
|
} catch {
|
|
6521
6517
|
this.history = [];
|
|
@@ -6523,8 +6519,8 @@ var ReadlineInputReader = class {
|
|
|
6523
6519
|
}
|
|
6524
6520
|
async saveHistory() {
|
|
6525
6521
|
try {
|
|
6526
|
-
await
|
|
6527
|
-
await
|
|
6522
|
+
await fsp3.mkdir(path25.dirname(this.historyFile), { recursive: true });
|
|
6523
|
+
await fsp3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
6528
6524
|
} catch {
|
|
6529
6525
|
}
|
|
6530
6526
|
}
|
|
@@ -6766,7 +6762,7 @@ async function safeDelete(filePath) {
|
|
|
6766
6762
|
const filename = path25.basename(filePath);
|
|
6767
6763
|
try {
|
|
6768
6764
|
assertSafeToDelete(filename, dir);
|
|
6769
|
-
await
|
|
6765
|
+
await fsp3.unlink(filePath);
|
|
6770
6766
|
} catch (err) {
|
|
6771
6767
|
if (err instanceof Error && err.message.startsWith("Refusing")) {
|
|
6772
6768
|
process.stderr.write(`[config-history] SAFETY: ${err.message}
|
|
@@ -6806,7 +6802,7 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
6806
6802
|
}
|
|
6807
6803
|
return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
|
|
6808
6804
|
}
|
|
6809
|
-
var defaultHomeDir = () =>
|
|
6805
|
+
var defaultHomeDir = () => os6__default.homedir();
|
|
6810
6806
|
function historyDir(homeFn = defaultHomeDir) {
|
|
6811
6807
|
return path25.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
6812
6808
|
}
|
|
@@ -6824,7 +6820,7 @@ function entryId(ts) {
|
|
|
6824
6820
|
}
|
|
6825
6821
|
async function ensureHistoryDir(homeFn = defaultHomeDir) {
|
|
6826
6822
|
try {
|
|
6827
|
-
await
|
|
6823
|
+
await fsp3.mkdir(historyDir(homeFn), { recursive: true });
|
|
6828
6824
|
} catch (err) {
|
|
6829
6825
|
throw new FsError({
|
|
6830
6826
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -6836,7 +6832,7 @@ async function ensureHistoryDir(homeFn = defaultHomeDir) {
|
|
|
6836
6832
|
}
|
|
6837
6833
|
async function readIndex(homeFn = defaultHomeDir) {
|
|
6838
6834
|
try {
|
|
6839
|
-
const raw = await
|
|
6835
|
+
const raw = await fsp3.readFile(historyIndexPath(homeFn), "utf8");
|
|
6840
6836
|
return JSON.parse(raw);
|
|
6841
6837
|
} catch {
|
|
6842
6838
|
return { version: 1, entries: [] };
|
|
@@ -6861,7 +6857,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
6861
6857
|
const ts = Date.now();
|
|
6862
6858
|
let content;
|
|
6863
6859
|
try {
|
|
6864
|
-
content = await
|
|
6860
|
+
content = await fsp3.readFile(cfg, "utf8");
|
|
6865
6861
|
} catch {
|
|
6866
6862
|
}
|
|
6867
6863
|
if (content !== void 0) {
|
|
@@ -6879,7 +6875,7 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
6879
6875
|
}
|
|
6880
6876
|
try {
|
|
6881
6877
|
const dir = path25.join(homeFn(), ".wrongstack");
|
|
6882
|
-
const files = await
|
|
6878
|
+
const files = await fsp3.readdir(dir);
|
|
6883
6879
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
6884
6880
|
for (const f of baks.slice(10)) {
|
|
6885
6881
|
await safeDelete(path25.join(dir, f));
|
|
@@ -6899,7 +6895,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
6899
6895
|
diffSummary: diffSummary(oldCfg, newCfg)
|
|
6900
6896
|
};
|
|
6901
6897
|
try {
|
|
6902
|
-
await
|
|
6898
|
+
await fsp3.writeFile(
|
|
6903
6899
|
path25.join(historyDir(homeFn), `${id}.json`),
|
|
6904
6900
|
JSON.stringify(entry, null, 2),
|
|
6905
6901
|
"utf8"
|
|
@@ -6923,7 +6919,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
6923
6919
|
}
|
|
6924
6920
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
6925
6921
|
try {
|
|
6926
|
-
const raw = await
|
|
6922
|
+
const raw = await fsp3.readFile(path25.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
6927
6923
|
return JSON.parse(raw);
|
|
6928
6924
|
} catch {
|
|
6929
6925
|
return null;
|
|
@@ -6935,7 +6931,7 @@ async function restoreFromHistory(id, homeFn = defaultHomeDir) {
|
|
|
6935
6931
|
await backupCurrent(homeFn);
|
|
6936
6932
|
let oldCfg = {};
|
|
6937
6933
|
try {
|
|
6938
|
-
const raw = await
|
|
6934
|
+
const raw = await fsp3.readFile(configPath(homeFn), "utf8");
|
|
6939
6935
|
oldCfg = JSON.parse(raw);
|
|
6940
6936
|
} catch {
|
|
6941
6937
|
}
|
|
@@ -6957,13 +6953,13 @@ async function restoreLast(homeFn = defaultHomeDir) {
|
|
|
6957
6953
|
const cfg = configPath(homeFn);
|
|
6958
6954
|
let oldCfg = {};
|
|
6959
6955
|
try {
|
|
6960
|
-
const raw = await
|
|
6956
|
+
const raw = await fsp3.readFile(cfg, "utf8");
|
|
6961
6957
|
oldCfg = JSON.parse(raw);
|
|
6962
6958
|
} catch {
|
|
6963
6959
|
}
|
|
6964
6960
|
let lastCfg = {};
|
|
6965
6961
|
try {
|
|
6966
|
-
const raw = await
|
|
6962
|
+
const raw = await fsp3.readFile(last, "utf8");
|
|
6967
6963
|
lastCfg = JSON.parse(raw);
|
|
6968
6964
|
} catch {
|
|
6969
6965
|
return { ok: false, error: "No prior backup found" };
|
|
@@ -6994,7 +6990,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
|
|
|
6994
6990
|
existing.provider = provider;
|
|
6995
6991
|
existing.model = model;
|
|
6996
6992
|
await backupCurrent(homeFn);
|
|
6997
|
-
await atomicWrite8(configPath2, JSON.stringify(existing, null, 2));
|
|
6993
|
+
await atomicWrite8(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
6998
6994
|
try {
|
|
6999
6995
|
await appendHistory(
|
|
7000
6996
|
oldCfg,
|
|
@@ -8424,7 +8420,7 @@ async function readKeyInput(deps, intent) {
|
|
|
8424
8420
|
async function loadProviders(deps) {
|
|
8425
8421
|
let raw;
|
|
8426
8422
|
try {
|
|
8427
|
-
raw = await
|
|
8423
|
+
raw = await fsp3.readFile(deps.globalConfigPath, "utf8");
|
|
8428
8424
|
} catch (err) {
|
|
8429
8425
|
if (err.code !== "ENOENT") {
|
|
8430
8426
|
deps.renderer.writeWarning(
|
|
@@ -8449,7 +8445,7 @@ async function mutateProviders(deps, mutator) {
|
|
|
8449
8445
|
let raw;
|
|
8450
8446
|
let fileExists = true;
|
|
8451
8447
|
try {
|
|
8452
|
-
raw = await
|
|
8448
|
+
raw = await fsp3.readFile(deps.globalConfigPath, "utf8");
|
|
8453
8449
|
} catch (err) {
|
|
8454
8450
|
if (err.code !== "ENOENT") {
|
|
8455
8451
|
throw new Error(
|
|
@@ -8595,7 +8591,7 @@ var diagCmd = async (_args, deps) => {
|
|
|
8595
8591
|
` modelsCache: ${deps.paths.modelsCache}`,
|
|
8596
8592
|
` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
|
|
8597
8593
|
` node: ${process.version}`,
|
|
8598
|
-
` os: ${
|
|
8594
|
+
` os: ${os6.platform()} ${os6.release()}`,
|
|
8599
8595
|
` provider: ${cfg.provider ?? "<unset>"}`,
|
|
8600
8596
|
` model: ${cfg.model ?? "<unset>"}`,
|
|
8601
8597
|
` tools: ${deps.toolRegistry?.list().length ?? 0}`,
|
|
@@ -8663,7 +8659,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
8663
8659
|
});
|
|
8664
8660
|
}
|
|
8665
8661
|
try {
|
|
8666
|
-
await
|
|
8662
|
+
await fsp3.access(deps.paths.secretsKey);
|
|
8667
8663
|
checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
|
|
8668
8664
|
} catch {
|
|
8669
8665
|
checks.push({
|
|
@@ -8673,10 +8669,10 @@ var doctorCmd = async (_args, deps) => {
|
|
|
8673
8669
|
});
|
|
8674
8670
|
}
|
|
8675
8671
|
try {
|
|
8676
|
-
await
|
|
8672
|
+
await fsp3.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
8677
8673
|
const probe = path25.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
8678
|
-
await
|
|
8679
|
-
await
|
|
8674
|
+
await fsp3.writeFile(probe, "");
|
|
8675
|
+
await fsp3.unlink(probe);
|
|
8680
8676
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
8681
8677
|
} catch (err) {
|
|
8682
8678
|
checks.push({
|
|
@@ -8777,8 +8773,8 @@ var exportCmd = async (args, deps) => {
|
|
|
8777
8773
|
return 1;
|
|
8778
8774
|
}
|
|
8779
8775
|
if (output) {
|
|
8780
|
-
await
|
|
8781
|
-
await
|
|
8776
|
+
await fsp3.mkdir(path25.dirname(path25.resolve(deps.cwd, output)), { recursive: true });
|
|
8777
|
+
await fsp3.writeFile(path25.resolve(deps.cwd, output), rendered, "utf8");
|
|
8782
8778
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
8783
8779
|
`);
|
|
8784
8780
|
} else {
|
|
@@ -8845,13 +8841,13 @@ var initCmd = async (_args, deps) => {
|
|
|
8845
8841
|
} else {
|
|
8846
8842
|
deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
|
|
8847
8843
|
}
|
|
8848
|
-
await
|
|
8844
|
+
await fsp3.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
8849
8845
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
8850
8846
|
if (apiKey) config.apiKey = apiKey;
|
|
8851
8847
|
const vault = new DefaultSecretVault$1({ keyFile: deps.paths.secretsKey });
|
|
8852
8848
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
8853
|
-
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
8854
|
-
await
|
|
8849
|
+
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
8850
|
+
await fsp3.mkdir(path25.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
8855
8851
|
const agentsFile = path25.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
8856
8852
|
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
8857
8853
|
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
@@ -8926,7 +8922,7 @@ async function addMcpServer(args, deps) {
|
|
|
8926
8922
|
serverCfg.enabled = enable;
|
|
8927
8923
|
let existing = {};
|
|
8928
8924
|
try {
|
|
8929
|
-
existing = JSON.parse(await
|
|
8925
|
+
existing = JSON.parse(await fsp3.readFile(deps.paths.globalConfig, "utf8"));
|
|
8930
8926
|
} catch {
|
|
8931
8927
|
}
|
|
8932
8928
|
const mcpServers = existing.mcpServers ?? {};
|
|
@@ -8935,7 +8931,7 @@ async function addMcpServer(args, deps) {
|
|
|
8935
8931
|
`);
|
|
8936
8932
|
mcpServers[name] = serverCfg;
|
|
8937
8933
|
existing.mcpServers = mcpServers;
|
|
8938
|
-
await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2));
|
|
8934
|
+
await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
8939
8935
|
const verb = enable ? "Enabled" : "Added (disabled \u2014 set enabled:true to activate)";
|
|
8940
8936
|
deps.renderer.writeInfo(
|
|
8941
8937
|
`${verb} "${name}" (${serverCfg.transport}). Config written to ${deps.paths.globalConfig}.
|
|
@@ -8946,7 +8942,7 @@ async function addMcpServer(args, deps) {
|
|
|
8946
8942
|
async function removeMcpServer(name, deps) {
|
|
8947
8943
|
let existing = {};
|
|
8948
8944
|
try {
|
|
8949
|
-
existing = JSON.parse(await
|
|
8945
|
+
existing = JSON.parse(await fsp3.readFile(deps.paths.globalConfig, "utf8"));
|
|
8950
8946
|
} catch {
|
|
8951
8947
|
deps.renderer.writeError("No config file found.\n");
|
|
8952
8948
|
return 1;
|
|
@@ -8959,7 +8955,7 @@ async function removeMcpServer(name, deps) {
|
|
|
8959
8955
|
}
|
|
8960
8956
|
delete mcpServers[name];
|
|
8961
8957
|
existing.mcpServers = mcpServers;
|
|
8962
|
-
await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2));
|
|
8958
|
+
await atomicWrite(deps.paths.globalConfig, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
8963
8959
|
deps.renderer.writeInfo(`Removed "${name}" from config.
|
|
8964
8960
|
`);
|
|
8965
8961
|
return 0;
|
|
@@ -9067,7 +9063,7 @@ function renderConfiguredPlugins(config) {
|
|
|
9067
9063
|
}
|
|
9068
9064
|
async function readConfig2(file) {
|
|
9069
9065
|
try {
|
|
9070
|
-
return JSON.parse(await
|
|
9066
|
+
return JSON.parse(await fsp3.readFile(file, "utf8"));
|
|
9071
9067
|
} catch {
|
|
9072
9068
|
return {};
|
|
9073
9069
|
}
|
|
@@ -9097,7 +9093,7 @@ async function upsertPlugin(spec, opts, deps, verb) {
|
|
|
9097
9093
|
};
|
|
9098
9094
|
existing.plugins = plugins;
|
|
9099
9095
|
existing.features = features;
|
|
9100
|
-
await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2));
|
|
9096
|
+
await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
9101
9097
|
return {
|
|
9102
9098
|
code: 0,
|
|
9103
9099
|
level: "info",
|
|
@@ -9114,7 +9110,7 @@ async function removePlugin(spec, deps) {
|
|
|
9114
9110
|
return errorResult(`Plugin "${spec}" not in config.`);
|
|
9115
9111
|
}
|
|
9116
9112
|
existing.plugins = next;
|
|
9117
|
-
await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2));
|
|
9113
|
+
await atomicWrite(deps.configPath, JSON.stringify(existing, null, 2), { mode: 384 });
|
|
9118
9114
|
return {
|
|
9119
9115
|
code: 0,
|
|
9120
9116
|
level: "info",
|
|
@@ -9160,7 +9156,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
9160
9156
|
var projectsCmd = async (_args, deps) => {
|
|
9161
9157
|
const projectsRoot = path25.join(deps.paths.globalRoot, "projects");
|
|
9162
9158
|
try {
|
|
9163
|
-
const entries = await
|
|
9159
|
+
const entries = await fsp3.readdir(projectsRoot);
|
|
9164
9160
|
if (entries.length === 0) {
|
|
9165
9161
|
deps.renderer.write("No projects tracked.\n");
|
|
9166
9162
|
return 0;
|
|
@@ -9168,7 +9164,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
9168
9164
|
for (const hash of entries) {
|
|
9169
9165
|
try {
|
|
9170
9166
|
const meta = JSON.parse(
|
|
9171
|
-
await
|
|
9167
|
+
await fsp3.readFile(path25.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
9172
9168
|
);
|
|
9173
9169
|
deps.renderer.write(
|
|
9174
9170
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -9319,7 +9315,7 @@ var sessionsFleetCmd = async (args, deps) => {
|
|
|
9319
9315
|
async function listFleetRuns(deps) {
|
|
9320
9316
|
let entries = [];
|
|
9321
9317
|
try {
|
|
9322
|
-
entries = await
|
|
9318
|
+
entries = await fsp3.readdir(deps.paths.projectSessions);
|
|
9323
9319
|
} catch {
|
|
9324
9320
|
deps.renderer.writeError(`Cannot read projectSessions: ${deps.paths.projectSessions}
|
|
9325
9321
|
`);
|
|
@@ -9330,7 +9326,7 @@ async function listFleetRuns(deps) {
|
|
|
9330
9326
|
const runDir = path25.join(deps.paths.projectSessions, id);
|
|
9331
9327
|
let stat3;
|
|
9332
9328
|
try {
|
|
9333
|
-
stat3 = await
|
|
9329
|
+
stat3 = await fsp3.stat(runDir);
|
|
9334
9330
|
} catch {
|
|
9335
9331
|
continue;
|
|
9336
9332
|
}
|
|
@@ -9340,18 +9336,18 @@ async function listFleetRuns(deps) {
|
|
|
9340
9336
|
let subagentCount = 0;
|
|
9341
9337
|
let subagentsDir;
|
|
9342
9338
|
try {
|
|
9343
|
-
await
|
|
9339
|
+
await fsp3.access(path25.join(runDir, "fleet.json"));
|
|
9344
9340
|
manifest = true;
|
|
9345
9341
|
} catch {
|
|
9346
9342
|
}
|
|
9347
9343
|
try {
|
|
9348
|
-
await
|
|
9344
|
+
await fsp3.access(path25.join(runDir, "checkpoint.json"));
|
|
9349
9345
|
checkpoint = true;
|
|
9350
9346
|
} catch {
|
|
9351
9347
|
}
|
|
9352
9348
|
try {
|
|
9353
9349
|
subagentsDir = path25.join(runDir, "subagents");
|
|
9354
|
-
const files = await
|
|
9350
|
+
const files = await fsp3.readdir(subagentsDir);
|
|
9355
9351
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
9356
9352
|
} catch {
|
|
9357
9353
|
}
|
|
@@ -9382,7 +9378,7 @@ async function showFleetRun(runId, deps) {
|
|
|
9382
9378
|
const runDir = path25.join(deps.paths.projectSessions, runId);
|
|
9383
9379
|
let stat3;
|
|
9384
9380
|
try {
|
|
9385
|
-
stat3 = await
|
|
9381
|
+
stat3 = await fsp3.stat(runDir);
|
|
9386
9382
|
} catch {
|
|
9387
9383
|
deps.renderer.writeError(`Fleet run not found: ${runId}
|
|
9388
9384
|
`);
|
|
@@ -9399,7 +9395,7 @@ Fleet Run: ${runId}
|
|
|
9399
9395
|
const manifestPath = path25.join(runDir, "fleet.json");
|
|
9400
9396
|
let manifestData = null;
|
|
9401
9397
|
try {
|
|
9402
|
-
manifestData = await
|
|
9398
|
+
manifestData = await fsp3.readFile(manifestPath, "utf8");
|
|
9403
9399
|
const manifest = JSON.parse(manifestData);
|
|
9404
9400
|
const subagents = manifest.subagents ?? [];
|
|
9405
9401
|
const tasks = manifest.tasks ?? [];
|
|
@@ -9415,12 +9411,12 @@ Fleet Run: ${runId}
|
|
|
9415
9411
|
const checkpointPath = path25.join(runDir, "checkpoint.json");
|
|
9416
9412
|
let checkpointData = null;
|
|
9417
9413
|
try {
|
|
9418
|
-
checkpointData = await
|
|
9414
|
+
checkpointData = await fsp3.readFile(checkpointPath, "utf8");
|
|
9419
9415
|
const snap = JSON.parse(checkpointData);
|
|
9420
9416
|
const lockPath = `${checkpointPath}.lock`;
|
|
9421
9417
|
let lockStatus = color.dim("\u25CB no lock");
|
|
9422
9418
|
try {
|
|
9423
|
-
const lockRaw = await
|
|
9419
|
+
const lockRaw = await fsp3.readFile(lockPath, "utf8");
|
|
9424
9420
|
const lock = JSON.parse(lockRaw);
|
|
9425
9421
|
lockStatus = `${color.yellow("\u25B8")} lock held by pid ${lock.pid} on ${lock.hostname} (started ${lock.startedAt})`;
|
|
9426
9422
|
} catch {
|
|
@@ -9462,7 +9458,7 @@ Fleet Run: ${runId}
|
|
|
9462
9458
|
const subagentsDir = path25.join(runDir, "subagents");
|
|
9463
9459
|
let subagentFiles = [];
|
|
9464
9460
|
try {
|
|
9465
|
-
subagentFiles = await
|
|
9461
|
+
subagentFiles = await fsp3.readdir(subagentsDir);
|
|
9466
9462
|
subagentFiles = subagentFiles.filter((f) => f.endsWith(".jsonl"));
|
|
9467
9463
|
} catch {
|
|
9468
9464
|
}
|
|
@@ -9474,7 +9470,7 @@ Fleet Run: ${runId}
|
|
|
9474
9470
|
const filePath = path25.join(subagentsDir, f);
|
|
9475
9471
|
let size;
|
|
9476
9472
|
try {
|
|
9477
|
-
const s = await
|
|
9473
|
+
const s = await fsp3.stat(filePath);
|
|
9478
9474
|
size = s.size;
|
|
9479
9475
|
} catch {
|
|
9480
9476
|
size = 0;
|
|
@@ -9490,7 +9486,7 @@ Fleet Run: ${runId}
|
|
|
9490
9486
|
}
|
|
9491
9487
|
const sharedDir = path25.join(runDir, "shared");
|
|
9492
9488
|
try {
|
|
9493
|
-
const files = await
|
|
9489
|
+
const files = await fsp3.readdir(sharedDir);
|
|
9494
9490
|
deps.renderer.write(`
|
|
9495
9491
|
Shared scratchpad: ${files.length} file(s)
|
|
9496
9492
|
`);
|
|
@@ -9787,7 +9783,7 @@ var skillsCmd = async (_args, deps) => {
|
|
|
9787
9783
|
};
|
|
9788
9784
|
var versionCmd = async (_args, deps) => {
|
|
9789
9785
|
deps.renderer.write(
|
|
9790
|
-
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${
|
|
9786
|
+
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os6.platform()})
|
|
9791
9787
|
`
|
|
9792
9788
|
);
|
|
9793
9789
|
return 0;
|
|
@@ -10977,6 +10973,43 @@ async function execute(deps) {
|
|
|
10977
10973
|
const banneredFamily = savedProviderCfg?.family ?? resolvedProvider?.family;
|
|
10978
10974
|
const banneredKey = savedProviderCfg?.apiKey ?? config.apiKey ?? (resolvedProvider?.envVars ?? savedProviderCfg?.envVars ?? []).map((v) => process.env[v]).find((v) => !!v);
|
|
10979
10975
|
const banneredKeyTail = banneredKey && banneredKey.length >= 3 ? banneredKey.slice(-3) : void 0;
|
|
10976
|
+
const autoPhaseHandlers = /* @__PURE__ */ new Map();
|
|
10977
|
+
const subscribeAutoPhase = (handler) => {
|
|
10978
|
+
const registrations = [];
|
|
10979
|
+
const autoPhaseEvents = [
|
|
10980
|
+
"phase.started",
|
|
10981
|
+
"phase.completed",
|
|
10982
|
+
"phase.failed",
|
|
10983
|
+
"phase.statusChange",
|
|
10984
|
+
"phase.taskCompleted",
|
|
10985
|
+
"phase.taskFailed",
|
|
10986
|
+
"phase.taskRetrying",
|
|
10987
|
+
"autonomous.tick",
|
|
10988
|
+
"graph.completed",
|
|
10989
|
+
"graph.failed",
|
|
10990
|
+
"agent.assigned",
|
|
10991
|
+
"agent.released",
|
|
10992
|
+
// Git-worktree isolation lifecycle → TUI worktree panel/monitor.
|
|
10993
|
+
"worktree.allocated",
|
|
10994
|
+
"worktree.committed",
|
|
10995
|
+
"worktree.merged",
|
|
10996
|
+
"worktree.conflict",
|
|
10997
|
+
"worktree.released",
|
|
10998
|
+
"worktree.failed"
|
|
10999
|
+
];
|
|
11000
|
+
const onUntyped = events.on.bind(events);
|
|
11001
|
+
const offUntyped = events.off.bind(events);
|
|
11002
|
+
for (const ev of autoPhaseEvents) {
|
|
11003
|
+
const h = (p) => handler(ev, p);
|
|
11004
|
+
autoPhaseHandlers.set(ev, h);
|
|
11005
|
+
onUntyped(ev, h);
|
|
11006
|
+
registrations.push(() => offUntyped(ev, h));
|
|
11007
|
+
}
|
|
11008
|
+
return () => {
|
|
11009
|
+
for (const unregister of registrations) unregister();
|
|
11010
|
+
autoPhaseHandlers.clear();
|
|
11011
|
+
};
|
|
11012
|
+
};
|
|
10980
11013
|
try {
|
|
10981
11014
|
code = await runTui({
|
|
10982
11015
|
agent,
|
|
@@ -10995,6 +11028,7 @@ async function execute(deps) {
|
|
|
10995
11028
|
getEternalEngine,
|
|
10996
11029
|
subscribeEternalIteration,
|
|
10997
11030
|
subscribeEternalStage,
|
|
11031
|
+
subscribeAutoPhase,
|
|
10998
11032
|
appVersion: CLI_VERSION,
|
|
10999
11033
|
provider: config.provider,
|
|
11000
11034
|
family: banneredFamily,
|
|
@@ -11279,8 +11313,9 @@ var MultiAgentHost = class {
|
|
|
11279
11313
|
return async (subCfg) => {
|
|
11280
11314
|
const events = new EventBus();
|
|
11281
11315
|
const provider = await this.buildSubagentProvider(config, subCfg.provider);
|
|
11316
|
+
const subCwd = subCfg.cwd ?? this.deps.cwd;
|
|
11282
11317
|
const baseSystem = await this.deps.systemPromptBuilder.build({
|
|
11283
|
-
cwd:
|
|
11318
|
+
cwd: subCwd,
|
|
11284
11319
|
projectRoot: this.deps.projectRoot,
|
|
11285
11320
|
tools: this.filterTools(subCfg.tools),
|
|
11286
11321
|
model: subCfg.model ?? config.model,
|
|
@@ -11316,7 +11351,7 @@ var MultiAgentHost = class {
|
|
|
11316
11351
|
session: subSession,
|
|
11317
11352
|
signal: new AbortController().signal,
|
|
11318
11353
|
tokenCounter: this.deps.tokenCounter,
|
|
11319
|
-
cwd:
|
|
11354
|
+
cwd: subCwd,
|
|
11320
11355
|
projectRoot: this.deps.projectRoot,
|
|
11321
11356
|
model: subCfg.model ?? config.model,
|
|
11322
11357
|
tools: this.filterTools(subCfg.tools)
|
|
@@ -11884,6 +11919,218 @@ function samplePaths(set) {
|
|
|
11884
11919
|
if (arr.length <= 2) return arr.join(", ");
|
|
11885
11920
|
return `${arr[0]}, \u2026 (+${arr.length - 1} more)`;
|
|
11886
11921
|
}
|
|
11922
|
+
var WORKTREE_PHASE_CONCURRENCY = 4;
|
|
11923
|
+
function gitText(args, cwd) {
|
|
11924
|
+
return new Promise((resolve4) => {
|
|
11925
|
+
let out = "";
|
|
11926
|
+
const child = spawn("git", args, { cwd, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"] });
|
|
11927
|
+
child.stdout?.on("data", (c) => {
|
|
11928
|
+
out += c.toString();
|
|
11929
|
+
});
|
|
11930
|
+
child.stderr?.on("data", (c) => {
|
|
11931
|
+
out += c.toString();
|
|
11932
|
+
});
|
|
11933
|
+
child.on("error", () => resolve4({ code: 1, out }));
|
|
11934
|
+
child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
|
|
11935
|
+
});
|
|
11936
|
+
}
|
|
11937
|
+
async function isGitRepo2(cwd) {
|
|
11938
|
+
const { code, out } = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
11939
|
+
return code === 0 && out.trim() === "true";
|
|
11940
|
+
}
|
|
11941
|
+
function createAutoPhaseHost(deps) {
|
|
11942
|
+
const store = new PhaseStore({ baseDir: deps.storeDir });
|
|
11943
|
+
let active = null;
|
|
11944
|
+
const log = deps.log ?? (() => {
|
|
11945
|
+
});
|
|
11946
|
+
async function runOnce(prompt, label, signal, cwd) {
|
|
11947
|
+
const factory = deps.multiAgentHost.makeSubagentFactory(deps.getConfig());
|
|
11948
|
+
const built = await factory({ name: label, cwd });
|
|
11949
|
+
try {
|
|
11950
|
+
const result = await built.agent.run(prompt, { signal });
|
|
11951
|
+
if (result.status !== "done") {
|
|
11952
|
+
throw new Error(result.error?.message ?? `subagent ended with status "${result.status}"`);
|
|
11953
|
+
}
|
|
11954
|
+
return result.finalText ?? "";
|
|
11955
|
+
} finally {
|
|
11956
|
+
await built.dispose?.();
|
|
11957
|
+
}
|
|
11958
|
+
}
|
|
11959
|
+
function buildTaskPrompt(task, phaseName, goal) {
|
|
11960
|
+
return [
|
|
11961
|
+
`You are executing one task inside an autonomous, phase-based build.`,
|
|
11962
|
+
`Overall goal: ${goal}`,
|
|
11963
|
+
`Current phase: ${phaseName}`,
|
|
11964
|
+
"",
|
|
11965
|
+
`TASK: ${task.title}`,
|
|
11966
|
+
task.description ? `Details: ${task.description}` : "",
|
|
11967
|
+
`Type: ${task.type} \xB7 Priority: ${task.priority}`,
|
|
11968
|
+
"",
|
|
11969
|
+
`Do the work now using your tools (read, edit, write, bash, \u2026). Make the`,
|
|
11970
|
+
`change real \u2014 do not just describe it. When finished, end with a one-line`,
|
|
11971
|
+
`summary of what you changed. If the task is impossible or already done,`,
|
|
11972
|
+
`say so explicitly.`
|
|
11973
|
+
].filter(Boolean).join("\n");
|
|
11974
|
+
}
|
|
11975
|
+
async function persist(graph) {
|
|
11976
|
+
try {
|
|
11977
|
+
await store.save(graph);
|
|
11978
|
+
} catch (err) {
|
|
11979
|
+
log(`\u26A0 AutoPhase save failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11980
|
+
}
|
|
11981
|
+
}
|
|
11982
|
+
return {
|
|
11983
|
+
async onAutoPhaseStart({ goal, projectContext }) {
|
|
11984
|
+
if (active?.orchestrator.isRunning()) {
|
|
11985
|
+
return { ok: false, error: "An AutoPhase run is already in progress. Use /autophase stop first." };
|
|
11986
|
+
}
|
|
11987
|
+
const abort = new AbortController();
|
|
11988
|
+
log(`\u{1F9E0} Planning phases for: ${goal}`);
|
|
11989
|
+
let phases;
|
|
11990
|
+
try {
|
|
11991
|
+
const planner = new AutoPhasePlanner({
|
|
11992
|
+
goal,
|
|
11993
|
+
projectContext,
|
|
11994
|
+
runOnce: (p) => runOnce(p, "autophase-planner", abort.signal)
|
|
11995
|
+
});
|
|
11996
|
+
const result = await planner.plan();
|
|
11997
|
+
if (result.parseFailed || result.phases.length === 0) {
|
|
11998
|
+
return { ok: false, error: "The planner did not produce a usable phase plan. Try a more specific goal." };
|
|
11999
|
+
}
|
|
12000
|
+
phases = result.phases;
|
|
12001
|
+
} catch (err) {
|
|
12002
|
+
return { ok: false, error: `Planning failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
12003
|
+
}
|
|
12004
|
+
const todoCount = phases.reduce((n, p) => n + (p.taskTemplates?.length ?? 0), 0);
|
|
12005
|
+
log(`\u{1F4CB} Plan ready: ${phases.length} phases, ${todoCount} todos.`);
|
|
12006
|
+
const graph = await new PhaseGraphBuilder({
|
|
12007
|
+
title: goal,
|
|
12008
|
+
phases,
|
|
12009
|
+
autonomous: true
|
|
12010
|
+
}).build();
|
|
12011
|
+
await persist(graph);
|
|
12012
|
+
const worktreesEnabled = deps.worktrees !== false && process.env["WRONGSTACK_AUTOPHASE_WORKTREES"] !== "0";
|
|
12013
|
+
let worktrees;
|
|
12014
|
+
if (worktreesEnabled && await isGitRepo2(deps.projectRoot)) {
|
|
12015
|
+
worktrees = new WorktreeManager({ projectRoot: deps.projectRoot, events: deps.events });
|
|
12016
|
+
log(`\u{1F33F} Worktree isolation on \u2014 up to ${deps.maxConcurrentPhases ?? WORKTREE_PHASE_CONCURRENCY} phases run in parallel.`);
|
|
12017
|
+
}
|
|
12018
|
+
const orchestrator = new PhaseOrchestrator({
|
|
12019
|
+
graph,
|
|
12020
|
+
ctx: {
|
|
12021
|
+
executeTask: async (task, phaseId, env) => {
|
|
12022
|
+
const phase = graph.phases.get(phaseId);
|
|
12023
|
+
const phaseName = phase?.name ?? phaseId;
|
|
12024
|
+
return runOnce(
|
|
12025
|
+
buildTaskPrompt(task, phaseName, goal),
|
|
12026
|
+
`autophase-${phaseName}-${task.title}`.slice(0, 48),
|
|
12027
|
+
abort.signal,
|
|
12028
|
+
env?.cwd
|
|
12029
|
+
);
|
|
12030
|
+
},
|
|
12031
|
+
onPhaseComplete: (phase) => {
|
|
12032
|
+
log(`\u2705 Phase completed: ${phase.name}`);
|
|
12033
|
+
void persist(graph);
|
|
12034
|
+
},
|
|
12035
|
+
onPhaseFail: (phase, error) => {
|
|
12036
|
+
log(`\u274C Phase failed: ${phase.name} \u2014 ${error.message}`);
|
|
12037
|
+
void persist(graph);
|
|
12038
|
+
}
|
|
12039
|
+
},
|
|
12040
|
+
events: deps.events,
|
|
12041
|
+
worktrees,
|
|
12042
|
+
autonomous: true,
|
|
12043
|
+
// With isolation, parallelizable phases run concurrently; without it,
|
|
12044
|
+
// stay strictly sequential to protect the shared working tree.
|
|
12045
|
+
maxConcurrentPhases: worktrees ? deps.maxConcurrentPhases ?? WORKTREE_PHASE_CONCURRENCY : 1,
|
|
12046
|
+
// Sequential within a phase: each todo is a full-tool agent and todos in
|
|
12047
|
+
// a phase typically build on one another (they share the phase worktree).
|
|
12048
|
+
maxConcurrentTasks: 1
|
|
12049
|
+
});
|
|
12050
|
+
const onUntyped = deps.events.on;
|
|
12051
|
+
const offUntyped = deps.events.off;
|
|
12052
|
+
const onDone = () => {
|
|
12053
|
+
log(`\u{1F389} AutoPhase complete: ${graph.title}`);
|
|
12054
|
+
void persist(graph);
|
|
12055
|
+
};
|
|
12056
|
+
const onFailed = () => void persist(graph);
|
|
12057
|
+
onUntyped("graph.completed", onDone);
|
|
12058
|
+
onUntyped("graph.failed", onFailed);
|
|
12059
|
+
const unsubscribe = () => {
|
|
12060
|
+
offUntyped("graph.completed", onDone);
|
|
12061
|
+
offUntyped("graph.failed", onFailed);
|
|
12062
|
+
};
|
|
12063
|
+
active = { graph, orchestrator, abort, unsubscribe };
|
|
12064
|
+
void orchestrator.start().catch((err) => {
|
|
12065
|
+
log(`\u{1F4A5} AutoPhase aborted: ${err instanceof Error ? err.message : String(err)}`);
|
|
12066
|
+
});
|
|
12067
|
+
return { ok: true, graph };
|
|
12068
|
+
},
|
|
12069
|
+
onAutoPhasePause() {
|
|
12070
|
+
active?.orchestrator.pause();
|
|
12071
|
+
},
|
|
12072
|
+
onAutoPhaseResume() {
|
|
12073
|
+
active?.orchestrator.resume();
|
|
12074
|
+
},
|
|
12075
|
+
onAutoPhaseStop() {
|
|
12076
|
+
if (!active) return;
|
|
12077
|
+
active.abort.abort();
|
|
12078
|
+
active.orchestrator.stop();
|
|
12079
|
+
active.unsubscribe();
|
|
12080
|
+
void persist(active.graph);
|
|
12081
|
+
active = null;
|
|
12082
|
+
},
|
|
12083
|
+
getAutoPhaseRunner() {
|
|
12084
|
+
if (!active) return null;
|
|
12085
|
+
const a = active;
|
|
12086
|
+
return {
|
|
12087
|
+
graph: a.graph,
|
|
12088
|
+
getProgress: () => a.orchestrator.getProgress(),
|
|
12089
|
+
isRunning: () => a.orchestrator.isRunning()
|
|
12090
|
+
};
|
|
12091
|
+
},
|
|
12092
|
+
async onWorktree(action, target) {
|
|
12093
|
+
const root = deps.projectRoot;
|
|
12094
|
+
if (!await isGitRepo2(root)) return "\u26A0 Not a git repository \u2014 worktrees unavailable.";
|
|
12095
|
+
switch (action) {
|
|
12096
|
+
case "list": {
|
|
12097
|
+
const { out } = await gitText(["worktree", "list"], root);
|
|
12098
|
+
return out || "No worktrees.";
|
|
12099
|
+
}
|
|
12100
|
+
case "prune": {
|
|
12101
|
+
await gitText(["worktree", "prune"], root);
|
|
12102
|
+
const { out } = await gitText(["worktree", "list"], root);
|
|
12103
|
+
return `Pruned stale worktree entries.
|
|
12104
|
+
${out}`;
|
|
12105
|
+
}
|
|
12106
|
+
case "merge": {
|
|
12107
|
+
if (!target) return "Usage: /worktree merge <branch>";
|
|
12108
|
+
if (target.startsWith("-")) return `Refusing unsafe branch name: ${target}`;
|
|
12109
|
+
const base = (await gitText(["rev-parse", "--abbrev-ref", "HEAD"], root)).out || "HEAD";
|
|
12110
|
+
await gitText(["merge", "--squash", target], root);
|
|
12111
|
+
const commit = await gitText(["commit", "-m", `merge ${target} (squash)`], root);
|
|
12112
|
+
if (commit.code !== 0 && !/nothing to commit/i.test(commit.out)) {
|
|
12113
|
+
await gitText(["reset", "--hard", "HEAD"], root);
|
|
12114
|
+
return `\u26A0 Merge of "${target}" into ${base} hit conflicts and was rolled back.
|
|
12115
|
+
${commit.out}`;
|
|
12116
|
+
}
|
|
12117
|
+
return `\u2713 Merged "${target}" into ${base} (squash).`;
|
|
12118
|
+
}
|
|
12119
|
+
case "clean": {
|
|
12120
|
+
const list = (await gitText(["worktree", "list", "--porcelain"], root)).out;
|
|
12121
|
+
const dirs = list.split("\n").filter((l) => l.startsWith("worktree ")).map((l) => l.slice("worktree ".length)).filter((d) => d.includes(".wrongstack") && d.includes("worktrees"));
|
|
12122
|
+
for (const d of dirs) await gitText(["worktree", "remove", "--force", d], root);
|
|
12123
|
+
await gitText(["worktree", "prune"], root);
|
|
12124
|
+
const branches = (await gitText(["branch", "--list", "wstack/ap/*"], root)).out.split("\n").map((b) => b.replace(/^[*+]?\s*/, "").trim()).filter(Boolean);
|
|
12125
|
+
for (const b of branches) await gitText(["branch", "-D", b], root);
|
|
12126
|
+
return `\u{1F9F9} Removed ${dirs.length} worktree(s) and ${branches.length} branch(es).`;
|
|
12127
|
+
}
|
|
12128
|
+
default:
|
|
12129
|
+
return `Unknown worktree action: ${action}`;
|
|
12130
|
+
}
|
|
12131
|
+
}
|
|
12132
|
+
};
|
|
12133
|
+
}
|
|
11887
12134
|
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
11888
12135
|
var FILLED2 = "\u2588";
|
|
11889
12136
|
var EMPTY2 = "\u2591";
|
|
@@ -12051,7 +12298,7 @@ function setupMetrics(params) {
|
|
|
12051
12298
|
name: "session-store",
|
|
12052
12299
|
check: async () => {
|
|
12053
12300
|
try {
|
|
12054
|
-
await
|
|
12301
|
+
await fsp3.access(wpaths.projectSessions);
|
|
12055
12302
|
return { status: "healthy" };
|
|
12056
12303
|
} catch (e) {
|
|
12057
12304
|
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
@@ -12707,6 +12954,15 @@ async function main(argv) {
|
|
|
12707
12954
|
this.visible = visible;
|
|
12708
12955
|
}
|
|
12709
12956
|
};
|
|
12957
|
+
const autoPhaseHost = createAutoPhaseHost({
|
|
12958
|
+
multiAgentHost,
|
|
12959
|
+
getConfig: () => config,
|
|
12960
|
+
events,
|
|
12961
|
+
storeDir: wpaths.projectAutophase,
|
|
12962
|
+
projectRoot,
|
|
12963
|
+
log: (line) => renderer.write(`${line}
|
|
12964
|
+
`)
|
|
12965
|
+
});
|
|
12710
12966
|
const slashCmds = buildBuiltinSlashCommands({
|
|
12711
12967
|
registry: slashRegistry,
|
|
12712
12968
|
toolRegistry,
|
|
@@ -12716,6 +12972,7 @@ async function main(argv) {
|
|
|
12716
12972
|
skillLoader,
|
|
12717
12973
|
tokenCounter,
|
|
12718
12974
|
renderer,
|
|
12975
|
+
events,
|
|
12719
12976
|
memoryStore,
|
|
12720
12977
|
context,
|
|
12721
12978
|
cwd,
|
|
@@ -12944,7 +13201,7 @@ async function main(argv) {
|
|
|
12944
13201
|
const subagentsRoot = path25.join(fleetRootForPromotion, "subagents");
|
|
12945
13202
|
let runDirs;
|
|
12946
13203
|
try {
|
|
12947
|
-
runDirs = await
|
|
13204
|
+
runDirs = await fsp3.readdir(subagentsRoot);
|
|
12948
13205
|
} catch {
|
|
12949
13206
|
return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
|
|
12950
13207
|
}
|
|
@@ -12953,7 +13210,7 @@ async function main(argv) {
|
|
|
12953
13210
|
const runDir = path25.join(subagentsRoot, runId);
|
|
12954
13211
|
let files;
|
|
12955
13212
|
try {
|
|
12956
|
-
files = await
|
|
13213
|
+
files = await fsp3.readdir(runDir);
|
|
12957
13214
|
} catch {
|
|
12958
13215
|
continue;
|
|
12959
13216
|
}
|
|
@@ -12961,7 +13218,7 @@ async function main(argv) {
|
|
|
12961
13218
|
if (!f.endsWith(".jsonl")) continue;
|
|
12962
13219
|
const full = path25.join(runDir, f);
|
|
12963
13220
|
try {
|
|
12964
|
-
const stat3 = await
|
|
13221
|
+
const stat3 = await fsp3.stat(full);
|
|
12965
13222
|
found.push({
|
|
12966
13223
|
runId,
|
|
12967
13224
|
subagentId: f.replace(/\.jsonl$/, ""),
|
|
@@ -13000,7 +13257,7 @@ async function main(argv) {
|
|
|
13000
13257
|
].join("\n");
|
|
13001
13258
|
}
|
|
13002
13259
|
const t = matches[0];
|
|
13003
|
-
const raw = await
|
|
13260
|
+
const raw = await fsp3.readFile(t.file, "utf8");
|
|
13004
13261
|
if (mode === "raw") return raw;
|
|
13005
13262
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
13006
13263
|
const counts = {};
|
|
@@ -13226,10 +13483,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
13226
13483
|
void mcpRegistry.stopAll();
|
|
13227
13484
|
},
|
|
13228
13485
|
onBeforeExit: async () => {
|
|
13229
|
-
const { spawn:
|
|
13486
|
+
const { spawn: spawn4 } = await import('child_process');
|
|
13230
13487
|
const cwd2 = projectRoot;
|
|
13231
13488
|
const statusResult = await new Promise((resolve4, reject) => {
|
|
13232
|
-
const child =
|
|
13489
|
+
const child = spawn4("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
|
|
13233
13490
|
let stdout = "";
|
|
13234
13491
|
child.stdout?.on("data", (d) => {
|
|
13235
13492
|
stdout += d;
|
|
@@ -13332,7 +13589,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
13332
13589
|
onSddParallelStop: () => {
|
|
13333
13590
|
const run = globalThis.__sddParallelRun;
|
|
13334
13591
|
run?.stop();
|
|
13335
|
-
}
|
|
13592
|
+
},
|
|
13593
|
+
onAutoPhaseStart: autoPhaseHost.onAutoPhaseStart,
|
|
13594
|
+
onAutoPhasePause: autoPhaseHost.onAutoPhasePause,
|
|
13595
|
+
onAutoPhaseResume: autoPhaseHost.onAutoPhaseResume,
|
|
13596
|
+
onAutoPhaseStop: autoPhaseHost.onAutoPhaseStop,
|
|
13597
|
+
getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner,
|
|
13598
|
+
onWorktree: autoPhaseHost.onWorktree
|
|
13336
13599
|
});
|
|
13337
13600
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
13338
13601
|
const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";
|