@jaggerxtrm/specialists 3.3.4 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/README.md +4 -2
  2. package/bin/install.js +15 -204
  3. package/config/skills/using-specialists/SKILL.md +1 -1
  4. package/config/specialists/debugger.specialist.yaml +111 -0
  5. package/config/specialists/explorer.specialist.yaml +1 -1
  6. package/dist/index.js +791 -566
  7. package/package.json +1 -1
  8. package/config/skills/specialists-usage-workspace/iteration-1/eval-bead-background/old_skill/outputs/result.md +0 -105
  9. package/config/skills/specialists-usage-workspace/iteration-1/eval-bead-background/with_skill/outputs/result.md +0 -93
  10. package/config/skills/specialists-usage-workspace/iteration-1/eval-fresh-setup/old_skill/outputs/result.md +0 -113
  11. package/config/skills/specialists-usage-workspace/iteration-1/eval-fresh-setup/with_skill/outputs/result.md +0 -131
  12. package/config/skills/specialists-usage-workspace/iteration-1/eval-yaml-debug/old_skill/outputs/result.md +0 -159
  13. package/config/skills/specialists-usage-workspace/iteration-1/eval-yaml-debug/with_skill/outputs/result.md +0 -150
  14. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/with_skill/outputs/result.md +0 -180
  15. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/with_skill/timing.json +0 -5
  16. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/without_skill/outputs/result.md +0 -223
  17. package/config/skills/specialists-usage-workspace/iteration-2/eval-bug-investigation/without_skill/timing.json +0 -5
  18. package/config/skills/specialists-usage-workspace/iteration-2/eval-code-review/with_skill/timing.json +0 -5
  19. package/config/skills/specialists-usage-workspace/iteration-2/eval-code-review/without_skill/outputs/result.md +0 -146
  20. package/config/skills/specialists-usage-workspace/iteration-2/eval-code-review/without_skill/timing.json +0 -5
  21. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/with_skill/outputs/result.md +0 -89
  22. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/with_skill/timing.json +0 -5
  23. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/without_skill/outputs/result.md +0 -96
  24. package/config/skills/specialists-usage-workspace/iteration-2/eval-test-coverage/without_skill/timing.json +0 -5
  25. package/config/skills/specialists-usage-workspace/skill-snapshot/SKILL.md.old +0 -237
  26. package/config/specialists/bug-hunt.specialist.yaml +0 -96
package/dist/index.js CHANGED
@@ -17410,7 +17410,98 @@ var init_dist = __esm(() => {
17410
17410
  });
17411
17411
 
17412
17412
  // src/specialist/schema.ts
17413
+ function formatPath(path) {
17414
+ return path.map((p) => typeof p === "number" ? `[${p}]` : p).join(".");
17415
+ }
17416
+ function getFriendlyMessage(issue2) {
17417
+ const path = formatPath(issue2.path);
17418
+ if (issue2.code === "invalid_string" && issue2.validation === "regex") {
17419
+ if (path.includes("name")) {
17420
+ return `Invalid specialist name: must be kebab-case (lowercase letters, numbers, hyphens). Got: "${issue2.path.at(-1) === "name" ? "invalid value" : "see schema"}"`;
17421
+ }
17422
+ if (path.includes("version")) {
17423
+ return `Invalid version: must be semver format (e.g., "1.0.0"). Got value that doesn't match pattern.`;
17424
+ }
17425
+ }
17426
+ if (issue2.code === "invalid_enum_value") {
17427
+ const allowed = issue2.options.map((o) => `"${o}"`).join(", ");
17428
+ if (path.includes("permission_required")) {
17429
+ return `Invalid permission_required: must be one of ${allowed}. This controls which pi tools are available.`;
17430
+ }
17431
+ if (path.includes("mode")) {
17432
+ return `Invalid execution.mode: must be one of ${allowed}.`;
17433
+ }
17434
+ if (path.includes("beads_integration")) {
17435
+ return `Invalid beads_integration: must be one of ${allowed}.`;
17436
+ }
17437
+ return `Invalid value at "${path}": expected one of ${allowed}, got "${issue2.received}"`;
17438
+ }
17439
+ if (issue2.code === "invalid_type") {
17440
+ return `Invalid type at "${path}": expected ${issue2.expected}, got ${issue2.received}`;
17441
+ }
17442
+ if (issue2.code === "invalid_literal") {
17443
+ return `Invalid value at "${path}": expected "${issue2.expected}"`;
17444
+ }
17445
+ if (issue2.code === "missing_key") {
17446
+ return `Missing required field: "${formatPath(issue2.path)}"`;
17447
+ }
17448
+ return issue2.message;
17449
+ }
17450
+ async function validateSpecialist(yamlContent) {
17451
+ const errors5 = [];
17452
+ const warnings = [];
17453
+ let raw;
17454
+ try {
17455
+ raw = $parse(yamlContent);
17456
+ } catch (e) {
17457
+ const msg = e instanceof Error ? e.message : String(e);
17458
+ errors5.push({
17459
+ path: "yaml",
17460
+ message: `YAML parse error: ${msg}`,
17461
+ code: "yaml_parse_error"
17462
+ });
17463
+ return { valid: false, errors: errors5, warnings };
17464
+ }
17465
+ const result = SpecialistSchema.safeParse(raw);
17466
+ if (!result.success) {
17467
+ for (const issue2 of result.error.issues) {
17468
+ errors5.push({
17469
+ path: formatPath(issue2.path),
17470
+ message: getFriendlyMessage(issue2),
17471
+ code: issue2.code
17472
+ });
17473
+ }
17474
+ } else {
17475
+ const spec = result.data;
17476
+ if (spec.specialist.prompt.normalize_template) {
17477
+ warnings.push("prompt.normalize_template is deprecated (Mercury compat) and will be ignored");
17478
+ }
17479
+ if (spec.specialist.execution.preferred_profile) {
17480
+ warnings.push("execution.preferred_profile is deprecated (Agent Forge compat) and will be ignored");
17481
+ }
17482
+ if (spec.specialist.execution.approval_mode) {
17483
+ warnings.push("execution.approval_mode is deprecated (Agent Forge compat) and will be ignored");
17484
+ }
17485
+ if (!spec.specialist.execution.model.includes("/")) {
17486
+ warnings.push(`Model "${spec.specialist.execution.model}" doesn't include a provider prefix. Expected format: "provider/model-id" (e.g., "anthropic/claude-sonnet-4-5")`);
17487
+ }
17488
+ }
17489
+ return { valid: errors5.length === 0, errors: errors5, warnings };
17490
+ }
17413
17491
  async function parseSpecialist(yamlContent) {
17492
+ const result = await validateSpecialist(yamlContent);
17493
+ if (!result.valid) {
17494
+ const errorList = result.errors.map((e) => ` • ${e.message}`).join(`
17495
+ `);
17496
+ throw new Error(`Schema validation failed:
17497
+ ${errorList}`);
17498
+ }
17499
+ if (result.warnings.length > 0) {
17500
+ process.stderr.write(`[specialists] warnings:
17501
+ ${result.warnings.map((w) => ` ⚠ ${w}`).join(`
17502
+ `)}
17503
+ `);
17504
+ }
17414
17505
  const raw = $parse(yamlContent);
17415
17506
  return SpecialistSchema.parseAsync(raw);
17416
17507
  }
@@ -17767,13 +17858,36 @@ class PiAgentSession {
17767
17858
  handler?.(event);
17768
17859
  return;
17769
17860
  }
17770
- if (type === "message_start" && event.message?.role === "assistant") {
17771
- const { provider, model } = event.message ?? {};
17772
- if (provider || model) {
17773
- this.options.onMeta?.({ backend: provider ?? "", model: model ?? "" });
17861
+ if (type === "message_start") {
17862
+ const role = event.message?.role;
17863
+ if (role === "assistant") {
17864
+ this.options.onEvent?.("message_start_assistant");
17865
+ const { provider, model } = event.message ?? {};
17866
+ if (provider || model) {
17867
+ this.options.onMeta?.({ backend: provider ?? "", model: model ?? "" });
17868
+ }
17869
+ } else if (role === "toolResult") {
17870
+ this.options.onEvent?.("message_start_tool_result");
17871
+ }
17872
+ return;
17873
+ }
17874
+ if (type === "message_end") {
17875
+ const role = event.message?.role;
17876
+ if (role === "assistant") {
17877
+ this.options.onEvent?.("message_end_assistant");
17878
+ } else if (role === "toolResult") {
17879
+ this.options.onEvent?.("message_end_tool_result");
17774
17880
  }
17775
17881
  return;
17776
17882
  }
17883
+ if (type === "turn_start") {
17884
+ this.options.onEvent?.("turn_start");
17885
+ return;
17886
+ }
17887
+ if (type === "turn_end") {
17888
+ this.options.onEvent?.("turn_end");
17889
+ return;
17890
+ }
17777
17891
  if (type === "agent_end") {
17778
17892
  const messages = event.messages ?? [];
17779
17893
  const last = [...messages].reverse().find((m) => m.role === "assistant");
@@ -17786,11 +17900,11 @@ class PiAgentSession {
17786
17900
  return;
17787
17901
  }
17788
17902
  if (type === "tool_execution_start") {
17789
- this.options.onEvent?.("tool_execution");
17903
+ this.options.onEvent?.("tool_execution_start");
17790
17904
  return;
17791
17905
  }
17792
17906
  if (type === "tool_execution_update") {
17793
- this.options.onEvent?.("tool_execution");
17907
+ this.options.onEvent?.("tool_execution_update");
17794
17908
  return;
17795
17909
  }
17796
17910
  if (type === "tool_execution_end") {
@@ -18475,6 +18589,23 @@ function mapCallbackEventToTimelineEvent(callbackEvent, context) {
18475
18589
  phase: "start",
18476
18590
  tool_call_id: context.toolCallId
18477
18591
  };
18592
+ case "tool_execution_start":
18593
+ return {
18594
+ t,
18595
+ type: TIMELINE_EVENT_TYPES.TOOL,
18596
+ tool: context.tool ?? "unknown",
18597
+ phase: "start",
18598
+ tool_call_id: context.toolCallId
18599
+ };
18600
+ case "tool_execution_update":
18601
+ case "tool_execution":
18602
+ return {
18603
+ t,
18604
+ type: TIMELINE_EVENT_TYPES.TOOL,
18605
+ tool: context.tool ?? "unknown",
18606
+ phase: "update",
18607
+ tool_call_id: context.toolCallId
18608
+ };
18478
18609
  case "tool_execution_end":
18479
18610
  return {
18480
18611
  t,
@@ -18484,6 +18615,18 @@ function mapCallbackEventToTimelineEvent(callbackEvent, context) {
18484
18615
  tool_call_id: context.toolCallId,
18485
18616
  is_error: context.isError
18486
18617
  };
18618
+ case "message_start_assistant":
18619
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "start", role: "assistant" };
18620
+ case "message_end_assistant":
18621
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "end", role: "assistant" };
18622
+ case "message_start_tool_result":
18623
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "start", role: "toolResult" };
18624
+ case "message_end_tool_result":
18625
+ return { t, type: TIMELINE_EVENT_TYPES.MESSAGE, phase: "end", role: "toolResult" };
18626
+ case "turn_start":
18627
+ return { t, type: TIMELINE_EVENT_TYPES.TURN, phase: "start" };
18628
+ case "turn_end":
18629
+ return { t, type: TIMELINE_EVENT_TYPES.TURN, phase: "end" };
18487
18630
  case "text":
18488
18631
  return { t, type: TIMELINE_EVENT_TYPES.TEXT };
18489
18632
  case "agent_end":
@@ -18564,6 +18707,8 @@ var init_timeline_events = __esm(() => {
18564
18707
  THINKING: "thinking",
18565
18708
  TOOL: "tool",
18566
18709
  TEXT: "text",
18710
+ MESSAGE: "message",
18711
+ TURN: "turn",
18567
18712
  RUN_COMPLETE: "run_complete",
18568
18713
  DONE: "done",
18569
18714
  AGENT_END: "agent_end"
@@ -18659,6 +18804,7 @@ class Supervisor {
18659
18804
  return jobs.sort((a, b) => b.started_at_ms - a.started_at_ms);
18660
18805
  }
18661
18806
  writeStatusFile(id, data) {
18807
+ mkdirSync(this.jobDir(id), { recursive: true });
18662
18808
  const path = this.statusPath(id);
18663
18809
  const tmp = path + ".tmp";
18664
18810
  writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
@@ -18726,6 +18872,14 @@ class Supervisor {
18726
18872
  pid: process.pid
18727
18873
  };
18728
18874
  this.writeStatusFile(id, initialStatus);
18875
+ writeFileSync(join3(this.opts.jobsDir, "latest"), `${id}
18876
+ `, "utf-8");
18877
+ this.opts.onJobStarted?.({ id });
18878
+ let statusSnapshot = initialStatus;
18879
+ const setStatus = (updates) => {
18880
+ statusSnapshot = { ...statusSnapshot, ...updates };
18881
+ this.writeStatusFile(id, statusSnapshot);
18882
+ };
18729
18883
  const eventsFd = openSync(this.eventsPath(id), "a");
18730
18884
  const appendTimelineEvent = (event) => {
18731
18885
  try {
@@ -18741,7 +18895,7 @@ class Supervisor {
18741
18895
  if (needsFifo) {
18742
18896
  try {
18743
18897
  execFileSync("mkfifo", [fifoPath]);
18744
- this.updateStatus(id, { fifo_path: fifoPath });
18898
+ setStatus({ fifo_path: fifoPath });
18745
18899
  } catch {}
18746
18900
  }
18747
18901
  let textLogged = false;
@@ -18760,12 +18914,12 @@ class Supervisor {
18760
18914
  const toolMatch = delta.match(/⚙ (.+?)…/);
18761
18915
  if (toolMatch) {
18762
18916
  currentTool = toolMatch[1];
18763
- this.updateStatus(id, { current_tool: currentTool });
18917
+ setStatus({ current_tool: currentTool });
18764
18918
  }
18765
18919
  this.opts.onProgress?.(delta);
18766
18920
  }, (eventType) => {
18767
18921
  const now = Date.now();
18768
- this.updateStatus(id, {
18922
+ setStatus({
18769
18923
  status: "running",
18770
18924
  current_event: eventType,
18771
18925
  last_event_at_ms: now,
@@ -18782,13 +18936,13 @@ class Supervisor {
18782
18936
  appendTimelineEvent({ t: Date.now(), type: TIMELINE_EVENT_TYPES.TEXT });
18783
18937
  }
18784
18938
  }, (meta) => {
18785
- this.updateStatus(id, { model: meta.model, backend: meta.backend });
18939
+ setStatus({ model: meta.model, backend: meta.backend });
18786
18940
  appendTimelineEvent(createMetaEvent(meta.model, meta.backend));
18787
18941
  this.opts.onMeta?.(meta);
18788
18942
  }, (fn) => {
18789
18943
  killFn = fn;
18790
18944
  }, (beadId) => {
18791
- this.updateStatus(id, { bead_id: beadId });
18945
+ setStatus({ bead_id: beadId });
18792
18946
  }, (fn) => {
18793
18947
  steerFn = fn;
18794
18948
  if (!needsFifo || !existsSync4(fifoPath))
@@ -18802,17 +18956,18 @@ class Supervisor {
18802
18956
  steerFn?.(parsed.message).catch(() => {});
18803
18957
  } else if (parsed?.type === "prompt" && typeof parsed.message === "string") {
18804
18958
  if (resumeFn) {
18805
- this.updateStatus(id, { status: "running", current_event: "starting" });
18959
+ setStatus({ status: "running", current_event: "starting" });
18806
18960
  resumeFn(parsed.message).then((output) => {
18961
+ mkdirSync(this.jobDir(id), { recursive: true });
18807
18962
  writeFileSync(this.resultPath(id), output, "utf-8");
18808
- this.updateStatus(id, {
18963
+ setStatus({
18809
18964
  status: "waiting",
18810
18965
  current_event: "waiting",
18811
18966
  elapsed_s: Math.round((Date.now() - startedAtMs) / 1000),
18812
18967
  last_event_at_ms: Date.now()
18813
18968
  });
18814
18969
  }).catch((err) => {
18815
- this.updateStatus(id, { status: "error", error: err?.message ?? String(err) });
18970
+ setStatus({ status: "error", error: err?.message ?? String(err) });
18816
18971
  });
18817
18972
  }
18818
18973
  } else if (parsed?.type === "close") {
@@ -18824,14 +18979,15 @@ class Supervisor {
18824
18979
  }, (rFn, cFn) => {
18825
18980
  resumeFn = rFn;
18826
18981
  closeFn = cFn;
18827
- this.updateStatus(id, { status: "waiting", current_event: "waiting" });
18982
+ setStatus({ status: "waiting", current_event: "waiting" });
18828
18983
  });
18829
18984
  const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
18985
+ mkdirSync(this.jobDir(id), { recursive: true });
18830
18986
  writeFileSync(this.resultPath(id), result.output, "utf-8");
18831
18987
  if (result.beadId) {
18832
18988
  this.opts.beadsClient?.updateBeadNotes(result.beadId, formatBeadNotes(result));
18833
18989
  }
18834
- this.updateStatus(id, {
18990
+ setStatus({
18835
18991
  status: "done",
18836
18992
  elapsed_s: elapsed,
18837
18993
  last_event_at_ms: Date.now(),
@@ -18844,12 +19000,13 @@ class Supervisor {
18844
19000
  backend: result.backend,
18845
19001
  bead_id: result.beadId
18846
19002
  }));
19003
+ mkdirSync(this.readyDir(), { recursive: true });
18847
19004
  writeFileSync(join3(this.readyDir(), id), "", "utf-8");
18848
19005
  return id;
18849
19006
  } catch (err) {
18850
19007
  const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
18851
19008
  const errorMsg = err?.message ?? String(err);
18852
- this.updateStatus(id, {
19009
+ setStatus({
18853
19010
  status: "error",
18854
19011
  elapsed_s: elapsed,
18855
19012
  error: errorMsg
@@ -18888,14 +19045,21 @@ var exports_install = {};
18888
19045
  __export(exports_install, {
18889
19046
  run: () => run
18890
19047
  });
18891
- import { execFileSync as execFileSync2 } from "node:child_process";
18892
- import { fileURLToPath } from "node:url";
18893
- import { dirname as dirname2, join as join8 } from "node:path";
18894
19048
  async function run() {
18895
- const installerPath = join8(dirname2(fileURLToPath(import.meta.url)), "..", "bin", "install.js");
18896
- execFileSync2(process.execPath, [installerPath], { stdio: "inherit" });
19049
+ console.log("");
19050
+ console.log(yellow("⚠ DEPRECATED: `specialists install` is deprecated"));
19051
+ console.log("");
19052
+ console.log(` Use ${bold("specialists init")} instead.`);
19053
+ console.log("");
19054
+ console.log(" The init command:");
19055
+ console.log(" • creates specialists/ and .specialists/ directories");
19056
+ console.log(" • registers the MCP server in .mcp.json");
19057
+ console.log(" • injects workflow context into AGENTS.md/CLAUDE.md");
19058
+ console.log("");
19059
+ console.log(` ${dim("Run: specialists init --help for full details")}`);
19060
+ console.log("");
18897
19061
  }
18898
- var init_install = () => {};
19062
+ var bold = (s) => `\x1B[1m${s}\x1B[0m`, yellow = (s) => `\x1B[33m${s}\x1B[0m`, dim = (s) => `\x1B[2m${s}\x1B[0m`;
18899
19063
 
18900
19064
  // src/cli/version.ts
18901
19065
  var exports_version = {};
@@ -18903,9 +19067,23 @@ __export(exports_version, {
18903
19067
  run: () => run2
18904
19068
  });
18905
19069
  import { createRequire as createRequire2 } from "node:module";
19070
+ import { fileURLToPath } from "node:url";
19071
+ import { dirname as dirname2, join as join8 } from "node:path";
19072
+ import { existsSync as existsSync6 } from "node:fs";
18906
19073
  async function run2() {
18907
19074
  const req = createRequire2(import.meta.url);
18908
- const pkg = req("../package.json");
19075
+ const here = dirname2(fileURLToPath(import.meta.url));
19076
+ const bundlePkgPath = join8(here, "..", "package.json");
19077
+ const sourcePkgPath = join8(here, "..", "..", "package.json");
19078
+ let pkg;
19079
+ if (existsSync6(bundlePkgPath)) {
19080
+ pkg = req("../package.json");
19081
+ } else if (existsSync6(sourcePkgPath)) {
19082
+ pkg = req("../../package.json");
19083
+ } else {
19084
+ console.error("Cannot find package.json");
19085
+ process.exit(1);
19086
+ }
18909
19087
  console.log(`${pkg.name} v${pkg.version}`);
18910
19088
  }
18911
19089
  var init_version = () => {};
@@ -18970,19 +19148,19 @@ async function run3() {
18970
19148
  }
18971
19149
  const nameWidth = Math.max(...specialists.map((s) => s.name.length), 4);
18972
19150
  console.log(`
18973
- ${bold(`Specialists (${specialists.length})`)}
19151
+ ${bold2(`Specialists (${specialists.length})`)}
18974
19152
  `);
18975
19153
  for (const s of specialists) {
18976
19154
  const name = cyan(s.name.padEnd(nameWidth));
18977
- const scopeTag = s.scope === "default" ? green("[default]") : yellow("[user]");
18978
- const model = dim(s.model);
19155
+ const scopeTag = s.scope === "default" ? green("[default]") : yellow2("[user]");
19156
+ const model = dim2(s.model);
18979
19157
  const desc = s.description.length > 80 ? s.description.slice(0, 79) + "…" : s.description;
18980
19158
  console.log(` ${name} ${scopeTag} ${model}`);
18981
- console.log(` ${" ".repeat(nameWidth)} ${dim(desc)}`);
19159
+ console.log(` ${" ".repeat(nameWidth)} ${dim2(desc)}`);
18982
19160
  console.log();
18983
19161
  }
18984
19162
  }
18985
- var dim = (s) => `\x1B[2m${s}\x1B[0m`, bold = (s) => `\x1B[1m${s}\x1B[0m`, cyan = (s) => `\x1B[36m${s}\x1B[0m`, green = (s) => `\x1B[32m${s}\x1B[0m`, yellow = (s) => `\x1B[33m${s}\x1B[0m`, ArgParseError;
19163
+ var dim2 = (s) => `\x1B[2m${s}\x1B[0m`, bold2 = (s) => `\x1B[1m${s}\x1B[0m`, cyan = (s) => `\x1B[36m${s}\x1B[0m`, green = (s) => `\x1B[32m${s}\x1B[0m`, yellow2 = (s) => `\x1B[33m${s}\x1B[0m`, ArgParseError;
18986
19164
  var init_list = __esm(() => {
18987
19165
  init_loader();
18988
19166
  ArgParseError = class ArgParseError extends Error {
@@ -19069,31 +19247,31 @@ async function run4() {
19069
19247
  }
19070
19248
  const total = models.length;
19071
19249
  console.log(`
19072
- ${bold2(`Models on pi`)} ${dim2(`(${total} total)`)}
19250
+ ${bold3(`Models on pi`)} ${dim3(`(${total} total)`)}
19073
19251
  `);
19074
19252
  for (const [provider, pModels] of byProvider) {
19075
- console.log(` ${cyan2(provider)} ${dim2(`${pModels.length} model${pModels.length !== 1 ? "s" : ""}`)}`);
19253
+ console.log(` ${cyan2(provider)} ${dim3(`${pModels.length} model${pModels.length !== 1 ? "s" : ""}`)}`);
19076
19254
  const modelWidth = Math.max(...pModels.map((m) => m.model.length));
19077
19255
  for (const m of pModels) {
19078
19256
  const key = `${m.provider}/${m.model}`;
19079
19257
  const inUse = usedBy.get(key);
19080
19258
  const flags = [
19081
- m.thinking ? green2("thinking") : dim2("·"),
19082
- m.images ? dim2("images") : ""
19259
+ m.thinking ? green2("thinking") : dim3("·"),
19260
+ m.images ? dim3("images") : ""
19083
19261
  ].filter(Boolean).join(" ");
19084
- const ctx = dim2(`ctx ${m.context}`);
19085
- const usedLabel = inUse ? ` ${yellow2("←")} ${dim2(inUse.join(", "))}` : "";
19262
+ const ctx = dim3(`ctx ${m.context}`);
19263
+ const usedLabel = inUse ? ` ${yellow3("←")} ${dim3(inUse.join(", "))}` : "";
19086
19264
  console.log(` ${m.model.padEnd(modelWidth)} ${ctx.padEnd(18)} ${flags}${usedLabel}`);
19087
19265
  }
19088
19266
  console.log();
19089
19267
  }
19090
19268
  if (!args.used) {
19091
- console.log(dim2(` --provider <name> filter by provider`));
19092
- console.log(dim2(` --used only show models used by your specialists`));
19269
+ console.log(dim3(` --provider <name> filter by provider`));
19270
+ console.log(dim3(` --used only show models used by your specialists`));
19093
19271
  console.log();
19094
19272
  }
19095
19273
  }
19096
- var bold2 = (s) => `\x1B[1m${s}\x1B[0m`, dim2 = (s) => `\x1B[2m${s}\x1B[0m`, cyan2 = (s) => `\x1B[36m${s}\x1B[0m`, yellow2 = (s) => `\x1B[33m${s}\x1B[0m`, green2 = (s) => `\x1B[32m${s}\x1B[0m`;
19274
+ var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, dim3 = (s) => `\x1B[2m${s}\x1B[0m`, cyan2 = (s) => `\x1B[36m${s}\x1B[0m`, yellow3 = (s) => `\x1B[33m${s}\x1B[0m`, green2 = (s) => `\x1B[32m${s}\x1B[0m`;
19097
19275
  var init_models = __esm(() => {
19098
19276
  init_loader();
19099
19277
  });
@@ -19103,17 +19281,17 @@ var exports_init = {};
19103
19281
  __export(exports_init, {
19104
19282
  run: () => run5
19105
19283
  });
19106
- import { copyFileSync, cpSync, existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
19284
+ import { copyFileSync, cpSync, existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
19107
19285
  import { join as join9 } from "node:path";
19108
19286
  import { fileURLToPath as fileURLToPath2 } from "node:url";
19109
19287
  function ok(msg) {
19110
19288
  console.log(` ${green3("✓")} ${msg}`);
19111
19289
  }
19112
19290
  function skip(msg) {
19113
- console.log(` ${yellow3("○")} ${msg}`);
19291
+ console.log(` ${yellow4("○")} ${msg}`);
19114
19292
  }
19115
19293
  function loadJson(path, fallback) {
19116
- if (!existsSync6(path))
19294
+ if (!existsSync7(path))
19117
19295
  return structuredClone(fallback);
19118
19296
  try {
19119
19297
  return JSON.parse(readFileSync3(path, "utf-8"));
@@ -19128,10 +19306,10 @@ function saveJson(path, value) {
19128
19306
  function resolvePackagePath(relativePath) {
19129
19307
  const configPath = `config/${relativePath}`;
19130
19308
  let resolved = fileURLToPath2(new URL(`../${configPath}`, import.meta.url));
19131
- if (existsSync6(resolved))
19309
+ if (existsSync7(resolved))
19132
19310
  return resolved;
19133
19311
  resolved = fileURLToPath2(new URL(`../../${configPath}`, import.meta.url));
19134
- if (existsSync6(resolved))
19312
+ if (existsSync7(resolved))
19135
19313
  return resolved;
19136
19314
  return null;
19137
19315
  }
@@ -19147,7 +19325,7 @@ function copyCanonicalSpecialists(cwd) {
19147
19325
  skip("no specialist files found in package");
19148
19326
  return;
19149
19327
  }
19150
- if (!existsSync6(targetDir)) {
19328
+ if (!existsSync7(targetDir)) {
19151
19329
  mkdirSync2(targetDir, { recursive: true });
19152
19330
  }
19153
19331
  let copied = 0;
@@ -19155,7 +19333,7 @@ function copyCanonicalSpecialists(cwd) {
19155
19333
  for (const file of files) {
19156
19334
  const src = join9(sourceDir, file);
19157
19335
  const dest = join9(targetDir, file);
19158
- if (existsSync6(dest)) {
19336
+ if (existsSync7(dest)) {
19159
19337
  skipped++;
19160
19338
  } else {
19161
19339
  copyFileSync(src, dest);
@@ -19169,19 +19347,19 @@ function copyCanonicalSpecialists(cwd) {
19169
19347
  skip(`${skipped} specialist${skipped === 1 ? "" : "s"} already exist (not overwritten)`);
19170
19348
  }
19171
19349
  }
19172
- function copyCanonicalHooks(cwd) {
19350
+ function installProjectHooks(cwd) {
19173
19351
  const sourceDir = resolvePackagePath("hooks");
19174
19352
  if (!sourceDir) {
19175
19353
  skip("no canonical hooks found in package");
19176
19354
  return;
19177
19355
  }
19178
- const targetDir = join9(cwd, ".specialists", "default", "hooks");
19356
+ const targetDir = join9(cwd, ".claude", "hooks");
19179
19357
  const hooks = readdirSync2(sourceDir).filter((f) => f.endsWith(".mjs"));
19180
19358
  if (hooks.length === 0) {
19181
19359
  skip("no hook files found in package");
19182
19360
  return;
19183
19361
  }
19184
- if (!existsSync6(targetDir)) {
19362
+ if (!existsSync7(targetDir)) {
19185
19363
  mkdirSync2(targetDir, { recursive: true });
19186
19364
  }
19187
19365
  let copied = 0;
@@ -19189,7 +19367,7 @@ function copyCanonicalHooks(cwd) {
19189
19367
  for (const file of hooks) {
19190
19368
  const src = join9(sourceDir, file);
19191
19369
  const dest = join9(targetDir, file);
19192
- if (existsSync6(dest)) {
19370
+ if (existsSync7(dest)) {
19193
19371
  skipped++;
19194
19372
  } else {
19195
19373
  copyFileSync(src, dest);
@@ -19197,16 +19375,16 @@ function copyCanonicalHooks(cwd) {
19197
19375
  }
19198
19376
  }
19199
19377
  if (copied > 0) {
19200
- ok(`copied ${copied} hook${copied === 1 ? "" : "s"} to .specialists/default/hooks/`);
19378
+ ok(`installed ${copied} hook${copied === 1 ? "" : "s"} to .claude/hooks/`);
19201
19379
  }
19202
19380
  if (skipped > 0) {
19203
19381
  skip(`${skipped} hook${skipped === 1 ? "" : "s"} already exist (not overwritten)`);
19204
19382
  }
19205
19383
  }
19206
- function ensureProjectHooks(cwd) {
19384
+ function ensureProjectHookWiring(cwd) {
19207
19385
  const settingsPath = join9(cwd, ".claude", "settings.json");
19208
19386
  const settingsDir = join9(cwd, ".claude");
19209
- if (!existsSync6(settingsDir)) {
19387
+ if (!existsSync7(settingsDir)) {
19210
19388
  mkdirSync2(settingsDir, { recursive: true });
19211
19389
  }
19212
19390
  const settings = loadJson(settingsPath, {});
@@ -19220,8 +19398,8 @@ function ensureProjectHooks(cwd) {
19220
19398
  changed = true;
19221
19399
  }
19222
19400
  }
19223
- addHook("UserPromptSubmit", "node .specialists/default/hooks/specialists-complete.mjs");
19224
- addHook("SessionStart", "node .specialists/default/hooks/specialists-session-start.mjs");
19401
+ addHook("UserPromptSubmit", "node .claude/hooks/specialists-complete.mjs");
19402
+ addHook("SessionStart", "node .claude/hooks/specialists-session-start.mjs");
19225
19403
  if (changed) {
19226
19404
  saveJson(settingsPath, settings);
19227
19405
  ok("wired specialists hooks in .claude/settings.json");
@@ -19229,7 +19407,7 @@ function ensureProjectHooks(cwd) {
19229
19407
  skip(".claude/settings.json already has specialists hooks");
19230
19408
  }
19231
19409
  }
19232
- function copyCanonicalSkills(cwd) {
19410
+ function installProjectSkills(cwd) {
19233
19411
  const sourceDir = resolvePackagePath("skills");
19234
19412
  if (!sourceDir) {
19235
19413
  skip("no canonical skills found in package");
@@ -19240,44 +19418,39 @@ function copyCanonicalSkills(cwd) {
19240
19418
  skip("no skill directories found in package");
19241
19419
  return;
19242
19420
  }
19243
- const targetDir = join9(cwd, ".specialists", "default", "skills");
19244
- if (!existsSync6(targetDir)) {
19245
- mkdirSync2(targetDir, { recursive: true });
19246
- }
19247
- let copied = 0;
19248
- let skipped = 0;
19249
- for (const skill of skills) {
19250
- const src = join9(sourceDir, skill);
19251
- const dest = join9(targetDir, skill);
19252
- if (existsSync6(dest)) {
19253
- skipped++;
19254
- } else {
19255
- cpSync(src, dest, { recursive: true });
19256
- copied++;
19421
+ const targetDirs = [
19422
+ join9(cwd, ".claude", "skills"),
19423
+ join9(cwd, ".pi", "skills")
19424
+ ];
19425
+ let totalCopied = 0;
19426
+ let totalSkipped = 0;
19427
+ for (const targetDir of targetDirs) {
19428
+ if (!existsSync7(targetDir)) {
19429
+ mkdirSync2(targetDir, { recursive: true });
19430
+ }
19431
+ for (const skill of skills) {
19432
+ const src = join9(sourceDir, skill);
19433
+ const dest = join9(targetDir, skill);
19434
+ if (existsSync7(dest)) {
19435
+ totalSkipped++;
19436
+ } else {
19437
+ cpSync(src, dest, { recursive: true });
19438
+ totalCopied++;
19439
+ }
19257
19440
  }
19258
19441
  }
19259
- if (copied > 0) {
19260
- ok(`copied ${copied} skill${copied === 1 ? "" : "s"} to .specialists/default/skills/`);
19442
+ if (totalCopied > 0) {
19443
+ ok(`installed ${skills.length} skill${skills.length === 1 ? "" : "s"} to .claude/skills/ and .pi/skills/`);
19261
19444
  }
19262
- if (skipped > 0) {
19263
- skip(`${skipped} skill${skipped === 1 ? "" : "s"} already exist (not overwritten)`);
19445
+ if (totalSkipped > 0) {
19446
+ skip(`${totalSkipped} skill location${totalSkipped === 1 ? "" : "s"} already exist (not overwritten)`);
19264
19447
  }
19265
19448
  }
19266
19449
  function createUserDirs(cwd) {
19267
- const userDirs = [
19268
- join9(cwd, ".specialists", "user", "specialists"),
19269
- join9(cwd, ".specialists", "user", "hooks"),
19270
- join9(cwd, ".specialists", "user", "skills")
19271
- ];
19272
- let created = 0;
19273
- for (const dir of userDirs) {
19274
- if (!existsSync6(dir)) {
19275
- mkdirSync2(dir, { recursive: true });
19276
- created++;
19277
- }
19278
- }
19279
- if (created > 0) {
19280
- ok("created .specialists/user/ directories for custom assets");
19450
+ const userDir = join9(cwd, ".specialists", "user", "specialists");
19451
+ if (!existsSync7(userDir)) {
19452
+ mkdirSync2(userDir, { recursive: true });
19453
+ ok("created .specialists/user/specialists/ for custom specialists");
19281
19454
  }
19282
19455
  }
19283
19456
  function createRuntimeDirs(cwd) {
@@ -19287,7 +19460,7 @@ function createRuntimeDirs(cwd) {
19287
19460
  ];
19288
19461
  let created = 0;
19289
19462
  for (const dir of runtimeDirs) {
19290
- if (!existsSync6(dir)) {
19463
+ if (!existsSync7(dir)) {
19291
19464
  mkdirSync2(dir, { recursive: true });
19292
19465
  created++;
19293
19466
  }
@@ -19311,7 +19484,7 @@ function ensureProjectMcp(cwd) {
19311
19484
  }
19312
19485
  function ensureGitignore(cwd) {
19313
19486
  const gitignorePath = join9(cwd, ".gitignore");
19314
- const existing = existsSync6(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
19487
+ const existing = existsSync7(gitignorePath) ? readFileSync3(gitignorePath, "utf-8") : "";
19315
19488
  let added = 0;
19316
19489
  const lines = existing.split(`
19317
19490
  `);
@@ -19332,7 +19505,7 @@ function ensureGitignore(cwd) {
19332
19505
  }
19333
19506
  function ensureAgentsMd(cwd) {
19334
19507
  const agentsPath = join9(cwd, "AGENTS.md");
19335
- if (existsSync6(agentsPath)) {
19508
+ if (existsSync7(agentsPath)) {
19336
19509
  const existing = readFileSync3(agentsPath, "utf-8");
19337
19510
  if (existing.includes(AGENTS_MARKER)) {
19338
19511
  skip("AGENTS.md already has Specialists section");
@@ -19350,40 +19523,42 @@ function ensureAgentsMd(cwd) {
19350
19523
  async function run5() {
19351
19524
  const cwd = process.cwd();
19352
19525
  console.log(`
19353
- ${bold3("specialists init")}
19526
+ ${bold4("specialists init")}
19354
19527
  `);
19355
19528
  copyCanonicalSpecialists(cwd);
19356
- copyCanonicalHooks(cwd);
19357
- copyCanonicalSkills(cwd);
19358
19529
  createUserDirs(cwd);
19359
19530
  createRuntimeDirs(cwd);
19360
19531
  ensureGitignore(cwd);
19361
19532
  ensureAgentsMd(cwd);
19362
19533
  ensureProjectMcp(cwd);
19363
- ensureProjectHooks(cwd);
19534
+ installProjectHooks(cwd);
19535
+ ensureProjectHookWiring(cwd);
19536
+ installProjectSkills(cwd);
19364
19537
  console.log(`
19365
- ${bold3("Done!")}
19538
+ ${bold4("Done!")}
19366
19539
  `);
19367
- console.log(` ${dim3("Directory structure:")}`);
19540
+ console.log(` ${dim4("Project-local installation:")}`);
19541
+ console.log(` .claude/hooks/ ${dim4("# hooks (Claude Code)")}`);
19542
+ console.log(` .claude/settings.json ${dim4("# hook wiring")}`);
19543
+ console.log(` .claude/skills/ ${dim4("# skills (Claude Code)")}`);
19544
+ console.log(` .pi/skills/ ${dim4("# skills (pi)")}`);
19545
+ console.log("");
19546
+ console.log(` ${dim4(".specialists/ structure:")}`);
19368
19547
  console.log(` .specialists/`);
19369
- console.log(` ├── default/ ${dim3("# canonical assets (from init)")}`);
19370
- console.log(` │ ├── specialists/`);
19371
- console.log(` ├── hooks/`);
19372
- console.log(` │ └── skills/`);
19373
- console.log(` ├── user/ ${dim3("# your custom additions")}`);
19374
- console.log(` │ ├── specialists/`);
19375
- console.log(` │ ├── hooks/`);
19376
- console.log(` │ └── skills/`);
19377
- console.log(` ├── jobs/ ${dim3("# runtime (gitignored)")}`);
19378
- console.log(` └── ready/ ${dim3("# runtime (gitignored)")}`);
19548
+ console.log(` ├── default/ ${dim4("# canonical specialists (from init)")}`);
19549
+ console.log(` │ └── specialists/`);
19550
+ console.log(` ├── user/ ${dim4("# your custom specialists")}`);
19551
+ console.log(` │ └── specialists/`);
19552
+ console.log(` ├── jobs/ ${dim4("# runtime (gitignored)")}`);
19553
+ console.log(` └── ready/ ${dim4("# runtime (gitignored)")}`);
19379
19554
  console.log(`
19380
- ${dim3("Next steps:")}`);
19381
- console.log(` 1. Run ${yellow3("specialists list")} to see available specialists`);
19382
- console.log(` 2. Add custom specialists to ${yellow3(".specialists/user/specialists/")}`);
19383
- console.log(` 3. Restart Claude Code to pick up changes
19555
+ ${dim4("Next steps:")}`);
19556
+ console.log(` 1. Run ${yellow4("specialists list")} to see available specialists`);
19557
+ console.log(` 2. Add custom specialists to ${yellow4(".specialists/user/specialists/")}`);
19558
+ console.log(` 3. Restart Claude Code or pi to pick up changes
19384
19559
  `);
19385
19560
  }
19386
- var bold3 = (s) => `\x1B[1m${s}\x1B[0m`, green3 = (s) => `\x1B[32m${s}\x1B[0m`, yellow3 = (s) => `\x1B[33m${s}\x1B[0m`, dim3 = (s) => `\x1B[2m${s}\x1B[0m`, AGENTS_BLOCK, AGENTS_MARKER = "## Specialists", GITIGNORE_ENTRIES, MCP_FILE = ".mcp.json", MCP_SERVER_NAME = "specialists", MCP_SERVER_CONFIG;
19561
+ var bold4 = (s) => `\x1B[1m${s}\x1B[0m`, green3 = (s) => `\x1B[32m${s}\x1B[0m`, yellow4 = (s) => `\x1B[33m${s}\x1B[0m`, dim4 = (s) => `\x1B[2m${s}\x1B[0m`, AGENTS_BLOCK, AGENTS_MARKER = "## Specialists", GITIGNORE_ENTRIES, MCP_FILE = ".mcp.json", MCP_SERVER_NAME = "specialists", MCP_SERVER_CONFIG;
19387
19562
  var init_init = __esm(() => {
19388
19563
  AGENTS_BLOCK = `
19389
19564
  ## Specialists
@@ -19399,13 +19574,126 @@ Add custom specialists to \`.specialists/user/specialists/\` to extend the defau
19399
19574
  MCP_SERVER_CONFIG = { command: "specialists", args: [] };
19400
19575
  });
19401
19576
 
19577
+ // src/cli/validate.ts
19578
+ var exports_validate = {};
19579
+ __export(exports_validate, {
19580
+ run: () => run6,
19581
+ parseArgs: () => parseArgs3,
19582
+ ArgParseError: () => ArgParseError2
19583
+ });
19584
+ import { readFile as readFile2 } from "node:fs/promises";
19585
+ import { existsSync as existsSync8 } from "node:fs";
19586
+ import { join as join10 } from "node:path";
19587
+ function parseArgs3(argv) {
19588
+ const name = argv[0];
19589
+ if (!name || name.startsWith("--")) {
19590
+ throw new ArgParseError2("Usage: specialists validate <name> [--json]");
19591
+ }
19592
+ const json = argv.includes("--json");
19593
+ return { name, json };
19594
+ }
19595
+ function findSpecialistFile(name) {
19596
+ const scanDirs = [
19597
+ join10(process.cwd(), ".specialists", "user", "specialists"),
19598
+ join10(process.cwd(), ".specialists", "default", "specialists"),
19599
+ join10(process.cwd(), "specialists")
19600
+ ];
19601
+ for (const dir of scanDirs) {
19602
+ const candidate = join10(dir, `${name}.specialist.yaml`);
19603
+ if (existsSync8(candidate)) {
19604
+ return candidate;
19605
+ }
19606
+ }
19607
+ return;
19608
+ }
19609
+ async function run6() {
19610
+ let args;
19611
+ try {
19612
+ args = parseArgs3(process.argv.slice(3));
19613
+ } catch (err) {
19614
+ if (err instanceof ArgParseError2) {
19615
+ console.error(`Error: ${err.message}`);
19616
+ process.exit(1);
19617
+ }
19618
+ throw err;
19619
+ }
19620
+ const { name, json } = args;
19621
+ const filePath = findSpecialistFile(name);
19622
+ if (!filePath) {
19623
+ if (json) {
19624
+ console.log(JSON.stringify({ valid: false, errors: [{ path: "name", message: `Specialist not found: ${name}`, code: "not_found" }] }));
19625
+ } else {
19626
+ console.error(`${red("✗")} Specialist not found: ${cyan3(name)}`);
19627
+ }
19628
+ process.exit(1);
19629
+ }
19630
+ let content;
19631
+ try {
19632
+ content = await readFile2(filePath, "utf-8");
19633
+ } catch (e) {
19634
+ const msg = e instanceof Error ? e.message : String(e);
19635
+ if (json) {
19636
+ console.log(JSON.stringify({ valid: false, errors: [{ path: "file", message: `Failed to read file: ${msg}`, code: "read_error" }] }));
19637
+ } else {
19638
+ console.error(`${red("✗")} Failed to read file: ${msg}`);
19639
+ }
19640
+ process.exit(1);
19641
+ }
19642
+ const result = await validateSpecialist(content);
19643
+ if (json) {
19644
+ console.log(JSON.stringify({
19645
+ valid: result.valid,
19646
+ errors: result.errors,
19647
+ warnings: result.warnings,
19648
+ file: filePath
19649
+ }, null, 2));
19650
+ process.exit(result.valid ? 0 : 1);
19651
+ }
19652
+ console.log(`
19653
+ ${bold5("Validating")} ${cyan3(name)} ${dim5(`(${filePath})`)}
19654
+ `);
19655
+ if (result.valid) {
19656
+ console.log(`${green4("✓")} Schema validation passed
19657
+ `);
19658
+ } else {
19659
+ console.log(`${red("✗")} Schema validation failed:
19660
+ `);
19661
+ for (const error2 of result.errors) {
19662
+ console.log(` ${red("•")} ${error2.message}`);
19663
+ if (error2.path && error2.path !== "yaml") {
19664
+ console.log(` ${dim5(`path: ${error2.path}`)}`);
19665
+ }
19666
+ }
19667
+ console.log();
19668
+ }
19669
+ if (result.warnings.length > 0) {
19670
+ console.log(`${yellow5("Warnings")}:
19671
+ `);
19672
+ for (const warning of result.warnings) {
19673
+ console.log(` ${yellow5("⚠")} ${warning}`);
19674
+ }
19675
+ console.log();
19676
+ }
19677
+ process.exit(result.valid ? 0 : 1);
19678
+ }
19679
+ var bold5 = (s) => `\x1B[1m${s}\x1B[0m`, dim5 = (s) => `\x1B[2m${s}\x1B[0m`, green4 = (s) => `\x1B[32m${s}\x1B[0m`, red = (s) => `\x1B[31m${s}\x1B[0m`, yellow5 = (s) => `\x1B[33m${s}\x1B[0m`, cyan3 = (s) => `\x1B[36m${s}\x1B[0m`, ArgParseError2;
19680
+ var init_validate = __esm(() => {
19681
+ init_schema();
19682
+ ArgParseError2 = class ArgParseError2 extends Error {
19683
+ constructor(message) {
19684
+ super(message);
19685
+ this.name = "ArgParseError";
19686
+ }
19687
+ };
19688
+ });
19689
+
19402
19690
  // src/cli/edit.ts
19403
19691
  var exports_edit = {};
19404
19692
  __export(exports_edit, {
19405
- run: () => run6
19693
+ run: () => run7
19406
19694
  });
19407
19695
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
19408
- function parseArgs3(argv) {
19696
+ function parseArgs4(argv) {
19409
19697
  const name = argv[0];
19410
19698
  if (!name || name.startsWith("--")) {
19411
19699
  console.error("Usage: specialists|sp edit <name> --<field> <value> [--dry-run]");
@@ -19424,8 +19712,8 @@ function parseArgs3(argv) {
19424
19712
  }
19425
19713
  if (token === "--scope") {
19426
19714
  const v = argv[++i];
19427
- if (v !== "project" && v !== "user") {
19428
- console.error(`Error: --scope must be "project" or "user", got: "${v ?? ""}"`);
19715
+ if (v !== "default" && v !== "user") {
19716
+ console.error(`Error: --scope must be "default" or "user", got: "${v ?? ""}"`);
19429
19717
  process.exit(1);
19430
19718
  }
19431
19719
  scope = v;
@@ -19467,8 +19755,8 @@ function setIn(doc2, path, value) {
19467
19755
  node.set(leaf, value);
19468
19756
  }
19469
19757
  }
19470
- async function run6() {
19471
- const args = parseArgs3(process.argv.slice(3));
19758
+ async function run7() {
19759
+ const args = parseArgs4(process.argv.slice(3));
19472
19760
  const { name, field, value, dryRun, scope } = args;
19473
19761
  const loader = new SpecialistLoader;
19474
19762
  const all = await loader.list();
@@ -19476,7 +19764,7 @@ async function run6() {
19476
19764
  if (!match) {
19477
19765
  const hint = scope ? ` (scope: ${scope})` : "";
19478
19766
  console.error(`Error: specialist "${name}" not found${hint}`);
19479
- console.error(` Run ${yellow4("specialists list")} to see available specialists`);
19767
+ console.error(` Run ${yellow6("specialists list")} to see available specialists`);
19480
19768
  process.exit(1);
19481
19769
  }
19482
19770
  const raw = readFileSync4(match.filePath, "utf-8");
@@ -19492,10 +19780,10 @@ async function run6() {
19492
19780
  const updated = doc2.toString();
19493
19781
  if (dryRun) {
19494
19782
  console.log(`
19495
- ${bold4(`[dry-run] ${match.filePath}`)}
19783
+ ${bold6(`[dry-run] ${match.filePath}`)}
19496
19784
  `);
19497
- console.log(dim4("--- current"));
19498
- console.log(dim4(`+++ updated`));
19785
+ console.log(dim6("--- current"));
19786
+ console.log(dim6(`+++ updated`));
19499
19787
  const oldLines = raw.split(`
19500
19788
  `);
19501
19789
  const newLines = updated.split(`
@@ -19503,8 +19791,8 @@ ${bold4(`[dry-run] ${match.filePath}`)}
19503
19791
  newLines.forEach((line, i) => {
19504
19792
  if (line !== oldLines[i]) {
19505
19793
  if (oldLines[i] !== undefined)
19506
- console.log(dim4(`- ${oldLines[i]}`));
19507
- console.log(green4(`+ ${line}`));
19794
+ console.log(dim6(`- ${oldLines[i]}`));
19795
+ console.log(green5(`+ ${line}`));
19508
19796
  }
19509
19797
  });
19510
19798
  console.log();
@@ -19512,9 +19800,9 @@ ${bold4(`[dry-run] ${match.filePath}`)}
19512
19800
  }
19513
19801
  writeFileSync5(match.filePath, updated, "utf-8");
19514
19802
  const displayValue = field === "tags" ? `[${typedValue.join(", ")}]` : String(typedValue);
19515
- console.log(`${green4("✓")} ${bold4(name)}: ${yellow4(field)} = ${displayValue}` + dim4(` (${match.filePath})`));
19803
+ console.log(`${green5("✓")} ${bold6(name)}: ${yellow6(field)} = ${displayValue}` + dim6(` (${match.filePath})`));
19516
19804
  }
19517
- var bold4 = (s) => `\x1B[1m${s}\x1B[0m`, green4 = (s) => `\x1B[32m${s}\x1B[0m`, yellow4 = (s) => `\x1B[33m${s}\x1B[0m`, dim4 = (s) => `\x1B[2m${s}\x1B[0m`, FIELD_MAP, VALID_PERMISSIONS;
19805
+ var bold6 = (s) => `\x1B[1m${s}\x1B[0m`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, yellow6 = (s) => `\x1B[33m${s}\x1B[0m`, dim6 = (s) => `\x1B[2m${s}\x1B[0m`, FIELD_MAP, VALID_PERMISSIONS;
19518
19806
  var init_edit = __esm(() => {
19519
19807
  init_dist();
19520
19808
  init_loader();
@@ -19532,10 +19820,10 @@ var init_edit = __esm(() => {
19532
19820
  // src/cli/run.ts
19533
19821
  var exports_run = {};
19534
19822
  __export(exports_run, {
19535
- run: () => run7
19823
+ run: () => run8
19536
19824
  });
19537
- import { join as join10 } from "node:path";
19538
- async function parseArgs4(argv) {
19825
+ import { join as join11 } from "node:path";
19826
+ async function parseArgs5(argv) {
19539
19827
  const name = argv[0];
19540
19828
  if (!name || name.startsWith("--")) {
19541
19829
  console.error('Usage: specialists|sp run <name> [--prompt "..."] [--bead <id>] [--context-depth <n>] [--model <model>] [--no-beads] [--keep-alive]');
@@ -19594,11 +19882,11 @@ async function parseArgs4(argv) {
19594
19882
  }
19595
19883
  return { name, prompt, beadId, model, noBeads, keepAlive, contextDepth };
19596
19884
  }
19597
- async function run7() {
19598
- const args = await parseArgs4(process.argv.slice(3));
19885
+ async function run8() {
19886
+ const args = await parseArgs5(process.argv.slice(3));
19599
19887
  const loader = new SpecialistLoader;
19600
19888
  const circuitBreaker = new CircuitBreaker;
19601
- const hooks = new HookEmitter({ tracePath: join10(process.cwd(), ".specialists", "trace.jsonl") });
19889
+ const hooks = new HookEmitter({ tracePath: join11(process.cwd(), ".specialists", "trace.jsonl") });
19602
19890
  const beadsClient = args.noBeads ? undefined : new BeadsClient;
19603
19891
  const beadReader = beadsClient ?? new BeadsClient;
19604
19892
  let prompt = args.prompt;
@@ -19610,7 +19898,7 @@ async function run7() {
19610
19898
  }
19611
19899
  const blockers = args.contextDepth > 0 ? beadReader.getCompletedBlockers(args.beadId, args.contextDepth) : [];
19612
19900
  if (blockers.length > 0) {
19613
- process.stderr.write(dim5(`
19901
+ process.stderr.write(dim7(`
19614
19902
  [context: ${blockers.length} completed dep${blockers.length > 1 ? "s" : ""} injected]
19615
19903
  `));
19616
19904
  }
@@ -19627,7 +19915,7 @@ async function run7() {
19627
19915
  circuitBreaker,
19628
19916
  beadsClient
19629
19917
  });
19630
- const jobsDir = join10(process.cwd(), ".specialists", "jobs");
19918
+ const jobsDir = join11(process.cwd(), ".specialists", "jobs");
19631
19919
  const supervisor = new Supervisor({
19632
19920
  runner,
19633
19921
  runOptions: {
@@ -19641,13 +19929,22 @@ async function run7() {
19641
19929
  jobsDir,
19642
19930
  beadsClient,
19643
19931
  onProgress: (delta) => process.stdout.write(delta),
19644
- onMeta: (meta) => process.stderr.write(dim5(`
19932
+ onMeta: (meta) => process.stderr.write(dim7(`
19645
19933
  [${meta.backend} / ${meta.model}]
19646
19934
 
19935
+ `)),
19936
+ onJobStarted: ({ id }) => process.stderr.write(dim7(`[job started: ${id}]
19647
19937
  `))
19648
19938
  });
19939
+ try {
19940
+ await loader.get(args.name);
19941
+ } catch (err) {
19942
+ process.stderr.write(`Error: ${err?.message ?? err}
19943
+ `);
19944
+ process.exit(1);
19945
+ }
19649
19946
  process.stderr.write(`
19650
- ${bold5(`Running ${cyan3(args.name)}`)}
19947
+ ${bold7(`Running ${cyan4(args.name)}`)}
19651
19948
 
19652
19949
  `);
19653
19950
  let jobId;
@@ -19664,17 +19961,18 @@ ${bold5(`Running ${cyan3(args.name)}`)}
19664
19961
  `job ${jobId}`,
19665
19962
  status?.bead_id ? `bead ${status.bead_id}` : "",
19666
19963
  `${secs.toFixed(1)}s`,
19667
- status?.model ? dim5(`${status.backend}/${status.model}`) : ""
19964
+ status?.model ? dim7(`${status.backend}/${status.model}`) : ""
19668
19965
  ].filter(Boolean).join(" ");
19669
19966
  process.stderr.write(`
19670
- ${green5("✓")} ${footer}
19967
+ ${green6("✓")} ${footer}
19671
19968
 
19672
19969
  `);
19673
- process.stderr.write(dim5(`Poll: specialists poll ${jobId} --json
19970
+ process.stderr.write(dim7(`Poll: specialists poll ${jobId} --json
19674
19971
 
19675
19972
  `));
19973
+ process.exit(0);
19676
19974
  }
19677
- var bold5 = (s) => `\x1B[1m${s}\x1B[0m`, dim5 = (s) => `\x1B[2m${s}\x1B[0m`, green5 = (s) => `\x1B[32m${s}\x1B[0m`, cyan3 = (s) => `\x1B[36m${s}\x1B[0m`;
19975
+ var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim7 = (s) => `\x1B[2m${s}\x1B[0m`, green6 = (s) => `\x1B[32m${s}\x1B[0m`, cyan4 = (s) => `\x1B[36m${s}\x1B[0m`;
19678
19976
  var init_run = __esm(() => {
19679
19977
  init_loader();
19680
19978
  init_runner();
@@ -19721,9 +20019,9 @@ class JobColorMap {
19721
20019
  }
19722
20020
  }
19723
20021
  function formatEventLine(event, options) {
19724
- const ts = dim6(formatTime(event.t));
19725
- const label = options.colorize(bold6(getEventLabel(event.type).padEnd(5)));
19726
- const prefix = `${options.colorize(`[${options.jobId}]`)} ${options.specialist}${options.beadId ? ` ${dim6(`[${options.beadId}]`)}` : ""}`;
20022
+ const ts = dim8(formatTime(event.t));
20023
+ const label = options.colorize(bold8(getEventLabel(event.type).padEnd(5)));
20024
+ const prefix = `${options.colorize(`[${options.jobId}]`)} ${options.specialist}${options.beadId ? ` ${dim8(`[${options.beadId}]`)}` : ""}`;
19727
20025
  const detailParts = [];
19728
20026
  if (event.type === "meta") {
19729
20027
  detailParts.push(`model=${event.model}`);
@@ -19752,19 +20050,26 @@ function formatEventLine(event, options) {
19752
20050
  detailParts.push("kind=assistant");
19753
20051
  } else if (event.type === "thinking") {
19754
20052
  detailParts.push("kind=model");
20053
+ } else if (event.type === "message") {
20054
+ detailParts.push(`phase=${event.phase}`);
20055
+ detailParts.push(`role=${event.role}`);
20056
+ } else if (event.type === "turn") {
20057
+ detailParts.push(`phase=${event.phase}`);
19755
20058
  }
19756
- const detail = detailParts.length > 0 ? dim6(detailParts.join(" ")) : "";
20059
+ const detail = detailParts.length > 0 ? dim8(detailParts.join(" ")) : "";
19757
20060
  return `${ts} ${prefix} ${label}${detail ? ` ${detail}` : ""}`.trimEnd();
19758
20061
  }
19759
- var dim6 = (s) => `\x1B[2m${s}\x1B[0m`, bold6 = (s) => `\x1B[1m${s}\x1B[0m`, cyan4 = (s) => `\x1B[36m${s}\x1B[0m`, yellow5 = (s) => `\x1B[33m${s}\x1B[0m`, red = (s) => `\x1B[31m${s}\x1B[0m`, green6 = (s) => `\x1B[32m${s}\x1B[0m`, blue = (s) => `\x1B[34m${s}\x1B[0m`, magenta = (s) => `\x1B[35m${s}\x1B[0m`, JOB_COLORS, EVENT_LABELS;
20062
+ var dim8 = (s) => `\x1B[2m${s}\x1B[0m`, bold8 = (s) => `\x1B[1m${s}\x1B[0m`, cyan5 = (s) => `\x1B[36m${s}\x1B[0m`, yellow7 = (s) => `\x1B[33m${s}\x1B[0m`, red2 = (s) => `\x1B[31m${s}\x1B[0m`, green7 = (s) => `\x1B[32m${s}\x1B[0m`, blue = (s) => `\x1B[34m${s}\x1B[0m`, magenta = (s) => `\x1B[35m${s}\x1B[0m`, JOB_COLORS, EVENT_LABELS;
19760
20063
  var init_format_helpers = __esm(() => {
19761
- JOB_COLORS = [cyan4, yellow5, magenta, green6, blue, red];
20064
+ JOB_COLORS = [cyan5, yellow7, magenta, green7, blue, red2];
19762
20065
  EVENT_LABELS = {
19763
20066
  run_start: "START",
19764
20067
  meta: "META",
19765
20068
  thinking: "THINK",
19766
20069
  tool: "TOOL",
19767
20070
  text: "TEXT",
20071
+ message: "MSG",
20072
+ turn: "TURN",
19768
20073
  run_complete: "DONE",
19769
20074
  done: "DONE",
19770
20075
  agent_end: "DONE",
@@ -19775,27 +20080,27 @@ var init_format_helpers = __esm(() => {
19775
20080
  // src/cli/status.ts
19776
20081
  var exports_status = {};
19777
20082
  __export(exports_status, {
19778
- run: () => run8
20083
+ run: () => run9
19779
20084
  });
19780
20085
  import { spawnSync as spawnSync6 } from "node:child_process";
19781
- import { existsSync as existsSync7 } from "node:fs";
19782
- import { join as join11 } from "node:path";
20086
+ import { existsSync as existsSync9 } from "node:fs";
20087
+ import { join as join12 } from "node:path";
19783
20088
  function ok2(msg) {
19784
- console.log(` ${green6("✓")} ${msg}`);
20089
+ console.log(` ${green7("✓")} ${msg}`);
19785
20090
  }
19786
20091
  function warn(msg) {
19787
- console.log(` ${yellow5("○")} ${msg}`);
20092
+ console.log(` ${yellow7("○")} ${msg}`);
19788
20093
  }
19789
20094
  function fail(msg) {
19790
- console.log(` ${red("✗")} ${msg}`);
20095
+ console.log(` ${red2("✗")} ${msg}`);
19791
20096
  }
19792
20097
  function info(msg) {
19793
- console.log(` ${dim6(msg)}`);
20098
+ console.log(` ${dim8(msg)}`);
19794
20099
  }
19795
20100
  function section(label) {
19796
20101
  const line = "─".repeat(Math.max(0, 38 - label.length));
19797
20102
  console.log(`
19798
- ${bold6(`── ${label} ${line}`)}`);
20103
+ ${bold8(`── ${label} ${line}`)}`);
19799
20104
  }
19800
20105
  function cmd(bin, args) {
19801
20106
  const r = spawnSync6(bin, args, {
@@ -19818,18 +20123,18 @@ function formatElapsed2(s) {
19818
20123
  function statusColor(status) {
19819
20124
  switch (status) {
19820
20125
  case "running":
19821
- return cyan4(status);
20126
+ return cyan5(status);
19822
20127
  case "done":
19823
- return green6(status);
20128
+ return green7(status);
19824
20129
  case "error":
19825
- return red(status);
20130
+ return red2(status);
19826
20131
  case "starting":
19827
- return yellow5(status);
20132
+ return yellow7(status);
19828
20133
  default:
19829
20134
  return status;
19830
20135
  }
19831
20136
  }
19832
- async function run8() {
20137
+ async function run9() {
19833
20138
  const argv = process.argv.slice(3);
19834
20139
  const jsonMode = argv.includes("--json");
19835
20140
  const loader = new SpecialistLoader;
@@ -19841,11 +20146,11 @@ async function run8() {
19841
20146
  `).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
19842
20147
  const bdInstalled = isInstalled("bd");
19843
20148
  const bdVersion = bdInstalled ? cmd("bd", ["--version"]) : null;
19844
- const beadsPresent = existsSync7(join11(process.cwd(), ".beads"));
20149
+ const beadsPresent = existsSync9(join12(process.cwd(), ".beads"));
19845
20150
  const specialistsBin = cmd("which", ["specialists"]);
19846
- const jobsDir = join11(process.cwd(), ".specialists", "jobs");
20151
+ const jobsDir = join12(process.cwd(), ".specialists", "jobs");
19847
20152
  let jobs = [];
19848
- if (existsSync7(jobsDir)) {
20153
+ if (existsSync9(jobsDir)) {
19849
20154
  const supervisor = new Supervisor({
19850
20155
  runner: null,
19851
20156
  runOptions: null,
@@ -19896,60 +20201,62 @@ async function run8() {
19896
20201
  return;
19897
20202
  }
19898
20203
  console.log(`
19899
- ${bold6("specialists status")}
20204
+ ${bold8("specialists status")}
19900
20205
  `);
19901
20206
  section("Specialists");
19902
20207
  if (allSpecialists.length === 0) {
19903
- warn(`no specialists found — run ${yellow5("specialists init")} to scaffold`);
20208
+ warn(`no specialists found — run ${yellow7("specialists init")} to scaffold`);
19904
20209
  } else {
19905
20210
  const byScope = allSpecialists.reduce((acc, s) => {
19906
20211
  acc[s.scope] = (acc[s.scope] ?? 0) + 1;
19907
20212
  return acc;
19908
20213
  }, {});
19909
20214
  const scopeSummary = Object.entries(byScope).map(([scope, n]) => `${n} ${scope}`).join(", ");
19910
- ok2(`${allSpecialists.length} found ${dim6(`(${scopeSummary})`)}`);
20215
+ ok2(`${allSpecialists.length} found ${dim8(`(${scopeSummary})`)}`);
19911
20216
  for (const s of allSpecialists) {
19912
20217
  const staleness = stalenessMap[s.name];
19913
20218
  if (staleness === "AGED") {
19914
- warn(`${s.name} ${red("AGED")} ${dim6(s.scope)}`);
20219
+ warn(`${s.name} ${red2("AGED")} ${dim8(s.scope)}`);
19915
20220
  } else if (staleness === "STALE") {
19916
- warn(`${s.name} ${yellow5("STALE")} ${dim6(s.scope)}`);
20221
+ warn(`${s.name} ${yellow7("STALE")} ${dim8(s.scope)}`);
19917
20222
  }
19918
20223
  }
19919
20224
  }
19920
20225
  section("pi (coding agent runtime)");
19921
20226
  if (!piInstalled) {
19922
- fail(`pi not installed — install ${yellow5("pi")} first`);
20227
+ fail(`pi not installed — install ${yellow7("pi")} first`);
19923
20228
  } else {
19924
20229
  const vStr = piVersion?.ok ? `v${piVersion.stdout}` : "unknown version";
19925
- const pStr = piProviders.size > 0 ? `${piProviders.size} provider${piProviders.size > 1 ? "s" : ""} active ${dim6(`(${[...piProviders].join(", ")})`)} ` : yellow5("no providers configured — run pi config");
20230
+ const pStr = piProviders.size > 0 ? `${piProviders.size} provider${piProviders.size > 1 ? "s" : ""} active ${dim8(`(${[...piProviders].join(", ")})`)} ` : yellow7("no providers configured — run pi config");
19926
20231
  ok2(`${vStr} — ${pStr}`);
19927
20232
  }
19928
20233
  section("beads (issue tracker)");
19929
20234
  if (!bdInstalled) {
19930
- fail(`bd not installed — install ${yellow5("bd")} first`);
20235
+ fail(`bd not installed — install ${yellow7("bd")} first`);
19931
20236
  } else {
19932
- ok2(`bd installed${bdVersion?.ok ? ` ${dim6(bdVersion.stdout)}` : ""}`);
20237
+ ok2(`bd installed${bdVersion?.ok ? ` ${dim8(bdVersion.stdout)}` : ""}`);
19933
20238
  if (beadsPresent) {
19934
20239
  ok2(".beads/ present in project");
19935
20240
  } else {
19936
- warn(`.beads/ not found — run ${yellow5("bd init")} to enable issue tracking`);
20241
+ warn(`.beads/ not found — run ${yellow7("bd init")} to enable issue tracking`);
19937
20242
  }
19938
20243
  }
19939
20244
  section("MCP");
19940
20245
  if (!specialistsBin.ok) {
19941
- fail(`specialists not installed globally — run ${yellow5("npm install -g @jaggerxtrm/specialists")}`);
20246
+ fail(`specialists not installed globally — run ${yellow7("npm install -g @jaggerxtrm/specialists")}`);
19942
20247
  } else {
19943
- ok2(`specialists binary installed ${dim6(specialistsBin.stdout)}`);
20248
+ ok2(`specialists binary installed ${dim8(specialistsBin.stdout)}`);
19944
20249
  info(`verify registration: claude mcp get specialists`);
19945
20250
  info(`re-register: specialists install`);
19946
20251
  }
19947
- if (jobs.length > 0) {
19948
- section("Active Jobs");
20252
+ section("Active Jobs");
20253
+ if (jobs.length === 0) {
20254
+ info(" (none)");
20255
+ } else {
19949
20256
  for (const job of jobs) {
19950
20257
  const elapsed = formatElapsed2(job);
19951
- const detail = job.status === "error" ? red(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim6(`tool: ${job.current_tool}`) : dim6(job.current_event ?? "");
19952
- console.log(` ${dim6(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
20258
+ const detail = job.status === "error" ? red2(job.error?.slice(0, 40) ?? "error") : job.current_tool ? dim8(`tool: ${job.current_tool}`) : dim8(job.current_event ?? "");
20259
+ console.log(` ${dim8(job.id)} ${job.specialist.padEnd(20)} ${statusColor(job.status).padEnd(7)} ${elapsed.padStart(6)} ${detail}`);
19953
20260
  }
19954
20261
  }
19955
20262
  console.log();
@@ -19963,51 +20270,57 @@ var init_status = __esm(() => {
19963
20270
  // src/cli/result.ts
19964
20271
  var exports_result = {};
19965
20272
  __export(exports_result, {
19966
- run: () => run9
20273
+ run: () => run10
19967
20274
  });
19968
- import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
19969
- import { join as join12 } from "node:path";
19970
- async function run9() {
20275
+ import { existsSync as existsSync10, readFileSync as readFileSync5 } from "node:fs";
20276
+ import { join as join13 } from "node:path";
20277
+ async function run10() {
19971
20278
  const jobId = process.argv[3];
19972
20279
  if (!jobId) {
19973
20280
  console.error("Usage: specialists|sp result <job-id>");
19974
20281
  process.exit(1);
19975
20282
  }
19976
- const jobsDir = join12(process.cwd(), ".specialists", "jobs");
20283
+ const jobsDir = join13(process.cwd(), ".specialists", "jobs");
19977
20284
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
19978
20285
  const status = supervisor.readStatus(jobId);
19979
20286
  if (!status) {
19980
20287
  console.error(`No job found: ${jobId}`);
19981
20288
  process.exit(1);
19982
20289
  }
20290
+ const resultPath = join13(jobsDir, jobId, "result.txt");
19983
20291
  if (status.status === "running" || status.status === "starting") {
19984
- process.stderr.write(`${dim7(`Job ${jobId} is still ${status.status}. Use 'specialists feed --job ${jobId}' to follow.`)}
20292
+ if (!existsSync10(resultPath)) {
20293
+ process.stderr.write(`${dim9(`Job ${jobId} is still ${status.status}. Use 'specialists feed --job ${jobId}' to follow.`)}
19985
20294
  `);
19986
- process.exit(1);
20295
+ process.exit(1);
20296
+ }
20297
+ process.stderr.write(`${dim9(`Job ${jobId} is currently ${status.status}. Showing last completed output while it continues.`)}
20298
+ `);
20299
+ process.stdout.write(readFileSync5(resultPath, "utf-8"));
20300
+ return;
19987
20301
  }
19988
20302
  if (status.status === "error") {
19989
- process.stderr.write(`${red2(`Job ${jobId} failed:`)} ${status.error ?? "unknown error"}
20303
+ process.stderr.write(`${red3(`Job ${jobId} failed:`)} ${status.error ?? "unknown error"}
19990
20304
  `);
19991
20305
  process.exit(1);
19992
20306
  }
19993
- const resultPath = join12(jobsDir, jobId, "result.txt");
19994
- if (!existsSync8(resultPath)) {
20307
+ if (!existsSync10(resultPath)) {
19995
20308
  console.error(`Result file not found for job ${jobId}`);
19996
20309
  process.exit(1);
19997
20310
  }
19998
20311
  process.stdout.write(readFileSync5(resultPath, "utf-8"));
19999
20312
  }
20000
- var dim7 = (s) => `\x1B[2m${s}\x1B[0m`, red2 = (s) => `\x1B[31m${s}\x1B[0m`;
20313
+ var dim9 = (s) => `\x1B[2m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
20001
20314
  var init_result = __esm(() => {
20002
20315
  init_supervisor();
20003
20316
  });
20004
20317
 
20005
20318
  // src/specialist/timeline-query.ts
20006
- import { existsSync as existsSync9, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
20007
- import { join as join13 } from "node:path";
20319
+ import { existsSync as existsSync11, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
20320
+ import { join as join14 } from "node:path";
20008
20321
  function readJobEvents(jobDir) {
20009
- const eventsPath = join13(jobDir, "events.jsonl");
20010
- if (!existsSync9(eventsPath))
20322
+ const eventsPath = join14(jobDir, "events.jsonl");
20323
+ if (!existsSync11(eventsPath))
20011
20324
  return [];
20012
20325
  const content = readFileSync6(eventsPath, "utf-8");
20013
20326
  const lines = content.split(`
@@ -20022,15 +20335,15 @@ function readJobEvents(jobDir) {
20022
20335
  return events;
20023
20336
  }
20024
20337
  function readJobEventsById(jobsDir, jobId) {
20025
- return readJobEvents(join13(jobsDir, jobId));
20338
+ return readJobEvents(join14(jobsDir, jobId));
20026
20339
  }
20027
20340
  function readAllJobEvents(jobsDir) {
20028
- if (!existsSync9(jobsDir))
20341
+ if (!existsSync11(jobsDir))
20029
20342
  return [];
20030
20343
  const batches = [];
20031
20344
  const entries = readdirSync3(jobsDir);
20032
20345
  for (const entry of entries) {
20033
- const jobDir = join13(jobsDir, entry);
20346
+ const jobDir = join14(jobsDir, entry);
20034
20347
  try {
20035
20348
  const stat2 = __require("node:fs").statSync(jobDir);
20036
20349
  if (!stat2.isDirectory())
@@ -20039,10 +20352,10 @@ function readAllJobEvents(jobsDir) {
20039
20352
  continue;
20040
20353
  }
20041
20354
  const jobId = entry;
20042
- const statusPath = join13(jobDir, "status.json");
20355
+ const statusPath = join14(jobDir, "status.json");
20043
20356
  let specialist = "unknown";
20044
20357
  let beadId;
20045
- if (existsSync9(statusPath)) {
20358
+ if (existsSync11(statusPath)) {
20046
20359
  try {
20047
20360
  const status = JSON.parse(readFileSync6(statusPath, "utf-8"));
20048
20361
  specialist = status.specialist ?? "unknown";
@@ -20105,10 +20418,10 @@ var init_timeline_query = __esm(() => {
20105
20418
  // src/cli/feed.ts
20106
20419
  var exports_feed = {};
20107
20420
  __export(exports_feed, {
20108
- run: () => run10
20421
+ run: () => run11
20109
20422
  });
20110
- import { existsSync as existsSync10 } from "node:fs";
20111
- import { join as join14 } from "node:path";
20423
+ import { existsSync as existsSync12 } from "node:fs";
20424
+ import { join as join15 } from "node:path";
20112
20425
  function getHumanEventKey(event) {
20113
20426
  switch (event.type) {
20114
20427
  case "meta":
@@ -20119,6 +20432,10 @@ function getHumanEventKey(event) {
20119
20432
  return "text";
20120
20433
  case "thinking":
20121
20434
  return "thinking";
20435
+ case "message":
20436
+ return `message:${event.role}:${event.phase}`;
20437
+ case "turn":
20438
+ return `turn:${event.phase}`;
20122
20439
  case "run_start":
20123
20440
  return `run_start:${event.specialist}:${event.bead_id ?? ""}`;
20124
20441
  case "run_complete":
@@ -20156,7 +20473,7 @@ function parseSince(value) {
20156
20473
  }
20157
20474
  return;
20158
20475
  }
20159
- function parseArgs5(argv) {
20476
+ function parseArgs6(argv) {
20160
20477
  let jobId;
20161
20478
  let specialist;
20162
20479
  let since;
@@ -20201,7 +20518,7 @@ function parseArgs5(argv) {
20201
20518
  function printSnapshot(merged, options) {
20202
20519
  if (merged.length === 0) {
20203
20520
  if (!options.json)
20204
- console.log(dim6("No events found."));
20521
+ console.log(dim8("No events found."));
20205
20522
  return;
20206
20523
  }
20207
20524
  const colorMap = new JobColorMap;
@@ -20247,13 +20564,13 @@ async function followMerged(jobsDir, options) {
20247
20564
  const initialBatchCount = filteredBatches().length;
20248
20565
  if (!options.forever && initialBatchCount > 0 && completedJobs.size === initialBatchCount) {
20249
20566
  if (!options.json) {
20250
- process.stderr.write(dim6(`All jobs complete.
20567
+ process.stderr.write(dim8(`All jobs complete.
20251
20568
  `));
20252
20569
  }
20253
20570
  return;
20254
20571
  }
20255
20572
  if (!options.json) {
20256
- process.stderr.write(dim6(`Following... (Ctrl+C to stop)
20573
+ process.stderr.write(dim8(`Following... (Ctrl+C to stop)
20257
20574
  `));
20258
20575
  }
20259
20576
  const lastPrintedEventKey = new Map;
@@ -20300,11 +20617,37 @@ async function followMerged(jobsDir, options) {
20300
20617
  }, 500);
20301
20618
  });
20302
20619
  }
20303
- async function run10() {
20304
- const options = parseArgs5(process.argv.slice(3));
20305
- const jobsDir = join14(process.cwd(), ".specialists", "jobs");
20306
- if (!existsSync10(jobsDir)) {
20307
- console.log(dim6("No jobs directory found."));
20620
+ function showUsage() {
20621
+ console.log(`Usage: specialists feed <job-id> [options]
20622
+ specialists feed -f [--forever]
20623
+
20624
+ Read background job events.
20625
+
20626
+ Modes:
20627
+ specialists feed <job-id> Replay events for one job
20628
+ specialists feed <job-id> -f Follow one job until completion
20629
+ specialists feed -f Follow all jobs globally
20630
+
20631
+ Options:
20632
+ -f, --follow Follow live updates
20633
+ --forever Keep following in global mode even when all jobs complete
20634
+
20635
+ Examples:
20636
+ specialists feed 49adda
20637
+ specialists feed 49adda --follow
20638
+ specialists feed -f
20639
+ specialists feed -f --forever
20640
+ `);
20641
+ }
20642
+ async function run11() {
20643
+ const options = parseArgs6(process.argv.slice(3));
20644
+ if (!options.jobId && !options.follow) {
20645
+ showUsage();
20646
+ process.exit(1);
20647
+ }
20648
+ const jobsDir = join15(process.cwd(), ".specialists", "jobs");
20649
+ if (!existsSync12(jobsDir)) {
20650
+ console.log(dim8("No jobs directory found."));
20308
20651
  return;
20309
20652
  }
20310
20653
  if (options.follow) {
@@ -20328,11 +20671,11 @@ var init_feed = __esm(() => {
20328
20671
  // src/cli/poll.ts
20329
20672
  var exports_poll = {};
20330
20673
  __export(exports_poll, {
20331
- run: () => run11
20674
+ run: () => run12
20332
20675
  });
20333
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
20334
- import { join as join15 } from "node:path";
20335
- function parseArgs6(argv) {
20676
+ import { existsSync as existsSync13, readFileSync as readFileSync7 } from "node:fs";
20677
+ import { join as join16 } from "node:path";
20678
+ function parseArgs7(argv) {
20336
20679
  let jobId;
20337
20680
  let cursor = 0;
20338
20681
  let json = false;
@@ -20357,11 +20700,11 @@ function parseArgs6(argv) {
20357
20700
  }
20358
20701
  return { jobId, cursor, json };
20359
20702
  }
20360
- async function run11() {
20361
- const { jobId, cursor, json } = parseArgs6(process.argv.slice(3));
20362
- const jobsDir = join15(process.cwd(), ".specialists", "jobs");
20363
- const jobDir = join15(jobsDir, jobId);
20364
- if (!existsSync11(jobDir)) {
20703
+ async function run12() {
20704
+ const { jobId, cursor, json } = parseArgs7(process.argv.slice(3));
20705
+ const jobsDir = join16(process.cwd(), ".specialists", "jobs");
20706
+ const jobDir = join16(jobsDir, jobId);
20707
+ if (!existsSync13(jobDir)) {
20365
20708
  const result2 = {
20366
20709
  job_id: jobId,
20367
20710
  status: "error",
@@ -20375,16 +20718,16 @@ async function run11() {
20375
20718
  console.log(JSON.stringify(result2));
20376
20719
  process.exit(1);
20377
20720
  }
20378
- const statusPath = join15(jobDir, "status.json");
20721
+ const statusPath = join16(jobDir, "status.json");
20379
20722
  let status = null;
20380
- if (existsSync11(statusPath)) {
20723
+ if (existsSync13(statusPath)) {
20381
20724
  try {
20382
20725
  status = JSON.parse(readFileSync7(statusPath, "utf-8"));
20383
20726
  } catch {}
20384
20727
  }
20385
- const resultPath = join15(jobDir, "result.txt");
20728
+ const resultPath = join16(jobDir, "result.txt");
20386
20729
  let output = "";
20387
- if (existsSync11(resultPath)) {
20730
+ if (existsSync13(resultPath)) {
20388
20731
  try {
20389
20732
  output = readFileSync7(resultPath, "utf-8");
20390
20733
  } catch {}
@@ -20439,18 +20782,18 @@ var init_poll = __esm(() => {
20439
20782
  // src/cli/steer.ts
20440
20783
  var exports_steer = {};
20441
20784
  __export(exports_steer, {
20442
- run: () => run12
20785
+ run: () => run13
20443
20786
  });
20444
- import { join as join16 } from "node:path";
20787
+ import { join as join17 } from "node:path";
20445
20788
  import { writeFileSync as writeFileSync6 } from "node:fs";
20446
- async function run12() {
20789
+ async function run13() {
20447
20790
  const jobId = process.argv[3];
20448
20791
  const message = process.argv[4];
20449
20792
  if (!jobId || !message) {
20450
20793
  console.error('Usage: specialists|sp steer <job-id> "<message>"');
20451
20794
  process.exit(1);
20452
20795
  }
20453
- const jobsDir = join16(process.cwd(), ".specialists", "jobs");
20796
+ const jobsDir = join17(process.cwd(), ".specialists", "jobs");
20454
20797
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
20455
20798
  const status = supervisor.readStatus(jobId);
20456
20799
  if (!status) {
@@ -20463,7 +20806,7 @@ async function run12() {
20463
20806
  process.exit(1);
20464
20807
  }
20465
20808
  if (!status.fifo_path) {
20466
- process.stderr.write(`${red3("Error:")} Job ${jobId} has no steer pipe.
20809
+ process.stderr.write(`${red4("Error:")} Job ${jobId} has no steer pipe.
20467
20810
  `);
20468
20811
  process.stderr.write(`Only jobs started with --background support mid-run steering.
20469
20812
  `);
@@ -20473,15 +20816,15 @@ async function run12() {
20473
20816
  const payload = JSON.stringify({ type: "steer", message }) + `
20474
20817
  `;
20475
20818
  writeFileSync6(status.fifo_path, payload, { flag: "a" });
20476
- process.stdout.write(`${green7("✓")} Steer message sent to job ${jobId}
20819
+ process.stdout.write(`${green8("✓")} Steer message sent to job ${jobId}
20477
20820
  `);
20478
20821
  } catch (err) {
20479
- process.stderr.write(`${red3("Error:")} Failed to write to steer pipe: ${err?.message}
20822
+ process.stderr.write(`${red4("Error:")} Failed to write to steer pipe: ${err?.message}
20480
20823
  `);
20481
20824
  process.exit(1);
20482
20825
  }
20483
20826
  }
20484
- var green7 = (s) => `\x1B[32m${s}\x1B[0m`, red3 = (s) => `\x1B[31m${s}\x1B[0m`;
20827
+ var green8 = (s) => `\x1B[32m${s}\x1B[0m`, red4 = (s) => `\x1B[31m${s}\x1B[0m`;
20485
20828
  var init_steer = __esm(() => {
20486
20829
  init_supervisor();
20487
20830
  });
@@ -20489,18 +20832,18 @@ var init_steer = __esm(() => {
20489
20832
  // src/cli/follow-up.ts
20490
20833
  var exports_follow_up = {};
20491
20834
  __export(exports_follow_up, {
20492
- run: () => run13
20835
+ run: () => run14
20493
20836
  });
20494
- import { join as join17 } from "node:path";
20837
+ import { join as join18 } from "node:path";
20495
20838
  import { writeFileSync as writeFileSync7 } from "node:fs";
20496
- async function run13() {
20839
+ async function run14() {
20497
20840
  const jobId = process.argv[3];
20498
20841
  const message = process.argv[4];
20499
20842
  if (!jobId || !message) {
20500
20843
  console.error('Usage: specialists|sp follow-up <job-id> "<message>"');
20501
20844
  process.exit(1);
20502
20845
  }
20503
- const jobsDir = join17(process.cwd(), ".specialists", "jobs");
20846
+ const jobsDir = join18(process.cwd(), ".specialists", "jobs");
20504
20847
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
20505
20848
  const status = supervisor.readStatus(jobId);
20506
20849
  if (!status) {
@@ -20508,14 +20851,14 @@ async function run13() {
20508
20851
  process.exit(1);
20509
20852
  }
20510
20853
  if (status.status !== "waiting") {
20511
- process.stderr.write(`${red4("Error:")} Job ${jobId} is not in waiting state (status: ${status.status}).
20854
+ process.stderr.write(`${red5("Error:")} Job ${jobId} is not in waiting state (status: ${status.status}).
20512
20855
  `);
20513
20856
  process.stderr.write(`Only jobs started with --keep-alive and --background support follow-up prompts.
20514
20857
  `);
20515
20858
  process.exit(1);
20516
20859
  }
20517
20860
  if (!status.fifo_path) {
20518
- process.stderr.write(`${red4("Error:")} Job ${jobId} has no steer pipe.
20861
+ process.stderr.write(`${red5("Error:")} Job ${jobId} has no steer pipe.
20519
20862
  `);
20520
20863
  process.exit(1);
20521
20864
  }
@@ -20523,17 +20866,17 @@ async function run13() {
20523
20866
  const payload = JSON.stringify({ type: "prompt", message }) + `
20524
20867
  `;
20525
20868
  writeFileSync7(status.fifo_path, payload, { flag: "a" });
20526
- process.stdout.write(`${green8("✓")} Follow-up sent to job ${jobId}
20869
+ process.stdout.write(`${green9("✓")} Follow-up sent to job ${jobId}
20527
20870
  `);
20528
20871
  process.stdout.write(` Use 'specialists feed ${jobId} --follow' to watch the response.
20529
20872
  `);
20530
20873
  } catch (err) {
20531
- process.stderr.write(`${red4("Error:")} Failed to write to steer pipe: ${err?.message}
20874
+ process.stderr.write(`${red5("Error:")} Failed to write to steer pipe: ${err?.message}
20532
20875
  `);
20533
20876
  process.exit(1);
20534
20877
  }
20535
20878
  }
20536
- var green8 = (s) => `\x1B[32m${s}\x1B[0m`, red4 = (s) => `\x1B[31m${s}\x1B[0m`;
20879
+ var green9 = (s) => `\x1B[32m${s}\x1B[0m`, red5 = (s) => `\x1B[31m${s}\x1B[0m`;
20537
20880
  var init_follow_up = __esm(() => {
20538
20881
  init_supervisor();
20539
20882
  });
@@ -20541,16 +20884,16 @@ var init_follow_up = __esm(() => {
20541
20884
  // src/cli/stop.ts
20542
20885
  var exports_stop = {};
20543
20886
  __export(exports_stop, {
20544
- run: () => run14
20887
+ run: () => run15
20545
20888
  });
20546
- import { join as join18 } from "node:path";
20547
- async function run14() {
20889
+ import { join as join19 } from "node:path";
20890
+ async function run15() {
20548
20891
  const jobId = process.argv[3];
20549
20892
  if (!jobId) {
20550
20893
  console.error("Usage: specialists|sp stop <job-id>");
20551
20894
  process.exit(1);
20552
20895
  }
20553
- const jobsDir = join18(process.cwd(), ".specialists", "jobs");
20896
+ const jobsDir = join19(process.cwd(), ".specialists", "jobs");
20554
20897
  const supervisor = new Supervisor({ runner: null, runOptions: null, jobsDir });
20555
20898
  const status = supervisor.readStatus(jobId);
20556
20899
  if (!status) {
@@ -20558,31 +20901,31 @@ async function run14() {
20558
20901
  process.exit(1);
20559
20902
  }
20560
20903
  if (status.status === "done" || status.status === "error") {
20561
- process.stderr.write(`${dim8(`Job ${jobId} is already ${status.status}.`)}
20904
+ process.stderr.write(`${dim10(`Job ${jobId} is already ${status.status}.`)}
20562
20905
  `);
20563
20906
  return;
20564
20907
  }
20565
20908
  if (!status.pid) {
20566
- process.stderr.write(`${red5(`No PID recorded for job ${jobId}.`)}
20909
+ process.stderr.write(`${red6(`No PID recorded for job ${jobId}.`)}
20567
20910
  `);
20568
20911
  process.exit(1);
20569
20912
  }
20570
20913
  try {
20571
20914
  process.kill(status.pid, "SIGTERM");
20572
- process.stdout.write(`${green9("✓")} Sent SIGTERM to PID ${status.pid} (job ${jobId})
20915
+ process.stdout.write(`${green10("✓")} Sent SIGTERM to PID ${status.pid} (job ${jobId})
20573
20916
  `);
20574
20917
  } catch (err) {
20575
20918
  if (err.code === "ESRCH") {
20576
- process.stderr.write(`${red5(`Process ${status.pid} not found.`)} Job may have already completed.
20919
+ process.stderr.write(`${red6(`Process ${status.pid} not found.`)} Job may have already completed.
20577
20920
  `);
20578
20921
  } else {
20579
- process.stderr.write(`${red5("Error:")} ${err.message}
20922
+ process.stderr.write(`${red6("Error:")} ${err.message}
20580
20923
  `);
20581
20924
  process.exit(1);
20582
20925
  }
20583
20926
  }
20584
20927
  }
20585
- var green9 = (s) => `\x1B[32m${s}\x1B[0m`, red5 = (s) => `\x1B[31m${s}\x1B[0m`, dim8 = (s) => `\x1B[2m${s}\x1B[0m`;
20928
+ var green10 = (s) => `\x1B[32m${s}\x1B[0m`, red6 = (s) => `\x1B[31m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`;
20586
20929
  var init_stop = __esm(() => {
20587
20930
  init_supervisor();
20588
20931
  });
@@ -20590,33 +20933,33 @@ var init_stop = __esm(() => {
20590
20933
  // src/cli/quickstart.ts
20591
20934
  var exports_quickstart = {};
20592
20935
  __export(exports_quickstart, {
20593
- run: () => run15
20936
+ run: () => run16
20594
20937
  });
20595
20938
  function section2(title) {
20596
20939
  const bar = "─".repeat(60);
20597
20940
  return `
20598
- ${bold7(cyan5(title))}
20599
- ${dim9(bar)}`;
20941
+ ${bold9(cyan6(title))}
20942
+ ${dim11(bar)}`;
20600
20943
  }
20601
20944
  function cmd2(s) {
20602
- return yellow6(s);
20945
+ return yellow8(s);
20603
20946
  }
20604
20947
  function flag(s) {
20605
- return green10(s);
20948
+ return green11(s);
20606
20949
  }
20607
- async function run15() {
20950
+ async function run16() {
20608
20951
  const lines = [
20609
20952
  "",
20610
- bold7("specialists · Quick Start Guide"),
20611
- dim9("One MCP server. Multiple AI backends. Intelligent orchestration."),
20612
- dim9("Tip: sp is a shorter alias — sp run, sp list, sp feed etc. work identically."),
20953
+ bold9("specialists · Quick Start Guide"),
20954
+ dim11("One MCP server. Multiple AI backends. Intelligent orchestration."),
20955
+ dim11("Tip: sp is a shorter alias — sp run, sp list, sp feed etc. work identically."),
20613
20956
  ""
20614
20957
  ];
20615
20958
  lines.push(section2("1. Installation"));
20616
20959
  lines.push("");
20617
20960
  lines.push(` ${cmd2("npm install -g @jaggerxtrm/specialists")} # install globally`);
20618
- lines.push(` ${cmd2("specialists install")} # project setup:`);
20619
- lines.push(` ${dim9(" # checks pi · bd · xt, then wires MCP + hooks")}`);
20961
+ lines.push(` ${cmd2("specialists init")} # project setup:`);
20962
+ lines.push(` ${dim11(" # creates dirs, wires MCP + hooks, injects context")}`);
20620
20963
  lines.push("");
20621
20964
  lines.push(` Verify everything is healthy:`);
20622
20965
  lines.push(` ${cmd2("specialists status")} # shows pi, beads, MCP, active jobs`);
@@ -20627,9 +20970,9 @@ async function run15() {
20627
20970
  lines.push(` ${cmd2("specialists init")} # creates specialists/, .specialists/, AGENTS.md`);
20628
20971
  lines.push("");
20629
20972
  lines.push(` What this creates:`);
20630
- lines.push(` ${dim9("specialists/")} — put your .specialist.yaml files here`);
20631
- lines.push(` ${dim9(".specialists/")} — runtime data (jobs/, ready/) — gitignored`);
20632
- lines.push(` ${dim9("AGENTS.md")} — context block injected into Claude sessions`);
20973
+ lines.push(` ${dim11("specialists/")} — put your .specialist.yaml files here`);
20974
+ lines.push(` ${dim11(".specialists/")} — runtime data (jobs/, ready/) — gitignored`);
20975
+ lines.push(` ${dim11("AGENTS.md")} — context block injected into Claude sessions`);
20633
20976
  lines.push("");
20634
20977
  lines.push(section2("3. Discover Specialists"));
20635
20978
  lines.push("");
@@ -20645,66 +20988,64 @@ async function run15() {
20645
20988
  lines.push("");
20646
20989
  lines.push(section2("4. Running a Specialist"));
20647
20990
  lines.push("");
20648
- lines.push(` ${bold7("Foreground")} (streams output to stdout):`);
20649
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"Review src/api.ts for security issues"')}`);
20650
- lines.push("");
20651
- lines.push(` ${bold7("Background")} (returns a job ID immediately):`);
20652
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"..."')} ${flag("--background")}`);
20653
- lines.push(` ${dim9(" # → Job started: job_a1b2c3d4")}`);
20991
+ lines.push(` ${bold9("Foreground")} (streams output to stdout):`);
20992
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim11('"Review src/api.ts for security issues"')}`);
20654
20993
  lines.push("");
20655
- lines.push(` ${bold7("Follow")} (background + stream live output in one command):`);
20656
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--prompt")} ${dim9('"..."')} ${flag("--follow")}`);
20657
- lines.push(` ${dim9(" # starts in background, streams output live, exits when complete")}`);
20994
+ lines.push(` ${bold9("Tracked run")} (linked to a beads issue for workflow integration):`);
20995
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--bead")} ${dim11("unitAI-abc")}`);
20996
+ lines.push(` ${dim11(" # uses bead description as prompt, tracks result in issue")}`);
20658
20997
  lines.push("");
20659
20998
  lines.push(` Override model for one run:`);
20660
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${dim9("anthropic/claude-opus-4-6")} ${flag("--prompt")} ${dim9('"..."')}`);
20999
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--model")} ${dim11("anthropic/claude-opus-4-6")} ${flag("--prompt")} ${dim11('"..."')}`);
20661
21000
  lines.push("");
20662
21001
  lines.push(` Run without beads issue tracking:`);
20663
- lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${dim9('"..."')}`);
21002
+ lines.push(` ${cmd2("specialists run code-review")} ${flag("--no-beads")} ${flag("--prompt")} ${dim11('"..."')}`);
20664
21003
  lines.push("");
20665
21004
  lines.push(` Pipe a prompt from stdin:`);
20666
21005
  lines.push(` ${cmd2("cat my-brief.md | specialists run code-review")}`);
20667
21006
  lines.push("");
20668
21007
  lines.push(section2("5. Background Job Lifecycle"));
20669
21008
  lines.push("");
20670
- lines.push(` ${bold7("Watch progress")} stream events as they arrive:`);
21009
+ lines.push(` Use Claude Code's native backgrounding or run in a separate terminal.`);
21010
+ lines.push("");
21011
+ lines.push(` ${bold9("Watch progress")} — stream events as they arrive:`);
20671
21012
  lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} # print events so far`);
20672
21013
  lines.push(` ${cmd2("specialists feed job_a1b2c3d4")} ${flag("--follow")} # tail and stream live updates`);
20673
21014
  lines.push("");
20674
- lines.push(` ${bold7("Read results")} — print the final output:`);
21015
+ lines.push(` ${bold9("Read results")} — print the final output:`);
20675
21016
  lines.push(` ${cmd2("specialists result job_a1b2c3d4")} # exits 1 if still running`);
20676
21017
  lines.push("");
20677
- lines.push(` ${bold7("Steer a running job")} — redirect the agent mid-run without cancelling:`);
21018
+ lines.push(` ${bold9("Steer a running job")} — redirect the agent mid-run without cancelling:`);
20678
21019
  lines.push(` ${cmd2("specialists steer job_a1b2c3d4")} ${flag('"focus only on supervisor.ts"')}`);
20679
- lines.push(` ${dim9(" # delivered after current tool calls finish, before the next LLM call")}`);
21020
+ lines.push(` ${dim11(" # delivered after current tool calls finish, before the next LLM call")}`);
20680
21021
  lines.push("");
20681
- lines.push(` ${bold7("Keep-alive multi-turn")} — start with ${flag("--keep-alive")}, then follow up:`);
20682
- lines.push(` ${cmd2("specialists run bug-hunt")} ${flag("--bead unitAI-abc --keep-alive --background")}`);
20683
- lines.push(` ${dim9(" # → Job started: a1b2c3 (status: waiting after first turn)")}`);
21022
+ lines.push(` ${bold9("Keep-alive multi-turn")} — start with ${flag("--keep-alive")}, then follow up:`);
21023
+ lines.push(` ${cmd2("specialists run debugger")} ${flag("--bead unitAI-abc --keep-alive")}`);
21024
+ lines.push(` ${dim11(" # → status: waiting after first turn")}`);
20684
21025
  lines.push(` ${cmd2("specialists result a1b2c3")} # read first turn`);
20685
21026
  lines.push(` ${cmd2("specialists follow-up a1b2c3")} ${flag('"now write the fix"')} # next turn, same Pi context`);
20686
21027
  lines.push(` ${cmd2("specialists feed a1b2c3")} ${flag("--follow")} # watch response`);
20687
21028
  lines.push("");
20688
- lines.push(` ${bold7("Cancel a job")}:`);
21029
+ lines.push(` ${bold9("Cancel a job")}:`);
20689
21030
  lines.push(` ${cmd2("specialists stop job_a1b2c3d4")} # sends SIGTERM to the agent process`);
20690
21031
  lines.push("");
20691
- lines.push(` ${bold7("Job files")} in ${dim9(".specialists/jobs/<job-id>/")}:`);
20692
- lines.push(` ${dim9("status.json")} — id, specialist, status, pid, started_at, elapsed_s, current_tool`);
20693
- lines.push(` ${dim9("events.jsonl")} — one JSON event per line (tool_use, text, agent_end, error …)`);
20694
- lines.push(` ${dim9("result.txt")} — final output (written when status=done)`);
20695
- lines.push(` ${dim9("steer.pipe")} — named FIFO for mid-run steering (removed on job completion)`);
21032
+ lines.push(` ${bold9("Job files")} in ${dim11(".specialists/jobs/<job-id>/")}:`);
21033
+ lines.push(` ${dim11("status.json")} — id, specialist, status, pid, started_at, elapsed_s, current_tool`);
21034
+ lines.push(` ${dim11("events.jsonl")} — one JSON event per line (tool_use, text, agent_end, error …)`);
21035
+ lines.push(` ${dim11("result.txt")} — final output (written when status=done)`);
21036
+ lines.push(` ${dim11("steer.pipe")} — named FIFO for mid-run steering (removed on job completion)`);
20696
21037
  lines.push("");
20697
21038
  lines.push(section2("6. Editing Specialists"));
20698
21039
  lines.push("");
20699
21040
  lines.push(` Change a field without opening the YAML manually:`);
20700
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("anthropic/claude-sonnet-4-6")}`);
20701
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${dim9('"Updated description"')}`);
20702
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${dim9("120000")}`);
20703
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${dim9("HIGH")}`);
20704
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${dim9("analysis,security,review")}`);
21041
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim11("anthropic/claude-sonnet-4-6")}`);
21042
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--description")} ${dim11('"Updated description"')}`);
21043
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--timeout")} ${dim11("120000")}`);
21044
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--permission")} ${dim11("HIGH")}`);
21045
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--tags")} ${dim11("analysis,security,review")}`);
20705
21046
  lines.push("");
20706
21047
  lines.push(` Preview without writing:`);
20707
- lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim9("...")} ${flag("--dry-run")}`);
21048
+ lines.push(` ${cmd2("specialists edit code-review")} ${flag("--model")} ${dim11("...")} ${flag("--dry-run")}`);
20708
21049
  lines.push("");
20709
21050
  lines.push(section2("7. .specialist.yaml Schema"));
20710
21051
  lines.push("");
@@ -20751,100 +21092,98 @@ async function run15() {
20751
21092
  " priority: 2 # 0=critical … 4=backlog"
20752
21093
  ];
20753
21094
  for (const l of schemaLines) {
20754
- lines.push(` ${dim9(l)}`);
21095
+ lines.push(` ${dim11(l)}`);
20755
21096
  }
20756
21097
  lines.push("");
20757
21098
  lines.push(section2("8. Hook System"));
20758
21099
  lines.push("");
20759
- lines.push(` Specialists emits lifecycle events to ${dim9(".specialists/trace.jsonl")}:`);
21100
+ lines.push(` Specialists emits lifecycle events to ${dim11(".specialists/trace.jsonl")}:`);
20760
21101
  lines.push("");
20761
- lines.push(` ${bold7("Hook point")} ${bold7("When fired")}`);
20762
- lines.push(` ${yellow6("specialist:start")} before the agent session begins`);
20763
- lines.push(` ${yellow6("specialist:token")} on each streamed token (delta)`);
20764
- lines.push(` ${yellow6("specialist:done")} after successful completion`);
20765
- lines.push(` ${yellow6("specialist:error")} on failure or timeout`);
21102
+ lines.push(` ${bold9("Hook point")} ${bold9("When fired")}`);
21103
+ lines.push(` ${yellow8("specialist:start")} before the agent session begins`);
21104
+ lines.push(` ${yellow8("specialist:token")} on each streamed token (delta)`);
21105
+ lines.push(` ${yellow8("specialist:done")} after successful completion`);
21106
+ lines.push(` ${yellow8("specialist:error")} on failure or timeout`);
20766
21107
  lines.push("");
20767
21108
  lines.push(` Each event line in trace.jsonl:`);
20768
- lines.push(` ${dim9('{"t":"<ISO>","hook":"specialist:done","specialist":"code-review","durationMs":4120}')}`);
21109
+ lines.push(` ${dim11('{"t":"<ISO>","hook":"specialist:done","specialist":"code-review","durationMs":4120}')}`);
20769
21110
  lines.push("");
20770
21111
  lines.push(` Tail the trace file to observe all activity:`);
20771
21112
  lines.push(` ${cmd2("tail -f .specialists/trace.jsonl | jq .")}`);
20772
21113
  lines.push("");
20773
21114
  lines.push(section2("9. MCP Integration (Claude Code)"));
20774
21115
  lines.push("");
20775
- lines.push(` After ${cmd2("specialists install")}, these MCP tools are available to Claude:`);
21116
+ lines.push(` After ${cmd2("specialists init")}, these MCP tools are available to Claude:`);
20776
21117
  lines.push("");
20777
- lines.push(` ${bold7("specialist_init")} — bootstrap: bd init + list specialists`);
20778
- lines.push(` ${bold7("list_specialists")} — discover specialists (project/user/system)`);
20779
- lines.push(` ${bold7("use_specialist")} — full lifecycle: load → agents.md → run → output`);
20780
- lines.push(` ${bold7("run_parallel")} — concurrent or pipeline execution`);
20781
- lines.push(` ${bold7("start_specialist")} — async job start, returns job ID`);
20782
- lines.push(` ${bold7("poll_specialist")} — poll job status/output by ID`);
20783
- lines.push(` ${bold7("steer_specialist")} — send a mid-run message to a running job`);
20784
- lines.push(` ${bold7("follow_up_specialist")} — send a next-turn prompt to a keep-alive session`);
20785
- lines.push(` ${bold7("stop_specialist")} — cancel a running job by ID`);
20786
- lines.push(` ${bold7("specialist_status")} — circuit breaker health + staleness`);
21118
+ lines.push(` ${bold9("specialist_init")} — bootstrap: bd init + list specialists`);
21119
+ lines.push(` ${bold9("list_specialists")} — discover specialists (project/user/system)`);
21120
+ lines.push(` ${bold9("use_specialist")} — full lifecycle: load → agents.md → run → output`);
21121
+ lines.push(` ${bold9("run_parallel")} — concurrent or pipeline execution`);
21122
+ lines.push(` ${bold9("start_specialist")} — async job start, returns job ID`);
21123
+ lines.push(` ${bold9("poll_specialist")} — poll job status/output by ID`);
21124
+ lines.push(` ${bold9("steer_specialist")} — send a mid-run message to a running job`);
21125
+ lines.push(` ${bold9("follow_up_specialist")} — send a next-turn prompt to a keep-alive session`);
21126
+ lines.push(` ${bold9("stop_specialist")} — cancel a running job by ID`);
21127
+ lines.push(` ${bold9("specialist_status")} — circuit breaker health + staleness`);
20787
21128
  lines.push("");
20788
21129
  lines.push(section2("10. Common Workflows"));
20789
21130
  lines.push("");
20790
- lines.push(` ${bold7("Foreground review, save to file:")}`);
21131
+ lines.push(` ${bold9("Foreground review, save to file:")}`);
20791
21132
  lines.push(` ${cmd2('specialists run code-review --prompt "Audit src/" > review.md')}`);
20792
21133
  lines.push("");
20793
- lines.push(` ${bold7("Fire-and-forget, check later:")}`);
20794
- lines.push(` ${cmd2('specialists run deep-analysis --prompt "..." --background')}`);
20795
- lines.push(` ${cmd2("specialists feed <job-id> --follow")}`);
20796
- lines.push(` ${cmd2("specialists result <job-id> > analysis.md")}`);
21134
+ lines.push(` ${bold9("Tracked run with beads integration:")}`);
21135
+ lines.push(` ${cmd2("specialists run deep-analysis --bead unitAI-abc")}`);
21136
+ lines.push(` ${dim11(" # prompt from bead, result tracked in bead")}`);
20797
21137
  lines.push("");
20798
- lines.push(` ${bold7("Steer a job mid-run:")}`);
20799
- lines.push(` ${cmd2('specialists run deep-analysis --prompt "..." --background')}`);
21138
+ lines.push(` ${bold9("Steer a job mid-run:")}`);
20800
21139
  lines.push(` ${cmd2('specialists steer <job-id> "focus only on the auth module"')}`);
20801
21140
  lines.push(` ${cmd2("specialists result <job-id>")}`);
20802
21141
  lines.push("");
20803
- lines.push(` ${bold7("Multi-turn keep-alive (iterative work):")}`);
20804
- lines.push(` ${cmd2("specialists run bug-hunt --bead unitAI-abc --keep-alive --background")}`);
21142
+ lines.push(` ${bold9("Multi-turn keep-alive (iterative work):")}`);
21143
+ lines.push(` ${cmd2("specialists run debugger --bead unitAI-abc --keep-alive")}`);
20805
21144
  lines.push(` ${cmd2("specialists result <job-id>")}`);
20806
21145
  lines.push(` ${cmd2('specialists follow-up <job-id> "now write the fix for the root cause"')}`);
20807
21146
  lines.push(` ${cmd2("specialists feed <job-id> --follow")}`);
20808
21147
  lines.push("");
20809
- lines.push(` ${bold7("Override model for a single run:")}`);
21148
+ lines.push(` ${bold9("Override model for a single run:")}`);
20810
21149
  lines.push(` ${cmd2('specialists run code-review --model anthropic/claude-opus-4-6 --prompt "..."')}`);
20811
21150
  lines.push("");
20812
- lines.push(dim9("─".repeat(62)));
20813
- lines.push(` ${dim9("specialists help")} command list ${dim9("specialists <cmd> --help")} per-command flags`);
20814
- lines.push(` ${dim9("specialists status")} health check ${dim9("specialists models")} available models`);
21151
+ lines.push(dim11("─".repeat(62)));
21152
+ lines.push(` ${dim11("specialists help")} command list ${dim11("specialists <cmd> --help")} per-command flags`);
21153
+ lines.push(` ${dim11("specialists status")} health check ${dim11("specialists models")} available models`);
20815
21154
  lines.push("");
20816
21155
  console.log(lines.join(`
20817
21156
  `));
20818
21157
  }
20819
- var bold7 = (s) => `\x1B[1m${s}\x1B[0m`, dim9 = (s) => `\x1B[2m${s}\x1B[0m`, yellow6 = (s) => `\x1B[33m${s}\x1B[0m`, cyan5 = (s) => `\x1B[36m${s}\x1B[0m`, blue2 = (s) => `\x1B[34m${s}\x1B[0m`, green10 = (s) => `\x1B[32m${s}\x1B[0m`;
21158
+ var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, yellow8 = (s) => `\x1B[33m${s}\x1B[0m`, cyan6 = (s) => `\x1B[36m${s}\x1B[0m`, blue2 = (s) => `\x1B[34m${s}\x1B[0m`, green11 = (s) => `\x1B[32m${s}\x1B[0m`;
20820
21159
 
20821
21160
  // src/cli/doctor.ts
20822
21161
  var exports_doctor = {};
20823
21162
  __export(exports_doctor, {
20824
- run: () => run16
21163
+ run: () => run17
20825
21164
  });
20826
21165
  import { spawnSync as spawnSync7 } from "node:child_process";
20827
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
20828
- import { join as join19 } from "node:path";
21166
+ import { existsSync as existsSync14, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync4 } from "node:fs";
21167
+ import { join as join20 } from "node:path";
20829
21168
  function ok3(msg) {
20830
- console.log(` ${green11("✓")} ${msg}`);
21169
+ console.log(` ${green12("✓")} ${msg}`);
20831
21170
  }
20832
21171
  function warn2(msg) {
20833
- console.log(` ${yellow7("○")} ${msg}`);
21172
+ console.log(` ${yellow9("○")} ${msg}`);
20834
21173
  }
20835
21174
  function fail2(msg) {
20836
- console.log(` ${red6("✗")} ${msg}`);
21175
+ console.log(` ${red7("✗")} ${msg}`);
20837
21176
  }
20838
21177
  function fix(msg) {
20839
- console.log(` ${dim10("→ fix:")} ${yellow7(msg)}`);
21178
+ console.log(` ${dim12("→ fix:")} ${yellow9(msg)}`);
20840
21179
  }
20841
21180
  function hint(msg) {
20842
- console.log(` ${dim10(msg)}`);
21181
+ console.log(` ${dim12(msg)}`);
20843
21182
  }
20844
21183
  function section3(label) {
20845
21184
  const line = "─".repeat(Math.max(0, 38 - label.length));
20846
21185
  console.log(`
20847
- ${bold8(`── ${label} ${line}`)}`);
21186
+ ${bold10(`── ${label} ${line}`)}`);
20848
21187
  }
20849
21188
  function sp(bin, args) {
20850
21189
  const r = spawnSync7(bin, args, { encoding: "utf8", stdio: "pipe", timeout: 5000 });
@@ -20854,7 +21193,7 @@ function isInstalled2(bin) {
20854
21193
  return spawnSync7("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
20855
21194
  }
20856
21195
  function loadJson2(path) {
20857
- if (!existsSync12(path))
21196
+ if (!existsSync14(path))
20858
21197
  return null;
20859
21198
  try {
20860
21199
  return JSON.parse(readFileSync8(path, "utf8"));
@@ -20879,9 +21218,19 @@ function checkPi() {
20879
21218
  fix("pi config (add at least one API key)");
20880
21219
  return false;
20881
21220
  }
20882
- ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim10(`(${[...providers].join(", ")})`)}`);
21221
+ ok3(`pi ${vStr} — ${providers.size} provider${providers.size > 1 ? "s" : ""} active ${dim12(`(${[...providers].join(", ")})`)}`);
20883
21222
  return true;
20884
21223
  }
21224
+ function checkSpAlias() {
21225
+ section3("sp alias (specialists shortcut)");
21226
+ if (isInstalled2("sp")) {
21227
+ ok3("sp alias installed");
21228
+ return true;
21229
+ }
21230
+ fail2("sp alias not found in PATH");
21231
+ fix("npm install -g @jaggerxtrm/specialists@latest (reinstall to create symlink)");
21232
+ return false;
21233
+ }
20885
21234
  function checkBd() {
20886
21235
  section3("beads (issue tracker)");
20887
21236
  if (!isInstalled2("bd")) {
@@ -20889,8 +21238,8 @@ function checkBd() {
20889
21238
  fix("install beads (bd) first");
20890
21239
  return false;
20891
21240
  }
20892
- ok3(`bd installed ${dim10(sp("bd", ["--version"]).stdout || "")}`);
20893
- if (existsSync12(join19(CWD, ".beads")))
21241
+ ok3(`bd installed ${dim12(sp("bd", ["--version"]).stdout || "")}`);
21242
+ if (existsSync14(join20(CWD, ".beads")))
20894
21243
  ok3(".beads/ present in project");
20895
21244
  else
20896
21245
  warn2(".beads/ not found in project");
@@ -20903,16 +21252,16 @@ function checkXt() {
20903
21252
  fix("install xtrm-tools first");
20904
21253
  return false;
20905
21254
  }
20906
- ok3(`xt installed ${dim10(sp("xt", ["--version"]).stdout || "")}`);
21255
+ ok3(`xt installed ${dim12(sp("xt", ["--version"]).stdout || "")}`);
20907
21256
  return true;
20908
21257
  }
20909
21258
  function checkHooks() {
20910
21259
  section3("Claude Code hooks (2 expected)");
20911
21260
  let allPresent = true;
20912
21261
  for (const name of HOOK_NAMES) {
20913
- const dest = join19(HOOKS_DIR, name);
20914
- if (!existsSync12(dest)) {
20915
- fail2(`${name} ${red6("missing")}`);
21262
+ const dest = join20(HOOKS_DIR, name);
21263
+ if (!existsSync14(dest)) {
21264
+ fail2(`${name} ${red7("missing")}`);
20916
21265
  fix("specialists install");
20917
21266
  allPresent = false;
20918
21267
  } else {
@@ -20955,18 +21304,18 @@ function checkMCP() {
20955
21304
  }
20956
21305
  function checkRuntimeDirs() {
20957
21306
  section3(".specialists/ runtime directories");
20958
- const rootDir = join19(CWD, ".specialists");
20959
- const jobsDir = join19(rootDir, "jobs");
20960
- const readyDir = join19(rootDir, "ready");
21307
+ const rootDir = join20(CWD, ".specialists");
21308
+ const jobsDir = join20(rootDir, "jobs");
21309
+ const readyDir = join20(rootDir, "ready");
20961
21310
  let allOk = true;
20962
- if (!existsSync12(rootDir)) {
21311
+ if (!existsSync14(rootDir)) {
20963
21312
  warn2(".specialists/ not found in current project");
20964
21313
  fix("specialists init");
20965
21314
  allOk = false;
20966
21315
  } else {
20967
21316
  ok3(".specialists/ present");
20968
21317
  for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
20969
- if (!existsSync12(subDir)) {
21318
+ if (!existsSync14(subDir)) {
20970
21319
  warn2(`.specialists/${label}/ missing — auto-creating`);
20971
21320
  mkdirSync3(subDir, { recursive: true });
20972
21321
  ok3(`.specialists/${label}/ created`);
@@ -20979,8 +21328,8 @@ function checkRuntimeDirs() {
20979
21328
  }
20980
21329
  function checkZombieJobs() {
20981
21330
  section3("Background jobs");
20982
- const jobsDir = join19(CWD, ".specialists", "jobs");
20983
- if (!existsSync12(jobsDir)) {
21331
+ const jobsDir = join20(CWD, ".specialists", "jobs");
21332
+ if (!existsSync14(jobsDir)) {
20984
21333
  hint("No .specialists/jobs/ — skipping");
20985
21334
  return true;
20986
21335
  }
@@ -20998,8 +21347,8 @@ function checkZombieJobs() {
20998
21347
  let total = 0;
20999
21348
  let running = 0;
21000
21349
  for (const jobId of entries) {
21001
- const statusPath = join19(jobsDir, jobId, "status.json");
21002
- if (!existsSync12(statusPath))
21350
+ const statusPath = join20(jobsDir, jobId, "status.json");
21351
+ if (!existsSync14(statusPath))
21003
21352
  continue;
21004
21353
  try {
21005
21354
  const status = JSON.parse(readFileSync8(statusPath, "utf8"));
@@ -21016,7 +21365,7 @@ function checkZombieJobs() {
21016
21365
  running++;
21017
21366
  else {
21018
21367
  zombies++;
21019
- warn2(`${jobId} ${yellow7("ZOMBIE")} ${dim10(`pid ${pid} not found, status=${status.status}`)}`);
21368
+ warn2(`${jobId} ${yellow9("ZOMBIE")} ${dim12(`pid ${pid} not found, status=${status.status}`)}`);
21020
21369
  fix(`Edit .specialists/jobs/${jobId}/status.json → set "status": "error"`);
21021
21370
  }
21022
21371
  }
@@ -21029,11 +21378,12 @@ function checkZombieJobs() {
21029
21378
  }
21030
21379
  return zombies === 0;
21031
21380
  }
21032
- async function run16() {
21381
+ async function run17() {
21033
21382
  console.log(`
21034
- ${bold8("specialists doctor")}
21383
+ ${bold10("specialists doctor")}
21035
21384
  `);
21036
21385
  const piOk = checkPi();
21386
+ const spOk = checkSpAlias();
21037
21387
  const bdOk = checkBd();
21038
21388
  const xtOk = checkXt();
21039
21389
  const hooksOk = checkHooks();
@@ -21043,21 +21393,21 @@ ${bold8("specialists doctor")}
21043
21393
  const allOk = piOk && bdOk && xtOk && hooksOk && mcpOk && dirsOk && jobsOk;
21044
21394
  console.log("");
21045
21395
  if (allOk) {
21046
- console.log(` ${green11("✓")} ${bold8("All checks passed")} — specialists is healthy`);
21396
+ console.log(` ${green12("✓")} ${bold10("All checks passed")} — specialists is healthy`);
21047
21397
  } else {
21048
- console.log(` ${yellow7("○")} ${bold8("Some checks failed")} — follow the fix hints above`);
21049
- console.log(` ${dim10("specialists install fixes hook + MCP registration; pi, bd, and xt must be installed separately.")}`);
21398
+ console.log(` ${yellow9("○")} ${bold10("Some checks failed")} — follow the fix hints above`);
21399
+ console.log(` ${dim12("specialists install fixes hook + MCP registration; pi, bd, and xt must be installed separately.")}`);
21050
21400
  }
21051
21401
  console.log("");
21052
21402
  }
21053
- var bold8 = (s) => `\x1B[1m${s}\x1B[0m`, dim10 = (s) => `\x1B[2m${s}\x1B[0m`, green11 = (s) => `\x1B[32m${s}\x1B[0m`, yellow7 = (s) => `\x1B[33m${s}\x1B[0m`, red6 = (s) => `\x1B[31m${s}\x1B[0m`, CWD, CLAUDE_DIR, SPECIALISTS_DIR, HOOKS_DIR, SETTINGS_FILE, MCP_FILE2, HOOK_NAMES;
21403
+ var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, green12 = (s) => `\x1B[32m${s}\x1B[0m`, yellow9 = (s) => `\x1B[33m${s}\x1B[0m`, red7 = (s) => `\x1B[31m${s}\x1B[0m`, CWD, CLAUDE_DIR, SPECIALISTS_DIR, HOOKS_DIR, SETTINGS_FILE, MCP_FILE2, HOOK_NAMES;
21054
21404
  var init_doctor = __esm(() => {
21055
21405
  CWD = process.cwd();
21056
- CLAUDE_DIR = join19(CWD, ".claude");
21057
- SPECIALISTS_DIR = join19(CWD, ".specialists");
21058
- HOOKS_DIR = join19(SPECIALISTS_DIR, "default", "hooks");
21059
- SETTINGS_FILE = join19(CLAUDE_DIR, "settings.json");
21060
- MCP_FILE2 = join19(CWD, ".mcp.json");
21406
+ CLAUDE_DIR = join20(CWD, ".claude");
21407
+ SPECIALISTS_DIR = join20(CWD, ".specialists");
21408
+ HOOKS_DIR = join20(SPECIALISTS_DIR, "default", "hooks");
21409
+ SETTINGS_FILE = join20(CLAUDE_DIR, "settings.json");
21410
+ MCP_FILE2 = join20(CWD, ".mcp.json");
21061
21411
  HOOK_NAMES = [
21062
21412
  "specialists-complete.mjs",
21063
21413
  "specialists-session-start.mjs"
@@ -21067,191 +21417,49 @@ var init_doctor = __esm(() => {
21067
21417
  // src/cli/setup.ts
21068
21418
  var exports_setup = {};
21069
21419
  __export(exports_setup, {
21070
- run: () => run17
21420
+ run: () => run18
21071
21421
  });
21072
- import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync8 } from "node:fs";
21073
- import { homedir as homedir3 } from "node:os";
21074
- import { join as join20, resolve as resolve2 } from "node:path";
21075
- function ok4(msg) {
21076
- console.log(` ${green12("")} ${msg}`);
21077
- }
21078
- function skip2(msg) {
21079
- console.log(` ${yellow8("")} ${msg}`);
21080
- }
21081
- function resolveTarget(target) {
21082
- switch (target) {
21083
- case "global":
21084
- return join20(homedir3(), ".claude", "CLAUDE.md");
21085
- case "agents":
21086
- return join20(process.cwd(), "AGENTS.md");
21087
- case "project":
21088
- default:
21089
- return join20(process.cwd(), "CLAUDE.md");
21090
- }
21091
- }
21092
- function parseArgs7() {
21093
- const argv = process.argv.slice(3);
21094
- let target = "project";
21095
- let dryRun = false;
21096
- for (let i = 0;i < argv.length; i++) {
21097
- const token = argv[i];
21098
- if (token === "--global" || token === "-g") {
21099
- target = "global";
21100
- continue;
21101
- }
21102
- if (token === "--agents" || token === "-a") {
21103
- target = "agents";
21104
- continue;
21105
- }
21106
- if (token === "--project" || token === "-p") {
21107
- target = "project";
21108
- continue;
21109
- }
21110
- if (token === "--dry-run") {
21111
- dryRun = true;
21112
- continue;
21113
- }
21114
- }
21115
- return { target, dryRun };
21116
- }
21117
- async function run17() {
21118
- const { target, dryRun } = parseArgs7();
21119
- const filePath = resolve2(resolveTarget(target));
21120
- const label = target === "global" ? "~/.claude/CLAUDE.md" : filePath.replace(process.cwd() + "/", "");
21121
- console.log(`
21122
- ${bold9("specialists setup")}
21123
- `);
21124
- console.log(` Target: ${yellow8(label)}${dryRun ? dim11(" (dry-run)") : ""}
21125
- `);
21126
- if (existsSync13(filePath)) {
21127
- const existing = readFileSync9(filePath, "utf8");
21128
- if (existing.includes(MARKER)) {
21129
- skip2(`${label} already contains Specialists Workflow section`);
21130
- console.log(`
21131
- ${dim11("To force-update, remove the ## Specialists Workflow section and re-run.")}
21132
- `);
21133
- return;
21134
- }
21135
- if (dryRun) {
21136
- console.log(dim11("─".repeat(60)));
21137
- console.log(dim11("Would append to existing file:"));
21138
- console.log("");
21139
- console.log(WORKFLOW_BLOCK);
21140
- console.log(dim11("─".repeat(60)));
21141
- return;
21142
- }
21143
- const separator = existing.trimEnd().endsWith(`
21144
- `) ? `
21145
- ` : `
21146
-
21147
- `;
21148
- writeFileSync8(filePath, existing.trimEnd() + separator + WORKFLOW_BLOCK, "utf8");
21149
- ok4(`Appended Specialists Workflow section to ${label}`);
21150
- } else {
21151
- if (dryRun) {
21152
- console.log(dim11("─".repeat(60)));
21153
- console.log(dim11(`Would create ${label}:`));
21154
- console.log("");
21155
- console.log(WORKFLOW_BLOCK);
21156
- console.log(dim11("─".repeat(60)));
21157
- return;
21158
- }
21159
- writeFileSync8(filePath, WORKFLOW_BLOCK, "utf8");
21160
- ok4(`Created ${label} with Specialists Workflow section`);
21161
- }
21422
+ async function run18() {
21423
+ console.log("");
21424
+ console.log(yellow10("⚠ DEPRECATED: `specialists setup` is deprecated"));
21425
+ console.log("");
21426
+ console.log(` Use ${bold11("specialists init")} instead.`);
21427
+ console.log("");
21428
+ console.log(" The init command handles all setup tasks:");
21429
+ console.log(" • creates specialists/ and .specialists/ directories");
21430
+ console.log(" • adds .specialists/ to .gitignore");
21431
+ console.log(" • registers the MCP server in .mcp.json");
21432
+ console.log(" • injects workflow context into AGENTS.md/CLAUDE.md");
21162
21433
  console.log("");
21163
- console.log(` ${dim11("Next steps:")}`);
21164
- console.log(` • Restart Claude Code to pick up the new context`);
21165
- console.log(` • Run ${yellow8("specialists list")} to see available specialists`);
21166
- console.log(` • Run ${yellow8("specialist_init")} in a new session to bootstrap context`);
21434
+ console.log(" Options:");
21435
+ console.log(" --force-workflow Overwrite existing workflow blocks");
21436
+ console.log("");
21437
+ console.log(` ${dim13("Run: specialists init --help for full details")}`);
21167
21438
  console.log("");
21168
21439
  }
21169
- var bold9 = (s) => `\x1B[1m${s}\x1B[0m`, dim11 = (s) => `\x1B[2m${s}\x1B[0m`, green12 = (s) => `\x1B[32m${s}\x1B[0m`, yellow8 = (s) => `\x1B[33m${s}\x1B[0m`, MARKER = "## Specialists Workflow", WORKFLOW_BLOCK = `## Specialists Workflow
21170
-
21171
- > Injected by \`specialists setup\`. Keep this section — agents use it for context.
21172
-
21173
- ### When to use specialists
21174
-
21175
- Specialists are autonomous AI agents (running via the \`specialists\` MCP server)
21176
- optimised for heavy tasks: code review, deep bug analysis, test generation,
21177
- architecture design. Use them instead of doing the work yourself when the task
21178
- would benefit from a fresh perspective, a second opinion, or a different model.
21179
-
21180
- ### Quick reference
21181
-
21182
- \`\`\`
21183
- # List available specialists
21184
- specialists list # all scopes
21185
- specialists list --scope project # this project only
21186
-
21187
- # Run a specialist (foreground — streams output)
21188
- specialists run <name> --prompt "..."
21189
-
21190
- # Run async (background — immediate job ID)
21191
- specialists run <name> --prompt "..." --background
21192
- → Job started: job_a1b2c3d4
21193
-
21194
- # Watch / get results
21195
- specialists feed job_a1b2c3d4 --follow # tail live events
21196
- specialists result job_a1b2c3d4 # read final output
21197
- specialists stop job_a1b2c3d4 # cancel if needed
21198
- \`\`\`
21199
-
21200
- ### MCP tools (available in this session)
21201
-
21202
- | Tool | Purpose |
21203
- |------|---------|
21204
- | \`specialist_init\` | Bootstrap: bd init + list specialists |
21205
- | \`list_specialists\` | Discover specialists across scopes |
21206
- | \`use_specialist\` | Run foreground: load → inject context → execute → output |
21207
- | \`start_specialist\` | Start async: returns job ID immediately |
21208
- | \`poll_specialist\` | Poll job status + delta output by ID |
21209
- | \`stop_specialist\` | Cancel a running job |
21210
- | \`run_parallel\` | Run multiple specialists concurrently or as a pipeline |
21211
- | \`specialist_status\` | Circuit breaker health + staleness |
21212
-
21213
- ### Completion banner format
21214
-
21215
- When a specialist finishes, you may see:
21216
-
21217
- \`\`\`
21218
- ✓ bead unitAI-xxx 4.1s anthropic/claude-sonnet-4-6
21219
- \`\`\`
21220
-
21221
- This means:
21222
- - The specialist completed successfully
21223
- - A beads issue (\`unitAI-xxx\`) was created to track the run
21224
- - The result can be fetched with \`specialists result <job-id>\`
21225
-
21226
- ### When NOT to use specialists
21227
-
21228
- - Simple single-file edits — just do it directly
21229
- - Tasks that need interactive back-and-forth — use foreground mode or work yourself
21230
- - Short read-only queries — faster to answer directly
21231
- `;
21232
- var init_setup = () => {};
21440
+ var bold11 = (s) => `\x1B[1m${s}\x1B[0m`, yellow10 = (s) => `\x1B[33m${s}\x1B[0m`, dim13 = (s) => `\x1B[2m${s}\x1B[0m`;
21233
21441
 
21234
21442
  // src/cli/help.ts
21235
21443
  var exports_help = {};
21236
21444
  __export(exports_help, {
21237
- run: () => run18
21445
+ run: () => run19
21238
21446
  });
21239
21447
  function formatCommands(entries) {
21240
21448
  const width = Math.max(...entries.map(([cmd3]) => cmd3.length));
21241
21449
  return entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(width)} ${desc}`);
21242
21450
  }
21243
- async function run18() {
21451
+ async function run19() {
21244
21452
  const lines = [
21245
21453
  "",
21246
21454
  "Specialists lets you run project-scoped specialist agents with a bead-first workflow.",
21247
21455
  "",
21248
- bold10("Usage:"),
21456
+ bold12("Usage:"),
21249
21457
  " specialists|sp [command]",
21250
21458
  " specialists|sp [command] --help",
21251
21459
  "",
21252
- dim12(" sp is a shorter alias — sp run, sp list, sp feed etc. all work identically."),
21460
+ dim14(" sp is a shorter alias — sp run, sp list, sp feed etc. all work identically."),
21253
21461
  "",
21254
- bold10("Common flows:"),
21462
+ bold12("Common flows:"),
21255
21463
  "",
21256
21464
  " Tracked work (primary)",
21257
21465
  ' bd create "Task title" -t task -p 1 --json',
@@ -21273,19 +21481,19 @@ async function run18() {
21273
21481
  " or run in a separate terminal and poll with:",
21274
21482
  " specialists poll <job-id> --json",
21275
21483
  "",
21276
- bold10("Core commands:"),
21484
+ bold12("Core commands:"),
21277
21485
  ...formatCommands(CORE_COMMANDS),
21278
21486
  "",
21279
- bold10("Extended commands:"),
21487
+ bold12("Extended commands:"),
21280
21488
  ...formatCommands(EXTENDED_COMMANDS),
21281
21489
  "",
21282
- bold10("xtrm worktree commands:"),
21490
+ bold12("xtrm worktree commands:"),
21283
21491
  ...formatCommands(WORKTREE_COMMANDS),
21284
21492
  "",
21285
- bold10("Examples:"),
21493
+ bold12("Examples:"),
21286
21494
  " specialists init",
21287
21495
  " specialists list",
21288
- " specialists run bug-hunt --bead unitAI-123",
21496
+ " specialists run debugger --bead unitAI-123",
21289
21497
  ' specialists run codebase-explorer --prompt "Map the CLI architecture"',
21290
21498
  " specialists poll abc123 --json # check job status",
21291
21499
  " specialists feed -f # stream all job events",
@@ -21293,7 +21501,7 @@ async function run18() {
21293
21501
  ' specialists follow-up <job-id> "now write the fix"',
21294
21502
  " specialists result <job-id>",
21295
21503
  "",
21296
- bold10("More help:"),
21504
+ bold12("More help:"),
21297
21505
  " specialists quickstart Full guide and workflow reference",
21298
21506
  " specialists run --help Run command details and flags",
21299
21507
  " specialists poll --help Job status polling details",
@@ -21302,17 +21510,18 @@ async function run18() {
21302
21510
  " specialists init --help Bootstrap behavior and workflow injection",
21303
21511
  " specialists feed --help Job event streaming details",
21304
21512
  "",
21305
- dim12("Project model: specialists are project-only; user-scope discovery is deprecated."),
21513
+ dim14("Project model: specialists are project-only; user-scope discovery is deprecated."),
21306
21514
  ""
21307
21515
  ];
21308
21516
  console.log(lines.join(`
21309
21517
  `));
21310
21518
  }
21311
- var bold10 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, CORE_COMMANDS, EXTENDED_COMMANDS, WORKTREE_COMMANDS;
21519
+ var bold12 = (s) => `\x1B[1m${s}\x1B[0m`, dim14 = (s) => `\x1B[2m${s}\x1B[0m`, CORE_COMMANDS, EXTENDED_COMMANDS, WORKTREE_COMMANDS;
21312
21520
  var init_help = __esm(() => {
21313
21521
  CORE_COMMANDS = [
21314
21522
  ["init", "Bootstrap a project: dirs, workflow injection, project MCP registration"],
21315
21523
  ["list", "List specialists in this project"],
21524
+ ["validate", "Validate a specialist YAML against the schema"],
21316
21525
  ["run", "Run a specialist with --bead for tracked work or --prompt for ad-hoc work"],
21317
21526
  ["feed", "Tail job events; use -f to follow all jobs"],
21318
21527
  ["poll", "Machine-readable job status polling (for scripts/Claude Code)"],
@@ -29308,23 +29517,20 @@ var next = process.argv[3];
29308
29517
  function wantsHelp() {
29309
29518
  return next === "--help" || next === "-h";
29310
29519
  }
29311
- async function run19() {
29520
+ async function run20() {
29312
29521
  if (sub === "install") {
29313
29522
  if (wantsHelp()) {
29314
29523
  console.log([
29315
29524
  "",
29316
- "Usage: specialists install",
29525
+ "⚠ DEPRECATED: Use `specialists init` instead.",
29317
29526
  "",
29318
- "Project setup: checks pi/bd/xt prerequisites, registers the MCP server,",
29319
- "and installs specialists-specific project hooks.",
29320
- "",
29321
- "No flags — just run it.",
29527
+ "The install command is deprecated. Run `specialists init` for project setup.",
29322
29528
  ""
29323
29529
  ].join(`
29324
29530
  `));
29325
29531
  return;
29326
29532
  }
29327
- const { run: handler } = await Promise.resolve().then(() => (init_install(), exports_install));
29533
+ const { run: handler } = await Promise.resolve().then(() => exports_install);
29328
29534
  return handler();
29329
29535
  }
29330
29536
  if (sub === "version" || sub === "--version" || sub === "-v") {
@@ -29413,6 +29619,38 @@ async function run19() {
29413
29619
  const { run: handler } = await Promise.resolve().then(() => (init_init(), exports_init));
29414
29620
  return handler();
29415
29621
  }
29622
+ if (sub === "validate") {
29623
+ if (wantsHelp()) {
29624
+ console.log([
29625
+ "",
29626
+ "Usage: specialists validate <name> [--json]",
29627
+ "",
29628
+ "Validate a specialist YAML file against the schema.",
29629
+ "",
29630
+ "What it checks:",
29631
+ " - YAML syntax is valid",
29632
+ " - Required fields are present (name, version, description, category, model)",
29633
+ " - Field values match expected formats (kebab-case names, semver versions)",
29634
+ " - Enum values are valid (permission_required, mode, beads_integration)",
29635
+ "",
29636
+ "Options:",
29637
+ " --json Output validation result as JSON",
29638
+ "",
29639
+ "Examples:",
29640
+ " specialists validate my-specialist",
29641
+ " specialists validate my-specialist --json",
29642
+ "",
29643
+ "Exit codes:",
29644
+ " 0 — validation passed",
29645
+ " 1 — validation failed (errors) or specialist not found",
29646
+ ""
29647
+ ].join(`
29648
+ `));
29649
+ return;
29650
+ }
29651
+ const { run: handler } = await Promise.resolve().then(() => (init_validate(), exports_validate));
29652
+ return handler();
29653
+ }
29416
29654
  if (sub === "edit") {
29417
29655
  if (wantsHelp()) {
29418
29656
  console.log([
@@ -29431,7 +29669,7 @@ async function run19() {
29431
29669
  "",
29432
29670
  "Options:",
29433
29671
  " --dry-run Preview the change without writing",
29434
- " --scope <project|user> Disambiguate if same name exists in multiple scopes",
29672
+ " --scope <default|user> Disambiguate if same name exists in multiple scopes",
29435
29673
  "",
29436
29674
  "Examples:",
29437
29675
  " specialists edit code-review --model anthropic/claude-opus-4-6",
@@ -29466,8 +29704,8 @@ async function run19() {
29466
29704
  " --keep-alive Keep session alive for follow-up prompts",
29467
29705
  "",
29468
29706
  "Examples:",
29469
- " specialists run bug-hunt --bead unitAI-55d",
29470
- " specialists run bug-hunt --bead unitAI-55d --context-depth 2",
29707
+ " specialists run debugger --bead unitAI-55d",
29708
+ " specialists run debugger --bead unitAI-55d --context-depth 2",
29471
29709
  ' specialists run code-review --prompt "Audit src/api.ts"',
29472
29710
  " cat brief.md | specialists run report-generator",
29473
29711
  "",
@@ -29632,7 +29870,7 @@ async function run19() {
29632
29870
  ' specialists steer a1b2c3 "skip tests, just fix the bug"',
29633
29871
  "",
29634
29872
  "Notes:",
29635
- " - Only works for jobs started with --background.",
29873
+ " - Only works for running jobs.",
29636
29874
  " - Delivery is best-effort: the agent processes it on its next turn.",
29637
29875
  ""
29638
29876
  ].join(`
@@ -29651,14 +29889,14 @@ async function run19() {
29651
29889
  "Send a follow-up prompt to a waiting keep-alive specialist session.",
29652
29890
  "The Pi session retains full conversation history between turns.",
29653
29891
  "",
29654
- "Requires: job started with --keep-alive --background.",
29892
+ "Requires: job started with --keep-alive.",
29655
29893
  "",
29656
29894
  "Examples:",
29657
29895
  ' specialists follow-up a1b2c3 "Now write the fix for the bug you found"',
29658
29896
  ' specialists follow-up a1b2c3 "Focus only on the auth module"',
29659
29897
  "",
29660
29898
  "Workflow:",
29661
- " specialists run bug-hunt --bead <id> --keep-alive --background",
29899
+ " specialists run debugger --bead <id> --keep-alive",
29662
29900
  " # → Job started: a1b2c3 (status: waiting after first turn)",
29663
29901
  " specialists result a1b2c3 # read first turn output",
29664
29902
  ' specialists follow-up a1b2c3 "..." # send next prompt',
@@ -29731,28 +29969,15 @@ async function run19() {
29731
29969
  if (wantsHelp()) {
29732
29970
  console.log([
29733
29971
  "",
29734
- "Usage: specialists setup [options]",
29735
- "",
29736
- "Inject the Specialists Workflow context block into AGENTS.md or CLAUDE.md.",
29737
- "This teaches agents in that project how to use specialists.",
29972
+ "⚠ DEPRECATED: Use `specialists init` instead.",
29738
29973
  "",
29739
- "Options:",
29740
- " --project, -p Write to ./CLAUDE.md (default)",
29741
- " --agents, -a Write to ./AGENTS.md",
29742
- " --global, -g Write to ~/.claude/CLAUDE.md",
29743
- " --dry-run Preview the block without writing",
29744
- "",
29745
- "Examples:",
29746
- " specialists setup # → ./CLAUDE.md",
29747
- " specialists setup --agents # → ./AGENTS.md",
29748
- " specialists setup --global # → ~/.claude/CLAUDE.md",
29749
- " specialists setup --dry-run # preview only",
29974
+ "The setup command is deprecated. Run `specialists init` for project setup.",
29750
29975
  ""
29751
29976
  ].join(`
29752
29977
  `));
29753
29978
  return;
29754
29979
  }
29755
- const { run: handler } = await Promise.resolve().then(() => (init_setup(), exports_setup));
29980
+ const { run: handler } = await Promise.resolve().then(() => exports_setup);
29756
29981
  return handler();
29757
29982
  }
29758
29983
  if (sub === "help" || sub === "--help" || sub === "-h") {
@@ -29768,7 +29993,7 @@ Run 'specialists help' to see available commands.`);
29768
29993
  const server = new SpecialistsServer;
29769
29994
  await server.start();
29770
29995
  }
29771
- run19().catch((error2) => {
29996
+ run20().catch((error2) => {
29772
29997
  logger.error(`Fatal error: ${error2}`);
29773
29998
  process.exit(1);
29774
29999
  });