@wrongstack/cli 0.4.1 → 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,10 +1,10 @@
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 as encryptConfigSecrets$1, 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';
6
- import * as path15 from 'path';
7
- import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets } from '@wrongstack/core/security';
7
+ import { DefaultSecretVault as DefaultSecretVault$1, encryptConfigSecrets, decryptConfigSecrets as decryptConfigSecrets$1 } from '@wrongstack/core/security';
8
8
  import { WebSocketServer, WebSocket } from 'ws';
9
9
  import { writeFileSync } from 'fs';
10
10
  import { createRequire } from 'module';
@@ -12,12 +12,15 @@ import { MCPRegistry } from '@wrongstack/mcp';
12
12
  import { buildProviderFactoriesFromRegistry, makeProviderFromConfig, capabilitiesFor } from '@wrongstack/providers';
13
13
  import { createDefaultContainer, routeImagesForModel, readClipboardImage } from '@wrongstack/runtime';
14
14
  import { builtinToolsPack, rememberTool, forgetTool } from '@wrongstack/tools';
15
- import * as os3 from 'os';
15
+ import * as os4 from 'os';
16
16
  import * as readline from 'readline';
17
+ import { SkillInstaller } from '@wrongstack/core/skills';
17
18
  import { createToolVisionAdapters } from '@wrongstack/runtime/vision';
18
19
 
19
20
  var __defProp = Object.defineProperty;
21
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
20
22
  var __getOwnPropNames = Object.getOwnPropertyNames;
23
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
21
24
  var __esm = (fn, res) => function __init() {
22
25
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
23
26
  };
@@ -25,6 +28,835 @@ var __export = (target, all) => {
25
28
  for (var name in all)
26
29
  __defProp(target, name, { get: all[name], enumerable: true });
27
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
+ });
28
860
  function normalizeKeys(cfg) {
29
861
  if (Array.isArray(cfg.apiKeys) && cfg.apiKeys.length > 0) {
30
862
  return cfg.apiKeys.map((k) => ({ ...k }));
@@ -559,7 +1391,10 @@ async function runWebUI(opts) {
559
1391
  } catch {
560
1392
  return {};
561
1393
  }
562
- 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);
563
1398
  }
564
1399
  async function saveProviders(providers) {
565
1400
  if (!opts.globalConfigPath) return;
@@ -576,7 +1411,7 @@ async function runWebUI(opts) {
576
1411
  parsed = {};
577
1412
  }
578
1413
  parsed.providers = providers;
579
- const keyFile = path15.join(path15.dirname(opts.globalConfigPath), ".key");
1414
+ const keyFile = path17.join(path17.dirname(opts.globalConfigPath), ".key");
580
1415
  const vault = new DefaultSecretVault$1({ keyFile });
581
1416
  const encrypted = encryptConfigSecrets(parsed, vault);
582
1417
  await atomicWrite(opts.globalConfigPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
@@ -728,10 +1563,10 @@ function parseSpawnFlags(input) {
728
1563
  return { description: rest.trim(), opts };
729
1564
  }
730
1565
  async function bootConfig(flags) {
731
- const cwd = typeof flags["cwd"] === "string" ? path15.resolve(flags["cwd"]) : process.cwd();
1566
+ const cwd = typeof flags["cwd"] === "string" ? path17.resolve(flags["cwd"]) : process.cwd();
732
1567
  const pathResolver = new DefaultPathResolver(cwd);
733
1568
  const projectRoot = pathResolver.projectRoot;
734
- const userHome = os3.homedir();
1569
+ const userHome = os4.homedir();
735
1570
  const wpaths = resolveWstackPaths({ projectRoot, userHome });
736
1571
  await ensureProjectMeta(wpaths, projectRoot);
737
1572
  const vault = new DefaultSecretVault({ keyFile: wpaths.secretsKey });
@@ -795,7 +1630,7 @@ var ReadlineInputReader = class {
795
1630
  history = [];
796
1631
  pending = false;
797
1632
  constructor(opts = {}) {
798
- this.historyFile = opts.historyFile ?? path15.join(os3.homedir(), ".wrongstack", "history");
1633
+ this.historyFile = opts.historyFile ?? path17.join(os4.homedir(), ".wrongstack", "history");
799
1634
  }
800
1635
  async loadHistory() {
801
1636
  try {
@@ -807,7 +1642,7 @@ var ReadlineInputReader = class {
807
1642
  }
808
1643
  async saveHistory() {
809
1644
  try {
810
- await fs14.mkdir(path15.dirname(this.historyFile), { recursive: true });
1645
+ await fs14.mkdir(path17.dirname(this.historyFile), { recursive: true });
811
1646
  await fs14.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
812
1647
  } catch {
813
1648
  }
@@ -1289,10 +2124,10 @@ async function detectPackageManager(root, declared) {
1289
2124
  const name = declared.split("@")[0];
1290
2125
  if (name) return name;
1291
2126
  }
1292
- if (await pathExists(path15.join(root, "pnpm-lock.yaml"))) return "pnpm";
1293
- if (await pathExists(path15.join(root, "bun.lockb"))) return "bun";
1294
- if (await pathExists(path15.join(root, "bun.lock"))) return "bun";
1295
- if (await pathExists(path15.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";
1296
2131
  return "npm";
1297
2132
  }
1298
2133
  function hasUsableScript(scripts, name) {
@@ -1313,7 +2148,7 @@ function parseMakeTargets(makefile) {
1313
2148
  async function detectProjectFacts(root) {
1314
2149
  const facts = { hints: [] };
1315
2150
  try {
1316
- const pkg = JSON.parse(await fs14.readFile(path15.join(root, "package.json"), "utf8"));
2151
+ const pkg = JSON.parse(await fs14.readFile(path17.join(root, "package.json"), "utf8"));
1317
2152
  const scripts = pkg.scripts ?? {};
1318
2153
  const pm = await detectPackageManager(root, pkg.packageManager);
1319
2154
  if (hasUsableScript(scripts, "build")) facts.build = `${pm} run build`;
@@ -1327,14 +2162,14 @@ async function detectProjectFacts(root) {
1327
2162
  } catch {
1328
2163
  }
1329
2164
  try {
1330
- if (!await pathExists(path15.join(root, "pyproject.toml"))) throw new Error("not python");
2165
+ if (!await pathExists(path17.join(root, "pyproject.toml"))) throw new Error("not python");
1331
2166
  facts.test ??= "pytest";
1332
2167
  facts.lint ??= "ruff check .";
1333
2168
  facts.hints.push("pyproject.toml");
1334
2169
  } catch {
1335
2170
  }
1336
2171
  try {
1337
- if (!await pathExists(path15.join(root, "go.mod"))) throw new Error("not go");
2172
+ if (!await pathExists(path17.join(root, "go.mod"))) throw new Error("not go");
1338
2173
  facts.build ??= "go build ./...";
1339
2174
  facts.test ??= "go test ./...";
1340
2175
  facts.run ??= "go run .";
@@ -1342,7 +2177,7 @@ async function detectProjectFacts(root) {
1342
2177
  } catch {
1343
2178
  }
1344
2179
  try {
1345
- if (!await pathExists(path15.join(root, "Cargo.toml"))) throw new Error("not rust");
2180
+ if (!await pathExists(path17.join(root, "Cargo.toml"))) throw new Error("not rust");
1346
2181
  facts.build ??= "cargo build";
1347
2182
  facts.test ??= "cargo test";
1348
2183
  facts.lint ??= "cargo clippy";
@@ -1351,7 +2186,7 @@ async function detectProjectFacts(root) {
1351
2186
  } catch {
1352
2187
  }
1353
2188
  try {
1354
- const makefile = await fs14.readFile(path15.join(root, "Makefile"), "utf8");
2189
+ const makefile = await fs14.readFile(path17.join(root, "Makefile"), "utf8");
1355
2190
  const targets = parseMakeTargets(makefile);
1356
2191
  facts.build ??= targets.has("build") ? "make build" : "make";
1357
2192
  if (targets.has("test")) facts.test ??= "make test";
@@ -1814,8 +2649,8 @@ function buildInitCommand(opts) {
1814
2649
  description: "Create .wrongstack/AGENTS.md project context for the system prompt.",
1815
2650
  async run(args, ctx) {
1816
2651
  const force = args.trim() === "--force";
1817
- const dir = path15.join(ctx.projectRoot, ".wrongstack");
1818
- const file = path15.join(dir, "AGENTS.md");
2652
+ const dir = path17.join(ctx.projectRoot, ".wrongstack");
2653
+ const file = path17.join(dir, "AGENTS.md");
1819
2654
  try {
1820
2655
  await fs14.access(file);
1821
2656
  if (!force) {
@@ -2065,7 +2900,7 @@ function buildExitCommand(opts) {
2065
2900
  function buildSkillCommand(opts) {
2066
2901
  return {
2067
2902
  name: "skill",
2068
- description: "Show skill details or list available skills.",
2903
+ description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
2069
2904
  async run(args) {
2070
2905
  if (!opts.skillLoader) return { message: "No skill loader configured." };
2071
2906
  if (!args.trim()) {
@@ -2203,6 +3038,392 @@ ${lines.join("\n")}
2203
3038
  }
2204
3039
  };
2205
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
+ }
2206
3427
 
2207
3428
  // src/slash-commands/index.ts
2208
3429
  function buildBuiltinSlashCommands(opts) {
@@ -2214,6 +3435,10 @@ function buildBuiltinSlashCommands(opts) {
2214
3435
  buildContextCommand(opts),
2215
3436
  buildToolsCommand(opts),
2216
3437
  buildSkillCommand(opts),
3438
+ buildSkillGeneratorCommand(opts),
3439
+ buildSkillInstallCommand(opts),
3440
+ buildSkillUpdateCommand(opts),
3441
+ buildSkillUninstallCommand(opts),
2217
3442
  buildPluginCommand(opts),
2218
3443
  buildDiagCommand(opts),
2219
3444
  buildStatsCommand(opts),
@@ -2226,8 +3451,12 @@ function buildBuiltinSlashCommands(opts) {
2226
3451
  buildMemoryCommand(opts),
2227
3452
  buildTodosCommand(opts),
2228
3453
  buildPlanCommand(opts),
3454
+ buildSddCommand(opts),
2229
3455
  buildSaveCommand(opts),
2230
3456
  buildLoadCommand(opts),
3457
+ buildYoloCommand(opts),
3458
+ buildAutonomyCommand(opts),
3459
+ buildModeCommand(opts),
2231
3460
  buildExitCommand(opts)
2232
3461
  ];
2233
3462
  }
@@ -2247,13 +3476,13 @@ var MANIFESTS = [
2247
3476
  ];
2248
3477
  async function detectProjectKind(projectRoot) {
2249
3478
  try {
2250
- await fs14.access(path15.join(projectRoot, ".wrongstack", "AGENTS.md"));
3479
+ await fs14.access(path17.join(projectRoot, ".wrongstack", "AGENTS.md"));
2251
3480
  return "initialized";
2252
3481
  } catch {
2253
3482
  }
2254
3483
  for (const m of MANIFESTS) {
2255
3484
  try {
2256
- await fs14.access(path15.join(projectRoot, m));
3485
+ await fs14.access(path17.join(projectRoot, m));
2257
3486
  return "project";
2258
3487
  } catch {
2259
3488
  }
@@ -2261,8 +3490,8 @@ async function detectProjectKind(projectRoot) {
2261
3490
  return "empty";
2262
3491
  }
2263
3492
  async function scaffoldAgentsMd(projectRoot) {
2264
- const dir = path15.join(projectRoot, ".wrongstack");
2265
- const file = path15.join(dir, "AGENTS.md");
3493
+ const dir = path17.join(projectRoot, ".wrongstack");
3494
+ const file = path17.join(dir, "AGENTS.md");
2266
3495
  const facts = await detectProjectFacts(projectRoot);
2267
3496
  const body = renderAgentsTemplate(facts);
2268
3497
  await fs14.mkdir(dir, { recursive: true });
@@ -2275,7 +3504,7 @@ async function runProjectCheck(opts) {
2275
3504
  if (kind === "initialized") {
2276
3505
  renderer.write(
2277
3506
  `
2278
- ${color.green("\u2713")} Project initialized ${color.dim(`(${path15.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
3507
+ ${color.green("\u2713")} Project initialized ${color.dim(`(${path17.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
2279
3508
  `
2280
3509
  );
2281
3510
  return true;
@@ -2331,9 +3560,9 @@ async function runLaunchPrompts(opts) {
2331
3560
  yolo = yoloPinned;
2332
3561
  } else {
2333
3562
  const answer = (await reader.readLine(
2334
- ` ${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]")} `
2335
3564
  )).trim().toLowerCase();
2336
- yolo = answer === "y" || answer === "yes";
3565
+ yolo = answer !== "n" && answer !== "no";
2337
3566
  }
2338
3567
  renderer.write(
2339
3568
  `
@@ -2571,14 +3800,14 @@ function summarize(value, name) {
2571
3800
  if (typeof v === "object" && v !== null) {
2572
3801
  const o = v;
2573
3802
  if (name === "edit") {
2574
- const path16 = typeof o["path"] === "string" ? o["path"] : "";
3803
+ const path18 = typeof o["path"] === "string" ? o["path"] : "";
2575
3804
  const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
2576
- return `${path16} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
3805
+ return `${path18} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
2577
3806
  }
2578
3807
  if (name === "write") {
2579
- const path16 = typeof o["path"] === "string" ? o["path"] : "";
3808
+ const path18 = typeof o["path"] === "string" ? o["path"] : "";
2580
3809
  const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
2581
- return bytes !== void 0 ? `${path16} ${bytes}B` : path16;
3810
+ return bytes !== void 0 ? `${path18} ${bytes}B` : path18;
2582
3811
  }
2583
3812
  if (typeof o["count"] === "number") {
2584
3813
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
@@ -3262,7 +4491,7 @@ var diagCmd = async (_args, deps) => {
3262
4491
  ` modelsCache: ${deps.paths.modelsCache}`,
3263
4492
  ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
3264
4493
  ` node: ${process.version}`,
3265
- ` os: ${os3.platform()} ${os3.release()}`,
4494
+ ` os: ${os4.platform()} ${os4.release()}`,
3266
4495
  ` provider: ${cfg.provider ?? "<unset>"}`,
3267
4496
  ` model: ${cfg.model ?? "<unset>"}`,
3268
4497
  ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
@@ -3341,7 +4570,7 @@ var doctorCmd = async (_args, deps) => {
3341
4570
  }
3342
4571
  try {
3343
4572
  await fs14.mkdir(deps.paths.projectSessions, { recursive: true });
3344
- const probe = path15.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
4573
+ const probe = path17.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
3345
4574
  await fs14.writeFile(probe, "");
3346
4575
  await fs14.unlink(probe);
3347
4576
  checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
@@ -3444,8 +4673,8 @@ var exportCmd = async (args, deps) => {
3444
4673
  return 1;
3445
4674
  }
3446
4675
  if (output) {
3447
- await fs14.mkdir(path15.dirname(path15.resolve(deps.cwd, output)), { recursive: true });
3448
- await fs14.writeFile(path15.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");
3449
4678
  deps.renderer.write(`Wrote ${rendered.length} bytes to ${output}
3450
4679
  `);
3451
4680
  } else {
@@ -3505,12 +4734,12 @@ var initCmd = async (_args, deps) => {
3505
4734
  await fs14.mkdir(deps.paths.globalRoot, { recursive: true });
3506
4735
  const config = { version: 1, provider: providerId, model: modelId };
3507
4736
  if (apiKey) config.apiKey = apiKey;
3508
- const keyFile = path15.join(path15.dirname(deps.paths.globalConfig), ".key");
4737
+ const keyFile = path17.join(path17.dirname(deps.paths.globalConfig), ".key");
3509
4738
  const vault = new DefaultSecretVault$1({ keyFile });
3510
4739
  const encrypted = encryptConfigSecrets(config, vault);
3511
4740
  await atomicWrite(deps.paths.globalConfig, JSON.stringify(encrypted, null, 2));
3512
- await fs14.mkdir(path15.join(deps.projectRoot, ".wrongstack"), { recursive: true });
3513
- const agentsFile = path15.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
4741
+ await fs14.mkdir(path17.join(deps.projectRoot, ".wrongstack"), { recursive: true });
4742
+ const agentsFile = path17.join(deps.projectRoot, ".wrongstack", "AGENTS.md");
3514
4743
  try {
3515
4744
  await fs14.access(agentsFile);
3516
4745
  } catch {
@@ -3820,7 +5049,7 @@ var usageCmd = async (_args, deps) => {
3820
5049
  return 0;
3821
5050
  };
3822
5051
  var projectsCmd = async (_args, deps) => {
3823
- const projectsRoot = path15.join(deps.paths.globalRoot, "projects");
5052
+ const projectsRoot = path17.join(deps.paths.globalRoot, "projects");
3824
5053
  try {
3825
5054
  const entries = await fs14.readdir(projectsRoot);
3826
5055
  if (entries.length === 0) {
@@ -3830,7 +5059,7 @@ var projectsCmd = async (_args, deps) => {
3830
5059
  for (const hash of entries) {
3831
5060
  try {
3832
5061
  const meta = JSON.parse(
3833
- await fs14.readFile(path15.join(projectsRoot, hash, "meta.json"), "utf8")
5062
+ await fs14.readFile(path17.join(projectsRoot, hash, "meta.json"), "utf8")
3834
5063
  );
3835
5064
  deps.renderer.write(
3836
5065
  ` ${color.dim(hash)} ${color.dim(meta.lastSeen ?? "")} ${meta.root ?? "?"}
@@ -4029,7 +5258,7 @@ var skillsCmd = async (_args, deps) => {
4029
5258
  };
4030
5259
  var versionCmd = async (_args, deps) => {
4031
5260
  deps.renderer.write(
4032
- `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os3.platform()})
5261
+ `WrongStack ${CLI_VERSION} (apiVersion ${API_VERSION}, node ${process.version}, ${os4.platform()})
4033
5262
  `
4034
5263
  );
4035
5264
  return 0;
@@ -4105,31 +5334,29 @@ function fmtDuration(ms) {
4105
5334
  const remMin = m - h * 60;
4106
5335
  return `${h}h${remMin}m`;
4107
5336
  }
4108
- function fmtTaskResultLine(r, color28) {
5337
+ function fmtTaskResultLine(r, color30) {
4109
5338
  const stats = `${r.iterations}it ${r.toolCalls}tc ${fmtDuration(r.durationMs)}`;
4110
5339
  const errMsg = typeof r.error === "string" ? r.error : r.error?.message;
4111
5340
  const errKind = typeof r.error === "object" ? r.error?.kind : void 0;
4112
5341
  const errTail = errMsg ? ` \u2014 ${errMsg.replace(/\s+/g, " ").slice(0, 80)}${errMsg.length > 80 ? "\u2026" : ""}` : "";
4113
- const errKindChip = errKind ? color28.dim(` [${errKind}]`) : "";
4114
- const errSnip = errMsg || errKind ? `${errKindChip}${color28.dim(errTail)}` : "";
5342
+ const errKindChip = errKind ? color30.dim(` [${errKind}]`) : "";
5343
+ const errSnip = errMsg || errKind ? `${errKindChip}${color30.dim(errTail)}` : "";
4115
5344
  switch (r.status) {
4116
5345
  case "success":
4117
- return { mark: color28.green("\u2713"), stats, tail: "" };
5346
+ return { mark: color30.green("\u2713"), stats, tail: "" };
4118
5347
  case "timeout":
4119
- 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 };
4120
5349
  case "stopped":
4121
- 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 };
4122
5351
  case "failed":
4123
- 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 };
4124
5353
  }
4125
5354
  }
4126
-
4127
- // src/boot.ts
4128
5355
  function resolveBundledSkillsDir() {
4129
5356
  try {
4130
5357
  const req2 = createRequire(import.meta.url);
4131
5358
  const corePkg = req2.resolve("@wrongstack/core/package.json");
4132
- return path15.join(path15.dirname(corePkg), "skills");
5359
+ return path17.join(path17.dirname(corePkg), "skills");
4133
5360
  } catch {
4134
5361
  return void 0;
4135
5362
  }
@@ -4160,11 +5387,15 @@ async function boot(argv) {
4160
5387
  });
4161
5388
  const first = positional[0];
4162
5389
  if (first && subcommands[first]) {
4163
- const sessionStore = new DefaultSessionStore({ dir: wpaths.projectSessions });
4164
- const skillLoader = new DefaultSkillLoader({
4165
- paths: wpaths,
4166
- bundledDir: resolveBundledSkillsDir()
5390
+ const container = createDefaultContainer({
5391
+ config,
5392
+ wpaths,
5393
+ logger,
5394
+ modelsRegistry,
5395
+ bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir() : void 0
4167
5396
  });
5397
+ const sessionStore = container.resolve(TOKENS.SessionStore);
5398
+ const skillLoader = container.resolve(TOKENS.SkillLoader);
4168
5399
  const toolRegistryForSubcmd = new ToolRegistry();
4169
5400
  toolRegistryForSubcmd.registerAllOrThrow(
4170
5401
  [...builtinToolsPack.tools ?? []],
@@ -4265,6 +5496,9 @@ async function boot(argv) {
4265
5496
  logger
4266
5497
  };
4267
5498
  }
5499
+
5500
+ // src/repl.ts
5501
+ init_sdd();
4268
5502
  async function runRepl(opts) {
4269
5503
  if (opts.banner !== false) printBanner(opts.renderer, opts.projectName);
4270
5504
  let activeCtrl;
@@ -4308,6 +5542,58 @@ async function runRepl(opts) {
4308
5542
  if (res?.message) opts.renderer.write(`${res.message}
4309
5543
  `);
4310
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
+ }
4311
5597
  } catch (err) {
4312
5598
  opts.renderer.writeError(err instanceof Error ? err.message : String(err));
4313
5599
  }
@@ -4320,20 +5606,47 @@ async function runRepl(opts) {
4320
5606
  `));
4321
5607
  }
4322
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;
4323
5636
  const runCtrl = new AbortController();
4324
5637
  activeCtrl = runCtrl;
4325
5638
  try {
4326
5639
  const startedAt = Date.now();
4327
5640
  const before = opts.tokenCounter?.total();
4328
5641
  const costBefore = opts.tokenCounter?.estimateCost().total ?? 0;
4329
- const routed = blocks.some((block) => block.type === "image") ? await routeImagesForModel(blocks, {
5642
+ const routed = effectiveBlocks.some((block) => block.type === "image") ? await routeImagesForModel(effectiveBlocks, {
4330
5643
  supportsVision: opts.supportsVision ? await opts.supportsVision() : opts.agent.ctx.provider.capabilities.vision,
4331
5644
  adapters: opts.visionAdapters ?? [],
4332
5645
  ctx: opts.agent.ctx,
4333
5646
  signal: runCtrl.signal,
4334
5647
  providerId: opts.agent.ctx.provider.id,
4335
5648
  model: opts.agent.ctx.model
4336
- }) : { blocks, route: "none", convertedImages: 0 };
5649
+ }) : { blocks: effectiveBlocks, route: "none", convertedImages: 0 };
4337
5650
  if (routed.route === "adapter") {
4338
5651
  opts.renderer.write(
4339
5652
  color.dim(
@@ -4356,6 +5669,55 @@ async function runRepl(opts) {
4356
5669
  } else if (result.status === "max_iterations") {
4357
5670
  opts.renderer.writeWarning(`Hit max iterations (${result.iterations}).`);
4358
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
+ }
4359
5721
  if (opts.tokenCounter && before) {
4360
5722
  const after = opts.tokenCounter.total();
4361
5723
  const costAfter = opts.tokenCounter.estimateCost().total;
@@ -4368,6 +5730,49 @@ ${color.dim(
4368
5730
  `
4369
5731
  );
4370
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
+ }
4371
5776
  } catch (err) {
4372
5777
  opts.renderer.writeError(err instanceof Error ? err.message : String(err));
4373
5778
  } finally {
@@ -4480,7 +5885,10 @@ async function execute(deps) {
4480
5885
  switchProviderAndModel,
4481
5886
  director,
4482
5887
  fleetRoster,
4483
- fleetStreamController
5888
+ fleetStreamController,
5889
+ getYolo,
5890
+ getAutonomy,
5891
+ skillLoader
4484
5892
  } = deps;
4485
5893
  let code = 0;
4486
5894
  try {
@@ -4584,6 +5992,7 @@ async function execute(deps) {
4584
5992
  banner: !flags["no-banner"],
4585
5993
  queueStore,
4586
5994
  yolo: !!config.yolo,
5995
+ getYolo,
4587
5996
  appVersion: CLI_VERSION,
4588
5997
  provider: config.provider,
4589
5998
  family: banneredFamily,
@@ -4610,7 +6019,36 @@ async function execute(deps) {
4610
6019
  },
4611
6020
  fleetStreamController,
4612
6021
  initialGoal: goalFlag,
4613
- 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
+ }
4614
6052
  });
4615
6053
  } finally {
4616
6054
  renderer.setSilent(false);
@@ -4636,7 +6074,9 @@ async function execute(deps) {
4636
6074
  supportsVision,
4637
6075
  attachments,
4638
6076
  effectiveMaxContext,
4639
- projectName: path15.basename(projectRoot) || void 0
6077
+ projectName: path17.basename(projectRoot) || void 0,
6078
+ getAutonomy,
6079
+ skillLoader
4640
6080
  });
4641
6081
  } finally {
4642
6082
  await webuiPromise.catch(() => void 0);
@@ -4652,7 +6092,9 @@ async function execute(deps) {
4652
6092
  supportsVision,
4653
6093
  attachments,
4654
6094
  effectiveMaxContext,
4655
- projectName: path15.basename(projectRoot) || void 0
6095
+ projectName: path17.basename(projectRoot) || void 0,
6096
+ getAutonomy,
6097
+ skillLoader
4656
6098
  });
4657
6099
  }
4658
6100
  } finally {
@@ -4752,6 +6194,15 @@ var MultiAgentHost = class {
4752
6194
  this.pending.delete(task.id);
4753
6195
  this.emitLifecycleCompleted(task.id, result);
4754
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
+ });
4755
6206
  this.coordinator = this.director.coordinator;
4756
6207
  } else {
4757
6208
  this.coordinator = new DefaultMultiAgentCoordinator(coordinatorConfig, {});
@@ -4913,7 +6364,7 @@ var MultiAgentHost = class {
4913
6364
  model: opts?.model,
4914
6365
  tools: opts?.tools
4915
6366
  };
4916
- const transcriptPath = this.sessionFactory ? path15.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
6367
+ const transcriptPath = this.sessionFactory ? path17.join(this.sessionFactory.dir, `${subagentConfig.name}.jsonl`) : void 0;
4917
6368
  if (this.director) {
4918
6369
  const subagentId = await this.director.spawn(subagentConfig);
4919
6370
  const taskId2 = randomUUID();
@@ -4982,8 +6433,16 @@ var MultiAgentHost = class {
4982
6433
  description: v.description,
4983
6434
  subagentId: v.subagentId
4984
6435
  }));
4985
- const summary = !this.coordinator ? "No subagents have been spawned." : `${pending.length} pending, ${this.results.length} completed.`;
4986
- 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 };
4987
6446
  }
4988
6447
  /**
4989
6448
  * Roll up per-subagent runtime cost from completed TaskResults. We don't
@@ -5065,16 +6524,16 @@ var MultiAgentHost = class {
5065
6524
  }
5066
6525
  this.opts.directorMode = true;
5067
6526
  if (this.opts.fleetRoot && !this.opts.manifestPath) {
5068
- this.opts.manifestPath = path15.join(this.opts.fleetRoot, "fleet.json");
6527
+ this.opts.manifestPath = path17.join(this.opts.fleetRoot, "fleet.json");
5069
6528
  }
5070
6529
  if (this.opts.fleetRoot && !this.opts.sharedScratchpadPath) {
5071
- this.opts.sharedScratchpadPath = path15.join(this.opts.fleetRoot, "shared");
6530
+ this.opts.sharedScratchpadPath = path17.join(this.opts.fleetRoot, "shared");
5072
6531
  }
5073
6532
  if (this.opts.fleetRoot && !this.opts.sessionsRoot) {
5074
- this.opts.sessionsRoot = path15.join(this.opts.fleetRoot, "subagents");
6533
+ this.opts.sessionsRoot = path17.join(this.opts.fleetRoot, "subagents");
5075
6534
  }
5076
6535
  if (this.opts.fleetRoot && !this.opts.stateCheckpointPath) {
5077
- this.opts.stateCheckpointPath = path15.join(this.opts.fleetRoot, "director-state.json");
6536
+ this.opts.stateCheckpointPath = path17.join(this.opts.fleetRoot, "director-state.json");
5078
6537
  }
5079
6538
  await this.ensureDirector();
5080
6539
  return this.director ?? null;
@@ -5195,11 +6654,11 @@ var SessionStats = class {
5195
6654
  if (e.name === "bash") this.bashCommands++;
5196
6655
  else if (e.name === "fetch") this.fetches++;
5197
6656
  if (!e.ok) return;
5198
- const path16 = typeof input?.path === "string" ? input.path : void 0;
5199
- if (e.name === "read" && path16) this.readPaths.add(path16);
5200
- else if (e.name === "edit" && path16) this.editedPaths.add(path16);
5201
- else if (e.name === "write" && path16) {
5202
- this.writtenPaths.add(path16);
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);
5203
6662
  const content = typeof input?.content === "string" ? input.content : "";
5204
6663
  this.bytesWritten += Buffer.byteLength(content, "utf8");
5205
6664
  }
@@ -5405,36 +6864,14 @@ async function setupCompaction(params) {
5405
6864
  const { compactor, events, modelsRegistry, context, config, provider, pipelines } = params;
5406
6865
  const resolvedCaps = await capabilitiesFor(modelsRegistry, provider.id, context.model).catch(() => void 0);
5407
6866
  const effectiveMaxContext = config.context.effectiveMaxContext ?? resolvedCaps?.maxContext ?? provider.capabilities.maxContext;
5408
- console.error("[DEBUG] setupCompaction:", {
5409
- providerId: provider.id,
5410
- model: context.model,
5411
- resolvedCapsMaxContext: resolvedCaps?.maxContext,
5412
- providerCapMaxContext: provider.capabilities.maxContext,
5413
- configEffectiveMaxContext: config.context.effectiveMaxContext,
5414
- effectiveMaxContext,
5415
- resolvedCapsKeys: resolvedCaps ? Object.keys(resolvedCaps) : null
5416
- });
6867
+ let autoCompactor;
5417
6868
  if (config.context.autoCompact !== false) {
5418
- const autoCompactor = new AutoCompactionMiddleware(
6869
+ autoCompactor = new AutoCompactionMiddleware(
5419
6870
  compactor,
5420
6871
  effectiveMaxContext,
5421
- (ctx) => {
5422
- let total = 0;
5423
- for (const m of ctx.messages) {
5424
- if (typeof m.content === "string") {
5425
- total += Math.ceil(m.content.length / 4);
5426
- } else if (Array.isArray(m.content)) {
5427
- for (const b of m.content) {
5428
- if (b.type === "text") {
5429
- total += Math.ceil(b.text.length / 4);
5430
- } else if (b.type === "tool_use" || b.type === "tool_result") {
5431
- total += Math.ceil(JSON.stringify(b).length / 4);
5432
- }
5433
- }
5434
- }
5435
- }
5436
- return total;
5437
- },
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,
5438
6875
  {
5439
6876
  warn: config.context.warnThreshold,
5440
6877
  soft: config.context.softThreshold,
@@ -5444,7 +6881,7 @@ async function setupCompaction(params) {
5444
6881
  );
5445
6882
  pipelines.contextWindow.use({ name: "AutoCompaction", handler: autoCompactor.handler() });
5446
6883
  }
5447
- return effectiveMaxContext;
6884
+ return { effectiveMaxContext, autoCompactor };
5448
6885
  }
5449
6886
  function createAgent(params) {
5450
6887
  return new Agent({
@@ -5552,12 +6989,12 @@ async function setupSession(params) {
5552
6989
  }
5553
6990
  const sessionRef = { current: session };
5554
6991
  await recoveryLock.write(session.id).catch(() => void 0);
5555
- const attachments = new DefaultAttachmentStore({ spoolDir: path15.join(wpaths.projectSessions, session.id, "attachments") });
5556
- const queueStore = new QueueStore({ dir: path15.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) });
5557
6994
  const ctxSignal = new AbortController().signal;
5558
6995
  const context = new Context({ systemPrompt, provider, session, signal: ctxSignal, tokenCounter, cwd, projectRoot, model: config.model });
5559
6996
  if (restoredMessages.length > 0) context.state.replaceMessages(restoredMessages);
5560
- const todosCheckpointPath = path15.join(wpaths.projectSessions, `${session.id}.todos.json`);
6997
+ const todosCheckpointPath = path17.join(wpaths.projectSessions, `${session.id}.todos.json`);
5561
6998
  if (resumeId) {
5562
6999
  try {
5563
7000
  const restoredTodos = await loadTodosCheckpoint(todosCheckpointPath);
@@ -5569,12 +7006,12 @@ async function setupSession(params) {
5569
7006
  }
5570
7007
  }
5571
7008
  const detachTodosCheckpoint = attachTodosCheckpoint(context.state, todosCheckpointPath, session.id);
5572
- const planPath = path15.join(wpaths.projectSessions, `${session.id}.plan.json`);
7009
+ const planPath = path17.join(wpaths.projectSessions, `${session.id}.plan.json`);
5573
7010
  context.state.setMeta("plan.path", planPath);
5574
7011
  if (resumeId) {
5575
7012
  try {
5576
- const fleetRoot = path15.join(wpaths.projectSessions, session.id);
5577
- const dirState = await loadDirectorState(path15.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"));
5578
7015
  if (dirState) {
5579
7016
  const tCounts = {};
5580
7017
  for (const t of dirState.tasks) tCounts[t.status] = (tCounts[t.status] ?? 0) + 1;
@@ -5601,7 +7038,7 @@ function resolveBundledSkillsDir2() {
5601
7038
  try {
5602
7039
  const req2 = createRequire(import.meta.url);
5603
7040
  const corePkg = req2.resolve("@wrongstack/core/package.json");
5604
- return path15.join(path15.dirname(corePkg), "skills");
7041
+ return path17.join(path17.dirname(corePkg), "skills");
5605
7042
  } catch {
5606
7043
  return void 0;
5607
7044
  }
@@ -5688,7 +7125,7 @@ async function main(argv) {
5688
7125
  modeId,
5689
7126
  modePrompt,
5690
7127
  modelCapabilities,
5691
- planPath: () => sessionRef.current ? path15.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
5692
7129
  })
5693
7130
  );
5694
7131
  const toolRegistry = new ToolRegistry();
@@ -5733,7 +7170,7 @@ async function main(argv) {
5733
7170
  const dumpMetrics = () => {
5734
7171
  if (!metricsSink) return;
5735
7172
  try {
5736
- const out = path15.join(wpaths.projectSessions, "metrics.json");
7173
+ const out = path17.join(wpaths.projectSessions, "metrics.json");
5737
7174
  const snap = metricsSink.snapshot();
5738
7175
  writeFileSync(out, JSON.stringify(snap, null, 2));
5739
7176
  } catch {
@@ -5861,7 +7298,13 @@ async function main(argv) {
5861
7298
  });
5862
7299
  const pipelines = setupPipelines({ events, logger });
5863
7300
  const compactor = container.resolve(TOKENS.Compactor);
5864
- 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
+ };
5865
7308
  const updateSpinnerContext = () => {
5866
7309
  if (effectiveMaxContext > 0 && lastInputTokens > 0) {
5867
7310
  spinner.setContext({ used: lastInputTokens, max: effectiveMaxContext });
@@ -5924,23 +7367,20 @@ async function main(argv) {
5924
7367
  }
5925
7368
  const switchProviderAndModel = (providerId, modelId) => {
5926
7369
  try {
5927
- console.error("[DEBUG] switchProviderAndModel called with:", { providerId, modelId });
5928
7370
  const savedCfg = config.providers?.[providerId];
5929
7371
  const resolvedProviderId = savedCfg?.type ?? providerId;
5930
- console.error("[DEBUG] switchProviderAndModel: resolvedProviderId:", resolvedProviderId, "savedCfg.type:", savedCfg?.type);
5931
7372
  const newCfg = savedCfg ?? {
5932
7373
  type: providerId,
5933
7374
  apiKey: config.apiKey,
5934
7375
  baseUrl: config.baseUrl
5935
7376
  };
5936
7377
  const cfgWithType = { ...newCfg, type: resolvedProviderId };
5937
- console.error("[DEBUG] switchProviderAndModel: cfgWithType:", cfgWithType);
5938
7378
  const newProvider = config.features.modelsRegistry && providerRegistry.has(resolvedProviderId) ? providerRegistry.create(cfgWithType) : makeProviderFromConfig(resolvedProviderId, cfgWithType);
5939
- console.error("[DEBUG] switchProviderAndModel: new provider id:", newProvider.id, "maxContext:", newProvider.capabilities.maxContext);
5940
7379
  context.provider = newProvider;
5941
7380
  context.model = modelId;
5942
7381
  config = patchConfig(config, { provider: providerId, model: modelId });
5943
7382
  configStore.update({ provider: providerId, model: modelId });
7383
+ void refreshMaxContext(resolvedProviderId, modelId);
5944
7384
  return null;
5945
7385
  } catch (err) {
5946
7386
  return err instanceof Error ? err.message : String(err);
@@ -5948,12 +7388,13 @@ async function main(argv) {
5948
7388
  };
5949
7389
  const directorMode = flags["director"] === true;
5950
7390
  let director = null;
5951
- const fleetRoot = directorMode ? path15.join(wpaths.projectSessions, session.id) : void 0;
5952
- const manifestPath = directorMode ? typeof process.env["WRONGSTACK_FLEET_MANIFEST"] === "string" ? process.env["WRONGSTACK_FLEET_MANIFEST"] : path15.join(fleetRoot, "fleet.json") : void 0;
5953
- const sharedScratchpadPath = directorMode ? path15.join(fleetRoot, "shared") : void 0;
5954
- const subagentSessionsRoot = directorMode ? path15.join(fleetRoot, "subagents") : void 0;
5955
- const stateCheckpointPath = directorMode ? path15.join(fleetRoot, "director-state.json") : void 0;
5956
- const fleetRootForPromotion = path15.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);
5957
7398
  const multiAgentHost = new MultiAgentHost(
5958
7399
  {
5959
7400
  container,
@@ -6024,6 +7465,7 @@ async function main(argv) {
6024
7465
  metricsSink,
6025
7466
  healthRegistry,
6026
7467
  planPath,
7468
+ modeStore,
6027
7469
  fleetStreamController,
6028
7470
  onSpawn: async (description, spawnOpts) => {
6029
7471
  const { subagentId, taskId } = await multiAgentHost.spawn(description, spawnOpts);
@@ -6037,8 +7479,19 @@ async function main(argv) {
6037
7479
  onAgents: () => {
6038
7480
  const s = multiAgentHost.status();
6039
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
+ }
6040
7493
  for (const p of s.pending) {
6041
- 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)}`);
6042
7495
  }
6043
7496
  for (const r of s.completed) {
6044
7497
  const fmt = fmtTaskResultLine(r, color);
@@ -6050,11 +7503,26 @@ async function main(argv) {
6050
7503
  if (action === "status") {
6051
7504
  const s = multiAgentHost.status();
6052
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
+ }
6053
7521
  if (s.pending.length > 0) {
6054
7522
  lines.push("", color.dim(" Pending"));
6055
7523
  for (const p of s.pending) {
6056
7524
  lines.push(
6057
- ` ${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)}`
6058
7526
  );
6059
7527
  }
6060
7528
  }
@@ -6105,7 +7573,7 @@ async function main(argv) {
6105
7573
  return `Unknown fleet action: ${action}`;
6106
7574
  },
6107
7575
  onFleetLog: async (subagentId, mode) => {
6108
- const subagentsRoot = path15.join(fleetRootForPromotion, "subagents");
7576
+ const subagentsRoot = path17.join(fleetRootForPromotion, "subagents");
6109
7577
  let runDirs;
6110
7578
  try {
6111
7579
  runDirs = await fs14.readdir(subagentsRoot);
@@ -6114,7 +7582,7 @@ async function main(argv) {
6114
7582
  }
6115
7583
  const found = [];
6116
7584
  for (const runId of runDirs) {
6117
- const runDir = path15.join(subagentsRoot, runId);
7585
+ const runDir = path17.join(subagentsRoot, runId);
6118
7586
  let files;
6119
7587
  try {
6120
7588
  files = await fs14.readdir(runDir);
@@ -6123,7 +7591,7 @@ async function main(argv) {
6123
7591
  }
6124
7592
  for (const f of files) {
6125
7593
  if (!f.endsWith(".jsonl")) continue;
6126
- const full = path15.join(runDir, f);
7594
+ const full = path17.join(runDir, f);
6127
7595
  try {
6128
7596
  const stat2 = await fs14.stat(full);
6129
7597
  found.push({
@@ -6220,7 +7688,7 @@ async function main(argv) {
6220
7688
  }
6221
7689
  const dir = await multiAgentHost.ensureDirector();
6222
7690
  if (!dir) return "Director is not available.";
6223
- const dirStatePath = path15.join(fleetRootForPromotion, "director-state.json");
7691
+ const dirStatePath = path17.join(fleetRootForPromotion, "director-state.json");
6224
7692
  const prior = await loadDirectorState(dirStatePath);
6225
7693
  if (!prior) {
6226
7694
  return "No prior director-state.json found \u2014 nothing to retry.";
@@ -6291,9 +7759,9 @@ async function main(argv) {
6291
7759
  for (const tool of director2.tools(FLEET_ROSTER)) {
6292
7760
  toolRegistry.register(tool);
6293
7761
  }
6294
- const mp = path15.join(fleetRootForPromotion, "fleet.json");
6295
- const sp = path15.join(fleetRootForPromotion, "shared");
6296
- const ss = path15.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");
6297
7765
  const lines = [
6298
7766
  `${color.green("\u2713")} Promoted to director mode.`,
6299
7767
  ` Roster: ${Object.keys(FLEET_ROSTER).join(", ")}`,
@@ -6320,6 +7788,22 @@ Restart WrongStack to load or unload plugin code in this session.`;
6320
7788
  }
6321
7789
  return result.message;
6322
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
+ },
6323
7807
  onExit: () => {
6324
7808
  void mcpRegistry.stopAll();
6325
7809
  },
@@ -6382,7 +7866,13 @@ Restart WrongStack to load or unload plugin code in this session.`;
6382
7866
  switchProviderAndModel,
6383
7867
  director: director ?? null,
6384
7868
  fleetRoster: FLEET_ROSTER,
6385
- 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
6386
7876
  });
6387
7877
  }
6388
7878
  async function promptRecovery(reader, renderer, abandoned, autoRecover) {