@wrongstack/cli 0.8.5 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +142 -18
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as path25 from 'path';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
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, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, 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';
|
|
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
8
|
import * as os6 from 'os';
|
|
9
9
|
import os6__default from 'os';
|
|
@@ -6141,6 +6141,51 @@ function buildAutoPhaseCommand(opts) {
|
|
|
6141
6141
|
};
|
|
6142
6142
|
}
|
|
6143
6143
|
|
|
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." };
|
|
6165
|
+
}
|
|
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
|
+
};
|
|
6184
|
+
}
|
|
6185
|
+
}
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
6188
|
+
|
|
6144
6189
|
// src/slash-commands/index.ts
|
|
6145
6190
|
function buildBuiltinSlashCommands(opts) {
|
|
6146
6191
|
return [
|
|
@@ -6183,6 +6228,7 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
6183
6228
|
buildSecurityCommand(opts),
|
|
6184
6229
|
buildFixCommand(opts),
|
|
6185
6230
|
buildAutoPhaseCommand(opts),
|
|
6231
|
+
buildWorktreeCommand(opts),
|
|
6186
6232
|
buildStatuslineCommand({
|
|
6187
6233
|
cwd: opts.cwd,
|
|
6188
6234
|
hiddenItems: opts.statuslineHiddenItems ?? [],
|
|
@@ -6291,9 +6337,9 @@ async function runProjectCheck(opts) {
|
|
|
6291
6337
|
}
|
|
6292
6338
|
if (answer2 === "y" || answer2 === "yes") {
|
|
6293
6339
|
try {
|
|
6294
|
-
const { spawn:
|
|
6340
|
+
const { spawn: spawn4 } = await import('child_process');
|
|
6295
6341
|
await new Promise((resolve4, reject) => {
|
|
6296
|
-
const child =
|
|
6342
|
+
const child = spawn4("git", ["init"], { cwd: projectRoot });
|
|
6297
6343
|
child.on("error", reject);
|
|
6298
6344
|
child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
|
|
6299
6345
|
});
|
|
@@ -9606,7 +9652,7 @@ var rewindCmd = async (args, deps) => {
|
|
|
9606
9652
|
const flags = parseRewindFlags(args);
|
|
9607
9653
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
9608
9654
|
const sessionsDir = path25.join(wpaths.globalRoot, "sessions");
|
|
9609
|
-
const rewind = new DefaultSessionRewinder(sessionsDir);
|
|
9655
|
+
const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
|
|
9610
9656
|
let sessionId = findSessionId(args);
|
|
9611
9657
|
if (!sessionId) {
|
|
9612
9658
|
if (!deps.sessionStore) {
|
|
@@ -10942,7 +10988,14 @@ async function execute(deps) {
|
|
|
10942
10988
|
"graph.completed",
|
|
10943
10989
|
"graph.failed",
|
|
10944
10990
|
"agent.assigned",
|
|
10945
|
-
"agent.released"
|
|
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"
|
|
10946
10999
|
];
|
|
10947
11000
|
const onUntyped = events.on.bind(events);
|
|
10948
11001
|
const offUntyped = events.off.bind(events);
|
|
@@ -11260,8 +11313,9 @@ var MultiAgentHost = class {
|
|
|
11260
11313
|
return async (subCfg) => {
|
|
11261
11314
|
const events = new EventBus();
|
|
11262
11315
|
const provider = await this.buildSubagentProvider(config, subCfg.provider);
|
|
11316
|
+
const subCwd = subCfg.cwd ?? this.deps.cwd;
|
|
11263
11317
|
const baseSystem = await this.deps.systemPromptBuilder.build({
|
|
11264
|
-
cwd:
|
|
11318
|
+
cwd: subCwd,
|
|
11265
11319
|
projectRoot: this.deps.projectRoot,
|
|
11266
11320
|
tools: this.filterTools(subCfg.tools),
|
|
11267
11321
|
model: subCfg.model ?? config.model,
|
|
@@ -11297,7 +11351,7 @@ var MultiAgentHost = class {
|
|
|
11297
11351
|
session: subSession,
|
|
11298
11352
|
signal: new AbortController().signal,
|
|
11299
11353
|
tokenCounter: this.deps.tokenCounter,
|
|
11300
|
-
cwd:
|
|
11354
|
+
cwd: subCwd,
|
|
11301
11355
|
projectRoot: this.deps.projectRoot,
|
|
11302
11356
|
model: subCfg.model ?? config.model,
|
|
11303
11357
|
tools: this.filterTools(subCfg.tools)
|
|
@@ -11865,14 +11919,33 @@ function samplePaths(set) {
|
|
|
11865
11919
|
if (arr.length <= 2) return arr.join(", ");
|
|
11866
11920
|
return `${arr[0]}, \u2026 (+${arr.length - 1} more)`;
|
|
11867
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
|
+
}
|
|
11868
11941
|
function createAutoPhaseHost(deps) {
|
|
11869
11942
|
const store = new PhaseStore({ baseDir: deps.storeDir });
|
|
11870
11943
|
let active = null;
|
|
11871
11944
|
const log = deps.log ?? (() => {
|
|
11872
11945
|
});
|
|
11873
|
-
async function runOnce(prompt, label, signal) {
|
|
11946
|
+
async function runOnce(prompt, label, signal, cwd) {
|
|
11874
11947
|
const factory = deps.multiAgentHost.makeSubagentFactory(deps.getConfig());
|
|
11875
|
-
const built = await factory({ name: label });
|
|
11948
|
+
const built = await factory({ name: label, cwd });
|
|
11876
11949
|
try {
|
|
11877
11950
|
const result = await built.agent.run(prompt, { signal });
|
|
11878
11951
|
if (result.status !== "done") {
|
|
@@ -11936,16 +12009,23 @@ function createAutoPhaseHost(deps) {
|
|
|
11936
12009
|
autonomous: true
|
|
11937
12010
|
}).build();
|
|
11938
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
|
+
}
|
|
11939
12018
|
const orchestrator = new PhaseOrchestrator({
|
|
11940
12019
|
graph,
|
|
11941
12020
|
ctx: {
|
|
11942
|
-
executeTask: async (task, phaseId) => {
|
|
12021
|
+
executeTask: async (task, phaseId, env) => {
|
|
11943
12022
|
const phase = graph.phases.get(phaseId);
|
|
11944
12023
|
const phaseName = phase?.name ?? phaseId;
|
|
11945
12024
|
return runOnce(
|
|
11946
12025
|
buildTaskPrompt(task, phaseName, goal),
|
|
11947
12026
|
`autophase-${phaseName}-${task.title}`.slice(0, 48),
|
|
11948
|
-
abort.signal
|
|
12027
|
+
abort.signal,
|
|
12028
|
+
env?.cwd
|
|
11949
12029
|
);
|
|
11950
12030
|
},
|
|
11951
12031
|
onPhaseComplete: (phase) => {
|
|
@@ -11958,11 +12038,13 @@ function createAutoPhaseHost(deps) {
|
|
|
11958
12038
|
}
|
|
11959
12039
|
},
|
|
11960
12040
|
events: deps.events,
|
|
12041
|
+
worktrees,
|
|
11961
12042
|
autonomous: true,
|
|
11962
|
-
|
|
11963
|
-
//
|
|
11964
|
-
|
|
11965
|
-
//
|
|
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).
|
|
11966
12048
|
maxConcurrentTasks: 1
|
|
11967
12049
|
});
|
|
11968
12050
|
const onUntyped = deps.events.on;
|
|
@@ -12006,6 +12088,46 @@ function createAutoPhaseHost(deps) {
|
|
|
12006
12088
|
getProgress: () => a.orchestrator.getProgress(),
|
|
12007
12089
|
isRunning: () => a.orchestrator.isRunning()
|
|
12008
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
|
+
}
|
|
12009
12131
|
}
|
|
12010
12132
|
};
|
|
12011
12133
|
}
|
|
@@ -12837,6 +12959,7 @@ async function main(argv) {
|
|
|
12837
12959
|
getConfig: () => config,
|
|
12838
12960
|
events,
|
|
12839
12961
|
storeDir: wpaths.projectAutophase,
|
|
12962
|
+
projectRoot,
|
|
12840
12963
|
log: (line) => renderer.write(`${line}
|
|
12841
12964
|
`)
|
|
12842
12965
|
});
|
|
@@ -13360,10 +13483,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
13360
13483
|
void mcpRegistry.stopAll();
|
|
13361
13484
|
},
|
|
13362
13485
|
onBeforeExit: async () => {
|
|
13363
|
-
const { spawn:
|
|
13486
|
+
const { spawn: spawn4 } = await import('child_process');
|
|
13364
13487
|
const cwd2 = projectRoot;
|
|
13365
13488
|
const statusResult = await new Promise((resolve4, reject) => {
|
|
13366
|
-
const child =
|
|
13489
|
+
const child = spawn4("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
|
|
13367
13490
|
let stdout = "";
|
|
13368
13491
|
child.stdout?.on("data", (d) => {
|
|
13369
13492
|
stdout += d;
|
|
@@ -13471,7 +13594,8 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
13471
13594
|
onAutoPhasePause: autoPhaseHost.onAutoPhasePause,
|
|
13472
13595
|
onAutoPhaseResume: autoPhaseHost.onAutoPhaseResume,
|
|
13473
13596
|
onAutoPhaseStop: autoPhaseHost.onAutoPhaseStop,
|
|
13474
|
-
getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner
|
|
13597
|
+
getAutoPhaseRunner: autoPhaseHost.getAutoPhaseRunner,
|
|
13598
|
+
onWorktree: autoPhaseHost.onWorktree
|
|
13475
13599
|
});
|
|
13476
13600
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
13477
13601
|
const eternalFlag = typeof flags["eternal"] === "string" ? flags["eternal"].trim() : "";
|