@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 +432 -1014
- package/dist/index.js.map +1 -1
- package/package.json +11 -11
package/dist/index.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import * as
|
|
3
|
-
import { join } from 'path';
|
|
2
|
+
import * as path24 from 'path';
|
|
4
3
|
import * as fsp3 from 'fs/promises';
|
|
5
|
-
import {
|
|
6
|
-
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, emptyPlan, clearPlan, savePlan, formatPlanTemplates, getPlanTemplate, addPlanItem, formatPlan, deriveTodosFromPlanItem, removePlanItem, setPlanItemStatus, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, InputBuilder, FsError, ERROR_CODES, projectHash, WrongStackError, defaultOrchestrator, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
4
|
+
import { color, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, makeAutonomyPromptContributor, ToolRegistry, createContextManagerTool, EventBus, SlashCommandRegistry, createDelegateTool, FLEET_ROSTER, createMcpControlTool, EternalAutonomyEngine, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, loadPlugins, FleetManager, makeDirectorSessionFactory, Director, AutoApprovePermissionPolicy, PhaseStore, AutoPhasePlanner, PhaseGraphBuilder, WorktreeManager, PhaseOrchestrator, makeLLMClassifier, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, atomicWrite, DefaultPluginAPI, makeAgentSubagentRunner, NULL_FLEET_BUS, buildChildEnv, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, AGENTS_BY_PHASE, dispatchAgent, formatTodosList, SpecStore, TaskGraphStore, analyzeCriticalPath, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, renderProgress, renderTaskGraph, loadGoal, goalFilePath, summarizeUsage, saveGoal, emptyGoal, buildGoalPreamble, formatGoal, pendingBtwCount, setBtwNote, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, InputBuilder, FsError, ERROR_CODES, SpecVersioning, ParallelEternalEngine, allServers as allServers$1 } from '@wrongstack/core';
|
|
7
5
|
import { createRequire } from 'module';
|
|
8
|
-
import * as
|
|
9
|
-
import
|
|
6
|
+
import * as os3 from 'os';
|
|
7
|
+
import os3__default from 'os';
|
|
10
8
|
import * as crypto2 from 'crypto';
|
|
11
9
|
import { randomUUID } from 'crypto';
|
|
12
10
|
import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, isSecretField, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
|
|
@@ -15,14 +13,13 @@ import { MCPRegistry } from '@wrongstack/mcp';
|
|
|
15
13
|
import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
|
|
16
14
|
import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
|
|
17
15
|
import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
|
|
18
|
-
import { spawn } from 'child_process';
|
|
19
|
-
import { SkillInstaller } from '@wrongstack/core/skills';
|
|
20
16
|
import * as readline from 'readline';
|
|
21
|
-
import * as
|
|
17
|
+
import * as fs10 from 'fs';
|
|
22
18
|
import { writeFileSync } from 'fs';
|
|
23
19
|
import { WrongStackACPServer } from '@wrongstack/acp/agent';
|
|
24
20
|
import { ACP_AGENT_COMMANDS, makeACPSubagentRunner, makeACPSubagentRunnerWithStop } from '@wrongstack/acp';
|
|
25
21
|
import { ACP_AGENTS, SubagentBudget } from '@wrongstack/core/coordination';
|
|
22
|
+
import { spawn } from 'child_process';
|
|
26
23
|
import { allServers } from '@wrongstack/core/infrastructure';
|
|
27
24
|
import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
|
|
28
25
|
import { ToolExecutor } from '@wrongstack/core/execution';
|
|
@@ -384,6 +381,7 @@ function buildSddCommand(opts) {
|
|
|
384
381
|
description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
|
|
385
382
|
async run(args) {
|
|
386
383
|
opts.context;
|
|
384
|
+
if (!opts.paths) return { message: "SDD not available \u2014 paths not configured." };
|
|
387
385
|
const specsDir = opts.paths.projectSpecs;
|
|
388
386
|
const graphsDir = opts.paths.projectTaskGraphs;
|
|
389
387
|
const specStore = new SpecStore({ baseDir: specsDir });
|
|
@@ -1398,7 +1396,7 @@ function sddHelp() {
|
|
|
1398
1396
|
async function gatherProjectContext(projectRoot) {
|
|
1399
1397
|
const parts = [];
|
|
1400
1398
|
try {
|
|
1401
|
-
const pkgPath =
|
|
1399
|
+
const pkgPath = path24.join(projectRoot, "package.json");
|
|
1402
1400
|
const pkgRaw = await fsp3.readFile(pkgPath, "utf8");
|
|
1403
1401
|
const pkg = JSON.parse(pkgRaw);
|
|
1404
1402
|
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
@@ -1414,13 +1412,13 @@ async function gatherProjectContext(projectRoot) {
|
|
|
1414
1412
|
} catch {
|
|
1415
1413
|
}
|
|
1416
1414
|
try {
|
|
1417
|
-
const tsconfigPath =
|
|
1415
|
+
const tsconfigPath = path24.join(projectRoot, "tsconfig.json");
|
|
1418
1416
|
await fsp3.access(tsconfigPath);
|
|
1419
1417
|
parts.push("Language: TypeScript");
|
|
1420
1418
|
} catch {
|
|
1421
1419
|
}
|
|
1422
1420
|
try {
|
|
1423
|
-
const srcDir =
|
|
1421
|
+
const srcDir = path24.join(projectRoot, "src");
|
|
1424
1422
|
const entries = await fsp3.readdir(srcDir, { withFileTypes: true });
|
|
1425
1423
|
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1426
1424
|
if (dirs.length > 0) {
|
|
@@ -1576,7 +1574,7 @@ __export(update_check_exports, {
|
|
|
1576
1574
|
getUpdateNotification: () => getUpdateNotification
|
|
1577
1575
|
});
|
|
1578
1576
|
function cachePath(homeFn = defaultHomeDir2) {
|
|
1579
|
-
return
|
|
1577
|
+
return path24.join(homeFn(), ".wrongstack", "update-cache.json");
|
|
1580
1578
|
}
|
|
1581
1579
|
function currentVersion() {
|
|
1582
1580
|
const req2 = createRequire(import.meta.url);
|
|
@@ -1613,7 +1611,7 @@ async function readCache(homeFn = defaultHomeDir2) {
|
|
|
1613
1611
|
}
|
|
1614
1612
|
async function writeCache(entry, homeFn = defaultHomeDir2) {
|
|
1615
1613
|
try {
|
|
1616
|
-
const dir =
|
|
1614
|
+
const dir = path24.dirname(cachePath(homeFn));
|
|
1617
1615
|
await fsp3.mkdir(dir, { recursive: true });
|
|
1618
1616
|
await fsp3.writeFile(cachePath(homeFn), JSON.stringify(entry, null, 2), "utf8");
|
|
1619
1617
|
} catch {
|
|
@@ -1686,7 +1684,7 @@ async function getUpdateNotification(signal, homeFn) {
|
|
|
1686
1684
|
var defaultHomeDir2, CACHE_TTL_MS;
|
|
1687
1685
|
var init_update_check = __esm({
|
|
1688
1686
|
"src/update-check.ts"() {
|
|
1689
|
-
defaultHomeDir2 = () =>
|
|
1687
|
+
defaultHomeDir2 = () => os3.homedir();
|
|
1690
1688
|
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1691
1689
|
}
|
|
1692
1690
|
});
|
|
@@ -2234,7 +2232,7 @@ async function runWebUI(opts) {
|
|
|
2234
2232
|
return {};
|
|
2235
2233
|
}
|
|
2236
2234
|
if (!parsed.providers) return {};
|
|
2237
|
-
const keyFile =
|
|
2235
|
+
const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
|
|
2238
2236
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
2239
2237
|
return decryptConfigSecrets$1(parsed.providers, vault);
|
|
2240
2238
|
}
|
|
@@ -2267,7 +2265,7 @@ async function runWebUI(opts) {
|
|
|
2267
2265
|
parsed = {};
|
|
2268
2266
|
}
|
|
2269
2267
|
parsed.providers = providers;
|
|
2270
|
-
const keyFile =
|
|
2268
|
+
const keyFile = path24.join(path24.dirname(opts.globalConfigPath), ".key");
|
|
2271
2269
|
const vault = new DefaultSecretVault$1({ keyFile });
|
|
2272
2270
|
const encrypted = encryptConfigSecrets(parsed, vault);
|
|
2273
2271
|
await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
@@ -2487,10 +2485,10 @@ async function detectPackageManager(root, declared) {
|
|
|
2487
2485
|
const name = declared.split("@")[0];
|
|
2488
2486
|
if (name) return name;
|
|
2489
2487
|
}
|
|
2490
|
-
if (await pathExists(
|
|
2491
|
-
if (await pathExists(
|
|
2492
|
-
if (await pathExists(
|
|
2493
|
-
if (await pathExists(
|
|
2488
|
+
if (await pathExists(path24.join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
2489
|
+
if (await pathExists(path24.join(root, "bun.lockb"))) return "bun";
|
|
2490
|
+
if (await pathExists(path24.join(root, "bun.lock"))) return "bun";
|
|
2491
|
+
if (await pathExists(path24.join(root, "yarn.lock"))) return "yarn";
|
|
2494
2492
|
return "npm";
|
|
2495
2493
|
}
|
|
2496
2494
|
function hasUsableScript(scripts, name) {
|
|
@@ -2511,7 +2509,7 @@ function parseMakeTargets(makefile) {
|
|
|
2511
2509
|
async function detectProjectFacts(root) {
|
|
2512
2510
|
const facts = { hints: [] };
|
|
2513
2511
|
try {
|
|
2514
|
-
const pkg = JSON.parse(await fsp3.readFile(
|
|
2512
|
+
const pkg = JSON.parse(await fsp3.readFile(path24.join(root, "package.json"), "utf8"));
|
|
2515
2513
|
const scripts = pkg.scripts ?? {};
|
|
2516
2514
|
const pm = await detectPackageManager(root, pkg.packageManager);
|
|
2517
2515
|
if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
|
|
@@ -2525,14 +2523,14 @@ async function detectProjectFacts(root) {
|
|
|
2525
2523
|
} catch {
|
|
2526
2524
|
}
|
|
2527
2525
|
try {
|
|
2528
|
-
if (!await pathExists(
|
|
2526
|
+
if (!await pathExists(path24.join(root, "pyproject.toml"))) throw new Error("not python");
|
|
2529
2527
|
facts.test ??= "pytest";
|
|
2530
2528
|
facts.lint ??= "ruff check .";
|
|
2531
2529
|
facts.hints.push("pyproject.toml");
|
|
2532
2530
|
} catch {
|
|
2533
2531
|
}
|
|
2534
2532
|
try {
|
|
2535
|
-
if (!await pathExists(
|
|
2533
|
+
if (!await pathExists(path24.join(root, "go.mod"))) throw new Error("not go");
|
|
2536
2534
|
facts.build ??= "go build ./...";
|
|
2537
2535
|
facts.test ??= "go test ./...";
|
|
2538
2536
|
facts.run ??= "go run .";
|
|
@@ -2540,7 +2538,7 @@ async function detectProjectFacts(root) {
|
|
|
2540
2538
|
} catch {
|
|
2541
2539
|
}
|
|
2542
2540
|
try {
|
|
2543
|
-
if (!await pathExists(
|
|
2541
|
+
if (!await pathExists(path24.join(root, "Cargo.toml"))) throw new Error("not rust");
|
|
2544
2542
|
facts.build ??= "cargo build";
|
|
2545
2543
|
facts.test ??= "cargo test";
|
|
2546
2544
|
facts.lint ??= "cargo clippy";
|
|
@@ -2549,7 +2547,7 @@ async function detectProjectFacts(root) {
|
|
|
2549
2547
|
} catch {
|
|
2550
2548
|
}
|
|
2551
2549
|
try {
|
|
2552
|
-
const makefile = await fsp3.readFile(
|
|
2550
|
+
const makefile = await fsp3.readFile(path24.join(root, "Makefile"), "utf8");
|
|
2553
2551
|
const targets = parseMakeTargets(makefile);
|
|
2554
2552
|
facts.build ??= targets.has("build") ? "make build" : "make";
|
|
2555
2553
|
if (targets.has("test")) facts.test ??= "make test";
|
|
@@ -2674,11 +2672,6 @@ function estimateTokens(messages) {
|
|
|
2674
2672
|
}
|
|
2675
2673
|
return total;
|
|
2676
2674
|
}
|
|
2677
|
-
function statusIcon(status) {
|
|
2678
|
-
if (status === "healthy") return color.green("\u25CF");
|
|
2679
|
-
if (status === "degraded") return color.yellow("\u25CF");
|
|
2680
|
-
return color.red("\u25CF");
|
|
2681
|
-
}
|
|
2682
2675
|
|
|
2683
2676
|
// src/slash-commands/clear.ts
|
|
2684
2677
|
function buildClearCommand(opts) {
|
|
@@ -2717,203 +2710,6 @@ function buildClearCommand(opts) {
|
|
|
2717
2710
|
}
|
|
2718
2711
|
};
|
|
2719
2712
|
}
|
|
2720
|
-
async function runGit(args, cwd) {
|
|
2721
|
-
try {
|
|
2722
|
-
return await new Promise((resolve4, reject) => {
|
|
2723
|
-
const child = spawn("git", args, {
|
|
2724
|
-
cwd,
|
|
2725
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
2726
|
-
});
|
|
2727
|
-
let stdout = "";
|
|
2728
|
-
let stderr = "";
|
|
2729
|
-
child.stdout?.on("data", (d) => {
|
|
2730
|
-
stdout += d;
|
|
2731
|
-
});
|
|
2732
|
-
child.stderr?.on("data", (d) => {
|
|
2733
|
-
stderr += d;
|
|
2734
|
-
});
|
|
2735
|
-
child.on("error", (err) => {
|
|
2736
|
-
reject(new WrongStackError({
|
|
2737
|
-
message: `Failed to run git: ${err.message}`,
|
|
2738
|
-
code: ERROR_CODES.TOOL_EXECUTION_FAILED,
|
|
2739
|
-
subsystem: "tool",
|
|
2740
|
-
context: { command: "git", args, cwd },
|
|
2741
|
-
cause: err
|
|
2742
|
-
}));
|
|
2743
|
-
});
|
|
2744
|
-
child.on("close", (code) => resolve4({ stdout, stderr, code: code ?? 0 }));
|
|
2745
|
-
});
|
|
2746
|
-
} catch (err) {
|
|
2747
|
-
if (err instanceof WrongStackError) throw err;
|
|
2748
|
-
throw new WrongStackError({
|
|
2749
|
-
message: err instanceof Error ? err.message : String(err),
|
|
2750
|
-
code: ERROR_CODES.TOOL_EXECUTION_FAILED,
|
|
2751
|
-
subsystem: "tool",
|
|
2752
|
-
context: { command: "git", args, cwd },
|
|
2753
|
-
cause: err
|
|
2754
|
-
});
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2757
|
-
function detectCommitType(stats) {
|
|
2758
|
-
const lines = stats.split("\n");
|
|
2759
|
-
const hasTestFiles = lines.some(
|
|
2760
|
-
(l) => l.includes("_test.") || l.includes(".test.") || l.includes(".spec.")
|
|
2761
|
-
);
|
|
2762
|
-
const hasDocs = lines.some(
|
|
2763
|
-
(l) => l.includes("README") || l.includes("CHANGELOG") || l.includes("docs/") || l.includes(".md")
|
|
2764
|
-
);
|
|
2765
|
-
const hasConfig = lines.some(
|
|
2766
|
-
(l) => l.includes("config") || l.includes("tsconfig") || l.includes(".json")
|
|
2767
|
-
);
|
|
2768
|
-
if (hasTestFiles) return "test";
|
|
2769
|
-
if (hasDocs) return "docs";
|
|
2770
|
-
if (hasConfig) return "chore";
|
|
2771
|
-
return "feat";
|
|
2772
|
-
}
|
|
2773
|
-
async function generateCommitMessageHeuristics(cwd) {
|
|
2774
|
-
const statsResult = await runGit(["diff", "--stat"], cwd);
|
|
2775
|
-
if (statsResult.code !== 0) return "chore: update";
|
|
2776
|
-
const nameResult = await runGit(["diff", "--name-only"], cwd);
|
|
2777
|
-
const files = nameResult.stdout.split("\n").filter(Boolean);
|
|
2778
|
-
const commitType = detectCommitType(statsResult.stdout);
|
|
2779
|
-
let scope = "";
|
|
2780
|
-
if (files.length > 0) {
|
|
2781
|
-
const primary = files[0].split("/")[0];
|
|
2782
|
-
if (primary && primary !== "packages" && primary !== "apps" && primary !== "node_modules") {
|
|
2783
|
-
scope = `(${primary})`;
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
if (files.length === 0) {
|
|
2787
|
-
return `${commitType}${scope}: update`;
|
|
2788
|
-
}
|
|
2789
|
-
if (files.length <= 3) {
|
|
2790
|
-
const summary2 = files.map((f) => f.split("/").pop()).join(", ");
|
|
2791
|
-
return `${commitType}${scope}: ${summary2}`;
|
|
2792
|
-
}
|
|
2793
|
-
const summary = files.slice(0, 3).map((f) => f.split("/").pop()).join(", ") + ` and ${files.length - 3} more`;
|
|
2794
|
-
return `${commitType}${scope}: ${summary}`;
|
|
2795
|
-
}
|
|
2796
|
-
async function hasUncommittedChanges(cwd) {
|
|
2797
|
-
const result = await runGit(["status", "--porcelain"], cwd);
|
|
2798
|
-
return result.stdout.trim().length > 0;
|
|
2799
|
-
}
|
|
2800
|
-
async function isGitRepo(cwd) {
|
|
2801
|
-
const result = await runGit(["rev-parse", "--git-dir"], cwd);
|
|
2802
|
-
return result.code === 0;
|
|
2803
|
-
}
|
|
2804
|
-
function buildCommitCommand(_opts, generateCommitMessage) {
|
|
2805
|
-
return {
|
|
2806
|
-
name: "commit",
|
|
2807
|
-
description: "Stage all changes and commit with auto-generated message.",
|
|
2808
|
-
aliases: ["gc"],
|
|
2809
|
-
async run(args, ctx) {
|
|
2810
|
-
const cwd = ctx?.cwd ?? process.cwd();
|
|
2811
|
-
if (!await isGitRepo(cwd)) {
|
|
2812
|
-
return { message: "Not a git repository." };
|
|
2813
|
-
}
|
|
2814
|
-
if (!await hasUncommittedChanges(cwd)) {
|
|
2815
|
-
return { message: "Nothing to commit (working tree clean)." };
|
|
2816
|
-
}
|
|
2817
|
-
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
2818
|
-
args.includes("--no-llm");
|
|
2819
|
-
let message;
|
|
2820
|
-
{
|
|
2821
|
-
message = await generateCommitMessageHeuristics(cwd);
|
|
2822
|
-
}
|
|
2823
|
-
if (dryRun) {
|
|
2824
|
-
return {
|
|
2825
|
-
message: `Would commit:
|
|
2826
|
-
|
|
2827
|
-
${color.green(message)}
|
|
2828
|
-
|
|
2829
|
-
${color.dim("(dry-run \u2014 no actual commit)")}`
|
|
2830
|
-
};
|
|
2831
|
-
}
|
|
2832
|
-
const stageResult = await runGit(["add", "."], cwd);
|
|
2833
|
-
if (stageResult.code !== 0) {
|
|
2834
|
-
return { message: `Stage failed: ${stageResult.stderr}` };
|
|
2835
|
-
}
|
|
2836
|
-
const commitResult = await runGit(["commit", "-m", message], cwd);
|
|
2837
|
-
if (commitResult.code !== 0) {
|
|
2838
|
-
return { message: `Commit failed: ${commitResult.stderr}` };
|
|
2839
|
-
}
|
|
2840
|
-
const hashResult = await runGit(["rev-parse", "--short", "HEAD"], cwd);
|
|
2841
|
-
const hash = hashResult.stdout.trim();
|
|
2842
|
-
const pushResult = await runGit(["remote"], cwd);
|
|
2843
|
-
const hasRemote = pushResult.stdout.trim().length > 0;
|
|
2844
|
-
let pushMsg = "";
|
|
2845
|
-
if (hasRemote) {
|
|
2846
|
-
pushMsg = `
|
|
2847
|
-
|
|
2848
|
-
${color.dim("Tip: Run /push to push to remote")}`;
|
|
2849
|
-
}
|
|
2850
|
-
return {
|
|
2851
|
-
message: `${color.green("\u2713")} Committed: ${color.bold(message)}
|
|
2852
|
-
${color.dim(hash)}${pushMsg}`
|
|
2853
|
-
};
|
|
2854
|
-
}
|
|
2855
|
-
};
|
|
2856
|
-
}
|
|
2857
|
-
function buildGitcheckCommand(_opts) {
|
|
2858
|
-
return {
|
|
2859
|
-
name: "gitcheck",
|
|
2860
|
-
description: "Check for uncommitted changes (for system prompt integration).",
|
|
2861
|
-
aliases: ["gcstatus"],
|
|
2862
|
-
async run(_args, ctx) {
|
|
2863
|
-
const cwd = ctx?.cwd ?? process.cwd();
|
|
2864
|
-
if (!await isGitRepo(cwd)) {
|
|
2865
|
-
return { message: "" };
|
|
2866
|
-
}
|
|
2867
|
-
if (!await hasUncommittedChanges(cwd)) {
|
|
2868
|
-
return { message: "" };
|
|
2869
|
-
}
|
|
2870
|
-
const statusResult = await runGit(["status", "--porcelain"], cwd);
|
|
2871
|
-
const lines = statusResult.stdout.split("\n").filter(Boolean);
|
|
2872
|
-
const count = lines.length;
|
|
2873
|
-
if (count === 0) return { message: "" };
|
|
2874
|
-
return {
|
|
2875
|
-
message: `\u26A0 ${color.yellow(`${count} uncommitted change${count > 1 ? "s" : ""}`)} \u2014 consider /commit`
|
|
2876
|
-
};
|
|
2877
|
-
}
|
|
2878
|
-
};
|
|
2879
|
-
}
|
|
2880
|
-
function buildPushCommand(_opts) {
|
|
2881
|
-
return {
|
|
2882
|
-
name: "push",
|
|
2883
|
-
description: "Push to remote after commit.",
|
|
2884
|
-
async run(args, ctx) {
|
|
2885
|
-
const cwd = ctx?.cwd ?? process.cwd();
|
|
2886
|
-
if (!await isGitRepo(cwd)) {
|
|
2887
|
-
return { message: "Not a git repository." };
|
|
2888
|
-
}
|
|
2889
|
-
const dryRun = args.includes("--dry-run") || args.includes("-n");
|
|
2890
|
-
const force = args.includes("--force") || args.includes("-f");
|
|
2891
|
-
const remoteResult = await runGit(["remote"], cwd);
|
|
2892
|
-
const remotes = remoteResult.stdout.split("\n").filter(Boolean);
|
|
2893
|
-
if (remotes.length === 0) {
|
|
2894
|
-
return { message: "No remote configured. Add one with: git remote add origin <url>" };
|
|
2895
|
-
}
|
|
2896
|
-
if (dryRun) {
|
|
2897
|
-
return {
|
|
2898
|
-
message: `Would push to ${remotes.join(", ")}${force ? " (force)" : ""}
|
|
2899
|
-
${color.dim("(dry-run)")}`
|
|
2900
|
-
};
|
|
2901
|
-
}
|
|
2902
|
-
const branchResult = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
2903
|
-
const branch = branchResult.stdout.trim() || "main";
|
|
2904
|
-
const pushArgs = ["push"];
|
|
2905
|
-
if (force) pushArgs.push("--force");
|
|
2906
|
-
pushArgs.push(...remotes, branch);
|
|
2907
|
-
const pushResult = await runGit(pushArgs, cwd);
|
|
2908
|
-
if (pushResult.code !== 0) {
|
|
2909
|
-
return { message: `Push failed: ${pushResult.stderr}` };
|
|
2910
|
-
}
|
|
2911
|
-
return {
|
|
2912
|
-
message: `${color.green("\u2713")} Pushed to ${remotes.join(", ")} (${branch})`
|
|
2913
|
-
};
|
|
2914
|
-
}
|
|
2915
|
-
};
|
|
2916
|
-
}
|
|
2917
2713
|
|
|
2918
2714
|
// src/slash-commands/compact.ts
|
|
2919
2715
|
function buildCompactCommand(opts) {
|
|
@@ -3355,25 +3151,6 @@ function buildFleetCommand(opts) {
|
|
|
3355
3151
|
}
|
|
3356
3152
|
};
|
|
3357
3153
|
}
|
|
3358
|
-
function buildHealthCommand(opts) {
|
|
3359
|
-
return {
|
|
3360
|
-
name: "health",
|
|
3361
|
-
description: "Run health checks (requires --metrics flag).",
|
|
3362
|
-
async run() {
|
|
3363
|
-
if (!opts.healthRegistry)
|
|
3364
|
-
return { message: "Health checks not enabled. Restart with --metrics." };
|
|
3365
|
-
const result = await opts.healthRegistry.run();
|
|
3366
|
-
const lines = [
|
|
3367
|
-
`${statusIcon(result.status)} overall: ${result.status}`,
|
|
3368
|
-
...result.checks.map((c) => {
|
|
3369
|
-
const detail = c.detail ? color.dim(` \u2014 ${c.detail}`) : "";
|
|
3370
|
-
return ` ${statusIcon(c.status)} ${c.name}: ${c.status}${detail}`;
|
|
3371
|
-
})
|
|
3372
|
-
];
|
|
3373
|
-
return { message: lines.join("\n") };
|
|
3374
|
-
}
|
|
3375
|
-
};
|
|
3376
|
-
}
|
|
3377
3154
|
|
|
3378
3155
|
// src/slash-commands/help.ts
|
|
3379
3156
|
function buildHelpCommand(opts) {
|
|
@@ -3441,8 +3218,8 @@ function buildInitCommand(opts) {
|
|
|
3441
3218
|
name: "init",
|
|
3442
3219
|
description: "Create or update .wrongstack/AGENTS.md project context for the system prompt.",
|
|
3443
3220
|
async run(_args, ctx) {
|
|
3444
|
-
const dir =
|
|
3445
|
-
const file =
|
|
3221
|
+
const dir = path24.join(ctx.projectRoot, ".wrongstack");
|
|
3222
|
+
const file = path24.join(dir, "AGENTS.md");
|
|
3446
3223
|
const detected = await detectProjectFacts(ctx.projectRoot);
|
|
3447
3224
|
const body = renderAgentsTemplate(detected);
|
|
3448
3225
|
await fsp3.mkdir(dir, { recursive: true });
|
|
@@ -3633,18 +3410,18 @@ function stateBadge(state) {
|
|
|
3633
3410
|
return color.dim(state);
|
|
3634
3411
|
}
|
|
3635
3412
|
}
|
|
3636
|
-
async function readConfig(
|
|
3413
|
+
async function readConfig(path25) {
|
|
3637
3414
|
try {
|
|
3638
|
-
return JSON.parse(await fsp3.readFile(
|
|
3415
|
+
return JSON.parse(await fsp3.readFile(path25, "utf8"));
|
|
3639
3416
|
} catch {
|
|
3640
3417
|
return {};
|
|
3641
3418
|
}
|
|
3642
3419
|
}
|
|
3643
|
-
async function writeConfig(
|
|
3420
|
+
async function writeConfig(path25, cfg) {
|
|
3644
3421
|
const raw = JSON.stringify(cfg, null, 2);
|
|
3645
|
-
const tmp =
|
|
3422
|
+
const tmp = path25 + ".tmp";
|
|
3646
3423
|
await fsp3.writeFile(tmp, raw, "utf8");
|
|
3647
|
-
await fsp3.rename(tmp,
|
|
3424
|
+
await fsp3.rename(tmp, path25);
|
|
3648
3425
|
}
|
|
3649
3426
|
|
|
3650
3427
|
// src/slash-commands/mcp.ts
|
|
@@ -3726,150 +3503,6 @@ function buildMemoryCommand(opts) {
|
|
|
3726
3503
|
}
|
|
3727
3504
|
};
|
|
3728
3505
|
}
|
|
3729
|
-
function buildMetricsCommand(opts) {
|
|
3730
|
-
return {
|
|
3731
|
-
name: "metrics",
|
|
3732
|
-
description: "Show metrics snapshot (requires --metrics flag).",
|
|
3733
|
-
async run() {
|
|
3734
|
-
if (!opts.metricsSink)
|
|
3735
|
-
return { message: "Metrics not enabled. Restart with --metrics to collect." };
|
|
3736
|
-
const snap = opts.metricsSink.snapshot();
|
|
3737
|
-
if (snap.series.length === 0) return { message: "No metrics recorded yet." };
|
|
3738
|
-
const lines = [];
|
|
3739
|
-
const byName = /* @__PURE__ */ new Map();
|
|
3740
|
-
for (const s of snap.series) {
|
|
3741
|
-
const bucket = byName.get(s.name) ?? [];
|
|
3742
|
-
bucket.push(s);
|
|
3743
|
-
byName.set(s.name, bucket);
|
|
3744
|
-
}
|
|
3745
|
-
for (const [name, series] of [...byName.entries()].sort()) {
|
|
3746
|
-
lines.push(color.dim(`# ${name}`));
|
|
3747
|
-
for (const s of series) {
|
|
3748
|
-
const labels = Object.entries(s.labels).map(([k, v]) => `${k}=${v}`).join(" ");
|
|
3749
|
-
const labelStr = labels ? color.dim(` {${labels}}`) : "";
|
|
3750
|
-
if (s.type === "histogram")
|
|
3751
|
-
lines.push(
|
|
3752
|
-
` count=${s.values.count} sum=${s.values.sum} min=${s.values.min} max=${s.values.max} p50=${s.values.p50} p95=${s.values.p95} p99=${s.values.p99}${labelStr}`
|
|
3753
|
-
);
|
|
3754
|
-
else lines.push(` ${s.values.value}${labelStr}`);
|
|
3755
|
-
}
|
|
3756
|
-
}
|
|
3757
|
-
return { message: lines.join("\n") };
|
|
3758
|
-
}
|
|
3759
|
-
};
|
|
3760
|
-
}
|
|
3761
|
-
function buildPlanCommand(opts) {
|
|
3762
|
-
return {
|
|
3763
|
-
name: "plan",
|
|
3764
|
-
description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
|
|
3765
|
-
async run(args) {
|
|
3766
|
-
const planPath = opts.paths?.projectPlan ?? opts.planPath;
|
|
3767
|
-
if (!planPath) return { message: "Plan storage is not configured for this session." };
|
|
3768
|
-
const ctx = opts.context;
|
|
3769
|
-
const sessionId = ctx?.session.id ?? "unknown";
|
|
3770
|
-
const [verb, ...rest] = args.trim().split(/\s+/);
|
|
3771
|
-
const restJoined = rest.join(" ").trim();
|
|
3772
|
-
const plan = await loadPlan(planPath) ?? emptyPlan(sessionId);
|
|
3773
|
-
switch (verb) {
|
|
3774
|
-
case "":
|
|
3775
|
-
case "show":
|
|
3776
|
-
case "list": {
|
|
3777
|
-
return { message: formatPlan(plan) };
|
|
3778
|
-
}
|
|
3779
|
-
case "add": {
|
|
3780
|
-
if (!restJoined) return { message: "Usage: /plan add <title>" };
|
|
3781
|
-
const { plan: updated, item } = addPlanItem(plan, restJoined);
|
|
3782
|
-
await savePlan(planPath, updated);
|
|
3783
|
-
return { message: `Added: ${item.title}
|
|
3784
|
-
${formatPlan(updated)}` };
|
|
3785
|
-
}
|
|
3786
|
-
case "start":
|
|
3787
|
-
case "progress": {
|
|
3788
|
-
if (!restJoined) return { message: "Usage: /plan start <id|index>" };
|
|
3789
|
-
const updated = setPlanItemStatus(plan, restJoined, "in_progress");
|
|
3790
|
-
await savePlan(planPath, updated);
|
|
3791
|
-
return { message: formatPlan(updated) };
|
|
3792
|
-
}
|
|
3793
|
-
case "done":
|
|
3794
|
-
case "complete": {
|
|
3795
|
-
if (!restJoined) return { message: "Usage: /plan done <id|index>" };
|
|
3796
|
-
const updated = setPlanItemStatus(plan, restJoined, "done");
|
|
3797
|
-
await savePlan(planPath, updated);
|
|
3798
|
-
return { message: formatPlan(updated) };
|
|
3799
|
-
}
|
|
3800
|
-
case "remove":
|
|
3801
|
-
case "delete":
|
|
3802
|
-
case "rm": {
|
|
3803
|
-
if (!restJoined) return { message: "Usage: /plan remove <id|index>" };
|
|
3804
|
-
const updated = removePlanItem(plan, restJoined);
|
|
3805
|
-
await savePlan(planPath, updated);
|
|
3806
|
-
return { message: formatPlan(updated) };
|
|
3807
|
-
}
|
|
3808
|
-
case "promote": {
|
|
3809
|
-
if (!restJoined) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3810
|
-
const [target, ...subtasks] = restJoined.split(/\s+/);
|
|
3811
|
-
if (!target) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
|
|
3812
|
-
const derived = deriveTodosFromPlanItem(plan, target, subtasks.length > 0 ? subtasks : void 0);
|
|
3813
|
-
if (!derived) return { message: `No plan item matched "${target}".` };
|
|
3814
|
-
await savePlan(planPath, derived.plan);
|
|
3815
|
-
if (ctx) {
|
|
3816
|
-
ctx.state.replaceTodos(derived.todos);
|
|
3817
|
-
}
|
|
3818
|
-
return {
|
|
3819
|
-
message: `Promoted to ${derived.todos.length} todo(s):
|
|
3820
|
-
${formatTodosList(derived.todos)}
|
|
3821
|
-
|
|
3822
|
-
${formatPlan(derived.plan)}`
|
|
3823
|
-
};
|
|
3824
|
-
}
|
|
3825
|
-
case "derive": {
|
|
3826
|
-
if (!restJoined) return { message: "Usage: /plan derive <id|index>" };
|
|
3827
|
-
const derived = deriveTodosFromPlanItem(plan, restJoined);
|
|
3828
|
-
if (!derived) return { message: `No plan item matched "${restJoined}".` };
|
|
3829
|
-
await savePlan(planPath, derived.plan);
|
|
3830
|
-
if (ctx) {
|
|
3831
|
-
ctx.state.replaceTodos(derived.todos);
|
|
3832
|
-
}
|
|
3833
|
-
return {
|
|
3834
|
-
message: `Derived ${derived.todos.length} todo(s):
|
|
3835
|
-
${formatTodosList(derived.todos)}
|
|
3836
|
-
|
|
3837
|
-
${formatPlan(derived.plan)}`
|
|
3838
|
-
};
|
|
3839
|
-
}
|
|
3840
|
-
case "template": {
|
|
3841
|
-
const subVerb = rest[0] ?? "";
|
|
3842
|
-
const subRest = rest.slice(1).join(" ").trim();
|
|
3843
|
-
if (subVerb === "" || subVerb === "list") {
|
|
3844
|
-
return { message: formatPlanTemplates() };
|
|
3845
|
-
}
|
|
3846
|
-
if (subVerb === "use") {
|
|
3847
|
-
if (!subRest) return { message: "Usage: /plan template use <template-name>" };
|
|
3848
|
-
const template = getPlanTemplate(subRest);
|
|
3849
|
-
if (!template) return { message: `Unknown template "${subRest}". Use /plan template list to see available templates.` };
|
|
3850
|
-
let updated = plan;
|
|
3851
|
-
for (const item of template.items) {
|
|
3852
|
-
({ plan: updated } = addPlanItem(updated, item.title, item.details));
|
|
3853
|
-
}
|
|
3854
|
-
await savePlan(planPath, updated);
|
|
3855
|
-
return { message: `Applied template "${template.name}" (${template.items.length} items):
|
|
3856
|
-
${formatPlan(updated)}` };
|
|
3857
|
-
}
|
|
3858
|
-
return { message: `Unknown template subcommand "${subVerb}". Try: list | use <name>` };
|
|
3859
|
-
}
|
|
3860
|
-
case "clear": {
|
|
3861
|
-
const updated = clearPlan(plan);
|
|
3862
|
-
await savePlan(planPath, updated);
|
|
3863
|
-
return { message: "Plan cleared." };
|
|
3864
|
-
}
|
|
3865
|
-
default:
|
|
3866
|
-
return {
|
|
3867
|
-
message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | promote <id|#> | derive <id|#> | template [list|use <name>] | clear`
|
|
3868
|
-
};
|
|
3869
|
-
}
|
|
3870
|
-
}
|
|
3871
|
-
};
|
|
3872
|
-
}
|
|
3873
3506
|
|
|
3874
3507
|
// src/slash-commands/plugin.ts
|
|
3875
3508
|
function buildPluginCommand(opts) {
|
|
@@ -3956,30 +3589,6 @@ function buildExitCommand(opts) {
|
|
|
3956
3589
|
}
|
|
3957
3590
|
};
|
|
3958
3591
|
}
|
|
3959
|
-
function buildSkillCommand(opts) {
|
|
3960
|
-
return {
|
|
3961
|
-
name: "skill",
|
|
3962
|
-
description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
|
|
3963
|
-
async run(args) {
|
|
3964
|
-
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
3965
|
-
if (!args.trim()) {
|
|
3966
|
-
const entries = await opts.skillLoader.listEntries();
|
|
3967
|
-
if (entries.length === 0) return { message: "No skills found." };
|
|
3968
|
-
const lines = entries.map((e) => {
|
|
3969
|
-
const scopeTag = e.scope.length > 0 ? ` ${color.dim(`(${e.scope.slice(0, 3).join(", ")})`)}` : "";
|
|
3970
|
-
return ` ${color.bold(e.name)}${scopeTag}
|
|
3971
|
-
Use when: ${e.trigger}`;
|
|
3972
|
-
});
|
|
3973
|
-
return { message: `Available skills:
|
|
3974
|
-
${lines.join("\n\n")}
|
|
3975
|
-
` };
|
|
3976
|
-
}
|
|
3977
|
-
const skill = await opts.skillLoader.find(args.trim());
|
|
3978
|
-
if (!skill) return { message: `Skill "${args.trim()}" not found.` };
|
|
3979
|
-
return { message: await opts.skillLoader.readBody(skill.name) };
|
|
3980
|
-
}
|
|
3981
|
-
};
|
|
3982
|
-
}
|
|
3983
3592
|
|
|
3984
3593
|
// src/slash-commands/spawn-agents.ts
|
|
3985
3594
|
function buildSpawnCommand(opts) {
|
|
@@ -4439,6 +4048,7 @@ function buildGoalCommand(opts) {
|
|
|
4439
4048
|
const [verbRaw, ...rest] = trimmed.split(/\s+/);
|
|
4440
4049
|
const verb = (verbRaw ?? "").toLowerCase();
|
|
4441
4050
|
const restJoined = rest.join(" ").trim();
|
|
4051
|
+
if (!opts.paths) return { message: "Goal not available \u2014 paths not configured." };
|
|
4442
4052
|
const goalPath = opts.paths.projectGoal;
|
|
4443
4053
|
const verbForDispatch = verb && !KNOWN_VERBS.has(verb) ? "set" : verb;
|
|
4444
4054
|
const setText = verbForDispatch === "set" && !KNOWN_VERBS.has(verb) ? trimmed : restJoined;
|
|
@@ -4616,290 +4226,6 @@ ${targetMode.description}`
|
|
|
4616
4226
|
|
|
4617
4227
|
// src/slash-commands/index.ts
|
|
4618
4228
|
init_sdd();
|
|
4619
|
-
|
|
4620
|
-
// src/slash-commands/skill-generator.ts
|
|
4621
|
-
function buildSkillGeneratorCommand(opts) {
|
|
4622
|
-
return {
|
|
4623
|
-
name: "skill-gen",
|
|
4624
|
-
description: "Create a new AI skill interactively. The AI will guide you.",
|
|
4625
|
-
help: [
|
|
4626
|
-
"\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557",
|
|
4627
|
-
"",
|
|
4628
|
-
"Create new AI skills with AI guidance.",
|
|
4629
|
-
"",
|
|
4630
|
-
"Usage:",
|
|
4631
|
-
" /skill-gen Start skill creation",
|
|
4632
|
-
" /skill-gen list List existing skills",
|
|
4633
|
-
" /skill-gen edit <name> View an existing skill",
|
|
4634
|
-
"",
|
|
4635
|
-
"The AI will ask you questions and create the skill file.",
|
|
4636
|
-
"Skills are saved to .wrongstack/skills/<name>/SKILL.md"
|
|
4637
|
-
].join("\n"),
|
|
4638
|
-
async run(args) {
|
|
4639
|
-
const trimmed = args.trim();
|
|
4640
|
-
if (trimmed === "list" || trimmed === "ls") {
|
|
4641
|
-
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
4642
|
-
const entries = await opts.skillLoader.listEntries();
|
|
4643
|
-
if (entries.length === 0) return { message: "No skills found." };
|
|
4644
|
-
const lines = entries.map((e) => {
|
|
4645
|
-
const src = e.source === "project" ? "\u{1F4C1}" : e.source === "user" ? "\u{1F464}" : "\u{1F4E6}";
|
|
4646
|
-
return ` ${src} ${e.name}
|
|
4647
|
-
${e.trigger}`;
|
|
4648
|
-
});
|
|
4649
|
-
return { message: `Available Skills:
|
|
4650
|
-
${lines.join("\n\n")}
|
|
4651
|
-
` };
|
|
4652
|
-
}
|
|
4653
|
-
if (trimmed.startsWith("edit ")) {
|
|
4654
|
-
const skillName = trimmed.slice(5).trim();
|
|
4655
|
-
if (!opts.skillLoader) return { message: "No skill loader configured." };
|
|
4656
|
-
const skill = await opts.skillLoader.find(skillName);
|
|
4657
|
-
if (!skill) return { message: `Skill "${skillName}" not found.` };
|
|
4658
|
-
const body = await opts.skillLoader.readBody(skillName);
|
|
4659
|
-
return {
|
|
4660
|
-
message: [
|
|
4661
|
-
`Skill: ${skillName}`,
|
|
4662
|
-
`Path: ${skill.path}`,
|
|
4663
|
-
"",
|
|
4664
|
-
body
|
|
4665
|
-
].join("\n")
|
|
4666
|
-
};
|
|
4667
|
-
}
|
|
4668
|
-
return {
|
|
4669
|
-
message: "\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557\n\nThe AI will guide you through creating a new skill.\nAnswer its questions naturally.",
|
|
4670
|
-
runText: "I want to create a new AI skill. Read the skill-creator skill and guide me through the process. Ask me questions one at a time \u2014 name, description, what to cover \u2014 then create the SKILL.md file."
|
|
4671
|
-
};
|
|
4672
|
-
}
|
|
4673
|
-
};
|
|
4674
|
-
}
|
|
4675
|
-
function getProviderFromContext(ctx, opts) {
|
|
4676
|
-
if (opts.llmProvider && typeof opts.llmProvider.complete === "function") {
|
|
4677
|
-
return { provider: opts.llmProvider, model: opts.llmModel };
|
|
4678
|
-
}
|
|
4679
|
-
if (ctx.provider && typeof ctx.provider.complete === "function") {
|
|
4680
|
-
return { provider: ctx.provider, model: ctx.model };
|
|
4681
|
-
}
|
|
4682
|
-
return null;
|
|
4683
|
-
}
|
|
4684
|
-
function buildSecurityCommand(opts) {
|
|
4685
|
-
return {
|
|
4686
|
-
name: "security",
|
|
4687
|
-
description: "Security scanning: scan, audit, report",
|
|
4688
|
-
argsHint: "[scan|audit|report] [options]",
|
|
4689
|
-
help: `
|
|
4690
|
-
# /security \u2014 Security Scanner
|
|
4691
|
-
|
|
4692
|
-
Automated security scanning with tech stack detection.
|
|
4693
|
-
|
|
4694
|
-
## Commands
|
|
4695
|
-
|
|
4696
|
-
### /security scan [options]
|
|
4697
|
-
Run a full security scan on the current project.
|
|
4698
|
-
Options:
|
|
4699
|
-
--depth quick|standard|deep Scan depth (default: standard)
|
|
4700
|
-
--format markdown|json|html Report format (default: markdown)
|
|
4701
|
-
|
|
4702
|
-
### /security audit
|
|
4703
|
-
Run dependency audit + security scan.
|
|
4704
|
-
|
|
4705
|
-
### /security report [id]
|
|
4706
|
-
List or view security reports.
|
|
4707
|
-
|
|
4708
|
-
## Examples
|
|
4709
|
-
|
|
4710
|
-
/security scan
|
|
4711
|
-
/security scan --depth deep --format html
|
|
4712
|
-
/security audit
|
|
4713
|
-
/security report
|
|
4714
|
-
`,
|
|
4715
|
-
async run(args, ctx) {
|
|
4716
|
-
const parts = args.trim().split(/\s+/);
|
|
4717
|
-
const subcommand = parts[0] || "";
|
|
4718
|
-
switch (subcommand) {
|
|
4719
|
-
case "scan":
|
|
4720
|
-
return handleScan(parts.slice(1).join(" "), ctx, opts);
|
|
4721
|
-
case "audit":
|
|
4722
|
-
return handleAudit(ctx, opts);
|
|
4723
|
-
case "report":
|
|
4724
|
-
return handleReport(parts[1] || "");
|
|
4725
|
-
default:
|
|
4726
|
-
return { message: getHelpMessage() };
|
|
4727
|
-
}
|
|
4728
|
-
}
|
|
4729
|
-
};
|
|
4730
|
-
}
|
|
4731
|
-
async function handleScan(args, ctx, opts) {
|
|
4732
|
-
const options = parseArgs2(args);
|
|
4733
|
-
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
4734
|
-
try {
|
|
4735
|
-
const providerInfo = getProviderFromContext(ctx, opts);
|
|
4736
|
-
if (!providerInfo) {
|
|
4737
|
-
return { message: "\u274C Security scan requires an active LLM provider. No provider configured." };
|
|
4738
|
-
}
|
|
4739
|
-
const result = await defaultOrchestrator.run(providerInfo, {
|
|
4740
|
-
projectRoot,
|
|
4741
|
-
scanOptions: {
|
|
4742
|
-
depth: options.depth || "standard",
|
|
4743
|
-
includeSecrets: true,
|
|
4744
|
-
includeInjection: true,
|
|
4745
|
-
includeConfig: true
|
|
4746
|
-
},
|
|
4747
|
-
reportOptions: {
|
|
4748
|
-
format: options.format || "markdown"
|
|
4749
|
-
}
|
|
4750
|
-
});
|
|
4751
|
-
const summary = result.scanResult.summary;
|
|
4752
|
-
const status = summary.total === 0 ? "\u2705 No issues found" : `\u26A0\uFE0F Found ${summary.total} issues`;
|
|
4753
|
-
const reportContent = result.synthesizedReport || `# Security Scan Complete
|
|
4754
|
-
|
|
4755
|
-
**Project:** ${projectRoot}
|
|
4756
|
-
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
4757
|
-
**Scanned Files:** ${result.scanResult.scannedFiles}
|
|
4758
|
-
**Duration:** ${result.scanResult.scanDurationMs}ms
|
|
4759
|
-
|
|
4760
|
-
## Summary
|
|
4761
|
-
|
|
4762
|
-
| Severity | Count |
|
|
4763
|
-
|----------|-------|
|
|
4764
|
-
| \u{1F534} Critical | ${summary.critical} |
|
|
4765
|
-
| \u{1F7E0} High | ${summary.high} |
|
|
4766
|
-
| \u{1F7E1} Medium | ${summary.medium} |
|
|
4767
|
-
| \u{1F7E2} Low | ${summary.low} |
|
|
4768
|
-
|
|
4769
|
-
**Status:** ${status}
|
|
4770
|
-
|
|
4771
|
-
**Report:** ${result.reportPath}
|
|
4772
|
-
`;
|
|
4773
|
-
return {
|
|
4774
|
-
message: reportContent,
|
|
4775
|
-
metadata: {
|
|
4776
|
-
scanResult: result.scanResult,
|
|
4777
|
-
reportPath: result.reportPath,
|
|
4778
|
-
techStack: result.detectionResult.detectedStacks[0]
|
|
4779
|
-
}
|
|
4780
|
-
};
|
|
4781
|
-
} catch (error) {
|
|
4782
|
-
return { message: `\u274C Scan failed: ${error}` };
|
|
4783
|
-
}
|
|
4784
|
-
}
|
|
4785
|
-
async function handleAudit(ctx, opts) {
|
|
4786
|
-
const projectRoot = ctx.projectRoot || opts.projectRoot;
|
|
4787
|
-
try {
|
|
4788
|
-
const providerInfo = getProviderFromContext(ctx, opts);
|
|
4789
|
-
if (!providerInfo) {
|
|
4790
|
-
return { message: "\u274C Security audit requires an active LLM provider. No provider configured." };
|
|
4791
|
-
}
|
|
4792
|
-
const result = await defaultOrchestrator.run(providerInfo, {
|
|
4793
|
-
projectRoot,
|
|
4794
|
-
reportOptions: { format: "markdown" }
|
|
4795
|
-
});
|
|
4796
|
-
const summary = result.scanResult.summary;
|
|
4797
|
-
const depIssues = summary.critical + summary.high;
|
|
4798
|
-
const reportContent = result.synthesizedReport || `# Security Audit Complete
|
|
4799
|
-
|
|
4800
|
-
**Project:** ${projectRoot}
|
|
4801
|
-
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
4802
|
-
|
|
4803
|
-
## Dependency Health
|
|
4804
|
-
|
|
4805
|
-
| Status | Count |
|
|
4806
|
-
|--------|-------|
|
|
4807
|
-
| Critical Issues | ${summary.critical} |
|
|
4808
|
-
| High Priority | ${summary.high} |
|
|
4809
|
-
| Medium Priority | ${summary.medium} |
|
|
4810
|
-
| Low Priority | ${summary.low} |
|
|
4811
|
-
|
|
4812
|
-
${depIssues === 0 ? "\u2705 No known vulnerabilities detected" : `\u26A0\uFE0F ${depIssues} vulnerabilities need attention`}
|
|
4813
|
-
|
|
4814
|
-
**Full Report:** ${result.reportPath}
|
|
4815
|
-
`;
|
|
4816
|
-
return {
|
|
4817
|
-
message: reportContent,
|
|
4818
|
-
metadata: {
|
|
4819
|
-
scanResult: result.scanResult,
|
|
4820
|
-
reportPath: result.reportPath
|
|
4821
|
-
}
|
|
4822
|
-
};
|
|
4823
|
-
} catch (error) {
|
|
4824
|
-
return { message: `\u274C Audit failed: ${error}` };
|
|
4825
|
-
}
|
|
4826
|
-
}
|
|
4827
|
-
async function handleReport(reportId) {
|
|
4828
|
-
const reportsDir = "security-reports";
|
|
4829
|
-
try {
|
|
4830
|
-
const files = await readdir(reportsDir);
|
|
4831
|
-
const reports = files.filter((f) => f.startsWith("security-report-") && (f.endsWith(".md") || f.endsWith(".json"))).sort().reverse();
|
|
4832
|
-
if (!reportId) {
|
|
4833
|
-
if (reports.length === 0) {
|
|
4834
|
-
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4835
|
-
}
|
|
4836
|
-
const list = reports.map((r, i) => {
|
|
4837
|
-
const date = r.replace("security-report-", "").replace(/\.(md|json)$/, "");
|
|
4838
|
-
return ` ${i + 1}. ${date}`;
|
|
4839
|
-
}).join("\n");
|
|
4840
|
-
return { message: `# Available Security Reports
|
|
4841
|
-
|
|
4842
|
-
${list}
|
|
4843
|
-
|
|
4844
|
-
Use \`/security report <number>\` to view a specific report.` };
|
|
4845
|
-
}
|
|
4846
|
-
const index = Number.parseInt(reportId, 10) - 1;
|
|
4847
|
-
if (!Number.isNaN(index) && reports[index]) {
|
|
4848
|
-
const content = await readFile(join(reportsDir, reports[index]), "utf-8");
|
|
4849
|
-
return { message: `# Security Report
|
|
4850
|
-
|
|
4851
|
-
${content}` };
|
|
4852
|
-
}
|
|
4853
|
-
const match = reports.find((r) => r.includes(reportId));
|
|
4854
|
-
if (match) {
|
|
4855
|
-
const content = await readFile(join(reportsDir, match), "utf-8");
|
|
4856
|
-
return { message: `# Security Report
|
|
4857
|
-
|
|
4858
|
-
${content}` };
|
|
4859
|
-
}
|
|
4860
|
-
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
4861
|
-
} catch {
|
|
4862
|
-
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
4863
|
-
}
|
|
4864
|
-
}
|
|
4865
|
-
function parseArgs2(args) {
|
|
4866
|
-
const result = {};
|
|
4867
|
-
const parts = args.split(/\s+/);
|
|
4868
|
-
for (let i = 0; i < parts.length; i++) {
|
|
4869
|
-
const part = parts[i];
|
|
4870
|
-
if (!part || !part.startsWith("--")) continue;
|
|
4871
|
-
const key = part.slice(2);
|
|
4872
|
-
const next = parts[i + 1];
|
|
4873
|
-
if (next && !next.startsWith("--")) {
|
|
4874
|
-
result[key] = next;
|
|
4875
|
-
i++;
|
|
4876
|
-
} else {
|
|
4877
|
-
result[key] = "true";
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
4880
|
-
return result;
|
|
4881
|
-
}
|
|
4882
|
-
function getHelpMessage() {
|
|
4883
|
-
return `# /security \u2014 Security Scanner
|
|
4884
|
-
|
|
4885
|
-
**Available Commands:**
|
|
4886
|
-
|
|
4887
|
-
1. **/security scan** \u2014 Run full security scan
|
|
4888
|
-
\`/security scan --depth deep --format html\`
|
|
4889
|
-
|
|
4890
|
-
2. **/security audit** \u2014 Run dependency audit + security scan
|
|
4891
|
-
|
|
4892
|
-
3. **/security report** \u2014 List available reports
|
|
4893
|
-
|
|
4894
|
-
**Features:**
|
|
4895
|
-
- Automatic tech stack detection
|
|
4896
|
-
- Dynamic security skill generation
|
|
4897
|
-
- Secrets, injection, and config vulnerability scanning
|
|
4898
|
-
- Markdown/JSON/HTML reports
|
|
4899
|
-
- .gitignore auto-update
|
|
4900
|
-
|
|
4901
|
-
Run \`/security scan\` to start.`;
|
|
4902
|
-
}
|
|
4903
4229
|
var CONFIG_ENV = "WRONGSTACK_STATUSLINE_CONFIG";
|
|
4904
4230
|
var DEFAULTS = {
|
|
4905
4231
|
todos: true,
|
|
@@ -4911,7 +4237,7 @@ var DEFAULTS = {
|
|
|
4911
4237
|
cost: true
|
|
4912
4238
|
};
|
|
4913
4239
|
function resolveConfigPath() {
|
|
4914
|
-
return process.env[CONFIG_ENV] ??
|
|
4240
|
+
return process.env[CONFIG_ENV] ?? path24.join(process.env.HOME ?? "", ".wrongstack", "statusline.json");
|
|
4915
4241
|
}
|
|
4916
4242
|
async function loadStatuslineConfig() {
|
|
4917
4243
|
const p = resolveConfigPath();
|
|
@@ -4925,7 +4251,7 @@ async function loadStatuslineConfig() {
|
|
|
4925
4251
|
async function saveStatuslineConfig(cfg) {
|
|
4926
4252
|
const p = resolveConfigPath();
|
|
4927
4253
|
try {
|
|
4928
|
-
await fsp3.mkdir(
|
|
4254
|
+
await fsp3.mkdir(path24.dirname(p), { recursive: true });
|
|
4929
4255
|
await atomicWrite(p, JSON.stringify(cfg, null, 2));
|
|
4930
4256
|
} catch (err) {
|
|
4931
4257
|
throw new FsError({
|
|
@@ -5823,168 +5149,8 @@ When the error confidence is low (< 0.85) or the problem spans multiple files,
|
|
|
5823
5149
|
}
|
|
5824
5150
|
};
|
|
5825
5151
|
}
|
|
5826
|
-
function makeInstaller(opts, projectRoot, global) {
|
|
5827
|
-
const globalRoot = path25.join(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(
|
|
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(
|
|
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(
|
|
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 =
|
|
6274
|
-
const file =
|
|
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(`(${
|
|
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 =
|
|
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:
|
|
5635
|
+
const { spawn: spawn3 } = await import('child_process');
|
|
6341
5636
|
await new Promise((resolve4, reject) => {
|
|
6342
|
-
const child =
|
|
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" ?
|
|
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 =
|
|
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 ??
|
|
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(
|
|
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 !==
|
|
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 =
|
|
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 =
|
|
6762
|
-
const filename =
|
|
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 = () =>
|
|
6118
|
+
var defaultHomeDir = () => os3__default.homedir();
|
|
6806
6119
|
function historyDir(homeFn = defaultHomeDir) {
|
|
6807
|
-
return
|
|
6120
|
+
return path24.join(homeFn(), ".wrongstack", "config.history", "entries");
|
|
6808
6121
|
}
|
|
6809
6122
|
function historyIndexPath(homeFn = defaultHomeDir) {
|
|
6810
|
-
return
|
|
6123
|
+
return path24.join(homeFn(), ".wrongstack", "config.history", "index.json");
|
|
6811
6124
|
}
|
|
6812
6125
|
function configPath(homeFn = defaultHomeDir) {
|
|
6813
|
-
return
|
|
6126
|
+
return path24.join(homeFn(), ".wrongstack", "config.json");
|
|
6814
6127
|
}
|
|
6815
6128
|
function backupLastPath(homeFn = defaultHomeDir) {
|
|
6816
|
-
return
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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:
|
|
6982
|
-
const
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
|
|
7321
|
-
|
|
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
|
|
6975
|
+
const path25 = typeof o["path"] === "string" ? o["path"] : "";
|
|
7657
6976
|
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
7658
|
-
return `${
|
|
6977
|
+
return `${path25} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
7659
6978
|
}
|
|
7660
6979
|
if (name === "write") {
|
|
7661
|
-
const
|
|
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 ? `${
|
|
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: ${
|
|
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 =
|
|
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(
|
|
8777
|
-
await fsp3.writeFile(
|
|
8095
|
+
await fsp3.mkdir(path24.dirname(path24.resolve(deps.cwd, output)), { recursive: true });
|
|
8096
|
+
await fsp3.writeFile(path24.resolve(deps.cwd, output), rendered, "utf8");
|
|
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(
|
|
8851
|
-
const agentsFile =
|
|
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 =
|
|
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(
|
|
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 =
|
|
9327
|
-
let
|
|
8645
|
+
const runDir = path24.join(deps.paths.projectSessions, id);
|
|
8646
|
+
let stat4;
|
|
9328
8647
|
try {
|
|
9329
|
-
|
|
8648
|
+
stat4 = await fsp3.stat(runDir);
|
|
9330
8649
|
} catch {
|
|
9331
8650
|
continue;
|
|
9332
8651
|
}
|
|
9333
|
-
if (!
|
|
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(
|
|
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(
|
|
8663
|
+
await fsp3.access(path24.join(runDir, "checkpoint.json"));
|
|
9345
8664
|
checkpoint = true;
|
|
9346
8665
|
} catch {
|
|
9347
8666
|
}
|
|
9348
8667
|
try {
|
|
9349
|
-
subagentsDir =
|
|
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 =
|
|
9379
|
-
let
|
|
8697
|
+
const runDir = path24.join(deps.paths.projectSessions, runId);
|
|
8698
|
+
let stat4;
|
|
9380
8699
|
try {
|
|
9381
|
-
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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}, ${
|
|
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,
|
|
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 ?
|
|
9872
|
-
const errSnip = errMsg || errKind ? `${errKindChip}${
|
|
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:
|
|
9194
|
+
return { mark: color37.green("\u2713"), stats, tail: "" };
|
|
9876
9195
|
case "timeout":
|
|
9877
|
-
return { mark:
|
|
9196
|
+
return { mark: color37.yellow("\u23F1"), stats: `${color37.yellow("timeout")} ${stats}`, tail: errSnip };
|
|
9878
9197
|
case "stopped":
|
|
9879
|
-
return { mark:
|
|
9198
|
+
return { mark: color37.dim("\u2298"), stats: `${color37.dim("stopped")} ${stats}`, tail: errSnip };
|
|
9880
9199
|
case "failed":
|
|
9881
|
-
return { mark:
|
|
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
|
|
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:
|
|
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
|
|
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: ${
|
|
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:
|
|
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:
|
|
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 ?
|
|
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 ?
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
11815
|
-
if (e.name === "read" &&
|
|
11816
|
-
else if (e.name === "edit" &&
|
|
11817
|
-
else if (e.name === "write" &&
|
|
11818
|
-
this.writtenPaths.add(
|
|
11158
|
+
const path25 = typeof input?.path === "string" ? input.path : void 0;
|
|
11159
|
+
if (e.name === "read" && path25) this.readPaths.add(path25);
|
|
11160
|
+
else if (e.name === "edit" && path25) this.editedPaths.add(path25);
|
|
11161
|
+
else if (e.name === "write" && path25) {
|
|
11162
|
+
this.writtenPaths.add(path25);
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
12370
|
-
const
|
|
12371
|
-
|
|
12372
|
-
|
|
12373
|
-
|
|
12374
|
-
|
|
12375
|
-
|
|
12376
|
-
|
|
12377
|
-
|
|
12378
|
-
|
|
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
|
-
|
|
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 =
|
|
12384
|
-
|
|
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:
|
|
12510
|
-
const queueStore = new QueueStore({ dir:
|
|
11921
|
+
const attachments = new DefaultAttachmentStore({ spoolDir: path24.join(wpaths.projectSessions, session.id, "attachments") });
|
|
11922
|
+
const queueStore = new QueueStore({ dir: path24.join(wpaths.projectSessions, session.id) });
|
|
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 =
|
|
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 =
|
|
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 =
|
|
12532
|
-
dirState = await loadDirectorState(
|
|
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
|
|
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 ?
|
|
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
|
-
|
|
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 ?
|
|
12864
|
-
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] :
|
|
12865
|
-
const sharedScratchpadPath = directorMode ?
|
|
12866
|
-
const subagentSessionsRoot = directorMode ?
|
|
12867
|
-
const stateCheckpointPath = directorMode ?
|
|
12868
|
-
const fleetRootForPromotion =
|
|
12278
|
+
const fleetRoot = directorMode ? path24.join(wpaths.projectSessions, session.id) : void 0;
|
|
12279
|
+
const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path24.join(fleetRoot, "fleet.json") : void 0;
|
|
12280
|
+
const sharedScratchpadPath = directorMode ? path24.join(fleetRoot, "shared") : void 0;
|
|
12281
|
+
const subagentSessionsRoot = directorMode ? path24.join(fleetRoot, "subagents") : void 0;
|
|
12282
|
+
const stateCheckpointPath = directorMode ? path24.join(fleetRoot, "director-state.json") : void 0;
|
|
12283
|
+
const fleetRootForPromotion = path24.join(wpaths.projectSessions, session.id);
|
|
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 =
|
|
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 =
|
|
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 =
|
|
12636
|
+
const full = path24.join(runDir, f);
|
|
13220
12637
|
try {
|
|
13221
|
-
const
|
|
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:
|
|
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 =
|
|
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 =
|
|
13388
|
-
const sp =
|
|
13389
|
-
const ss =
|
|
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:
|
|
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 =
|
|
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,
|