@wrongstack/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,22 +1,26 @@
1
1
  #!/usr/bin/env node
2
- import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, DefaultSessionStore, DefaultSkillLoader, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, InputBuilder, decryptConfigSecrets, encryptConfigSecrets, DefaultPluginAPI } from '@wrongstack/core';
2
+ import * as path17 from 'path';
3
+ import { color, allServers, DefaultPathResolver, TOKENS, DefaultSystemPromptBuilder, ToolRegistry, createContextManagerTool, EventBus, InMemoryMetricsSink, wireMetricsToEvents, DefaultHealthRegistry, startMetricsServer, SlashCommandRegistry, loadPlugins, createDelegateTool, FLEET_ROSTER, DefaultLogger, DefaultModelsRegistry, ProviderRegistry, RecoveryLock, DefaultAttachmentStore, QueueStore, Context, loadTodosCheckpoint, attachTodosCheckpoint, loadDirectorState, loadPlan, createDefaultPipelines, AutoCompactionMiddleware, estimateRequestTokens, Agent, makeDirectorSessionFactory, Director, DefaultMultiAgentCoordinator, makeAgentSubagentRunner, resolveWstackPaths, DefaultSecretVault, migratePlaintextSecrets, DefaultConfigLoader, DefaultSessionReader, atomicWrite, AutoApprovePermissionPolicy, formatContextWindowModeList, repairToolUseAdjacency, getContextWindowMode, resolveContextWindowPolicy, formatTodosList, emptyPlan, clearPlan, savePlan, removePlanItem, formatPlan, setPlanItemStatus, addPlanItem, SpecStore, TaskGraphStore, SpecVersioning, getTemplate, listTemplates, templateToMarkdown, SpecParser, renderSpecAnalysis, AISpecBuilder, DefaultTaskStore, TaskTracker, InputBuilder, projectHash, decryptConfigSecrets, encryptConfigSecrets as encryptConfigSecrets$1, DefaultPluginAPI } from '@wrongstack/core';
3
4
  import * as crypto from 'crypto';
4
5
  import { randomUUID } from 'crypto';
5
6
  import * as fs14 from 'fs/promises';
7
+ import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
6
8
  import { WebSocketServer, WebSocket } from 'ws';
7
9
  import { writeFileSync } from 'fs';
8
10
  import { createRequire } from 'module';
9
- import * as path14 from 'path';
10
11
  import { MCPRegistry } from '@wrongstack/mcp';
11
12
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
12
13
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
13
14
  import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
14
- import * as os3 from 'os';
15
+ import * as os4 from 'os';
15
16
  import * as readline from 'readline';
17
+ import { SkillInstaller } from '@wrongstack/core/skills';
16
18
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
17
19
 
18
20
  var __defProp = Object.defineProperty;
21
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
19
22
  var __getOwnPropNames = Object.getOwnPropertyNames;
23
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
20
24
  var __esm = (fn, res) => function __init() {
21
25
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
22
26
  };
@@ -24,6 +28,835 @@ var __export = (target, all) => {
24
28
  for (var name in all)
25
29
  __defProp(target, name, { get: all[name], enumerable: true });
26
30
  };
31
+ var __copyProps = (to, from, except, desc) => {
32
+ if (from && typeof from === "object" || typeof from === "function") {
33
+ for (let key of __getOwnPropNames(from))
34
+ if (!__hasOwnProp.call(to, key) && key !== except)
35
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
36
+ }
37
+ return to;
38
+ };
39
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
40
+
41
+ // src/slash-commands/sdd.ts
42
+ var sdd_exports = {};
43
+ __export(sdd_exports, {
44
+ autoDetectTaskCompletion: () => autoDetectTaskCompletion,
45
+ buildSddCommand: () => buildSddCommand,
46
+ getActiveBuilder: () => getActiveBuilder,
47
+ getActiveSDDContext: () => getActiveSDDContext,
48
+ getActiveSDDPhase: () => getActiveSDDPhase,
49
+ getTaskListText: () => getTaskListText,
50
+ getTaskProgress: () => getTaskProgress,
51
+ markTaskCompleted: () => markTaskCompleted,
52
+ trySaveImplementationPlan: () => trySaveImplementationPlan,
53
+ trySaveSpecFromAIOutput: () => trySaveSpecFromAIOutput,
54
+ trySaveTasksFromAIOutput: () => trySaveTasksFromAIOutput
55
+ });
56
+ function getActiveSDDContext() {
57
+ if (!activeBuilder) return null;
58
+ const session = activeBuilder.getSession();
59
+ if (session.phase === "done") return null;
60
+ return activeBuilder.getAIPrompt();
61
+ }
62
+ function getActiveSDDPhase() {
63
+ if (!activeBuilder) return null;
64
+ return activeBuilder.getPhase();
65
+ }
66
+ async function trySaveSpecFromAIOutput(aiOutput) {
67
+ if (!activeBuilder) return false;
68
+ const spec = activeBuilder.tryParseSpecFromOutput(aiOutput);
69
+ if (!spec) return false;
70
+ activeBuilder.setSpec(spec);
71
+ return true;
72
+ }
73
+ async function trySaveTasksFromAIOutput(aiOutput) {
74
+ if (!activeBuilder) return false;
75
+ const session = activeBuilder.getSession();
76
+ if (!session.spec) return false;
77
+ const json = activeBuilder.extractJSONArray(aiOutput);
78
+ if (!json) return false;
79
+ let tasks;
80
+ try {
81
+ tasks = JSON.parse(json);
82
+ } catch {
83
+ return false;
84
+ }
85
+ if (!Array.isArray(tasks) || tasks.length === 0) return false;
86
+ const validTasks = tasks.filter((t) => t && typeof t === "object" && typeof t.title === "string" && t.title.length > 0);
87
+ if (validTasks.length === 0) return false;
88
+ const store = new DefaultTaskStore();
89
+ const tracker = new TaskTracker({ store });
90
+ const graph = await tracker.createGraph(session.spec.id, session.spec.title);
91
+ for (const task of validTasks) {
92
+ const title = String(task.title);
93
+ const description = String(task.description ?? "");
94
+ const type = ["feature", "bugfix", "refactor", "docs", "test", "chore"].includes(String(task.type)) ? String(task.type) : "feature";
95
+ const priority = ["critical", "high", "medium", "low"].includes(String(task.priority)) ? String(task.priority) : "medium";
96
+ const estimateHours = Number(task.estimateHours) || 2;
97
+ const tags = Array.isArray(task.tags) ? task.tags.map(String) : [];
98
+ tracker.addNode({
99
+ title,
100
+ description,
101
+ type,
102
+ priority,
103
+ status: "pending",
104
+ estimateHours,
105
+ tags
106
+ });
107
+ }
108
+ activeTaskStore = store;
109
+ activeTaskTracker = tracker;
110
+ activeTaskGraphId = graph.id;
111
+ activeBuilder.setTaskGraphId(graph.id);
112
+ return true;
113
+ }
114
+ function getTaskProgress() {
115
+ if (!activeTaskTracker) return null;
116
+ const progress = activeTaskTracker.getProgress();
117
+ return {
118
+ total: progress.total,
119
+ completed: progress.completed,
120
+ pending: progress.pending,
121
+ percent: progress.percentComplete
122
+ };
123
+ }
124
+ function getTaskListText() {
125
+ if (!activeTaskTracker) return null;
126
+ const nodes = activeTaskTracker.getAllNodes();
127
+ if (nodes.length === 0) return null;
128
+ const lines = nodes.map((n, i) => {
129
+ const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : "\u23F3";
130
+ return `${i + 1}. ${status} [${n.priority}] ${n.title}`;
131
+ });
132
+ return lines.join("\n");
133
+ }
134
+ function markTaskCompleted(taskTitle) {
135
+ if (!activeTaskTracker) return false;
136
+ const nodes = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
137
+ const match = nodes.find(
138
+ (n) => n.title.toLowerCase().includes(taskTitle.toLowerCase()) || taskTitle.toLowerCase().includes(n.title.toLowerCase())
139
+ );
140
+ if (!match) return false;
141
+ activeTaskTracker.updateNodeStatus(match.id, "completed");
142
+ return true;
143
+ }
144
+ function autoDetectTaskCompletion(aiOutput) {
145
+ if (!activeTaskTracker) return 0;
146
+ const pending = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
147
+ if (pending.length === 0) return 0;
148
+ let completed = 0;
149
+ const lines = aiOutput.split("\n");
150
+ for (const line of lines) {
151
+ const trimmed = line.trim();
152
+ const sddDoneMatch = trimmed.match(/\/sdd\s+done\s+(.+)/i);
153
+ if (sddDoneMatch?.[1]) {
154
+ const target = sddDoneMatch[1].trim();
155
+ const num = Number(target);
156
+ if (!Number.isNaN(num) && num >= 1 && num <= pending.length) {
157
+ const node = pending[num - 1];
158
+ if (node && node.status !== "completed") {
159
+ activeTaskTracker.updateNodeStatus(node.id, "completed");
160
+ completed++;
161
+ }
162
+ } else {
163
+ const match = pending.find(
164
+ (n) => n.title.toLowerCase().includes(target.toLowerCase()) || target.toLowerCase().includes(n.title.toLowerCase())
165
+ );
166
+ if (match && match.status !== "completed") {
167
+ activeTaskTracker.updateNodeStatus(match.id, "completed");
168
+ completed++;
169
+ }
170
+ }
171
+ continue;
172
+ }
173
+ const checkmarkMatch = trimmed.match(/^✅\s*(?:Task:\s*)?(.+)/i);
174
+ if (checkmarkMatch?.[1]) {
175
+ const title = checkmarkMatch[1].trim();
176
+ const match = pending.find(
177
+ (n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
178
+ );
179
+ if (match && match.status !== "completed") {
180
+ activeTaskTracker.updateNodeStatus(match.id, "completed");
181
+ completed++;
182
+ }
183
+ continue;
184
+ }
185
+ const taskNumMatch = trimmed.match(/Task\s+(\d+)\s*[:]\s*(?:complete|done|finished)/i);
186
+ if (taskNumMatch?.[1]) {
187
+ const num = Number(taskNumMatch[1]);
188
+ if (num >= 1 && num <= pending.length) {
189
+ const node = pending[num - 1];
190
+ if (node && node.status !== "completed") {
191
+ activeTaskTracker.updateNodeStatus(node.id, "completed");
192
+ completed++;
193
+ }
194
+ }
195
+ continue;
196
+ }
197
+ const completedMatch = trimmed.match(/^(?:Completed|Done|Finished)\s*[:]\s*(.+)/i);
198
+ if (completedMatch?.[1]) {
199
+ const title = completedMatch[1].trim();
200
+ const match = pending.find(
201
+ (n) => n.title.toLowerCase().includes(title.toLowerCase()) || title.toLowerCase().includes(n.title.toLowerCase())
202
+ );
203
+ if (match && match.status !== "completed") {
204
+ activeTaskTracker.updateNodeStatus(match.id, "completed");
205
+ completed++;
206
+ }
207
+ }
208
+ }
209
+ return completed;
210
+ }
211
+ function trySaveImplementationPlan(aiOutput) {
212
+ if (!activeBuilder) return false;
213
+ const session = activeBuilder.getSession();
214
+ if (session.phase !== "implementation") return false;
215
+ const jsonMatch = aiOutput.match(/```json\s*\[/);
216
+ if (jsonMatch?.index && jsonMatch.index > 0) {
217
+ const plan = aiOutput.substring(0, jsonMatch.index).trim();
218
+ if (plan.length > 50) {
219
+ activeBuilder.setImplementation(plan);
220
+ return true;
221
+ }
222
+ }
223
+ if (aiOutput.length > 100 && !aiOutput.includes("```json")) {
224
+ activeBuilder.setImplementation(aiOutput.trim());
225
+ return true;
226
+ }
227
+ return false;
228
+ }
229
+ function getActiveBuilder() {
230
+ return activeBuilder;
231
+ }
232
+ function buildSddCommand(opts) {
233
+ return {
234
+ name: "sdd",
235
+ description: "AI-driven SDD: /sdd [new|approve|execute|cancel|status|list|show|templates]",
236
+ async run(args) {
237
+ const ctx = opts.context;
238
+ const projectRoot = ctx?.projectRoot ?? process.cwd();
239
+ const specsDir = path17.join(projectRoot, ".wrongstack", "specs");
240
+ const graphsDir = path17.join(projectRoot, ".wrongstack", "task-graphs");
241
+ const specStore = new SpecStore({ baseDir: specsDir });
242
+ new TaskGraphStore({ baseDir: graphsDir });
243
+ const versioning = new SpecVersioning();
244
+ const [verb, ...rest] = args.trim().split(/\s+/);
245
+ const restJoined = rest.join(" ").trim();
246
+ switch (verb) {
247
+ case "":
248
+ case "help":
249
+ return { message: sddHelp() };
250
+ // ── AI-Driven Spec Session ─────────────────────────────────────────
251
+ case "new":
252
+ case "create": {
253
+ const forceFlag = rest.includes("--force") || rest.includes("-f");
254
+ const title = rest.filter((a) => !a.startsWith("-")).join(" ").trim() || "Untitled Feature";
255
+ if (!activeBuilder && !forceFlag) {
256
+ const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
257
+ try {
258
+ const fsp = await import('fs/promises');
259
+ await fsp.access(sessionPath);
260
+ const projectContext2 = await gatherProjectContext(projectRoot);
261
+ const tempBuilder = new AISpecBuilder({
262
+ store: specStore,
263
+ projectContext: projectContext2,
264
+ sessionPath
265
+ });
266
+ const loaded = await tempBuilder.loadSession();
267
+ if (loaded) {
268
+ const existing = tempBuilder.getSession();
269
+ if (existing.phase !== "done") {
270
+ return {
271
+ message: [
272
+ `An existing SDD session was found:`,
273
+ ` Feature: "${existing.title}"`,
274
+ ` Phase: ${existing.phase}`,
275
+ ` Questions: ${existing.questionCount}`,
276
+ "",
277
+ "Use /sdd resume to continue, or /sdd new --force to start fresh."
278
+ ].join("\n")
279
+ };
280
+ }
281
+ }
282
+ } catch {
283
+ }
284
+ }
285
+ activeTaskStore = null;
286
+ activeTaskTracker = null;
287
+ activeTaskGraphId = null;
288
+ const projectContext = await gatherProjectContext(projectRoot);
289
+ activeBuilder = new AISpecBuilder({
290
+ store: specStore,
291
+ projectContext,
292
+ minQuestions: 2,
293
+ maxQuestions: 10,
294
+ sessionPath: path17.join(projectRoot, ".wrongstack", "sdd-session.json")
295
+ });
296
+ activeBuilder.startSession(title);
297
+ const aiPrompt = activeBuilder.getAIPrompt();
298
+ return {
299
+ message: [
300
+ `\u2554\u2550\u2550\u2550 SDD: AI Spec Builder \u2550\u2550\u2550\u2557`,
301
+ "",
302
+ `Feature: "${title}"`,
303
+ "",
304
+ "The AI will now ask you contextual questions.",
305
+ "Answer naturally \u2014 it will generate the spec when ready.",
306
+ "",
307
+ "Commands: /sdd approve \xB7 /sdd status \xB7 /sdd cancel"
308
+ ].join("\n"),
309
+ runText: `[SDD SESSION ACTIVE]
310
+ ${aiPrompt}
311
+
312
+ ---
313
+ User message:
314
+ Start the specification interview for "${title}". Ask your first contextual question.`
315
+ };
316
+ }
317
+ // ── Phase Transitions ──────────────────────────────────────────────
318
+ case "approve":
319
+ case "ok":
320
+ case "confirm": {
321
+ if (!activeBuilder) {
322
+ return {
323
+ message: "No active SDD session. Use /sdd new to start one."
324
+ };
325
+ }
326
+ const phase = activeBuilder.getSession().phase;
327
+ if (phase === "questioning") {
328
+ const sddCtx = activeBuilder.getAIPrompt();
329
+ return {
330
+ message: "No spec generated yet. Generating now...",
331
+ runText: `[SDD SESSION ACTIVE]
332
+ ${sddCtx}
333
+
334
+ ---
335
+ User message:
336
+ Generate the complete specification now based on the conversation so far.`
337
+ };
338
+ }
339
+ if (phase === "spec_review") {
340
+ const spec = activeBuilder.getSession().spec;
341
+ if (!spec) {
342
+ return { message: "No spec to approve." };
343
+ }
344
+ await activeBuilder.saveSpec();
345
+ versioning.recordVersion(spec, "Initial spec approved");
346
+ activeBuilder.approve();
347
+ const implPrompt = activeBuilder.getAIPrompt();
348
+ return {
349
+ message: [
350
+ `\u2705 Spec "${spec.title}" approved and saved!`,
351
+ `ID: ${spec.id}`,
352
+ `Requirements: ${spec.requirements.length}`,
353
+ "",
354
+ "The AI will now generate an implementation plan and tasks."
355
+ ].join("\n"),
356
+ runText: `[SDD SESSION ACTIVE]
357
+ ${implPrompt}
358
+
359
+ ---
360
+ User message:
361
+ Generate the implementation plan and tasks for the approved spec.`
362
+ };
363
+ }
364
+ if (phase === "task_review") {
365
+ activeBuilder.approve();
366
+ const execPrompt = activeBuilder.getAIPrompt();
367
+ return {
368
+ message: "\u2705 Tasks approved! The AI will now execute them one by one.",
369
+ runText: `[SDD SESSION ACTIVE]
370
+ ${execPrompt}
371
+
372
+ ---
373
+ User message:
374
+ Start executing the tasks one by one.`
375
+ };
376
+ }
377
+ return {
378
+ message: `Current phase is "${phase}". Use /sdd status to see details.`
379
+ };
380
+ }
381
+ // ── Task Execution ─────────────────────────────────────────────────
382
+ case "execute":
383
+ case "run": {
384
+ if (!activeBuilder) {
385
+ return {
386
+ message: "No active SDD session. Use /sdd new to start one."
387
+ };
388
+ }
389
+ const session = activeBuilder.getSession();
390
+ if (session.phase !== "executing" && session.phase !== "task_review") {
391
+ return {
392
+ message: `Cannot execute in phase "${session.phase}". Use /sdd approve first.`
393
+ };
394
+ }
395
+ const execPrompt = activeBuilder.getAIPrompt();
396
+ return {
397
+ message: "\u26A1 Starting task execution. The AI will execute tasks one by one.",
398
+ runText: `[SDD SESSION ACTIVE]
399
+ ${execPrompt}
400
+
401
+ ---
402
+ User message:
403
+ Start executing the tasks one by one.`
404
+ };
405
+ }
406
+ case "plan":
407
+ case "impl": {
408
+ if (!activeBuilder) {
409
+ return { message: "No active SDD session. Use /sdd new to start one." };
410
+ }
411
+ const session = activeBuilder.getSession();
412
+ if (!session.implementation) {
413
+ return {
414
+ message: session.phase === "implementation" ? "No implementation plan yet. The AI will generate it after /sdd approve." : "No implementation plan in this session."
415
+ };
416
+ }
417
+ return {
418
+ message: [
419
+ "\u2550\u2550\u2550 Implementation Plan \u2550\u2550\u2550",
420
+ "",
421
+ session.implementation
422
+ ].join("\n")
423
+ };
424
+ }
425
+ case "spec": {
426
+ if (!activeBuilder) {
427
+ return { message: "No active SDD session. Use /sdd new to start one." };
428
+ }
429
+ const session = activeBuilder.getSession();
430
+ if (!session.spec) {
431
+ return {
432
+ message: session.phase === "questioning" ? "No spec generated yet. Keep answering the AI's questions." : "No spec in this session."
433
+ };
434
+ }
435
+ const spec = session.spec;
436
+ const lines = [
437
+ `\u2550\u2550\u2550 Current Spec \u2550\u2550\u2550`,
438
+ "",
439
+ `Title: ${spec.title}`,
440
+ `Version: ${spec.version}`,
441
+ `Status: ${spec.status}`,
442
+ "",
443
+ "## Overview",
444
+ spec.overview
445
+ ];
446
+ if (spec.requirements.length > 0) {
447
+ lines.push("", `## Requirements (${spec.requirements.length})`);
448
+ for (const r of spec.requirements) {
449
+ const ac = r.acceptanceCriteria.length > 0 ? ` \u2192 ${r.acceptanceCriteria.join(", ")}` : "";
450
+ lines.push(` [${r.priority}] ${r.description}${ac}`);
451
+ }
452
+ }
453
+ return { message: lines.join("\n") };
454
+ }
455
+ case "tasks":
456
+ case "task": {
457
+ if (!activeTaskTracker) {
458
+ return { message: "No tasks generated yet. Use /sdd new to start." };
459
+ }
460
+ const nodes = activeTaskTracker.getAllNodes();
461
+ if (nodes.length === 0) {
462
+ return { message: "No tasks in the current graph." };
463
+ }
464
+ const progress = activeTaskTracker.getProgress();
465
+ const lines = [
466
+ `\u2550\u2550\u2550 Task List (${progress.completed}/${progress.total} done) \u2550\u2550\u2550`,
467
+ ""
468
+ ];
469
+ for (let i = 0; i < nodes.length; i++) {
470
+ const n = nodes[i];
471
+ const status = n.status === "completed" ? "\u2705" : n.status === "in_progress" ? "\u{1F504}" : n.status === "failed" ? "\u274C" : "\u23F3";
472
+ lines.push(`${i + 1}. ${status} [${n.priority}] ${n.title}`);
473
+ if (n.description) {
474
+ lines.push(` ${n.description.split("\n")[0]}`);
475
+ }
476
+ }
477
+ return { message: lines.join("\n") };
478
+ }
479
+ case "done":
480
+ case "complete": {
481
+ if (!activeTaskTracker) {
482
+ return { message: "No tasks to complete." };
483
+ }
484
+ if (!restJoined) {
485
+ return { message: "Usage: /sdd done <task title or number>" };
486
+ }
487
+ const nodes = activeTaskTracker.getAllNodes({ status: ["pending", "in_progress"] });
488
+ const num = Number(restJoined);
489
+ let matched = false;
490
+ if (!Number.isNaN(num) && num >= 1 && num <= nodes.length) {
491
+ const node = nodes[num - 1];
492
+ if (node) {
493
+ activeTaskTracker.updateNodeStatus(node.id, "completed");
494
+ matched = true;
495
+ }
496
+ }
497
+ if (!matched) {
498
+ const match = nodes.find(
499
+ (n) => n.title.toLowerCase().includes(restJoined.toLowerCase()) || restJoined.toLowerCase().includes(n.title.toLowerCase())
500
+ );
501
+ if (match) {
502
+ activeTaskTracker.updateNodeStatus(match.id, "completed");
503
+ matched = true;
504
+ }
505
+ }
506
+ if (!matched) {
507
+ return { message: `No pending task matching "${restJoined}".` };
508
+ }
509
+ const remaining = activeTaskTracker.getProgress();
510
+ return {
511
+ message: `\u2705 Task completed! ${remaining.completed}/${remaining.total} done (${remaining.percentComplete}%)`
512
+ };
513
+ }
514
+ // ── Session Management ─────────────────────────────────────────────
515
+ case "status": {
516
+ if (!activeBuilder) {
517
+ return { message: "No active SDD session." };
518
+ }
519
+ const session = activeBuilder.getSession();
520
+ const phaseEmoji = {
521
+ questioning: "\u2753",
522
+ spec_review: "\u{1F4CB}",
523
+ implementation: "\u{1F3D7}\uFE0F",
524
+ task_review: "\u{1F4DD}",
525
+ executing: "\u26A1",
526
+ done: "\u2705"
527
+ };
528
+ const progress = getTaskProgress();
529
+ const lines = [
530
+ "\u2550\u2550\u2550 SDD Session Status \u2550\u2550\u2550",
531
+ "",
532
+ `Feature: "${session.title}"`,
533
+ `Phase: ${phaseEmoji[session.phase]} ${session.phase}`,
534
+ `Questions asked: ${session.questionCount}`
535
+ ];
536
+ if (session.spec) {
537
+ lines.push(`Spec: ${session.spec.title} (${session.spec.requirements.length} requirements)`);
538
+ lines.push(` Requirements: ${session.spec.requirements.map((r) => r.description).join(", ")}`);
539
+ }
540
+ if (session.implementation) {
541
+ const planPreview = session.implementation.split("\n").slice(0, 3).join(" ");
542
+ lines.push(`Implementation: ${planPreview}${session.implementation.length > 100 ? "..." : ""}`);
543
+ }
544
+ if (progress && progress.total > 0) {
545
+ lines.push(`Tasks: ${progress.completed}/${progress.total} (${progress.percent}%)`);
546
+ }
547
+ lines.push("", `Session ID: ${session.id}`);
548
+ lines.push("Commands: /sdd plan \xB7 /sdd tasks \xB7 /sdd approve \xB7 /sdd cancel");
549
+ return {
550
+ message: lines.join("\n")
551
+ };
552
+ }
553
+ case "cancel": {
554
+ const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
555
+ let deletedFromDisk = false;
556
+ try {
557
+ const fsp = await import('fs/promises');
558
+ await fsp.unlink(sessionPath);
559
+ deletedFromDisk = true;
560
+ } catch {
561
+ }
562
+ if (activeBuilder) {
563
+ const title = activeBuilder.getSession().title;
564
+ await activeBuilder.deleteSession();
565
+ activeBuilder = null;
566
+ activeTaskStore = null;
567
+ activeTaskTracker = null;
568
+ activeTaskGraphId = null;
569
+ return { message: `SDD session for "${title}" cancelled.` };
570
+ }
571
+ if (deletedFromDisk) {
572
+ return { message: "Stale SDD session file deleted. You can now use /sdd new." };
573
+ }
574
+ return { message: "No active SDD session." };
575
+ }
576
+ case "resume": {
577
+ if (activeBuilder) {
578
+ return { message: "An SDD session is already active. Use /sdd cancel first." };
579
+ }
580
+ const sessionPath = path17.join(projectRoot, ".wrongstack", "sdd-session.json");
581
+ const projectContext = await gatherProjectContext(projectRoot);
582
+ activeBuilder = new AISpecBuilder({
583
+ store: specStore,
584
+ projectContext,
585
+ minQuestions: 2,
586
+ maxQuestions: 10,
587
+ sessionPath
588
+ });
589
+ const loaded = await activeBuilder.loadSession();
590
+ if (!loaded) {
591
+ activeBuilder = null;
592
+ return { message: "No saved SDD session found. Use /sdd new to start one." };
593
+ }
594
+ const session = activeBuilder.getSession();
595
+ let taskCount = 0;
596
+ let completedCount = 0;
597
+ const taskGraphId = activeBuilder.getTaskGraphId();
598
+ if (taskGraphId) {
599
+ try {
600
+ const store = new DefaultTaskStore();
601
+ const tracker = new TaskTracker({ store });
602
+ const graph = await tracker.loadGraph(taskGraphId);
603
+ if (graph) {
604
+ activeTaskStore = store;
605
+ activeTaskTracker = tracker;
606
+ activeTaskGraphId = taskGraphId;
607
+ const progress = tracker.getProgress();
608
+ taskCount = progress.total;
609
+ completedCount = progress.completed;
610
+ }
611
+ } catch {
612
+ }
613
+ }
614
+ const resumePrompt = activeBuilder.getAIPrompt();
615
+ return {
616
+ message: [
617
+ `\u2554\u2550\u2550\u2550 SDD Session Resumed \u2550\u2550\u2550\u2557`,
618
+ "",
619
+ `Feature: "${session.title}"`,
620
+ `Phase: ${session.phase}`,
621
+ `Questions asked: ${session.questionCount}`,
622
+ session.spec ? `Spec: ${session.spec.title}` : "",
623
+ taskCount > 0 ? `Tasks: ${completedCount}/${taskCount} completed` : "",
624
+ "",
625
+ "The AI will continue from where you left off."
626
+ ].filter(Boolean).join("\n"),
627
+ runText: `[SDD SESSION ACTIVE]
628
+ ${resumePrompt}
629
+
630
+ ---
631
+ User message:
632
+ Continue from where we left off. Check the session status and proceed.`
633
+ };
634
+ }
635
+ // ── Spec Browsing ──────────────────────────────────────────────────
636
+ case "list":
637
+ case "ls": {
638
+ const entries = await specStore.list();
639
+ if (entries.length === 0) {
640
+ return { message: "No specs saved. Use /sdd new to create one." };
641
+ }
642
+ const lines = entries.map((e, i) => {
643
+ const status = e.status === "draft" ? "\u{1F4DD}" : e.status === "approved" ? "\u2705" : "\u{1F4CB}";
644
+ return `${i + 1}. ${status} ${e.title} (${e.version}) \u2014 ${e.id.slice(0, 8)}...`;
645
+ });
646
+ return { message: `Saved Specs:
647
+ ${lines.join("\n")}` };
648
+ }
649
+ case "show":
650
+ case "view": {
651
+ const spec = await findSpec(specStore, restJoined);
652
+ if (!spec) return { message: `Spec "${restJoined}" not found.` };
653
+ const parser = new SpecParser();
654
+ const analysis = parser.analyze(spec);
655
+ return {
656
+ message: [
657
+ `# ${spec.title}`,
658
+ `Version: ${spec.version} | Status: ${spec.status}`,
659
+ "",
660
+ "## Overview",
661
+ spec.overview,
662
+ "",
663
+ `## Requirements (${spec.requirements.length})`,
664
+ ...spec.requirements.map((r) => {
665
+ const tags = `[${r.type}][${r.priority}]`;
666
+ const ac = r.acceptanceCriteria.length > 0 ? `
667
+ AC: ${r.acceptanceCriteria.join(", ")}` : "";
668
+ return `- ${tags} ${r.description}${ac}`;
669
+ }),
670
+ "",
671
+ renderSpecAnalysis(spec, {
672
+ completeness: analysis.completeness,
673
+ gaps: analysis.gaps,
674
+ risks: analysis.risks.map((r) => r.risk),
675
+ suggestions: analysis.suggestions
676
+ })
677
+ ].join("\n")
678
+ };
679
+ }
680
+ case "templates": {
681
+ const templates = listTemplates();
682
+ const lines = templates.map(
683
+ (t) => ` ${t.id}: ${t.name} \u2014 ${t.description}`
684
+ );
685
+ return {
686
+ message: `Available Templates:
687
+ ${lines.join("\n")}`
688
+ };
689
+ }
690
+ case "from": {
691
+ const templateId = restJoined || "feature";
692
+ const template = getTemplate(templateId);
693
+ if (!template) {
694
+ return {
695
+ message: `Template "${templateId}" not found.
696
+ Available: ${listTemplates().map((t) => t.id).join(", ")}`
697
+ };
698
+ }
699
+ const skeleton = templateToMarkdown(template, "New Specification");
700
+ const spec = await specStore.createDraft("New Specification");
701
+ await specStore.update(spec.id, { sections: [] });
702
+ return {
703
+ message: [
704
+ `Created draft spec from template "${template.name}".`,
705
+ `ID: ${spec.id}`,
706
+ "",
707
+ "Edit the spec through the AI conversation or /sdd show to review.",
708
+ "",
709
+ skeleton
710
+ ].join("\n")
711
+ };
712
+ }
713
+ case "version":
714
+ case "history": {
715
+ const spec = await findSpec(specStore, restJoined);
716
+ if (!spec)
717
+ return { message: `Spec "${restJoined}" not found.` };
718
+ const history = versioning.getHistory(spec.id);
719
+ if (history.length === 0) {
720
+ return {
721
+ message: `No version history for "${spec.title}".`
722
+ };
723
+ }
724
+ const lines = history.map(
725
+ (v, i) => `${i + 1}. v${v.version} \u2014 ${new Date(v.timestamp).toISOString()}${v.changeDescription ? ` (${v.changeDescription})` : ""}`
726
+ );
727
+ return {
728
+ message: `Version History for "${spec.title}":
729
+ ${lines.join("\n")}`
730
+ };
731
+ }
732
+ default:
733
+ return {
734
+ message: `Unknown command "${verb}".
735
+
736
+ ${sddHelp()}`
737
+ };
738
+ }
739
+ }
740
+ };
741
+ }
742
+ function sddHelp() {
743
+ return [
744
+ "",
745
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
746
+ "\u2551 \u{1F680} SDD \u2014 AI-Driven Spec Builder \u2551",
747
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
748
+ "",
749
+ " \u250C\u2500 \u{1F195} Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
750
+ " \u2502 /sdd new [title] Start a new spec session \u2502",
751
+ " \u2502 /sdd new --force Start fresh (skip resume check) \u2502",
752
+ " \u2502 /sdd resume Resume a saved session \u2502",
753
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
754
+ "",
755
+ " \u250C\u2500 \u{1F504} Flow \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
756
+ " \u2502 /sdd approve Approve current phase \u2502",
757
+ " \u2502 /sdd spec Show current session's spec \u2502",
758
+ " \u2502 /sdd plan Show implementation plan \u2502",
759
+ " \u2502 /sdd execute Execute generated tasks \u2502",
760
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
761
+ "",
762
+ " \u250C\u2500 \u{1F4CB} Task Management \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
763
+ " \u2502 /sdd tasks Show current task list \u2502",
764
+ " \u2502 /sdd done <N> Mark task complete (by # or name) \u2502",
765
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
766
+ "",
767
+ " \u250C\u2500 \u{1F4CA} Info \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
768
+ " \u2502 /sdd status Show session status \u2502",
769
+ " \u2502 /sdd cancel Cancel session \u2502",
770
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
771
+ "",
772
+ " \u250C\u2500 \u{1F4C1} Spec History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
773
+ " \u2502 /sdd list List saved specs \u2502",
774
+ " \u2502 /sdd show <id> Show spec details \u2502",
775
+ " \u2502 /sdd templates List available templates \u2502",
776
+ " \u2502 /sdd from <tmpl> Create from template \u2502",
777
+ " \u2502 /sdd version <id> Show version history \u2502",
778
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
779
+ "",
780
+ " \u250C\u2500 \u{1F4A1} Quick Start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
781
+ " \u2502 \u2502",
782
+ " \u2502 1. /sdd new Auth System \u2502",
783
+ " \u2502 \u2192 AI starts asking questions \u2502",
784
+ " \u2502 \u2502",
785
+ " \u2502 2. Just type your answers naturally \u2502",
786
+ " \u2502 \u2192 AI continues the interview \u2502",
787
+ " \u2502 \u2502",
788
+ " \u2502 3. AI generates spec (auto-detected) \u2502",
789
+ " \u2502 \u2192 /sdd approve \u2502",
790
+ " \u2502 \u2502",
791
+ " \u2502 3. AI generates implementation + tasks \u2502",
792
+ " \u2502 \u2192 /sdd approve \u2502",
793
+ " \u2502 \u2502",
794
+ " \u2502 4. AI executes tasks one by one \u2502",
795
+ " \u2502 \u2192 /sdd tasks (view progress) \u2502",
796
+ " \u2502 \u2192 /sdd done 1 (manual completion) \u2502",
797
+ " \u2502 \u2502",
798
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
799
+ ""
800
+ ].join("\n");
801
+ }
802
+ async function gatherProjectContext(projectRoot) {
803
+ const parts = [];
804
+ try {
805
+ const fsp = await import('fs/promises');
806
+ const pkgPath = path17.join(projectRoot, "package.json");
807
+ const pkgRaw = await fsp.readFile(pkgPath, "utf8");
808
+ const pkg = JSON.parse(pkgRaw);
809
+ parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
810
+ parts.push(`Description: ${String(pkg.description ?? "none")}`);
811
+ if (pkg.dependencies) {
812
+ const deps = Object.keys(pkg.dependencies);
813
+ parts.push(`Dependencies: ${deps.slice(0, 20).join(", ")}${deps.length > 20 ? "..." : ""}`);
814
+ }
815
+ if (pkg.devDependencies) {
816
+ const devDeps = Object.keys(pkg.devDependencies);
817
+ parts.push(`Dev Dependencies: ${devDeps.slice(0, 15).join(", ")}${devDeps.length > 15 ? "..." : ""}`);
818
+ }
819
+ } catch {
820
+ }
821
+ try {
822
+ const fsp = await import('fs/promises');
823
+ const tsconfigPath = path17.join(projectRoot, "tsconfig.json");
824
+ await fsp.access(tsconfigPath);
825
+ parts.push("Language: TypeScript");
826
+ } catch {
827
+ }
828
+ try {
829
+ const fsp = await import('fs/promises');
830
+ const srcDir = path17.join(projectRoot, "src");
831
+ const entries = await fsp.readdir(srcDir, { withFileTypes: true });
832
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
833
+ if (dirs.length > 0) {
834
+ parts.push(`Source structure: src/${dirs.join(", src/")}`);
835
+ }
836
+ } catch {
837
+ }
838
+ return parts.join("\n");
839
+ }
840
+ async function findSpec(store, idOrTitle) {
841
+ if (!idOrTitle) return null;
842
+ const byId = await store.load(idOrTitle);
843
+ if (byId) return byId;
844
+ const all = await store.list();
845
+ const match = all.find(
846
+ (e) => e.id.startsWith(idOrTitle) || e.title.toLowerCase().includes(idOrTitle.toLowerCase())
847
+ );
848
+ if (match) return store.load(match.id);
849
+ return null;
850
+ }
851
+ var activeBuilder, activeTaskStore, activeTaskTracker, activeTaskGraphId;
852
+ var init_sdd = __esm({
853
+ "src/slash-commands/sdd.ts"() {
854
+ activeBuilder = null;
855
+ activeTaskStore = null;
856
+ activeTaskTracker = null;
857
+ activeTaskGraphId = null;
858
+ }
859
+ });
27
860
  function normalizeKeys(cfg) {
28
861
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
29
862
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -76,9 +909,8 @@ async function runWebUI(opts) {
76
909
  const clients = /* @__PURE__ */ new Map();
77
910
  let abortController = null;
78
911
  const authToken = crypto.randomBytes(16).toString("hex");
79
- const wss = new WebSocketServer({ port, host: "127.0.0.1" });
912
+ const wss = new WebSocketServer({ port, host: "127.0.0.1", maxPayload: 1 * 1024 * 1024 });
80
913
  console.log(`[WebUI] WebSocket server starting on ws://localhost:${port}`);
81
- console.log(`[WebUI] Auth token: ${authToken}`);
82
914
  const eventUnsubscribers = [];
83
915
  function setupEvents() {
84
916
  for (const unsub of eventUnsubscribers) unsub();
@@ -198,6 +1030,8 @@ async function runWebUI(opts) {
198
1030
  }
199
1031
  } else {
200
1032
  if (!tokenOk) {
1033
+ ws.close(4003, "Forbidden: auth token required for non-browser clients");
1034
+ return;
201
1035
  }
202
1036
  }
203
1037
  } catch {
@@ -557,7 +1391,10 @@ async function runWebUI(opts) {
557
1391
  } catch {
558
1392
  return {};
559
1393
  }
560
- return parsed.providers ?? {};
1394
+ if (!parsed.providers) return {};
1395
+ const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
1396
+ const vault = new DefaultSecretVault$1({ keyFile });
1397
+ return decryptConfigSecrets$1(parsed.providers, vault);
561
1398
  }
562
1399
  async function saveProviders(providers) {
563
1400
  if (!opts.globalConfigPath) return;
@@ -574,7 +1411,10 @@ async function runWebUI(opts) {
574
1411
  parsed = {};
575
1412
  }
576
1413
  parsed.providers = providers;
577
- await atomicWrite(opts.globalConfigPath, JSON.stringify(parsed, null, 2), { mode: 384 });
1414
+ const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
1415
+ const vault = new DefaultSecretVault$1({ keyFile });
1416
+ const encrypted = encryptConfigSecrets(parsed, vault);
1417
+ await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
578
1418
  }
579
1419
  function sendResult(ws, success, message) {
580
1420
  send(ws, { type: "key.operation_result", payload: { success, message } });
@@ -723,10 +1563,10 @@ function parseSpawnFlags(input) {
723
1563
  return { description: rest.trim(), opts };
724
1564
  }
725
1565
  async function bootConfig(flags) {
726
- const cwd = typeof flags["cwd"] === "string" ? path14.resolve(flags["cwd"]) : process.cwd();
1566
+ const cwd = typeof flags["cwd"] === "string" ? path17.resolve(flags["cwd"]) : process.cwd();
727
1567
  const pathResolver = new DefaultPathResolver(cwd);
728
1568
  const projectRoot = pathResolver.projectRoot;
729
- const userHome = os3.homedir();
1569
+ const userHome = os4.homedir();
730
1570
  const wpaths = resolveWstackPaths({ projectRoot, userHome });
731
1571
  await ensureProjectMeta(wpaths, projectRoot);
732
1572
  const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
@@ -790,7 +1630,7 @@ var ReadlineInputReader = class {
790
1630
  history = [];
791
1631
  pending = false;
792
1632
  constructor(opts = {}) {
793
- this.historyFile = opts.historyFile ?? path14.join(os3.homedir(), ".wrongstack", "history");
1633
+ this.historyFile = opts.historyFile ?? path17.join(os4.homedir(), ".wrongstack", "history");
794
1634
  }
795
1635
  async loadHistory() {
796
1636
  try {
@@ -802,7 +1642,7 @@ var ReadlineInputReader = class {
802
1642
  }
803
1643
  async saveHistory() {
804
1644
  try {
805
- await fs14.mkdir(path14.dirname(this.historyFile), { recursive: true });
1645
+ await fs14.mkdir(path17.dirname(this.historyFile), { recursive: true });
806
1646
  await fs14.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
807
1647
  } catch {
808
1648
  }
@@ -1284,10 +2124,10 @@ async function detectPackageManager(root, declared) {
1284
2124
  const name = declared.split("@")[0];
1285
2125
  if (name) return name;
1286
2126
  }
1287
- if (await pathExists(path14.join(root, "pnpm-lock.yaml"))) return "pnpm";
1288
- if (await pathExists(path14.join(root, "bun.lockb"))) return "bun";
1289
- if (await pathExists(path14.join(root, "bun.lock"))) return "bun";
1290
- if (await pathExists(path14.join(root, "yarn.lock"))) return "yarn";
2127
+ if (await pathExists(path17.join(root, "pnpm-lock.yaml"))) return "pnpm";
2128
+ if (await pathExists(path17.join(root, "bun.lockb"))) return "bun";
2129
+ if (await pathExists(path17.join(root, "bun.lock"))) return "bun";
2130
+ if (await pathExists(path17.join(root, "yarn.lock"))) return "yarn";
1291
2131
  return "npm";
1292
2132
  }
1293
2133
  function hasUsableScript(scripts, name) {
@@ -1308,7 +2148,7 @@ function parseMakeTargets(makefile) {
1308
2148
  async function detectProjectFacts(root) {
1309
2149
  const facts = { hints: [] };
1310
2150
  try {
1311
- const pkg = JSON.parse(await fs14.readFile(path14.join(root, "package.json"), "utf8"));
2151
+ const pkg = JSON.parse(await fs14.readFile(path17.join(root, "package.json"), "utf8"));
1312
2152
  const scripts = pkg.scripts ?? {};
1313
2153
  const pm = await detectPackageManager(root, pkg.packageManager);
1314
2154
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -1322,14 +2162,14 @@ async function detectProjectFacts(root) {
1322
2162
  } catch {
1323
2163
  }
1324
2164
  try {
1325
- if (!await pathExists(path14.join(root, "pyproject.toml"))) throw new Error("not python");
2165
+ if (!await pathExists(path17.join(root, "pyproject.toml"))) throw new Error("not python");
1326
2166
  facts.test ??= "pytest";
1327
2167
  facts.lint ??= "ruff check .";
1328
2168
  facts.hints.push("pyproject.toml");
1329
2169
  } catch {
1330
2170
  }
1331
2171
  try {
1332
- if (!await pathExists(path14.join(root, "go.mod"))) throw new Error("not go");
2172
+ if (!await pathExists(path17.join(root, "go.mod"))) throw new Error("not go");
1333
2173
  facts.build ??= "go build ./...";
1334
2174
  facts.test ??= "go test ./...";
1335
2175
  facts.run ??= "go run .";
@@ -1337,7 +2177,7 @@ async function detectProjectFacts(root) {
1337
2177
  } catch {
1338
2178
  }
1339
2179
  try {
1340
- if (!await pathExists(path14.join(root, "Cargo.toml"))) throw new Error("not rust");
2180
+ if (!await pathExists(path17.join(root, "Cargo.toml"))) throw new Error("not rust");
1341
2181
  facts.build ??= "cargo build";
1342
2182
  facts.test ??= "cargo test";
1343
2183
  facts.lint ??= "cargo clippy";
@@ -1346,7 +2186,7 @@ async function detectProjectFacts(root) {
1346
2186
  } catch {
1347
2187
  }
1348
2188
  try {
1349
- const makefile = await fs14.readFile(path14.join(root, "Makefile"), "utf8");
2189
+ const makefile = await fs14.readFile(path17.join(root, "Makefile"), "utf8");
1350
2190
  const targets = parseMakeTargets(makefile);
1351
2191
  facts.build ??= targets.has("build") ? "make build" : "make";
1352
2192
  if (targets.has("test")) facts.test ??= "make test";
@@ -1809,8 +2649,8 @@ function buildInitCommand(opts) {
1809
2649
  description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
1810
2650
  async run(args, ctx) {
1811
2651
  const force = args.trim() === "--force";
1812
- const dir = path14.join(ctx.projectRoot, ".wrongstack");
1813
- const file = path14.join(dir, "AGENTS.md");
2652
+ const dir = path17.join(ctx.projectRoot, ".wrongstack");
2653
+ const file = path17.join(dir, "AGENTS.md");
1814
2654
  try {
1815
2655
  await fs14.access(file);
1816
2656
  if (!force) {
@@ -2060,7 +2900,7 @@ function buildExitCommand(opts) {
2060
2900
  function buildSkillCommand(opts) {
2061
2901
  return {
2062
2902
  name: "skill",
2063
- description: "Show skill details or list available skills.",
2903
+ description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
2064
2904
  async run(args) {
2065
2905
  if (!opts.skillLoader) return { message: "No skill loader configured." };
2066
2906
  if (!args.trim()) {
@@ -2198,6 +3038,392 @@ ${lines.join("\n")}
2198
3038
  }
2199
3039
  };
2200
3040
  }
3041
+ function buildYoloCommand(opts) {
3042
+ return {
3043
+ name: "yolo",
3044
+ description: "Toggle or query YOLO (auto-approve) mode.",
3045
+ help: [
3046
+ "Usage:",
3047
+ " /yolo Show current YOLO status",
3048
+ " /yolo on Enable YOLO mode (auto-approve every tool call)",
3049
+ " /yolo off Disable YOLO mode (restore permission prompts)",
3050
+ " /yolo toggle Toggle YOLO mode",
3051
+ "",
3052
+ "YOLO mode skips all permission prompts and auto-approves every tool call.",
3053
+ "Use with caution \u2014 the agent can execute any tool without asking."
3054
+ ].join("\n"),
3055
+ async run(args) {
3056
+ const arg = args.trim().toLowerCase();
3057
+ if (!opts.onYolo) {
3058
+ const msg2 = "YOLO toggle is not available in this session.";
3059
+ opts.renderer.writeWarning(msg2);
3060
+ return { message: msg2 };
3061
+ }
3062
+ if (!arg) {
3063
+ const current = opts.onYolo();
3064
+ const status = current ? `${color.yellow("ON")} ${color.dim("(auto-approving all tool calls)")}` : `${color.green("OFF")} ${color.dim("(permission prompts active)")}`;
3065
+ const msg2 = `YOLO mode: ${status}`;
3066
+ opts.renderer.write(msg2);
3067
+ return { message: msg2 };
3068
+ }
3069
+ let newState;
3070
+ if (arg === "on" || arg === "enable" || arg === "true" || arg === "1") {
3071
+ newState = true;
3072
+ } else if (arg === "off" || arg === "disable" || arg === "false" || arg === "0") {
3073
+ newState = false;
3074
+ } else if (arg === "toggle") {
3075
+ newState = !opts.onYolo();
3076
+ } else {
3077
+ const msg2 = `Unknown argument: ${arg}. Use /yolo on, /yolo off, or /yolo toggle.`;
3078
+ opts.renderer.writeWarning(msg2);
3079
+ return { message: msg2 };
3080
+ }
3081
+ opts.onYolo(newState);
3082
+ const label = newState ? `${color.yellow("ENABLED")} \u2014 all tool calls will be auto-approved` : `${color.green("DISABLED")} \u2014 permission prompts are active`;
3083
+ const msg = `YOLO mode: ${label}`;
3084
+ opts.renderer.write(msg);
3085
+ return { message: msg };
3086
+ }
3087
+ };
3088
+ }
3089
+ function buildAutonomyCommand(opts) {
3090
+ return {
3091
+ name: "autonomy",
3092
+ description: "Toggle or query autonomy mode (self-driving agent).",
3093
+ help: [
3094
+ "Usage:",
3095
+ " /autonomy Show current autonomy status",
3096
+ " /autonomy off Disabled \u2014 agent stops after each turn (default)",
3097
+ " /autonomy suggest Show next-step suggestions after each turn",
3098
+ " /autonomy on Auto-continue \u2014 agent picks next step and proceeds",
3099
+ " /autonomy toggle Cycle: off \u2192 suggest \u2192 auto \u2192 off",
3100
+ "",
3101
+ "Modes:",
3102
+ " off \u2014 Normal interactive mode. Agent stops and waits.",
3103
+ " suggest \u2014 After each turn, agent suggests next steps. You pick.",
3104
+ " auto \u2014 After each turn, agent picks the best next step and continues.",
3105
+ " Runs indefinitely until you press Esc or Ctrl+C.",
3106
+ "",
3107
+ "In auto mode the agent works autonomously. Press Esc to redirect,",
3108
+ "Ctrl+C to stop. The agent suggests context-aware next steps based on",
3109
+ "the conversation history."
3110
+ ].join("\n"),
3111
+ async run(args) {
3112
+ const arg = args.trim().toLowerCase();
3113
+ if (!opts.onAutonomy) {
3114
+ const msg2 = "Autonomy mode is not available in this session.";
3115
+ opts.renderer.writeWarning(msg2);
3116
+ return { message: msg2 };
3117
+ }
3118
+ if (!arg) {
3119
+ const current = opts.onAutonomy();
3120
+ const labels2 = {
3121
+ off: `${color.green("OFF")} ${color.dim("(agent stops after each turn)")}`,
3122
+ suggest: `${color.cyan("SUGGEST")} ${color.dim("(shows next-step suggestions)")}`,
3123
+ auto: `${color.yellow("AUTO")} ${color.dim("(self-driving \u2014 Esc to redirect, Ctrl+C to stop)")}`
3124
+ };
3125
+ const msg2 = `Autonomy mode: ${labels2[current]}`;
3126
+ opts.renderer.write(msg2);
3127
+ return { message: msg2 };
3128
+ }
3129
+ let newMode;
3130
+ if (arg === "on" || arg === "enable" || arg === "true" || arg === "auto") {
3131
+ newMode = "auto";
3132
+ } else if (arg === "off" || arg === "disable" || arg === "false") {
3133
+ newMode = "off";
3134
+ } else if (arg === "suggest" || arg === "suggestions") {
3135
+ newMode = "suggest";
3136
+ } else if (arg === "toggle" || arg === "cycle") {
3137
+ const current = opts.onAutonomy() ?? "off";
3138
+ const cycle = ["off", "suggest", "auto"];
3139
+ newMode = cycle[(cycle.indexOf(current) + 1) % cycle.length] ?? "off";
3140
+ } else {
3141
+ const msg2 = `Unknown argument: ${arg}. Use /autonomy on, /autonomy off, /autonomy suggest, or /autonomy toggle.`;
3142
+ opts.renderer.writeWarning(msg2);
3143
+ return { message: msg2 };
3144
+ }
3145
+ opts.onAutonomy(newMode);
3146
+ const labels = {
3147
+ off: `${color.green("OFF")} \u2014 agent stops after each turn`,
3148
+ suggest: `${color.cyan("SUGGEST")} \u2014 shows next-step suggestions after each turn`,
3149
+ auto: `${color.yellow("AUTO")} \u2014 self-driving, agent continues automatically`
3150
+ };
3151
+ const msg = `Autonomy mode: ${labels[newMode]}`;
3152
+ opts.renderer.write(msg);
3153
+ return { message: msg };
3154
+ }
3155
+ };
3156
+ }
3157
+
3158
+ // src/slash-commands/mode.ts
3159
+ function buildModeCommand(opts) {
3160
+ return {
3161
+ name: "mode",
3162
+ description: "Switch or view the current mode",
3163
+ help: [
3164
+ "Usage:",
3165
+ " /mode Show current mode and available modes",
3166
+ " /mode <id> Switch to a different mode",
3167
+ "",
3168
+ "Available modes:",
3169
+ " default General-purpose coding assistant",
3170
+ " brief Fast, no-nonsense \u2014 get to the point",
3171
+ " teach Mentor mode \u2014 explains why, not just what",
3172
+ " code-reviewer, code-auditor, architect, debugger, tester, devops, refactorer",
3173
+ "",
3174
+ "Example:",
3175
+ " /mode brief Switch to brief mode",
3176
+ " /mode teach Switch to teach mode"
3177
+ ].join("\n"),
3178
+ async run(args) {
3179
+ const modeStore = opts.modeStore;
3180
+ if (!modeStore) {
3181
+ return { message: "Mode store not available in this context." };
3182
+ }
3183
+ const modes = await modeStore.listModes();
3184
+ const active = await modeStore.getActiveMode();
3185
+ if (!args.trim()) {
3186
+ const lines = [`Current mode: ${active?.name ?? "none"}`, "", "Available modes:"];
3187
+ for (const m of modes) {
3188
+ const mark = m.id === active?.id ? " [active]" : "";
3189
+ lines.push(` ${m.id} \u2014 ${m.description}${mark}`);
3190
+ }
3191
+ return { message: lines.join("\n") };
3192
+ }
3193
+ const target = args.trim().toLowerCase();
3194
+ const targetMode = modes.find((m) => m.id === target);
3195
+ if (!targetMode) {
3196
+ const available = modes.map((m) => m.id).join(", ");
3197
+ return { message: `Unknown mode "${target}". Available: ${available}` };
3198
+ }
3199
+ await modeStore.setActiveMode(targetMode.id);
3200
+ return {
3201
+ message: `Switched to "${targetMode.name}" mode.
3202
+ ${targetMode.description}`
3203
+ };
3204
+ }
3205
+ };
3206
+ }
3207
+
3208
+ // src/slash-commands/index.ts
3209
+ init_sdd();
3210
+
3211
+ // src/slash-commands/skill-generator.ts
3212
+ function buildSkillGeneratorCommand(opts) {
3213
+ return {
3214
+ name: "skill-gen",
3215
+ description: "Create a new AI skill interactively. The AI will guide you.",
3216
+ help: [
3217
+ "\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557",
3218
+ "",
3219
+ "Create new AI skills with AI guidance.",
3220
+ "",
3221
+ "Usage:",
3222
+ " /skill-gen Start skill creation",
3223
+ " /skill-gen list List existing skills",
3224
+ " /skill-gen edit <name> View an existing skill",
3225
+ "",
3226
+ "The AI will ask you questions and create the skill file.",
3227
+ "Skills are saved to .wrongstack/skills/<name>/SKILL.md"
3228
+ ].join("\n"),
3229
+ async run(args) {
3230
+ const trimmed = args.trim();
3231
+ if (trimmed === "list" || trimmed === "ls") {
3232
+ if (!opts.skillLoader) return { message: "No skill loader configured." };
3233
+ const entries = await opts.skillLoader.listEntries();
3234
+ if (entries.length === 0) return { message: "No skills found." };
3235
+ const lines = entries.map((e) => {
3236
+ const src = e.source === "project" ? "\u{1F4C1}" : e.source === "user" ? "\u{1F464}" : "\u{1F4E6}";
3237
+ return ` ${src} ${e.name}
3238
+ ${e.trigger}`;
3239
+ });
3240
+ return { message: `Available Skills:
3241
+ ${lines.join("\n\n")}
3242
+ ` };
3243
+ }
3244
+ if (trimmed.startsWith("edit ")) {
3245
+ const skillName = trimmed.slice(5).trim();
3246
+ if (!opts.skillLoader) return { message: "No skill loader configured." };
3247
+ const skill = await opts.skillLoader.find(skillName);
3248
+ if (!skill) return { message: `Skill "${skillName}" not found.` };
3249
+ const body = await opts.skillLoader.readBody(skillName);
3250
+ return {
3251
+ message: [
3252
+ `Skill: ${skillName}`,
3253
+ `Path: ${skill.path}`,
3254
+ "",
3255
+ body
3256
+ ].join("\n")
3257
+ };
3258
+ }
3259
+ return {
3260
+ 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.",
3261
+ 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."
3262
+ };
3263
+ }
3264
+ };
3265
+ }
3266
+ function makeInstaller(opts, projectRoot, global) {
3267
+ const globalRoot = path17.join(os4.homedir(), ".wrongstack");
3268
+ return new SkillInstaller({
3269
+ manifestPath: path17.join(globalRoot, "installed-skills.json"),
3270
+ projectSkillsDir: path17.join(projectRoot, ".wrongstack", "skills"),
3271
+ globalSkillsDir: path17.join(globalRoot, "skills"),
3272
+ projectHash: projectHash(projectRoot),
3273
+ skillLoader: opts.skillLoader
3274
+ });
3275
+ }
3276
+ function buildSkillInstallCommand(opts) {
3277
+ return {
3278
+ name: "skill-install",
3279
+ description: "Install skills from a GitHub repository.",
3280
+ argsHint: "<user/repo[@ref]> [--global]",
3281
+ help: [
3282
+ "\u2554\u2550\u2550\u2550 Skill Install \u2550\u2550\u2550\u2557",
3283
+ "",
3284
+ "Install skills from a GitHub repository.",
3285
+ "",
3286
+ "Usage:",
3287
+ " /skill-install <user/repo> Install from default branch (main)",
3288
+ " /skill-install <user/repo@ref> Install specific tag/branch/commit",
3289
+ " /skill-install <user/repo> --global Install to user-global skills",
3290
+ "",
3291
+ "Supports both single-skill repos (SKILL.md at root)",
3292
+ "and multi-skill repos (skills/ subdirectory).",
3293
+ "",
3294
+ "Examples:",
3295
+ " /skill-install wrongstack/awesome-skills",
3296
+ " /skill-install wrongstack/skills@v1.0",
3297
+ " /skill-install user/my-skills --global"
3298
+ ].join("\n"),
3299
+ async run(args, ctx) {
3300
+ const parts = args.trim().split(/\s+/);
3301
+ const ref = parts.find((p) => !p.startsWith("--"));
3302
+ const isGlobal = parts.includes("--global");
3303
+ if (!ref) {
3304
+ return { message: "Usage: /skill-install <user/repo[@ref]> [--global]" };
3305
+ }
3306
+ const installer = makeInstaller(opts, ctx.projectRoot);
3307
+ try {
3308
+ const results = await installer.install(ref, { global: isGlobal });
3309
+ if (results.length === 0) {
3310
+ return { message: "No skills found in the repository." };
3311
+ }
3312
+ const scope = isGlobal ? "user-global" : "project";
3313
+ const lines = [`Installed ${results.length} skill(s) [${scope}]:`];
3314
+ for (const r of results) {
3315
+ lines.push(` \u2713 ${r.name} (${r.source}@${r.ref})`);
3316
+ lines.push(` \u2192 ${r.path}`);
3317
+ }
3318
+ return { message: lines.join("\n") };
3319
+ } catch (err) {
3320
+ const msg = err instanceof Error ? err.message : String(err);
3321
+ opts.renderer.writeError(`Install failed: ${msg}`);
3322
+ return { message: `\u2717 Install failed: ${msg}` };
3323
+ }
3324
+ }
3325
+ };
3326
+ }
3327
+ function buildSkillUpdateCommand(opts) {
3328
+ return {
3329
+ name: "skill-update",
3330
+ description: "Update installed skills from their GitHub source.",
3331
+ argsHint: "[name|ref] [--global]",
3332
+ help: [
3333
+ "\u2554\u2550\u2550\u2550 Skill Update \u2550\u2550\u2550\u2557",
3334
+ "",
3335
+ "Update installed skills from their GitHub source.",
3336
+ "",
3337
+ "Usage:",
3338
+ " /skill-update Update all installed skills",
3339
+ " /skill-update <name> Update a specific skill",
3340
+ " /skill-update <user/repo@ref> Update to a different ref",
3341
+ " /skill-update <name> --global Update a global skill"
3342
+ ].join("\n"),
3343
+ async run(args, ctx) {
3344
+ const parts = args.trim().split(/\s+/);
3345
+ const nameOrRef = parts.find((p) => !p.startsWith("--"));
3346
+ const isGlobal = parts.includes("--global");
3347
+ const installer = makeInstaller(opts, ctx.projectRoot);
3348
+ try {
3349
+ const result = await installer.update(nameOrRef, { global: isGlobal });
3350
+ const lines = [];
3351
+ if (result.updated.length > 0) {
3352
+ lines.push(`Updated ${result.updated.length} skill(s):`);
3353
+ for (const u of result.updated) {
3354
+ if (u.oldRef !== u.newRef) {
3355
+ lines.push(` \u2713 ${u.name} (${u.oldRef} \u2192 ${u.newRef})`);
3356
+ } else {
3357
+ lines.push(` \u2713 ${u.name} (refreshed)`);
3358
+ }
3359
+ }
3360
+ }
3361
+ if (result.unchanged.length > 0) {
3362
+ lines.push(`Up to date: ${result.unchanged.join(", ")}`);
3363
+ }
3364
+ if (result.errors.length > 0) {
3365
+ for (const e of result.errors) {
3366
+ lines.push(` \u2717 ${e.name}: ${e.error}`);
3367
+ }
3368
+ }
3369
+ if (lines.length === 0) {
3370
+ return { message: "No installed skills to update." };
3371
+ }
3372
+ return { message: lines.join("\n") };
3373
+ } catch (err) {
3374
+ const msg = err instanceof Error ? err.message : String(err);
3375
+ return { message: `\u2717 Update failed: ${msg}` };
3376
+ }
3377
+ }
3378
+ };
3379
+ }
3380
+ function buildSkillUninstallCommand(opts) {
3381
+ return {
3382
+ name: "skill-uninstall",
3383
+ description: "Remove an installed skill.",
3384
+ argsHint: "<name> [--global]",
3385
+ help: [
3386
+ "\u2554\u2550\u2550\u2550 Skill Uninstall \u2550\u2550\u2550\u2557",
3387
+ "",
3388
+ "Remove an installed skill and its files.",
3389
+ "",
3390
+ "Usage:",
3391
+ " /skill-uninstall <name> Remove from project skills",
3392
+ " /skill-uninstall <name> --global Remove from user-global skills"
3393
+ ].join("\n"),
3394
+ async run(args, ctx) {
3395
+ const parts = args.trim().split(/\s+/);
3396
+ const name = parts.find((p) => !p.startsWith("--"));
3397
+ const isGlobal = parts.includes("--global");
3398
+ if (!name) {
3399
+ const installer2 = makeInstaller(opts, ctx.projectRoot);
3400
+ const installed = await installer2.listInstalled();
3401
+ if (installed.length === 0) {
3402
+ return { message: "No installed skills found." };
3403
+ }
3404
+ const scope = isGlobal ? "user" : "project";
3405
+ const filtered = installed.filter((s) => s.scope === scope);
3406
+ if (filtered.length === 0) {
3407
+ return { message: `No installed skills found (${scope} scope).` };
3408
+ }
3409
+ const lines = [`Installed skills (${scope}):`];
3410
+ for (const s of filtered) {
3411
+ lines.push(` ${s.name} ${s.source}@${s.ref} (${s.installedAt.slice(0, 10)})`);
3412
+ }
3413
+ lines.push("", "Use /skill-uninstall <name> to remove.");
3414
+ return { message: lines.join("\n") };
3415
+ }
3416
+ const installer = makeInstaller(opts, ctx.projectRoot);
3417
+ try {
3418
+ await installer.uninstall(name, { global: isGlobal });
3419
+ return { message: `\u2713 Skill "${name}" uninstalled.` };
3420
+ } catch (err) {
3421
+ const msg = err instanceof Error ? err.message : String(err);
3422
+ return { message: `\u2717 Uninstall failed: ${msg}` };
3423
+ }
3424
+ }
3425
+ };
3426
+ }
2201
3427
 
2202
3428
  // src/slash-commands/index.ts
2203
3429
  function buildBuiltinSlashCommands(opts) {
@@ -2209,6 +3435,10 @@ function buildBuiltinSlashCommands(opts) {
2209
3435
  buildContextCommand(opts),
2210
3436
  buildToolsCommand(opts),
2211
3437
  buildSkillCommand(opts),
3438
+ buildSkillGeneratorCommand(opts),
3439
+ buildSkillInstallCommand(opts),
3440
+ buildSkillUpdateCommand(opts),
3441
+ buildSkillUninstallCommand(opts),
2212
3442
  buildPluginCommand(opts),
2213
3443
  buildDiagCommand(opts),
2214
3444
  buildStatsCommand(opts),
@@ -2221,8 +3451,12 @@ function buildBuiltinSlashCommands(opts) {
2221
3451
  buildMemoryCommand(opts),
2222
3452
  buildTodosCommand(opts),
2223
3453
  buildPlanCommand(opts),
3454
+ buildSddCommand(opts),
2224
3455
  buildSaveCommand(opts),
2225
3456
  buildLoadCommand(opts),
3457
+ buildYoloCommand(opts),
3458
+ buildAutonomyCommand(opts),
3459
+ buildModeCommand(opts),
2226
3460
  buildExitCommand(opts)
2227
3461
  ];
2228
3462
  }
@@ -2242,13 +3476,13 @@ var MANIFESTS = [
2242
3476
  ];
2243
3477
  async function detectProjectKind(projectRoot) {
2244
3478
  try {
2245
- await fs14.access(path14.join(projectRoot, ".wrongstack", "AGENTS.md"));
3479
+ await fs14.access(path17.join(projectRoot, ".wrongstack", "AGENTS.md"));
2246
3480
  return "initialized";
2247
3481
  } catch {
2248
3482
  }
2249
3483
  for (const m of MANIFESTS) {
2250
3484
  try {
2251
- await fs14.access(path14.join(projectRoot, m));
3485
+ await fs14.access(path17.join(projectRoot, m));
2252
3486
  return "project";
2253
3487
  } catch {
2254
3488
  }
@@ -2256,8 +3490,8 @@ async function detectProjectKind(projectRoot) {
2256
3490
  return "empty";
2257
3491
  }
2258
3492
  async function scaffoldAgentsMd(projectRoot) {
2259
- const dir = path14.join(projectRoot, ".wrongstack");
2260
- const file = path14.join(dir, "AGENTS.md");
3493
+ const dir = path17.join(projectRoot, ".wrongstack");
3494
+ const file = path17.join(dir, "AGENTS.md");
2261
3495
  const facts = await detectProjectFacts(projectRoot);
2262
3496
  const body = renderAgentsTemplate(facts);
2263
3497
  await fs14.mkdir(dir, { recursive: true });
@@ -2270,7 +3504,7 @@ async function runProjectCheck(opts) {
2270
3504
  if (kind === "initialized") {
2271
3505
  renderer.write(
2272
3506
  `
2273
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path14.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
3507
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path17.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
2274
3508
  `
2275
3509
  );
2276
3510
  return true;
@@ -2326,9 +3560,9 @@ async function runLaunchPrompts(opts) {
2326
3560
  yolo = yoloPinned;
2327
3561
  } else {
2328
3562
  const answer = (await reader.readLine(
2329
- ` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[y/N]")} `
3563
+ ` ${color.amber("?")} YOLO mode ${color.dim("(auto-approve every tool call)")} ${color.dim("[Y/n]")} `
2330
3564
  )).trim().toLowerCase();
2331
- yolo = answer === "y" || answer === "yes";
3565
+ yolo = answer !== "n" && answer !== "no";
2332
3566
  }
2333
3567
  renderer.write(
2334
3568
  `
@@ -2566,14 +3800,14 @@ function summarize(value, name) {
2566
3800
  if (typeof v === "object" && v !== null) {
2567
3801
  const o = v;
2568
3802
  if (name === "edit") {
2569
- const path15 = typeof o["path"] === "string" ? o["path"] : "";
3803
+ const path18 = typeof o["path"] === "string" ? o["path"] : "";
2570
3804
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
2571
- return `${path15} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
3805
+ return `${path18} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
2572
3806
  }
2573
3807
  if (name === "write") {
2574
- const path15 = typeof o["path"] === "string" ? o["path"] : "";
3808
+ const path18 = typeof o["path"] === "string" ? o["path"] : "";
2575
3809
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
2576
- return bytes !== void 0 ? `${path15} ${bytes}B` : path15;
3810
+ return bytes !== void 0 ? `${path18} ${bytes}B` : path18;
2577
3811
  }
2578
3812
  if (typeof o["count"] === "number") {
2579
3813
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -3199,7 +4433,7 @@ async function mutateProviders(deps, mutator) {
3199
4433
  const providers = decrypted.providers ?? {};
3200
4434
  mutator(providers);
3201
4435
  decrypted.providers = providers;
3202
- const encrypted = encryptConfigSecrets(decrypted, deps.vault);
4436
+ const encrypted = encryptConfigSecrets$1(decrypted, deps.vault);
3203
4437
  await atomicWrite(deps.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3204
4438
  }
3205
4439
 
@@ -3257,7 +4491,7 @@ var diagCmd = async (_args, deps) => {
3257
4491
  ` modelsCache: ${deps.paths.modelsCache}`,
3258
4492
  ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
3259
4493
  ` node: ${process.version}`,
3260
- ` os: ${os3.platform()} ${os3.release()}`,
4494
+ ` os: ${os4.platform()} ${os4.release()}`,
3261
4495
  ` provider: ${cfg.provider ?? "<unset>"}`,
3262
4496
  ` model: ${cfg.model ?? "<unset>"}`,
3263
4497
  ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
@@ -3336,7 +4570,7 @@ var doctorCmd = async (_args, deps) => {
3336
4570
  }
3337
4571
  try {
3338
4572
  await fs14.mkdir(deps.paths.projectSessions, { recursive: true });
3339
- const probe = path14.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
4573
+ const probe = path17.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
3340
4574
  await fs14.writeFile(probe, "");
3341
4575
  await fs14.unlink(probe);
3342
4576
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -3439,8 +4673,8 @@ var exportCmd = async (args, deps) => {
3439
4673
  return 1;
3440
4674
  }
3441
4675
  if (output) {
3442
- await fs14.mkdir(path14.dirname(path14.resolve(deps.cwd, output)), { recursive: true });
3443
- await fs14.writeFile(path14.resolve(deps.cwd, output), rendered, "utf8");
4676
+ await fs14.mkdir(path17.dirname(path17.resolve(deps.cwd, output)), { recursive: true });
4677
+ await fs14.writeFile(path17.resolve(deps.cwd, output), rendered, "utf8");
3444
4678
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
3445
4679
  `);
3446
4680
  } else {
@@ -3500,9 +4734,12 @@ var initCmd = async (_args, deps) => {
3500
4734
  await fs14.mkdir(deps.paths.globalRoot, { recursive: true });
3501
4735
  const config = { version: 1, provider: providerId, model: modelId };
3502
4736
  if (apiKey) config.apiKey = apiKey;
3503
- await atomicWrite(deps.paths.globalConfig, JSON.stringify(config, null, 2));
3504
- await fs14.mkdir(path14.join(deps.projectRoot, ".wrongstack"), { recursive: true });
3505
- const agentsFile = path14.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
4737
+ const keyFile = path17.join(path17.dirname(deps.paths.globalConfig), ".key");
4738
+ const vault = new DefaultSecretVault$1({ keyFile });
4739
+ const encrypted = encryptConfigSecrets(config, vault);
4740
+ await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
4741
+ await fs14.mkdir(path17.join(deps.projectRoot, ".wrongstack"), { recursive: true });
4742
+ const agentsFile = path17.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
3506
4743
  try {
3507
4744
  await fs14.access(agentsFile);
3508
4745
  } catch {
@@ -3812,7 +5049,7 @@ var usageCmd = async (_args, deps) => {
3812
5049
  return 0;
3813
5050
  };
3814
5051
  var projectsCmd = async (_args, deps) => {
3815
- const projectsRoot = path14.join(deps.paths.globalRoot, "projects");
5052
+ const projectsRoot = path17.join(deps.paths.globalRoot, "projects");
3816
5053
  try {
3817
5054
  const entries = await fs14.readdir(projectsRoot);
3818
5055
  if (entries.length === 0) {
@@ -3822,7 +5059,7 @@ var projectsCmd = async (_args, deps) => {
3822
5059
  for (const hash of entries) {
3823
5060
  try {
3824
5061
  const meta = JSON.parse(
3825
- await fs14.readFile(path14.join(projectsRoot, hash, "meta.json"), "utf8")
5062
+ await fs14.readFile(path17.join(projectsRoot, hash, "meta.json"), "utf8")
3826
5063
  );
3827
5064
  deps.renderer.write(
3828
5065
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -4021,7 +5258,7 @@ var skillsCmd = async (_args, deps) => {
4021
5258
  };
4022
5259
  var versionCmd = async (_args, deps) => {
4023
5260
  deps.renderer.write(
4024
- `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os3.platform()})
5261
+ `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os4.platform()})
4025
5262
  `
4026
5263
  );
4027
5264
  return 0;
@@ -4097,31 +5334,29 @@ function fmtDuration(ms) {
4097
5334
  const remMin = m - h * 60;
4098
5335
  return `${h}h${remMin}m`;
4099
5336
  }
4100
- function fmtTaskResultLine(r, color28) {
5337
+ function fmtTaskResultLine(r, color30) {
4101
5338
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
4102
5339
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
4103
5340
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
4104
5341
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
4105
- const errKindChip = errKind ? color28.dim(` [${errKind}]`) : "";
4106
- const errSnip = errMsg || errKind ? `${errKindChip}${color28.dim(errTail)}` : "";
5342
+ const errKindChip = errKind ? color30.dim(` [${errKind}]`) : "";
5343
+ const errSnip = errMsg || errKind ? `${errKindChip}${color30.dim(errTail)}` : "";
4107
5344
  switch (r.status) {
4108
5345
  case "success":
4109
- return { mark: color28.green("\u2713"), stats, tail: "" };
5346
+ return { mark: color30.green("\u2713"), stats, tail: "" };
4110
5347
  case "timeout":
4111
- return { mark: color28.yellow("\u23F1"), stats: `${color28.yellow("timeout")} ${stats}`, tail: errSnip };
5348
+ return { mark: color30.yellow("\u23F1"), stats: `${color30.yellow("timeout")} ${stats}`, tail: errSnip };
4112
5349
  case "stopped":
4113
- return { mark: color28.dim("\u2298"), stats: `${color28.dim("stopped")} ${stats}`, tail: errSnip };
5350
+ return { mark: color30.dim("\u2298"), stats: `${color30.dim("stopped")} ${stats}`, tail: errSnip };
4114
5351
  case "failed":
4115
- return { mark: color28.red("\u2717"), stats: `${color28.red("failed")} ${stats}`, tail: errSnip };
5352
+ return { mark: color30.red("\u2717"), stats: `${color30.red("failed")} ${stats}`, tail: errSnip };
4116
5353
  }
4117
5354
  }
4118
-
4119
- // src/boot.ts
4120
5355
  function resolveBundledSkillsDir() {
4121
5356
  try {
4122
5357
  const req2 = createRequire(import.meta.url);
4123
5358
  const corePkg = req2.resolve("@wrongstack/core/package.json");
4124
- return path14.join(path14.dirname(corePkg), "skills");
5359
+ return path17.join(path17.dirname(corePkg), "skills");
4125
5360
  } catch {
4126
5361
  return void 0;
4127
5362
  }
@@ -4152,11 +5387,15 @@ async function boot(argv) {
4152
5387
  });
4153
5388
  const first = positional[0];
4154
5389
  if (first && subcommands[first]) {
4155
- const sessionStore = new DefaultSessionStore({ dir: wpaths.projectSessions });
4156
- const skillLoader = new DefaultSkillLoader({
4157
- paths: wpaths,
4158
- bundledDir: resolveBundledSkillsDir()
5390
+ const container = createDefaultContainer({
5391
+ config,
5392
+ wpaths,
5393
+ logger,
5394
+ modelsRegistry,
5395
+ bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir() : void 0
4159
5396
  });
5397
+ const sessionStore = container.resolve(TOKENS.SessionStore);
5398
+ const skillLoader = container.resolve(TOKENS.SkillLoader);
4160
5399
  const toolRegistryForSubcmd = new ToolRegistry();
4161
5400
  toolRegistryForSubcmd.registerAllOrThrow(
4162
5401
  [...builtinToolsPack.tools ?? []],
@@ -4257,6 +5496,9 @@ async function boot(argv) {
4257
5496
  logger
4258
5497
  };
4259
5498
  }
5499
+
5500
+ // src/repl.ts
5501
+ init_sdd();
4260
5502
  async function runRepl(opts) {
4261
5503
  if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
4262
5504
  let activeCtrl;
@@ -4300,6 +5542,58 @@ async function runRepl(opts) {
4300
5542
  if (res?.message) opts.renderer.write(`${res.message}
4301
5543
  `);
4302
5544
  if (res?.exit) break;
5545
+ if (res?.runText) {
5546
+ const runBlocks = [{ type: "text", text: res.runText }];
5547
+ const runCtrl2 = new AbortController();
5548
+ activeCtrl = runCtrl2;
5549
+ try {
5550
+ const runResult = await opts.agent.run(runBlocks, { signal: runCtrl2.signal });
5551
+ if (runResult.status === "done" && runResult.finalText) {
5552
+ const specSaved = await trySaveSpecFromAIOutput(runResult.finalText);
5553
+ if (specSaved) {
5554
+ opts.renderer.write(
5555
+ `
5556
+ ${color.cyan(" \u2713 Spec detected and saved! Use /sdd approve to continue.")}
5557
+ `
5558
+ );
5559
+ }
5560
+ const planSaved = trySaveImplementationPlan(runResult.finalText);
5561
+ if (planSaved) {
5562
+ opts.renderer.write(
5563
+ `
5564
+ ${color.cyan(" \u2713 Implementation plan saved!")}
5565
+ `
5566
+ );
5567
+ }
5568
+ const tasksSaved = await trySaveTasksFromAIOutput(runResult.finalText);
5569
+ if (tasksSaved) {
5570
+ const progress = getTaskProgress();
5571
+ const count = progress?.total ?? 0;
5572
+ opts.renderer.write(
5573
+ `
5574
+ ${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`)}
5575
+ `
5576
+ );
5577
+ }
5578
+ const sddPhase2 = getActiveSDDPhase();
5579
+ if (sddPhase2 === "executing") {
5580
+ const autoCompleted = autoDetectTaskCompletion(runResult.finalText);
5581
+ if (autoCompleted > 0) {
5582
+ const progress = getTaskProgress();
5583
+ if (progress) {
5584
+ opts.renderer.write(
5585
+ `
5586
+ ${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`)}
5587
+ `
5588
+ );
5589
+ }
5590
+ }
5591
+ }
5592
+ }
5593
+ } catch (runErr) {
5594
+ opts.renderer.writeWarning("AI auto-trigger failed. You can continue manually.");
5595
+ }
5596
+ }
4303
5597
  } catch (err) {
4304
5598
  opts.renderer.writeError(err instanceof Error ? err.message : String(err));
4305
5599
  }
@@ -4312,20 +5606,47 @@ async function runRepl(opts) {
4312
5606
  `));
4313
5607
  }
4314
5608
  const blocks = await builder.submit();
5609
+ const sddContext = getActiveSDDContext();
5610
+ const taskList = getTaskListText();
5611
+ const taskProgress = getTaskProgress();
5612
+ const sddPhase = getActiveSDDPhase();
5613
+ let sddPrefix = "";
5614
+ if (sddContext) {
5615
+ sddPrefix = `[SDD SESSION ACTIVE]
5616
+ ${sddContext}`;
5617
+ if (taskList) {
5618
+ sddPrefix += `
5619
+
5620
+ **Current Task List:**
5621
+ ${taskList}`;
5622
+ }
5623
+ if (taskProgress && taskProgress.total > 0) {
5624
+ sddPrefix += `
5625
+ **Progress:** ${taskProgress.completed}/${taskProgress.total} (${taskProgress.percent}%)`;
5626
+ }
5627
+ if (sddPhase === "executing" && taskProgress && taskProgress.percent === 100) {
5628
+ sddPrefix += "\n\n**All tasks completed! Provide a summary of everything implemented.**";
5629
+ }
5630
+ sddPrefix += "\n\n---\nUser message:\n";
5631
+ }
5632
+ const effectiveBlocks = sddPrefix ? [
5633
+ { type: "text", text: sddPrefix },
5634
+ ...blocks
5635
+ ] : blocks;
4315
5636
  const runCtrl = new AbortController();
4316
5637
  activeCtrl = runCtrl;
4317
5638
  try {
4318
5639
  const startedAt = Date.now();
4319
5640
  const before = opts.tokenCounter?.total();
4320
5641
  const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
4321
- const routed = blocks.some((block) => block.type === "image") ? await routeImagesForModel(blocks, {
5642
+ const routed = effectiveBlocks.some((block) => block.type === "image") ? await routeImagesForModel(effectiveBlocks, {
4322
5643
  supportsVision: opts.supportsVision ? await opts.supportsVision() : opts.agent.ctx.provider.capabilities.vision,
4323
5644
  adapters: opts.visionAdapters ?? [],
4324
5645
  ctx: opts.agent.ctx,
4325
5646
  signal: runCtrl.signal,
4326
5647
  providerId: opts.agent.ctx.provider.id,
4327
5648
  model: opts.agent.ctx.model
4328
- }) : { blocks, route: "none", convertedImages: 0 };
5649
+ }) : { blocks: effectiveBlocks, route: "none", convertedImages: 0 };
4329
5650
  if (routed.route === "adapter") {
4330
5651
  opts.renderer.write(
4331
5652
  color.dim(
@@ -4348,6 +5669,55 @@ async function runRepl(opts) {
4348
5669
  } else if (result.status === "max_iterations") {
4349
5670
  opts.renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
4350
5671
  }
5672
+ if (result.status === "done" && result.finalText && sddContext) {
5673
+ const specSaved = await trySaveSpecFromAIOutput(result.finalText);
5674
+ if (specSaved) {
5675
+ opts.renderer.write(
5676
+ `
5677
+ ${color.cyan(" \u2713 Spec detected and saved! Use /sdd approve to continue.")}
5678
+ `
5679
+ );
5680
+ }
5681
+ const planSaved = trySaveImplementationPlan(result.finalText);
5682
+ if (planSaved) {
5683
+ opts.renderer.write(
5684
+ `
5685
+ ${color.cyan(" \u2713 Implementation plan saved!")}
5686
+ `
5687
+ );
5688
+ }
5689
+ const tasksSaved = await trySaveTasksFromAIOutput(result.finalText);
5690
+ if (tasksSaved) {
5691
+ const progress = getTaskProgress();
5692
+ const count = progress?.total ?? 0;
5693
+ opts.renderer.write(
5694
+ `
5695
+ ${color.cyan(` \u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`)}
5696
+ `
5697
+ );
5698
+ }
5699
+ const phase = getActiveSDDPhase();
5700
+ if (phase === "executing") {
5701
+ const autoCompleted = autoDetectTaskCompletion(result.finalText);
5702
+ if (autoCompleted > 0) {
5703
+ const progress = getTaskProgress();
5704
+ if (progress) {
5705
+ opts.renderer.write(
5706
+ `
5707
+ ${color.cyan(` \u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`)}
5708
+ `
5709
+ );
5710
+ if (progress.percent === 100) {
5711
+ opts.renderer.write(
5712
+ `
5713
+ ${color.green(" \u{1F389} All tasks completed! Use /sdd cancel to end the session.")}
5714
+ `
5715
+ );
5716
+ }
5717
+ }
5718
+ }
5719
+ }
5720
+ }
4351
5721
  if (opts.tokenCounter && before) {
4352
5722
  const after = opts.tokenCounter.total();
4353
5723
  const costAfter = opts.tokenCounter.estimateCost().total;
@@ -4360,6 +5730,49 @@ ${color.dim(
4360
5730
  `
4361
5731
  );
4362
5732
  }
5733
+ if (result.status === "done" && opts.getAutonomy) {
5734
+ const autonomy = opts.getAutonomy();
5735
+ if (autonomy === "auto") {
5736
+ const nextPrompt = 'Based on what you just did, what is the single most important next step? Just do it \u2014 execute the next logical step without asking for confirmation. If there is nothing meaningful left to do, say "DONE" and nothing else.';
5737
+ opts.renderer.write(color.dim("\n \u21B3 [autonomy] continuing\u2026\n"));
5738
+ const nextBlocks = [{ type: "text", text: nextPrompt }];
5739
+ const nextCtrl = new AbortController();
5740
+ activeCtrl = nextCtrl;
5741
+ try {
5742
+ const nextResult = await opts.agent.run(nextBlocks, { signal: nextCtrl.signal });
5743
+ if (nextResult.status === "done" && nextResult.finalText?.trim() === "DONE") {
5744
+ opts.renderer.write(color.dim("\n \u21B3 [autonomy] agent reports task complete.\n"));
5745
+ }
5746
+ if (opts.getAutonomy() === "auto" && nextResult.status === "done") {
5747
+ }
5748
+ } catch (err) {
5749
+ opts.renderer.writeError(
5750
+ `[autonomy] ${err instanceof Error ? err.message : String(err)}`
5751
+ );
5752
+ } finally {
5753
+ activeCtrl = void 0;
5754
+ }
5755
+ } else if (autonomy === "suggest") {
5756
+ const suggestPrompt = 'Based on what you just did, suggest 3 concrete next steps. Format: numbered list, one line each, no explanation. If there is nothing meaningful left, say "No further steps needed."';
5757
+ const suggestBlocks = [{ type: "text", text: suggestPrompt }];
5758
+ const suggestCtrl = new AbortController();
5759
+ activeCtrl = suggestCtrl;
5760
+ try {
5761
+ const suggestResult = await opts.agent.run(suggestBlocks, { signal: suggestCtrl.signal });
5762
+ if (suggestResult.status === "done" && suggestResult.finalText) {
5763
+ opts.renderer.write(
5764
+ `
5765
+ ${color.cyan(" Suggested next steps:")}
5766
+ ${suggestResult.finalText}
5767
+ `
5768
+ );
5769
+ }
5770
+ } catch {
5771
+ } finally {
5772
+ activeCtrl = void 0;
5773
+ }
5774
+ }
5775
+ }
4363
5776
  } catch (err) {
4364
5777
  opts.renderer.writeError(err instanceof Error ? err.message : String(err));
4365
5778
  } finally {
@@ -4472,7 +5885,10 @@ async function execute(deps) {
4472
5885
  switchProviderAndModel,
4473
5886
  director,
4474
5887
  fleetRoster,
4475
- fleetStreamController
5888
+ fleetStreamController,
5889
+ getYolo,
5890
+ getAutonomy,
5891
+ skillLoader
4476
5892
  } = deps;
4477
5893
  let code = 0;
4478
5894
  try {
@@ -4576,6 +5992,7 @@ async function execute(deps) {
4576
5992
  banner: !flags["no-banner"],
4577
5993
  queueStore,
4578
5994
  yolo: !!config.yolo,
5995
+ getYolo,
4579
5996
  appVersion: CLI_VERSION,
4580
5997
  provider: config.provider,
4581
5998
  family: banneredFamily,
@@ -4602,7 +6019,36 @@ async function execute(deps) {
4602
6019
  },
4603
6020
  fleetStreamController,
4604
6021
  initialGoal: goalFlag,
4605
- initialAsk: askFlag
6022
+ initialAsk: askFlag,
6023
+ getSDDContext: () => {
6024
+ const { getActiveSDDContext: getActiveSDDContext2 } = (init_sdd(), __toCommonJS(sdd_exports));
6025
+ return getActiveSDDContext2();
6026
+ },
6027
+ onSDDOutput: async (output) => {
6028
+ const { trySaveSpecFromAIOutput: trySaveSpecFromAIOutput2, trySaveImplementationPlan: trySaveImplementationPlan2, trySaveTasksFromAIOutput: trySaveTasksFromAIOutput2, autoDetectTaskCompletion: autoDetectTaskCompletion2, getTaskProgress: getTaskProgress2, getActiveSDDPhase: getActiveSDDPhase2 } = (init_sdd(), __toCommonJS(sdd_exports));
6029
+ const messages = [];
6030
+ const specSaved = await trySaveSpecFromAIOutput2(output);
6031
+ if (specSaved) messages.push("\u2713 Spec detected and saved! Use /sdd approve to continue.");
6032
+ const planSaved = trySaveImplementationPlan2(output);
6033
+ if (planSaved) messages.push("\u2713 Implementation plan saved!");
6034
+ const tasksSaved = await trySaveTasksFromAIOutput2(output);
6035
+ if (tasksSaved) {
6036
+ const progress = getTaskProgress2();
6037
+ const count = progress?.total ?? 0;
6038
+ messages.push(`\u2713 ${count} tasks detected and saved! Use /sdd approve to execute.`);
6039
+ }
6040
+ const sddPhase = getActiveSDDPhase2();
6041
+ if (sddPhase === "executing") {
6042
+ const autoCompleted = autoDetectTaskCompletion2(output);
6043
+ if (autoCompleted > 0) {
6044
+ const progress = getTaskProgress2();
6045
+ if (progress) {
6046
+ messages.push(`\u2713 ${autoCompleted} task(s) auto-completed! Progress: ${progress.completed}/${progress.total} (${progress.percent}%)`);
6047
+ }
6048
+ }
6049
+ }
6050
+ return messages;
6051
+ }
4606
6052
  });
4607
6053
  } finally {
4608
6054
  renderer.setSilent(false);
@@ -4628,7 +6074,9 @@ async function execute(deps) {
4628
6074
  supportsVision,
4629
6075
  attachments,
4630
6076
  effectiveMaxContext,
4631
- projectName: path14.basename(projectRoot) || void 0
6077
+ projectName: path17.basename(projectRoot) || void 0,
6078
+ getAutonomy,
6079
+ skillLoader
4632
6080
  });
4633
6081
  } finally {
4634
6082
  await webuiPromise.catch(() => void 0);
@@ -4644,7 +6092,9 @@ async function execute(deps) {
4644
6092
  supportsVision,
4645
6093
  attachments,
4646
6094
  effectiveMaxContext,
4647
- projectName: path14.basename(projectRoot) || void 0
6095
+ projectName: path17.basename(projectRoot) || void 0,
6096
+ getAutonomy,
6097
+ skillLoader
4648
6098
  });
4649
6099
  }
4650
6100
  } finally {
@@ -4744,6 +6194,15 @@ var MultiAgentHost = class {
4744
6194
  this.pending.delete(task.id);
4745
6195
  this.emitLifecycleCompleted(task.id, result);
4746
6196
  });
6197
+ this.director.fleet.filter("budget.threshold_reached", (e) => {
6198
+ const payload = e.payload;
6199
+ this.deps.events.emit("subagent.budget_warning", {
6200
+ subagentId: e.subagentId,
6201
+ kind: payload.kind,
6202
+ used: payload.used,
6203
+ limit: payload.limit
6204
+ });
6205
+ });
4747
6206
  this.coordinator = this.director.coordinator;
4748
6207
  } else {
4749
6208
  this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
@@ -4905,7 +6364,7 @@ var MultiAgentHost = class {
4905
6364
  model: opts?.model,
4906
6365
  tools: opts?.tools
4907
6366
  };
4908
- const transcriptPath = this.sessionFactory ? path14.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
6367
+ const transcriptPath = this.sessionFactory ? path17.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
4909
6368
  if (this.director) {
4910
6369
  const subagentId = await this.director.spawn(subagentConfig);
4911
6370
  const taskId2 = randomUUID();
@@ -4974,8 +6433,16 @@ var MultiAgentHost = class {
4974
6433
  description: v.description,
4975
6434
  subagentId: v.subagentId
4976
6435
  }));
4977
- const summary = !this.coordinator ? "No subagents have been spawned." : `${pending.length} pending, ${this.results.length} completed.`;
4978
- return { pending, completed: this.results, summary };
6436
+ const live = [];
6437
+ if (this.coordinator) {
6438
+ const s = this.coordinator.getStatus();
6439
+ for (const a of s.subagents) {
6440
+ live.push({ subagentId: a.id, status: a.status, task: a.currentTask });
6441
+ }
6442
+ }
6443
+ const liveCount = live.filter((s) => s.status === "running" || s.status === "idle").length;
6444
+ const summary = !this.coordinator ? "No subagents have been spawned." : liveCount > 0 ? `${pending.length} pending, ${liveCount} active, ${this.results.length} completed.` : `${pending.length} pending, ${this.results.length} completed.`;
6445
+ return { pending, completed: this.results, live, summary };
4979
6446
  }
4980
6447
  /**
4981
6448
  * Roll up per-subagent runtime cost from completed TaskResults. We don't
@@ -5057,16 +6524,16 @@ var MultiAgentHost = class {
5057
6524
  }
5058
6525
  this.opts.directorMode = true;
5059
6526
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
5060
- this.opts.manifestPath = path14.join(this.opts.fleetRoot, "fleet.json");
6527
+ this.opts.manifestPath = path17.join(this.opts.fleetRoot, "fleet.json");
5061
6528
  }
5062
6529
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
5063
- this.opts.sharedScratchpadPath = path14.join(this.opts.fleetRoot, "shared");
6530
+ this.opts.sharedScratchpadPath = path17.join(this.opts.fleetRoot, "shared");
5064
6531
  }
5065
6532
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
5066
- this.opts.sessionsRoot = path14.join(this.opts.fleetRoot, "subagents");
6533
+ this.opts.sessionsRoot = path17.join(this.opts.fleetRoot, "subagents");
5067
6534
  }
5068
6535
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
5069
- this.opts.stateCheckpointPath = path14.join(this.opts.fleetRoot, "director-state.json");
6536
+ this.opts.stateCheckpointPath = path17.join(this.opts.fleetRoot, "director-state.json");
5070
6537
  }
5071
6538
  await this.ensureDirector();
5072
6539
  return this.director ?? null;
@@ -5187,11 +6654,11 @@ var SessionStats = class {
5187
6654
  if (e.name === "bash") this.bashCommands++;
5188
6655
  else if (e.name === "fetch") this.fetches++;
5189
6656
  if (!e.ok) return;
5190
- const path15 = typeof input?.path === "string" ? input.path : void 0;
5191
- if (e.name === "read" && path15) this.readPaths.add(path15);
5192
- else if (e.name === "edit" && path15) this.editedPaths.add(path15);
5193
- else if (e.name === "write" && path15) {
5194
- this.writtenPaths.add(path15);
6657
+ const path18 = typeof input?.path === "string" ? input.path : void 0;
6658
+ if (e.name === "read" && path18) this.readPaths.add(path18);
6659
+ else if (e.name === "edit" && path18) this.editedPaths.add(path18);
6660
+ else if (e.name === "write" && path18) {
6661
+ this.writtenPaths.add(path18);
5195
6662
  const content = typeof input?.content === "string" ? input.content : "";
5196
6663
  this.bytesWritten += Buffer.byteLength(content, "utf8");
5197
6664
  }
@@ -5397,27 +6864,14 @@ async function setupCompaction(params) {
5397
6864
  const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
5398
6865
  const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
5399
6866
  const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
6867
+ let autoCompactor;
5400
6868
  if (config.context.autoCompact !== false) {
5401
- const autoCompactor = new AutoCompactionMiddleware(
6869
+ autoCompactor = new AutoCompactionMiddleware(
5402
6870
  compactor,
5403
6871
  effectiveMaxContext,
5404
- (ctx) => {
5405
- let total = 0;
5406
- for (const m of ctx.messages) {
5407
- if (typeof m.content === "string") {
5408
- total += Math.ceil(m.content.length / 4);
5409
- } else if (Array.isArray(m.content)) {
5410
- for (const b of m.content) {
5411
- if (b.type === "text") {
5412
- total += Math.ceil(b.text.length / 4);
5413
- } else if (b.type === "tool_use" || b.type === "tool_result") {
5414
- total += Math.ceil(JSON.stringify(b).length / 4);
5415
- }
5416
- }
5417
- }
5418
- }
5419
- return total;
5420
- },
6872
+ // Use the full API request estimator: messages + system prompt + tool definitions.
6873
+ // This matches what the provider actually counts as input tokens.
6874
+ (ctx) => estimateRequestTokens(ctx.messages, ctx.systemPrompt, ctx.tools ?? []).total,
5421
6875
  {
5422
6876
  warn: config.context.warnThreshold,
5423
6877
  soft: config.context.softThreshold,
@@ -5427,7 +6881,7 @@ async function setupCompaction(params) {
5427
6881
  );
5428
6882
  pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
5429
6883
  }
5430
- return effectiveMaxContext;
6884
+ return { effectiveMaxContext, autoCompactor };
5431
6885
  }
5432
6886
  function createAgent(params) {
5433
6887
  return new Agent({
@@ -5535,12 +6989,12 @@ async function setupSession(params) {
5535
6989
  }
5536
6990
  const sessionRef = { current: session };
5537
6991
  await recoveryLock.write(session.id).catch(() => void 0);
5538
- const attachments = new DefaultAttachmentStore({ spoolDir: path14.join(wpaths.projectSessions, session.id, "attachments") });
5539
- const queueStore = new QueueStore({ dir: path14.join(wpaths.projectSessions, session.id) });
6992
+ const attachments = new DefaultAttachmentStore({ spoolDir: path17.join(wpaths.projectSessions, session.id, "attachments") });
6993
+ const queueStore = new QueueStore({ dir: path17.join(wpaths.projectSessions, session.id) });
5540
6994
  const ctxSignal = new AbortController().signal;
5541
6995
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
5542
6996
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
5543
- const todosCheckpointPath = path14.join(wpaths.projectSessions, `${session.id}.todos.json`);
6997
+ const todosCheckpointPath = path17.join(wpaths.projectSessions, `${session.id}.todos.json`);
5544
6998
  if (resumeId) {
5545
6999
  try {
5546
7000
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -5552,12 +7006,12 @@ async function setupSession(params) {
5552
7006
  }
5553
7007
  }
5554
7008
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
5555
- const planPath = path14.join(wpaths.projectSessions, `${session.id}.plan.json`);
7009
+ const planPath = path17.join(wpaths.projectSessions, `${session.id}.plan.json`);
5556
7010
  context.state.setMeta("plan.path", planPath);
5557
7011
  if (resumeId) {
5558
7012
  try {
5559
- const fleetRoot = path14.join(wpaths.projectSessions, session.id);
5560
- const dirState = await loadDirectorState(path14.join(fleetRoot, "director-state.json"));
7013
+ const fleetRoot = path17.join(wpaths.projectSessions, session.id);
7014
+ const dirState = await loadDirectorState(path17.join(fleetRoot, "director-state.json"));
5561
7015
  if (dirState) {
5562
7016
  const tCounts = {};
5563
7017
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -5584,7 +7038,7 @@ function resolveBundledSkillsDir2() {
5584
7038
  try {
5585
7039
  const req2 = createRequire(import.meta.url);
5586
7040
  const corePkg = req2.resolve("@wrongstack/core/package.json");
5587
- return path14.join(path14.dirname(corePkg), "skills");
7041
+ return path17.join(path17.dirname(corePkg), "skills");
5588
7042
  } catch {
5589
7043
  return void 0;
5590
7044
  }
@@ -5671,7 +7125,7 @@ async function main(argv) {
5671
7125
  modeId,
5672
7126
  modePrompt,
5673
7127
  modelCapabilities,
5674
- planPath: () => sessionRef.current ? path14.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
7128
+ planPath: () => sessionRef.current ? path17.join(wpaths.projectSessions, `${sessionRef.current.id}.plan.json`) : void 0
5675
7129
  })
5676
7130
  );
5677
7131
  const toolRegistry = new ToolRegistry();
@@ -5716,7 +7170,7 @@ async function main(argv) {
5716
7170
  const dumpMetrics = () => {
5717
7171
  if (!metricsSink) return;
5718
7172
  try {
5719
- const out = path14.join(wpaths.projectSessions, "metrics.json");
7173
+ const out = path17.join(wpaths.projectSessions, "metrics.json");
5720
7174
  const snap = metricsSink.snapshot();
5721
7175
  writeFileSync(out, JSON.stringify(snap, null, 2));
5722
7176
  } catch {
@@ -5844,7 +7298,13 @@ async function main(argv) {
5844
7298
  });
5845
7299
  const pipelines = setupPipelines({ events, logger });
5846
7300
  const compactor = container.resolve(TOKENS.Compactor);
5847
- const effectiveMaxContext = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
7301
+ const { effectiveMaxContext, autoCompactor } = await setupCompaction({ compactor, events, modelsRegistry, context, config, provider, pipelines });
7302
+ const refreshMaxContext = async (providerId, modelId) => {
7303
+ if (!autoCompactor) return;
7304
+ const cap = await capabilitiesFor(modelsRegistry, providerId, modelId).catch(() => void 0);
7305
+ const mc = cap?.maxContext ?? config.context.effectiveMaxContext ?? 2e5;
7306
+ autoCompactor.setMaxContext(mc);
7307
+ };
5848
7308
  const updateSpinnerContext = () => {
5849
7309
  if (effectiveMaxContext > 0 && lastInputTokens > 0) {
5850
7310
  spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
@@ -5907,17 +7367,20 @@ async function main(argv) {
5907
7367
  }
5908
7368
  const switchProviderAndModel = (providerId, modelId) => {
5909
7369
  try {
5910
- const newCfg = config.providers?.[providerId] ?? {
7370
+ const savedCfg = config.providers?.[providerId];
7371
+ const resolvedProviderId = savedCfg?.type ?? providerId;
7372
+ const newCfg = savedCfg ?? {
5911
7373
  type: providerId,
5912
7374
  apiKey: config.apiKey,
5913
7375
  baseUrl: config.baseUrl
5914
7376
  };
5915
- const cfgWithType = { ...newCfg, type: providerId };
5916
- const newProvider = config.features.modelsRegistry && providerRegistry.has(providerId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(providerId, cfgWithType);
7377
+ const cfgWithType = { ...newCfg, type: resolvedProviderId };
7378
+ const newProvider = config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
5917
7379
  context.provider = newProvider;
5918
7380
  context.model = modelId;
5919
7381
  config = patchConfig(config, { provider: providerId, model: modelId });
5920
7382
  configStore.update({ provider: providerId, model: modelId });
7383
+ void refreshMaxContext(resolvedProviderId, modelId);
5921
7384
  return null;
5922
7385
  } catch (err) {
5923
7386
  return err instanceof Error ? err.message : String(err);
@@ -5925,12 +7388,13 @@ async function main(argv) {
5925
7388
  };
5926
7389
  const directorMode = flags["director"] === true;
5927
7390
  let director = null;
5928
- const fleetRoot = directorMode ? path14.join(wpaths.projectSessions, session.id) : void 0;
5929
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path14.join(fleetRoot, "fleet.json") : void 0;
5930
- const sharedScratchpadPath = directorMode ? path14.join(fleetRoot, "shared") : void 0;
5931
- const subagentSessionsRoot = directorMode ? path14.join(fleetRoot, "subagents") : void 0;
5932
- const stateCheckpointPath = directorMode ? path14.join(fleetRoot, "director-state.json") : void 0;
5933
- const fleetRootForPromotion = path14.join(wpaths.projectSessions, session.id);
7391
+ let autonomyMode = "off";
7392
+ const fleetRoot = directorMode ? path17.join(wpaths.projectSessions, session.id) : void 0;
7393
+ const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path17.join(fleetRoot, "fleet.json") : void 0;
7394
+ const sharedScratchpadPath = directorMode ? path17.join(fleetRoot, "shared") : void 0;
7395
+ const subagentSessionsRoot = directorMode ? path17.join(fleetRoot, "subagents") : void 0;
7396
+ const stateCheckpointPath = directorMode ? path17.join(fleetRoot, "director-state.json") : void 0;
7397
+ const fleetRootForPromotion = path17.join(wpaths.projectSessions, session.id);
5934
7398
  const multiAgentHost = new MultiAgentHost(
5935
7399
  {
5936
7400
  container,
@@ -6001,6 +7465,7 @@ async function main(argv) {
6001
7465
  metricsSink,
6002
7466
  healthRegistry,
6003
7467
  planPath,
7468
+ modeStore,
6004
7469
  fleetStreamController,
6005
7470
  onSpawn: async (description, spawnOpts) => {
6006
7471
  const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
@@ -6014,8 +7479,19 @@ async function main(argv) {
6014
7479
  onAgents: () => {
6015
7480
  const s = multiAgentHost.status();
6016
7481
  const lines = [s.summary];
7482
+ const STATUS_ICON = {
7483
+ running: "\u25CF",
7484
+ idle: "\u25CB",
7485
+ stopped: "\u2298"
7486
+ };
7487
+ for (const a of s.live) {
7488
+ if (a.status === "running" || a.status === "idle") {
7489
+ const task = a.task ? ` \u2014 ${a.task.slice(0, 60)}` : "";
7490
+ lines.push(` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`);
7491
+ }
7492
+ }
6017
7493
  for (const p of s.pending) {
6018
- lines.push(` pending ${p.taskId.slice(0, 8)} \u2192 ${p.description.slice(0, 60)}`);
7494
+ lines.push(` \xB7 pending ${p.taskId.slice(0, 8)} \u2192 ${p.description.slice(0, 60)}`);
6019
7495
  }
6020
7496
  for (const r of s.completed) {
6021
7497
  const fmt = fmtTaskResultLine(r, color);
@@ -6027,11 +7503,26 @@ async function main(argv) {
6027
7503
  if (action === "status") {
6028
7504
  const s = multiAgentHost.status();
6029
7505
  const lines = [color.bold("Fleet status"), ` ${s.summary}`];
7506
+ const STATUS_ICON = {
7507
+ running: "\u25CF",
7508
+ idle: "\u25CB",
7509
+ stopped: "\u2298"
7510
+ };
7511
+ const liveActive = s.live.filter((a) => a.status === "running" || a.status === "idle");
7512
+ if (liveActive.length > 0) {
7513
+ lines.push("", color.dim(" Active"));
7514
+ for (const a of liveActive) {
7515
+ const task = a.task ? ` \xB7 ${a.task.slice(0, 50)}` : "";
7516
+ lines.push(
7517
+ ` ${STATUS_ICON[a.status] ?? "?"} ${a.subagentId.slice(0, 8)} ${a.status}${task}`
7518
+ );
7519
+ }
7520
+ }
6030
7521
  if (s.pending.length > 0) {
6031
7522
  lines.push("", color.dim(" Pending"));
6032
7523
  for (const p of s.pending) {
6033
7524
  lines.push(
6034
- ` ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`
7525
+ ` \xB7 ${p.taskId.slice(0, 8)} \u2192 ${p.subagentId.slice(0, 8)} \xB7 ${p.description.slice(0, 60)}`
6035
7526
  );
6036
7527
  }
6037
7528
  }
@@ -6082,7 +7573,7 @@ async function main(argv) {
6082
7573
  return `Unknown fleet action: ${action}`;
6083
7574
  },
6084
7575
  onFleetLog: async (subagentId, mode) => {
6085
- const subagentsRoot = path14.join(fleetRootForPromotion, "subagents");
7576
+ const subagentsRoot = path17.join(fleetRootForPromotion, "subagents");
6086
7577
  let runDirs;
6087
7578
  try {
6088
7579
  runDirs = await fs14.readdir(subagentsRoot);
@@ -6091,7 +7582,7 @@ async function main(argv) {
6091
7582
  }
6092
7583
  const found = [];
6093
7584
  for (const runId of runDirs) {
6094
- const runDir = path14.join(subagentsRoot, runId);
7585
+ const runDir = path17.join(subagentsRoot, runId);
6095
7586
  let files;
6096
7587
  try {
6097
7588
  files = await fs14.readdir(runDir);
@@ -6100,7 +7591,7 @@ async function main(argv) {
6100
7591
  }
6101
7592
  for (const f of files) {
6102
7593
  if (!f.endsWith(".jsonl")) continue;
6103
- const full = path14.join(runDir, f);
7594
+ const full = path17.join(runDir, f);
6104
7595
  try {
6105
7596
  const stat2 = await fs14.stat(full);
6106
7597
  found.push({
@@ -6197,7 +7688,7 @@ async function main(argv) {
6197
7688
  }
6198
7689
  const dir = await multiAgentHost.ensureDirector();
6199
7690
  if (!dir) return "Director is not available.";
6200
- const dirStatePath = path14.join(fleetRootForPromotion, "director-state.json");
7691
+ const dirStatePath = path17.join(fleetRootForPromotion, "director-state.json");
6201
7692
  const prior = await loadDirectorState(dirStatePath);
6202
7693
  if (!prior) {
6203
7694
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -6268,9 +7759,9 @@ async function main(argv) {
6268
7759
  for (const tool of director2.tools(FLEET_ROSTER)) {
6269
7760
  toolRegistry.register(tool);
6270
7761
  }
6271
- const mp = path14.join(fleetRootForPromotion, "fleet.json");
6272
- const sp = path14.join(fleetRootForPromotion, "shared");
6273
- const ss = path14.join(fleetRootForPromotion, "subagents");
7762
+ const mp = path17.join(fleetRootForPromotion, "fleet.json");
7763
+ const sp = path17.join(fleetRootForPromotion, "shared");
7764
+ const ss = path17.join(fleetRootForPromotion, "subagents");
6274
7765
  const lines = [
6275
7766
  `${color.green("\u2713")} Promoted to director mode.`,
6276
7767
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -6297,6 +7788,22 @@ Restart WrongStack to load or unload plugin code in this session.`;
6297
7788
  }
6298
7789
  return result.message;
6299
7790
  },
7791
+ onYolo: (setTo) => {
7792
+ const policy = container.resolve(TOKENS.PermissionPolicy);
7793
+ if (setTo !== void 0) {
7794
+ policy.setYolo(setTo);
7795
+ config = patchConfig(config, { yolo: setTo });
7796
+ return setTo;
7797
+ }
7798
+ return policy.getYolo();
7799
+ },
7800
+ onAutonomy: (setTo) => {
7801
+ if (setTo !== void 0) {
7802
+ autonomyMode = setTo;
7803
+ return setTo;
7804
+ }
7805
+ return autonomyMode;
7806
+ },
6300
7807
  onExit: () => {
6301
7808
  void mcpRegistry.stopAll();
6302
7809
  },
@@ -6359,7 +7866,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
6359
7866
  switchProviderAndModel,
6360
7867
  director: director ?? null,
6361
7868
  fleetRoster: FLEET_ROSTER,
6362
- fleetStreamController
7869
+ fleetStreamController,
7870
+ getYolo: () => {
7871
+ const policy = container.resolve(TOKENS.PermissionPolicy);
7872
+ return policy.getYolo();
7873
+ },
7874
+ getAutonomy: () => autonomyMode,
7875
+ skillLoader: config.features.skills ? skillLoader : void 0
6363
7876
  });
6364
7877
  }
6365
7878
  async function promptRecovery(reader, renderer, abandoned, autoRecover) {