tldr-experts 0.9.2 → 0.10.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/CHANGELOG.md +105 -0
- package/README.md +1 -0
- package/dist/hooks/answer-capture.js +6 -8
- package/dist/hooks/budget-gate.js +7 -10
- package/dist/hooks/{chunk-vyvjbbmb.js → chunk-14496ta8.js} +1 -1
- package/dist/hooks/{chunk-x3zy7zrn.js → chunk-3rwbyema.js} +1 -1
- package/dist/hooks/{chunk-cf4az0j5.js → chunk-7ye9jde9.js} +1 -1
- package/dist/hooks/{chunk-4w03q2kj.js → chunk-br08fp2d.js} +5 -5
- package/dist/hooks/{chunk-hg43zexg.js → chunk-cmx01x0r.js} +11 -15
- package/dist/hooks/{chunk-fv66tdsm.js → chunk-dtpjhddq.js} +3 -7
- package/dist/hooks/{chunk-2y3kwfrx.js → chunk-dzm2bzqp.js} +1 -1
- package/dist/hooks/{chunk-f6484ykg.js → chunk-ecxt4p6n.js} +6 -8
- package/dist/hooks/{chunk-y3cxqvnx.js → chunk-f6d6cp4h.js} +403 -10
- package/dist/hooks/{chunk-8awygmqw.js → chunk-gvs5fvve.js} +1 -1
- package/dist/hooks/{chunk-rk1nm39z.js → chunk-w3jssmwe.js} +1 -1
- package/dist/hooks/claim-sources.js +5 -5
- package/dist/hooks/dod-gate.js +8 -99
- package/dist/hooks/no-reask.js +8 -9
- package/dist/hooks/session-start.js +10 -14
- package/dist/hooks/statusline.js +6 -9
- package/dist/tldrx.js +1290 -795
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/hooks/chunk-39zh2e44.js +0 -97
- package/dist/hooks/chunk-5w5eg00k.js +0 -136
- package/dist/hooks/chunk-rrkdfk7s.js +0 -30
|
@@ -7051,9 +7051,9 @@ var bunRuntime = {
|
|
|
7051
7051
|
clearTimeout(timer);
|
|
7052
7052
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
7053
7053
|
const [stdout, stderr] = await withinGrace(collected);
|
|
7054
|
-
return { exitCode, stdout, stderr, timedOut };
|
|
7054
|
+
return { exitCode, stdout, stderr, timedOut, spawnFailed: false };
|
|
7055
7055
|
} catch (error) {
|
|
7056
|
-
return { exitCode: 127, stdout: "", stderr: messageOf(error), timedOut };
|
|
7056
|
+
return { exitCode: 127, stdout: "", stderr: messageOf(error), timedOut, spawnFailed: true };
|
|
7057
7057
|
}
|
|
7058
7058
|
},
|
|
7059
7059
|
async readText(path) {
|
|
@@ -7186,6 +7186,7 @@ var nodeRuntime = {
|
|
|
7186
7186
|
spawn(cmd, args, opts = {}) {
|
|
7187
7187
|
return new Promise((resolve) => {
|
|
7188
7188
|
let timedOut = false;
|
|
7189
|
+
let spawnFailed = false;
|
|
7189
7190
|
let settled = false;
|
|
7190
7191
|
let stdout = "";
|
|
7191
7192
|
let stderr = "";
|
|
@@ -7224,7 +7225,7 @@ var nodeRuntime = {
|
|
|
7224
7225
|
clearTimeout(timer);
|
|
7225
7226
|
if (graceTimer !== null)
|
|
7226
7227
|
clearTimeout(graceTimer);
|
|
7227
|
-
resolve({ exitCode, stdout, stderr, timedOut });
|
|
7228
|
+
resolve({ exitCode, stdout, stderr, timedOut, spawnFailed });
|
|
7228
7229
|
};
|
|
7229
7230
|
child.stdout?.on("data", (chunk) => {
|
|
7230
7231
|
const text = chunk.toString("utf8");
|
|
@@ -7240,6 +7241,7 @@ var nodeRuntime = {
|
|
|
7240
7241
|
child.stderr?.on("end", () => errSplitter?.end());
|
|
7241
7242
|
child.on("error", (error) => {
|
|
7242
7243
|
stderr += error.message;
|
|
7244
|
+
spawnFailed = true;
|
|
7243
7245
|
finish(127);
|
|
7244
7246
|
});
|
|
7245
7247
|
child.on("exit", (code, signal) => {
|
|
@@ -7412,9 +7414,375 @@ function isEpicWorktreeOf(name, runId) {
|
|
|
7412
7414
|
return name.startsWith(`${EPIC_WORKTREE_PREFIX}${runId}-`);
|
|
7413
7415
|
}
|
|
7414
7416
|
|
|
7417
|
+
// src/core/schemas/validation.ts
|
|
7418
|
+
function result(issues, deprecations = []) {
|
|
7419
|
+
return { ok: issues.length === 0, issues, deprecations };
|
|
7420
|
+
}
|
|
7421
|
+
var VERSION_KEY = "version";
|
|
7422
|
+
var LEGACY_VERSION_KEY = "schema_version";
|
|
7423
|
+
var SCHEMA_VERSION = 1;
|
|
7424
|
+
var LEGACY_VERSION_NOTE = `${LEGACY_VERSION_KEY} is deprecated — say ${VERSION_KEY}: ${String(SCHEMA_VERSION)}`;
|
|
7425
|
+
function requireVersion(doc, issues, deprecations) {
|
|
7426
|
+
const declared = doc[VERSION_KEY];
|
|
7427
|
+
if (declared !== undefined) {
|
|
7428
|
+
if (declared !== SCHEMA_VERSION) {
|
|
7429
|
+
issues.push({
|
|
7430
|
+
path: VERSION_KEY,
|
|
7431
|
+
message: `unknown schema version ${describeValue(declared)} (expected ${String(SCHEMA_VERSION)})`
|
|
7432
|
+
});
|
|
7433
|
+
}
|
|
7434
|
+
return;
|
|
7435
|
+
}
|
|
7436
|
+
if (!(LEGACY_VERSION_KEY in doc) || doc[LEGACY_VERSION_KEY] === undefined) {
|
|
7437
|
+
issues.push({ path: VERSION_KEY, message: `missing required key \`${VERSION_KEY}\`` });
|
|
7438
|
+
return;
|
|
7439
|
+
}
|
|
7440
|
+
requireNumber(doc[LEGACY_VERSION_KEY], LEGACY_VERSION_KEY, issues);
|
|
7441
|
+
deprecations.push(LEGACY_VERSION_NOTE);
|
|
7442
|
+
}
|
|
7443
|
+
function isRecord(value) {
|
|
7444
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
7445
|
+
}
|
|
7446
|
+
function joinPath(base, key) {
|
|
7447
|
+
return base === "" ? key : `${base}.${key}`;
|
|
7448
|
+
}
|
|
7449
|
+
function requireKeys(value, keys, base, issues) {
|
|
7450
|
+
for (const key of keys) {
|
|
7451
|
+
if (!(key in value) || value[key] === undefined) {
|
|
7452
|
+
issues.push({ path: joinPath(base, key), message: `missing required key \`${key}\`` });
|
|
7453
|
+
}
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
function requireEnum(value, allowed, path, issues) {
|
|
7457
|
+
if (value === undefined)
|
|
7458
|
+
return;
|
|
7459
|
+
if (typeof value !== "string" || !allowed.includes(value)) {
|
|
7460
|
+
issues.push({ path, message: `expected one of ${allowed.join(" | ")}, got ${describe(value)}` });
|
|
7461
|
+
}
|
|
7462
|
+
}
|
|
7463
|
+
function requireArray(value, path, issues) {
|
|
7464
|
+
if (value === undefined)
|
|
7465
|
+
return false;
|
|
7466
|
+
if (!Array.isArray(value)) {
|
|
7467
|
+
issues.push({ path, message: `expected an array, got ${describe(value)}` });
|
|
7468
|
+
return false;
|
|
7469
|
+
}
|
|
7470
|
+
return true;
|
|
7471
|
+
}
|
|
7472
|
+
function requireNumber(value, path, issues) {
|
|
7473
|
+
if (value === undefined)
|
|
7474
|
+
return;
|
|
7475
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
7476
|
+
issues.push({ path, message: `expected a number, got ${describe(value)}` });
|
|
7477
|
+
}
|
|
7478
|
+
}
|
|
7479
|
+
function requireString(value, path, issues) {
|
|
7480
|
+
if (value === undefined)
|
|
7481
|
+
return;
|
|
7482
|
+
if (typeof value !== "string") {
|
|
7483
|
+
issues.push({ path, message: `expected a string, got ${describe(value)}` });
|
|
7484
|
+
}
|
|
7485
|
+
}
|
|
7486
|
+
function requireRecord(value, path, issues) {
|
|
7487
|
+
if (value === undefined)
|
|
7488
|
+
return false;
|
|
7489
|
+
if (!isRecord(value)) {
|
|
7490
|
+
issues.push({ path, message: `expected a mapping, got ${describe(value)}` });
|
|
7491
|
+
return false;
|
|
7492
|
+
}
|
|
7493
|
+
return true;
|
|
7494
|
+
}
|
|
7495
|
+
function asDocument(input, issues) {
|
|
7496
|
+
if (!isRecord(input)) {
|
|
7497
|
+
issues.push({ path: "", message: `expected a mapping at the document root, got ${describe(input)}` });
|
|
7498
|
+
return null;
|
|
7499
|
+
}
|
|
7500
|
+
return input;
|
|
7501
|
+
}
|
|
7502
|
+
function describe(value) {
|
|
7503
|
+
if (value === null)
|
|
7504
|
+
return "null";
|
|
7505
|
+
if (Array.isArray(value))
|
|
7506
|
+
return "an array";
|
|
7507
|
+
return typeof value;
|
|
7508
|
+
}
|
|
7509
|
+
function describeValue(value) {
|
|
7510
|
+
return typeof value === "number" || typeof value === "boolean" ? String(value) : describe(value);
|
|
7511
|
+
}
|
|
7512
|
+
|
|
7513
|
+
// src/core/schemas/planCommon.ts
|
|
7514
|
+
var STORY_ID_RE = /^S\d{1,4}$/;
|
|
7515
|
+
var WAVE_ID_RE = /^W\d{1,3}$/;
|
|
7516
|
+
var MAX_LIST_ITEMS = 64;
|
|
7517
|
+
var MAX_ITEM_CHARS = 512;
|
|
7518
|
+
var MAX_WAVES = 32;
|
|
7519
|
+
var MAX_STORIES_PER_WAVE = 32;
|
|
7520
|
+
var MAX_PLAN_STORIES = 200;
|
|
7521
|
+
function requireStringList(value, path, issues, rules = {}) {
|
|
7522
|
+
if (value === undefined)
|
|
7523
|
+
return [];
|
|
7524
|
+
if (!Array.isArray(value)) {
|
|
7525
|
+
issues.push({ path, message: `expected a list, got ${describe2(value)}` });
|
|
7526
|
+
return [];
|
|
7527
|
+
}
|
|
7528
|
+
const max = rules.max ?? MAX_LIST_ITEMS;
|
|
7529
|
+
if (value.length > max) {
|
|
7530
|
+
issues.push({ path, message: `${value.length} items exceeds the ${max}-item cap` });
|
|
7531
|
+
}
|
|
7532
|
+
if (rules.nonEmpty === true && value.length === 0) {
|
|
7533
|
+
issues.push({ path, message: "must not be empty" });
|
|
7534
|
+
}
|
|
7535
|
+
const seen = new Set;
|
|
7536
|
+
const out = [];
|
|
7537
|
+
value.forEach((item, i) => {
|
|
7538
|
+
const at = `${path}[${i}]`;
|
|
7539
|
+
if (typeof item !== "string") {
|
|
7540
|
+
issues.push({ path: at, message: `expected a string, got ${describe2(item)}` });
|
|
7541
|
+
return;
|
|
7542
|
+
}
|
|
7543
|
+
if (item.trim() === "") {
|
|
7544
|
+
issues.push({ path: at, message: "must not be blank" });
|
|
7545
|
+
return;
|
|
7546
|
+
}
|
|
7547
|
+
if (item.length > MAX_ITEM_CHARS) {
|
|
7548
|
+
issues.push({
|
|
7549
|
+
path: at,
|
|
7550
|
+
message: `${item.length} characters exceeds the ${MAX_ITEM_CHARS}-character cap on one list item ` + "— split it into several items"
|
|
7551
|
+
});
|
|
7552
|
+
}
|
|
7553
|
+
if (rules.pattern !== undefined && !rules.pattern.test(item)) {
|
|
7554
|
+
issues.push({ path: at, message: `expected ${rules.patternName ?? String(rules.pattern)}, got \`${item}\`` });
|
|
7555
|
+
return;
|
|
7556
|
+
}
|
|
7557
|
+
if (rules.unique === true && seen.has(item)) {
|
|
7558
|
+
issues.push({ path: at, message: `\`${item}\` is listed twice` });
|
|
7559
|
+
return;
|
|
7560
|
+
}
|
|
7561
|
+
seen.add(item);
|
|
7562
|
+
out.push(item);
|
|
7563
|
+
});
|
|
7564
|
+
return out;
|
|
7565
|
+
}
|
|
7566
|
+
function requirePattern(value, pattern, patternName, path, issues) {
|
|
7567
|
+
if (value === undefined)
|
|
7568
|
+
return;
|
|
7569
|
+
if (typeof value !== "string") {
|
|
7570
|
+
issues.push({ path, message: `expected a string, got ${describe2(value)}` });
|
|
7571
|
+
return;
|
|
7572
|
+
}
|
|
7573
|
+
if (!pattern.test(value)) {
|
|
7574
|
+
issues.push({ path, message: `expected ${patternName}, got \`${value}\`` });
|
|
7575
|
+
}
|
|
7576
|
+
}
|
|
7577
|
+
function requireVersion1(doc, issues) {
|
|
7578
|
+
if (doc.version !== undefined && doc.version !== 1) {
|
|
7579
|
+
issues.push({ path: "version", message: `unknown schema version ${String(doc.version)} (expected 1)` });
|
|
7580
|
+
}
|
|
7581
|
+
}
|
|
7582
|
+
function describe2(value) {
|
|
7583
|
+
if (value === null)
|
|
7584
|
+
return "null";
|
|
7585
|
+
if (Array.isArray(value))
|
|
7586
|
+
return "a list";
|
|
7587
|
+
if (isRecord(value))
|
|
7588
|
+
return "a mapping";
|
|
7589
|
+
return typeof value;
|
|
7590
|
+
}
|
|
7591
|
+
|
|
7592
|
+
// src/core/schemas/frontMatter.ts
|
|
7593
|
+
var FENCE = "---";
|
|
7594
|
+
function splitFrontMatter(text) {
|
|
7595
|
+
const lines = text.split(`
|
|
7596
|
+
`);
|
|
7597
|
+
if ((lines[0] ?? "").trimEnd() !== FENCE) {
|
|
7598
|
+
return { present: false, raw: "", body: text, bodyLine: 1 };
|
|
7599
|
+
}
|
|
7600
|
+
for (let i = 1;i < lines.length; i++) {
|
|
7601
|
+
if ((lines[i] ?? "").trimEnd() !== FENCE)
|
|
7602
|
+
continue;
|
|
7603
|
+
return {
|
|
7604
|
+
present: true,
|
|
7605
|
+
raw: lines.slice(1, i).join(`
|
|
7606
|
+
`),
|
|
7607
|
+
body: lines.slice(i + 1).join(`
|
|
7608
|
+
`),
|
|
7609
|
+
bodyLine: i + 2
|
|
7610
|
+
};
|
|
7611
|
+
}
|
|
7612
|
+
return { present: false, raw: "", body: text, bodyLine: 1 };
|
|
7613
|
+
}
|
|
7614
|
+
function parseFrontMatter(text) {
|
|
7615
|
+
const frontMatter = splitFrontMatter(text);
|
|
7616
|
+
if (!frontMatter.present) {
|
|
7617
|
+
return {
|
|
7618
|
+
frontMatter,
|
|
7619
|
+
doc: null,
|
|
7620
|
+
issue: {
|
|
7621
|
+
path: "",
|
|
7622
|
+
message: "no YAML front matter — the file must open with `---` and close the block with `---`"
|
|
7623
|
+
}
|
|
7624
|
+
};
|
|
7625
|
+
}
|
|
7626
|
+
try {
|
|
7627
|
+
return { frontMatter, doc: parseYaml(frontMatter.raw), issue: null };
|
|
7628
|
+
} catch (error) {
|
|
7629
|
+
return {
|
|
7630
|
+
frontMatter,
|
|
7631
|
+
doc: null,
|
|
7632
|
+
issue: { path: "", message: `front matter is not valid YAML: ${message(error)}` }
|
|
7633
|
+
};
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
function message(error) {
|
|
7637
|
+
return error instanceof Error ? error.message.split(`
|
|
7638
|
+
`)[0] ?? "parse error" : String(error);
|
|
7639
|
+
}
|
|
7640
|
+
// src/core/schemas/story.ts
|
|
7641
|
+
var DOD_FENCE_OPEN_RE = /^\s*```+\s*dod\s*$/i;
|
|
7642
|
+
var DOD_FENCE_CLOSE_RE = /^\s*```+\s*$/;
|
|
7643
|
+
var FENCE_OPEN_RE = DOD_FENCE_OPEN_RE;
|
|
7644
|
+
var FENCE_CLOSE_RE = DOD_FENCE_CLOSE_RE;
|
|
7645
|
+
function parseDodBlock(text) {
|
|
7646
|
+
const commands = [];
|
|
7647
|
+
let present = false;
|
|
7648
|
+
let inside = false;
|
|
7649
|
+
for (const line of text.split(`
|
|
7650
|
+
`)) {
|
|
7651
|
+
if (inside) {
|
|
7652
|
+
if (FENCE_CLOSE_RE.test(line)) {
|
|
7653
|
+
inside = false;
|
|
7654
|
+
continue;
|
|
7655
|
+
}
|
|
7656
|
+
const command = line.trim();
|
|
7657
|
+
if (command !== "" && !command.startsWith("#"))
|
|
7658
|
+
commands.push(command);
|
|
7659
|
+
continue;
|
|
7660
|
+
}
|
|
7661
|
+
if (FENCE_OPEN_RE.test(line)) {
|
|
7662
|
+
present = true;
|
|
7663
|
+
inside = true;
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
return { present, commands };
|
|
7667
|
+
}
|
|
7668
|
+
|
|
7669
|
+
// src/hooks/lib/story.ts
|
|
7670
|
+
var DONE_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?status(?:\*\*)?\s*:\s*["'`]?done["'`]?\.?\s*$/i;
|
|
7671
|
+
var REPO_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?repo(?:\*\*)?\s*:\s*["'`]?([A-Za-z0-9._-]{1,64})["'`]?\s*$/i;
|
|
7672
|
+
var TIMEOUT_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?timeout_s(?:\*\*)?\s*:\s*(\d{1,6})\s*$/i;
|
|
7673
|
+
var FENCE_OPEN_RE2 = /^\s*```+\s*dod\s*$/i;
|
|
7674
|
+
var FENCE_CLOSE_RE2 = /^\s*```+\s*$/;
|
|
7675
|
+
function readStory(text) {
|
|
7676
|
+
const lines = text.split(`
|
|
7677
|
+
`);
|
|
7678
|
+
const dod = parseDodBlock(text);
|
|
7679
|
+
let setsDone = false;
|
|
7680
|
+
let repo = null;
|
|
7681
|
+
let timeoutS = null;
|
|
7682
|
+
let inDod = false;
|
|
7683
|
+
for (const line of lines) {
|
|
7684
|
+
if (inDod) {
|
|
7685
|
+
if (FENCE_CLOSE_RE2.test(line))
|
|
7686
|
+
inDod = false;
|
|
7687
|
+
continue;
|
|
7688
|
+
}
|
|
7689
|
+
if (FENCE_OPEN_RE2.test(line)) {
|
|
7690
|
+
inDod = true;
|
|
7691
|
+
continue;
|
|
7692
|
+
}
|
|
7693
|
+
if (!setsDone && DONE_RE.test(line))
|
|
7694
|
+
setsDone = true;
|
|
7695
|
+
if (repo === null) {
|
|
7696
|
+
const m = REPO_RE.exec(line);
|
|
7697
|
+
if (m?.[1] !== undefined)
|
|
7698
|
+
repo = m[1];
|
|
7699
|
+
}
|
|
7700
|
+
if (timeoutS === null) {
|
|
7701
|
+
const m = TIMEOUT_RE.exec(line);
|
|
7702
|
+
if (m?.[1] !== undefined)
|
|
7703
|
+
timeoutS = Number(m[1]);
|
|
7704
|
+
}
|
|
7705
|
+
}
|
|
7706
|
+
return { setsDone, repo, dodCommands: dod.commands, hasDodBlock: dod.present, timeoutS };
|
|
7707
|
+
}
|
|
7708
|
+
function isAllowedDodCommand(command, allowed) {
|
|
7709
|
+
return allowed.has(command);
|
|
7710
|
+
}
|
|
7711
|
+
var META_RE = /[|&;<>$`(){}*?~\\]/;
|
|
7712
|
+
function splitArgv(command) {
|
|
7713
|
+
if (/[\n\r]/.test(command))
|
|
7714
|
+
return null;
|
|
7715
|
+
const argv = [];
|
|
7716
|
+
const pattern = /"([^"]*)"|'([^']*)'|([^\s"']+)/g;
|
|
7717
|
+
for (const match of command.matchAll(pattern)) {
|
|
7718
|
+
if (match[1] !== undefined || match[2] !== undefined) {
|
|
7719
|
+
argv.push(match[1] ?? match[2] ?? "");
|
|
7720
|
+
continue;
|
|
7721
|
+
}
|
|
7722
|
+
const bare = match[3] ?? "";
|
|
7723
|
+
if (META_RE.test(bare))
|
|
7724
|
+
return null;
|
|
7725
|
+
argv.push(bare);
|
|
7726
|
+
}
|
|
7727
|
+
return argv.length === 0 ? null : argv;
|
|
7728
|
+
}
|
|
7729
|
+
|
|
7730
|
+
class DodCommandRefused extends Error {
|
|
7731
|
+
}
|
|
7732
|
+
async function runDodCommand(command, cwd, timeoutMs, allowed) {
|
|
7733
|
+
if (!isAllowedDodCommand(command, allowed)) {
|
|
7734
|
+
throw new DodCommandRefused(allowed.size === 0 ? `\`${command}\` cannot be run: .tldrx/workspace.yml declares no commands, so there is nothing ` + "to check it against. An empty allowlist is not a permit." : `\`${command}\` is not one of .tldrx/workspace.yml's commands. Declared: ` + `${[...allowed].map((c) => `\`${c}\``).join(", ")}.`);
|
|
7735
|
+
}
|
|
7736
|
+
const argv = splitArgv(command);
|
|
7737
|
+
if (argv === null) {
|
|
7738
|
+
throw new DodCommandRefused(`\`${command}\` needs a shell to run (it contains a metacharacter), and this gate does not open one. ` + "Put it in a script, declare the script under the repo's `commands:`, and cite that.");
|
|
7739
|
+
}
|
|
7740
|
+
const head = argv[0] ?? "";
|
|
7741
|
+
const { exitCode, stdout, stderr, timedOut } = await runtime.spawn(head, argv.slice(1), {
|
|
7742
|
+
cwd,
|
|
7743
|
+
timeoutMs
|
|
7744
|
+
});
|
|
7745
|
+
return { command, exitCode, timedOut, tail: lastMeaningfulLine(`${stdout}
|
|
7746
|
+
${stderr}`) };
|
|
7747
|
+
}
|
|
7748
|
+
function lastMeaningfulLine(output, max = 200) {
|
|
7749
|
+
const lines = output.split(`
|
|
7750
|
+
`).map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
|
|
7751
|
+
const last = lines[lines.length - 1] ?? "";
|
|
7752
|
+
return last.length > max ? `${last.slice(0, max - 1)}…` : last;
|
|
7753
|
+
}
|
|
7754
|
+
|
|
7415
7755
|
// src/hooks/lib/workspace.ts
|
|
7416
7756
|
import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "node:fs";
|
|
7417
7757
|
import { basename, dirname as dirname3, isAbsolute, join as join2, resolve, sep } from "node:path";
|
|
7758
|
+
|
|
7759
|
+
// src/core/detect/probeCommands.ts
|
|
7760
|
+
var PROBE_STATUSES = ["ok", "failed", "timed-out", "unspawnable", "not-probed", "skipped"];
|
|
7761
|
+
|
|
7762
|
+
// src/core/schemas/workspace.ts
|
|
7763
|
+
function commandProbeIssues(probe, path) {
|
|
7764
|
+
if (!isRecord(probe))
|
|
7765
|
+
return [{ path, message: "expected a mapping" }];
|
|
7766
|
+
const issues = [];
|
|
7767
|
+
if (!PROBE_STATUSES.includes(probe.status)) {
|
|
7768
|
+
issues.push({ path: `${path}.status`, message: `expected one of ${PROBE_STATUSES.join(" | ")}` });
|
|
7769
|
+
}
|
|
7770
|
+
if (typeof probe.verified !== "boolean") {
|
|
7771
|
+
issues.push({ path: `${path}.verified`, message: "expected a boolean" });
|
|
7772
|
+
}
|
|
7773
|
+
if (probe.exit_code !== null && typeof probe.exit_code !== "number") {
|
|
7774
|
+
issues.push({ path: `${path}.exit_code`, message: "expected a number or null" });
|
|
7775
|
+
}
|
|
7776
|
+
if (typeof probe.at !== "string" || probe.at === "") {
|
|
7777
|
+
issues.push({ path: `${path}.at`, message: "expected a non-empty RFC3339 string" });
|
|
7778
|
+
}
|
|
7779
|
+
if (typeof probe.reason !== "string" || probe.reason === "") {
|
|
7780
|
+
issues.push({ path: `${path}.reason`, message: "expected a non-empty sentence saying why" });
|
|
7781
|
+
}
|
|
7782
|
+
return issues;
|
|
7783
|
+
}
|
|
7784
|
+
|
|
7785
|
+
// src/hooks/lib/workspace.ts
|
|
7418
7786
|
var RUN_YML = "run.yml";
|
|
7419
7787
|
function locateWork(filePath) {
|
|
7420
7788
|
if (filePath === "")
|
|
@@ -7468,11 +7836,33 @@ function commandRolesOf(doc) {
|
|
|
7468
7836
|
}
|
|
7469
7837
|
return out;
|
|
7470
7838
|
}
|
|
7839
|
+
function commandProbesOf(doc) {
|
|
7840
|
+
const out = new Map;
|
|
7841
|
+
const list = doc?.repos;
|
|
7842
|
+
if (!Array.isArray(list))
|
|
7843
|
+
return out;
|
|
7844
|
+
for (const entry of list) {
|
|
7845
|
+
if (typeof entry?.name !== "string")
|
|
7846
|
+
continue;
|
|
7847
|
+
const probes = new Map;
|
|
7848
|
+
const raw = entry.command_probes;
|
|
7849
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
7850
|
+
for (const [slot, value] of Object.entries(raw)) {
|
|
7851
|
+
if (commandProbeIssues(value, slot).length > 0)
|
|
7852
|
+
continue;
|
|
7853
|
+
probes.set(slot, value);
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
out.set(entry.name, probes);
|
|
7857
|
+
}
|
|
7858
|
+
return out;
|
|
7859
|
+
}
|
|
7471
7860
|
function loadWorkspace(root) {
|
|
7472
7861
|
const repos = new Map;
|
|
7473
7862
|
const commands = new Set;
|
|
7474
7863
|
const repoCommands = new Map;
|
|
7475
7864
|
const commandRoles = new Map;
|
|
7865
|
+
const commandProbes = new Map;
|
|
7476
7866
|
const defaultBranches = new Map;
|
|
7477
7867
|
let seedTriageThresholdTokens = null;
|
|
7478
7868
|
const empty = () => ({
|
|
@@ -7481,6 +7871,7 @@ function loadWorkspace(root) {
|
|
|
7481
7871
|
commands,
|
|
7482
7872
|
repoCommands,
|
|
7483
7873
|
commandRoles,
|
|
7874
|
+
commandProbes,
|
|
7484
7875
|
defaultBranches,
|
|
7485
7876
|
seedTriageThresholdTokens
|
|
7486
7877
|
});
|
|
@@ -7504,6 +7895,7 @@ function loadWorkspace(root) {
|
|
|
7504
7895
|
if (!Array.isArray(list))
|
|
7505
7896
|
return empty();
|
|
7506
7897
|
const declared = commandRolesOf(doc);
|
|
7898
|
+
const measured = commandProbesOf(doc);
|
|
7507
7899
|
for (const entry of list) {
|
|
7508
7900
|
if (typeof entry?.name !== "string")
|
|
7509
7901
|
continue;
|
|
@@ -7518,6 +7910,7 @@ function loadWorkspace(root) {
|
|
|
7518
7910
|
}
|
|
7519
7911
|
repoCommands.set(entry.name, own);
|
|
7520
7912
|
commandRoles.set(entry.name, roles);
|
|
7913
|
+
commandProbes.set(entry.name, measured.get(entry.name) ?? new Map);
|
|
7521
7914
|
}
|
|
7522
7915
|
return empty();
|
|
7523
7916
|
}
|
|
@@ -7933,14 +8326,14 @@ function parseFileSrc(raw, repos) {
|
|
|
7933
8326
|
}
|
|
7934
8327
|
var lineCountCache = new Map;
|
|
7935
8328
|
var OK = { ok: true, outcome: "ok" };
|
|
7936
|
-
function refused(
|
|
7937
|
-
return resolved === undefined ? { ok: false, outcome: "refused", message } : { ok: false, outcome: "refused", message, resolved };
|
|
8329
|
+
function refused(message2, resolved) {
|
|
8330
|
+
return resolved === undefined ? { ok: false, outcome: "refused", message: message2 } : { ok: false, outcome: "refused", message: message2, resolved };
|
|
7938
8331
|
}
|
|
7939
|
-
function unverified(
|
|
7940
|
-
return { ok: true, outcome: "unverified", message };
|
|
8332
|
+
function unverified(message2) {
|
|
8333
|
+
return { ok: true, outcome: "unverified", message: message2 };
|
|
7941
8334
|
}
|
|
7942
|
-
function noted(
|
|
7943
|
-
return { ok: true, outcome: "noted", message };
|
|
8335
|
+
function noted(message2) {
|
|
8336
|
+
return { ok: true, outcome: "noted", message: message2 };
|
|
7944
8337
|
}
|
|
7945
8338
|
function resolveSrc(ref, ctx, section, claim = "") {
|
|
7946
8339
|
switch (ref.kind) {
|
|
@@ -8721,4 +9114,4 @@ function noneBullet(lookedAt) {
|
|
|
8721
9114
|
return `- none [src: absent:${lookedAt}]`;
|
|
8722
9115
|
}
|
|
8723
9116
|
|
|
8724
|
-
export { runtime, parseYaml, parseYamlRepairing, STAGES_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, classifySrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
9117
|
+
export { runtime, parseYaml, parseYamlRepairing, STAGES_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, result, requireVersion, isRecord, requireKeys, requireEnum, requireArray, requireNumber, requireString, requireRecord, asDocument, STORY_ID_RE, WAVE_ID_RE, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter, readStory, DodCommandRefused, runDodCommand, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, classifySrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROJECT_FRAMEWORK_DIR,
|
|
3
3
|
PROJECT_WORK_DIR
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-f6d6cp4h.js";
|
|
5
5
|
|
|
6
6
|
// src/core/lock/workspaceLock.ts
|
|
7
7
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, rmSync as rmSync2, writeSync, closeSync } from "node:fs";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
wouldBeContent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-w3jssmwe.js";
|
|
5
5
|
import {
|
|
6
6
|
claimSourcesDeny,
|
|
7
7
|
claimSourcesEmptySectionDeny,
|
|
8
8
|
claimSourcesMalformedDeny,
|
|
9
9
|
claimSourcesUnresolvedDeny
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7ye9jde9.js";
|
|
11
11
|
import {
|
|
12
12
|
allow,
|
|
13
13
|
deny,
|
|
@@ -16,8 +16,8 @@ import {
|
|
|
16
16
|
postContext,
|
|
17
17
|
readPayload,
|
|
18
18
|
runHook
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import"./chunk-
|
|
19
|
+
} from "./chunk-dzm2bzqp.js";
|
|
20
|
+
import"./chunk-3rwbyema.js";
|
|
21
21
|
import {
|
|
22
22
|
isHandoff,
|
|
23
23
|
loadWorkspace,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
toSrcContext,
|
|
26
26
|
validateCitations,
|
|
27
27
|
validateHandoff
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-f6d6cp4h.js";
|
|
29
29
|
|
|
30
30
|
// src/hooks/claim-sources.ts
|
|
31
31
|
await runHook("claim-sources", async () => {
|
package/dist/hooks/dod-gate.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
wouldBeContent
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-w3jssmwe.js";
|
|
5
5
|
import {
|
|
6
6
|
dodGateDeny,
|
|
7
7
|
dodGateInternalErrorDeny,
|
|
8
8
|
dodGateMissingBlockDeny,
|
|
9
9
|
dodGateRefusedCommandDeny
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-7ye9jde9.js";
|
|
11
11
|
import {
|
|
12
12
|
allow,
|
|
13
13
|
deny,
|
|
@@ -15,111 +15,20 @@ import {
|
|
|
15
15
|
filePathOf,
|
|
16
16
|
isWriteOrEdit,
|
|
17
17
|
readPayload
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import"./chunk-
|
|
20
|
-
import {
|
|
21
|
-
parseDodBlock
|
|
22
|
-
} from "./chunk-rrkdfk7s.js";
|
|
23
|
-
import"./chunk-5w5eg00k.js";
|
|
24
|
-
import"./chunk-39zh2e44.js";
|
|
18
|
+
} from "./chunk-dzm2bzqp.js";
|
|
19
|
+
import"./chunk-3rwbyema.js";
|
|
25
20
|
import {
|
|
21
|
+
DodCommandRefused,
|
|
26
22
|
loadWorkspace,
|
|
27
23
|
locateWork,
|
|
24
|
+
readStory,
|
|
28
25
|
repoPath,
|
|
29
|
-
|
|
30
|
-
} from "./chunk-
|
|
26
|
+
runDodCommand
|
|
27
|
+
} from "./chunk-f6d6cp4h.js";
|
|
31
28
|
|
|
32
29
|
// src/hooks/dod-gate.ts
|
|
33
30
|
import { basename, join } from "node:path";
|
|
34
31
|
import { existsSync } from "node:fs";
|
|
35
|
-
|
|
36
|
-
// src/hooks/lib/story.ts
|
|
37
|
-
var DONE_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?status(?:\*\*)?\s*:\s*["'`]?done["'`]?\.?\s*$/i;
|
|
38
|
-
var REPO_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?repo(?:\*\*)?\s*:\s*["'`]?([A-Za-z0-9._-]{1,64})["'`]?\s*$/i;
|
|
39
|
-
var TIMEOUT_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?timeout_s(?:\*\*)?\s*:\s*(\d{1,6})\s*$/i;
|
|
40
|
-
var FENCE_OPEN_RE = /^\s*```+\s*dod\s*$/i;
|
|
41
|
-
var FENCE_CLOSE_RE = /^\s*```+\s*$/;
|
|
42
|
-
function readStory(text) {
|
|
43
|
-
const lines = text.split(`
|
|
44
|
-
`);
|
|
45
|
-
const dod = parseDodBlock(text);
|
|
46
|
-
let setsDone = false;
|
|
47
|
-
let repo = null;
|
|
48
|
-
let timeoutS = null;
|
|
49
|
-
let inDod = false;
|
|
50
|
-
for (const line of lines) {
|
|
51
|
-
if (inDod) {
|
|
52
|
-
if (FENCE_CLOSE_RE.test(line))
|
|
53
|
-
inDod = false;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (FENCE_OPEN_RE.test(line)) {
|
|
57
|
-
inDod = true;
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (!setsDone && DONE_RE.test(line))
|
|
61
|
-
setsDone = true;
|
|
62
|
-
if (repo === null) {
|
|
63
|
-
const m = REPO_RE.exec(line);
|
|
64
|
-
if (m?.[1] !== undefined)
|
|
65
|
-
repo = m[1];
|
|
66
|
-
}
|
|
67
|
-
if (timeoutS === null) {
|
|
68
|
-
const m = TIMEOUT_RE.exec(line);
|
|
69
|
-
if (m?.[1] !== undefined)
|
|
70
|
-
timeoutS = Number(m[1]);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return { setsDone, repo, dodCommands: dod.commands, hasDodBlock: dod.present, timeoutS };
|
|
74
|
-
}
|
|
75
|
-
function isAllowedDodCommand(command, allowed) {
|
|
76
|
-
return allowed.has(command);
|
|
77
|
-
}
|
|
78
|
-
var META_RE = /[|&;<>$`(){}*?~\\]/;
|
|
79
|
-
function splitArgv(command) {
|
|
80
|
-
if (/[\n\r]/.test(command))
|
|
81
|
-
return null;
|
|
82
|
-
const argv = [];
|
|
83
|
-
const pattern = /"([^"]*)"|'([^']*)'|([^\s"']+)/g;
|
|
84
|
-
for (const match of command.matchAll(pattern)) {
|
|
85
|
-
if (match[1] !== undefined || match[2] !== undefined) {
|
|
86
|
-
argv.push(match[1] ?? match[2] ?? "");
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
const bare = match[3] ?? "";
|
|
90
|
-
if (META_RE.test(bare))
|
|
91
|
-
return null;
|
|
92
|
-
argv.push(bare);
|
|
93
|
-
}
|
|
94
|
-
return argv.length === 0 ? null : argv;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
class DodCommandRefused extends Error {
|
|
98
|
-
}
|
|
99
|
-
async function runDodCommand(command, cwd, timeoutMs, allowed) {
|
|
100
|
-
if (!isAllowedDodCommand(command, allowed)) {
|
|
101
|
-
throw new DodCommandRefused(allowed.size === 0 ? `\`${command}\` cannot be run: .tldrx/workspace.yml declares no commands, so there is nothing ` + "to check it against. An empty allowlist is not a permit." : `\`${command}\` is not one of .tldrx/workspace.yml's commands. Declared: ` + `${[...allowed].map((c) => `\`${c}\``).join(", ")}.`);
|
|
102
|
-
}
|
|
103
|
-
const argv = splitArgv(command);
|
|
104
|
-
if (argv === null) {
|
|
105
|
-
throw new DodCommandRefused(`\`${command}\` needs a shell to run (it contains a metacharacter), and this gate does not open one. ` + "Put it in a script, declare the script under the repo's `commands:`, and cite that.");
|
|
106
|
-
}
|
|
107
|
-
const head = argv[0] ?? "";
|
|
108
|
-
const { exitCode, stdout, stderr, timedOut } = await runtime.spawn(head, argv.slice(1), {
|
|
109
|
-
cwd,
|
|
110
|
-
timeoutMs
|
|
111
|
-
});
|
|
112
|
-
return { command, exitCode, timedOut, tail: lastMeaningfulLine(`${stdout}
|
|
113
|
-
${stderr}`) };
|
|
114
|
-
}
|
|
115
|
-
function lastMeaningfulLine(output, max = 200) {
|
|
116
|
-
const lines = output.split(`
|
|
117
|
-
`).map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
|
|
118
|
-
const last = lines[lines.length - 1] ?? "";
|
|
119
|
-
return last.length > max ? `${last.slice(0, max - 1)}…` : last;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// src/hooks/dod-gate.ts
|
|
123
32
|
var DEFAULT_TIMEOUT_S = 900;
|
|
124
33
|
var storyId = "?";
|
|
125
34
|
try {
|