@jaggerxtrm/specialists 3.13.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.
package/dist/index.js CHANGED
@@ -17561,6 +17561,7 @@ var init_schema = __esm(() => {
17561
17561
  max_retries: numberType().int().min(0).default(0),
17562
17562
  interactive: booleanType().default(false),
17563
17563
  stdout_limit_bytes: numberType().int().positive().optional(),
17564
+ prompt_limit_bytes: numberType().int().positive().optional(),
17564
17565
  response_format: enumType(["text", "json", "markdown"]).default("text"),
17565
17566
  output_type: enumType(["codegen", "analysis", "review", "synthesis", "orchestration", "workflow", "research", "custom"]).default("custom"),
17566
17567
  permission_required: enumType(["READ_ONLY", "LOW", "MEDIUM", "HIGH"]).default("READ_ONLY"),
@@ -19156,8 +19157,8 @@ function resolveCurrentBranch(cwd = process.cwd()) {
19156
19157
  var init_job_root = () => {};
19157
19158
 
19158
19159
  // src/specialist/observability-sqlite.ts
19159
- import { existsSync as existsSync5, readFileSync as readFileSync3, statSync } from "fs";
19160
- 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";
19161
19162
  function loadBunDatabase() {
19162
19163
  if (_probed)
19163
19164
  return _BunDatabase;
@@ -21220,23 +21221,30 @@ function hasRunCompleteEvent(jobId, cwd = process.cwd()) {
21220
21221
  }
21221
21222
  return false;
21222
21223
  }
21223
- function createObservabilitySqliteClient(cwd = process.cwd()) {
21224
+ function openObservabilitySqliteClient(dbPath) {
21224
21225
  if (!loadBunDatabase())
21225
21226
  return null;
21226
- const location = resolveObservabilityDbLocation(cwd);
21227
- if (!existsSync5(location.dbPath))
21228
- return null;
21229
21227
  try {
21230
21228
  const Ctor = loadBunDatabase();
21231
- const initDb = new Ctor(location.dbPath);
21229
+ const initDb = new Ctor(dbPath);
21232
21230
  initDb.run(`PRAGMA busy_timeout=${BUSY_TIMEOUT_MS}`);
21233
21231
  initSchema(initDb);
21234
21232
  initDb.close();
21235
- return new SqliteClient(location.dbPath);
21233
+ return new SqliteClient(dbPath);
21236
21234
  } catch {
21237
21235
  return null;
21238
21236
  }
21239
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
+ }
21240
21248
  var _BunDatabase = null, _probed = false, BUSY_TIMEOUT_MS = 5000, MAX_RETRY_ATTEMPTS = 5, BASE_RETRY_DELAY_MS = 50, STALE_CLAIM_AGE_MS = 60000;
21241
21249
  var init_observability_sqlite = __esm(() => {
21242
21250
  init_observability_db();
@@ -23023,7 +23031,7 @@ var init_runner = __esm(() => {
23023
23031
 
23024
23032
  // src/specialist/hooks.ts
23025
23033
  import { appendFile, mkdir } from "fs/promises";
23026
- import { dirname as dirname3 } from "path";
23034
+ import { dirname as dirname4 } from "path";
23027
23035
 
23028
23036
  class HookEmitter {
23029
23037
  tracePath;
@@ -23031,7 +23039,7 @@ class HookEmitter {
23031
23039
  ready;
23032
23040
  constructor(options) {
23033
23041
  this.tracePath = options.tracePath;
23034
- this.ready = mkdir(dirname3(options.tracePath), { recursive: true }).then(() => {});
23042
+ this.ready = mkdir(dirname4(options.tracePath), { recursive: true }).then(() => {});
23035
23043
  }
23036
23044
  async emit(hook, invocationId, specialistName, specialistVersion, payload) {
23037
23045
  await this.ready;
@@ -23085,11 +23093,11 @@ __export(exports_version, {
23085
23093
  });
23086
23094
  import { createRequire } from "module";
23087
23095
  import { fileURLToPath as fileURLToPath2 } from "url";
23088
- import { dirname as dirname4, join as join7 } from "path";
23096
+ import { dirname as dirname5, join as join7 } from "path";
23089
23097
  import { existsSync as existsSync8 } from "fs";
23090
23098
  async function run2() {
23091
23099
  const req = createRequire(import.meta.url);
23092
- const here = dirname4(fileURLToPath2(import.meta.url));
23100
+ const here = dirname5(fileURLToPath2(import.meta.url));
23093
23101
  const bundlePkgPath = join7(here, "..", "package.json");
23094
23102
  const sourcePkgPath = join7(here, "..", "..", "package.json");
23095
23103
  let pkg;
@@ -24219,7 +24227,7 @@ import {
24219
24227
  closeSync,
24220
24228
  existsSync as existsSync10,
24221
24229
  fsyncSync,
24222
- mkdirSync as mkdirSync3,
24230
+ mkdirSync as mkdirSync4,
24223
24231
  openSync,
24224
24232
  readdirSync as readdirSync2,
24225
24233
  readFileSync as readFileSync8,
@@ -24656,7 +24664,7 @@ class Supervisor {
24656
24664
  return join9(this.resolvedJobsDir, "..", "ready");
24657
24665
  }
24658
24666
  writeReadyMarker(id) {
24659
- mkdirSync3(this.readyDir(), { recursive: true });
24667
+ mkdirSync4(this.readyDir(), { recursive: true });
24660
24668
  writeFileSync3(join9(this.readyDir(), id), "", "utf-8");
24661
24669
  }
24662
24670
  withComputedLiveness(status) {
@@ -24787,7 +24795,7 @@ class Supervisor {
24787
24795
  if (!this.isJobFileOutputEnabled)
24788
24796
  return;
24789
24797
  const normalizedStatus = this.withStatusLineageDefaults(id, data);
24790
- mkdirSync3(this.jobDir(id), { recursive: true });
24798
+ mkdirSync4(this.jobDir(id), { recursive: true });
24791
24799
  const path = this.statusPath(id);
24792
24800
  const tmp = path + ".tmp";
24793
24801
  writeFileSync3(tmp, JSON.stringify(normalizedStatus, null, 2), "utf-8");
@@ -24949,8 +24957,8 @@ class Supervisor {
24949
24957
  const id = crypto.randomUUID().slice(0, 6);
24950
24958
  const dir = this.jobDir(id);
24951
24959
  const startedAtMs = Date.now();
24952
- mkdirSync3(dir, { recursive: true });
24953
- mkdirSync3(this.readyDir(), { recursive: true });
24960
+ mkdirSync4(dir, { recursive: true });
24961
+ mkdirSync4(this.readyDir(), { recursive: true });
24954
24962
  const nodeId = runOptions.variables?.node_id ?? runOptions.variables?.SPECIALISTS_NODE_ID;
24955
24963
  const variablesKeys = Object.keys(runOptions.variables ?? {});
24956
24964
  const activatedSkills = (runOptions.variables?.activated_skills ?? runOptions.variables?.skills_activated ?? "").split(",").map((skill) => skill.trim()).filter((skill) => skill.length > 0);
@@ -25228,7 +25236,7 @@ ${appendError}
25228
25236
  try {
25229
25237
  const output = await resumeFn(task);
25230
25238
  latestOutput = output;
25231
- mkdirSync3(this.jobDir(id), { recursive: true });
25239
+ mkdirSync4(this.jobDir(id), { recursive: true });
25232
25240
  writeFileSync3(this.resultPath(id), output, "utf-8");
25233
25241
  try {
25234
25242
  this.withSqliteOperation("upsertResult:resume_turn", (client) => client.upsertResult(id, output));
@@ -25636,7 +25644,7 @@ ${appendError}
25636
25644
  });
25637
25645
  latestOutput = result.output;
25638
25646
  if (this.isJobFileOutputEnabled) {
25639
- mkdirSync3(this.jobDir(id), { recursive: true });
25647
+ mkdirSync4(this.jobDir(id), { recursive: true });
25640
25648
  writeFileSync3(this.resultPath(id), latestOutput, "utf-8");
25641
25649
  }
25642
25650
  const elapsed = Math.round((Date.now() - startedAtMs) / 1000);
@@ -26624,9 +26632,9 @@ var exports_init = {};
26624
26632
  __export(exports_init, {
26625
26633
  run: () => run7
26626
26634
  });
26627
- 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";
26628
26636
  import { spawnSync as spawnSync9 } from "child_process";
26629
- 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";
26630
26638
  function ok(msg) {
26631
26639
  console.log(` ${green4("\u2713")} ${msg}`);
26632
26640
  }
@@ -26680,7 +26688,7 @@ function migrateLegacySpecialists(cwd, scope) {
26680
26688
  return;
26681
26689
  const targetDir = join11(cwd, ".specialists", scope);
26682
26690
  if (!existsSync12(targetDir)) {
26683
- mkdirSync4(targetDir, { recursive: true });
26691
+ mkdirSync5(targetDir, { recursive: true });
26684
26692
  }
26685
26693
  const files = readdirSync4(sourceDir).filter((f) => f.endsWith(".specialist.json") || f.endsWith(".specialist.json"));
26686
26694
  if (files.length === 0)
@@ -26717,7 +26725,7 @@ function copyCanonicalSpecialists(cwd) {
26717
26725
  return;
26718
26726
  }
26719
26727
  if (!existsSync12(targetDir)) {
26720
- mkdirSync4(targetDir, { recursive: true });
26728
+ mkdirSync5(targetDir, { recursive: true });
26721
26729
  }
26722
26730
  let copied = 0;
26723
26731
  let refreshed = 0;
@@ -26752,7 +26760,7 @@ function copyCanonicalMandatoryRules(cwd) {
26752
26760
  return;
26753
26761
  }
26754
26762
  if (!existsSync12(targetDir)) {
26755
- mkdirSync4(targetDir, { recursive: true });
26763
+ mkdirSync5(targetDir, { recursive: true });
26756
26764
  }
26757
26765
  let copied = 0;
26758
26766
  let refreshed = 0;
@@ -26787,7 +26795,7 @@ function copyCanonicalNodeConfigs(cwd) {
26787
26795
  return;
26788
26796
  }
26789
26797
  if (!existsSync12(targetDir)) {
26790
- mkdirSync4(targetDir, { recursive: true });
26798
+ mkdirSync5(targetDir, { recursive: true });
26791
26799
  }
26792
26800
  let copied = 0;
26793
26801
  let refreshed = 0;
@@ -26823,8 +26831,8 @@ function installProjectHooks(cwd) {
26823
26831
  skip("no hook files found in package");
26824
26832
  return;
26825
26833
  }
26826
- mkdirSync4(targetDir, { recursive: true });
26827
- mkdirSync4(claudeHooksDir, { recursive: true });
26834
+ mkdirSync5(targetDir, { recursive: true });
26835
+ mkdirSync5(claudeHooksDir, { recursive: true });
26828
26836
  let copied = 0;
26829
26837
  let skippedCopies = 0;
26830
26838
  let linked = 0;
@@ -26849,7 +26857,7 @@ function installProjectHooks(cwd) {
26849
26857
  rewiredLinks++;
26850
26858
  continue;
26851
26859
  }
26852
- const currentTarget = resolve6(dirname5(claudeHookPath), readlinkSync(claudeHookPath));
26860
+ const currentTarget = resolve6(dirname6(claudeHookPath), readlinkSync(claudeHookPath));
26853
26861
  if (currentTarget !== xtrmDest) {
26854
26862
  unlinkSync(claudeHookPath);
26855
26863
  symlinkSync(relativeTarget, claudeHookPath);
@@ -26877,7 +26885,7 @@ function ensureProjectHookWiring(cwd) {
26877
26885
  const settingsPath = join11(cwd, ".claude", "settings.json");
26878
26886
  const settingsDir = join11(cwd, ".claude");
26879
26887
  if (!existsSync12(settingsDir)) {
26880
- mkdirSync4(settingsDir, { recursive: true });
26888
+ mkdirSync5(settingsDir, { recursive: true });
26881
26889
  }
26882
26890
  const settings = loadJson(settingsPath, {});
26883
26891
  if (!settings.hooks || typeof settings.hooks !== "object") {
@@ -26913,10 +26921,10 @@ function ensureProjectHookWiring(cwd) {
26913
26921
  }
26914
26922
  function ensureRootSymlink(rootPath, expectedTargetPath) {
26915
26923
  if (!existsSync12(rootPath)) {
26916
- mkdirSync4(dirname5(rootPath), { recursive: true });
26917
- const relTarget = relative(dirname5(rootPath), expectedTargetPath);
26924
+ mkdirSync5(dirname6(rootPath), { recursive: true });
26925
+ const relTarget = relative(dirname6(rootPath), expectedTargetPath);
26918
26926
  symlinkSync(relTarget, rootPath);
26919
- ok(`created ${basename4(dirname5(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26927
+ ok(`created ${basename4(dirname6(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26920
26928
  return;
26921
26929
  }
26922
26930
  const stats = lstatSync(rootPath);
@@ -26924,7 +26932,7 @@ function ensureRootSymlink(rootPath, expectedTargetPath) {
26924
26932
  throw new Error(`${rootPath} must be a symlink to ${expectedTargetPath}. Aborting.`);
26925
26933
  }
26926
26934
  const linkTarget = readlinkSync(rootPath);
26927
- const resolvedTarget = resolve6(dirname5(rootPath), linkTarget);
26935
+ const resolvedTarget = resolve6(dirname6(rootPath), linkTarget);
26928
26936
  const resolvedExpected = resolve6(expectedTargetPath);
26929
26937
  if (resolvedTarget === resolvedExpected) {
26930
26938
  return;
@@ -26935,9 +26943,9 @@ function ensureRootSymlink(rootPath, expectedTargetPath) {
26935
26943
  ];
26936
26944
  if (legacyTargets.includes(resolvedTarget)) {
26937
26945
  unlinkSync(rootPath);
26938
- const relTarget = relative(dirname5(rootPath), expectedTargetPath);
26946
+ const relTarget = relative(dirname6(rootPath), expectedTargetPath);
26939
26947
  symlinkSync(relTarget, rootPath);
26940
- ok(`rewired ${basename4(dirname5(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26948
+ ok(`rewired ${basename4(dirname6(rootPath))}/${basename4(rootPath)} \u2192 ${relTarget}`);
26941
26949
  return;
26942
26950
  }
26943
26951
  throw new Error(`${rootPath} points to ${linkTarget}, expected ${expectedTargetPath}. Aborting.`);
@@ -26958,7 +26966,7 @@ function ensureActiveSkillSymlink(defaultSkillPath, activeLinkPath) {
26958
26966
  if (!stats.isSymbolicLink()) {
26959
26967
  throw new Error(`${activeLinkPath} already exists and is not a symlink.`);
26960
26968
  }
26961
- const currentTarget = resolve6(dirname5(activeLinkPath), readlinkSync(activeLinkPath));
26969
+ const currentTarget = resolve6(dirname6(activeLinkPath), readlinkSync(activeLinkPath));
26962
26970
  if (currentTarget !== resolve6(defaultSkillPath)) {
26963
26971
  throw new Error(`${activeLinkPath} points to an unexpected target.`);
26964
26972
  }
@@ -26980,8 +26988,8 @@ function installProjectSkills(cwd, syncSkills) {
26980
26988
  }
26981
26989
  const defaultRoot = join11(cwd, ".xtrm", "skills", "default");
26982
26990
  const activeRoot = join11(cwd, ".xtrm", "skills", "active");
26983
- mkdirSync4(defaultRoot, { recursive: true });
26984
- mkdirSync4(activeRoot, { recursive: true });
26991
+ mkdirSync5(defaultRoot, { recursive: true });
26992
+ mkdirSync5(activeRoot, { recursive: true });
26985
26993
  ensureRootSymlink(join11(cwd, ".claude", "skills"), activeRoot);
26986
26994
  ensureRootSymlink(join11(cwd, ".pi", "skills"), activeRoot);
26987
26995
  let copied = 0;
@@ -27012,7 +27020,7 @@ function createSpecialistsDirs(cwd) {
27012
27020
  let created = 0;
27013
27021
  for (const dir of [defaultDir, userDir]) {
27014
27022
  if (!existsSync12(dir)) {
27015
- mkdirSync4(dir, { recursive: true });
27023
+ mkdirSync5(dir, { recursive: true });
27016
27024
  created++;
27017
27025
  }
27018
27026
  }
@@ -27028,7 +27036,7 @@ function createRuntimeDirs(cwd) {
27028
27036
  let created = 0;
27029
27037
  for (const dir of runtimeDirs) {
27030
27038
  if (!existsSync12(dir)) {
27031
- mkdirSync4(dir, { recursive: true });
27039
+ mkdirSync5(dir, { recursive: true });
27032
27040
  created++;
27033
27041
  }
27034
27042
  }
@@ -27161,7 +27169,7 @@ function validateInitPostconditions(cwd) {
27161
27169
  continue;
27162
27170
  }
27163
27171
  const expectedTarget = resolve6(xtrmHooksDir, hookFile);
27164
- const resolvedTarget = resolve6(dirname5(claudeHookPath), readlinkSync(claudeHookPath));
27172
+ const resolvedTarget = resolve6(dirname6(claudeHookPath), readlinkSync(claudeHookPath));
27165
27173
  if (resolvedTarget !== expectedTarget) {
27166
27174
  warnings.push(`.claude/hooks/${hookFile} points to unexpected target`);
27167
27175
  }
@@ -27215,7 +27223,7 @@ function validateInitPostconditions(cwd) {
27215
27223
  warnings.push(`${relative(cwd, symlink.linkPath)} is not a symlink`);
27216
27224
  continue;
27217
27225
  }
27218
- const resolvedTarget = resolve6(dirname5(symlink.linkPath), readlinkSync(symlink.linkPath));
27226
+ const resolvedTarget = resolve6(dirname6(symlink.linkPath), readlinkSync(symlink.linkPath));
27219
27227
  if (resolvedTarget !== resolve6(symlink.expectedTarget)) {
27220
27228
  warnings.push(`${relative(cwd, symlink.linkPath)} points to an unexpected target`);
27221
27229
  }
@@ -27392,8 +27400,8 @@ var exports_db = {};
27392
27400
  __export(exports_db, {
27393
27401
  run: () => run9
27394
27402
  });
27395
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
27396
- 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";
27397
27405
  function formatBytes(bytes) {
27398
27406
  if (bytes < 1024)
27399
27407
  return `${bytes} B`;
@@ -28053,8 +28061,8 @@ function runBenchmarkExport(options) {
28053
28061
  });
28054
28062
  }
28055
28063
  rows.sort((a, b) => a.task_id.localeCompare(b.task_id) || a.executor_job_id.localeCompare(b.executor_job_id));
28056
- const outputDirectory = dirname6(options.outputPath);
28057
- mkdirSync5(outputDirectory, { recursive: true });
28064
+ const outputDirectory = dirname7(options.outputPath);
28065
+ mkdirSync6(outputDirectory, { recursive: true });
28058
28066
  const jsonl = rows.map((row) => JSON.stringify(row)).join(`
28059
28067
  `);
28060
28068
  writeFileSync5(options.outputPath, rows.length > 0 ? `${jsonl}
@@ -28145,6 +28153,19 @@ var init_db = __esm(() => {
28145
28153
  import { spawn as spawn3 } from "child_process";
28146
28154
  import { createHash as createHash3, randomUUID } from "crypto";
28147
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
+ }
28148
28169
  function hasUnsubstitutedVariables(template, variables) {
28149
28170
  const matches = template.match(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g) ?? [];
28150
28171
  for (const match of matches) {
@@ -28163,8 +28184,8 @@ function compatGuard(spec, trust) {
28163
28184
  if (execution.permission_required !== "READ_ONLY")
28164
28185
  throw new CompatGuardError("execution.permission_required", "permission_required must be READ_ONLY");
28165
28186
  const hasScripts = (spec.specialist.skills?.scripts?.length ?? 0) > 0;
28166
- if (hasScripts && !trust?.allowLocalScripts) {
28167
- 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");
28168
28189
  }
28169
28190
  const hasPaths = (spec.specialist.skills?.paths?.length ?? 0) > 0;
28170
28191
  const hasSkillInherit = Boolean(spec.specialist.prompt.skill_inherit);
@@ -28174,26 +28195,34 @@ function compatGuard(spec, trust) {
28174
28195
  if (hasSkillInherit && !trust?.allowSkills) {
28175
28196
  throw new CompatGuardError("prompt.skill_inherit", "skills not allowed (enable with --allow-skills)");
28176
28197
  }
28177
- if (hasPaths && trust?.allowSkills && trust.allowSkillsRoots && trust.allowSkillsRoots.length > 0) {
28198
+ if (trust?.allowSkills && trust.allowSkillsRoots && trust.allowSkillsRoots.length > 0) {
28178
28199
  const paths = spec.specialist.skills?.paths ?? [];
28179
- for (const path of paths) {
28180
- const allowed = trust.allowSkillsRoots.some((root) => path.startsWith(root));
28181
- if (!allowed) {
28182
- throw new CompatGuardError("skills.paths", `skill path '${path}' not under any --allow-skills-roots entry`);
28183
- }
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);
28184
28204
  }
28185
28205
  }
28186
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
+ }
28187
28216
  function computeSkillSources(spec) {
28188
- const paths = spec.specialist.skills?.paths ?? [];
28217
+ const entries = collectSkillPathEntries(spec);
28189
28218
  const sources = [];
28190
- for (const path of paths) {
28219
+ for (const { path, source } of entries) {
28191
28220
  try {
28192
28221
  const content = readFileSync12(path);
28193
28222
  const sha256 = createHash3("sha256").update(content).digest("hex");
28194
- sources.push({ path, sha256 });
28223
+ sources.push({ path, sha256, source });
28195
28224
  } catch {
28196
- sources.push({ path, sha256: "unreadable" });
28225
+ sources.push({ path, sha256: "unreadable", source });
28197
28226
  }
28198
28227
  }
28199
28228
  return sources;
@@ -28204,6 +28233,34 @@ function renderTaskTemplate(template, variables) {
28204
28233
  throw new Error(`Missing template variable: ${missing}`);
28205
28234
  return renderTemplate(template, variables);
28206
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
+ }
28207
28264
  function mapErrorType(message) {
28208
28265
  if (message.includes("Specialist not found"))
28209
28266
  return "specialist_not_found";
@@ -28211,6 +28268,8 @@ function mapErrorType(message) {
28211
28268
  return "specialist_load_error";
28212
28269
  if (message.includes("Missing template variable"))
28213
28270
  return "template_variable_missing";
28271
+ if (message.includes("prompt too large"))
28272
+ return "prompt_too_large";
28214
28273
  if (message.includes("output too large"))
28215
28274
  return "output_too_large";
28216
28275
  if (message.includes("auth") || message.includes("403") || message.includes("401"))
@@ -28278,6 +28337,18 @@ function writeTraceRow(client, specialist, model, traceId, output2, durationMs,
28278
28337
  onAuditFailure?.(error2);
28279
28338
  }
28280
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
+ }
28281
28352
  function resolveAssistantTextLimitBytes(spec) {
28282
28353
  return spec.specialist.execution.stdout_limit_bytes ?? resolveEnvAssistantTextLimitBytes() ?? DEFAULT_ASSISTANT_TEXT_LIMIT_BYTES;
28283
28354
  }
@@ -28293,8 +28364,9 @@ function resolveEnvAssistantTextLimitBytes() {
28293
28364
  return Math.floor(envLimit);
28294
28365
  }
28295
28366
  function openObservabilityClient(options) {
28296
- const dbPath = options.observabilityDbPath ?? options.projectDir;
28297
- return createObservabilitySqliteClient(dbPath);
28367
+ if (options.observabilityDbPath)
28368
+ return createObservabilitySqliteClientAtPath(options.observabilityDbPath);
28369
+ return createObservabilitySqliteClient(options.projectDir);
28298
28370
  }
28299
28371
  function resolveScriptSpecialistName(name) {
28300
28372
  if (name === "changelog-keeper")
@@ -28308,9 +28380,28 @@ async function runScriptSpecialist(input2, options) {
28308
28380
  const resolvedSpecialist = resolveScriptSpecialistName(input2.specialist);
28309
28381
  const spec = await options.loader.get(resolvedSpecialist);
28310
28382
  compatGuard(spec, options.trust);
28383
+ const skillPaths = options.trust?.allowSkills ? collectSkillPaths(spec) : [];
28311
28384
  const skillSources = options.trust?.allowSkills ? computeSkillSources(spec) : undefined;
28312
28385
  const template = input2.template ?? spec.specialist.prompt.task_template;
28313
- 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
+ }
28314
28405
  if (process.env.SPECIALISTS_SCRIPT_STUB_OUTPUT) {
28315
28406
  return {
28316
28407
  success: true,
@@ -28326,11 +28417,11 @@ async function runScriptSpecialist(input2, options) {
28326
28417
  };
28327
28418
  }
28328
28419
  const timeoutMs = input2.timeout_ms ?? spec.specialist.execution.timeout_ms ?? 120000;
28329
- const modelCandidates = collectModelCandidates(input2, spec, options);
28330
28420
  const assistantTextLimitBytes = resolveAssistantTextLimitBytes(spec);
28331
28421
  const attempts = [];
28332
28422
  for (const model of modelCandidates) {
28333
- 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);
28334
28425
  attempts.push(attempt);
28335
28426
  const parsed = classifyAttempt(attempt);
28336
28427
  if (parsed.retryable)
@@ -28373,14 +28464,23 @@ function collectModelCandidates(input2, spec, options) {
28373
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);
28374
28465
  return [...new Set(candidates)];
28375
28466
  }
28376
- function runSingleAttempt(prompt, model, thinkingLevel, timeoutMs, assistantTextLimitBytes, options) {
28377
- return new Promise((resolve8, reject) => {
28378
- 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);
28379
28475
  if (thinkingLevel)
28380
28476
  args.push("--thinking", thinkingLevel);
28381
- args.push(prompt);
28382
- 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() });
28383
28480
  options.onChild?.(pi);
28481
+ pi.stdin?.on("error", () => {});
28482
+ pi.stdin?.write(prompt);
28483
+ pi.stdin?.end();
28384
28484
  let stderr = "";
28385
28485
  let timedOut = false;
28386
28486
  let outputTooLarge = false;
@@ -28449,7 +28549,7 @@ function runSingleAttempt(prompt, model, thinkingLevel, timeoutMs, assistantText
28449
28549
  pi.on("error", reject);
28450
28550
  pi.on("close", (code) => {
28451
28551
  clearTimeout(timer);
28452
- resolve8({
28552
+ resolve9({
28453
28553
  model,
28454
28554
  text: assistantText,
28455
28555
  stderr,
@@ -28486,7 +28586,7 @@ function classifyAttempt(attempt) {
28486
28586
  function isRetryableModelFailure(stderr, text) {
28487
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();
28488
28588
  }
28489
- 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;
28490
28590
  var init_script_runner = __esm(() => {
28491
28591
  init_observability_sqlite();
28492
28592
  CompatGuardError = class CompatGuardError extends Error {
@@ -28500,6 +28600,7 @@ var init_script_runner = __esm(() => {
28500
28600
  DEFAULT_PENDING_LINE_LIMIT_BYTES = 16 * 1024 * 1024;
28501
28601
  DEFAULT_ASSISTANT_TEXT_LIMIT_BYTES = 4 * 1024 * 1024;
28502
28602
  DEFAULT_STDERR_LIMIT_BYTES = 1 * 1024 * 1024;
28603
+ DEFAULT_PROMPT_LIMIT_BYTES = 4 * 1024 * 1024;
28503
28604
  });
28504
28605
 
28505
28606
  // src/cli/validate.ts
@@ -28652,7 +28753,7 @@ var exports_edit = {};
28652
28753
  __export(exports_edit, {
28653
28754
  run: () => run11
28654
28755
  });
28655
- 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";
28656
28757
  import { join as join13 } from "path";
28657
28758
  function loadPresets() {
28658
28759
  const paths = [
@@ -29090,7 +29191,7 @@ function createUserFork(source, targetName) {
29090
29191
  if (source.scope === "user")
29091
29192
  return source;
29092
29193
  const targetDir = join13(process.cwd(), ".specialists", "user");
29093
- mkdirSync6(targetDir, { recursive: true });
29194
+ mkdirSync7(targetDir, { recursive: true });
29094
29195
  const targetFile = join13(targetDir, `${targetName}.specialist.json`);
29095
29196
  const doc2 = readJsonFile2(source.filePath);
29096
29197
  doc2.specialist = doc2.specialist ?? {};
@@ -29400,7 +29501,7 @@ __export(exports_config, {
29400
29501
  });
29401
29502
  import { readFileSync as readFileSync14 } from "fs";
29402
29503
  import { spawnSync as spawnSync10 } from "child_process";
29403
- import { dirname as dirname7, join as join15 } from "path";
29504
+ import { dirname as dirname8, join as join15 } from "path";
29404
29505
  import { fileURLToPath as fileURLToPath3 } from "url";
29405
29506
  function usage2() {
29406
29507
  return [
@@ -29446,7 +29547,7 @@ function getGitTopLevel(projectDir) {
29446
29547
  return result.stdout.trim() || undefined;
29447
29548
  }
29448
29549
  function getRuntimePackageVersion() {
29449
- const runtimeDir = dirname7(fileURLToPath3(import.meta.url));
29550
+ const runtimeDir = dirname8(fileURLToPath3(import.meta.url));
29450
29551
  const packageJsonPath = runtimeDir.includes("/dist/") ? join15(runtimeDir, "..", "package.json") : join15(runtimeDir, "..", "..", "package.json");
29451
29552
  return readPackageVersion(packageJsonPath);
29452
29553
  }
@@ -29585,8 +29686,8 @@ var init_config = __esm(() => {
29585
29686
  });
29586
29687
 
29587
29688
  // src/specialist/worktree.ts
29588
- import { existsSync as existsSync16, symlinkSync as symlinkSync2, mkdirSync as mkdirSync7 } from "fs";
29589
- 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";
29590
29691
  import { spawnSync as spawnSync11, execFileSync as execFileSync2 } from "child_process";
29591
29692
  function deriveBranchName(beadId, specialistName) {
29592
29693
  return `feature/${beadId}-${slugify(specialistName)}`;
@@ -29619,11 +29720,11 @@ function provisionWorktree(options) {
29619
29720
  const branch = deriveBranchName(options.beadId, options.specialistName);
29620
29721
  const existingPath = findExistingWorktree(branch, cwd);
29621
29722
  if (existingPath) {
29622
- return { branch, worktreePath: resolve8(existingPath), reused: true };
29723
+ return { branch, worktreePath: resolve9(existingPath), reused: true };
29623
29724
  }
29624
29725
  const worktreeBase = options.worktreeBase ?? join16(commonRoot, ".worktrees", options.beadId);
29625
29726
  const worktreeName = deriveWorktreeName(options.beadId, options.specialistName);
29626
- const worktreePath = resolve8(join16(worktreeBase, worktreeName));
29727
+ const worktreePath = resolve9(join16(worktreeBase, worktreeName));
29627
29728
  createWorktreeViaBd(worktreePath, branch, commonRoot);
29628
29729
  symlinkPiNpmCache(commonRoot, worktreePath);
29629
29730
  return { branch, worktreePath, reused: false };
@@ -29634,7 +29735,7 @@ function symlinkPiNpmCache(commonRoot, worktreePath) {
29634
29735
  if (!existsSync16(source) || existsSync16(target))
29635
29736
  return;
29636
29737
  try {
29637
- mkdirSync7(join16(worktreePath, ".pi"), { recursive: true });
29738
+ mkdirSync8(join16(worktreePath, ".pi"), { recursive: true });
29638
29739
  symlinkSync2(source, target);
29639
29740
  } catch {}
29640
29741
  }
@@ -29674,12 +29775,12 @@ var init_worktree = __esm(() => {
29674
29775
  });
29675
29776
 
29676
29777
  // src/specialist/epic-reconciler.ts
29677
- 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";
29678
29779
  import { join as join17 } from "path";
29679
29780
  function buildEpicLockPath(epicId) {
29680
29781
  const location = resolveObservabilityDbLocation();
29681
29782
  const lockDir = join17(location.dbDirectory, "locks");
29682
- mkdirSync8(lockDir, { recursive: true });
29783
+ mkdirSync9(lockDir, { recursive: true });
29683
29784
  return join17(lockDir, `epic-${epicId}.lock`);
29684
29785
  }
29685
29786
  function withEpicAdvisoryLock(epicId, action) {
@@ -31021,13 +31122,13 @@ async function parseArgs7(argv) {
31021
31122
  process.exit(1);
31022
31123
  }
31023
31124
  if (!prompt && !beadId && !process.stdin.isTTY) {
31024
- prompt = await new Promise((resolve9) => {
31125
+ prompt = await new Promise((resolve10) => {
31025
31126
  let buf = "";
31026
31127
  process.stdin.setEncoding("utf-8");
31027
31128
  process.stdin.on("data", (chunk) => {
31028
31129
  buf += chunk;
31029
31130
  });
31030
- process.stdin.on("end", () => resolve9(buf.trim()));
31131
+ process.stdin.on("end", () => resolve10(buf.trim()));
31031
31132
  });
31032
31133
  }
31033
31134
  if (!prompt && !beadId && !reuseJobId) {
@@ -31700,8 +31801,8 @@ class JobControl {
31700
31801
  }
31701
31802
  };
31702
31803
  let resolveJobId;
31703
- const jobIdPromise = new Promise((resolve9) => {
31704
- resolveJobId = resolve9;
31804
+ const jobIdPromise = new Promise((resolve10) => {
31805
+ resolveJobId = resolve10;
31705
31806
  });
31706
31807
  this.supervisor = new Supervisor({
31707
31808
  runner: this.runner,
@@ -31766,7 +31867,7 @@ class JobControl {
31766
31867
  if (deadline !== undefined && Date.now() >= deadline) {
31767
31868
  throw new Error(`Timed out waiting for terminal status for job ${jobId}`);
31768
31869
  }
31769
- await new Promise((resolve9) => setTimeout(resolve9, backoffMs));
31870
+ await new Promise((resolve10) => setTimeout(resolve10, backoffMs));
31770
31871
  backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
31771
31872
  }
31772
31873
  }
@@ -31945,7 +32046,7 @@ function hashOutput(output2, salt) {
31945
32046
  return createHash4("sha256").update(value).digest("hex");
31946
32047
  }
31947
32048
  function sleep2(ms) {
31948
- return new Promise((resolve9) => setTimeout(resolve9, ms));
32049
+ return new Promise((resolve10) => setTimeout(resolve10, ms));
31949
32050
  }
31950
32051
  function toContextHealth(contextPct) {
31951
32052
  if (contextPct === null)
@@ -33861,7 +33962,7 @@ __export(exports_node, {
33861
33962
  import { existsSync as existsSync19, readFileSync as readFileSync19, readdirSync as readdirSync7 } from "fs";
33862
33963
  import { randomUUID as randomUUID2 } from "crypto";
33863
33964
  import { spawnSync as spawnSync14 } from "child_process";
33864
- 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";
33865
33966
  function parseNodeArgs(argv) {
33866
33967
  const command = argv[0];
33867
33968
  const supportedCommands = new Set(["run", "list", "promote", "members", "memory", "stop", "spawn-member", "create-bead", "complete", "wait-phase"]);
@@ -34050,7 +34151,7 @@ function toNodeName(filePath) {
34050
34151
  function discoverNodeConfigs(cwd) {
34051
34152
  const discoveredByName = new Map;
34052
34153
  for (const directory of NODE_DISCOVERY_DIRS) {
34053
- const absoluteDir = resolve9(cwd, directory.path);
34154
+ const absoluteDir = resolve10(cwd, directory.path);
34054
34155
  if (!existsSync19(absoluteDir))
34055
34156
  continue;
34056
34157
  const files = readdirSync7(absoluteDir).filter((fileName) => fileName.endsWith(NODE_CONFIG_SUFFIX));
@@ -34072,7 +34173,7 @@ function getNodeDiscoverySummary() {
34072
34173
  `);
34073
34174
  }
34074
34175
  function resolveNodeConfigPath(cwd, input2) {
34075
- const explicitPath = resolve9(cwd, input2);
34176
+ const explicitPath = resolve10(cwd, input2);
34076
34177
  if (existsSync19(explicitPath)) {
34077
34178
  return { name: toNodeName(explicitPath), path: explicitPath, source: "repo" };
34078
34179
  }
@@ -34625,7 +34726,7 @@ async function handleNodeAction(args) {
34625
34726
  if (deadline !== null && Date.now() >= deadline) {
34626
34727
  throw new Error(`Timed out waiting for phase '${args.phaseId}' members: ${memberKeys.join(", ")}`);
34627
34728
  }
34628
- await new Promise((resolve10) => setTimeout(resolve10, 500));
34729
+ await new Promise((resolve11) => setTimeout(resolve11, 500));
34629
34730
  }
34630
34731
  } finally {
34631
34732
  sqliteClient.close();
@@ -35497,8 +35598,8 @@ var init_epic = __esm(() => {
35497
35598
 
35498
35599
  // src/cli/version-check.ts
35499
35600
  import { spawnSync as spawnSync16 } from "child_process";
35500
- import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync9 } from "fs";
35501
- 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";
35502
35603
  import { createRequire as createRequire3 } from "module";
35503
35604
  function readBundledPackageVersion(requireFn = require3) {
35504
35605
  for (const candidate of ["../package.json", "../../package.json"]) {
@@ -35532,7 +35633,7 @@ function readCachedVersionCheck() {
35532
35633
  return readCache();
35533
35634
  }
35534
35635
  function writeCache(cache) {
35535
- mkdirSync9(dirname8(CACHE_PATH), { recursive: true });
35636
+ mkdirSync10(dirname9(CACHE_PATH), { recursive: true });
35536
35637
  writeFileSync9(CACHE_PATH, `${JSON.stringify(cache, null, 2)}
35537
35638
  `, "utf8");
35538
35639
  }
@@ -38105,7 +38206,7 @@ async function followMerged(sqliteClient, jobsDir, options) {
38105
38206
  }
38106
38207
  const lastPrintedEventKey = new Map;
38107
38208
  const seenMetaKey = new Map;
38108
- await new Promise((resolve10) => {
38209
+ await new Promise((resolve11) => {
38109
38210
  const interval = setInterval(() => {
38110
38211
  const currentJobIds = listMatchingJobIds(sqliteClient, jobsDir, options);
38111
38212
  const statusByJobId = new Map;
@@ -38193,7 +38294,7 @@ async function followMerged(sqliteClient, jobsDir, options) {
38193
38294
  });
38194
38295
  if (completedJobs.size === trackedJobs.size || allTrackedTerminal) {
38195
38296
  clearInterval(interval);
38196
- resolve10();
38297
+ resolve11();
38197
38298
  }
38198
38299
  }
38199
38300
  }, 750);
@@ -38959,7 +39060,7 @@ async function waitForProcessExit(pid, timeoutMs) {
38959
39060
  while (Date.now() < deadline) {
38960
39061
  if (!isProcessAlive(pid))
38961
39062
  return true;
38962
- await new Promise((resolve10) => setTimeout(resolve10, 100));
39063
+ await new Promise((resolve11) => setTimeout(resolve11, 100));
38963
39064
  }
38964
39065
  return !isProcessAlive(pid);
38965
39066
  }
@@ -39134,7 +39235,7 @@ var init_attach = () => {};
39134
39235
 
39135
39236
  // src/specialist/drift-detector.ts
39136
39237
  import { existsSync as existsSync28, readFileSync as readFileSync29, readdirSync as readdirSync13, rmSync as rmSync4 } from "fs";
39137
- import { join as join31, resolve as resolve10, relative as relative2 } from "path";
39238
+ import { join as join31, resolve as resolve11, relative as relative3 } from "path";
39138
39239
  function listFiles(root) {
39139
39240
  if (!existsSync28(root))
39140
39241
  return [];
@@ -39154,7 +39255,7 @@ function listFiles(root) {
39154
39255
  return out;
39155
39256
  }
39156
39257
  function relPath(path, base) {
39157
- return relative2(base, path) || ".";
39258
+ return relative3(base, path) || ".";
39158
39259
  }
39159
39260
  function makeFinding(repoRoot, kind, scope, path, canonicalPath, bytesEqual) {
39160
39261
  const rel = relPath(path, repoRoot);
@@ -39172,8 +39273,8 @@ function detectDriftForRepo(repoRoot) {
39172
39273
  if (!asset.canonicalDir)
39173
39274
  continue;
39174
39275
  const scopes = [
39175
- { scope: "default", dir: resolve10(repoRoot, asset.managedDir) },
39176
- { scope: "user", dir: resolve10(repoRoot, ".specialists/user") }
39276
+ { scope: "default", dir: resolve11(repoRoot, asset.managedDir) },
39277
+ { scope: "user", dir: resolve11(repoRoot, ".specialists/user") }
39177
39278
  ];
39178
39279
  for (const { scope, dir } of scopes) {
39179
39280
  if (!existsSync28(dir))
@@ -39210,10 +39311,10 @@ function detectDriftUnderRoot(root) {
39210
39311
  visit2(join31(dir, entry.name));
39211
39312
  }
39212
39313
  };
39213
- visit2(resolve10(root));
39314
+ visit2(resolve11(root));
39214
39315
  const summary = repos.flatMap((r) => r.findings);
39215
39316
  return {
39216
- root: resolve10(root),
39317
+ root: resolve11(root),
39217
39318
  repos,
39218
39319
  summary: {
39219
39320
  repos: repos.length,
@@ -39249,7 +39350,7 @@ var exports_prune_stale_defaults = {};
39249
39350
  __export(exports_prune_stale_defaults, {
39250
39351
  run: () => run26
39251
39352
  });
39252
- import { resolve as resolve11 } from "path";
39353
+ import { resolve as resolve12 } from "path";
39253
39354
  function parseArgs11(argv) {
39254
39355
  let dryRun = false;
39255
39356
  let root = process.cwd();
@@ -39264,7 +39365,7 @@ function parseArgs11(argv) {
39264
39365
  const value = argv[i + 1];
39265
39366
  if (!value || value.startsWith("--"))
39266
39367
  throw new Error("--root requires a value");
39267
- root = resolve11(value);
39368
+ root = resolve12(value);
39268
39369
  i += 1;
39269
39370
  continue;
39270
39371
  }
@@ -39545,8 +39646,8 @@ __export(exports_doctor, {
39545
39646
  });
39546
39647
  import { createHash as createHash5 } from "crypto";
39547
39648
  import { spawnSync as spawnSync22 } from "child_process";
39548
- import { existsSync as existsSync29, lstatSync as lstatSync2, mkdirSync as mkdirSync10, readdirSync as readdirSync14, readFileSync as readFileSync30, readlinkSync as readlinkSync2, writeFileSync as writeFileSync12 } from "fs";
39549
- import { dirname as dirname9, join as join32, 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";
39550
39651
  function ok3(msg) {
39551
39652
  console.log(` ${green14("\u2713")} ${msg}`);
39552
39653
  }
@@ -39643,20 +39744,20 @@ function checkHooks() {
39643
39744
  for (const name of HOOK_NAMES) {
39644
39745
  const canonicalPath = join32(HOOKS_DIR, name);
39645
39746
  if (!existsSync29(canonicalPath)) {
39646
- fail4(`${relative3(CWD, canonicalPath)} ${red7("missing")}`);
39747
+ fail4(`${relative4(CWD, canonicalPath)} ${red7("missing")}`);
39647
39748
  fix("specialists init");
39648
39749
  allPresent = false;
39649
39750
  } else {
39650
- ok3(relative3(CWD, canonicalPath));
39751
+ ok3(relative4(CWD, canonicalPath));
39651
39752
  }
39652
39753
  const claudeHookPath = join32(CLAUDE_HOOKS_DIR, name);
39653
39754
  const symlinkState = isSymlinkTo(claudeHookPath, canonicalPath);
39654
39755
  if (symlinkState.ok) {
39655
- ok3(`${relative3(CWD, claudeHookPath)} -> ${relative3(dirname9(claudeHookPath), canonicalPath)}`);
39756
+ ok3(`${relative4(CWD, claudeHookPath)} -> ${relative4(dirname10(claudeHookPath), canonicalPath)}`);
39656
39757
  continue;
39657
39758
  }
39658
39759
  allPresent = false;
39659
- const relHookPath = relative3(CWD, claudeHookPath);
39760
+ const relHookPath = relative4(CWD, claudeHookPath);
39660
39761
  if (symlinkState.reason === "missing") {
39661
39762
  fail4(`${relHookPath} missing`);
39662
39763
  } else if (symlinkState.reason === "not-symlink") {
@@ -39738,7 +39839,7 @@ function collectFileHashes(rootDir) {
39738
39839
  }
39739
39840
  if (!entry.isFile())
39740
39841
  continue;
39741
- const relPath2 = relative3(rootDir, fullPath);
39842
+ const relPath2 = relative4(rootDir, fullPath);
39742
39843
  hashes.set(relPath2, hashFile(fullPath));
39743
39844
  }
39744
39845
  };
@@ -39759,8 +39860,8 @@ function isSymlinkTo(linkPath, expectedTargetPath) {
39759
39860
  return { ok: false, reason: "not-symlink" };
39760
39861
  try {
39761
39862
  const rawTarget = readlinkSync2(linkPath);
39762
- const resolvedTarget = resolve12(dirname9(linkPath), rawTarget);
39763
- const resolvedExpected = resolve12(expectedTargetPath);
39863
+ const resolvedTarget = resolve13(dirname10(linkPath), rawTarget);
39864
+ const resolvedExpected = resolve13(expectedTargetPath);
39764
39865
  if (resolvedTarget !== resolvedExpected) {
39765
39866
  return { ok: false, reason: "wrong-target", target: rawTarget };
39766
39867
  }
@@ -39820,7 +39921,7 @@ function checkSkillDrift() {
39820
39921
  for (const scope of ["claude", "pi"]) {
39821
39922
  const activeRoot = join32(XTRM_ACTIVE_SKILLS_DIR, scope);
39822
39923
  if (!existsSync29(activeRoot)) {
39823
- fail4(`${relative3(CWD, activeRoot)}/ missing`);
39924
+ fail4(`${relative4(CWD, activeRoot)}/ missing`);
39824
39925
  fix("specialists init --sync-skills");
39825
39926
  linksOk = false;
39826
39927
  continue;
@@ -39833,7 +39934,7 @@ function checkSkillDrift() {
39833
39934
  if (state.ok)
39834
39935
  continue;
39835
39936
  linksOk = false;
39836
- const relLink = relative3(CWD, activeLinkPath);
39937
+ const relLink = relative4(CWD, activeLinkPath);
39837
39938
  if (state.reason === "missing") {
39838
39939
  fail4(`${relLink} missing`);
39839
39940
  } else if (state.reason === "not-symlink") {
@@ -39854,11 +39955,11 @@ function checkSkillDrift() {
39854
39955
  for (const check2 of skillRootChecks) {
39855
39956
  const state = isSymlinkTo(check2.root, check2.expected);
39856
39957
  if (state.ok) {
39857
- ok3(`${relative3(CWD, check2.root)} -> ${relative3(dirname9(check2.root), check2.expected)}`);
39958
+ ok3(`${relative4(CWD, check2.root)} -> ${relative4(dirname10(check2.root), check2.expected)}`);
39858
39959
  continue;
39859
39960
  }
39860
39961
  rootLinksOk = false;
39861
- const relRoot = relative3(CWD, check2.root);
39962
+ const relRoot = relative4(CWD, check2.root);
39862
39963
  if (state.reason === "missing") {
39863
39964
  fail4(`${relRoot} missing`);
39864
39965
  } else if (state.reason === "not-symlink") {
@@ -39874,12 +39975,12 @@ function checkSkillDrift() {
39874
39975
  }
39875
39976
  function checkManagedMirror(label, sourceDir, mirrorDir, fixHint) {
39876
39977
  if (!existsSync29(sourceDir)) {
39877
- warn3(`${label} source missing: ${relative3(CWD, sourceDir)}`);
39978
+ warn3(`${label} source missing: ${relative4(CWD, sourceDir)}`);
39878
39979
  fix(fixHint);
39879
39980
  return false;
39880
39981
  }
39881
39982
  if (!existsSync29(mirrorDir)) {
39882
- fail4(`${label} mirror missing: ${relative3(CWD, mirrorDir)}`);
39983
+ fail4(`${label} mirror missing: ${relative4(CWD, mirrorDir)}`);
39883
39984
  fix(fixHint);
39884
39985
  return false;
39885
39986
  }
@@ -39975,7 +40076,7 @@ function checkRuntimeDirs() {
39975
40076
  for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
39976
40077
  if (!existsSync29(subDir)) {
39977
40078
  warn3(`.specialists/${label}/ missing \u2014 auto-creating`);
39978
- mkdirSync10(subDir, { recursive: true });
40079
+ mkdirSync11(subDir, { recursive: true });
39979
40080
  ok3(`.specialists/${label}/ created`);
39980
40081
  } else {
39981
40082
  ok3(`.specialists/${label}/ present`);
@@ -40057,7 +40158,7 @@ function parseDoctorArgs(argv) {
40057
40158
  const value = argv[i + 1];
40058
40159
  if (!value || value.startsWith("--"))
40059
40160
  throw new Error("--root requires a value");
40060
- opts.root = resolve12(value);
40161
+ opts.root = resolve13(value);
40061
40162
  i += 1;
40062
40163
  continue;
40063
40164
  }
@@ -40478,10 +40579,13 @@ __export(exports_serve, {
40478
40579
  run: () => run30,
40479
40580
  recordAuditFailure: () => recordAuditFailure,
40480
40581
  evaluateReadiness: () => evaluateReadiness2,
40481
- createReadinessState: () => createReadinessState
40582
+ createReadinessState: () => createReadinessState,
40583
+ checkPiHelpForFlags: () => checkPiHelpForFlags
40482
40584
  });
40483
40585
  import { createServer } from "http";
40586
+ import { randomUUID as randomUUID3 } from "crypto";
40484
40587
  import { once } from "events";
40588
+ import { spawnSync as spawnSync23 } from "child_process";
40485
40589
  import { access, readdir as readdir2, readFile as readFile5, constants } from "fs/promises";
40486
40590
  import { existsSync as existsSync31 } from "fs";
40487
40591
  import { homedir as homedir3 } from "os";
@@ -40539,13 +40643,23 @@ async function evaluateReadiness2(opts) {
40539
40643
  } catch {
40540
40644
  return { ready: false, reason: "db_not_writable" };
40541
40645
  }
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
+ }
40542
40656
  const userDir = join34(opts.projectDir, ".specialists", "user");
40543
40657
  const userDirResult = await checkUserDirSpecs(userDir);
40544
40658
  if (userDirResult === "empty")
40545
40659
  return { ready: false, reason: "empty_user_dir" };
40546
40660
  if (userDirResult === "invalid")
40547
40661
  return { ready: false, reason: "invalid_spec_in_user_dir" };
40548
- return { ready: true };
40662
+ return warning ? { ready: true, warning } : { ready: true };
40549
40663
  }
40550
40664
  function parseArgs12(argv) {
40551
40665
  let port = 8000;
@@ -40553,12 +40667,17 @@ function parseArgs12(argv) {
40553
40667
  let queueTimeoutMs = 5000;
40554
40668
  let shutdownGraceMs = 30000;
40555
40669
  let projectDir = process.cwd();
40670
+ let dbPath;
40556
40671
  let fallbackModel;
40557
40672
  let auditFailureThreshold = 5;
40558
40673
  let allowSkills = false;
40559
40674
  let allowSkillsRoots = [];
40560
- let allowLocalScripts = false;
40561
40675
  let reloadPollMs = 0;
40676
+ let readinessCanaryMode = "off";
40677
+ const readinessRequiredPiFlags = [];
40678
+ let readinessCanarySpecialist;
40679
+ let readinessCanaryTimeoutMs = 5000;
40680
+ let logLevel = "info";
40562
40681
  for (let i = 0;i < argv.length; i++) {
40563
40682
  const token = argv[i];
40564
40683
  if (token === "--port" && argv[i + 1])
@@ -40571,6 +40690,8 @@ function parseArgs12(argv) {
40571
40690
  shutdownGraceMs = Number(argv[++i]);
40572
40691
  else if ((token === "--project-dir" || token === "--user-dir") && argv[i + 1])
40573
40692
  projectDir = argv[++i];
40693
+ else if (token === "--db-path" && argv[i + 1])
40694
+ dbPath = argv[++i];
40574
40695
  else if (token === "--fallback-model" && argv[i + 1])
40575
40696
  fallbackModel = argv[++i];
40576
40697
  else if (token === "--audit-failure-threshold" && argv[i + 1])
@@ -40580,16 +40701,51 @@ function parseArgs12(argv) {
40580
40701
  else if (token === "--allow-skills-roots" && argv[i + 1])
40581
40702
  allowSkillsRoots = argv[++i].split(":").filter(Boolean);
40582
40703
  else if (token === "--allow-local-scripts")
40583
- allowLocalScripts = true;
40704
+ throw new Error("--allow-local-scripts is not supported for script-class specialists");
40584
40705
  else if (token === "--reload-poll-ms" && argv[i + 1])
40585
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
+ }
40586
40724
  }
40587
- 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;
40588
40735
  }
40589
40736
  function sendJson(res, statusCode, body) {
40590
40737
  res.writeHead(statusCode, { "content-type": "application/json" });
40591
40738
  res.end(JSON.stringify(body));
40592
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
+ }
40593
40749
  async function readBody(req) {
40594
40750
  const chunks = [];
40595
40751
  for await (const chunk of req)
@@ -40604,7 +40760,7 @@ async function waitForSlot(limit, timeoutMs, getActive) {
40604
40760
  while (getActive() >= limit) {
40605
40761
  if (Date.now() - startedAt >= timeoutMs)
40606
40762
  return false;
40607
- await new Promise((resolve13) => setTimeout(resolve13, 25));
40763
+ await new Promise((resolve14) => setTimeout(resolve14, 25));
40608
40764
  }
40609
40765
  return true;
40610
40766
  }
@@ -40612,13 +40768,30 @@ async function startServe(argv = process.argv.slice(3)) {
40612
40768
  const args = parseArgs12(argv);
40613
40769
  const loader = new SpecialistLoader({ projectDir: args.projectDir });
40614
40770
  const dbLocation = resolveObservabilityDbLocation(args.projectDir);
40615
- ensureObservabilityDbFile(dbLocation);
40616
- 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
+ })();
40617
40776
  const readinessState = createReadinessState();
40618
40777
  const userDir = join34(args.projectDir, ".specialists", "user");
40619
40778
  const hotReload = createUserDirWatcher({ loader, userDir, pollMs: args.reloadPollMs });
40620
40779
  let active = 0;
40621
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
+ };
40622
40795
  const server = createServer(async (req, res) => {
40623
40796
  if (req.url === "/healthz")
40624
40797
  return sendJson(res, 200, { ok: true });
@@ -40626,11 +40799,13 @@ async function startServe(argv = process.argv.slice(3)) {
40626
40799
  const result = await evaluateReadiness2({
40627
40800
  state: readinessState,
40628
40801
  projectDir: args.projectDir,
40629
- dbPath: dbLocation.dbPath,
40630
- auditFailureThreshold: args.auditFailureThreshold
40802
+ dbPath,
40803
+ auditFailureThreshold: args.auditFailureThreshold,
40804
+ piCanaryMode: args.readinessCanaryMode,
40805
+ piCanaryCheck
40631
40806
  });
40632
40807
  if (result.ready) {
40633
- 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 });
40634
40809
  }
40635
40810
  return sendJson(res, 503, {
40636
40811
  ready: false,
@@ -40640,27 +40815,65 @@ async function startServe(argv = process.argv.slice(3)) {
40640
40815
  }
40641
40816
  if (req.method !== "POST" || req.url !== "/v1/generate")
40642
40817
  return sendJson(res, 404, { success: false, error: "not_found", error_type: "internal" });
40643
- 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
+ });
40644
40833
  return sendJson(res, 503, { success: false, error: "shutting_down", error_type: "internal" });
40834
+ }
40645
40835
  const entered = await waitForSlot(args.concurrency, args.queueTimeoutMs, () => active);
40646
- 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
+ });
40647
40847
  return sendJson(res, 429, { success: false, error: "too_many_requests", error_type: "quota" });
40848
+ }
40648
40849
  active++;
40649
40850
  const work = (async () => {
40851
+ let promptBytes = 0;
40852
+ let requestedSpecialist = "unknown";
40650
40853
  try {
40651
40854
  const raw = await readBody(req);
40855
+ promptBytes = Buffer.byteLength(raw, "utf8");
40652
40856
  let parsed;
40653
40857
  try {
40654
40858
  parsed = JSON.parse(raw);
40655
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" });
40656
40863
  return sendJson(res, 400, { success: false, error: "malformed_request", error_type: "invalid_json" });
40657
40864
  }
40658
- 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" });
40659
40869
  return sendJson(res, 400, { success: false, error: "malformed_request", error_type: "invalid_json" });
40870
+ }
40871
+ requestedSpecialist = parsed.specialist;
40660
40872
  const result = await runScriptSpecialist(parsed, {
40661
40873
  loader,
40874
+ projectDir: args.projectDir,
40662
40875
  fallbackModel: args.fallbackModel,
40663
- observabilityDbPath: args.projectDir,
40876
+ ...args.dbPath ? { observabilityDbPath: args.dbPath } : {},
40664
40877
  onChild: (child) => {
40665
40878
  children.add(child);
40666
40879
  child.once("exit", () => children.delete(child));
@@ -40668,11 +40881,37 @@ async function startServe(argv = process.argv.slice(3)) {
40668
40881
  onAuditFailure: () => recordAuditFailure(readinessState),
40669
40882
  trust: {
40670
40883
  allowSkills: args.allowSkills,
40671
- allowSkillsRoots: args.allowSkillsRoots,
40672
- allowLocalScripts: args.allowLocalScripts
40884
+ allowSkillsRoots: args.allowSkillsRoots
40673
40885
  }
40674
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
+ });
40675
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" });
40676
40915
  } finally {
40677
40916
  active--;
40678
40917
  }
@@ -40703,7 +40942,7 @@ async function startServe(argv = process.argv.slice(3)) {
40703
40942
  async function run30(argv = process.argv.slice(3)) {
40704
40943
  await startServe(argv);
40705
40944
  }
40706
- var AUDIT_WINDOW_MS = 60000;
40945
+ var AUDIT_WINDOW_MS = 60000, DEFAULT_REQUIRED_PI_FLAGS;
40707
40946
  var init_serve = __esm(() => {
40708
40947
  init_loader();
40709
40948
  init_script_runner();
@@ -40711,6 +40950,7 @@ var init_serve = __esm(() => {
40711
40950
  init_observability_db();
40712
40951
  init_schema();
40713
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"];
40714
40954
  });
40715
40955
 
40716
40956
  // src/cli/script.ts
@@ -40721,7 +40961,7 @@ __export(exports_script, {
40721
40961
  parseArgs: () => parseArgs13,
40722
40962
  mapExitCode: () => mapExitCode
40723
40963
  });
40724
- import { spawnSync as spawnSync23 } from "child_process";
40964
+ import { spawnSync as spawnSync24 } from "child_process";
40725
40965
  function parseVar(entry) {
40726
40966
  const index = entry.indexOf("=");
40727
40967
  if (index <= 0)
@@ -40824,7 +41064,7 @@ function printResult(result, json) {
40824
41064
  console.error(result.error);
40825
41065
  }
40826
41066
  function runUnderLock(lockPath, argv) {
40827
- 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], {
40828
41068
  encoding: "utf-8",
40829
41069
  stdio: "inherit"
40830
41070
  });
@@ -40840,7 +41080,11 @@ async function run31(argv = process.argv.slice(3)) {
40840
41080
  process.exit(runUnderLock(args.singleInstance, argv));
40841
41081
  }
40842
41082
  const loader = new SpecialistLoader({ projectDir: args.projectDir });
40843
- 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
+ });
40844
41088
  printResult(result, args.json);
40845
41089
  process.exit(mapExitCode(result));
40846
41090
  }
@@ -41013,7 +41257,7 @@ var init_help = __esm(() => {
41013
41257
  });
41014
41258
 
41015
41259
  // src/index.ts
41016
- import { spawnSync as spawnSync24 } from "child_process";
41260
+ import { spawnSync as spawnSync25 } from "child_process";
41017
41261
 
41018
41262
  // node_modules/zod/v4/core/core.js
41019
41263
  var NEVER2 = Object.freeze({
@@ -49423,7 +49667,7 @@ async function run33() {
49423
49667
  if (wantsHelp()) {
49424
49668
  console.log([
49425
49669
  "",
49426
- "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]",
49427
49671
  "",
49428
49672
  "HTTP wrapper for script-class specialists.",
49429
49673
  "",
@@ -49459,7 +49703,7 @@ async function run33() {
49459
49703
  }
49460
49704
  if (sub === "release") {
49461
49705
  console.error("Deprecated. Use `xt release prepare/publish`. This alias will be removed in v4.0.");
49462
- const result = spawnSync24("xt", ["release", ...process.argv.slice(3)], { stdio: "inherit" });
49706
+ const result = spawnSync25("xt", ["release", ...process.argv.slice(3)], { stdio: "inherit" });
49463
49707
  if (result.error) {
49464
49708
  console.error(`Failed to run xt release: ${result.error.message}`);
49465
49709
  process.exit(1);