tldr-experts 0.9.2 → 0.11.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 +286 -0
- package/README.md +6 -2
- package/dist/hooks/answer-capture.js +297 -25
- package/dist/hooks/budget-gate.js +7 -10
- package/dist/hooks/{chunk-hg43zexg.js → chunk-1c43349s.js} +65 -51
- package/dist/hooks/{chunk-x3zy7zrn.js → chunk-6c84kjd7.js} +1 -1
- package/dist/hooks/{chunk-2y3kwfrx.js → chunk-6ws778wq.js} +1 -1
- package/dist/hooks/{chunk-fv66tdsm.js → chunk-9v45j71e.js} +23 -8
- package/dist/hooks/chunk-bk7rkdzw.js +103 -0
- package/dist/hooks/{chunk-4w03q2kj.js → chunk-d20x2aew.js} +27 -19
- package/dist/hooks/{chunk-rk1nm39z.js → chunk-ecwes7kd.js} +1 -1
- package/dist/hooks/{chunk-f6484ykg.js → chunk-kqvbzyc0.js} +24 -10
- package/dist/hooks/{chunk-y3cxqvnx.js → chunk-r1efatxk.js} +411 -10
- package/dist/hooks/{chunk-cf4az0j5.js → chunk-stw0dxna.js} +1 -1
- package/dist/hooks/{chunk-8awygmqw.js → chunk-tg7ppsxr.js} +4 -1
- package/dist/hooks/{chunk-vyvjbbmb.js → chunk-wzzywf6c.js} +32 -2
- 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 +38 -123
- package/dist/hooks/statusline.js +6 -9
- package/dist/tldrx.js +3503 -1648
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/stages/build/stage.yml +14 -0
- package/stages/how/stage.yml +6 -0
- package/stages/plan/stage.yml +6 -0
- package/stages/watch/stage.yml +6 -0
- package/stages/what/stage.yml +6 -0
- package/templates/experts/stack/dotnet.md +4 -3
- package/templates/experts/stack/javascript.md +4 -3
- package/templates/experts/stack/python.md +4 -3
- package/templates/experts/stack/typescript.md +4 -3
- package/templates/workspace.yml +6 -0
- package/workflows/bugfix.yml +7 -0
- package/workflows/docs.yml +7 -0
- package/workflows/feature.yml +7 -0
- package/workflows/hotfix.yml +7 -0
- package/workflows/integration.yml +7 -0
- package/workflows/migration.yml +7 -0
- package/workflows/performance.yml +7 -0
- package/workflows/prototype.yml +7 -0
- package/workflows/refactor.yml +7 -0
- package/workflows/retro.yml +7 -0
- package/workflows/security-patch.yml +7 -0
- package/workflows/spike.yml +7 -0
- package/workflows/upgrade.yml +7 -0
- package/dist/hooks/chunk-39zh2e44.js +0 -97
- package/dist/hooks/chunk-5w5eg00k.js +0 -136
- package/dist/hooks/chunk-rrkdfk7s.js +0 -30
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
withWorkspaceLock,
|
|
4
4
|
workspaceRootOfFactsPath,
|
|
5
5
|
writeAtomic
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-tg7ppsxr.js";
|
|
7
7
|
import {
|
|
8
8
|
FACT_CONFIDENCES,
|
|
9
9
|
FACT_KINDS,
|
|
@@ -14,22 +14,20 @@ import {
|
|
|
14
14
|
isLive,
|
|
15
15
|
isRetired,
|
|
16
16
|
noteDeprecations
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-wzzywf6c.js";
|
|
18
18
|
import {
|
|
19
|
+
SRC_PATTERNS,
|
|
19
20
|
asDocument,
|
|
20
21
|
isRecord,
|
|
22
|
+
parseYaml,
|
|
23
|
+
readableSource,
|
|
21
24
|
requireArray,
|
|
22
25
|
requireEnum,
|
|
23
26
|
requireKeys,
|
|
24
27
|
requireString,
|
|
25
28
|
requireVersion,
|
|
26
29
|
result
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import {
|
|
29
|
-
SRC_PATTERNS,
|
|
30
|
-
parseYaml,
|
|
31
|
-
readableSource
|
|
32
|
-
} from "./chunk-y3cxqvnx.js";
|
|
30
|
+
} from "./chunk-r1efatxk.js";
|
|
33
31
|
|
|
34
32
|
// src/core/facts/FactsStore.ts
|
|
35
33
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -99,6 +97,18 @@ function validateFactsFile(input) {
|
|
|
99
97
|
if (row.truncated !== undefined && typeof row.truncated !== "boolean") {
|
|
100
98
|
issues.push({ path: `${path}.truncated`, message: "expected true, false or absent" });
|
|
101
99
|
}
|
|
100
|
+
if (row.conflicts_with !== undefined) {
|
|
101
|
+
if (requireArray(row.conflicts_with, `${path}.conflicts_with`, issues)) {
|
|
102
|
+
const links = row.conflicts_with;
|
|
103
|
+
if (links.length === 0) {
|
|
104
|
+
issues.push({
|
|
105
|
+
path: `${path}.conflicts_with`,
|
|
106
|
+
message: "expected at least one fact id, or the key absent"
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
links.forEach((id2, i2) => requireString(id2, `${path}.conflicts_with[${i2}]`, issues));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
102
112
|
const superseded = typeof row.superseded_by === "string";
|
|
103
113
|
const retired = isRecord(row.retired) && row.retired.at !== null && row.retired.at !== undefined;
|
|
104
114
|
if (superseded && retired) {
|
|
@@ -155,6 +165,9 @@ function jaccard(a, b) {
|
|
|
155
165
|
const union = a.size + b.size - intersection;
|
|
156
166
|
return union === 0 ? 0 : intersection / union;
|
|
157
167
|
}
|
|
168
|
+
function formatJaccard(score) {
|
|
169
|
+
return score.toFixed(2);
|
|
170
|
+
}
|
|
158
171
|
function findDuplicate(question, area, facts, threshold = DEFAULT_JACCARD_THRESHOLD) {
|
|
159
172
|
const asked = tokenize(question);
|
|
160
173
|
if (asked.size === 0)
|
|
@@ -231,7 +244,8 @@ class FactsStore {
|
|
|
231
244
|
supersedes: input.supersedes ?? null,
|
|
232
245
|
superseded_by: null,
|
|
233
246
|
retired: input.retired ?? null,
|
|
234
|
-
...cut || input.truncated === true ? { truncated: true } : {}
|
|
247
|
+
...cut || input.truncated === true ? { truncated: true } : {},
|
|
248
|
+
...input.conflicts_with !== undefined && input.conflicts_with.length > 0 ? { conflicts_with: [...input.conflicts_with] } : {}
|
|
235
249
|
};
|
|
236
250
|
this.rows.push(fact);
|
|
237
251
|
return fact;
|
|
@@ -301,4 +315,4 @@ class FactsStore {
|
|
|
301
315
|
}
|
|
302
316
|
}
|
|
303
317
|
|
|
304
|
-
export { FactsStore };
|
|
318
|
+
export { formatJaccard, findDuplicate, FactsStore };
|
|
@@ -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,378 @@ 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
|
+
|
|
7641
|
+
// src/core/schemas/commandAllowlist.ts
|
|
7642
|
+
var ITERATION_ONLY_SLOT = "test_fast";
|
|
7643
|
+
// src/core/schemas/story.ts
|
|
7644
|
+
var DOD_FENCE_OPEN_RE = /^\s*```+\s*dod\s*$/i;
|
|
7645
|
+
var DOD_FENCE_CLOSE_RE = /^\s*```+\s*$/;
|
|
7646
|
+
var FENCE_OPEN_RE = DOD_FENCE_OPEN_RE;
|
|
7647
|
+
var FENCE_CLOSE_RE = DOD_FENCE_CLOSE_RE;
|
|
7648
|
+
function parseDodBlock(text) {
|
|
7649
|
+
const commands = [];
|
|
7650
|
+
let present = false;
|
|
7651
|
+
let inside = false;
|
|
7652
|
+
for (const line of text.split(`
|
|
7653
|
+
`)) {
|
|
7654
|
+
if (inside) {
|
|
7655
|
+
if (FENCE_CLOSE_RE.test(line)) {
|
|
7656
|
+
inside = false;
|
|
7657
|
+
continue;
|
|
7658
|
+
}
|
|
7659
|
+
const command = line.trim();
|
|
7660
|
+
if (command !== "" && !command.startsWith("#"))
|
|
7661
|
+
commands.push(command);
|
|
7662
|
+
continue;
|
|
7663
|
+
}
|
|
7664
|
+
if (FENCE_OPEN_RE.test(line)) {
|
|
7665
|
+
present = true;
|
|
7666
|
+
inside = true;
|
|
7667
|
+
}
|
|
7668
|
+
}
|
|
7669
|
+
return { present, commands };
|
|
7670
|
+
}
|
|
7671
|
+
|
|
7672
|
+
// src/hooks/lib/story.ts
|
|
7673
|
+
var DONE_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?status(?:\*\*)?\s*:\s*["'`]?done["'`]?\.?\s*$/i;
|
|
7674
|
+
var REPO_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?repo(?:\*\*)?\s*:\s*["'`]?([A-Za-z0-9._-]{1,64})["'`]?\s*$/i;
|
|
7675
|
+
var TIMEOUT_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?timeout_s(?:\*\*)?\s*:\s*(\d{1,6})\s*$/i;
|
|
7676
|
+
var FENCE_OPEN_RE2 = /^\s*```+\s*dod\s*$/i;
|
|
7677
|
+
var FENCE_CLOSE_RE2 = /^\s*```+\s*$/;
|
|
7678
|
+
function readStory(text) {
|
|
7679
|
+
const lines = text.split(`
|
|
7680
|
+
`);
|
|
7681
|
+
const dod = parseDodBlock(text);
|
|
7682
|
+
let setsDone = false;
|
|
7683
|
+
let repo = null;
|
|
7684
|
+
let timeoutS = null;
|
|
7685
|
+
let inDod = false;
|
|
7686
|
+
for (const line of lines) {
|
|
7687
|
+
if (inDod) {
|
|
7688
|
+
if (FENCE_CLOSE_RE2.test(line))
|
|
7689
|
+
inDod = false;
|
|
7690
|
+
continue;
|
|
7691
|
+
}
|
|
7692
|
+
if (FENCE_OPEN_RE2.test(line)) {
|
|
7693
|
+
inDod = true;
|
|
7694
|
+
continue;
|
|
7695
|
+
}
|
|
7696
|
+
if (!setsDone && DONE_RE.test(line))
|
|
7697
|
+
setsDone = true;
|
|
7698
|
+
if (repo === null) {
|
|
7699
|
+
const m = REPO_RE.exec(line);
|
|
7700
|
+
if (m?.[1] !== undefined)
|
|
7701
|
+
repo = m[1];
|
|
7702
|
+
}
|
|
7703
|
+
if (timeoutS === null) {
|
|
7704
|
+
const m = TIMEOUT_RE.exec(line);
|
|
7705
|
+
if (m?.[1] !== undefined)
|
|
7706
|
+
timeoutS = Number(m[1]);
|
|
7707
|
+
}
|
|
7708
|
+
}
|
|
7709
|
+
return { setsDone, repo, dodCommands: dod.commands, hasDodBlock: dod.present, timeoutS };
|
|
7710
|
+
}
|
|
7711
|
+
function isAllowedDodCommand(command, allowed) {
|
|
7712
|
+
return allowed.has(command);
|
|
7713
|
+
}
|
|
7714
|
+
var META_RE = /[|&;<>$`(){}*?~\\]/;
|
|
7715
|
+
function splitArgv(command) {
|
|
7716
|
+
if (/[\n\r]/.test(command))
|
|
7717
|
+
return null;
|
|
7718
|
+
const argv = [];
|
|
7719
|
+
const pattern = /"([^"]*)"|'([^']*)'|([^\s"']+)/g;
|
|
7720
|
+
for (const match of command.matchAll(pattern)) {
|
|
7721
|
+
if (match[1] !== undefined || match[2] !== undefined) {
|
|
7722
|
+
argv.push(match[1] ?? match[2] ?? "");
|
|
7723
|
+
continue;
|
|
7724
|
+
}
|
|
7725
|
+
const bare = match[3] ?? "";
|
|
7726
|
+
if (META_RE.test(bare))
|
|
7727
|
+
return null;
|
|
7728
|
+
argv.push(bare);
|
|
7729
|
+
}
|
|
7730
|
+
return argv.length === 0 ? null : argv;
|
|
7731
|
+
}
|
|
7732
|
+
|
|
7733
|
+
class DodCommandRefused extends Error {
|
|
7734
|
+
}
|
|
7735
|
+
async function runDodCommand(command, cwd, timeoutMs, allowed) {
|
|
7736
|
+
if (!isAllowedDodCommand(command, allowed)) {
|
|
7737
|
+
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(", ")}.`);
|
|
7738
|
+
}
|
|
7739
|
+
const argv = splitArgv(command);
|
|
7740
|
+
if (argv === null) {
|
|
7741
|
+
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.");
|
|
7742
|
+
}
|
|
7743
|
+
const head = argv[0] ?? "";
|
|
7744
|
+
const { exitCode, stdout, stderr, timedOut } = await runtime.spawn(head, argv.slice(1), {
|
|
7745
|
+
cwd,
|
|
7746
|
+
timeoutMs
|
|
7747
|
+
});
|
|
7748
|
+
return { command, exitCode, timedOut, tail: lastMeaningfulLine(`${stdout}
|
|
7749
|
+
${stderr}`) };
|
|
7750
|
+
}
|
|
7751
|
+
function lastMeaningfulLine(output, max = 200) {
|
|
7752
|
+
const lines = output.split(`
|
|
7753
|
+
`).map((l) => l.trimEnd()).filter((l) => l.trim() !== "");
|
|
7754
|
+
const last = lines[lines.length - 1] ?? "";
|
|
7755
|
+
return last.length > max ? `${last.slice(0, max - 1)}…` : last;
|
|
7756
|
+
}
|
|
7757
|
+
|
|
7415
7758
|
// src/hooks/lib/workspace.ts
|
|
7416
7759
|
import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "node:fs";
|
|
7417
7760
|
import { basename, dirname as dirname3, isAbsolute, join as join2, resolve, sep } from "node:path";
|
|
7761
|
+
|
|
7762
|
+
// src/core/detect/probeCommands.ts
|
|
7763
|
+
var PROBE_STATUSES = ["ok", "failed", "timed-out", "unspawnable", "not-probed", "skipped"];
|
|
7764
|
+
|
|
7765
|
+
// src/core/schemas/workspace.ts
|
|
7766
|
+
function commandProbeIssues(probe, path) {
|
|
7767
|
+
if (!isRecord(probe))
|
|
7768
|
+
return [{ path, message: "expected a mapping" }];
|
|
7769
|
+
const issues = [];
|
|
7770
|
+
if (!PROBE_STATUSES.includes(probe.status)) {
|
|
7771
|
+
issues.push({ path: `${path}.status`, message: `expected one of ${PROBE_STATUSES.join(" | ")}` });
|
|
7772
|
+
}
|
|
7773
|
+
if (typeof probe.verified !== "boolean") {
|
|
7774
|
+
issues.push({ path: `${path}.verified`, message: "expected a boolean" });
|
|
7775
|
+
}
|
|
7776
|
+
if (probe.exit_code !== null && typeof probe.exit_code !== "number") {
|
|
7777
|
+
issues.push({ path: `${path}.exit_code`, message: "expected a number or null" });
|
|
7778
|
+
}
|
|
7779
|
+
if (typeof probe.at !== "string" || probe.at === "") {
|
|
7780
|
+
issues.push({ path: `${path}.at`, message: "expected a non-empty RFC3339 string" });
|
|
7781
|
+
}
|
|
7782
|
+
if (typeof probe.reason !== "string" || probe.reason === "") {
|
|
7783
|
+
issues.push({ path: `${path}.reason`, message: "expected a non-empty sentence saying why" });
|
|
7784
|
+
}
|
|
7785
|
+
return issues;
|
|
7786
|
+
}
|
|
7787
|
+
|
|
7788
|
+
// src/hooks/lib/workspace.ts
|
|
7418
7789
|
var RUN_YML = "run.yml";
|
|
7419
7790
|
function locateWork(filePath) {
|
|
7420
7791
|
if (filePath === "")
|
|
@@ -7468,11 +7839,34 @@ function commandRolesOf(doc) {
|
|
|
7468
7839
|
}
|
|
7469
7840
|
return out;
|
|
7470
7841
|
}
|
|
7842
|
+
function commandProbesOf(doc) {
|
|
7843
|
+
const out = new Map;
|
|
7844
|
+
const list = doc?.repos;
|
|
7845
|
+
if (!Array.isArray(list))
|
|
7846
|
+
return out;
|
|
7847
|
+
for (const entry of list) {
|
|
7848
|
+
if (typeof entry?.name !== "string")
|
|
7849
|
+
continue;
|
|
7850
|
+
const probes = new Map;
|
|
7851
|
+
const raw = entry.command_probes;
|
|
7852
|
+
if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
|
|
7853
|
+
for (const [slot, value] of Object.entries(raw)) {
|
|
7854
|
+
if (commandProbeIssues(value, slot).length > 0)
|
|
7855
|
+
continue;
|
|
7856
|
+
probes.set(slot, value);
|
|
7857
|
+
}
|
|
7858
|
+
}
|
|
7859
|
+
out.set(entry.name, probes);
|
|
7860
|
+
}
|
|
7861
|
+
return out;
|
|
7862
|
+
}
|
|
7471
7863
|
function loadWorkspace(root) {
|
|
7472
7864
|
const repos = new Map;
|
|
7473
7865
|
const commands = new Set;
|
|
7474
7866
|
const repoCommands = new Map;
|
|
7475
7867
|
const commandRoles = new Map;
|
|
7868
|
+
const commandProbes = new Map;
|
|
7869
|
+
const iterationCommands = new Set;
|
|
7476
7870
|
const defaultBranches = new Map;
|
|
7477
7871
|
let seedTriageThresholdTokens = null;
|
|
7478
7872
|
const empty = () => ({
|
|
@@ -7481,6 +7875,8 @@ function loadWorkspace(root) {
|
|
|
7481
7875
|
commands,
|
|
7482
7876
|
repoCommands,
|
|
7483
7877
|
commandRoles,
|
|
7878
|
+
commandProbes,
|
|
7879
|
+
iterationCommands,
|
|
7484
7880
|
defaultBranches,
|
|
7485
7881
|
seedTriageThresholdTokens
|
|
7486
7882
|
});
|
|
@@ -7504,6 +7900,7 @@ function loadWorkspace(root) {
|
|
|
7504
7900
|
if (!Array.isArray(list))
|
|
7505
7901
|
return empty();
|
|
7506
7902
|
const declared = commandRolesOf(doc);
|
|
7903
|
+
const measured = commandProbesOf(doc);
|
|
7507
7904
|
for (const entry of list) {
|
|
7508
7905
|
if (typeof entry?.name !== "string")
|
|
7509
7906
|
continue;
|
|
@@ -7518,6 +7915,10 @@ function loadWorkspace(root) {
|
|
|
7518
7915
|
}
|
|
7519
7916
|
repoCommands.set(entry.name, own);
|
|
7520
7917
|
commandRoles.set(entry.name, roles);
|
|
7918
|
+
const fast = roles.get(ITERATION_ONLY_SLOT);
|
|
7919
|
+
if (fast !== undefined)
|
|
7920
|
+
iterationCommands.add(fast);
|
|
7921
|
+
commandProbes.set(entry.name, measured.get(entry.name) ?? new Map);
|
|
7521
7922
|
}
|
|
7522
7923
|
return empty();
|
|
7523
7924
|
}
|
|
@@ -7933,14 +8334,14 @@ function parseFileSrc(raw, repos) {
|
|
|
7933
8334
|
}
|
|
7934
8335
|
var lineCountCache = new Map;
|
|
7935
8336
|
var OK = { ok: true, outcome: "ok" };
|
|
7936
|
-
function refused(
|
|
7937
|
-
return resolved === undefined ? { ok: false, outcome: "refused", message } : { ok: false, outcome: "refused", message, resolved };
|
|
8337
|
+
function refused(message2, resolved) {
|
|
8338
|
+
return resolved === undefined ? { ok: false, outcome: "refused", message: message2 } : { ok: false, outcome: "refused", message: message2, resolved };
|
|
7938
8339
|
}
|
|
7939
|
-
function unverified(
|
|
7940
|
-
return { ok: true, outcome: "unverified", message };
|
|
8340
|
+
function unverified(message2) {
|
|
8341
|
+
return { ok: true, outcome: "unverified", message: message2 };
|
|
7941
8342
|
}
|
|
7942
|
-
function noted(
|
|
7943
|
-
return { ok: true, outcome: "noted", message };
|
|
8343
|
+
function noted(message2) {
|
|
8344
|
+
return { ok: true, outcome: "noted", message: message2 };
|
|
7944
8345
|
}
|
|
7945
8346
|
function resolveSrc(ref, ctx, section, claim = "") {
|
|
7946
8347
|
switch (ref.kind) {
|
|
@@ -8721,4 +9122,4 @@ function noneBullet(lookedAt) {
|
|
|
8721
9122
|
return `- none [src: absent:${lookedAt}]`;
|
|
8722
9123
|
}
|
|
8723
9124
|
|
|
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 };
|
|
9125
|
+
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, ITERATION_ONLY_SLOT, readStory, DodCommandRefused, runDodCommand, commandProbeIssues, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, classifySrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
EMPTY_SECTION_RULE,
|
|
4
4
|
noneBullet,
|
|
5
5
|
srcRule
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-r1efatxk.js";
|
|
7
7
|
|
|
8
8
|
// src/hooks/lib/messages.ts
|
|
9
9
|
var GRAMMAR = "[src: <repo:path:line> | https://… | Q<n> | F<n> | $ <cmd> → exit <n> | graph:<node> | absent:<path>]";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PROJECT_FRAMEWORK_DIR,
|
|
3
3
|
PROJECT_WORK_DIR
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-r1efatxk.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";
|
|
@@ -157,6 +157,9 @@ function emitFact(fact, indent = " ") {
|
|
|
157
157
|
];
|
|
158
158
|
if (fact.truncated === true)
|
|
159
159
|
lines.push(`${inner}truncated: true`);
|
|
160
|
+
if (fact.conflicts_with !== undefined && fact.conflicts_with.length > 0) {
|
|
161
|
+
lines.push(`${inner}conflicts_with: ${inlineList(fact.conflicts_with)}`);
|
|
162
|
+
}
|
|
160
163
|
if (fact.retired === null) {
|
|
161
164
|
lines.push(`${inner}retired: null`);
|
|
162
165
|
} else {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parseSrcToken
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-r1efatxk.js";
|
|
4
4
|
|
|
5
5
|
// src/core/text/questions.ts
|
|
6
6
|
var REQUIRED_METADATA_KEYS = ["id", "status", "area", "asked_by", "asked_at"];
|
|
@@ -137,12 +137,42 @@ function serializeQuestions(doc) {
|
|
|
137
137
|
return doc.trailingNewline ? `${text}
|
|
138
138
|
` : text;
|
|
139
139
|
}
|
|
140
|
+
function renderQuestionBlock(block) {
|
|
141
|
+
const meta = block.metadata;
|
|
142
|
+
const metaLine = meta === null ? "" : `<!-- ${[
|
|
143
|
+
`id: ${meta.id}`,
|
|
144
|
+
`status: ${meta.status}`,
|
|
145
|
+
`area: ${meta.area}`,
|
|
146
|
+
`asked_by: ${meta.asked_by}`,
|
|
147
|
+
`asked_at: ${meta.asked_at}`,
|
|
148
|
+
...meta.extra.map(([k, v]) => `${k}: ${v}`)
|
|
149
|
+
].join(" | ")} -->`;
|
|
150
|
+
const lines = [`## ${block.id} · ${block.title}`];
|
|
151
|
+
if (metaLine !== "")
|
|
152
|
+
lines.push(metaLine);
|
|
153
|
+
if (block.whyAsked !== null)
|
|
154
|
+
lines.push(`Why asked: ${block.whyAsked}`);
|
|
155
|
+
lines.push("");
|
|
156
|
+
for (const option of block.options)
|
|
157
|
+
lines.push(`- ${option.letter}) ${option.text}`);
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push(block.answer === "" ? "[Answer]:" : `[Answer]: ${block.answer}`);
|
|
160
|
+
if (block.footer !== null) {
|
|
161
|
+
lines.push(`<!-- answered_by: ${block.footer.answered_by} | answered_at: ${block.footer.answered_at} | fact: ${block.footer.fact} -->`);
|
|
162
|
+
}
|
|
163
|
+
return lines.join(`
|
|
164
|
+
`);
|
|
165
|
+
}
|
|
140
166
|
function detectAnswered(blocks) {
|
|
141
167
|
return blocks.filter((b) => b.metadata?.status === "open" && b.answer !== "");
|
|
142
168
|
}
|
|
143
169
|
function openBlocks(blocks) {
|
|
144
170
|
return blocks.filter((b) => b.metadata?.status === "open");
|
|
145
171
|
}
|
|
172
|
+
var ADVISORY_KEY = "advisory";
|
|
173
|
+
function isAdvisory(block) {
|
|
174
|
+
return block.metadata?.extra.some(([key, value]) => key === ADVISORY_KEY && value === "true") === true;
|
|
175
|
+
}
|
|
146
176
|
function recordAnswer(block, footer) {
|
|
147
177
|
const lines = [...block.lines];
|
|
148
178
|
if (block.metadataIndex >= 0) {
|
|
@@ -196,4 +226,4 @@ function noteDeprecations(file, outcome) {
|
|
|
196
226
|
}
|
|
197
227
|
}
|
|
198
228
|
|
|
199
|
-
export { FACT_KINDS, FACT_CONFIDENCES, MAX_FACTS, MAX_FACT_CHARS, isRetired, isLive, factNumber, formatFactId, noteDeprecations, parseQuestions, serializeQuestions, detectAnswered, openBlocks, recordAnswer, replaceBlock };
|
|
229
|
+
export { FACT_KINDS, FACT_CONFIDENCES, MAX_FACTS, MAX_FACT_CHARS, isRetired, isLive, factNumber, formatFactId, noteDeprecations, parseQuestions, serializeQuestions, renderQuestionBlock, detectAnswered, openBlocks, ADVISORY_KEY, isAdvisory, recordAnswer, replaceBlock };
|