@wrongstack/cli 0.9.1 → 0.9.4
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 +341 -1151
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
3
|
-
import { join } from 'path';
|
|
2
|
+
import * as path24 from 'path';
|
|
4
3
|
import * as fsp3 from 'fs/promises';
|
|
5
|
-
import {
|
|
6
|
-
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
4
|
+
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, InputBuilder, FsError, ERROR_CODES, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
7
5
|
import { createRequire } from 'module';
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
6
|
+
import * as os3 from 'os';
|
|
7
|
+
import os3__default from 'os';
|
|
10
8
|
import * as crypto2 from 'crypto';
|
|
11
9
|
import { randomUUID } from 'crypto';
|
|
12
10
|
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, isSecretField, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
@@ -15,14 +13,13 @@ import { MCPRegistry } from '@wrongstack/mcp';
|
|
|
15
13
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
16
14
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
17
15
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
18
|
-
import { spawn } from 'child_process';
|
|
19
|
-
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
20
16
|
import * as readline from 'readline';
|
|
21
17
|
import * as fs10 from 'fs';
|
|
22
18
|
import { writeFileSync } from 'fs';
|
|
23
19
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
24
20
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
25
21
|
import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
|
|
22
|
+
import { spawn } from 'child_process';
|
|
26
23
|
import { allServers } from '@wrongstack/core/infrastructure';
|
|
27
24
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
28
25
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
@@ -384,6 +381,7 @@ function buildSddCommand(opts) {
|
|
|
384
381
|
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
385
382
|
async run(args) {
|
|
386
383
|
opts.context;
|
|
384
|
+
if (!opts.paths) return { message: "SDD not available \u2014 paths not configured." };
|
|
387
385
|
const specsDir = opts.paths.projectSpecs;
|
|
388
386
|
const graphsDir = opts.paths.projectTaskGraphs;
|
|
389
387
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
@@ -1398,7 +1396,7 @@ function sddHelp() {
|
|
|
1398
1396
|
async function gatherProjectContext(projectRoot) {
|
|
1399
1397
|
const parts = [];
|
|
1400
1398
|
try {
|
|
1401
|
-
const pkgPath =
|
|
1399
|
+
const pkgPath = path24.join(projectRoot, "package.json");
|
|
1402
1400
|
const pkgRaw = await fsp3.readFile(pkgPath, "utf8");
|
|
1403
1401
|
const pkg = JSON.parse(pkgRaw);
|
|
1404
1402
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -1414,13 +1412,13 @@ async function gatherProjectContext(projectRoot) {
|
|
|
1414
1412
|
} catch {
|
|
1415
1413
|
}
|
|
1416
1414
|
try {
|
|
1417
|
-
const tsconfigPath =
|
|
1415
|
+
const tsconfigPath = path24.join(projectRoot, "tsconfig.json");
|
|
1418
1416
|
await fsp3.access(tsconfigPath);
|
|
1419
1417
|
parts.push("Language: TypeScript");
|
|
1420
1418
|
} catch {
|
|
1421
1419
|
}
|
|
1422
1420
|
try {
|
|
1423
|
-
const srcDir =
|
|
1421
|
+
const srcDir = path24.join(projectRoot, "src");
|
|
1424
1422
|
const entries = await fsp3.readdir(srcDir, { withFileTypes: true });
|
|
1425
1423
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1426
1424
|
if (dirs.length > 0) {
|
|
@@ -1576,7 +1574,7 @@ __export(update_check_exports, {
|
|
|
1576
1574
|
getUpdateNotification: () => getUpdateNotification
|
|
1577
1575
|
});
|
|
1578
1576
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
1579
|
-
return
|
|
1577
|
+
return path24.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
1580
1578
|
}
|
|
1581
1579
|
function currentVersion() {
|
|
1582
1580
|
const req2 = createRequire(import.meta.url);
|
|
@@ -1613,7 +1611,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1613
1611
|
}
|
|
1614
1612
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1615
1613
|
try {
|
|
1616
|
-
const dir =
|
|
1614
|
+
const dir = path24.dirname(cachePath(homeFn));
|
|
1617
1615
|
await fsp3.mkdir(dir, { recursive: true });
|
|
1618
1616
|
await fsp3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1619
1617
|
} catch {
|
|
@@ -1686,7 +1684,7 @@ async function getUpdateNotification(signal, homeFn) {
|
|
|
1686
1684
|
var defaultHomeDir2, CACHE_TTL_MS;
|
|
1687
1685
|
var init_update_check = __esm({
|
|
1688
1686
|
"src/update-check.ts"() {
|
|
1689
|
-
defaultHomeDir2 = () =>
|
|
1687
|
+
defaultHomeDir2 = () => os3.homedir();
|
|
1690
1688
|
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1691
1689
|
}
|
|
1692
1690
|
});
|
|
@@ -2234,7 +2232,7 @@ async function runWebUI(opts) {
|
|
|
2234
2232
|
return {};
|
|
2235
2233
|
}
|
|
2236
2234
|
if (!parsed.providers) return {};
|
|
2237
|
-
const keyFile =
|
|
2235
|
+
const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
|
|
2238
2236
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
2239
2237
|
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
2240
2238
|
}
|
|
@@ -2267,7 +2265,7 @@ async function runWebUI(opts) {
|
|
|
2267
2265
|
parsed = {};
|
|
2268
2266
|
}
|
|
2269
2267
|
parsed.providers = providers;
|
|
2270
|
-
const keyFile =
|
|
2268
|
+
const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
|
|
2271
2269
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
2272
2270
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
2273
2271
|
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
@@ -2487,10 +2485,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2487
2485
|
const name = declared.split("@")[0];
|
|
2488
2486
|
if (name) return name;
|
|
2489
2487
|
}
|
|
2490
|
-
if (await pathExists(
|
|
2491
|
-
if (await pathExists(
|
|
2492
|
-
if (await pathExists(
|
|
2493
|
-
if (await pathExists(
|
|
2488
|
+
if (await pathExists(path24.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2489
|
+
if (await pathExists(path24.join(root, "bun.lockb"))) return "bun";
|
|
2490
|
+
if (await pathExists(path24.join(root, "bun.lock"))) return "bun";
|
|
2491
|
+
if (await pathExists(path24.join(root, "yarn.lock"))) return "yarn";
|
|
2494
2492
|
return "npm";
|
|
2495
2493
|
}
|
|
2496
2494
|
function hasUsableScript(scripts, name) {
|
|
@@ -2511,7 +2509,7 @@ function parseMakeTargets(makefile) {
|
|
|
2511
2509
|
async function detectProjectFacts(root) {
|
|
2512
2510
|
const facts = { hints: [] };
|
|
2513
2511
|
try {
|
|
2514
|
-
const pkg = JSON.parse(await fsp3.readFile(
|
|
2512
|
+
const pkg = JSON.parse(await fsp3.readFile(path24.join(root, "package.json"), "utf8"));
|
|
2515
2513
|
const scripts = pkg.scripts ?? {};
|
|
2516
2514
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2517
2515
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2525,14 +2523,14 @@ async function detectProjectFacts(root) {
|
|
|
2525
2523
|
} catch {
|
|
2526
2524
|
}
|
|
2527
2525
|
try {
|
|
2528
|
-
if (!await pathExists(
|
|
2526
|
+
if (!await pathExists(path24.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2529
2527
|
facts.test ??= "pytest";
|
|
2530
2528
|
facts.lint ??= "ruff check .";
|
|
2531
2529
|
facts.hints.push("pyproject.toml");
|
|
2532
2530
|
} catch {
|
|
2533
2531
|
}
|
|
2534
2532
|
try {
|
|
2535
|
-
if (!await pathExists(
|
|
2533
|
+
if (!await pathExists(path24.join(root, "go.mod"))) throw new Error("not go");
|
|
2536
2534
|
facts.build ??= "go build ./...";
|
|
2537
2535
|
facts.test ??= "go test ./...";
|
|
2538
2536
|
facts.run ??= "go run .";
|
|
@@ -2540,7 +2538,7 @@ async function detectProjectFacts(root) {
|
|
|
2540
2538
|
} catch {
|
|
2541
2539
|
}
|
|
2542
2540
|
try {
|
|
2543
|
-
if (!await pathExists(
|
|
2541
|
+
if (!await pathExists(path24.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2544
2542
|
facts.build ??= "cargo build";
|
|
2545
2543
|
facts.test ??= "cargo test";
|
|
2546
2544
|
facts.lint ??= "cargo clippy";
|
|
@@ -2549,7 +2547,7 @@ async function detectProjectFacts(root) {
|
|
|
2549
2547
|
} catch {
|
|
2550
2548
|
}
|
|
2551
2549
|
try {
|
|
2552
|
-
const makefile = await fsp3.readFile(
|
|
2550
|
+
const makefile = await fsp3.readFile(path24.join(root, "Makefile"), "utf8");
|
|
2553
2551
|
const targets = parseMakeTargets(makefile);
|
|
2554
2552
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2555
2553
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -2674,11 +2672,6 @@ function estimateTokens(messages) {
|
|
|
2674
2672
|
}
|
|
2675
2673
|
return total;
|
|
2676
2674
|
}
|
|
2677
|
-
function statusIcon(status) {
|
|
2678
|
-
if (status === "healthy") return color.green("\u25CF");
|
|
2679
|
-
if (status === "degraded") return color.yellow("\u25CF");
|
|
2680
|
-
return color.red("\u25CF");
|
|
2681
|
-
}
|
|
2682
2675
|
|
|
2683
2676
|
// src/slash-commands/clear.ts
|
|
2684
2677
|
function buildClearCommand(opts) {
|
|
@@ -2717,203 +2710,6 @@ function buildClearCommand(opts) {
|
|
|
2717
2710
|
}
|
|
2718
2711
|
};
|
|
2719
2712
|
}
|
|
2720
|
-
async function runGit(args, cwd) {
|
|
2721
|
-
try {
|
|
2722
|
-
return await new Promise((resolve4, reject) => {
|
|
2723
|
-
const child = spawn("git", args, {
|
|
2724
|
-
cwd,
|
|
2725
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2726
|
-
});
|
|
2727
|
-
let stdout = "";
|
|
2728
|
-
let stderr = "";
|
|
2729
|
-
child.stdout?.on("data", (d) => {
|
|
2730
|
-
stdout += d;
|
|
2731
|
-
});
|
|
2732
|
-
child.stderr?.on("data", (d) => {
|
|
2733
|
-
stderr += d;
|
|
2734
|
-
});
|
|
2735
|
-
child.on("error", (err) => {
|
|
2736
|
-
reject(new WrongStackError({
|
|
2737
|
-
message: `Failed to run git: ${err.message}`,
|
|
2738
|
-
code: ERROR_CODES.TOOL_EXECUTION_FAILED,
|
|
2739
|
-
subsystem: "tool",
|
|
2740
|
-
context: { command: "git", args, cwd },
|
|
2741
|
-
cause: err
|
|
2742
|
-
}));
|
|
2743
|
-
});
|
|
2744
|
-
child.on("close", (code) => resolve4({ stdout, stderr, code: code ?? 0 }));
|
|
2745
|
-
});
|
|
2746
|
-
} catch (err) {
|
|
2747
|
-
if (err instanceof WrongStackError) throw err;
|
|
2748
|
-
throw new WrongStackError({
|
|
2749
|
-
message: err instanceof Error ? err.message : String(err),
|
|
2750
|
-
code: ERROR_CODES.TOOL_EXECUTION_FAILED,
|
|
2751
|
-
subsystem: "tool",
|
|
2752
|
-
context: { command: "git", args, cwd },
|
|
2753
|
-
cause: err
|
|
2754
|
-
});
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2757
|
-
function detectCommitType(stats) {
|
|
2758
|
-
const lines = stats.split("\n");
|
|
2759
|
-
const hasTestFiles = lines.some(
|
|
2760
|
-
(l) => l.includes("_test.") || l.includes(".test.") || l.includes(".spec.")
|
|
2761
|
-
);
|
|
2762
|
-
const hasDocs = lines.some(
|
|
2763
|
-
(l) => l.includes("README") || l.includes("CHANGELOG") || l.includes("docs/") || l.includes(".md")
|
|
2764
|
-
);
|
|
2765
|
-
const hasConfig = lines.some(
|
|
2766
|
-
(l) => l.includes("config") || l.includes("tsconfig") || l.includes(".json")
|
|
2767
|
-
);
|
|
2768
|
-
if (hasTestFiles) return "test";
|
|
2769
|
-
if (hasDocs) return "docs";
|
|
2770
|
-
if (hasConfig) return "chore";
|
|
2771
|
-
return "feat";
|
|
2772
|
-
}
|
|
2773
|
-
async function generateCommitMessageHeuristics(cwd) {
|
|
2774
|
-
const statsResult = await runGit(["diff", "--stat"], cwd);
|
|
2775
|
-
if (statsResult.code !== 0) return "chore: update";
|
|
2776
|
-
const nameResult = await runGit(["diff", "--name-only"], cwd);
|
|
2777
|
-
const files = nameResult.stdout.split("\n").filter(Boolean);
|
|
2778
|
-
const commitType = detectCommitType(statsResult.stdout);
|
|
2779
|
-
let scope = "";
|
|
2780
|
-
if (files.length > 0) {
|
|
2781
|
-
const primary = files[0].split("/")[0];
|
|
2782
|
-
if (primary && primary !== "packages" && primary !== "apps" && primary !== "node_modules") {
|
|
2783
|
-
scope = `(${primary})`;
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
if (files.length === 0) {
|
|
2787
|
-
return `${commitType}${scope}: update`;
|
|
2788
|
-
}
|
|
2789
|
-
if (files.length <= 3) {
|
|
2790
|
-
const summary2 = files.map((f) => f.split("/").pop()).join(", ");
|
|
2791
|
-
return `${commitType}${scope}: ${summary2}`;
|
|
2792
|
-
}
|
|
2793
|
-
const summary = files.slice(0, 3).map((f) => f.split("/").pop()).join(", ") + ` and ${files.length - 3} more`;
|
|
2794
|
-
return `${commitType}${scope}: ${summary}`;
|
|
2795
|
-
}
|
|
2796
|
-
async function hasUncommittedChanges(cwd) {
|
|
2797
|
-
const result = await runGit(["status", "--porcelain"], cwd);
|
|
2798
|
-
return result.stdout.trim().length > 0;
|
|
2799
|
-
}
|
|
2800
|
-
async function isGitRepo(cwd) {
|
|
2801
|
-
const result = await runGit(["rev-parse", "--git-dir"], cwd);
|
|
2802
|
-
return result.code === 0;
|
|
2803
|
-
}
|
|
2804
|
-
function buildCommitCommand(_opts, generateCommitMessage) {
|
|
2805
|
-
return {
|
|
2806
|
-
name: "commit",
|
|
2807
|
-
description: "Stage all changes and commit with auto-generated message.",
|
|
2808
|
-
aliases: ["gc"],
|
|
2809
|
-
async run(args, ctx) {
|
|
2810
|
-
const cwd = ctx?.cwd ?? process.cwd();
|
|
2811
|
-
if (!await isGitRepo(cwd)) {
|
|
2812
|
-
return { message: "Not a git repository." };
|
|
2813
|
-
}
|
|
2814
|
-
if (!await hasUncommittedChanges(cwd)) {
|
|
2815
|
-
return { message: "Nothing to commit (working tree clean)." };
|
|
2816
|
-
}
|
|
2817
|
-
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
2818
|
-
args.includes("--no-llm");
|
|
2819
|
-
let message;
|
|
2820
|
-
{
|
|
2821
|
-
message = await generateCommitMessageHeuristics(cwd);
|
|
2822
|
-
}
|
|
2823
|
-
if (dryRun) {
|
|
2824
|
-
return {
|
|
2825
|
-
message: `Would commit:
|
|
2826
|
-
|
|
2827
|
-
${color.green(message)}
|
|
2828
|
-
|
|
2829
|
-
${color.dim("(dry-run \u2014 no actual commit)")}`
|
|
2830
|
-
};
|
|
2831
|
-
}
|
|
2832
|
-
const stageResult = await runGit(["add", "."], cwd);
|
|
2833
|
-
if (stageResult.code !== 0) {
|
|
2834
|
-
return { message: `Stage failed: ${stageResult.stderr}` };
|
|
2835
|
-
}
|
|
2836
|
-
const commitResult = await runGit(["commit", "-m", message], cwd);
|
|
2837
|
-
if (commitResult.code !== 0) {
|
|
2838
|
-
return { message: `Commit failed: ${commitResult.stderr}` };
|
|
2839
|
-
}
|
|
2840
|
-
const hashResult = await runGit(["rev-parse", "--short", "HEAD"], cwd);
|
|
2841
|
-
const hash = hashResult.stdout.trim();
|
|
2842
|
-
const pushResult = await runGit(["remote"], cwd);
|
|
2843
|
-
const hasRemote = pushResult.stdout.trim().length > 0;
|
|
2844
|
-
let pushMsg = "";
|
|
2845
|
-
if (hasRemote) {
|
|
2846
|
-
pushMsg = `
|
|
2847
|
-
|
|
2848
|
-
${color.dim("Tip: Run /push to push to remote")}`;
|
|
2849
|
-
}
|
|
2850
|
-
return {
|
|
2851
|
-
message: `${color.green("\u2713")} Committed: ${color.bold(message)}
|
|
2852
|
-
${color.dim(hash)}${pushMsg}`
|
|
2853
|
-
};
|
|
2854
|
-
}
|
|
2855
|
-
};
|
|
2856
|
-
}
|
|
2857
|
-
function buildGitcheckCommand(_opts) {
|
|
2858
|
-
return {
|
|
2859
|
-
name: "gitcheck",
|
|
2860
|
-
description: "Check for uncommitted changes (for system prompt integration).",
|
|
2861
|
-
aliases: ["gcstatus"],
|
|
2862
|
-
async run(_args, ctx) {
|
|
2863
|
-
const cwd = ctx?.cwd ?? process.cwd();
|
|
2864
|
-
if (!await isGitRepo(cwd)) {
|
|
2865
|
-
return { message: "" };
|
|
2866
|
-
}
|
|
2867
|
-
if (!await hasUncommittedChanges(cwd)) {
|
|
2868
|
-
return { message: "" };
|
|
2869
|
-
}
|
|
2870
|
-
const statusResult = await runGit(["status", "--porcelain"], cwd);
|
|
2871
|
-
const lines = statusResult.stdout.split("\n").filter(Boolean);
|
|
2872
|
-
const count = lines.length;
|
|
2873
|
-
if (count === 0) return { message: "" };
|
|
2874
|
-
return {
|
|
2875
|
-
message: `\u26A0 ${color.yellow(`${count} uncommitted change${count > 1 ? "s" : ""}`)} \u2014 consider /commit`
|
|
2876
|
-
};
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
function buildPushCommand(_opts) {
|
|
2881
|
-
return {
|
|
2882
|
-
name: "push",
|
|
2883
|
-
description: "Push to remote after commit.",
|
|
2884
|
-
async run(args, ctx) {
|
|
2885
|
-
const cwd = ctx?.cwd ?? process.cwd();
|
|
2886
|
-
if (!await isGitRepo(cwd)) {
|
|
2887
|
-
return { message: "Not a git repository." };
|
|
2888
|
-
}
|
|
2889
|
-
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
2890
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
2891
|
-
const remoteResult = await runGit(["remote"], cwd);
|
|
2892
|
-
const remotes = remoteResult.stdout.split("\n").filter(Boolean);
|
|
2893
|
-
if (remotes.length === 0) {
|
|
2894
|
-
return { message: "No remote configured. Add one with: git remote add origin <url>" };
|
|
2895
|
-
}
|
|
2896
|
-
if (dryRun) {
|
|
2897
|
-
return {
|
|
2898
|
-
message: `Would push to ${remotes.join(", ")}${force ? " (force)" : ""}
|
|
2899
|
-
${color.dim("(dry-run)")}`
|
|
2900
|
-
};
|
|
2901
|
-
}
|
|
2902
|
-
const branchResult = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
2903
|
-
const branch = branchResult.stdout.trim() || "main";
|
|
2904
|
-
const pushArgs = ["push"];
|
|
2905
|
-
if (force) pushArgs.push("--force");
|
|
2906
|
-
pushArgs.push(...remotes, branch);
|
|
2907
|
-
const pushResult = await runGit(pushArgs, cwd);
|
|
2908
|
-
if (pushResult.code !== 0) {
|
|
2909
|
-
return { message: `Push failed: ${pushResult.stderr}` };
|
|
2910
|
-
}
|
|
2911
|
-
return {
|
|
2912
|
-
message: `${color.green("\u2713")} Pushed to ${remotes.join(", ")} (${branch})`
|
|
2913
|
-
};
|
|
2914
|
-
}
|
|
2915
|
-
};
|
|
2916
|
-
}
|
|
2917
2713
|
|
|
2918
2714
|
// src/slash-commands/compact.ts
|
|
2919
2715
|
function buildCompactCommand(opts) {
|
|
@@ -3355,25 +3151,6 @@ function buildFleetCommand(opts) {
|
|
|
3355
3151
|
}
|
|
3356
3152
|
};
|
|
3357
3153
|
}
|
|
3358
|
-
function buildHealthCommand(opts) {
|
|
3359
|
-
return {
|
|
3360
|
-
name: "health",
|
|
3361
|
-
description: "Run health checks (requires --metrics flag).",
|
|
3362
|
-
async run() {
|
|
3363
|
-
if (!opts.healthRegistry)
|
|
3364
|
-
return { message: "Health checks not enabled. Restart with --metrics." };
|
|
3365
|
-
const result = await opts.healthRegistry.run();
|
|
3366
|
-
const lines = [
|
|
3367
|
-
`${statusIcon(result.status)} overall: ${result.status}`,
|
|
3368
|
-
...result.checks.map((c) => {
|
|
3369
|
-
const detail = c.detail ? color.dim(` \u2014 ${c.detail}`) : "";
|
|
3370
|
-
return ` ${statusIcon(c.status)} ${c.name}: ${c.status}${detail}`;
|
|
3371
|
-
})
|
|
3372
|
-
];
|
|
3373
|
-
return { message: lines.join("\n") };
|
|
3374
|
-
}
|
|
3375
|
-
};
|
|
3376
|
-
}
|
|
3377
3154
|
|
|
3378
3155
|
// src/slash-commands/help.ts
|
|
3379
3156
|
function buildHelpCommand(opts) {
|
|
@@ -3441,8 +3218,8 @@ function buildInitCommand(opts) {
|
|
|
3441
3218
|
name: "init",
|
|
3442
3219
|
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3443
3220
|
async run(_args, ctx) {
|
|
3444
|
-
const dir =
|
|
3445
|
-
const file =
|
|
3221
|
+
const dir = path24.join(ctx.projectRoot, ".wrongstack");
|
|
3222
|
+
const file = path24.join(dir, "AGENTS.md");
|
|
3446
3223
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
3447
3224
|
const body = renderAgentsTemplate(detected);
|
|
3448
3225
|
await fsp3.mkdir(dir, { recursive: true });
|
|
@@ -3633,18 +3410,18 @@ function stateBadge(state) {
|
|
|
3633
3410
|
return color.dim(state);
|
|
3634
3411
|
}
|
|
3635
3412
|
}
|
|
3636
|
-
async function readConfig(
|
|
3413
|
+
async function readConfig(path25) {
|
|
3637
3414
|
try {
|
|
3638
|
-
return JSON.parse(await fsp3.readFile(
|
|
3415
|
+
return JSON.parse(await fsp3.readFile(path25, "utf8"));
|
|
3639
3416
|
} catch {
|
|
3640
3417
|
return {};
|
|
3641
3418
|
}
|
|
3642
3419
|
}
|
|
3643
|
-
async function writeConfig(
|
|
3420
|
+
async function writeConfig(path25, cfg) {
|
|
3644
3421
|
const raw = JSON.stringify(cfg, null, 2);
|
|
3645
|
-
const tmp =
|
|
3422
|
+
const tmp = path25 + ".tmp";
|
|
3646
3423
|
await fsp3.writeFile(tmp, raw, "utf8");
|
|
3647
|
-
await fsp3.rename(tmp,
|
|
3424
|
+
await fsp3.rename(tmp, path25);
|
|
3648
3425
|
}
|
|
3649
3426
|
|
|
3650
3427
|
// src/slash-commands/mcp.ts
|
|
@@ -3726,150 +3503,6 @@ function buildMemoryCommand(opts) {
|
|
|
3726
3503
|
}
|
|
3727
3504
|
};
|
|
3728
3505
|
}
|
|
3729
|
-
function buildMetricsCommand(opts) {
|
|
3730
|
-
return {
|
|
3731
|
-
name: "metrics",
|
|
3732
|
-
description: "Show metrics snapshot (requires --metrics flag).",
|
|
3733
|
-
async run() {
|
|
3734
|
-
if (!opts.metricsSink)
|
|
3735
|
-
return { message: "Metrics not enabled. Restart with --metrics to collect." };
|
|
3736
|
-
const snap = opts.metricsSink.snapshot();
|
|
3737
|
-
if (snap.series.length === 0) return { message: "No metrics recorded yet." };
|
|
3738
|
-
const lines = [];
|
|
3739
|
-
const byName = /* @__PURE__ */ new Map();
|
|
3740
|
-
for (const s of snap.series) {
|
|
3741
|
-
const bucket = byName.get(s.name) ?? [];
|
|
3742
|
-
bucket.push(s);
|
|
3743
|
-
byName.set(s.name, bucket);
|
|
3744
|
-
}
|
|
3745
|
-
for (const [name, series] of [...byName.entries()].sort()) {
|
|
3746
|
-
lines.push(color.dim(`# ${name}`));
|
|
3747
|
-
for (const s of series) {
|
|
3748
|
-
const labels = Object.entries(s.labels).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
3749
|
-
const labelStr = labels ? color.dim(` {${labels}}`) : "";
|
|
3750
|
-
if (s.type === "histogram")
|
|
3751
|
-
lines.push(
|
|
3752
|
-
` count=${s.values.count} sum=${s.values.sum} min=${s.values.min} max=${s.values.max} p50=${s.values.p50} p95=${s.values.p95} p99=${s.values.p99}${labelStr}`
|
|
3753
|
-
);
|
|
3754
|
-
else lines.push(` ${s.values.value}${labelStr}`);
|
|
3755
|
-
}
|
|
3756
|
-
}
|
|
3757
|
-
return { message: lines.join("\n") };
|
|
3758
|
-
}
|
|
3759
|
-
};
|
|
3760
|
-
}
|
|
3761
|
-
function buildPlanCommand(opts) {
|
|
3762
|
-
return {
|
|
3763
|
-
name: "plan",
|
|
3764
|
-
description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
|
|
3765
|
-
async run(args) {
|
|
3766
|
-
const planPath = opts.paths?.projectPlan ?? opts.planPath;
|
|
3767
|
-
if (!planPath) return { message: "Plan storage is not configured for this session." };
|
|
3768
|
-
const ctx = opts.context;
|
|
3769
|
-
const sessionId = ctx?.session.id ?? "unknown";
|
|
3770
|
-
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
3771
|
-
const restJoined = rest.join(" ").trim();
|
|
3772
|
-
const plan = await loadPlan(planPath) ?? emptyPlan(sessionId);
|
|
3773
|
-
switch (verb) {
|
|
3774
|
-
case "":
|
|
3775
|
-
case "show":
|
|
3776
|
-
case "list": {
|
|
3777
|
-
return { message: formatPlan(plan) };
|
|
3778
|
-
}
|
|
3779
|
-
case "add": {
|
|
3780
|
-
if (!restJoined) return { message: "Usage: /plan add <title>" };
|
|
3781
|
-
const { plan: updated, item } = addPlanItem(plan, restJoined);
|
|
3782
|
-
await savePlan(planPath, updated);
|
|
3783
|
-
return { message: `Added: ${item.title}
|
|
3784
|
-
${formatPlan(updated)}` };
|
|
3785
|
-
}
|
|
3786
|
-
case "start":
|
|
3787
|
-
case "progress": {
|
|
3788
|
-
if (!restJoined) return { message: "Usage: /plan start <id|index>" };
|
|
3789
|
-
const updated = setPlanItemStatus(plan, restJoined, "in_progress");
|
|
3790
|
-
await savePlan(planPath, updated);
|
|
3791
|
-
return { message: formatPlan(updated) };
|
|
3792
|
-
}
|
|
3793
|
-
case "done":
|
|
3794
|
-
case "complete": {
|
|
3795
|
-
if (!restJoined) return { message: "Usage: /plan done <id|index>" };
|
|
3796
|
-
const updated = setPlanItemStatus(plan, restJoined, "done");
|
|
3797
|
-
await savePlan(planPath, updated);
|
|
3798
|
-
return { message: formatPlan(updated) };
|
|
3799
|
-
}
|
|
3800
|
-
case "remove":
|
|
3801
|
-
case "delete":
|
|
3802
|
-
case "rm": {
|
|
3803
|
-
if (!restJoined) return { message: "Usage: /plan remove <id|index>" };
|
|
3804
|
-
const updated = removePlanItem(plan, restJoined);
|
|
3805
|
-
await savePlan(planPath, updated);
|
|
3806
|
-
return { message: formatPlan(updated) };
|
|
3807
|
-
}
|
|
3808
|
-
case "promote": {
|
|
3809
|
-
if (!restJoined) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3810
|
-
const [target, ...subtasks] = restJoined.split(/\s+/);
|
|
3811
|
-
if (!target) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3812
|
-
const derived = deriveTodosFromPlanItem(plan, target, subtasks.length > 0 ? subtasks : void 0);
|
|
3813
|
-
if (!derived) return { message: `No plan item matched "${target}".` };
|
|
3814
|
-
await savePlan(planPath, derived.plan);
|
|
3815
|
-
if (ctx) {
|
|
3816
|
-
ctx.state.replaceTodos(derived.todos);
|
|
3817
|
-
}
|
|
3818
|
-
return {
|
|
3819
|
-
message: `Promoted to ${derived.todos.length} todo(s):
|
|
3820
|
-
${formatTodosList(derived.todos)}
|
|
3821
|
-
|
|
3822
|
-
${formatPlan(derived.plan)}`
|
|
3823
|
-
};
|
|
3824
|
-
}
|
|
3825
|
-
case "derive": {
|
|
3826
|
-
if (!restJoined) return { message: "Usage: /plan derive <id|index>" };
|
|
3827
|
-
const derived = deriveTodosFromPlanItem(plan, restJoined);
|
|
3828
|
-
if (!derived) return { message: `No plan item matched "${restJoined}".` };
|
|
3829
|
-
await savePlan(planPath, derived.plan);
|
|
3830
|
-
if (ctx) {
|
|
3831
|
-
ctx.state.replaceTodos(derived.todos);
|
|
3832
|
-
}
|
|
3833
|
-
return {
|
|
3834
|
-
message: `Derived ${derived.todos.length} todo(s):
|
|
3835
|
-
${formatTodosList(derived.todos)}
|
|
3836
|
-
|
|
3837
|
-
${formatPlan(derived.plan)}`
|
|
3838
|
-
};
|
|
3839
|
-
}
|
|
3840
|
-
case "template": {
|
|
3841
|
-
const subVerb = rest[0] ?? "";
|
|
3842
|
-
const subRest = rest.slice(1).join(" ").trim();
|
|
3843
|
-
if (subVerb === "" || subVerb === "list") {
|
|
3844
|
-
return { message: formatPlanTemplates() };
|
|
3845
|
-
}
|
|
3846
|
-
if (subVerb === "use") {
|
|
3847
|
-
if (!subRest) return { message: "Usage: /plan template use <template-name>" };
|
|
3848
|
-
const template = getPlanTemplate(subRest);
|
|
3849
|
-
if (!template) return { message: `Unknown template "${subRest}". Use /plan template list to see available templates.` };
|
|
3850
|
-
let updated = plan;
|
|
3851
|
-
for (const item of template.items) {
|
|
3852
|
-
({ plan: updated } = addPlanItem(updated, item.title, item.details));
|
|
3853
|
-
}
|
|
3854
|
-
await savePlan(planPath, updated);
|
|
3855
|
-
return { message: `Applied template "${template.name}" (${template.items.length} items):
|
|
3856
|
-
${formatPlan(updated)}` };
|
|
3857
|
-
}
|
|
3858
|
-
return { message: `Unknown template subcommand "${subVerb}". Try: list | use <name>` };
|
|
3859
|
-
}
|
|
3860
|
-
case "clear": {
|
|
3861
|
-
const updated = clearPlan(plan);
|
|
3862
|
-
await savePlan(planPath, updated);
|
|
3863
|
-
return { message: "Plan cleared." };
|
|
3864
|
-
}
|
|
3865
|
-
default:
|
|
3866
|
-
return {
|
|
3867
|
-
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | promote <id|#> | derive <id|#> | template [list|use <name>] | clear`
|
|
3868
|
-
};
|
|
3869
|
-
}
|
|
3870
|
-
}
|
|
3871
|
-
};
|
|
3872
|
-
}
|
|
3873
3506
|
|
|
3874
3507
|
// src/slash-commands/plugin.ts
|
|
3875
3508
|
function buildPluginCommand(opts) {
|
|
@@ -3956,30 +3589,6 @@ function buildExitCommand(opts) {
|
|
|
3956
3589
|
}
|
|
3957
3590
|
};
|
|
3958
3591
|
}
|
|
3959
|
-
function buildSkillCommand(opts) {
|
|
3960
|
-
return {
|
|
3961
|
-
name: "skill",
|
|
3962
|
-
description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
|
|
3963
|
-
async run(args) {
|
|
3964
|
-
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
3965
|
-
if (!args.trim()) {
|
|
3966
|
-
const entries = await opts.skillLoader.listEntries();
|
|
3967
|
-
if (entries.length === 0) return { message: "No skills found." };
|
|
3968
|
-
const lines = entries.map((e) => {
|
|
3969
|
-
const scopeTag = e.scope.length > 0 ? ` ${color.dim(`(${e.scope.slice(0, 3).join(", ")})`)}` : "";
|
|
3970
|
-
return ` ${color.bold(e.name)}${scopeTag}
|
|
3971
|
-
Use when: ${e.trigger}`;
|
|
3972
|
-
});
|
|
3973
|
-
return { message: `Available skills:
|
|
3974
|
-
${lines.join("\n\n")}
|
|
3975
|
-
` };
|
|
3976
|
-
}
|
|
3977
|
-
const skill = await opts.skillLoader.find(args.trim());
|
|
3978
|
-
if (!skill) return { message: `Skill "${args.trim()}" not found.` };
|
|
3979
|
-
return { message: await opts.skillLoader.readBody(skill.name) };
|
|
3980
|
-
}
|
|
3981
|
-
};
|
|
3982
|
-
}
|
|
3983
3592
|
|
|
3984
3593
|
// src/slash-commands/spawn-agents.ts
|
|
3985
3594
|
function buildSpawnCommand(opts) {
|
|
@@ -4439,6 +4048,7 @@ function buildGoalCommand(opts) {
|
|
|
4439
4048
|
const [verbRaw, ...rest] = trimmed.split(/\s+/);
|
|
4440
4049
|
const verb = (verbRaw ?? "").toLowerCase();
|
|
4441
4050
|
const restJoined = rest.join(" ").trim();
|
|
4051
|
+
if (!opts.paths) return { message: "Goal not available \u2014 paths not configured." };
|
|
4442
4052
|
const goalPath = opts.paths.projectGoal;
|
|
4443
4053
|
const verbForDispatch = verb && !KNOWN_VERBS.has(verb) ? "set" : verb;
|
|
4444
4054
|
const setText = verbForDispatch === "set" && !KNOWN_VERBS.has(verb) ? trimmed : restJoined;
|
|
@@ -4616,290 +4226,6 @@ ${targetMode.description}`
|
|
|
4616
4226
|
|
|
4617
4227
|
// src/slash-commands/index.ts
|
|
4618
4228
|
init_sdd();
|
|
4619
|
-
|
|
4620
|
-
// src/slash-commands/skill-generator.ts
|
|
4621
|
-
function buildSkillGeneratorCommand(opts) {
|
|
4622
|
-
return {
|
|
4623
|
-
name: "skill-gen",
|
|
4624
|
-
description: "Create a new AI skill interactively. The AI will guide you.",
|
|
4625
|
-
help: [
|
|
4626
|
-
"\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557",
|
|
4627
|
-
"",
|
|
4628
|
-
"Create new AI skills with AI guidance.",
|
|
4629
|
-
"",
|
|
4630
|
-
"Usage:",
|
|
4631
|
-
" /skill-gen Start skill creation",
|
|
4632
|
-
" /skill-gen list List existing skills",
|
|
4633
|
-
" /skill-gen edit <name> View an existing skill",
|
|
4634
|
-
"",
|
|
4635
|
-
"The AI will ask you questions and create the skill file.",
|
|
4636
|
-
"Skills are saved to .wrongstack/skills/<name>/SKILL.md"
|
|
4637
|
-
].join("\n"),
|
|
4638
|
-
async run(args) {
|
|
4639
|
-
const trimmed = args.trim();
|
|
4640
|
-
if (trimmed === "list" || trimmed === "ls") {
|
|
4641
|
-
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
4642
|
-
const entries = await opts.skillLoader.listEntries();
|
|
4643
|
-
if (entries.length === 0) return { message: "No skills found." };
|
|
4644
|
-
const lines = entries.map((e) => {
|
|
4645
|
-
const src = e.source === "project" ? "\u{1F4C1}" : e.source === "user" ? "\u{1F464}" : "\u{1F4E6}";
|
|
4646
|
-
return ` ${src} ${e.name}
|
|
4647
|
-
${e.trigger}`;
|
|
4648
|
-
});
|
|
4649
|
-
return { message: `Available Skills:
|
|
4650
|
-
${lines.join("\n\n")}
|
|
4651
|
-
` };
|
|
4652
|
-
}
|
|
4653
|
-
if (trimmed.startsWith("edit ")) {
|
|
4654
|
-
const skillName = trimmed.slice(5).trim();
|
|
4655
|
-
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
4656
|
-
const skill = await opts.skillLoader.find(skillName);
|
|
4657
|
-
if (!skill) return { message: `Skill "${skillName}" not found.` };
|
|
4658
|
-
const body = await opts.skillLoader.readBody(skillName);
|
|
4659
|
-
return {
|
|
4660
|
-
message: [
|
|
4661
|
-
`Skill: ${skillName}`,
|
|
4662
|
-
`Path: ${skill.path}`,
|
|
4663
|
-
"",
|
|
4664
|
-
body
|
|
4665
|
-
].join("\n")
|
|
4666
|
-
};
|
|
4667
|
-
}
|
|
4668
|
-
return {
|
|
4669
|
-
message: "\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557\n\nThe AI will guide you through creating a new skill.\nAnswer its questions naturally.",
|
|
4670
|
-
runText: "I want to create a new AI skill. Read the skill-creator skill and guide me through the process. Ask me questions one at a time \u2014 name, description, what to cover \u2014 then create the SKILL.md file."
|
|
4671
|
-
};
|
|
4672
|
-
}
|
|
4673
|
-
};
|
|
4674
|
-
}
|
|
4675
|
-
function getProviderFromContext(ctx, opts) {
|
|
4676
|
-
if (opts.llmProvider && typeof opts.llmProvider.complete === "function") {
|
|
4677
|
-
return { provider: opts.llmProvider, model: opts.llmModel };
|
|
4678
|
-
}
|
|
4679
|
-
if (ctx.provider && typeof ctx.provider.complete === "function") {
|
|
4680
|
-
return { provider: ctx.provider, model: ctx.model };
|
|
4681
|
-
}
|
|
4682
|
-
return null;
|
|
4683
|
-
}
|
|
4684
|
-
function buildSecurityCommand(opts) {
|
|
4685
|
-
return {
|
|
4686
|
-
name: "security",
|
|
4687
|
-
description: "Security scanning: scan, audit, report",
|
|
4688
|
-
argsHint: "[scan|audit|report] [options]",
|
|
4689
|
-
help: `
|
|
4690
|
-
# /security \u2014 Security Scanner
|
|
4691
|
-
|
|
4692
|
-
Automated security scanning with tech stack detection.
|
|
4693
|
-
|
|
4694
|
-
## Commands
|
|
4695
|
-
|
|
4696
|
-
### /security scan [options]
|
|
4697
|
-
Run a full security scan on the current project.
|
|
4698
|
-
Options:
|
|
4699
|
-
--depth quick|standard|deep Scan depth (default: standard)
|
|
4700
|
-
--format markdown|json|html Report format (default: markdown)
|
|
4701
|
-
|
|
4702
|
-
### /security audit
|
|
4703
|
-
Run dependency audit + security scan.
|
|
4704
|
-
|
|
4705
|
-
### /security report [id]
|
|
4706
|
-
List or view security reports.
|
|
4707
|
-
|
|
4708
|
-
## Examples
|
|
4709
|
-
|
|
4710
|
-
/security scan
|
|
4711
|
-
/security scan --depth deep --format html
|
|
4712
|
-
/security audit
|
|
4713
|
-
/security report
|
|
4714
|
-
`,
|
|
4715
|
-
async run(args, ctx) {
|
|
4716
|
-
const parts = args.trim().split(/\s+/);
|
|
4717
|
-
const subcommand = parts[0] || "";
|
|
4718
|
-
switch (subcommand) {
|
|
4719
|
-
case "scan":
|
|
4720
|
-
return handleScan(parts.slice(1).join(" "), ctx, opts);
|
|
4721
|
-
case "audit":
|
|
4722
|
-
return handleAudit(ctx, opts);
|
|
4723
|
-
case "report":
|
|
4724
|
-
return handleReport(parts[1] || "");
|
|
4725
|
-
default:
|
|
4726
|
-
return { message: getHelpMessage() };
|
|
4727
|
-
}
|
|
4728
|
-
}
|
|
4729
|
-
};
|
|
4730
|
-
}
|
|
4731
|
-
async function handleScan(args, ctx, opts) {
|
|
4732
|
-
const options = parseArgs2(args);
|
|
4733
|
-
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
4734
|
-
try {
|
|
4735
|
-
const providerInfo = getProviderFromContext(ctx, opts);
|
|
4736
|
-
if (!providerInfo) {
|
|
4737
|
-
return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
|
|
4738
|
-
}
|
|
4739
|
-
const result = await defaultOrchestrator.run(providerInfo, {
|
|
4740
|
-
projectRoot,
|
|
4741
|
-
scanOptions: {
|
|
4742
|
-
depth: options.depth || "standard",
|
|
4743
|
-
includeSecrets: true,
|
|
4744
|
-
includeInjection: true,
|
|
4745
|
-
includeConfig: true
|
|
4746
|
-
},
|
|
4747
|
-
reportOptions: {
|
|
4748
|
-
format: options.format || "markdown"
|
|
4749
|
-
}
|
|
4750
|
-
});
|
|
4751
|
-
const summary = result.scanResult.summary;
|
|
4752
|
-
const status = summary.total === 0 ? "\u2705 No issues found" : `\u26A0\uFE0F Found ${summary.total} issues`;
|
|
4753
|
-
const reportContent = result.synthesizedReport || `# Security Scan Complete
|
|
4754
|
-
|
|
4755
|
-
**Project:** ${projectRoot}
|
|
4756
|
-
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
4757
|
-
**Scanned Files:** ${result.scanResult.scannedFiles}
|
|
4758
|
-
**Duration:** ${result.scanResult.scanDurationMs}ms
|
|
4759
|
-
|
|
4760
|
-
## Summary
|
|
4761
|
-
|
|
4762
|
-
| Severity | Count |
|
|
4763
|
-
|----------|-------|
|
|
4764
|
-
| \u{1F534} Critical | ${summary.critical} |
|
|
4765
|
-
| \u{1F7E0} High | ${summary.high} |
|
|
4766
|
-
| \u{1F7E1} Medium | ${summary.medium} |
|
|
4767
|
-
| \u{1F7E2} Low | ${summary.low} |
|
|
4768
|
-
|
|
4769
|
-
**Status:** ${status}
|
|
4770
|
-
|
|
4771
|
-
**Report:** ${result.reportPath}
|
|
4772
|
-
`;
|
|
4773
|
-
return {
|
|
4774
|
-
message: reportContent,
|
|
4775
|
-
metadata: {
|
|
4776
|
-
scanResult: result.scanResult,
|
|
4777
|
-
reportPath: result.reportPath,
|
|
4778
|
-
techStack: result.detectionResult.detectedStacks[0]
|
|
4779
|
-
}
|
|
4780
|
-
};
|
|
4781
|
-
} catch (error) {
|
|
4782
|
-
return { message: `\u274C Scan failed: ${error}` };
|
|
4783
|
-
}
|
|
4784
|
-
}
|
|
4785
|
-
async function handleAudit(ctx, opts) {
|
|
4786
|
-
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
4787
|
-
try {
|
|
4788
|
-
const providerInfo = getProviderFromContext(ctx, opts);
|
|
4789
|
-
if (!providerInfo) {
|
|
4790
|
-
return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
|
|
4791
|
-
}
|
|
4792
|
-
const result = await defaultOrchestrator.run(providerInfo, {
|
|
4793
|
-
projectRoot,
|
|
4794
|
-
reportOptions: { format: "markdown" }
|
|
4795
|
-
});
|
|
4796
|
-
const summary = result.scanResult.summary;
|
|
4797
|
-
const depIssues = summary.critical + summary.high;
|
|
4798
|
-
const reportContent = result.synthesizedReport || `# Security Audit Complete
|
|
4799
|
-
|
|
4800
|
-
**Project:** ${projectRoot}
|
|
4801
|
-
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
4802
|
-
|
|
4803
|
-
## Dependency Health
|
|
4804
|
-
|
|
4805
|
-
| Status | Count |
|
|
4806
|
-
|--------|-------|
|
|
4807
|
-
| Critical Issues | ${summary.critical} |
|
|
4808
|
-
| High Priority | ${summary.high} |
|
|
4809
|
-
| Medium Priority | ${summary.medium} |
|
|
4810
|
-
| Low Priority | ${summary.low} |
|
|
4811
|
-
|
|
4812
|
-
${depIssues === 0 ? "\u2705 No known vulnerabilities detected" : `\u26A0\uFE0F ${depIssues} vulnerabilities need attention`}
|
|
4813
|
-
|
|
4814
|
-
**Full Report:** ${result.reportPath}
|
|
4815
|
-
`;
|
|
4816
|
-
return {
|
|
4817
|
-
message: reportContent,
|
|
4818
|
-
metadata: {
|
|
4819
|
-
scanResult: result.scanResult,
|
|
4820
|
-
reportPath: result.reportPath
|
|
4821
|
-
}
|
|
4822
|
-
};
|
|
4823
|
-
} catch (error) {
|
|
4824
|
-
return { message: `\u274C Audit failed: ${error}` };
|
|
4825
|
-
}
|
|
4826
|
-
}
|
|
4827
|
-
async function handleReport(reportId) {
|
|
4828
|
-
const reportsDir = "security-reports";
|
|
4829
|
-
try {
|
|
4830
|
-
const files = await readdir(reportsDir);
|
|
4831
|
-
const reports = files.filter((f) => f.startsWith("security-report-") && (f.endsWith(".md") || f.endsWith(".json"))).sort().reverse();
|
|
4832
|
-
if (!reportId) {
|
|
4833
|
-
if (reports.length === 0) {
|
|
4834
|
-
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4835
|
-
}
|
|
4836
|
-
const list = reports.map((r, i) => {
|
|
4837
|
-
const date = r.replace("security-report-", "").replace(/\.(md|json)$/, "");
|
|
4838
|
-
return ` ${i + 1}. ${date}`;
|
|
4839
|
-
}).join("\n");
|
|
4840
|
-
return { message: `# Available Security Reports
|
|
4841
|
-
|
|
4842
|
-
${list}
|
|
4843
|
-
|
|
4844
|
-
Use \`/security report <number>\` to view a specific report.` };
|
|
4845
|
-
}
|
|
4846
|
-
const index = Number.parseInt(reportId, 10) - 1;
|
|
4847
|
-
if (!Number.isNaN(index) && reports[index]) {
|
|
4848
|
-
const content = await readFile(join(reportsDir, reports[index]), "utf-8");
|
|
4849
|
-
return { message: `# Security Report
|
|
4850
|
-
|
|
4851
|
-
${content}` };
|
|
4852
|
-
}
|
|
4853
|
-
const match = reports.find((r) => r.includes(reportId));
|
|
4854
|
-
if (match) {
|
|
4855
|
-
const content = await readFile(join(reportsDir, match), "utf-8");
|
|
4856
|
-
return { message: `# Security Report
|
|
4857
|
-
|
|
4858
|
-
${content}` };
|
|
4859
|
-
}
|
|
4860
|
-
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
4861
|
-
} catch {
|
|
4862
|
-
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4863
|
-
}
|
|
4864
|
-
}
|
|
4865
|
-
function parseArgs2(args) {
|
|
4866
|
-
const result = {};
|
|
4867
|
-
const parts = args.split(/\s+/);
|
|
4868
|
-
for (let i = 0; i < parts.length; i++) {
|
|
4869
|
-
const part = parts[i];
|
|
4870
|
-
if (!part || !part.startsWith("--")) continue;
|
|
4871
|
-
const key = part.slice(2);
|
|
4872
|
-
const next = parts[i + 1];
|
|
4873
|
-
if (next && !next.startsWith("--")) {
|
|
4874
|
-
result[key] = next;
|
|
4875
|
-
i++;
|
|
4876
|
-
} else {
|
|
4877
|
-
result[key] = "true";
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
4880
|
-
return result;
|
|
4881
|
-
}
|
|
4882
|
-
function getHelpMessage() {
|
|
4883
|
-
return `# /security \u2014 Security Scanner
|
|
4884
|
-
|
|
4885
|
-
**Available Commands:**
|
|
4886
|
-
|
|
4887
|
-
1. **/security scan** \u2014 Run full security scan
|
|
4888
|
-
\`/security scan --depth deep --format html\`
|
|
4889
|
-
|
|
4890
|
-
2. **/security audit** \u2014 Run dependency audit + security scan
|
|
4891
|
-
|
|
4892
|
-
3. **/security report** \u2014 List available reports
|
|
4893
|
-
|
|
4894
|
-
**Features:**
|
|
4895
|
-
- Automatic tech stack detection
|
|
4896
|
-
- Dynamic security skill generation
|
|
4897
|
-
- Secrets, injection, and config vulnerability scanning
|
|
4898
|
-
- Markdown/JSON/HTML reports
|
|
4899
|
-
- .gitignore auto-update
|
|
4900
|
-
|
|
4901
|
-
Run \`/security scan\` to start.`;
|
|
4902
|
-
}
|
|
4903
4229
|
var CONFIG_ENV = "WRONGSTACK_STATUSLINE_CONFIG";
|
|
4904
4230
|
var DEFAULTS = {
|
|
4905
4231
|
todos: true,
|
|
@@ -4911,7 +4237,7 @@ var DEFAULTS = {
|
|
|
4911
4237
|
cost: true
|
|
4912
4238
|
};
|
|
4913
4239
|
function resolveConfigPath() {
|
|
4914
|
-
return process.env[CONFIG_ENV] ??
|
|
4240
|
+
return process.env[CONFIG_ENV] ?? path24.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
4915
4241
|
}
|
|
4916
4242
|
async function loadStatuslineConfig() {
|
|
4917
4243
|
const p = resolveConfigPath();
|
|
@@ -4925,7 +4251,7 @@ async function loadStatuslineConfig() {
|
|
|
4925
4251
|
async function saveStatuslineConfig(cfg) {
|
|
4926
4252
|
const p = resolveConfigPath();
|
|
4927
4253
|
try {
|
|
4928
|
-
await fsp3.mkdir(
|
|
4254
|
+
await fsp3.mkdir(path24.dirname(p), { recursive: true });
|
|
4929
4255
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
4930
4256
|
} catch (err) {
|
|
4931
4257
|
throw new FsError({
|
|
@@ -5823,168 +5149,8 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
|
5823
5149
|
}
|
|
5824
5150
|
};
|
|
5825
5151
|
}
|
|
5826
|
-
function makeInstaller(opts, projectRoot, global) {
|
|
5827
|
-
const globalRoot = path25.join(os4.homedir(), ".wrongstack");
|
|
5828
|
-
return new SkillInstaller({
|
|
5829
|
-
manifestPath: path25.join(globalRoot, "installed-skills.json"),
|
|
5830
|
-
projectSkillsDir: path25.join(projectRoot, ".wrongstack", "skills"),
|
|
5831
|
-
globalSkillsDir: path25.join(globalRoot, "skills"),
|
|
5832
|
-
projectHash: projectHash(projectRoot),
|
|
5833
|
-
skillLoader: opts.skillLoader
|
|
5834
|
-
});
|
|
5835
|
-
}
|
|
5836
|
-
function buildSkillInstallCommand(opts) {
|
|
5837
|
-
return {
|
|
5838
|
-
name: "skill-install",
|
|
5839
|
-
description: "Install skills from a GitHub repository.",
|
|
5840
|
-
argsHint: "<user/repo[@ref]> [--global]",
|
|
5841
|
-
help: [
|
|
5842
|
-
"\u2554\u2550\u2550\u2550 Skill Install \u2550\u2550\u2550\u2557",
|
|
5843
|
-
"",
|
|
5844
|
-
"Install skills from a GitHub repository.",
|
|
5845
|
-
"",
|
|
5846
|
-
"Usage:",
|
|
5847
|
-
" /skill-install <user/repo> Install from default branch (main)",
|
|
5848
|
-
" /skill-install <user/repo@ref> Install specific tag/branch/commit",
|
|
5849
|
-
" /skill-install <user/repo> --global Install to user-global skills",
|
|
5850
|
-
"",
|
|
5851
|
-
"Supports both single-skill repos (SKILL.md at root)",
|
|
5852
|
-
"and multi-skill repos (skills/ subdirectory).",
|
|
5853
|
-
"",
|
|
5854
|
-
"Examples:",
|
|
5855
|
-
" /skill-install wrongstack/awesome-skills",
|
|
5856
|
-
" /skill-install wrongstack/skills@v1.0",
|
|
5857
|
-
" /skill-install user/my-skills --global"
|
|
5858
|
-
].join("\n"),
|
|
5859
|
-
async run(args, ctx) {
|
|
5860
|
-
const parts = args.trim().split(/\s+/);
|
|
5861
|
-
const ref = parts.find((p) => !p.startsWith("--"));
|
|
5862
|
-
const isGlobal = parts.includes("--global");
|
|
5863
|
-
if (!ref) {
|
|
5864
|
-
return { message: "Usage: /skill-install <user/repo[@ref]> [--global]" };
|
|
5865
|
-
}
|
|
5866
|
-
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
5867
|
-
try {
|
|
5868
|
-
const results = await installer.install(ref, { global: isGlobal });
|
|
5869
|
-
if (results.length === 0) {
|
|
5870
|
-
return { message: "No skills found in the repository." };
|
|
5871
|
-
}
|
|
5872
|
-
const scope = isGlobal ? "user-global" : "project";
|
|
5873
|
-
const lines = [`Installed ${results.length} skill(s) [${scope}]:`];
|
|
5874
|
-
for (const r of results) {
|
|
5875
|
-
lines.push(` \u2713 ${r.name} (${r.source}@${r.ref})`);
|
|
5876
|
-
lines.push(` \u2192 ${r.path}`);
|
|
5877
|
-
}
|
|
5878
|
-
return { message: lines.join("\n") };
|
|
5879
|
-
} catch (err) {
|
|
5880
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5881
|
-
opts.renderer.writeError(`Install failed: ${msg}`);
|
|
5882
|
-
return { message: `\u2717 Install failed: ${msg}` };
|
|
5883
|
-
}
|
|
5884
|
-
}
|
|
5885
|
-
};
|
|
5886
|
-
}
|
|
5887
|
-
function buildSkillUpdateCommand(opts) {
|
|
5888
|
-
return {
|
|
5889
|
-
name: "skill-update",
|
|
5890
|
-
description: "Update installed skills from their GitHub source.",
|
|
5891
|
-
argsHint: "[name|ref] [--global]",
|
|
5892
|
-
help: [
|
|
5893
|
-
"\u2554\u2550\u2550\u2550 Skill Update \u2550\u2550\u2550\u2557",
|
|
5894
|
-
"",
|
|
5895
|
-
"Update installed skills from their GitHub source.",
|
|
5896
|
-
"",
|
|
5897
|
-
"Usage:",
|
|
5898
|
-
" /skill-update Update all installed skills",
|
|
5899
|
-
" /skill-update <name> Update a specific skill",
|
|
5900
|
-
" /skill-update <user/repo@ref> Update to a different ref",
|
|
5901
|
-
" /skill-update <name> --global Update a global skill"
|
|
5902
|
-
].join("\n"),
|
|
5903
|
-
async run(args, ctx) {
|
|
5904
|
-
const parts = args.trim().split(/\s+/);
|
|
5905
|
-
const nameOrRef = parts.find((p) => !p.startsWith("--"));
|
|
5906
|
-
const isGlobal = parts.includes("--global");
|
|
5907
|
-
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
5908
|
-
try {
|
|
5909
|
-
const result = await installer.update(nameOrRef, { global: isGlobal });
|
|
5910
|
-
const lines = [];
|
|
5911
|
-
if (result.updated.length > 0) {
|
|
5912
|
-
lines.push(`Updated ${result.updated.length} skill(s):`);
|
|
5913
|
-
for (const u of result.updated) {
|
|
5914
|
-
if (u.oldRef !== u.newRef) {
|
|
5915
|
-
lines.push(` \u2713 ${u.name} (${u.oldRef} \u2192 ${u.newRef})`);
|
|
5916
|
-
} else {
|
|
5917
|
-
lines.push(` \u2713 ${u.name} (refreshed)`);
|
|
5918
|
-
}
|
|
5919
|
-
}
|
|
5920
|
-
}
|
|
5921
|
-
if (result.unchanged.length > 0) {
|
|
5922
|
-
lines.push(`Up to date: ${result.unchanged.join(", ")}`);
|
|
5923
|
-
}
|
|
5924
|
-
if (result.errors.length > 0) {
|
|
5925
|
-
for (const e of result.errors) {
|
|
5926
|
-
lines.push(` \u2717 ${e.name}: ${e.error}`);
|
|
5927
|
-
}
|
|
5928
|
-
}
|
|
5929
|
-
if (lines.length === 0) {
|
|
5930
|
-
return { message: "No installed skills to update." };
|
|
5931
|
-
}
|
|
5932
|
-
return { message: lines.join("\n") };
|
|
5933
|
-
} catch (err) {
|
|
5934
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5935
|
-
return { message: `\u2717 Update failed: ${msg}` };
|
|
5936
|
-
}
|
|
5937
|
-
}
|
|
5938
|
-
};
|
|
5939
|
-
}
|
|
5940
|
-
function buildSkillUninstallCommand(opts) {
|
|
5941
|
-
return {
|
|
5942
|
-
name: "skill-uninstall",
|
|
5943
|
-
description: "Remove an installed skill.",
|
|
5944
|
-
argsHint: "<name> [--global]",
|
|
5945
|
-
help: [
|
|
5946
|
-
"\u2554\u2550\u2550\u2550 Skill Uninstall \u2550\u2550\u2550\u2557",
|
|
5947
|
-
"",
|
|
5948
|
-
"Remove an installed skill and its files.",
|
|
5949
|
-
"",
|
|
5950
|
-
"Usage:",
|
|
5951
|
-
" /skill-uninstall <name> Remove from project skills",
|
|
5952
|
-
" /skill-uninstall <name> --global Remove from user-global skills"
|
|
5953
|
-
].join("\n"),
|
|
5954
|
-
async run(args, ctx) {
|
|
5955
|
-
const parts = args.trim().split(/\s+/);
|
|
5956
|
-
const name = parts.find((p) => !p.startsWith("--"));
|
|
5957
|
-
const isGlobal = parts.includes("--global");
|
|
5958
|
-
if (!name) {
|
|
5959
|
-
const installer2 = makeInstaller(opts, ctx.projectRoot);
|
|
5960
|
-
const installed = await installer2.listInstalled();
|
|
5961
|
-
if (installed.length === 0) {
|
|
5962
|
-
return { message: "No installed skills found." };
|
|
5963
|
-
}
|
|
5964
|
-
const scope = isGlobal ? "user" : "project";
|
|
5965
|
-
const filtered = installed.filter((s) => s.scope === scope);
|
|
5966
|
-
if (filtered.length === 0) {
|
|
5967
|
-
return { message: `No installed skills found (${scope} scope).` };
|
|
5968
|
-
}
|
|
5969
|
-
const lines = [`Installed skills (${scope}):`];
|
|
5970
|
-
for (const s of filtered) {
|
|
5971
|
-
lines.push(` ${s.name} ${s.source}@${s.ref} (${s.installedAt.slice(0, 10)})`);
|
|
5972
|
-
}
|
|
5973
|
-
lines.push("", "Use /skill-uninstall <name> to remove.");
|
|
5974
|
-
return { message: lines.join("\n") };
|
|
5975
|
-
}
|
|
5976
|
-
const installer = makeInstaller(opts, ctx.projectRoot);
|
|
5977
|
-
try {
|
|
5978
|
-
await installer.uninstall(name, { global: isGlobal });
|
|
5979
|
-
return { message: `\u2713 Skill "${name}" uninstalled.` };
|
|
5980
|
-
} catch (err) {
|
|
5981
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5982
|
-
return { message: `\u2717 Uninstall failed: ${msg}` };
|
|
5983
|
-
}
|
|
5984
|
-
}
|
|
5985
|
-
};
|
|
5986
|
-
}
|
|
5987
5152
|
function getStore(opts) {
|
|
5153
|
+
if (!opts.paths) throw new Error("PhaseStore not available \u2014 paths not configured.");
|
|
5988
5154
|
return new PhaseStore({ baseDir: opts.paths.projectAutophase });
|
|
5989
5155
|
}
|
|
5990
5156
|
function formatProgress(p) {
|
|
@@ -6022,7 +5188,7 @@ function formatPhaseList(graph) {
|
|
|
6022
5188
|
}
|
|
6023
5189
|
async function gatherProjectContext2(projectRoot) {
|
|
6024
5190
|
try {
|
|
6025
|
-
const raw = await fsp3.readFile(
|
|
5191
|
+
const raw = await fsp3.readFile(path24.join(projectRoot, "package.json"), "utf8");
|
|
6026
5192
|
const pkg = JSON.parse(raw);
|
|
6027
5193
|
const parts = [
|
|
6028
5194
|
`Project: ${String(pkg.name ?? "unknown")}`,
|
|
@@ -6185,127 +5351,7 @@ function buildWorktreeCommand(opts) {
|
|
|
6185
5351
|
}
|
|
6186
5352
|
};
|
|
6187
5353
|
}
|
|
6188
|
-
async function
|
|
6189
|
-
for (; ; ) {
|
|
6190
|
-
const config = deps.configStore.get();
|
|
6191
|
-
renderSettingsTopMenu(deps.renderer, config);
|
|
6192
|
-
const choice = (await deps.reader.readLine(`
|
|
6193
|
-
${color.amber("?")} Pick setting to edit: `)).trim().toLowerCase();
|
|
6194
|
-
if (!choice || choice === "q" || choice === "quit" || choice === "exit") {
|
|
6195
|
-
deps.renderer.write(color.dim("Done.\n"));
|
|
6196
|
-
return 0;
|
|
6197
|
-
}
|
|
6198
|
-
switch (choice) {
|
|
6199
|
-
case "1":
|
|
6200
|
-
await editAutoProceedDelay(deps);
|
|
6201
|
-
break;
|
|
6202
|
-
case "2":
|
|
6203
|
-
await editDefaultAutonomy(deps);
|
|
6204
|
-
break;
|
|
6205
|
-
case "d":
|
|
6206
|
-
await showDefaults(deps);
|
|
6207
|
-
break;
|
|
6208
|
-
default:
|
|
6209
|
-
deps.renderer.writeError(`Unknown selection: "${choice}". Try 1, 2, or q to quit.`);
|
|
6210
|
-
}
|
|
6211
|
-
}
|
|
6212
|
-
}
|
|
6213
|
-
function renderSettingsTopMenu(renderer, config) {
|
|
6214
|
-
const delay = config.autonomy?.autoProceedDelayMs ?? 45e3;
|
|
6215
|
-
const defMode = config.autonomy?.defaultMode ?? "off";
|
|
6216
|
-
renderer.write(`
|
|
6217
|
-
${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}
|
|
6218
|
-
|
|
6219
|
-
`);
|
|
6220
|
-
renderer.write(` ${color.bold("1.")} auto-proceed delay: ${color.cyan(formatDelay(delay))} (in auto mode, wait before continuing)
|
|
6221
|
-
`);
|
|
6222
|
-
renderer.write(` ${color.bold("2.")} default autonomy mode: ${color.cyan(defMode)}
|
|
6223
|
-
`);
|
|
6224
|
-
renderer.write(`
|
|
6225
|
-
${color.dim("Actions:")}
|
|
6226
|
-
`);
|
|
6227
|
-
renderer.write(` ${color.bold("1")} Edit auto-proceed delay
|
|
6228
|
-
`);
|
|
6229
|
-
renderer.write(` ${color.bold("2")} Edit default autonomy mode
|
|
6230
|
-
`);
|
|
6231
|
-
renderer.write(` ${color.bold("d")} Show all defaults
|
|
6232
|
-
`);
|
|
6233
|
-
renderer.write(` ${color.bold("q")} Quit
|
|
6234
|
-
`);
|
|
6235
|
-
}
|
|
6236
|
-
async function editAutoProceedDelay(deps) {
|
|
6237
|
-
deps.renderer.write(`
|
|
6238
|
-
${color.bold("Auto-proceed delay")} ${color.dim("\u2014 wait time before auto-continuing in auto mode")}
|
|
6239
|
-
`);
|
|
6240
|
-
deps.renderer.write(color.dim(` Current: ${formatDelay(deps.configStore.get().autonomy?.autoProceedDelayMs ?? 45e3)}
|
|
6241
|
-
`));
|
|
6242
|
-
deps.renderer.write(color.dim(` Enter value in SECONDS (e.g. 30 for 30 seconds, 0 to disable)
|
|
6243
|
-
`));
|
|
6244
|
-
const raw = (await deps.reader.readLine(` ${color.amber("?")} Delay (seconds): `)).trim();
|
|
6245
|
-
if (!raw || raw === "q") return;
|
|
6246
|
-
const seconds = Number.parseFloat(raw);
|
|
6247
|
-
if (Number.isNaN(seconds) || seconds < 0) {
|
|
6248
|
-
deps.renderer.writeError(`Invalid number: "${raw}"`);
|
|
6249
|
-
return;
|
|
6250
|
-
}
|
|
6251
|
-
const ms = Math.round(seconds * 1e3);
|
|
6252
|
-
await mutateAutonomyConfig(deps, (autonomy) => {
|
|
6253
|
-
autonomy.autoProceedDelayMs = ms;
|
|
6254
|
-
});
|
|
6255
|
-
deps.renderer.write(` ${color.green("\u2713")} auto-proceed delay \u2192 ${formatDelay(ms)}
|
|
6256
|
-
`);
|
|
6257
|
-
}
|
|
6258
|
-
async function editDefaultAutonomy(deps) {
|
|
6259
|
-
deps.renderer.write(`
|
|
6260
|
-
${color.bold("Default Autonomy Mode")}
|
|
6261
|
-
|
|
6262
|
-
`);
|
|
6263
|
-
deps.renderer.write(` ${color.bold("1.")} off \u2014 agent stops after each turn (normal)
|
|
6264
|
-
`);
|
|
6265
|
-
deps.renderer.write(` ${color.bold("2.")} suggest \u2014 shows next-step suggestions
|
|
6266
|
-
`);
|
|
6267
|
-
deps.renderer.write(` ${color.bold("3.")} auto \u2014 self-driving, agent continues automatically
|
|
6268
|
-
`);
|
|
6269
|
-
deps.renderer.write(` ${color.bold("q")} Quit without changing
|
|
6270
|
-
`);
|
|
6271
|
-
const raw = (await deps.reader.readLine(` ${color.amber("?")} Default mode: `)).trim().toLowerCase();
|
|
6272
|
-
if (!raw || raw === "q") return;
|
|
6273
|
-
const modes = ["off", "suggest", "auto"];
|
|
6274
|
-
let selected = null;
|
|
6275
|
-
if (raw === "1") selected = "off";
|
|
6276
|
-
else if (raw === "2") selected = "suggest";
|
|
6277
|
-
else if (raw === "3") selected = "auto";
|
|
6278
|
-
else if (modes.includes(raw)) selected = raw;
|
|
6279
|
-
if (!selected) {
|
|
6280
|
-
deps.renderer.writeError(`Invalid mode: "${raw}". Use off, suggest, or auto.`);
|
|
6281
|
-
return;
|
|
6282
|
-
}
|
|
6283
|
-
await mutateAutonomyConfig(deps, (autonomy) => {
|
|
6284
|
-
autonomy.defaultMode = selected;
|
|
6285
|
-
});
|
|
6286
|
-
deps.renderer.write(` ${color.green("\u2713")} default autonomy \u2192 ${color.bold(selected)}
|
|
6287
|
-
`);
|
|
6288
|
-
}
|
|
6289
|
-
async function showDefaults(deps) {
|
|
6290
|
-
deps.renderer.write(`
|
|
6291
|
-
${color.bold("Default Values")}
|
|
6292
|
-
|
|
6293
|
-
`);
|
|
6294
|
-
deps.renderer.write(` auto-proceed delay: ${color.cyan("45s")} (WRONGSTACK_AUTO_PROCEED_DELAY_MS env)
|
|
6295
|
-
`);
|
|
6296
|
-
deps.renderer.write(` default autonomy mode: ${color.cyan("off")}
|
|
6297
|
-
`);
|
|
6298
|
-
deps.renderer.write(` iteration timeout: ${color.cyan("5 min")}
|
|
6299
|
-
`);
|
|
6300
|
-
deps.renderer.write(` session timeout: ${color.cyan("30 min")}
|
|
6301
|
-
`);
|
|
6302
|
-
deps.renderer.write(` max iterations: ${color.cyan("100")}
|
|
6303
|
-
`);
|
|
6304
|
-
deps.renderer.write(`
|
|
6305
|
-
${color.dim(" Press Enter to continue...")}`);
|
|
6306
|
-
await deps.reader.readLine("");
|
|
6307
|
-
}
|
|
6308
|
-
async function mutateAutonomyConfig(deps, mutator) {
|
|
5354
|
+
async function persistAutonomySetting(deps, mutator) {
|
|
6309
5355
|
let raw;
|
|
6310
5356
|
let fileExists = true;
|
|
6311
5357
|
try {
|
|
@@ -6334,11 +5380,6 @@ async function mutateAutonomyConfig(deps, mutator) {
|
|
|
6334
5380
|
await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
6335
5381
|
deps.configStore.update({ autonomy: decrypted.autonomy });
|
|
6336
5382
|
}
|
|
6337
|
-
function formatDelay(ms) {
|
|
6338
|
-
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
6339
|
-
if (ms === 0) return "disabled";
|
|
6340
|
-
return `${Math.round(ms / 1e3)}s`;
|
|
6341
|
-
}
|
|
6342
5383
|
|
|
6343
5384
|
// src/slash-commands/settings.ts
|
|
6344
5385
|
var noOpVault = {
|
|
@@ -6346,42 +5387,106 @@ var noOpVault = {
|
|
|
6346
5387
|
decrypt: (v) => v,
|
|
6347
5388
|
isEncrypted: () => false
|
|
6348
5389
|
};
|
|
5390
|
+
function formatDelay(ms) {
|
|
5391
|
+
if (ms >= 6e4) return `${Math.round(ms / 6e4)}m`;
|
|
5392
|
+
if (ms === 0) return "disabled";
|
|
5393
|
+
return `${Math.round(ms / 1e3)}s`;
|
|
5394
|
+
}
|
|
6349
5395
|
function buildSettingsCommand(opts) {
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
5396
|
+
const help = [
|
|
5397
|
+
"Usage:",
|
|
5398
|
+
" /settings Show current settings",
|
|
5399
|
+
" /settings delay <seconds> Auto-proceed delay in auto mode (0 disables)",
|
|
5400
|
+
" /settings mode <off|suggest|auto> Default autonomy mode at startup",
|
|
5401
|
+
" /settings defaults Show built-in default values",
|
|
5402
|
+
"",
|
|
5403
|
+
"Settings are persisted to ~/.wrongstack/config.json."
|
|
5404
|
+
].join("\n");
|
|
5405
|
+
function currentView() {
|
|
5406
|
+
const autonomy = opts.configStore.get().autonomy;
|
|
5407
|
+
const delay = autonomy?.autoProceedDelayMs ?? 45e3;
|
|
5408
|
+
const mode = autonomy?.defaultMode ?? "off";
|
|
5409
|
+
return [
|
|
5410
|
+
`${color.bold("WrongStack")} ${color.dim("\u2014 Settings")}`,
|
|
6356
5411
|
"",
|
|
6357
|
-
"
|
|
6358
|
-
|
|
6359
|
-
" default autonomy mode \u2014 startup autonomy mode",
|
|
5412
|
+
` auto-proceed delay: ${color.cyan(formatDelay(delay))} ${color.dim("change: /settings delay <seconds>")}`,
|
|
5413
|
+
` default autonomy mode: ${color.cyan(mode)} ${color.dim("change: /settings mode off|suggest|auto")}`,
|
|
6360
5414
|
"",
|
|
6361
|
-
"
|
|
6362
|
-
].join("\n")
|
|
5415
|
+
color.dim(" Persisted to ~/.wrongstack/config.json \xB7 /settings help for more")
|
|
5416
|
+
].join("\n");
|
|
5417
|
+
}
|
|
5418
|
+
return {
|
|
5419
|
+
name: "settings",
|
|
5420
|
+
description: "View or change settings (auto-proceed delay, default autonomy mode).",
|
|
5421
|
+
help,
|
|
6363
5422
|
async run(args) {
|
|
6364
|
-
const
|
|
6365
|
-
|
|
5423
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
5424
|
+
const sub = (parts[0] ?? "").toLowerCase();
|
|
5425
|
+
if (sub === "help" || sub === "--help") {
|
|
6366
5426
|
return { message: this.help ?? "" };
|
|
6367
5427
|
}
|
|
6368
5428
|
if (!opts.configStore || !opts.paths) {
|
|
6369
5429
|
return { message: `${color.red("Error")} config store not available.` };
|
|
6370
5430
|
}
|
|
6371
|
-
if (!
|
|
6372
|
-
return { message:
|
|
5431
|
+
if (!sub) {
|
|
5432
|
+
return { message: currentView() };
|
|
6373
5433
|
}
|
|
5434
|
+
if (sub === "defaults") {
|
|
5435
|
+
return {
|
|
5436
|
+
message: [
|
|
5437
|
+
`${color.bold("Default Values")}`,
|
|
5438
|
+
"",
|
|
5439
|
+
` auto-proceed delay: ${color.cyan("45s")} ${color.dim("(WRONGSTACK_AUTO_PROCEED_DELAY_MS env)")}`,
|
|
5440
|
+
` default autonomy mode: ${color.cyan("off")}`,
|
|
5441
|
+
` iteration timeout: ${color.cyan("5 min")}`,
|
|
5442
|
+
` session timeout: ${color.cyan("30 min")}`,
|
|
5443
|
+
` max iterations: ${color.cyan("100")}`
|
|
5444
|
+
].join("\n")
|
|
5445
|
+
};
|
|
5446
|
+
}
|
|
5447
|
+
const persistDeps = {
|
|
5448
|
+
configStore: opts.configStore,
|
|
5449
|
+
globalConfigPath: opts.paths.globalConfig,
|
|
5450
|
+
vault: noOpVault
|
|
5451
|
+
};
|
|
6374
5452
|
try {
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
5453
|
+
if (sub === "delay") {
|
|
5454
|
+
const raw = parts[1];
|
|
5455
|
+
if (raw === void 0) {
|
|
5456
|
+
return {
|
|
5457
|
+
message: `${color.amber("Usage:")} /settings delay <seconds> ${color.dim("(0 disables)")}`
|
|
5458
|
+
};
|
|
5459
|
+
}
|
|
5460
|
+
const seconds = Number.parseFloat(raw);
|
|
5461
|
+
if (Number.isNaN(seconds) || seconds < 0) {
|
|
5462
|
+
return {
|
|
5463
|
+
message: `${color.red("Invalid number")}: "${raw}". Enter seconds, e.g. /settings delay 30`
|
|
5464
|
+
};
|
|
5465
|
+
}
|
|
5466
|
+
const ms = Math.round(seconds * 1e3);
|
|
5467
|
+
await persistAutonomySetting(persistDeps, (autonomy) => {
|
|
5468
|
+
autonomy.autoProceedDelayMs = ms;
|
|
5469
|
+
});
|
|
5470
|
+
return { message: `${color.green("\u2713")} auto-proceed delay \u2192 ${formatDelay(ms)}` };
|
|
5471
|
+
}
|
|
5472
|
+
if (sub === "mode") {
|
|
5473
|
+
const raw = (parts[1] ?? "").toLowerCase();
|
|
5474
|
+
const modes = ["off", "suggest", "auto"];
|
|
5475
|
+
if (!modes.includes(raw)) {
|
|
5476
|
+
return { message: `${color.amber("Usage:")} /settings mode off|suggest|auto` };
|
|
5477
|
+
}
|
|
5478
|
+
await persistAutonomySetting(persistDeps, (autonomy) => {
|
|
5479
|
+
autonomy.defaultMode = raw;
|
|
5480
|
+
});
|
|
5481
|
+
return { message: `${color.green("\u2713")} default autonomy \u2192 ${color.bold(raw)}` };
|
|
5482
|
+
}
|
|
5483
|
+
return {
|
|
5484
|
+
message: `${color.red("Unknown setting")} "${sub}". Try ${color.dim("/settings")}, ${color.dim("/settings delay <s>")}, or ${color.dim("/settings mode <m>")}.`
|
|
5485
|
+
};
|
|
6383
5486
|
} catch (err) {
|
|
6384
|
-
return {
|
|
5487
|
+
return {
|
|
5488
|
+
message: `${color.red("Settings error")}: ${err instanceof Error ? err.message : String(err)}`
|
|
5489
|
+
};
|
|
6385
5490
|
}
|
|
6386
5491
|
}
|
|
6387
5492
|
};
|
|
@@ -6396,11 +5501,6 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
6396
5501
|
buildCompactCommand(opts),
|
|
6397
5502
|
buildContextCommand(opts),
|
|
6398
5503
|
buildToolsCommand(opts),
|
|
6399
|
-
buildSkillCommand(opts),
|
|
6400
|
-
buildSkillGeneratorCommand(opts),
|
|
6401
|
-
buildSkillInstallCommand(opts),
|
|
6402
|
-
buildSkillUpdateCommand(opts),
|
|
6403
|
-
buildSkillUninstallCommand(opts),
|
|
6404
5504
|
buildPluginCommand(opts),
|
|
6405
5505
|
buildMcpSlashCommand(opts),
|
|
6406
5506
|
buildDiagCommand(opts),
|
|
@@ -6409,11 +5509,8 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
6409
5509
|
buildAgentsCommand(opts),
|
|
6410
5510
|
buildDirectorCommand(opts),
|
|
6411
5511
|
buildFleetCommand(opts),
|
|
6412
|
-
buildMetricsCommand(opts),
|
|
6413
|
-
buildHealthCommand(opts),
|
|
6414
5512
|
buildMemoryCommand(opts),
|
|
6415
5513
|
buildTodosCommand(opts),
|
|
6416
|
-
buildPlanCommand(opts),
|
|
6417
5514
|
buildSddCommand(opts),
|
|
6418
5515
|
buildSaveCommand(opts),
|
|
6419
5516
|
buildLoadCommand(opts),
|
|
@@ -6423,10 +5520,6 @@ function buildBuiltinSlashCommands(opts) {
|
|
|
6423
5520
|
buildBtwCommand(opts),
|
|
6424
5521
|
buildModeCommand(opts),
|
|
6425
5522
|
buildExitCommand(opts),
|
|
6426
|
-
buildCommitCommand(),
|
|
6427
|
-
buildGitcheckCommand(),
|
|
6428
|
-
buildPushCommand(),
|
|
6429
|
-
buildSecurityCommand(opts),
|
|
6430
5523
|
buildFixCommand(opts),
|
|
6431
5524
|
buildAutoPhaseCommand(opts),
|
|
6432
5525
|
buildWorktreeCommand(opts),
|
|
@@ -6458,13 +5551,13 @@ var MANIFESTS = [
|
|
|
6458
5551
|
];
|
|
6459
5552
|
async function detectProjectKind(projectRoot) {
|
|
6460
5553
|
try {
|
|
6461
|
-
await fsp3.access(
|
|
5554
|
+
await fsp3.access(path24.join(projectRoot, ".wrongstack", "AGENTS.md"));
|
|
6462
5555
|
return "initialized";
|
|
6463
5556
|
} catch {
|
|
6464
5557
|
}
|
|
6465
5558
|
for (const m of MANIFESTS) {
|
|
6466
5559
|
try {
|
|
6467
|
-
await fsp3.access(
|
|
5560
|
+
await fsp3.access(path24.join(projectRoot, m));
|
|
6468
5561
|
return "project";
|
|
6469
5562
|
} catch {
|
|
6470
5563
|
}
|
|
@@ -6472,8 +5565,8 @@ async function detectProjectKind(projectRoot) {
|
|
|
6472
5565
|
return "empty";
|
|
6473
5566
|
}
|
|
6474
5567
|
async function scaffoldAgentsMd(projectRoot) {
|
|
6475
|
-
const dir =
|
|
6476
|
-
const file =
|
|
5568
|
+
const dir = path24.join(projectRoot, ".wrongstack");
|
|
5569
|
+
const file = path24.join(dir, "AGENTS.md");
|
|
6477
5570
|
const facts = await detectProjectFacts(projectRoot);
|
|
6478
5571
|
const body = renderAgentsTemplate(facts);
|
|
6479
5572
|
await fsp3.mkdir(dir, { recursive: true });
|
|
@@ -6486,7 +5579,7 @@ async function runProjectCheck(opts) {
|
|
|
6486
5579
|
if (kind === "initialized") {
|
|
6487
5580
|
renderer.write(
|
|
6488
5581
|
`
|
|
6489
|
-
${color.green("\u2713")} Project initialized ${color.dim(`(${
|
|
5582
|
+
${color.green("\u2713")} Project initialized ${color.dim(`(${path24.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
6490
5583
|
`
|
|
6491
5584
|
);
|
|
6492
5585
|
return true;
|
|
@@ -6517,7 +5610,7 @@ async function runProjectCheck(opts) {
|
|
|
6517
5610
|
}
|
|
6518
5611
|
return true;
|
|
6519
5612
|
}
|
|
6520
|
-
const gitDir =
|
|
5613
|
+
const gitDir = path24.join(projectRoot, ".git");
|
|
6521
5614
|
let hasGit = false;
|
|
6522
5615
|
try {
|
|
6523
5616
|
await fsp3.access(gitDir);
|
|
@@ -6539,9 +5632,9 @@ async function runProjectCheck(opts) {
|
|
|
6539
5632
|
}
|
|
6540
5633
|
if (answer2 === "y" || answer2 === "yes") {
|
|
6541
5634
|
try {
|
|
6542
|
-
const { spawn:
|
|
5635
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
6543
5636
|
await new Promise((resolve4, reject) => {
|
|
6544
|
-
const child =
|
|
5637
|
+
const child = spawn3("git", ["init"], { cwd: projectRoot });
|
|
6545
5638
|
child.on("error", reject);
|
|
6546
5639
|
child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
|
|
6547
5640
|
});
|
|
@@ -6642,10 +5735,10 @@ async function runLaunchPrompts(opts) {
|
|
|
6642
5735
|
return { mode, yolo, director, autonomy };
|
|
6643
5736
|
}
|
|
6644
5737
|
async function bootConfig(flags) {
|
|
6645
|
-
const cwd = typeof flags["cwd"] === "string" ?
|
|
5738
|
+
const cwd = typeof flags["cwd"] === "string" ? path24.resolve(flags["cwd"]) : process.cwd();
|
|
6646
5739
|
const pathResolver = new DefaultPathResolver(cwd);
|
|
6647
5740
|
const projectRoot = pathResolver.projectRoot;
|
|
6648
|
-
const userHome =
|
|
5741
|
+
const userHome = os3.homedir();
|
|
6649
5742
|
const wpaths = resolveWstackPaths({ projectRoot, userHome });
|
|
6650
5743
|
await ensureProjectMeta(wpaths, projectRoot);
|
|
6651
5744
|
const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
|
|
@@ -6709,7 +5802,7 @@ var ReadlineInputReader = class {
|
|
|
6709
5802
|
history = [];
|
|
6710
5803
|
pending = false;
|
|
6711
5804
|
constructor(opts = {}) {
|
|
6712
|
-
this.historyFile = opts.historyFile ??
|
|
5805
|
+
this.historyFile = opts.historyFile ?? path24.join(os3.homedir(), ".wrongstack", "history");
|
|
6713
5806
|
}
|
|
6714
5807
|
async loadHistory() {
|
|
6715
5808
|
try {
|
|
@@ -6721,7 +5814,7 @@ var ReadlineInputReader = class {
|
|
|
6721
5814
|
}
|
|
6722
5815
|
async saveHistory() {
|
|
6723
5816
|
try {
|
|
6724
|
-
await fsp3.mkdir(
|
|
5817
|
+
await fsp3.mkdir(path24.dirname(this.historyFile), { recursive: true });
|
|
6725
5818
|
await fsp3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
6726
5819
|
} catch {
|
|
6727
5820
|
}
|
|
@@ -6939,7 +6032,7 @@ async function buildPickableProviders(modelsRegistry, config) {
|
|
|
6939
6032
|
}
|
|
6940
6033
|
return out;
|
|
6941
6034
|
}
|
|
6942
|
-
var defaultUidFn = () =>
|
|
6035
|
+
var defaultUidFn = () => os3__default.userInfo().uid;
|
|
6943
6036
|
async function getFileUid(filePath) {
|
|
6944
6037
|
try {
|
|
6945
6038
|
const stat4 = await fsp3.stat(filePath);
|
|
@@ -6949,7 +6042,7 @@ async function getFileUid(filePath) {
|
|
|
6949
6042
|
}
|
|
6950
6043
|
}
|
|
6951
6044
|
async function checkConfigOwnership(homeFn, uidFn = defaultUidFn) {
|
|
6952
|
-
if (
|
|
6045
|
+
if (os3__default.platform() === "win32") return true;
|
|
6953
6046
|
const cfg = configPath(homeFn);
|
|
6954
6047
|
const fileUid = await getFileUid(cfg);
|
|
6955
6048
|
if (fileUid === void 0) return true;
|
|
@@ -6966,20 +6059,20 @@ function assertSafeToDelete(filename, parentDir) {
|
|
|
6966
6059
|
if (PROTECTED_BASENAMES.has(filename)) {
|
|
6967
6060
|
throw new Error(`Refusing to delete protected file: ${filename}`);
|
|
6968
6061
|
}
|
|
6969
|
-
if (filename !==
|
|
6062
|
+
if (filename !== path24.basename(filename)) {
|
|
6970
6063
|
throw new Error(`Refusing to delete path with traversal: ${filename}`);
|
|
6971
6064
|
}
|
|
6972
6065
|
if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
|
|
6973
6066
|
throw new Error(`Refusing to delete unknown file: ${filename}`);
|
|
6974
6067
|
}
|
|
6975
|
-
const resolvedParent =
|
|
6068
|
+
const resolvedParent = path24.resolve(parentDir);
|
|
6976
6069
|
if (!resolvedParent.endsWith(".wrongstack")) {
|
|
6977
6070
|
throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
|
|
6978
6071
|
}
|
|
6979
6072
|
}
|
|
6980
6073
|
async function safeDelete(filePath) {
|
|
6981
|
-
const dir =
|
|
6982
|
-
const filename =
|
|
6074
|
+
const dir = path24.dirname(filePath);
|
|
6075
|
+
const filename = path24.basename(filePath);
|
|
6983
6076
|
try {
|
|
6984
6077
|
assertSafeToDelete(filename, dir);
|
|
6985
6078
|
await fsp3.unlink(filePath);
|
|
@@ -7022,18 +6115,18 @@ function diffSummary(oldCfg, newCfg) {
|
|
|
7022
6115
|
}
|
|
7023
6116
|
return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
|
|
7024
6117
|
}
|
|
7025
|
-
var defaultHomeDir = () =>
|
|
6118
|
+
var defaultHomeDir = () => os3__default.homedir();
|
|
7026
6119
|
function historyDir(homeFn = defaultHomeDir) {
|
|
7027
|
-
return
|
|
6120
|
+
return path24.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
7028
6121
|
}
|
|
7029
6122
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
7030
|
-
return
|
|
6123
|
+
return path24.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
7031
6124
|
}
|
|
7032
6125
|
function configPath(homeFn = defaultHomeDir) {
|
|
7033
|
-
return
|
|
6126
|
+
return path24.join(homeFn(), ".wrongstack", "config.json");
|
|
7034
6127
|
}
|
|
7035
6128
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
7036
|
-
return
|
|
6129
|
+
return path24.join(homeFn(), ".wrongstack", "config.json.last");
|
|
7037
6130
|
}
|
|
7038
6131
|
function entryId(ts) {
|
|
7039
6132
|
return ts.replace(/[:.]/g, "-").slice(0, 19);
|
|
@@ -7088,17 +6181,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
|
|
|
7088
6181
|
}
|
|
7089
6182
|
if (content !== void 0) {
|
|
7090
6183
|
try {
|
|
7091
|
-
const bakPath =
|
|
6184
|
+
const bakPath = path24.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
|
|
7092
6185
|
await atomicWrite(bakPath, content);
|
|
7093
6186
|
} catch {
|
|
7094
6187
|
}
|
|
7095
6188
|
}
|
|
7096
6189
|
try {
|
|
7097
|
-
const dir =
|
|
6190
|
+
const dir = path24.join(homeFn(), ".wrongstack");
|
|
7098
6191
|
const files = await fsp3.readdir(dir);
|
|
7099
6192
|
const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
|
|
7100
6193
|
for (const f of baks.slice(10)) {
|
|
7101
|
-
await safeDelete(
|
|
6194
|
+
await safeDelete(path24.join(dir, f));
|
|
7102
6195
|
}
|
|
7103
6196
|
} catch {
|
|
7104
6197
|
}
|
|
@@ -7116,7 +6209,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
7116
6209
|
};
|
|
7117
6210
|
try {
|
|
7118
6211
|
await fsp3.writeFile(
|
|
7119
|
-
|
|
6212
|
+
path24.join(historyDir(homeFn), `${id}.json`),
|
|
7120
6213
|
JSON.stringify(entry, null, 2),
|
|
7121
6214
|
"utf8"
|
|
7122
6215
|
);
|
|
@@ -7124,7 +6217,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
|
|
|
7124
6217
|
throw new FsError({
|
|
7125
6218
|
message: err instanceof Error ? err.message : String(err),
|
|
7126
6219
|
code: ERROR_CODES.FS_WRITE_FAILED,
|
|
7127
|
-
path:
|
|
6220
|
+
path: path24.join(historyDir(homeFn), `${id}.json`),
|
|
7128
6221
|
cause: err
|
|
7129
6222
|
});
|
|
7130
6223
|
}
|
|
@@ -7139,7 +6232,7 @@ async function listHistory(homeFn = defaultHomeDir) {
|
|
|
7139
6232
|
}
|
|
7140
6233
|
async function getHistoryEntry(id, homeFn = defaultHomeDir) {
|
|
7141
6234
|
try {
|
|
7142
|
-
const raw = await fsp3.readFile(
|
|
6235
|
+
const raw = await fsp3.readFile(path24.join(historyDir(homeFn), `${id}.json`), "utf8");
|
|
7143
6236
|
return JSON.parse(raw);
|
|
7144
6237
|
} catch {
|
|
7145
6238
|
return null;
|
|
@@ -7543,7 +6636,7 @@ function pickGroupIndex(opts) {
|
|
|
7543
6636
|
if (Number.isFinite(parsed)) current = wrap(parsed);
|
|
7544
6637
|
} catch {
|
|
7545
6638
|
}
|
|
7546
|
-
fs10.mkdirSync(
|
|
6639
|
+
fs10.mkdirSync(path24.dirname(opts.cursorFile), { recursive: true });
|
|
7547
6640
|
fs10.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
|
|
7548
6641
|
return current;
|
|
7549
6642
|
} catch {
|
|
@@ -7879,14 +6972,14 @@ function summarize(value, name) {
|
|
|
7879
6972
|
if (typeof v === "object" && v !== null) {
|
|
7880
6973
|
const o = v;
|
|
7881
6974
|
if (name === "edit") {
|
|
7882
|
-
const
|
|
6975
|
+
const path25 = typeof o["path"] === "string" ? o["path"] : "";
|
|
7883
6976
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
7884
|
-
return `${
|
|
6977
|
+
return `${path25} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
7885
6978
|
}
|
|
7886
6979
|
if (name === "write") {
|
|
7887
|
-
const
|
|
6980
|
+
const path25 = typeof o["path"] === "string" ? o["path"] : "";
|
|
7888
6981
|
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
7889
|
-
return bytes !== void 0 ? `${
|
|
6982
|
+
return bytes !== void 0 ? `${path25} ${bytes}B` : path25;
|
|
7890
6983
|
}
|
|
7891
6984
|
if (typeof o["count"] === "number") {
|
|
7892
6985
|
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
@@ -8817,7 +7910,7 @@ var diagCmd = async (_args, deps) => {
|
|
|
8817
7910
|
` modelsCache: ${deps.paths.modelsCache}`,
|
|
8818
7911
|
` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
|
|
8819
7912
|
` node: ${process.version}`,
|
|
8820
|
-
` os: ${
|
|
7913
|
+
` os: ${os3.platform()} ${os3.release()}`,
|
|
8821
7914
|
` provider: ${cfg.provider ?? "<unset>"}`,
|
|
8822
7915
|
` model: ${cfg.model ?? "<unset>"}`,
|
|
8823
7916
|
` tools: ${deps.toolRegistry?.list().length ?? 0}`,
|
|
@@ -8896,7 +7989,7 @@ var doctorCmd = async (_args, deps) => {
|
|
|
8896
7989
|
}
|
|
8897
7990
|
try {
|
|
8898
7991
|
await fsp3.mkdir(deps.paths.projectSessions, { recursive: true });
|
|
8899
|
-
const probe =
|
|
7992
|
+
const probe = path24.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
|
|
8900
7993
|
await fsp3.writeFile(probe, "");
|
|
8901
7994
|
await fsp3.unlink(probe);
|
|
8902
7995
|
checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
|
|
@@ -8999,8 +8092,8 @@ var exportCmd = async (args, deps) => {
|
|
|
8999
8092
|
return 1;
|
|
9000
8093
|
}
|
|
9001
8094
|
if (output) {
|
|
9002
|
-
await fsp3.mkdir(
|
|
9003
|
-
await fsp3.writeFile(
|
|
8095
|
+
await fsp3.mkdir(path24.dirname(path24.resolve(deps.cwd, output)), { recursive: true });
|
|
8096
|
+
await fsp3.writeFile(path24.resolve(deps.cwd, output), rendered, "utf8");
|
|
9004
8097
|
deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
|
|
9005
8098
|
`);
|
|
9006
8099
|
} else {
|
|
@@ -9073,8 +8166,8 @@ var initCmd = async (_args, deps) => {
|
|
|
9073
8166
|
const vault = new DefaultSecretVault$1({ keyFile: deps.paths.secretsKey });
|
|
9074
8167
|
const encrypted = encryptConfigSecrets(config, vault);
|
|
9075
8168
|
await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
9076
|
-
await fsp3.mkdir(
|
|
9077
|
-
const agentsFile =
|
|
8169
|
+
await fsp3.mkdir(path24.join(deps.projectRoot, ".wrongstack"), { recursive: true });
|
|
8170
|
+
const agentsFile = path24.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
|
|
9078
8171
|
const projectFacts = await detectProjectFacts(deps.projectRoot);
|
|
9079
8172
|
await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
|
|
9080
8173
|
deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
|
|
@@ -9380,7 +8473,7 @@ var usageCmd = async (_args, deps) => {
|
|
|
9380
8473
|
return 0;
|
|
9381
8474
|
};
|
|
9382
8475
|
var projectsCmd = async (_args, deps) => {
|
|
9383
|
-
const projectsRoot =
|
|
8476
|
+
const projectsRoot = path24.join(deps.paths.globalRoot, "projects");
|
|
9384
8477
|
try {
|
|
9385
8478
|
const entries = await fsp3.readdir(projectsRoot);
|
|
9386
8479
|
if (entries.length === 0) {
|
|
@@ -9390,7 +8483,7 @@ var projectsCmd = async (_args, deps) => {
|
|
|
9390
8483
|
for (const hash of entries) {
|
|
9391
8484
|
try {
|
|
9392
8485
|
const meta = JSON.parse(
|
|
9393
|
-
await fsp3.readFile(
|
|
8486
|
+
await fsp3.readFile(path24.join(projectsRoot, hash, "meta.json"), "utf8")
|
|
9394
8487
|
);
|
|
9395
8488
|
deps.renderer.write(
|
|
9396
8489
|
` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
|
|
@@ -9549,7 +8642,7 @@ async function listFleetRuns(deps) {
|
|
|
9549
8642
|
}
|
|
9550
8643
|
const runs = [];
|
|
9551
8644
|
for (const id of entries) {
|
|
9552
|
-
const runDir =
|
|
8645
|
+
const runDir = path24.join(deps.paths.projectSessions, id);
|
|
9553
8646
|
let stat4;
|
|
9554
8647
|
try {
|
|
9555
8648
|
stat4 = await fsp3.stat(runDir);
|
|
@@ -9562,17 +8655,17 @@ async function listFleetRuns(deps) {
|
|
|
9562
8655
|
let subagentCount = 0;
|
|
9563
8656
|
let subagentsDir;
|
|
9564
8657
|
try {
|
|
9565
|
-
await fsp3.access(
|
|
8658
|
+
await fsp3.access(path24.join(runDir, "fleet.json"));
|
|
9566
8659
|
manifest = true;
|
|
9567
8660
|
} catch {
|
|
9568
8661
|
}
|
|
9569
8662
|
try {
|
|
9570
|
-
await fsp3.access(
|
|
8663
|
+
await fsp3.access(path24.join(runDir, "checkpoint.json"));
|
|
9571
8664
|
checkpoint = true;
|
|
9572
8665
|
} catch {
|
|
9573
8666
|
}
|
|
9574
8667
|
try {
|
|
9575
|
-
subagentsDir =
|
|
8668
|
+
subagentsDir = path24.join(runDir, "subagents");
|
|
9576
8669
|
const files = await fsp3.readdir(subagentsDir);
|
|
9577
8670
|
subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
|
|
9578
8671
|
} catch {
|
|
@@ -9601,7 +8694,7 @@ async function listFleetRuns(deps) {
|
|
|
9601
8694
|
return 0;
|
|
9602
8695
|
}
|
|
9603
8696
|
async function showFleetRun(runId, deps) {
|
|
9604
|
-
const runDir =
|
|
8697
|
+
const runDir = path24.join(deps.paths.projectSessions, runId);
|
|
9605
8698
|
let stat4;
|
|
9606
8699
|
try {
|
|
9607
8700
|
stat4 = await fsp3.stat(runDir);
|
|
@@ -9618,7 +8711,7 @@ async function showFleetRun(runId, deps) {
|
|
|
9618
8711
|
deps.renderer.write(color.bold(`
|
|
9619
8712
|
Fleet Run: ${runId}
|
|
9620
8713
|
`) + "\n");
|
|
9621
|
-
const manifestPath =
|
|
8714
|
+
const manifestPath = path24.join(runDir, "fleet.json");
|
|
9622
8715
|
let manifestData = null;
|
|
9623
8716
|
try {
|
|
9624
8717
|
manifestData = await fsp3.readFile(manifestPath, "utf8");
|
|
@@ -9634,7 +8727,7 @@ Fleet Run: ${runId}
|
|
|
9634
8727
|
deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
|
|
9635
8728
|
`);
|
|
9636
8729
|
}
|
|
9637
|
-
const checkpointPath =
|
|
8730
|
+
const checkpointPath = path24.join(runDir, "checkpoint.json");
|
|
9638
8731
|
let checkpointData = null;
|
|
9639
8732
|
try {
|
|
9640
8733
|
checkpointData = await fsp3.readFile(checkpointPath, "utf8");
|
|
@@ -9681,7 +8774,7 @@ Fleet Run: ${runId}
|
|
|
9681
8774
|
} catch {
|
|
9682
8775
|
}
|
|
9683
8776
|
}
|
|
9684
|
-
const subagentsDir =
|
|
8777
|
+
const subagentsDir = path24.join(runDir, "subagents");
|
|
9685
8778
|
let subagentFiles = [];
|
|
9686
8779
|
try {
|
|
9687
8780
|
subagentFiles = await fsp3.readdir(subagentsDir);
|
|
@@ -9693,7 +8786,7 @@ Fleet Run: ${runId}
|
|
|
9693
8786
|
Subagent transcripts (${subagentFiles.length}):
|
|
9694
8787
|
`);
|
|
9695
8788
|
for (const f of subagentFiles.sort()) {
|
|
9696
|
-
const filePath =
|
|
8789
|
+
const filePath = path24.join(subagentsDir, f);
|
|
9697
8790
|
let size;
|
|
9698
8791
|
try {
|
|
9699
8792
|
const s = await fsp3.stat(filePath);
|
|
@@ -9710,7 +8803,7 @@ Fleet Run: ${runId}
|
|
|
9710
8803
|
${color.dim("\u25CB")} No subagent transcripts
|
|
9711
8804
|
`);
|
|
9712
8805
|
}
|
|
9713
|
-
const sharedDir =
|
|
8806
|
+
const sharedDir = path24.join(runDir, "shared");
|
|
9714
8807
|
try {
|
|
9715
8808
|
const files = await fsp3.readdir(sharedDir);
|
|
9716
8809
|
deps.renderer.write(`
|
|
@@ -9877,7 +8970,7 @@ function findSessionId(args) {
|
|
|
9877
8970
|
var rewindCmd = async (args, deps) => {
|
|
9878
8971
|
const flags = parseRewindFlags(args);
|
|
9879
8972
|
const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
|
|
9880
|
-
const sessionsDir =
|
|
8973
|
+
const sessionsDir = path24.join(wpaths.globalRoot, "sessions");
|
|
9881
8974
|
const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
|
|
9882
8975
|
let sessionId = findSessionId(args);
|
|
9883
8976
|
if (!sessionId) {
|
|
@@ -10009,7 +9102,7 @@ var skillsCmd = async (_args, deps) => {
|
|
|
10009
9102
|
};
|
|
10010
9103
|
var versionCmd = async (_args, deps) => {
|
|
10011
9104
|
deps.renderer.write(
|
|
10012
|
-
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${
|
|
9105
|
+
`WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os3.platform()})
|
|
10013
9106
|
`
|
|
10014
9107
|
);
|
|
10015
9108
|
return 0;
|
|
@@ -10089,22 +9182,22 @@ function fmtDuration(ms) {
|
|
|
10089
9182
|
const remMin = m - h * 60;
|
|
10090
9183
|
return `${h}h${remMin}m`;
|
|
10091
9184
|
}
|
|
10092
|
-
function fmtTaskResultLine(r,
|
|
9185
|
+
function fmtTaskResultLine(r, color37) {
|
|
10093
9186
|
const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
|
|
10094
9187
|
const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
|
|
10095
9188
|
const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
|
|
10096
9189
|
const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
|
|
10097
|
-
const errKindChip = errKind ?
|
|
10098
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
9190
|
+
const errKindChip = errKind ? color37.dim(` [${errKind}]`) : "";
|
|
9191
|
+
const errSnip = errMsg || errKind ? `${errKindChip}${color37.dim(errTail)}` : "";
|
|
10099
9192
|
switch (r.status) {
|
|
10100
9193
|
case "success":
|
|
10101
|
-
return { mark:
|
|
9194
|
+
return { mark: color37.green("\u2713"), stats, tail: "" };
|
|
10102
9195
|
case "timeout":
|
|
10103
|
-
return { mark:
|
|
9196
|
+
return { mark: color37.yellow("\u23F1"), stats: `${color37.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
10104
9197
|
case "stopped":
|
|
10105
|
-
return { mark:
|
|
9198
|
+
return { mark: color37.dim("\u2298"), stats: `${color37.dim("stopped")} ${stats}`, tail: errSnip };
|
|
10106
9199
|
case "failed":
|
|
10107
|
-
return { mark:
|
|
9200
|
+
return { mark: color37.red("\u2717"), stats: `${color37.red("failed")} ${stats}`, tail: errSnip };
|
|
10108
9201
|
}
|
|
10109
9202
|
}
|
|
10110
9203
|
|
|
@@ -10114,7 +9207,7 @@ function resolveBundledSkillsDir() {
|
|
|
10114
9207
|
try {
|
|
10115
9208
|
const req2 = createRequire(import.meta.url);
|
|
10116
9209
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
10117
|
-
return
|
|
9210
|
+
return path24.join(path24.dirname(corePkg), "skills");
|
|
10118
9211
|
} catch {
|
|
10119
9212
|
return void 0;
|
|
10120
9213
|
}
|
|
@@ -10276,7 +9369,7 @@ async function boot(argv) {
|
|
|
10276
9369
|
if (choices.director) flags["director"] = true;
|
|
10277
9370
|
flags["autonomy"] = choices.autonomy;
|
|
10278
9371
|
printLaunchHints(renderer, flags, {
|
|
10279
|
-
cursorFile:
|
|
9372
|
+
cursorFile: path24.join(wpaths.cacheDir, "hint-cursor")
|
|
10280
9373
|
});
|
|
10281
9374
|
}
|
|
10282
9375
|
return {
|
|
@@ -10961,9 +10054,9 @@ async function renderGoalBanner(opts) {
|
|
|
10961
10054
|
);
|
|
10962
10055
|
if (goal.journal.length > 0) {
|
|
10963
10056
|
const lastEntry = goal.journal[goal.journal.length - 1];
|
|
10964
|
-
const
|
|
10057
|
+
const statusIcon = lastEntry.status === "success" ? "\u2713" : lastEntry.status === "failure" ? "\u2717" : lastEntry.status === "aborted" ? "\u2298" : lastEntry.status === "skipped" ? "\u229D" : "\xB7";
|
|
10965
10058
|
opts.renderer.write(
|
|
10966
|
-
color.dim(` Last: ${
|
|
10059
|
+
color.dim(` Last: ${statusIcon} ${lastEntry.task} (${lastEntry.status})`) + "\n"
|
|
10967
10060
|
);
|
|
10968
10061
|
}
|
|
10969
10062
|
if (goal.engineState === "running") {
|
|
@@ -11066,6 +10159,7 @@ async function execute(deps) {
|
|
|
11066
10159
|
attachments,
|
|
11067
10160
|
tokenCounter,
|
|
11068
10161
|
config,
|
|
10162
|
+
configStore,
|
|
11069
10163
|
renderer,
|
|
11070
10164
|
reader,
|
|
11071
10165
|
session,
|
|
@@ -11265,6 +10359,30 @@ async function execute(deps) {
|
|
|
11265
10359
|
onAutonomy?.(mode);
|
|
11266
10360
|
return null;
|
|
11267
10361
|
},
|
|
10362
|
+
getSettings: () => {
|
|
10363
|
+
const autonomy = configStore.get().autonomy;
|
|
10364
|
+
const rawMode = autonomy?.defaultMode;
|
|
10365
|
+
const mode = rawMode === "suggest" || rawMode === "auto" ? rawMode : "off";
|
|
10366
|
+
return { mode, delayMs: autonomy?.autoProceedDelayMs ?? 45e3 };
|
|
10367
|
+
},
|
|
10368
|
+
async saveSettings(s) {
|
|
10369
|
+
try {
|
|
10370
|
+
await persistAutonomySetting(
|
|
10371
|
+
{
|
|
10372
|
+
configStore,
|
|
10373
|
+
globalConfigPath: wpaths.globalConfig,
|
|
10374
|
+
vault: { encrypt: (v) => v, decrypt: (v) => v, isEncrypted: () => false }
|
|
10375
|
+
},
|
|
10376
|
+
(autonomy) => {
|
|
10377
|
+
autonomy.defaultMode = s.mode;
|
|
10378
|
+
autonomy.autoProceedDelayMs = s.delayMs;
|
|
10379
|
+
}
|
|
10380
|
+
);
|
|
10381
|
+
return null;
|
|
10382
|
+
} catch (err) {
|
|
10383
|
+
return err instanceof Error ? err.message : String(err);
|
|
10384
|
+
}
|
|
10385
|
+
},
|
|
11268
10386
|
effectiveMaxContext,
|
|
11269
10387
|
// Default OFF so the terminal's native scrollback works for chat
|
|
11270
10388
|
// history out of the box (mouse wheel / Shift+PgUp). Users who hit
|
|
@@ -11345,7 +10463,7 @@ async function execute(deps) {
|
|
|
11345
10463
|
supportsVision,
|
|
11346
10464
|
attachments,
|
|
11347
10465
|
effectiveMaxContext,
|
|
11348
|
-
projectName:
|
|
10466
|
+
projectName: path24.basename(projectRoot) || void 0,
|
|
11349
10467
|
projectRoot,
|
|
11350
10468
|
getAutonomy,
|
|
11351
10469
|
onAutonomy,
|
|
@@ -11369,7 +10487,7 @@ async function execute(deps) {
|
|
|
11369
10487
|
supportsVision,
|
|
11370
10488
|
attachments,
|
|
11371
10489
|
effectiveMaxContext,
|
|
11372
|
-
projectName:
|
|
10490
|
+
projectName: path24.basename(projectRoot) || void 0,
|
|
11373
10491
|
getAutonomy,
|
|
11374
10492
|
onAutonomy,
|
|
11375
10493
|
getEternalEngine,
|
|
@@ -11473,7 +10591,7 @@ var MultiAgentHost = class {
|
|
|
11473
10591
|
doneCondition: { type: "all_tasks_done" },
|
|
11474
10592
|
maxConcurrent: this.opts.maxConcurrent ?? 4
|
|
11475
10593
|
};
|
|
11476
|
-
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ?
|
|
10594
|
+
const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path24.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
|
|
11477
10595
|
this.director = new Director({
|
|
11478
10596
|
config: coordinatorConfig,
|
|
11479
10597
|
manifestPath: this.opts.manifestPath,
|
|
@@ -11719,7 +10837,7 @@ var MultiAgentHost = class {
|
|
|
11719
10837
|
model: opts?.model,
|
|
11720
10838
|
tools: opts?.tools
|
|
11721
10839
|
};
|
|
11722
|
-
const transcriptPath = this.sessionFactory ?
|
|
10840
|
+
const transcriptPath = this.sessionFactory ? path24.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
|
|
11723
10841
|
const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
|
|
11724
10842
|
this.fleetManager?.addPendingTask(taskId, subagentId, description);
|
|
11725
10843
|
this.deps.events.emit("subagent.spawned", {
|
|
@@ -11862,16 +10980,16 @@ var MultiAgentHost = class {
|
|
|
11862
10980
|
if (this.director) return this.director;
|
|
11863
10981
|
this.opts.directorMode = true;
|
|
11864
10982
|
if (this.opts.fleetRoot && !this.opts.manifestPath) {
|
|
11865
|
-
this.opts.manifestPath =
|
|
10983
|
+
this.opts.manifestPath = path24.join(this.opts.fleetRoot, "fleet.json");
|
|
11866
10984
|
}
|
|
11867
10985
|
if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
|
|
11868
|
-
this.opts.sharedScratchpadPath =
|
|
10986
|
+
this.opts.sharedScratchpadPath = path24.join(this.opts.fleetRoot, "shared");
|
|
11869
10987
|
}
|
|
11870
10988
|
if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
|
|
11871
|
-
this.opts.sessionsRoot =
|
|
10989
|
+
this.opts.sessionsRoot = path24.join(this.opts.fleetRoot, "subagents");
|
|
11872
10990
|
}
|
|
11873
10991
|
if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
|
|
11874
|
-
this.opts.stateCheckpointPath =
|
|
10992
|
+
this.opts.stateCheckpointPath = path24.join(this.opts.fleetRoot, "director-state.json");
|
|
11875
10993
|
}
|
|
11876
10994
|
await this.ensureDirector();
|
|
11877
10995
|
return this.director ?? null;
|
|
@@ -12037,11 +11155,11 @@ var SessionStats = class {
|
|
|
12037
11155
|
if (e.name === "bash") this.bashCommands++;
|
|
12038
11156
|
else if (e.name === "fetch") this.fetches++;
|
|
12039
11157
|
if (!e.ok) return;
|
|
12040
|
-
const
|
|
12041
|
-
if (e.name === "read" &&
|
|
12042
|
-
else if (e.name === "edit" &&
|
|
12043
|
-
else if (e.name === "write" &&
|
|
12044
|
-
this.writtenPaths.add(
|
|
11158
|
+
const path25 = typeof input?.path === "string" ? input.path : void 0;
|
|
11159
|
+
if (e.name === "read" && path25) this.readPaths.add(path25);
|
|
11160
|
+
else if (e.name === "edit" && path25) this.editedPaths.add(path25);
|
|
11161
|
+
else if (e.name === "write" && path25) {
|
|
11162
|
+
this.writtenPaths.add(path25);
|
|
12045
11163
|
const content = typeof input?.content === "string" ? input.content : "";
|
|
12046
11164
|
this.bytesWritten += Buffer.byteLength(content, "utf8");
|
|
12047
11165
|
}
|
|
@@ -12160,7 +11278,7 @@ function gitText(args, cwd) {
|
|
|
12160
11278
|
child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
|
|
12161
11279
|
});
|
|
12162
11280
|
}
|
|
12163
|
-
async function
|
|
11281
|
+
async function isGitRepo(cwd) {
|
|
12164
11282
|
const { code, out } = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
|
|
12165
11283
|
return code === 0 && out.trim() === "true";
|
|
12166
11284
|
}
|
|
@@ -12237,7 +11355,7 @@ function createAutoPhaseHost(deps) {
|
|
|
12237
11355
|
await persist(graph);
|
|
12238
11356
|
const worktreesEnabled = deps.worktrees !== false && process.env["WRONGSTACK_AUTOPHASE_WORKTREES"] !== "0";
|
|
12239
11357
|
let worktrees;
|
|
12240
|
-
if (worktreesEnabled && await
|
|
11358
|
+
if (worktreesEnabled && await isGitRepo(deps.projectRoot)) {
|
|
12241
11359
|
worktrees = new WorktreeManager({ projectRoot: deps.projectRoot, events: deps.events });
|
|
12242
11360
|
log(`\u{1F33F} Worktree isolation on \u2014 up to ${deps.maxConcurrentPhases ?? WORKTREE_PHASE_CONCURRENCY} phases run in parallel.`);
|
|
12243
11361
|
}
|
|
@@ -12317,7 +11435,7 @@ function createAutoPhaseHost(deps) {
|
|
|
12317
11435
|
},
|
|
12318
11436
|
async onWorktree(action, target) {
|
|
12319
11437
|
const root = deps.projectRoot;
|
|
12320
|
-
if (!await
|
|
11438
|
+
if (!await isGitRepo(root)) return "\u26A0 Not a git repository \u2014 worktrees unavailable.";
|
|
12321
11439
|
switch (action) {
|
|
12322
11440
|
case "list": {
|
|
12323
11441
|
const { out } = await gitText(["worktree", "list"], root);
|
|
@@ -12541,7 +11659,7 @@ function setupMetrics(params) {
|
|
|
12541
11659
|
const dumpMetrics = () => {
|
|
12542
11660
|
if (!metricsSink) return;
|
|
12543
11661
|
try {
|
|
12544
|
-
const out =
|
|
11662
|
+
const out = path24.join(wpaths.projectSessions, "metrics.json");
|
|
12545
11663
|
const snap = metricsSink.snapshot();
|
|
12546
11664
|
writeFileSync(out, JSON.stringify(snap, null, 2));
|
|
12547
11665
|
} catch {
|
|
@@ -12576,6 +11694,36 @@ function createApi(ownerName, base) {
|
|
|
12576
11694
|
}
|
|
12577
11695
|
|
|
12578
11696
|
// src/wiring/plugins.ts
|
|
11697
|
+
var BUILTIN_PLUGIN_FACTORIES = [
|
|
11698
|
+
async () => {
|
|
11699
|
+
const { createPromptsPlugin } = await import('@wrongstack/core');
|
|
11700
|
+
return createPromptsPlugin();
|
|
11701
|
+
},
|
|
11702
|
+
async () => {
|
|
11703
|
+
const { createSyncPlugin } = await import('@wrongstack/core');
|
|
11704
|
+
return createSyncPlugin();
|
|
11705
|
+
},
|
|
11706
|
+
async () => {
|
|
11707
|
+
const { createGitPlugin } = await import('@wrongstack/core');
|
|
11708
|
+
return createGitPlugin();
|
|
11709
|
+
},
|
|
11710
|
+
async () => {
|
|
11711
|
+
const { createObservabilityPlugin } = await import('@wrongstack/core');
|
|
11712
|
+
return createObservabilityPlugin();
|
|
11713
|
+
},
|
|
11714
|
+
async () => {
|
|
11715
|
+
const { createSecurityPlugin } = await import('@wrongstack/core');
|
|
11716
|
+
return createSecurityPlugin();
|
|
11717
|
+
},
|
|
11718
|
+
async () => {
|
|
11719
|
+
const { createSkillsPlugin } = await import('@wrongstack/core');
|
|
11720
|
+
return createSkillsPlugin();
|
|
11721
|
+
},
|
|
11722
|
+
async () => {
|
|
11723
|
+
const { createPlanPlugin } = await import('@wrongstack/core');
|
|
11724
|
+
return createPlanPlugin();
|
|
11725
|
+
}
|
|
11726
|
+
];
|
|
12579
11727
|
async function setupPlugins(params) {
|
|
12580
11728
|
const {
|
|
12581
11729
|
config,
|
|
@@ -12589,28 +11737,65 @@ async function setupPlugins(params) {
|
|
|
12589
11737
|
agent,
|
|
12590
11738
|
sessionWriter,
|
|
12591
11739
|
metricsSink,
|
|
11740
|
+
healthRegistry,
|
|
11741
|
+
skillLoader,
|
|
12592
11742
|
configStore,
|
|
12593
|
-
pipelines
|
|
11743
|
+
pipelines,
|
|
11744
|
+
paths
|
|
12594
11745
|
} = params;
|
|
12595
|
-
|
|
12596
|
-
const
|
|
12597
|
-
|
|
12598
|
-
|
|
12599
|
-
|
|
12600
|
-
|
|
12601
|
-
|
|
12602
|
-
|
|
12603
|
-
|
|
12604
|
-
|
|
11746
|
+
const builtinPlugins = [];
|
|
11747
|
+
const disabledBuiltins = new Set(
|
|
11748
|
+
(config.plugins ?? []).filter(
|
|
11749
|
+
(p) => typeof p === "object" && p.enabled === false
|
|
11750
|
+
).map((p) => p.name)
|
|
11751
|
+
);
|
|
11752
|
+
if (paths && config.features?.plugins !== false) {
|
|
11753
|
+
for (const factory of BUILTIN_PLUGIN_FACTORIES) {
|
|
11754
|
+
try {
|
|
11755
|
+
const plugin = await factory();
|
|
11756
|
+
if (!plugin) continue;
|
|
11757
|
+
if (disabledBuiltins.has(plugin.name)) {
|
|
11758
|
+
log.info(`[setupPlugins] built-in plugin "${plugin.name}" disabled by config`);
|
|
11759
|
+
continue;
|
|
11760
|
+
}
|
|
11761
|
+
builtinPlugins.push(plugin);
|
|
11762
|
+
} catch (err) {
|
|
11763
|
+
log.warn("[setupPlugins] builtin plugin failed to load:", err);
|
|
11764
|
+
}
|
|
12605
11765
|
}
|
|
12606
11766
|
}
|
|
12607
|
-
|
|
11767
|
+
const userPlugins = [];
|
|
11768
|
+
if (config.features?.plugins !== false) {
|
|
11769
|
+
for (const p of config.plugins ?? []) {
|
|
11770
|
+
if (typeof p === "object" && p.enabled === false) continue;
|
|
11771
|
+
const spec = typeof p === "string" ? p : p.name;
|
|
11772
|
+
try {
|
|
11773
|
+
const mod = await import(spec);
|
|
11774
|
+
if (mod.default) userPlugins.push(mod.default);
|
|
11775
|
+
} catch (err) {
|
|
11776
|
+
log.warn(`Plugin "${spec}" failed to load`, err);
|
|
11777
|
+
}
|
|
11778
|
+
}
|
|
11779
|
+
}
|
|
11780
|
+
const allPlugins = [...builtinPlugins, ...userPlugins];
|
|
11781
|
+
if (allPlugins.length === 0) return;
|
|
12608
11782
|
const pluginOptions = buildPluginOptions(config);
|
|
12609
|
-
const pluginConfig =
|
|
12610
|
-
|
|
11783
|
+
const pluginConfig = patchConfig(config, {
|
|
11784
|
+
extensions: pluginOptions,
|
|
11785
|
+
paths,
|
|
11786
|
+
configStore,
|
|
11787
|
+
metricsSink,
|
|
11788
|
+
healthRegistry,
|
|
11789
|
+
skillLoader
|
|
11790
|
+
});
|
|
11791
|
+
await loadPlugins(allPlugins, {
|
|
12611
11792
|
log,
|
|
12612
11793
|
pluginOptions,
|
|
12613
11794
|
apiFactory: (plugin) => createApi(plugin.name, {
|
|
11795
|
+
// First-party plugins come from BUILTIN_PLUGIN_FACTORIES — trust them
|
|
11796
|
+
// ("official") so they can claim bare slash command names (/prompts,
|
|
11797
|
+
// /sync) and override built-ins. User plugins stay namespaced.
|
|
11798
|
+
official: builtinPlugins.includes(plugin),
|
|
12614
11799
|
container,
|
|
12615
11800
|
events,
|
|
12616
11801
|
pipelines,
|
|
@@ -12629,6 +11814,7 @@ async function setupPlugins(params) {
|
|
|
12629
11814
|
configStore
|
|
12630
11815
|
})
|
|
12631
11816
|
});
|
|
11817
|
+
log.info(`[setupPlugins] loaded ${builtinPlugins.length} built-in, ${userPlugins.length} user plugin(s)`);
|
|
12632
11818
|
}
|
|
12633
11819
|
function buildPluginOptions(config) {
|
|
12634
11820
|
const options = {};
|
|
@@ -12732,12 +11918,12 @@ async function setupSession(params) {
|
|
|
12732
11918
|
}
|
|
12733
11919
|
const sessionRef = { current: session };
|
|
12734
11920
|
await recoveryLock.write(session.id).catch(() => void 0);
|
|
12735
|
-
const attachments = new DefaultAttachmentStore({ spoolDir:
|
|
12736
|
-
const queueStore = new QueueStore({ dir:
|
|
11921
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path24.join(wpaths.projectSessions, session.id, "attachments") });
|
|
11922
|
+
const queueStore = new QueueStore({ dir: path24.join(wpaths.projectSessions, session.id) });
|
|
12737
11923
|
const ctxSignal = new AbortController().signal;
|
|
12738
11924
|
const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
|
|
12739
11925
|
if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
|
|
12740
|
-
const todosCheckpointPath =
|
|
11926
|
+
const todosCheckpointPath = path24.join(wpaths.projectSessions, `${session.id}.todos.json`);
|
|
12741
11927
|
if (resumeId) {
|
|
12742
11928
|
try {
|
|
12743
11929
|
const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
|
|
@@ -12749,13 +11935,13 @@ async function setupSession(params) {
|
|
|
12749
11935
|
}
|
|
12750
11936
|
}
|
|
12751
11937
|
const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
|
|
12752
|
-
const planPath =
|
|
11938
|
+
const planPath = path24.join(wpaths.projectSessions, `${session.id}.plan.json`);
|
|
12753
11939
|
context.state.setMeta("plan.path", planPath);
|
|
12754
11940
|
let dirState;
|
|
12755
11941
|
if (resumeId) {
|
|
12756
11942
|
try {
|
|
12757
|
-
const fleetRoot =
|
|
12758
|
-
dirState = await loadDirectorState(
|
|
11943
|
+
const fleetRoot = path24.join(wpaths.projectSessions, session.id);
|
|
11944
|
+
dirState = await loadDirectorState(path24.join(fleetRoot, "director-state.json"));
|
|
12759
11945
|
if (dirState) {
|
|
12760
11946
|
const tCounts = {};
|
|
12761
11947
|
for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
|
|
@@ -12782,7 +11968,7 @@ function resolveBundledSkillsDir2() {
|
|
|
12782
11968
|
try {
|
|
12783
11969
|
const req2 = createRequire(import.meta.url);
|
|
12784
11970
|
const corePkg = req2.resolve("@wrongstack/core/package.json");
|
|
12785
|
-
return
|
|
11971
|
+
return path24.join(path24.dirname(corePkg), "skills");
|
|
12786
11972
|
} catch {
|
|
12787
11973
|
return void 0;
|
|
12788
11974
|
}
|
|
@@ -12882,7 +12068,7 @@ async function main(argv) {
|
|
|
12882
12068
|
modeId,
|
|
12883
12069
|
modePrompt,
|
|
12884
12070
|
modelCapabilities,
|
|
12885
|
-
planPath: () => sessionRef.current ?
|
|
12071
|
+
planPath: () => sessionRef.current ? path24.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
|
|
12886
12072
|
contributors: [
|
|
12887
12073
|
// Injects the ETERNAL AUTONOMY block when the user has activated
|
|
12888
12074
|
// `/autonomy eternal`. Without this, the per-iteration directive
|
|
@@ -13041,7 +12227,10 @@ async function main(argv) {
|
|
|
13041
12227
|
agent,
|
|
13042
12228
|
sessionWriter: context.session,
|
|
13043
12229
|
metricsSink,
|
|
13044
|
-
|
|
12230
|
+
healthRegistry,
|
|
12231
|
+
skillLoader: config.features.skills ? skillLoader : void 0,
|
|
12232
|
+
configStore,
|
|
12233
|
+
paths: wpaths
|
|
13045
12234
|
});
|
|
13046
12235
|
const switchProviderAndModel = (providerId, modelId) => {
|
|
13047
12236
|
try {
|
|
@@ -13086,12 +12275,12 @@ async function main(argv) {
|
|
|
13086
12275
|
}
|
|
13087
12276
|
}
|
|
13088
12277
|
};
|
|
13089
|
-
const fleetRoot = directorMode ?
|
|
13090
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
13091
|
-
const sharedScratchpadPath = directorMode ?
|
|
13092
|
-
const subagentSessionsRoot = directorMode ?
|
|
13093
|
-
const stateCheckpointPath = directorMode ?
|
|
13094
|
-
const fleetRootForPromotion =
|
|
12278
|
+
const fleetRoot = directorMode ? path24.join(wpaths.projectSessions, session.id) : void 0;
|
|
12279
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path24.join(fleetRoot, "fleet.json") : void 0;
|
|
12280
|
+
const sharedScratchpadPath = directorMode ? path24.join(fleetRoot, "shared") : void 0;
|
|
12281
|
+
const subagentSessionsRoot = directorMode ? path24.join(fleetRoot, "subagents") : void 0;
|
|
12282
|
+
const stateCheckpointPath = directorMode ? path24.join(fleetRoot, "director-state.json") : void 0;
|
|
12283
|
+
const fleetRootForPromotion = path24.join(wpaths.projectSessions, session.id);
|
|
13095
12284
|
const multiAgentHost = new MultiAgentHost(
|
|
13096
12285
|
{
|
|
13097
12286
|
container,
|
|
@@ -13426,7 +12615,7 @@ async function main(argv) {
|
|
|
13426
12615
|
return director.spawn(cfg);
|
|
13427
12616
|
},
|
|
13428
12617
|
onFleetLog: async (subagentId, mode) => {
|
|
13429
|
-
const subagentsRoot =
|
|
12618
|
+
const subagentsRoot = path24.join(fleetRootForPromotion, "subagents");
|
|
13430
12619
|
let runDirs;
|
|
13431
12620
|
try {
|
|
13432
12621
|
runDirs = await fsp3.readdir(subagentsRoot);
|
|
@@ -13435,7 +12624,7 @@ async function main(argv) {
|
|
|
13435
12624
|
}
|
|
13436
12625
|
const found = [];
|
|
13437
12626
|
for (const runId of runDirs) {
|
|
13438
|
-
const runDir =
|
|
12627
|
+
const runDir = path24.join(subagentsRoot, runId);
|
|
13439
12628
|
let files;
|
|
13440
12629
|
try {
|
|
13441
12630
|
files = await fsp3.readdir(runDir);
|
|
@@ -13444,7 +12633,7 @@ async function main(argv) {
|
|
|
13444
12633
|
}
|
|
13445
12634
|
for (const f of files) {
|
|
13446
12635
|
if (!f.endsWith(".jsonl")) continue;
|
|
13447
|
-
const full =
|
|
12636
|
+
const full = path24.join(runDir, f);
|
|
13448
12637
|
try {
|
|
13449
12638
|
const stat4 = await fsp3.stat(full);
|
|
13450
12639
|
found.push({
|
|
@@ -13541,7 +12730,7 @@ async function main(argv) {
|
|
|
13541
12730
|
}
|
|
13542
12731
|
const dir = await multiAgentHost.ensureDirector();
|
|
13543
12732
|
if (!dir) return "Director is not available.";
|
|
13544
|
-
const dirStatePath =
|
|
12733
|
+
const dirStatePath = path24.join(fleetRootForPromotion, "director-state.json");
|
|
13545
12734
|
const prior = await loadDirectorState(dirStatePath);
|
|
13546
12735
|
if (!prior) {
|
|
13547
12736
|
return "No prior director-state.json found \u2014 nothing to retry.";
|
|
@@ -13612,9 +12801,9 @@ async function main(argv) {
|
|
|
13612
12801
|
for (const tool of director2.tools(FLEET_ROSTER)) {
|
|
13613
12802
|
toolRegistry.register(tool);
|
|
13614
12803
|
}
|
|
13615
|
-
const mp =
|
|
13616
|
-
const sp =
|
|
13617
|
-
const ss =
|
|
12804
|
+
const mp = path24.join(fleetRootForPromotion, "fleet.json");
|
|
12805
|
+
const sp = path24.join(fleetRootForPromotion, "shared");
|
|
12806
|
+
const ss = path24.join(fleetRootForPromotion, "subagents");
|
|
13618
12807
|
const lines = [
|
|
13619
12808
|
`${color.green("\u2713")} Promoted to director mode.`,
|
|
13620
12809
|
` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
|
|
@@ -13711,10 +12900,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
13711
12900
|
void mcpRegistry.stopAll();
|
|
13712
12901
|
},
|
|
13713
12902
|
onBeforeExit: async () => {
|
|
13714
|
-
const { spawn:
|
|
12903
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
13715
12904
|
const cwd2 = projectRoot;
|
|
13716
12905
|
const statusResult = await new Promise((resolve4, reject) => {
|
|
13717
|
-
const child =
|
|
12906
|
+
const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
|
|
13718
12907
|
let stdout = "";
|
|
13719
12908
|
child.stdout?.on("data", (d) => {
|
|
13720
12909
|
stdout += d;
|
|
@@ -13858,6 +13047,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
|
|
|
13858
13047
|
attachments,
|
|
13859
13048
|
tokenCounter,
|
|
13860
13049
|
config,
|
|
13050
|
+
configStore,
|
|
13861
13051
|
renderer,
|
|
13862
13052
|
reader,
|
|
13863
13053
|
session,
|