@wrongstack/core 0.9.0 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/{agent-bridge-Bad3ZGQ7.d.ts → agent-bridge-DMVOX0cF.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-Cc8sRJOF.d.ts → agent-subagent-runner-DaF_EgRG.d.ts} +3 -3
  3. package/dist/{compactor-Mw7-rNyb.d.ts → compactor-DVTKL7XD.d.ts} +1 -1
  4. package/dist/{config-Bi4Q0fnz.d.ts → config-SkMIDN9L.d.ts} +22 -2
  5. package/dist/{context-z2x5gv_V.d.ts → context-BRNbHmRM.d.ts} +6 -0
  6. package/dist/coordination/index.d.ts +12 -12
  7. package/dist/coordination/index.js +58 -37
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/{default-config-DvRSTELf.d.ts → default-config-Uzla85H-.d.ts} +5 -1
  10. package/dist/defaults/index.d.ts +23 -23
  11. package/dist/defaults/index.js +102 -72
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/{events-yCpJD4wa.d.ts → events-CiG9qUM_.d.ts} +1 -1
  14. package/dist/execution/index.d.ts +14 -14
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{index-Dl3PaD7f.d.ts → index-Bsha5K4D.d.ts} +8 -8
  18. package/dist/{index-SxpnwRlJ.d.ts → index-CP8638Wm.d.ts} +5 -5
  19. package/dist/index.d.ts +148 -39
  20. package/dist/index.js +1545 -98
  21. package/dist/index.js.map +1 -1
  22. package/dist/infrastructure/index.d.ts +6 -6
  23. package/dist/kernel/index.d.ts +9 -9
  24. package/dist/{mcp-servers-n2L9ohMX.d.ts → mcp-servers-BouUWYW6.d.ts} +3 -3
  25. package/dist/models/index.d.ts +2 -2
  26. package/dist/{multi-agent-C8uY45AY.d.ts → multi-agent-3ZnTB1aT.d.ts} +2 -2
  27. package/dist/{multi-agent-coordinator-C7YbZUY8.d.ts → multi-agent-coordinator-DTXF2aAl.d.ts} +2 -2
  28. package/dist/{null-fleet-bus-Bb2jVLKr.d.ts → null-fleet-bus-Chrc_3Pp.d.ts} +6 -6
  29. package/dist/observability/index.d.ts +2 -2
  30. package/dist/{path-resolver-DnC1nOjb.d.ts → path-resolver-TcJfc29Y.d.ts} +2 -2
  31. package/dist/{permission-policy-BBa1M1xc.d.ts → permission-policy-BpCGYBud.d.ts} +2 -2
  32. package/dist/{plan-templates-Cc-04Z8o.d.ts → plan-templates-envSmNlZ.d.ts} +10 -5
  33. package/dist/{provider-runner-C4ouU7--.d.ts → provider-runner-BEpikbbN.d.ts} +3 -3
  34. package/dist/{retry-policy-OwtKNxo8.d.ts → retry-policy-BYkq0ugs.d.ts} +1 -1
  35. package/dist/sdd/index.d.ts +8 -8
  36. package/dist/{secret-scrubber-CyE1-EMG.d.ts → secret-scrubber-DttNiGYA.d.ts} +1 -1
  37. package/dist/{secret-scrubber-C0n1EqrC.d.ts → secret-scrubber-QSeI0ADi.d.ts} +1 -1
  38. package/dist/security/index.d.ts +4 -4
  39. package/dist/security/index.js +5 -2
  40. package/dist/security/index.js.map +1 -1
  41. package/dist/{selector-DkvgYVS4.d.ts → selector-pox8abg0.d.ts} +1 -1
  42. package/dist/{session-reader-DsadjyF9.d.ts → session-reader-CSWcb5Ga.d.ts} +1 -1
  43. package/dist/storage/index.d.ts +77 -8
  44. package/dist/storage/index.js +433 -62
  45. package/dist/storage/index.js.map +1 -1
  46. package/dist/{system-prompt-CWA6ml-d.d.ts → system-prompt-Bs-Wliab.d.ts} +1 -1
  47. package/dist/{tool-executor-Ch25BDwa.d.ts → tool-executor-CsktM3h9.d.ts} +4 -4
  48. package/dist/types/index.d.ts +16 -16
  49. package/dist/types/index.js +8 -2
  50. package/dist/types/index.js.map +1 -1
  51. package/dist/utils/index.d.ts +2 -2
  52. package/dist/utils/index.js +3 -1
  53. package/dist/utils/index.js.map +1 -1
  54. package/dist/{wstack-paths-CJYrjQU8.d.ts → wstack-paths-BCgmTNlG.d.ts} +4 -0
  55. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -56,8 +56,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
56
56
  }
57
57
  let mode;
58
58
  try {
59
- const stat8 = await fsp2.stat(targetPath);
60
- mode = stat8.mode & 511;
59
+ const stat9 = await fsp2.stat(targetPath);
60
+ mode = stat9.mode & 511;
61
61
  } catch {
62
62
  mode = opts.mode;
63
63
  }
@@ -1021,6 +1021,9 @@ var DEFAULT_CONTEXT_CONFIG = Object.freeze({
1021
1021
  preserveK: 10,
1022
1022
  eliseThreshold: 2e3
1023
1023
  });
1024
+ var DEFAULT_AUTONOMY_CONFIG = Object.freeze({
1025
+ autoProceedDelayMs: 45e3
1026
+ });
1024
1027
 
1025
1028
  // src/types/secret-vault.ts
1026
1029
  var ENCRYPTED_PREFIX = "enc:v1:";
@@ -2061,7 +2064,10 @@ var PATTERNS = [
2061
2064
  type: "bearer_token",
2062
2065
  // Anchored with alternation instead of negative lookahead — avoids V8
2063
2066
  // backtracking risk on adversarial input. Bounded at 512 chars.
2064
- regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{20,512}=*(?:$|[^A-Za-z0-9_.~+/-])/g
2067
+ // Min 12 chars: some OAuth providers issue shorter-lived tokens (< 20
2068
+ // chars). A 12-char base64 string has ~71 bits of entropy — above the
2069
+ // threshold where random strings are unlikely to produce false matches.
2070
+ regex: /(?:^|[^A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?:$|[^A-Za-z0-9_.~+/-])/g
2065
2071
  },
2066
2072
  {
2067
2073
  type: "high_entropy_env",
@@ -3910,6 +3916,7 @@ function resolveWstackPaths(opts) {
3910
3916
  secretsKey: path6.join(globalRoot, ".key"),
3911
3917
  globalMemory: path6.join(globalRoot, "memory.md"),
3912
3918
  globalSkills: path6.join(globalRoot, "skills"),
3919
+ globalPrompts: path6.join(globalRoot, "prompts"),
3913
3920
  cacheDir: path6.join(globalRoot, "cache"),
3914
3921
  modelsCache: path6.join(globalRoot, "cache", "models.dev.json"),
3915
3922
  historyFile: path6.join(globalRoot, "history"),
@@ -3929,7 +3936,8 @@ function resolveWstackPaths(opts) {
3929
3936
  projectTaskGraphs: path6.join(projectDir, "task-graphs"),
3930
3937
  projectSddSession: path6.join(projectDir, "sdd-session.json"),
3931
3938
  projectPlan: path6.join(projectDir, "plan.json"),
3932
- projectAutophase: path6.join(projectDir, "autophase")
3939
+ projectAutophase: path6.join(projectDir, "autophase"),
3940
+ syncConfig: path6.join(globalRoot, "sync.json")
3933
3941
  };
3934
3942
  }
3935
3943
 
@@ -4028,11 +4036,11 @@ function validateAgainstSchema(value, schema) {
4028
4036
  walk2(value, schema, "", errors);
4029
4037
  return { ok: errors.length === 0, errors };
4030
4038
  }
4031
- function walk2(value, schema, path28, errors) {
4039
+ function walk2(value, schema, path31, errors) {
4032
4040
  if (schema.enum !== void 0) {
4033
4041
  if (!schema.enum.some((e) => deepEqual(e, value))) {
4034
4042
  errors.push({
4035
- path: path28 || "<root>",
4043
+ path: path31 || "<root>",
4036
4044
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
4037
4045
  });
4038
4046
  return;
@@ -4041,7 +4049,7 @@ function walk2(value, schema, path28, errors) {
4041
4049
  if (typeof schema.type === "string") {
4042
4050
  if (!checkType(value, schema.type)) {
4043
4051
  errors.push({
4044
- path: path28 || "<root>",
4052
+ path: path31 || "<root>",
4045
4053
  message: `expected ${schema.type}, got ${describeType(value)}`
4046
4054
  });
4047
4055
  return;
@@ -4051,19 +4059,19 @@ function walk2(value, schema, path28, errors) {
4051
4059
  const obj = value;
4052
4060
  for (const req of schema.required ?? []) {
4053
4061
  if (!(req in obj)) {
4054
- errors.push({ path: joinPath(path28, req), message: "required property missing" });
4062
+ errors.push({ path: joinPath(path31, req), message: "required property missing" });
4055
4063
  }
4056
4064
  }
4057
4065
  if (schema.properties) {
4058
4066
  for (const [key, subSchema] of Object.entries(schema.properties)) {
4059
4067
  if (key in obj) {
4060
- walk2(obj[key], subSchema, joinPath(path28, key), errors);
4068
+ walk2(obj[key], subSchema, joinPath(path31, key), errors);
4061
4069
  }
4062
4070
  }
4063
4071
  }
4064
4072
  }
4065
4073
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
4066
- value.forEach((item, i) => walk2(item, schema.items, `${path28}[${i}]`, errors));
4074
+ value.forEach((item, i) => walk2(item, schema.items, `${path31}[${i}]`, errors));
4067
4075
  }
4068
4076
  }
4069
4077
  function checkType(value, type) {
@@ -4125,24 +4133,30 @@ var DefaultSessionStore = class {
4125
4133
  this.dir = opts.dir;
4126
4134
  this.events = opts.events;
4127
4135
  }
4128
- async create(meta) {
4136
+ /** Join session ID to its absolute path within the store directory. */
4137
+ sessionPath(id, ext) {
4138
+ return path6.join(this.dir, `${id}${ext}`);
4139
+ }
4140
+ async ensureShardDir(_id) {
4129
4141
  await ensureDir(this.dir);
4142
+ return this.dir;
4143
+ }
4144
+ async create(meta) {
4130
4145
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
4131
4146
  const id = meta.id ?? `${startedAt.replace(/[:.]/g, "-")}-${randomBytes(2).toString("hex")}`;
4132
- const file = path6.join(this.dir, `${id}.jsonl`);
4147
+ const shardDir = await this.ensureShardDir(id);
4148
+ const file = path6.join(shardDir, `${id}.jsonl`);
4133
4149
  let handle;
4134
4150
  try {
4135
4151
  handle = await fsp2.open(file, "a", 384);
4136
4152
  } catch (err) {
4137
4153
  throw new Error(
4138
4154
  `Failed to open session file: ${err instanceof Error ? err.message : String(err)}`,
4139
- {
4140
- cause: err
4141
- }
4155
+ { cause: err }
4142
4156
  );
4143
4157
  }
4144
4158
  try {
4145
- return new FileSessionWriter(id, handle, startedAt, meta, this.events, { dir: this.dir, filePath: file });
4159
+ return new FileSessionWriter(id, handle, startedAt, meta, this.events, { dir: shardDir, filePath: file });
4146
4160
  } catch (err) {
4147
4161
  await handle.close().catch(() => {
4148
4162
  });
@@ -4150,15 +4164,7 @@ var DefaultSessionStore = class {
4150
4164
  }
4151
4165
  }
4152
4166
  async resume(id) {
4153
- const file = path6.join(this.dir, `${id}.jsonl`);
4154
- try {
4155
- await fsp2.access(file, fsp2.constants.R_OK);
4156
- } catch {
4157
- throw new Error(
4158
- `Session "${id}" not found \u2014 the session file does not exist or was deleted.`,
4159
- { cause: new Error("ENOENT") }
4160
- );
4161
- }
4167
+ const file = this.sessionPath(id, ".jsonl");
4162
4168
  const data = await this.load(id);
4163
4169
  let handle;
4164
4170
  try {
@@ -4190,7 +4196,7 @@ var DefaultSessionStore = class {
4190
4196
  }
4191
4197
  }
4192
4198
  async load(id) {
4193
- const file = path6.join(this.dir, `${id}.jsonl`);
4199
+ const file = this.sessionPath(id, ".jsonl");
4194
4200
  const raw = await fsp2.readFile(file, "utf8");
4195
4201
  const lines = raw.split("\n").filter((l) => l.trim());
4196
4202
  const events = [];
@@ -4210,8 +4216,7 @@ var DefaultSessionStore = class {
4210
4216
  async list(limit = 20) {
4211
4217
  try {
4212
4218
  await ensureDir(this.dir);
4213
- const files = await fsp2.readdir(this.dir);
4214
- const ids = files.filter((f) => f.endsWith(".jsonl")).map((f) => f.replace(/\.jsonl$/, ""));
4219
+ const ids = await this.collectSessionIds(this.dir);
4215
4220
  const sessions = await Promise.all(ids.map((id) => this.summaryFor(id).catch(() => null)));
4216
4221
  const out = sessions.filter((s) => s !== null);
4217
4222
  out.sort((a, b) => {
@@ -4224,15 +4229,29 @@ var DefaultSessionStore = class {
4224
4229
  return [];
4225
4230
  }
4226
4231
  }
4232
+ /** Recursively collect all session IDs from shard subdirectories. */
4233
+ async collectSessionIds(dir) {
4234
+ const ids = [];
4235
+ const entries = await fsp2.readdir(dir, { withFileTypes: true });
4236
+ for (const entry of entries) {
4237
+ const full = path6.join(dir, entry.name);
4238
+ if (entry.isDirectory()) {
4239
+ ids.push(...await this.collectSessionIds(full));
4240
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
4241
+ ids.push(entry.name.replace(/\.jsonl$/, ""));
4242
+ }
4243
+ }
4244
+ return ids;
4245
+ }
4227
4246
  async summaryFor(id) {
4228
- const manifest = path6.join(this.dir, `${id}.summary.json`);
4247
+ const manifest = this.sessionPath(id, ".summary.json");
4229
4248
  try {
4230
4249
  const raw = await fsp2.readFile(manifest, "utf8");
4231
4250
  return JSON.parse(raw);
4232
4251
  } catch {
4233
- const full = path6.join(this.dir, `${id}.jsonl`);
4234
- const stat8 = await fsp2.stat(full);
4235
- const summary = await this.summarize(id, stat8.mtime.toISOString());
4252
+ const full = this.sessionPath(id, ".jsonl");
4253
+ const stat9 = await fsp2.stat(full);
4254
+ const summary = await this.summarize(id, stat9.mtime.toISOString());
4236
4255
  await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
4237
4256
  console.warn(
4238
4257
  `[session-store] Failed to write manifest for "${id}":`,
@@ -4243,12 +4262,13 @@ var DefaultSessionStore = class {
4243
4262
  }
4244
4263
  }
4245
4264
  async delete(id) {
4246
- await fsp2.unlink(path6.join(this.dir, `${id}.jsonl`));
4247
- await fsp2.unlink(path6.join(this.dir, `${id}.summary.json`)).catch(() => void 0);
4265
+ await fsp2.unlink(this.sessionPath(id, ".jsonl"));
4266
+ await fsp2.unlink(this.sessionPath(id, ".summary.json")).catch(() => void 0);
4248
4267
  }
4249
4268
  async clearHistory(id) {
4250
- const file = path6.join(this.dir, `${id}.jsonl`);
4251
- const meta = path6.join(this.dir, `${id}.summary.json`);
4269
+ await this.ensureShardDir(id);
4270
+ const file = this.sessionPath(id, ".jsonl");
4271
+ const meta = this.sessionPath(id, ".summary.json");
4252
4272
  const record = `${JSON.stringify({
4253
4273
  type: "session_start",
4254
4274
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4292,7 +4312,8 @@ var DefaultSessionStore = class {
4292
4312
  startedAt: start?.ts ?? (/* @__PURE__ */ new Date(0)).toISOString(),
4293
4313
  endedAt: end?.ts,
4294
4314
  model: start?.model,
4295
- provider: start?.provider
4315
+ provider: start?.provider,
4316
+ pendingToolUses: end?.pendingToolUses
4296
4317
  };
4297
4318
  }
4298
4319
  replay(events, sessionId = "unknown") {
@@ -4392,9 +4413,6 @@ var FileSessionWriter = class {
4392
4413
  tokenIn = 0;
4393
4414
  tokenOut = 0;
4394
4415
  filePath;
4395
- /** Public accessor for the JSONL path — required by SessionWriter so
4396
- * observability surfaces (`/fleet log`, FleetPanel) can locate the
4397
- * transcript without recomputing the path from session metadata. */
4398
4416
  get transcriptPath() {
4399
4417
  return this.filePath || void 0;
4400
4418
  }
@@ -4402,13 +4420,16 @@ var FileSessionWriter = class {
4402
4420
  resumed;
4403
4421
  appendFailCount = 0;
4404
4422
  lastAppendWarnAt = 0;
4405
- // Rewind support: track pending file changes and prompt index
4406
4423
  promptIndex = 0;
4407
4424
  pendingFileSnapshots = [];
4408
- // Register a pending file change. Call recordFileChange() from tools.
4425
+ /** Tracks open tool_use IDs during the current run to serialize on close for resume. */
4426
+ openToolUses = /* @__PURE__ */ new Set();
4409
4427
  recordFileChange(input) {
4410
4428
  this.pendingFileSnapshots.push(input);
4411
4429
  }
4430
+ get pendingToolUses() {
4431
+ return Array.from(this.openToolUses);
4432
+ }
4412
4433
  async writeSessionStartLazy() {
4413
4434
  const record = `${JSON.stringify({
4414
4435
  type: this.resumed ? "session_resumed" : "session_start",
@@ -4451,13 +4472,12 @@ var FileSessionWriter = class {
4451
4472
  }
4452
4473
  }
4453
4474
  }
4454
- /**
4455
- * Watch events as they're appended and keep the summary state hot, so
4456
- * `close()` can flush a `<id>.summary.json` manifest without re-reading
4457
- * the JSONL. `list()` reads only manifests, turning a per-session full
4458
- * parse into a single stat+read.
4459
- */
4460
4475
  observeForSummary(event) {
4476
+ if (event.type === "tool_use") {
4477
+ this.openToolUses.add(event.id);
4478
+ } else if (event.type === "tool_result") {
4479
+ this.openToolUses.delete(event.id);
4480
+ }
4461
4481
  if (event.type === "user_input" && this.summary.title === "(empty session)") {
4462
4482
  this.summary = { ...this.summary, title: userInputTitle(event.content) };
4463
4483
  } else if (event.type === "llm_response") {
@@ -4551,7 +4571,16 @@ var FileSessionWriter = class {
4551
4571
  }
4552
4572
  }
4553
4573
  const truncated = kept.join("\n");
4554
- await fsp2.writeFile(this.filePath, truncated + "\n", "utf8");
4574
+ const tmpPath = `${this.filePath}.rewind.tmp`;
4575
+ await fsp2.writeFile(tmpPath, truncated + "\n", "utf8");
4576
+ try {
4577
+ await this.handle.close();
4578
+ await fsp2.rename(tmpPath, this.filePath);
4579
+ this.handle = await fsp2.open(this.filePath, "a", 384);
4580
+ } catch (err) {
4581
+ await fsp2.unlink(tmpPath).catch(() => void 0);
4582
+ throw err;
4583
+ }
4555
4584
  await this.append({
4556
4585
  type: "rewound",
4557
4586
  ts: (/* @__PURE__ */ new Date()).toISOString(),
@@ -5244,7 +5273,10 @@ var DefaultConfigLoader = class {
5244
5273
  }
5245
5274
  if (c.mode !== void 0 && !isContextWindowModeId(c.mode)) {
5246
5275
  const known = listContextWindowModes().map((m) => m.id).join(", ");
5247
- throw new Error(`Config: context.mode must be one of: ${known}`);
5276
+ console.warn(
5277
+ `[config] Ignoring unknown context.mode "${c.mode}" (expected one of: ${known}); falling back to "${DEFAULT_CONTEXT_WINDOW_MODE_ID}".`
5278
+ );
5279
+ c.mode = DEFAULT_CONTEXT_WINDOW_MODE_ID;
5248
5280
  }
5249
5281
  }
5250
5282
  validateIdentity(cfg) {
@@ -6165,7 +6197,7 @@ var DefaultPermissionPolicy = class {
6165
6197
  return { permission: "auto", source: "context", reason: "file already read in this session" };
6166
6198
  }
6167
6199
  }
6168
- if (tool.permission === "auto") {
6200
+ if (tool.permission === "auto" && !tool.mutating) {
6169
6201
  return { permission: "auto", source: "default" };
6170
6202
  }
6171
6203
  if (this.promptDelegate) {
@@ -8335,8 +8367,8 @@ ${recentJournal}` : "No prior iterations.",
8335
8367
  await saveGoal(this.goalPath, abandoned);
8336
8368
  }
8337
8369
  try {
8338
- const { unlink: unlink12 } = await import('fs/promises');
8339
- await unlink12(this.goalPath);
8370
+ const { unlink: unlink13 } = await import('fs/promises');
8371
+ await unlink13(this.goalPath);
8340
8372
  } catch {
8341
8373
  }
8342
8374
  this.opts.onEternalStop?.();
@@ -14317,8 +14349,8 @@ async function loadProjectModes(modesDir) {
14317
14349
  for (const entry of entries) {
14318
14350
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
14319
14351
  const filePath = path6.join(modesDir, entry);
14320
- const stat8 = await fsp2.stat(filePath);
14321
- if (!stat8.isFile()) continue;
14352
+ const stat9 = await fsp2.stat(filePath);
14353
+ if (!stat9.isFile()) continue;
14322
14354
  const content = await fsp2.readFile(filePath, "utf8");
14323
14355
  const id = path6.basename(entry, path6.extname(entry));
14324
14356
  modes.push({
@@ -15592,9 +15624,9 @@ var AISpecBuilder = class {
15592
15624
  if (!this.sessionPath) return;
15593
15625
  try {
15594
15626
  const fsp18 = await import('fs/promises');
15595
- const path28 = await import('path');
15627
+ const path31 = await import('path');
15596
15628
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
15597
- await fsp18.mkdir(path28.dirname(this.sessionPath), { recursive: true });
15629
+ await fsp18.mkdir(path31.dirname(this.sessionPath), { recursive: true });
15598
15630
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
15599
15631
  } catch {
15600
15632
  }
@@ -16304,15 +16336,15 @@ function computeCriticalPath(graph, topoOrder, blockedByMap) {
16304
16336
  maxId = id;
16305
16337
  }
16306
16338
  }
16307
- const path28 = [];
16339
+ const path31 = [];
16308
16340
  let current = maxId;
16309
16341
  const visited = /* @__PURE__ */ new Set();
16310
16342
  while (current && !visited.has(current)) {
16311
16343
  visited.add(current);
16312
- path28.unshift(current);
16344
+ path31.unshift(current);
16313
16345
  current = prev.get(current) ?? null;
16314
16346
  }
16315
- return path28;
16347
+ return path31;
16316
16348
  }
16317
16349
  function computeParallelGroups(graph, blockedByMap) {
16318
16350
  const groups = [];
@@ -17292,7 +17324,7 @@ async function startMetricsServer(opts) {
17292
17324
  const tls = opts.tls;
17293
17325
  const useHttps = !!(tls?.cert && tls?.key);
17294
17326
  const host = opts.host ?? "127.0.0.1";
17295
- const path28 = opts.path ?? "/metrics";
17327
+ const path31 = opts.path ?? "/metrics";
17296
17328
  const healthPath = opts.healthPath ?? "/healthz";
17297
17329
  const healthRegistry = opts.healthRegistry;
17298
17330
  const listener = (req, res) => {
@@ -17302,7 +17334,7 @@ async function startMetricsServer(opts) {
17302
17334
  return;
17303
17335
  }
17304
17336
  const url = req.url.split("?")[0];
17305
- if (url === path28) {
17337
+ if (url === path31) {
17306
17338
  let body;
17307
17339
  try {
17308
17340
  body = renderPrometheus(opts.sink.snapshot());
@@ -17366,7 +17398,7 @@ async function startMetricsServer(opts) {
17366
17398
  const protocol = useHttps ? "https" : "http";
17367
17399
  return {
17368
17400
  port: boundPort,
17369
- url: `${protocol}://${host}:${boundPort}${path28}`,
17401
+ url: `${protocol}://${host}:${boundPort}${path31}`,
17370
17402
  close: () => new Promise((resolve9, reject) => {
17371
17403
  server.close((err) => err ? reject(err) : resolve9());
17372
17404
  })
@@ -18213,10 +18245,10 @@ var SkillInstaller = class {
18213
18245
  if (!resolved.startsWith(path6.resolve(destDir))) {
18214
18246
  throw new Error(`Path traversal detected in skill file: ${file}`);
18215
18247
  }
18216
- const stat8 = await fsp2.stat(srcPath);
18217
- if (stat8.size > MAX_SKILL_FILE_SIZE) {
18248
+ const stat9 = await fsp2.stat(srcPath);
18249
+ if (stat9.size > MAX_SKILL_FILE_SIZE) {
18218
18250
  throw new Error(
18219
- `Skill file "${file}" is too large (${(stat8.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`
18251
+ `Skill file "${file}" is too large (${(stat9.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`
18220
18252
  );
18221
18253
  }
18222
18254
  await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
@@ -18624,6 +18656,356 @@ async function revertSnapshots(snapshots, projectRoot) {
18624
18656
  }
18625
18657
  return { revertedFiles, errors };
18626
18658
  }
18659
+
18660
+ // src/storage/prompt-store.ts
18661
+ init_atomic_write();
18662
+ var DefaultPromptStore = class {
18663
+ dir;
18664
+ constructor(paths) {
18665
+ this.dir = paths.globalPrompts;
18666
+ }
18667
+ async list() {
18668
+ await ensureDir(this.dir);
18669
+ let entries = [];
18670
+ try {
18671
+ const files = await fsp2.readdir(this.dir);
18672
+ for (const file of files) {
18673
+ if (!file.endsWith(".json")) continue;
18674
+ try {
18675
+ const raw = JSON.parse(
18676
+ await fsp2.readFile(path6.join(this.dir, file), "utf8")
18677
+ );
18678
+ entries.push(raw.entry);
18679
+ } catch {
18680
+ }
18681
+ }
18682
+ } catch {
18683
+ }
18684
+ return entries.sort(
18685
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
18686
+ );
18687
+ }
18688
+ async get(id) {
18689
+ const file = path6.join(this.dir, `${id}.json`);
18690
+ try {
18691
+ const raw = JSON.parse(await fsp2.readFile(file, "utf8"));
18692
+ return raw.entry;
18693
+ } catch {
18694
+ return null;
18695
+ }
18696
+ }
18697
+ async save(entry) {
18698
+ await ensureDir(this.dir);
18699
+ const file = path6.join(this.dir, `${entry.id}.json`);
18700
+ const raw = { version: 1, entry };
18701
+ await atomicWrite(file, JSON.stringify(raw, null, 2));
18702
+ }
18703
+ async delete(id) {
18704
+ const file = path6.join(this.dir, `${id}.json`);
18705
+ try {
18706
+ await fsp2.unlink(file);
18707
+ return true;
18708
+ } catch {
18709
+ return false;
18710
+ }
18711
+ }
18712
+ async find(query) {
18713
+ const all = await this.list();
18714
+ const lower = query.toLowerCase();
18715
+ return all.filter(
18716
+ (e) => e.title.toLowerCase().includes(lower) || e.content.toLowerCase().includes(lower) || e.tags.some((t2) => t2.toLowerCase().includes(lower))
18717
+ );
18718
+ }
18719
+ /** Create a new entry and return it. Does NOT persist — call save() afterwards. */
18720
+ createNew(title, content, tags = []) {
18721
+ const now = (/* @__PURE__ */ new Date()).toISOString();
18722
+ return {
18723
+ id: randomUUID().slice(0, 8),
18724
+ title,
18725
+ content,
18726
+ tags,
18727
+ createdAt: now,
18728
+ updatedAt: now
18729
+ };
18730
+ }
18731
+ };
18732
+ var ALL_SYNC_CATEGORIES = ["settings", "skills", "prompts", "memory", "history"];
18733
+ var CloudSync = class {
18734
+ constructor(paths, getConfig, setConfig) {
18735
+ this.paths = paths;
18736
+ this.getConfig = getConfig;
18737
+ this.setConfig = setConfig;
18738
+ this.statePath = path6.join(paths.globalRoot, "sync-state.json");
18739
+ }
18740
+ paths;
18741
+ getConfig;
18742
+ setConfig;
18743
+ statePath;
18744
+ state = null;
18745
+ // ── Public API ─────────────────────────────────────────────────────
18746
+ async status() {
18747
+ const cfg = this.getConfig();
18748
+ if (!cfg?.enabled) {
18749
+ return "CloudSync: disabled. Run `/sync enable` to activate.";
18750
+ }
18751
+ const last = this.state?.lastSyncedAt;
18752
+ const since = last ? timeAgo(last) : "never";
18753
+ return [
18754
+ `CloudSync: enabled`,
18755
+ ` repo: ${cfg.repo}`,
18756
+ ` categories: ${cfg.categories.join(", ")}`,
18757
+ ` last sync: ${since}`
18758
+ ].join("\n");
18759
+ }
18760
+ async enable(_repo, _categories) {
18761
+ return "Enable via /sync enable.";
18762
+ }
18763
+ async disable() {
18764
+ const cfg = this.getConfig();
18765
+ if (!cfg) return "CloudSync is not configured.";
18766
+ const next = { ...cfg, enabled: false };
18767
+ await this.setConfig(next);
18768
+ return "CloudSync disabled. Local data kept.";
18769
+ }
18770
+ async push(token) {
18771
+ const cfg = this.getConfig();
18772
+ if (!cfg?.enabled) return { ok: false, action: "push", categories: [], message: "Not enabled." };
18773
+ const parts = cfg.repo.split("/");
18774
+ const owner = parts[0];
18775
+ const repoName = parts[1];
18776
+ const branch = "main";
18777
+ const baseTreeSha = this.state?.sha;
18778
+ const { treeEntries, rev } = await this.buildLocalTree(cfg.categories);
18779
+ const newTreeSha = await this.createGitTree(token, owner, repoName, treeEntries, baseTreeSha);
18780
+ const commitSha = await this.createCommit(
18781
+ token,
18782
+ owner,
18783
+ repoName,
18784
+ newTreeSha,
18785
+ baseTreeSha,
18786
+ `Sync ${cfg.categories.join(", ")} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`
18787
+ );
18788
+ await this.updateRef(token, owner, repoName, branch, commitSha);
18789
+ const syncState = {
18790
+ version: 1,
18791
+ sha: commitSha,
18792
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
18793
+ localRev: rev
18794
+ };
18795
+ await fsp2.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
18796
+ this.state = syncState;
18797
+ return {
18798
+ ok: true,
18799
+ action: "push",
18800
+ categories: cfg.categories,
18801
+ committedAt: commitSha,
18802
+ message: `Pushed ${cfg.categories.join(", ")} to ${cfg.repo}. Commit: ${commitSha.slice(0, 7)}`
18803
+ };
18804
+ }
18805
+ async pull(token) {
18806
+ const cfg = this.getConfig();
18807
+ if (!cfg?.enabled) return { ok: false, action: "pull", categories: [], message: "Not enabled." };
18808
+ const pullParts = cfg.repo.split("/");
18809
+ const owner = pullParts[0];
18810
+ const repoName = pullParts[1];
18811
+ const branchData = await this.getRef(token, owner, repoName, "main");
18812
+ const currentSha = branchData.object.sha;
18813
+ const commitData = await this.getCommit(token, owner, repoName, currentSha);
18814
+ const treeSha = commitData.tree.sha;
18815
+ const treeEntries = await this.getTreeEntries(token, owner, repoName, treeSha);
18816
+ for (const entry of treeEntries) {
18817
+ if (entry.type !== "blob") continue;
18818
+ const segments = entry.path.split("/");
18819
+ if (segments[0] !== "data" || !segments[1]) continue;
18820
+ const cat = segments[1];
18821
+ if (!["settings", "skills", "prompts", "memory", "history"].includes(cat)) continue;
18822
+ const localPath = this.categoryToPath(cat);
18823
+ if (!localPath) continue;
18824
+ const rel = segments.slice(2).join("/");
18825
+ const destPath = rel ? path6.join(localPath, rel) : localPath;
18826
+ const blobData = await this.getBlob(token, owner, repoName, entry.sha);
18827
+ await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
18828
+ await fsp2.writeFile(destPath, Buffer.from(blobData, "base64"));
18829
+ }
18830
+ const localRev = await this.hashLocalCategories(cfg.categories);
18831
+ const syncState = {
18832
+ version: 1,
18833
+ sha: currentSha,
18834
+ lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString(),
18835
+ localRev
18836
+ };
18837
+ await fsp2.writeFile(this.statePath, JSON.stringify(syncState, null, 2), "utf8");
18838
+ this.state = syncState;
18839
+ return {
18840
+ ok: true,
18841
+ action: "pull",
18842
+ categories: cfg.categories,
18843
+ committedAt: currentSha,
18844
+ message: `Pulled ${cfg.categories.join(", ")} from ${cfg.repo}. Commit: ${currentSha.slice(0, 7)}`
18845
+ };
18846
+ }
18847
+ async hasLocalChanges() {
18848
+ if (!this.state) return true;
18849
+ const cfg = this.getConfig();
18850
+ if (!cfg) return true;
18851
+ const current = await this.hashLocalCategories(cfg.categories);
18852
+ return current !== this.state.localRev;
18853
+ }
18854
+ async loadState() {
18855
+ try {
18856
+ const raw = await fsp2.readFile(this.statePath, "utf8");
18857
+ this.state = JSON.parse(raw);
18858
+ } catch {
18859
+ this.state = null;
18860
+ }
18861
+ }
18862
+ // ── GitHub API helpers ──────────────────────────────────────────────
18863
+ async githubFetch(token, owner, repo, method, pathSegment, body) {
18864
+ const url = `https://api.github.com/repos/${owner}/${repo}${pathSegment}`;
18865
+ const res = await fetch(url, {
18866
+ method,
18867
+ headers: {
18868
+ Authorization: `Bearer ${token}`,
18869
+ Accept: "application/vnd.github+json",
18870
+ "X-GitHub-Api-Version": "2022-11-28",
18871
+ "Content-Type": "application/json"
18872
+ },
18873
+ body: body !== void 0 ? JSON.stringify(body) : void 0
18874
+ });
18875
+ if (!res.ok) {
18876
+ const errText = await res.text();
18877
+ throw new Error(`GitHub API ${method} ${pathSegment} failed (${res.status}): ${errText}`);
18878
+ }
18879
+ const text = await res.text();
18880
+ return text ? JSON.parse(text) : {};
18881
+ }
18882
+ async getRef(token, owner, repo, ref) {
18883
+ return await this.githubFetch(token, owner, repo, "GET", `/git/refs/heads/${ref}`);
18884
+ }
18885
+ async updateRef(token, owner, repo, ref, sha) {
18886
+ await this.githubFetch(token, owner, repo, "PATCH", `/git/refs/heads/${ref}`, {
18887
+ sha,
18888
+ force: false
18889
+ });
18890
+ }
18891
+ async getCommit(token, owner, repo, sha) {
18892
+ return await this.githubFetch(token, owner, repo, "GET", `/git/commits/${sha}`);
18893
+ }
18894
+ async getTreeEntries(token, owner, repo, treeSha) {
18895
+ return await this.githubFetch(token, owner, repo, "GET", `/git/trees/${treeSha}?recursive=1`);
18896
+ }
18897
+ async createCommit(token, owner, repo, treeSha, parentSha, message = "sync") {
18898
+ const body = { message, tree: treeSha };
18899
+ if (parentSha) body.parents = [parentSha];
18900
+ const result = await this.githubFetch(token, owner, repo, "POST", "/git/commits", body);
18901
+ return result.sha;
18902
+ }
18903
+ async createGitTree(token, owner, repo, entries, baseTreeSha) {
18904
+ const tree = entries.map((e) => ({
18905
+ path: e.path,
18906
+ mode: e.mode,
18907
+ type: "blob",
18908
+ content: e.content
18909
+ }));
18910
+ const body = { tree };
18911
+ if (baseTreeSha) body.base_tree = baseTreeSha;
18912
+ const result = await this.githubFetch(token, owner, repo, "POST", "/git/trees", body);
18913
+ return result.sha;
18914
+ }
18915
+ async getBlob(token, owner, repo, sha) {
18916
+ const result = await this.githubFetch(token, owner, repo, "GET", `/git/blobs/${sha}`);
18917
+ return result.content;
18918
+ }
18919
+ // ── Local file helpers ──────────────────────────────────────────────
18920
+ async buildLocalTree(categories) {
18921
+ const entries = [];
18922
+ const hashes = [];
18923
+ for (const cat of categories) {
18924
+ const localPath = this.categoryToPath(cat);
18925
+ if (!localPath) continue;
18926
+ try {
18927
+ const stat9 = await fsp2.stat(localPath);
18928
+ if (stat9.isDirectory()) {
18929
+ const files = await this.walkDir(localPath, localPath);
18930
+ for (const file of files) {
18931
+ const content = await fsp2.readFile(file, "utf8");
18932
+ const rel = path6.relative(localPath, file).replace(/\\/g, "/");
18933
+ entries.push({ path: `data/${cat}/${rel}`, content, mode: "100644" });
18934
+ hashes.push(content);
18935
+ }
18936
+ } else {
18937
+ const content = await fsp2.readFile(localPath, "utf8");
18938
+ entries.push({ path: `data/${cat}`, content, mode: "100644" });
18939
+ hashes.push(content);
18940
+ }
18941
+ } catch {
18942
+ }
18943
+ }
18944
+ const rev = createHash("sha256").update(hashes.join("")).digest("hex").slice(0, 12);
18945
+ return { treeEntries: entries, rev };
18946
+ }
18947
+ async hashLocalCategories(categories) {
18948
+ const hashes = [];
18949
+ for (const cat of categories) {
18950
+ const localPath = this.categoryToPath(cat);
18951
+ if (!localPath) continue;
18952
+ try {
18953
+ const stat9 = await fsp2.stat(localPath);
18954
+ if (stat9.isDirectory()) {
18955
+ const files = await this.walkDir(localPath, localPath);
18956
+ for (const file of files) {
18957
+ const content = await fsp2.readFile(file);
18958
+ hashes.push(content.toString("base64") + file);
18959
+ }
18960
+ } else {
18961
+ const content = await fsp2.readFile(localPath);
18962
+ hashes.push(content.toString("base64") + localPath);
18963
+ }
18964
+ } catch {
18965
+ }
18966
+ }
18967
+ return createHash("sha256").update(hashes.join("")).digest("hex").slice(0, 12);
18968
+ }
18969
+ categoryToPath(cat) {
18970
+ switch (cat) {
18971
+ case "settings":
18972
+ return this.paths.globalConfig;
18973
+ case "skills":
18974
+ return this.paths.globalSkills;
18975
+ case "prompts":
18976
+ return this.paths.globalPrompts;
18977
+ case "memory":
18978
+ return this.paths.globalMemory;
18979
+ case "history":
18980
+ return this.paths.historyFile;
18981
+ default:
18982
+ return null;
18983
+ }
18984
+ }
18985
+ async walkDir(dir, base) {
18986
+ const results = [];
18987
+ const entries = await fsp2.readdir(dir, { withFileTypes: true });
18988
+ for (const entry of entries) {
18989
+ const full = path6.join(dir, entry.name);
18990
+ if (entry.isDirectory()) {
18991
+ results.push(...await this.walkDir(full, base));
18992
+ } else {
18993
+ results.push(full);
18994
+ }
18995
+ }
18996
+ return results;
18997
+ }
18998
+ };
18999
+ function timeAgo(iso) {
19000
+ const diff = Date.now() - new Date(iso).getTime();
19001
+ const mins = Math.floor(diff / 6e4);
19002
+ if (mins < 1) return "just now";
19003
+ if (mins < 60) return `${mins}m ago`;
19004
+ const hrs = Math.floor(mins / 60);
19005
+ if (hrs < 24) return `${hrs}h ago`;
19006
+ const days = Math.floor(hrs / 24);
19007
+ return `${days}d ago`;
19008
+ }
18627
19009
  var MATCHERS = {
18628
19010
  pnpmWorkspace: (files) => files.includes("pnpm-workspace.yaml"),
18629
19011
  gradlew: (_files, dirs) => dirs.includes("gradlew"),
@@ -19501,8 +19883,8 @@ var ReportGenerator = class {
19501
19883
  try {
19502
19884
  await stat(this.options.outputDir);
19503
19885
  } catch {
19504
- const { mkdir: mkdir14 } = await import('fs/promises');
19505
- await mkdir14(this.options.outputDir, { recursive: true });
19886
+ const { mkdir: mkdir15 } = await import('fs/promises');
19887
+ await mkdir15(this.options.outputDir, { recursive: true });
19506
19888
  }
19507
19889
  }
19508
19890
  generateMarkdown(result) {
@@ -20424,16 +20806,16 @@ Use \`/security report <number>\` to view a specific report.` };
20424
20806
  }
20425
20807
  const index = Number.parseInt(reportId, 10) - 1;
20426
20808
  if (!Number.isNaN(index) && reports[index]) {
20427
- const { readFile: readFile32 } = await import('fs/promises');
20428
- const content = await readFile32(join(reportsDir, reports[index]), "utf-8");
20809
+ const { readFile: readFile34 } = await import('fs/promises');
20810
+ const content = await readFile34(join(reportsDir, reports[index]), "utf-8");
20429
20811
  return { message: `# Security Report
20430
20812
 
20431
20813
  ${content}` };
20432
20814
  }
20433
20815
  const match = reports.find((r) => r.includes(reportId));
20434
20816
  if (match) {
20435
- const { readFile: readFile32 } = await import('fs/promises');
20436
- const content = await readFile32(join(reportsDir, match), "utf-8");
20817
+ const { readFile: readFile34 } = await import('fs/promises');
20818
+ const content = await readFile34(join(reportsDir, match), "utf-8");
20437
20819
  return { message: `# Security Report
20438
20820
 
20439
20821
  ${content}` };
@@ -20793,7 +21175,7 @@ function createMcpControlTool(opts) {
20793
21175
  description: "Manage MCP server lifecycle: list available servers, search by name or capability, enable or disable servers at runtime, restart running servers.",
20794
21176
  category: "mcp",
20795
21177
  permission: "auto",
20796
- mutating: false,
21178
+ mutating: true,
20797
21179
  riskTier: "standard",
20798
21180
  inputSchema,
20799
21181
  async execute(raw, ctx) {
@@ -22595,8 +22977,8 @@ ${mem}`);
22595
22977
  }
22596
22978
  async dirExists(p) {
22597
22979
  try {
22598
- const stat8 = await fsp2.stat(p);
22599
- return stat8.isDirectory();
22980
+ const stat9 = await fsp2.stat(p);
22981
+ return stat9.isDirectory();
22600
22982
  } catch {
22601
22983
  return false;
22602
22984
  }
@@ -22857,39 +23239,55 @@ var ProviderRegistry = class {
22857
23239
  var SlashCommandRegistry = class {
22858
23240
  cmds = /* @__PURE__ */ new Map();
22859
23241
  /**
22860
- * Register a command. For plugins the `owner` is the plugin name and the
22861
- * registry auto-prefixes the command name as `owner:name` to prevent
22862
- * collisions with built-in commands and other plugins.
23242
+ * Register a command.
23243
+ *
23244
+ * Trust tiers, by `owner` and `opts.official`:
23245
+ *
23246
+ * - **Built-ins** (`owner === 'core'`) and **official plugins**
23247
+ * (`opts.official === true`, set by the host only for first-party plugins
23248
+ * loaded from the built-in factory list) claim the **bare** command name
23249
+ * (`/prompts`). They may override one another — last write wins — so an
23250
+ * official plugin can replace a built-in. Official plugins are *also*
23251
+ * reachable under their `owner:name` namespace.
23252
+ * - **External plugins** (any other `owner`) are isolated under the
23253
+ * `owner:name` namespace: invocable only as `/owner:cmd`, never by bare
23254
+ * name, and unable to shadow or override a built-in or official command.
23255
+ *
23256
+ * Officiality is supplied by the host based on the plugin's load source, not
23257
+ * self-declared by the plugin — an external plugin cannot name itself into
23258
+ * the official tier.
22863
23259
  */
22864
- register(cmd, owner = "core") {
23260
+ register(cmd, owner = "core", opts) {
22865
23261
  const isPlugin = owner !== "core";
22866
- const fullName = isPlugin ? `${owner}:${cmd.name}` : cmd.name;
22867
- for (const entry of this.cmds.values()) {
22868
- if (entry.cmd.name === cmd.name && entry.owner !== owner) {
22869
- throw new Error(`Slash command "${cmd.name}" already registered by ${entry.owner}`);
23262
+ const official = !isPlugin || opts?.official === true;
23263
+ if (official) {
23264
+ const existing = this.cmds.get(cmd.name);
23265
+ if (existing) {
23266
+ const existingIsOfficialPlugin = existing.official && existing.owner !== "core";
23267
+ if (!isPlugin && existingIsOfficialPlugin) {
23268
+ return;
23269
+ }
23270
+ if (!isPlugin && existing.owner === "core") return;
22870
23271
  }
22871
- }
22872
- if (this.cmds.has(fullName)) {
22873
- if (!isPlugin) return;
22874
- this.cmds.set(fullName, { cmd, owner });
23272
+ this.cmds.set(cmd.name, { cmd, owner, official });
22875
23273
  for (const a of cmd.aliases ?? []) {
22876
- this.cmds.set(`${owner}:${a}`, { cmd, owner });
23274
+ this.cmds.set(a, { cmd, owner, official });
22877
23275
  }
22878
- return;
22879
23276
  }
22880
- this.cmds.set(fullName, { cmd, owner });
22881
- for (const a of cmd.aliases ?? []) {
22882
- this.cmds.set(isPlugin ? `${owner}:${a}` : a, { cmd, owner });
23277
+ if (isPlugin) {
23278
+ this.cmds.set(`${owner}:${cmd.name}`, { cmd, owner, official });
23279
+ for (const a of cmd.aliases ?? []) {
23280
+ this.cmds.set(`${owner}:${a}`, { cmd, owner, official });
23281
+ }
22883
23282
  }
22884
23283
  }
22885
23284
  unregister(name) {
22886
23285
  const entry = this.cmds.get(name);
22887
23286
  if (!entry) return false;
22888
- for (const a of entry.cmd.aliases ?? []) {
22889
- const fullAlias = entry.owner !== "core" ? `${entry.owner}:${a}` : a;
22890
- this.cmds.delete(fullAlias);
23287
+ for (const [key, e] of this.cmds.entries()) {
23288
+ if (e.cmd === entry.cmd) this.cmds.delete(key);
22891
23289
  }
22892
- return this.cmds.delete(name);
23290
+ return true;
22893
23291
  }
22894
23292
  /**
22895
23293
  * Bulk-register multiple slash commands at once.
@@ -23008,8 +23406,9 @@ var DefaultPluginAPI = class {
23008
23406
  };
23009
23407
  this.mcp = init.mcpRegistry ?? noopMcp;
23010
23408
  const scr = init.slashCommandRegistry;
23409
+ const official = init.official === true;
23011
23410
  this.slashCommands = scr ? {
23012
- register: (cmd) => scr.register(cmd, owner),
23411
+ register: (cmd) => scr.register(cmd, owner, { official }),
23013
23412
  unregister: (name) => scr.unregister(name),
23014
23413
  get: (name) => scr.get(name),
23015
23414
  list: () => scr.list()
@@ -24897,6 +25296,1054 @@ function assertSafePath(dir, projectRoot) {
24897
25296
  }
24898
25297
  }
24899
25298
 
24900
- export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CheckpointManager, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMcpControlTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeLLMClassifier, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, pendingBtwCount, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
25299
+ // src/plugins/prompts-plugin.ts
25300
+ function createPromptsPlugin(opts) {
25301
+ let store = null;
25302
+ return {
25303
+ name: "wstack-prompts",
25304
+ version: "1.0.0",
25305
+ description: "Personal prompt library with LLM-powered enhancements",
25306
+ apiVersion: "^0.1",
25307
+ capabilities: { slashCommands: true },
25308
+ defaultConfig: {},
25309
+ setup(api) {
25310
+ const rawConfig = api.config;
25311
+ const paths = opts?.paths ?? rawConfig.paths;
25312
+ store = opts?.store ?? (paths ? new DefaultPromptStore(paths) : null);
25313
+ api.slashCommands.register(buildPromptsCommand(store));
25314
+ api.log.info("[prompts] loaded \u2014 /prompts available");
25315
+ },
25316
+ teardown(api) {
25317
+ api.slashCommands.unregister("prompts");
25318
+ api.log.info("[prompts] unloaded");
25319
+ },
25320
+ async health() {
25321
+ return { ok: true, message: "Prompt store accessible" };
25322
+ }
25323
+ };
25324
+ }
25325
+ function buildPromptsCommand(store) {
25326
+ return {
25327
+ name: "prompts",
25328
+ description: "Manage your prompt library: /prompts [list|view|add|delete|edit|extend]",
25329
+ async run(args, ctx) {
25330
+ if (!store) return { message: "Prompt store not available." };
25331
+ const [verb, ...rest] = args.trim().split(/\s+/);
25332
+ const restJoined = rest.join(" ");
25333
+ switch (verb) {
25334
+ case "":
25335
+ case "list":
25336
+ case "ls": {
25337
+ const entries = await store.list();
25338
+ if (entries.length === 0) {
25339
+ return { message: 'Prompt library empty. Add: /prompts add "title" "content"' };
25340
+ }
25341
+ const lines = entries.map(
25342
+ (e) => ` ${e.title} ${dim2(e.id)} ${e.tags.join(", ") || ""}`
25343
+ );
25344
+ return { message: `Prompt library (${entries.length}):
25345
+ ${lines.join("\n")}
25346
+ ` };
25347
+ }
25348
+ case "view":
25349
+ case "show": {
25350
+ if (!restJoined) return { message: "Usage: /prompts view <title>" };
25351
+ const matches = await store.find(restJoined);
25352
+ if (matches.length === 0) return { message: `No prompt matching "${restJoined}".` };
25353
+ const entry = matches.find((m) => m.title.toLowerCase() === restJoined.toLowerCase()) ?? matches[0];
25354
+ const tags = entry.tags.length > 0 ? ` [${entry.tags.join(", ")}]` : "";
25355
+ return {
25356
+ message: `# ${entry.title}${tags}
25357
+
25358
+ ${entry.content}
25359
+
25360
+ ${dim2(`id: ${entry.id} | created: ${entry.createdAt}`)}`
25361
+ };
25362
+ }
25363
+ case "add":
25364
+ case "new": {
25365
+ const parsed = parseTitleContent(restJoined);
25366
+ if (!parsed.title) return { message: 'Usage: /prompts add "title" "prompt content"' };
25367
+ const entry = store.createNew(parsed.title, parsed.content);
25368
+ await store.save(entry);
25369
+ return { message: `Added prompt "${entry.title}" (${entry.id}).` };
25370
+ }
25371
+ case "delete":
25372
+ case "rm": {
25373
+ if (!restJoined) return { message: "Usage: /prompts delete <title>" };
25374
+ const matches = await store.find(restJoined);
25375
+ if (matches.length === 0) return { message: `No prompt matching "${restJoined}".` };
25376
+ const exact = matches.find((m) => m.title.toLowerCase() === restJoined.toLowerCase()) ?? matches[0];
25377
+ const deleted = await store.delete(exact.id);
25378
+ return { message: deleted ? `Deleted "${exact.title}".` : "Delete failed." };
25379
+ }
25380
+ case "edit":
25381
+ case "update": {
25382
+ const parsed = parseTitleContent(restJoined);
25383
+ if (!parsed.title) return { message: 'Usage: /prompts edit "title" "new content"' };
25384
+ const matches = await store.find(parsed.title);
25385
+ if (matches.length === 0) return { message: `No prompt matching "${parsed.title}".` };
25386
+ const exact = matches.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase()) ?? matches[0];
25387
+ exact.content = parsed.content;
25388
+ exact.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
25389
+ await store.save(exact);
25390
+ return { message: `Updated "${exact.title}".` };
25391
+ }
25392
+ case "extend": {
25393
+ if (!restJoined) return { message: 'Usage: /prompts extend "title" <instructions>' };
25394
+ const parsed = parseTitleContent(restJoined);
25395
+ if (!parsed.title) return { message: 'Usage: /prompts extend "title" <instructions>' };
25396
+ const matches = await store.find(parsed.title);
25397
+ if (matches.length === 0) return { message: `No prompt matching "${parsed.title}".` };
25398
+ const exact = matches.find((m) => m.title.toLowerCase() === parsed.title.toLowerCase()) ?? matches[0];
25399
+ const prov = ctx.provider;
25400
+ if (!prov?.complete) return { message: "LLM not available. Configure a provider first." };
25401
+ const enhanced = await prov.complete(ctx.model, [
25402
+ "[SYSTEM INSTRUCTIONS]",
25403
+ "You are a prompt engineering assistant. Improve the following prompt by integrating the additional instructions seamlessly. Keep the same tone and format. Return only the improved prompt.",
25404
+ "",
25405
+ `EXISTING PROMPT:
25406
+ ${exact.content}`,
25407
+ "",
25408
+ `ADDITIONAL INSTRUCTIONS:
25409
+ ${parsed.content}`,
25410
+ "",
25411
+ "Respond with ONLY the improved prompt, no commentary."
25412
+ ].join("\n"));
25413
+ exact.content = enhanced.trim();
25414
+ exact.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
25415
+ await store.save(exact);
25416
+ return { message: `Extended "${exact.title}".
25417
+
25418
+ ${dim2("New content:")}
25419
+ ${exact.content}` };
25420
+ }
25421
+ default:
25422
+ return {
25423
+ message: `Unknown subcommand "${verb}". Try: list | view | add | delete | edit | extend`
25424
+ };
25425
+ }
25426
+ }
25427
+ };
25428
+ }
25429
+ function parseTitleContent(args) {
25430
+ const trimmed = args.trim();
25431
+ if (!trimmed) return { title: "", content: "" };
25432
+ const doubleMatch = /^"([^"]+)"\s+"([^"]+)"$/.exec(trimmed) || /^'([^']+)'\s+'([^']+)'$/.exec(trimmed);
25433
+ if (doubleMatch) return { title: doubleMatch[1], content: doubleMatch[2] };
25434
+ const singleMatch = /^'([^']+)'\s+(.+)$/.exec(trimmed);
25435
+ if (singleMatch) return { title: singleMatch[1], content: singleMatch[2] };
25436
+ const firstSpace = trimmed.indexOf(" ");
25437
+ if (firstSpace === -1) return { title: trimmed, content: "" };
25438
+ return { title: trimmed.slice(0, firstSpace), content: trimmed.slice(firstSpace + 1) };
25439
+ }
25440
+ function dim2(s) {
25441
+ return `\x1B[2m${s}\x1B[0m`;
25442
+ }
25443
+
25444
+ // src/plugins/sync-plugin.ts
25445
+ function createSyncPlugin(opts) {
25446
+ let cloud = null;
25447
+ let configStore;
25448
+ return {
25449
+ name: "wstack-sync",
25450
+ version: "1.0.0",
25451
+ description: "GitHub cloud sync for prompts, skills, settings, memory and history",
25452
+ apiVersion: "^0.1",
25453
+ capabilities: { slashCommands: true },
25454
+ defaultConfig: {},
25455
+ setup(api) {
25456
+ const rawConfig = api.config;
25457
+ const paths = opts?.paths ?? rawConfig.paths;
25458
+ configStore = opts?.configStore ?? rawConfig.configStore;
25459
+ if (!paths || !configStore) {
25460
+ api.log.warn("[sync] paths or configStore not available \u2014 /sync disabled");
25461
+ return;
25462
+ }
25463
+ cloud = new CloudSync(
25464
+ paths,
25465
+ () => {
25466
+ const cfg = configStore.get();
25467
+ return cfg.sync;
25468
+ },
25469
+ async (cfg) => {
25470
+ configStore.update({ sync: cfg });
25471
+ }
25472
+ );
25473
+ void cloud.loadState();
25474
+ api.slashCommands.register(buildSyncCommand(cloud, configStore));
25475
+ api.log.info("[sync] loaded \u2014 /sync available. Run /sync to get started.");
25476
+ },
25477
+ teardown(api) {
25478
+ api.slashCommands.unregister("sync");
25479
+ api.log.info("[sync] unloaded");
25480
+ },
25481
+ async health() {
25482
+ return { ok: true, message: "CloudSync ready" };
25483
+ }
25484
+ };
25485
+ }
25486
+ function buildSyncCommand(cloud, configStore) {
25487
+ return {
25488
+ name: "sync",
25489
+ description: "Cloud sync: /sync [status|enable|disable|push|pull|categories]",
25490
+ async run(args, _ctx) {
25491
+ const [verb, ...rest] = args.trim().split(/\s+/);
25492
+ const restJoined = rest.join(" ");
25493
+ switch (verb) {
25494
+ case "":
25495
+ case "status": {
25496
+ const msg = await cloud.status();
25497
+ return { message: msg };
25498
+ }
25499
+ case "enable": {
25500
+ const parts = restJoined.trim().split(/\s+/);
25501
+ if (parts.length < 2) {
25502
+ return {
25503
+ message: [
25504
+ "Usage: /sync enable owner/repo TOKEN [cat1 cat2 ...]",
25505
+ " e.g. /sync enable myname/wrongstack-data ghp_xxx",
25506
+ ` Categories (default: all): ${ALL_SYNC_CATEGORIES.join(", ")}`
25507
+ ].join("\n")
25508
+ };
25509
+ }
25510
+ const [repo, token, ...cats] = parts;
25511
+ if (!repo || !repo.includes("/")) {
25512
+ return { message: 'Invalid repo format. Expected "owner/repo".' };
25513
+ }
25514
+ const syncConfig = {
25515
+ enabled: true,
25516
+ repo,
25517
+ githubToken: token,
25518
+ categories: cats.length > 0 ? cats : ALL_SYNC_CATEGORIES,
25519
+ lastSyncedAt: void 0
25520
+ };
25521
+ configStore.update({ sync: syncConfig });
25522
+ await cloud.loadState();
25523
+ return {
25524
+ message: [
25525
+ `CloudSync enabled for ${repo}`,
25526
+ ` categories: ${syncConfig.categories.join(", ")}`,
25527
+ "",
25528
+ "Run `/sync push` to upload your data to GitHub."
25529
+ ].join("\n")
25530
+ };
25531
+ }
25532
+ case "disable": {
25533
+ const msg = await cloud.disable();
25534
+ return { message: msg };
25535
+ }
25536
+ case "push": {
25537
+ const cfg = configStore.get().sync;
25538
+ if (!cfg?.enabled) return { message: "CloudSync not enabled. Run `/sync enable`." };
25539
+ if (!cfg?.githubToken) return { message: "No GitHub token found. Run `/sync enable owner/repo TOKEN`." };
25540
+ let result;
25541
+ try {
25542
+ result = await cloud.push(cfg.githubToken);
25543
+ } catch (err) {
25544
+ return { message: `Push failed: ${err instanceof Error ? err.message : String(err)}` };
25545
+ }
25546
+ if (result.ok) {
25547
+ configStore.update({
25548
+ sync: { ...cfg, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() }
25549
+ });
25550
+ }
25551
+ return { message: result.message };
25552
+ }
25553
+ case "pull": {
25554
+ const cfg = configStore.get().sync;
25555
+ if (!cfg?.enabled) return { message: "CloudSync not enabled. Run `/sync enable`." };
25556
+ if (!cfg?.githubToken) return { message: "No GitHub token. Run `/sync enable owner/repo TOKEN`." };
25557
+ let result;
25558
+ try {
25559
+ result = await cloud.pull(cfg.githubToken);
25560
+ } catch (err) {
25561
+ return { message: `Pull failed: ${err instanceof Error ? err.message : String(err)}` };
25562
+ }
25563
+ if (result.ok) {
25564
+ configStore.update({
25565
+ sync: { ...cfg, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() }
25566
+ });
25567
+ }
25568
+ return { message: result.message };
25569
+ }
25570
+ case "categories": {
25571
+ const [action, ...catRest] = restJoined.trim().split(/\s+/);
25572
+ const cfg = configStore.get().sync;
25573
+ if (!cfg?.enabled) return { message: "CloudSync not enabled. Run `/sync enable`." };
25574
+ if (action === "list" || !action) {
25575
+ return {
25576
+ message: [
25577
+ `Synced categories: ${cfg.categories.join(", ")}`,
25578
+ `Available: ${ALL_SYNC_CATEGORIES.join(", ")}`,
25579
+ "Update: /sync categories add <name> | remove <name>"
25580
+ ].join("\n")
25581
+ };
25582
+ }
25583
+ if (action === "add") {
25584
+ const catName = catRest[0];
25585
+ if (!ALL_SYNC_CATEGORIES.includes(catName)) {
25586
+ return { message: `Unknown. Available: ${ALL_SYNC_CATEGORIES.join(", ")}` };
25587
+ }
25588
+ if (cfg.categories.includes(catName)) return { message: `"${catName}" already synced.` };
25589
+ configStore.update({
25590
+ sync: { ...cfg, categories: [...cfg.categories, catName] }
25591
+ });
25592
+ return { message: `Added "${catName}" to sync categories.` };
25593
+ }
25594
+ if (action === "remove") {
25595
+ const catName = catRest[0];
25596
+ if (!cfg.categories.includes(catName)) return { message: `"${catName}" not in sync categories.` };
25597
+ configStore.update({
25598
+ sync: { ...cfg, categories: cfg.categories.filter((c) => c !== catName) }
25599
+ });
25600
+ return { message: `Removed "${catName}" from sync categories.` };
25601
+ }
25602
+ return { message: "Usage: /sync categories list | add <name> | remove <name>" };
25603
+ }
25604
+ default:
25605
+ return {
25606
+ message: [
25607
+ "/sync \u2014 Cloud Sync",
25608
+ "",
25609
+ " /sync status Show current sync status",
25610
+ " /sync enable owner/repo TOKEN Enable for a repo (TOKEN = fine-grained PAT)",
25611
+ " /sync disable Disable sync (keeps local data)",
25612
+ " /sync push Upload selected categories",
25613
+ " /sync pull Download from repo",
25614
+ " /sync categories list|add|remove Manage synced categories",
25615
+ "",
25616
+ `Categories: ${ALL_SYNC_CATEGORIES.join(", ")}`
25617
+ ].join("\n")
25618
+ };
25619
+ }
25620
+ }
25621
+ };
25622
+ }
25623
+ function createGitPlugin() {
25624
+ return {
25625
+ name: "wstack-git",
25626
+ version: "1.0.0",
25627
+ description: "Git helpers: /commit (LLM message), /gitcheck, /push",
25628
+ apiVersion: "^0.1",
25629
+ capabilities: { slashCommands: true },
25630
+ defaultConfig: {},
25631
+ setup(api) {
25632
+ api.slashCommands.register(buildCommitCommand());
25633
+ api.slashCommands.register(buildGitcheckCommand());
25634
+ api.slashCommands.register(buildPushCommand());
25635
+ api.log.info("[git] loaded \u2014 /commit, /gitcheck, /push available");
25636
+ },
25637
+ teardown(api) {
25638
+ api.slashCommands.unregister("commit");
25639
+ api.slashCommands.unregister("gitcheck");
25640
+ api.slashCommands.unregister("push");
25641
+ api.log.info("[git] unloaded");
25642
+ },
25643
+ async health() {
25644
+ return { ok: true, message: "git helpers ready" };
25645
+ }
25646
+ };
25647
+ }
25648
+ async function runGit(args, cwd) {
25649
+ try {
25650
+ return await new Promise((resolve9, reject) => {
25651
+ const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
25652
+ let stdout = "";
25653
+ let stderr = "";
25654
+ child.stdout?.on("data", (d) => {
25655
+ stdout += d;
25656
+ });
25657
+ child.stderr?.on("data", (d) => {
25658
+ stderr += d;
25659
+ });
25660
+ child.on("error", (err) => {
25661
+ reject(
25662
+ new WrongStackError({
25663
+ message: `Failed to run git: ${err.message}`,
25664
+ code: ERROR_CODES.TOOL_EXECUTION_FAILED,
25665
+ subsystem: "tool",
25666
+ context: { command: "git", args, cwd },
25667
+ cause: err
25668
+ })
25669
+ );
25670
+ });
25671
+ child.on("close", (code) => resolve9({ stdout, stderr, code: code ?? 0 }));
25672
+ });
25673
+ } catch (err) {
25674
+ if (err instanceof WrongStackError) throw err;
25675
+ throw new WrongStackError({
25676
+ message: err instanceof Error ? err.message : String(err),
25677
+ code: ERROR_CODES.TOOL_EXECUTION_FAILED,
25678
+ subsystem: "tool",
25679
+ context: { command: "git", args, cwd },
25680
+ cause: err
25681
+ });
25682
+ }
25683
+ }
25684
+ async function isGitRepo(cwd) {
25685
+ const result = await runGit(["rev-parse", "--git-dir"], cwd);
25686
+ return result.code === 0;
25687
+ }
25688
+ async function hasUncommittedChanges(cwd) {
25689
+ const result = await runGit(["status", "--porcelain"], cwd);
25690
+ return result.stdout.trim().length > 0;
25691
+ }
25692
+ function asLLMProvider(provider) {
25693
+ if (provider && typeof provider.complete === "function") {
25694
+ return provider;
25695
+ }
25696
+ return null;
25697
+ }
25698
+ async function generateCommitMessageWithLLM(diff, provider, model) {
25699
+ const systemPrompt = "You are a helpful assistant that generates concise, conventional-commit-formatted git commit messages. Analyze the provided diff and output ONLY the commit message (no explanation, no quotes). Format: <type>(<scope>): <short description> \u2014 <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, ci, build, temp. If the diff contains multiple unrelated changes, pick the most important one. Keep the description under 72 characters. Example: feat(cli): add /commit LLM integration";
25700
+ try {
25701
+ const ac = new AbortController();
25702
+ const timeout = setTimeout(() => ac.abort(), 15e3);
25703
+ const resp = await provider.complete(
25704
+ {
25705
+ model,
25706
+ system: [{ type: "text", text: systemPrompt }],
25707
+ messages: [
25708
+ { role: "user", content: [{ type: "text", text: `Here is the git diff:
25709
+
25710
+ ${diff}` }] }
25711
+ ],
25712
+ maxTokens: 80,
25713
+ temperature: 0.3
25714
+ },
25715
+ { signal: ac.signal }
25716
+ );
25717
+ clearTimeout(timeout);
25718
+ const raw = resp.content;
25719
+ const text = Array.isArray(raw) ? raw[0]?.text ?? "" : typeof raw === "object" && raw !== null ? raw.text ?? "" : String(raw ?? "");
25720
+ const message = text.trim().split("\n")[0] ?? "";
25721
+ if (message.length > 0 && message.length < 200) return message;
25722
+ } catch {
25723
+ }
25724
+ return null;
25725
+ }
25726
+ function detectCommitType(stats) {
25727
+ const lines = stats.split("\n");
25728
+ const hasTestFiles = lines.some(
25729
+ (l) => l.includes("_test.") || l.includes(".test.") || l.includes(".spec.")
25730
+ );
25731
+ const hasDocs = lines.some(
25732
+ (l) => l.includes("README") || l.includes("CHANGELOG") || l.includes("docs/") || l.includes(".md")
25733
+ );
25734
+ const hasConfig = lines.some(
25735
+ (l) => l.includes("config") || l.includes("tsconfig") || l.includes(".json")
25736
+ );
25737
+ if (hasTestFiles) return "test";
25738
+ if (hasDocs) return "docs";
25739
+ if (hasConfig) return "chore";
25740
+ return "feat";
25741
+ }
25742
+ async function generateCommitMessageHeuristics(cwd) {
25743
+ const statsResult = await runGit(["diff", "--stat"], cwd);
25744
+ if (statsResult.code !== 0) return "chore: update";
25745
+ const nameResult = await runGit(["diff", "--name-only"], cwd);
25746
+ const files = nameResult.stdout.split("\n").filter(Boolean);
25747
+ const commitType = detectCommitType(statsResult.stdout);
25748
+ let scope = "";
25749
+ if (files.length > 0) {
25750
+ const primary = files[0].split("/")[0];
25751
+ if (primary && primary !== "packages" && primary !== "apps" && primary !== "node_modules") {
25752
+ scope = `(${primary})`;
25753
+ }
25754
+ }
25755
+ if (files.length === 0) return `${commitType}${scope}: update`;
25756
+ if (files.length <= 3) {
25757
+ const summary2 = files.map((f) => f.split("/").pop()).join(", ");
25758
+ return `${commitType}${scope}: ${summary2}`;
25759
+ }
25760
+ const summary = `${files.slice(0, 3).map((f) => f.split("/").pop()).join(", ")} and ${files.length - 3} more`;
25761
+ return `${commitType}${scope}: ${summary}`;
25762
+ }
25763
+ function buildCommitCommand() {
25764
+ return {
25765
+ name: "commit",
25766
+ description: "Stage all changes and commit with auto-generated message.",
25767
+ aliases: ["gc"],
25768
+ async run(args, ctx) {
25769
+ const cwd = ctx?.cwd ?? process.cwd();
25770
+ if (!await isGitRepo(cwd)) return { message: "Not a git repository." };
25771
+ if (!await hasUncommittedChanges(cwd)) {
25772
+ return { message: "Nothing to commit (working tree clean)." };
25773
+ }
25774
+ const dryRun = args.includes("--dry-run") || args.includes("-n");
25775
+ const noLlm = args.includes("--no-llm");
25776
+ let message = null;
25777
+ const provider = noLlm ? null : asLLMProvider(ctx?.provider);
25778
+ if (provider && ctx?.model) {
25779
+ const diff = (await runGit(["diff"], cwd)).stdout;
25780
+ message = await generateCommitMessageWithLLM(diff, provider, ctx.model);
25781
+ }
25782
+ if (!message) message = await generateCommitMessageHeuristics(cwd);
25783
+ if (dryRun) {
25784
+ return {
25785
+ message: `Would commit:
25786
+
25787
+ ${color.green(message)}
25788
+
25789
+ ${color.dim("(dry-run \u2014 no actual commit)")}`
25790
+ };
25791
+ }
25792
+ const stageResult = await runGit(["add", "."], cwd);
25793
+ if (stageResult.code !== 0) return { message: `Stage failed: ${stageResult.stderr}` };
25794
+ const commitResult = await runGit(["commit", "-m", message], cwd);
25795
+ if (commitResult.code !== 0) return { message: `Commit failed: ${commitResult.stderr}` };
25796
+ const hash = (await runGit(["rev-parse", "--short", "HEAD"], cwd)).stdout.trim();
25797
+ const hasRemote = (await runGit(["remote"], cwd)).stdout.trim().length > 0;
25798
+ const pushMsg = hasRemote ? `
25799
+
25800
+ ${color.dim("Tip: Run /push to push to remote")}` : "";
25801
+ return {
25802
+ message: `${color.green("\u2713")} Committed: ${color.bold(message)}
25803
+ ${color.dim(hash)}${pushMsg}`
25804
+ };
25805
+ }
25806
+ };
25807
+ }
25808
+ function buildGitcheckCommand() {
25809
+ return {
25810
+ name: "gitcheck",
25811
+ description: "Check for uncommitted changes (for system prompt integration).",
25812
+ aliases: ["gcstatus"],
25813
+ async run(_args, ctx) {
25814
+ const cwd = ctx?.cwd ?? process.cwd();
25815
+ if (!await isGitRepo(cwd)) return { message: "" };
25816
+ if (!await hasUncommittedChanges(cwd)) return { message: "" };
25817
+ const statusResult = await runGit(["status", "--porcelain"], cwd);
25818
+ const count = statusResult.stdout.split("\n").filter(Boolean).length;
25819
+ if (count === 0) return { message: "" };
25820
+ return {
25821
+ message: `\u26A0 ${color.yellow(`${count} uncommitted change${count > 1 ? "s" : ""}`)} \u2014 consider /commit`
25822
+ };
25823
+ }
25824
+ };
25825
+ }
25826
+ function buildPushCommand() {
25827
+ return {
25828
+ name: "push",
25829
+ description: "Push to remote after commit.",
25830
+ async run(args, ctx) {
25831
+ const cwd = ctx?.cwd ?? process.cwd();
25832
+ if (!await isGitRepo(cwd)) return { message: "Not a git repository." };
25833
+ const dryRun = args.includes("--dry-run") || args.includes("-n");
25834
+ const force = args.includes("--force") || args.includes("-f");
25835
+ const remotes = (await runGit(["remote"], cwd)).stdout.split("\n").filter(Boolean);
25836
+ if (remotes.length === 0) {
25837
+ return { message: "No remote configured. Add one with: git remote add origin <url>" };
25838
+ }
25839
+ if (dryRun) {
25840
+ return {
25841
+ message: `Would push to ${remotes.join(", ")}${force ? " (force)" : ""}
25842
+ ${color.dim("(dry-run)")}`
25843
+ };
25844
+ }
25845
+ const branch = (await runGit(["rev-parse", "--abbrev-ref", "HEAD"], cwd)).stdout.trim() || "main";
25846
+ const pushArgs = ["push"];
25847
+ if (force) pushArgs.push("--force");
25848
+ pushArgs.push(...remotes, branch);
25849
+ const pushResult = await runGit(pushArgs, cwd);
25850
+ if (pushResult.code !== 0) return { message: `Push failed: ${pushResult.stderr}` };
25851
+ return { message: `${color.green("\u2713")} Pushed to ${remotes.join(", ")} (${branch})` };
25852
+ }
25853
+ };
25854
+ }
25855
+
25856
+ // src/plugins/observability-plugin.ts
25857
+ function createObservabilityPlugin(opts) {
25858
+ return {
25859
+ name: "wstack-observability",
25860
+ version: "1.0.0",
25861
+ description: "Runtime metrics and health checks: /metrics, /health",
25862
+ apiVersion: "^0.1",
25863
+ capabilities: { slashCommands: true },
25864
+ defaultConfig: {},
25865
+ setup(api) {
25866
+ const rawConfig = api.config;
25867
+ const metricsSink = opts?.metricsSink ?? rawConfig.metricsSink;
25868
+ const healthRegistry = opts?.healthRegistry ?? rawConfig.healthRegistry;
25869
+ api.slashCommands.register(buildMetricsCommand(metricsSink));
25870
+ api.slashCommands.register(buildHealthCommand(healthRegistry));
25871
+ api.log.info("[observability] loaded \u2014 /metrics, /health available");
25872
+ },
25873
+ teardown(api) {
25874
+ api.slashCommands.unregister("metrics");
25875
+ api.slashCommands.unregister("health");
25876
+ api.log.info("[observability] unloaded");
25877
+ },
25878
+ async health() {
25879
+ return { ok: true, message: "observability ready" };
25880
+ }
25881
+ };
25882
+ }
25883
+ function statusIcon(status) {
25884
+ if (status === "healthy") return color.green("\u25CF");
25885
+ if (status === "degraded") return color.yellow("\u25CF");
25886
+ return color.red("\u25CF");
25887
+ }
25888
+ function buildMetricsCommand(metricsSink) {
25889
+ return {
25890
+ name: "metrics",
25891
+ description: "Show metrics snapshot (requires --metrics flag).",
25892
+ async run() {
25893
+ if (!metricsSink) {
25894
+ return { message: "Metrics not enabled. Restart with --metrics to collect." };
25895
+ }
25896
+ const snap = metricsSink.snapshot();
25897
+ if (snap.series.length === 0) return { message: "No metrics recorded yet." };
25898
+ const lines = [];
25899
+ const byName = /* @__PURE__ */ new Map();
25900
+ for (const s of snap.series) {
25901
+ const bucket = byName.get(s.name) ?? [];
25902
+ bucket.push(s);
25903
+ byName.set(s.name, bucket);
25904
+ }
25905
+ for (const [name, series] of [...byName.entries()].sort()) {
25906
+ lines.push(color.dim(`# ${name}`));
25907
+ for (const s of series) {
25908
+ const labels = Object.entries(s.labels).map(([k, v]) => `${k}=${v}`).join(" ");
25909
+ const labelStr = labels ? color.dim(` {${labels}}`) : "";
25910
+ if (s.type === "histogram") {
25911
+ lines.push(
25912
+ ` count=${s.values.count} sum=${s.values.sum} min=${s.values.min} max=${s.values.max} p50=${s.values.p50} p95=${s.values.p95} p99=${s.values.p99}${labelStr}`
25913
+ );
25914
+ } else {
25915
+ lines.push(` ${s.values.value}${labelStr}`);
25916
+ }
25917
+ }
25918
+ }
25919
+ return { message: lines.join("\n") };
25920
+ }
25921
+ };
25922
+ }
25923
+ function buildHealthCommand(healthRegistry) {
25924
+ return {
25925
+ name: "health",
25926
+ description: "Run health checks (requires --metrics flag).",
25927
+ async run() {
25928
+ if (!healthRegistry) {
25929
+ return { message: "Health checks not enabled. Restart with --metrics." };
25930
+ }
25931
+ const result = await healthRegistry.run();
25932
+ const lines = [
25933
+ `${statusIcon(result.status)} overall: ${result.status}`,
25934
+ ...result.checks.map((c) => {
25935
+ const detail = c.detail ? color.dim(` \u2014 ${c.detail}`) : "";
25936
+ return ` ${statusIcon(c.status)} ${c.name}: ${c.status}${detail}`;
25937
+ })
25938
+ ];
25939
+ return { message: lines.join("\n") };
25940
+ }
25941
+ };
25942
+ }
25943
+
25944
+ // src/plugins/security-plugin.ts
25945
+ function createSecurityPlugin() {
25946
+ return {
25947
+ name: "wstack-security",
25948
+ version: "1.0.0",
25949
+ description: "Security scanning: /security scan | audit | report",
25950
+ apiVersion: "^0.1",
25951
+ capabilities: { slashCommands: true },
25952
+ defaultConfig: {},
25953
+ setup(api) {
25954
+ api.slashCommands.register(createSecuritySlashCommand());
25955
+ api.log.info("[security] loaded \u2014 /security available");
25956
+ },
25957
+ teardown(api) {
25958
+ api.slashCommands.unregister("security");
25959
+ api.log.info("[security] unloaded");
25960
+ },
25961
+ async health() {
25962
+ return { ok: true, message: "security scanner ready" };
25963
+ }
25964
+ };
25965
+ }
25966
+ function createSkillsPlugin(opts) {
25967
+ return {
25968
+ name: "wstack-skills",
25969
+ version: "1.0.0",
25970
+ description: "Skill library and GitHub installer: /skill, /skill-gen, /skill-install, ...",
25971
+ apiVersion: "^0.1",
25972
+ capabilities: { slashCommands: true },
25973
+ defaultConfig: {},
25974
+ setup(api) {
25975
+ const rawConfig = api.config;
25976
+ const skillLoader = opts?.skillLoader ?? rawConfig.skillLoader;
25977
+ api.slashCommands.register(buildSkillCommand(skillLoader));
25978
+ api.slashCommands.register(buildSkillGeneratorCommand(skillLoader));
25979
+ api.slashCommands.register(buildSkillInstallCommand(skillLoader));
25980
+ api.slashCommands.register(buildSkillUpdateCommand(skillLoader));
25981
+ api.slashCommands.register(buildSkillUninstallCommand(skillLoader));
25982
+ api.log.info("[skills] loaded \u2014 /skill, /skill-gen, /skill-install/update/uninstall available");
25983
+ },
25984
+ teardown(api) {
25985
+ for (const name of ["skill", "skill-gen", "skill-install", "skill-update", "skill-uninstall"]) {
25986
+ api.slashCommands.unregister(name);
25987
+ }
25988
+ api.log.info("[skills] unloaded");
25989
+ },
25990
+ async health() {
25991
+ return { ok: true, message: "skills ready" };
25992
+ }
25993
+ };
25994
+ }
25995
+ function makeInstaller(skillLoader, projectRoot) {
25996
+ const globalRoot = path6.join(os5.homedir(), ".wrongstack");
25997
+ return new SkillInstaller({
25998
+ manifestPath: path6.join(globalRoot, "installed-skills.json"),
25999
+ projectSkillsDir: path6.join(projectRoot, ".wrongstack", "skills"),
26000
+ globalSkillsDir: path6.join(globalRoot, "skills"),
26001
+ projectHash: projectHash(projectRoot),
26002
+ skillLoader
26003
+ });
26004
+ }
26005
+ function buildSkillCommand(skillLoader) {
26006
+ return {
26007
+ name: "skill",
26008
+ description: "Show skill details or list available skills. Use /skill-gen to create new skills.",
26009
+ async run(args) {
26010
+ if (!skillLoader) return { message: "No skill loader configured." };
26011
+ if (!args.trim()) {
26012
+ const entries = await skillLoader.listEntries();
26013
+ if (entries.length === 0) return { message: "No skills found." };
26014
+ const lines = entries.map((e) => {
26015
+ const scopeTag = e.scope.length > 0 ? ` ${color.dim(`(${e.scope.slice(0, 3).join(", ")})`)}` : "";
26016
+ return ` ${color.bold(e.name)}${scopeTag}
26017
+ Use when: ${e.trigger}`;
26018
+ });
26019
+ return { message: `Available skills:
26020
+ ${lines.join("\n\n")}
26021
+ ` };
26022
+ }
26023
+ const skill = await skillLoader.find(args.trim());
26024
+ if (!skill) return { message: `Skill "${args.trim()}" not found.` };
26025
+ return { message: await skillLoader.readBody(skill.name) };
26026
+ }
26027
+ };
26028
+ }
26029
+ function buildSkillGeneratorCommand(skillLoader) {
26030
+ return {
26031
+ name: "skill-gen",
26032
+ description: "Create a new AI skill interactively. The AI will guide you.",
26033
+ help: [
26034
+ "\u2554\u2550\u2550\u2550 Skill Generator \u2550\u2550\u2550\u2557",
26035
+ "",
26036
+ "Create new AI skills with AI guidance.",
26037
+ "",
26038
+ "Usage:",
26039
+ " /skill-gen Start skill creation",
26040
+ " /skill-gen list List existing skills",
26041
+ " /skill-gen edit <name> View an existing skill",
26042
+ "",
26043
+ "The AI will ask you questions and create the skill file.",
26044
+ "Skills are saved to .wrongstack/skills/<name>/SKILL.md"
26045
+ ].join("\n"),
26046
+ async run(args) {
26047
+ const trimmed = args.trim();
26048
+ if (trimmed === "list" || trimmed === "ls") {
26049
+ if (!skillLoader) return { message: "No skill loader configured." };
26050
+ const entries = await skillLoader.listEntries();
26051
+ if (entries.length === 0) return { message: "No skills found." };
26052
+ const lines = entries.map((e) => {
26053
+ const src = e.source === "project" ? "\u{1F4C1}" : e.source === "user" ? "\u{1F464}" : "\u{1F4E6}";
26054
+ return ` ${src} ${e.name}
26055
+ ${e.trigger}`;
26056
+ });
26057
+ return { message: `Available Skills:
26058
+ ${lines.join("\n\n")}
26059
+ ` };
26060
+ }
26061
+ if (trimmed.startsWith("edit ")) {
26062
+ const skillName = trimmed.slice(5).trim();
26063
+ if (!skillLoader) return { message: "No skill loader configured." };
26064
+ const skill = await skillLoader.find(skillName);
26065
+ if (!skill) return { message: `Skill "${skillName}" not found.` };
26066
+ const body = await skillLoader.readBody(skillName);
26067
+ return { message: [`Skill: ${skillName}`, `Path: ${skill.path}`, "", body].join("\n") };
26068
+ }
26069
+ return {
26070
+ 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.",
26071
+ 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."
26072
+ };
26073
+ }
26074
+ };
26075
+ }
26076
+ function buildSkillInstallCommand(skillLoader) {
26077
+ return {
26078
+ name: "skill-install",
26079
+ description: "Install skills from a GitHub repository.",
26080
+ argsHint: "<user/repo[@ref]> [--global]",
26081
+ help: [
26082
+ "\u2554\u2550\u2550\u2550 Skill Install \u2550\u2550\u2550\u2557",
26083
+ "",
26084
+ "Install skills from a GitHub repository.",
26085
+ "",
26086
+ "Usage:",
26087
+ " /skill-install <user/repo> Install from default branch (main)",
26088
+ " /skill-install <user/repo@ref> Install specific tag/branch/commit",
26089
+ " /skill-install <user/repo> --global Install to user-global skills",
26090
+ "",
26091
+ "Supports both single-skill repos (SKILL.md at root)",
26092
+ "and multi-skill repos (skills/ subdirectory).",
26093
+ "",
26094
+ "Examples:",
26095
+ " /skill-install wrongstack/awesome-skills",
26096
+ " /skill-install wrongstack/skills@v1.0",
26097
+ " /skill-install user/my-skills --global"
26098
+ ].join("\n"),
26099
+ async run(args, ctx) {
26100
+ const parts = args.trim().split(/\s+/);
26101
+ const ref = parts.find((p) => !p.startsWith("--"));
26102
+ const isGlobal = parts.includes("--global");
26103
+ if (!ref) return { message: "Usage: /skill-install <user/repo[@ref]> [--global]" };
26104
+ const installer = makeInstaller(skillLoader, ctx.projectRoot);
26105
+ try {
26106
+ const results = await installer.install(ref, { global: isGlobal });
26107
+ if (results.length === 0) return { message: "No skills found in the repository." };
26108
+ const scope = isGlobal ? "user-global" : "project";
26109
+ const lines = [`Installed ${results.length} skill(s) [${scope}]:`];
26110
+ for (const r of results) {
26111
+ lines.push(` \u2713 ${r.name} (${r.source}@${r.ref})`);
26112
+ lines.push(` \u2192 ${r.path}`);
26113
+ }
26114
+ return { message: lines.join("\n") };
26115
+ } catch (err) {
26116
+ const msg = err instanceof Error ? err.message : String(err);
26117
+ return { message: `\u2717 Install failed: ${msg}` };
26118
+ }
26119
+ }
26120
+ };
26121
+ }
26122
+ function buildSkillUpdateCommand(skillLoader) {
26123
+ return {
26124
+ name: "skill-update",
26125
+ description: "Update installed skills from their GitHub source.",
26126
+ argsHint: "[name|ref] [--global]",
26127
+ help: [
26128
+ "\u2554\u2550\u2550\u2550 Skill Update \u2550\u2550\u2550\u2557",
26129
+ "",
26130
+ "Update installed skills from their GitHub source.",
26131
+ "",
26132
+ "Usage:",
26133
+ " /skill-update Update all installed skills",
26134
+ " /skill-update <name> Update a specific skill",
26135
+ " /skill-update <user/repo@ref> Update to a different ref",
26136
+ " /skill-update <name> --global Update a global skill"
26137
+ ].join("\n"),
26138
+ async run(args, ctx) {
26139
+ const parts = args.trim().split(/\s+/);
26140
+ const nameOrRef = parts.find((p) => !p.startsWith("--"));
26141
+ const isGlobal = parts.includes("--global");
26142
+ const installer = makeInstaller(skillLoader, ctx.projectRoot);
26143
+ try {
26144
+ const result = await installer.update(nameOrRef, { global: isGlobal });
26145
+ const lines = [];
26146
+ if (result.updated.length > 0) {
26147
+ lines.push(`Updated ${result.updated.length} skill(s):`);
26148
+ for (const u of result.updated) {
26149
+ lines.push(
26150
+ u.oldRef !== u.newRef ? ` \u2713 ${u.name} (${u.oldRef} \u2192 ${u.newRef})` : ` \u2713 ${u.name} (refreshed)`
26151
+ );
26152
+ }
26153
+ }
26154
+ if (result.unchanged.length > 0) lines.push(`Up to date: ${result.unchanged.join(", ")}`);
26155
+ if (result.errors.length > 0) {
26156
+ for (const e of result.errors) lines.push(` \u2717 ${e.name}: ${e.error}`);
26157
+ }
26158
+ if (lines.length === 0) return { message: "No installed skills to update." };
26159
+ return { message: lines.join("\n") };
26160
+ } catch (err) {
26161
+ const msg = err instanceof Error ? err.message : String(err);
26162
+ return { message: `\u2717 Update failed: ${msg}` };
26163
+ }
26164
+ }
26165
+ };
26166
+ }
26167
+ function buildSkillUninstallCommand(skillLoader) {
26168
+ return {
26169
+ name: "skill-uninstall",
26170
+ description: "Remove an installed skill.",
26171
+ argsHint: "<name> [--global]",
26172
+ help: [
26173
+ "\u2554\u2550\u2550\u2550 Skill Uninstall \u2550\u2550\u2550\u2557",
26174
+ "",
26175
+ "Remove an installed skill and its files.",
26176
+ "",
26177
+ "Usage:",
26178
+ " /skill-uninstall <name> Remove from project skills",
26179
+ " /skill-uninstall <name> --global Remove from user-global skills"
26180
+ ].join("\n"),
26181
+ async run(args, ctx) {
26182
+ const parts = args.trim().split(/\s+/);
26183
+ const name = parts.find((p) => !p.startsWith("--"));
26184
+ const isGlobal = parts.includes("--global");
26185
+ if (!name) {
26186
+ const installer2 = makeInstaller(skillLoader, ctx.projectRoot);
26187
+ const installed = await installer2.listInstalled();
26188
+ if (installed.length === 0) return { message: "No installed skills found." };
26189
+ const scope = isGlobal ? "user" : "project";
26190
+ const filtered = installed.filter((s) => s.scope === scope);
26191
+ if (filtered.length === 0) {
26192
+ return { message: `No installed skills found (${scope} scope).` };
26193
+ }
26194
+ const lines = [`Installed skills (${scope}):`];
26195
+ for (const s of filtered) {
26196
+ lines.push(` ${s.name} ${s.source}@${s.ref} (${s.installedAt.slice(0, 10)})`);
26197
+ }
26198
+ lines.push("", "Use /skill-uninstall <name> to remove.");
26199
+ return { message: lines.join("\n") };
26200
+ }
26201
+ const installer = makeInstaller(skillLoader, ctx.projectRoot);
26202
+ try {
26203
+ await installer.uninstall(name, { global: isGlobal });
26204
+ return { message: `\u2713 Skill "${name}" uninstalled.` };
26205
+ } catch (err) {
26206
+ const msg = err instanceof Error ? err.message : String(err);
26207
+ return { message: `\u2717 Uninstall failed: ${msg}` };
26208
+ }
26209
+ }
26210
+ };
26211
+ }
26212
+
26213
+ // src/plugins/plan-plugin.ts
26214
+ function createPlanPlugin(opts) {
26215
+ return {
26216
+ name: "wstack-plan",
26217
+ version: "1.0.0",
26218
+ description: "Strategic plan board: /plan show | add | start | done | promote | template | clear",
26219
+ apiVersion: "^0.1",
26220
+ capabilities: { slashCommands: true },
26221
+ defaultConfig: {},
26222
+ setup(api) {
26223
+ const rawConfig = api.config;
26224
+ const paths = opts?.paths ?? rawConfig.paths;
26225
+ api.slashCommands.register(buildPlanCommand(paths?.projectPlan));
26226
+ api.log.info("[plan] loaded \u2014 /plan available");
26227
+ },
26228
+ teardown(api) {
26229
+ api.slashCommands.unregister("plan");
26230
+ api.log.info("[plan] unloaded");
26231
+ },
26232
+ async health() {
26233
+ return { ok: true, message: "plan board ready" };
26234
+ }
26235
+ };
26236
+ }
26237
+ function buildPlanCommand(planPath) {
26238
+ return {
26239
+ name: "plan",
26240
+ description: "Strategic plan board: /plan [show|add <title>|start <id|#>|done <id|#>|remove <id|#>|promote <id|#> [subtask ...]|derive <id|#>|template [list|use <name>]|clear]",
26241
+ async run(args, ctx) {
26242
+ if (!planPath) return { message: "Plan storage is not configured for this session." };
26243
+ const sessionId = ctx?.session?.id ?? "unknown";
26244
+ const [verb, ...rest] = args.trim().split(/\s+/);
26245
+ const restJoined = rest.join(" ").trim();
26246
+ const plan = await loadPlan(planPath) ?? emptyPlan(sessionId);
26247
+ switch (verb) {
26248
+ case "":
26249
+ case "show":
26250
+ case "list":
26251
+ return { message: formatPlan(plan) };
26252
+ case "add": {
26253
+ if (!restJoined) return { message: "Usage: /plan add <title>" };
26254
+ const { plan: updated, item } = addPlanItem(plan, restJoined);
26255
+ await savePlan(planPath, updated);
26256
+ return { message: `Added: ${item.title}
26257
+ ${formatPlan(updated)}` };
26258
+ }
26259
+ case "start":
26260
+ case "progress": {
26261
+ if (!restJoined) return { message: "Usage: /plan start <id|index>" };
26262
+ const updated = setPlanItemStatus(plan, restJoined, "in_progress");
26263
+ await savePlan(planPath, updated);
26264
+ return { message: formatPlan(updated) };
26265
+ }
26266
+ case "done":
26267
+ case "complete": {
26268
+ if (!restJoined) return { message: "Usage: /plan done <id|index>" };
26269
+ const updated = setPlanItemStatus(plan, restJoined, "done");
26270
+ await savePlan(planPath, updated);
26271
+ return { message: formatPlan(updated) };
26272
+ }
26273
+ case "remove":
26274
+ case "delete":
26275
+ case "rm": {
26276
+ if (!restJoined) return { message: "Usage: /plan remove <id|index>" };
26277
+ const updated = removePlanItem(plan, restJoined);
26278
+ await savePlan(planPath, updated);
26279
+ return { message: formatPlan(updated) };
26280
+ }
26281
+ case "promote": {
26282
+ if (!restJoined) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
26283
+ const [target, ...subtasks] = restJoined.split(/\s+/);
26284
+ if (!target) return { message: "Usage: /plan promote <id|index> [subtask ...]" };
26285
+ const derived = deriveTodosFromPlanItem(plan, target, subtasks.length > 0 ? subtasks : void 0);
26286
+ if (!derived) return { message: `No plan item matched "${target}".` };
26287
+ await savePlan(planPath, derived.plan);
26288
+ ctx?.state?.replaceTodos(derived.todos);
26289
+ return {
26290
+ message: `Promoted to ${derived.todos.length} todo(s):
26291
+ ${formatTodosList(derived.todos)}
26292
+
26293
+ ${formatPlan(derived.plan)}`
26294
+ };
26295
+ }
26296
+ case "derive": {
26297
+ if (!restJoined) return { message: "Usage: /plan derive <id|index>" };
26298
+ const derived = deriveTodosFromPlanItem(plan, restJoined);
26299
+ if (!derived) return { message: `No plan item matched "${restJoined}".` };
26300
+ await savePlan(planPath, derived.plan);
26301
+ ctx?.state?.replaceTodos(derived.todos);
26302
+ return {
26303
+ message: `Derived ${derived.todos.length} todo(s):
26304
+ ${formatTodosList(derived.todos)}
26305
+
26306
+ ${formatPlan(derived.plan)}`
26307
+ };
26308
+ }
26309
+ case "template": {
26310
+ const subVerb = rest[0] ?? "";
26311
+ const subRest = rest.slice(1).join(" ").trim();
26312
+ if (subVerb === "" || subVerb === "list") return { message: formatPlanTemplates() };
26313
+ if (subVerb === "use") {
26314
+ if (!subRest) return { message: "Usage: /plan template use <template-name>" };
26315
+ const template = getPlanTemplate(subRest);
26316
+ if (!template) {
26317
+ return {
26318
+ message: `Unknown template "${subRest}". Use /plan template list to see available templates.`
26319
+ };
26320
+ }
26321
+ let updated = plan;
26322
+ for (const item of template.items) {
26323
+ ({ plan: updated } = addPlanItem(updated, item.title, item.details));
26324
+ }
26325
+ await savePlan(planPath, updated);
26326
+ return {
26327
+ message: `Applied template "${template.name}" (${template.items.length} items):
26328
+ ${formatPlan(updated)}`
26329
+ };
26330
+ }
26331
+ return { message: `Unknown template subcommand "${subVerb}". Try: list | use <name>` };
26332
+ }
26333
+ case "clear": {
26334
+ const updated = clearPlan(plan);
26335
+ await savePlan(planPath, updated);
26336
+ return { message: "Plan cleared." };
26337
+ }
26338
+ default:
26339
+ return {
26340
+ message: `Unknown subcommand "${verb}". Try: show | add <title> | start <id|#> | done <id|#> | remove <id|#> | promote <id|#> | derive <id|#> | template [list|use <name>] | clear`
26341
+ };
26342
+ }
26343
+ }
26344
+ };
26345
+ }
26346
+
26347
+ export { AGENTS_BY_PHASE, AGENT_CATALOG, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, CheckpointManager, CloudSync, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorStateCheckpoint, DoneConditionChecker, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetManager, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, MAX_JOURNAL_ENTRIES, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyRosterBudget, asBlocks, asText, assertSafePath, atomicWrite, attachAutoExtend, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildBtwBlock, buildChildEnv, buildGoalPreamble, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, consumeBtwNotes, context7Server, contextManagerTool, createAutoExecutor, createAutoPhaseFromTaskGraph, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createMcpControlTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSkillsPlugin, createSyncPlugin, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, dispatchAgent, downloadGitHubTarball, emptyGoal, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatGoal, formatPlan, formatPlanTemplates, formatTodosList, getAgentDefinition, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, goalFilePath, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeAutonomyPromptContributor, makeContinueToNextIterationTool, makeDirectorSessionFactory, makeLLMClassifier, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseContinueDirective, parseSkillRef, pendingBtwCount, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, saveGoal, savePlan, saveTodosCheckpoint, scoreAgents, securitySlashCommand, sentinelServer, setBtwNote, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, summarizeUsage, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
24901
26348
  //# sourceMappingURL=index.js.map
24902
26349
  //# sourceMappingURL=index.js.map