@jaggerxtrm/specialists 3.6.19 → 3.7.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/config/mandatory-rules/core-session-boundary.md +5 -0
- package/config/mandatory-rules/debugger-trace-first.md +5 -0
- package/config/mandatory-rules/executor-delivery.md +5 -0
- package/config/mandatory-rules/explorer-readonly.md +5 -0
- package/config/mandatory-rules/git-workflow-safe.md +5 -0
- package/config/mandatory-rules/gitnexus-required.md +5 -0
- package/config/mandatory-rules/index.json +8 -0
- package/config/mandatory-rules/overthinker-4phase.md +5 -0
- package/config/mandatory-rules/researcher-source-discipline.md +5 -0
- package/config/mandatory-rules/reviewer-verdict-format.md +5 -0
- package/config/mandatory-rules/test-runner-execution-scope.md +5 -0
- package/config/specialists/debugger.specialist.json +6 -0
- package/config/specialists/executor.specialist.json +7 -0
- package/config/specialists/explorer.specialist.json +7 -1
- package/config/specialists/overthinker.specialist.json +5 -0
- package/config/specialists/researcher.specialist.json +5 -0
- package/config/specialists/reviewer.specialist.json +6 -0
- package/config/specialists/sync-docs.specialist.json +5 -0
- package/config/specialists/test-runner.specialist.json +5 -0
- package/dist/index.js +471 -236
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17511,7 +17511,7 @@ ${result.warnings.map((w) => ` \u26A0 ${w}`).join(`
|
|
|
17511
17511
|
const raw = JSON.parse(jsonContent);
|
|
17512
17512
|
return SpecialistSchema.parseAsync(raw);
|
|
17513
17513
|
}
|
|
17514
|
-
var KebabCase, Semver, MetadataSchema, ExecutionSchema, PromptSchema2, ScriptEntrySchema, SkillsSchema, CapabilitiesSchema, CommunicationSchema, ValidationSchema, StallDetectionSchema, SpecialistSchema;
|
|
17514
|
+
var KebabCase, Semver, MetadataSchema, ExecutionSchema, PromptSchema2, ScriptEntrySchema, SkillsSchema, CapabilitiesSchema, CommunicationSchema, ValidationSchema, MandatoryRuleSchema, MandatoryRulesSchema, StallDetectionSchema, SpecialistSchema;
|
|
17515
17515
|
var init_schema = __esm(() => {
|
|
17516
17516
|
init_zod();
|
|
17517
17517
|
KebabCase = stringType().regex(/^[a-z][a-z0-9-]*$/, "Must be kebab-case");
|
|
@@ -17580,6 +17580,17 @@ var init_schema = __esm(() => {
|
|
|
17580
17580
|
files_to_watch: arrayType(stringType()).optional(),
|
|
17581
17581
|
stale_threshold_days: numberType().optional()
|
|
17582
17582
|
}).optional();
|
|
17583
|
+
MandatoryRuleSchema = objectType({
|
|
17584
|
+
id: stringType(),
|
|
17585
|
+
level: enumType(["error", "warn", "info"]).default("error"),
|
|
17586
|
+
text: stringType(),
|
|
17587
|
+
when: stringType().optional()
|
|
17588
|
+
});
|
|
17589
|
+
MandatoryRulesSchema = objectType({
|
|
17590
|
+
template_sets: arrayType(KebabCase).default([]),
|
|
17591
|
+
disable_default_globals: booleanType().default(false),
|
|
17592
|
+
inline_rules: arrayType(MandatoryRuleSchema).default([])
|
|
17593
|
+
}).optional();
|
|
17583
17594
|
StallDetectionSchema = objectType({
|
|
17584
17595
|
running_silence_warn_ms: numberType().optional(),
|
|
17585
17596
|
running_silence_error_ms: numberType().optional(),
|
|
@@ -17596,6 +17607,7 @@ var init_schema = __esm(() => {
|
|
|
17596
17607
|
communication: CommunicationSchema,
|
|
17597
17608
|
validation: ValidationSchema,
|
|
17598
17609
|
stall_detection: StallDetectionSchema,
|
|
17610
|
+
mandatory_rules: MandatoryRulesSchema,
|
|
17599
17611
|
output_file: stringType().optional(),
|
|
17600
17612
|
beads_integration: enumType(["auto", "always", "never"]).default("auto"),
|
|
17601
17613
|
beads_write_notes: booleanType().default(true),
|
|
@@ -18750,6 +18762,152 @@ function stripJsonFences(text) {
|
|
|
18750
18762
|
return text.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
|
|
18751
18763
|
}
|
|
18752
18764
|
|
|
18765
|
+
// src/specialist/mandatory-rules.ts
|
|
18766
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
18767
|
+
import { resolve as resolve2 } from "path";
|
|
18768
|
+
function readJsonFile(filePath) {
|
|
18769
|
+
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
18770
|
+
}
|
|
18771
|
+
function loadMandatoryRulesIndex(cwd) {
|
|
18772
|
+
const indexPath = resolve2(cwd, "config/mandatory-rules/index.json");
|
|
18773
|
+
if (!existsSync3(indexPath)) {
|
|
18774
|
+
console.warn("[specialist runner] Missing config/mandatory-rules/index.json; skipping MANDATORY_RULES injection");
|
|
18775
|
+
return null;
|
|
18776
|
+
}
|
|
18777
|
+
return readJsonFile(indexPath);
|
|
18778
|
+
}
|
|
18779
|
+
function parseQuotedScalar(value) {
|
|
18780
|
+
const trimmed = value.trim();
|
|
18781
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
18782
|
+
return trimmed.slice(1, -1);
|
|
18783
|
+
}
|
|
18784
|
+
return trimmed;
|
|
18785
|
+
}
|
|
18786
|
+
function parseRuleEntry(lines, startIndex) {
|
|
18787
|
+
const entryLine = lines[startIndex]?.trim();
|
|
18788
|
+
if (!entryLine?.startsWith("- "))
|
|
18789
|
+
return null;
|
|
18790
|
+
const firstLine = entryLine.slice(2).trim();
|
|
18791
|
+
const inlineFields = {};
|
|
18792
|
+
if (firstLine.length > 0 && !firstLine.includes(":")) {
|
|
18793
|
+
inlineFields.text = parseQuotedScalar(firstLine);
|
|
18794
|
+
} else if (firstLine.length > 0) {
|
|
18795
|
+
const [key, ...rest] = firstLine.split(":");
|
|
18796
|
+
inlineFields[key.trim()] = parseQuotedScalar(rest.join(":"));
|
|
18797
|
+
}
|
|
18798
|
+
let nextIndex = startIndex + 1;
|
|
18799
|
+
while (nextIndex < lines.length) {
|
|
18800
|
+
const line = lines[nextIndex];
|
|
18801
|
+
if (!line.trim()) {
|
|
18802
|
+
nextIndex += 1;
|
|
18803
|
+
continue;
|
|
18804
|
+
}
|
|
18805
|
+
if (/^\s*-\s+/.test(line))
|
|
18806
|
+
break;
|
|
18807
|
+
if (!/^\s+/.test(line))
|
|
18808
|
+
break;
|
|
18809
|
+
const trimmed = line.trim();
|
|
18810
|
+
const match = trimmed.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
|
|
18811
|
+
if (!match) {
|
|
18812
|
+
nextIndex += 1;
|
|
18813
|
+
continue;
|
|
18814
|
+
}
|
|
18815
|
+
inlineFields[match[1]] = parseQuotedScalar(match[2]);
|
|
18816
|
+
nextIndex += 1;
|
|
18817
|
+
}
|
|
18818
|
+
if (!inlineFields.text)
|
|
18819
|
+
return null;
|
|
18820
|
+
return {
|
|
18821
|
+
rule: {
|
|
18822
|
+
id: inlineFields.id ?? "",
|
|
18823
|
+
level: inlineFields.level ?? "required",
|
|
18824
|
+
text: inlineFields.text,
|
|
18825
|
+
...inlineFields.when ? { when: inlineFields.when } : {}
|
|
18826
|
+
},
|
|
18827
|
+
nextIndex
|
|
18828
|
+
};
|
|
18829
|
+
}
|
|
18830
|
+
function parseMandatoryRulesFrontmatter(content, setId) {
|
|
18831
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
18832
|
+
if (!frontmatterMatch)
|
|
18833
|
+
return [];
|
|
18834
|
+
const lines = frontmatterMatch[1].split(`
|
|
18835
|
+
`);
|
|
18836
|
+
const rulesHeaderIndex = lines.findIndex((line) => /^rules:\s*$/.test(line.trim()));
|
|
18837
|
+
if (rulesHeaderIndex === -1)
|
|
18838
|
+
return [];
|
|
18839
|
+
const rules = [];
|
|
18840
|
+
let index = rulesHeaderIndex + 1;
|
|
18841
|
+
while (index < lines.length) {
|
|
18842
|
+
const line = lines[index];
|
|
18843
|
+
if (!line.trim()) {
|
|
18844
|
+
index += 1;
|
|
18845
|
+
continue;
|
|
18846
|
+
}
|
|
18847
|
+
if (!/^\s*-\s+/.test(line))
|
|
18848
|
+
break;
|
|
18849
|
+
const parsed = parseRuleEntry(lines, index);
|
|
18850
|
+
if (!parsed)
|
|
18851
|
+
break;
|
|
18852
|
+
const ruleIndex = rules.length + 1;
|
|
18853
|
+
rules.push({
|
|
18854
|
+
id: parsed.rule.id || `${setId}-${ruleIndex}`,
|
|
18855
|
+
level: parsed.rule.level,
|
|
18856
|
+
text: parsed.rule.text,
|
|
18857
|
+
...parsed.rule.when ? { when: parsed.rule.when } : {}
|
|
18858
|
+
});
|
|
18859
|
+
index = parsed.nextIndex;
|
|
18860
|
+
}
|
|
18861
|
+
return rules;
|
|
18862
|
+
}
|
|
18863
|
+
function readMandatoryRuleSet(cwd, id) {
|
|
18864
|
+
const candidates = [
|
|
18865
|
+
resolve2(cwd, `.specialists/mandatory-rules/${id}.md`),
|
|
18866
|
+
resolve2(cwd, `config/mandatory-rules/${id}.md`)
|
|
18867
|
+
];
|
|
18868
|
+
const filePath = candidates.find((path) => existsSync3(path));
|
|
18869
|
+
if (!filePath)
|
|
18870
|
+
return null;
|
|
18871
|
+
const content = readFileSync(filePath, "utf8");
|
|
18872
|
+
const rules = parseMandatoryRulesFrontmatter(content, id);
|
|
18873
|
+
if (rules.length > 0)
|
|
18874
|
+
return { id, rules };
|
|
18875
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, "").trim();
|
|
18876
|
+
if (!body)
|
|
18877
|
+
return null;
|
|
18878
|
+
return {
|
|
18879
|
+
id,
|
|
18880
|
+
rules: [{ id: `${id}-1`, level: "required", text: body.replace(/\s+/g, " ") }]
|
|
18881
|
+
};
|
|
18882
|
+
}
|
|
18883
|
+
function formatMandatoryRulesBlock(sets) {
|
|
18884
|
+
if (sets.length === 0)
|
|
18885
|
+
return "";
|
|
18886
|
+
const sections = sets.map((set2) => {
|
|
18887
|
+
const rules = set2.rules.map((rule) => `- [${rule.level}] ${rule.text}`).join(`
|
|
18888
|
+
`);
|
|
18889
|
+
return `### ${set2.id}
|
|
18890
|
+
${rules}`;
|
|
18891
|
+
});
|
|
18892
|
+
return `## MANDATORY_RULES
|
|
18893
|
+
${sections.join(`
|
|
18894
|
+
|
|
18895
|
+
`)}`;
|
|
18896
|
+
}
|
|
18897
|
+
function buildMandatoryRulesBlock(specialistConfig) {
|
|
18898
|
+
const cwd = specialistConfig.cwd ?? process.cwd();
|
|
18899
|
+
const index = loadMandatoryRulesIndex(cwd);
|
|
18900
|
+
if (!index)
|
|
18901
|
+
return "";
|
|
18902
|
+
const setIds = [
|
|
18903
|
+
...index.required_template_sets ?? [],
|
|
18904
|
+
...index.default_template_sets ?? []
|
|
18905
|
+
];
|
|
18906
|
+
const sets = setIds.map((id) => readMandatoryRuleSet(cwd, id)).filter((set2) => set2 !== null);
|
|
18907
|
+
return formatMandatoryRulesBlock(sets);
|
|
18908
|
+
}
|
|
18909
|
+
var init_mandatory_rules = () => {};
|
|
18910
|
+
|
|
18753
18911
|
// src/specialist/beads.ts
|
|
18754
18912
|
import { spawnSync } from "child_process";
|
|
18755
18913
|
function buildBeadContext(bead, completedBlockers = []) {
|
|
@@ -18903,7 +19061,7 @@ function shouldCreateBead(beadsIntegration, permissionRequired) {
|
|
|
18903
19061
|
var init_beads = () => {};
|
|
18904
19062
|
|
|
18905
19063
|
// src/specialist/observability-db.ts
|
|
18906
|
-
import { chmodSync, existsSync as
|
|
19064
|
+
import { chmodSync, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
18907
19065
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
18908
19066
|
import { join as join3, sep as sep2 } from "path";
|
|
18909
19067
|
function resolveGitRootFrom(cwd) {
|
|
@@ -18953,7 +19111,7 @@ function resolveObservabilityDbLocation(cwd = process.cwd()) {
|
|
|
18953
19111
|
}
|
|
18954
19112
|
function ensureObservabilityDbFile(location) {
|
|
18955
19113
|
mkdirSync2(location.dbDirectory, { recursive: true });
|
|
18956
|
-
const alreadyExists =
|
|
19114
|
+
const alreadyExists = existsSync4(location.dbPath);
|
|
18957
19115
|
if (!alreadyExists) {
|
|
18958
19116
|
writeFileSync2(location.dbPath, "", { encoding: "utf-8", flag: "wx" });
|
|
18959
19117
|
}
|
|
@@ -18967,7 +19125,7 @@ function ensureGitignoreHasObservabilityDbEntries(gitRoot) {
|
|
|
18967
19125
|
".specialists/db/*.db-wal",
|
|
18968
19126
|
".specialists/db/*.db-shm"
|
|
18969
19127
|
];
|
|
18970
|
-
const existing =
|
|
19128
|
+
const existing = existsSync4(gitignorePath) ? readFileSync2(gitignorePath, "utf-8") : "";
|
|
18971
19129
|
const existingLines = new Set(existing.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
18972
19130
|
const missingEntries = requiredEntries.filter((entry) => !existingLines.has(entry));
|
|
18973
19131
|
if (missingEntries.length === 0) {
|
|
@@ -18996,7 +19154,7 @@ var init_observability_db = __esm(() => {
|
|
|
18996
19154
|
|
|
18997
19155
|
// src/specialist/job-root.ts
|
|
18998
19156
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
18999
|
-
import { dirname as dirname2, join as join4, resolve as
|
|
19157
|
+
import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
|
|
19000
19158
|
function resolveCommonGitRoot(cwd) {
|
|
19001
19159
|
const result = spawnSync3("git", ["rev-parse", "--git-common-dir"], {
|
|
19002
19160
|
cwd,
|
|
@@ -19008,7 +19166,7 @@ function resolveCommonGitRoot(cwd) {
|
|
|
19008
19166
|
const gitCommonDir = result.stdout?.trim();
|
|
19009
19167
|
if (!gitCommonDir)
|
|
19010
19168
|
return;
|
|
19011
|
-
return dirname2(
|
|
19169
|
+
return dirname2(resolve3(cwd, gitCommonDir));
|
|
19012
19170
|
}
|
|
19013
19171
|
function resolveJobsDir(cwd = process.cwd()) {
|
|
19014
19172
|
const commonRoot = resolveCommonGitRoot(cwd) ?? cwd;
|
|
@@ -19028,7 +19186,7 @@ function resolveCurrentBranch(cwd = process.cwd()) {
|
|
|
19028
19186
|
var init_job_root = () => {};
|
|
19029
19187
|
|
|
19030
19188
|
// src/specialist/observability-sqlite.ts
|
|
19031
|
-
import { existsSync as
|
|
19189
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, statSync } from "fs";
|
|
19032
19190
|
import { join as join5 } from "path";
|
|
19033
19191
|
function loadBunDatabase() {
|
|
19034
19192
|
if (_probed)
|
|
@@ -20632,7 +20790,7 @@ class SqliteClient {
|
|
|
20632
20790
|
WHERE worktree_column IS NOT NULL AND worktree_column != ''
|
|
20633
20791
|
`).all();
|
|
20634
20792
|
for (const row of worktreeRows) {
|
|
20635
|
-
if (
|
|
20793
|
+
if (existsSync5(row.worktree_column))
|
|
20636
20794
|
continue;
|
|
20637
20795
|
findings.push({
|
|
20638
20796
|
kind: "stale-pointer",
|
|
@@ -20659,10 +20817,10 @@ function hasRunCompleteEvent(jobId, cwd = process.cwd()) {
|
|
|
20659
20817
|
sqliteClient?.close();
|
|
20660
20818
|
}
|
|
20661
20819
|
const eventsPath = join5(resolveJobsDir(cwd), jobId, "events.jsonl");
|
|
20662
|
-
if (!
|
|
20820
|
+
if (!existsSync5(eventsPath))
|
|
20663
20821
|
return false;
|
|
20664
20822
|
try {
|
|
20665
|
-
const lines =
|
|
20823
|
+
const lines = readFileSync3(eventsPath, "utf-8").split(`
|
|
20666
20824
|
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
20667
20825
|
for (const line of lines) {
|
|
20668
20826
|
const event = JSON.parse(line);
|
|
@@ -20678,7 +20836,7 @@ function createObservabilitySqliteClient(cwd = process.cwd()) {
|
|
|
20678
20836
|
if (!loadBunDatabase())
|
|
20679
20837
|
return null;
|
|
20680
20838
|
const location = resolveObservabilityDbLocation(cwd);
|
|
20681
|
-
if (!
|
|
20839
|
+
if (!existsSync5(location.dbPath))
|
|
20682
20840
|
return null;
|
|
20683
20841
|
try {
|
|
20684
20842
|
const Ctor = loadBunDatabase();
|
|
@@ -20926,8 +21084,8 @@ var init_memory_retrieval = __esm(() => {
|
|
|
20926
21084
|
import { createHash as createHash2 } from "crypto";
|
|
20927
21085
|
import { writeFile } from "fs/promises";
|
|
20928
21086
|
import { execSync as execSync2, spawnSync as spawnSync4 } from "child_process";
|
|
20929
|
-
import { existsSync as
|
|
20930
|
-
import { basename as basename2, resolve as
|
|
21087
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
21088
|
+
import { basename as basename2, resolve as resolve4 } from "path";
|
|
20931
21089
|
import { homedir as homedir2 } from "os";
|
|
20932
21090
|
function runScript(command, cwd) {
|
|
20933
21091
|
const run = (command ?? "").trim();
|
|
@@ -20958,7 +21116,7 @@ ${blocks}
|
|
|
20958
21116
|
</pre_flight_context>`;
|
|
20959
21117
|
}
|
|
20960
21118
|
function resolvePath(p) {
|
|
20961
|
-
return p.startsWith("~/") ?
|
|
21119
|
+
return p.startsWith("~/") ? resolve4(homedir2(), p.slice(2)) : resolve4(p);
|
|
20962
21120
|
}
|
|
20963
21121
|
function commandExists(cmd) {
|
|
20964
21122
|
const result = spawnSync4("which", [cmd], { stdio: "ignore" });
|
|
@@ -20966,7 +21124,7 @@ function commandExists(cmd) {
|
|
|
20966
21124
|
}
|
|
20967
21125
|
function validateShebang(filePath, errors5) {
|
|
20968
21126
|
try {
|
|
20969
|
-
const head =
|
|
21127
|
+
const head = readFileSync4(filePath, "utf-8").slice(0, 120);
|
|
20970
21128
|
if (!head.startsWith("#!"))
|
|
20971
21129
|
return;
|
|
20972
21130
|
const shebang = head.split(`
|
|
@@ -20996,7 +21154,7 @@ function validateBeforeRun(spec, permissionLevel) {
|
|
|
20996
21154
|
const warnings = [];
|
|
20997
21155
|
for (const p of spec.specialist.skills?.paths ?? []) {
|
|
20998
21156
|
const abs = resolvePath(p);
|
|
20999
|
-
if (!
|
|
21157
|
+
if (!existsSync6(abs))
|
|
21000
21158
|
warnings.push(` \u26A0 skills.paths: file not found: ${p}`);
|
|
21001
21159
|
}
|
|
21002
21160
|
for (const script of spec.specialist.skills?.scripts ?? []) {
|
|
@@ -21006,7 +21164,7 @@ function validateBeforeRun(spec, permissionLevel) {
|
|
|
21006
21164
|
const isFilePath = run.startsWith("./") || run.startsWith("../") || run.startsWith("/") || run.startsWith("~/");
|
|
21007
21165
|
if (isFilePath) {
|
|
21008
21166
|
const abs = resolvePath(run);
|
|
21009
|
-
if (!
|
|
21167
|
+
if (!existsSync6(abs)) {
|
|
21010
21168
|
errors5.push(` \u2717 skills.scripts: script not found: ${run}`);
|
|
21011
21169
|
} else {
|
|
21012
21170
|
validateShebang(abs, errors5);
|
|
@@ -21041,7 +21199,7 @@ ${errors5.join(`
|
|
|
21041
21199
|
}
|
|
21042
21200
|
}
|
|
21043
21201
|
function sleep(ms) {
|
|
21044
|
-
return new Promise((
|
|
21202
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
21045
21203
|
}
|
|
21046
21204
|
function getRetryDelayMs(attemptNumber) {
|
|
21047
21205
|
const baseDelay = RETRY_BASE_DELAY_MS * 2 ** Math.max(0, attemptNumber - 1);
|
|
@@ -21332,7 +21490,7 @@ ${buildBeadBoundaryInstruction(runCwd, options.worktreeBoundary)}`.trim();
|
|
|
21332
21490
|
execution.extensions?.gitnexus === false ? "pi-gitnexus" : undefined
|
|
21333
21491
|
].filter((value) => Boolean(value));
|
|
21334
21492
|
validateBeforeRun(spec, permissionLevel);
|
|
21335
|
-
const runCwd =
|
|
21493
|
+
const runCwd = resolve4(options.workingDirectory ?? process.cwd());
|
|
21336
21494
|
const preScripts = spec.specialist.skills?.scripts?.filter((s) => s.phase === "pre") ?? [];
|
|
21337
21495
|
const preResults = preScripts.map((s) => runScript(s.run ?? s.path, runCwd)).filter((_, i) => preScripts[i].inject_output);
|
|
21338
21496
|
const preScriptOutput = formatScriptOutput(preResults);
|
|
@@ -21357,7 +21515,23 @@ ${buildBeadBoundaryInstruction(runCwd, options.worktreeBoundary)}`.trim();
|
|
|
21357
21515
|
...beadVariables
|
|
21358
21516
|
};
|
|
21359
21517
|
const taskTemplate = options.inputBeadId ? renderTemplate(prompt.task_template, beadTemplateVariables) : prompt.task_template;
|
|
21360
|
-
|
|
21518
|
+
let renderedTask = renderTemplate(taskTemplate, variables);
|
|
21519
|
+
let mandatoryRulesBlock = "";
|
|
21520
|
+
try {
|
|
21521
|
+
mandatoryRulesBlock = buildMandatoryRulesBlock({ cwd: runCwd });
|
|
21522
|
+
if (mandatoryRulesBlock.trim()) {
|
|
21523
|
+
const rulesTokens = Math.ceil(mandatoryRulesBlock.length / 4);
|
|
21524
|
+
if (rulesTokens <= 2000) {
|
|
21525
|
+
renderedTask = `${renderedTask}
|
|
21526
|
+
|
|
21527
|
+
${mandatoryRulesBlock}`;
|
|
21528
|
+
} else {
|
|
21529
|
+
console.warn(`[specialist runner] Skipping MANDATORY_RULES injection: rules block too large (${rulesTokens} tokens, limit 2000)`);
|
|
21530
|
+
}
|
|
21531
|
+
}
|
|
21532
|
+
} catch (error2) {
|
|
21533
|
+
console.warn(`[specialist runner] Skipping MANDATORY_RULES injection: ${String(error2)}`);
|
|
21534
|
+
}
|
|
21361
21535
|
const promptHash = createHash2("sha256").update(renderedTask).digest("hex").slice(0, 16);
|
|
21362
21536
|
await hooks.emit("post_render", invocationId, metadata.name, metadata.version, {
|
|
21363
21537
|
prompt_hash: promptHash,
|
|
@@ -21398,8 +21572,8 @@ Respond like smart caveman. Cut all filler, keep technical substance.
|
|
|
21398
21572
|
---
|
|
21399
21573
|
`;
|
|
21400
21574
|
try {
|
|
21401
|
-
const gitnexusMetaPath =
|
|
21402
|
-
if (
|
|
21575
|
+
const gitnexusMetaPath = resolve4(runCwd, ".gitnexus/meta.json");
|
|
21576
|
+
if (existsSync6(gitnexusMetaPath)) {
|
|
21403
21577
|
agentsMd += `
|
|
21404
21578
|
|
|
21405
21579
|
---
|
|
@@ -21452,8 +21626,8 @@ ${memoryInjection.block}
|
|
|
21452
21626
|
memoryTokens = memoryInjection.estimatedTokens;
|
|
21453
21627
|
}
|
|
21454
21628
|
try {
|
|
21455
|
-
const gitnexusMetaPath =
|
|
21456
|
-
if (
|
|
21629
|
+
const gitnexusMetaPath = resolve4(runCwd, ".gitnexus/meta.json");
|
|
21630
|
+
if (existsSync6(gitnexusMetaPath)) {
|
|
21457
21631
|
const symbolCandidates = (beadForMemory.title.match(/\b(?:[A-Z][a-z0-9]+(?:[A-Z][a-z0-9]+)+|[a-z]+[A-Z][A-Za-z0-9]*)\b/g) ?? []).slice(0, 2);
|
|
21458
21632
|
const summaries = [];
|
|
21459
21633
|
for (const symbol of symbolCandidates) {
|
|
@@ -21503,6 +21677,35 @@ ${summaries.join(`
|
|
|
21503
21677
|
}
|
|
21504
21678
|
})
|
|
21505
21679
|
});
|
|
21680
|
+
const mandatoryRulesInjection = (() => {
|
|
21681
|
+
if (!mandatoryRulesBlock.trim())
|
|
21682
|
+
return null;
|
|
21683
|
+
const setsLoaded = mandatoryRulesBlock.match(/^###\s+(.+)$/gm)?.map((line) => line.replace(/^###\s+/, "").trim()) ?? [];
|
|
21684
|
+
const ruleCount = (mandatoryRulesBlock.match(/^- \[[^\]]+\]/gm) ?? []).length;
|
|
21685
|
+
const payload = {
|
|
21686
|
+
source: "mandatory_rules_injection",
|
|
21687
|
+
data: {
|
|
21688
|
+
sets_loaded: setsLoaded,
|
|
21689
|
+
rules_count: ruleCount,
|
|
21690
|
+
inline_rules_count: ruleCount,
|
|
21691
|
+
globals_disabled: false,
|
|
21692
|
+
token_estimate: estimateInjectedTokens(mandatoryRulesBlock)
|
|
21693
|
+
}
|
|
21694
|
+
};
|
|
21695
|
+
return {
|
|
21696
|
+
payload,
|
|
21697
|
+
summary: JSON.stringify({
|
|
21698
|
+
kind: "meta",
|
|
21699
|
+
...payload
|
|
21700
|
+
})
|
|
21701
|
+
};
|
|
21702
|
+
})();
|
|
21703
|
+
if (mandatoryRulesInjection) {
|
|
21704
|
+
onEvent?.("meta", {
|
|
21705
|
+
...mandatoryRulesInjection.payload,
|
|
21706
|
+
summary: mandatoryRulesInjection.summary
|
|
21707
|
+
});
|
|
21708
|
+
}
|
|
21506
21709
|
if (metadata.name === "reviewer" && options.reusedFromJobId) {
|
|
21507
21710
|
const reviewerDiffContext = buildReviewerDiffContext(runCwd);
|
|
21508
21711
|
agentsMd += buildReviewerDiffInstruction(reviewerDiffContext);
|
|
@@ -21723,6 +21926,7 @@ var PERMISSION_GATED_TOOLS, RETRY_BASE_DELAY_MS = 1000, RETRY_MAX_JITTER = 0.2,
|
|
|
21723
21926
|
var init_runner = __esm(() => {
|
|
21724
21927
|
init_session();
|
|
21725
21928
|
init_circuitBreaker();
|
|
21929
|
+
init_mandatory_rules();
|
|
21726
21930
|
init_beads();
|
|
21727
21931
|
init_memory_retrieval();
|
|
21728
21932
|
PERMISSION_GATED_TOOLS = {
|
|
@@ -21964,16 +22168,16 @@ __export(exports_version, {
|
|
|
21964
22168
|
import { createRequire } from "module";
|
|
21965
22169
|
import { fileURLToPath } from "url";
|
|
21966
22170
|
import { dirname as dirname4, join as join7 } from "path";
|
|
21967
|
-
import { existsSync as
|
|
22171
|
+
import { existsSync as existsSync7 } from "fs";
|
|
21968
22172
|
async function run2() {
|
|
21969
22173
|
const req = createRequire(import.meta.url);
|
|
21970
22174
|
const here = dirname4(fileURLToPath(import.meta.url));
|
|
21971
22175
|
const bundlePkgPath = join7(here, "..", "package.json");
|
|
21972
22176
|
const sourcePkgPath = join7(here, "..", "..", "package.json");
|
|
21973
22177
|
let pkg;
|
|
21974
|
-
if (
|
|
22178
|
+
if (existsSync7(bundlePkgPath)) {
|
|
21975
22179
|
pkg = req("../package.json");
|
|
21976
|
-
} else if (
|
|
22180
|
+
} else if (existsSync7(sourcePkgPath)) {
|
|
21977
22181
|
pkg = req("../../package.json");
|
|
21978
22182
|
} else {
|
|
21979
22183
|
console.error("Cannot find package.json");
|
|
@@ -22121,6 +22325,17 @@ function mapCallbackEventToTimelineEvent(callbackEvent, context) {
|
|
|
22121
22325
|
backend: "injected",
|
|
22122
22326
|
...context.memoryInjection ? { memory_injection: context.memoryInjection } : {}
|
|
22123
22327
|
};
|
|
22328
|
+
case "meta": {
|
|
22329
|
+
const payload = context.metaPayload;
|
|
22330
|
+
return {
|
|
22331
|
+
t,
|
|
22332
|
+
type: TIMELINE_EVENT_TYPES.META,
|
|
22333
|
+
model: payload?.model ?? "meta",
|
|
22334
|
+
backend: payload?.backend ?? "injected",
|
|
22335
|
+
...payload?.source ? { source: payload.source } : {},
|
|
22336
|
+
...payload?.data ? { data: payload.data } : {}
|
|
22337
|
+
};
|
|
22338
|
+
}
|
|
22124
22339
|
case "text":
|
|
22125
22340
|
return {
|
|
22126
22341
|
t,
|
|
@@ -22400,7 +22615,7 @@ var init_epic_lifecycle = __esm(() => {
|
|
|
22400
22615
|
});
|
|
22401
22616
|
|
|
22402
22617
|
// src/specialist/process-liveness.ts
|
|
22403
|
-
import { readFileSync as
|
|
22618
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
22404
22619
|
function isValidPid(pid) {
|
|
22405
22620
|
return Number.isInteger(pid) && pid > 0;
|
|
22406
22621
|
}
|
|
@@ -22414,7 +22629,7 @@ function isAliveBySignal(pid) {
|
|
|
22414
22629
|
}
|
|
22415
22630
|
function parseBootTimeMs() {
|
|
22416
22631
|
try {
|
|
22417
|
-
const procStat =
|
|
22632
|
+
const procStat = readFileSync5("/proc/stat", "utf-8");
|
|
22418
22633
|
const bootLine = procStat.split(`
|
|
22419
22634
|
`).find((line) => line.startsWith("btime "));
|
|
22420
22635
|
if (!bootLine)
|
|
@@ -22429,7 +22644,7 @@ function parseBootTimeMs() {
|
|
|
22429
22644
|
}
|
|
22430
22645
|
function parseProcessStartTimeMs(pid) {
|
|
22431
22646
|
try {
|
|
22432
|
-
const statRaw =
|
|
22647
|
+
const statRaw = readFileSync5(`/proc/${pid}/stat`, "utf-8");
|
|
22433
22648
|
const closeParenIndex = statRaw.lastIndexOf(")");
|
|
22434
22649
|
if (closeParenIndex < 0)
|
|
22435
22650
|
return;
|
|
@@ -22791,12 +23006,12 @@ var init_tmux_utils = () => {};
|
|
|
22791
23006
|
import {
|
|
22792
23007
|
appendFileSync,
|
|
22793
23008
|
closeSync,
|
|
22794
|
-
existsSync as
|
|
23009
|
+
existsSync as existsSync8,
|
|
22795
23010
|
fsyncSync,
|
|
22796
23011
|
mkdirSync as mkdirSync3,
|
|
22797
23012
|
openSync,
|
|
22798
23013
|
readdirSync,
|
|
22799
|
-
readFileSync as
|
|
23014
|
+
readFileSync as readFileSync6,
|
|
22800
23015
|
renameSync,
|
|
22801
23016
|
rmSync,
|
|
22802
23017
|
statSync as statSync2,
|
|
@@ -23136,8 +23351,8 @@ class Supervisor {
|
|
|
23136
23351
|
} finally {
|
|
23137
23352
|
this.pendingSqliteOperations -= 1;
|
|
23138
23353
|
if (this.pendingSqliteOperations === 0) {
|
|
23139
|
-
for (const
|
|
23140
|
-
|
|
23354
|
+
for (const resolve5 of this.pendingSqliteDrainResolvers) {
|
|
23355
|
+
resolve5();
|
|
23141
23356
|
}
|
|
23142
23357
|
this.pendingSqliteDrainResolvers.clear();
|
|
23143
23358
|
}
|
|
@@ -23146,8 +23361,8 @@ class Supervisor {
|
|
|
23146
23361
|
async waitForPendingSqliteOperations() {
|
|
23147
23362
|
if (this.pendingSqliteOperations === 0)
|
|
23148
23363
|
return;
|
|
23149
|
-
await new Promise((
|
|
23150
|
-
this.pendingSqliteDrainResolvers.add(
|
|
23364
|
+
await new Promise((resolve5) => {
|
|
23365
|
+
this.pendingSqliteDrainResolvers.add(resolve5);
|
|
23151
23366
|
});
|
|
23152
23367
|
}
|
|
23153
23368
|
async dispose() {
|
|
@@ -23207,10 +23422,10 @@ class Supervisor {
|
|
|
23207
23422
|
}
|
|
23208
23423
|
}
|
|
23209
23424
|
const path = this.statusPath(id);
|
|
23210
|
-
if (!
|
|
23425
|
+
if (!existsSync8(path))
|
|
23211
23426
|
return null;
|
|
23212
23427
|
try {
|
|
23213
|
-
const status = JSON.parse(
|
|
23428
|
+
const status = JSON.parse(readFileSync6(path, "utf-8"));
|
|
23214
23429
|
return this.withComputedLiveness(status);
|
|
23215
23430
|
} catch {
|
|
23216
23431
|
return null;
|
|
@@ -23244,15 +23459,15 @@ class Supervisor {
|
|
|
23244
23459
|
console.warn(`[supervisor] SQLite listStatuses failed, falling back to file state: ${String(error2)}`);
|
|
23245
23460
|
}
|
|
23246
23461
|
}
|
|
23247
|
-
if (!
|
|
23462
|
+
if (!existsSync8(this.resolvedJobsDir))
|
|
23248
23463
|
return [];
|
|
23249
23464
|
const jobs = [];
|
|
23250
23465
|
for (const entry of readdirSync(this.resolvedJobsDir)) {
|
|
23251
23466
|
const path = join8(this.resolvedJobsDir, entry, "status.json");
|
|
23252
|
-
if (!
|
|
23467
|
+
if (!existsSync8(path))
|
|
23253
23468
|
continue;
|
|
23254
23469
|
try {
|
|
23255
|
-
const status = JSON.parse(
|
|
23470
|
+
const status = JSON.parse(readFileSync6(path, "utf-8"));
|
|
23256
23471
|
jobs.push(this.withComputedLiveness(status));
|
|
23257
23472
|
} catch {}
|
|
23258
23473
|
}
|
|
@@ -23326,7 +23541,7 @@ class Supervisor {
|
|
|
23326
23541
|
}
|
|
23327
23542
|
}
|
|
23328
23543
|
gc() {
|
|
23329
|
-
if (!
|
|
23544
|
+
if (!existsSync8(this.resolvedJobsDir))
|
|
23330
23545
|
return;
|
|
23331
23546
|
const cutoff = Date.now() - JOB_TTL_DAYS * 86400000;
|
|
23332
23547
|
for (const entry of readdirSync(this.resolvedJobsDir)) {
|
|
@@ -23341,7 +23556,7 @@ class Supervisor {
|
|
|
23341
23556
|
}
|
|
23342
23557
|
}
|
|
23343
23558
|
crashRecovery() {
|
|
23344
|
-
if (!
|
|
23559
|
+
if (!existsSync8(this.resolvedJobsDir))
|
|
23345
23560
|
return;
|
|
23346
23561
|
const thresholds = {
|
|
23347
23562
|
...STALL_DETECTION_DEFAULTS,
|
|
@@ -23350,10 +23565,10 @@ class Supervisor {
|
|
|
23350
23565
|
const now = Date.now();
|
|
23351
23566
|
for (const entry of readdirSync(this.resolvedJobsDir)) {
|
|
23352
23567
|
const statusPath = join8(this.resolvedJobsDir, entry, "status.json");
|
|
23353
|
-
if (!
|
|
23568
|
+
if (!existsSync8(statusPath))
|
|
23354
23569
|
continue;
|
|
23355
23570
|
try {
|
|
23356
|
-
const s = JSON.parse(
|
|
23571
|
+
const s = JSON.parse(readFileSync6(statusPath, "utf-8"));
|
|
23357
23572
|
if (s.status === "running" || s.status === "starting") {
|
|
23358
23573
|
if (!s.pid)
|
|
23359
23574
|
continue;
|
|
@@ -23574,8 +23789,8 @@ class Supervisor {
|
|
|
23574
23789
|
let keepAliveExitResolved = false;
|
|
23575
23790
|
let isReadOnlySpecialist = false;
|
|
23576
23791
|
let resolveKeepAliveExit;
|
|
23577
|
-
const keepAliveExitPromise = new Promise((
|
|
23578
|
-
resolveKeepAliveExit =
|
|
23792
|
+
const keepAliveExitPromise = new Promise((resolve5) => {
|
|
23793
|
+
resolveKeepAliveExit = resolve5;
|
|
23579
23794
|
});
|
|
23580
23795
|
const finishKeepAlive = (exit) => {
|
|
23581
23796
|
if (keepAliveExitResolved)
|
|
@@ -23817,29 +24032,35 @@ ${appendError}
|
|
|
23817
24032
|
}
|
|
23818
24033
|
const toolCallId = details?.toolCallId;
|
|
23819
24034
|
const toolState = toolCallId ? activeToolCalls.get(toolCallId) : latestUncorrelatedToolState;
|
|
23820
|
-
const
|
|
23821
|
-
if (eventType !== "memory_injection" || !details?.summary)
|
|
24035
|
+
const parsedMeta = (() => {
|
|
24036
|
+
if (eventType !== "memory_injection" && eventType !== "meta" || !details?.summary)
|
|
23822
24037
|
return;
|
|
23823
24038
|
try {
|
|
23824
|
-
|
|
23825
|
-
const injection = parsed.memory_injection;
|
|
23826
|
-
if (!injection)
|
|
23827
|
-
return;
|
|
23828
|
-
return {
|
|
23829
|
-
static_tokens: injection.static_tokens ?? 0,
|
|
23830
|
-
memory_tokens: injection.memory_tokens ?? 0,
|
|
23831
|
-
gitnexus_tokens: injection.gitnexus_tokens ?? 0,
|
|
23832
|
-
total_tokens: injection.total_tokens ?? 0
|
|
23833
|
-
};
|
|
24039
|
+
return JSON.parse(details.summary);
|
|
23834
24040
|
} catch {
|
|
23835
24041
|
return;
|
|
23836
24042
|
}
|
|
23837
24043
|
})();
|
|
23838
|
-
|
|
24044
|
+
const metaDetails = details;
|
|
24045
|
+
const memoryInjection = parsedMeta?.memory_injection ? {
|
|
24046
|
+
static_tokens: parsedMeta.memory_injection.static_tokens ?? 0,
|
|
24047
|
+
memory_tokens: parsedMeta.memory_injection.memory_tokens ?? 0,
|
|
24048
|
+
gitnexus_tokens: parsedMeta.memory_injection.gitnexus_tokens ?? 0,
|
|
24049
|
+
total_tokens: parsedMeta.memory_injection.total_tokens ?? 0
|
|
24050
|
+
} : undefined;
|
|
24051
|
+
const mandatoryRulesInjection = parsedMeta?.source === "mandatory_rules_injection" && parsedMeta.data ? {
|
|
24052
|
+
sets_loaded: parsedMeta.data.sets_loaded ?? [],
|
|
24053
|
+
rules_count: parsedMeta.data.rules_count ?? 0,
|
|
24054
|
+
inline_rules_count: parsedMeta.data.inline_rules_count ?? 0,
|
|
24055
|
+
globals_disabled: parsedMeta.data.globals_disabled ?? false,
|
|
24056
|
+
token_estimate: parsedMeta.data.token_estimate ?? 0
|
|
24057
|
+
} : undefined;
|
|
24058
|
+
if (memoryInjection || mandatoryRulesInjection) {
|
|
23839
24059
|
setStatus({
|
|
23840
24060
|
startup_context: {
|
|
23841
24061
|
...statusSnapshot.startup_context ?? {},
|
|
23842
|
-
memory_injection: memoryInjection
|
|
24062
|
+
...memoryInjection ? { memory_injection: memoryInjection } : {},
|
|
24063
|
+
...mandatoryRulesInjection ? { mandatory_rules_injection: mandatoryRulesInjection } : {}
|
|
23843
24064
|
}
|
|
23844
24065
|
});
|
|
23845
24066
|
}
|
|
@@ -23871,7 +24092,13 @@ ${appendError}
|
|
|
23871
24092
|
extension: details?.extension,
|
|
23872
24093
|
errorMessage: details?.errorMessage
|
|
23873
24094
|
},
|
|
23874
|
-
memoryInjection
|
|
24095
|
+
memoryInjection,
|
|
24096
|
+
metaPayload: eventType === "meta" ? {
|
|
24097
|
+
model: details?.model,
|
|
24098
|
+
backend: metaDetails?.backend,
|
|
24099
|
+
source: metaDetails?.source,
|
|
24100
|
+
data: metaDetails?.data
|
|
24101
|
+
} : undefined
|
|
23875
24102
|
});
|
|
23876
24103
|
if (timelineEvent) {
|
|
23877
24104
|
appendTimelineEvent(timelineEvent);
|
|
@@ -23956,7 +24183,7 @@ ${appendError}
|
|
|
23956
24183
|
setStatus({ bead_id: beadId });
|
|
23957
24184
|
}, (fn) => {
|
|
23958
24185
|
steerFn = fn;
|
|
23959
|
-
if (!
|
|
24186
|
+
if (!existsSync8(fifoPath))
|
|
23960
24187
|
return;
|
|
23961
24188
|
fifoFd = openSync(fifoPath, "r+");
|
|
23962
24189
|
fifoReadStream = createReadStream("", { fd: fifoFd, autoClose: false });
|
|
@@ -24252,7 +24479,7 @@ ${appendError}
|
|
|
24252
24479
|
} catch {}
|
|
24253
24480
|
closeSync(eventsFd);
|
|
24254
24481
|
try {
|
|
24255
|
-
if (
|
|
24482
|
+
if (existsSync8(fifoPath))
|
|
24256
24483
|
rmSync(fifoPath);
|
|
24257
24484
|
} catch {}
|
|
24258
24485
|
if (statusSnapshot.tmux_session) {
|
|
@@ -24301,7 +24528,7 @@ __export(exports_list, {
|
|
|
24301
24528
|
ArgParseError: () => ArgParseError
|
|
24302
24529
|
});
|
|
24303
24530
|
import { spawnSync as spawnSync7 } from "child_process";
|
|
24304
|
-
import { existsSync as
|
|
24531
|
+
import { existsSync as existsSync9, readdirSync as readdirSync2, readFileSync as readFileSync7 } from "fs";
|
|
24305
24532
|
import { join as join9 } from "path";
|
|
24306
24533
|
import readline from "readline";
|
|
24307
24534
|
function permissionBadge(permission) {
|
|
@@ -24334,14 +24561,14 @@ function toLiveJob(status) {
|
|
|
24334
24561
|
}
|
|
24335
24562
|
function readJobStatus(statusPath) {
|
|
24336
24563
|
try {
|
|
24337
|
-
return JSON.parse(
|
|
24564
|
+
return JSON.parse(readFileSync7(statusPath, "utf-8"));
|
|
24338
24565
|
} catch {
|
|
24339
24566
|
return null;
|
|
24340
24567
|
}
|
|
24341
24568
|
}
|
|
24342
24569
|
function listLiveJobs(showDead) {
|
|
24343
24570
|
const jobsDir = join9(process.cwd(), ".specialists", "jobs");
|
|
24344
|
-
if (!
|
|
24571
|
+
if (!existsSync9(jobsDir))
|
|
24345
24572
|
return [];
|
|
24346
24573
|
const jobs = readdirSync2(jobsDir).map((entry) => toLiveJob(readJobStatus(join9(jobsDir, entry, "status.json")))).filter((job) => job !== null).filter((job) => showDead || !job.isDead).sort((a, b) => b.startedAtMs - a.startedAtMs);
|
|
24347
24574
|
return jobs;
|
|
@@ -24360,7 +24587,7 @@ function renderLiveSelector(jobs, selectedIndex) {
|
|
|
24360
24587
|
];
|
|
24361
24588
|
}
|
|
24362
24589
|
function selectLiveJob(jobs) {
|
|
24363
|
-
return new Promise((
|
|
24590
|
+
return new Promise((resolve5) => {
|
|
24364
24591
|
const input = process.stdin;
|
|
24365
24592
|
const output = process.stdout;
|
|
24366
24593
|
const wasRawMode = input.isTTY ? input.isRaw : false;
|
|
@@ -24376,7 +24603,7 @@ function selectLiveJob(jobs) {
|
|
|
24376
24603
|
readline.moveCursor(output, 0, -renderedLineCount);
|
|
24377
24604
|
readline.clearScreenDown(output);
|
|
24378
24605
|
}
|
|
24379
|
-
|
|
24606
|
+
resolve5(selected);
|
|
24380
24607
|
};
|
|
24381
24608
|
const render = () => {
|
|
24382
24609
|
if (renderedLineCount > 0) {
|
|
@@ -24903,9 +25130,9 @@ var exports_init = {};
|
|
|
24903
25130
|
__export(exports_init, {
|
|
24904
25131
|
run: () => run6
|
|
24905
25132
|
});
|
|
24906
|
-
import { copyFileSync, cpSync, existsSync as
|
|
25133
|
+
import { copyFileSync, cpSync, existsSync as existsSync10, lstatSync, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync8, readlinkSync, renameSync as renameSync2, symlinkSync, unlinkSync, writeFileSync as writeFileSync4 } from "fs";
|
|
24907
25134
|
import { spawnSync as spawnSync9 } from "child_process";
|
|
24908
|
-
import { basename as basename3, dirname as dirname5, join as join10, relative, resolve as
|
|
25135
|
+
import { basename as basename3, dirname as dirname5, join as join10, relative, resolve as resolve5 } from "path";
|
|
24909
25136
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24910
25137
|
function ok(msg) {
|
|
24911
25138
|
console.log(` ${green4("\u2713")} ${msg}`);
|
|
@@ -24920,7 +25147,7 @@ function isInstalled(bin) {
|
|
|
24920
25147
|
return spawnSync9("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
24921
25148
|
}
|
|
24922
25149
|
function assertXtrmPrerequisites(cwd) {
|
|
24923
|
-
const hasXtrmDir =
|
|
25150
|
+
const hasXtrmDir = existsSync10(join10(cwd, ".xtrm"));
|
|
24924
25151
|
const hasXtCli = isInstalled("xt");
|
|
24925
25152
|
if (hasXtrmDir && hasXtCli)
|
|
24926
25153
|
return;
|
|
@@ -24942,10 +25169,10 @@ function warnMissingOptionalPrerequisites() {
|
|
|
24942
25169
|
}
|
|
24943
25170
|
}
|
|
24944
25171
|
function loadJson(path, fallback) {
|
|
24945
|
-
if (!
|
|
25172
|
+
if (!existsSync10(path))
|
|
24946
25173
|
return structuredClone(fallback);
|
|
24947
25174
|
try {
|
|
24948
|
-
return JSON.parse(
|
|
25175
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
24949
25176
|
} catch {
|
|
24950
25177
|
return structuredClone(fallback);
|
|
24951
25178
|
}
|
|
@@ -24957,19 +25184,19 @@ function saveJson(path, value) {
|
|
|
24957
25184
|
function resolvePackagePath(relativePath) {
|
|
24958
25185
|
const configPath = `config/${relativePath}`;
|
|
24959
25186
|
let resolved = fileURLToPath2(new URL(`../${configPath}`, import.meta.url));
|
|
24960
|
-
if (
|
|
25187
|
+
if (existsSync10(resolved))
|
|
24961
25188
|
return resolved;
|
|
24962
25189
|
resolved = fileURLToPath2(new URL(`../../${configPath}`, import.meta.url));
|
|
24963
|
-
if (
|
|
25190
|
+
if (existsSync10(resolved))
|
|
24964
25191
|
return resolved;
|
|
24965
25192
|
return null;
|
|
24966
25193
|
}
|
|
24967
25194
|
function migrateLegacySpecialists(cwd, scope) {
|
|
24968
25195
|
const sourceDir = join10(cwd, ".specialists", scope, "specialists");
|
|
24969
|
-
if (!
|
|
25196
|
+
if (!existsSync10(sourceDir))
|
|
24970
25197
|
return;
|
|
24971
25198
|
const targetDir = join10(cwd, ".specialists", scope);
|
|
24972
|
-
if (!
|
|
25199
|
+
if (!existsSync10(targetDir)) {
|
|
24973
25200
|
mkdirSync4(targetDir, { recursive: true });
|
|
24974
25201
|
}
|
|
24975
25202
|
const files = readdirSync3(sourceDir).filter((f) => f.endsWith(".specialist.json") || f.endsWith(".specialist.json"));
|
|
@@ -24980,7 +25207,7 @@ function migrateLegacySpecialists(cwd, scope) {
|
|
|
24980
25207
|
for (const file of files) {
|
|
24981
25208
|
const src = join10(sourceDir, file);
|
|
24982
25209
|
const dest = join10(targetDir, file);
|
|
24983
|
-
if (
|
|
25210
|
+
if (existsSync10(dest)) {
|
|
24984
25211
|
skipped++;
|
|
24985
25212
|
continue;
|
|
24986
25213
|
}
|
|
@@ -25006,7 +25233,7 @@ function copyCanonicalSpecialists(cwd) {
|
|
|
25006
25233
|
skip("no specialist files found in package");
|
|
25007
25234
|
return;
|
|
25008
25235
|
}
|
|
25009
|
-
if (!
|
|
25236
|
+
if (!existsSync10(targetDir)) {
|
|
25010
25237
|
mkdirSync4(targetDir, { recursive: true });
|
|
25011
25238
|
}
|
|
25012
25239
|
let copied = 0;
|
|
@@ -25014,7 +25241,7 @@ function copyCanonicalSpecialists(cwd) {
|
|
|
25014
25241
|
for (const file of files) {
|
|
25015
25242
|
const src = join10(sourceDir, file);
|
|
25016
25243
|
const dest = join10(targetDir, file);
|
|
25017
|
-
if (
|
|
25244
|
+
if (existsSync10(dest)) {
|
|
25018
25245
|
copyFileSync(src, dest);
|
|
25019
25246
|
refreshed++;
|
|
25020
25247
|
} else {
|
|
@@ -25041,7 +25268,7 @@ function copyCanonicalNodeConfigs(cwd) {
|
|
|
25041
25268
|
skip("no node config files found in package");
|
|
25042
25269
|
return;
|
|
25043
25270
|
}
|
|
25044
|
-
if (!
|
|
25271
|
+
if (!existsSync10(targetDir)) {
|
|
25045
25272
|
mkdirSync4(targetDir, { recursive: true });
|
|
25046
25273
|
}
|
|
25047
25274
|
let copied = 0;
|
|
@@ -25049,7 +25276,7 @@ function copyCanonicalNodeConfigs(cwd) {
|
|
|
25049
25276
|
for (const file of files) {
|
|
25050
25277
|
const src = join10(sourceDir, file);
|
|
25051
25278
|
const dest = join10(targetDir, file);
|
|
25052
|
-
if (
|
|
25279
|
+
if (existsSync10(dest)) {
|
|
25053
25280
|
copyFileSync(src, dest);
|
|
25054
25281
|
refreshed++;
|
|
25055
25282
|
} else {
|
|
@@ -25088,7 +25315,7 @@ function installProjectHooks(cwd) {
|
|
|
25088
25315
|
for (const file of hooks) {
|
|
25089
25316
|
const src = join10(sourceDir, file);
|
|
25090
25317
|
const xtrmDest = join10(targetDir, file);
|
|
25091
|
-
if (
|
|
25318
|
+
if (existsSync10(xtrmDest)) {
|
|
25092
25319
|
skippedCopies++;
|
|
25093
25320
|
} else {
|
|
25094
25321
|
copyFileSync(src, xtrmDest);
|
|
@@ -25096,7 +25323,7 @@ function installProjectHooks(cwd) {
|
|
|
25096
25323
|
}
|
|
25097
25324
|
const claudeHookPath = join10(claudeHooksDir, file);
|
|
25098
25325
|
const relativeTarget = `../../.xtrm/hooks/specialists/${file}`;
|
|
25099
|
-
if (
|
|
25326
|
+
if (existsSync10(claudeHookPath)) {
|
|
25100
25327
|
const stats = lstatSync(claudeHookPath);
|
|
25101
25328
|
if (!stats.isSymbolicLink()) {
|
|
25102
25329
|
unlinkSync(claudeHookPath);
|
|
@@ -25104,7 +25331,7 @@ function installProjectHooks(cwd) {
|
|
|
25104
25331
|
rewiredLinks++;
|
|
25105
25332
|
continue;
|
|
25106
25333
|
}
|
|
25107
|
-
const currentTarget =
|
|
25334
|
+
const currentTarget = resolve5(dirname5(claudeHookPath), readlinkSync(claudeHookPath));
|
|
25108
25335
|
if (currentTarget !== xtrmDest) {
|
|
25109
25336
|
unlinkSync(claudeHookPath);
|
|
25110
25337
|
symlinkSync(relativeTarget, claudeHookPath);
|
|
@@ -25131,7 +25358,7 @@ function installProjectHooks(cwd) {
|
|
|
25131
25358
|
function ensureProjectHookWiring(cwd) {
|
|
25132
25359
|
const settingsPath = join10(cwd, ".claude", "settings.json");
|
|
25133
25360
|
const settingsDir = join10(cwd, ".claude");
|
|
25134
|
-
if (!
|
|
25361
|
+
if (!existsSync10(settingsDir)) {
|
|
25135
25362
|
mkdirSync4(settingsDir, { recursive: true });
|
|
25136
25363
|
}
|
|
25137
25364
|
const settings = loadJson(settingsPath, {});
|
|
@@ -25167,7 +25394,7 @@ function ensureProjectHookWiring(cwd) {
|
|
|
25167
25394
|
}
|
|
25168
25395
|
}
|
|
25169
25396
|
function ensureRootSymlink(rootPath, expectedTargetPath) {
|
|
25170
|
-
if (!
|
|
25397
|
+
if (!existsSync10(rootPath)) {
|
|
25171
25398
|
mkdirSync4(dirname5(rootPath), { recursive: true });
|
|
25172
25399
|
const relTarget = relative(dirname5(rootPath), expectedTargetPath);
|
|
25173
25400
|
symlinkSync(relTarget, rootPath);
|
|
@@ -25179,14 +25406,14 @@ function ensureRootSymlink(rootPath, expectedTargetPath) {
|
|
|
25179
25406
|
throw new Error(`${rootPath} must be a symlink to ${expectedTargetPath}. Aborting.`);
|
|
25180
25407
|
}
|
|
25181
25408
|
const linkTarget = readlinkSync(rootPath);
|
|
25182
|
-
const resolvedTarget =
|
|
25183
|
-
const resolvedExpected =
|
|
25409
|
+
const resolvedTarget = resolve5(dirname5(rootPath), linkTarget);
|
|
25410
|
+
const resolvedExpected = resolve5(expectedTargetPath);
|
|
25184
25411
|
if (resolvedTarget === resolvedExpected) {
|
|
25185
25412
|
return;
|
|
25186
25413
|
}
|
|
25187
25414
|
const legacyTargets = [
|
|
25188
|
-
|
|
25189
|
-
|
|
25415
|
+
resolve5(expectedTargetPath, "claude"),
|
|
25416
|
+
resolve5(expectedTargetPath, "pi")
|
|
25190
25417
|
];
|
|
25191
25418
|
if (legacyTargets.includes(resolvedTarget)) {
|
|
25192
25419
|
unlinkSync(rootPath);
|
|
@@ -25213,14 +25440,14 @@ function ensureActiveSkillSymlink(defaultSkillPath, activeLinkPath) {
|
|
|
25213
25440
|
if (!stats.isSymbolicLink()) {
|
|
25214
25441
|
throw new Error(`${activeLinkPath} already exists and is not a symlink.`);
|
|
25215
25442
|
}
|
|
25216
|
-
const currentTarget =
|
|
25217
|
-
if (currentTarget !==
|
|
25443
|
+
const currentTarget = resolve5(dirname5(activeLinkPath), readlinkSync(activeLinkPath));
|
|
25444
|
+
if (currentTarget !== resolve5(defaultSkillPath)) {
|
|
25218
25445
|
throw new Error(`${activeLinkPath} points to an unexpected target.`);
|
|
25219
25446
|
}
|
|
25220
25447
|
}
|
|
25221
25448
|
function installProjectSkills(cwd, syncSkills) {
|
|
25222
25449
|
const xtrmRoot = join10(cwd, ".xtrm");
|
|
25223
|
-
if (!
|
|
25450
|
+
if (!existsSync10(xtrmRoot)) {
|
|
25224
25451
|
throw new Error(".xtrm/ is missing. Install xtrm first, then run specialists init.");
|
|
25225
25452
|
}
|
|
25226
25453
|
const sourceDir = resolvePackagePath("skills");
|
|
@@ -25244,7 +25471,7 @@ function installProjectSkills(cwd, syncSkills) {
|
|
|
25244
25471
|
for (const skill of skills) {
|
|
25245
25472
|
const src = join10(sourceDir, skill);
|
|
25246
25473
|
const defaultSkillPath = join10(defaultRoot, skill);
|
|
25247
|
-
if (
|
|
25474
|
+
if (existsSync10(defaultSkillPath)) {
|
|
25248
25475
|
if (syncSkills) {
|
|
25249
25476
|
cpSync(src, defaultSkillPath, { recursive: true, force: true });
|
|
25250
25477
|
refreshed++;
|
|
@@ -25266,7 +25493,7 @@ function createSpecialistsDirs(cwd) {
|
|
|
25266
25493
|
const userDir = join10(cwd, ".specialists", "user");
|
|
25267
25494
|
let created = 0;
|
|
25268
25495
|
for (const dir of [defaultDir, userDir]) {
|
|
25269
|
-
if (!
|
|
25496
|
+
if (!existsSync10(dir)) {
|
|
25270
25497
|
mkdirSync4(dir, { recursive: true });
|
|
25271
25498
|
created++;
|
|
25272
25499
|
}
|
|
@@ -25282,7 +25509,7 @@ function createRuntimeDirs(cwd) {
|
|
|
25282
25509
|
];
|
|
25283
25510
|
let created = 0;
|
|
25284
25511
|
for (const dir of runtimeDirs) {
|
|
25285
|
-
if (!
|
|
25512
|
+
if (!existsSync10(dir)) {
|
|
25286
25513
|
mkdirSync4(dir, { recursive: true });
|
|
25287
25514
|
created++;
|
|
25288
25515
|
}
|
|
@@ -25306,7 +25533,7 @@ function ensureProjectMcp(cwd) {
|
|
|
25306
25533
|
}
|
|
25307
25534
|
function ensureGitignore(cwd) {
|
|
25308
25535
|
const gitignorePath = join10(cwd, ".gitignore");
|
|
25309
|
-
const existing =
|
|
25536
|
+
const existing = existsSync10(gitignorePath) ? readFileSync8(gitignorePath, "utf-8") : "";
|
|
25310
25537
|
let added = 0;
|
|
25311
25538
|
const lines = existing.split(`
|
|
25312
25539
|
`);
|
|
@@ -25331,7 +25558,7 @@ function ensureObservabilityDb(cwd) {
|
|
|
25331
25558
|
skip("observability DB path resolves inside jobs directory \u2014 skipped");
|
|
25332
25559
|
return;
|
|
25333
25560
|
}
|
|
25334
|
-
const alreadyExists =
|
|
25561
|
+
const alreadyExists = existsSync10(location.dbPath);
|
|
25335
25562
|
if (alreadyExists) {
|
|
25336
25563
|
skip("observability database already exists (not touched)");
|
|
25337
25564
|
return;
|
|
@@ -25352,8 +25579,8 @@ function ensureObservabilityDb(cwd) {
|
|
|
25352
25579
|
}
|
|
25353
25580
|
function ensureAgentsMd(cwd) {
|
|
25354
25581
|
const agentsPath = join10(cwd, "AGENTS.md");
|
|
25355
|
-
if (
|
|
25356
|
-
const existing =
|
|
25582
|
+
if (existsSync10(agentsPath)) {
|
|
25583
|
+
const existing = readFileSync8(agentsPath, "utf-8");
|
|
25357
25584
|
if (existing.includes(AGENTS_MARKER)) {
|
|
25358
25585
|
skip("AGENTS.md already has Specialists section");
|
|
25359
25586
|
} else {
|
|
@@ -25368,10 +25595,10 @@ function ensureAgentsMd(cwd) {
|
|
|
25368
25595
|
}
|
|
25369
25596
|
}
|
|
25370
25597
|
function readJsonObject(path) {
|
|
25371
|
-
if (!
|
|
25598
|
+
if (!existsSync10(path))
|
|
25372
25599
|
return {};
|
|
25373
25600
|
try {
|
|
25374
|
-
return JSON.parse(
|
|
25601
|
+
return JSON.parse(readFileSync8(path, "utf-8"));
|
|
25375
25602
|
} catch {
|
|
25376
25603
|
return {};
|
|
25377
25604
|
}
|
|
@@ -25399,14 +25626,14 @@ function hasHookCommand(settings, eventName, command) {
|
|
|
25399
25626
|
function validateInitPostconditions(cwd) {
|
|
25400
25627
|
const warnings = [];
|
|
25401
25628
|
const xtrmHooksDir = join10(cwd, ".xtrm", "hooks", "specialists");
|
|
25402
|
-
const xtrmHookFiles =
|
|
25629
|
+
const xtrmHookFiles = existsSync10(xtrmHooksDir) ? readdirSync3(xtrmHooksDir).filter((file) => file.endsWith(".mjs")) : [];
|
|
25403
25630
|
if (xtrmHookFiles.length === 0) {
|
|
25404
25631
|
warnings.push(".xtrm/hooks/specialists/ is missing or has no .mjs hooks");
|
|
25405
25632
|
}
|
|
25406
25633
|
const claudeHooksDir = join10(cwd, ".claude", "hooks");
|
|
25407
25634
|
for (const hookFile of xtrmHookFiles) {
|
|
25408
25635
|
const claudeHookPath = join10(claudeHooksDir, hookFile);
|
|
25409
|
-
if (!
|
|
25636
|
+
if (!existsSync10(claudeHookPath)) {
|
|
25410
25637
|
warnings.push(`.claude/hooks/${hookFile} is missing`);
|
|
25411
25638
|
continue;
|
|
25412
25639
|
}
|
|
@@ -25415,8 +25642,8 @@ function validateInitPostconditions(cwd) {
|
|
|
25415
25642
|
warnings.push(`.claude/hooks/${hookFile} is not a symlink`);
|
|
25416
25643
|
continue;
|
|
25417
25644
|
}
|
|
25418
|
-
const expectedTarget =
|
|
25419
|
-
const resolvedTarget =
|
|
25645
|
+
const expectedTarget = resolve5(xtrmHooksDir, hookFile);
|
|
25646
|
+
const resolvedTarget = resolve5(dirname5(claudeHookPath), readlinkSync(claudeHookPath));
|
|
25420
25647
|
if (resolvedTarget !== expectedTarget) {
|
|
25421
25648
|
warnings.push(`.claude/hooks/${hookFile} points to unexpected target`);
|
|
25422
25649
|
}
|
|
@@ -25441,12 +25668,12 @@ function validateInitPostconditions(cwd) {
|
|
|
25441
25668
|
}
|
|
25442
25669
|
const runtimeDirs = [join10(cwd, ".specialists", "jobs"), join10(cwd, ".specialists", "ready")];
|
|
25443
25670
|
for (const runtimeDir of runtimeDirs) {
|
|
25444
|
-
if (!
|
|
25671
|
+
if (!existsSync10(runtimeDir)) {
|
|
25445
25672
|
warnings.push(`${relative(cwd, runtimeDir)} is missing`);
|
|
25446
25673
|
}
|
|
25447
25674
|
}
|
|
25448
25675
|
const defaultSkillsRoot = join10(cwd, ".xtrm", "skills", "default");
|
|
25449
|
-
const defaultSkills =
|
|
25676
|
+
const defaultSkills = existsSync10(defaultSkillsRoot) ? readdirSync3(defaultSkillsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory()) : [];
|
|
25450
25677
|
if (defaultSkills.length === 0) {
|
|
25451
25678
|
warnings.push(".xtrm/skills/default/ is missing or has no skill directories");
|
|
25452
25679
|
}
|
|
@@ -25461,7 +25688,7 @@ function validateInitPostconditions(cwd) {
|
|
|
25461
25688
|
}
|
|
25462
25689
|
];
|
|
25463
25690
|
for (const symlink of rootSymlinks) {
|
|
25464
|
-
if (!
|
|
25691
|
+
if (!existsSync10(symlink.linkPath)) {
|
|
25465
25692
|
warnings.push(`${relative(cwd, symlink.linkPath)} is missing`);
|
|
25466
25693
|
continue;
|
|
25467
25694
|
}
|
|
@@ -25470,8 +25697,8 @@ function validateInitPostconditions(cwd) {
|
|
|
25470
25697
|
warnings.push(`${relative(cwd, symlink.linkPath)} is not a symlink`);
|
|
25471
25698
|
continue;
|
|
25472
25699
|
}
|
|
25473
|
-
const resolvedTarget =
|
|
25474
|
-
if (resolvedTarget !==
|
|
25700
|
+
const resolvedTarget = resolve5(dirname5(symlink.linkPath), readlinkSync(symlink.linkPath));
|
|
25701
|
+
if (resolvedTarget !== resolve5(symlink.expectedTarget)) {
|
|
25475
25702
|
warnings.push(`${relative(cwd, symlink.linkPath)} points to an unexpected target`);
|
|
25476
25703
|
}
|
|
25477
25704
|
}
|
|
@@ -25645,8 +25872,8 @@ var exports_db = {};
|
|
|
25645
25872
|
__export(exports_db, {
|
|
25646
25873
|
run: () => run8
|
|
25647
25874
|
});
|
|
25648
|
-
import { existsSync as
|
|
25649
|
-
import { dirname as dirname6, join as join11, resolve as
|
|
25875
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
25876
|
+
import { dirname as dirname6, join as join11, resolve as resolve6 } from "path";
|
|
25650
25877
|
function formatBytes(bytes) {
|
|
25651
25878
|
if (bytes < 1024)
|
|
25652
25879
|
return `${bytes} B`;
|
|
@@ -25795,7 +26022,7 @@ function parsePruneOptions(argv) {
|
|
|
25795
26022
|
}
|
|
25796
26023
|
function parseStatusFile(jobDirectoryPath, fallbackJobId) {
|
|
25797
26024
|
const statusPath = join11(jobDirectoryPath, "status.json");
|
|
25798
|
-
const statusRaw =
|
|
26025
|
+
const statusRaw = readFileSync9(statusPath, "utf-8");
|
|
25799
26026
|
const parsed = JSON.parse(statusRaw);
|
|
25800
26027
|
const jobId = typeof parsed.id === "string" && parsed.id.length > 0 ? parsed.id : fallbackJobId;
|
|
25801
26028
|
const specialist = typeof parsed.specialist === "string" && parsed.specialist.length > 0 ? parsed.specialist : "unknown";
|
|
@@ -25810,9 +26037,9 @@ function parseStatusFile(jobDirectoryPath, fallbackJobId) {
|
|
|
25810
26037
|
};
|
|
25811
26038
|
}
|
|
25812
26039
|
function replayEvents(eventsPath, sqliteClient, status) {
|
|
25813
|
-
if (!
|
|
26040
|
+
if (!existsSync11(eventsPath))
|
|
25814
26041
|
return 0;
|
|
25815
|
-
const rawContent =
|
|
26042
|
+
const rawContent = readFileSync9(eventsPath, "utf-8");
|
|
25816
26043
|
const lines = rawContent.split(`
|
|
25817
26044
|
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
25818
26045
|
let importedEvents = 0;
|
|
@@ -25838,7 +26065,7 @@ function runBackfill(options) {
|
|
|
25838
26065
|
};
|
|
25839
26066
|
try {
|
|
25840
26067
|
const jobsDirectoryPath = resolveJobsDir(process.cwd());
|
|
25841
|
-
if (!
|
|
26068
|
+
if (!existsSync11(jobsDirectoryPath)) {
|
|
25842
26069
|
console.log("No jobs directory found. Nothing to backfill.");
|
|
25843
26070
|
return;
|
|
25844
26071
|
}
|
|
@@ -25848,7 +26075,7 @@ function runBackfill(options) {
|
|
|
25848
26075
|
continue;
|
|
25849
26076
|
const jobDirectoryPath = join11(jobsDirectoryPath, jobEntry.name);
|
|
25850
26077
|
const statusPath = join11(jobDirectoryPath, "status.json");
|
|
25851
|
-
if (!
|
|
26078
|
+
if (!existsSync11(statusPath))
|
|
25852
26079
|
continue;
|
|
25853
26080
|
try {
|
|
25854
26081
|
const status = parseStatusFile(jobDirectoryPath, jobEntry.name);
|
|
@@ -25961,14 +26188,14 @@ ${bold7("specialists db prune")}
|
|
|
25961
26188
|
}
|
|
25962
26189
|
}
|
|
25963
26190
|
function parseBenchmarkExportOptions(argv) {
|
|
25964
|
-
const defaultOutput =
|
|
26191
|
+
const defaultOutput = resolve6(process.cwd(), ".specialists/benchmarks/executor-benchmark-rows.jsonl");
|
|
25965
26192
|
let outputPath = defaultOutput;
|
|
25966
26193
|
let epicId;
|
|
25967
26194
|
let includePrepJobs = false;
|
|
25968
26195
|
for (let i = 0;i < argv.length; i += 1) {
|
|
25969
26196
|
const argument = argv[i];
|
|
25970
26197
|
if (argument === "--output" && argv[i + 1]) {
|
|
25971
|
-
outputPath =
|
|
26198
|
+
outputPath = resolve6(process.cwd(), argv[i + 1]);
|
|
25972
26199
|
i += 1;
|
|
25973
26200
|
continue;
|
|
25974
26201
|
}
|
|
@@ -26201,7 +26428,7 @@ __export(exports_validate, {
|
|
|
26201
26428
|
ArgParseError: () => ArgParseError3
|
|
26202
26429
|
});
|
|
26203
26430
|
import { readFile as readFile3 } from "fs/promises";
|
|
26204
|
-
import { existsSync as
|
|
26431
|
+
import { existsSync as existsSync12 } from "fs";
|
|
26205
26432
|
import { join as join12 } from "path";
|
|
26206
26433
|
function parseArgs4(argv) {
|
|
26207
26434
|
const name = argv[0];
|
|
@@ -26221,11 +26448,11 @@ function findSpecialistFile(name) {
|
|
|
26221
26448
|
];
|
|
26222
26449
|
for (const dir of scanDirs) {
|
|
26223
26450
|
const jsonCandidate = join12(dir, `${name}.specialist.json`);
|
|
26224
|
-
if (
|
|
26451
|
+
if (existsSync12(jsonCandidate)) {
|
|
26225
26452
|
return jsonCandidate;
|
|
26226
26453
|
}
|
|
26227
26454
|
const yamlCandidate = join12(dir, `${name}.specialist.json`);
|
|
26228
|
-
if (
|
|
26455
|
+
if (existsSync12(yamlCandidate)) {
|
|
26229
26456
|
process.stderr.write(`[specialists] DEPRECATED: YAML specialist config detected at ${yamlCandidate}. Please migrate to .specialist.json
|
|
26230
26457
|
`);
|
|
26231
26458
|
return yamlCandidate;
|
|
@@ -26320,7 +26547,7 @@ __export(exports_edit, {
|
|
|
26320
26547
|
run: () => run10
|
|
26321
26548
|
});
|
|
26322
26549
|
import { spawnSync as spawnSync10 } from "child_process";
|
|
26323
|
-
import { existsSync as
|
|
26550
|
+
import { existsSync as existsSync13, readdirSync as readdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
26324
26551
|
import { join as join13 } from "path";
|
|
26325
26552
|
function loadPresets() {
|
|
26326
26553
|
const paths = [
|
|
@@ -26328,9 +26555,9 @@ function loadPresets() {
|
|
|
26328
26555
|
join13(process.cwd(), "config", "specialists", "presets.json")
|
|
26329
26556
|
];
|
|
26330
26557
|
for (const p of paths) {
|
|
26331
|
-
if (
|
|
26558
|
+
if (existsSync13(p)) {
|
|
26332
26559
|
try {
|
|
26333
|
-
const data = JSON.parse(
|
|
26560
|
+
const data = JSON.parse(readFileSync10(p, "utf-8"));
|
|
26334
26561
|
return data;
|
|
26335
26562
|
} catch {
|
|
26336
26563
|
return {};
|
|
@@ -26528,7 +26755,7 @@ ${usage()}`);
|
|
|
26528
26755
|
if (action === "get" && (pendingArrayOp || filePath)) {
|
|
26529
26756
|
fail("Error: --get cannot be combined with --append/--remove/--file");
|
|
26530
26757
|
}
|
|
26531
|
-
if (filePath && !
|
|
26758
|
+
if (filePath && !existsSync13(filePath)) {
|
|
26532
26759
|
fail(`Error: file not found: ${filePath}`);
|
|
26533
26760
|
}
|
|
26534
26761
|
return { name, all, scope, dryRun, action, path, value, filePath, preset };
|
|
@@ -26663,7 +26890,7 @@ function formatOutputValue(value) {
|
|
|
26663
26890
|
}
|
|
26664
26891
|
function openAllConfigSpecialistsInEditor() {
|
|
26665
26892
|
const configDir = join13(process.cwd(), "config", "specialists");
|
|
26666
|
-
if (!
|
|
26893
|
+
if (!existsSync13(configDir)) {
|
|
26667
26894
|
fail(`Error: missing directory: ${configDir}`);
|
|
26668
26895
|
}
|
|
26669
26896
|
const files = readdirSync5(configDir).filter((file) => file.endsWith(".specialist.json")).sort().map((file) => join13(configDir, file));
|
|
@@ -26683,7 +26910,7 @@ function getRawValue(args, resolvedPath) {
|
|
|
26683
26910
|
if (!MULTILINE_FILE_PATHS.has(resolvedPath.normalizedPath)) {
|
|
26684
26911
|
fail(`Error: --file is only supported for: ${Array.from(MULTILINE_FILE_PATHS).join(", ")}`);
|
|
26685
26912
|
}
|
|
26686
|
-
return
|
|
26913
|
+
return readFileSync10(args.filePath, "utf-8");
|
|
26687
26914
|
}
|
|
26688
26915
|
function getAtPath(root, segments) {
|
|
26689
26916
|
let current = root;
|
|
@@ -26793,7 +27020,7 @@ async function run10() {
|
|
|
26793
27020
|
}
|
|
26794
27021
|
const targets2 = await resolveTargets(args);
|
|
26795
27022
|
for (const target of targets2) {
|
|
26796
|
-
const raw =
|
|
27023
|
+
const raw = readFileSync10(target.filePath, "utf-8");
|
|
26797
27024
|
const doc2 = JSON.parse(raw);
|
|
26798
27025
|
for (const [fieldPath, fieldValue] of Object.entries(preset.fields)) {
|
|
26799
27026
|
const resolved = resolvePath2(fieldPath);
|
|
@@ -26819,7 +27046,7 @@ async function run10() {
|
|
|
26819
27046
|
fail("Error: no specialists found");
|
|
26820
27047
|
}
|
|
26821
27048
|
for (const target of targets) {
|
|
26822
|
-
const raw =
|
|
27049
|
+
const raw = readFileSync10(target.filePath, "utf-8");
|
|
26823
27050
|
let doc2;
|
|
26824
27051
|
try {
|
|
26825
27052
|
const parsed = JSON.parse(raw);
|
|
@@ -26962,8 +27189,8 @@ var init_config = __esm(() => {
|
|
|
26962
27189
|
});
|
|
26963
27190
|
|
|
26964
27191
|
// src/specialist/worktree.ts
|
|
26965
|
-
import { existsSync as
|
|
26966
|
-
import { join as join14, resolve as
|
|
27192
|
+
import { existsSync as existsSync14, symlinkSync as symlinkSync2, mkdirSync as mkdirSync6 } from "fs";
|
|
27193
|
+
import { join as join14, resolve as resolve7 } from "path";
|
|
26967
27194
|
import { spawnSync as spawnSync11, execFileSync as execFileSync2 } from "child_process";
|
|
26968
27195
|
function deriveBranchName(beadId, specialistName) {
|
|
26969
27196
|
return `feature/${beadId}-${slugify(specialistName)}`;
|
|
@@ -26996,11 +27223,11 @@ function provisionWorktree(options) {
|
|
|
26996
27223
|
const branch = deriveBranchName(options.beadId, options.specialistName);
|
|
26997
27224
|
const existingPath = findExistingWorktree(branch, cwd);
|
|
26998
27225
|
if (existingPath) {
|
|
26999
|
-
return { branch, worktreePath:
|
|
27226
|
+
return { branch, worktreePath: resolve7(existingPath), reused: true };
|
|
27000
27227
|
}
|
|
27001
27228
|
const worktreeBase = options.worktreeBase ?? join14(commonRoot, ".worktrees", options.beadId);
|
|
27002
27229
|
const worktreeName = deriveWorktreeName(options.beadId, options.specialistName);
|
|
27003
|
-
const worktreePath =
|
|
27230
|
+
const worktreePath = resolve7(join14(worktreeBase, worktreeName));
|
|
27004
27231
|
createWorktreeViaBd(worktreePath, branch, commonRoot);
|
|
27005
27232
|
symlinkPiNpmCache(commonRoot, worktreePath);
|
|
27006
27233
|
return { branch, worktreePath, reused: false };
|
|
@@ -27008,7 +27235,7 @@ function provisionWorktree(options) {
|
|
|
27008
27235
|
function symlinkPiNpmCache(commonRoot, worktreePath) {
|
|
27009
27236
|
const source = join14(commonRoot, ".pi", "npm");
|
|
27010
27237
|
const target = join14(worktreePath, ".pi", "npm");
|
|
27011
|
-
if (!
|
|
27238
|
+
if (!existsSync14(source) || existsSync14(target))
|
|
27012
27239
|
return;
|
|
27013
27240
|
try {
|
|
27014
27241
|
mkdirSync6(join14(worktreePath, ".pi"), { recursive: true });
|
|
@@ -27074,7 +27301,7 @@ __export(exports_merge, {
|
|
|
27074
27301
|
checkEpicUnresolvedGuard: () => checkEpicUnresolvedGuard,
|
|
27075
27302
|
assertMainRepoCleanForMerge: () => assertMainRepoCleanForMerge
|
|
27076
27303
|
});
|
|
27077
|
-
import { existsSync as
|
|
27304
|
+
import { existsSync as existsSync15, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
27078
27305
|
import { spawnSync as spawnSync12 } from "child_process";
|
|
27079
27306
|
import { join as join15 } from "path";
|
|
27080
27307
|
function parseOptions(argv) {
|
|
@@ -27254,7 +27481,7 @@ Use 'sp epic merge ${membership.epicId}' to publish all chains together, or 'sp
|
|
|
27254
27481
|
}
|
|
27255
27482
|
function readAllJobStatuses() {
|
|
27256
27483
|
const jobsDir = resolveJobsDir();
|
|
27257
|
-
if (!
|
|
27484
|
+
if (!existsSync15(jobsDir))
|
|
27258
27485
|
return [];
|
|
27259
27486
|
const entries = readdirSync6(jobsDir, { withFileTypes: true });
|
|
27260
27487
|
const statuses = [];
|
|
@@ -27262,9 +27489,9 @@ function readAllJobStatuses() {
|
|
|
27262
27489
|
if (!entry.isDirectory())
|
|
27263
27490
|
continue;
|
|
27264
27491
|
const statusPath = join15(jobsDir, entry.name, "status.json");
|
|
27265
|
-
if (!
|
|
27492
|
+
if (!existsSync15(statusPath))
|
|
27266
27493
|
continue;
|
|
27267
|
-
const parsed = readJson(
|
|
27494
|
+
const parsed = readJson(readFileSync11(statusPath, "utf-8"));
|
|
27268
27495
|
if (!parsed || typeof parsed !== "object")
|
|
27269
27496
|
continue;
|
|
27270
27497
|
statuses.push(parsed);
|
|
@@ -27763,7 +27990,9 @@ function formatToolDetail(event) {
|
|
|
27763
27990
|
return `${toolName}: ${dim8("start")}`;
|
|
27764
27991
|
}
|
|
27765
27992
|
if (event.phase === "end" && event.is_error) {
|
|
27766
|
-
|
|
27993
|
+
const summary = event.result_summary?.split(`
|
|
27994
|
+
`)[0]?.trim().slice(0, 120);
|
|
27995
|
+
return summary ? `${toolName}: ${red2(summary)}` : `${toolName}: ${red2("error")}`;
|
|
27767
27996
|
}
|
|
27768
27997
|
return `${toolName}: ${dim8(event.phase)}`;
|
|
27769
27998
|
}
|
|
@@ -27781,6 +28010,8 @@ function formatEventLine(event, options) {
|
|
|
27781
28010
|
if (event.type === "meta") {
|
|
27782
28011
|
detailParts.push(`model=${event.model}`);
|
|
27783
28012
|
detailParts.push(`backend=${event.backend}`);
|
|
28013
|
+
if (event.source)
|
|
28014
|
+
detailParts.push(`source=${event.source}`);
|
|
27784
28015
|
} else if (event.type === "tool") {
|
|
27785
28016
|
detail = formatToolDetail(event);
|
|
27786
28017
|
} else if (event.type === "error") {
|
|
@@ -27920,7 +28151,7 @@ __export(exports_run, {
|
|
|
27920
28151
|
run: () => run13
|
|
27921
28152
|
});
|
|
27922
28153
|
import { join as join16 } from "path";
|
|
27923
|
-
import { readFileSync as
|
|
28154
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
27924
28155
|
import { randomBytes } from "crypto";
|
|
27925
28156
|
import { spawn as cpSpawn, execSync as execSync3 } from "child_process";
|
|
27926
28157
|
async function parseArgs6(argv) {
|
|
@@ -28035,13 +28266,13 @@ async function parseArgs6(argv) {
|
|
|
28035
28266
|
process.exit(1);
|
|
28036
28267
|
}
|
|
28037
28268
|
if (!prompt && !beadId && !process.stdin.isTTY) {
|
|
28038
|
-
prompt = await new Promise((
|
|
28269
|
+
prompt = await new Promise((resolve8) => {
|
|
28039
28270
|
let buf = "";
|
|
28040
28271
|
process.stdin.setEncoding("utf-8");
|
|
28041
28272
|
process.stdin.on("data", (chunk) => {
|
|
28042
28273
|
buf += chunk;
|
|
28043
28274
|
});
|
|
28044
|
-
process.stdin.on("end", () =>
|
|
28275
|
+
process.stdin.on("end", () => resolve8(buf.trim()));
|
|
28045
28276
|
});
|
|
28046
28277
|
}
|
|
28047
28278
|
if (!prompt && !beadId && !reuseJobId) {
|
|
@@ -28199,7 +28430,7 @@ function startEventTailer(jobId, jobsDir, mode, specialist, beadId) {
|
|
|
28199
28430
|
const drain = () => {
|
|
28200
28431
|
let content;
|
|
28201
28432
|
try {
|
|
28202
|
-
content =
|
|
28433
|
+
content = readFileSync12(eventsPath, "utf-8");
|
|
28203
28434
|
} catch {
|
|
28204
28435
|
return;
|
|
28205
28436
|
}
|
|
@@ -28300,7 +28531,7 @@ async function run13() {
|
|
|
28300
28531
|
const latestPath = join16(jobsDir2, "latest");
|
|
28301
28532
|
const oldLatest = (() => {
|
|
28302
28533
|
try {
|
|
28303
|
-
return
|
|
28534
|
+
return readFileSync12(latestPath, "utf-8").trim();
|
|
28304
28535
|
} catch {
|
|
28305
28536
|
return "";
|
|
28306
28537
|
}
|
|
@@ -28328,7 +28559,7 @@ async function run13() {
|
|
|
28328
28559
|
while (Date.now() < deadline) {
|
|
28329
28560
|
await new Promise((r) => setTimeout(r, 100));
|
|
28330
28561
|
try {
|
|
28331
|
-
const current =
|
|
28562
|
+
const current = readFileSync12(latestPath, "utf-8").trim();
|
|
28332
28563
|
if (current && current !== oldLatest) {
|
|
28333
28564
|
jobId2 = current;
|
|
28334
28565
|
break;
|
|
@@ -28583,7 +28814,7 @@ var init_node_resolve = __esm(() => {
|
|
|
28583
28814
|
});
|
|
28584
28815
|
|
|
28585
28816
|
// src/specialist/job-control.ts
|
|
28586
|
-
import { existsSync as
|
|
28817
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
|
|
28587
28818
|
import { join as join17 } from "path";
|
|
28588
28819
|
|
|
28589
28820
|
class JobControl {
|
|
@@ -28615,8 +28846,8 @@ class JobControl {
|
|
|
28615
28846
|
}
|
|
28616
28847
|
};
|
|
28617
28848
|
let resolveJobId;
|
|
28618
|
-
const jobIdPromise = new Promise((
|
|
28619
|
-
resolveJobId =
|
|
28849
|
+
const jobIdPromise = new Promise((resolve8) => {
|
|
28850
|
+
resolveJobId = resolve8;
|
|
28620
28851
|
});
|
|
28621
28852
|
this.supervisor = new Supervisor({
|
|
28622
28853
|
runner: this.runner,
|
|
@@ -28659,10 +28890,10 @@ class JobControl {
|
|
|
28659
28890
|
return sqliteResult;
|
|
28660
28891
|
} catch {}
|
|
28661
28892
|
const resultPath = this.resultPath(jobId);
|
|
28662
|
-
if (!
|
|
28893
|
+
if (!existsSync16(resultPath))
|
|
28663
28894
|
return null;
|
|
28664
28895
|
try {
|
|
28665
|
-
return
|
|
28896
|
+
return readFileSync13(resultPath, "utf-8");
|
|
28666
28897
|
} catch {
|
|
28667
28898
|
return null;
|
|
28668
28899
|
}
|
|
@@ -28681,7 +28912,7 @@ class JobControl {
|
|
|
28681
28912
|
if (deadline !== undefined && Date.now() >= deadline) {
|
|
28682
28913
|
throw new Error(`Timed out waiting for terminal status for job ${jobId}`);
|
|
28683
28914
|
}
|
|
28684
|
-
await new Promise((
|
|
28915
|
+
await new Promise((resolve8) => setTimeout(resolve8, backoffMs));
|
|
28685
28916
|
backoffMs = Math.min(backoffMs * 2, MAX_BACKOFF_MS);
|
|
28686
28917
|
}
|
|
28687
28918
|
}
|
|
@@ -28860,7 +29091,7 @@ function hashOutput(output2, salt) {
|
|
|
28860
29091
|
return createHash3("sha256").update(value).digest("hex");
|
|
28861
29092
|
}
|
|
28862
29093
|
function sleep2(ms) {
|
|
28863
|
-
return new Promise((
|
|
29094
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
28864
29095
|
}
|
|
28865
29096
|
function toContextHealth(contextPct) {
|
|
28866
29097
|
if (contextPct === null)
|
|
@@ -30773,10 +31004,10 @@ var exports_node = {};
|
|
|
30773
31004
|
__export(exports_node, {
|
|
30774
31005
|
handleNodeCommand: () => handleNodeCommand
|
|
30775
31006
|
});
|
|
30776
|
-
import { existsSync as
|
|
31007
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14, readdirSync as readdirSync7 } from "fs";
|
|
30777
31008
|
import { randomUUID } from "crypto";
|
|
30778
31009
|
import { spawnSync as spawnSync14 } from "child_process";
|
|
30779
|
-
import { basename as basename4, join as join18, resolve as
|
|
31010
|
+
import { basename as basename4, join as join18, resolve as resolve8 } from "path";
|
|
30780
31011
|
function parseNodeArgs(argv) {
|
|
30781
31012
|
const command = argv[0];
|
|
30782
31013
|
const supportedCommands = new Set(["run", "list", "promote", "members", "memory", "stop", "spawn-member", "create-bead", "complete", "wait-phase"]);
|
|
@@ -30960,8 +31191,8 @@ function toNodeName(filePath) {
|
|
|
30960
31191
|
function discoverNodeConfigs(cwd) {
|
|
30961
31192
|
const discoveredByName = new Map;
|
|
30962
31193
|
for (const directory of NODE_DISCOVERY_DIRS) {
|
|
30963
|
-
const absoluteDir =
|
|
30964
|
-
if (!
|
|
31194
|
+
const absoluteDir = resolve8(cwd, directory.path);
|
|
31195
|
+
if (!existsSync17(absoluteDir))
|
|
30965
31196
|
continue;
|
|
30966
31197
|
const files = readdirSync7(absoluteDir).filter((fileName) => fileName.endsWith(NODE_CONFIG_SUFFIX));
|
|
30967
31198
|
for (const fileName of files) {
|
|
@@ -30975,8 +31206,8 @@ function discoverNodeConfigs(cwd) {
|
|
|
30975
31206
|
return [...discoveredByName.values()].sort((left, right) => left.name.localeCompare(right.name));
|
|
30976
31207
|
}
|
|
30977
31208
|
function resolveNodeConfigPath(cwd, input2) {
|
|
30978
|
-
const explicitPath =
|
|
30979
|
-
if (
|
|
31209
|
+
const explicitPath = resolve8(cwd, input2);
|
|
31210
|
+
if (existsSync17(explicitPath)) {
|
|
30980
31211
|
return explicitPath;
|
|
30981
31212
|
}
|
|
30982
31213
|
const normalizedName = input2.endsWith(NODE_CONFIG_SUFFIX) ? input2.slice(0, -NODE_CONFIG_SUFFIX.length) : input2;
|
|
@@ -31071,7 +31302,7 @@ async function handleNodeRun(args) {
|
|
|
31071
31302
|
throw new Error("Observability SQLite DB is unavailable. Run: specialists db setup");
|
|
31072
31303
|
}
|
|
31073
31304
|
try {
|
|
31074
|
-
const rawConfig = args.inlineJson ? args.inlineJson :
|
|
31305
|
+
const rawConfig = args.inlineJson ? args.inlineJson : readFileSync14(resolveNodeConfigPath(process.cwd(), args.nodeConfigInput), "utf-8");
|
|
31075
31306
|
const config2 = parseNodeConfig(rawConfig);
|
|
31076
31307
|
const loader = new SpecialistLoader;
|
|
31077
31308
|
const runner = new SpecialistRunner({
|
|
@@ -31520,7 +31751,7 @@ async function handleNodeAction(args) {
|
|
|
31520
31751
|
if (deadline !== null && Date.now() >= deadline) {
|
|
31521
31752
|
throw new Error(`Timed out waiting for phase '${args.phaseId}' members: ${memberKeys.join(", ")}`);
|
|
31522
31753
|
}
|
|
31523
|
-
await new Promise((
|
|
31754
|
+
await new Promise((resolve9) => setTimeout(resolve9, 500));
|
|
31524
31755
|
}
|
|
31525
31756
|
} finally {
|
|
31526
31757
|
sqliteClient.close();
|
|
@@ -31607,7 +31838,7 @@ var init_node = __esm(() => {
|
|
|
31607
31838
|
});
|
|
31608
31839
|
|
|
31609
31840
|
// src/specialist/epic-reconciler.ts
|
|
31610
|
-
import { mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as
|
|
31841
|
+
import { mkdirSync as mkdirSync7, openSync as openSync2, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync8 } from "fs";
|
|
31611
31842
|
import { join as join19 } from "path";
|
|
31612
31843
|
function buildEpicLockPath(epicId) {
|
|
31613
31844
|
const location = resolveObservabilityDbLocation();
|
|
@@ -31624,7 +31855,7 @@ function withEpicAdvisoryLock(epicId, action) {
|
|
|
31624
31855
|
} catch {
|
|
31625
31856
|
let holder = "unknown";
|
|
31626
31857
|
try {
|
|
31627
|
-
holder =
|
|
31858
|
+
holder = readFileSync15(lockPath, "utf-8");
|
|
31628
31859
|
} catch {
|
|
31629
31860
|
holder = "unknown";
|
|
31630
31861
|
}
|
|
@@ -32600,7 +32831,7 @@ __export(exports_status, {
|
|
|
32600
32831
|
run: () => run14
|
|
32601
32832
|
});
|
|
32602
32833
|
import { spawnSync as spawnSync16 } from "child_process";
|
|
32603
|
-
import { existsSync as
|
|
32834
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
32604
32835
|
import { join as join20 } from "path";
|
|
32605
32836
|
function ok2(msg) {
|
|
32606
32837
|
console.log(` ${green8("\u2713")} ${msg}`);
|
|
@@ -32693,9 +32924,9 @@ function countJobEvents(sqliteClient, jobsDir, jobId) {
|
|
|
32693
32924
|
console.warn(`SQLite events read failed for job ${jobId}; falling back to events.jsonl`, error2);
|
|
32694
32925
|
}
|
|
32695
32926
|
const eventsFile = join20(jobsDir, jobId, "events.jsonl");
|
|
32696
|
-
if (!
|
|
32927
|
+
if (!existsSync18(eventsFile))
|
|
32697
32928
|
return 0;
|
|
32698
|
-
const raw =
|
|
32929
|
+
const raw = readFileSync16(eventsFile, "utf-8").trim();
|
|
32699
32930
|
if (!raw)
|
|
32700
32931
|
return 0;
|
|
32701
32932
|
return raw.split(`
|
|
@@ -32727,9 +32958,9 @@ function getLatestContextSnapshot(sqliteClient, jobsDir, jobId) {
|
|
|
32727
32958
|
console.warn(`SQLite events read failed for job ${jobId}; falling back to events.jsonl`, error2);
|
|
32728
32959
|
}
|
|
32729
32960
|
const eventsFile = join20(jobsDir, jobId, "events.jsonl");
|
|
32730
|
-
if (!
|
|
32961
|
+
if (!existsSync18(eventsFile))
|
|
32731
32962
|
return null;
|
|
32732
|
-
const lines =
|
|
32963
|
+
const lines = readFileSync16(eventsFile, "utf-8").split(`
|
|
32733
32964
|
`);
|
|
32734
32965
|
for (let index = lines.length - 1;index >= 0; index -= 1) {
|
|
32735
32966
|
const line = lines[index].trim();
|
|
@@ -32834,11 +33065,11 @@ async function run14() {
|
|
|
32834
33065
|
`).slice(1).map((line) => line.split(/\s+/)[0]).filter(Boolean)) : new Set;
|
|
32835
33066
|
const bdInstalled = isInstalled2("bd");
|
|
32836
33067
|
const bdVersion = bdInstalled ? cmd("bd", ["--version"]) : null;
|
|
32837
|
-
const beadsPresent =
|
|
33068
|
+
const beadsPresent = existsSync18(join20(process.cwd(), ".beads"));
|
|
32838
33069
|
const specialistsBin = cmd("which", ["specialists"]);
|
|
32839
33070
|
const jobsDir = resolveJobsDir();
|
|
32840
33071
|
let jobs = [];
|
|
32841
|
-
if (
|
|
33072
|
+
if (existsSync18(jobsDir)) {
|
|
32842
33073
|
supervisor = new Supervisor({
|
|
32843
33074
|
runner: null,
|
|
32844
33075
|
runOptions: null,
|
|
@@ -32995,7 +33226,7 @@ __export(exports_ps, {
|
|
|
32995
33226
|
run: () => run15
|
|
32996
33227
|
});
|
|
32997
33228
|
import { spawnSync as spawnSync17 } from "child_process";
|
|
32998
|
-
import { existsSync as
|
|
33229
|
+
import { existsSync as existsSync19, readdirSync as readdirSync8, readFileSync as readFileSync17 } from "fs";
|
|
32999
33230
|
import { join as join21 } from "path";
|
|
33000
33231
|
function parseArgs7(argv) {
|
|
33001
33232
|
let nodeId;
|
|
@@ -33026,25 +33257,25 @@ function isVisibleStatus(status, all) {
|
|
|
33026
33257
|
return ACTIVE_STATES.includes(status);
|
|
33027
33258
|
}
|
|
33028
33259
|
function readStatusesFromFiles(jobsDir) {
|
|
33029
|
-
if (!
|
|
33260
|
+
if (!existsSync19(jobsDir))
|
|
33030
33261
|
return [];
|
|
33031
33262
|
const statuses = [];
|
|
33032
33263
|
for (const entry of readdirSync8(jobsDir)) {
|
|
33033
33264
|
const statusPath = join21(jobsDir, entry, "status.json");
|
|
33034
|
-
if (!
|
|
33265
|
+
if (!existsSync19(statusPath))
|
|
33035
33266
|
continue;
|
|
33036
33267
|
try {
|
|
33037
|
-
statuses.push(JSON.parse(
|
|
33268
|
+
statuses.push(JSON.parse(readFileSync17(statusPath, "utf-8")));
|
|
33038
33269
|
} catch {}
|
|
33039
33270
|
}
|
|
33040
33271
|
return statuses.sort((a, b) => b.started_at_ms - a.started_at_ms);
|
|
33041
33272
|
}
|
|
33042
33273
|
function readLastToolEventFromFile(jobsDir, jobId) {
|
|
33043
33274
|
const eventsPath = join21(jobsDir, jobId, "events.jsonl");
|
|
33044
|
-
if (!
|
|
33275
|
+
if (!existsSync19(eventsPath))
|
|
33045
33276
|
return;
|
|
33046
33277
|
try {
|
|
33047
|
-
const lines =
|
|
33278
|
+
const lines = readFileSync17(eventsPath, "utf-8").split(`
|
|
33048
33279
|
`);
|
|
33049
33280
|
for (let index = lines.length - 1;index >= 0; index -= 1) {
|
|
33050
33281
|
const line = lines[index]?.trim();
|
|
@@ -33802,7 +34033,7 @@ var exports_result = {};
|
|
|
33802
34033
|
__export(exports_result, {
|
|
33803
34034
|
run: () => run16
|
|
33804
34035
|
});
|
|
33805
|
-
import { existsSync as
|
|
34036
|
+
import { existsSync as existsSync20, readFileSync as readFileSync18 } from "fs";
|
|
33806
34037
|
import { join as join22 } from "path";
|
|
33807
34038
|
function parseArgs8(argv) {
|
|
33808
34039
|
let jobId;
|
|
@@ -33894,9 +34125,9 @@ function readTimelineEventsForResult(sqliteClient, jobsDir, jobId) {
|
|
|
33894
34125
|
} catch {}
|
|
33895
34126
|
}
|
|
33896
34127
|
const eventsPath = join22(jobsDir, jobId, "events.jsonl");
|
|
33897
|
-
if (!
|
|
34128
|
+
if (!existsSync20(eventsPath))
|
|
33898
34129
|
return [];
|
|
33899
|
-
return
|
|
34130
|
+
return readFileSync18(eventsPath, "utf-8").split(`
|
|
33900
34131
|
`).map((line) => line.trim()).filter(Boolean).map((line) => parseTimelineEvent(line)).filter((event) => event !== null);
|
|
33901
34132
|
}
|
|
33902
34133
|
function deriveStartupSnapshot(status, events) {
|
|
@@ -34037,10 +34268,10 @@ async function run16() {
|
|
|
34037
34268
|
} catch (error2) {
|
|
34038
34269
|
console.warn(`SQLite result read failed for job ${jobId}; falling back to result.txt`, error2);
|
|
34039
34270
|
}
|
|
34040
|
-
if (!
|
|
34271
|
+
if (!existsSync20(resultPath)) {
|
|
34041
34272
|
return null;
|
|
34042
34273
|
}
|
|
34043
|
-
return
|
|
34274
|
+
return readFileSync18(resultPath, "utf-8");
|
|
34044
34275
|
};
|
|
34045
34276
|
if (args.wait) {
|
|
34046
34277
|
const startMs = Date.now();
|
|
@@ -34214,7 +34445,7 @@ var init_result = __esm(() => {
|
|
|
34214
34445
|
});
|
|
34215
34446
|
|
|
34216
34447
|
// src/specialist/timeline-query.ts
|
|
34217
|
-
import { existsSync as
|
|
34448
|
+
import { existsSync as existsSync21, readdirSync as readdirSync9, readFileSync as readFileSync19 } from "fs";
|
|
34218
34449
|
import { basename as basename5, join as join23 } from "path";
|
|
34219
34450
|
function readJobEvents(jobDir) {
|
|
34220
34451
|
const jobId = basename5(jobDir);
|
|
@@ -34226,9 +34457,9 @@ function readJobEvents(jobDir) {
|
|
|
34226
34457
|
}
|
|
34227
34458
|
} catch {}
|
|
34228
34459
|
const eventsPath = join23(jobDir, "events.jsonl");
|
|
34229
|
-
if (!
|
|
34460
|
+
if (!existsSync21(eventsPath))
|
|
34230
34461
|
return [];
|
|
34231
|
-
const content =
|
|
34462
|
+
const content = readFileSync19(eventsPath, "utf-8");
|
|
34232
34463
|
const lines = content.split(`
|
|
34233
34464
|
`).filter(Boolean);
|
|
34234
34465
|
const events = [];
|
|
@@ -34244,7 +34475,7 @@ function readJobEventsById(jobsDir, jobId) {
|
|
|
34244
34475
|
return readJobEvents(join23(jobsDir, jobId));
|
|
34245
34476
|
}
|
|
34246
34477
|
function readAllJobEvents(jobsDir) {
|
|
34247
|
-
if (!
|
|
34478
|
+
if (!existsSync21(jobsDir))
|
|
34248
34479
|
return [];
|
|
34249
34480
|
const batches = [];
|
|
34250
34481
|
const entries = readdirSync9(jobsDir);
|
|
@@ -34261,9 +34492,9 @@ function readAllJobEvents(jobsDir) {
|
|
|
34261
34492
|
const statusPath = join23(jobDir, "status.json");
|
|
34262
34493
|
let specialist = "unknown";
|
|
34263
34494
|
let beadId;
|
|
34264
|
-
if (
|
|
34495
|
+
if (existsSync21(statusPath)) {
|
|
34265
34496
|
try {
|
|
34266
|
-
const status = JSON.parse(
|
|
34497
|
+
const status = JSON.parse(readFileSync19(statusPath, "utf-8"));
|
|
34267
34498
|
specialist = status.specialist ?? "unknown";
|
|
34268
34499
|
beadId = status.bead_id;
|
|
34269
34500
|
} catch {}
|
|
@@ -34360,9 +34591,9 @@ __export(exports_feed, {
|
|
|
34360
34591
|
});
|
|
34361
34592
|
import {
|
|
34362
34593
|
closeSync as closeSync2,
|
|
34363
|
-
existsSync as
|
|
34594
|
+
existsSync as existsSync22,
|
|
34364
34595
|
openSync as openSync3,
|
|
34365
|
-
readFileSync as
|
|
34596
|
+
readFileSync as readFileSync20,
|
|
34366
34597
|
readdirSync as readdirSync10,
|
|
34367
34598
|
statSync as statSync3
|
|
34368
34599
|
} from "fs";
|
|
@@ -34474,6 +34705,10 @@ function formatStartupContextLine(event) {
|
|
|
34474
34705
|
parts.push(`skills=${snapshot.skills.count}`);
|
|
34475
34706
|
return parts.length > 0 ? dim8(` \u21B3 startup ${parts.join(" ")}`) : null;
|
|
34476
34707
|
}
|
|
34708
|
+
if (event.type === "meta" && event.source === "mandatory_rules_injection" && event.data) {
|
|
34709
|
+
const data = event.data;
|
|
34710
|
+
return dim8(` \u21B3 mandatory_rules sets=${(data.sets_loaded ?? []).join(",") || "none"} rules=${data.rules_count ?? 0} tokens=~${data.token_estimate ?? 0}`);
|
|
34711
|
+
}
|
|
34477
34712
|
if (event.type === "meta" && event.memory_injection) {
|
|
34478
34713
|
const mem = event.memory_injection;
|
|
34479
34714
|
return dim8(` \u21B3 memory static=${mem.static_tokens} dynamic=${mem.memory_tokens} gitnexus=${mem.gitnexus_tokens} total=${mem.total_tokens}`);
|
|
@@ -34507,7 +34742,7 @@ function readFileFresh(filePath) {
|
|
|
34507
34742
|
let fd = null;
|
|
34508
34743
|
try {
|
|
34509
34744
|
fd = openSync3(filePath, "r");
|
|
34510
|
-
return
|
|
34745
|
+
return readFileSync20(fd, "utf-8");
|
|
34511
34746
|
} catch {
|
|
34512
34747
|
return null;
|
|
34513
34748
|
} finally {
|
|
@@ -34733,7 +34968,7 @@ function filterMergedEventsByNode(sqliteClient, jobsDir, merged, nodeId) {
|
|
|
34733
34968
|
});
|
|
34734
34969
|
}
|
|
34735
34970
|
function listMatchingJobIds(sqliteClient, jobsDir, options) {
|
|
34736
|
-
if (!
|
|
34971
|
+
if (!existsSync22(jobsDir))
|
|
34737
34972
|
return [];
|
|
34738
34973
|
const jobIds = [];
|
|
34739
34974
|
for (const entry of readdirSync10(jobsDir)) {
|
|
@@ -34847,7 +35082,7 @@ async function followMerged(sqliteClient, jobsDir, options) {
|
|
|
34847
35082
|
}
|
|
34848
35083
|
const lastPrintedEventKey = new Map;
|
|
34849
35084
|
const seenMetaKey = new Map;
|
|
34850
|
-
await new Promise((
|
|
35085
|
+
await new Promise((resolve9) => {
|
|
34851
35086
|
const interval = setInterval(() => {
|
|
34852
35087
|
const batches = filteredBatches();
|
|
34853
35088
|
for (const jobId of listMatchingJobIds(sqliteClient, jobsDir, options)) {
|
|
@@ -34924,7 +35159,7 @@ async function followMerged(sqliteClient, jobsDir, options) {
|
|
|
34924
35159
|
}
|
|
34925
35160
|
if (!options.forever && trackedJobs.size > 0 && completedJobs.size === trackedJobs.size) {
|
|
34926
35161
|
clearInterval(interval);
|
|
34927
|
-
|
|
35162
|
+
resolve9();
|
|
34928
35163
|
}
|
|
34929
35164
|
}, 500);
|
|
34930
35165
|
});
|
|
@@ -34934,7 +35169,7 @@ async function run17() {
|
|
|
34934
35169
|
const sqliteClient = createObservabilitySqliteClient();
|
|
34935
35170
|
try {
|
|
34936
35171
|
const jobsDir = join24(process.cwd(), ".specialists", "jobs");
|
|
34937
|
-
if (!
|
|
35172
|
+
if (!existsSync22(jobsDir)) {
|
|
34938
35173
|
console.log(dim8("No jobs directory found."));
|
|
34939
35174
|
return;
|
|
34940
35175
|
}
|
|
@@ -34973,7 +35208,7 @@ var exports_poll = {};
|
|
|
34973
35208
|
__export(exports_poll, {
|
|
34974
35209
|
run: () => run18
|
|
34975
35210
|
});
|
|
34976
|
-
import { existsSync as
|
|
35211
|
+
import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
|
|
34977
35212
|
import { join as join25 } from "path";
|
|
34978
35213
|
function parseArgs10(argv) {
|
|
34979
35214
|
let jobId;
|
|
@@ -35014,16 +35249,16 @@ function readJobState(jobsDir, jobId, cursor, outputCursor) {
|
|
|
35014
35249
|
const jobDir = join25(jobsDir, jobId);
|
|
35015
35250
|
const statusPath = join25(jobDir, "status.json");
|
|
35016
35251
|
let status = null;
|
|
35017
|
-
if (
|
|
35252
|
+
if (existsSync23(statusPath)) {
|
|
35018
35253
|
try {
|
|
35019
|
-
status = JSON.parse(
|
|
35254
|
+
status = JSON.parse(readFileSync21(statusPath, "utf-8"));
|
|
35020
35255
|
} catch {}
|
|
35021
35256
|
}
|
|
35022
35257
|
const resultPath = join25(jobDir, "result.txt");
|
|
35023
35258
|
let fullOutput = "";
|
|
35024
|
-
if (
|
|
35259
|
+
if (existsSync23(resultPath)) {
|
|
35025
35260
|
try {
|
|
35026
|
-
fullOutput =
|
|
35261
|
+
fullOutput = readFileSync21(resultPath, "utf-8");
|
|
35027
35262
|
} catch {}
|
|
35028
35263
|
}
|
|
35029
35264
|
const events = readJobEventsById(jobsDir, jobId);
|
|
@@ -35057,7 +35292,7 @@ async function run18() {
|
|
|
35057
35292
|
const { jobId, cursor, outputCursor } = parseArgs10(process.argv.slice(3));
|
|
35058
35293
|
const jobsDir = join25(process.cwd(), ".specialists", "jobs");
|
|
35059
35294
|
const jobDir = join25(jobsDir, jobId);
|
|
35060
|
-
if (!
|
|
35295
|
+
if (!existsSync23(jobDir)) {
|
|
35061
35296
|
const result2 = {
|
|
35062
35297
|
job_id: jobId,
|
|
35063
35298
|
status: "error",
|
|
@@ -35205,15 +35440,15 @@ async function run21() {
|
|
|
35205
35440
|
}
|
|
35206
35441
|
|
|
35207
35442
|
// src/specialist/worktree-gc.ts
|
|
35208
|
-
import { existsSync as
|
|
35443
|
+
import { existsSync as existsSync24, readdirSync as readdirSync11, readFileSync as readFileSync22 } from "fs";
|
|
35209
35444
|
import { join as join26 } from "path";
|
|
35210
35445
|
import { spawnSync as spawnSync18 } from "child_process";
|
|
35211
35446
|
function readJobStatus2(jobDir) {
|
|
35212
35447
|
const statusPath = join26(jobDir, "status.json");
|
|
35213
|
-
if (!
|
|
35448
|
+
if (!existsSync24(statusPath))
|
|
35214
35449
|
return null;
|
|
35215
35450
|
try {
|
|
35216
|
-
return JSON.parse(
|
|
35451
|
+
return JSON.parse(readFileSync22(statusPath, "utf-8"));
|
|
35217
35452
|
} catch {
|
|
35218
35453
|
return null;
|
|
35219
35454
|
}
|
|
@@ -35225,7 +35460,7 @@ function isActive(status) {
|
|
|
35225
35460
|
return ACTIVE_STATUSES.has(status);
|
|
35226
35461
|
}
|
|
35227
35462
|
function collectWorktreeGcCandidates(jobsDir) {
|
|
35228
|
-
if (!
|
|
35463
|
+
if (!existsSync24(jobsDir))
|
|
35229
35464
|
return [];
|
|
35230
35465
|
const candidates = [];
|
|
35231
35466
|
for (const entry of readdirSync11(jobsDir, { withFileTypes: true })) {
|
|
@@ -35241,7 +35476,7 @@ function collectWorktreeGcCandidates(jobsDir) {
|
|
|
35241
35476
|
const { worktree_path: worktreePath, branch } = status;
|
|
35242
35477
|
if (!worktreePath)
|
|
35243
35478
|
continue;
|
|
35244
|
-
if (!
|
|
35479
|
+
if (!existsSync24(worktreePath))
|
|
35245
35480
|
continue;
|
|
35246
35481
|
candidates.push({
|
|
35247
35482
|
jobId: status.id,
|
|
@@ -35287,9 +35522,9 @@ __export(exports_clean, {
|
|
|
35287
35522
|
run: () => run22
|
|
35288
35523
|
});
|
|
35289
35524
|
import {
|
|
35290
|
-
existsSync as
|
|
35525
|
+
existsSync as existsSync25,
|
|
35291
35526
|
readdirSync as readdirSync12,
|
|
35292
|
-
readFileSync as
|
|
35527
|
+
readFileSync as readFileSync23,
|
|
35293
35528
|
rmSync as rmSync3,
|
|
35294
35529
|
statSync as statSync4
|
|
35295
35530
|
} from "fs";
|
|
@@ -35381,11 +35616,11 @@ function readCompletedJobDirectory(baseDirectory, entry) {
|
|
|
35381
35616
|
if (containsProtectedSqliteArtifact(directoryPath))
|
|
35382
35617
|
return null;
|
|
35383
35618
|
const statusFilePath = join27(directoryPath, "status.json");
|
|
35384
|
-
if (!
|
|
35619
|
+
if (!existsSync25(statusFilePath))
|
|
35385
35620
|
return null;
|
|
35386
35621
|
let statusData;
|
|
35387
35622
|
try {
|
|
35388
|
-
statusData = JSON.parse(
|
|
35623
|
+
statusData = JSON.parse(readFileSync23(statusFilePath, "utf-8"));
|
|
35389
35624
|
} catch {
|
|
35390
35625
|
return null;
|
|
35391
35626
|
}
|
|
@@ -35482,7 +35717,7 @@ async function run22() {
|
|
|
35482
35717
|
printUsageAndExit2(message);
|
|
35483
35718
|
}
|
|
35484
35719
|
const jobsDirectoryPath = resolveJobsDir();
|
|
35485
|
-
if (!
|
|
35720
|
+
if (!existsSync25(jobsDirectoryPath)) {
|
|
35486
35721
|
console.log("No jobs directory found.");
|
|
35487
35722
|
return;
|
|
35488
35723
|
}
|
|
@@ -35667,7 +35902,7 @@ async function waitForProcessExit(pid, timeoutMs) {
|
|
|
35667
35902
|
while (Date.now() < deadline) {
|
|
35668
35903
|
if (!isProcessAlive(pid))
|
|
35669
35904
|
return true;
|
|
35670
|
-
await new Promise((
|
|
35905
|
+
await new Promise((resolve9) => setTimeout(resolve9, 100));
|
|
35671
35906
|
}
|
|
35672
35907
|
return !isProcessAlive(pid);
|
|
35673
35908
|
}
|
|
@@ -35777,7 +36012,7 @@ __export(exports_attach, {
|
|
|
35777
36012
|
run: () => run25
|
|
35778
36013
|
});
|
|
35779
36014
|
import { execFileSync as execFileSync3, spawnSync as spawnSync20 } from "child_process";
|
|
35780
|
-
import { readFileSync as
|
|
36015
|
+
import { readFileSync as readFileSync24 } from "fs";
|
|
35781
36016
|
import { join as join28 } from "path";
|
|
35782
36017
|
function exitWithError(message) {
|
|
35783
36018
|
console.error(message);
|
|
@@ -35785,7 +36020,7 @@ function exitWithError(message) {
|
|
|
35785
36020
|
}
|
|
35786
36021
|
function readStatus(statusPath, jobId) {
|
|
35787
36022
|
try {
|
|
35788
|
-
return JSON.parse(
|
|
36023
|
+
return JSON.parse(readFileSync24(statusPath, "utf-8"));
|
|
35789
36024
|
} catch (error2) {
|
|
35790
36025
|
if (error2 && typeof error2 === "object" && "code" in error2 && error2.code === "ENOENT") {
|
|
35791
36026
|
exitWithError(`Job \`${jobId}\` not found. Run \`specialists status\` to see active jobs.`);
|
|
@@ -36061,8 +36296,8 @@ __export(exports_doctor, {
|
|
|
36061
36296
|
});
|
|
36062
36297
|
import { createHash as createHash4 } from "crypto";
|
|
36063
36298
|
import { spawnSync as spawnSync21 } from "child_process";
|
|
36064
|
-
import { existsSync as
|
|
36065
|
-
import { dirname as dirname7, join as join29, relative as relative2, resolve as
|
|
36299
|
+
import { existsSync as existsSync26, lstatSync as lstatSync2, mkdirSync as mkdirSync8, readdirSync as readdirSync13, readFileSync as readFileSync25, readlinkSync as readlinkSync2, writeFileSync as writeFileSync11 } from "fs";
|
|
36300
|
+
import { dirname as dirname7, join as join29, relative as relative2, resolve as resolve9 } from "path";
|
|
36066
36301
|
function ok3(msg) {
|
|
36067
36302
|
console.log(` ${green14("\u2713")} ${msg}`);
|
|
36068
36303
|
}
|
|
@@ -36091,10 +36326,10 @@ function isInstalled3(bin) {
|
|
|
36091
36326
|
return spawnSync21("which", [bin], { encoding: "utf8", timeout: 2000 }).status === 0;
|
|
36092
36327
|
}
|
|
36093
36328
|
function loadJson2(path) {
|
|
36094
|
-
if (!
|
|
36329
|
+
if (!existsSync26(path))
|
|
36095
36330
|
return null;
|
|
36096
36331
|
try {
|
|
36097
|
-
return JSON.parse(
|
|
36332
|
+
return JSON.parse(readFileSync25(path, "utf8"));
|
|
36098
36333
|
} catch {
|
|
36099
36334
|
return null;
|
|
36100
36335
|
}
|
|
@@ -36137,7 +36372,7 @@ function checkBd() {
|
|
|
36137
36372
|
return false;
|
|
36138
36373
|
}
|
|
36139
36374
|
ok3(`bd installed ${dim13(sp("bd", ["--version"]).stdout || "")}`);
|
|
36140
|
-
if (
|
|
36375
|
+
if (existsSync26(join29(CWD, ".beads")))
|
|
36141
36376
|
ok3(".beads/ present in project");
|
|
36142
36377
|
else
|
|
36143
36378
|
warn3(".beads/ not found in project");
|
|
@@ -36158,7 +36393,7 @@ function checkHooks() {
|
|
|
36158
36393
|
let allPresent = true;
|
|
36159
36394
|
for (const name of HOOK_NAMES) {
|
|
36160
36395
|
const canonicalPath = join29(HOOKS_DIR, name);
|
|
36161
|
-
if (!
|
|
36396
|
+
if (!existsSync26(canonicalPath)) {
|
|
36162
36397
|
fail4(`${relative2(CWD, canonicalPath)} ${red7("missing")}`);
|
|
36163
36398
|
fix("specialists init");
|
|
36164
36399
|
allPresent = false;
|
|
@@ -36220,7 +36455,7 @@ function checkMCP() {
|
|
|
36220
36455
|
}
|
|
36221
36456
|
function hashFile(path) {
|
|
36222
36457
|
const hash = createHash4("sha256");
|
|
36223
|
-
hash.update(
|
|
36458
|
+
hash.update(readFileSync25(path));
|
|
36224
36459
|
return hash.digest("hex");
|
|
36225
36460
|
}
|
|
36226
36461
|
function collectFileHashes(rootDir) {
|
|
@@ -36238,12 +36473,12 @@ function collectFileHashes(rootDir) {
|
|
|
36238
36473
|
hashes.set(relPath, hashFile(fullPath));
|
|
36239
36474
|
}
|
|
36240
36475
|
};
|
|
36241
|
-
if (
|
|
36476
|
+
if (existsSync26(rootDir))
|
|
36242
36477
|
visit2(rootDir);
|
|
36243
36478
|
return hashes;
|
|
36244
36479
|
}
|
|
36245
36480
|
function isSymlinkTo(linkPath, expectedTargetPath) {
|
|
36246
|
-
if (!
|
|
36481
|
+
if (!existsSync26(linkPath))
|
|
36247
36482
|
return { ok: false, reason: "missing" };
|
|
36248
36483
|
let stats;
|
|
36249
36484
|
try {
|
|
@@ -36255,8 +36490,8 @@ function isSymlinkTo(linkPath, expectedTargetPath) {
|
|
|
36255
36490
|
return { ok: false, reason: "not-symlink" };
|
|
36256
36491
|
try {
|
|
36257
36492
|
const rawTarget = readlinkSync2(linkPath);
|
|
36258
|
-
const resolvedTarget =
|
|
36259
|
-
const resolvedExpected =
|
|
36493
|
+
const resolvedTarget = resolve9(dirname7(linkPath), rawTarget);
|
|
36494
|
+
const resolvedExpected = resolve9(expectedTargetPath);
|
|
36260
36495
|
if (resolvedTarget !== resolvedExpected) {
|
|
36261
36496
|
return { ok: false, reason: "wrong-target", target: rawTarget };
|
|
36262
36497
|
}
|
|
@@ -36267,12 +36502,12 @@ function isSymlinkTo(linkPath, expectedTargetPath) {
|
|
|
36267
36502
|
}
|
|
36268
36503
|
function checkSkillDrift() {
|
|
36269
36504
|
section3("Skill drift (.xtrm skill sync)");
|
|
36270
|
-
if (!
|
|
36505
|
+
if (!existsSync26(CONFIG_SKILLS_DIR)) {
|
|
36271
36506
|
fail4("config/skills/ missing");
|
|
36272
36507
|
fix("restore config/skills/ from git");
|
|
36273
36508
|
return false;
|
|
36274
36509
|
}
|
|
36275
|
-
if (!
|
|
36510
|
+
if (!existsSync26(XTRM_DEFAULT_SKILLS_DIR)) {
|
|
36276
36511
|
fail4(".xtrm/skills/default/ missing");
|
|
36277
36512
|
fix("specialists init --sync-skills");
|
|
36278
36513
|
return false;
|
|
@@ -36315,7 +36550,7 @@ function checkSkillDrift() {
|
|
|
36315
36550
|
let linksOk = true;
|
|
36316
36551
|
for (const scope of ["claude", "pi"]) {
|
|
36317
36552
|
const activeRoot = join29(XTRM_ACTIVE_SKILLS_DIR, scope);
|
|
36318
|
-
if (!
|
|
36553
|
+
if (!existsSync26(activeRoot)) {
|
|
36319
36554
|
fail4(`${relative2(CWD, activeRoot)}/ missing`);
|
|
36320
36555
|
fix("specialists init --sync-skills");
|
|
36321
36556
|
linksOk = false;
|
|
@@ -36374,14 +36609,14 @@ function checkRuntimeDirs() {
|
|
|
36374
36609
|
const jobsDir = join29(rootDir, "jobs");
|
|
36375
36610
|
const readyDir = join29(rootDir, "ready");
|
|
36376
36611
|
let allOk = true;
|
|
36377
|
-
if (!
|
|
36612
|
+
if (!existsSync26(rootDir)) {
|
|
36378
36613
|
warn3(".specialists/ not found in current project");
|
|
36379
36614
|
fix("specialists init");
|
|
36380
36615
|
allOk = false;
|
|
36381
36616
|
} else {
|
|
36382
36617
|
ok3(".specialists/ present");
|
|
36383
36618
|
for (const [subDir, label] of [[jobsDir, "jobs"], [readyDir, "ready"]]) {
|
|
36384
|
-
if (!
|
|
36619
|
+
if (!existsSync26(subDir)) {
|
|
36385
36620
|
warn3(`.specialists/${label}/ missing \u2014 auto-creating`);
|
|
36386
36621
|
mkdirSync8(subDir, { recursive: true });
|
|
36387
36622
|
ok3(`.specialists/${label}/ created`);
|
|
@@ -36414,7 +36649,7 @@ function compareVersions(left, right) {
|
|
|
36414
36649
|
}
|
|
36415
36650
|
function setStatusError(statusPath) {
|
|
36416
36651
|
try {
|
|
36417
|
-
const raw =
|
|
36652
|
+
const raw = readFileSync25(statusPath, "utf8");
|
|
36418
36653
|
const status = JSON.parse(raw);
|
|
36419
36654
|
status.status = "error";
|
|
36420
36655
|
writeFileSync11(statusPath, `${JSON.stringify(status, null, 2)}
|
|
@@ -36437,10 +36672,10 @@ function cleanupProcesses(jobsDir, dryRun) {
|
|
|
36437
36672
|
};
|
|
36438
36673
|
for (const jobId of entries) {
|
|
36439
36674
|
const statusPath = join29(jobsDir, jobId, "status.json");
|
|
36440
|
-
if (!
|
|
36675
|
+
if (!existsSync26(statusPath))
|
|
36441
36676
|
continue;
|
|
36442
36677
|
try {
|
|
36443
|
-
const status = JSON.parse(
|
|
36678
|
+
const status = JSON.parse(readFileSync25(statusPath, "utf8"));
|
|
36444
36679
|
result.total += 1;
|
|
36445
36680
|
if (status.status !== "running" && status.status !== "starting")
|
|
36446
36681
|
continue;
|
|
@@ -36517,7 +36752,7 @@ ${bold13("specialists doctor orphans")}
|
|
|
36517
36752
|
function checkZombieJobs() {
|
|
36518
36753
|
section3("Background jobs");
|
|
36519
36754
|
const jobsDir = join29(CWD, ".specialists", "jobs");
|
|
36520
|
-
if (!
|
|
36755
|
+
if (!existsSync26(jobsDir)) {
|
|
36521
36756
|
hint("No .specialists/jobs/ \u2014 skipping");
|
|
36522
36757
|
return true;
|
|
36523
36758
|
}
|