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.
Files changed (50) hide show
  1. package/CHANGELOG.md +286 -0
  2. package/README.md +6 -2
  3. package/dist/hooks/answer-capture.js +297 -25
  4. package/dist/hooks/budget-gate.js +7 -10
  5. package/dist/hooks/{chunk-hg43zexg.js → chunk-1c43349s.js} +65 -51
  6. package/dist/hooks/{chunk-x3zy7zrn.js → chunk-6c84kjd7.js} +1 -1
  7. package/dist/hooks/{chunk-2y3kwfrx.js → chunk-6ws778wq.js} +1 -1
  8. package/dist/hooks/{chunk-fv66tdsm.js → chunk-9v45j71e.js} +23 -8
  9. package/dist/hooks/chunk-bk7rkdzw.js +103 -0
  10. package/dist/hooks/{chunk-4w03q2kj.js → chunk-d20x2aew.js} +27 -19
  11. package/dist/hooks/{chunk-rk1nm39z.js → chunk-ecwes7kd.js} +1 -1
  12. package/dist/hooks/{chunk-f6484ykg.js → chunk-kqvbzyc0.js} +24 -10
  13. package/dist/hooks/{chunk-y3cxqvnx.js → chunk-r1efatxk.js} +411 -10
  14. package/dist/hooks/{chunk-cf4az0j5.js → chunk-stw0dxna.js} +1 -1
  15. package/dist/hooks/{chunk-8awygmqw.js → chunk-tg7ppsxr.js} +4 -1
  16. package/dist/hooks/{chunk-vyvjbbmb.js → chunk-wzzywf6c.js} +32 -2
  17. package/dist/hooks/claim-sources.js +5 -5
  18. package/dist/hooks/dod-gate.js +8 -99
  19. package/dist/hooks/no-reask.js +8 -9
  20. package/dist/hooks/session-start.js +38 -123
  21. package/dist/hooks/statusline.js +6 -9
  22. package/dist/tldrx.js +3503 -1648
  23. package/package.json +1 -1
  24. package/plugin/.claude-plugin/plugin.json +1 -1
  25. package/stages/build/stage.yml +14 -0
  26. package/stages/how/stage.yml +6 -0
  27. package/stages/plan/stage.yml +6 -0
  28. package/stages/watch/stage.yml +6 -0
  29. package/stages/what/stage.yml +6 -0
  30. package/templates/experts/stack/dotnet.md +4 -3
  31. package/templates/experts/stack/javascript.md +4 -3
  32. package/templates/experts/stack/python.md +4 -3
  33. package/templates/experts/stack/typescript.md +4 -3
  34. package/templates/workspace.yml +6 -0
  35. package/workflows/bugfix.yml +7 -0
  36. package/workflows/docs.yml +7 -0
  37. package/workflows/feature.yml +7 -0
  38. package/workflows/hotfix.yml +7 -0
  39. package/workflows/integration.yml +7 -0
  40. package/workflows/migration.yml +7 -0
  41. package/workflows/performance.yml +7 -0
  42. package/workflows/prototype.yml +7 -0
  43. package/workflows/refactor.yml +7 -0
  44. package/workflows/retro.yml +7 -0
  45. package/workflows/security-patch.yml +7 -0
  46. package/workflows/spike.yml +7 -0
  47. package/workflows/upgrade.yml +7 -0
  48. package/dist/hooks/chunk-39zh2e44.js +0 -97
  49. package/dist/hooks/chunk-5w5eg00k.js +0 -136
  50. 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-8awygmqw.js";
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-vyvjbbmb.js";
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-39zh2e44.js";
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(message, resolved) {
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(message) {
7940
- return { ok: true, outcome: "unverified", message };
8340
+ function unverified(message2) {
8341
+ return { ok: true, outcome: "unverified", message: message2 };
7941
8342
  }
7942
- function noted(message) {
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-y3cxqvnx.js";
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-y3cxqvnx.js";
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-y3cxqvnx.js";
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 };