@jaggerxtrm/specialists 3.13.0 → 3.14.1
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/config/mandatory-rules/changelog-keeper-scope.md +18 -30
- package/config/skills/using-script-specialists/SKILL.md +7 -5
- package/config/skills/using-specialists-v3/SKILL.md +140 -0
- package/config/specialists/changelog-keeper.specialist.json +7 -12
- package/dist/index.js +390 -146
- package/dist/lib.js +145 -43
- package/dist/types/cli/script.d.ts.map +1 -1
- package/dist/types/cli/serve.d.ts +11 -2
- package/dist/types/cli/serve.d.ts.map +1 -1
- package/dist/types/specialist/observability-sqlite.d.ts +1 -0
- package/dist/types/specialist/observability-sqlite.d.ts.map +1 -1
- package/dist/types/specialist/schema.d.ts +27 -0
- package/dist/types/specialist/schema.d.ts.map +1 -1
- package/dist/types/specialist/script-runner.d.ts +5 -1
- package/dist/types/specialist/script-runner.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
24953
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26827
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
26917
|
-
const relTarget = relative(
|
|
26924
|
+
mkdirSync5(dirname6(rootPath), { recursive: true });
|
|
26925
|
+
const relTarget = relative(dirname6(rootPath), expectedTargetPath);
|
|
26918
26926
|
symlinkSync(relTarget, rootPath);
|
|
26919
|
-
ok(`created ${basename4(
|
|
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(
|
|
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(
|
|
26946
|
+
const relTarget = relative(dirname6(rootPath), expectedTargetPath);
|
|
26939
26947
|
symlinkSync(relTarget, rootPath);
|
|
26940
|
-
ok(`rewired ${basename4(
|
|
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(
|
|
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
|
-
|
|
26984
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
27396
|
-
import { dirname as
|
|
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 =
|
|
28057
|
-
|
|
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
|
|
28167
|
-
throw new CompatGuardError("skills.scripts", "scripts not
|
|
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 (
|
|
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
|
-
|
|
28181
|
-
|
|
28182
|
-
|
|
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
|
|
28217
|
+
const entries = collectSkillPathEntries(spec);
|
|
28189
28218
|
const sources = [];
|
|
28190
|
-
for (const path of
|
|
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
|
-
|
|
28297
|
-
|
|
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
|
|
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((
|
|
28378
|
-
const args = ["--mode", "json", "--no-session", "--no-extensions", "--no-tools", "--
|
|
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
|
-
|
|
28382
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
29589
|
-
import { join as join16, resolve as
|
|
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:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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((
|
|
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", () =>
|
|
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((
|
|
31704
|
-
resolveJobId =
|
|
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((
|
|
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((
|
|
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
|
|
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 =
|
|
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 =
|
|
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((
|
|
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
|
|
35501
|
-
import { dirname as
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
|
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
|
|
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:
|
|
39176
|
-
{ scope: "user", dir:
|
|
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(
|
|
39314
|
+
visit2(resolve11(root));
|
|
39214
39315
|
const summary = repos.flatMap((r) => r.findings);
|
|
39215
39316
|
return {
|
|
39216
|
-
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
|
|
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 =
|
|
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
|
|
39549
|
-
import { dirname as
|
|
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(`${
|
|
39747
|
+
fail4(`${relative4(CWD, canonicalPath)} ${red7("missing")}`);
|
|
39647
39748
|
fix("specialists init");
|
|
39648
39749
|
allPresent = false;
|
|
39649
39750
|
} else {
|
|
39650
|
-
ok3(
|
|
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(`${
|
|
39756
|
+
ok3(`${relative4(CWD, claudeHookPath)} -> ${relative4(dirname10(claudeHookPath), canonicalPath)}`);
|
|
39656
39757
|
continue;
|
|
39657
39758
|
}
|
|
39658
39759
|
allPresent = false;
|
|
39659
|
-
const relHookPath =
|
|
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 =
|
|
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 =
|
|
39763
|
-
const resolvedExpected =
|
|
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(`${
|
|
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 =
|
|
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(`${
|
|
39958
|
+
ok3(`${relative4(CWD, check2.root)} -> ${relative4(dirname10(check2.root), check2.expected)}`);
|
|
39858
39959
|
continue;
|
|
39859
39960
|
}
|
|
39860
39961
|
rootLinksOk = false;
|
|
39861
|
-
const relRoot =
|
|
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: ${
|
|
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: ${
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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((
|
|
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
|
-
|
|
40616
|
-
const db =
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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), {
|
|
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
|
|
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 =
|
|
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);
|