tldr-experts 0.14.1 → 0.14.2

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 CHANGED
@@ -1,6 +1,29 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.14.2 — 2026-09-09
5
+
6
+ ### Fixed
7
+
8
+ - **A stage's `claim-sources` check reads the disk it is judging, not a snapshot taken stages
9
+ ago (#206).** `src/core/text/srcToken.ts` memoises the question and fact indexes at MODULE
10
+ scope, keyed by run dir and workspace root, and nothing outside `test/` ever dropped them —
11
+ `clearSrcCaches()` had 20 call sites, all in tests. `tldrx run auto` is one Node process for a
12
+ whole run (`runAuto.ts`, no re-exec per stage), so the FIRST citation resolved anywhere in the
13
+ run froze both indexes at whatever was on disk at that instant, and every later stage's check
14
+ judged its own outputs against that frozen view. Measured live on three real workspaces at
15
+ 0.14.1: a `how` stage refused with `no such question Q2 … declared: Q1` over Q2–Q4 it had
16
+ itself written into `02-how/questions.md` minutes earlier, and another refused with
17
+ `it has 145 live fact(s)` over a `facts.yml` that held 148 — the three extra facts written by
18
+ the owner's own answers two seconds BEFORE the stage started. Roughly $11 of paid turns thrown
19
+ away for ids that were real the whole time. The indexes are now refreshed inside
20
+ `toSrcContext` — the one place a citation context is built, and a place every caller reaches
21
+ exactly once per check, gate or hook invocation — so the memoisation that pays for itself (one
22
+ document's forty citations read `facts.yml` once) is kept and the memory that outlives the
23
+ check is not. The git blob memo is deliberately left per-process: its cap bounds forks, not
24
+ staleness. Resolution rules are unchanged; what changed is when the checker last looked.
25
+
26
+
4
27
  ## 0.14.1 — 2026-09-09
5
28
 
6
29
  ### Fixed
package/README.md CHANGED
@@ -316,6 +316,7 @@ back on the registry is 0.3.0.
316
316
 
317
317
  | Version | Date | Status | Contains |
318
318
  |---|---|---|---|
319
+ | 0.14.2 | 2026-09-09 | `beta` | every citation check starts from a fresh view of `questions.md` and `facts.yml`: `srcToken.ts` memoised both indexes at module scope and nothing outside `test/` ever dropped them, so in `tldrx run auto` — one Node process for a whole run — the FIRST citation resolved anywhere froze the view every later stage was then judged against; measured on three real unattended workspaces at 0.14.1, a `how` stage refused with `no such question Q2 … declared: Q1` over questions it had itself written minutes earlier, and another refused over `145 live fact(s)` when `facts.yml` held 148, the three extra written by the owner's answers two seconds before the stage started — roughly $11 of paid turns thrown away for ids that were real the whole time; the indexes are now refreshed inside `toSrcContext`, the one place a citation context is built and a place every caller reaches exactly once per check, gate or hook, so a document's forty citations still read `facts.yml` once while nothing survives the check that read it |
319
320
  | 0.14.1 | 2026-09-09 | `beta` | an auto gate that says what holds it and closes itself, and three derivations cut to one: measured on a real workspace at 0.14.0, the first `run auto --wait-answers 4h --wait-gates 4h` with a notify hook finished its What stage for $1.98 with every declared check green and told the owner *"waiting at a auto gate that did not close by itself — a person signs it"* — no reason, because `evaluateAutoGate` had computed exactly that sentence into a stdout line nobody was watching while `gate.requested` was appended a hundred lines BEFORE the verdict existed; the verdict is now taken one statement earlier and `why`/`held_by` ride the event to the phone (present only for an `auto` policy, since `held_by: []` on a `human` gate would read as "checked, and nothing held it"), the questions that hold a gate are notified BEFORE the gate downstream of them so the owner stops being told to sign a thing before being told what it is, and an `auto` gate whose only blocker was an open question stops permanently degrading into a `human` one — each poll re-runs the seven conditions off disk and signs through the same `approve` door `next` uses, never for `human` and never for `agent`, a person's `approve` or `reject` still landing first; a question can carry its own `Recommended: <letter> — <why> [src: …]`, parsed tolerantly so an older note reads as not recorded rather than refusing; `tldrx facts add --repo <name>` is now checked against `workspace.yml` through the ONE leaf `answer --repo` already refused on, because the unvalidated flag wrote `repos: [ghost]` at exit 0 and every prompt or filter keyed on a real repo name was then silently blind to that fact; the five phase ids are written out in one file instead of three — `PHASE_IDS`, `QUESTION_PHASES`, and an inline literal walked by `questions lint` that `QUESTION_PHASES`' own doc comment claimed could not exist — pinned from both ends, because an identical second copy and a copy that has already drifted redden different guards and neither alone is enough; a training test stops running on bun's fixed 5000 ms after a spawn probe, validated first against a file the heuristic already claimed (87 spawns recorded, so the instrument can see the thing), found it spawning three real children unclaimed by any marker, with the 14 further unclaimed files filed rather than fixed; and `release-check.sh` refuses a rewritten released section — every dated heading whose tag is present must equal `git show vX.Y.Z:CHANGELOG.md` byte for byte, three sections having drifted with nothing checking, and an amendment recorded in `CHANGELOG.amendments` must still contain the tag's section as an ordered subsequence and may only add lines that exist verbatim in its source sha, after a reviewer proved the first version of that check happily passed an invented bullet |
320
321
  | 0.14.0 | 2026-09-09 | `beta` | an `agent` gate the engine can actually close, and three computations of the same answer cut to one: `gates_policy: agent` named who MAY sign a gate but nothing in the engine produced the evidence note it is signed over — measured on 0.13.1, an owner ran `tldrx run gates set what:agent`, was told "an agent may now close it", and the loop stopped at the next gate anyway with exit 4 — so `run auto` now spawns one bounded **gate signer** when a stage's checks pass under an `agent` policy: the stage's own model and effort, a quarter of its per-agent ceiling, a tool allowance that reads anything and writes exactly one file, and a prompt carrying the stage's declared outputs, the seven `auto` conditions as measured and the §2.8 skeleton `gate template` itself renders — the note going through the UNCHANGED `approve --as-agent` path, so a refusal, a note that does not validate, a signer that wrote nothing and a signer that died are one outcome, pending for a person with the reason named on stdout and now in the `gate.requested` summary, and there is no flag to turn it on because an `agent` policy is already the owner's recorded decision; the turn is recorded like any other (`role: gate-signer`, a `run.yml` task row, a row in `tldrx cost`) and taken BEFORE the stage moves to `awaiting_gate`, because the other order had a person sign the gate the engine was mid-signing, three runs out of three; and the suite stops being run three times per change — the pre-merge reviewer now runs only the test files that cover its diff plus `typecheck`, never the full `bun test`, since the wave re-runs every gate on the MERGED tree anyway, `publish.yml` refuses unless `ci` has a `success` run for the same sha instead of recomputing typecheck/tests/build (~87 min/week of runner time, with a `cancelled` ci run failing by name and the remedy), `ci` cancels a run a newer push has already superseded (25 of 122 push runs began under 10 minutes apart), the docs deploy finally fires on `src/cli/helpText.ts` so a help-registry change stops deploying nothing while the published CLI reference goes stale, and `AGENTS.md` §2 now says out loud that a slash in a branch name is a directory, which is what the review-record gate builds |
321
322
  | 0.13.1 | 2026-09-08 | `beta` | a stage prompt that opens by saying what to do: every stage prompt now leads with a generated brief — who the reader is, which stage of which run, that the template below is to be FILLED, the exact path of every declared output, and that a question goes in the questions file rather than back to an operator who is not there — because on a real workspace at 0.13.0 a What sub-agent read its 66,452-byte prompt, found no request in it, wrote none of its six declared outputs and asked what to do, $0.29 spent; the brief is generated from the same `outputs:` list `pending.json` records, so it cannot name a path the commit will not look for, and the failure was never a regression — the spliced citation grammar grew the stage section 5,007 B → 13,180 B and the missing instruction was finally outnumbered; and the `N runs are open` nudge, the one imperative-shaped sentence in that agent's window and the one it duly answered, stops reaching sub-agents at all — `spawnAgent` marks every child it spawns and `session-start` emits nothing when it sees the marker, an absent marker still being a human's session and behaving exactly as before |
@@ -1,23 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  conflictOf
4
- } from "./chunk-djsf80bc.js";
4
+ } from "./chunk-w8qm1jbn.js";
5
5
  import {
6
6
  FactsStore,
7
7
  formatJaccard
8
- } from "./chunk-yn87xw4f.js";
8
+ } from "./chunk-mzwqb08p.js";
9
9
  import {
10
10
  parseHookInput,
11
11
  readStdin
12
- } from "./chunk-cyzm0n45.js";
12
+ } from "./chunk-tpz0zb0t.js";
13
13
  import {
14
14
  PHASE_IDS
15
15
  } from "./chunk-dzkg8c68.js";
16
16
  import {
17
17
  EventLog,
18
18
  PHASE_ID_RE
19
- } from "./chunk-ek3tsewd.js";
20
- import"./chunk-693npq3d.js";
19
+ } from "./chunk-dnat9z30.js";
20
+ import"./chunk-db7wf3hy.js";
21
21
  import {
22
22
  ADVISORY_KEY,
23
23
  MAX_FACT_CHARS,
@@ -27,7 +27,7 @@ import {
27
27
  renderQuestionBlock,
28
28
  replaceBlock,
29
29
  serializeQuestions
30
- } from "./chunk-381n8p4g.js";
30
+ } from "./chunk-bqmstq21.js";
31
31
  import {
32
32
  ITERATION_ONLY_SLOT,
33
33
  PROJECT_FRAMEWORK_DIR,
@@ -35,7 +35,7 @@ import {
35
35
  commandProbeIssues,
36
36
  factsPath,
37
37
  parseYaml
38
- } from "./chunk-xe36zh9a.js";
38
+ } from "./chunk-wwwmkey4.js";
39
39
 
40
40
  // src/hooks/answer-capture.ts
41
41
  import { existsSync as existsSync5 } from "fs";
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  budgetGateDeny
4
- } from "./chunk-vnnmbjkz.js";
4
+ } from "./chunk-dxk7eaqn.js";
5
5
  import {
6
6
  allow,
7
7
  deny,
8
8
  readPayload,
9
9
  runHook,
10
10
  toolInput
11
- } from "./chunk-5m3f8wme.js";
12
- import"./chunk-cyzm0n45.js";
11
+ } from "./chunk-gvz0rzb9.js";
12
+ import"./chunk-tpz0zb0t.js";
13
13
  import {
14
14
  asRunBudget,
15
15
  currentActor,
@@ -29,20 +29,20 @@ import {
29
29
  validateRunBudget,
30
30
  wouldExceed,
31
31
  wouldExceedHostTokens
32
- } from "./chunk-g73p9wgp.js";
32
+ } from "./chunk-fefnr71h.js";
33
33
  import {
34
34
  EventLog
35
- } from "./chunk-ek3tsewd.js";
35
+ } from "./chunk-dnat9z30.js";
36
36
  import {
37
37
  noteDeprecations
38
- } from "./chunk-381n8p4g.js";
38
+ } from "./chunk-bqmstq21.js";
39
39
  import {
40
40
  PROJECT_WORK_DIR,
41
41
  findWorkspaceRoot,
42
42
  locateWork,
43
43
  parseYaml,
44
44
  stageYamlPath
45
- } from "./chunk-xe36zh9a.js";
45
+ } from "./chunk-wwwmkey4.js";
46
46
 
47
47
  // src/hooks/budget-gate.ts
48
48
  import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "node:fs";
@@ -10,7 +10,7 @@ import {
10
10
  spentBasis,
11
11
  tallyOf,
12
12
  validateRunBudget
13
- } from "./chunk-g73p9wgp.js";
13
+ } from "./chunk-fefnr71h.js";
14
14
  import {
15
15
  EventLog,
16
16
  asRunFile,
@@ -22,7 +22,7 @@ import {
22
22
  isTerminal,
23
23
  stageAt,
24
24
  validateRunFile
25
- } from "./chunk-ek3tsewd.js";
25
+ } from "./chunk-dnat9z30.js";
26
26
  import {
27
27
  backupPathFor,
28
28
  isAlive,
@@ -31,19 +31,19 @@ import {
31
31
  workspaceRootOfRunDir,
32
32
  writeAtomic,
33
33
  yamlScalar
34
- } from "./chunk-693npq3d.js";
34
+ } from "./chunk-db7wf3hy.js";
35
35
  import {
36
36
  isAdvisory,
37
37
  noteDeprecations,
38
38
  openBlocks,
39
39
  parseQuestions
40
- } from "./chunk-381n8p4g.js";
40
+ } from "./chunk-bqmstq21.js";
41
41
  import {
42
42
  FRAMEWORK_ROOT,
43
43
  listRunDirs,
44
44
  parseYaml,
45
45
  parseYamlRepairing
46
- } from "./chunk-xe36zh9a.js";
46
+ } from "./chunk-wwwmkey4.js";
47
47
 
48
48
  // src/core/statusline/runSnapshot.ts
49
49
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
@@ -3,7 +3,7 @@ import {
3
3
  parseSrcToken,
4
4
  srcToken,
5
5
  withoutSrcToken
6
- } from "./chunk-xe36zh9a.js";
6
+ } from "./chunk-wwwmkey4.js";
7
7
 
8
8
  // src/core/text/questions.ts
9
9
  var REQUIRED_METADATA_KEYS = ["id", "status", "area", "asked_by", "asked_at"];
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PROJECT_FRAMEWORK_DIR,
3
3
  PROJECT_WORK_DIR
4
- } from "./chunk-xe36zh9a.js";
4
+ } from "./chunk-wwwmkey4.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";
@@ -10,7 +10,7 @@ import {
10
10
  requireString,
11
11
  requireVersion,
12
12
  result
13
- } from "./chunk-xe36zh9a.js";
13
+ } from "./chunk-wwwmkey4.js";
14
14
 
15
15
  // src/core/events/EventLog.ts
16
16
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
@@ -3,7 +3,7 @@ import {
3
3
  EMPTY_SECTION_RULE,
4
4
  noneBullet,
5
5
  srcRule
6
- } from "./chunk-xe36zh9a.js";
6
+ } from "./chunk-wwwmkey4.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>]";
@@ -23,7 +23,7 @@ import {
23
23
  requireVersion,
24
24
  requireVersion1,
25
25
  result
26
- } from "./chunk-xe36zh9a.js";
26
+ } from "./chunk-wwwmkey4.js";
27
27
 
28
28
  // src/hooks/lib/runFile.ts
29
29
  import { existsSync, readFileSync } from "node:fs";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseHookInput,
3
3
  readStdin
4
- } from "./chunk-cyzm0n45.js";
4
+ } from "./chunk-tpz0zb0t.js";
5
5
 
6
6
  // src/hooks/lib/decide.ts
7
7
  function deny(reason) {
@@ -3,7 +3,7 @@ import {
3
3
  withWorkspaceLock,
4
4
  workspaceRootOfFactsPath,
5
5
  writeAtomic
6
- } from "./chunk-693npq3d.js";
6
+ } from "./chunk-db7wf3hy.js";
7
7
  import {
8
8
  FACT_CONFIDENCES,
9
9
  FACT_KINDS,
@@ -14,7 +14,7 @@ import {
14
14
  isLive,
15
15
  isRetired,
16
16
  noteDeprecations
17
- } from "./chunk-381n8p4g.js";
17
+ } from "./chunk-bqmstq21.js";
18
18
  import {
19
19
  SRC_PATTERNS,
20
20
  asDocument,
@@ -27,7 +27,7 @@ import {
27
27
  requireString,
28
28
  requireVersion,
29
29
  result
30
- } from "./chunk-xe36zh9a.js";
30
+ } from "./chunk-wwwmkey4.js";
31
31
 
32
32
  // src/core/facts/FactsStore.ts
33
33
  import { existsSync, readFileSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  toolInput
3
- } from "./chunk-5m3f8wme.js";
3
+ } from "./chunk-gvz0rzb9.js";
4
4
 
5
5
  // src/hooks/lib/wouldBe.ts
6
6
  import { existsSync, readFileSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runtime
3
- } from "./chunk-xe36zh9a.js";
3
+ } from "./chunk-wwwmkey4.js";
4
4
 
5
5
  // src/core/hooks/passthrough.ts
6
6
  async function readStdin() {
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  findDuplicate
3
- } from "./chunk-yn87xw4f.js";
3
+ } from "./chunk-mzwqb08p.js";
4
4
  import {
5
5
  evidencePath,
6
6
  gateEvidencePath,
7
7
  parseEvidence
8
- } from "./chunk-ek3tsewd.js";
8
+ } from "./chunk-dnat9z30.js";
9
9
  import {
10
10
  openBlocks,
11
11
  parseQuestions
12
- } from "./chunk-381n8p4g.js";
12
+ } from "./chunk-bqmstq21.js";
13
13
 
14
14
  // src/core/distill/distill.ts
15
15
  var CONFLICT_THRESHOLD = 0.6;
@@ -7756,268 +7756,13 @@ function lastMeaningfulLine(output, max = 200) {
7756
7756
  }
7757
7757
 
7758
7758
  // src/hooks/lib/workspace.ts
7759
- import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "node:fs";
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
7789
- var RUN_YML = "run.yml";
7790
- function locateWork(filePath) {
7791
- if (filePath === "")
7792
- return null;
7793
- const abs = isAbsolute(filePath) ? filePath : resolve(filePath);
7794
- const parts = abs.split(sep);
7795
- const at = parts.lastIndexOf(PROJECT_WORK_DIR);
7796
- if (at === -1 || at + 1 >= parts.length)
7797
- return null;
7798
- const run = parts[at + 1] ?? "";
7799
- if (run === "")
7800
- return null;
7801
- return {
7802
- root: parts.slice(0, at).join(sep) || sep,
7803
- runDir: parts.slice(0, at + 2).join(sep),
7804
- run,
7805
- relative: parts.slice(at + 2).join("/")
7806
- };
7807
- }
7808
- function findWorkspaceRoot(start) {
7809
- let current = isAbsolute(start) ? start : resolve(start);
7810
- for (let i = 0;i < 64; i++) {
7811
- if (existsSync2(join2(current, PROJECT_FRAMEWORK_DIR)))
7812
- return current;
7813
- const parent = dirname3(current);
7814
- if (parent === current)
7815
- return null;
7816
- current = parent;
7817
- }
7818
- return null;
7819
- }
7820
- var FALLBACK_DEFAULT_BRANCH = "main";
7821
- function commandRolesOf(doc) {
7822
- const out = new Map;
7823
- const list = doc?.repos;
7824
- if (!Array.isArray(list))
7825
- return out;
7826
- for (const entry of list) {
7827
- if (typeof entry?.name !== "string")
7828
- continue;
7829
- const roles = new Map;
7830
- const cmds = entry.commands;
7831
- if (cmds !== null && typeof cmds === "object") {
7832
- for (const [role, value] of Object.entries(cmds)) {
7833
- if (typeof value !== "string" || value.trim() === "")
7834
- continue;
7835
- roles.set(role, value);
7836
- }
7837
- }
7838
- out.set(entry.name, roles);
7839
- }
7840
- return out;
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
- }
7863
- function loadWorkspace(root) {
7864
- const repos = new Map;
7865
- const commands = new Set;
7866
- const repoCommands = new Map;
7867
- const commandRoles = new Map;
7868
- const commandProbes = new Map;
7869
- const iterationCommands = new Set;
7870
- const defaultBranches = new Map;
7871
- let seedTriageThresholdTokens = null;
7872
- const empty = () => ({
7873
- root,
7874
- repos,
7875
- commands,
7876
- repoCommands,
7877
- commandRoles,
7878
- commandProbes,
7879
- iterationCommands,
7880
- defaultBranches,
7881
- seedTriageThresholdTokens
7882
- });
7883
- const path = join2(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
7884
- if (!existsSync2(path))
7885
- return empty();
7886
- let doc;
7887
- try {
7888
- doc = parseYaml(readFileSync(path, "utf8"));
7889
- } catch {
7890
- return empty();
7891
- }
7892
- const triage = doc?.seed_triage;
7893
- if (triage !== null && typeof triage === "object") {
7894
- const tokens = triage.threshold_tokens;
7895
- if (typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0) {
7896
- seedTriageThresholdTokens = tokens;
7897
- }
7898
- }
7899
- const list = doc?.repos;
7900
- if (!Array.isArray(list))
7901
- return empty();
7902
- const declared = commandRolesOf(doc);
7903
- const measured = commandProbesOf(doc);
7904
- for (const entry of list) {
7905
- if (typeof entry?.name !== "string")
7906
- continue;
7907
- repos.set(entry.name, typeof entry.path === "string" ? entry.path : ".");
7908
- defaultBranches.set(entry.name, typeof entry.default_branch === "string" && entry.default_branch !== "" ? entry.default_branch : FALLBACK_DEFAULT_BRANCH);
7909
- const own = [];
7910
- const roles = declared.get(entry.name) ?? new Map;
7911
- for (const value of roles.values()) {
7912
- commands.add(value);
7913
- if (!own.includes(value))
7914
- own.push(value);
7915
- }
7916
- repoCommands.set(entry.name, own);
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);
7922
- }
7923
- return empty();
7924
- }
7925
- function toSrcContext(workspace, runDir, options = {}) {
7926
- return {
7927
- root: workspace.root,
7928
- repos: workspace.repos,
7929
- commands: workspace.commands,
7930
- runDir: runDir ?? null,
7931
- epicWorktrees: epicWorktreesOf(workspace, runDir ?? null),
7932
- epicRefs: options.epicRefs === true ? epicRefsOf(workspace, runDir ?? null) : []
7933
- };
7934
- }
7935
- function epicRefsOf(workspace, runDir) {
7936
- if (runDir === null || runDir === "")
7937
- return [];
7938
- const path = join2(runDir, RUN_YML);
7939
- if (!existsSync2(path))
7940
- return [];
7941
- let doc;
7942
- try {
7943
- doc = parseYaml(readFileSync(path, "utf8"));
7944
- } catch {
7945
- return [];
7946
- }
7947
- const recorded = doc?.build?.epic_branch;
7948
- if (!Array.isArray(recorded))
7949
- return [];
7950
- const branches = [];
7951
- for (const entry of recorded) {
7952
- if (typeof entry === "string" && entry !== "" && !branches.includes(entry))
7953
- branches.push(entry);
7954
- }
7955
- if (branches.length === 0)
7956
- return [];
7957
- const refs = [];
7958
- for (const [repo, rel] of workspace.repos) {
7959
- const dir = join2(workspace.root, rel);
7960
- for (const ref of branches)
7961
- refs.push({ repo, dir, ref });
7962
- }
7963
- return refs;
7964
- }
7965
- function epicWorktreesOf(workspace, runDir) {
7966
- if (runDir === null || runDir === "")
7967
- return [];
7968
- const runId = basename(runDir);
7969
- if (runId === "")
7970
- return [];
7971
- const trees = [];
7972
- for (const repo of workspace.repos.keys()) {
7973
- const dir = join2(workspace.root, PROJECT_FRAMEWORK_DIR, PROJECT_WORKTREES_DIR, repo);
7974
- if (!existsSync2(dir))
7975
- continue;
7976
- let entries;
7977
- try {
7978
- entries = readdirSync(dir);
7979
- } catch {
7980
- continue;
7981
- }
7982
- for (const name of entries.slice().sort()) {
7983
- if (!isEpicWorktreeOf(name, runId))
7984
- continue;
7985
- const abs = join2(dir, name);
7986
- try {
7987
- if (statSync2(abs).isDirectory())
7988
- trees.push({ repo, dir: abs });
7989
- } catch {}
7990
- }
7991
- }
7992
- return trees;
7993
- }
7994
- function repoPath(workspace, name) {
7995
- const rel = workspace.repos.get(name);
7996
- return rel === undefined ? null : join2(workspace.root, rel);
7997
- }
7998
- function factsPath(root) {
7999
- return join2(root, PROJECT_FRAMEWORK_DIR, "memory", "facts.yml");
8000
- }
8001
- function stageYamlPath(root, stage) {
8002
- return join2(root, PROJECT_FRAMEWORK_DIR, "stages", stage, "stage.yml");
8003
- }
8004
- function listRunDirs(root) {
8005
- const work = join2(root, PROJECT_WORK_DIR);
8006
- if (!existsSync2(work))
8007
- return [];
8008
- const dirs = [];
8009
- for (const entry of readdirSync(work)) {
8010
- const dir = join2(work, entry);
8011
- if (existsSync2(join2(dir, RUN_YML)) && statSync2(dir).isDirectory())
8012
- dirs.push(dir);
8013
- }
8014
- return dirs.sort().reverse();
8015
- }
7759
+ import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
7760
+ import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join3, resolve, sep as sep2 } from "node:path";
8016
7761
 
8017
7762
  // src/core/text/srcToken.ts
8018
7763
  import { spawnSync } from "node:child_process";
8019
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
8020
- import { isAbsolute as isAbsolute2, join as join3, normalize, relative, sep as sep2 } from "node:path";
7764
+ import { existsSync as existsSync2, readdirSync, readFileSync, statSync as statSync2 } from "node:fs";
7765
+ import { isAbsolute, join as join2, normalize, relative, sep } from "node:path";
8021
7766
  var LINE_RE = /^\d{1,9}$/;
8022
7767
  var LINE_RANGE_RE = /^(\d{1,9})-(\d{1,9})$/;
8023
7768
  var ANSWER_RE = /^Q\d{1,6}$/;
@@ -8359,7 +8104,7 @@ function resolveSrc(ref, ctx, section, claim = "") {
8359
8104
  const bases = pathBases(ref.repo, ref.path, ctx);
8360
8105
  let short = null;
8361
8106
  for (const base of bases) {
8362
- if (!existsSync3(base.abs) || !statSync3(base.abs).isFile())
8107
+ if (!existsSync2(base.abs) || !statSync2(base.abs).isFile())
8363
8108
  continue;
8364
8109
  const lines = countLines(base.abs);
8365
8110
  const highest = ref.endLine ?? ref.startLine;
@@ -8480,7 +8225,7 @@ var EMPTY_PROBE_MAX_BYTES = 64 * 1024;
8480
8225
  function searchForNeedle(abs, needle, path) {
8481
8226
  let stat;
8482
8227
  try {
8483
- stat = statSync3(abs);
8228
+ stat = statSync2(abs);
8484
8229
  } catch {
8485
8230
  return OK;
8486
8231
  }
@@ -8492,7 +8237,7 @@ function searchForNeedle(abs, needle, path) {
8492
8237
  }
8493
8238
  let text;
8494
8239
  try {
8495
- text = readFileSync2(abs, "utf8");
8240
+ text = readFileSync(abs, "utf8");
8496
8241
  } catch {
8497
8242
  return noted(`${path} could not be read here to search for \`${needle}\``);
8498
8243
  }
@@ -8509,14 +8254,14 @@ function searchForNeedle(abs, needle, path) {
8509
8254
  function whatIsThere(abs) {
8510
8255
  let stat;
8511
8256
  try {
8512
- stat = statSync3(abs);
8257
+ stat = statSync2(abs);
8513
8258
  } catch {
8514
8259
  return null;
8515
8260
  }
8516
8261
  if (stat.isDirectory()) {
8517
8262
  let entries;
8518
8263
  try {
8519
- entries = readdirSync2(abs);
8264
+ entries = readdirSync(abs);
8520
8265
  } catch {
8521
8266
  return "a directory this check could not read";
8522
8267
  }
@@ -8532,7 +8277,7 @@ function whatIsThere(abs) {
8532
8277
  return `${String(stat.size)} bytes`;
8533
8278
  let text;
8534
8279
  try {
8535
- text = readFileSync2(abs, "utf8");
8280
+ text = readFileSync(abs, "utf8");
8536
8281
  } catch {
8537
8282
  return `${String(stat.size)} bytes`;
8538
8283
  }
@@ -8553,7 +8298,7 @@ function absentBases(path, ctx) {
8553
8298
  }
8554
8299
  function firstExisting(bases) {
8555
8300
  for (const base of bases) {
8556
- if (existsSync3(base.abs))
8301
+ if (existsSync2(base.abs))
8557
8302
  return base.abs;
8558
8303
  }
8559
8304
  return null;
@@ -8563,14 +8308,14 @@ function isNegativeClaim(text) {
8563
8308
  return NEGATIVE_RE.test(text);
8564
8309
  }
8565
8310
  function pathBases(repo, path, ctx) {
8566
- if (isAbsolute2(path)) {
8311
+ if (isAbsolute(path)) {
8567
8312
  return [{ label: "the absolute path as written", abs: normalize(path) }];
8568
8313
  }
8569
8314
  if (repo !== null) {
8570
8315
  const rel2 = ctx.repos.get(repo) ?? "";
8571
8316
  return [
8572
8317
  ...epicBases(ctx, repo, path),
8573
- { label: repoLabel(repo, rel2), abs: normalize(join3(ctx.root, rel2, path)) }
8318
+ { label: repoLabel(repo, rel2), abs: normalize(join2(ctx.root, rel2, path)) }
8574
8319
  ];
8575
8320
  }
8576
8321
  const bases = [];
@@ -8579,15 +8324,15 @@ function pathBases(repo, path, ctx) {
8579
8324
  const rel = named === "" ? undefined : ctx.repos.get(named);
8580
8325
  if (rel !== undefined)
8581
8326
  bases.push(...epicBases(ctx, named, path.slice(slash + 1)));
8582
- bases.push({ label: "workspace root", abs: normalize(join3(ctx.root, path)) });
8327
+ bases.push({ label: "workspace root", abs: normalize(join2(ctx.root, path)) });
8583
8328
  const runDir = ctx.runDir ?? null;
8584
8329
  if (runDir !== null && runDir !== "") {
8585
- bases.push({ label: `run dir ${displayPath(ctx.root, runDir)}`, abs: normalize(join3(runDir, path)) });
8330
+ bases.push({ label: `run dir ${displayPath(ctx.root, runDir)}`, abs: normalize(join2(runDir, path)) });
8586
8331
  }
8587
8332
  if (rel !== undefined) {
8588
8333
  bases.push({
8589
8334
  label: repoLabel(named, rel),
8590
- abs: normalize(join3(ctx.root, rel, path.slice(slash + 1)))
8335
+ abs: normalize(join2(ctx.root, rel, path.slice(slash + 1)))
8591
8336
  });
8592
8337
  }
8593
8338
  return bases;
@@ -8600,7 +8345,7 @@ function epicBases(ctx, repo, path) {
8600
8345
  continue;
8601
8346
  bases.push({
8602
8347
  label: `epic worktree ${displayPath(ctx.root, tree.dir)}`,
8603
- abs: normalize(join3(tree.dir, path)),
8348
+ abs: normalize(join2(tree.dir, path)),
8604
8349
  unmerged: `the epic worktree ${displayPath(ctx.root, tree.dir)}`
8605
8350
  });
8606
8351
  }
@@ -8612,13 +8357,13 @@ function repoLabel(name, rel) {
8612
8357
  }
8613
8358
  function displayPath(root, dir) {
8614
8359
  const rel = relative(root, dir);
8615
- return rel === "" || rel.startsWith("..") ? dir : rel.split(sep2).join("/");
8360
+ return rel === "" || rel.startsWith("..") ? dir : rel.split(sep).join("/");
8616
8361
  }
8617
8362
  function countLines(path) {
8618
8363
  const cached = lineCountCache.get(path);
8619
8364
  if (cached !== undefined)
8620
8365
  return cached;
8621
- const count = countTextLines(readFileSync2(path, "utf8"));
8366
+ const count = countTextLines(readFileSync(path, "utf8"));
8622
8367
  lineCountCache.set(path, count);
8623
8368
  return count;
8624
8369
  }
@@ -8684,7 +8429,7 @@ function resolveOnEpicRefs(ref, ctx) {
8684
8429
  }
8685
8430
  function epicTargets(ref, ctx) {
8686
8431
  const refs = ctx.epicRefs ?? [];
8687
- if (refs.length === 0 || isAbsolute2(ref.path))
8432
+ if (refs.length === 0 || isAbsolute(ref.path))
8688
8433
  return [];
8689
8434
  const out = [];
8690
8435
  const push = (repo, path) => {
@@ -8727,17 +8472,17 @@ function loadFacts(root) {
8727
8472
  const cached = factsCache.get(root);
8728
8473
  if (cached !== undefined)
8729
8474
  return cached;
8730
- const index = readFacts(join3(root, PROJECT_FRAMEWORK_DIR, "memory", "facts.yml"));
8475
+ const index = readFacts(join2(root, PROJECT_FRAMEWORK_DIR, "memory", "facts.yml"));
8731
8476
  factsCache.set(root, index);
8732
8477
  return index;
8733
8478
  }
8734
8479
  function readFacts(path) {
8735
- if (!existsSync3(path))
8480
+ if (!existsSync2(path))
8736
8481
  return null;
8737
8482
  const live2 = new Set;
8738
8483
  const retired = new Set;
8739
8484
  try {
8740
- const doc = parseYaml(readFileSync2(path, "utf8"));
8485
+ const doc = parseYaml(readFileSync(path, "utf8"));
8741
8486
  const rows = doc?.facts;
8742
8487
  if (!Array.isArray(rows))
8743
8488
  return { live: live2, retired };
@@ -8787,10 +8532,10 @@ function loadGraphNodes(root) {
8787
8532
  return ids;
8788
8533
  }
8789
8534
  function readGraphNodes(root) {
8790
- const roots = [join3(root, PROJECT_FRAMEWORK_DIR, "graphify-out"), join3(root, "graphify-out")];
8535
+ const roots = [join2(root, PROJECT_FRAMEWORK_DIR, "graphify-out"), join2(root, "graphify-out")];
8791
8536
  const files = [];
8792
8537
  for (const dir of roots) {
8793
- if (!existsSync3(dir))
8538
+ if (!existsSync2(dir))
8794
8539
  continue;
8795
8540
  files.push(...findFiles(dir, (name) => name === "graph.json", MAX_SCAN_DEPTH));
8796
8541
  }
@@ -8820,8 +8565,8 @@ function loadMapTokens(root) {
8820
8565
  const cached = mapCache.get(root);
8821
8566
  if (cached !== undefined)
8822
8567
  return cached;
8823
- const dir = join3(root, PROJECT_FRAMEWORK_DIR, "map");
8824
- if (!existsSync3(dir)) {
8568
+ const dir = join2(root, PROJECT_FRAMEWORK_DIR, "map");
8569
+ if (!existsSync2(dir)) {
8825
8570
  mapCache.set(root, null);
8826
8571
  return null;
8827
8572
  }
@@ -8843,13 +8588,13 @@ function loadDeclaredUrls(ctx) {
8843
8588
  const url = /https:\/\/[^\s)\]"'`<>]+/g;
8844
8589
  const runDir = ctx.runDir ?? "";
8845
8590
  const dirs = [
8846
- join3(ctx.root, PROJECT_FRAMEWORK_DIR, "map"),
8847
- join3(ctx.root, PROJECT_FRAMEWORK_DIR, "knowledge"),
8848
- join3(ctx.root, PROJECT_FRAMEWORK_DIR, "experts"),
8591
+ join2(ctx.root, PROJECT_FRAMEWORK_DIR, "map"),
8592
+ join2(ctx.root, PROJECT_FRAMEWORK_DIR, "knowledge"),
8593
+ join2(ctx.root, PROJECT_FRAMEWORK_DIR, "experts"),
8849
8594
  ...runDir === "" ? [] : [runDir]
8850
8595
  ];
8851
8596
  for (const dir of dirs) {
8852
- if (!existsSync3(dir))
8597
+ if (!existsSync2(dir))
8853
8598
  continue;
8854
8599
  for (const file of findFiles(dir, (name) => name.endsWith(".md") && name !== "handoff.md", MAX_SCAN_DEPTH)) {
8855
8600
  for (const match of readCapped(file).matchAll(url)) {
@@ -8867,7 +8612,7 @@ function findFiles(dir, accept, depth) {
8867
8612
  return;
8868
8613
  let entries;
8869
8614
  try {
8870
- entries = readdirSync2(current);
8615
+ entries = readdirSync(current);
8871
8616
  } catch {
8872
8617
  return;
8873
8618
  }
@@ -8878,10 +8623,10 @@ function findFiles(dir, accept, depth) {
8878
8623
  continue;
8879
8624
  if (name === "node_modules")
8880
8625
  continue;
8881
- const abs = join3(current, name);
8626
+ const abs = join2(current, name);
8882
8627
  let isDir;
8883
8628
  try {
8884
- isDir = statSync3(abs).isDirectory();
8629
+ isDir = statSync2(abs).isDirectory();
8885
8630
  } catch {
8886
8631
  continue;
8887
8632
  }
@@ -8896,12 +8641,276 @@ function findFiles(dir, accept, depth) {
8896
8641
  }
8897
8642
  function readCapped(path) {
8898
8643
  try {
8899
- const text = readFileSync2(path, "utf8");
8644
+ const text = readFileSync(path, "utf8");
8900
8645
  return text.length > MAX_SCAN_BYTES ? text.slice(0, MAX_SCAN_BYTES) : text;
8901
8646
  } catch {
8902
8647
  return "";
8903
8648
  }
8904
8649
  }
8650
+ function refreshSrcIndexes() {
8651
+ lineCountCache.clear();
8652
+ factsCache.clear();
8653
+ questionsCache.clear();
8654
+ graphCache.clear();
8655
+ mapCache.clear();
8656
+ urlCache.clear();
8657
+ }
8658
+
8659
+ // src/core/detect/probeCommands.ts
8660
+ var PROBE_STATUSES = ["ok", "failed", "timed-out", "unspawnable", "not-probed", "skipped"];
8661
+
8662
+ // src/core/schemas/workspace.ts
8663
+ function commandProbeIssues(probe, path) {
8664
+ if (!isRecord(probe))
8665
+ return [{ path, message: "expected a mapping" }];
8666
+ const issues = [];
8667
+ if (!PROBE_STATUSES.includes(probe.status)) {
8668
+ issues.push({ path: `${path}.status`, message: `expected one of ${PROBE_STATUSES.join(" | ")}` });
8669
+ }
8670
+ if (typeof probe.verified !== "boolean") {
8671
+ issues.push({ path: `${path}.verified`, message: "expected a boolean" });
8672
+ }
8673
+ if (probe.exit_code !== null && typeof probe.exit_code !== "number") {
8674
+ issues.push({ path: `${path}.exit_code`, message: "expected a number or null" });
8675
+ }
8676
+ if (typeof probe.at !== "string" || probe.at === "") {
8677
+ issues.push({ path: `${path}.at`, message: "expected a non-empty RFC3339 string" });
8678
+ }
8679
+ if (typeof probe.reason !== "string" || probe.reason === "") {
8680
+ issues.push({ path: `${path}.reason`, message: "expected a non-empty sentence saying why" });
8681
+ }
8682
+ return issues;
8683
+ }
8684
+
8685
+ // src/hooks/lib/workspace.ts
8686
+ var RUN_YML = "run.yml";
8687
+ function locateWork(filePath) {
8688
+ if (filePath === "")
8689
+ return null;
8690
+ const abs = isAbsolute2(filePath) ? filePath : resolve(filePath);
8691
+ const parts = abs.split(sep2);
8692
+ const at = parts.lastIndexOf(PROJECT_WORK_DIR);
8693
+ if (at === -1 || at + 1 >= parts.length)
8694
+ return null;
8695
+ const run = parts[at + 1] ?? "";
8696
+ if (run === "")
8697
+ return null;
8698
+ return {
8699
+ root: parts.slice(0, at).join(sep2) || sep2,
8700
+ runDir: parts.slice(0, at + 2).join(sep2),
8701
+ run,
8702
+ relative: parts.slice(at + 2).join("/")
8703
+ };
8704
+ }
8705
+ function findWorkspaceRoot(start) {
8706
+ let current = isAbsolute2(start) ? start : resolve(start);
8707
+ for (let i = 0;i < 64; i++) {
8708
+ if (existsSync3(join3(current, PROJECT_FRAMEWORK_DIR)))
8709
+ return current;
8710
+ const parent = dirname3(current);
8711
+ if (parent === current)
8712
+ return null;
8713
+ current = parent;
8714
+ }
8715
+ return null;
8716
+ }
8717
+ var FALLBACK_DEFAULT_BRANCH = "main";
8718
+ function commandRolesOf(doc) {
8719
+ const out = new Map;
8720
+ const list = doc?.repos;
8721
+ if (!Array.isArray(list))
8722
+ return out;
8723
+ for (const entry of list) {
8724
+ if (typeof entry?.name !== "string")
8725
+ continue;
8726
+ const roles = new Map;
8727
+ const cmds = entry.commands;
8728
+ if (cmds !== null && typeof cmds === "object") {
8729
+ for (const [role, value] of Object.entries(cmds)) {
8730
+ if (typeof value !== "string" || value.trim() === "")
8731
+ continue;
8732
+ roles.set(role, value);
8733
+ }
8734
+ }
8735
+ out.set(entry.name, roles);
8736
+ }
8737
+ return out;
8738
+ }
8739
+ function commandProbesOf(doc) {
8740
+ const out = new Map;
8741
+ const list = doc?.repos;
8742
+ if (!Array.isArray(list))
8743
+ return out;
8744
+ for (const entry of list) {
8745
+ if (typeof entry?.name !== "string")
8746
+ continue;
8747
+ const probes = new Map;
8748
+ const raw = entry.command_probes;
8749
+ if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
8750
+ for (const [slot, value] of Object.entries(raw)) {
8751
+ if (commandProbeIssues(value, slot).length > 0)
8752
+ continue;
8753
+ probes.set(slot, value);
8754
+ }
8755
+ }
8756
+ out.set(entry.name, probes);
8757
+ }
8758
+ return out;
8759
+ }
8760
+ function loadWorkspace(root) {
8761
+ const repos = new Map;
8762
+ const commands = new Set;
8763
+ const repoCommands = new Map;
8764
+ const commandRoles = new Map;
8765
+ const commandProbes = new Map;
8766
+ const iterationCommands = new Set;
8767
+ const defaultBranches = new Map;
8768
+ let seedTriageThresholdTokens = null;
8769
+ const empty = () => ({
8770
+ root,
8771
+ repos,
8772
+ commands,
8773
+ repoCommands,
8774
+ commandRoles,
8775
+ commandProbes,
8776
+ iterationCommands,
8777
+ defaultBranches,
8778
+ seedTriageThresholdTokens
8779
+ });
8780
+ const path = join3(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
8781
+ if (!existsSync3(path))
8782
+ return empty();
8783
+ let doc;
8784
+ try {
8785
+ doc = parseYaml(readFileSync2(path, "utf8"));
8786
+ } catch {
8787
+ return empty();
8788
+ }
8789
+ const triage = doc?.seed_triage;
8790
+ if (triage !== null && typeof triage === "object") {
8791
+ const tokens = triage.threshold_tokens;
8792
+ if (typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0) {
8793
+ seedTriageThresholdTokens = tokens;
8794
+ }
8795
+ }
8796
+ const list = doc?.repos;
8797
+ if (!Array.isArray(list))
8798
+ return empty();
8799
+ const declared = commandRolesOf(doc);
8800
+ const measured = commandProbesOf(doc);
8801
+ for (const entry of list) {
8802
+ if (typeof entry?.name !== "string")
8803
+ continue;
8804
+ repos.set(entry.name, typeof entry.path === "string" ? entry.path : ".");
8805
+ defaultBranches.set(entry.name, typeof entry.default_branch === "string" && entry.default_branch !== "" ? entry.default_branch : FALLBACK_DEFAULT_BRANCH);
8806
+ const own = [];
8807
+ const roles = declared.get(entry.name) ?? new Map;
8808
+ for (const value of roles.values()) {
8809
+ commands.add(value);
8810
+ if (!own.includes(value))
8811
+ own.push(value);
8812
+ }
8813
+ repoCommands.set(entry.name, own);
8814
+ commandRoles.set(entry.name, roles);
8815
+ const fast = roles.get(ITERATION_ONLY_SLOT);
8816
+ if (fast !== undefined)
8817
+ iterationCommands.add(fast);
8818
+ commandProbes.set(entry.name, measured.get(entry.name) ?? new Map);
8819
+ }
8820
+ return empty();
8821
+ }
8822
+ function toSrcContext(workspace, runDir, options = {}) {
8823
+ refreshSrcIndexes();
8824
+ return {
8825
+ root: workspace.root,
8826
+ repos: workspace.repos,
8827
+ commands: workspace.commands,
8828
+ runDir: runDir ?? null,
8829
+ epicWorktrees: epicWorktreesOf(workspace, runDir ?? null),
8830
+ epicRefs: options.epicRefs === true ? epicRefsOf(workspace, runDir ?? null) : []
8831
+ };
8832
+ }
8833
+ function epicRefsOf(workspace, runDir) {
8834
+ if (runDir === null || runDir === "")
8835
+ return [];
8836
+ const path = join3(runDir, RUN_YML);
8837
+ if (!existsSync3(path))
8838
+ return [];
8839
+ let doc;
8840
+ try {
8841
+ doc = parseYaml(readFileSync2(path, "utf8"));
8842
+ } catch {
8843
+ return [];
8844
+ }
8845
+ const recorded = doc?.build?.epic_branch;
8846
+ if (!Array.isArray(recorded))
8847
+ return [];
8848
+ const branches = [];
8849
+ for (const entry of recorded) {
8850
+ if (typeof entry === "string" && entry !== "" && !branches.includes(entry))
8851
+ branches.push(entry);
8852
+ }
8853
+ if (branches.length === 0)
8854
+ return [];
8855
+ const refs = [];
8856
+ for (const [repo, rel] of workspace.repos) {
8857
+ const dir = join3(workspace.root, rel);
8858
+ for (const ref of branches)
8859
+ refs.push({ repo, dir, ref });
8860
+ }
8861
+ return refs;
8862
+ }
8863
+ function epicWorktreesOf(workspace, runDir) {
8864
+ if (runDir === null || runDir === "")
8865
+ return [];
8866
+ const runId = basename(runDir);
8867
+ if (runId === "")
8868
+ return [];
8869
+ const trees = [];
8870
+ for (const repo of workspace.repos.keys()) {
8871
+ const dir = join3(workspace.root, PROJECT_FRAMEWORK_DIR, PROJECT_WORKTREES_DIR, repo);
8872
+ if (!existsSync3(dir))
8873
+ continue;
8874
+ let entries;
8875
+ try {
8876
+ entries = readdirSync2(dir);
8877
+ } catch {
8878
+ continue;
8879
+ }
8880
+ for (const name of entries.slice().sort()) {
8881
+ if (!isEpicWorktreeOf(name, runId))
8882
+ continue;
8883
+ const abs = join3(dir, name);
8884
+ try {
8885
+ if (statSync3(abs).isDirectory())
8886
+ trees.push({ repo, dir: abs });
8887
+ } catch {}
8888
+ }
8889
+ }
8890
+ return trees;
8891
+ }
8892
+ function repoPath(workspace, name) {
8893
+ const rel = workspace.repos.get(name);
8894
+ return rel === undefined ? null : join3(workspace.root, rel);
8895
+ }
8896
+ function factsPath(root) {
8897
+ return join3(root, PROJECT_FRAMEWORK_DIR, "memory", "facts.yml");
8898
+ }
8899
+ function stageYamlPath(root, stage) {
8900
+ return join3(root, PROJECT_FRAMEWORK_DIR, "stages", stage, "stage.yml");
8901
+ }
8902
+ function listRunDirs(root) {
8903
+ const work = join3(root, PROJECT_WORK_DIR);
8904
+ if (!existsSync3(work))
8905
+ return [];
8906
+ const dirs = [];
8907
+ for (const entry of readdirSync2(work)) {
8908
+ const dir = join3(work, entry);
8909
+ if (existsSync3(join3(dir, RUN_YML)) && statSync3(dir).isDirectory())
8910
+ dirs.push(dir);
8911
+ }
8912
+ return dirs.sort().reverse();
8913
+ }
8905
8914
 
8906
8915
  // src/core/text/handoff.ts
8907
8916
  var HANDOFF_SECTIONS = ["Findings", "Decisions", "Unknowns", "Evidence ledger"];
@@ -9129,4 +9138,4 @@ function noneBullet(lookedAt) {
9129
9138
  return `- none [src: absent:${lookedAt}]`;
9130
9139
  }
9131
9140
 
9132
- export { runtime, parseYaml, parseYamlRepairing, FRAMEWORK_ROOT, 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, SRC_SEPARATOR, srcToken, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, withoutSrcToken, classifySrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
9141
+ export { runtime, parseYaml, parseYamlRepairing, FRAMEWORK_ROOT, STAGES_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, SRC_SEPARATOR, srcToken, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, withoutSrcToken, classifySrc, 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, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  wouldBeContent
4
- } from "./chunk-a0y3ze46.js";
4
+ } from "./chunk-r95h9ynf.js";
5
5
  import {
6
6
  claimSourcesDeny,
7
7
  claimSourcesEmptySectionDeny,
8
8
  claimSourcesMalformedDeny,
9
9
  claimSourcesUnresolvedDeny
10
- } from "./chunk-vnnmbjkz.js";
10
+ } from "./chunk-dxk7eaqn.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-5m3f8wme.js";
20
- import"./chunk-cyzm0n45.js";
19
+ } from "./chunk-gvz0rzb9.js";
20
+ import"./chunk-tpz0zb0t.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-xe36zh9a.js";
28
+ } from "./chunk-wwwmkey4.js";
29
29
 
30
30
  // src/hooks/claim-sources.ts
31
31
  await runHook("claim-sources", async () => {
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  wouldBeContent
4
- } from "./chunk-a0y3ze46.js";
4
+ } from "./chunk-r95h9ynf.js";
5
5
  import {
6
6
  dodGateDeny,
7
7
  dodGateInternalErrorDeny,
8
8
  dodGateMissingBlockDeny,
9
9
  dodGateRefusedCommandDeny
10
- } from "./chunk-vnnmbjkz.js";
10
+ } from "./chunk-dxk7eaqn.js";
11
11
  import {
12
12
  allow,
13
13
  deny,
@@ -15,8 +15,8 @@ import {
15
15
  filePathOf,
16
16
  isWriteOrEdit,
17
17
  readPayload
18
- } from "./chunk-5m3f8wme.js";
19
- import"./chunk-cyzm0n45.js";
18
+ } from "./chunk-gvz0rzb9.js";
19
+ import"./chunk-tpz0zb0t.js";
20
20
  import {
21
21
  DodCommandRefused,
22
22
  loadWorkspace,
@@ -24,7 +24,7 @@ import {
24
24
  readStory,
25
25
  repoPath,
26
26
  runDodCommand
27
- } from "./chunk-xe36zh9a.js";
27
+ } from "./chunk-wwwmkey4.js";
28
28
 
29
29
  // src/hooks/dod-gate.ts
30
30
  import { basename, join } from "node:path";
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  wouldBeContent
4
- } from "./chunk-a0y3ze46.js";
4
+ } from "./chunk-r95h9ynf.js";
5
5
  import {
6
6
  noReAskDeny
7
- } from "./chunk-vnnmbjkz.js";
7
+ } from "./chunk-dxk7eaqn.js";
8
8
  import {
9
9
  FactsStore
10
- } from "./chunk-yn87xw4f.js";
10
+ } from "./chunk-mzwqb08p.js";
11
11
  import {
12
12
  allow,
13
13
  deny,
@@ -15,17 +15,17 @@ import {
15
15
  isWriteOrEdit,
16
16
  readPayload,
17
17
  runHook
18
- } from "./chunk-5m3f8wme.js";
19
- import"./chunk-cyzm0n45.js";
20
- import"./chunk-693npq3d.js";
18
+ } from "./chunk-gvz0rzb9.js";
19
+ import"./chunk-tpz0zb0t.js";
20
+ import"./chunk-db7wf3hy.js";
21
21
  import {
22
22
  openBlocks,
23
23
  parseQuestions
24
- } from "./chunk-381n8p4g.js";
24
+ } from "./chunk-bqmstq21.js";
25
25
  import {
26
26
  factsPath,
27
27
  locateWork
28
- } from "./chunk-xe36zh9a.js";
28
+ } from "./chunk-wwwmkey4.js";
29
29
 
30
30
  // src/hooks/no-reask.ts
31
31
  import { existsSync, readFileSync } from "node:fs";
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  questionsCard
4
- } from "./chunk-djsf80bc.js";
5
- import"./chunk-yn87xw4f.js";
4
+ } from "./chunk-w8qm1jbn.js";
5
+ import"./chunk-mzwqb08p.js";
6
6
  import {
7
7
  allow,
8
8
  readPayload,
9
9
  runHook,
10
10
  sessionContext
11
- } from "./chunk-5m3f8wme.js";
12
- import"./chunk-cyzm0n45.js";
11
+ } from "./chunk-gvz0rzb9.js";
12
+ import"./chunk-tpz0zb0t.js";
13
13
  import {
14
14
  RunStore,
15
15
  closedByMachine,
@@ -18,7 +18,7 @@ import {
18
18
  openQuestions,
19
19
  runSnapshot,
20
20
  whatIsWaiting
21
- } from "./chunk-9zee1zpr.js";
21
+ } from "./chunk-97zs34wb.js";
22
22
  import"./chunk-dzkg8c68.js";
23
23
  import {
24
24
  expertsDir,
@@ -27,13 +27,13 @@ import {
27
27
  pathsIntersect,
28
28
  readExpertDomain,
29
29
  stackExpertNames
30
- } from "./chunk-g73p9wgp.js";
31
- import"./chunk-ek3tsewd.js";
32
- import"./chunk-693npq3d.js";
30
+ } from "./chunk-fefnr71h.js";
31
+ import"./chunk-dnat9z30.js";
32
+ import"./chunk-db7wf3hy.js";
33
33
  import {
34
34
  openBlocks,
35
35
  parseQuestions
36
- } from "./chunk-381n8p4g.js";
36
+ } from "./chunk-bqmstq21.js";
37
37
  import {
38
38
  PROJECT_FRAMEWORK_DIR,
39
39
  PROJECT_WORK_DIR,
@@ -43,7 +43,7 @@ import {
43
43
  isRecord,
44
44
  listRunDirs,
45
45
  parseYaml
46
- } from "./chunk-xe36zh9a.js";
46
+ } from "./chunk-wwwmkey4.js";
47
47
 
48
48
  // src/core/status/PendingItem.ts
49
49
  function isPending(item) {
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  bar,
4
4
  runSnapshot
5
- } from "./chunk-9zee1zpr.js";
5
+ } from "./chunk-97zs34wb.js";
6
6
  import"./chunk-dzkg8c68.js";
7
- import"./chunk-g73p9wgp.js";
8
- import"./chunk-ek3tsewd.js";
9
- import"./chunk-693npq3d.js";
10
- import"./chunk-381n8p4g.js";
7
+ import"./chunk-fefnr71h.js";
8
+ import"./chunk-dnat9z30.js";
9
+ import"./chunk-db7wf3hy.js";
10
+ import"./chunk-bqmstq21.js";
11
11
  import {
12
12
  findWorkspaceRoot,
13
13
  runtime
14
- } from "./chunk-xe36zh9a.js";
14
+ } from "./chunk-wwwmkey4.js";
15
15
 
16
16
  // src/core/statusline/renderStatusLine.ts
17
17
  var NO_SESSION_DATA = "[tldrx] no session data";
package/dist/tldrx.js CHANGED
@@ -9001,6 +9001,14 @@ function readCapped(path) {
9001
9001
  return "";
9002
9002
  }
9003
9003
  }
9004
+ function refreshSrcIndexes() {
9005
+ lineCountCache.clear();
9006
+ factsCache.clear();
9007
+ questionsCache.clear();
9008
+ graphCache.clear();
9009
+ mapCache.clear();
9010
+ urlCache.clear();
9011
+ }
9004
9012
 
9005
9013
  // src/core/facts/validateFactsFile.ts
9006
9014
  var ID_RE = SRC_PATTERNS.fact;
@@ -10738,6 +10746,7 @@ function loadWorkspace(root) {
10738
10746
  return empty();
10739
10747
  }
10740
10748
  function toSrcContext(workspace, runDir, options = {}) {
10749
+ refreshSrcIndexes();
10741
10750
  return {
10742
10751
  root: workspace.root,
10743
10752
  repos: workspace.repos,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tldr-experts",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "tldr-experts: an evidence-first, file-based AI development framework - five stages, a gate on every one, and every claim cited or refused. Installs the `tldrx` (and `tldr-experts`) command. Beta.",
5
5
  "license": "MIT",
6
6
  "author": "Alan Martinez",
@@ -2,7 +2,7 @@
2
2
  "$doc": "Shape verified from https://code.claude.com/docs/en/plugins.md (Quickstart > Create the plugin manifest). Fields used here: name, description, version, author.name. Only plugin.json goes inside .claude-plugin/; skills/, agents/ and hooks/ live at the plugin root.",
3
3
  "name": "tldrx",
4
4
  "description": "tldr-experts: an evidence-first, file-based AI development framework. Five stages, a gate on every one, every claim cited or refused. Beta.",
5
- "version": "0.14.1",
5
+ "version": "0.14.2",
6
6
  "author": {
7
7
  "name": "Alan Martinez"
8
8
  }