@wrongstack/cli 0.141.0 → 0.148.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 +1151 -194
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { color, writeErr, renderProgress, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, expectDefined, DefaultTaskStore, TaskTracker, renderTaskGraph, DefaultSecretScrubber, DefaultPathResolver, EventBus, TOKENS, mergeCustomModelDefs, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, resolveSessionLoggingConfig, createSessionEventBridge, HookRegistry, HookRunner, SlashCommandRegistry, SessionMemoryConsolidator, BrainDecisionQueue, ObservableBrainArbiter, HumanEscalatingBrainArbiter, DefaultBrainArbiter, createDelegateTool, FLEET_ROSTER, createMcpControlTool, SpecVersioning, atomicWrite, DefaultLogger, DefaultModelsRegistry, isStdinTTY, writeOut, runProviderWithRetry, ReplayLogStore, ReplayProviderRunner, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, DEFAULT_SESSION_PRUNE_DAYS, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, resolveContextWindowPolicy, resolveAuditLevel, AutoCompactionMiddleware, estimateRequestTokensCalibrated, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, makeFleetEmitTool, makeFleetStatusTool, resolveModelMatrix, DEFAULT_SUBAGENT_BASELINE, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, ParallelEternalEngine, EternalAutonomyEngine, allServers as allServers$1, decryptConfigSecrets as decryptConfigSecrets$1, encryptConfigSecrets as encryptConfigSecrets$1, bootConfig as bootConfig$1, setOutputLineGuard, setRawMode, DefaultSessionReader, resolveWstackPaths, ToolAuditLog, DefaultSessionRewinder, DefaultSessionStore, DefaultPluginAPI, StreamHangError, ProviderError, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, loadTasks, emptyTaskFile, saveTasks, formatTaskProgress, formatTaskList, SessionRecovery, loadGoal, goalFilePath, summarizeUsage, saveGoal, formatGoal, emptyGoal, buildGoalPreamble, pendingBtwCount, setBtwNote, MATRIX_PHASE_KEYS, AGENT_CATALOG, matrixKeyKind, phaseForRole, onResize, ERROR_CODES, InputBuilder, FsError, estimateMessageTokens } from '@wrongstack/core';
|
|
3
3
|
import * as fsp4 from 'fs/promises';
|
|
4
4
|
import { DefaultSecretVault, decryptConfigSecrets, encryptConfigSecrets, isSecretField } from '@wrongstack/core/security';
|
|
5
|
-
import * as
|
|
5
|
+
import * as path10 from 'path';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import * as os2 from 'os';
|
|
@@ -11,14 +11,14 @@ import * as crypto2 from 'crypto';
|
|
|
11
11
|
import { randomUUID } from 'crypto';
|
|
12
12
|
import { findFreePort, createHttpServer, openBrowser, registerInstance, unregisterInstance } from '@wrongstack/webui/server';
|
|
13
13
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
14
|
-
import { spawn } from 'child_process';
|
|
14
|
+
import { spawn, exec, execFileSync } from 'child_process';
|
|
15
15
|
import { MCPRegistry, MCPServer, serveHttp, serveStdio } from '@wrongstack/mcp';
|
|
16
16
|
import { capabilitiesFor, buildProviderFactoriesFromRegistry, makeProviderFromConfig } from '@wrongstack/providers';
|
|
17
17
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
18
18
|
import { builtinToolsPack, rememberTool, forgetTool, searchMemoryTool, relatedMemoryTool, runStartupIndex, isIndexableFile, enqueueReindex, cancelPendingReindexes } from '@wrongstack/tools';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
20
|
import * as readline from 'readline';
|
|
21
|
-
import * as
|
|
21
|
+
import * as fs15 from 'fs';
|
|
22
22
|
import { writeFileSync, existsSync, readFileSync } from 'fs';
|
|
23
23
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
24
24
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
@@ -96,7 +96,7 @@ async function loadConfigProviders(configPath2, vault, opts) {
|
|
|
96
96
|
}
|
|
97
97
|
async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
98
98
|
let raw;
|
|
99
|
-
let
|
|
99
|
+
let fileExists2 = true;
|
|
100
100
|
try {
|
|
101
101
|
raw = await fsp4.readFile(configPath2, "utf8");
|
|
102
102
|
} catch (err) {
|
|
@@ -106,14 +106,14 @@ async function mutateConfigProviders(configPath2, vault, mutator) {
|
|
|
106
106
|
{ cause: err }
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
fileExists2 = false;
|
|
110
110
|
raw = "{}";
|
|
111
111
|
}
|
|
112
112
|
let parsed;
|
|
113
113
|
try {
|
|
114
114
|
parsed = JSON.parse(raw);
|
|
115
115
|
} catch (err) {
|
|
116
|
-
if (
|
|
116
|
+
if (fileExists2) {
|
|
117
117
|
throw new Error(
|
|
118
118
|
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}). Fix or move the file aside before retrying.`,
|
|
119
119
|
{ cause: err }
|
|
@@ -386,7 +386,7 @@ async function findSpec(store, idOrTitle) {
|
|
|
386
386
|
async function gatherProjectContext2(projectRoot) {
|
|
387
387
|
const parts = [];
|
|
388
388
|
try {
|
|
389
|
-
const pkgPath =
|
|
389
|
+
const pkgPath = path10.join(projectRoot, "package.json");
|
|
390
390
|
const pkgRaw = await fsp4.readFile(pkgPath, "utf8");
|
|
391
391
|
const pkg = JSON.parse(pkgRaw);
|
|
392
392
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -402,13 +402,13 @@ async function gatherProjectContext2(projectRoot) {
|
|
|
402
402
|
} catch {
|
|
403
403
|
}
|
|
404
404
|
try {
|
|
405
|
-
const tsconfigPath =
|
|
405
|
+
const tsconfigPath = path10.join(projectRoot, "tsconfig.json");
|
|
406
406
|
await fsp4.access(tsconfigPath);
|
|
407
407
|
parts.push("Language: TypeScript");
|
|
408
408
|
} catch {
|
|
409
409
|
}
|
|
410
410
|
try {
|
|
411
|
-
const srcDir =
|
|
411
|
+
const srcDir = path10.join(projectRoot, "src");
|
|
412
412
|
const entries = await fsp4.readdir(srcDir, { withFileTypes: true });
|
|
413
413
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
414
414
|
if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
@@ -1602,7 +1602,7 @@ __export(update_check_exports, {
|
|
|
1602
1602
|
getUpdateNotification: () => getUpdateNotification
|
|
1603
1603
|
});
|
|
1604
1604
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
1605
|
-
return
|
|
1605
|
+
return path10.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
1606
1606
|
}
|
|
1607
1607
|
function currentVersion() {
|
|
1608
1608
|
const req2 = createRequire(import.meta.url);
|
|
@@ -1639,7 +1639,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1639
1639
|
}
|
|
1640
1640
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1641
1641
|
try {
|
|
1642
|
-
const dir =
|
|
1642
|
+
const dir = path10.dirname(cachePath(homeFn));
|
|
1643
1643
|
await fsp4.mkdir(dir, { recursive: true });
|
|
1644
1644
|
await fsp4.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1645
1645
|
} catch {
|
|
@@ -1746,7 +1746,7 @@ async function runWebUI(opts) {
|
|
|
1746
1746
|
try {
|
|
1747
1747
|
const requireFromHere = createRequire(import.meta.url);
|
|
1748
1748
|
const serverEntry = requireFromHere.resolve("@wrongstack/webui/server");
|
|
1749
|
-
const distDir =
|
|
1749
|
+
const distDir = path10.resolve(path10.dirname(serverEntry), "..");
|
|
1750
1750
|
httpServer = createHttpServer({ host, distDir, wsPort });
|
|
1751
1751
|
const openUrl = `http://${host}:${httpPort}`;
|
|
1752
1752
|
httpServer?.listen(httpPort, host, () => {
|
|
@@ -1763,7 +1763,7 @@ async function runWebUI(opts) {
|
|
|
1763
1763
|
`[WebUI] Frontend not served (run \`pnpm --filter @wrongstack/webui build\`): ${err instanceof Error ? err.message : String(err)}. WS bridge still active on ws://${host}:${wsPort}.`
|
|
1764
1764
|
);
|
|
1765
1765
|
}
|
|
1766
|
-
const registryBaseDir = opts.globalConfigPath ?
|
|
1766
|
+
const registryBaseDir = opts.globalConfigPath ? path10.dirname(opts.globalConfigPath) : void 0;
|
|
1767
1767
|
if (opts.projectRoot) {
|
|
1768
1768
|
void registerInstance(
|
|
1769
1769
|
{
|
|
@@ -1772,7 +1772,7 @@ async function runWebUI(opts) {
|
|
|
1772
1772
|
wsPort,
|
|
1773
1773
|
host,
|
|
1774
1774
|
projectRoot: opts.projectRoot,
|
|
1775
|
-
projectName:
|
|
1775
|
+
projectName: path10.basename(opts.projectRoot) || opts.projectRoot,
|
|
1776
1776
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1777
1777
|
url: `http://${host}:${httpPort}`
|
|
1778
1778
|
},
|
|
@@ -2159,15 +2159,12 @@ async function runWebUI(opts) {
|
|
|
2159
2159
|
await handleProviderRemove(ws, m.payload.providerId);
|
|
2160
2160
|
break;
|
|
2161
2161
|
}
|
|
2162
|
-
default:
|
|
2163
|
-
|
|
2164
|
-
type:
|
|
2165
|
-
|
|
2166
|
-
phase: "ws.dispatch",
|
|
2167
|
-
message: `Unknown message type: ${String(msg.type)}`
|
|
2168
|
-
}
|
|
2169
|
-
});
|
|
2162
|
+
default: {
|
|
2163
|
+
console.debug(
|
|
2164
|
+
`[WebUI] Unhandled message type: ${String(msg.type)}`
|
|
2165
|
+
);
|
|
2170
2166
|
break;
|
|
2167
|
+
}
|
|
2171
2168
|
}
|
|
2172
2169
|
}
|
|
2173
2170
|
async function handleUserMessage(ws, _client, content) {
|
|
@@ -2408,7 +2405,7 @@ async function runWebUI(opts) {
|
|
|
2408
2405
|
}
|
|
2409
2406
|
}
|
|
2410
2407
|
function getVault() {
|
|
2411
|
-
const keyFile =
|
|
2408
|
+
const keyFile = path10.join(path10.dirname(opts.globalConfigPath ?? ""), ".key");
|
|
2412
2409
|
return new DefaultSecretVault({ keyFile });
|
|
2413
2410
|
}
|
|
2414
2411
|
async function loadSavedProviders() {
|
|
@@ -2709,10 +2706,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2709
2706
|
const name = declared.split("@")[0];
|
|
2710
2707
|
if (name) return name;
|
|
2711
2708
|
}
|
|
2712
|
-
if (await pathExists(
|
|
2713
|
-
if (await pathExists(
|
|
2714
|
-
if (await pathExists(
|
|
2715
|
-
if (await pathExists(
|
|
2709
|
+
if (await pathExists(path10.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2710
|
+
if (await pathExists(path10.join(root, "bun.lockb"))) return "bun";
|
|
2711
|
+
if (await pathExists(path10.join(root, "bun.lock"))) return "bun";
|
|
2712
|
+
if (await pathExists(path10.join(root, "yarn.lock"))) return "yarn";
|
|
2716
2713
|
return "npm";
|
|
2717
2714
|
}
|
|
2718
2715
|
function hasUsableScript(scripts, name) {
|
|
@@ -2733,7 +2730,7 @@ function parseMakeTargets(makefile) {
|
|
|
2733
2730
|
async function detectProjectFacts(root) {
|
|
2734
2731
|
const facts = { hints: [] };
|
|
2735
2732
|
try {
|
|
2736
|
-
const pkg = JSON.parse(await fsp4.readFile(
|
|
2733
|
+
const pkg = JSON.parse(await fsp4.readFile(path10.join(root, "package.json"), "utf8"));
|
|
2737
2734
|
const scripts = pkg.scripts ?? {};
|
|
2738
2735
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2739
2736
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2747,14 +2744,14 @@ async function detectProjectFacts(root) {
|
|
|
2747
2744
|
} catch {
|
|
2748
2745
|
}
|
|
2749
2746
|
try {
|
|
2750
|
-
if (!await pathExists(
|
|
2747
|
+
if (!await pathExists(path10.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2751
2748
|
facts.test ??= "pytest";
|
|
2752
2749
|
facts.lint ??= "ruff check .";
|
|
2753
2750
|
facts.hints.push("pyproject.toml");
|
|
2754
2751
|
} catch {
|
|
2755
2752
|
}
|
|
2756
2753
|
try {
|
|
2757
|
-
if (!await pathExists(
|
|
2754
|
+
if (!await pathExists(path10.join(root, "go.mod"))) throw new Error("not go");
|
|
2758
2755
|
facts.build ??= "go build ./...";
|
|
2759
2756
|
facts.test ??= "go test ./...";
|
|
2760
2757
|
facts.run ??= "go run .";
|
|
@@ -2762,7 +2759,7 @@ async function detectProjectFacts(root) {
|
|
|
2762
2759
|
} catch {
|
|
2763
2760
|
}
|
|
2764
2761
|
try {
|
|
2765
|
-
if (!await pathExists(
|
|
2762
|
+
if (!await pathExists(path10.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2766
2763
|
facts.build ??= "cargo build";
|
|
2767
2764
|
facts.test ??= "cargo test";
|
|
2768
2765
|
facts.lint ??= "cargo clippy";
|
|
@@ -2771,7 +2768,7 @@ async function detectProjectFacts(root) {
|
|
|
2771
2768
|
} catch {
|
|
2772
2769
|
}
|
|
2773
2770
|
try {
|
|
2774
|
-
const makefile = await fsp4.readFile(
|
|
2771
|
+
const makefile = await fsp4.readFile(path10.join(root, "Makefile"), "utf8");
|
|
2775
2772
|
const targets = parseMakeTargets(makefile);
|
|
2776
2773
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2777
2774
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -3066,7 +3063,7 @@ function formatPhaseList(graph) {
|
|
|
3066
3063
|
}
|
|
3067
3064
|
async function gatherProjectContext(projectRoot) {
|
|
3068
3065
|
try {
|
|
3069
|
-
const raw = await fsp4.readFile(
|
|
3066
|
+
const raw = await fsp4.readFile(path10.join(projectRoot, "package.json"), "utf8");
|
|
3070
3067
|
const pkg = JSON.parse(raw);
|
|
3071
3068
|
const parts = [
|
|
3072
3069
|
`Project: ${String(pkg.name ?? "unknown")}`,
|
|
@@ -3964,6 +3961,94 @@ function formatLimit(limit) {
|
|
|
3964
3961
|
function pct(n) {
|
|
3965
3962
|
return `${Math.round(n * 100)}%`;
|
|
3966
3963
|
}
|
|
3964
|
+
var DEFAULT_TIMEOUT_MS = 6e4;
|
|
3965
|
+
var MAX_OUTPUT_LINES = 500;
|
|
3966
|
+
function runCommand(cmd, cwd, timeout) {
|
|
3967
|
+
return new Promise((resolve5) => {
|
|
3968
|
+
exec(
|
|
3969
|
+
cmd,
|
|
3970
|
+
{
|
|
3971
|
+
cwd,
|
|
3972
|
+
timeout,
|
|
3973
|
+
maxBuffer: 2 * 1024 * 1024,
|
|
3974
|
+
// 2 MB
|
|
3975
|
+
windowsHide: true
|
|
3976
|
+
},
|
|
3977
|
+
(error, stdout, stderr) => {
|
|
3978
|
+
resolve5({
|
|
3979
|
+
stdout,
|
|
3980
|
+
stderr,
|
|
3981
|
+
exitCode: error?.code ?? 0,
|
|
3982
|
+
killed: error?.killed ?? false
|
|
3983
|
+
});
|
|
3984
|
+
}
|
|
3985
|
+
);
|
|
3986
|
+
});
|
|
3987
|
+
}
|
|
3988
|
+
function formatOutput(cmd, result, elapsed) {
|
|
3989
|
+
const lines = [];
|
|
3990
|
+
const exitLabel = result.killed ? color.red("TIMEOUT") : result.exitCode === 0 ? color.green("OK") : color.red(`EXIT ${result.exitCode}`);
|
|
3991
|
+
lines.push(`${color.cyan("$")} ${color.bold(cmd)} ${exitLabel} ${color.dim(`${elapsed}ms`)}`);
|
|
3992
|
+
const combined = (result.stdout + result.stderr).trimEnd();
|
|
3993
|
+
if (combined) {
|
|
3994
|
+
const outputLines = combined.split("\n");
|
|
3995
|
+
const truncated = outputLines.length > MAX_OUTPUT_LINES;
|
|
3996
|
+
const shown = truncated ? outputLines.slice(0, MAX_OUTPUT_LINES) : outputLines;
|
|
3997
|
+
lines.push("");
|
|
3998
|
+
lines.push(color.dim("\u2500\u2500"));
|
|
3999
|
+
for (const line of shown) {
|
|
4000
|
+
lines.push(line);
|
|
4001
|
+
}
|
|
4002
|
+
if (truncated) {
|
|
4003
|
+
lines.push(color.dim(`\u2026 (truncated, showing first ${MAX_OUTPUT_LINES} of ${outputLines.length} lines)`));
|
|
4004
|
+
}
|
|
4005
|
+
lines.push(color.dim("\u2500\u2500"));
|
|
4006
|
+
} else {
|
|
4007
|
+
lines.push(color.dim("(no output)"));
|
|
4008
|
+
}
|
|
4009
|
+
return lines.join("\n");
|
|
4010
|
+
}
|
|
4011
|
+
function buildDevCommand(opts) {
|
|
4012
|
+
return {
|
|
4013
|
+
name: "dev",
|
|
4014
|
+
category: "Run",
|
|
4015
|
+
description: "Run a shell command and see the output (LLM does not see it).",
|
|
4016
|
+
argsHint: "<shell command>",
|
|
4017
|
+
help: [
|
|
4018
|
+
"Usage:",
|
|
4019
|
+
" /dev <shell command> Run a command from the chat input.",
|
|
4020
|
+
"",
|
|
4021
|
+
"Examples:",
|
|
4022
|
+
" /dev pnpm release:check",
|
|
4023
|
+
" /dev git diff --stat",
|
|
4024
|
+
" /dev ls -la src/",
|
|
4025
|
+
"",
|
|
4026
|
+
"The command runs in the current working directory. Output is displayed",
|
|
4027
|
+
"in the chat history but is NOT fed to the LLM \u2014 use this for your own",
|
|
4028
|
+
"eyes only. Timeout: 60s. Max output: 500 lines.",
|
|
4029
|
+
"",
|
|
4030
|
+
"This is a convenience shortcut \u2014 equivalent to switching to a terminal",
|
|
4031
|
+
"tab. For commands the LLM should see, use the `exec` tool instead."
|
|
4032
|
+
].join("\n"),
|
|
4033
|
+
async run(args, _ctx) {
|
|
4034
|
+
const cmd = args.trim();
|
|
4035
|
+
if (!cmd) {
|
|
4036
|
+
return { message: `${color.yellow("Usage:")} /dev <shell command>
|
|
4037
|
+
|
|
4038
|
+
Examples:
|
|
4039
|
+
/dev pnpm release:check
|
|
4040
|
+
/dev git diff --stat` };
|
|
4041
|
+
}
|
|
4042
|
+
const cwd = opts.cwd;
|
|
4043
|
+
const startedAt = Date.now();
|
|
4044
|
+
opts.renderer.write(color.dim(`$ ${cmd}`));
|
|
4045
|
+
const result = await runCommand(cmd, cwd, DEFAULT_TIMEOUT_MS);
|
|
4046
|
+
const elapsed = Date.now() - startedAt;
|
|
4047
|
+
const display = formatOutput(cmd, result, elapsed);
|
|
4048
|
+
return { message: display };
|
|
4049
|
+
}
|
|
4050
|
+
};
|
|
4051
|
+
}
|
|
3967
4052
|
|
|
3968
4053
|
// src/slash-commands/diag-stats.ts
|
|
3969
4054
|
function buildDiagCommand(opts) {
|
|
@@ -3997,7 +4082,7 @@ function resolvePersistPath(deps) {
|
|
|
3997
4082
|
return deps.globalConfigPath;
|
|
3998
4083
|
}
|
|
3999
4084
|
async function ensureProjectDir(filePath) {
|
|
4000
|
-
const dir =
|
|
4085
|
+
const dir = path10.dirname(filePath);
|
|
4001
4086
|
try {
|
|
4002
4087
|
await fsp4.mkdir(dir, { recursive: true });
|
|
4003
4088
|
} catch {
|
|
@@ -4036,21 +4121,21 @@ async function persistAutonomySetting(deps, mutator) {
|
|
|
4036
4121
|
const targetPath = resolvePersistPath(deps);
|
|
4037
4122
|
await ensureProjectDir(targetPath);
|
|
4038
4123
|
let raw;
|
|
4039
|
-
let
|
|
4124
|
+
let fileExists2 = true;
|
|
4040
4125
|
try {
|
|
4041
4126
|
raw = await fsp4.readFile(targetPath, "utf8");
|
|
4042
4127
|
} catch (err) {
|
|
4043
4128
|
if (err.code !== "ENOENT") {
|
|
4044
4129
|
throw new Error(`Could not read ${targetPath}: ${err.message}`);
|
|
4045
4130
|
}
|
|
4046
|
-
|
|
4131
|
+
fileExists2 = false;
|
|
4047
4132
|
raw = "{}";
|
|
4048
4133
|
}
|
|
4049
4134
|
let parsed;
|
|
4050
4135
|
try {
|
|
4051
4136
|
parsed = JSON.parse(raw);
|
|
4052
4137
|
} catch (err) {
|
|
4053
|
-
if (
|
|
4138
|
+
if (fileExists2) {
|
|
4054
4139
|
throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
|
|
4055
4140
|
}
|
|
4056
4141
|
parsed = {};
|
|
@@ -4073,21 +4158,21 @@ async function persistConfigSetting(deps, mutator) {
|
|
|
4073
4158
|
const targetPath = resolvePersistPath(deps);
|
|
4074
4159
|
await ensureProjectDir(targetPath);
|
|
4075
4160
|
let raw;
|
|
4076
|
-
let
|
|
4161
|
+
let fileExists2 = true;
|
|
4077
4162
|
try {
|
|
4078
4163
|
raw = await fsp4.readFile(targetPath, "utf8");
|
|
4079
4164
|
} catch (err) {
|
|
4080
4165
|
if (err.code !== "ENOENT") {
|
|
4081
4166
|
throw new Error(`Could not read ${targetPath}: ${err.message}`);
|
|
4082
4167
|
}
|
|
4083
|
-
|
|
4168
|
+
fileExists2 = false;
|
|
4084
4169
|
raw = "{}";
|
|
4085
4170
|
}
|
|
4086
4171
|
let parsed;
|
|
4087
4172
|
try {
|
|
4088
4173
|
parsed = JSON.parse(raw);
|
|
4089
4174
|
} catch (err) {
|
|
4090
|
-
if (
|
|
4175
|
+
if (fileExists2) {
|
|
4091
4176
|
throw new Error(`Config at ${targetPath} is not valid JSON: ${err.message}`);
|
|
4092
4177
|
}
|
|
4093
4178
|
parsed = {};
|
|
@@ -4106,21 +4191,21 @@ async function persistConfigSetting(deps, mutator) {
|
|
|
4106
4191
|
}
|
|
4107
4192
|
async function persistTelegramConfig(deps, mutator) {
|
|
4108
4193
|
let raw;
|
|
4109
|
-
let
|
|
4194
|
+
let fileExists2 = true;
|
|
4110
4195
|
try {
|
|
4111
4196
|
raw = await fsp4.readFile(deps.globalConfigPath, "utf8");
|
|
4112
4197
|
} catch (err) {
|
|
4113
4198
|
if (err.code !== "ENOENT") {
|
|
4114
4199
|
throw new Error(`Could not read ${deps.globalConfigPath}: ${err.message}`);
|
|
4115
4200
|
}
|
|
4116
|
-
|
|
4201
|
+
fileExists2 = false;
|
|
4117
4202
|
raw = "{}";
|
|
4118
4203
|
}
|
|
4119
4204
|
let parsed;
|
|
4120
4205
|
try {
|
|
4121
4206
|
parsed = JSON.parse(raw);
|
|
4122
4207
|
} catch (err) {
|
|
4123
|
-
if (
|
|
4208
|
+
if (fileExists2) {
|
|
4124
4209
|
throw new Error(`Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`);
|
|
4125
4210
|
}
|
|
4126
4211
|
parsed = {};
|
|
@@ -4214,6 +4299,7 @@ function buildEnhanceCommand(opts) {
|
|
|
4214
4299
|
|
|
4215
4300
|
// src/slash-commands/fix-classifier.ts
|
|
4216
4301
|
var TS = ["typescript-strict"];
|
|
4302
|
+
var TC = ["tech-stack"];
|
|
4217
4303
|
var BH = ["bug-hunter"];
|
|
4218
4304
|
var SS = ["security-scanner"];
|
|
4219
4305
|
var NM = ["node-modern"];
|
|
@@ -4588,6 +4674,90 @@ var P = [
|
|
|
4588
4674
|
detail: "JavaScript runtime error",
|
|
4589
4675
|
conf: 0.95
|
|
4590
4676
|
},
|
|
4677
|
+
// ── Tech stack validation — BEFORE generic dep patterns ─────────────
|
|
4678
|
+
// Technology choice questions: "should I use X?", "is Y deprecated?", etc.
|
|
4679
|
+
{
|
|
4680
|
+
pat: /\b(should I (use|install|add|pick|choose)|is .+ (still (good|maintained|supported|relevant)|deprecated|dead|obsolete|outdated)|what replaces|alternative to|instead of)\b/i,
|
|
4681
|
+
cat: "tech",
|
|
4682
|
+
sub: "tech-choice",
|
|
4683
|
+
lang: void 0,
|
|
4684
|
+
hints: TC,
|
|
4685
|
+
detail: "Technology choice validation",
|
|
4686
|
+
conf: 0.85
|
|
4687
|
+
},
|
|
4688
|
+
{
|
|
4689
|
+
pat: /\bwhat (is the latest|are the latest|version of|versions of)|what version|upgrade to latest|downgrade to|which version of\b/i,
|
|
4690
|
+
cat: "tech",
|
|
4691
|
+
sub: "version-check",
|
|
4692
|
+
lang: void 0,
|
|
4693
|
+
hints: TC,
|
|
4694
|
+
detail: "Version verification needed",
|
|
4695
|
+
conf: 0.9
|
|
4696
|
+
},
|
|
4697
|
+
{
|
|
4698
|
+
pat: /\b(adding|installing|using|switching to|migrating to) (a |an |the )?(package|dependency|library|module|framework|gem|crate)\b/i,
|
|
4699
|
+
cat: "tech",
|
|
4700
|
+
sub: "tech-choice",
|
|
4701
|
+
lang: void 0,
|
|
4702
|
+
hints: TC,
|
|
4703
|
+
detail: "Technology choice validation",
|
|
4704
|
+
conf: 0.8
|
|
4705
|
+
},
|
|
4706
|
+
// Commands that imply tech choices: "pip install X", "cargo add X", etc.
|
|
4707
|
+
{
|
|
4708
|
+
pat: /\b(pip install|pip3 install|pipenv install|poetry add|uv add)\s+[a-zA-Z0-9_-]+/i,
|
|
4709
|
+
cat: "tech",
|
|
4710
|
+
sub: "python-pkg",
|
|
4711
|
+
lang: "python",
|
|
4712
|
+
hints: TC,
|
|
4713
|
+
detail: "Python package choice \u2014 validate before installing",
|
|
4714
|
+
conf: 0.85
|
|
4715
|
+
},
|
|
4716
|
+
{
|
|
4717
|
+
pat: /\b(cargo add|cargo install)\s+[a-zA-Z0-9_-]+/i,
|
|
4718
|
+
cat: "tech",
|
|
4719
|
+
sub: "rust-crate",
|
|
4720
|
+
lang: "rust",
|
|
4721
|
+
hints: TC,
|
|
4722
|
+
detail: "Rust crate choice \u2014 validate before installing",
|
|
4723
|
+
conf: 0.85
|
|
4724
|
+
},
|
|
4725
|
+
{
|
|
4726
|
+
pat: /\b(go get|go install)\s+[a-zA-Z0-9_./-]+/i,
|
|
4727
|
+
cat: "tech",
|
|
4728
|
+
sub: "go-module",
|
|
4729
|
+
lang: "go",
|
|
4730
|
+
hints: TC,
|
|
4731
|
+
detail: "Go module choice \u2014 validate before installing",
|
|
4732
|
+
conf: 0.85
|
|
4733
|
+
},
|
|
4734
|
+
{
|
|
4735
|
+
pat: /\b(gem install|bundle add)\s+[a-zA-Z0-9_-]+/i,
|
|
4736
|
+
cat: "tech",
|
|
4737
|
+
sub: "ruby-gem",
|
|
4738
|
+
lang: "ruby",
|
|
4739
|
+
hints: TC,
|
|
4740
|
+
detail: "Ruby gem choice \u2014 validate before installing",
|
|
4741
|
+
conf: 0.85
|
|
4742
|
+
},
|
|
4743
|
+
{
|
|
4744
|
+
pat: /\b(npm install|pnpm add|yarn add)\s+[a-zA-Z0-9@/_-]+/i,
|
|
4745
|
+
cat: "tech",
|
|
4746
|
+
sub: "js-pkg",
|
|
4747
|
+
lang: "javascript",
|
|
4748
|
+
hints: TC,
|
|
4749
|
+
detail: "JS package choice \u2014 validate before installing",
|
|
4750
|
+
conf: 0.85
|
|
4751
|
+
},
|
|
4752
|
+
{
|
|
4753
|
+
pat: /\b(composer require|nuget install|dotnet add package)\s+[a-zA-Z0-9./_-]+/i,
|
|
4754
|
+
cat: "tech",
|
|
4755
|
+
sub: "pkg-choice",
|
|
4756
|
+
lang: void 0,
|
|
4757
|
+
hints: TC,
|
|
4758
|
+
detail: "Package choice \u2014 validate before installing",
|
|
4759
|
+
conf: 0.85
|
|
4760
|
+
},
|
|
4591
4761
|
// ── Dependency / Import ───────────────────────────────────────────────
|
|
4592
4762
|
{
|
|
4593
4763
|
pat: /\bcannot find module|modulenotfounderror|no such module|missing module/i,
|
|
@@ -4724,7 +4894,7 @@ function needsSubagent(c) {
|
|
|
4724
4894
|
return c.confidence < 0.85;
|
|
4725
4895
|
}
|
|
4726
4896
|
function isSimpleFix(c) {
|
|
4727
|
-
return c.category === "ts" && c.confidence >= 0.9 || c.category === "runtime" && c.subcategory === "null-undefined-access" && c.confidence >= 0.85;
|
|
4897
|
+
return c.category === "ts" && c.confidence >= 0.9 || c.category === "runtime" && c.subcategory === "null-undefined-access" && c.confidence >= 0.85 || c.category === "tech" && c.confidence >= 0.85;
|
|
4728
4898
|
}
|
|
4729
4899
|
|
|
4730
4900
|
// src/slash-commands/fix.ts
|
|
@@ -4820,6 +4990,22 @@ function buildDirective(cli, errorText) {
|
|
|
4820
4990
|
"3. Fix the config",
|
|
4821
4991
|
"4. Verify"
|
|
4822
4992
|
].join("\n");
|
|
4993
|
+
case "tech":
|
|
4994
|
+
return [
|
|
4995
|
+
`## Tech Stack Validation${lang}`,
|
|
4996
|
+
"",
|
|
4997
|
+
"```",
|
|
4998
|
+
`${errorText}`,
|
|
4999
|
+
"```",
|
|
5000
|
+
"",
|
|
5001
|
+
"Your task:",
|
|
5002
|
+
"1. Detect the ecosystem from project files or context",
|
|
5003
|
+
"2. Verify the package/version against the correct registry",
|
|
5004
|
+
"3. Check if the package is dead, deprecated, or superseded (prehistoric)",
|
|
5005
|
+
"4. Check if the language standard library already covers this need",
|
|
5006
|
+
"5. Report APPROVED (with install command) or REJECTED (with replacement + migration)",
|
|
5007
|
+
`6. Use "This isn't code, this is X-year-old technology" when rejecting on age grounds`
|
|
5008
|
+
].join("\n");
|
|
4823
5009
|
case "perf":
|
|
4824
5010
|
return [
|
|
4825
5011
|
`## Fix: Performance / Memory Issue${lang}`,
|
|
@@ -4859,6 +5045,8 @@ function delegateRoleFor(cli) {
|
|
|
4859
5045
|
return "security-scanner";
|
|
4860
5046
|
case "perf":
|
|
4861
5047
|
return "refactor-planner";
|
|
5048
|
+
case "tech":
|
|
5049
|
+
return "tech-stack";
|
|
4862
5050
|
default:
|
|
4863
5051
|
return "bug-hunter";
|
|
4864
5052
|
}
|
|
@@ -4905,7 +5093,8 @@ Docker, Git, CI/CD, and more.
|
|
|
4905
5093
|
| Security / secrets | Any | \`security-scanner\` |
|
|
4906
5094
|
| Compiler error | Rust, Go, C/C++, Python | \`bug-hunter\` |
|
|
4907
5095
|
| Dependency / import | Any | \`bug-hunter\` |
|
|
4908
|
-
|
|
|
5096
|
+
| Tech choice / pkg | Any | \`tech-stack\` |
|
|
5097
|
+
| Performance / leak | Any | \`bug-hunter\` + \`refactor-planner\` |
|
|
4909
5098
|
| Infrastructure | Config, Docker, Git, CI | \`bug-hunter\` |
|
|
4910
5099
|
| React / Next.js | JavaScript | \`react-modern\` |
|
|
4911
5100
|
| Node.js | JavaScript | \`node-modern\` |
|
|
@@ -4927,6 +5116,10 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
|
4927
5116
|
/fix react-dom.development.js:172 Error: Invalid hook call
|
|
4928
5117
|
/fix Security: hardcoded API key in config.ts
|
|
4929
5118
|
/fix ERRO1014: SQL injection vulnerability in query builder
|
|
5119
|
+
/fix Should I use axios for API calls?
|
|
5120
|
+
/fix is moment.js still maintained?
|
|
5121
|
+
/fix pip install urllib2
|
|
5122
|
+
/fix what replaces lodash?
|
|
4930
5123
|
\`\`\`
|
|
4931
5124
|
`,
|
|
4932
5125
|
async run(args, _ctx) {
|
|
@@ -4948,6 +5141,7 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
|
4948
5141
|
` /fix ${color.dim("AttributeError: 'NoneType' object has no attribute 'encode'")}`,
|
|
4949
5142
|
` /fix ${color.dim("react-dom.development.js:172 Error: Invalid hook call")}`,
|
|
4950
5143
|
` /fix ${color.dim("Security: hardcoded API key in config.ts")}`,
|
|
5144
|
+
` /fix ${color.dim("Should I use axios for API calls?")}`,
|
|
4951
5145
|
"",
|
|
4952
5146
|
"Run `/help fix` for full documentation."
|
|
4953
5147
|
].join("\n")
|
|
@@ -5746,28 +5940,52 @@ function buildInitCommand(opts) {
|
|
|
5746
5940
|
category: "Config",
|
|
5747
5941
|
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
5748
5942
|
async run(_args, ctx) {
|
|
5749
|
-
const dir =
|
|
5750
|
-
const file =
|
|
5943
|
+
const dir = path10.join(ctx.projectRoot, ".wrongstack");
|
|
5944
|
+
const file = path10.join(dir, "AGENTS.md");
|
|
5945
|
+
const isFirstInit = !await fileExists(file);
|
|
5751
5946
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
5752
5947
|
const body = renderAgentsTemplate(detected);
|
|
5753
5948
|
await fsp4.mkdir(dir, { recursive: true });
|
|
5754
5949
|
await fsp4.writeFile(file, body, "utf8");
|
|
5950
|
+
let nodePkg = false;
|
|
5951
|
+
try {
|
|
5952
|
+
await fsp4.access(path10.join(ctx.projectRoot, "package.json"));
|
|
5953
|
+
nodePkg = true;
|
|
5954
|
+
} catch {
|
|
5955
|
+
}
|
|
5956
|
+
const lines = [];
|
|
5957
|
+
lines.push(`Wrote ${file}`);
|
|
5755
5958
|
if (detected.hints.length > 0) {
|
|
5756
|
-
const msg2 = `Wrote ${file}
|
|
5757
|
-
Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
5758
5959
|
opts.renderer.writeInfo(`Wrote ${file}`);
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
);
|
|
5762
|
-
|
|
5960
|
+
const hintLine = `Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
5961
|
+
opts.renderer.writeInfo(hintLine);
|
|
5962
|
+
lines.push(hintLine);
|
|
5963
|
+
} else {
|
|
5964
|
+
opts.renderer.writeInfo(`Wrote ${file}`);
|
|
5965
|
+
lines.push("No project type auto-detected. Edit the file with project context and instructions the system prompt should carry.");
|
|
5763
5966
|
}
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5967
|
+
if (nodePkg && isFirstInit) {
|
|
5968
|
+
const techHint = [
|
|
5969
|
+
"",
|
|
5970
|
+
`${color.cyan("\u{1F4A1}")} ${color.bold("Tech Stack Audit")} \u2014 This is a Node.js project with a fresh init.`,
|
|
5971
|
+
color.dim(" The LLM may have suggested stale version numbers. Run"),
|
|
5972
|
+
` ${color.cyan("/techstack --init")} to scan dependencies and verify versions.`
|
|
5973
|
+
].join("\n");
|
|
5974
|
+
opts.renderer.write(techHint);
|
|
5975
|
+
lines.push(techHint);
|
|
5976
|
+
}
|
|
5977
|
+
return { message: lines.join("\n") };
|
|
5768
5978
|
}
|
|
5769
5979
|
};
|
|
5770
5980
|
}
|
|
5981
|
+
async function fileExists(filePath) {
|
|
5982
|
+
try {
|
|
5983
|
+
await fsp4.access(filePath);
|
|
5984
|
+
return true;
|
|
5985
|
+
} catch {
|
|
5986
|
+
return false;
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5771
5989
|
function parseMcpArgs(args) {
|
|
5772
5990
|
const trimmed = args.trim();
|
|
5773
5991
|
if (!trimmed || trimmed === "list") return { action: "list", name: "" };
|
|
@@ -5962,9 +6180,9 @@ function stateBadge(state) {
|
|
|
5962
6180
|
return color.dim(state);
|
|
5963
6181
|
}
|
|
5964
6182
|
}
|
|
5965
|
-
async function readConfig(
|
|
6183
|
+
async function readConfig(path28) {
|
|
5966
6184
|
try {
|
|
5967
|
-
return JSON.parse(await fsp4.readFile(
|
|
6185
|
+
return JSON.parse(await fsp4.readFile(path28, "utf8"));
|
|
5968
6186
|
} catch {
|
|
5969
6187
|
return {};
|
|
5970
6188
|
}
|
|
@@ -5972,11 +6190,11 @@ async function readConfig(path27) {
|
|
|
5972
6190
|
function isMcpServerRecord(value) {
|
|
5973
6191
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5974
6192
|
}
|
|
5975
|
-
async function writeConfig(
|
|
6193
|
+
async function writeConfig(path28, cfg) {
|
|
5976
6194
|
const raw = JSON.stringify(cfg, null, 2);
|
|
5977
|
-
const tmp =
|
|
6195
|
+
const tmp = path28 + ".tmp";
|
|
5978
6196
|
await fsp4.writeFile(tmp, raw, "utf8");
|
|
5979
|
-
await fsp4.rename(tmp,
|
|
6197
|
+
await fsp4.rename(tmp, path28);
|
|
5980
6198
|
}
|
|
5981
6199
|
|
|
5982
6200
|
// src/slash-commands/mcp.ts
|
|
@@ -6506,18 +6724,18 @@ var noOpVault2 = {
|
|
|
6506
6724
|
};
|
|
6507
6725
|
async function patchGlobalConfig(globalConfigPath, mutate) {
|
|
6508
6726
|
let raw = "{}";
|
|
6509
|
-
let
|
|
6727
|
+
let fileExists2 = true;
|
|
6510
6728
|
try {
|
|
6511
6729
|
raw = await fsp4.readFile(globalConfigPath, "utf8");
|
|
6512
6730
|
} catch (err) {
|
|
6513
6731
|
if (err.code !== "ENOENT") throw err;
|
|
6514
|
-
|
|
6732
|
+
fileExists2 = false;
|
|
6515
6733
|
}
|
|
6516
6734
|
let parsed;
|
|
6517
6735
|
try {
|
|
6518
6736
|
parsed = JSON.parse(raw);
|
|
6519
6737
|
} catch (err) {
|
|
6520
|
-
if (
|
|
6738
|
+
if (fileExists2) {
|
|
6521
6739
|
throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
|
|
6522
6740
|
}
|
|
6523
6741
|
parsed = {};
|
|
@@ -6724,31 +6942,51 @@ function buildModelsCommand(opts) {
|
|
|
6724
6942
|
function buildNextCommand(opts) {
|
|
6725
6943
|
return {
|
|
6726
6944
|
name: "next",
|
|
6727
|
-
category: "
|
|
6728
|
-
description: "
|
|
6729
|
-
argsHint: "[on|off|toggle]",
|
|
6945
|
+
category: "Agent",
|
|
6946
|
+
description: "Show or select next-step suggestions. /next 1 to execute, /next list to view.",
|
|
6947
|
+
argsHint: "[on|off|toggle|list|clear|1 2 3...]",
|
|
6730
6948
|
help: [
|
|
6731
6949
|
"Usage:",
|
|
6732
|
-
" /next
|
|
6733
|
-
" /next on
|
|
6734
|
-
" /next off
|
|
6735
|
-
" /next toggle
|
|
6950
|
+
" /next Show whether next-task prediction is on or off",
|
|
6951
|
+
" /next on Enable \u2014 after each turn, show 1-3 predicted next steps",
|
|
6952
|
+
" /next off Disable (default)",
|
|
6953
|
+
" /next toggle Flip the current state",
|
|
6954
|
+
" /next list Show the current suggestion list",
|
|
6955
|
+
" /next clear Clear the suggestion list",
|
|
6956
|
+
" /next 1 Execute suggestion #1 as the next agent turn",
|
|
6957
|
+
" /next 1 2 3 Execute suggestions 1, 2, and 3 in sequence",
|
|
6958
|
+
" /next 1,2,3 Same \u2014 comma separators work too",
|
|
6736
6959
|
"",
|
|
6737
|
-
"
|
|
6738
|
-
|
|
6739
|
-
"
|
|
6960
|
+
"Suggestions are generated automatically after each turn (when prediction is",
|
|
6961
|
+
`on) or manually via ${color.cyan("/suggest")}. Selected suggestions execute`,
|
|
6962
|
+
"immediately \u2014 no refinement or classification step."
|
|
6740
6963
|
].join("\n"),
|
|
6741
6964
|
async run(args) {
|
|
6965
|
+
const trimmed = args.trim();
|
|
6966
|
+
if (/^\d[\d,\s]*$/.test(trimmed) && /\d/.test(trimmed)) {
|
|
6967
|
+
return handleSelection(trimmed, opts);
|
|
6968
|
+
}
|
|
6969
|
+
const arg = trimmed.toLowerCase();
|
|
6970
|
+
if (arg === "list" || arg === "ls" || arg === "show") {
|
|
6971
|
+
return handleList(opts);
|
|
6972
|
+
}
|
|
6973
|
+
if (arg === "clear" || arg === "reset") {
|
|
6974
|
+
opts.onSuggestions?.([]);
|
|
6975
|
+
return { message: color.dim("Suggestion list cleared.") };
|
|
6976
|
+
}
|
|
6742
6977
|
if (!opts.onNextPredict) {
|
|
6743
6978
|
const msg2 = "Next-task prediction is not available in this session.";
|
|
6744
6979
|
opts.renderer.writeWarning(msg2);
|
|
6745
6980
|
return { message: msg2 };
|
|
6746
6981
|
}
|
|
6747
|
-
const arg = args.trim().toLowerCase();
|
|
6748
6982
|
const current = opts.onNextPredict();
|
|
6749
6983
|
const label = (on) => on ? `${color.cyan("ON")} ${color.dim("(predicted next steps shown after each turn)")}` : `${color.green("OFF")} ${color.dim("(no predictions)")}`;
|
|
6750
6984
|
if (!arg || arg === "status") {
|
|
6751
|
-
const
|
|
6985
|
+
const suggestions = opts.onSuggestions?.() ?? [];
|
|
6986
|
+
const msg2 = [
|
|
6987
|
+
`Next-task prediction: ${label(current)}`,
|
|
6988
|
+
suggestions.length > 0 ? color.dim(` ${suggestions.length} suggestion(s) available \u2014 use /next list to view, /next 1 to execute`) : color.dim(" No suggestions stored \u2014 use /suggest to generate")
|
|
6989
|
+
].join("\n");
|
|
6752
6990
|
opts.renderer.write(msg2);
|
|
6753
6991
|
return { message: msg2 };
|
|
6754
6992
|
}
|
|
@@ -6760,7 +6998,7 @@ function buildNextCommand(opts) {
|
|
|
6760
6998
|
} else if (arg === "toggle" || arg === "cycle") {
|
|
6761
6999
|
target = !current;
|
|
6762
7000
|
} else {
|
|
6763
|
-
const msg2 = `Unknown argument: ${arg}. Use /next on, off, or
|
|
7001
|
+
const msg2 = `Unknown argument: ${arg}. Use /next on, off, toggle, list, clear, or a number (e.g. /next 1).`;
|
|
6764
7002
|
opts.renderer.writeWarning(msg2);
|
|
6765
7003
|
return { message: msg2 };
|
|
6766
7004
|
}
|
|
@@ -6771,6 +7009,67 @@ function buildNextCommand(opts) {
|
|
|
6771
7009
|
}
|
|
6772
7010
|
};
|
|
6773
7011
|
}
|
|
7012
|
+
function handleSelection(input, opts) {
|
|
7013
|
+
const parts = input.split(/[\s,]+/).filter(Boolean);
|
|
7014
|
+
const indices = parts.map((p) => Number.parseInt(p, 10)).filter((n) => !Number.isNaN(n) && n > 0);
|
|
7015
|
+
if (indices.length === 0) {
|
|
7016
|
+
return { message: color.amber("No valid suggestion numbers found. Use /next 1, /next 1 2 3, etc.") };
|
|
7017
|
+
}
|
|
7018
|
+
const suggestions = opts.onSuggestions?.() ?? [];
|
|
7019
|
+
if (suggestions.length === 0) {
|
|
7020
|
+
return {
|
|
7021
|
+
message: color.amber("No suggestions available. Run /suggest first, or enable prediction with /next on.")
|
|
7022
|
+
};
|
|
7023
|
+
}
|
|
7024
|
+
const invalid = indices.filter((i) => i > suggestions.length);
|
|
7025
|
+
if (invalid.length > 0) {
|
|
7026
|
+
const max = suggestions.length;
|
|
7027
|
+
return {
|
|
7028
|
+
message: color.amber(`Invalid suggestion number(s): ${invalid.join(", ")}. Valid range: 1\u2013${max}.`)
|
|
7029
|
+
};
|
|
7030
|
+
}
|
|
7031
|
+
const selected = indices.map((i) => suggestions[i - 1]).filter((s) => s !== void 0);
|
|
7032
|
+
if (selected.length === 1) {
|
|
7033
|
+
const text = selected[0] ?? "";
|
|
7034
|
+
return {
|
|
7035
|
+
message: `${color.green("\u25B6")} Executing suggestion #${indices[0]}: ${color.dim(text)}`,
|
|
7036
|
+
runText: text
|
|
7037
|
+
};
|
|
7038
|
+
}
|
|
7039
|
+
const tasks = selected.map((s, i) => `${i + 1}. ${s}`).join("\n");
|
|
7040
|
+
const runText = [
|
|
7041
|
+
`## Execute the following tasks in order`,
|
|
7042
|
+
"",
|
|
7043
|
+
tasks,
|
|
7044
|
+
"",
|
|
7045
|
+
"Complete each task before moving to the next. Report results as you go."
|
|
7046
|
+
].join("\n");
|
|
7047
|
+
return {
|
|
7048
|
+
message: `${color.green("\u25B6")} Executing ${selected.length} tasks: ${indices.join(", ")}`,
|
|
7049
|
+
runText
|
|
7050
|
+
};
|
|
7051
|
+
}
|
|
7052
|
+
function handleList(opts) {
|
|
7053
|
+
const suggestions = opts.onSuggestions?.() ?? [];
|
|
7054
|
+
if (suggestions.length === 0) {
|
|
7055
|
+
return {
|
|
7056
|
+
message: [
|
|
7057
|
+
color.dim("No suggestions available."),
|
|
7058
|
+
"",
|
|
7059
|
+
`Generate suggestions with ${color.cyan("/suggest")} or enable`,
|
|
7060
|
+
`prediction with ${color.cyan("/next on")}.`
|
|
7061
|
+
].join("\n")
|
|
7062
|
+
};
|
|
7063
|
+
}
|
|
7064
|
+
const lines = [
|
|
7065
|
+
` ${color.cyan("\u{1F4A1} Suggestions")} ${color.dim(`(use /next 1, /next 1 2 3 to execute)`)}`,
|
|
7066
|
+
""
|
|
7067
|
+
];
|
|
7068
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
7069
|
+
lines.push(` ${color.bold(`${i + 1}.`)} ${suggestions[i]}`);
|
|
7070
|
+
}
|
|
7071
|
+
return { message: lines.join("\n") };
|
|
7072
|
+
}
|
|
6774
7073
|
|
|
6775
7074
|
// src/slash-commands/plugin.ts
|
|
6776
7075
|
function buildPluginCommand(opts) {
|
|
@@ -7108,18 +7407,18 @@ function fmtEntry(e) {
|
|
|
7108
7407
|
}
|
|
7109
7408
|
async function patchGlobalConfig2(globalConfigPath, mutate) {
|
|
7110
7409
|
let raw = "{}";
|
|
7111
|
-
let
|
|
7410
|
+
let fileExists2 = true;
|
|
7112
7411
|
try {
|
|
7113
7412
|
raw = await fsp4.readFile(globalConfigPath, "utf8");
|
|
7114
7413
|
} catch (err) {
|
|
7115
7414
|
if (err.code !== "ENOENT") throw err;
|
|
7116
|
-
|
|
7415
|
+
fileExists2 = false;
|
|
7117
7416
|
}
|
|
7118
7417
|
let parsed;
|
|
7119
7418
|
try {
|
|
7120
7419
|
parsed = JSON.parse(raw);
|
|
7121
7420
|
} catch (err) {
|
|
7122
|
-
if (
|
|
7421
|
+
if (fileExists2)
|
|
7123
7422
|
throw new Error(`Config at ${globalConfigPath} is not valid JSON: ${err.message}`);
|
|
7124
7423
|
parsed = {};
|
|
7125
7424
|
}
|
|
@@ -7559,6 +7858,161 @@ function buildModelCapsCommand(opts) {
|
|
|
7559
7858
|
}
|
|
7560
7859
|
};
|
|
7561
7860
|
}
|
|
7861
|
+
function collectContext(opts) {
|
|
7862
|
+
const parts = [];
|
|
7863
|
+
try {
|
|
7864
|
+
const gitStatus = execFileSync("git", ["status", "--short", "--branch"], {
|
|
7865
|
+
cwd: opts.projectRoot,
|
|
7866
|
+
encoding: "utf8",
|
|
7867
|
+
timeout: 5e3,
|
|
7868
|
+
windowsHide: true
|
|
7869
|
+
}).trim();
|
|
7870
|
+
if (gitStatus) {
|
|
7871
|
+
parts.push("### Git Status", "```", gitStatus, "```");
|
|
7872
|
+
}
|
|
7873
|
+
} catch {
|
|
7874
|
+
}
|
|
7875
|
+
parts.push(`Working directory: ${opts.cwd}`);
|
|
7876
|
+
parts.push(`Project root: ${opts.projectRoot}`);
|
|
7877
|
+
return parts.join("\n");
|
|
7878
|
+
}
|
|
7879
|
+
function buildSuggestPrompt(contextText) {
|
|
7880
|
+
return [
|
|
7881
|
+
"## Suggest Next Steps",
|
|
7882
|
+
"",
|
|
7883
|
+
"Based on the current project state below, generate 3-5 actionable next-step",
|
|
7884
|
+
"suggestions. Each suggestion must be a single imperative sentence that can be",
|
|
7885
|
+
"executed immediately. Be specific \u2014 mention file names, tool names, or commands",
|
|
7886
|
+
"when relevant. Do NOT include preamble, explanation, or wrap in code blocks.",
|
|
7887
|
+
"",
|
|
7888
|
+
"Rules:",
|
|
7889
|
+
'- One suggestion per line, prefixed with the number (e.g. "1. Run tests...")',
|
|
7890
|
+
"- Order by priority: most impactful first",
|
|
7891
|
+
"- Suggestions should be independent \u2014 user can pick any subset",
|
|
7892
|
+
'- If nothing is needed, say "No pending actions \u2014 everything is up to date."',
|
|
7893
|
+
"",
|
|
7894
|
+
contextText || "(No project context available \u2014 suggest generic next steps.)",
|
|
7895
|
+
"",
|
|
7896
|
+
"Output format (strict \u2014 no other text):",
|
|
7897
|
+
"1. First suggestion here",
|
|
7898
|
+
"2. Second suggestion here",
|
|
7899
|
+
"3. Third suggestion here"
|
|
7900
|
+
].join("\n");
|
|
7901
|
+
}
|
|
7902
|
+
function buildSuggestCommand(opts) {
|
|
7903
|
+
return {
|
|
7904
|
+
name: "suggest",
|
|
7905
|
+
aliases: ["next-steps", "what-next"],
|
|
7906
|
+
category: "Agent",
|
|
7907
|
+
description: "Generate context-aware next-step suggestions for the current session.",
|
|
7908
|
+
argsHint: "[--fast]",
|
|
7909
|
+
help: [
|
|
7910
|
+
"Usage:",
|
|
7911
|
+
" /suggest Generate suggestions using a lightweight subagent",
|
|
7912
|
+
" /suggest --fast Heuristic-only suggestions (no subagent, instant)",
|
|
7913
|
+
"",
|
|
7914
|
+
"Analyzes the current session state (git status, working directory, recent",
|
|
7915
|
+
"activity) and generates 3-5 actionable next-step suggestions. Suggestions",
|
|
7916
|
+
"are stored and can be selected with `/next 1`, `/next 1 2 3`, etc.",
|
|
7917
|
+
"",
|
|
7918
|
+
"Use `/next list` to see the current suggestions at any time."
|
|
7919
|
+
].join("\n"),
|
|
7920
|
+
async run(args, _ctx) {
|
|
7921
|
+
const trimmed = args.trim().toLowerCase();
|
|
7922
|
+
const fast = /\b(--fast|-f)\b/.test(trimmed);
|
|
7923
|
+
if (fast) {
|
|
7924
|
+
const suggestions = generateHeuristicSuggestions(opts);
|
|
7925
|
+
opts.onSuggestions?.(suggestions);
|
|
7926
|
+
const display = formatSuggestions(suggestions);
|
|
7927
|
+
return { message: display };
|
|
7928
|
+
}
|
|
7929
|
+
if (!opts.onSpawnAndWait) {
|
|
7930
|
+
const suggestions = generateHeuristicSuggestions(opts);
|
|
7931
|
+
opts.onSuggestions?.(suggestions);
|
|
7932
|
+
const display = formatSuggestions(suggestions) + "\n" + color.dim("(Heuristic fallback \u2014 multi-agent not enabled)");
|
|
7933
|
+
return { message: display };
|
|
7934
|
+
}
|
|
7935
|
+
const contextText = collectContext({
|
|
7936
|
+
cwd: opts.cwd,
|
|
7937
|
+
projectRoot: opts.projectRoot
|
|
7938
|
+
});
|
|
7939
|
+
const task = buildSuggestPrompt(contextText);
|
|
7940
|
+
opts.renderer.write(color.dim("Generating suggestions..."));
|
|
7941
|
+
try {
|
|
7942
|
+
const raw = await opts.onSpawnAndWait(task, {
|
|
7943
|
+
name: "suggest"
|
|
7944
|
+
});
|
|
7945
|
+
const suggestions = parseSuggestions(raw);
|
|
7946
|
+
if (suggestions.length === 0) {
|
|
7947
|
+
const fallback = ["No pending actions \u2014 everything is up to date."];
|
|
7948
|
+
opts.onSuggestions?.(fallback);
|
|
7949
|
+
return { message: formatSuggestions(fallback) };
|
|
7950
|
+
}
|
|
7951
|
+
opts.onSuggestions?.(suggestions);
|
|
7952
|
+
return { message: formatSuggestions(suggestions) };
|
|
7953
|
+
} catch (err) {
|
|
7954
|
+
const msg = `Suggestion generation failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
7955
|
+
opts.renderer.writeWarning(msg);
|
|
7956
|
+
return { message: msg };
|
|
7957
|
+
}
|
|
7958
|
+
}
|
|
7959
|
+
};
|
|
7960
|
+
}
|
|
7961
|
+
function parseSuggestions(raw) {
|
|
7962
|
+
const lines = raw.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
7963
|
+
const numbered = lines.filter((l) => /^\d+[.)]\s/.test(l)).map((l) => l.replace(/^\d+[.)]\s*/, "").trim());
|
|
7964
|
+
if (numbered.length > 0) return numbered.slice(0, 5);
|
|
7965
|
+
const bullets = lines.filter((l) => /^[-*•]\s/.test(l)).map((l) => l.replace(/^[-*•]\s*/, "").trim());
|
|
7966
|
+
if (bullets.length > 0) return bullets.slice(0, 5);
|
|
7967
|
+
return lines.filter((l) => l.length > 10 && !l.startsWith("#") && !l.startsWith("```")).slice(0, 5);
|
|
7968
|
+
}
|
|
7969
|
+
function generateHeuristicSuggestions(opts) {
|
|
7970
|
+
const suggestions = [];
|
|
7971
|
+
try {
|
|
7972
|
+
const gitStatus = execFileSync("git", ["status", "--short"], {
|
|
7973
|
+
cwd: opts.projectRoot,
|
|
7974
|
+
encoding: "utf8",
|
|
7975
|
+
timeout: 5e3,
|
|
7976
|
+
windowsHide: true
|
|
7977
|
+
}).trim();
|
|
7978
|
+
if (gitStatus) {
|
|
7979
|
+
const staged = gitStatus.split("\n").filter((l) => /^[MADRC]/.test(l)).length;
|
|
7980
|
+
const unstaged = gitStatus.split("\n").filter((l) => /^.[MADRC]/.test(l)).length;
|
|
7981
|
+
const untracked = gitStatus.split("\n").filter((l) => l.startsWith("??")).length;
|
|
7982
|
+
if (staged > 0) {
|
|
7983
|
+
suggestions.push(`Commit ${staged} staged file(s) with a descriptive message`);
|
|
7984
|
+
}
|
|
7985
|
+
if (unstaged > 0) {
|
|
7986
|
+
suggestions.push(`Stage and review ${unstaged} modified file(s)`);
|
|
7987
|
+
}
|
|
7988
|
+
if (untracked > 0) {
|
|
7989
|
+
suggestions.push(`Review ${untracked} untracked file(s) \u2014 add to git or .gitignore`);
|
|
7990
|
+
}
|
|
7991
|
+
}
|
|
7992
|
+
} catch {
|
|
7993
|
+
}
|
|
7994
|
+
if (suggestions.length === 0) {
|
|
7995
|
+
suggestions.push("Review recent changes with a diff");
|
|
7996
|
+
suggestions.push("Run the test suite to verify everything passes");
|
|
7997
|
+
suggestions.push("Check for lint or type errors");
|
|
7998
|
+
}
|
|
7999
|
+
return suggestions.slice(0, 5);
|
|
8000
|
+
}
|
|
8001
|
+
function formatSuggestions(suggestions) {
|
|
8002
|
+
if (suggestions.length === 0) {
|
|
8003
|
+
return color.dim("No suggestions available.");
|
|
8004
|
+
}
|
|
8005
|
+
const lines = [
|
|
8006
|
+
` ${color.cyan("\u{1F4A1} Next steps")} ${color.dim("(use /next 1, /next 2, or /next 1 2 3)")}`,
|
|
8007
|
+
""
|
|
8008
|
+
];
|
|
8009
|
+
for (let i = 0; i < suggestions.length; i++) {
|
|
8010
|
+
const num = color.bold(`${i + 1}.`);
|
|
8011
|
+
const text = suggestions[i] ?? "";
|
|
8012
|
+
lines.push(` ${num} ${text}`);
|
|
8013
|
+
}
|
|
8014
|
+
return lines.join("\n");
|
|
8015
|
+
}
|
|
7562
8016
|
var noOpVault4 = {
|
|
7563
8017
|
encrypt: (v) => v,
|
|
7564
8018
|
decrypt: (v) => v,
|
|
@@ -7956,7 +8410,7 @@ var DEFAULTS = {
|
|
|
7956
8410
|
cost: true
|
|
7957
8411
|
};
|
|
7958
8412
|
function resolveConfigPath() {
|
|
7959
|
-
return process.env[CONFIG_ENV] ??
|
|
8413
|
+
return process.env[CONFIG_ENV] ?? path10.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
7960
8414
|
}
|
|
7961
8415
|
async function loadStatuslineConfig() {
|
|
7962
8416
|
const p = resolveConfigPath();
|
|
@@ -7970,7 +8424,7 @@ async function loadStatuslineConfig() {
|
|
|
7970
8424
|
async function saveStatuslineConfig(cfg) {
|
|
7971
8425
|
const p = resolveConfigPath();
|
|
7972
8426
|
try {
|
|
7973
|
-
await fsp4.mkdir(
|
|
8427
|
+
await fsp4.mkdir(path10.dirname(p), { recursive: true });
|
|
7974
8428
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
7975
8429
|
} catch (err) {
|
|
7976
8430
|
throw new FsError({
|
|
@@ -8397,6 +8851,199 @@ function buildWorktreeCommand(opts) {
|
|
|
8397
8851
|
}
|
|
8398
8852
|
};
|
|
8399
8853
|
}
|
|
8854
|
+
async function discoverPackageFiles(projectRoot) {
|
|
8855
|
+
const files = [];
|
|
8856
|
+
const rootPkg = path10.join(projectRoot, "package.json");
|
|
8857
|
+
try {
|
|
8858
|
+
await fsp4.access(rootPkg);
|
|
8859
|
+
files.push(rootPkg);
|
|
8860
|
+
} catch {
|
|
8861
|
+
}
|
|
8862
|
+
const workspaceFile = path10.join(projectRoot, "pnpm-workspace.yaml");
|
|
8863
|
+
try {
|
|
8864
|
+
await fsp4.access(workspaceFile);
|
|
8865
|
+
const content = await fsp4.readFile(workspaceFile, "utf8");
|
|
8866
|
+
const globMatch = /packages?:\s*\[([^\]]+)\]/s.exec(content);
|
|
8867
|
+
const rawGlobs = globMatch?.[1];
|
|
8868
|
+
if (!rawGlobs) return files;
|
|
8869
|
+
const globs = rawGlobs.split(/[\s,]+/).filter(Boolean).map((g) => g.replace(/['"]/g, ""));
|
|
8870
|
+
for (const g of globs) {
|
|
8871
|
+
const dirPrefix = g.replace(/\/?\*$/, "").replace(/\/\*$/, "");
|
|
8872
|
+
const dir = path10.join(projectRoot, dirPrefix);
|
|
8873
|
+
try {
|
|
8874
|
+
const entries = await fsp4.readdir(dir, { withFileTypes: true });
|
|
8875
|
+
for (const e of entries) {
|
|
8876
|
+
if (!e.isDirectory()) continue;
|
|
8877
|
+
const subPkg = path10.join(dir, e.name, "package.json");
|
|
8878
|
+
try {
|
|
8879
|
+
await fsp4.access(subPkg);
|
|
8880
|
+
files.push(subPkg);
|
|
8881
|
+
} catch {
|
|
8882
|
+
}
|
|
8883
|
+
}
|
|
8884
|
+
} catch {
|
|
8885
|
+
}
|
|
8886
|
+
}
|
|
8887
|
+
} catch {
|
|
8888
|
+
}
|
|
8889
|
+
return files;
|
|
8890
|
+
}
|
|
8891
|
+
function buildTechStackTask(opts) {
|
|
8892
|
+
const pkgList = opts.packageFiles.map((f) => ` - ${path10.relative(opts.projectRoot, f)}`).join("\n");
|
|
8893
|
+
const header = opts.isInit ? [
|
|
8894
|
+
"## Tech Stack Audit \u2014 First-Time Project Init",
|
|
8895
|
+
"",
|
|
8896
|
+
"This project is being initialized for the first time. Scan its dependencies,",
|
|
8897
|
+
"check every package and framework version against the npm registry, and produce",
|
|
8898
|
+
"a report that warns about outdated choices. The LLM that scaffolded this project",
|
|
8899
|
+
"may have suggested stale version numbers \u2014 verify every single one.",
|
|
8900
|
+
""
|
|
8901
|
+
].join("\n") : [
|
|
8902
|
+
"## Tech Stack Audit \u2014 Full Project Scan",
|
|
8903
|
+
"",
|
|
8904
|
+
"Scan all project dependencies, verify every package version against the npm",
|
|
8905
|
+
"registry, and produce a report that flags outdated, dead, or obsolete packages.",
|
|
8906
|
+
""
|
|
8907
|
+
].join("\n");
|
|
8908
|
+
const outputPath = opts.outputFormat === "json" ? "techstack.json" : "techstack.md";
|
|
8909
|
+
return [
|
|
8910
|
+
header,
|
|
8911
|
+
"",
|
|
8912
|
+
"### Project package files to scan",
|
|
8913
|
+
`${pkgList || " - package.json (root only)"}`,
|
|
8914
|
+
"",
|
|
8915
|
+
"### Instructions",
|
|
8916
|
+
"",
|
|
8917
|
+
"1. **Read** each package.json and extract ALL dependencies (dependencies +",
|
|
8918
|
+
" devDependencies + peerDependencies). Include the workspace root.",
|
|
8919
|
+
"",
|
|
8920
|
+
"2. **For every dependency**, look up its latest version from the npm registry:",
|
|
8921
|
+
' - `fetch("https://registry.npmjs.org/<package>/latest")`',
|
|
8922
|
+
" - Extract the `version` field from the JSON response",
|
|
8923
|
+
" - Also check `description`, `license`, and `time` fields for age/dead checks",
|
|
8924
|
+
"",
|
|
8925
|
+
"3. **For each package, determine status:**",
|
|
8926
|
+
" - \u{1F7E2} CURRENT: installed version is within 1 minor of latest",
|
|
8927
|
+
" - \u{1F7E1} OUTDATED: installed version is behind latest (major or >1 minor gap)",
|
|
8928
|
+
" - \u{1F534} CRITICAL: package has known CVEs, is deprecated, or >2 years without release",
|
|
8929
|
+
" - \u2620\uFE0F DEAD: package is deprecated, archived, or superseded \u22655 years ago",
|
|
8930
|
+
"",
|
|
8931
|
+
"4. **Apply the tech-stack skill rules:**",
|
|
8932
|
+
' - Reject packages that are "prehistoric" (superseded \u22655 years ago)',
|
|
8933
|
+
" - Flag dead packages (no release >2 years + critical issues)",
|
|
8934
|
+
" - Prefer Node.js built-ins over third-party packages",
|
|
8935
|
+
"",
|
|
8936
|
+
`5. **Write the report** to \`${outputPath}\` in the project root:`,
|
|
8937
|
+
" - Markdown format: grouped by category, with version tables and warnings",
|
|
8938
|
+
" - JSON format: structured array with name, current, latest, status, notes",
|
|
8939
|
+
"",
|
|
8940
|
+
"6. **Report a summary to chat**: total packages scanned, counts per status,",
|
|
8941
|
+
" top 5 most urgent issues, and total cost estimate (optional).",
|
|
8942
|
+
"",
|
|
8943
|
+
"### Output format (markdown)",
|
|
8944
|
+
"",
|
|
8945
|
+
"```markdown",
|
|
8946
|
+
"# Tech Stack Report",
|
|
8947
|
+
"",
|
|
8948
|
+
"Generated: <date> \xB7 Scanned: <total> packages across <N> files",
|
|
8949
|
+
"",
|
|
8950
|
+
"## \u{1F7E2} Up to Date (<count>)",
|
|
8951
|
+
"| Package | Current | Latest | Age | Notes |",
|
|
8952
|
+
"|---------|---------|--------|-----|-------|",
|
|
8953
|
+
"",
|
|
8954
|
+
"## \u{1F7E1} Outdated (<count>)",
|
|
8955
|
+
"...",
|
|
8956
|
+
"",
|
|
8957
|
+
"## \u{1F534} Critical Issues (<count>)",
|
|
8958
|
+
"...",
|
|
8959
|
+
"",
|
|
8960
|
+
"## \u2620\uFE0F Dead / Obsolete (<count>)",
|
|
8961
|
+
"...",
|
|
8962
|
+
"",
|
|
8963
|
+
"## Recommendations",
|
|
8964
|
+
"- Top 3-5 actionable fixes",
|
|
8965
|
+
"```",
|
|
8966
|
+
"",
|
|
8967
|
+
"### Guardrails",
|
|
8968
|
+
"",
|
|
8969
|
+
"- Use `fetch()` with `AbortSignal.timeout(10000)` on every npm registry call.",
|
|
8970
|
+
"- Skip packages that return 404 (private/internal packages).",
|
|
8971
|
+
"- Deduplicate: same package in multiple package.json files = one row.",
|
|
8972
|
+
"- Do NOT modify any files except writing the report.",
|
|
8973
|
+
"- Run in 2-3 iterations max. Parallel fetch where possible.",
|
|
8974
|
+
"- **IMPORTANT**: Output the chat summary FIRST, then write the file. I need to see results."
|
|
8975
|
+
].join("\n");
|
|
8976
|
+
}
|
|
8977
|
+
function buildTechStackCommand(opts) {
|
|
8978
|
+
return {
|
|
8979
|
+
name: "techstack",
|
|
8980
|
+
category: "Inspect",
|
|
8981
|
+
aliases: ["tech", "deps"],
|
|
8982
|
+
description: "Scan all project dependencies, verify versions against npm, and produce a techstack report.",
|
|
8983
|
+
argsHint: "[--json] [--init]",
|
|
8984
|
+
help: [
|
|
8985
|
+
"Usage:",
|
|
8986
|
+
" /techstack Scan dependencies + write techstack.md report",
|
|
8987
|
+
" /techstack --json Write techstack.json instead of markdown",
|
|
8988
|
+
" /techstack --init Init-mode scan (compares scaffolded vs latest)",
|
|
8989
|
+
"",
|
|
8990
|
+
"Spawns a subagent that:",
|
|
8991
|
+
" 1. Reads every package.json in the project",
|
|
8992
|
+
" 2. Looks up latest versions on the npm registry",
|
|
8993
|
+
" 3. Flags outdated, dead, or obsolete packages",
|
|
8994
|
+
` 4. Writes a ${color.cyan("techstack.md")} (or .json) report to the project root`,
|
|
8995
|
+
"",
|
|
8996
|
+
"Uses the `tech-stack` skill for version verification rules.",
|
|
8997
|
+
`Hooked into ${color.cyan("/init")} \u2014 runs automatically on first project setup.`
|
|
8998
|
+
].join("\n"),
|
|
8999
|
+
async run(args, _ctx) {
|
|
9000
|
+
const trimmed = args.trim().toLowerCase();
|
|
9001
|
+
const outputFormat = /\b(--json|-j)\b/.test(trimmed) ? "json" : "md";
|
|
9002
|
+
const isInit = /\b(--init|-i)\b/.test(trimmed);
|
|
9003
|
+
let packageFiles = [];
|
|
9004
|
+
let discoveryNote = "";
|
|
9005
|
+
try {
|
|
9006
|
+
packageFiles = await discoverPackageFiles(opts.projectRoot);
|
|
9007
|
+
if (packageFiles.length === 0) {
|
|
9008
|
+
discoveryNote = color.amber(
|
|
9009
|
+
"\u26A0 No package.json files found. This does not look like a Node.js project."
|
|
9010
|
+
);
|
|
9011
|
+
}
|
|
9012
|
+
} catch (err) {
|
|
9013
|
+
discoveryNote = color.red(
|
|
9014
|
+
`Could not scan for package files: ${err instanceof Error ? err.message : String(err)}`
|
|
9015
|
+
);
|
|
9016
|
+
}
|
|
9017
|
+
const task = buildTechStackTask({
|
|
9018
|
+
projectRoot: opts.projectRoot,
|
|
9019
|
+
packageFiles,
|
|
9020
|
+
outputFormat,
|
|
9021
|
+
isInit
|
|
9022
|
+
});
|
|
9023
|
+
if (!opts.onSpawnAndWait) {
|
|
9024
|
+
const msg = "Multi-agent is not enabled in this session. Cannot spawn techstack subagent.";
|
|
9025
|
+
opts.renderer.writeWarning(msg);
|
|
9026
|
+
return { message: msg };
|
|
9027
|
+
}
|
|
9028
|
+
const header = isInit ? "Tech Stack Init Audit" : "Tech Stack Audit";
|
|
9029
|
+
const label = `${color.cyan("\u{1F50D}")} ${color.bold(header)} ${color.dim(`(${packageFiles.length} package files)`)}`;
|
|
9030
|
+
opts.renderer.write(label);
|
|
9031
|
+
if (discoveryNote) opts.renderer.write(discoveryNote);
|
|
9032
|
+
opts.renderer.write(
|
|
9033
|
+
color.dim(`Spawning tech-stack subagent \u2192 writes ${outputFormat === "json" ? "techstack.json" : "techstack.md"} when done.`)
|
|
9034
|
+
);
|
|
9035
|
+
try {
|
|
9036
|
+
const name = isInit ? "techstack-init" : "techstack-audit";
|
|
9037
|
+
const summary = await opts.onSpawnAndWait(task, { name });
|
|
9038
|
+
return { message: summary };
|
|
9039
|
+
} catch (err) {
|
|
9040
|
+
const msg = `Tech stack scan failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
9041
|
+
opts.renderer.writeWarning(msg);
|
|
9042
|
+
return { message: msg };
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
9045
|
+
};
|
|
9046
|
+
}
|
|
8400
9047
|
function buildYoloCommand(opts) {
|
|
8401
9048
|
return {
|
|
8402
9049
|
name: "yolo",
|
|
@@ -8455,11 +9102,14 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
8455
9102
|
buildClearCommand(opts),
|
|
8456
9103
|
buildCompactCommand(opts),
|
|
8457
9104
|
buildContextCommand(opts),
|
|
9105
|
+
buildDevCommand(opts),
|
|
8458
9106
|
buildCodebaseReindexCommand(opts),
|
|
9107
|
+
buildTechStackCommand(opts),
|
|
8459
9108
|
buildToolsCommand(opts),
|
|
8460
9109
|
buildPluginCommand(opts),
|
|
8461
9110
|
buildPruneCommand(opts),
|
|
8462
9111
|
buildMcpSlashCommand(opts),
|
|
9112
|
+
buildSuggestCommand(opts),
|
|
8463
9113
|
buildAuthCommand(opts),
|
|
8464
9114
|
buildDiagCommand(opts),
|
|
8465
9115
|
buildStatsCommand(opts),
|
|
@@ -8517,13 +9167,13 @@ var MANIFESTS = [
|
|
|
8517
9167
|
];
|
|
8518
9168
|
async function detectProjectKind(projectRoot) {
|
|
8519
9169
|
try {
|
|
8520
|
-
await fsp4.access(
|
|
9170
|
+
await fsp4.access(path10.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
8521
9171
|
return "initialized";
|
|
8522
9172
|
} catch {
|
|
8523
9173
|
}
|
|
8524
9174
|
for (const m of MANIFESTS) {
|
|
8525
9175
|
try {
|
|
8526
|
-
await fsp4.access(
|
|
9176
|
+
await fsp4.access(path10.join(projectRoot, m));
|
|
8527
9177
|
return "project";
|
|
8528
9178
|
} catch {
|
|
8529
9179
|
}
|
|
@@ -8531,8 +9181,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
8531
9181
|
return "empty";
|
|
8532
9182
|
}
|
|
8533
9183
|
async function scaffoldAgentsMd(projectRoot) {
|
|
8534
|
-
const dir =
|
|
8535
|
-
const file =
|
|
9184
|
+
const dir = path10.join(projectRoot, ".wrongstack");
|
|
9185
|
+
const file = path10.join(dir, "AGENTS.md");
|
|
8536
9186
|
const facts = await detectProjectFacts(projectRoot);
|
|
8537
9187
|
const body = renderAgentsTemplate(facts);
|
|
8538
9188
|
await fsp4.mkdir(dir, { recursive: true });
|
|
@@ -8545,7 +9195,7 @@ async function runProjectCheck(opts) {
|
|
|
8545
9195
|
if (kind === "initialized") {
|
|
8546
9196
|
renderer.write(
|
|
8547
9197
|
`
|
|
8548
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
9198
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path10.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
8549
9199
|
`
|
|
8550
9200
|
);
|
|
8551
9201
|
return true;
|
|
@@ -8576,7 +9226,7 @@ async function runProjectCheck(opts) {
|
|
|
8576
9226
|
}
|
|
8577
9227
|
return true;
|
|
8578
9228
|
}
|
|
8579
|
-
const gitDir =
|
|
9229
|
+
const gitDir = path10.join(projectRoot, ".git");
|
|
8580
9230
|
let hasGit = false;
|
|
8581
9231
|
try {
|
|
8582
9232
|
await fsp4.access(gitDir);
|
|
@@ -8770,7 +9420,7 @@ var ReadlineInputReader = class {
|
|
|
8770
9420
|
history = [];
|
|
8771
9421
|
pending = false;
|
|
8772
9422
|
constructor(opts = {}) {
|
|
8773
|
-
this.historyFile = opts.historyFile ??
|
|
9423
|
+
this.historyFile = opts.historyFile ?? path10.join(os2.homedir(), ".wrongstack", "history");
|
|
8774
9424
|
}
|
|
8775
9425
|
async loadHistory() {
|
|
8776
9426
|
try {
|
|
@@ -8782,7 +9432,7 @@ var ReadlineInputReader = class {
|
|
|
8782
9432
|
}
|
|
8783
9433
|
async saveHistory() {
|
|
8784
9434
|
try {
|
|
8785
|
-
await fsp4.mkdir(
|
|
9435
|
+
await fsp4.mkdir(path10.dirname(this.historyFile), { recursive: true });
|
|
8786
9436
|
await fsp4.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
8787
9437
|
} catch {
|
|
8788
9438
|
}
|
|
@@ -9075,20 +9725,20 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
9075
9725
|
if (PROTECTED_BASENAMES.has(filename)) {
|
|
9076
9726
|
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
9077
9727
|
}
|
|
9078
|
-
if (filename !==
|
|
9728
|
+
if (filename !== path10.basename(filename)) {
|
|
9079
9729
|
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
9080
9730
|
}
|
|
9081
9731
|
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
9082
9732
|
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
9083
9733
|
}
|
|
9084
|
-
const resolvedParent =
|
|
9734
|
+
const resolvedParent = path10.resolve(parentDir);
|
|
9085
9735
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
9086
9736
|
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
9087
9737
|
}
|
|
9088
9738
|
}
|
|
9089
9739
|
async function safeDelete(filePath) {
|
|
9090
|
-
const dir =
|
|
9091
|
-
const filename =
|
|
9740
|
+
const dir = path10.dirname(filePath);
|
|
9741
|
+
const filename = path10.basename(filePath);
|
|
9092
9742
|
try {
|
|
9093
9743
|
assertSafeToDelete(filename, dir);
|
|
9094
9744
|
await fsp4.unlink(filePath);
|
|
@@ -9133,16 +9783,16 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
9133
9783
|
}
|
|
9134
9784
|
var defaultHomeDir = () => os2__default.homedir();
|
|
9135
9785
|
function historyDir(homeFn = defaultHomeDir) {
|
|
9136
|
-
return
|
|
9786
|
+
return path10.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
9137
9787
|
}
|
|
9138
9788
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
9139
|
-
return
|
|
9789
|
+
return path10.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
9140
9790
|
}
|
|
9141
9791
|
function configPath(homeFn = defaultHomeDir) {
|
|
9142
|
-
return
|
|
9792
|
+
return path10.join(homeFn(), ".wrongstack", "config.json");
|
|
9143
9793
|
}
|
|
9144
9794
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
9145
|
-
return
|
|
9795
|
+
return path10.join(homeFn(), ".wrongstack", "config.json.last");
|
|
9146
9796
|
}
|
|
9147
9797
|
function entryId(ts) {
|
|
9148
9798
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -9197,17 +9847,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
9197
9847
|
}
|
|
9198
9848
|
if (content !== void 0) {
|
|
9199
9849
|
try {
|
|
9200
|
-
const bakPath =
|
|
9850
|
+
const bakPath = path10.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
9201
9851
|
await atomicWrite(bakPath, content);
|
|
9202
9852
|
} catch {
|
|
9203
9853
|
}
|
|
9204
9854
|
}
|
|
9205
9855
|
try {
|
|
9206
|
-
const dir =
|
|
9856
|
+
const dir = path10.join(homeFn(), ".wrongstack");
|
|
9207
9857
|
const files = await fsp4.readdir(dir);
|
|
9208
9858
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
9209
9859
|
for (const f of baks.slice(10)) {
|
|
9210
|
-
await safeDelete(
|
|
9860
|
+
await safeDelete(path10.join(dir, f));
|
|
9211
9861
|
}
|
|
9212
9862
|
} catch {
|
|
9213
9863
|
}
|
|
@@ -9225,7 +9875,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
9225
9875
|
};
|
|
9226
9876
|
try {
|
|
9227
9877
|
await fsp4.writeFile(
|
|
9228
|
-
|
|
9878
|
+
path10.join(historyDir(homeFn), `${id}.json`),
|
|
9229
9879
|
JSON.stringify(entry, null, 2),
|
|
9230
9880
|
"utf8"
|
|
9231
9881
|
);
|
|
@@ -9233,7 +9883,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
9233
9883
|
throw new FsError({
|
|
9234
9884
|
message: err instanceof Error ? err.message : String(err),
|
|
9235
9885
|
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
9236
|
-
path:
|
|
9886
|
+
path: path10.join(historyDir(homeFn), `${id}.json`),
|
|
9237
9887
|
cause: err
|
|
9238
9888
|
});
|
|
9239
9889
|
}
|
|
@@ -9248,7 +9898,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
9248
9898
|
}
|
|
9249
9899
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
9250
9900
|
try {
|
|
9251
|
-
const raw = await fsp4.readFile(
|
|
9901
|
+
const raw = await fsp4.readFile(path10.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
9252
9902
|
return JSON.parse(raw);
|
|
9253
9903
|
} catch {
|
|
9254
9904
|
return null;
|
|
@@ -9314,10 +9964,10 @@ var theme = { primary: color.amber };
|
|
|
9314
9964
|
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? os2__default.homedir()) {
|
|
9315
9965
|
try {
|
|
9316
9966
|
const { atomicWrite: atomicWrite14 } = await import('@wrongstack/core');
|
|
9317
|
-
const
|
|
9967
|
+
const fs30 = await import('fs/promises');
|
|
9318
9968
|
let existing = {};
|
|
9319
9969
|
try {
|
|
9320
|
-
const raw = await
|
|
9970
|
+
const raw = await fs30.readFile(configPath2, "utf8");
|
|
9321
9971
|
existing = JSON.parse(raw);
|
|
9322
9972
|
} catch {
|
|
9323
9973
|
}
|
|
@@ -9653,12 +10303,12 @@ function pickGroupIndex(opts) {
|
|
|
9653
10303
|
try {
|
|
9654
10304
|
let current = 0;
|
|
9655
10305
|
try {
|
|
9656
|
-
const parsed = Number.parseInt(
|
|
10306
|
+
const parsed = Number.parseInt(fs15.readFileSync(opts.cursorFile, "utf8").trim(), 10);
|
|
9657
10307
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
9658
10308
|
} catch {
|
|
9659
10309
|
}
|
|
9660
|
-
|
|
9661
|
-
|
|
10310
|
+
fs15.mkdirSync(path10.dirname(opts.cursorFile), { recursive: true });
|
|
10311
|
+
fs15.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
9662
10312
|
return current;
|
|
9663
10313
|
} catch {
|
|
9664
10314
|
}
|
|
@@ -9994,14 +10644,14 @@ function summarize(value, name) {
|
|
|
9994
10644
|
if (typeof v === "object" && v !== null) {
|
|
9995
10645
|
const o = v;
|
|
9996
10646
|
if (name === "edit") {
|
|
9997
|
-
const
|
|
10647
|
+
const path28 = typeof o["path"] === "string" ? o["path"] : "";
|
|
9998
10648
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
9999
|
-
return `${
|
|
10649
|
+
return `${path28} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
10000
10650
|
}
|
|
10001
10651
|
if (name === "write") {
|
|
10002
|
-
const
|
|
10652
|
+
const path28 = typeof o["path"] === "string" ? o["path"] : "";
|
|
10003
10653
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
10004
|
-
return bytes !== void 0 ? `${
|
|
10654
|
+
return bytes !== void 0 ? `${path28} ${bytes}B` : path28;
|
|
10005
10655
|
}
|
|
10006
10656
|
if (typeof o["count"] === "number") {
|
|
10007
10657
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -11219,7 +11869,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
11219
11869
|
}
|
|
11220
11870
|
try {
|
|
11221
11871
|
await fsp4.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
11222
|
-
const probe =
|
|
11872
|
+
const probe = path10.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
11223
11873
|
await fsp4.writeFile(probe, "");
|
|
11224
11874
|
await fsp4.unlink(probe);
|
|
11225
11875
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -11322,8 +11972,8 @@ var exportCmd = async (args, deps) => {
|
|
|
11322
11972
|
return 1;
|
|
11323
11973
|
}
|
|
11324
11974
|
if (output) {
|
|
11325
|
-
await fsp4.mkdir(
|
|
11326
|
-
await fsp4.writeFile(
|
|
11975
|
+
await fsp4.mkdir(path10.dirname(path10.resolve(deps.cwd, output)), { recursive: true });
|
|
11976
|
+
await fsp4.writeFile(path10.resolve(deps.cwd, output), rendered, "utf8");
|
|
11327
11977
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
11328
11978
|
`);
|
|
11329
11979
|
} else {
|
|
@@ -11396,8 +12046,8 @@ var initCmd = async (_args, deps) => {
|
|
|
11396
12046
|
const vault = new DefaultSecretVault({ keyFile: deps.paths.secretsKey });
|
|
11397
12047
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
11398
12048
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
11399
|
-
await fsp4.mkdir(
|
|
11400
|
-
const agentsFile =
|
|
12049
|
+
await fsp4.mkdir(path10.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
12050
|
+
const agentsFile = path10.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
11401
12051
|
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
11402
12052
|
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
11403
12053
|
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
@@ -11854,7 +12504,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
11854
12504
|
return 0;
|
|
11855
12505
|
};
|
|
11856
12506
|
var projectsCmd = async (_args, deps) => {
|
|
11857
|
-
const projectsRoot =
|
|
12507
|
+
const projectsRoot = path10.join(deps.paths.globalRoot, "projects");
|
|
11858
12508
|
try {
|
|
11859
12509
|
const entries = await fsp4.readdir(projectsRoot);
|
|
11860
12510
|
if (entries.length === 0) {
|
|
@@ -11864,7 +12514,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
11864
12514
|
for (const hash of entries) {
|
|
11865
12515
|
try {
|
|
11866
12516
|
const meta = JSON.parse(
|
|
11867
|
-
await fsp4.readFile(
|
|
12517
|
+
await fsp4.readFile(path10.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
11868
12518
|
);
|
|
11869
12519
|
deps.renderer.write(
|
|
11870
12520
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -12071,20 +12721,20 @@ ${navLines.join(" \xB7 ")}
|
|
|
12071
12721
|
async function mutateModelsConfig(deps, mutator) {
|
|
12072
12722
|
const vault = deps.vault;
|
|
12073
12723
|
const configPath2 = deps.paths.globalConfig;
|
|
12074
|
-
let
|
|
12724
|
+
let fileExists2 = true;
|
|
12075
12725
|
let raw;
|
|
12076
12726
|
try {
|
|
12077
12727
|
raw = await fsp4.readFile(configPath2, "utf8");
|
|
12078
12728
|
} catch (err) {
|
|
12079
12729
|
if (err.code !== "ENOENT") throw err;
|
|
12080
|
-
|
|
12730
|
+
fileExists2 = false;
|
|
12081
12731
|
raw = "{}";
|
|
12082
12732
|
}
|
|
12083
12733
|
let parsed;
|
|
12084
12734
|
try {
|
|
12085
12735
|
parsed = JSON.parse(raw);
|
|
12086
12736
|
} catch (err) {
|
|
12087
|
-
if (
|
|
12737
|
+
if (fileExists2) {
|
|
12088
12738
|
throw new Error(
|
|
12089
12739
|
`Refusing to overwrite corrupt config at ${configPath2} (${err.message}).`
|
|
12090
12740
|
);
|
|
@@ -12243,7 +12893,7 @@ async function listFleetRuns(deps) {
|
|
|
12243
12893
|
}
|
|
12244
12894
|
const runs = [];
|
|
12245
12895
|
for (const id of entries) {
|
|
12246
|
-
const runDir =
|
|
12896
|
+
const runDir = path10.join(deps.paths.projectSessions, id);
|
|
12247
12897
|
let stat4;
|
|
12248
12898
|
try {
|
|
12249
12899
|
stat4 = await fsp4.stat(runDir);
|
|
@@ -12256,17 +12906,17 @@ async function listFleetRuns(deps) {
|
|
|
12256
12906
|
let subagentCount = 0;
|
|
12257
12907
|
let subagentsDir;
|
|
12258
12908
|
try {
|
|
12259
|
-
await fsp4.access(
|
|
12909
|
+
await fsp4.access(path10.join(runDir, "fleet.json"));
|
|
12260
12910
|
manifest = true;
|
|
12261
12911
|
} catch {
|
|
12262
12912
|
}
|
|
12263
12913
|
try {
|
|
12264
|
-
await fsp4.access(
|
|
12914
|
+
await fsp4.access(path10.join(runDir, "checkpoint.json"));
|
|
12265
12915
|
checkpoint = true;
|
|
12266
12916
|
} catch {
|
|
12267
12917
|
}
|
|
12268
12918
|
try {
|
|
12269
|
-
subagentsDir =
|
|
12919
|
+
subagentsDir = path10.join(runDir, "subagents");
|
|
12270
12920
|
const files = await fsp4.readdir(subagentsDir);
|
|
12271
12921
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
12272
12922
|
} catch {
|
|
@@ -12295,7 +12945,7 @@ async function listFleetRuns(deps) {
|
|
|
12295
12945
|
return 0;
|
|
12296
12946
|
}
|
|
12297
12947
|
async function showFleetRun(runId, deps) {
|
|
12298
|
-
const runDir =
|
|
12948
|
+
const runDir = path10.join(deps.paths.projectSessions, runId);
|
|
12299
12949
|
let stat4;
|
|
12300
12950
|
try {
|
|
12301
12951
|
stat4 = await fsp4.stat(runDir);
|
|
@@ -12312,7 +12962,7 @@ async function showFleetRun(runId, deps) {
|
|
|
12312
12962
|
deps.renderer.write(color.bold(`
|
|
12313
12963
|
Fleet Run: ${runId}
|
|
12314
12964
|
`) + "\n");
|
|
12315
|
-
const manifestPath =
|
|
12965
|
+
const manifestPath = path10.join(runDir, "fleet.json");
|
|
12316
12966
|
let manifestData = null;
|
|
12317
12967
|
try {
|
|
12318
12968
|
manifestData = await fsp4.readFile(manifestPath, "utf8");
|
|
@@ -12328,7 +12978,7 @@ Fleet Run: ${runId}
|
|
|
12328
12978
|
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
12329
12979
|
`);
|
|
12330
12980
|
}
|
|
12331
|
-
const checkpointPath =
|
|
12981
|
+
const checkpointPath = path10.join(runDir, "checkpoint.json");
|
|
12332
12982
|
let checkpointData = null;
|
|
12333
12983
|
try {
|
|
12334
12984
|
checkpointData = await fsp4.readFile(checkpointPath, "utf8");
|
|
@@ -12375,7 +13025,7 @@ Fleet Run: ${runId}
|
|
|
12375
13025
|
} catch {
|
|
12376
13026
|
}
|
|
12377
13027
|
}
|
|
12378
|
-
const subagentsDir =
|
|
13028
|
+
const subagentsDir = path10.join(runDir, "subagents");
|
|
12379
13029
|
let subagentFiles = [];
|
|
12380
13030
|
try {
|
|
12381
13031
|
subagentFiles = await fsp4.readdir(subagentsDir);
|
|
@@ -12387,7 +13037,7 @@ Fleet Run: ${runId}
|
|
|
12387
13037
|
Subagent transcripts (${subagentFiles.length}):
|
|
12388
13038
|
`);
|
|
12389
13039
|
for (const f of subagentFiles.sort()) {
|
|
12390
|
-
const filePath =
|
|
13040
|
+
const filePath = path10.join(subagentsDir, f);
|
|
12391
13041
|
let size;
|
|
12392
13042
|
try {
|
|
12393
13043
|
const s = await fsp4.stat(filePath);
|
|
@@ -12404,7 +13054,7 @@ Fleet Run: ${runId}
|
|
|
12404
13054
|
${color.dim("\u25CB")} No subagent transcripts
|
|
12405
13055
|
`);
|
|
12406
13056
|
}
|
|
12407
|
-
const sharedDir =
|
|
13057
|
+
const sharedDir = path10.join(runDir, "shared");
|
|
12408
13058
|
try {
|
|
12409
13059
|
const files = await fsp4.readdir(sharedDir);
|
|
12410
13060
|
deps.renderer.write(`
|
|
@@ -12571,7 +13221,7 @@ function findSessionId(args) {
|
|
|
12571
13221
|
var rewindCmd = async (args, deps) => {
|
|
12572
13222
|
const flags = parseRewindFlags(args);
|
|
12573
13223
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
12574
|
-
const sessionsDir =
|
|
13224
|
+
const sessionsDir = path10.join(wpaths.globalRoot, "sessions");
|
|
12575
13225
|
const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
|
|
12576
13226
|
let sessionId = findSessionId(args);
|
|
12577
13227
|
if (!sessionId) {
|
|
@@ -12811,10 +13461,10 @@ var auditCmd = async (args, deps) => {
|
|
|
12811
13461
|
return verify.ok ? 0 : 1;
|
|
12812
13462
|
};
|
|
12813
13463
|
async function listAudits(log, dir, deps) {
|
|
12814
|
-
const
|
|
13464
|
+
const fs30 = await import('fs/promises');
|
|
12815
13465
|
let entries;
|
|
12816
13466
|
try {
|
|
12817
|
-
entries = await
|
|
13467
|
+
entries = await fs30.readdir(dir);
|
|
12818
13468
|
} catch {
|
|
12819
13469
|
deps.renderer.write(
|
|
12820
13470
|
color.dim(`No sessions dir found at ${dir}. Run a session first.`) + "\n"
|
|
@@ -13083,7 +13733,7 @@ Output ONLY a ranked list, one per line:
|
|
|
13083
13733
|
const text = resp.content[0] && "text" in resp.content[0] ? resp.content[0].text : "";
|
|
13084
13734
|
const rankings = [];
|
|
13085
13735
|
for (const line of text.split("\n")) {
|
|
13086
|
-
const m = line.match(/^\s*(\d+)[
|
|
13736
|
+
const m = line.match(/^\s*(\d+)[.)]\s*Response\s+([A-Z])/i);
|
|
13087
13737
|
if (m) {
|
|
13088
13738
|
const label = m[2].toUpperCase();
|
|
13089
13739
|
const idx = labelToIdx.get(label);
|
|
@@ -13714,22 +14364,22 @@ function fmtDuration(ms) {
|
|
|
13714
14364
|
const remMin = m - h * 60;
|
|
13715
14365
|
return `${h}h${remMin}m`;
|
|
13716
14366
|
}
|
|
13717
|
-
function fmtTaskResultLine(r,
|
|
14367
|
+
function fmtTaskResultLine(r, color62) {
|
|
13718
14368
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
13719
14369
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
13720
14370
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
13721
14371
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
13722
|
-
const errKindChip = errKind ?
|
|
13723
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
14372
|
+
const errKindChip = errKind ? color62.dim(` [${errKind}]`) : "";
|
|
14373
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color62.dim(errTail)}` : "";
|
|
13724
14374
|
switch (r.status) {
|
|
13725
14375
|
case "success":
|
|
13726
|
-
return { mark:
|
|
14376
|
+
return { mark: color62.green("\u2713"), stats, tail: "" };
|
|
13727
14377
|
case "timeout":
|
|
13728
|
-
return { mark:
|
|
14378
|
+
return { mark: color62.yellow("\u23F1"), stats: `${color62.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
13729
14379
|
case "stopped":
|
|
13730
|
-
return { mark:
|
|
14380
|
+
return { mark: color62.dim("\u2298"), stats: `${color62.dim("stopped")} ${stats}`, tail: errSnip };
|
|
13731
14381
|
case "failed":
|
|
13732
|
-
return { mark:
|
|
14382
|
+
return { mark: color62.red("\u2717"), stats: `${color62.red("failed")} ${stats}`, tail: errSnip };
|
|
13733
14383
|
}
|
|
13734
14384
|
}
|
|
13735
14385
|
|
|
@@ -13747,7 +14397,7 @@ function resolveBundledSkillsDir() {
|
|
|
13747
14397
|
try {
|
|
13748
14398
|
const req2 = createRequire(import.meta.url);
|
|
13749
14399
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
13750
|
-
return
|
|
14400
|
+
return path10.join(path10.dirname(corePkg), "skills");
|
|
13751
14401
|
} catch {
|
|
13752
14402
|
return void 0;
|
|
13753
14403
|
}
|
|
@@ -13997,7 +14647,7 @@ async function boot(argv) {
|
|
|
13997
14647
|
} catch {
|
|
13998
14648
|
}
|
|
13999
14649
|
printLaunchHints(renderer, flags, {
|
|
14000
|
-
cursorFile:
|
|
14650
|
+
cursorFile: path10.join(wpaths.cacheDir, "hint-cursor")
|
|
14001
14651
|
});
|
|
14002
14652
|
}
|
|
14003
14653
|
return {
|
|
@@ -14018,7 +14668,7 @@ async function boot(argv) {
|
|
|
14018
14668
|
}
|
|
14019
14669
|
async function checkGitInCwd(opts) {
|
|
14020
14670
|
const { cwd, renderer, reader } = opts;
|
|
14021
|
-
const cwdGit =
|
|
14671
|
+
const cwdGit = path10.join(cwd, ".git");
|
|
14022
14672
|
let hasCwdGit = false;
|
|
14023
14673
|
try {
|
|
14024
14674
|
await fsp4.access(cwdGit);
|
|
@@ -14059,10 +14709,10 @@ async function checkGitInCwd(opts) {
|
|
|
14059
14709
|
}
|
|
14060
14710
|
}
|
|
14061
14711
|
}
|
|
14062
|
-
const parentDir =
|
|
14712
|
+
const parentDir = path10.dirname(cwd);
|
|
14063
14713
|
if (parentDir !== cwd) {
|
|
14064
14714
|
try {
|
|
14065
|
-
await fsp4.access(
|
|
14715
|
+
await fsp4.access(path10.join(parentDir, ".git"));
|
|
14066
14716
|
renderer.write(
|
|
14067
14717
|
` ${color.dim("\u2139")} A ${color.bold(".git")} repo exists in the parent directory: ${color.dim(parentDir)}
|
|
14068
14718
|
`
|
|
@@ -14379,11 +15029,29 @@ async function predictNextTasks(input, opts) {
|
|
|
14379
15029
|
}
|
|
14380
15030
|
}
|
|
14381
15031
|
init_sdd();
|
|
15032
|
+
function parseSuggestionsFromOutput(finalText) {
|
|
15033
|
+
const patterns = [
|
|
15034
|
+
/💡\s*Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i,
|
|
15035
|
+
/##?\s*Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i,
|
|
15036
|
+
/Next\s+steps?\s*\n((?:\d+\.\s+.+\n?)+)/i
|
|
15037
|
+
];
|
|
15038
|
+
for (const pat of patterns) {
|
|
15039
|
+
const m = pat.exec(finalText);
|
|
15040
|
+
if (m && m[1]) {
|
|
15041
|
+
const block = m[1].trim();
|
|
15042
|
+
const lines = block.split("\n").filter(Boolean);
|
|
15043
|
+
const suggestions = lines.map((l) => l.replace(/^\d+\.\s*/, "").trim()).filter((s) => s.length > 3);
|
|
15044
|
+
if (suggestions.length > 0) return suggestions.slice(0, 5);
|
|
15045
|
+
}
|
|
15046
|
+
}
|
|
15047
|
+
return null;
|
|
15048
|
+
}
|
|
14382
15049
|
async function runRepl(opts) {
|
|
14383
15050
|
if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
|
|
14384
15051
|
await renderGoalBanner(opts);
|
|
14385
15052
|
let activeCtrl;
|
|
14386
15053
|
let interrupts = 0;
|
|
15054
|
+
let autoIterCount = 0;
|
|
14387
15055
|
let exiting = false;
|
|
14388
15056
|
const onSigint = () => {
|
|
14389
15057
|
interrupts++;
|
|
@@ -14538,6 +15206,96 @@ async function runRepl(opts) {
|
|
|
14538
15206
|
continue;
|
|
14539
15207
|
}
|
|
14540
15208
|
}
|
|
15209
|
+
{
|
|
15210
|
+
const mode = opts.getAutonomy?.() ?? "off";
|
|
15211
|
+
const suggestions = opts.getSuggestions?.() ?? [];
|
|
15212
|
+
if (mode === "suggest" && suggestions.length > 0) {
|
|
15213
|
+
const lines = suggestions.map(
|
|
15214
|
+
(s, i) => ` ${color.bold(`${i + 1}.`)} ${color.dim(s)}`
|
|
15215
|
+
);
|
|
15216
|
+
opts.renderer.write(
|
|
15217
|
+
`
|
|
15218
|
+
${color.cyan(" \u{1F4A1} Suggested next steps")} ${color.dim("(use /next 1, /next 2, or /next 1 2 3)")}
|
|
15219
|
+
${lines.join("\n")}
|
|
15220
|
+
|
|
15221
|
+
`
|
|
15222
|
+
);
|
|
15223
|
+
}
|
|
15224
|
+
if (mode === "auto" && suggestions.length > 0) {
|
|
15225
|
+
const maxIter = opts.autoProceedMaxIterations ?? 50;
|
|
15226
|
+
if (maxIter > 0 && autoIterCount >= maxIter) {
|
|
15227
|
+
console.log(JSON.stringify({
|
|
15228
|
+
level: "info",
|
|
15229
|
+
event: "auto_proceed_limit_reached",
|
|
15230
|
+
iterations: autoIterCount,
|
|
15231
|
+
maxIterations: maxIter
|
|
15232
|
+
}));
|
|
15233
|
+
opts.renderer.write(
|
|
15234
|
+
`
|
|
15235
|
+
${color.amber(" \u26A0 Auto-proceed limit reached")} \u2014 ${color.dim(`${autoIterCount} iterations. Waiting for input.`)}
|
|
15236
|
+
|
|
15237
|
+
`
|
|
15238
|
+
);
|
|
15239
|
+
autoIterCount = 0;
|
|
15240
|
+
} else {
|
|
15241
|
+
const top = suggestions[0] ?? "";
|
|
15242
|
+
const delay = opts.autoProceedDelayMs ?? 45e3;
|
|
15243
|
+
if (opts.onValidateAutoProceed) {
|
|
15244
|
+
try {
|
|
15245
|
+
const lastOutput = (
|
|
15246
|
+
/* last known agent output — use the stored suggestions
|
|
15247
|
+
and any recent context the host can provide */
|
|
15248
|
+
""
|
|
15249
|
+
);
|
|
15250
|
+
const ok = await opts.onValidateAutoProceed(top, lastOutput);
|
|
15251
|
+
if (!ok) {
|
|
15252
|
+
opts.renderer.write(
|
|
15253
|
+
`
|
|
15254
|
+
${color.amber(" \u26A0 Auto-proceed held")} \u2014 ${color.dim("suggestions need review. Waiting for input.")}
|
|
15255
|
+
|
|
15256
|
+
`
|
|
15257
|
+
);
|
|
15258
|
+
const lines = suggestions.map(
|
|
15259
|
+
(s, i) => ` ${color.bold(`${i + 1}.`)} ${color.dim(s)}`
|
|
15260
|
+
);
|
|
15261
|
+
opts.renderer.write(
|
|
15262
|
+
`${color.dim(" Next steps:")}
|
|
15263
|
+
${lines.join("\n")}
|
|
15264
|
+
|
|
15265
|
+
`
|
|
15266
|
+
);
|
|
15267
|
+
autoIterCount = 0;
|
|
15268
|
+
} else {
|
|
15269
|
+
const ctrl = new AbortController();
|
|
15270
|
+
activeCtrl = ctrl;
|
|
15271
|
+
try {
|
|
15272
|
+
autoIterCount++;
|
|
15273
|
+
await runAutoProceed(opts, top, delay, ctrl);
|
|
15274
|
+
} finally {
|
|
15275
|
+
activeCtrl = void 0;
|
|
15276
|
+
}
|
|
15277
|
+
continue;
|
|
15278
|
+
}
|
|
15279
|
+
} catch {
|
|
15280
|
+
opts.renderer.write(
|
|
15281
|
+
color.dim(" Auto-proceed validation unavailable \u2014 waiting for input.\n")
|
|
15282
|
+
);
|
|
15283
|
+
autoIterCount = 0;
|
|
15284
|
+
}
|
|
15285
|
+
} else {
|
|
15286
|
+
const ctrl = new AbortController();
|
|
15287
|
+
activeCtrl = ctrl;
|
|
15288
|
+
try {
|
|
15289
|
+
autoIterCount++;
|
|
15290
|
+
await runAutoProceed(opts, top, delay, ctrl);
|
|
15291
|
+
} finally {
|
|
15292
|
+
activeCtrl = void 0;
|
|
15293
|
+
}
|
|
15294
|
+
continue;
|
|
15295
|
+
}
|
|
15296
|
+
}
|
|
15297
|
+
}
|
|
15298
|
+
}
|
|
14541
15299
|
let raw;
|
|
14542
15300
|
try {
|
|
14543
15301
|
raw = await readPossiblyMultiline(opts);
|
|
@@ -14631,6 +15389,10 @@ ${color.dim(taskList2)}
|
|
|
14631
15389
|
}
|
|
14632
15390
|
}
|
|
14633
15391
|
}
|
|
15392
|
+
if (opts.onSuggestionsParsed) {
|
|
15393
|
+
const parsed = parseSuggestionsFromOutput(runResult.finalText);
|
|
15394
|
+
opts.onSuggestionsParsed(parsed);
|
|
15395
|
+
}
|
|
14634
15396
|
}
|
|
14635
15397
|
} catch (_runErr) {
|
|
14636
15398
|
opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
|
|
@@ -14791,6 +15553,10 @@ ${color.dim(taskList2)}
|
|
|
14791
15553
|
}
|
|
14792
15554
|
}
|
|
14793
15555
|
}
|
|
15556
|
+
if (result.status === "done" && result.finalText && opts.onSuggestionsParsed) {
|
|
15557
|
+
const parsed = parseSuggestionsFromOutput(result.finalText);
|
|
15558
|
+
opts.onSuggestionsParsed(parsed);
|
|
15559
|
+
}
|
|
14794
15560
|
if (opts.tokenCounter && before) {
|
|
14795
15561
|
const after = opts.tokenCounter.total();
|
|
14796
15562
|
const costAfter = opts.tokenCounter.estimateCost().total;
|
|
@@ -14853,6 +15619,10 @@ ${color.cyan(" Suggested next steps:")}
|
|
|
14853
15619
|
${suggestResult.finalText}
|
|
14854
15620
|
`
|
|
14855
15621
|
);
|
|
15622
|
+
if (opts.onSuggestionsParsed) {
|
|
15623
|
+
const parsed = parseSuggestionsFromOutput(suggestResult.finalText);
|
|
15624
|
+
opts.onSuggestionsParsed(parsed);
|
|
15625
|
+
}
|
|
14856
15626
|
}
|
|
14857
15627
|
} catch {
|
|
14858
15628
|
} finally {
|
|
@@ -14983,6 +15753,79 @@ async function renderGoalBanner(opts) {
|
|
|
14983
15753
|
}
|
|
14984
15754
|
opts.renderer.write("\n");
|
|
14985
15755
|
}
|
|
15756
|
+
async function runAutoProceed(opts, suggestion, delayMs, ctrl) {
|
|
15757
|
+
const truncated = suggestion.length > 80 ? `${suggestion.slice(0, 77)}\u2026` : suggestion;
|
|
15758
|
+
console.log(JSON.stringify({
|
|
15759
|
+
level: "info",
|
|
15760
|
+
event: "auto_proceed_started",
|
|
15761
|
+
suggestion: truncated,
|
|
15762
|
+
delayMs
|
|
15763
|
+
}));
|
|
15764
|
+
try {
|
|
15765
|
+
await autoProceedCountdown(opts, delayMs, suggestion, ctrl.signal);
|
|
15766
|
+
const runBlocks = [{ type: "text", text: suggestion }];
|
|
15767
|
+
const runResult = await opts.agent.run(runBlocks, { signal: ctrl.signal });
|
|
15768
|
+
console.log(JSON.stringify({
|
|
15769
|
+
level: "info",
|
|
15770
|
+
event: "auto_proceed_completed",
|
|
15771
|
+
suggestion: truncated,
|
|
15772
|
+
status: runResult.status,
|
|
15773
|
+
iterations: runResult.iterations
|
|
15774
|
+
}));
|
|
15775
|
+
opts.onAgentIterationComplete?.(
|
|
15776
|
+
estimateRequestTokensCalibrated(
|
|
15777
|
+
opts.agent.ctx.messages,
|
|
15778
|
+
opts.agent.ctx.systemPrompt,
|
|
15779
|
+
opts.agent.ctx.tools ?? []
|
|
15780
|
+
).total
|
|
15781
|
+
);
|
|
15782
|
+
if (runResult.status === "done" && runResult.finalText && opts.onSuggestionsParsed) {
|
|
15783
|
+
const parsed = parseSuggestionsFromOutput(runResult.finalText);
|
|
15784
|
+
opts.onSuggestionsParsed(parsed);
|
|
15785
|
+
}
|
|
15786
|
+
} finally {
|
|
15787
|
+
}
|
|
15788
|
+
}
|
|
15789
|
+
async function autoProceedCountdown(opts, delayMs, suggestion, signal) {
|
|
15790
|
+
const sec = Math.ceil(delayMs / 1e3);
|
|
15791
|
+
const truncated = suggestion.length > 100 ? `${suggestion.slice(0, 97)}\u2026` : suggestion;
|
|
15792
|
+
opts.renderer.write(
|
|
15793
|
+
`
|
|
15794
|
+
${color.cyan("\u23F3 Auto-proceeding")} in ${sec}s\u2026 ${color.dim("(Ctrl+C to cancel)")}
|
|
15795
|
+
`
|
|
15796
|
+
);
|
|
15797
|
+
opts.renderer.write(`${color.dim(" \u25B8")} ${color.dim(truncated)}
|
|
15798
|
+
`);
|
|
15799
|
+
const start = Date.now();
|
|
15800
|
+
let lastSec = sec;
|
|
15801
|
+
return new Promise((resolve5, reject) => {
|
|
15802
|
+
const onAbort = () => {
|
|
15803
|
+
clearInterval(interval);
|
|
15804
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
15805
|
+
};
|
|
15806
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
15807
|
+
const interval = setInterval(() => {
|
|
15808
|
+
if (signal.aborted) return;
|
|
15809
|
+
const elapsed = Date.now() - start;
|
|
15810
|
+
const remaining = Math.max(0, Math.ceil((delayMs - elapsed) / 1e3));
|
|
15811
|
+
if (remaining <= 0) {
|
|
15812
|
+
clearInterval(interval);
|
|
15813
|
+
signal.removeEventListener("abort", onAbort);
|
|
15814
|
+
opts.renderer.write(color.dim(` \u21B3 Proceeding with: ${truncated}
|
|
15815
|
+
`));
|
|
15816
|
+
resolve5();
|
|
15817
|
+
return;
|
|
15818
|
+
}
|
|
15819
|
+
if (remaining !== lastSec) {
|
|
15820
|
+
lastSec = remaining;
|
|
15821
|
+
opts.renderer.write(
|
|
15822
|
+
color.dim(` \u23F3 ${remaining}s remaining\u2026 (Ctrl+C to cancel)
|
|
15823
|
+
`)
|
|
15824
|
+
);
|
|
15825
|
+
}
|
|
15826
|
+
}, 500);
|
|
15827
|
+
});
|
|
15828
|
+
}
|
|
14986
15829
|
async function readPossiblyMultiline(opts) {
|
|
14987
15830
|
const firstPrompt = theme2.primary("\u203A ");
|
|
14988
15831
|
const contPrompt = color.dim("\xB7 ");
|
|
@@ -15075,6 +15918,11 @@ async function execute(deps) {
|
|
|
15075
15918
|
getAutonomy,
|
|
15076
15919
|
onAutonomy,
|
|
15077
15920
|
getNextPredict,
|
|
15921
|
+
onSuggestionsParsed,
|
|
15922
|
+
getSuggestions,
|
|
15923
|
+
autoProceedDelayMs,
|
|
15924
|
+
autoProceedMaxIterations,
|
|
15925
|
+
onValidateAutoProceed,
|
|
15078
15926
|
getEternalEngine,
|
|
15079
15927
|
getParallelEngine,
|
|
15080
15928
|
subscribeEternalIteration,
|
|
@@ -15303,6 +16151,7 @@ async function execute(deps) {
|
|
|
15303
16151
|
auditLevel: cfg.session?.auditLevel ?? "standard",
|
|
15304
16152
|
indexOnStart: cfg.indexing?.onSessionStart !== false,
|
|
15305
16153
|
maxIterations: cfg.tools?.maxIterations ?? 500,
|
|
16154
|
+
autoProceedMaxIterations: cfg.autonomy?.autoProceedMaxIterations ?? 50,
|
|
15306
16155
|
debugStream: cfg.debugStream ?? false,
|
|
15307
16156
|
configScope: cfg.configScope ?? "global",
|
|
15308
16157
|
enhanceDelayMs: cfg.autonomy?.enhanceDelayMs ?? 6e4
|
|
@@ -15386,10 +16235,15 @@ async function execute(deps) {
|
|
|
15386
16235
|
autonomy.enhanceDelayMs = s.enhanceDelayMs;
|
|
15387
16236
|
decrypted.autonomy = autonomy;
|
|
15388
16237
|
}
|
|
16238
|
+
if (s.autoProceedMaxIterations !== void 0) {
|
|
16239
|
+
const autonomy = decrypted.autonomy ?? {};
|
|
16240
|
+
autonomy.autoProceedMaxIterations = s.autoProceedMaxIterations;
|
|
16241
|
+
decrypted.autonomy = autonomy;
|
|
16242
|
+
}
|
|
15389
16243
|
const toWrite = targetPath === wpaths.globalConfig ? decrypted : filterSafeForProject(decrypted);
|
|
15390
16244
|
const encrypted = encryptConfigSecrets$1(toWrite, vault);
|
|
15391
16245
|
if (targetPath !== wpaths.globalConfig) {
|
|
15392
|
-
await fsp4.mkdir(
|
|
16246
|
+
await fsp4.mkdir(path10.dirname(targetPath), { recursive: true }).catch((err) => console.debug(`[execution] mkdir failed: ${err}`));
|
|
15393
16247
|
}
|
|
15394
16248
|
await atomicWrite(targetPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
15395
16249
|
configStore.update({
|
|
@@ -15519,11 +16373,16 @@ async function execute(deps) {
|
|
|
15519
16373
|
supportsVision,
|
|
15520
16374
|
attachments,
|
|
15521
16375
|
effectiveMaxContext,
|
|
15522
|
-
projectName:
|
|
16376
|
+
projectName: path10.basename(projectRoot) || void 0,
|
|
15523
16377
|
projectRoot,
|
|
15524
16378
|
getAutonomy,
|
|
15525
16379
|
onAutonomy,
|
|
15526
16380
|
getNextPredict,
|
|
16381
|
+
onSuggestionsParsed,
|
|
16382
|
+
getSuggestions,
|
|
16383
|
+
autoProceedDelayMs,
|
|
16384
|
+
autoProceedMaxIterations,
|
|
16385
|
+
onValidateAutoProceed,
|
|
15527
16386
|
getEternalEngine,
|
|
15528
16387
|
getParallelEngine,
|
|
15529
16388
|
skillLoader,
|
|
@@ -15547,10 +16406,15 @@ async function execute(deps) {
|
|
|
15547
16406
|
supportsVision,
|
|
15548
16407
|
attachments,
|
|
15549
16408
|
effectiveMaxContext,
|
|
15550
|
-
projectName:
|
|
16409
|
+
projectName: path10.basename(projectRoot) || void 0,
|
|
15551
16410
|
getAutonomy,
|
|
15552
16411
|
onAutonomy,
|
|
15553
16412
|
getNextPredict,
|
|
16413
|
+
onSuggestionsParsed,
|
|
16414
|
+
getSuggestions,
|
|
16415
|
+
autoProceedDelayMs,
|
|
16416
|
+
onValidateAutoProceed,
|
|
16417
|
+
autoProceedMaxIterations,
|
|
15554
16418
|
getEternalEngine,
|
|
15555
16419
|
getParallelEngine,
|
|
15556
16420
|
skillLoader,
|
|
@@ -15679,7 +16543,7 @@ var MultiAgentHost = class {
|
|
|
15679
16543
|
doneCondition: { type: "all_tasks_done" },
|
|
15680
16544
|
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
15681
16545
|
};
|
|
15682
|
-
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ?
|
|
16546
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path10.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
15683
16547
|
this.director = new Director({
|
|
15684
16548
|
config: coordinatorConfig,
|
|
15685
16549
|
manifestPath: this.opts.manifestPath,
|
|
@@ -16014,10 +16878,30 @@ var MultiAgentHost = class {
|
|
|
16014
16878
|
model: opts?.model,
|
|
16015
16879
|
tools: opts?.tools
|
|
16016
16880
|
};
|
|
16017
|
-
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
|
|
16881
|
+
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig, description);
|
|
16018
16882
|
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
16019
16883
|
return { subagentId, taskId };
|
|
16020
16884
|
}
|
|
16885
|
+
/**
|
|
16886
|
+
* Spawn a fresh subagent, assign a task, and **await** its completion.
|
|
16887
|
+
*
|
|
16888
|
+
* Unlike `spawn()`, which returns immediately with spawn metadata, this
|
|
16889
|
+
* method blocks until the subagent finishes (success, failure, or timeout)
|
|
16890
|
+
* and returns the full `TaskResult`. Use this when the caller needs the
|
|
16891
|
+
* subagent's actual output — e.g. `/techstack` displaying the generated report
|
|
16892
|
+
* in chat, or `/spawn` showing the result inline.
|
|
16893
|
+
*
|
|
16894
|
+
* Optional `opts` lets the caller override the subagent's provider, model,
|
|
16895
|
+
* and tool slice per spawn.
|
|
16896
|
+
*/
|
|
16897
|
+
async spawnAndWait(description, opts) {
|
|
16898
|
+
const { taskId } = await this.spawn(description, opts);
|
|
16899
|
+
if (!this.director) throw new Error("Director is not initialized");
|
|
16900
|
+
const results = await this.director.awaitTasks([taskId]);
|
|
16901
|
+
const result = results[0];
|
|
16902
|
+
if (!result) throw new Error(`Task ${taskId} completed but no result returned`);
|
|
16903
|
+
return result;
|
|
16904
|
+
}
|
|
16021
16905
|
/**
|
|
16022
16906
|
* Common spawn + assign logic shared by both director mode and raw
|
|
16023
16907
|
* coordinator mode. Extracts the identical body from the two branches
|
|
@@ -16027,11 +16911,11 @@ var MultiAgentHost = class {
|
|
|
16027
16911
|
* Returns `{ subagentId, taskId }`. Caller holds `pending` tracking
|
|
16028
16912
|
* and event emission — the helper only talks to the coordinator.
|
|
16029
16913
|
*/
|
|
16030
|
-
async _spawnAndAssign(subagentConfig) {
|
|
16914
|
+
async _spawnAndAssign(subagentConfig, description = "") {
|
|
16031
16915
|
const taskId = randomUUID();
|
|
16032
16916
|
if (!this.director) throw new Error("Director is not initialized");
|
|
16033
16917
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
16034
|
-
await this.director.assign({ id: taskId, description
|
|
16918
|
+
await this.director.assign({ id: taskId, description, subagentId });
|
|
16035
16919
|
return { subagentId, taskId };
|
|
16036
16920
|
}
|
|
16037
16921
|
/**
|
|
@@ -16148,16 +17032,16 @@ var MultiAgentHost = class {
|
|
|
16148
17032
|
if (this.director) return this.director;
|
|
16149
17033
|
this.opts.directorMode = true;
|
|
16150
17034
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
16151
|
-
this.opts.manifestPath =
|
|
17035
|
+
this.opts.manifestPath = path10.join(this.opts.fleetRoot, "fleet.json");
|
|
16152
17036
|
}
|
|
16153
17037
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
16154
|
-
this.opts.sharedScratchpadPath =
|
|
17038
|
+
this.opts.sharedScratchpadPath = path10.join(this.opts.fleetRoot, "shared");
|
|
16155
17039
|
}
|
|
16156
17040
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
16157
|
-
this.opts.sessionsRoot =
|
|
17041
|
+
this.opts.sessionsRoot = path10.join(this.opts.fleetRoot, "subagents");
|
|
16158
17042
|
}
|
|
16159
17043
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
16160
|
-
this.opts.stateCheckpointPath =
|
|
17044
|
+
this.opts.stateCheckpointPath = path10.join(this.opts.fleetRoot, "director-state.json");
|
|
16161
17045
|
}
|
|
16162
17046
|
await this.ensureDirector();
|
|
16163
17047
|
return this.director ?? null;
|
|
@@ -16329,11 +17213,11 @@ var SessionStats = class {
|
|
|
16329
17213
|
if (e.name === "bash") this.bashCommands++;
|
|
16330
17214
|
else if (e.name === "fetch") this.fetches++;
|
|
16331
17215
|
if (!e.ok) return;
|
|
16332
|
-
const
|
|
16333
|
-
if (e.name === "read" &&
|
|
16334
|
-
else if (e.name === "edit" &&
|
|
16335
|
-
else if (e.name === "write" &&
|
|
16336
|
-
this.writtenPaths.add(
|
|
17216
|
+
const path28 = typeof input?.path === "string" ? input.path : void 0;
|
|
17217
|
+
if (e.name === "read" && path28) this.readPaths.add(path28);
|
|
17218
|
+
else if (e.name === "edit" && path28) this.editedPaths.add(path28);
|
|
17219
|
+
else if (e.name === "write" && path28) {
|
|
17220
|
+
this.writtenPaths.add(path28);
|
|
16337
17221
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
16338
17222
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
16339
17223
|
}
|
|
@@ -17160,7 +18044,7 @@ function setupMetrics(params) {
|
|
|
17160
18044
|
const dumpMetrics = () => {
|
|
17161
18045
|
if (!metricsSink) return;
|
|
17162
18046
|
try {
|
|
17163
|
-
const out =
|
|
18047
|
+
const out = path10.join(wpaths.projectSessions, "metrics.json");
|
|
17164
18048
|
const snap = metricsSink.snapshot();
|
|
17165
18049
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
17166
18050
|
} catch {
|
|
@@ -17235,7 +18119,7 @@ async function setupCodebaseIndexing(deps) {
|
|
|
17235
18119
|
if (tool?.mutating && FILE_EDIT_TOOLS.has(tool.name)) {
|
|
17236
18120
|
const fp = payload.toolUse.input?.file_path;
|
|
17237
18121
|
if (typeof fp === "string" && fp.length > 0) {
|
|
17238
|
-
const abs =
|
|
18122
|
+
const abs = path10.resolve(payload.ctx.cwd, fp);
|
|
17239
18123
|
if (isIndexableFile(abs)) {
|
|
17240
18124
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
17241
18125
|
}
|
|
@@ -17250,11 +18134,11 @@ async function setupCodebaseIndexing(deps) {
|
|
|
17250
18134
|
let watcher;
|
|
17251
18135
|
if (idx.watchExternal) {
|
|
17252
18136
|
try {
|
|
17253
|
-
watcher =
|
|
18137
|
+
watcher = fs15.watch(projectRoot, { recursive: true }, (_event, filename) => {
|
|
17254
18138
|
if (!filename) return;
|
|
17255
18139
|
const rel = filename.toString();
|
|
17256
18140
|
if (isIgnored(rel)) return;
|
|
17257
|
-
const abs =
|
|
18141
|
+
const abs = path10.resolve(projectRoot, rel);
|
|
17258
18142
|
if (!isIndexableFile(abs)) return;
|
|
17259
18143
|
enqueueReindex({ projectRoot, files: [abs], debounceMs, onError });
|
|
17260
18144
|
});
|
|
@@ -17543,9 +18427,9 @@ async function setupSession(params) {
|
|
|
17543
18427
|
const sessionRef = { current: session };
|
|
17544
18428
|
await recoveryLock.write(session?.id).catch(() => void 0);
|
|
17545
18429
|
const attachments = new DefaultAttachmentStore({
|
|
17546
|
-
spoolDir:
|
|
18430
|
+
spoolDir: path10.join(wpaths.projectSessions, session?.id, "attachments")
|
|
17547
18431
|
});
|
|
17548
|
-
const queueStore = new QueueStore({ dir:
|
|
18432
|
+
const queueStore = new QueueStore({ dir: path10.join(wpaths.projectSessions, session?.id) });
|
|
17549
18433
|
const ctxSignal = new AbortController().signal;
|
|
17550
18434
|
const context = new Context({
|
|
17551
18435
|
systemPrompt,
|
|
@@ -17558,7 +18442,7 @@ async function setupSession(params) {
|
|
|
17558
18442
|
model: config.model
|
|
17559
18443
|
});
|
|
17560
18444
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
17561
|
-
const todosCheckpointPath =
|
|
18445
|
+
const todosCheckpointPath = path10.join(wpaths.projectSessions, `${session?.id}.todos.json`);
|
|
17562
18446
|
if (resumeId) {
|
|
17563
18447
|
try {
|
|
17564
18448
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -17576,15 +18460,15 @@ async function setupSession(params) {
|
|
|
17576
18460
|
todosCheckpointPath,
|
|
17577
18461
|
session?.id
|
|
17578
18462
|
);
|
|
17579
|
-
const planPath =
|
|
18463
|
+
const planPath = path10.join(wpaths.projectSessions, `${session?.id}.plan.json`);
|
|
17580
18464
|
context.state.setMeta("plan.path", planPath);
|
|
17581
|
-
const taskPath =
|
|
18465
|
+
const taskPath = path10.join(wpaths.projectSessions, `${session?.id}.tasks.json`);
|
|
17582
18466
|
context.state.setMeta("task.path", taskPath);
|
|
17583
18467
|
let dirState;
|
|
17584
18468
|
if (resumeId) {
|
|
17585
18469
|
try {
|
|
17586
|
-
const fleetRoot =
|
|
17587
|
-
dirState = await loadDirectorState(
|
|
18470
|
+
const fleetRoot = path10.join(wpaths.projectSessions, session?.id);
|
|
18471
|
+
dirState = await loadDirectorState(path10.join(fleetRoot, "director-state.json"));
|
|
17588
18472
|
if (dirState) {
|
|
17589
18473
|
const tCounts = {};
|
|
17590
18474
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -17624,7 +18508,7 @@ function resolveBundledSkillsDir2() {
|
|
|
17624
18508
|
try {
|
|
17625
18509
|
const req2 = createRequire(import.meta.url);
|
|
17626
18510
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
17627
|
-
return
|
|
18511
|
+
return path10.join(path10.dirname(corePkg), "skills");
|
|
17628
18512
|
} catch {
|
|
17629
18513
|
return void 0;
|
|
17630
18514
|
}
|
|
@@ -17735,9 +18619,8 @@ async function main(argv) {
|
|
|
17735
18619
|
updateInfo
|
|
17736
18620
|
} = ctx;
|
|
17737
18621
|
updateInfo = await printUpdateNotice(updateInfo);
|
|
17738
|
-
const { setDebugStreamEnabled
|
|
18622
|
+
const { setDebugStreamEnabled } = await import('@wrongstack/providers');
|
|
17739
18623
|
if (config.debugStream) setDebugStreamEnabled(true);
|
|
17740
|
-
setDebugStreamCallback(defaultDebugStreamCallback);
|
|
17741
18624
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
17742
18625
|
const events = new EventBus();
|
|
17743
18626
|
events.setLogger(logger);
|
|
@@ -17824,7 +18707,7 @@ async function main(argv) {
|
|
|
17824
18707
|
modeId,
|
|
17825
18708
|
modePrompt,
|
|
17826
18709
|
modelCapabilities,
|
|
17827
|
-
planPath: () => sessionRef.current ?
|
|
18710
|
+
planPath: () => sessionRef.current ? path10.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
|
|
17828
18711
|
contributors: [
|
|
17829
18712
|
// Injects the ETERNAL AUTONOMY block when the user has activated
|
|
17830
18713
|
// a long-running autonomy engine. Without this, the per-iteration
|
|
@@ -18194,6 +19077,7 @@ async function main(argv) {
|
|
|
18194
19077
|
})();
|
|
18195
19078
|
autonomyModeRef.current = autonomyMode;
|
|
18196
19079
|
let nextPredictEnabled = config.nextPrediction === true;
|
|
19080
|
+
let currentSuggestions = [];
|
|
18197
19081
|
let eternalEngine = null;
|
|
18198
19082
|
let parallelEngine = null;
|
|
18199
19083
|
const eternalListeners = /* @__PURE__ */ new Set();
|
|
@@ -18214,12 +19098,12 @@ async function main(argv) {
|
|
|
18214
19098
|
}
|
|
18215
19099
|
}
|
|
18216
19100
|
};
|
|
18217
|
-
const fleetRoot = directorMode ?
|
|
18218
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
18219
|
-
const sharedScratchpadPath = directorMode ?
|
|
18220
|
-
const subagentSessionsRoot = directorMode ?
|
|
18221
|
-
const stateCheckpointPath = directorMode ?
|
|
18222
|
-
const fleetRootForPromotion =
|
|
19101
|
+
const fleetRoot = directorMode ? path10.join(wpaths.projectSessions, session.id) : void 0;
|
|
19102
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path10.join(expectDefined(fleetRoot), "fleet.json") : void 0;
|
|
19103
|
+
const sharedScratchpadPath = directorMode ? path10.join(expectDefined(fleetRoot), "shared") : void 0;
|
|
19104
|
+
const subagentSessionsRoot = directorMode ? path10.join(expectDefined(fleetRoot), "subagents") : void 0;
|
|
19105
|
+
const stateCheckpointPath = directorMode ? path10.join(expectDefined(fleetRoot), "director-state.json") : void 0;
|
|
19106
|
+
const fleetRootForPromotion = path10.join(wpaths.projectSessions, session.id);
|
|
18223
19107
|
const brainQueue = new BrainDecisionQueue(events);
|
|
18224
19108
|
const brain = new ObservableBrainArbiter(
|
|
18225
19109
|
new HumanEscalatingBrainArbiter(new DefaultBrainArbiter(), brainQueue),
|
|
@@ -18392,6 +19276,25 @@ async function main(argv) {
|
|
|
18392
19276
|
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
18393
19277
|
return `Spawned subagent ${subagentId}${tag} for task ${taskId}. Use /agents to track progress.`;
|
|
18394
19278
|
},
|
|
19279
|
+
onSpawnAndWait: async (description, spawnOpts) => {
|
|
19280
|
+
const result = await multiAgentHost.spawnAndWait(description, spawnOpts);
|
|
19281
|
+
const tags = [];
|
|
19282
|
+
if (spawnOpts?.provider) tags.push(spawnOpts.provider);
|
|
19283
|
+
if (spawnOpts?.model) tags.push(spawnOpts.model);
|
|
19284
|
+
if (spawnOpts?.name) tags.push(spawnOpts.name);
|
|
19285
|
+
const tag = tags.length > 0 ? ` (${tags.join(" / ")})` : "";
|
|
19286
|
+
const secs = (result.durationMs / 1e3).toFixed(result.durationMs < 1e4 ? 1 : 0);
|
|
19287
|
+
const icon = result.status === "success" ? "\u2713" : result.status === "timeout" ? "\u23F1" : result.status === "stopped" ? "\u2298" : "\u2717";
|
|
19288
|
+
const resultPreview = typeof result.result === "string" && result.result.trim() ? `
|
|
19289
|
+
${color.dim("\u2500".repeat(40))}
|
|
19290
|
+
${result.result.trim().slice(0, 600)}${result.result.trim().length > 600 ? "\n\u2026" : ""}
|
|
19291
|
+
${color.dim("\u2500".repeat(40))}` : "";
|
|
19292
|
+
return [
|
|
19293
|
+
`${icon} ${color.bold(tag ? tag.slice(1) : "subagent")} ${result.status} (${result.iterations} iter \xB7 ${result.toolCalls} tools \xB7 ${secs}s)`,
|
|
19294
|
+
resultPreview,
|
|
19295
|
+
result.error ? ` ${color.amber(`error: ${result.error.message}`)}` : ""
|
|
19296
|
+
].filter(Boolean).join("\n");
|
|
19297
|
+
},
|
|
18395
19298
|
onAgents: (subagentId) => {
|
|
18396
19299
|
const s = multiAgentHost.status();
|
|
18397
19300
|
if (subagentId) {
|
|
@@ -18584,7 +19487,7 @@ async function main(argv) {
|
|
|
18584
19487
|
return director.spawn(cfg);
|
|
18585
19488
|
},
|
|
18586
19489
|
onFleetLog: async (subagentId, mode) => {
|
|
18587
|
-
const subagentsRoot =
|
|
19490
|
+
const subagentsRoot = path10.join(fleetRootForPromotion, "subagents");
|
|
18588
19491
|
let runDirs;
|
|
18589
19492
|
try {
|
|
18590
19493
|
runDirs = await fsp4.readdir(subagentsRoot);
|
|
@@ -18593,7 +19496,7 @@ async function main(argv) {
|
|
|
18593
19496
|
}
|
|
18594
19497
|
const found = [];
|
|
18595
19498
|
for (const runId of runDirs) {
|
|
18596
|
-
const runDir =
|
|
19499
|
+
const runDir = path10.join(subagentsRoot, runId);
|
|
18597
19500
|
let files;
|
|
18598
19501
|
try {
|
|
18599
19502
|
files = await fsp4.readdir(runDir);
|
|
@@ -18602,7 +19505,7 @@ async function main(argv) {
|
|
|
18602
19505
|
}
|
|
18603
19506
|
for (const f of files) {
|
|
18604
19507
|
if (!f.endsWith(".jsonl")) continue;
|
|
18605
|
-
const full =
|
|
19508
|
+
const full = path10.join(runDir, f);
|
|
18606
19509
|
try {
|
|
18607
19510
|
const stat4 = await fsp4.stat(full);
|
|
18608
19511
|
found.push({
|
|
@@ -18703,7 +19606,7 @@ async function main(argv) {
|
|
|
18703
19606
|
}
|
|
18704
19607
|
const dir = await multiAgentHost.ensureDirector();
|
|
18705
19608
|
if (!dir) return "Director is not available.";
|
|
18706
|
-
const dirStatePath =
|
|
19609
|
+
const dirStatePath = path10.join(fleetRootForPromotion, "director-state.json");
|
|
18707
19610
|
const prior = await loadDirectorState(dirStatePath);
|
|
18708
19611
|
if (!prior) {
|
|
18709
19612
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -18772,9 +19675,9 @@ async function main(argv) {
|
|
|
18772
19675
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
18773
19676
|
toolRegistry.register(tool);
|
|
18774
19677
|
}
|
|
18775
|
-
const mp =
|
|
18776
|
-
const sp =
|
|
18777
|
-
const ss =
|
|
19678
|
+
const mp = path10.join(fleetRootForPromotion, "fleet.json");
|
|
19679
|
+
const sp = path10.join(fleetRootForPromotion, "shared");
|
|
19680
|
+
const ss = path10.join(fleetRootForPromotion, "subagents");
|
|
18778
19681
|
const lines = [
|
|
18779
19682
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
18780
19683
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -18848,6 +19751,12 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
18848
19751
|
}
|
|
18849
19752
|
return nextPredictEnabled;
|
|
18850
19753
|
},
|
|
19754
|
+
onSuggestions: (suggestions) => {
|
|
19755
|
+
if (suggestions !== void 0) {
|
|
19756
|
+
currentSuggestions = suggestions;
|
|
19757
|
+
}
|
|
19758
|
+
return currentSuggestions;
|
|
19759
|
+
},
|
|
18851
19760
|
onAutonomy: (setTo) => {
|
|
18852
19761
|
if (setTo !== void 0) {
|
|
18853
19762
|
autonomyMode = setTo;
|
|
@@ -19094,6 +20003,54 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
19094
20003
|
return autonomyMode;
|
|
19095
20004
|
},
|
|
19096
20005
|
getNextPredict: () => nextPredictEnabled,
|
|
20006
|
+
onSuggestionsParsed: (suggestions) => {
|
|
20007
|
+
currentSuggestions = suggestions ?? [];
|
|
20008
|
+
},
|
|
20009
|
+
getSuggestions: () => currentSuggestions,
|
|
20010
|
+
autoProceedDelayMs: config.autonomy?.autoProceedDelayMs ?? 45e3,
|
|
20011
|
+
autoProceedMaxIterations: config.autonomy?.autoProceedMaxIterations ?? 50,
|
|
20012
|
+
onValidateAutoProceed: async (suggestion, lastOutput) => {
|
|
20013
|
+
try {
|
|
20014
|
+
const resp = await context.provider.complete(
|
|
20015
|
+
{
|
|
20016
|
+
model: context.model,
|
|
20017
|
+
system: [
|
|
20018
|
+
{
|
|
20019
|
+
type: "text",
|
|
20020
|
+
text: "You are a safety validator for an autonomous coding agent. Your ONLY job is to decide whether the agent should auto-proceed with a suggested next step, or whether a human should review first. Reply with exactly one word: YES or NO."
|
|
20021
|
+
}
|
|
20022
|
+
],
|
|
20023
|
+
messages: [
|
|
20024
|
+
{
|
|
20025
|
+
role: "user",
|
|
20026
|
+
content: [
|
|
20027
|
+
{
|
|
20028
|
+
type: "text",
|
|
20029
|
+
text: `The autonomous agent just completed a turn and generated this top-ranked next-step suggestion:
|
|
20030
|
+
|
|
20031
|
+
"${suggestion}"
|
|
20032
|
+
|
|
20033
|
+
${lastOutput ? `Recent agent output:
|
|
20034
|
+
${lastOutput.slice(0, 500)}
|
|
20035
|
+
|
|
20036
|
+
` : ""}Should the agent auto-proceed with this suggestion, or should a human review first?
|
|
20037
|
+
|
|
20038
|
+
Reply YES to auto-proceed, NO to wait for human input.`
|
|
20039
|
+
}
|
|
20040
|
+
]
|
|
20041
|
+
}
|
|
20042
|
+
],
|
|
20043
|
+
maxTokens: 5,
|
|
20044
|
+
temperature: 0
|
|
20045
|
+
},
|
|
20046
|
+
{ signal: AbortSignal.timeout(1e4) }
|
|
20047
|
+
);
|
|
20048
|
+
const text = resp.content.filter((b) => b.type === "text").map((b) => "text" in b ? b.text : "").join("").trim().toUpperCase();
|
|
20049
|
+
return text.startsWith("YES");
|
|
20050
|
+
} catch {
|
|
20051
|
+
return false;
|
|
20052
|
+
}
|
|
20053
|
+
},
|
|
19097
20054
|
getEternalEngine: () => eternalEngine,
|
|
19098
20055
|
getParallelEngine: () => parallelEngine,
|
|
19099
20056
|
subscribeEternalIteration: (fn) => {
|