@wrongstack/cli 0.5.2 → 0.5.3
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 +739 -169
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
2
|
+
import * as path20 from 'path';
|
|
3
3
|
import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
import * as fs3 from 'fs/promises';
|
|
6
|
+
import * as os6 from 'os';
|
|
7
|
+
import os6__default from 'os';
|
|
4
8
|
import * as crypto from 'crypto';
|
|
5
9
|
import { randomUUID } from 'crypto';
|
|
6
|
-
import * as fs5 from 'fs/promises';
|
|
7
10
|
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
8
11
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
9
12
|
import { writeFileSync } from 'fs';
|
|
10
|
-
import { createRequire } from 'module';
|
|
11
13
|
import { MCPRegistry } from '@wrongstack/mcp';
|
|
12
14
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
13
15
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
14
16
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
15
|
-
import * as os4 from 'os';
|
|
16
17
|
import * as readline from 'readline';
|
|
17
18
|
import { spawn } from 'child_process';
|
|
18
19
|
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
@@ -22,6 +23,12 @@ var __defProp = Object.defineProperty;
|
|
|
22
23
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
23
24
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
24
25
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
26
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
27
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
28
|
+
}) : x)(function(x) {
|
|
29
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
30
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
31
|
+
});
|
|
25
32
|
var __esm = (fn, res) => function __init() {
|
|
26
33
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
27
34
|
};
|
|
@@ -237,8 +244,8 @@ function buildSddCommand(opts) {
|
|
|
237
244
|
async run(args) {
|
|
238
245
|
const ctx = opts.context;
|
|
239
246
|
const projectRoot = ctx?.projectRoot ?? process.cwd();
|
|
240
|
-
const specsDir =
|
|
241
|
-
const graphsDir =
|
|
247
|
+
const specsDir = path20.join(projectRoot, ".wrongstack", "specs");
|
|
248
|
+
const graphsDir = path20.join(projectRoot, ".wrongstack", "task-graphs");
|
|
242
249
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
243
250
|
new TaskGraphStore({ baseDir: graphsDir });
|
|
244
251
|
const versioning = new SpecVersioning();
|
|
@@ -254,7 +261,7 @@ function buildSddCommand(opts) {
|
|
|
254
261
|
const forceFlag = rest.includes("--force") || rest.includes("-f");
|
|
255
262
|
const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
|
|
256
263
|
if (!activeBuilder && !forceFlag) {
|
|
257
|
-
const sessionPath =
|
|
264
|
+
const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
258
265
|
try {
|
|
259
266
|
const fsp = await import('fs/promises');
|
|
260
267
|
await fsp.access(sessionPath);
|
|
@@ -292,7 +299,7 @@ function buildSddCommand(opts) {
|
|
|
292
299
|
projectContext,
|
|
293
300
|
minQuestions: 2,
|
|
294
301
|
maxQuestions: 10,
|
|
295
|
-
sessionPath:
|
|
302
|
+
sessionPath: path20.join(projectRoot, ".wrongstack", "sdd-session.json")
|
|
296
303
|
});
|
|
297
304
|
activeBuilder.startSession(title);
|
|
298
305
|
const aiPrompt = activeBuilder.getAIPrompt();
|
|
@@ -552,7 +559,7 @@ Start executing the tasks one by one.`
|
|
|
552
559
|
};
|
|
553
560
|
}
|
|
554
561
|
case "cancel": {
|
|
555
|
-
const sessionPath =
|
|
562
|
+
const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
556
563
|
let deletedFromDisk = false;
|
|
557
564
|
try {
|
|
558
565
|
const fsp = await import('fs/promises');
|
|
@@ -578,7 +585,7 @@ Start executing the tasks one by one.`
|
|
|
578
585
|
if (activeBuilder) {
|
|
579
586
|
return { message: "An SDD session is already active. Use /sdd cancel first." };
|
|
580
587
|
}
|
|
581
|
-
const sessionPath =
|
|
588
|
+
const sessionPath = path20.join(projectRoot, ".wrongstack", "sdd-session.json");
|
|
582
589
|
const projectContext = await gatherProjectContext(projectRoot);
|
|
583
590
|
activeBuilder = new AISpecBuilder({
|
|
584
591
|
store: specStore,
|
|
@@ -804,7 +811,7 @@ async function gatherProjectContext(projectRoot) {
|
|
|
804
811
|
const parts = [];
|
|
805
812
|
try {
|
|
806
813
|
const fsp = await import('fs/promises');
|
|
807
|
-
const pkgPath =
|
|
814
|
+
const pkgPath = path20.join(projectRoot, "package.json");
|
|
808
815
|
const pkgRaw = await fsp.readFile(pkgPath, "utf8");
|
|
809
816
|
const pkg = JSON.parse(pkgRaw);
|
|
810
817
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -821,14 +828,14 @@ async function gatherProjectContext(projectRoot) {
|
|
|
821
828
|
}
|
|
822
829
|
try {
|
|
823
830
|
const fsp = await import('fs/promises');
|
|
824
|
-
const tsconfigPath =
|
|
831
|
+
const tsconfigPath = path20.join(projectRoot, "tsconfig.json");
|
|
825
832
|
await fsp.access(tsconfigPath);
|
|
826
833
|
parts.push("Language: TypeScript");
|
|
827
834
|
} catch {
|
|
828
835
|
}
|
|
829
836
|
try {
|
|
830
837
|
const fsp = await import('fs/promises');
|
|
831
|
-
const srcDir =
|
|
838
|
+
const srcDir = path20.join(projectRoot, "src");
|
|
832
839
|
const entries = await fsp.readdir(srcDir, { withFileTypes: true });
|
|
833
840
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
834
841
|
if (dirs.length > 0) {
|
|
@@ -900,6 +907,130 @@ var init_provider_config_utils = __esm({
|
|
|
900
907
|
}
|
|
901
908
|
});
|
|
902
909
|
|
|
910
|
+
// src/update-check.ts
|
|
911
|
+
var update_check_exports = {};
|
|
912
|
+
__export(update_check_exports, {
|
|
913
|
+
cachePath: () => cachePath,
|
|
914
|
+
checkForUpdate: () => checkForUpdate,
|
|
915
|
+
currentVersion: () => currentVersion,
|
|
916
|
+
getUpdateNotification: () => getUpdateNotification
|
|
917
|
+
});
|
|
918
|
+
function cachePath(homeFn = defaultHomeDir2) {
|
|
919
|
+
return path20.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
920
|
+
}
|
|
921
|
+
function currentVersion() {
|
|
922
|
+
const req2 = createRequire(import.meta.url);
|
|
923
|
+
const candidates = ["../package.json", "../../package.json"];
|
|
924
|
+
for (const rel of candidates) {
|
|
925
|
+
try {
|
|
926
|
+
const pkg = req2(rel);
|
|
927
|
+
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return "dev";
|
|
932
|
+
}
|
|
933
|
+
function isNewer(a, b) {
|
|
934
|
+
const parse = (v) => v.replace(/^v/i, "").split(".").map((p) => parseInt(p, 10) || 0);
|
|
935
|
+
const [ap, bp] = [parse(a), parse(b)];
|
|
936
|
+
for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
|
|
937
|
+
const ai = ap[i] ?? 0;
|
|
938
|
+
const bi = bp[i] ?? 0;
|
|
939
|
+
if (ai > bi) return true;
|
|
940
|
+
if (ai < bi) return false;
|
|
941
|
+
}
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
async function readCache(homeFn = defaultHomeDir2) {
|
|
945
|
+
try {
|
|
946
|
+
const raw = await fs3.readFile(cachePath(homeFn), "utf8");
|
|
947
|
+
const entry = JSON.parse(raw);
|
|
948
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return null;
|
|
949
|
+
return entry;
|
|
950
|
+
} catch {
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
955
|
+
try {
|
|
956
|
+
const dir = path20.dirname(cachePath(homeFn));
|
|
957
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
958
|
+
await fs3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
959
|
+
} catch {
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
async function fetchLatestFromNpm(timeoutMs = 3e3) {
|
|
963
|
+
const controller = new AbortController();
|
|
964
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
965
|
+
try {
|
|
966
|
+
const res = await fetch("https://registry.npmjs.org/wrongstack/latest", {
|
|
967
|
+
signal: controller.signal,
|
|
968
|
+
headers: { "Accept": "application/json" }
|
|
969
|
+
});
|
|
970
|
+
clearTimeout(timer);
|
|
971
|
+
if (!res.ok) throw new Error(`npm registry responded ${res.status}`);
|
|
972
|
+
const data = await res.json();
|
|
973
|
+
if (typeof data.version === "string") return data.version;
|
|
974
|
+
throw new Error("No version field in npm response");
|
|
975
|
+
} finally {
|
|
976
|
+
clearTimeout(timer);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
async function checkForUpdate(signal, homeFn) {
|
|
980
|
+
const current = currentVersion();
|
|
981
|
+
const aborted = () => signal?.aborted ?? false;
|
|
982
|
+
const hf = homeFn ?? defaultHomeDir2;
|
|
983
|
+
if (aborted()) {
|
|
984
|
+
return { current, latest: current, outdated: false, checkFailed: true };
|
|
985
|
+
}
|
|
986
|
+
const cached = await readCache(hf);
|
|
987
|
+
if (cached && !cached.error) {
|
|
988
|
+
return {
|
|
989
|
+
current,
|
|
990
|
+
latest: cached.latestVersion,
|
|
991
|
+
outdated: isNewer(cached.latestVersion, current),
|
|
992
|
+
checkFailed: false
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
try {
|
|
996
|
+
const latest = await fetchLatestFromNpm();
|
|
997
|
+
await writeCache({ timestamp: Date.now(), latestVersion: latest }, hf);
|
|
998
|
+
return {
|
|
999
|
+
current,
|
|
1000
|
+
latest,
|
|
1001
|
+
outdated: isNewer(latest, current),
|
|
1002
|
+
checkFailed: false
|
|
1003
|
+
};
|
|
1004
|
+
} catch (err) {
|
|
1005
|
+
if (aborted()) {
|
|
1006
|
+
return { current, latest: current, outdated: false, checkFailed: true };
|
|
1007
|
+
}
|
|
1008
|
+
if (cached?.latestVersion) {
|
|
1009
|
+
return {
|
|
1010
|
+
current,
|
|
1011
|
+
latest: cached.latestVersion,
|
|
1012
|
+
outdated: isNewer(cached.latestVersion, current),
|
|
1013
|
+
checkFailed: true
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
return { current, latest: current, outdated: false, checkFailed: true };
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async function getUpdateNotification(signal, homeFn) {
|
|
1020
|
+
const info = await checkForUpdate(signal, homeFn);
|
|
1021
|
+
if (info.outdated) {
|
|
1022
|
+
return `Update available: v${info.current} \u2192 v${info.latest}`;
|
|
1023
|
+
}
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
var defaultHomeDir2, CACHE_TTL_MS;
|
|
1027
|
+
var init_update_check = __esm({
|
|
1028
|
+
"src/update-check.ts"() {
|
|
1029
|
+
defaultHomeDir2 = () => os6.homedir();
|
|
1030
|
+
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
|
|
903
1034
|
// src/webui-server.ts
|
|
904
1035
|
var webui_server_exports = {};
|
|
905
1036
|
__export(webui_server_exports, {
|
|
@@ -1006,7 +1137,7 @@ async function runWebUI(opts) {
|
|
|
1006
1137
|
})
|
|
1007
1138
|
);
|
|
1008
1139
|
}
|
|
1009
|
-
return new Promise((
|
|
1140
|
+
return new Promise((resolve4) => {
|
|
1010
1141
|
wss.on("listening", () => {
|
|
1011
1142
|
console.log(`[WebUI] WebSocket server running on ws://localhost:${port}`);
|
|
1012
1143
|
setupEvents();
|
|
@@ -1076,7 +1207,7 @@ async function runWebUI(opts) {
|
|
|
1076
1207
|
clients.clear();
|
|
1077
1208
|
wss.close(() => {
|
|
1078
1209
|
console.log("[WebUI] Server stopped");
|
|
1079
|
-
|
|
1210
|
+
resolve4();
|
|
1080
1211
|
});
|
|
1081
1212
|
}
|
|
1082
1213
|
process.on("SIGINT", shutdown);
|
|
@@ -1382,7 +1513,7 @@ async function runWebUI(opts) {
|
|
|
1382
1513
|
if (!opts.globalConfigPath) return {};
|
|
1383
1514
|
let raw;
|
|
1384
1515
|
try {
|
|
1385
|
-
raw = await
|
|
1516
|
+
raw = await fs3.readFile(opts.globalConfigPath, "utf8");
|
|
1386
1517
|
} catch {
|
|
1387
1518
|
return {};
|
|
1388
1519
|
}
|
|
@@ -1393,7 +1524,7 @@ async function runWebUI(opts) {
|
|
|
1393
1524
|
return {};
|
|
1394
1525
|
}
|
|
1395
1526
|
if (!parsed.providers) return {};
|
|
1396
|
-
const keyFile =
|
|
1527
|
+
const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
|
|
1397
1528
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1398
1529
|
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
1399
1530
|
}
|
|
@@ -1401,7 +1532,7 @@ async function runWebUI(opts) {
|
|
|
1401
1532
|
if (!opts.globalConfigPath) return;
|
|
1402
1533
|
let raw;
|
|
1403
1534
|
try {
|
|
1404
|
-
raw = await
|
|
1535
|
+
raw = await fs3.readFile(opts.globalConfigPath, "utf8");
|
|
1405
1536
|
} catch {
|
|
1406
1537
|
raw = "{}";
|
|
1407
1538
|
}
|
|
@@ -1412,7 +1543,7 @@ async function runWebUI(opts) {
|
|
|
1412
1543
|
parsed = {};
|
|
1413
1544
|
}
|
|
1414
1545
|
parsed.providers = providers;
|
|
1415
|
-
const keyFile =
|
|
1546
|
+
const keyFile = path20.join(path20.dirname(opts.globalConfigPath), ".key");
|
|
1416
1547
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
1417
1548
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
1418
1549
|
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
@@ -1440,6 +1571,37 @@ var init_plugin_api_factory = __esm({
|
|
|
1440
1571
|
}
|
|
1441
1572
|
});
|
|
1442
1573
|
|
|
1574
|
+
// src/slash-commands/commit-llm.ts
|
|
1575
|
+
async function generateCommitMessageWithLLM(diff, opts) {
|
|
1576
|
+
const systemPrompt = "You are a helpful assistant that generates concise, conventional-commit-formatted git commit messages. Analyze the provided diff and output ONLY the commit message (no explanation, no quotes). Format: <type>(<scope>): <short description> \u2014 <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, ci, build, temp. If the diff contains multiple unrelated changes, pick the most important one. Keep the description under 72 characters. Example: feat(cli): add /commit LLM integration";
|
|
1577
|
+
const userPrompt = `Here is the git diff:
|
|
1578
|
+
|
|
1579
|
+
${diff}`;
|
|
1580
|
+
try {
|
|
1581
|
+
const signal = new AbortController();
|
|
1582
|
+
const timeout = setTimeout(() => signal.abort(), 15e3);
|
|
1583
|
+
const resp = await opts.provider.complete(
|
|
1584
|
+
{
|
|
1585
|
+
model: opts.model,
|
|
1586
|
+
system: [{ type: "text", text: systemPrompt }],
|
|
1587
|
+
messages: [{ role: "user", content: [{ type: "text", text: userPrompt }] }],
|
|
1588
|
+
maxTokens: 80,
|
|
1589
|
+
temperature: 0.3
|
|
1590
|
+
},
|
|
1591
|
+
{ signal: signal.signal }
|
|
1592
|
+
);
|
|
1593
|
+
clearTimeout(timeout);
|
|
1594
|
+
const rawContent = resp.content;
|
|
1595
|
+
const text = Array.isArray(rawContent) ? rawContent[0]?.text ?? "" : typeof rawContent === "object" && rawContent !== null ? rawContent.text ?? "" : String(rawContent);
|
|
1596
|
+
const message = text.trim().split("\n")[0];
|
|
1597
|
+
if (message.length > 0 && message.length < 200) {
|
|
1598
|
+
return message;
|
|
1599
|
+
}
|
|
1600
|
+
} catch {
|
|
1601
|
+
}
|
|
1602
|
+
return "chore: update";
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1443
1605
|
// src/arg-parser.ts
|
|
1444
1606
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
1445
1607
|
"yolo",
|
|
@@ -1458,7 +1620,8 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
1458
1620
|
"output-json",
|
|
1459
1621
|
"prompt",
|
|
1460
1622
|
"metrics",
|
|
1461
|
-
"webui"
|
|
1623
|
+
"webui",
|
|
1624
|
+
"no-check"
|
|
1462
1625
|
]);
|
|
1463
1626
|
function parseArgs(argv) {
|
|
1464
1627
|
const flags = {};
|
|
@@ -1564,10 +1727,10 @@ function parseSpawnFlags(input) {
|
|
|
1564
1727
|
return { description: rest.trim(), opts };
|
|
1565
1728
|
}
|
|
1566
1729
|
async function bootConfig(flags) {
|
|
1567
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
1730
|
+
const cwd = typeof flags["cwd"] === "string" ? path20.resolve(flags["cwd"]) : process.cwd();
|
|
1568
1731
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
1569
1732
|
const projectRoot = pathResolver.projectRoot;
|
|
1570
|
-
const userHome =
|
|
1733
|
+
const userHome = os6.homedir();
|
|
1571
1734
|
const wpaths = resolveWstackPaths({ projectRoot, userHome });
|
|
1572
1735
|
await ensureProjectMeta(wpaths, projectRoot);
|
|
1573
1736
|
const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
|
|
@@ -1615,13 +1778,13 @@ function flagsToConfigPatch(flags) {
|
|
|
1615
1778
|
}
|
|
1616
1779
|
async function ensureProjectMeta(paths, projectRoot) {
|
|
1617
1780
|
try {
|
|
1618
|
-
await
|
|
1781
|
+
await fs3.mkdir(paths.projectDir, { recursive: true });
|
|
1619
1782
|
const meta = {
|
|
1620
1783
|
hash: paths.projectHash,
|
|
1621
1784
|
root: projectRoot,
|
|
1622
1785
|
lastSeen: (/* @__PURE__ */ new Date()).toISOString()
|
|
1623
1786
|
};
|
|
1624
|
-
await
|
|
1787
|
+
await fs3.writeFile(paths.projectMeta, JSON.stringify(meta, null, 2));
|
|
1625
1788
|
} catch {
|
|
1626
1789
|
}
|
|
1627
1790
|
}
|
|
@@ -1631,11 +1794,11 @@ var ReadlineInputReader = class {
|
|
|
1631
1794
|
history = [];
|
|
1632
1795
|
pending = false;
|
|
1633
1796
|
constructor(opts = {}) {
|
|
1634
|
-
this.historyFile = opts.historyFile ??
|
|
1797
|
+
this.historyFile = opts.historyFile ?? path20.join(os6.homedir(), ".wrongstack", "history");
|
|
1635
1798
|
}
|
|
1636
1799
|
async loadHistory() {
|
|
1637
1800
|
try {
|
|
1638
|
-
const raw = await
|
|
1801
|
+
const raw = await fs3.readFile(this.historyFile, "utf8");
|
|
1639
1802
|
this.history = raw.split("\n").filter(Boolean).slice(-1e3);
|
|
1640
1803
|
} catch {
|
|
1641
1804
|
this.history = [];
|
|
@@ -1643,8 +1806,8 @@ var ReadlineInputReader = class {
|
|
|
1643
1806
|
}
|
|
1644
1807
|
async saveHistory() {
|
|
1645
1808
|
try {
|
|
1646
|
-
await
|
|
1647
|
-
await
|
|
1809
|
+
await fs3.mkdir(path20.dirname(this.historyFile), { recursive: true });
|
|
1810
|
+
await fs3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
1648
1811
|
} catch {
|
|
1649
1812
|
}
|
|
1650
1813
|
}
|
|
@@ -1662,7 +1825,7 @@ var ReadlineInputReader = class {
|
|
|
1662
1825
|
async readLine(prompt) {
|
|
1663
1826
|
if (this.history.length === 0) await this.loadHistory();
|
|
1664
1827
|
while (this.pending) {
|
|
1665
|
-
await new Promise((
|
|
1828
|
+
await new Promise((resolve4) => setTimeout(resolve4, 50));
|
|
1666
1829
|
}
|
|
1667
1830
|
this.pending = true;
|
|
1668
1831
|
try {
|
|
@@ -1672,15 +1835,15 @@ var ReadlineInputReader = class {
|
|
|
1672
1835
|
this.rl = void 0;
|
|
1673
1836
|
}
|
|
1674
1837
|
const fresh = this.ensure();
|
|
1675
|
-
return new Promise((
|
|
1838
|
+
return new Promise((resolve4) => {
|
|
1676
1839
|
fresh.question(prompt ?? "> ", (line) => {
|
|
1677
1840
|
if (line.trim()) {
|
|
1678
1841
|
this.history.push(line);
|
|
1679
1842
|
void this.saveHistory();
|
|
1680
1843
|
}
|
|
1681
|
-
|
|
1844
|
+
resolve4(line);
|
|
1682
1845
|
});
|
|
1683
|
-
fresh.once("close", () =>
|
|
1846
|
+
fresh.once("close", () => resolve4(""));
|
|
1684
1847
|
}).then((result) => {
|
|
1685
1848
|
this.rl?.close();
|
|
1686
1849
|
this.rl = void 0;
|
|
@@ -1692,7 +1855,7 @@ var ReadlineInputReader = class {
|
|
|
1692
1855
|
}
|
|
1693
1856
|
async readKey(prompt, options) {
|
|
1694
1857
|
process.stdout.write(prompt);
|
|
1695
|
-
return new Promise((
|
|
1858
|
+
return new Promise((resolve4) => {
|
|
1696
1859
|
const stdin = process.stdin;
|
|
1697
1860
|
const wasRaw = stdin.isRaw;
|
|
1698
1861
|
const wasPaused = stdin.isPaused();
|
|
@@ -1703,7 +1866,7 @@ var ReadlineInputReader = class {
|
|
|
1703
1866
|
if (key === "") {
|
|
1704
1867
|
cleanup();
|
|
1705
1868
|
process.stdout.write("\n");
|
|
1706
|
-
|
|
1869
|
+
resolve4("");
|
|
1707
1870
|
return;
|
|
1708
1871
|
}
|
|
1709
1872
|
const opt = options.find(
|
|
@@ -1713,12 +1876,12 @@ var ReadlineInputReader = class {
|
|
|
1713
1876
|
cleanup();
|
|
1714
1877
|
process.stdout.write(`${opt.key}
|
|
1715
1878
|
`);
|
|
1716
|
-
|
|
1879
|
+
resolve4(opt.value);
|
|
1717
1880
|
}
|
|
1718
1881
|
};
|
|
1719
1882
|
const onClose = () => {
|
|
1720
1883
|
cleanup();
|
|
1721
|
-
|
|
1884
|
+
resolve4("");
|
|
1722
1885
|
};
|
|
1723
1886
|
const cleanup = () => {
|
|
1724
1887
|
stdin.off("data", onData);
|
|
@@ -1746,7 +1909,7 @@ var ReadlineInputReader = class {
|
|
|
1746
1909
|
this.rl?.close();
|
|
1747
1910
|
this.rl = void 0;
|
|
1748
1911
|
process.stdout.write(prompt);
|
|
1749
|
-
return new Promise((
|
|
1912
|
+
return new Promise((resolve4) => {
|
|
1750
1913
|
let buf = "";
|
|
1751
1914
|
const wasRaw = stdin.isRaw;
|
|
1752
1915
|
stdin.setRawMode(true);
|
|
@@ -1764,7 +1927,7 @@ var ReadlineInputReader = class {
|
|
|
1764
1927
|
cleanup();
|
|
1765
1928
|
process.stdout.write(` ${dim(`[${buf.length} chars]`)}
|
|
1766
1929
|
`);
|
|
1767
|
-
|
|
1930
|
+
resolve4(buf);
|
|
1768
1931
|
return;
|
|
1769
1932
|
}
|
|
1770
1933
|
if (ch === "") {
|
|
@@ -1861,8 +2024,247 @@ async function buildPickableProviders(modelsRegistry, config) {
|
|
|
1861
2024
|
}
|
|
1862
2025
|
return out;
|
|
1863
2026
|
}
|
|
2027
|
+
var PROTECTED_BASENAMES = /* @__PURE__ */ new Set([
|
|
2028
|
+
"config.json",
|
|
2029
|
+
".key",
|
|
2030
|
+
"index.json"
|
|
2031
|
+
]);
|
|
2032
|
+
function assertSafeToDelete(filename, parentDir) {
|
|
2033
|
+
if (PROTECTED_BASENAMES.has(filename)) {
|
|
2034
|
+
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
2035
|
+
}
|
|
2036
|
+
if (filename !== path20.basename(filename)) {
|
|
2037
|
+
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
2038
|
+
}
|
|
2039
|
+
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
2040
|
+
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
2041
|
+
}
|
|
2042
|
+
const resolvedParent = path20.resolve(parentDir);
|
|
2043
|
+
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
2044
|
+
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
async function safeDelete(filePath) {
|
|
2048
|
+
const dir = path20.dirname(filePath);
|
|
2049
|
+
const filename = path20.basename(filePath);
|
|
2050
|
+
try {
|
|
2051
|
+
assertSafeToDelete(filename, dir);
|
|
2052
|
+
await fs3.unlink(filePath);
|
|
2053
|
+
} catch (err) {
|
|
2054
|
+
if (err instanceof Error && err.message.startsWith("Refusing")) {
|
|
2055
|
+
process.stderr.write(`[config-history] SAFETY: ${err.message}
|
|
2056
|
+
`);
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
function maskConfigSecrets(cfg) {
|
|
2061
|
+
if (typeof cfg !== "object" || cfg === null) return {};
|
|
2062
|
+
const out = {};
|
|
2063
|
+
for (const [k, v] of Object.entries(cfg)) {
|
|
2064
|
+
if (k === "apiKey" || k === "apiKeys" || k === "secret" || k === "secrets") {
|
|
2065
|
+
out[k] = "[REDACTED]";
|
|
2066
|
+
} else if (typeof v === "object" && v !== null && !Array.isArray(v)) {
|
|
2067
|
+
out[k] = maskConfigSecrets(v);
|
|
2068
|
+
} else {
|
|
2069
|
+
out[k] = v;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
return out;
|
|
2073
|
+
}
|
|
2074
|
+
function diffSummary(oldCfg, newCfg) {
|
|
2075
|
+
const changes = [];
|
|
2076
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(oldCfg), ...Object.keys(newCfg)]);
|
|
2077
|
+
for (const k of allKeys) {
|
|
2078
|
+
const o = JSON.stringify(oldCfg[k]);
|
|
2079
|
+
const n = JSON.stringify(newCfg[k]);
|
|
2080
|
+
if (o !== n) {
|
|
2081
|
+
if (k === "apiKey" || k === "apiKeys" || k === "secret") {
|
|
2082
|
+
changes.push(`${k}: [CHANGED]`);
|
|
2083
|
+
} else if (typeof newCfg[k] !== "object") {
|
|
2084
|
+
changes.push(`${k}: ${oldCfg[k] ?? "(unset)"} \u2192 ${newCfg[k]}`);
|
|
2085
|
+
} else {
|
|
2086
|
+
changes.push(`${k}: [CHANGED]`);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
|
|
2091
|
+
}
|
|
2092
|
+
var defaultHomeDir = () => os6__default.homedir();
|
|
2093
|
+
function historyDir(homeFn = defaultHomeDir) {
|
|
2094
|
+
return path20.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
2095
|
+
}
|
|
2096
|
+
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
2097
|
+
return path20.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
2098
|
+
}
|
|
2099
|
+
function configPath(homeFn = defaultHomeDir) {
|
|
2100
|
+
return path20.join(homeFn(), ".wrongstack", "config.json");
|
|
2101
|
+
}
|
|
2102
|
+
function backupLastPath(homeFn = defaultHomeDir) {
|
|
2103
|
+
return path20.join(homeFn(), ".wrongstack", "config.json.last");
|
|
2104
|
+
}
|
|
2105
|
+
function entryId(ts) {
|
|
2106
|
+
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
2107
|
+
}
|
|
2108
|
+
async function ensureHistoryDir(homeFn = defaultHomeDir) {
|
|
2109
|
+
await fs3.mkdir(historyDir(homeFn), { recursive: true });
|
|
2110
|
+
}
|
|
2111
|
+
async function readIndex(homeFn = defaultHomeDir) {
|
|
2112
|
+
try {
|
|
2113
|
+
const raw = await fs3.readFile(historyIndexPath(homeFn), "utf8");
|
|
2114
|
+
return JSON.parse(raw);
|
|
2115
|
+
} catch {
|
|
2116
|
+
return { version: 1, entries: [] };
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
async function writeIndex(idx, homeFn = defaultHomeDir) {
|
|
2120
|
+
await ensureHistoryDir(homeFn);
|
|
2121
|
+
await fs3.writeFile(historyIndexPath(homeFn), JSON.stringify(idx, null, 2), "utf8");
|
|
2122
|
+
}
|
|
2123
|
+
async function backupCurrent(homeFn = defaultHomeDir) {
|
|
2124
|
+
const cfg = configPath(homeFn);
|
|
2125
|
+
const last = backupLastPath(homeFn);
|
|
2126
|
+
const ts = Date.now();
|
|
2127
|
+
let content;
|
|
2128
|
+
try {
|
|
2129
|
+
content = await fs3.readFile(cfg, "utf8");
|
|
2130
|
+
} catch {
|
|
2131
|
+
}
|
|
2132
|
+
if (content !== void 0) {
|
|
2133
|
+
try {
|
|
2134
|
+
await atomicWrite(last, content);
|
|
2135
|
+
} catch {
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
if (content !== void 0) {
|
|
2139
|
+
try {
|
|
2140
|
+
const bakPath = path20.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
2141
|
+
await atomicWrite(bakPath, content);
|
|
2142
|
+
} catch {
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
try {
|
|
2146
|
+
const dir = path20.join(homeFn(), ".wrongstack");
|
|
2147
|
+
const files = await fs3.readdir(dir);
|
|
2148
|
+
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
2149
|
+
for (const f of baks.slice(10)) {
|
|
2150
|
+
await safeDelete(path20.join(dir, f));
|
|
2151
|
+
}
|
|
2152
|
+
} catch {
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDir) {
|
|
2156
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2157
|
+
const id = entryId(timestamp);
|
|
2158
|
+
await ensureHistoryDir(homeFn);
|
|
2159
|
+
const entry = {
|
|
2160
|
+
id,
|
|
2161
|
+
timestamp,
|
|
2162
|
+
description,
|
|
2163
|
+
snapshotMasked: maskConfigSecrets(newCfg),
|
|
2164
|
+
diffSummary: diffSummary(oldCfg, newCfg)
|
|
2165
|
+
};
|
|
2166
|
+
await fs3.writeFile(
|
|
2167
|
+
path20.join(historyDir(homeFn), `${id}.json`),
|
|
2168
|
+
JSON.stringify(entry, null, 2),
|
|
2169
|
+
"utf8"
|
|
2170
|
+
);
|
|
2171
|
+
const idx = await readIndex(homeFn);
|
|
2172
|
+
idx.entries.unshift({ id, timestamp, description });
|
|
2173
|
+
await writeIndex(idx, homeFn);
|
|
2174
|
+
return id;
|
|
2175
|
+
}
|
|
2176
|
+
async function listHistory(homeFn = defaultHomeDir) {
|
|
2177
|
+
const idx = await readIndex(homeFn);
|
|
2178
|
+
return idx.entries;
|
|
2179
|
+
}
|
|
2180
|
+
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
2181
|
+
try {
|
|
2182
|
+
const raw = await fs3.readFile(path20.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
2183
|
+
return JSON.parse(raw);
|
|
2184
|
+
} catch {
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
async function restoreFromHistory(id, homeFn = defaultHomeDir) {
|
|
2189
|
+
const entry = await getHistoryEntry(id, homeFn);
|
|
2190
|
+
if (!entry) return { ok: false, backupId: null, error: "History entry not found" };
|
|
2191
|
+
await backupCurrent(homeFn);
|
|
2192
|
+
let oldCfg = {};
|
|
2193
|
+
try {
|
|
2194
|
+
const raw = await fs3.readFile(configPath(homeFn), "utf8");
|
|
2195
|
+
oldCfg = JSON.parse(raw);
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
try {
|
|
2199
|
+
await atomicWrite(configPath(homeFn), JSON.stringify(entry.snapshotMasked, null, 2));
|
|
2200
|
+
} catch (err) {
|
|
2201
|
+
return { ok: false, backupId: null, error: String(err) };
|
|
2202
|
+
}
|
|
2203
|
+
const backupId = await appendHistory(
|
|
2204
|
+
oldCfg,
|
|
2205
|
+
entry.snapshotMasked,
|
|
2206
|
+
`Restored from history ${id}`,
|
|
2207
|
+
homeFn
|
|
2208
|
+
);
|
|
2209
|
+
return { ok: true, backupId };
|
|
2210
|
+
}
|
|
2211
|
+
async function restoreLast(homeFn = defaultHomeDir) {
|
|
2212
|
+
const last = backupLastPath(homeFn);
|
|
2213
|
+
const cfg = configPath(homeFn);
|
|
2214
|
+
let oldCfg = {};
|
|
2215
|
+
try {
|
|
2216
|
+
const raw = await fs3.readFile(cfg, "utf8");
|
|
2217
|
+
oldCfg = JSON.parse(raw);
|
|
2218
|
+
} catch {
|
|
2219
|
+
}
|
|
2220
|
+
let lastCfg = {};
|
|
2221
|
+
try {
|
|
2222
|
+
const raw = await fs3.readFile(last, "utf8");
|
|
2223
|
+
lastCfg = JSON.parse(raw);
|
|
2224
|
+
} catch {
|
|
2225
|
+
return { ok: false, error: "No prior backup found" };
|
|
2226
|
+
}
|
|
2227
|
+
await backupCurrent(homeFn);
|
|
2228
|
+
try {
|
|
2229
|
+
await atomicWrite(cfg, JSON.stringify(lastCfg, null, 2));
|
|
2230
|
+
} catch (err) {
|
|
2231
|
+
return { ok: false, error: String(err) };
|
|
2232
|
+
}
|
|
2233
|
+
await appendHistory(oldCfg, lastCfg, "Restored from config.json.last", homeFn);
|
|
2234
|
+
return { ok: true };
|
|
2235
|
+
}
|
|
1864
2236
|
|
|
1865
2237
|
// src/picker.ts
|
|
2238
|
+
var theme = { primary: color.amber };
|
|
2239
|
+
async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
|
|
2240
|
+
try {
|
|
2241
|
+
const { atomicWrite: atomicWrite7 } = await import('@wrongstack/core');
|
|
2242
|
+
const fs17 = await import('fs/promises');
|
|
2243
|
+
let existing = {};
|
|
2244
|
+
try {
|
|
2245
|
+
const raw = await fs17.readFile(configPath2, "utf8");
|
|
2246
|
+
existing = JSON.parse(raw);
|
|
2247
|
+
} catch {
|
|
2248
|
+
}
|
|
2249
|
+
const oldCfg = { ...existing };
|
|
2250
|
+
existing.provider = provider;
|
|
2251
|
+
existing.model = model;
|
|
2252
|
+
await backupCurrent(homeFn);
|
|
2253
|
+
await atomicWrite7(configPath2, JSON.stringify(existing, null, 2));
|
|
2254
|
+
try {
|
|
2255
|
+
await appendHistory(
|
|
2256
|
+
oldCfg,
|
|
2257
|
+
existing,
|
|
2258
|
+
`Provider/model changed: ${oldCfg.provider ?? "(none)"} \u2192 ${provider}, ${oldCfg.model ?? "(none)"} \u2192 ${model}`,
|
|
2259
|
+
homeFn
|
|
2260
|
+
);
|
|
2261
|
+
} catch {
|
|
2262
|
+
}
|
|
2263
|
+
return true;
|
|
2264
|
+
} catch {
|
|
2265
|
+
return false;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
1866
2268
|
async function runPicker(deps) {
|
|
1867
2269
|
const { modelsRegistry, renderer, reader, config, defaultProvider, defaultModel } = deps;
|
|
1868
2270
|
renderer.write(
|
|
@@ -2093,28 +2495,9 @@ async function resolveModelSelection(answer, models, provider, _registry, render
|
|
|
2093
2495
|
`);
|
|
2094
2496
|
return { provider: provider.id, model: modelId };
|
|
2095
2497
|
}
|
|
2096
|
-
var theme = { primary: color.amber };
|
|
2097
|
-
async function saveToGlobalConfig(configPath, provider, model) {
|
|
2098
|
-
try {
|
|
2099
|
-
const { atomicWrite: atomicWrite6 } = await import('@wrongstack/core');
|
|
2100
|
-
const fs15 = await import('fs/promises');
|
|
2101
|
-
let existing = {};
|
|
2102
|
-
try {
|
|
2103
|
-
const raw = await fs15.readFile(configPath, "utf8");
|
|
2104
|
-
existing = JSON.parse(raw);
|
|
2105
|
-
} catch {
|
|
2106
|
-
}
|
|
2107
|
-
existing.provider = provider;
|
|
2108
|
-
existing.model = model;
|
|
2109
|
-
await atomicWrite6(configPath, JSON.stringify(existing, null, 2));
|
|
2110
|
-
return true;
|
|
2111
|
-
} catch {
|
|
2112
|
-
return false;
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
2498
|
async function pathExists(file) {
|
|
2116
2499
|
try {
|
|
2117
|
-
await
|
|
2500
|
+
await fs3.access(file);
|
|
2118
2501
|
return true;
|
|
2119
2502
|
} catch {
|
|
2120
2503
|
return false;
|
|
@@ -2125,10 +2508,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2125
2508
|
const name = declared.split("@")[0];
|
|
2126
2509
|
if (name) return name;
|
|
2127
2510
|
}
|
|
2128
|
-
if (await pathExists(
|
|
2129
|
-
if (await pathExists(
|
|
2130
|
-
if (await pathExists(
|
|
2131
|
-
if (await pathExists(
|
|
2511
|
+
if (await pathExists(path20.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2512
|
+
if (await pathExists(path20.join(root, "bun.lockb"))) return "bun";
|
|
2513
|
+
if (await pathExists(path20.join(root, "bun.lock"))) return "bun";
|
|
2514
|
+
if (await pathExists(path20.join(root, "yarn.lock"))) return "yarn";
|
|
2132
2515
|
return "npm";
|
|
2133
2516
|
}
|
|
2134
2517
|
function hasUsableScript(scripts, name) {
|
|
@@ -2149,7 +2532,7 @@ function parseMakeTargets(makefile) {
|
|
|
2149
2532
|
async function detectProjectFacts(root) {
|
|
2150
2533
|
const facts = { hints: [] };
|
|
2151
2534
|
try {
|
|
2152
|
-
const pkg = JSON.parse(await
|
|
2535
|
+
const pkg = JSON.parse(await fs3.readFile(path20.join(root, "package.json"), "utf8"));
|
|
2153
2536
|
const scripts = pkg.scripts ?? {};
|
|
2154
2537
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2155
2538
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2163,14 +2546,14 @@ async function detectProjectFacts(root) {
|
|
|
2163
2546
|
} catch {
|
|
2164
2547
|
}
|
|
2165
2548
|
try {
|
|
2166
|
-
if (!await pathExists(
|
|
2549
|
+
if (!await pathExists(path20.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2167
2550
|
facts.test ??= "pytest";
|
|
2168
2551
|
facts.lint ??= "ruff check .";
|
|
2169
2552
|
facts.hints.push("pyproject.toml");
|
|
2170
2553
|
} catch {
|
|
2171
2554
|
}
|
|
2172
2555
|
try {
|
|
2173
|
-
if (!await pathExists(
|
|
2556
|
+
if (!await pathExists(path20.join(root, "go.mod"))) throw new Error("not go");
|
|
2174
2557
|
facts.build ??= "go build ./...";
|
|
2175
2558
|
facts.test ??= "go test ./...";
|
|
2176
2559
|
facts.run ??= "go run .";
|
|
@@ -2178,7 +2561,7 @@ async function detectProjectFacts(root) {
|
|
|
2178
2561
|
} catch {
|
|
2179
2562
|
}
|
|
2180
2563
|
try {
|
|
2181
|
-
if (!await pathExists(
|
|
2564
|
+
if (!await pathExists(path20.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2182
2565
|
facts.build ??= "cargo build";
|
|
2183
2566
|
facts.test ??= "cargo test";
|
|
2184
2567
|
facts.lint ??= "cargo clippy";
|
|
@@ -2187,7 +2570,7 @@ async function detectProjectFacts(root) {
|
|
|
2187
2570
|
} catch {
|
|
2188
2571
|
}
|
|
2189
2572
|
try {
|
|
2190
|
-
const makefile = await
|
|
2573
|
+
const makefile = await fs3.readFile(path20.join(root, "Makefile"), "utf8");
|
|
2191
2574
|
const targets = parseMakeTargets(makefile);
|
|
2192
2575
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2193
2576
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -2322,7 +2705,7 @@ function buildClearCommand(opts) {
|
|
|
2322
2705
|
};
|
|
2323
2706
|
}
|
|
2324
2707
|
async function runGit(args, cwd) {
|
|
2325
|
-
return new Promise((
|
|
2708
|
+
return new Promise((resolve4) => {
|
|
2326
2709
|
const child = spawn("git", args, {
|
|
2327
2710
|
cwd,
|
|
2328
2711
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -2331,7 +2714,7 @@ async function runGit(args, cwd) {
|
|
|
2331
2714
|
let stderr = "";
|
|
2332
2715
|
child.stdout?.on("data", (d) => stdout += d);
|
|
2333
2716
|
child.stderr?.on("data", (d) => stderr += d);
|
|
2334
|
-
child.on("close", (code) =>
|
|
2717
|
+
child.on("close", (code) => resolve4({ stdout, stderr, code: code ?? 0 }));
|
|
2335
2718
|
});
|
|
2336
2719
|
}
|
|
2337
2720
|
function detectCommitType(stats) {
|
|
@@ -2350,7 +2733,7 @@ function detectCommitType(stats) {
|
|
|
2350
2733
|
if (hasConfig) return "chore";
|
|
2351
2734
|
return "feat";
|
|
2352
2735
|
}
|
|
2353
|
-
async function
|
|
2736
|
+
async function generateCommitMessageHeuristics(cwd) {
|
|
2354
2737
|
const statsResult = await runGit(["diff", "--stat"], cwd);
|
|
2355
2738
|
if (statsResult.code !== 0) return "chore: update";
|
|
2356
2739
|
const nameResult = await runGit(["diff", "--name-only"], cwd);
|
|
@@ -2381,7 +2764,7 @@ async function isGitRepo(cwd) {
|
|
|
2381
2764
|
const result = await runGit(["rev-parse", "--git-dir"], cwd);
|
|
2382
2765
|
return result.code === 0;
|
|
2383
2766
|
}
|
|
2384
|
-
function buildCommitCommand(_opts) {
|
|
2767
|
+
function buildCommitCommand(_opts, generateCommitMessage) {
|
|
2385
2768
|
return {
|
|
2386
2769
|
name: "commit",
|
|
2387
2770
|
description: "Stage all changes and commit with auto-generated message.",
|
|
@@ -2395,7 +2778,11 @@ function buildCommitCommand(_opts) {
|
|
|
2395
2778
|
return { message: "Nothing to commit (working tree clean)." };
|
|
2396
2779
|
}
|
|
2397
2780
|
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
2398
|
-
|
|
2781
|
+
args.includes("--no-llm");
|
|
2782
|
+
let message;
|
|
2783
|
+
{
|
|
2784
|
+
message = await generateCommitMessageHeuristics(cwd);
|
|
2785
|
+
}
|
|
2399
2786
|
if (dryRun) {
|
|
2400
2787
|
return {
|
|
2401
2788
|
message: `Would commit:
|
|
@@ -2819,10 +3206,10 @@ function buildInitCommand(opts) {
|
|
|
2819
3206
|
description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
|
|
2820
3207
|
async run(args, ctx) {
|
|
2821
3208
|
const force = args.trim() === "--force";
|
|
2822
|
-
const dir =
|
|
2823
|
-
const file =
|
|
3209
|
+
const dir = path20.join(ctx.projectRoot, ".wrongstack");
|
|
3210
|
+
const file = path20.join(dir, "AGENTS.md");
|
|
2824
3211
|
try {
|
|
2825
|
-
await
|
|
3212
|
+
await fs3.access(file);
|
|
2826
3213
|
if (!force) {
|
|
2827
3214
|
const msg2 = `AGENTS.md already exists at ${file}. Use "/init --force" to overwrite.`;
|
|
2828
3215
|
opts.renderer.writeWarning(msg2);
|
|
@@ -2832,8 +3219,8 @@ function buildInitCommand(opts) {
|
|
|
2832
3219
|
}
|
|
2833
3220
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
2834
3221
|
const body = renderAgentsTemplate(detected);
|
|
2835
|
-
await
|
|
2836
|
-
await
|
|
3222
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
3223
|
+
await fs3.writeFile(file, body, "utf8");
|
|
2837
3224
|
if (detected.hints.length > 0) {
|
|
2838
3225
|
const msg2 = `Wrote ${file}
|
|
2839
3226
|
Pre-filled: ${detected.hints.join(", ")}. Edit the file with project context and instructions the system prompt should carry.`;
|
|
@@ -3441,11 +3828,11 @@ ${lines.join("\n\n")}
|
|
|
3441
3828
|
};
|
|
3442
3829
|
}
|
|
3443
3830
|
function makeInstaller(opts, projectRoot, global) {
|
|
3444
|
-
const globalRoot =
|
|
3831
|
+
const globalRoot = path20.join(os6.homedir(), ".wrongstack");
|
|
3445
3832
|
return new SkillInstaller({
|
|
3446
|
-
manifestPath:
|
|
3447
|
-
projectSkillsDir:
|
|
3448
|
-
globalSkillsDir:
|
|
3833
|
+
manifestPath: path20.join(globalRoot, "installed-skills.json"),
|
|
3834
|
+
projectSkillsDir: path20.join(projectRoot, ".wrongstack", "skills"),
|
|
3835
|
+
globalSkillsDir: path20.join(globalRoot, "skills"),
|
|
3449
3836
|
projectHash: projectHash(projectRoot),
|
|
3450
3837
|
skillLoader: opts.skillLoader
|
|
3451
3838
|
});
|
|
@@ -3656,13 +4043,13 @@ var MANIFESTS = [
|
|
|
3656
4043
|
];
|
|
3657
4044
|
async function detectProjectKind(projectRoot) {
|
|
3658
4045
|
try {
|
|
3659
|
-
await
|
|
4046
|
+
await fs3.access(path20.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
3660
4047
|
return "initialized";
|
|
3661
4048
|
} catch {
|
|
3662
4049
|
}
|
|
3663
4050
|
for (const m of MANIFESTS) {
|
|
3664
4051
|
try {
|
|
3665
|
-
await
|
|
4052
|
+
await fs3.access(path20.join(projectRoot, m));
|
|
3666
4053
|
return "project";
|
|
3667
4054
|
} catch {
|
|
3668
4055
|
}
|
|
@@ -3670,12 +4057,12 @@ async function detectProjectKind(projectRoot) {
|
|
|
3670
4057
|
return "empty";
|
|
3671
4058
|
}
|
|
3672
4059
|
async function scaffoldAgentsMd(projectRoot) {
|
|
3673
|
-
const dir =
|
|
3674
|
-
const file =
|
|
4060
|
+
const dir = path20.join(projectRoot, ".wrongstack");
|
|
4061
|
+
const file = path20.join(dir, "AGENTS.md");
|
|
3675
4062
|
const facts = await detectProjectFacts(projectRoot);
|
|
3676
4063
|
const body = renderAgentsTemplate(facts);
|
|
3677
|
-
await
|
|
3678
|
-
await
|
|
4064
|
+
await fs3.mkdir(dir, { recursive: true });
|
|
4065
|
+
await fs3.writeFile(file, body, "utf8");
|
|
3679
4066
|
return file;
|
|
3680
4067
|
}
|
|
3681
4068
|
async function runProjectCheck(opts) {
|
|
@@ -3684,7 +4071,7 @@ async function runProjectCheck(opts) {
|
|
|
3684
4071
|
if (kind === "initialized") {
|
|
3685
4072
|
renderer.write(
|
|
3686
4073
|
`
|
|
3687
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
4074
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path20.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
3688
4075
|
`
|
|
3689
4076
|
);
|
|
3690
4077
|
return true;
|
|
@@ -3711,10 +4098,10 @@ async function runProjectCheck(opts) {
|
|
|
3711
4098
|
}
|
|
3712
4099
|
return true;
|
|
3713
4100
|
}
|
|
3714
|
-
const gitDir =
|
|
4101
|
+
const gitDir = path20.join(projectRoot, ".git");
|
|
3715
4102
|
let hasGit = false;
|
|
3716
4103
|
try {
|
|
3717
|
-
await
|
|
4104
|
+
await fs3.access(gitDir);
|
|
3718
4105
|
hasGit = true;
|
|
3719
4106
|
} catch {
|
|
3720
4107
|
}
|
|
@@ -3729,10 +4116,10 @@ async function runProjectCheck(opts) {
|
|
|
3729
4116
|
)).trim().toLowerCase();
|
|
3730
4117
|
if (answer2 === "y" || answer2 === "yes") {
|
|
3731
4118
|
try {
|
|
3732
|
-
const { spawn:
|
|
3733
|
-
await new Promise((
|
|
3734
|
-
const child =
|
|
3735
|
-
child.on("close", (code) => code === 0 ?
|
|
4119
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
4120
|
+
await new Promise((resolve4, reject) => {
|
|
4121
|
+
const child = spawn3("git", ["init"], { cwd: projectRoot });
|
|
4122
|
+
child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
|
|
3736
4123
|
});
|
|
3737
4124
|
renderer.write(` ${color.green("\u2713")} Git repository initialized
|
|
3738
4125
|
`);
|
|
@@ -4012,14 +4399,14 @@ function summarize(value, name) {
|
|
|
4012
4399
|
if (typeof v === "object" && v !== null) {
|
|
4013
4400
|
const o = v;
|
|
4014
4401
|
if (name === "edit") {
|
|
4015
|
-
const
|
|
4402
|
+
const path21 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4016
4403
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
4017
|
-
return `${
|
|
4404
|
+
return `${path21} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
4018
4405
|
}
|
|
4019
4406
|
if (name === "write") {
|
|
4020
|
-
const
|
|
4407
|
+
const path21 = typeof o["path"] === "string" ? o["path"] : "";
|
|
4021
4408
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
4022
|
-
return bytes !== void 0 ? `${
|
|
4409
|
+
return bytes !== void 0 ? `${path21} ${bytes}B` : path21;
|
|
4023
4410
|
}
|
|
4024
4411
|
if (typeof o["count"] === "number") {
|
|
4025
4412
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -4615,7 +5002,7 @@ async function readKeyInput(deps, intent) {
|
|
|
4615
5002
|
async function loadProviders(deps) {
|
|
4616
5003
|
let raw;
|
|
4617
5004
|
try {
|
|
4618
|
-
raw = await
|
|
5005
|
+
raw = await fs3.readFile(deps.globalConfigPath, "utf8");
|
|
4619
5006
|
} catch {
|
|
4620
5007
|
return {};
|
|
4621
5008
|
}
|
|
@@ -4631,7 +5018,7 @@ async function loadProviders(deps) {
|
|
|
4631
5018
|
async function mutateProviders(deps, mutator) {
|
|
4632
5019
|
let raw;
|
|
4633
5020
|
try {
|
|
4634
|
-
raw = await
|
|
5021
|
+
raw = await fs3.readFile(deps.globalConfigPath, "utf8");
|
|
4635
5022
|
} catch {
|
|
4636
5023
|
raw = "{}";
|
|
4637
5024
|
}
|
|
@@ -4668,6 +5055,66 @@ var authCmd = async (args, deps) => {
|
|
|
4668
5055
|
envVars: flags.envVars
|
|
4669
5056
|
});
|
|
4670
5057
|
};
|
|
5058
|
+
|
|
5059
|
+
// src/subcommands/handlers/update.ts
|
|
5060
|
+
init_update_check();
|
|
5061
|
+
var updateCmd = async (args, deps) => {
|
|
5062
|
+
const cwd = deps.cwd;
|
|
5063
|
+
const checkOnly = args.includes("--check-only") || args.includes("-c");
|
|
5064
|
+
const info = await checkForUpdate();
|
|
5065
|
+
if (checkOnly) {
|
|
5066
|
+
if (info.outdated) {
|
|
5067
|
+
deps.renderer.write(`Update available: v${info.current} \u2192 v${info.latest}
|
|
5068
|
+
`);
|
|
5069
|
+
} else {
|
|
5070
|
+
deps.renderer.write(`You are on the latest version: v${info.current}
|
|
5071
|
+
`);
|
|
5072
|
+
}
|
|
5073
|
+
return 0;
|
|
5074
|
+
}
|
|
5075
|
+
if (!info.outdated) {
|
|
5076
|
+
deps.renderer.write(`You are already on the latest version: v${info.current}
|
|
5077
|
+
`);
|
|
5078
|
+
return 0;
|
|
5079
|
+
}
|
|
5080
|
+
deps.renderer.write(`Updating wrongstack from v${info.current} to v${info.latest}...
|
|
5081
|
+
`);
|
|
5082
|
+
try {
|
|
5083
|
+
const result = await new Promise((resolve4) => {
|
|
5084
|
+
const child = spawn("npm", ["install", "-g", "wrongstack@latest"], {
|
|
5085
|
+
cwd,
|
|
5086
|
+
stdio: "pipe"
|
|
5087
|
+
});
|
|
5088
|
+
let stderr = "";
|
|
5089
|
+
child.stderr?.on("data", (d) => {
|
|
5090
|
+
stderr += d;
|
|
5091
|
+
});
|
|
5092
|
+
child.on("close", (code) => resolve4({ code: code ?? 0 }));
|
|
5093
|
+
});
|
|
5094
|
+
if (result.code === 0) {
|
|
5095
|
+
deps.renderer.write(`
|
|
5096
|
+
Updated to v${info.latest}. Restart wrongstack to use the new version.
|
|
5097
|
+
`);
|
|
5098
|
+
} else {
|
|
5099
|
+
deps.renderer.write(`
|
|
5100
|
+
Update failed with exit code ${result.code}.
|
|
5101
|
+
`);
|
|
5102
|
+
}
|
|
5103
|
+
return result.code;
|
|
5104
|
+
} catch (err) {
|
|
5105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5106
|
+
if (msg.includes("ENOENT")) {
|
|
5107
|
+
deps.renderer.write(`
|
|
5108
|
+
Update failed: npm not found in PATH.
|
|
5109
|
+
`);
|
|
5110
|
+
return 1;
|
|
5111
|
+
}
|
|
5112
|
+
deps.renderer.write(`
|
|
5113
|
+
Update failed: ${msg}
|
|
5114
|
+
`);
|
|
5115
|
+
return 1;
|
|
5116
|
+
}
|
|
5117
|
+
};
|
|
4671
5118
|
var req = createRequire(import.meta.url);
|
|
4672
5119
|
function readOwnVersion() {
|
|
4673
5120
|
const candidates = ["../package.json", "../../package.json"];
|
|
@@ -4703,7 +5150,7 @@ var diagCmd = async (_args, deps) => {
|
|
|
4703
5150
|
` modelsCache: ${deps.paths.modelsCache}`,
|
|
4704
5151
|
` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
|
|
4705
5152
|
` node: ${process.version}`,
|
|
4706
|
-
` os: ${
|
|
5153
|
+
` os: ${os6.platform()} ${os6.release()}`,
|
|
4707
5154
|
` provider: ${cfg.provider ?? "<unset>"}`,
|
|
4708
5155
|
` model: ${cfg.model ?? "<unset>"}`,
|
|
4709
5156
|
` tools: ${deps.toolRegistry?.list().length ?? 0}`,
|
|
@@ -4771,7 +5218,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
4771
5218
|
});
|
|
4772
5219
|
}
|
|
4773
5220
|
try {
|
|
4774
|
-
await
|
|
5221
|
+
await fs3.access(deps.paths.secretsKey);
|
|
4775
5222
|
checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
|
|
4776
5223
|
} catch {
|
|
4777
5224
|
checks.push({
|
|
@@ -4781,10 +5228,10 @@ var doctorCmd = async (_args, deps) => {
|
|
|
4781
5228
|
});
|
|
4782
5229
|
}
|
|
4783
5230
|
try {
|
|
4784
|
-
await
|
|
4785
|
-
const probe =
|
|
4786
|
-
await
|
|
4787
|
-
await
|
|
5231
|
+
await fs3.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
5232
|
+
const probe = path20.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
5233
|
+
await fs3.writeFile(probe, "");
|
|
5234
|
+
await fs3.unlink(probe);
|
|
4788
5235
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
4789
5236
|
} catch (err) {
|
|
4790
5237
|
checks.push({
|
|
@@ -4885,8 +5332,8 @@ var exportCmd = async (args, deps) => {
|
|
|
4885
5332
|
return 1;
|
|
4886
5333
|
}
|
|
4887
5334
|
if (output) {
|
|
4888
|
-
await
|
|
4889
|
-
await
|
|
5335
|
+
await fs3.mkdir(path20.dirname(path20.resolve(deps.cwd, output)), { recursive: true });
|
|
5336
|
+
await fs3.writeFile(path20.resolve(deps.cwd, output), rendered, "utf8");
|
|
4890
5337
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
4891
5338
|
`);
|
|
4892
5339
|
} else {
|
|
@@ -4943,17 +5390,17 @@ var initCmd = async (_args, deps) => {
|
|
|
4943
5390
|
} else {
|
|
4944
5391
|
deps.renderer.writeInfo(`Found API key in env (${provider.envVars.join(" / ")}).`);
|
|
4945
5392
|
}
|
|
4946
|
-
await
|
|
5393
|
+
await fs3.mkdir(deps.paths.globalRoot, { recursive: true });
|
|
4947
5394
|
const config = { version: 1, provider: providerId, model: modelId };
|
|
4948
5395
|
if (apiKey) config.apiKey = apiKey;
|
|
4949
|
-
const keyFile =
|
|
5396
|
+
const keyFile = path20.join(path20.dirname(deps.paths.globalConfig), ".key");
|
|
4950
5397
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
4951
5398
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
4952
5399
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
|
|
4953
|
-
await
|
|
4954
|
-
const agentsFile =
|
|
5400
|
+
await fs3.mkdir(path20.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
5401
|
+
const agentsFile = path20.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
4955
5402
|
try {
|
|
4956
|
-
await
|
|
5403
|
+
await fs3.access(agentsFile);
|
|
4957
5404
|
} catch {
|
|
4958
5405
|
const detected2 = await detectProjectFacts(deps.projectRoot);
|
|
4959
5406
|
await atomicWrite(agentsFile, renderAgentsTemplate(detected2));
|
|
@@ -5029,7 +5476,7 @@ async function addMcpServer(args, deps) {
|
|
|
5029
5476
|
serverCfg.enabled = enable;
|
|
5030
5477
|
let existing = {};
|
|
5031
5478
|
try {
|
|
5032
|
-
existing = JSON.parse(await
|
|
5479
|
+
existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
|
|
5033
5480
|
} catch {
|
|
5034
5481
|
}
|
|
5035
5482
|
const mcpServers = existing.mcpServers ?? {};
|
|
@@ -5049,7 +5496,7 @@ async function addMcpServer(args, deps) {
|
|
|
5049
5496
|
async function removeMcpServer(name, deps) {
|
|
5050
5497
|
let existing = {};
|
|
5051
5498
|
try {
|
|
5052
|
-
existing = JSON.parse(await
|
|
5499
|
+
existing = JSON.parse(await fs3.readFile(deps.paths.globalConfig, "utf8"));
|
|
5053
5500
|
} catch {
|
|
5054
5501
|
deps.renderer.writeError("No config file found.\n");
|
|
5055
5502
|
return 1;
|
|
@@ -5170,7 +5617,7 @@ function renderConfiguredPlugins(config) {
|
|
|
5170
5617
|
}
|
|
5171
5618
|
async function readConfig(file) {
|
|
5172
5619
|
try {
|
|
5173
|
-
return JSON.parse(await
|
|
5620
|
+
return JSON.parse(await fs3.readFile(file, "utf8"));
|
|
5174
5621
|
} catch {
|
|
5175
5622
|
return {};
|
|
5176
5623
|
}
|
|
@@ -5261,9 +5708,9 @@ var usageCmd = async (_args, deps) => {
|
|
|
5261
5708
|
return 0;
|
|
5262
5709
|
};
|
|
5263
5710
|
var projectsCmd = async (_args, deps) => {
|
|
5264
|
-
const projectsRoot =
|
|
5711
|
+
const projectsRoot = path20.join(deps.paths.globalRoot, "projects");
|
|
5265
5712
|
try {
|
|
5266
|
-
const entries = await
|
|
5713
|
+
const entries = await fs3.readdir(projectsRoot);
|
|
5267
5714
|
if (entries.length === 0) {
|
|
5268
5715
|
deps.renderer.write("No projects tracked.\n");
|
|
5269
5716
|
return 0;
|
|
@@ -5271,7 +5718,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
5271
5718
|
for (const hash of entries) {
|
|
5272
5719
|
try {
|
|
5273
5720
|
const meta = JSON.parse(
|
|
5274
|
-
await
|
|
5721
|
+
await fs3.readFile(path20.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
5275
5722
|
);
|
|
5276
5723
|
deps.renderer.write(
|
|
5277
5724
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -5445,9 +5892,94 @@ var configCmd = async (args, deps) => {
|
|
|
5445
5892
|
`);
|
|
5446
5893
|
return 0;
|
|
5447
5894
|
}
|
|
5448
|
-
|
|
5895
|
+
if (sub === "history") {
|
|
5896
|
+
return runHistory(args.slice(1), deps);
|
|
5897
|
+
}
|
|
5898
|
+
if (sub === "restore") {
|
|
5899
|
+
return runRestore(args.slice(1), deps);
|
|
5900
|
+
}
|
|
5901
|
+
deps.renderer.writeError(`Unknown config subcommand: ${sub}
|
|
5902
|
+
`);
|
|
5449
5903
|
return 1;
|
|
5450
5904
|
};
|
|
5905
|
+
function extractArg(args, key) {
|
|
5906
|
+
const idx = args.indexOf(key);
|
|
5907
|
+
if (idx !== -1 && args[idx + 1] !== void 0) return args[idx + 1];
|
|
5908
|
+
const eq = key.startsWith("--") ? args.find((a) => a.startsWith(`${key}=`)) : null;
|
|
5909
|
+
if (eq) return eq.slice(eq.indexOf("=") + 1);
|
|
5910
|
+
return null;
|
|
5911
|
+
}
|
|
5912
|
+
async function runHistory(args, deps) {
|
|
5913
|
+
const idFlag = extractArg(args, "--id");
|
|
5914
|
+
if (idFlag) {
|
|
5915
|
+
const entry = await getHistoryEntry(idFlag);
|
|
5916
|
+
if (!entry) {
|
|
5917
|
+
deps.renderer.writeError(`History entry '${idFlag}' not found.
|
|
5918
|
+
`);
|
|
5919
|
+
return 1;
|
|
5920
|
+
}
|
|
5921
|
+
deps.renderer.write(
|
|
5922
|
+
[
|
|
5923
|
+
`ID: ${entry.id}`,
|
|
5924
|
+
`Time: ${new Date(entry.timestamp).toLocaleString()}`,
|
|
5925
|
+
`Change: ${entry.description}`,
|
|
5926
|
+
`Diff: ${entry.diffSummary}`,
|
|
5927
|
+
"",
|
|
5928
|
+
"Snapshot (secrets masked):",
|
|
5929
|
+
JSON.stringify(entry.snapshotMasked, null, 2)
|
|
5930
|
+
].join("\n") + "\n"
|
|
5931
|
+
);
|
|
5932
|
+
return 0;
|
|
5933
|
+
}
|
|
5934
|
+
const entries = await listHistory();
|
|
5935
|
+
if (entries.length === 0) {
|
|
5936
|
+
deps.renderer.write("No config history yet.\n");
|
|
5937
|
+
return 0;
|
|
5938
|
+
}
|
|
5939
|
+
deps.renderer.write(
|
|
5940
|
+
[
|
|
5941
|
+
color.bold("Config History"),
|
|
5942
|
+
"",
|
|
5943
|
+
...entries.map((e, i) => {
|
|
5944
|
+
const ts = new Date(e.timestamp).toLocaleString();
|
|
5945
|
+
const desc = e.description.length > 60 ? e.description.slice(0, 60) + "\u2026" : e.description;
|
|
5946
|
+
return ` [${i + 1}] ${e.id} ${color.dim(ts)}
|
|
5947
|
+
${desc}`;
|
|
5948
|
+
}),
|
|
5949
|
+
"",
|
|
5950
|
+
" Run `wrongstack config history --id <id>` for details.",
|
|
5951
|
+
" Run `wrongstack config restore <id>` to restore."
|
|
5952
|
+
].join("\n") + "\n"
|
|
5953
|
+
);
|
|
5954
|
+
return 0;
|
|
5955
|
+
}
|
|
5956
|
+
async function runRestore(args, deps) {
|
|
5957
|
+
const latest = args.includes("--latest") || args.includes("-l");
|
|
5958
|
+
const id = extractArg(args, "--id") ?? (args[0] && !args[0].startsWith("-") ? args[0] : null);
|
|
5959
|
+
if (latest) {
|
|
5960
|
+
const result2 = await restoreLast();
|
|
5961
|
+
if (!result2.ok) {
|
|
5962
|
+
deps.renderer.writeError(`Restore failed: ${result2.error}
|
|
5963
|
+
`);
|
|
5964
|
+
return 1;
|
|
5965
|
+
}
|
|
5966
|
+
deps.renderer.write("Restored from config.json.last.\n");
|
|
5967
|
+
return 0;
|
|
5968
|
+
}
|
|
5969
|
+
if (!id) {
|
|
5970
|
+
deps.renderer.write("Usage: wrongstack config restore <id> | --latest\n");
|
|
5971
|
+
return 1;
|
|
5972
|
+
}
|
|
5973
|
+
const result = await restoreFromHistory(id);
|
|
5974
|
+
if (!result.ok) {
|
|
5975
|
+
deps.renderer.writeError(`Restore failed: ${result.error}
|
|
5976
|
+
`);
|
|
5977
|
+
return 1;
|
|
5978
|
+
}
|
|
5979
|
+
deps.renderer.write(`Restored to history entry '${id}'. Backup created.
|
|
5980
|
+
`);
|
|
5981
|
+
return 0;
|
|
5982
|
+
}
|
|
5451
5983
|
function parseRewindFlags(args) {
|
|
5452
5984
|
const flags = {};
|
|
5453
5985
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -5463,7 +5995,7 @@ function parseRewindFlags(args) {
|
|
|
5463
5995
|
var rewindCmd = async (args, deps) => {
|
|
5464
5996
|
const flags = parseRewindFlags(args);
|
|
5465
5997
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
5466
|
-
const sessionsDir =
|
|
5998
|
+
const sessionsDir = path20.join(wpaths.globalRoot, "sessions");
|
|
5467
5999
|
const rewind = new DefaultSessionRewinder(sessionsDir);
|
|
5468
6000
|
let sessionId = args.find((a) => !a.startsWith("--"));
|
|
5469
6001
|
if (!sessionId) {
|
|
@@ -5595,7 +6127,7 @@ var skillsCmd = async (_args, deps) => {
|
|
|
5595
6127
|
};
|
|
5596
6128
|
var versionCmd = async (_args, deps) => {
|
|
5597
6129
|
deps.renderer.write(
|
|
5598
|
-
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${
|
|
6130
|
+
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os6.platform()})
|
|
5599
6131
|
`
|
|
5600
6132
|
);
|
|
5601
6133
|
return 0;
|
|
@@ -5633,6 +6165,7 @@ var helpCmd = async (_args, deps) => {
|
|
|
5633
6165
|
var subcommands = {
|
|
5634
6166
|
init: initCmd,
|
|
5635
6167
|
auth: authCmd,
|
|
6168
|
+
update: updateCmd,
|
|
5636
6169
|
sessions: sessionsCmd,
|
|
5637
6170
|
config: configCmd,
|
|
5638
6171
|
rewind: rewindCmd,
|
|
@@ -5690,11 +6223,14 @@ function fmtTaskResultLine(r, color32) {
|
|
|
5690
6223
|
return { mark: color32.red("\u2717"), stats: `${color32.red("failed")} ${stats}`, tail: errSnip };
|
|
5691
6224
|
}
|
|
5692
6225
|
}
|
|
6226
|
+
|
|
6227
|
+
// src/boot.ts
|
|
6228
|
+
init_update_check();
|
|
5693
6229
|
function resolveBundledSkillsDir() {
|
|
5694
6230
|
try {
|
|
5695
6231
|
const req2 = createRequire(import.meta.url);
|
|
5696
6232
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
5697
|
-
return
|
|
6233
|
+
return path20.join(path20.dirname(corePkg), "skills");
|
|
5698
6234
|
} catch {
|
|
5699
6235
|
return void 0;
|
|
5700
6236
|
}
|
|
@@ -5723,6 +6259,13 @@ async function boot(argv) {
|
|
|
5723
6259
|
cacheFile: wpaths.modelsCache,
|
|
5724
6260
|
ttlSeconds: 24 * 3600
|
|
5725
6261
|
});
|
|
6262
|
+
let updateInfo;
|
|
6263
|
+
if (!flags["no-check"] && !process.env["WRONGSTACK_NO_CHECK"]) {
|
|
6264
|
+
checkForUpdate().then((info) => {
|
|
6265
|
+
updateInfo = info;
|
|
6266
|
+
}).catch(() => {
|
|
6267
|
+
});
|
|
6268
|
+
}
|
|
5726
6269
|
const first = positional[0];
|
|
5727
6270
|
if (first && subcommands[first]) {
|
|
5728
6271
|
const container = createDefaultContainer({
|
|
@@ -5831,7 +6374,8 @@ async function boot(argv) {
|
|
|
5831
6374
|
modelsRegistry,
|
|
5832
6375
|
renderer,
|
|
5833
6376
|
reader,
|
|
5834
|
-
logger
|
|
6377
|
+
logger,
|
|
6378
|
+
updateInfo
|
|
5835
6379
|
};
|
|
5836
6380
|
}
|
|
5837
6381
|
|
|
@@ -6412,7 +6956,7 @@ async function execute(deps) {
|
|
|
6412
6956
|
supportsVision,
|
|
6413
6957
|
attachments,
|
|
6414
6958
|
effectiveMaxContext,
|
|
6415
|
-
projectName:
|
|
6959
|
+
projectName: path20.basename(projectRoot) || void 0,
|
|
6416
6960
|
getAutonomy,
|
|
6417
6961
|
skillLoader
|
|
6418
6962
|
});
|
|
@@ -6430,7 +6974,7 @@ async function execute(deps) {
|
|
|
6430
6974
|
supportsVision,
|
|
6431
6975
|
attachments,
|
|
6432
6976
|
effectiveMaxContext,
|
|
6433
|
-
projectName:
|
|
6977
|
+
projectName: path20.basename(projectRoot) || void 0,
|
|
6434
6978
|
getAutonomy,
|
|
6435
6979
|
skillLoader
|
|
6436
6980
|
});
|
|
@@ -6702,7 +7246,7 @@ var MultiAgentHost = class {
|
|
|
6702
7246
|
model: opts?.model,
|
|
6703
7247
|
tools: opts?.tools
|
|
6704
7248
|
};
|
|
6705
|
-
const transcriptPath = this.sessionFactory ?
|
|
7249
|
+
const transcriptPath = this.sessionFactory ? path20.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
6706
7250
|
if (this.director) {
|
|
6707
7251
|
const subagentId = await this.director.spawn(subagentConfig);
|
|
6708
7252
|
const taskId2 = randomUUID();
|
|
@@ -6862,16 +7406,16 @@ var MultiAgentHost = class {
|
|
|
6862
7406
|
}
|
|
6863
7407
|
this.opts.directorMode = true;
|
|
6864
7408
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
6865
|
-
this.opts.manifestPath =
|
|
7409
|
+
this.opts.manifestPath = path20.join(this.opts.fleetRoot, "fleet.json");
|
|
6866
7410
|
}
|
|
6867
7411
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
6868
|
-
this.opts.sharedScratchpadPath =
|
|
7412
|
+
this.opts.sharedScratchpadPath = path20.join(this.opts.fleetRoot, "shared");
|
|
6869
7413
|
}
|
|
6870
7414
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
6871
|
-
this.opts.sessionsRoot =
|
|
7415
|
+
this.opts.sessionsRoot = path20.join(this.opts.fleetRoot, "subagents");
|
|
6872
7416
|
}
|
|
6873
7417
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
6874
|
-
this.opts.stateCheckpointPath =
|
|
7418
|
+
this.opts.stateCheckpointPath = path20.join(this.opts.fleetRoot, "director-state.json");
|
|
6875
7419
|
}
|
|
6876
7420
|
await this.ensureDirector();
|
|
6877
7421
|
return this.director ?? null;
|
|
@@ -6992,11 +7536,11 @@ var SessionStats = class {
|
|
|
6992
7536
|
if (e.name === "bash") this.bashCommands++;
|
|
6993
7537
|
else if (e.name === "fetch") this.fetches++;
|
|
6994
7538
|
if (!e.ok) return;
|
|
6995
|
-
const
|
|
6996
|
-
if (e.name === "read" &&
|
|
6997
|
-
else if (e.name === "edit" &&
|
|
6998
|
-
else if (e.name === "write" &&
|
|
6999
|
-
this.writtenPaths.add(
|
|
7539
|
+
const path21 = typeof input?.path === "string" ? input.path : void 0;
|
|
7540
|
+
if (e.name === "read" && path21) this.readPaths.add(path21);
|
|
7541
|
+
else if (e.name === "edit" && path21) this.editedPaths.add(path21);
|
|
7542
|
+
else if (e.name === "write" && path21) {
|
|
7543
|
+
this.writtenPaths.add(path21);
|
|
7000
7544
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
7001
7545
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
7002
7546
|
}
|
|
@@ -7327,12 +7871,12 @@ async function setupSession(params) {
|
|
|
7327
7871
|
}
|
|
7328
7872
|
const sessionRef = { current: session };
|
|
7329
7873
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
7330
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
7331
|
-
const queueStore = new QueueStore({ dir:
|
|
7874
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path20.join(wpaths.projectSessions, session.id, "attachments") });
|
|
7875
|
+
const queueStore = new QueueStore({ dir: path20.join(wpaths.projectSessions, session.id) });
|
|
7332
7876
|
const ctxSignal = new AbortController().signal;
|
|
7333
7877
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
7334
7878
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
7335
|
-
const todosCheckpointPath =
|
|
7879
|
+
const todosCheckpointPath = path20.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
7336
7880
|
if (resumeId) {
|
|
7337
7881
|
try {
|
|
7338
7882
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -7344,12 +7888,12 @@ async function setupSession(params) {
|
|
|
7344
7888
|
}
|
|
7345
7889
|
}
|
|
7346
7890
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
7347
|
-
const planPath =
|
|
7891
|
+
const planPath = path20.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
7348
7892
|
context.state.setMeta("plan.path", planPath);
|
|
7349
7893
|
if (resumeId) {
|
|
7350
7894
|
try {
|
|
7351
|
-
const fleetRoot =
|
|
7352
|
-
const dirState = await loadDirectorState(
|
|
7895
|
+
const fleetRoot = path20.join(wpaths.projectSessions, session.id);
|
|
7896
|
+
const dirState = await loadDirectorState(path20.join(fleetRoot, "director-state.json"));
|
|
7353
7897
|
if (dirState) {
|
|
7354
7898
|
const tCounts = {};
|
|
7355
7899
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -7376,7 +7920,7 @@ function resolveBundledSkillsDir2() {
|
|
|
7376
7920
|
try {
|
|
7377
7921
|
const req2 = createRequire(import.meta.url);
|
|
7378
7922
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
7379
|
-
return
|
|
7923
|
+
return path20.join(path20.dirname(corePkg), "skills");
|
|
7380
7924
|
} catch {
|
|
7381
7925
|
return void 0;
|
|
7382
7926
|
}
|
|
@@ -7407,8 +7951,28 @@ async function main(argv) {
|
|
|
7407
7951
|
modelsRegistry,
|
|
7408
7952
|
renderer,
|
|
7409
7953
|
reader,
|
|
7410
|
-
logger
|
|
7954
|
+
logger,
|
|
7955
|
+
updateInfo
|
|
7411
7956
|
} = ctx;
|
|
7957
|
+
if (!updateInfo?.outdated) {
|
|
7958
|
+
const ac = new AbortController();
|
|
7959
|
+
const timer = setTimeout(() => ac.abort(), 2e3);
|
|
7960
|
+
try {
|
|
7961
|
+
const { checkForUpdate: checkForUpdate2 } = await Promise.resolve().then(() => (init_update_check(), update_check_exports));
|
|
7962
|
+
updateInfo = await checkForUpdate2(ac.signal);
|
|
7963
|
+
} catch {
|
|
7964
|
+
} finally {
|
|
7965
|
+
clearTimeout(timer);
|
|
7966
|
+
}
|
|
7967
|
+
}
|
|
7968
|
+
if (updateInfo?.outdated) {
|
|
7969
|
+
process.stderr.write(
|
|
7970
|
+
`
|
|
7971
|
+
\x1B[33m\u2191 Update available: v${updateInfo.current} \u2192 v${updateInfo.latest}\x1B[0m Run \`wrongstack update\` to upgrade.
|
|
7972
|
+
|
|
7973
|
+
`
|
|
7974
|
+
);
|
|
7975
|
+
}
|
|
7412
7976
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
7413
7977
|
const container = createDefaultContainer({
|
|
7414
7978
|
config,
|
|
@@ -7463,7 +8027,7 @@ async function main(argv) {
|
|
|
7463
8027
|
modeId,
|
|
7464
8028
|
modePrompt,
|
|
7465
8029
|
modelCapabilities,
|
|
7466
|
-
planPath: () => sessionRef.current ?
|
|
8030
|
+
planPath: () => sessionRef.current ? path20.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
|
|
7467
8031
|
})
|
|
7468
8032
|
);
|
|
7469
8033
|
const toolRegistry = new ToolRegistry();
|
|
@@ -7491,7 +8055,7 @@ async function main(argv) {
|
|
|
7491
8055
|
name: "session-store",
|
|
7492
8056
|
check: async () => {
|
|
7493
8057
|
try {
|
|
7494
|
-
await
|
|
8058
|
+
await fs3.access(wpaths.projectSessions);
|
|
7495
8059
|
return { status: "healthy" };
|
|
7496
8060
|
} catch (e) {
|
|
7497
8061
|
return { status: "unhealthy", detail: e instanceof Error ? e.message : "access denied" };
|
|
@@ -7508,7 +8072,7 @@ async function main(argv) {
|
|
|
7508
8072
|
const dumpMetrics = () => {
|
|
7509
8073
|
if (!metricsSink) return;
|
|
7510
8074
|
try {
|
|
7511
|
-
const out =
|
|
8075
|
+
const out = path20.join(wpaths.projectSessions, "metrics.json");
|
|
7512
8076
|
const snap = metricsSink.snapshot();
|
|
7513
8077
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
7514
8078
|
} catch {
|
|
@@ -7727,12 +8291,12 @@ async function main(argv) {
|
|
|
7727
8291
|
const directorMode = flags["director"] === true;
|
|
7728
8292
|
let director = null;
|
|
7729
8293
|
let autonomyMode = "off";
|
|
7730
|
-
const fleetRoot = directorMode ?
|
|
7731
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
7732
|
-
const sharedScratchpadPath = directorMode ?
|
|
7733
|
-
const subagentSessionsRoot = directorMode ?
|
|
7734
|
-
const stateCheckpointPath = directorMode ?
|
|
7735
|
-
const fleetRootForPromotion =
|
|
8294
|
+
const fleetRoot = directorMode ? path20.join(wpaths.projectSessions, session.id) : void 0;
|
|
8295
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path20.join(fleetRoot, "fleet.json") : void 0;
|
|
8296
|
+
const sharedScratchpadPath = directorMode ? path20.join(fleetRoot, "shared") : void 0;
|
|
8297
|
+
const subagentSessionsRoot = directorMode ? path20.join(fleetRoot, "subagents") : void 0;
|
|
8298
|
+
const stateCheckpointPath = directorMode ? path20.join(fleetRoot, "director-state.json") : void 0;
|
|
8299
|
+
const fleetRootForPromotion = path20.join(wpaths.projectSessions, session.id);
|
|
7736
8300
|
const multiAgentHost = new MultiAgentHost(
|
|
7737
8301
|
{
|
|
7738
8302
|
container,
|
|
@@ -7911,27 +8475,27 @@ async function main(argv) {
|
|
|
7911
8475
|
return `Unknown fleet action: ${action}`;
|
|
7912
8476
|
},
|
|
7913
8477
|
onFleetLog: async (subagentId, mode) => {
|
|
7914
|
-
const subagentsRoot =
|
|
8478
|
+
const subagentsRoot = path20.join(fleetRootForPromotion, "subagents");
|
|
7915
8479
|
let runDirs;
|
|
7916
8480
|
try {
|
|
7917
|
-
runDirs = await
|
|
8481
|
+
runDirs = await fs3.readdir(subagentsRoot);
|
|
7918
8482
|
} catch {
|
|
7919
8483
|
return "No fleet transcripts on disk \u2014 no subagents have been spawned for this session.";
|
|
7920
8484
|
}
|
|
7921
8485
|
const found = [];
|
|
7922
8486
|
for (const runId of runDirs) {
|
|
7923
|
-
const runDir =
|
|
8487
|
+
const runDir = path20.join(subagentsRoot, runId);
|
|
7924
8488
|
let files;
|
|
7925
8489
|
try {
|
|
7926
|
-
files = await
|
|
8490
|
+
files = await fs3.readdir(runDir);
|
|
7927
8491
|
} catch {
|
|
7928
8492
|
continue;
|
|
7929
8493
|
}
|
|
7930
8494
|
for (const f of files) {
|
|
7931
8495
|
if (!f.endsWith(".jsonl")) continue;
|
|
7932
|
-
const full =
|
|
8496
|
+
const full = path20.join(runDir, f);
|
|
7933
8497
|
try {
|
|
7934
|
-
const stat2 = await
|
|
8498
|
+
const stat2 = await fs3.stat(full);
|
|
7935
8499
|
found.push({
|
|
7936
8500
|
runId,
|
|
7937
8501
|
subagentId: f.replace(/\.jsonl$/, ""),
|
|
@@ -7970,7 +8534,7 @@ async function main(argv) {
|
|
|
7970
8534
|
].join("\n");
|
|
7971
8535
|
}
|
|
7972
8536
|
const t = matches[0];
|
|
7973
|
-
const raw = await
|
|
8537
|
+
const raw = await fs3.readFile(t.file, "utf8");
|
|
7974
8538
|
if (mode === "raw") return raw;
|
|
7975
8539
|
const lines = raw.split("\n").filter((l) => l.trim());
|
|
7976
8540
|
const counts = {};
|
|
@@ -8026,7 +8590,7 @@ async function main(argv) {
|
|
|
8026
8590
|
}
|
|
8027
8591
|
const dir = await multiAgentHost.ensureDirector();
|
|
8028
8592
|
if (!dir) return "Director is not available.";
|
|
8029
|
-
const dirStatePath =
|
|
8593
|
+
const dirStatePath = path20.join(fleetRootForPromotion, "director-state.json");
|
|
8030
8594
|
const prior = await loadDirectorState(dirStatePath);
|
|
8031
8595
|
if (!prior) {
|
|
8032
8596
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -8097,9 +8661,9 @@ async function main(argv) {
|
|
|
8097
8661
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
8098
8662
|
toolRegistry.register(tool);
|
|
8099
8663
|
}
|
|
8100
|
-
const mp =
|
|
8101
|
-
const sp =
|
|
8102
|
-
const ss =
|
|
8664
|
+
const mp = path20.join(fleetRootForPromotion, "fleet.json");
|
|
8665
|
+
const sp = path20.join(fleetRootForPromotion, "shared");
|
|
8666
|
+
const ss = path20.join(fleetRootForPromotion, "subagents");
|
|
8103
8667
|
const lines = [
|
|
8104
8668
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
8105
8669
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -8146,13 +8710,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
8146
8710
|
void mcpRegistry.stopAll();
|
|
8147
8711
|
},
|
|
8148
8712
|
onBeforeExit: async () => {
|
|
8149
|
-
const { spawn:
|
|
8713
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
8150
8714
|
const cwd2 = projectRoot;
|
|
8151
|
-
const statusResult = await new Promise((
|
|
8152
|
-
const child =
|
|
8715
|
+
const statusResult = await new Promise((resolve4) => {
|
|
8716
|
+
const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
|
|
8153
8717
|
let stdout = "";
|
|
8154
8718
|
child.stdout?.on("data", (d) => stdout += d);
|
|
8155
|
-
child.on("close", (code) =>
|
|
8719
|
+
child.on("close", (code) => resolve4({ stdout, code: code ?? 0 }));
|
|
8156
8720
|
});
|
|
8157
8721
|
if (statusResult.stdout.trim().length > 0) {
|
|
8158
8722
|
const lines = statusResult.stdout.split("\n").filter(Boolean);
|
|
@@ -8190,7 +8754,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
8190
8754
|
...errSection
|
|
8191
8755
|
].join("\n");
|
|
8192
8756
|
},
|
|
8193
|
-
onStats: () => stats.format()
|
|
8757
|
+
onStats: () => stats.format(),
|
|
8758
|
+
generateCommitMessage: async (diff) => {
|
|
8759
|
+
return generateCommitMessageWithLLM(diff, {
|
|
8760
|
+
provider: context.provider,
|
|
8761
|
+
model: context.model
|
|
8762
|
+
});
|
|
8763
|
+
}
|
|
8194
8764
|
});
|
|
8195
8765
|
for (const cmd of slashCmds) slashRegistry.register(cmd);
|
|
8196
8766
|
const savedProviderCfg = config.providers?.[config.provider];
|