@jaggerxtrm/specialists 3.12.0 → 3.14.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 (42) hide show
  1. package/config/hooks/specialists-session-start.mjs +1 -1
  2. package/config/mandatory-rules/bead-id-verbatim.md +14 -0
  3. package/config/mandatory-rules/per-turn-handoff-schema.md +16 -0
  4. package/config/skills/specialists-creator/SKILL.md +16 -0
  5. package/config/skills/update-specialists/SKILL.md +183 -350
  6. package/config/skills/using-kpi/SKILL.md +86 -0
  7. package/config/skills/using-script-specialists/SKILL.md +7 -5
  8. package/config/skills/using-specialists-v2/SKILL.md +1 -1
  9. package/config/skills/using-specialists-v3/SKILL.md +530 -112
  10. package/config/specialists/changelog-keeper.specialist.json +2 -1
  11. package/config/specialists/code-sanity.specialist.json +3 -1
  12. package/config/specialists/debugger.specialist.json +3 -1
  13. package/config/specialists/executor.specialist.json +3 -1
  14. package/config/specialists/explorer.specialist.json +2 -1
  15. package/config/specialists/overthinker.specialist.json +2 -1
  16. package/config/specialists/planner.specialist.json +3 -1
  17. package/config/specialists/researcher.specialist.json +2 -1
  18. package/config/specialists/reviewer.specialist.json +3 -1
  19. package/config/specialists/security-auditor.specialist.json +53 -10
  20. package/config/specialists/specialists-creator.specialist.json +2 -2
  21. package/config/specialists/sync-docs.specialist.json +3 -1
  22. package/config/specialists/test-runner.specialist.json +2 -1
  23. package/dist/index.js +634 -498
  24. package/dist/lib.js +183 -62
  25. package/dist/types/cli/help.d.ts.map +1 -1
  26. package/dist/types/cli/run.d.ts.map +1 -1
  27. package/dist/types/cli/script.d.ts.map +1 -1
  28. package/dist/types/cli/serve.d.ts +11 -2
  29. package/dist/types/cli/serve.d.ts.map +1 -1
  30. package/dist/types/cli/version-check.d.ts +3 -0
  31. package/dist/types/cli/version-check.d.ts.map +1 -1
  32. package/dist/types/index.d.ts +1 -1
  33. package/dist/types/specialist/mandatory-rules.d.ts +5 -0
  34. package/dist/types/specialist/mandatory-rules.d.ts.map +1 -1
  35. package/dist/types/specialist/observability-sqlite.d.ts +1 -0
  36. package/dist/types/specialist/observability-sqlite.d.ts.map +1 -1
  37. package/dist/types/specialist/schema.d.ts +27 -0
  38. package/dist/types/specialist/schema.d.ts.map +1 -1
  39. package/dist/types/specialist/script-runner.d.ts +5 -1
  40. package/dist/types/specialist/script-runner.d.ts.map +1 -1
  41. package/package.json +4 -4
  42. package/config/specialists/.serena/project.yml +0 -151
package/dist/index.js CHANGED
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
5
5
  var __defProp = Object.defineProperty;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ function __accessProp(key) {
9
+ return this[key];
10
+ }
11
+ var __toESMCache_node;
12
+ var __toESMCache_esm;
8
13
  var __toESM = (mod, isNodeMode, target) => {
14
+ var canCache = mod != null && typeof mod === "object";
15
+ if (canCache) {
16
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
17
+ var cached = cache.get(mod);
18
+ if (cached)
19
+ return cached;
20
+ }
9
21
  target = mod != null ? __create(__getProtoOf(mod)) : {};
10
22
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
23
  for (let key of __getOwnPropNames(mod))
12
24
  if (!__hasOwnProp.call(to, key))
13
25
  __defProp(to, key, {
14
- get: () => mod[key],
26
+ get: __accessProp.bind(mod, key),
15
27
  enumerable: true
16
28
  });
29
+ if (canCache)
30
+ cache.set(mod, to);
17
31
  return to;
18
32
  };
19
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
34
+ var __returnValue = (v) => v;
35
+ function __exportSetter(name, newValue) {
36
+ this[name] = __returnValue.bind(null, newValue);
37
+ }
20
38
  var __export = (target, all) => {
21
39
  for (var name in all)
22
40
  __defProp(target, name, {
23
41
  get: all[name],
24
42
  enumerable: true,
25
43
  configurable: true,
26
- set: (newValue) => all[name] = () => newValue
44
+ set: __exportSetter.bind(all, name)
27
45
  });
28
46
  };
29
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -10248,7 +10266,7 @@ var require_formats = __commonJS((exports) => {
10248
10266
  }
10249
10267
  var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
10250
10268
  function getTime(strictTimeZone) {
10251
- return function time(str) {
10269
+ return function time3(str) {
10252
10270
  const matches = TIME.exec(str);
10253
10271
  if (!matches)
10254
10272
  return false;
@@ -11014,6 +11032,8 @@ var require_Alias = __commonJS((exports) => {
11014
11032
  });
11015
11033
  }
11016
11034
  resolve(doc2, ctx) {
11035
+ if (ctx?.maxAliasCount === 0)
11036
+ throw new ReferenceError("Alias resolution is disabled");
11017
11037
  let nodes;
11018
11038
  if (ctx?.aliasResolveCache) {
11019
11039
  nodes = ctx.aliasResolveCache;
@@ -11793,6 +11813,7 @@ var require_stringify = __commonJS((exports) => {
11793
11813
  nullStr: "null",
11794
11814
  simpleKeys: false,
11795
11815
  singleQuote: null,
11816
+ trailingComma: false,
11796
11817
  trueStr: "true",
11797
11818
  verifyAliasOrder: true
11798
11819
  }, doc2.schema.toStringOptions, options);
@@ -12062,18 +12083,18 @@ var require_merge = __commonJS((exports) => {
12062
12083
  };
12063
12084
  var isMergeKey = (ctx, key) => (merge2.identify(key) || identity.isScalar(key) && (!key.type || key.type === Scalar.Scalar.PLAIN) && merge2.identify(key.value)) && ctx?.doc.schema.tags.some((tag) => tag.tag === merge2.tag && tag.default);
12064
12085
  function addMergeToJSMap(ctx, map2, value) {
12065
- value = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
12066
- if (identity.isSeq(value))
12067
- for (const it of value.items)
12086
+ const source = resolveAliasValue(ctx, value);
12087
+ if (identity.isSeq(source))
12088
+ for (const it of source.items)
12068
12089
  mergeValue(ctx, map2, it);
12069
- else if (Array.isArray(value))
12070
- for (const it of value)
12090
+ else if (Array.isArray(source))
12091
+ for (const it of source)
12071
12092
  mergeValue(ctx, map2, it);
12072
12093
  else
12073
- mergeValue(ctx, map2, value);
12094
+ mergeValue(ctx, map2, source);
12074
12095
  }
12075
12096
  function mergeValue(ctx, map2, value) {
12076
- const source = ctx && identity.isAlias(value) ? value.resolve(ctx.doc) : value;
12097
+ const source = resolveAliasValue(ctx, value);
12077
12098
  if (!identity.isMap(source))
12078
12099
  throw new Error("Merge sources must be maps or map aliases");
12079
12100
  const srcMap = source.toJSON(null, ctx, Map);
@@ -12094,6 +12115,9 @@ var require_merge = __commonJS((exports) => {
12094
12115
  }
12095
12116
  return map2;
12096
12117
  }
12118
+ function resolveAliasValue(ctx, value) {
12119
+ return ctx && identity.isAlias(value) ? value.resolve(ctx.doc, ctx) : value;
12120
+ }
12097
12121
  exports.addMergeToJSMap = addMergeToJSMap;
12098
12122
  exports.isMergeKey = isMergeKey;
12099
12123
  exports.merge = merge2;
@@ -12301,13 +12325,20 @@ ${indent}${line}` : `
12301
12325
  if (comment)
12302
12326
  reqNewline = true;
12303
12327
  let str = stringify.stringify(item, itemCtx, () => comment = null);
12304
- if (i < items.length - 1)
12328
+ reqNewline || (reqNewline = lines.length > linesAtValue || str.includes(`
12329
+ `));
12330
+ if (i < items.length - 1) {
12305
12331
  str += ",";
12332
+ } else if (ctx.options.trailingComma) {
12333
+ if (ctx.options.lineWidth > 0) {
12334
+ reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
12335
+ }
12336
+ if (reqNewline) {
12337
+ str += ",";
12338
+ }
12339
+ }
12306
12340
  if (comment)
12307
12341
  str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
12308
- if (!reqNewline && (lines.length > linesAtValue || str.includes(`
12309
- `)))
12310
- reqNewline = true;
12311
12342
  lines.push(str);
12312
12343
  linesAtValue = lines.length;
12313
12344
  }
@@ -12662,7 +12693,7 @@ var require_stringifyNumber = __commonJS((exports) => {
12662
12693
  if (!isFinite(num))
12663
12694
  return isNaN(num) ? ".nan" : num < 0 ? "-.inf" : ".inf";
12664
12695
  let n = Object.is(value, -0) ? "-0" : JSON.stringify(value);
12665
- if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^\d/.test(n)) {
12696
+ if (!format && minFractionDigits && (!tag || tag === "tag:yaml.org,2002:float") && /^-?\d/.test(n) && !n.includes("e")) {
12666
12697
  let i = n.indexOf(".");
12667
12698
  if (i < 0) {
12668
12699
  i = n.length;
@@ -14887,7 +14918,7 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
14887
14918
  while (next === " " || next === "\t")
14888
14919
  next = source[++i + 1];
14889
14920
  } else if (next === "x" || next === "u" || next === "U") {
14890
- const length = { x: 2, u: 4, U: 8 }[next];
14921
+ const length = next === "x" ? 2 : next === "u" ? 4 : 8;
14891
14922
  res += parseCharCode(source, i + 1, length, onError);
14892
14923
  i += length;
14893
14924
  } else {
@@ -14956,12 +14987,13 @@ var require_resolve_flow_scalar = __commonJS((exports) => {
14956
14987
  const cc = source.substr(offset, length);
14957
14988
  const ok = cc.length === length && /^[0-9a-fA-F]+$/.test(cc);
14958
14989
  const code = ok ? parseInt(cc, 16) : NaN;
14959
- if (isNaN(code)) {
14990
+ try {
14991
+ return String.fromCodePoint(code);
14992
+ } catch {
14960
14993
  const raw = source.substr(offset - 2, length + 2);
14961
14994
  onError(offset - 2, "BAD_DQ_ESCAPE", `Invalid escape sequence ${raw}`);
14962
14995
  return raw;
14963
14996
  }
14964
- return String.fromCodePoint(code);
14965
14997
  }
14966
14998
  exports.resolveFlowScalar = resolveFlowScalar;
14967
14999
  });
@@ -15102,17 +15134,22 @@ var require_compose_node = __commonJS((exports) => {
15102
15134
  case "block-map":
15103
15135
  case "block-seq":
15104
15136
  case "flow-collection":
15105
- node = composeCollection.composeCollection(CN, ctx, token, props, onError);
15106
- if (anchor)
15107
- node.anchor = anchor.source.substring(1);
15137
+ try {
15138
+ node = composeCollection.composeCollection(CN, ctx, token, props, onError);
15139
+ if (anchor)
15140
+ node.anchor = anchor.source.substring(1);
15141
+ } catch (error2) {
15142
+ const message = error2 instanceof Error ? error2.message : String(error2);
15143
+ onError(token, "RESOURCE_EXHAUSTION", message);
15144
+ }
15108
15145
  break;
15109
15146
  default: {
15110
15147
  const message = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`;
15111
15148
  onError(token, "UNEXPECTED_TOKEN", message);
15112
- node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError);
15113
15149
  isSrcToken = false;
15114
15150
  }
15115
15151
  }
15152
+ node ?? (node = composeEmptyNode(ctx, token.offset, undefined, null, props, onError));
15116
15153
  if (anchor && node.anchor === "")
15117
15154
  onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
15118
15155
  if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
@@ -17524,6 +17561,7 @@ var init_schema = __esm(() => {
17524
17561
  max_retries: numberType().int().min(0).default(0),
17525
17562
  interactive: booleanType().default(false),
17526
17563
  stdout_limit_bytes: numberType().int().positive().optional(),
17564
+ prompt_limit_bytes: numberType().int().positive().optional(),
17527
17565
  response_format: enumType(["text", "json", "markdown"]).default("text"),
17528
17566
  output_type: enumType(["codegen", "analysis", "review", "synthesis", "orchestration", "workflow", "research", "custom"]).default("custom"),
17529
17567
  permission_required: enumType(["READ_ONLY", "LOW", "MEDIUM", "HIGH"]).default("READ_ONLY"),
@@ -19119,8 +19157,8 @@ function resolveCurrentBranch(cwd = process.cwd()) {
19119
19157
  var init_job_root = () => {};
19120
19158
 
19121
19159
  // src/specialist/observability-sqlite.ts
19122
- import { existsSync as existsSync5, readFileSync as readFileSync3, statSync } from "fs";
19123
- import { join as join5 } from "path";
19160
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, statSync } from "fs";
19161
+ import { dirname as dirname3, join as join5 } from "path";
19124
19162
  function loadBunDatabase() {
19125
19163
  if (_probed)
19126
19164
  return _BunDatabase;
@@ -21183,23 +21221,30 @@ function hasRunCompleteEvent(jobId, cwd = process.cwd()) {
21183
21221
  }
21184
21222
  return false;
21185
21223
  }
21186
- function createObservabilitySqliteClient(cwd = process.cwd()) {
21224
+ function openObservabilitySqliteClient(dbPath) {
21187
21225
  if (!loadBunDatabase())
21188
21226
  return null;
21189
- const location = resolveObservabilityDbLocation(cwd);
21190
- if (!existsSync5(location.dbPath))
21191
- return null;
21192
21227
  try {
21193
21228
  const Ctor = loadBunDatabase();
21194
- const initDb = new Ctor(location.dbPath);
21229
+ const initDb = new Ctor(dbPath);
21195
21230
  initDb.run(`PRAGMA busy_timeout=${BUSY_TIMEOUT_MS}`);
21196
21231
  initSchema(initDb);
21197
21232
  initDb.close();
21198
- return new SqliteClient(location.dbPath);
21233
+ return new SqliteClient(dbPath);
21199
21234
  } catch {
21200
21235
  return null;
21201
21236
  }
21202
21237
  }
21238
+ function createObservabilitySqliteClient(cwd = process.cwd()) {
21239
+ const location = resolveObservabilityDbLocation(cwd);
21240
+ if (!existsSync5(location.dbPath))
21241
+ return null;
21242
+ return openObservabilitySqliteClient(location.dbPath);
21243
+ }
21244
+ function createObservabilitySqliteClientAtPath(dbPath) {
21245
+ mkdirSync3(dirname3(dbPath), { recursive: true });
21246
+ return openObservabilitySqliteClient(dbPath);
21247
+ }
21203
21248
  var _BunDatabase = null, _probed = false, BUSY_TIMEOUT_MS = 5000, MAX_RETRY_ATTEMPTS = 5, BASE_RETRY_DELAY_MS = 50, STALE_CLAIM_AGE_MS = 60000;
21204
21249
  var init_observability_sqlite = __esm(() => {
21205
21250
  init_observability_db();
@@ -21582,24 +21627,27 @@ function readMandatoryRuleSet(cwd, id) {
21582
21627
  }
21583
21628
  function formatMandatoryRulesBlock(sets, inlineRules = []) {
21584
21629
  if (sets.length === 0 && inlineRules.length === 0)
21585
- return "";
21630
+ return { block: "", sections: [] };
21586
21631
  const sections = [
21587
21632
  ...sets.map((set2) => {
21588
21633
  const rules = set2.rules.map((rule) => `- [${rule.level}] ${rule.text}`).join(`
21589
21634
  `);
21590
- return `### ${set2.id}
21591
- ${rules}`;
21635
+ return { setId: set2.id, block: `### ${set2.id}
21636
+ ${rules}` };
21592
21637
  }),
21593
21638
  ...inlineRules.length > 0 ? [
21594
- `### specialist-inline-rules
21639
+ {
21640
+ setId: "specialist-inline-rules",
21641
+ block: `### specialist-inline-rules
21595
21642
  ${inlineRules.map((rule, index) => `- [${rule.level}] ${rule.text}${rule.id ? ` (id: ${rule.id})` : ` (id: inline-${index + 1})`}`).join(`
21596
21643
  `)}`
21644
+ }
21597
21645
  ] : []
21598
21646
  ];
21599
- return `## MANDATORY_RULES
21600
- ${sections.join(`
21647
+ return { block: `## MANDATORY_RULES
21648
+ ${sections.map((section) => section.block).join(`
21601
21649
 
21602
- `)}`;
21650
+ `)}`, sections };
21603
21651
  }
21604
21652
  function collectMandatoryRuleSets(cwd, setIds) {
21605
21653
  const seen = new Set;
@@ -21633,9 +21681,10 @@ function buildMandatoryRulesInjection(specialistConfig) {
21633
21681
  id: "workflow-quick-rules",
21634
21682
  rules: [{ id: "workflow-quick-rules-1", level: "required", text: STATIC_WORKFLOW_RULES_BLOCK.trim().replace(/^##\s+Beads Workflow Quick Rules\n/, "") }]
21635
21683
  }];
21636
- const block = formatMandatoryRulesBlock([...globals, ...sets], inlineRules);
21684
+ const formatted = formatMandatoryRulesBlock([...globals, ...sets], inlineRules);
21637
21685
  return {
21638
- block,
21686
+ block: formatted.block,
21687
+ sections: formatted.sections,
21639
21688
  setsLoaded: [...globals.map((set2) => set2.id), ...sets.map((set2) => set2.id)],
21640
21689
  ruleCount: [...globals, ...sets].reduce((count, set2) => count + set2.rules.length, 0) + inlineRules.length,
21641
21690
  inlineRulesCount: inlineRules.length,
@@ -22565,9 +22614,8 @@ ${summaries.join(`
22565
22614
  skillPaths.push(prompt.skill_inherit);
22566
22615
  skillPaths.push(...spec.specialist.skills?.paths ?? []);
22567
22616
  if (mandatoryRulesInjection) {
22568
- for (const setId of mandatoryRulesInjection.setsLoaded) {
22569
- payloadComponents.push(measurePayloadComponent("mandatory_rule", setId, `${setId}
22570
- ${mandatoryRulesBlock}`));
22617
+ for (const section of mandatoryRulesInjection.sections) {
22618
+ payloadComponents.push(measurePayloadComponent("mandatory_rule", section.setId, section.block));
22571
22619
  }
22572
22620
  }
22573
22621
  for (const skillPath of skillPaths) {
@@ -22983,7 +23031,7 @@ var init_runner = __esm(() => {
22983
23031
 
22984
23032
  // src/specialist/hooks.ts
22985
23033
  import { appendFile, mkdir } from "fs/promises";
22986
- import { dirname as dirname3 } from "path";
23034
+ import { dirname as dirname4 } from "path";
22987
23035
 
22988
23036
  class HookEmitter {
22989
23037
  tracePath;
@@ -22991,7 +23039,7 @@ class HookEmitter {
22991
23039
  ready;
22992
23040
  constructor(options) {
22993
23041
  this.tracePath = options.tracePath;
22994
- this.ready = mkdir(dirname3(options.tracePath), { recursive: true }).then(() => {});
23042
+ this.ready = mkdir(dirname4(options.tracePath), { recursive: true }).then(() => {});
22995
23043
  }
22996
23044
  async emit(hook, invocationId, specialistName, specialistVersion, payload) {
22997
23045
  await this.ready;
@@ -23045,11 +23093,11 @@ __export(exports_version, {
23045
23093
  });
23046
23094
  import { createRequire } from "module";
23047
23095
  import { fileURLToPath as fileURLToPath2 } from "url";
23048
- import { dirname as dirname4, join as join7 } from "path";
23096
+ import { dirname as dirname5, join as join7 } from "path";
23049
23097
  import { existsSync as existsSync8 } from "fs";
23050
23098
  async function run2() {
23051
23099
  const req = createRequire(import.meta.url);
23052
- const here = dirname4(fileURLToPath2(import.meta.url));
23100
+ const here = dirname5(fileURLToPath2(import.meta.url));
23053
23101
  const bundlePkgPath = join7(here, "..", "package.json");
23054
23102
  const sourcePkgPath = join7(here, "..", "..", "package.json");
23055
23103
  let pkg;
@@ -24179,7 +24227,7 @@ import {
24179
24227
  closeSync,
24180
24228
  existsSync as existsSync10,
24181
24229
  fsyncSync,
24182
- mkdirSync as mkdirSync3,
24230
+ mkdirSync as mkdirSync4,
24183
24231
  openSync,
24184
24232
  readdirSync as readdirSync2,
24185
24233
  readFileSync as readFileSync8,
@@ -24616,7 +24664,7 @@ class Supervisor {
24616
24664
  return join9(this.resolvedJobsDir, "..", "ready");
24617
24665
  }
24618
24666
  writeReadyMarker(id) {
24619
- mkdirSync3(this.readyDir(), { recursive: true });
24667
+ mkdirSync4(this.readyDir(), { recursive: true });
24620
24668
  writeFileSync3(join9(this.readyDir(), id), "", "utf-8");
24621
24669
  }
24622
24670
  withComputedLiveness(status) {
@@ -24747,7 +24795,7 @@ class Supervisor {
24747
24795
  if (!this.isJobFileOutputEnabled)
24748
24796
  return;
24749
24797
  const normalizedStatus = this.withStatusLineageDefaults(id, data);
24750
- mkdirSync3(this.jobDir(id), { recursive: true });
24798
+ mkdirSync4(this.jobDir(id), { recursive: true });
24751
24799
  const path = this.statusPath(id);
24752
24800
  const tmp = path + ".tmp";
24753
24801
  writeFileSync3(tmp, JSON.stringify(normalizedStatus, null, 2), "utf-8");
@@ -24909,8 +24957,8 @@ class Supervisor {
24909
24957
  const id = crypto.randomUUID().slice(0, 6);
24910
24958
  const dir = this.jobDir(id);
24911
24959
  const startedAtMs = Date.now();
24912
- mkdirSync3(dir, { recursive: true });
24913
- mkdirSync3(this.readyDir(), { recursive: true });
24960
+ mkdirSync4(dir, { recursive: true });
24961
+ mkdirSync4(this.readyDir(), { recursive: true });
24914
24962
  const nodeId = runOptions.variables?.node_id ?? runOptions.variables?.SPECIALISTS_NODE_ID;
24915
24963
  const variablesKeys = Object.keys(runOptions.variables ?? {});
24916
24964
  const activatedSkills = (runOptions.variables?.activated_skills ?? runOptions.variables?.skills_activated ?? "").split(",").map((skill) => skill.trim()).filter((skill) => skill.length > 0);
@@ -25188,7 +25236,7 @@ ${appendError}
25188
25236
  try {
25189
25237
  const output = await resumeFn(task);
25190
25238
  latestOutput = output;
25191
- mkdirSync3(this.jobDir(id), { recursive: true });
25239
+ mkdirSync4(this.jobDir(id), { recursive: true });
25192
25240
  writeFileSync3(this.resultPath(id), output, "utf-8");
25193
25241
  try {
25194
25242
  this.withSqliteOperation("upsertResult:resume_turn", (client) => client.upsertResult(id, output));
@@ -25596,7 +25644,7 @@ ${appendError}
25596
25644
  });
25597
25645
  latestOutput = result.output;
25598
25646
  if (this.isJobFileOutputEnabled) {
25599
- mkdirSync3(this.jobDir(id), { recursive: true });
25647
+ mkdirSync4(this.jobDir(id), { recursive: true });
25600
25648
  writeFileSync3(this.resultPath(id), latestOutput, "utf-8");
25601
25649
  }
25602
25650
  const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
@@ -26584,9 +26632,9 @@ var exports_init = {};
26584
26632
  __export(exports_init, {
26585
26633
  run: () => run7
26586
26634
  });
26587
- import { copyFileSync, cpSync, existsSync as existsSync12, lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync10, readlinkSync, renameSync as renameSync2, symlinkSync, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
26635
+ import { copyFileSync, cpSync, existsSync as existsSync12, lstatSync, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync10, readlinkSync, renameSync as renameSync2, symlinkSync, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
26588
26636
  import { spawnSync as spawnSync9 } from "child_process";
26589
- import { basename as basename4, dirname as dirname5, join as join11, relative, resolve as resolve6 } from "path";
26637
+ import { basename as basename4, dirname as dirname6, join as join11, relative, resolve as resolve6 } from "path";
26590
26638
  function ok(msg) {
26591
26639
  console.log(` ${green4("\u2713")} ${msg}`);
26592
26640
  }
@@ -26640,7 +26688,7 @@ function migrateLegacySpecialists(cwd, scope) {
26640
26688
  return;
26641
26689
  const targetDir = join11(cwd, ".specialists", scope);
26642
26690
  if (!existsSync12(targetDir)) {
26643
- mkdirSync4(targetDir, { recursive: true });
26691
+ mkdirSync5(targetDir, { recursive: true });
26644
26692
  }
26645
26693
  const files = readdirSync4(sourceDir).filter((f) => f.endsWith(".specialist.json") || f.endsWith(".specialist.json"));
26646
26694
  if (files.length === 0)
@@ -26677,7 +26725,7 @@ function copyCanonicalSpecialists(cwd) {
26677
26725
  return;
26678
26726
  }
26679
26727
  if (!existsSync12(targetDir)) {
26680
- mkdirSync4(targetDir, { recursive: true });
26728
+ mkdirSync5(targetDir, { recursive: true });
26681
26729
  }
26682
26730
  let copied = 0;
26683
26731
  let refreshed = 0;
@@ -26712,7 +26760,7 @@ function copyCanonicalMandatoryRules(cwd) {
26712
26760
  return;
26713
26761
  }
26714
26762
  if (!existsSync12(targetDir)) {
26715
- mkdirSync4(targetDir, { recursive: true });
26763
+ mkdirSync5(targetDir, { recursive: true });
26716
26764
  }
26717
26765
  let copied = 0;
26718
26766
  let refreshed = 0;
@@ -26747,7 +26795,7 @@ function copyCanonicalNodeConfigs(cwd) {
26747
26795
  return;
26748
26796
  }
26749
26797
  if (!existsSync12(targetDir)) {
26750
- mkdirSync4(targetDir, { recursive: true });
26798
+ mkdirSync5(targetDir, { recursive: true });
26751
26799
  }
26752
26800
  let copied = 0;
26753
26801
  let refreshed = 0;
@@ -26783,8 +26831,8 @@ function installProjectHooks(cwd) {
26783
26831
  skip("no hook files found in package");
26784
26832
  return;
26785
26833
  }
26786
- mkdirSync4(targetDir, { recursive: true });
26787
- mkdirSync4(claudeHooksDir, { recursive: true });
26834
+ mkdirSync5(targetDir, { recursive: true });
26835
+ mkdirSync5(claudeHooksDir, { recursive: true });
26788
26836
  let copied = 0;
26789
26837
  let skippedCopies = 0;
26790
26838
  let linked = 0;
@@ -26809,7 +26857,7 @@ function installProjectHooks(cwd) {
26809
26857
  rewiredLinks++;
26810
26858
  continue;
26811
26859
  }
26812
- const currentTarget = resolve6(dirname5(claudeHookPath), readlinkSync(claudeHookPath));
26860
+ const currentTarget = resolve6(dirname6(claudeHookPath), readlinkSync(claudeHookPath));
26813
26861
  if (currentTarget !== xtrmDest) {
26814
26862
  unlinkSync(claudeHookPath);
26815
26863
  symlinkSync(relativeTarget, claudeHookPath);
@@ -26837,7 +26885,7 @@ function ensureProjectHookWiring(cwd) {
26837
26885
  const settingsPath = join11(cwd, ".claude", "settings.json");
26838
26886
  const settingsDir = join11(cwd, ".claude");
26839
26887
  if (!existsSync12(settingsDir)) {
26840
- mkdirSync4(settingsDir, { recursive: true });
26888
+ mkdirSync5(settingsDir, { recursive: true });
26841
26889
  }
26842
26890
  const settings = loadJson(settingsPath, {});
26843
26891
  if (!settings.hooks || typeof settings.hooks !== "object") {
@@ -26873,10 +26921,10 @@ function ensureProjectHookWiring(cwd) {
26873
26921
  }
26874
26922
  function ensureRootSymlink(rootPath, expectedTargetPath) {
26875
26923
  if (!existsSync12(rootPath)) {
26876
- mkdirSync4(dirname5(rootPath), { recursive: true });
26877
- const relTarget = relative(dirname5(rootPath), expectedTargetPath);
26924
+ mkdirSync5(dirname6(rootPath), { recursive: true });
26925
+ const relTarget = relative(dirname6(rootPath), expectedTargetPath);
26878
26926
  symlinkSync(relTarget, rootPath);
26879
- ok(`created ${basename4(dirname5(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26927
+ ok(`created ${basename4(dirname6(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26880
26928
  return;
26881
26929
  }
26882
26930
  const stats = lstatSync(rootPath);
@@ -26884,7 +26932,7 @@ function ensureRootSymlink(rootPath, expectedTargetPath) {
26884
26932
  throw new Error(`${rootPath} must be a symlink to ${expectedTargetPath}. Aborting.`);
26885
26933
  }
26886
26934
  const linkTarget = readlinkSync(rootPath);
26887
- const resolvedTarget = resolve6(dirname5(rootPath), linkTarget);
26935
+ const resolvedTarget = resolve6(dirname6(rootPath), linkTarget);
26888
26936
  const resolvedExpected = resolve6(expectedTargetPath);
26889
26937
  if (resolvedTarget === resolvedExpected) {
26890
26938
  return;
@@ -26895,9 +26943,9 @@ function ensureRootSymlink(rootPath, expectedTargetPath) {
26895
26943
  ];
26896
26944
  if (legacyTargets.includes(resolvedTarget)) {
26897
26945
  unlinkSync(rootPath);
26898
- const relTarget = relative(dirname5(rootPath), expectedTargetPath);
26946
+ const relTarget = relative(dirname6(rootPath), expectedTargetPath);
26899
26947
  symlinkSync(relTarget, rootPath);
26900
- ok(`rewired ${basename4(dirname5(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26948
+ ok(`rewired ${basename4(dirname6(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26901
26949
  return;
26902
26950
  }
26903
26951
  throw new Error(`${rootPath} points to ${linkTarget}, expected ${expectedTargetPath}. Aborting.`);
@@ -26918,7 +26966,7 @@ function ensureActiveSkillSymlink(defaultSkillPath, activeLinkPath) {
26918
26966
  if (!stats.isSymbolicLink()) {
26919
26967
  throw new Error(`${activeLinkPath} already exists and is not a symlink.`);
26920
26968
  }
26921
- const currentTarget = resolve6(dirname5(activeLinkPath), readlinkSync(activeLinkPath));
26969
+ const currentTarget = resolve6(dirname6(activeLinkPath), readlinkSync(activeLinkPath));
26922
26970
  if (currentTarget !== resolve6(defaultSkillPath)) {
26923
26971
  throw new Error(`${activeLinkPath} points to an unexpected target.`);
26924
26972
  }
@@ -26940,8 +26988,8 @@ function installProjectSkills(cwd, syncSkills) {
26940
26988
  }
26941
26989
  const defaultRoot = join11(cwd, ".xtrm", "skills", "default");
26942
26990
  const activeRoot = join11(cwd, ".xtrm", "skills", "active");
26943
- mkdirSync4(defaultRoot, { recursive: true });
26944
- mkdirSync4(activeRoot, { recursive: true });
26991
+ mkdirSync5(defaultRoot, { recursive: true });
26992
+ mkdirSync5(activeRoot, { recursive: true });
26945
26993
  ensureRootSymlink(join11(cwd, ".claude", "skills"), activeRoot);
26946
26994
  ensureRootSymlink(join11(cwd, ".pi", "skills"), activeRoot);
26947
26995
  let copied = 0;
@@ -26972,7 +27020,7 @@ function createSpecialistsDirs(cwd) {
26972
27020
  let created = 0;
26973
27021
  for (const dir of [defaultDir, userDir]) {
26974
27022
  if (!existsSync12(dir)) {
26975
- mkdirSync4(dir, { recursive: true });
27023
+ mkdirSync5(dir, { recursive: true });
26976
27024
  created++;
26977
27025
  }
26978
27026
  }
@@ -26988,7 +27036,7 @@ function createRuntimeDirs(cwd) {
26988
27036
  let created = 0;
26989
27037
  for (const dir of runtimeDirs) {
26990
27038
  if (!existsSync12(dir)) {
26991
- mkdirSync4(dir, { recursive: true });
27039
+ mkdirSync5(dir, { recursive: true });
26992
27040
  created++;
26993
27041
  }
26994
27042
  }
@@ -27121,7 +27169,7 @@ function validateInitPostconditions(cwd) {
27121
27169
  continue;
27122
27170
  }
27123
27171
  const expectedTarget = resolve6(xtrmHooksDir, hookFile);
27124
- const resolvedTarget = resolve6(dirname5(claudeHookPath), readlinkSync(claudeHookPath));
27172
+ const resolvedTarget = resolve6(dirname6(claudeHookPath), readlinkSync(claudeHookPath));
27125
27173
  if (resolvedTarget !== expectedTarget) {
27126
27174
  warnings.push(`.claude/hooks/${hookFile} points to unexpected target`);
27127
27175
  }
@@ -27175,7 +27223,7 @@ function validateInitPostconditions(cwd) {
27175
27223
  warnings.push(`${relative(cwd, symlink.linkPath)} is not a symlink`);
27176
27224
  continue;
27177
27225
  }
27178
- const resolvedTarget = resolve6(dirname5(symlink.linkPath), readlinkSync(symlink.linkPath));
27226
+ const resolvedTarget = resolve6(dirname6(symlink.linkPath), readlinkSync(symlink.linkPath));
27179
27227
  if (resolvedTarget !== resolve6(symlink.expectedTarget)) {
27180
27228
  warnings.push(`${relative(cwd, symlink.linkPath)} points to an unexpected target`);
27181
27229
  }
@@ -27352,8 +27400,8 @@ var exports_db = {};
27352
27400
  __export(exports_db, {
27353
27401
  run: () => run9
27354
27402
  });
27355
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
27356
- import { dirname as dirname6, join as join12, resolve as resolve7 } from "path";
27403
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
27404
+ import { dirname as dirname7, join as join12, resolve as resolve7 } from "path";
27357
27405
  function formatBytes(bytes) {
27358
27406
  if (bytes < 1024)
27359
27407
  return `${bytes} B`;
@@ -28013,8 +28061,8 @@ function runBenchmarkExport(options) {
28013
28061
  });
28014
28062
  }
28015
28063
  rows.sort((a, b) => a.task_id.localeCompare(b.task_id) || a.executor_job_id.localeCompare(b.executor_job_id));
28016
- const outputDirectory = dirname6(options.outputPath);
28017
- mkdirSync5(outputDirectory, { recursive: true });
28064
+ const outputDirectory = dirname7(options.outputPath);
28065
+ mkdirSync6(outputDirectory, { recursive: true });
28018
28066
  const jsonl = rows.map((row) => JSON.stringify(row)).join(`
28019
28067
  `);
28020
28068
  writeFileSync5(options.outputPath, rows.length > 0 ? `${jsonl}
@@ -28105,6 +28153,19 @@ var init_db = __esm(() => {
28105
28153
  import { spawn as spawn3 } from "child_process";
28106
28154
  import { createHash as createHash3, randomUUID } from "crypto";
28107
28155
  import { readFileSync as readFileSync12 } from "fs";
28156
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve8 } from "path";
28157
+ function isPathWithinRoot(candidatePath, rootPath) {
28158
+ const candidate = resolve8(candidatePath);
28159
+ const root = resolve8(rootPath);
28160
+ const rel = relative2(root, candidate);
28161
+ return rel === "" || rel.length > 0 && !rel.startsWith("..") && !isAbsolute2(rel);
28162
+ }
28163
+ function assertSkillPathWithinRoots(field, path, roots) {
28164
+ const allowed = roots.some((root) => isPathWithinRoot(path, root));
28165
+ if (!allowed) {
28166
+ throw new CompatGuardError(field, `skill path '${path}' not under any --allow-skills-roots entry`);
28167
+ }
28168
+ }
28108
28169
  function hasUnsubstitutedVariables(template, variables) {
28109
28170
  const matches = template.match(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g) ?? [];
28110
28171
  for (const match of matches) {
@@ -28123,8 +28184,8 @@ function compatGuard(spec, trust) {
28123
28184
  if (execution.permission_required !== "READ_ONLY")
28124
28185
  throw new CompatGuardError("execution.permission_required", "permission_required must be READ_ONLY");
28125
28186
  const hasScripts = (spec.specialist.skills?.scripts?.length ?? 0) > 0;
28126
- if (hasScripts && !trust?.allowLocalScripts) {
28127
- throw new CompatGuardError("skills.scripts", "scripts not allowed (enable with --allow-local-scripts)");
28187
+ if (hasScripts) {
28188
+ throw new CompatGuardError("skills.scripts", "local scripts are not supported in script-class specialists");
28128
28189
  }
28129
28190
  const hasPaths = (spec.specialist.skills?.paths?.length ?? 0) > 0;
28130
28191
  const hasSkillInherit = Boolean(spec.specialist.prompt.skill_inherit);
@@ -28134,26 +28195,34 @@ function compatGuard(spec, trust) {
28134
28195
  if (hasSkillInherit && !trust?.allowSkills) {
28135
28196
  throw new CompatGuardError("prompt.skill_inherit", "skills not allowed (enable with --allow-skills)");
28136
28197
  }
28137
- if (hasPaths && trust?.allowSkills && trust.allowSkillsRoots && trust.allowSkillsRoots.length > 0) {
28198
+ if (trust?.allowSkills && trust.allowSkillsRoots && trust.allowSkillsRoots.length > 0) {
28138
28199
  const paths = spec.specialist.skills?.paths ?? [];
28139
- for (const path of paths) {
28140
- const allowed = trust.allowSkillsRoots.some((root) => path.startsWith(root));
28141
- if (!allowed) {
28142
- throw new CompatGuardError("skills.paths", `skill path '${path}' not under any --allow-skills-roots entry`);
28143
- }
28200
+ for (const path of paths)
28201
+ assertSkillPathWithinRoots("skills.paths", path, trust.allowSkillsRoots);
28202
+ if (typeof spec.specialist.prompt.skill_inherit === "string") {
28203
+ assertSkillPathWithinRoots("prompt.skill_inherit", spec.specialist.prompt.skill_inherit, trust.allowSkillsRoots);
28144
28204
  }
28145
28205
  }
28146
28206
  }
28207
+ function collectSkillPathEntries(spec) {
28208
+ return [
28209
+ ...(spec.specialist.skills?.paths ?? []).map((path) => ({ path, source: "skills.paths" })),
28210
+ ...typeof spec.specialist.prompt.skill_inherit === "string" ? [{ path: spec.specialist.prompt.skill_inherit, source: "prompt.skill_inherit" }] : []
28211
+ ];
28212
+ }
28213
+ function collectSkillPaths(spec) {
28214
+ return collectSkillPathEntries(spec).map((entry) => entry.path);
28215
+ }
28147
28216
  function computeSkillSources(spec) {
28148
- const paths = spec.specialist.skills?.paths ?? [];
28217
+ const entries = collectSkillPathEntries(spec);
28149
28218
  const sources = [];
28150
- for (const path of paths) {
28219
+ for (const { path, source } of entries) {
28151
28220
  try {
28152
28221
  const content = readFileSync12(path);
28153
28222
  const sha256 = createHash3("sha256").update(content).digest("hex");
28154
- sources.push({ path, sha256 });
28223
+ sources.push({ path, sha256, source });
28155
28224
  } catch {
28156
- sources.push({ path, sha256: "unreadable" });
28225
+ sources.push({ path, sha256: "unreadable", source });
28157
28226
  }
28158
28227
  }
28159
28228
  return sources;
@@ -28164,6 +28233,34 @@ function renderTaskTemplate(template, variables) {
28164
28233
  throw new Error(`Missing template variable: ${missing}`);
28165
28234
  return renderTemplate(template, variables);
28166
28235
  }
28236
+ function truncateForPrompt(value, limitBytes) {
28237
+ if (Buffer.byteLength(value, "utf8") <= limitBytes)
28238
+ return value;
28239
+ return `${value.slice(0, limitBytes)}
28240
+ ... truncated ...`;
28241
+ }
28242
+ function buildJsonOutputContract(spec) {
28243
+ if (spec.specialist.execution.response_format !== "json")
28244
+ return;
28245
+ const schema = spec.specialist.prompt.output_schema;
28246
+ const required2 = Array.isArray(schema?.required) ? schema.required.filter((value) => typeof value === "string") : [];
28247
+ const lines = [
28248
+ "Output contract:",
28249
+ "- Return only valid JSON. Do not include Markdown fences, prose, or commentary."
28250
+ ];
28251
+ if (required2.length > 0)
28252
+ lines.push(`- Include these required top-level keys: ${required2.join(", ")}.`);
28253
+ if (schema)
28254
+ lines.push(`- JSON schema: ${truncateForPrompt(JSON.stringify(schema), 4096)}`);
28255
+ return lines.join(`
28256
+ `);
28257
+ }
28258
+ function applyOutputContract(prompt, spec) {
28259
+ const contract = buildJsonOutputContract(spec);
28260
+ return contract ? `${prompt}
28261
+
28262
+ ${contract}` : prompt;
28263
+ }
28167
28264
  function mapErrorType(message) {
28168
28265
  if (message.includes("Specialist not found"))
28169
28266
  return "specialist_not_found";
@@ -28171,6 +28268,8 @@ function mapErrorType(message) {
28171
28268
  return "specialist_load_error";
28172
28269
  if (message.includes("Missing template variable"))
28173
28270
  return "template_variable_missing";
28271
+ if (message.includes("prompt too large"))
28272
+ return "prompt_too_large";
28174
28273
  if (message.includes("output too large"))
28175
28274
  return "output_too_large";
28176
28275
  if (message.includes("auth") || message.includes("403") || message.includes("401"))
@@ -28238,6 +28337,18 @@ function writeTraceRow(client, specialist, model, traceId, output2, durationMs,
28238
28337
  onAuditFailure?.(error2);
28239
28338
  }
28240
28339
  }
28340
+ function resolvePromptLimitBytes(spec) {
28341
+ return spec.specialist.execution.prompt_limit_bytes ?? resolveEnvPromptLimitBytes() ?? DEFAULT_PROMPT_LIMIT_BYTES;
28342
+ }
28343
+ function resolveEnvPromptLimitBytes() {
28344
+ const raw = process.env.SPECIALISTS_SCRIPT_PROMPT_LIMIT_BYTES;
28345
+ if (raw === undefined)
28346
+ return;
28347
+ const envLimit = Number(raw);
28348
+ if (!Number.isFinite(envLimit) || envLimit <= 0)
28349
+ return;
28350
+ return Math.floor(envLimit);
28351
+ }
28241
28352
  function resolveAssistantTextLimitBytes(spec) {
28242
28353
  return spec.specialist.execution.stdout_limit_bytes ?? resolveEnvAssistantTextLimitBytes() ?? DEFAULT_ASSISTANT_TEXT_LIMIT_BYTES;
28243
28354
  }
@@ -28253,8 +28364,9 @@ function resolveEnvAssistantTextLimitBytes() {
28253
28364
  return Math.floor(envLimit);
28254
28365
  }
28255
28366
  function openObservabilityClient(options) {
28256
- const dbPath = options.observabilityDbPath ?? options.projectDir;
28257
- return createObservabilitySqliteClient(dbPath);
28367
+ if (options.observabilityDbPath)
28368
+ return createObservabilitySqliteClientAtPath(options.observabilityDbPath);
28369
+ return createObservabilitySqliteClient(options.projectDir);
28258
28370
  }
28259
28371
  function resolveScriptSpecialistName(name) {
28260
28372
  if (name === "changelog-keeper")
@@ -28268,9 +28380,28 @@ async function runScriptSpecialist(input2, options) {
28268
28380
  const resolvedSpecialist = resolveScriptSpecialistName(input2.specialist);
28269
28381
  const spec = await options.loader.get(resolvedSpecialist);
28270
28382
  compatGuard(spec, options.trust);
28383
+ const skillPaths = options.trust?.allowSkills ? collectSkillPaths(spec) : [];
28271
28384
  const skillSources = options.trust?.allowSkills ? computeSkillSources(spec) : undefined;
28272
28385
  const template = input2.template ?? spec.specialist.prompt.task_template;
28273
- const prompt = renderTaskTemplate(template, input2.variables ?? {});
28386
+ const prompt = applyOutputContract(renderTaskTemplate(template, input2.variables ?? {}), spec);
28387
+ const modelCandidates = collectModelCandidates(input2, spec, options);
28388
+ const promptLimitBytes = resolvePromptLimitBytes(spec);
28389
+ const promptBytes = Buffer.byteLength(prompt, "utf8");
28390
+ if (promptBytes > promptLimitBytes) {
28391
+ return {
28392
+ success: false,
28393
+ error: `prompt too large: ${promptBytes} bytes exceeds limit ${promptLimitBytes} bytes`,
28394
+ error_type: "prompt_too_large",
28395
+ meta: {
28396
+ specialist: resolvedSpecialist,
28397
+ requested_specialist: input2.requested_specialist ?? input2.specialist,
28398
+ resolved_specialist: resolvedSpecialist,
28399
+ model: modelCandidates[0],
28400
+ duration_ms: Date.now() - startedAt,
28401
+ trace_id: traceId
28402
+ }
28403
+ };
28404
+ }
28274
28405
  if (process.env.SPECIALISTS_SCRIPT_STUB_OUTPUT) {
28275
28406
  return {
28276
28407
  success: true,
@@ -28286,11 +28417,11 @@ async function runScriptSpecialist(input2, options) {
28286
28417
  };
28287
28418
  }
28288
28419
  const timeoutMs = input2.timeout_ms ?? spec.specialist.execution.timeout_ms ?? 120000;
28289
- const modelCandidates = collectModelCandidates(input2, spec, options);
28290
28420
  const assistantTextLimitBytes = resolveAssistantTextLimitBytes(spec);
28291
28421
  const attempts = [];
28292
28422
  for (const model of modelCandidates) {
28293
- const attempt = await runSingleAttempt(prompt, model, input2.thinking_level ?? spec.specialist.execution.thinking_level, timeoutMs, assistantTextLimitBytes, options);
28423
+ const systemPrompt = spec.specialist.prompt.system || undefined;
28424
+ const attempt = await runSingleAttempt(prompt, model, input2.thinking_level ?? spec.specialist.execution.thinking_level, timeoutMs, assistantTextLimitBytes, options, systemPrompt, skillPaths);
28294
28425
  attempts.push(attempt);
28295
28426
  const parsed = classifyAttempt(attempt);
28296
28427
  if (parsed.retryable)
@@ -28333,14 +28464,23 @@ function collectModelCandidates(input2, spec, options) {
28333
28464
  const candidates = [input2.model_override, spec.specialist.execution.model, spec.specialist.execution.fallback_model, options.fallbackModel].filter((value) => typeof value === "string" && value.length > 0);
28334
28465
  return [...new Set(candidates)];
28335
28466
  }
28336
- function runSingleAttempt(prompt, model, thinkingLevel, timeoutMs, assistantTextLimitBytes, options) {
28337
- return new Promise((resolve8, reject) => {
28338
- const args = ["--mode", "json", "--no-session", "--no-extensions", "--no-tools", "--model", model];
28467
+ function runSingleAttempt(prompt, model, thinkingLevel, timeoutMs, assistantTextLimitBytes, options, systemPrompt, skillPaths = []) {
28468
+ return new Promise((resolve9, reject) => {
28469
+ const args = ["--mode", "json", "--no-session", "--no-extensions", "--no-tools", "--offline", "--no-context-files", "--no-prompt-templates", "--no-themes"];
28470
+ if (skillPaths.length === 0)
28471
+ args.push("--no-skills");
28472
+ for (const skillPath of skillPaths)
28473
+ args.push("--skill", skillPath);
28474
+ args.push("--model", model);
28339
28475
  if (thinkingLevel)
28340
28476
  args.push("--thinking", thinkingLevel);
28341
- args.push(prompt);
28342
- const pi = spawn3("pi", args, { stdio: ["ignore", "pipe", "pipe"] });
28477
+ if (systemPrompt)
28478
+ args.push("--system-prompt", systemPrompt);
28479
+ const pi = spawn3("pi", args, { stdio: ["pipe", "pipe", "pipe"], cwd: options.projectDir ?? process.cwd() });
28343
28480
  options.onChild?.(pi);
28481
+ pi.stdin?.on("error", () => {});
28482
+ pi.stdin?.write(prompt);
28483
+ pi.stdin?.end();
28344
28484
  let stderr = "";
28345
28485
  let timedOut = false;
28346
28486
  let outputTooLarge = false;
@@ -28409,7 +28549,7 @@ function runSingleAttempt(prompt, model, thinkingLevel, timeoutMs, assistantText
28409
28549
  pi.on("error", reject);
28410
28550
  pi.on("close", (code) => {
28411
28551
  clearTimeout(timer);
28412
- resolve8({
28552
+ resolve9({
28413
28553
  model,
28414
28554
  text: assistantText,
28415
28555
  stderr,
@@ -28446,7 +28586,7 @@ function classifyAttempt(attempt) {
28446
28586
  function isRetryableModelFailure(stderr, text) {
28447
28587
  return stderr.includes("0 tokens") || stderr.includes("quota") || stderr.includes("rate limit") || stderr.includes("403") || stderr.includes("401") || stderr.includes("insufficient_quota") || !text && !stderr.trim();
28448
28588
  }
28449
- var CompatGuardError, DEFAULT_PENDING_LINE_LIMIT_BYTES, DEFAULT_ASSISTANT_TEXT_LIMIT_BYTES, DEFAULT_STDERR_LIMIT_BYTES;
28589
+ var CompatGuardError, DEFAULT_PENDING_LINE_LIMIT_BYTES, DEFAULT_ASSISTANT_TEXT_LIMIT_BYTES, DEFAULT_STDERR_LIMIT_BYTES, DEFAULT_PROMPT_LIMIT_BYTES;
28450
28590
  var init_script_runner = __esm(() => {
28451
28591
  init_observability_sqlite();
28452
28592
  CompatGuardError = class CompatGuardError extends Error {
@@ -28460,6 +28600,7 @@ var init_script_runner = __esm(() => {
28460
28600
  DEFAULT_PENDING_LINE_LIMIT_BYTES = 16 * 1024 * 1024;
28461
28601
  DEFAULT_ASSISTANT_TEXT_LIMIT_BYTES = 4 * 1024 * 1024;
28462
28602
  DEFAULT_STDERR_LIMIT_BYTES = 1 * 1024 * 1024;
28603
+ DEFAULT_PROMPT_LIMIT_BYTES = 4 * 1024 * 1024;
28463
28604
  });
28464
28605
 
28465
28606
  // src/cli/validate.ts
@@ -28612,7 +28753,7 @@ var exports_edit = {};
28612
28753
  __export(exports_edit, {
28613
28754
  run: () => run11
28614
28755
  });
28615
- import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
28756
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
28616
28757
  import { join as join13 } from "path";
28617
28758
  function loadPresets() {
28618
28759
  const paths = [
@@ -29050,7 +29191,7 @@ function createUserFork(source, targetName) {
29050
29191
  if (source.scope === "user")
29051
29192
  return source;
29052
29193
  const targetDir = join13(process.cwd(), ".specialists", "user");
29053
- mkdirSync6(targetDir, { recursive: true });
29194
+ mkdirSync7(targetDir, { recursive: true });
29054
29195
  const targetFile = join13(targetDir, `${targetName}.specialist.json`);
29055
29196
  const doc2 = readJsonFile2(source.filePath);
29056
29197
  doc2.specialist = doc2.specialist ?? {};
@@ -29360,7 +29501,7 @@ __export(exports_config, {
29360
29501
  });
29361
29502
  import { readFileSync as readFileSync14 } from "fs";
29362
29503
  import { spawnSync as spawnSync10 } from "child_process";
29363
- import { dirname as dirname7, join as join15 } from "path";
29504
+ import { dirname as dirname8, join as join15 } from "path";
29364
29505
  import { fileURLToPath as fileURLToPath3 } from "url";
29365
29506
  function usage2() {
29366
29507
  return [
@@ -29406,7 +29547,7 @@ function getGitTopLevel(projectDir) {
29406
29547
  return result.stdout.trim() || undefined;
29407
29548
  }
29408
29549
  function getRuntimePackageVersion() {
29409
- const runtimeDir = dirname7(fileURLToPath3(import.meta.url));
29550
+ const runtimeDir = dirname8(fileURLToPath3(import.meta.url));
29410
29551
  const packageJsonPath = runtimeDir.includes("/dist/") ? join15(runtimeDir, "..", "package.json") : join15(runtimeDir, "..", "..", "package.json");
29411
29552
  return readPackageVersion(packageJsonPath);
29412
29553
  }
@@ -29545,8 +29686,8 @@ var init_config = __esm(() => {
29545
29686
  });
29546
29687
 
29547
29688
  // src/specialist/worktree.ts
29548
- import { existsSync as existsSync16, symlinkSync as symlinkSync2, mkdirSync as mkdirSync7 } from "fs";
29549
- import { join as join16, resolve as resolve8 } from "path";
29689
+ import { existsSync as existsSync16, symlinkSync as symlinkSync2, mkdirSync as mkdirSync8 } from "fs";
29690
+ import { join as join16, resolve as resolve9 } from "path";
29550
29691
  import { spawnSync as spawnSync11, execFileSync as execFileSync2 } from "child_process";
29551
29692
  function deriveBranchName(beadId, specialistName) {
29552
29693
  return `feature/${beadId}-${slugify(specialistName)}`;
@@ -29579,11 +29720,11 @@ function provisionWorktree(options) {
29579
29720
  const branch = deriveBranchName(options.beadId, options.specialistName);
29580
29721
  const existingPath = findExistingWorktree(branch, cwd);
29581
29722
  if (existingPath) {
29582
- return { branch, worktreePath: resolve8(existingPath), reused: true };
29723
+ return { branch, worktreePath: resolve9(existingPath), reused: true };
29583
29724
  }
29584
29725
  const worktreeBase = options.worktreeBase ?? join16(commonRoot, ".worktrees", options.beadId);
29585
29726
  const worktreeName = deriveWorktreeName(options.beadId, options.specialistName);
29586
- const worktreePath = resolve8(join16(worktreeBase, worktreeName));
29727
+ const worktreePath = resolve9(join16(worktreeBase, worktreeName));
29587
29728
  createWorktreeViaBd(worktreePath, branch, commonRoot);
29588
29729
  symlinkPiNpmCache(commonRoot, worktreePath);
29589
29730
  return { branch, worktreePath, reused: false };
@@ -29594,7 +29735,7 @@ function symlinkPiNpmCache(commonRoot, worktreePath) {
29594
29735
  if (!existsSync16(source) || existsSync16(target))
29595
29736
  return;
29596
29737
  try {
29597
- mkdirSync7(join16(worktreePath, ".pi"), { recursive: true });
29738
+ mkdirSync8(join16(worktreePath, ".pi"), { recursive: true });
29598
29739
  symlinkSync2(source, target);
29599
29740
  } catch {}
29600
29741
  }
@@ -29634,12 +29775,12 @@ var init_worktree = __esm(() => {
29634
29775
  });
29635
29776
 
29636
29777
  // src/specialist/epic-reconciler.ts
29637
- import { mkdirSync as mkdirSync8, openSync as openSync2, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
29778
+ import { mkdirSync as mkdirSync9, openSync as openSync2, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
29638
29779
  import { join as join17 } from "path";
29639
29780
  function buildEpicLockPath(epicId) {
29640
29781
  const location = resolveObservabilityDbLocation();
29641
29782
  const lockDir = join17(location.dbDirectory, "locks");
29642
- mkdirSync8(lockDir, { recursive: true });
29783
+ mkdirSync9(lockDir, { recursive: true });
29643
29784
  return join17(lockDir, `epic-${epicId}.lock`);
29644
29785
  }
29645
29786
  function withEpicAdvisoryLock(epicId, action) {
@@ -30981,13 +31122,13 @@ async function parseArgs7(argv) {
30981
31122
  process.exit(1);
30982
31123
  }
30983
31124
  if (!prompt && !beadId && !process.stdin.isTTY) {
30984
- prompt = await new Promise((resolve9) => {
31125
+ prompt = await new Promise((resolve10) => {
30985
31126
  let buf = "";
30986
31127
  process.stdin.setEncoding("utf-8");
30987
31128
  process.stdin.on("data", (chunk) => {
30988
31129
  buf += chunk;
30989
31130
  });
30990
- process.stdin.on("end", () => resolve9(buf.trim()));
31131
+ process.stdin.on("end", () => resolve10(buf.trim()));
30991
31132
  });
30992
31133
  }
30993
31134
  if (!prompt && !beadId && !reuseJobId) {
@@ -31105,7 +31246,7 @@ function resolveWorkingDirectory(args, jobsDir, permissionRequired, readStatus)
31105
31246
  if (args.reuseJobId !== undefined) {
31106
31247
  const targetStatus = readStatus(args.reuseJobId);
31107
31248
  if (!targetStatus) {
31108
- console.error(`Error: cannot read status for job '${args.reuseJobId}'. ` + `Check the job id with: specialists poll ${args.reuseJobId} --json`);
31249
+ console.error(`Error: cannot read status for job '${args.reuseJobId}'. ` + `Check the job id with: specialists ps ${args.reuseJobId} --json`);
31109
31250
  process.exit(1);
31110
31251
  }
31111
31252
  const targetJobStatus = targetStatus.status;
@@ -31553,7 +31694,9 @@ ${bold11(`Running ${cyan6(args.name)}`)}
31553
31694
  ${green9("\u2713")} ${footer}
31554
31695
 
31555
31696
  `);
31556
- process.stderr.write(dim9(`Poll: specialists poll ${jobId} --json
31697
+ process.stderr.write(dim9(`Status: specialists ps ${jobId} --json
31698
+ `));
31699
+ process.stderr.write(dim9(`Events: specialists feed ${jobId}
31557
31700
 
31558
31701
  `));
31559
31702
  process.exit(0);
@@ -31658,8 +31801,8 @@ class JobControl {
31658
31801
  }
31659
31802
  };
31660
31803
  let resolveJobId;
31661
- const jobIdPromise = new Promise((resolve9) => {
31662
- resolveJobId = resolve9;
31804
+ const jobIdPromise = new Promise((resolve10) => {
31805
+ resolveJobId = resolve10;
31663
31806
  });
31664
31807
  this.supervisor = new Supervisor({
31665
31808
  runner: this.runner,
@@ -31724,7 +31867,7 @@ class JobControl {
31724
31867
  if (deadline !== undefined && Date.now() >= deadline) {
31725
31868
  throw new Error(`Timed out waiting for terminal status for job ${jobId}`);
31726
31869
  }
31727
- await new Promise((resolve9) => setTimeout(resolve9, backoffMs));
31870
+ await new Promise((resolve10) => setTimeout(resolve10, backoffMs));
31728
31871
  backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
31729
31872
  }
31730
31873
  }
@@ -31903,7 +32046,7 @@ function hashOutput(output2, salt) {
31903
32046
  return createHash4("sha256").update(value).digest("hex");
31904
32047
  }
31905
32048
  function sleep2(ms) {
31906
- return new Promise((resolve9) => setTimeout(resolve9, ms));
32049
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
31907
32050
  }
31908
32051
  function toContextHealth(contextPct) {
31909
32052
  if (contextPct === null)
@@ -33819,7 +33962,7 @@ __export(exports_node, {
33819
33962
  import { existsSync as existsSync19, readFileSync as readFileSync19, readdirSync as readdirSync7 } from "fs";
33820
33963
  import { randomUUID as randomUUID2 } from "crypto";
33821
33964
  import { spawnSync as spawnSync14 } from "child_process";
33822
- import { basename as basename5, join as join21, resolve as resolve9 } from "path";
33965
+ import { basename as basename5, join as join21, resolve as resolve10 } from "path";
33823
33966
  function parseNodeArgs(argv) {
33824
33967
  const command = argv[0];
33825
33968
  const supportedCommands = new Set(["run", "list", "promote", "members", "memory", "stop", "spawn-member", "create-bead", "complete", "wait-phase"]);
@@ -34008,7 +34151,7 @@ function toNodeName(filePath) {
34008
34151
  function discoverNodeConfigs(cwd) {
34009
34152
  const discoveredByName = new Map;
34010
34153
  for (const directory of NODE_DISCOVERY_DIRS) {
34011
- const absoluteDir = resolve9(cwd, directory.path);
34154
+ const absoluteDir = resolve10(cwd, directory.path);
34012
34155
  if (!existsSync19(absoluteDir))
34013
34156
  continue;
34014
34157
  const files = readdirSync7(absoluteDir).filter((fileName) => fileName.endsWith(NODE_CONFIG_SUFFIX));
@@ -34030,7 +34173,7 @@ function getNodeDiscoverySummary() {
34030
34173
  `);
34031
34174
  }
34032
34175
  function resolveNodeConfigPath(cwd, input2) {
34033
- const explicitPath = resolve9(cwd, input2);
34176
+ const explicitPath = resolve10(cwd, input2);
34034
34177
  if (existsSync19(explicitPath)) {
34035
34178
  return { name: toNodeName(explicitPath), path: explicitPath, source: "repo" };
34036
34179
  }
@@ -34583,7 +34726,7 @@ async function handleNodeAction(args) {
34583
34726
  if (deadline !== null && Date.now() >= deadline) {
34584
34727
  throw new Error(`Timed out waiting for phase '${args.phaseId}' members: ${memberKeys.join(", ")}`);
34585
34728
  }
34586
- await new Promise((resolve10) => setTimeout(resolve10, 500));
34729
+ await new Promise((resolve11) => setTimeout(resolve11, 500));
34587
34730
  }
34588
34731
  } finally {
34589
34732
  sqliteClient.close();
@@ -35455,9 +35598,19 @@ var init_epic = __esm(() => {
35455
35598
 
35456
35599
  // src/cli/version-check.ts
35457
35600
  import { spawnSync as spawnSync16 } from "child_process";
35458
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
35459
- import { dirname as dirname8, join as join22 } from "path";
35601
+ import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
35602
+ import { dirname as dirname9, join as join22 } from "path";
35460
35603
  import { createRequire as createRequire3 } from "module";
35604
+ function readBundledPackageVersion(requireFn = require3) {
35605
+ for (const candidate of ["../package.json", "../../package.json"]) {
35606
+ try {
35607
+ const pkg = requireFn(candidate);
35608
+ if (typeof pkg.version === "string" && pkg.version.length > 0)
35609
+ return pkg.version;
35610
+ } catch {}
35611
+ }
35612
+ return "0.0.0";
35613
+ }
35461
35614
  function shouldRunVersionCheck() {
35462
35615
  if (process.env.SPECIALISTS_OFFLINE === "1")
35463
35616
  return false;
@@ -35480,7 +35633,7 @@ function readCachedVersionCheck() {
35480
35633
  return readCache();
35481
35634
  }
35482
35635
  function writeCache(cache) {
35483
- mkdirSync9(dirname8(CACHE_PATH), { recursive: true });
35636
+ mkdirSync10(dirname9(CACHE_PATH), { recursive: true });
35484
35637
  writeFileSync9(CACHE_PATH, `${JSON.stringify(cache, null, 2)}
35485
35638
  `, "utf8");
35486
35639
  }
@@ -35565,7 +35718,7 @@ function markVersionCheckNotified(result) {
35565
35718
  var require3, packageVersion, localVersion, CACHE_PATH, CACHE_MAX_AGE_MS2, NETWORK_TIMEOUT_MS = 2000;
35566
35719
  var init_version_check = __esm(() => {
35567
35720
  require3 = createRequire3(import.meta.url);
35568
- ({ version: packageVersion } = require3("../../package.json"));
35721
+ packageVersion = readBundledPackageVersion();
35569
35722
  localVersion = packageVersion;
35570
35723
  CACHE_PATH = join22(process.cwd(), ".specialists", "version-check.json");
35571
35724
  CACHE_MAX_AGE_MS2 = 6 * 60 * 60 * 1000;
@@ -37382,9 +37535,6 @@ function readJobEvents(jobDir) {
37382
37535
  events.sort(compareTimelineEvents);
37383
37536
  return events;
37384
37537
  }
37385
- function readJobEventsById(jobsDir, jobId) {
37386
- return readJobEvents(join26(jobsDir, jobId));
37387
- }
37388
37538
  function readAllJobEvents(jobsDir) {
37389
37539
  const sqliteClient = createObservabilitySqliteClient();
37390
37540
  try {
@@ -38056,7 +38206,7 @@ async function followMerged(sqliteClient, jobsDir, options) {
38056
38206
  }
38057
38207
  const lastPrintedEventKey = new Map;
38058
38208
  const seenMetaKey = new Map;
38059
- await new Promise((resolve10) => {
38209
+ await new Promise((resolve11) => {
38060
38210
  const interval = setInterval(() => {
38061
38211
  const currentJobIds = listMatchingJobIds(sqliteClient, jobsDir, options);
38062
38212
  const statusByJobId = new Map;
@@ -38144,7 +38294,7 @@ async function followMerged(sqliteClient, jobsDir, options) {
38144
38294
  });
38145
38295
  if (completedJobs.size === trackedJobs.size || allTrackedTerminal) {
38146
38296
  clearInterval(interval);
38147
- resolve10();
38297
+ resolve11();
38148
38298
  }
38149
38299
  }
38150
38300
  }, 750);
@@ -38189,130 +38339,13 @@ var init_feed = __esm(() => {
38189
38339
  init_format_helpers();
38190
38340
  });
38191
38341
 
38192
- // src/cli/poll.ts
38193
- var exports_poll = {};
38194
- __export(exports_poll, {
38195
- run: () => run19
38196
- });
38197
- import { existsSync as existsSync26, readFileSync as readFileSync26 } from "fs";
38198
- import { join as join28 } from "path";
38199
- function parseArgs11(argv) {
38200
- let jobId;
38201
- let cursor = 0;
38202
- let outputCursor = 0;
38203
- for (let i = 0;i < argv.length; i++) {
38204
- if (argv[i] === "--cursor" && argv[i + 1]) {
38205
- cursor = parseInt(argv[++i], 10);
38206
- if (isNaN(cursor) || cursor < 0)
38207
- cursor = 0;
38208
- continue;
38209
- }
38210
- if (argv[i] === "--output-cursor" && argv[i + 1]) {
38211
- outputCursor = parseInt(argv[++i], 10);
38212
- if (isNaN(outputCursor) || outputCursor < 0)
38213
- outputCursor = 0;
38214
- continue;
38215
- }
38216
- if (argv[i] === "--json") {
38217
- continue;
38218
- }
38219
- if (argv[i] === "--follow" || argv[i] === "-f") {
38220
- process.stderr.write(`--follow removed from poll. Use 'specialists feed --follow' for live human-readable output.
38221
- `);
38222
- process.exit(1);
38223
- }
38224
- if (!argv[i].startsWith("-")) {
38225
- jobId = argv[i];
38226
- }
38227
- }
38228
- if (!jobId) {
38229
- console.error("Usage: specialists poll <job-id> [--cursor N] [--output-cursor N]");
38230
- process.exit(1);
38231
- }
38232
- return { jobId, cursor, outputCursor };
38233
- }
38234
- function readJobState(jobsDir, jobId, cursor, outputCursor) {
38235
- const jobDir = join28(jobsDir, jobId);
38236
- const statusPath = join28(jobDir, "status.json");
38237
- let status = null;
38238
- if (existsSync26(statusPath)) {
38239
- try {
38240
- status = JSON.parse(readFileSync26(statusPath, "utf-8"));
38241
- } catch {}
38242
- }
38243
- const resultPath = join28(jobDir, "result.txt");
38244
- let fullOutput = "";
38245
- if (existsSync26(resultPath)) {
38246
- try {
38247
- fullOutput = readFileSync26(resultPath, "utf-8");
38248
- } catch {}
38249
- }
38250
- const events = readJobEventsById(jobsDir, jobId);
38251
- const newEvents = events.slice(cursor);
38252
- const nextCursor = events.length;
38253
- const startedAt = status?.started_at_ms ?? Date.now();
38254
- const lastEvent = status?.last_event_at_ms ?? Date.now();
38255
- const elapsedMs = status?.status === "done" || status?.status === "error" ? lastEvent - startedAt : Date.now() - startedAt;
38256
- const isDone = status?.status === "done";
38257
- const output2 = isDone ? fullOutput : "";
38258
- const outputDelta = fullOutput.length > outputCursor ? fullOutput.slice(outputCursor) : "";
38259
- const nextOutputCursor = fullOutput.length;
38260
- return {
38261
- job_id: jobId,
38262
- status: status?.status ?? "starting",
38263
- elapsed_ms: elapsedMs,
38264
- cursor: nextCursor,
38265
- output_cursor: nextOutputCursor,
38266
- output: output2,
38267
- output_delta: outputDelta,
38268
- events: newEvents,
38269
- current_event: status?.current_event,
38270
- current_tool: status?.current_tool,
38271
- model: status?.model,
38272
- backend: status?.backend,
38273
- bead_id: status?.bead_id,
38274
- error: status?.error
38275
- };
38276
- }
38277
- async function run19() {
38278
- process.stderr.write(`[DEPRECATED] 'specialists poll' is scheduled for removal. Use 'sp ps <id> --json' for status and 'sp feed <id>' for events.
38279
- `);
38280
- const { jobId, cursor, outputCursor } = parseArgs11(process.argv.slice(3));
38281
- const jobsDir = join28(process.cwd(), ".specialists", "jobs");
38282
- const jobDir = join28(jobsDir, jobId);
38283
- if (!existsSync26(jobDir)) {
38284
- const result2 = {
38285
- job_id: jobId,
38286
- status: "error",
38287
- elapsed_ms: 0,
38288
- cursor: 0,
38289
- output_cursor: 0,
38290
- output: "",
38291
- output_delta: "",
38292
- events: [],
38293
- error: `Job not found: ${jobId}`
38294
- };
38295
- console.log(JSON.stringify(result2));
38296
- process.exit(1);
38297
- }
38298
- const result = readJobState(jobsDir, jobId, cursor, outputCursor);
38299
- if (result.status !== "done" && result.status !== "error" && !result.output_delta) {
38300
- process.stderr.write(`Tip: use 'specialists feed --follow' for live human-readable output.
38301
- `);
38302
- }
38303
- console.log(JSON.stringify(result));
38304
- }
38305
- var init_poll = __esm(() => {
38306
- init_timeline_query();
38307
- });
38308
-
38309
38342
  // src/cli/steer.ts
38310
38343
  var exports_steer = {};
38311
38344
  __export(exports_steer, {
38312
- run: () => run20
38345
+ run: () => run19
38313
38346
  });
38314
38347
  import { writeFileSync as writeFileSync10 } from "fs";
38315
- async function run20() {
38348
+ async function run19() {
38316
38349
  const jobId = process.argv[3];
38317
38350
  const message = process.argv[4];
38318
38351
  if (!jobId || !message) {
@@ -38363,10 +38396,10 @@ var init_steer = __esm(() => {
38363
38396
  // src/cli/resume.ts
38364
38397
  var exports_resume = {};
38365
38398
  __export(exports_resume, {
38366
- run: () => run21
38399
+ run: () => run20
38367
38400
  });
38368
38401
  import { writeFileSync as writeFileSync11 } from "fs";
38369
- async function run21() {
38402
+ async function run20() {
38370
38403
  const jobId = process.argv[3];
38371
38404
  const task = process.argv[4];
38372
38405
  if (!jobId || !task) {
@@ -38419,24 +38452,24 @@ var init_resume = __esm(() => {
38419
38452
  // src/cli/follow-up.ts
38420
38453
  var exports_follow_up = {};
38421
38454
  __export(exports_follow_up, {
38422
- run: () => run22
38455
+ run: () => run21
38423
38456
  });
38424
- async function run22() {
38457
+ async function run21() {
38425
38458
  process.stderr.write("\x1B[33m\u26A0 DEPRECATED:\x1B[0m `specialists follow-up` is deprecated. Use `specialists resume` instead.\n\n");
38426
38459
  const { run: resumeRun } = await Promise.resolve().then(() => (init_resume(), exports_resume));
38427
38460
  return resumeRun();
38428
38461
  }
38429
38462
 
38430
38463
  // src/specialist/worktree-gc.ts
38431
- import { existsSync as existsSync27, readdirSync as readdirSync11, readFileSync as readFileSync27 } from "fs";
38432
- import { join as join29 } from "path";
38464
+ import { existsSync as existsSync26, readdirSync as readdirSync11, readFileSync as readFileSync26 } from "fs";
38465
+ import { join as join28 } from "path";
38433
38466
  import { spawnSync as spawnSync19 } from "child_process";
38434
38467
  function readJobStatus2(jobDir) {
38435
- const statusPath = join29(jobDir, "status.json");
38436
- if (!existsSync27(statusPath))
38468
+ const statusPath = join28(jobDir, "status.json");
38469
+ if (!existsSync26(statusPath))
38437
38470
  return null;
38438
38471
  try {
38439
- return JSON.parse(readFileSync27(statusPath, "utf-8"));
38472
+ return JSON.parse(readFileSync26(statusPath, "utf-8"));
38440
38473
  } catch {
38441
38474
  return null;
38442
38475
  }
@@ -38458,7 +38491,7 @@ function collectWorktreeGcCandidates(jobsDir) {
38458
38491
  const worktreePath = status.worktree_path;
38459
38492
  if (!worktreePath)
38460
38493
  return null;
38461
- if (!existsSync27(worktreePath))
38494
+ if (!existsSync26(worktreePath))
38462
38495
  return null;
38463
38496
  return {
38464
38497
  jobId: status.id,
@@ -38470,13 +38503,13 @@ function collectWorktreeGcCandidates(jobsDir) {
38470
38503
  }
38471
38504
  if (!getFileFallbackEnabled())
38472
38505
  return [];
38473
- if (!existsSync27(jobsDir))
38506
+ if (!existsSync26(jobsDir))
38474
38507
  return [];
38475
38508
  const candidates = [];
38476
38509
  for (const entry of readdirSync11(jobsDir, { withFileTypes: true })) {
38477
38510
  if (!entry.isDirectory())
38478
38511
  continue;
38479
- const status = readJobStatus2(join29(jobsDir, entry.name));
38512
+ const status = readJobStatus2(join28(jobsDir, entry.name));
38480
38513
  if (!status)
38481
38514
  continue;
38482
38515
  if (isActive(status.status))
@@ -38486,7 +38519,7 @@ function collectWorktreeGcCandidates(jobsDir) {
38486
38519
  const { worktree_path: worktreePath, branch } = status;
38487
38520
  if (!worktreePath)
38488
38521
  continue;
38489
- if (!existsSync27(worktreePath))
38522
+ if (!existsSync26(worktreePath))
38490
38523
  continue;
38491
38524
  candidates.push({
38492
38525
  jobId: status.id,
@@ -38530,10 +38563,10 @@ var init_worktree_gc = __esm(() => {
38530
38563
  // src/cli/clean.ts
38531
38564
  var exports_clean = {};
38532
38565
  __export(exports_clean, {
38533
- run: () => run23
38566
+ run: () => run22
38534
38567
  });
38535
- import { existsSync as existsSync28, readFileSync as readFileSync28, readdirSync as readdirSync12, rmSync as rmSync3, statSync as statSync4 } from "fs";
38536
- import { join as join30 } from "path";
38568
+ import { existsSync as existsSync27, readFileSync as readFileSync27, readdirSync as readdirSync12, rmSync as rmSync3, statSync as statSync4 } from "fs";
38569
+ import { join as join29 } from "path";
38537
38570
  function parseTtlDaysFromEnvironment() {
38538
38571
  const rawValue = process.env.SPECIALISTS_JOB_TTL_DAYS ?? process.env.JOB_TTL_DAYS;
38539
38572
  if (!rawValue)
@@ -38616,7 +38649,7 @@ function parseOptions2(argv) {
38616
38649
  function readDirectorySizeBytes(directoryPath) {
38617
38650
  let totalBytes = 0;
38618
38651
  for (const entry of readdirSync12(directoryPath, { withFileTypes: true })) {
38619
- const entryPath = join30(directoryPath, entry.name);
38652
+ const entryPath = join29(directoryPath, entry.name);
38620
38653
  const stats = statSync4(entryPath);
38621
38654
  totalBytes += stats.isDirectory() ? readDirectorySizeBytes(entryPath) : stats.size;
38622
38655
  }
@@ -38624,7 +38657,7 @@ function readDirectorySizeBytes(directoryPath) {
38624
38657
  }
38625
38658
  function containsProtectedSqliteArtifact(directoryPath) {
38626
38659
  for (const entry of readdirSync12(directoryPath, { withFileTypes: true })) {
38627
- const entryPath = join30(directoryPath, entry.name);
38660
+ const entryPath = join29(directoryPath, entry.name);
38628
38661
  if (entry.isDirectory()) {
38629
38662
  if (containsProtectedSqliteArtifact(entryPath))
38630
38663
  return true;
@@ -38645,15 +38678,15 @@ function getJobTimestamps(status) {
38645
38678
  function readCompletedJobDirectory(baseDirectory, entry) {
38646
38679
  if (!entry.isDirectory())
38647
38680
  return null;
38648
- const directoryPath = join30(baseDirectory, entry.name);
38681
+ const directoryPath = join29(baseDirectory, entry.name);
38649
38682
  if (containsProtectedSqliteArtifact(directoryPath))
38650
38683
  return null;
38651
- const statusFilePath = join30(directoryPath, "status.json");
38652
- if (!existsSync28(statusFilePath))
38684
+ const statusFilePath = join29(directoryPath, "status.json");
38685
+ if (!existsSync27(statusFilePath))
38653
38686
  return null;
38654
38687
  let statusData;
38655
38688
  try {
38656
- statusData = JSON.parse(readFileSync28(statusFilePath, "utf-8"));
38689
+ statusData = JSON.parse(readFileSync27(statusFilePath, "utf-8"));
38657
38690
  } catch {
38658
38691
  return null;
38659
38692
  }
@@ -38667,8 +38700,8 @@ function collectCompletedJobs(jobsDirectoryPath) {
38667
38700
  const statuses = sqliteClient?.listStatuses() ?? [];
38668
38701
  if (statuses.length > 0) {
38669
38702
  return statuses.filter((status) => COMPLETED_STATUSES.has(status.status)).map((status) => {
38670
- const directoryPath = join30(jobsDirectoryPath, status.id);
38671
- if (!existsSync28(directoryPath) || containsProtectedSqliteArtifact(directoryPath))
38703
+ const directoryPath = join29(jobsDirectoryPath, status.id);
38704
+ if (!existsSync27(directoryPath) || containsProtectedSqliteArtifact(directoryPath))
38672
38705
  return null;
38673
38706
  const { createdAtMs, completedAtMs } = getJobTimestamps(status);
38674
38707
  return { id: status.id, directoryPath, completedAtMs, createdAtMs, sizeBytes: readDirectorySizeBytes(directoryPath) };
@@ -38808,7 +38841,7 @@ function removeStaleProcesses(statuses, dryRun) {
38808
38841
  }
38809
38842
  return updatedCount;
38810
38843
  }
38811
- async function run23() {
38844
+ async function run22() {
38812
38845
  let options;
38813
38846
  try {
38814
38847
  options = parseOptions2(process.argv.slice(3));
@@ -38817,7 +38850,7 @@ async function run23() {
38817
38850
  printUsageAndExit2(message);
38818
38851
  }
38819
38852
  const jobsDirectoryPath = resolveJobsDir();
38820
- if (!existsSync28(jobsDirectoryPath)) {
38853
+ if (!existsSync27(jobsDirectoryPath)) {
38821
38854
  console.log("No jobs directory found.");
38822
38855
  return;
38823
38856
  }
@@ -38871,7 +38904,7 @@ var init_clean = __esm(() => {
38871
38904
  // src/cli/end.ts
38872
38905
  var exports_end = {};
38873
38906
  __export(exports_end, {
38874
- run: () => run24
38907
+ run: () => run23
38875
38908
  });
38876
38909
  import { spawnSync as spawnSync20 } from "child_process";
38877
38910
  function parseOptions3(argv) {
@@ -38955,7 +38988,7 @@ async function publishChain(beadId, options) {
38955
38988
  console.log("Publication mode: direct merge");
38956
38989
  }
38957
38990
  }
38958
- async function run24() {
38991
+ async function run23() {
38959
38992
  let options;
38960
38993
  try {
38961
38994
  options = parseOptions3(process.argv.slice(3));
@@ -38996,7 +39029,7 @@ var init_end = __esm(() => {
38996
39029
  // src/cli/stop.ts
38997
39030
  var exports_stop = {};
38998
39031
  __export(exports_stop, {
38999
- run: () => run25
39032
+ run: () => run24
39000
39033
  });
39001
39034
  function resolveTerminalStatus(jobId) {
39002
39035
  return hasRunCompleteEvent(jobId) ? "done" : "cancelled";
@@ -39027,7 +39060,7 @@ async function waitForProcessExit(pid, timeoutMs) {
39027
39060
  while (Date.now() < deadline) {
39028
39061
  if (!isProcessAlive(pid))
39029
39062
  return true;
39030
- await new Promise((resolve10) => setTimeout(resolve10, 100));
39063
+ await new Promise((resolve11) => setTimeout(resolve11, 100));
39031
39064
  }
39032
39065
  return !isProcessAlive(pid);
39033
39066
  }
@@ -39039,7 +39072,7 @@ function tryKillProcessGroup(pid) {
39039
39072
  throw err;
39040
39073
  }
39041
39074
  }
39042
- async function run25() {
39075
+ async function run24() {
39043
39076
  let parsed;
39044
39077
  try {
39045
39078
  parsed = parseStopArgs(process.argv.slice(3));
@@ -39153,18 +39186,18 @@ var init_stop = __esm(() => {
39153
39186
  // src/cli/attach.ts
39154
39187
  var exports_attach = {};
39155
39188
  __export(exports_attach, {
39156
- run: () => run26
39189
+ run: () => run25
39157
39190
  });
39158
39191
  import { execFileSync as execFileSync3, spawnSync as spawnSync21 } from "child_process";
39159
- import { readFileSync as readFileSync29 } from "fs";
39160
- import { join as join31 } from "path";
39192
+ import { readFileSync as readFileSync28 } from "fs";
39193
+ import { join as join30 } from "path";
39161
39194
  function exitWithError(message) {
39162
39195
  console.error(message);
39163
39196
  process.exit(1);
39164
39197
  }
39165
39198
  function readStatus(statusPath, jobId) {
39166
39199
  try {
39167
- return JSON.parse(readFileSync29(statusPath, "utf-8"));
39200
+ return JSON.parse(readFileSync28(statusPath, "utf-8"));
39168
39201
  } catch (error2) {
39169
39202
  if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
39170
39203
  exitWithError(`Job \`${jobId}\` not found. Run \`specialists status\` to see active jobs in current mode.`);
@@ -39173,13 +39206,13 @@ function readStatus(statusPath, jobId) {
39173
39206
  exitWithError(`Failed to read status for job \`${jobId}\`: ${details}`);
39174
39207
  }
39175
39208
  }
39176
- async function run26() {
39209
+ async function run25() {
39177
39210
  const [jobId] = process.argv.slice(3);
39178
39211
  if (!jobId) {
39179
39212
  exitWithError("Usage: specialists attach <job-id> (normal runtime is DB-backed; job files are legacy/operator-only)");
39180
39213
  }
39181
- const jobsDir = join31(process.cwd(), ".specialists", "jobs");
39182
- const statusPath = join31(jobsDir, jobId, "status.json");
39214
+ const jobsDir = join30(process.cwd(), ".specialists", "jobs");
39215
+ const statusPath = join30(jobsDir, jobId, "status.json");
39183
39216
  const status = readStatus(statusPath, jobId);
39184
39217
  if (status.status === "done" || status.status === "error") {
39185
39218
  exitWithError(`Job \`${jobId}\` has already completed (status: ${status.status}). Use \`specialists result ${jobId}\` to read output.`);
@@ -39201,15 +39234,15 @@ async function run26() {
39201
39234
  var init_attach = () => {};
39202
39235
 
39203
39236
  // src/specialist/drift-detector.ts
39204
- import { existsSync as existsSync29, readFileSync as readFileSync30, readdirSync as readdirSync13, rmSync as rmSync4 } from "fs";
39205
- import { join as join32, resolve as resolve10, relative as relative2 } from "path";
39237
+ import { existsSync as existsSync28, readFileSync as readFileSync29, readdirSync as readdirSync13, rmSync as rmSync4 } from "fs";
39238
+ import { join as join31, resolve as resolve11, relative as relative3 } from "path";
39206
39239
  function listFiles(root) {
39207
- if (!existsSync29(root))
39240
+ if (!existsSync28(root))
39208
39241
  return [];
39209
39242
  const out = [];
39210
39243
  const visit2 = (dir) => {
39211
39244
  for (const entry of readdirSync13(dir, { withFileTypes: true })) {
39212
- const full = join32(dir, entry.name);
39245
+ const full = join31(dir, entry.name);
39213
39246
  if (entry.isDirectory()) {
39214
39247
  visit2(full);
39215
39248
  continue;
@@ -39222,7 +39255,7 @@ function listFiles(root) {
39222
39255
  return out;
39223
39256
  }
39224
39257
  function relPath(path, base) {
39225
- return relative2(base, path) || ".";
39258
+ return relative3(base, path) || ".";
39226
39259
  }
39227
39260
  function makeFinding(repoRoot, kind, scope, path, canonicalPath, bytesEqual) {
39228
39261
  const rel = relPath(path, repoRoot);
@@ -39240,18 +39273,18 @@ function detectDriftForRepo(repoRoot) {
39240
39273
  if (!asset.canonicalDir)
39241
39274
  continue;
39242
39275
  const scopes = [
39243
- { scope: "default", dir: resolve10(repoRoot, asset.managedDir) },
39244
- { scope: "user", dir: resolve10(repoRoot, ".specialists/user") }
39276
+ { scope: "default", dir: resolve11(repoRoot, asset.managedDir) },
39277
+ { scope: "user", dir: resolve11(repoRoot, ".specialists/user") }
39245
39278
  ];
39246
39279
  for (const { scope, dir } of scopes) {
39247
- if (!existsSync29(dir))
39280
+ if (!existsSync28(dir))
39248
39281
  continue;
39249
39282
  for (const file of listFiles(dir)) {
39250
39283
  const rel = relPath(file, dir);
39251
- const canonicalPath = join32(asset.canonicalDir, rel);
39252
- if (!existsSync29(canonicalPath))
39284
+ const canonicalPath = join31(asset.canonicalDir, rel);
39285
+ if (!existsSync28(canonicalPath))
39253
39286
  continue;
39254
- const bytesEqual = readFileSync30(file).equals(readFileSync30(canonicalPath));
39287
+ const bytesEqual = readFileSync29(file).equals(readFileSync29(canonicalPath));
39255
39288
  findings.push(makeFinding(repoRoot, asset.kind, scope, file, canonicalPath, bytesEqual));
39256
39289
  }
39257
39290
  }
@@ -39275,13 +39308,13 @@ function detectDriftUnderRoot(root) {
39275
39308
  continue;
39276
39309
  if (entry.name === "node_modules" || entry.name === ".git")
39277
39310
  continue;
39278
- visit2(join32(dir, entry.name));
39311
+ visit2(join31(dir, entry.name));
39279
39312
  }
39280
39313
  };
39281
- visit2(resolve10(root));
39314
+ visit2(resolve11(root));
39282
39315
  const summary = repos.flatMap((r) => r.findings);
39283
39316
  return {
39284
- root: resolve10(root),
39317
+ root: resolve11(root),
39285
39318
  repos,
39286
39319
  summary: {
39287
39320
  repos: repos.length,
@@ -39315,10 +39348,10 @@ var init_drift_detector = __esm(() => {
39315
39348
  // src/cli/prune-stale-defaults.ts
39316
39349
  var exports_prune_stale_defaults = {};
39317
39350
  __export(exports_prune_stale_defaults, {
39318
- run: () => run27
39351
+ run: () => run26
39319
39352
  });
39320
- import { resolve as resolve11 } from "path";
39321
- function parseArgs12(argv) {
39353
+ import { resolve as resolve12 } from "path";
39354
+ function parseArgs11(argv) {
39322
39355
  let dryRun = false;
39323
39356
  let root = process.cwd();
39324
39357
  let help = false;
@@ -39332,7 +39365,7 @@ function parseArgs12(argv) {
39332
39365
  const value = argv[i + 1];
39333
39366
  if (!value || value.startsWith("--"))
39334
39367
  throw new Error("--root requires a value");
39335
- root = resolve11(value);
39368
+ root = resolve12(value);
39336
39369
  i += 1;
39337
39370
  continue;
39338
39371
  }
@@ -39349,8 +39382,8 @@ function printHelp() {
39349
39382
  console.log(" --dry-run List stale default snapshots without pruning");
39350
39383
  console.log(" --root Repo root to scan");
39351
39384
  }
39352
- async function run27(argv = process.argv.slice(3)) {
39353
- const { dryRun, root, help } = parseArgs12(argv);
39385
+ async function run26(argv = process.argv.slice(3)) {
39386
+ const { dryRun, root, help } = parseArgs11(argv);
39354
39387
  if (help) {
39355
39388
  printHelp();
39356
39389
  return;
@@ -39376,7 +39409,7 @@ var init_prune_stale_defaults = __esm(() => {
39376
39409
  // src/cli/quickstart.ts
39377
39410
  var exports_quickstart = {};
39378
39411
  __export(exports_quickstart, {
39379
- run: () => run28
39412
+ run: () => run27
39380
39413
  });
39381
39414
  function section2(title) {
39382
39415
  const bar = "\u2500".repeat(60);
@@ -39390,7 +39423,7 @@ function cmd2(s) {
39390
39423
  function flag(s) {
39391
39424
  return green13(s);
39392
39425
  }
39393
- async function run28() {
39426
+ async function run27() {
39394
39427
  const lines = [
39395
39428
  "",
39396
39429
  bold12("specialists \xB7 Quick Start Guide"),
@@ -39605,7 +39638,7 @@ var bold12 = (s) => `\x1B[1m${s}\x1B[0m`, dim12 = (s) => `\x1B[2m${s}\x1B[0m`, y
39605
39638
  var exports_doctor = {};
39606
39639
  __export(exports_doctor, {
39607
39640
  setStatusError: () => setStatusError,
39608
- run: () => run29,
39641
+ run: () => run28,
39609
39642
  renderProcessSummary: () => renderProcessSummary,
39610
39643
  parseVersionTuple: () => parseVersionTuple,
39611
39644
  compareVersions: () => compareVersions2,
@@ -39613,8 +39646,8 @@ __export(exports_doctor, {
39613
39646
  });
39614
39647
  import { createHash as createHash5 } from "crypto";
39615
39648
  import { spawnSync as spawnSync22 } from "child_process";
39616
- import { existsSync as existsSync30, lstatSync as lstatSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync14, readFileSync as readFileSync31, readlinkSync as readlinkSync2, writeFileSync as writeFileSync12 } from "fs";
39617
- import { dirname as dirname9, join as join33, relative as relative3, resolve as resolve12 } from "path";
39649
+ import { existsSync as existsSync29, lstatSync as lstatSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync14, readFileSync as readFileSync30, readlinkSync as readlinkSync2, writeFileSync as writeFileSync12 } from "fs";
39650
+ import { dirname as dirname10, join as join32, relative as relative4, resolve as resolve13 } from "path";
39618
39651
  function ok3(msg) {
39619
39652
  console.log(` ${green14("\u2713")} ${msg}`);
39620
39653
  }
@@ -39643,10 +39676,10 @@ function isInstalled3(bin) {
39643
39676
  return spawnSync22("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
39644
39677
  }
39645
39678
  function loadJson2(path) {
39646
- if (!existsSync30(path))
39679
+ if (!existsSync29(path))
39647
39680
  return null;
39648
39681
  try {
39649
- return JSON.parse(readFileSync31(path, "utf8"));
39682
+ return JSON.parse(readFileSync30(path, "utf8"));
39650
39683
  } catch {
39651
39684
  return null;
39652
39685
  }
@@ -39689,7 +39722,7 @@ function checkBd() {
39689
39722
  return false;
39690
39723
  }
39691
39724
  ok3(`bd installed ${dim13(sp("bd", ["--version"]).stdout || "")}`);
39692
- if (existsSync30(join33(CWD, ".beads")))
39725
+ if (existsSync29(join32(CWD, ".beads")))
39693
39726
  ok3(".beads/ present in project");
39694
39727
  else
39695
39728
  warn3(".beads/ not found in project");
@@ -39709,22 +39742,22 @@ function checkHooks() {
39709
39742
  section3("Claude Code hooks (2 expected)");
39710
39743
  let allPresent = true;
39711
39744
  for (const name of HOOK_NAMES) {
39712
- const canonicalPath = join33(HOOKS_DIR, name);
39713
- if (!existsSync30(canonicalPath)) {
39714
- fail4(`${relative3(CWD, canonicalPath)} ${red7("missing")}`);
39745
+ const canonicalPath = join32(HOOKS_DIR, name);
39746
+ if (!existsSync29(canonicalPath)) {
39747
+ fail4(`${relative4(CWD, canonicalPath)} ${red7("missing")}`);
39715
39748
  fix("specialists init");
39716
39749
  allPresent = false;
39717
39750
  } else {
39718
- ok3(relative3(CWD, canonicalPath));
39751
+ ok3(relative4(CWD, canonicalPath));
39719
39752
  }
39720
- const claudeHookPath = join33(CLAUDE_HOOKS_DIR, name);
39753
+ const claudeHookPath = join32(CLAUDE_HOOKS_DIR, name);
39721
39754
  const symlinkState = isSymlinkTo(claudeHookPath, canonicalPath);
39722
39755
  if (symlinkState.ok) {
39723
- ok3(`${relative3(CWD, claudeHookPath)} -> ${relative3(dirname9(claudeHookPath), canonicalPath)}`);
39756
+ ok3(`${relative4(CWD, claudeHookPath)} -> ${relative4(dirname10(claudeHookPath), canonicalPath)}`);
39724
39757
  continue;
39725
39758
  }
39726
39759
  allPresent = false;
39727
- const relHookPath = relative3(CWD, claudeHookPath);
39760
+ const relHookPath = relative4(CWD, claudeHookPath);
39728
39761
  if (symlinkState.reason === "missing") {
39729
39762
  fail4(`${relHookPath} missing`);
39730
39763
  } else if (symlinkState.reason === "not-symlink") {
@@ -39792,30 +39825,30 @@ function checkVersion() {
39792
39825
  }
39793
39826
  function hashFile(path) {
39794
39827
  const hash = createHash5("sha256");
39795
- hash.update(readFileSync31(path));
39828
+ hash.update(readFileSync30(path));
39796
39829
  return hash.digest("hex");
39797
39830
  }
39798
39831
  function collectFileHashes(rootDir) {
39799
39832
  const hashes = new Map;
39800
39833
  const visit2 = (dir) => {
39801
39834
  for (const entry of readdirSync14(dir, { withFileTypes: true })) {
39802
- const fullPath = join33(dir, entry.name);
39835
+ const fullPath = join32(dir, entry.name);
39803
39836
  if (entry.isDirectory()) {
39804
39837
  visit2(fullPath);
39805
39838
  continue;
39806
39839
  }
39807
39840
  if (!entry.isFile())
39808
39841
  continue;
39809
- const relPath2 = relative3(rootDir, fullPath);
39842
+ const relPath2 = relative4(rootDir, fullPath);
39810
39843
  hashes.set(relPath2, hashFile(fullPath));
39811
39844
  }
39812
39845
  };
39813
- if (existsSync30(rootDir))
39846
+ if (existsSync29(rootDir))
39814
39847
  visit2(rootDir);
39815
39848
  return hashes;
39816
39849
  }
39817
39850
  function isSymlinkTo(linkPath, expectedTargetPath) {
39818
- if (!existsSync30(linkPath))
39851
+ if (!existsSync29(linkPath))
39819
39852
  return { ok: false, reason: "missing" };
39820
39853
  let stats;
39821
39854
  try {
@@ -39827,8 +39860,8 @@ function isSymlinkTo(linkPath, expectedTargetPath) {
39827
39860
  return { ok: false, reason: "not-symlink" };
39828
39861
  try {
39829
39862
  const rawTarget = readlinkSync2(linkPath);
39830
- const resolvedTarget = resolve12(dirname9(linkPath), rawTarget);
39831
- const resolvedExpected = resolve12(expectedTargetPath);
39863
+ const resolvedTarget = resolve13(dirname10(linkPath), rawTarget);
39864
+ const resolvedExpected = resolve13(expectedTargetPath);
39832
39865
  if (resolvedTarget !== resolvedExpected) {
39833
39866
  return { ok: false, reason: "wrong-target", target: rawTarget };
39834
39867
  }
@@ -39839,12 +39872,12 @@ function isSymlinkTo(linkPath, expectedTargetPath) {
39839
39872
  }
39840
39873
  function checkSkillDrift() {
39841
39874
  section3("Skill drift (.xtrm skill sync)");
39842
- if (!existsSync30(CONFIG_SKILLS_DIR)) {
39875
+ if (!existsSync29(CONFIG_SKILLS_DIR)) {
39843
39876
  fail4("config/skills/ missing");
39844
39877
  fix("restore config/skills/ from git");
39845
39878
  return false;
39846
39879
  }
39847
- if (!existsSync30(XTRM_DEFAULT_SKILLS_DIR)) {
39880
+ if (!existsSync29(XTRM_DEFAULT_SKILLS_DIR)) {
39848
39881
  fail4(".xtrm/skills/default/ missing");
39849
39882
  fix("specialists init --sync-skills");
39850
39883
  return false;
@@ -39886,22 +39919,22 @@ function checkSkillDrift() {
39886
39919
  }
39887
39920
  let linksOk = true;
39888
39921
  for (const scope of ["claude", "pi"]) {
39889
- const activeRoot = join33(XTRM_ACTIVE_SKILLS_DIR, scope);
39890
- if (!existsSync30(activeRoot)) {
39891
- fail4(`${relative3(CWD, activeRoot)}/ missing`);
39922
+ const activeRoot = join32(XTRM_ACTIVE_SKILLS_DIR, scope);
39923
+ if (!existsSync29(activeRoot)) {
39924
+ fail4(`${relative4(CWD, activeRoot)}/ missing`);
39892
39925
  fix("specialists init --sync-skills");
39893
39926
  linksOk = false;
39894
39927
  continue;
39895
39928
  }
39896
39929
  const defaultSkills = readdirSync14(XTRM_DEFAULT_SKILLS_DIR, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
39897
39930
  for (const skillName of defaultSkills) {
39898
- const activeLinkPath = join33(activeRoot, skillName);
39899
- const expectedTarget = join33(XTRM_DEFAULT_SKILLS_DIR, skillName);
39931
+ const activeLinkPath = join32(activeRoot, skillName);
39932
+ const expectedTarget = join32(XTRM_DEFAULT_SKILLS_DIR, skillName);
39900
39933
  const state = isSymlinkTo(activeLinkPath, expectedTarget);
39901
39934
  if (state.ok)
39902
39935
  continue;
39903
39936
  linksOk = false;
39904
- const relLink = relative3(CWD, activeLinkPath);
39937
+ const relLink = relative4(CWD, activeLinkPath);
39905
39938
  if (state.reason === "missing") {
39906
39939
  fail4(`${relLink} missing`);
39907
39940
  } else if (state.reason === "not-symlink") {
@@ -39915,18 +39948,18 @@ function checkSkillDrift() {
39915
39948
  }
39916
39949
  }
39917
39950
  const skillRootChecks = [
39918
- { root: join33(CLAUDE_DIR, "skills"), expected: ACTIVE_CLAUDE_SKILLS_DIR },
39919
- { root: join33(PI_DIR, "skills"), expected: ACTIVE_PI_SKILLS_DIR }
39951
+ { root: join32(CLAUDE_DIR, "skills"), expected: ACTIVE_CLAUDE_SKILLS_DIR },
39952
+ { root: join32(PI_DIR, "skills"), expected: ACTIVE_PI_SKILLS_DIR }
39920
39953
  ];
39921
39954
  let rootLinksOk = true;
39922
39955
  for (const check2 of skillRootChecks) {
39923
39956
  const state = isSymlinkTo(check2.root, check2.expected);
39924
39957
  if (state.ok) {
39925
- ok3(`${relative3(CWD, check2.root)} -> ${relative3(dirname9(check2.root), check2.expected)}`);
39958
+ ok3(`${relative4(CWD, check2.root)} -> ${relative4(dirname10(check2.root), check2.expected)}`);
39926
39959
  continue;
39927
39960
  }
39928
39961
  rootLinksOk = false;
39929
- const relRoot = relative3(CWD, check2.root);
39962
+ const relRoot = relative4(CWD, check2.root);
39930
39963
  if (state.reason === "missing") {
39931
39964
  fail4(`${relRoot} missing`);
39932
39965
  } else if (state.reason === "not-symlink") {
@@ -39941,13 +39974,13 @@ function checkSkillDrift() {
39941
39974
  return drifted.length === 0 && missingInDefault.length === 0 && linksOk && rootLinksOk;
39942
39975
  }
39943
39976
  function checkManagedMirror(label, sourceDir, mirrorDir, fixHint) {
39944
- if (!existsSync30(sourceDir)) {
39945
- warn3(`${label} source missing: ${relative3(CWD, sourceDir)}`);
39977
+ if (!existsSync29(sourceDir)) {
39978
+ warn3(`${label} source missing: ${relative4(CWD, sourceDir)}`);
39946
39979
  fix(fixHint);
39947
39980
  return false;
39948
39981
  }
39949
- if (!existsSync30(mirrorDir)) {
39950
- fail4(`${label} mirror missing: ${relative3(CWD, mirrorDir)}`);
39982
+ if (!existsSync29(mirrorDir)) {
39983
+ fail4(`${label} mirror missing: ${relative4(CWD, mirrorDir)}`);
39951
39984
  fix(fixHint);
39952
39985
  return false;
39953
39986
  }
@@ -39978,13 +40011,13 @@ function checkManagedMirror(label, sourceDir, mirrorDir, fixHint) {
39978
40011
  function checkManagedAssetMirrors() {
39979
40012
  section3("Managed mirrors (specialists / mandatory-rules / nodes)");
39980
40013
  const specialistsOk = checkManagedMirror("specialists", CONFIG_SPECIALISTS_DIR, DEFAULT_SPECIALISTS_DIR, "specialists init --sync-defaults");
39981
- const rulesOk = checkManagedMirror("mandatory-rules", CONFIG_MANDATORY_RULES_DIR, join33(DEFAULT_SPECIALISTS_DIR, "mandatory-rules"), "specialists init --sync-defaults");
39982
- const nodesOk = checkManagedMirror("nodes", CONFIG_NODES_DIR, join33(DEFAULT_SPECIALISTS_DIR, "nodes"), "specialists init --sync-defaults");
40014
+ const rulesOk = checkManagedMirror("mandatory-rules", CONFIG_MANDATORY_RULES_DIR, join32(DEFAULT_SPECIALISTS_DIR, "mandatory-rules"), "specialists init --sync-defaults");
40015
+ const nodesOk = checkManagedMirror("nodes", CONFIG_NODES_DIR, join32(DEFAULT_SPECIALISTS_DIR, "nodes"), "specialists init --sync-defaults");
39983
40016
  return specialistsOk && rulesOk && nodesOk;
39984
40017
  }
39985
40018
  function checkUserOverlayDrift() {
39986
40019
  section3("User specialist overlays");
39987
- if (!existsSync30(USER_SPECIALISTS_DIR)) {
40020
+ if (!existsSync29(USER_SPECIALISTS_DIR)) {
39988
40021
  ok3("no user overlays present");
39989
40022
  return true;
39990
40023
  }
@@ -39995,14 +40028,14 @@ function checkUserOverlayDrift() {
39995
40028
  }
39996
40029
  let allOk = true;
39997
40030
  for (const name of overlays) {
39998
- const userPath = join33(USER_SPECIALISTS_DIR, name);
39999
- const defaultPath = join33(DEFAULT_SPECIALISTS_DIR, name);
40031
+ const userPath = join32(USER_SPECIALISTS_DIR, name);
40032
+ const defaultPath = join32(DEFAULT_SPECIALISTS_DIR, name);
40000
40033
  const userSpec = loadJson2(userPath);
40001
40034
  if (!userSpec) {
40002
40035
  warn3(`${name}: failed to parse \u2014 skipping drift check`);
40003
40036
  continue;
40004
40037
  }
40005
- if (!existsSync30(defaultPath)) {
40038
+ if (!existsSync29(defaultPath)) {
40006
40039
  ok3(`${name}: user-only overlay (no default to drift from)`);
40007
40040
  continue;
40008
40041
  }
@@ -40030,20 +40063,20 @@ function checkUserOverlayDrift() {
40030
40063
  }
40031
40064
  function checkRuntimeDirs() {
40032
40065
  section3(".specialists/ runtime directories");
40033
- const rootDir = join33(CWD, ".specialists");
40034
- const jobsDir = join33(rootDir, "jobs");
40035
- const readyDir = join33(rootDir, "ready");
40066
+ const rootDir = join32(CWD, ".specialists");
40067
+ const jobsDir = join32(rootDir, "jobs");
40068
+ const readyDir = join32(rootDir, "ready");
40036
40069
  let allOk = true;
40037
- if (!existsSync30(rootDir)) {
40070
+ if (!existsSync29(rootDir)) {
40038
40071
  warn3(".specialists/ not found in current project");
40039
40072
  fix("specialists init");
40040
40073
  allOk = false;
40041
40074
  } else {
40042
40075
  ok3(".specialists/ present");
40043
40076
  for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
40044
- if (!existsSync30(subDir)) {
40077
+ if (!existsSync29(subDir)) {
40045
40078
  warn3(`.specialists/${label}/ missing \u2014 auto-creating`);
40046
- mkdirSync10(subDir, { recursive: true });
40079
+ mkdirSync11(subDir, { recursive: true });
40047
40080
  ok3(`.specialists/${label}/ created`);
40048
40081
  } else {
40049
40082
  ok3(`.specialists/${label}/ present`);
@@ -40055,8 +40088,8 @@ function checkRuntimeDirs() {
40055
40088
  function checkClaudeMdFragments() {
40056
40089
  section3("CLAUDE.md fragments");
40057
40090
  const projectRoot = process.cwd();
40058
- const claudeMd = join33(projectRoot, "CLAUDE.md");
40059
- if (!existsSync30(claudeMd)) {
40091
+ const claudeMd = join32(projectRoot, "CLAUDE.md");
40092
+ if (!existsSync29(claudeMd)) {
40060
40093
  warn3("No CLAUDE.md in project root \u2014 skipping fragment check");
40061
40094
  return true;
40062
40095
  }
@@ -40125,7 +40158,7 @@ function parseDoctorArgs(argv) {
40125
40158
  const value = argv[i + 1];
40126
40159
  if (!value || value.startsWith("--"))
40127
40160
  throw new Error("--root requires a value");
40128
- opts.root = resolve12(value);
40161
+ opts.root = resolve13(value);
40129
40162
  i += 1;
40130
40163
  continue;
40131
40164
  }
@@ -40182,7 +40215,7 @@ function compareVersions2(left, right) {
40182
40215
  }
40183
40216
  function setStatusError(statusPath) {
40184
40217
  try {
40185
- const raw = readFileSync31(statusPath, "utf8");
40218
+ const raw = readFileSync30(statusPath, "utf8");
40186
40219
  const status = JSON.parse(raw);
40187
40220
  status.status = "error";
40188
40221
  writeFileSync12(statusPath, `${JSON.stringify(status, null, 2)}
@@ -40236,11 +40269,11 @@ function cleanupProcesses(jobsDir, dryRun) {
40236
40269
  zombieJobIds: []
40237
40270
  };
40238
40271
  for (const jobId of entries) {
40239
- const statusPath = join33(jobsDir, jobId, "status.json");
40240
- if (!existsSync30(statusPath))
40272
+ const statusPath = join32(jobsDir, jobId, "status.json");
40273
+ if (!existsSync29(statusPath))
40241
40274
  continue;
40242
40275
  try {
40243
- const status = JSON.parse(readFileSync31(statusPath, "utf8"));
40276
+ const status = JSON.parse(readFileSync30(statusPath, "utf8"));
40244
40277
  result.total += 1;
40245
40278
  if (status.status !== "running" && status.status !== "starting")
40246
40279
  continue;
@@ -40325,8 +40358,8 @@ function resolveWatchdogMode() {
40325
40358
  function checkZombieJobs() {
40326
40359
  section3("Background jobs");
40327
40360
  hint(`watchdog mode: ${resolveWatchdogMode()}`);
40328
- const jobsDir = join33(CWD, ".specialists", "jobs");
40329
- if (!existsSync30(jobsDir)) {
40361
+ const jobsDir = join32(CWD, ".specialists", "jobs");
40362
+ if (!existsSync29(jobsDir)) {
40330
40363
  hint("No .specialists/jobs/ \u2014 skipping");
40331
40364
  return true;
40332
40365
  }
@@ -40344,7 +40377,7 @@ function checkZombieJobs() {
40344
40377
  }
40345
40378
  return result.zombies === 0;
40346
40379
  }
40347
- async function run29(argv = process.argv.slice(3)) {
40380
+ async function run28(argv = process.argv.slice(3)) {
40348
40381
  const subcommand = argv[0];
40349
40382
  if (subcommand === "orphans") {
40350
40383
  runDoctorOrphans();
@@ -40391,24 +40424,24 @@ var init_doctor = __esm(() => {
40391
40424
  init_drift_detector();
40392
40425
  init_version_check();
40393
40426
  CWD = process.cwd();
40394
- CLAUDE_DIR = join33(CWD, ".claude");
40395
- PI_DIR = join33(CWD, ".pi");
40396
- XTRM_SKILLS_DIR = join33(CWD, ".xtrm", "skills");
40397
- XTRM_DEFAULT_SKILLS_DIR = join33(XTRM_SKILLS_DIR, "default");
40398
- XTRM_ACTIVE_SKILLS_DIR = join33(XTRM_SKILLS_DIR, "active");
40399
- ACTIVE_CLAUDE_SKILLS_DIR = join33(XTRM_ACTIVE_SKILLS_DIR, "claude");
40400
- ACTIVE_PI_SKILLS_DIR = join33(XTRM_ACTIVE_SKILLS_DIR, "pi");
40401
- CONFIG_SKILLS_DIR = join33(CWD, "config", "skills");
40402
- CONFIG_SPECIALISTS_DIR = join33(CWD, "config", "specialists");
40403
- CONFIG_MANDATORY_RULES_DIR = join33(CWD, "config", "mandatory-rules");
40404
- CONFIG_NODES_DIR = join33(CWD, "config", "nodes");
40405
- SPECIALISTS_DIR = join33(CWD, ".specialists");
40406
- DEFAULT_SPECIALISTS_DIR = join33(SPECIALISTS_DIR, "default");
40407
- USER_SPECIALISTS_DIR = join33(SPECIALISTS_DIR, "user");
40408
- HOOKS_DIR = join33(CWD, ".xtrm", "hooks", "specialists");
40409
- CLAUDE_HOOKS_DIR = join33(CLAUDE_DIR, "hooks");
40410
- SETTINGS_FILE = join33(CLAUDE_DIR, "settings.json");
40411
- MCP_FILE2 = join33(CWD, ".mcp.json");
40427
+ CLAUDE_DIR = join32(CWD, ".claude");
40428
+ PI_DIR = join32(CWD, ".pi");
40429
+ XTRM_SKILLS_DIR = join32(CWD, ".xtrm", "skills");
40430
+ XTRM_DEFAULT_SKILLS_DIR = join32(XTRM_SKILLS_DIR, "default");
40431
+ XTRM_ACTIVE_SKILLS_DIR = join32(XTRM_SKILLS_DIR, "active");
40432
+ ACTIVE_CLAUDE_SKILLS_DIR = join32(XTRM_ACTIVE_SKILLS_DIR, "claude");
40433
+ ACTIVE_PI_SKILLS_DIR = join32(XTRM_ACTIVE_SKILLS_DIR, "pi");
40434
+ CONFIG_SKILLS_DIR = join32(CWD, "config", "skills");
40435
+ CONFIG_SPECIALISTS_DIR = join32(CWD, "config", "specialists");
40436
+ CONFIG_MANDATORY_RULES_DIR = join32(CWD, "config", "mandatory-rules");
40437
+ CONFIG_NODES_DIR = join32(CWD, "config", "nodes");
40438
+ SPECIALISTS_DIR = join32(CWD, ".specialists");
40439
+ DEFAULT_SPECIALISTS_DIR = join32(SPECIALISTS_DIR, "default");
40440
+ USER_SPECIALISTS_DIR = join32(SPECIALISTS_DIR, "user");
40441
+ HOOKS_DIR = join32(CWD, ".xtrm", "hooks", "specialists");
40442
+ CLAUDE_HOOKS_DIR = join32(CLAUDE_DIR, "hooks");
40443
+ SETTINGS_FILE = join32(CLAUDE_DIR, "settings.json");
40444
+ MCP_FILE2 = join32(CWD, ".mcp.json");
40412
40445
  HOOK_NAMES = [
40413
40446
  "specialists-complete.mjs",
40414
40447
  "specialists-session-start.mjs"
@@ -40418,9 +40451,9 @@ var init_doctor = __esm(() => {
40418
40451
  // src/cli/setup.ts
40419
40452
  var exports_setup = {};
40420
40453
  __export(exports_setup, {
40421
- run: () => run30
40454
+ run: () => run29
40422
40455
  });
40423
- async function run30() {
40456
+ async function run29() {
40424
40457
  console.log("");
40425
40458
  console.log(yellow13("\u26A0 DEPRECATED: `specialists setup` is deprecated"));
40426
40459
  console.log("");
@@ -40441,20 +40474,20 @@ async function run30() {
40441
40474
  var bold14 = (s) => `\x1B[1m${s}\x1B[0m`, yellow13 = (s) => `\x1B[33m${s}\x1B[0m`, dim14 = (s) => `\x1B[2m${s}\x1B[0m`;
40442
40475
 
40443
40476
  // src/cli/serve-hot-reload.ts
40444
- import { existsSync as existsSync31, readdirSync as readdirSync15, statSync as statSync5, watch as fsWatch } from "fs";
40445
- import { join as join34 } from "path";
40477
+ import { existsSync as existsSync30, readdirSync as readdirSync15, statSync as statSync5, watch as fsWatch } from "fs";
40478
+ import { join as join33 } from "path";
40446
40479
  function specialistNameFromFile(file) {
40447
40480
  const match = file.match(/^(.+)\.specialist\.(json|yaml)$/);
40448
40481
  return match ? match[1] : null;
40449
40482
  }
40450
40483
  function snapshotMtimes(dir) {
40451
40484
  const out = new Map;
40452
- if (!existsSync31(dir))
40485
+ if (!existsSync30(dir))
40453
40486
  return out;
40454
40487
  const entries = readdirSync15(dir).filter((name) => specialistNameFromFile(name) !== null);
40455
40488
  for (const name of entries) {
40456
40489
  try {
40457
- out.set(name, statSync5(join34(dir, name)).mtimeMs);
40490
+ out.set(name, statSync5(join33(dir, name)).mtimeMs);
40458
40491
  } catch {}
40459
40492
  }
40460
40493
  return out;
@@ -40511,7 +40544,7 @@ function createUserDirWatcher(opts) {
40511
40544
  for (const file of changed)
40512
40545
  queue(file);
40513
40546
  }, opts.pollMs);
40514
- } else if (existsSync31(opts.userDir)) {
40547
+ } else if (existsSync30(opts.userDir)) {
40515
40548
  try {
40516
40549
  watcher = fsWatch(opts.userDir, { persistent: false }, (_eventType, filename) => {
40517
40550
  queue(filename ? String(filename) : null);
@@ -40543,17 +40576,20 @@ var init_serve_hot_reload = () => {};
40543
40576
  var exports_serve = {};
40544
40577
  __export(exports_serve, {
40545
40578
  startServe: () => startServe,
40546
- run: () => run31,
40579
+ run: () => run30,
40547
40580
  recordAuditFailure: () => recordAuditFailure,
40548
40581
  evaluateReadiness: () => evaluateReadiness2,
40549
- createReadinessState: () => createReadinessState
40582
+ createReadinessState: () => createReadinessState,
40583
+ checkPiHelpForFlags: () => checkPiHelpForFlags
40550
40584
  });
40551
40585
  import { createServer } from "http";
40586
+ import { randomUUID as randomUUID3 } from "crypto";
40552
40587
  import { once } from "events";
40588
+ import { spawnSync as spawnSync23 } from "child_process";
40553
40589
  import { access, readdir as readdir2, readFile as readFile5, constants } from "fs/promises";
40554
- import { existsSync as existsSync32 } from "fs";
40590
+ import { existsSync as existsSync31 } from "fs";
40555
40591
  import { homedir as homedir3 } from "os";
40556
- import { join as join35 } from "path";
40592
+ import { join as join34 } from "path";
40557
40593
  function createReadinessState() {
40558
40594
  return { shuttingDown: false, auditFailures: [], dbWriteFailuresTotal: 0 };
40559
40595
  }
@@ -40569,7 +40605,7 @@ function pruneAuditFailures(state, now = Date.now()) {
40569
40605
  }
40570
40606
  }
40571
40607
  async function checkUserDirSpecs(userDir) {
40572
- if (!existsSync32(userDir))
40608
+ if (!existsSync31(userDir))
40573
40609
  return "empty";
40574
40610
  const entries = await readdir2(userDir).catch(() => []);
40575
40611
  const specFiles = entries.filter((name) => name.endsWith(".specialist.json") || name.endsWith(".specialist.yaml"));
@@ -40578,7 +40614,7 @@ async function checkUserDirSpecs(userDir) {
40578
40614
  let validCount = 0;
40579
40615
  for (const file of specFiles) {
40580
40616
  try {
40581
- const content = await readFile5(join35(userDir, file), "utf-8");
40617
+ const content = await readFile5(join34(userDir, file), "utf-8");
40582
40618
  const json = file.endsWith(".json") ? content : null;
40583
40619
  if (!json)
40584
40620
  continue;
@@ -40596,7 +40632,7 @@ async function evaluateReadiness2(opts) {
40596
40632
  if (opts.state.auditFailures.length > opts.auditFailureThreshold) {
40597
40633
  return { ready: false, reason: "degraded:audit" };
40598
40634
  }
40599
- const piConfigPath = opts.piConfigPath ?? join35(homedir3(), ".pi", "agent", "auth.json");
40635
+ const piConfigPath = opts.piConfigPath ?? join34(homedir3(), ".pi", "agent", "auth.json");
40600
40636
  try {
40601
40637
  await access(piConfigPath, constants.R_OK);
40602
40638
  } catch {
@@ -40607,26 +40643,41 @@ async function evaluateReadiness2(opts) {
40607
40643
  } catch {
40608
40644
  return { ready: false, reason: "db_not_writable" };
40609
40645
  }
40610
- const userDir = join35(opts.projectDir, ".specialists", "user");
40646
+ let warning;
40647
+ const canaryMode = opts.piCanaryMode ?? "off";
40648
+ if (canaryMode !== "off" && opts.piCanaryCheck) {
40649
+ const canaryFailure = await opts.piCanaryCheck();
40650
+ if (canaryFailure) {
40651
+ if (canaryMode === "require")
40652
+ return { ready: false, reason: canaryFailure };
40653
+ warning = canaryFailure;
40654
+ }
40655
+ }
40656
+ const userDir = join34(opts.projectDir, ".specialists", "user");
40611
40657
  const userDirResult = await checkUserDirSpecs(userDir);
40612
40658
  if (userDirResult === "empty")
40613
40659
  return { ready: false, reason: "empty_user_dir" };
40614
40660
  if (userDirResult === "invalid")
40615
40661
  return { ready: false, reason: "invalid_spec_in_user_dir" };
40616
- return { ready: true };
40662
+ return warning ? { ready: true, warning } : { ready: true };
40617
40663
  }
40618
- function parseArgs13(argv) {
40664
+ function parseArgs12(argv) {
40619
40665
  let port = 8000;
40620
40666
  let concurrency = 4;
40621
40667
  let queueTimeoutMs = 5000;
40622
40668
  let shutdownGraceMs = 30000;
40623
40669
  let projectDir = process.cwd();
40670
+ let dbPath;
40624
40671
  let fallbackModel;
40625
40672
  let auditFailureThreshold = 5;
40626
40673
  let allowSkills = false;
40627
40674
  let allowSkillsRoots = [];
40628
- let allowLocalScripts = false;
40629
40675
  let reloadPollMs = 0;
40676
+ let readinessCanaryMode = "off";
40677
+ const readinessRequiredPiFlags = [];
40678
+ let readinessCanarySpecialist;
40679
+ let readinessCanaryTimeoutMs = 5000;
40680
+ let logLevel = "info";
40630
40681
  for (let i = 0;i < argv.length; i++) {
40631
40682
  const token = argv[i];
40632
40683
  if (token === "--port" && argv[i + 1])
@@ -40639,6 +40690,8 @@ function parseArgs13(argv) {
40639
40690
  shutdownGraceMs = Number(argv[++i]);
40640
40691
  else if ((token === "--project-dir" || token === "--user-dir") && argv[i + 1])
40641
40692
  projectDir = argv[++i];
40693
+ else if (token === "--db-path" && argv[i + 1])
40694
+ dbPath = argv[++i];
40642
40695
  else if (token === "--fallback-model" && argv[i + 1])
40643
40696
  fallbackModel = argv[++i];
40644
40697
  else if (token === "--audit-failure-threshold" && argv[i + 1])
@@ -40648,16 +40701,51 @@ function parseArgs13(argv) {
40648
40701
  else if (token === "--allow-skills-roots" && argv[i + 1])
40649
40702
  allowSkillsRoots = argv[++i].split(":").filter(Boolean);
40650
40703
  else if (token === "--allow-local-scripts")
40651
- allowLocalScripts = true;
40704
+ throw new Error("--allow-local-scripts is not supported for script-class specialists");
40652
40705
  else if (token === "--reload-poll-ms" && argv[i + 1])
40653
40706
  reloadPollMs = Number(argv[++i]);
40707
+ else if (token === "--readiness-canary" && argv[i + 1]) {
40708
+ const mode = argv[++i];
40709
+ if (mode === "off" || mode === "warn" || mode === "require")
40710
+ readinessCanaryMode = mode;
40711
+ } else if (token === "--readiness-required-pi-flag" && argv[i + 1])
40712
+ readinessRequiredPiFlags.push(argv[++i]);
40713
+ else if (token === "--readiness-canary-specialist" && argv[i + 1])
40714
+ readinessCanarySpecialist = argv[++i];
40715
+ else if (token === "--readiness-canary-timeout-ms" && argv[i + 1])
40716
+ readinessCanaryTimeoutMs = Number(argv[++i]);
40717
+ else if (token === "--log-level" && argv[i + 1]) {
40718
+ const value = argv[++i];
40719
+ if (value === "off" || value === "info" || value === "debug")
40720
+ logLevel = value;
40721
+ else
40722
+ throw new Error("--log-level must be one of: off, info, debug");
40723
+ }
40654
40724
  }
40655
- return { port, concurrency, queueTimeoutMs, shutdownGraceMs, projectDir, fallbackModel, auditFailureThreshold, allowSkills, allowSkillsRoots, allowLocalScripts, reloadPollMs };
40725
+ return { port, concurrency, queueTimeoutMs, shutdownGraceMs, projectDir, dbPath, fallbackModel, auditFailureThreshold, allowSkills, allowSkillsRoots, reloadPollMs, readinessCanaryMode, readinessRequiredPiFlags, readinessCanarySpecialist, readinessCanaryTimeoutMs, logLevel };
40726
+ }
40727
+ function checkPiHelpForFlags(flags = DEFAULT_REQUIRED_PI_FLAGS) {
40728
+ const result = spawnSync23("pi", ["--help"], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
40729
+ if (result.error || result.status === 127)
40730
+ return "pi_binary_missing";
40731
+ const help = `${result.stdout ?? ""}
40732
+ ${result.stderr ?? ""}`;
40733
+ const missing = flags.find((flag2) => !help.includes(flag2));
40734
+ return missing ? "pi_flag_missing" : undefined;
40656
40735
  }
40657
40736
  function sendJson(res, statusCode, body) {
40658
40737
  res.writeHead(statusCode, { "content-type": "application/json" });
40659
40738
  res.end(JSON.stringify(body));
40660
40739
  }
40740
+ function emitGenerateLog(logLevel, entry) {
40741
+ if (logLevel === "off")
40742
+ return;
40743
+ console.log(JSON.stringify({ level: logLevel, ts: new Date().toISOString(), ...entry }));
40744
+ }
40745
+ function shortLogError(value, limit = 240) {
40746
+ const message = value instanceof Error ? value.message : String(value);
40747
+ return message.length <= limit ? message : `${message.slice(0, limit)}\u2026`;
40748
+ }
40661
40749
  async function readBody(req) {
40662
40750
  const chunks = [];
40663
40751
  for await (const chunk of req)
@@ -40672,21 +40760,38 @@ async function waitForSlot(limit, timeoutMs, getActive) {
40672
40760
  while (getActive() >= limit) {
40673
40761
  if (Date.now() - startedAt >= timeoutMs)
40674
40762
  return false;
40675
- await new Promise((resolve13) => setTimeout(resolve13, 25));
40763
+ await new Promise((resolve14) => setTimeout(resolve14, 25));
40676
40764
  }
40677
40765
  return true;
40678
40766
  }
40679
40767
  async function startServe(argv = process.argv.slice(3)) {
40680
- const args = parseArgs13(argv);
40768
+ const args = parseArgs12(argv);
40681
40769
  const loader = new SpecialistLoader({ projectDir: args.projectDir });
40682
40770
  const dbLocation = resolveObservabilityDbLocation(args.projectDir);
40683
- ensureObservabilityDbFile(dbLocation);
40684
- const db = createObservabilitySqliteClient(args.projectDir);
40771
+ const dbPath = args.dbPath ?? dbLocation.dbPath;
40772
+ const db = args.dbPath ? createObservabilitySqliteClientAtPath(args.dbPath) : (() => {
40773
+ ensureObservabilityDbFile(dbLocation);
40774
+ return createObservabilitySqliteClient(args.projectDir);
40775
+ })();
40685
40776
  const readinessState = createReadinessState();
40686
- const userDir = join35(args.projectDir, ".specialists", "user");
40777
+ const userDir = join34(args.projectDir, ".specialists", "user");
40687
40778
  const hotReload = createUserDirWatcher({ loader, userDir, pollMs: args.reloadPollMs });
40688
40779
  let active = 0;
40689
40780
  const children = new Set;
40781
+ const piCanaryCheck = async () => {
40782
+ const requiredFlags = args.readinessRequiredPiFlags.length > 0 ? args.readinessRequiredPiFlags : DEFAULT_REQUIRED_PI_FLAGS;
40783
+ const compatibilityFailure = checkPiHelpForFlags(requiredFlags);
40784
+ if (compatibilityFailure)
40785
+ return compatibilityFailure;
40786
+ if (!args.readinessCanarySpecialist)
40787
+ return;
40788
+ const result = await runScriptSpecialist({ specialist: args.readinessCanarySpecialist, trace: false, timeout_ms: args.readinessCanaryTimeoutMs }, {
40789
+ loader,
40790
+ fallbackModel: args.fallbackModel,
40791
+ observabilityDbPath: args.projectDir
40792
+ });
40793
+ return result.success ? undefined : "pi_smoke_failed";
40794
+ };
40690
40795
  const server = createServer(async (req, res) => {
40691
40796
  if (req.url === "/healthz")
40692
40797
  return sendJson(res, 200, { ok: true });
@@ -40694,11 +40799,13 @@ async function startServe(argv = process.argv.slice(3)) {
40694
40799
  const result = await evaluateReadiness2({
40695
40800
  state: readinessState,
40696
40801
  projectDir: args.projectDir,
40697
- dbPath: dbLocation.dbPath,
40698
- auditFailureThreshold: args.auditFailureThreshold
40802
+ dbPath,
40803
+ auditFailureThreshold: args.auditFailureThreshold,
40804
+ piCanaryMode: args.readinessCanaryMode,
40805
+ piCanaryCheck
40699
40806
  });
40700
40807
  if (result.ready) {
40701
- return sendJson(res, 200, { ready: true, db_write_failures_total: readinessState.dbWriteFailuresTotal });
40808
+ return sendJson(res, 200, { ready: true, ...result.warning ? { warning: result.warning } : {}, db_write_failures_total: readinessState.dbWriteFailuresTotal });
40702
40809
  }
40703
40810
  return sendJson(res, 503, {
40704
40811
  ready: false,
@@ -40708,27 +40815,65 @@ async function startServe(argv = process.argv.slice(3)) {
40708
40815
  }
40709
40816
  if (req.method !== "POST" || req.url !== "/v1/generate")
40710
40817
  return sendJson(res, 404, { success: false, error: "not_found", error_type: "internal" });
40711
- if (readinessState.shuttingDown)
40818
+ const requestStartedAt = Date.now();
40819
+ const method = req.method ?? "POST";
40820
+ const path = req.url ?? "/v1/generate";
40821
+ const requestTraceId = randomUUID3();
40822
+ if (readinessState.shuttingDown) {
40823
+ emitGenerateLog(args.logLevel, {
40824
+ trace_id: requestTraceId,
40825
+ specialist: "unknown",
40826
+ status: "internal",
40827
+ duration_ms: Date.now() - requestStartedAt,
40828
+ prompt_bytes: 0,
40829
+ method,
40830
+ path,
40831
+ error: "shutting_down"
40832
+ });
40712
40833
  return sendJson(res, 503, { success: false, error: "shutting_down", error_type: "internal" });
40834
+ }
40713
40835
  const entered = await waitForSlot(args.concurrency, args.queueTimeoutMs, () => active);
40714
- if (!entered)
40836
+ if (!entered) {
40837
+ emitGenerateLog(args.logLevel, {
40838
+ trace_id: requestTraceId,
40839
+ specialist: "unknown",
40840
+ status: "quota",
40841
+ duration_ms: Date.now() - requestStartedAt,
40842
+ prompt_bytes: 0,
40843
+ method,
40844
+ path,
40845
+ error: "too_many_requests"
40846
+ });
40715
40847
  return sendJson(res, 429, { success: false, error: "too_many_requests", error_type: "quota" });
40848
+ }
40716
40849
  active++;
40717
40850
  const work = (async () => {
40851
+ let promptBytes = 0;
40852
+ let requestedSpecialist = "unknown";
40718
40853
  try {
40719
40854
  const raw = await readBody(req);
40855
+ promptBytes = Buffer.byteLength(raw, "utf8");
40720
40856
  let parsed;
40721
40857
  try {
40722
40858
  parsed = JSON.parse(raw);
40723
40859
  } catch {
40860
+ const duration_ms2 = Date.now() - requestStartedAt;
40861
+ const trace_id = requestTraceId;
40862
+ emitGenerateLog(args.logLevel, { trace_id, specialist: "unknown", status: "invalid_json", duration_ms: duration_ms2, prompt_bytes: promptBytes, method, path, error: "malformed_request" });
40724
40863
  return sendJson(res, 400, { success: false, error: "malformed_request", error_type: "invalid_json" });
40725
40864
  }
40726
- if (!isValidRequest(parsed))
40865
+ if (!isValidRequest(parsed)) {
40866
+ const duration_ms2 = Date.now() - requestStartedAt;
40867
+ const trace_id = requestTraceId;
40868
+ emitGenerateLog(args.logLevel, { trace_id, specialist: "unknown", status: "invalid_json", duration_ms: duration_ms2, prompt_bytes: promptBytes, method, path, error: "malformed_request" });
40727
40869
  return sendJson(res, 400, { success: false, error: "malformed_request", error_type: "invalid_json" });
40870
+ }
40871
+ requestedSpecialist = parsed.specialist;
40728
40872
  const result = await runScriptSpecialist(parsed, {
40729
40873
  loader,
40874
+ projectDir: args.projectDir,
40730
40875
  fallbackModel: args.fallbackModel,
40731
- observabilityDbPath: args.projectDir,
40876
+ ...args.dbPath ? { observabilityDbPath: args.dbPath } : {},
40732
40877
  onChild: (child) => {
40733
40878
  children.add(child);
40734
40879
  child.once("exit", () => children.delete(child));
@@ -40736,11 +40881,37 @@ async function startServe(argv = process.argv.slice(3)) {
40736
40881
  onAuditFailure: () => recordAuditFailure(readinessState),
40737
40882
  trust: {
40738
40883
  allowSkills: args.allowSkills,
40739
- allowSkillsRoots: args.allowSkillsRoots,
40740
- allowLocalScripts: args.allowLocalScripts
40884
+ allowSkillsRoots: args.allowSkillsRoots
40741
40885
  }
40742
40886
  });
40887
+ const duration_ms = Date.now() - requestStartedAt;
40888
+ const meta = result.meta ?? {};
40889
+ emitGenerateLog(args.logLevel, {
40890
+ trace_id: meta.trace_id ?? requestTraceId,
40891
+ specialist: meta.specialist ?? (typeof parsed === "object" && parsed !== null ? String(parsed.specialist ?? "unknown") : "unknown"),
40892
+ resolved_specialist: meta.resolved_specialist,
40893
+ model: meta.model,
40894
+ status: result.success ? "success" : result.error_type,
40895
+ duration_ms: meta.duration_ms ?? duration_ms,
40896
+ prompt_bytes: promptBytes,
40897
+ method,
40898
+ path,
40899
+ ...result.success ? {} : { error: shortLogError(result.error) }
40900
+ });
40743
40901
  return sendJson(res, 200, result);
40902
+ } catch (error2) {
40903
+ emitGenerateLog(args.logLevel, {
40904
+ trace_id: requestTraceId,
40905
+ specialist: requestedSpecialist,
40906
+ status: "internal",
40907
+ duration_ms: Date.now() - requestStartedAt,
40908
+ prompt_bytes: promptBytes,
40909
+ method,
40910
+ path,
40911
+ error: shortLogError(error2)
40912
+ });
40913
+ if (!res.headersSent)
40914
+ return sendJson(res, 500, { success: false, error: "internal_error", error_type: "internal" });
40744
40915
  } finally {
40745
40916
  active--;
40746
40917
  }
@@ -40768,10 +40939,10 @@ async function startServe(argv = process.argv.slice(3)) {
40768
40939
  console.log(`sp serve listening on ${args.port}`);
40769
40940
  return { server, args, db, readinessState };
40770
40941
  }
40771
- async function run31(argv = process.argv.slice(3)) {
40942
+ async function run30(argv = process.argv.slice(3)) {
40772
40943
  await startServe(argv);
40773
40944
  }
40774
- var AUDIT_WINDOW_MS = 60000;
40945
+ var AUDIT_WINDOW_MS = 60000, DEFAULT_REQUIRED_PI_FLAGS;
40775
40946
  var init_serve = __esm(() => {
40776
40947
  init_loader();
40777
40948
  init_script_runner();
@@ -40779,24 +40950,25 @@ var init_serve = __esm(() => {
40779
40950
  init_observability_db();
40780
40951
  init_schema();
40781
40952
  init_serve_hot_reload();
40953
+ DEFAULT_REQUIRED_PI_FLAGS = ["--mode", "--no-session", "--no-extensions", "--no-tools", "--no-context-files", "--no-skills", "--no-prompt-templates", "--no-themes"];
40782
40954
  });
40783
40955
 
40784
40956
  // src/cli/script.ts
40785
40957
  var exports_script = {};
40786
40958
  __export(exports_script, {
40787
40959
  scriptCli: () => scriptCli,
40788
- run: () => run32,
40789
- parseArgs: () => parseArgs14,
40960
+ run: () => run31,
40961
+ parseArgs: () => parseArgs13,
40790
40962
  mapExitCode: () => mapExitCode
40791
40963
  });
40792
- import { spawnSync as spawnSync23 } from "child_process";
40964
+ import { spawnSync as spawnSync24 } from "child_process";
40793
40965
  function parseVar(entry) {
40794
40966
  const index = entry.indexOf("=");
40795
40967
  if (index <= 0)
40796
40968
  throw new Error(`Invalid --vars entry: ${entry}`);
40797
40969
  return [entry.slice(0, index), entry.slice(index + 1)];
40798
40970
  }
40799
- function parseArgs14(argv) {
40971
+ function parseArgs13(argv) {
40800
40972
  if (argv.length === 0)
40801
40973
  throw new Error("Missing specialist name");
40802
40974
  const specialist = argv[0];
@@ -40892,7 +41064,7 @@ function printResult(result, json) {
40892
41064
  console.error(result.error);
40893
41065
  }
40894
41066
  function runUnderLock(lockPath, argv) {
40895
- const flock = spawnSync23("flock", ["-n", lockPath, "env", "SP_SCRIPT_NO_LOCK=1", process.execPath, process.argv[1], "script", ...argv], {
41067
+ const flock = spawnSync24("flock", ["-n", lockPath, "env", "SP_SCRIPT_NO_LOCK=1", process.execPath, process.argv[1], "script", ...argv], {
40896
41068
  encoding: "utf-8",
40897
41069
  stdio: "inherit"
40898
41070
  });
@@ -40902,13 +41074,17 @@ function runUnderLock(lockPath, argv) {
40902
41074
  return 75;
40903
41075
  return flock.status ?? 1;
40904
41076
  }
40905
- async function run32(argv = process.argv.slice(3)) {
40906
- const args = parseArgs14(argv);
41077
+ async function run31(argv = process.argv.slice(3)) {
41078
+ const args = parseArgs13(argv);
40907
41079
  if (args.singleInstance && !process.env.SP_SCRIPT_NO_LOCK) {
40908
41080
  process.exit(runUnderLock(args.singleInstance, argv));
40909
41081
  }
40910
41082
  const loader = new SpecialistLoader({ projectDir: args.projectDir });
40911
- const result = await runScriptSpecialist(buildRequest(args), { loader, projectDir: args.projectDir, observabilityDbPath: args.dbPath ?? args.projectDir });
41083
+ const result = await runScriptSpecialist(buildRequest(args), {
41084
+ loader,
41085
+ projectDir: args.projectDir,
41086
+ ...args.dbPath ? { observabilityDbPath: args.dbPath } : {}
41087
+ });
40912
41088
  printResult(result, args.json);
40913
41089
  process.exit(mapExitCode(result));
40914
41090
  }
@@ -40916,19 +41092,19 @@ var scriptCli;
40916
41092
  var init_script = __esm(() => {
40917
41093
  init_loader();
40918
41094
  init_script_runner();
40919
- scriptCli = { parseArgs: parseArgs14, mapExitCode };
41095
+ scriptCli = { parseArgs: parseArgs13, mapExitCode };
40920
41096
  });
40921
41097
 
40922
41098
  // src/cli/help.ts
40923
41099
  var exports_help = {};
40924
41100
  __export(exports_help, {
40925
- run: () => run33
41101
+ run: () => run32
40926
41102
  });
40927
41103
  function formatCommands(entries) {
40928
41104
  const width = Math.max(...entries.map(([cmd3]) => cmd3.length));
40929
41105
  return entries.map(([cmd3, desc]) => ` ${cmd3.padEnd(width)} ${desc}`);
40930
41106
  }
40931
- async function run33() {
41107
+ async function run32() {
40932
41108
  const lines = [
40933
41109
  "",
40934
41110
  "Specialists lets you run project-scoped specialist agents with a bead-first workflow.",
@@ -40964,7 +41140,7 @@ async function run33() {
40964
41140
  " Async patterns",
40965
41141
  " MCP: use_specialist (foreground, returns result directly)",
40966
41142
  ' CLI: specialists run <name> --prompt "..." # job ID prints on stderr',
40967
- " specialists feed|poll|result <job-id> # observe/progress/final output",
41143
+ " specialists ps|feed|result <job-id> # observe/progress/final output",
40968
41144
  ' Shell: specialists run <name> --prompt "..." & # native shell backgrounding',
40969
41145
  "",
40970
41146
  " Background workflow",
@@ -41014,7 +41190,6 @@ async function run33() {
41014
41190
  " specialists view --help View specialist configs",
41015
41191
  " specialists edit --help Edit specialist fields (dot-path, presets)",
41016
41192
  " specialists run --help Run command details and flags",
41017
- " specialists poll --help [DEPRECATED] use sp ps + sp feed",
41018
41193
  " specialists steer --help Mid-run steering details",
41019
41194
  " specialists resume --help Multi-turn keep-alive details",
41020
41195
  " specialists init --help Bootstrap behavior and workflow injection",
@@ -41042,7 +41217,6 @@ var init_help = __esm(() => {
41042
41217
  ["node", "Run and inspect NodeSupervisor nodes (run/status)"],
41043
41218
  ["epic", "Epic lifecycle management: list/status/resolve wave-bound chain groups"],
41044
41219
  ["feed", "Tail job events; use -f to follow all jobs"],
41045
- ["poll", "Machine-readable job status polling (for scripts/Claude Code)"],
41046
41220
  ["result", "Print final output of a completed job; --wait polls until done, --timeout <ms> sets a limit"],
41047
41221
  ["clean", "Purge completed job directories (TTL, --all, --keep, --dry-run)"],
41048
41222
  ["merge", "Publish one standalone chain (refuses unresolved epic chains)"],
@@ -41083,7 +41257,7 @@ var init_help = __esm(() => {
41083
41257
  });
41084
41258
 
41085
41259
  // src/index.ts
41086
- import { spawnSync as spawnSync24 } from "child_process";
41260
+ import { spawnSync as spawnSync25 } from "child_process";
41087
41261
 
41088
41262
  // node_modules/zod/v4/core/core.js
41089
41263
  var NEVER2 = Object.freeze({
@@ -44857,7 +45031,7 @@ var AssertObjectSchema = custom2((v) => v !== null && (typeof v === "object" ||
44857
45031
  var ProgressTokenSchema = union([string2(), number2().int()]);
44858
45032
  var CursorSchema = string2();
44859
45033
  var TaskCreationParamsSchema = looseObject({
44860
- ttl: union([number2(), _null3()]).optional(),
45034
+ ttl: number2().optional(),
44861
45035
  pollInterval: number2().optional()
44862
45036
  });
44863
45037
  var TaskMetadataSchema = object2({
@@ -45011,7 +45185,8 @@ var ClientCapabilitiesSchema = object2({
45011
45185
  roots: object2({
45012
45186
  listChanged: boolean2().optional()
45013
45187
  }).optional(),
45014
- tasks: ClientTasksCapabilitySchema.optional()
45188
+ tasks: ClientTasksCapabilitySchema.optional(),
45189
+ extensions: record(string2(), AssertObjectSchema).optional()
45015
45190
  });
45016
45191
  var InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
45017
45192
  protocolVersion: string2(),
@@ -45036,7 +45211,8 @@ var ServerCapabilitiesSchema = object2({
45036
45211
  tools: object2({
45037
45212
  listChanged: boolean2().optional()
45038
45213
  }).optional(),
45039
- tasks: ServerTasksCapabilitySchema.optional()
45214
+ tasks: ServerTasksCapabilitySchema.optional(),
45215
+ extensions: record(string2(), AssertObjectSchema).optional()
45040
45216
  });
45041
45217
  var InitializeResultSchema = ResultSchema.extend({
45042
45218
  protocolVersion: string2(),
@@ -45151,6 +45327,7 @@ var ResourceSchema = object2({
45151
45327
  uri: string2(),
45152
45328
  description: optional(string2()),
45153
45329
  mimeType: optional(string2()),
45330
+ size: optional(number2()),
45154
45331
  annotations: AnnotationsSchema.optional(),
45155
45332
  _meta: optional(looseObject({}))
45156
45333
  });
@@ -47146,6 +47323,10 @@ class Protocol {
47146
47323
  this._progressHandlers.clear();
47147
47324
  this._taskProgressTokens.clear();
47148
47325
  this._pendingDebouncedNotifications.clear();
47326
+ for (const info of this._timeoutInfo.values()) {
47327
+ clearTimeout(info.timeoutId);
47328
+ }
47329
+ this._timeoutInfo.clear();
47149
47330
  for (const controller of this._requestHandlerAbortControllers.values()) {
47150
47331
  controller.abort();
47151
47332
  }
@@ -47276,7 +47457,9 @@ class Protocol {
47276
47457
  await capturedTransport?.send(errorResponse);
47277
47458
  }
47278
47459
  }).catch((error2) => this._onerror(new Error(`Failed to send response: ${error2}`))).finally(() => {
47279
- this._requestHandlerAbortControllers.delete(request.id);
47460
+ if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
47461
+ this._requestHandlerAbortControllers.delete(request.id);
47462
+ }
47280
47463
  });
47281
47464
  }
47282
47465
  _onprogress(notification) {
@@ -48578,7 +48761,7 @@ var next = process.argv[3];
48578
48761
  function wantsHelp() {
48579
48762
  return next === "--help" || next === "-h";
48580
48763
  }
48581
- async function run34() {
48764
+ async function run33() {
48582
48765
  if (sub === "install") {
48583
48766
  if (wantsHelp()) {
48584
48767
  console.log([
@@ -48963,7 +49146,7 @@ async function run34() {
48963
49146
  "",
48964
49147
  "Async execution patterns:",
48965
49148
  " MCP: use_specialist (foreground, returns result directly)",
48966
- " CLI: run prints [job started: <id>] on stderr, then use feed/poll/result",
49149
+ " CLI: run prints [job started: <id>] on stderr, then use ps/feed/result",
48967
49150
  ' Shell: specialists run <name> --prompt "..." &',
48968
49151
  ""
48969
49152
  ].join(`
@@ -49201,53 +49384,6 @@ async function run34() {
49201
49384
  const { run: handler } = await Promise.resolve().then(() => (init_feed(), exports_feed));
49202
49385
  return handler();
49203
49386
  }
49204
- if (sub === "poll") {
49205
- if (wantsHelp()) {
49206
- console.log([
49207
- "",
49208
- "Usage: specialists poll <job-id> [--cursor N] [--json]",
49209
- "",
49210
- "[DEPRECATED] Scheduled for removal. Use:",
49211
- " sp ps <id> --json for status",
49212
- " sp feed <id> for events",
49213
- "",
49214
- "Machine-readable job status polling for scripts and Claude Code.",
49215
- "Currently file-based; replacements above are DB-canonical.",
49216
- "",
49217
- "Output (JSON mode):",
49218
- " {",
49219
- ' "job_id": "abc123",',
49220
- ' "status": "running" | "done" | "error" | "waiting",',
49221
- ' "elapsed_ms": 45000,',
49222
- ' "cursor": 15,',
49223
- ' "events": [...], // new events since cursor',
49224
- ' "output": "...", // full output when done',
49225
- ' "model": "claude-sonnet-4-6",',
49226
- ' "bead_id": "unitAI-123"',
49227
- " }",
49228
- "",
49229
- "Options:",
49230
- " --cursor N Event index to start from (default: 0)",
49231
- " --json Output as JSON (machine-readable)",
49232
- "",
49233
- "Examples:",
49234
- " specialists poll abc123 --json",
49235
- " specialists poll abc123 --cursor 5 --json",
49236
- "",
49237
- "Polling pattern in Claude Code:",
49238
- " 1. Start job (blocks until done):",
49239
- " specialists run planner --bead xtrm-p38n.1",
49240
- " 2. Or use Claude Code native backgrounding",
49241
- " 3. Poll for incremental status:",
49242
- " specialists poll <job-id> --json",
49243
- ""
49244
- ].join(`
49245
- `));
49246
- return;
49247
- }
49248
- const { run: pollHandler } = await Promise.resolve().then(() => (init_poll(), exports_poll));
49249
- return pollHandler();
49250
- }
49251
49387
  if (sub === "steer") {
49252
49388
  if (wantsHelp()) {
49253
49389
  console.log([
@@ -49531,7 +49667,7 @@ async function run34() {
49531
49667
  if (wantsHelp()) {
49532
49668
  console.log([
49533
49669
  "",
49534
- "Usage: specialists serve [--port <n>] [--concurrency <n>] [--shutdown-grace-ms <n>] [--project-dir <path>]",
49670
+ "Usage: specialists serve [--port <n>] [--concurrency <n>] [--shutdown-grace-ms <n>] [--project-dir <path>] [--db-path <observability.db>] [--readiness-canary off|warn|require] [--log-level off|info|debug]",
49535
49671
  "",
49536
49672
  "HTTP wrapper for script-class specialists.",
49537
49673
  "",
@@ -49567,7 +49703,7 @@ async function run34() {
49567
49703
  }
49568
49704
  if (sub === "release") {
49569
49705
  console.error("Deprecated. Use `xt release prepare/publish`. This alias will be removed in v4.0.");
49570
- const result = spawnSync24("xt", ["release", ...process.argv.slice(3)], { stdio: "inherit" });
49706
+ const result = spawnSync25("xt", ["release", ...process.argv.slice(3)], { stdio: "inherit" });
49571
49707
  if (result.error) {
49572
49708
  console.error(`Failed to run xt release: ${result.error.message}`);
49573
49709
  process.exit(1);
@@ -49587,7 +49723,7 @@ Run 'specialists help' to see available commands.`);
49587
49723
  const server = new SpecialistsServer;
49588
49724
  await server.start();
49589
49725
  }
49590
- run34().catch((error2) => {
49726
+ run33().catch((error2) => {
49591
49727
  logger.error(`Fatal error: ${error2}`);
49592
49728
  process.exit(1);
49593
49729
  });