@wrongstack/cli 0.9.0 → 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 CHANGED
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import * as path25 from 'path';
3
- import { join } from 'path';
2
+ import * as path24 from 'path';
4
3
  import * as fsp3 from 'fs/promises';
5
- import { readdir, readFile } from 'fs/promises';
6
- import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, 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 os6 from 'os';
9
- import os6__default from 'os';
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
- import * as fs9 from 'fs';
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 = path25.join(projectRoot, "package.json");
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 = path25.join(projectRoot, "tsconfig.json");
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 = path25.join(projectRoot, "src");
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 path25.join(homeFn(), ".wrongstack", "update-cache.json");
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 = path25.dirname(cachePath(homeFn));
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 = () => os6.homedir();
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 = path25.join(path25.dirname(opts.globalConfigPath), ".key");
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 = path25.join(path25.dirname(opts.globalConfigPath), ".key");
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(path25.join(root, "pnpm-lock.yaml"))) return "pnpm";
2491
- if (await pathExists(path25.join(root, "bun.lockb"))) return "bun";
2492
- if (await pathExists(path25.join(root, "bun.lock"))) return "bun";
2493
- if (await pathExists(path25.join(root, "yarn.lock"))) return "yarn";
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(path25.join(root, "package.json"), "utf8"));
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(path25.join(root, "pyproject.toml"))) throw new Error("not python");
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(path25.join(root, "go.mod"))) throw new Error("not go");
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(path25.join(root, "Cargo.toml"))) throw new Error("not rust");
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(path25.join(root, "Makefile"), "utf8");
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 = path25.join(ctx.projectRoot, ".wrongstack");
3445
- const file = path25.join(dir, "AGENTS.md");
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(path26) {
3413
+ async function readConfig(path25) {
3637
3414
  try {
3638
- return JSON.parse(await fsp3.readFile(path26, "utf8"));
3415
+ return JSON.parse(await fsp3.readFile(path25, "utf8"));
3639
3416
  } catch {
3640
3417
  return {};
3641
3418
  }
3642
3419
  }
3643
- async function writeConfig(path26, cfg) {
3420
+ async function writeConfig(path25, cfg) {
3644
3421
  const raw = JSON.stringify(cfg, null, 2);
3645
- const tmp = path26 + ".tmp";
3422
+ const tmp = path25 + ".tmp";
3646
3423
  await fsp3.writeFile(tmp, raw, "utf8");
3647
- await fsp3.rename(tmp, path26);
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] ?? path25.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
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(path25.dirname(p), { recursive: true });
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(os6.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(path25.join(projectRoot, "package.json"), "utf8");
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,6 +5351,146 @@ function buildWorktreeCommand(opts) {
6185
5351
  }
6186
5352
  };
6187
5353
  }
5354
+ async function persistAutonomySetting(deps, mutator) {
5355
+ let raw;
5356
+ let fileExists = true;
5357
+ try {
5358
+ raw = await fsp3.readFile(deps.globalConfigPath, "utf8");
5359
+ } catch (err) {
5360
+ if (err.code !== "ENOENT") {
5361
+ throw new Error(`Could not read ${deps.globalConfigPath}: ${err.message}`);
5362
+ }
5363
+ fileExists = false;
5364
+ raw = "{}";
5365
+ }
5366
+ let parsed;
5367
+ try {
5368
+ parsed = JSON.parse(raw);
5369
+ } catch (err) {
5370
+ if (fileExists) {
5371
+ throw new Error(`Config at ${deps.globalConfigPath} is not valid JSON: ${err.message}`);
5372
+ }
5373
+ parsed = {};
5374
+ }
5375
+ const decrypted = decryptConfigSecrets(parsed, deps.vault);
5376
+ const autonomy = decrypted.autonomy ?? {};
5377
+ mutator(autonomy);
5378
+ decrypted.autonomy = autonomy;
5379
+ const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
5380
+ await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
5381
+ deps.configStore.update({ autonomy: decrypted.autonomy });
5382
+ }
5383
+
5384
+ // src/slash-commands/settings.ts
5385
+ var noOpVault = {
5386
+ encrypt: (v) => v,
5387
+ decrypt: (v) => v,
5388
+ isEncrypted: () => false
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
+ }
5395
+ function buildSettingsCommand(opts) {
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")}`,
5411
+ "",
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")}`,
5414
+ "",
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,
5422
+ async run(args) {
5423
+ const parts = args.trim().split(/\s+/).filter(Boolean);
5424
+ const sub = (parts[0] ?? "").toLowerCase();
5425
+ if (sub === "help" || sub === "--help") {
5426
+ return { message: this.help ?? "" };
5427
+ }
5428
+ if (!opts.configStore || !opts.paths) {
5429
+ return { message: `${color.red("Error")} config store not available.` };
5430
+ }
5431
+ if (!sub) {
5432
+ return { message: currentView() };
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
+ };
5452
+ try {
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
+ };
5486
+ } catch (err) {
5487
+ return {
5488
+ message: `${color.red("Settings error")}: ${err instanceof Error ? err.message : String(err)}`
5489
+ };
5490
+ }
5491
+ }
5492
+ };
5493
+ }
6188
5494
 
6189
5495
  // src/slash-commands/index.ts
6190
5496
  function buildBuiltinSlashCommands(opts) {
@@ -6195,11 +5501,6 @@ function buildBuiltinSlashCommands(opts) {
6195
5501
  buildCompactCommand(opts),
6196
5502
  buildContextCommand(opts),
6197
5503
  buildToolsCommand(opts),
6198
- buildSkillCommand(opts),
6199
- buildSkillGeneratorCommand(opts),
6200
- buildSkillInstallCommand(opts),
6201
- buildSkillUpdateCommand(opts),
6202
- buildSkillUninstallCommand(opts),
6203
5504
  buildPluginCommand(opts),
6204
5505
  buildMcpSlashCommand(opts),
6205
5506
  buildDiagCommand(opts),
@@ -6208,11 +5509,8 @@ function buildBuiltinSlashCommands(opts) {
6208
5509
  buildAgentsCommand(opts),
6209
5510
  buildDirectorCommand(opts),
6210
5511
  buildFleetCommand(opts),
6211
- buildMetricsCommand(opts),
6212
- buildHealthCommand(opts),
6213
5512
  buildMemoryCommand(opts),
6214
5513
  buildTodosCommand(opts),
6215
- buildPlanCommand(opts),
6216
5514
  buildSddCommand(opts),
6217
5515
  buildSaveCommand(opts),
6218
5516
  buildLoadCommand(opts),
@@ -6222,13 +5520,10 @@ function buildBuiltinSlashCommands(opts) {
6222
5520
  buildBtwCommand(opts),
6223
5521
  buildModeCommand(opts),
6224
5522
  buildExitCommand(opts),
6225
- buildCommitCommand(),
6226
- buildGitcheckCommand(),
6227
- buildPushCommand(),
6228
- buildSecurityCommand(opts),
6229
5523
  buildFixCommand(opts),
6230
5524
  buildAutoPhaseCommand(opts),
6231
5525
  buildWorktreeCommand(opts),
5526
+ buildSettingsCommand(opts),
6232
5527
  buildStatuslineCommand({
6233
5528
  cwd: opts.cwd,
6234
5529
  hiddenItems: opts.statuslineHiddenItems ?? [],
@@ -6256,13 +5551,13 @@ var MANIFESTS = [
6256
5551
  ];
6257
5552
  async function detectProjectKind(projectRoot) {
6258
5553
  try {
6259
- await fsp3.access(path25.join(projectRoot, ".wrongstack", "AGENTS.md"));
5554
+ await fsp3.access(path24.join(projectRoot, ".wrongstack", "AGENTS.md"));
6260
5555
  return "initialized";
6261
5556
  } catch {
6262
5557
  }
6263
5558
  for (const m of MANIFESTS) {
6264
5559
  try {
6265
- await fsp3.access(path25.join(projectRoot, m));
5560
+ await fsp3.access(path24.join(projectRoot, m));
6266
5561
  return "project";
6267
5562
  } catch {
6268
5563
  }
@@ -6270,8 +5565,8 @@ async function detectProjectKind(projectRoot) {
6270
5565
  return "empty";
6271
5566
  }
6272
5567
  async function scaffoldAgentsMd(projectRoot) {
6273
- const dir = path25.join(projectRoot, ".wrongstack");
6274
- const file = path25.join(dir, "AGENTS.md");
5568
+ const dir = path24.join(projectRoot, ".wrongstack");
5569
+ const file = path24.join(dir, "AGENTS.md");
6275
5570
  const facts = await detectProjectFacts(projectRoot);
6276
5571
  const body = renderAgentsTemplate(facts);
6277
5572
  await fsp3.mkdir(dir, { recursive: true });
@@ -6284,7 +5579,7 @@ async function runProjectCheck(opts) {
6284
5579
  if (kind === "initialized") {
6285
5580
  renderer.write(
6286
5581
  `
6287
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path25.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
5582
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path24.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
6288
5583
  `
6289
5584
  );
6290
5585
  return true;
@@ -6315,7 +5610,7 @@ async function runProjectCheck(opts) {
6315
5610
  }
6316
5611
  return true;
6317
5612
  }
6318
- const gitDir = path25.join(projectRoot, ".git");
5613
+ const gitDir = path24.join(projectRoot, ".git");
6319
5614
  let hasGit = false;
6320
5615
  try {
6321
5616
  await fsp3.access(gitDir);
@@ -6337,9 +5632,9 @@ async function runProjectCheck(opts) {
6337
5632
  }
6338
5633
  if (answer2 === "y" || answer2 === "yes") {
6339
5634
  try {
6340
- const { spawn: spawn4 } = await import('child_process');
5635
+ const { spawn: spawn3 } = await import('child_process');
6341
5636
  await new Promise((resolve4, reject) => {
6342
- const child = spawn4("git", ["init"], { cwd: projectRoot });
5637
+ const child = spawn3("git", ["init"], { cwd: projectRoot });
6343
5638
  child.on("error", reject);
6344
5639
  child.on("close", (code) => code === 0 ? resolve4() : reject(new Error(`git init failed with ${code}`)));
6345
5640
  });
@@ -6440,10 +5735,10 @@ async function runLaunchPrompts(opts) {
6440
5735
  return { mode, yolo, director, autonomy };
6441
5736
  }
6442
5737
  async function bootConfig(flags) {
6443
- const cwd = typeof flags["cwd"] === "string" ? path25.resolve(flags["cwd"]) : process.cwd();
5738
+ const cwd = typeof flags["cwd"] === "string" ? path24.resolve(flags["cwd"]) : process.cwd();
6444
5739
  const pathResolver = new DefaultPathResolver(cwd);
6445
5740
  const projectRoot = pathResolver.projectRoot;
6446
- const userHome = os6.homedir();
5741
+ const userHome = os3.homedir();
6447
5742
  const wpaths = resolveWstackPaths({ projectRoot, userHome });
6448
5743
  await ensureProjectMeta(wpaths, projectRoot);
6449
5744
  const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
@@ -6507,7 +5802,7 @@ var ReadlineInputReader = class {
6507
5802
  history = [];
6508
5803
  pending = false;
6509
5804
  constructor(opts = {}) {
6510
- this.historyFile = opts.historyFile ?? path25.join(os6.homedir(), ".wrongstack", "history");
5805
+ this.historyFile = opts.historyFile ?? path24.join(os3.homedir(), ".wrongstack", "history");
6511
5806
  }
6512
5807
  async loadHistory() {
6513
5808
  try {
@@ -6519,7 +5814,7 @@ var ReadlineInputReader = class {
6519
5814
  }
6520
5815
  async saveHistory() {
6521
5816
  try {
6522
- await fsp3.mkdir(path25.dirname(this.historyFile), { recursive: true });
5817
+ await fsp3.mkdir(path24.dirname(this.historyFile), { recursive: true });
6523
5818
  await fsp3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
6524
5819
  } catch {
6525
5820
  }
@@ -6737,6 +6032,24 @@ async function buildPickableProviders(modelsRegistry, config) {
6737
6032
  }
6738
6033
  return out;
6739
6034
  }
6035
+ var defaultUidFn = () => os3__default.userInfo().uid;
6036
+ async function getFileUid(filePath) {
6037
+ try {
6038
+ const stat4 = await fsp3.stat(filePath);
6039
+ return stat4.uid;
6040
+ } catch {
6041
+ return void 0;
6042
+ }
6043
+ }
6044
+ async function checkConfigOwnership(homeFn, uidFn = defaultUidFn) {
6045
+ if (os3__default.platform() === "win32") return true;
6046
+ const cfg = configPath(homeFn);
6047
+ const fileUid = await getFileUid(cfg);
6048
+ if (fileUid === void 0) return true;
6049
+ const callerUid = uidFn();
6050
+ if (callerUid === void 0) return true;
6051
+ return fileUid === callerUid;
6052
+ }
6740
6053
  var PROTECTED_BASENAMES = /* @__PURE__ */ new Set([
6741
6054
  "config.json",
6742
6055
  ".key",
@@ -6746,20 +6059,20 @@ function assertSafeToDelete(filename, parentDir) {
6746
6059
  if (PROTECTED_BASENAMES.has(filename)) {
6747
6060
  throw new Error(`Refusing to delete protected file: ${filename}`);
6748
6061
  }
6749
- if (filename !== path25.basename(filename)) {
6062
+ if (filename !== path24.basename(filename)) {
6750
6063
  throw new Error(`Refusing to delete path with traversal: ${filename}`);
6751
6064
  }
6752
6065
  if (!filename.startsWith("config.json.") || !filename.endsWith(".bak")) {
6753
6066
  throw new Error(`Refusing to delete unknown file: ${filename}`);
6754
6067
  }
6755
- const resolvedParent = path25.resolve(parentDir);
6068
+ const resolvedParent = path24.resolve(parentDir);
6756
6069
  if (!resolvedParent.endsWith(".wrongstack")) {
6757
6070
  throw new Error(`Unexpected parent directory for bak prune: ${resolvedParent}`);
6758
6071
  }
6759
6072
  }
6760
6073
  async function safeDelete(filePath) {
6761
- const dir = path25.dirname(filePath);
6762
- const filename = path25.basename(filePath);
6074
+ const dir = path24.dirname(filePath);
6075
+ const filename = path24.basename(filePath);
6763
6076
  try {
6764
6077
  assertSafeToDelete(filename, dir);
6765
6078
  await fsp3.unlink(filePath);
@@ -6802,18 +6115,18 @@ function diffSummary(oldCfg, newCfg) {
6802
6115
  }
6803
6116
  return changes.length > 0 ? changes.slice(0, 5).join(", ") : "no changes";
6804
6117
  }
6805
- var defaultHomeDir = () => os6__default.homedir();
6118
+ var defaultHomeDir = () => os3__default.homedir();
6806
6119
  function historyDir(homeFn = defaultHomeDir) {
6807
- return path25.join(homeFn(), ".wrongstack", "config.history", "entries");
6120
+ return path24.join(homeFn(), ".wrongstack", "config.history", "entries");
6808
6121
  }
6809
6122
  function historyIndexPath(homeFn = defaultHomeDir) {
6810
- return path25.join(homeFn(), ".wrongstack", "config.history", "index.json");
6123
+ return path24.join(homeFn(), ".wrongstack", "config.history", "index.json");
6811
6124
  }
6812
6125
  function configPath(homeFn = defaultHomeDir) {
6813
- return path25.join(homeFn(), ".wrongstack", "config.json");
6126
+ return path24.join(homeFn(), ".wrongstack", "config.json");
6814
6127
  }
6815
6128
  function backupLastPath(homeFn = defaultHomeDir) {
6816
- return path25.join(homeFn(), ".wrongstack", "config.json.last");
6129
+ return path24.join(homeFn(), ".wrongstack", "config.json.last");
6817
6130
  }
6818
6131
  function entryId(ts) {
6819
6132
  return ts.replace(/[:.]/g, "-").slice(0, 19);
@@ -6868,17 +6181,17 @@ async function backupCurrent(homeFn = defaultHomeDir) {
6868
6181
  }
6869
6182
  if (content !== void 0) {
6870
6183
  try {
6871
- const bakPath = path25.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
6184
+ const bakPath = path24.join(homeFn(), ".wrongstack", `config.json.${ts}.bak`);
6872
6185
  await atomicWrite(bakPath, content);
6873
6186
  } catch {
6874
6187
  }
6875
6188
  }
6876
6189
  try {
6877
- const dir = path25.join(homeFn(), ".wrongstack");
6190
+ const dir = path24.join(homeFn(), ".wrongstack");
6878
6191
  const files = await fsp3.readdir(dir);
6879
6192
  const baks = files.filter((f) => f.startsWith("config.json.") && f.endsWith(".bak")).sort().reverse();
6880
6193
  for (const f of baks.slice(10)) {
6881
- await safeDelete(path25.join(dir, f));
6194
+ await safeDelete(path24.join(dir, f));
6882
6195
  }
6883
6196
  } catch {
6884
6197
  }
@@ -6896,7 +6209,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6896
6209
  };
6897
6210
  try {
6898
6211
  await fsp3.writeFile(
6899
- path25.join(historyDir(homeFn), `${id}.json`),
6212
+ path24.join(historyDir(homeFn), `${id}.json`),
6900
6213
  JSON.stringify(entry, null, 2),
6901
6214
  "utf8"
6902
6215
  );
@@ -6904,7 +6217,7 @@ async function appendHistory(oldCfg, newCfg, description, homeFn = defaultHomeDi
6904
6217
  throw new FsError({
6905
6218
  message: err instanceof Error ? err.message : String(err),
6906
6219
  code: ERROR_CODES.FS_WRITE_FAILED,
6907
- path: path25.join(historyDir(homeFn), `${id}.json`),
6220
+ path: path24.join(historyDir(homeFn), `${id}.json`),
6908
6221
  cause: err
6909
6222
  });
6910
6223
  }
@@ -6919,7 +6232,7 @@ async function listHistory(homeFn = defaultHomeDir) {
6919
6232
  }
6920
6233
  async function getHistoryEntry(id, homeFn = defaultHomeDir) {
6921
6234
  try {
6922
- const raw = await fsp3.readFile(path25.join(historyDir(homeFn), `${id}.json`), "utf8");
6235
+ const raw = await fsp3.readFile(path24.join(historyDir(homeFn), `${id}.json`), "utf8");
6923
6236
  return JSON.parse(raw);
6924
6237
  } catch {
6925
6238
  return null;
@@ -6928,6 +6241,9 @@ async function getHistoryEntry(id, homeFn = defaultHomeDir) {
6928
6241
  async function restoreFromHistory(id, homeFn = defaultHomeDir) {
6929
6242
  const entry = await getHistoryEntry(id, homeFn);
6930
6243
  if (!entry) return { ok: false, backupId: null, error: "History entry not found" };
6244
+ if (!await checkConfigOwnership(homeFn)) {
6245
+ return { ok: false, backupId: null, error: "Operation denied: config file is not owned by current user" };
6246
+ }
6931
6247
  await backupCurrent(homeFn);
6932
6248
  let oldCfg = {};
6933
6249
  try {
@@ -6964,6 +6280,9 @@ async function restoreLast(homeFn = defaultHomeDir) {
6964
6280
  } catch {
6965
6281
  return { ok: false, error: "No prior backup found" };
6966
6282
  }
6283
+ if (!await checkConfigOwnership(homeFn)) {
6284
+ return { ok: false, error: "Operation denied: config file is not owned by current user" };
6285
+ }
6967
6286
  await backupCurrent(homeFn);
6968
6287
  try {
6969
6288
  await atomicWrite(cfg, JSON.stringify(lastCfg, null, 2));
@@ -6978,11 +6297,11 @@ async function restoreLast(homeFn = defaultHomeDir) {
6978
6297
  var theme = { primary: color.amber };
6979
6298
  async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => process.env.HOME ?? __require("os").homedir()) {
6980
6299
  try {
6981
- const { atomicWrite: atomicWrite8 } = await import('@wrongstack/core');
6982
- const fs21 = await import('fs/promises');
6300
+ const { atomicWrite: atomicWrite9 } = await import('@wrongstack/core');
6301
+ const fs22 = await import('fs/promises');
6983
6302
  let existing = {};
6984
6303
  try {
6985
- const raw = await fs21.readFile(configPath2, "utf8");
6304
+ const raw = await fs22.readFile(configPath2, "utf8");
6986
6305
  existing = JSON.parse(raw);
6987
6306
  } catch {
6988
6307
  }
@@ -6990,7 +6309,7 @@ async function saveToGlobalConfig(configPath2, provider, model, homeFn = () => p
6990
6309
  existing.provider = provider;
6991
6310
  existing.model = model;
6992
6311
  await backupCurrent(homeFn);
6993
- await atomicWrite8(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
6312
+ await atomicWrite9(configPath2, JSON.stringify(existing, null, 2), { mode: 384 });
6994
6313
  try {
6995
6314
  await appendHistory(
6996
6315
  oldCfg,
@@ -7313,12 +6632,12 @@ function pickGroupIndex(opts) {
7313
6632
  try {
7314
6633
  let current = 0;
7315
6634
  try {
7316
- const parsed = Number.parseInt(fs9.readFileSync(opts.cursorFile, "utf8").trim(), 10);
6635
+ const parsed = Number.parseInt(fs10.readFileSync(opts.cursorFile, "utf8").trim(), 10);
7317
6636
  if (Number.isFinite(parsed)) current = wrap(parsed);
7318
6637
  } catch {
7319
6638
  }
7320
- fs9.mkdirSync(path25.dirname(opts.cursorFile), { recursive: true });
7321
- fs9.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
6639
+ fs10.mkdirSync(path24.dirname(opts.cursorFile), { recursive: true });
6640
+ fs10.writeFileSync(opts.cursorFile, String(wrap(current + 1)));
7322
6641
  return current;
7323
6642
  } catch {
7324
6643
  }
@@ -7653,14 +6972,14 @@ function summarize(value, name) {
7653
6972
  if (typeof v === "object" && v !== null) {
7654
6973
  const o = v;
7655
6974
  if (name === "edit") {
7656
- const path26 = typeof o["path"] === "string" ? o["path"] : "";
6975
+ const path25 = typeof o["path"] === "string" ? o["path"] : "";
7657
6976
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
7658
- return `${path26} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
6977
+ return `${path25} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
7659
6978
  }
7660
6979
  if (name === "write") {
7661
- const path26 = typeof o["path"] === "string" ? o["path"] : "";
6980
+ const path25 = typeof o["path"] === "string" ? o["path"] : "";
7662
6981
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
7663
- return bytes !== void 0 ? `${path26} ${bytes}B` : path26;
6982
+ return bytes !== void 0 ? `${path25} ${bytes}B` : path25;
7664
6983
  }
7665
6984
  if (typeof o["count"] === "number") {
7666
6985
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -8591,7 +7910,7 @@ var diagCmd = async (_args, deps) => {
8591
7910
  ` modelsCache: ${deps.paths.modelsCache}`,
8592
7911
  ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
8593
7912
  ` node: ${process.version}`,
8594
- ` os: ${os6.platform()} ${os6.release()}`,
7913
+ ` os: ${os3.platform()} ${os3.release()}`,
8595
7914
  ` provider: ${cfg.provider ?? "<unset>"}`,
8596
7915
  ` model: ${cfg.model ?? "<unset>"}`,
8597
7916
  ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
@@ -8670,7 +7989,7 @@ var doctorCmd = async (_args, deps) => {
8670
7989
  }
8671
7990
  try {
8672
7991
  await fsp3.mkdir(deps.paths.projectSessions, { recursive: true });
8673
- const probe = path25.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
7992
+ const probe = path24.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
8674
7993
  await fsp3.writeFile(probe, "");
8675
7994
  await fsp3.unlink(probe);
8676
7995
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -8773,8 +8092,8 @@ var exportCmd = async (args, deps) => {
8773
8092
  return 1;
8774
8093
  }
8775
8094
  if (output) {
8776
- await fsp3.mkdir(path25.dirname(path25.resolve(deps.cwd, output)), { recursive: true });
8777
- await fsp3.writeFile(path25.resolve(deps.cwd, output), rendered, "utf8");
8095
+ await fsp3.mkdir(path24.dirname(path24.resolve(deps.cwd, output)), { recursive: true });
8096
+ await fsp3.writeFile(path24.resolve(deps.cwd, output), rendered, "utf8");
8778
8097
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
8779
8098
  `);
8780
8099
  } else {
@@ -8847,8 +8166,8 @@ var initCmd = async (_args, deps) => {
8847
8166
  const vault = new DefaultSecretVault$1({ keyFile: deps.paths.secretsKey });
8848
8167
  const encrypted = encryptConfigSecrets(config, vault);
8849
8168
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2), { mode: 384 });
8850
- await fsp3.mkdir(path25.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8851
- const agentsFile = path25.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8169
+ await fsp3.mkdir(path24.join(deps.projectRoot, ".wrongstack"), { recursive: true });
8170
+ const agentsFile = path24.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
8852
8171
  const projectFacts = await detectProjectFacts(deps.projectRoot);
8853
8172
  await atomicWrite(agentsFile, renderAgentsTemplate(projectFacts));
8854
8173
  deps.renderer.writeInfo(`Wrote ${deps.paths.globalConfig}`);
@@ -9154,7 +8473,7 @@ var usageCmd = async (_args, deps) => {
9154
8473
  return 0;
9155
8474
  };
9156
8475
  var projectsCmd = async (_args, deps) => {
9157
- const projectsRoot = path25.join(deps.paths.globalRoot, "projects");
8476
+ const projectsRoot = path24.join(deps.paths.globalRoot, "projects");
9158
8477
  try {
9159
8478
  const entries = await fsp3.readdir(projectsRoot);
9160
8479
  if (entries.length === 0) {
@@ -9164,7 +8483,7 @@ var projectsCmd = async (_args, deps) => {
9164
8483
  for (const hash of entries) {
9165
8484
  try {
9166
8485
  const meta = JSON.parse(
9167
- await fsp3.readFile(path25.join(projectsRoot, hash, "meta.json"), "utf8")
8486
+ await fsp3.readFile(path24.join(projectsRoot, hash, "meta.json"), "utf8")
9168
8487
  );
9169
8488
  deps.renderer.write(
9170
8489
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -9323,30 +8642,30 @@ async function listFleetRuns(deps) {
9323
8642
  }
9324
8643
  const runs = [];
9325
8644
  for (const id of entries) {
9326
- const runDir = path25.join(deps.paths.projectSessions, id);
9327
- let stat3;
8645
+ const runDir = path24.join(deps.paths.projectSessions, id);
8646
+ let stat4;
9328
8647
  try {
9329
- stat3 = await fsp3.stat(runDir);
8648
+ stat4 = await fsp3.stat(runDir);
9330
8649
  } catch {
9331
8650
  continue;
9332
8651
  }
9333
- if (!stat3.isDirectory()) continue;
8652
+ if (!stat4.isDirectory()) continue;
9334
8653
  let manifest = false;
9335
8654
  let checkpoint = false;
9336
8655
  let subagentCount = 0;
9337
8656
  let subagentsDir;
9338
8657
  try {
9339
- await fsp3.access(path25.join(runDir, "fleet.json"));
8658
+ await fsp3.access(path24.join(runDir, "fleet.json"));
9340
8659
  manifest = true;
9341
8660
  } catch {
9342
8661
  }
9343
8662
  try {
9344
- await fsp3.access(path25.join(runDir, "checkpoint.json"));
8663
+ await fsp3.access(path24.join(runDir, "checkpoint.json"));
9345
8664
  checkpoint = true;
9346
8665
  } catch {
9347
8666
  }
9348
8667
  try {
9349
- subagentsDir = path25.join(runDir, "subagents");
8668
+ subagentsDir = path24.join(runDir, "subagents");
9350
8669
  const files = await fsp3.readdir(subagentsDir);
9351
8670
  subagentCount = files.filter((f) => f.endsWith(".jsonl")).length;
9352
8671
  } catch {
@@ -9375,16 +8694,16 @@ async function listFleetRuns(deps) {
9375
8694
  return 0;
9376
8695
  }
9377
8696
  async function showFleetRun(runId, deps) {
9378
- const runDir = path25.join(deps.paths.projectSessions, runId);
9379
- let stat3;
8697
+ const runDir = path24.join(deps.paths.projectSessions, runId);
8698
+ let stat4;
9380
8699
  try {
9381
- stat3 = await fsp3.stat(runDir);
8700
+ stat4 = await fsp3.stat(runDir);
9382
8701
  } catch {
9383
8702
  deps.renderer.writeError(`Fleet run not found: ${runId}
9384
8703
  `);
9385
8704
  return 1;
9386
8705
  }
9387
- if (!stat3.isDirectory()) {
8706
+ if (!stat4.isDirectory()) {
9388
8707
  deps.renderer.writeError(`Not a directory: ${runId}
9389
8708
  `);
9390
8709
  return 1;
@@ -9392,7 +8711,7 @@ async function showFleetRun(runId, deps) {
9392
8711
  deps.renderer.write(color.bold(`
9393
8712
  Fleet Run: ${runId}
9394
8713
  `) + "\n");
9395
- const manifestPath = path25.join(runDir, "fleet.json");
8714
+ const manifestPath = path24.join(runDir, "fleet.json");
9396
8715
  let manifestData = null;
9397
8716
  try {
9398
8717
  manifestData = await fsp3.readFile(manifestPath, "utf8");
@@ -9408,7 +8727,7 @@ Fleet Run: ${runId}
9408
8727
  deps.renderer.write(` ${color.dim("\u25CB")} fleet.json \u2014 not found
9409
8728
  `);
9410
8729
  }
9411
- const checkpointPath = path25.join(runDir, "checkpoint.json");
8730
+ const checkpointPath = path24.join(runDir, "checkpoint.json");
9412
8731
  let checkpointData = null;
9413
8732
  try {
9414
8733
  checkpointData = await fsp3.readFile(checkpointPath, "utf8");
@@ -9455,7 +8774,7 @@ Fleet Run: ${runId}
9455
8774
  } catch {
9456
8775
  }
9457
8776
  }
9458
- const subagentsDir = path25.join(runDir, "subagents");
8777
+ const subagentsDir = path24.join(runDir, "subagents");
9459
8778
  let subagentFiles = [];
9460
8779
  try {
9461
8780
  subagentFiles = await fsp3.readdir(subagentsDir);
@@ -9467,7 +8786,7 @@ Fleet Run: ${runId}
9467
8786
  Subagent transcripts (${subagentFiles.length}):
9468
8787
  `);
9469
8788
  for (const f of subagentFiles.sort()) {
9470
- const filePath = path25.join(subagentsDir, f);
8789
+ const filePath = path24.join(subagentsDir, f);
9471
8790
  let size;
9472
8791
  try {
9473
8792
  const s = await fsp3.stat(filePath);
@@ -9484,7 +8803,7 @@ Fleet Run: ${runId}
9484
8803
  ${color.dim("\u25CB")} No subagent transcripts
9485
8804
  `);
9486
8805
  }
9487
- const sharedDir = path25.join(runDir, "shared");
8806
+ const sharedDir = path24.join(runDir, "shared");
9488
8807
  try {
9489
8808
  const files = await fsp3.readdir(sharedDir);
9490
8809
  deps.renderer.write(`
@@ -9651,7 +8970,7 @@ function findSessionId(args) {
9651
8970
  var rewindCmd = async (args, deps) => {
9652
8971
  const flags = parseRewindFlags(args);
9653
8972
  const wpaths = resolveWstackPaths({ projectRoot: deps.projectRoot });
9654
- const sessionsDir = path25.join(wpaths.globalRoot, "sessions");
8973
+ const sessionsDir = path24.join(wpaths.globalRoot, "sessions");
9655
8974
  const rewind = new DefaultSessionRewinder(sessionsDir, deps.projectRoot);
9656
8975
  let sessionId = findSessionId(args);
9657
8976
  if (!sessionId) {
@@ -9783,7 +9102,7 @@ var skillsCmd = async (_args, deps) => {
9783
9102
  };
9784
9103
  var versionCmd = async (_args, deps) => {
9785
9104
  deps.renderer.write(
9786
- `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os6.platform()})
9105
+ `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os3.platform()})
9787
9106
  `
9788
9107
  );
9789
9108
  return 0;
@@ -9863,22 +9182,22 @@ function fmtDuration(ms) {
9863
9182
  const remMin = m - h * 60;
9864
9183
  return `${h}h${remMin}m`;
9865
9184
  }
9866
- function fmtTaskResultLine(r, color39) {
9185
+ function fmtTaskResultLine(r, color37) {
9867
9186
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
9868
9187
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
9869
9188
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
9870
9189
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
9871
- const errKindChip = errKind ? color39.dim(` [${errKind}]`) : "";
9872
- const errSnip = errMsg || errKind ? `${errKindChip}${color39.dim(errTail)}` : "";
9190
+ const errKindChip = errKind ? color37.dim(` [${errKind}]`) : "";
9191
+ const errSnip = errMsg || errKind ? `${errKindChip}${color37.dim(errTail)}` : "";
9873
9192
  switch (r.status) {
9874
9193
  case "success":
9875
- return { mark: color39.green("\u2713"), stats, tail: "" };
9194
+ return { mark: color37.green("\u2713"), stats, tail: "" };
9876
9195
  case "timeout":
9877
- return { mark: color39.yellow("\u23F1"), stats: `${color39.yellow("timeout")} ${stats}`, tail: errSnip };
9196
+ return { mark: color37.yellow("\u23F1"), stats: `${color37.yellow("timeout")} ${stats}`, tail: errSnip };
9878
9197
  case "stopped":
9879
- return { mark: color39.dim("\u2298"), stats: `${color39.dim("stopped")} ${stats}`, tail: errSnip };
9198
+ return { mark: color37.dim("\u2298"), stats: `${color37.dim("stopped")} ${stats}`, tail: errSnip };
9880
9199
  case "failed":
9881
- return { mark: color39.red("\u2717"), stats: `${color39.red("failed")} ${stats}`, tail: errSnip };
9200
+ return { mark: color37.red("\u2717"), stats: `${color37.red("failed")} ${stats}`, tail: errSnip };
9882
9201
  }
9883
9202
  }
9884
9203
 
@@ -9888,7 +9207,7 @@ function resolveBundledSkillsDir() {
9888
9207
  try {
9889
9208
  const req2 = createRequire(import.meta.url);
9890
9209
  const corePkg = req2.resolve("@wrongstack/core/package.json");
9891
- return path25.join(path25.dirname(corePkg), "skills");
9210
+ return path24.join(path24.dirname(corePkg), "skills");
9892
9211
  } catch {
9893
9212
  return void 0;
9894
9213
  }
@@ -10050,7 +9369,7 @@ async function boot(argv) {
10050
9369
  if (choices.director) flags["director"] = true;
10051
9370
  flags["autonomy"] = choices.autonomy;
10052
9371
  printLaunchHints(renderer, flags, {
10053
- cursorFile: path25.join(wpaths.cacheDir, "hint-cursor")
9372
+ cursorFile: path24.join(wpaths.cacheDir, "hint-cursor")
10054
9373
  });
10055
9374
  }
10056
9375
  return {
@@ -10735,9 +10054,9 @@ async function renderGoalBanner(opts) {
10735
10054
  );
10736
10055
  if (goal.journal.length > 0) {
10737
10056
  const lastEntry = goal.journal[goal.journal.length - 1];
10738
- const statusIcon2 = lastEntry.status === "success" ? "\u2713" : lastEntry.status === "failure" ? "\u2717" : lastEntry.status === "aborted" ? "\u2298" : lastEntry.status === "skipped" ? "\u229D" : "\xB7";
10057
+ const statusIcon = lastEntry.status === "success" ? "\u2713" : lastEntry.status === "failure" ? "\u2717" : lastEntry.status === "aborted" ? "\u2298" : lastEntry.status === "skipped" ? "\u229D" : "\xB7";
10739
10058
  opts.renderer.write(
10740
- color.dim(` Last: ${statusIcon2} ${lastEntry.task} (${lastEntry.status})`) + "\n"
10059
+ color.dim(` Last: ${statusIcon} ${lastEntry.task} (${lastEntry.status})`) + "\n"
10741
10060
  );
10742
10061
  }
10743
10062
  if (goal.engineState === "running") {
@@ -10840,6 +10159,7 @@ async function execute(deps) {
10840
10159
  attachments,
10841
10160
  tokenCounter,
10842
10161
  config,
10162
+ configStore,
10843
10163
  renderer,
10844
10164
  reader,
10845
10165
  session,
@@ -11039,6 +10359,30 @@ async function execute(deps) {
11039
10359
  onAutonomy?.(mode);
11040
10360
  return null;
11041
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
+ },
11042
10386
  effectiveMaxContext,
11043
10387
  // Default OFF so the terminal's native scrollback works for chat
11044
10388
  // history out of the box (mouse wheel / Shift+PgUp). Users who hit
@@ -11119,7 +10463,7 @@ async function execute(deps) {
11119
10463
  supportsVision,
11120
10464
  attachments,
11121
10465
  effectiveMaxContext,
11122
- projectName: path25.basename(projectRoot) || void 0,
10466
+ projectName: path24.basename(projectRoot) || void 0,
11123
10467
  projectRoot,
11124
10468
  getAutonomy,
11125
10469
  onAutonomy,
@@ -11143,7 +10487,7 @@ async function execute(deps) {
11143
10487
  supportsVision,
11144
10488
  attachments,
11145
10489
  effectiveMaxContext,
11146
- projectName: path25.basename(projectRoot) || void 0,
10490
+ projectName: path24.basename(projectRoot) || void 0,
11147
10491
  getAutonomy,
11148
10492
  onAutonomy,
11149
10493
  getEternalEngine,
@@ -11247,7 +10591,7 @@ var MultiAgentHost = class {
11247
10591
  doneCondition: { type: "all_tasks_done" },
11248
10592
  maxConcurrent: this.opts.maxConcurrent ?? 4
11249
10593
  };
11250
- const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path25.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
10594
+ const defaultScratchpad = this.opts.sharedScratchpadPath || (this.opts.sessionsRoot && this.opts.directorRunId ? path24.join(this.opts.sessionsRoot, this.opts.directorRunId, "shared") : void 0);
11251
10595
  this.director = new Director({
11252
10596
  config: coordinatorConfig,
11253
10597
  manifestPath: this.opts.manifestPath,
@@ -11493,7 +10837,7 @@ var MultiAgentHost = class {
11493
10837
  model: opts?.model,
11494
10838
  tools: opts?.tools
11495
10839
  };
11496
- const transcriptPath = this.sessionFactory ? path25.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
10840
+ const transcriptPath = this.sessionFactory ? path24.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
11497
10841
  const { subagentId, taskId } = await this._spawnAndAssign(subagentConfig);
11498
10842
  this.fleetManager?.addPendingTask(taskId, subagentId, description);
11499
10843
  this.deps.events.emit("subagent.spawned", {
@@ -11636,16 +10980,16 @@ var MultiAgentHost = class {
11636
10980
  if (this.director) return this.director;
11637
10981
  this.opts.directorMode = true;
11638
10982
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
11639
- this.opts.manifestPath = path25.join(this.opts.fleetRoot, "fleet.json");
10983
+ this.opts.manifestPath = path24.join(this.opts.fleetRoot, "fleet.json");
11640
10984
  }
11641
10985
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
11642
- this.opts.sharedScratchpadPath = path25.join(this.opts.fleetRoot, "shared");
10986
+ this.opts.sharedScratchpadPath = path24.join(this.opts.fleetRoot, "shared");
11643
10987
  }
11644
10988
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
11645
- this.opts.sessionsRoot = path25.join(this.opts.fleetRoot, "subagents");
10989
+ this.opts.sessionsRoot = path24.join(this.opts.fleetRoot, "subagents");
11646
10990
  }
11647
10991
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
11648
- this.opts.stateCheckpointPath = path25.join(this.opts.fleetRoot, "director-state.json");
10992
+ this.opts.stateCheckpointPath = path24.join(this.opts.fleetRoot, "director-state.json");
11649
10993
  }
11650
10994
  await this.ensureDirector();
11651
10995
  return this.director ?? null;
@@ -11811,11 +11155,11 @@ var SessionStats = class {
11811
11155
  if (e.name === "bash") this.bashCommands++;
11812
11156
  else if (e.name === "fetch") this.fetches++;
11813
11157
  if (!e.ok) return;
11814
- const path26 = typeof input?.path === "string" ? input.path : void 0;
11815
- if (e.name === "read" && path26) this.readPaths.add(path26);
11816
- else if (e.name === "edit" && path26) this.editedPaths.add(path26);
11817
- else if (e.name === "write" && path26) {
11818
- this.writtenPaths.add(path26);
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);
11819
11163
  const content = typeof input?.content === "string" ? input.content : "";
11820
11164
  this.bytesWritten += Buffer.byteLength(content, "utf8");
11821
11165
  }
@@ -11934,7 +11278,7 @@ function gitText(args, cwd) {
11934
11278
  child.on("close", (code) => resolve4({ code: code ?? 1, out: out.trim() }));
11935
11279
  });
11936
11280
  }
11937
- async function isGitRepo2(cwd) {
11281
+ async function isGitRepo(cwd) {
11938
11282
  const { code, out } = await gitText(["rev-parse", "--is-inside-work-tree"], cwd);
11939
11283
  return code === 0 && out.trim() === "true";
11940
11284
  }
@@ -12011,7 +11355,7 @@ function createAutoPhaseHost(deps) {
12011
11355
  await persist(graph);
12012
11356
  const worktreesEnabled = deps.worktrees !== false && process.env["WRONGSTACK_AUTOPHASE_WORKTREES"] !== "0";
12013
11357
  let worktrees;
12014
- if (worktreesEnabled && await isGitRepo2(deps.projectRoot)) {
11358
+ if (worktreesEnabled && await isGitRepo(deps.projectRoot)) {
12015
11359
  worktrees = new WorktreeManager({ projectRoot: deps.projectRoot, events: deps.events });
12016
11360
  log(`\u{1F33F} Worktree isolation on \u2014 up to ${deps.maxConcurrentPhases ?? WORKTREE_PHASE_CONCURRENCY} phases run in parallel.`);
12017
11361
  }
@@ -12091,7 +11435,7 @@ function createAutoPhaseHost(deps) {
12091
11435
  },
12092
11436
  async onWorktree(action, target) {
12093
11437
  const root = deps.projectRoot;
12094
- if (!await isGitRepo2(root)) return "\u26A0 Not a git repository \u2014 worktrees unavailable.";
11438
+ if (!await isGitRepo(root)) return "\u26A0 Not a git repository \u2014 worktrees unavailable.";
12095
11439
  switch (action) {
12096
11440
  case "list": {
12097
11441
  const { out } = await gitText(["worktree", "list"], root);
@@ -12315,7 +11659,7 @@ function setupMetrics(params) {
12315
11659
  const dumpMetrics = () => {
12316
11660
  if (!metricsSink) return;
12317
11661
  try {
12318
- const out = path25.join(wpaths.projectSessions, "metrics.json");
11662
+ const out = path24.join(wpaths.projectSessions, "metrics.json");
12319
11663
  const snap = metricsSink.snapshot();
12320
11664
  writeFileSync(out, JSON.stringify(snap, null, 2));
12321
11665
  } catch {
@@ -12350,6 +11694,36 @@ function createApi(ownerName, base) {
12350
11694
  }
12351
11695
 
12352
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
+ ];
12353
11727
  async function setupPlugins(params) {
12354
11728
  const {
12355
11729
  config,
@@ -12363,28 +11737,65 @@ async function setupPlugins(params) {
12363
11737
  agent,
12364
11738
  sessionWriter,
12365
11739
  metricsSink,
11740
+ healthRegistry,
11741
+ skillLoader,
12366
11742
  configStore,
12367
- pipelines
11743
+ pipelines,
11744
+ paths
12368
11745
  } = params;
12369
- if (!config.features.plugins || !config.plugins || config.plugins.length === 0) return;
12370
- const resolvedPlugins = [];
12371
- for (const p of config.plugins) {
12372
- if (typeof p === "object" && p.enabled === false) continue;
12373
- const spec = typeof p === "string" ? p : p.name;
12374
- try {
12375
- const mod = await import(spec);
12376
- if (mod.default) resolvedPlugins.push(mod.default);
12377
- } catch (err) {
12378
- log.warn(`Plugin "${spec}" failed to load`, err);
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
+ }
12379
11765
  }
12380
11766
  }
12381
- if (resolvedPlugins.length === 0) return;
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;
12382
11782
  const pluginOptions = buildPluginOptions(config);
12383
- const pluginConfig = Object.keys(pluginOptions).length > 0 ? patchConfig(config, { extensions: pluginOptions }) : config;
12384
- await loadPlugins(resolvedPlugins, {
11783
+ const pluginConfig = patchConfig(config, {
11784
+ extensions: pluginOptions,
11785
+ paths,
11786
+ configStore,
11787
+ metricsSink,
11788
+ healthRegistry,
11789
+ skillLoader
11790
+ });
11791
+ await loadPlugins(allPlugins, {
12385
11792
  log,
12386
11793
  pluginOptions,
12387
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),
12388
11799
  container,
12389
11800
  events,
12390
11801
  pipelines,
@@ -12403,6 +11814,7 @@ async function setupPlugins(params) {
12403
11814
  configStore
12404
11815
  })
12405
11816
  });
11817
+ log.info(`[setupPlugins] loaded ${builtinPlugins.length} built-in, ${userPlugins.length} user plugin(s)`);
12406
11818
  }
12407
11819
  function buildPluginOptions(config) {
12408
11820
  const options = {};
@@ -12506,12 +11918,12 @@ async function setupSession(params) {
12506
11918
  }
12507
11919
  const sessionRef = { current: session };
12508
11920
  await recoveryLock.write(session.id).catch(() => void 0);
12509
- const attachments = new DefaultAttachmentStore({ spoolDir: path25.join(wpaths.projectSessions, session.id, "attachments") });
12510
- const queueStore = new QueueStore({ dir: path25.join(wpaths.projectSessions, session.id) });
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) });
12511
11923
  const ctxSignal = new AbortController().signal;
12512
11924
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
12513
11925
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
12514
- const todosCheckpointPath = path25.join(wpaths.projectSessions, `${session.id}.todos.json`);
11926
+ const todosCheckpointPath = path24.join(wpaths.projectSessions, `${session.id}.todos.json`);
12515
11927
  if (resumeId) {
12516
11928
  try {
12517
11929
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -12523,13 +11935,13 @@ async function setupSession(params) {
12523
11935
  }
12524
11936
  }
12525
11937
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
12526
- const planPath = path25.join(wpaths.projectSessions, `${session.id}.plan.json`);
11938
+ const planPath = path24.join(wpaths.projectSessions, `${session.id}.plan.json`);
12527
11939
  context.state.setMeta("plan.path", planPath);
12528
11940
  let dirState;
12529
11941
  if (resumeId) {
12530
11942
  try {
12531
- const fleetRoot = path25.join(wpaths.projectSessions, session.id);
12532
- dirState = await loadDirectorState(path25.join(fleetRoot, "director-state.json"));
11943
+ const fleetRoot = path24.join(wpaths.projectSessions, session.id);
11944
+ dirState = await loadDirectorState(path24.join(fleetRoot, "director-state.json"));
12533
11945
  if (dirState) {
12534
11946
  const tCounts = {};
12535
11947
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -12556,7 +11968,7 @@ function resolveBundledSkillsDir2() {
12556
11968
  try {
12557
11969
  const req2 = createRequire(import.meta.url);
12558
11970
  const corePkg = req2.resolve("@wrongstack/core/package.json");
12559
- return path25.join(path25.dirname(corePkg), "skills");
11971
+ return path24.join(path24.dirname(corePkg), "skills");
12560
11972
  } catch {
12561
11973
  return void 0;
12562
11974
  }
@@ -12656,7 +12068,7 @@ async function main(argv) {
12656
12068
  modeId,
12657
12069
  modePrompt,
12658
12070
  modelCapabilities,
12659
- planPath: () => sessionRef.current ? path25.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
12071
+ planPath: () => sessionRef.current ? path24.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0,
12660
12072
  contributors: [
12661
12073
  // Injects the ETERNAL AUTONOMY block when the user has activated
12662
12074
  // `/autonomy eternal`. Without this, the per-iteration directive
@@ -12815,7 +12227,10 @@ async function main(argv) {
12815
12227
  agent,
12816
12228
  sessionWriter: context.session,
12817
12229
  metricsSink,
12818
- configStore
12230
+ healthRegistry,
12231
+ skillLoader: config.features.skills ? skillLoader : void 0,
12232
+ configStore,
12233
+ paths: wpaths
12819
12234
  });
12820
12235
  const switchProviderAndModel = (providerId, modelId) => {
12821
12236
  try {
@@ -12860,12 +12275,12 @@ async function main(argv) {
12860
12275
  }
12861
12276
  }
12862
12277
  };
12863
- const fleetRoot = directorMode ? path25.join(wpaths.projectSessions, session.id) : void 0;
12864
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path25.join(fleetRoot, "fleet.json") : void 0;
12865
- const sharedScratchpadPath = directorMode ? path25.join(fleetRoot, "shared") : void 0;
12866
- const subagentSessionsRoot = directorMode ? path25.join(fleetRoot, "subagents") : void 0;
12867
- const stateCheckpointPath = directorMode ? path25.join(fleetRoot, "director-state.json") : void 0;
12868
- const fleetRootForPromotion = path25.join(wpaths.projectSessions, session.id);
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);
12869
12284
  const multiAgentHost = new MultiAgentHost(
12870
12285
  {
12871
12286
  container,
@@ -12988,6 +12403,8 @@ async function main(argv) {
12988
12403
  statuslineHiddenItems: [...currentHiddenItems],
12989
12404
  setStatuslineHiddenItems,
12990
12405
  agentsMonitorController,
12406
+ configStore,
12407
+ reader,
12991
12408
  confirm: async (question, defaultYes = true) => {
12992
12409
  if (!process.stdin.isTTY) return false;
12993
12410
  const hint = defaultYes ? "[Y/n/q]" : "[y/N/q]";
@@ -13198,7 +12615,7 @@ async function main(argv) {
13198
12615
  return director.spawn(cfg);
13199
12616
  },
13200
12617
  onFleetLog: async (subagentId, mode) => {
13201
- const subagentsRoot = path25.join(fleetRootForPromotion, "subagents");
12618
+ const subagentsRoot = path24.join(fleetRootForPromotion, "subagents");
13202
12619
  let runDirs;
13203
12620
  try {
13204
12621
  runDirs = await fsp3.readdir(subagentsRoot);
@@ -13207,7 +12624,7 @@ async function main(argv) {
13207
12624
  }
13208
12625
  const found = [];
13209
12626
  for (const runId of runDirs) {
13210
- const runDir = path25.join(subagentsRoot, runId);
12627
+ const runDir = path24.join(subagentsRoot, runId);
13211
12628
  let files;
13212
12629
  try {
13213
12630
  files = await fsp3.readdir(runDir);
@@ -13216,14 +12633,14 @@ async function main(argv) {
13216
12633
  }
13217
12634
  for (const f of files) {
13218
12635
  if (!f.endsWith(".jsonl")) continue;
13219
- const full = path25.join(runDir, f);
12636
+ const full = path24.join(runDir, f);
13220
12637
  try {
13221
- const stat3 = await fsp3.stat(full);
12638
+ const stat4 = await fsp3.stat(full);
13222
12639
  found.push({
13223
12640
  runId,
13224
12641
  subagentId: f.replace(/\.jsonl$/, ""),
13225
12642
  file: full,
13226
- size: stat3.size
12643
+ size: stat4.size
13227
12644
  });
13228
12645
  } catch {
13229
12646
  }
@@ -13313,7 +12730,7 @@ async function main(argv) {
13313
12730
  }
13314
12731
  const dir = await multiAgentHost.ensureDirector();
13315
12732
  if (!dir) return "Director is not available.";
13316
- const dirStatePath = path25.join(fleetRootForPromotion, "director-state.json");
12733
+ const dirStatePath = path24.join(fleetRootForPromotion, "director-state.json");
13317
12734
  const prior = await loadDirectorState(dirStatePath);
13318
12735
  if (!prior) {
13319
12736
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -13384,9 +12801,9 @@ async function main(argv) {
13384
12801
  for (const tool of director2.tools(FLEET_ROSTER)) {
13385
12802
  toolRegistry.register(tool);
13386
12803
  }
13387
- const mp = path25.join(fleetRootForPromotion, "fleet.json");
13388
- const sp = path25.join(fleetRootForPromotion, "shared");
13389
- const ss = path25.join(fleetRootForPromotion, "subagents");
12804
+ const mp = path24.join(fleetRootForPromotion, "fleet.json");
12805
+ const sp = path24.join(fleetRootForPromotion, "shared");
12806
+ const ss = path24.join(fleetRootForPromotion, "subagents");
13390
12807
  const lines = [
13391
12808
  `${color.green("\u2713")} Promoted to director mode.`,
13392
12809
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -13483,10 +12900,10 @@ Restart WrongStack to load or unload plugin code in this session.`;
13483
12900
  void mcpRegistry.stopAll();
13484
12901
  },
13485
12902
  onBeforeExit: async () => {
13486
- const { spawn: spawn4 } = await import('child_process');
12903
+ const { spawn: spawn3 } = await import('child_process');
13487
12904
  const cwd2 = projectRoot;
13488
12905
  const statusResult = await new Promise((resolve4, reject) => {
13489
- const child = spawn4("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
12906
+ const child = spawn3("git", ["status", "--porcelain"], { cwd: cwd2, stdio: ["ignore", "pipe", "pipe"] });
13490
12907
  let stdout = "";
13491
12908
  child.stdout?.on("data", (d) => {
13492
12909
  stdout += d;
@@ -13630,6 +13047,7 @@ Restart WrongStack to load or unload plugin code in this session.`;
13630
13047
  attachments,
13631
13048
  tokenCounter,
13632
13049
  config,
13050
+ configStore,
13633
13051
  renderer,
13634
13052
  reader,
13635
13053
  session,