tldr-experts 0.5.0 → 0.6.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 (30) hide show
  1. package/CHANGELOG.md +792 -0
  2. package/README.md +19 -5
  3. package/dist/hooks/answer-capture.js +136 -12
  4. package/dist/hooks/budget-gate.js +9 -9
  5. package/dist/hooks/{chunk-889tybxc.js → chunk-17gv74sr.js} +3 -3
  6. package/dist/hooks/{chunk-3w55tp71.js → chunk-5k4dggq0.js} +51 -17
  7. package/dist/hooks/{chunk-14zn51kh.js → chunk-7eqhfddw.js} +28 -569
  8. package/dist/hooks/{chunk-nadqsr3w.js → chunk-97ncnegd.js} +1 -1
  9. package/dist/hooks/{chunk-a6rpj2cp.js → chunk-9w64mxdh.js} +345 -22
  10. package/dist/hooks/{chunk-a5dq2dcp.js → chunk-dhbxzjfs.js} +2 -4
  11. package/dist/hooks/{chunk-hcrbr430.js → chunk-g4db8rbs.js} +2 -30
  12. package/dist/hooks/chunk-h7151g8w.js +803 -0
  13. package/dist/hooks/{chunk-5556vjt5.js → chunk-ka6bkb64.js} +1 -1
  14. package/dist/hooks/chunk-rrkdfk7s.js +30 -0
  15. package/dist/hooks/{chunk-qw73rdbr.js → chunk-v4ay50nq.js} +1 -1
  16. package/dist/hooks/{chunk-v1c1hpb8.js → chunk-vrhczzxs.js} +1 -1
  17. package/dist/hooks/{chunk-bvm6vjrt.js → chunk-x6n8b5fp.js} +1 -1
  18. package/dist/hooks/claim-sources.js +8 -10
  19. package/dist/hooks/dod-gate.js +7 -7
  20. package/dist/hooks/no-reask.js +8 -9
  21. package/dist/hooks/session-start.js +23 -18
  22. package/dist/hooks/statusline.js +10 -10
  23. package/dist/tldrx.js +2539 -1006
  24. package/env.yml +9 -2
  25. package/package.json +1 -1
  26. package/plugin/.claude-plugin/plugin.json +1 -1
  27. package/plugin/skills/tldrx/SKILL.md +8 -1
  28. package/templates/questions.md +8 -0
  29. package/dist/hooks/chunk-q8d3sff9.js +0 -220
  30. package/dist/hooks/chunk-sae7sqty.js +0 -206
@@ -1,14 +1,17 @@
1
+ import {
2
+ EVIDENCE_FILE,
3
+ gateEvidenceRelPath
4
+ } from "./chunk-h7151g8w.js";
1
5
  import {
2
6
  MAX_PLAN_STORIES,
3
7
  MAX_STORIES_PER_WAVE,
4
8
  MAX_WAVES,
5
9
  STORY_ID_RE,
6
10
  WAVE_ID_RE,
7
- parseFrontMatter,
8
11
  requirePattern,
9
12
  requireStringList,
10
13
  requireVersion1
11
- } from "./chunk-hcrbr430.js";
14
+ } from "./chunk-g4db8rbs.js";
12
15
  import {
13
16
  asDocument,
14
17
  isRecord,
@@ -21,9 +24,6 @@ import {
21
24
  requireVersion,
22
25
  result
23
26
  } from "./chunk-39zh2e44.js";
24
- import {
25
- parseHandoff
26
- } from "./chunk-q8d3sff9.js";
27
27
  import {
28
28
  PROJECT_FRAMEWORK_DIR,
29
29
  SRC_PATTERNS,
@@ -31,7 +31,7 @@ import {
31
31
  listRunDirs,
32
32
  parseYaml,
33
33
  readableSource
34
- } from "./chunk-a6rpj2cp.js";
34
+ } from "./chunk-9w64mxdh.js";
35
35
 
36
36
  // src/hooks/lib/runFile.ts
37
37
  import { existsSync, readFileSync } from "node:fs";
@@ -424,19 +424,25 @@ var SKIPPED_DIRS = new Set([
424
424
  "test-results"
425
425
  ]);
426
426
 
427
- // src/core/plan/branchModel.ts
428
- var BRANCH_MODELS = ["per-epic", "integration"];
429
- function isBranchModelKind(value) {
430
- return typeof value === "string" && BRANCH_MODELS.includes(value);
427
+ // src/core/facilitator/paths.ts
428
+ import { join as join2, relative, sep } from "node:path";
429
+ function agentDir(runDir, stageId) {
430
+ return join2(runDir, ".agent", stageId);
431
+ }
432
+ function evidencePath(runDir, stageId) {
433
+ return join2(agentDir(runDir, stageId), EVIDENCE_FILE);
434
+ }
435
+ function gateEvidencePath(runDir, phaseId, stageId) {
436
+ return join2(runDir, ...gateEvidenceRelPath(phaseId, stageId).split("/"));
431
437
  }
432
438
 
433
439
  // src/core/experts/expertDocument.ts
434
440
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
435
- import { join as join3 } from "node:path";
441
+ import { join as join4 } from "node:path";
436
442
 
437
443
  // src/core/experts/loadExperts.ts
438
444
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
439
- import { join as join2 } from "node:path";
445
+ import { join as join3 } from "node:path";
440
446
 
441
447
  // src/core/init/competencyLevel.ts
442
448
  var EVIDENCE_KINDS = ["code", "run", "test", "doc", "answer"];
@@ -568,21 +574,21 @@ var EXPERTS_DIRNAME = "experts";
568
574
  var COMPETENCIES_FILE = "competencies.yml";
569
575
  var EXPERT_FILE = "expert.md";
570
576
  function expertsDir(root) {
571
- return join2(root, PROJECT_FRAMEWORK_DIR, EXPERTS_DIRNAME);
577
+ return join3(root, PROJECT_FRAMEWORK_DIR, EXPERTS_DIRNAME);
572
578
  }
573
579
  function expertDir(root, name) {
574
- return join2(expertsDir(root), name);
580
+ return join3(expertsDir(root), name);
575
581
  }
576
582
  function loadExperts(root, now = new Date) {
577
583
  const dir = expertsDir(root);
578
584
  if (!existsSync2(dir))
579
585
  return [];
580
- const names = readdirSync(dir).filter((entry) => statSync(join2(dir, entry)).isDirectory()).sort();
586
+ const names = readdirSync(dir).filter((entry) => statSync(join3(dir, entry)).isDirectory()).sort();
581
587
  return names.map((name) => loadExpert(root, name, now));
582
588
  }
583
589
  function loadExpert(root, name, now = new Date) {
584
590
  const dir = expertDir(root, name);
585
- const path = join2(dir, COMPETENCIES_FILE);
591
+ const path = join3(dir, COMPETENCIES_FILE);
586
592
  const empty = { name, dir, status: "unknown", lastTrained: null, areas: [], drifted: [] };
587
593
  if (!existsSync2(path))
588
594
  return { ...empty, error: `${COMPETENCIES_FILE} is missing` };
@@ -641,7 +647,7 @@ function str3(value) {
641
647
  // src/core/experts/expertDocument.ts
642
648
  var H2_RE = /^##\s+(.+?)\s*$/;
643
649
  function readExpertDocument(root, name) {
644
- const path = join3(expertDir(root, name), EXPERT_FILE);
650
+ const path = join4(expertDir(root, name), EXPERT_FILE);
645
651
  if (!existsSync3(path)) {
646
652
  return { path, exists: false, frontMatter: new Map, body: "" };
647
653
  }
@@ -746,218 +752,6 @@ function pathsIntersect(a, b) {
746
752
  // src/core/experts/expertKnowledge.ts
747
753
  var DEFAULT_KNOWLEDGE_MAX_BYTES = 48 * 1024;
748
754
 
749
- // src/core/facilitator/paths.ts
750
- import { join as join4, relative, sep } from "node:path";
751
-
752
- // src/core/text/evidence.ts
753
- var EVIDENCE_VERSION = 1;
754
- var EVIDENCE_FILE = "evidence.md";
755
- var GATE_EVIDENCE_DIR = "gate-evidence";
756
- function gateEvidenceRelPath(phaseId, stageId) {
757
- return `${phaseId}/${GATE_EVIDENCE_DIR}/${stageId}.md`;
758
- }
759
- var EVIDENCE_VERDICTS = ["sign", "sign-with-fixlist", "refuse"];
760
- var EVIDENCE_ROLES = ["agent"];
761
- var DIFF_VS_STORIES = ["matches", "diverges", "n-a"];
762
- var REQUIRED_EVIDENCE_KEYS = [
763
- "version",
764
- "gate",
765
- "role",
766
- "by",
767
- "at",
768
- "verdict",
769
- "read",
770
- "citations",
771
- "touches",
772
- "diff_vs_stories"
773
- ];
774
- function parseEvidence(text) {
775
- const parsed = parseFrontMatter(text);
776
- const body = parseHandoff(parsed.frontMatter.body);
777
- const bodyLine = parsed.frontMatter.bodyLine;
778
- if (parsed.doc === null) {
779
- return {
780
- front: null,
781
- frontIssues: [front(0, parsed.issue?.message ?? "front matter could not be read")],
782
- body,
783
- bodyLine
784
- };
785
- }
786
- if (!isRecord2(parsed.doc)) {
787
- return {
788
- front: null,
789
- frontIssues: [front(0, "front matter is empty — it must be a mapping of the keys design §A.5 names")],
790
- body,
791
- bodyLine
792
- };
793
- }
794
- const issues = [];
795
- const doc = parsed.doc;
796
- for (const key of REQUIRED_EVIDENCE_KEYS) {
797
- if (!(key in doc) || doc[key] === undefined) {
798
- issues.push(front(0, `front matter is missing required key \`${key}\``));
799
- }
800
- }
801
- const version = doc.version;
802
- if (version !== undefined && version !== EVIDENCE_VERSION) {
803
- issues.push(front(0, `\`version\` must be ${String(EVIDENCE_VERSION)}, got ${describe(version)}`));
804
- }
805
- const gate = requireText(doc.gate, "gate", issues);
806
- const role = requireEnum2(doc.role, EVIDENCE_ROLES, "role", issues);
807
- const by = requireText(doc.by, "by", issues);
808
- const at = requireText(doc.at, "at", issues);
809
- const verdict = requireEnum2(doc.verdict, EVIDENCE_VERDICTS, "verdict", issues);
810
- const read = requireStrings(doc.read, "read", issues);
811
- const citations = requireCitations(doc.citations, issues);
812
- const touches = requireTouches(doc.touches, issues);
813
- const diff = requireEnum2(doc.diff_vs_stories, DIFF_VS_STORIES, "diff_vs_stories", issues);
814
- const caveats = doc.caveats === undefined ? [] : requireStrings(doc.caveats, "caveats", issues);
815
- const recommend = requireRecommendations(doc.recommend, issues);
816
- if (issues.length > 0)
817
- return { front: null, frontIssues: issues, body, bodyLine };
818
- return {
819
- front: {
820
- version: EVIDENCE_VERSION,
821
- gate,
822
- role,
823
- by,
824
- at,
825
- verdict,
826
- read,
827
- citations,
828
- touches,
829
- diff_vs_stories: diff,
830
- caveats,
831
- recommend
832
- },
833
- frontIssues: [],
834
- body,
835
- bodyLine
836
- };
837
- }
838
- var GUIDANCE = {
839
- Read: "One bullet per file or artefact you opened, each ending with a `[src: …]` token for it.",
840
- "Citations checked": "Which of this stage's citations you re-checked, and what each one turned out to be. " + "Sample honestly: `citations.sampled` above must match what is listed here.",
841
- "Touches audited": "The touched paths you looked at, and every path outside the surface the What and the " + "stories declared. Name a new area rather than absorbing it.",
842
- Verdict: "SIGN, SIGN-with-fixlist or REFUSE, and the measurement behind it — a command you ran, " + "a file you read. A verdict with no source is an opinion."
843
- };
844
- function front(line, message) {
845
- return { kind: "front-matter", line, message };
846
- }
847
- function isRecord2(value) {
848
- return typeof value === "object" && value !== null && !Array.isArray(value);
849
- }
850
- function requireText(value, key, issues) {
851
- if (typeof value === "string" && value.trim() !== "")
852
- return value.trim();
853
- if (value !== undefined)
854
- issues.push(front(0, `\`${key}\` must be a non-empty string, got ${describe(value)}`));
855
- return "";
856
- }
857
- function requireEnum2(value, allowed, key, issues) {
858
- if (typeof value === "string" && allowed.includes(value))
859
- return value;
860
- if (value !== undefined) {
861
- issues.push(front(0, `\`${key}\` must be one of ${allowed.join(" | ")}, got ${describe(value)}`));
862
- }
863
- return allowed[0];
864
- }
865
- function requireStrings(value, key, issues) {
866
- if (!Array.isArray(value)) {
867
- if (value !== undefined)
868
- issues.push(front(0, `\`${key}\` must be a list, got ${describe(value)}`));
869
- return [];
870
- }
871
- const out = [];
872
- for (const [i, entry] of value.entries()) {
873
- if (typeof entry === "string")
874
- out.push(entry);
875
- else
876
- issues.push(front(0, `\`${key}[${String(i)}]\` must be a string, got ${describe(entry)}`));
877
- }
878
- return out;
879
- }
880
- function requireCount(value, key, issues) {
881
- if (typeof value === "number" && Number.isInteger(value) && value >= 0)
882
- return value;
883
- issues.push(front(0, `\`${key}\` must be a whole number of 0 or more, got ${describe(value)}`));
884
- return 0;
885
- }
886
- function requireCitations(value, issues) {
887
- if (!isRecord2(value)) {
888
- if (value !== undefined) {
889
- issues.push(front(0, "`citations` must be a mapping `{sampled, of, resolved, refuted}`, got " + describe(value)));
890
- }
891
- return { sampled: 0, of: 0, resolved: 0, refuted: 0 };
892
- }
893
- return {
894
- sampled: requireCount(value.sampled, "citations.sampled", issues),
895
- of: requireCount(value.of, "citations.of", issues),
896
- resolved: requireCount(value.resolved, "citations.resolved", issues),
897
- refuted: requireCount(value.refuted, "citations.refuted", issues)
898
- };
899
- }
900
- function requireTouches(value, issues) {
901
- if (!isRecord2(value)) {
902
- if (value !== undefined) {
903
- issues.push(front(0, "`touches` must be a mapping `{audited, outside_surface, new_areas}`, got " + describe(value)));
904
- }
905
- return { audited: 0, outside_surface: 0, new_areas: [] };
906
- }
907
- return {
908
- audited: requireCount(value.audited, "touches.audited", issues),
909
- outside_surface: requireCount(value.outside_surface, "touches.outside_surface", issues),
910
- new_areas: requireStrings(value.new_areas, "touches.new_areas", issues)
911
- };
912
- }
913
- function requireRecommendations(value, issues) {
914
- if (value === undefined)
915
- return [];
916
- if (!Array.isArray(value)) {
917
- issues.push(front(0, `\`recommend\` must be a list of {q, option, why, src}, got ${describe(value)}`));
918
- return [];
919
- }
920
- const out = [];
921
- for (const [i, entry] of value.entries()) {
922
- if (!isRecord2(entry)) {
923
- issues.push(front(0, `\`recommend[${String(i)}]\` must be a mapping {q, option, why, src}`));
924
- continue;
925
- }
926
- const path = `recommend[${String(i)}]`;
927
- for (const key of ["q", "option", "why", "src"]) {
928
- if (!(key in entry))
929
- issues.push(front(0, `\`${path}\` is missing \`${key}\``));
930
- }
931
- out.push({
932
- q: requireText(entry.q, `${path}.q`, issues),
933
- option: requireText(entry.option, `${path}.option`, issues),
934
- why: requireText(entry.why, `${path}.why`, issues),
935
- src: requireText(entry.src, `${path}.src`, issues)
936
- });
937
- }
938
- return out;
939
- }
940
- function describe(value) {
941
- if (value === null || value === undefined)
942
- return "nothing";
943
- if (Array.isArray(value))
944
- return "a list";
945
- if (typeof value === "object")
946
- return "a mapping";
947
- return typeof value === "string" ? `\`${value}\`` : String(value);
948
- }
949
-
950
- // src/core/facilitator/paths.ts
951
- function agentDir(runDir, stageId) {
952
- return join4(runDir, ".agent", stageId);
953
- }
954
- function evidencePath(runDir, stageId) {
955
- return join4(agentDir(runDir, stageId), EVIDENCE_FILE);
956
- }
957
- function gateEvidencePath(runDir, phaseId, stageId) {
958
- return join4(runDir, ...gateEvidenceRelPath(phaseId, stageId).split("/"));
959
- }
960
-
961
755
  // src/core/facilitator/dispatchNotes.ts
962
756
  var DISPATCH_NOTES_MAX_BYTES = 8 * 1024;
963
757
 
@@ -1001,7 +795,10 @@ var KINDS = {
1001
795
  fact: { shape: `\`F<nnn>\` (${readableSource(SRC_PATTERNS.fact)})`, example: "F102" },
1002
796
  cmd: { shape: `\`$ <command> → exit <n>\` (${readableSource(SRC_PATTERNS.cmd)})`, example: "$ bun test → exit 0" },
1003
797
  graph: { shape: "`graph:<node id>`", example: "graph:hunt-engine" },
1004
- absent: { shape: "`absent:<the path you looked at>`", example: "absent:docs/retention.md" },
798
+ absent: {
799
+ shape: "`absent:<the path you looked at>` — add `#<what you searched for>` to have it CHECKED",
800
+ example: "absent:docs/retention.md#retention"
801
+ },
1005
802
  aidlc: {
1006
803
  shape: `\`aidlc:<file>:<line>\` or \`aidlc:<file>#Q<n>\` (${readableSource(SRC_PATTERNS.aidlcLine)})`,
1007
804
  example: "aidlc:intents/260821/design.md:14"
@@ -1461,344 +1258,6 @@ function wouldExceedHostTokens(budget, phaseId, spentTokens) {
1461
1258
  };
1462
1259
  }
1463
1260
 
1464
- // src/core/run/gatePolicy.ts
1465
- var GATE_POLICIES = ["human", "auto", "agent"];
1466
- function isGatePolicy(value) {
1467
- return typeof value === "string" && GATE_POLICIES.includes(value);
1468
- }
1469
- function validateGatesPolicy(value, stageIds, issues) {
1470
- if (value === undefined || value === null)
1471
- return;
1472
- if (!isRecord(value)) {
1473
- issues.push({
1474
- path: "gates_policy",
1475
- message: `expected a mapping of stage id -> ${GATE_POLICIES.join(" | ")}`
1476
- });
1477
- return;
1478
- }
1479
- const known = new Set(stageIds);
1480
- for (const [key, raw] of Object.entries(value)) {
1481
- if (!isGatePolicy(raw)) {
1482
- issues.push({
1483
- path: `gates_policy.${key}`,
1484
- message: `expected one of ${GATE_POLICIES.join(" | ")}, got ${JSON.stringify(raw)}`
1485
- });
1486
- }
1487
- if (known.size > 0 && !known.has(key)) {
1488
- issues.push({ path: `gates_policy.${key}`, message: `names no stage in this run` });
1489
- }
1490
- }
1491
- }
1492
-
1493
- // src/core/run/RunFile.ts
1494
- var STAGE_STATUSES = [
1495
- "pending",
1496
- "ready",
1497
- "running",
1498
- "awaiting_answer",
1499
- "awaiting_gate",
1500
- "blocked",
1501
- "done",
1502
- "failed",
1503
- "skipped",
1504
- "cancelled"
1505
- ];
1506
- var TERMINAL_STATUSES2 = ["done", "failed", "skipped", "cancelled"];
1507
- var GATE_TYPES = ["approve", "checks", "auto"];
1508
- var GATE_STATUSES = ["pending", "approved", "rejected", "n-a"];
1509
- var ATTENDED_BY = ["host"];
1510
- var RUN_ID_RE = /^\d{6}-[a-z0-9-]{1,40}$/;
1511
- var PHASE_ID_RE = /^0[1-5]-[a-z]+$/;
1512
- var MAX_PHASES2 = 5;
1513
- var MAX_STAGES = 40;
1514
- var MAX_TASKS = 200;
1515
- function isTerminal2(status) {
1516
- return TERMINAL_STATUSES2.includes(status);
1517
- }
1518
- function isFinished(status) {
1519
- return status === "done" || status === "cancelled";
1520
- }
1521
- function isAttendedByHost(run) {
1522
- return run.attended_by === "host";
1523
- }
1524
- function flatten(run) {
1525
- const out = [];
1526
- for (const phase of run.phases)
1527
- for (const stage of phase.stages)
1528
- out.push({ phase, stage });
1529
- return out;
1530
- }
1531
- function stageAt(run, cursor) {
1532
- return flatten(run).find((e) => e.phase.id === cursor.phase && e.stage.id === cursor.stage) ?? null;
1533
- }
1534
- function deriveRunStatus(run) {
1535
- const all = flatten(run);
1536
- if (run.cancelled !== undefined)
1537
- return "cancelled";
1538
- if (all.some((e) => e.stage.status === "failed"))
1539
- return "failed";
1540
- if (all.length > 0 && all.every((e) => isTerminal2(e.stage.status)))
1541
- return "done";
1542
- return stageAt(run, run.cursor)?.stage.status ?? "pending";
1543
- }
1544
- function derivePhaseStatus(phase) {
1545
- if (phase.stages.length === 0)
1546
- return "skipped";
1547
- if (phase.stages.some((s) => s.status === "failed"))
1548
- return "failed";
1549
- if (phase.stages.every((s) => isTerminal2(s.status)))
1550
- return "done";
1551
- const live = phase.stages.find((s) => !isTerminal2(s.status));
1552
- return live?.status ?? "pending";
1553
- }
1554
- function validateRunFile(input) {
1555
- const issues = [];
1556
- const deprecations = [];
1557
- const doc = asDocument(input, issues);
1558
- if (!doc)
1559
- return result(issues);
1560
- requireVersion(doc, issues, deprecations);
1561
- requireKeys(doc, ["run", "title", "scope", "workflow", "repos", "created_at", "updated_at", "status", "cursor", "budget", "phases"], "", issues);
1562
- if (typeof doc.run !== "string" || !RUN_ID_RE.test(doc.run)) {
1563
- issues.push({ path: "run", message: "run id must match ^\\d{6}-[a-z0-9-]{1,40}$" });
1564
- }
1565
- requireString(doc.title, "title", issues);
1566
- requireString(doc.scope, "scope", issues);
1567
- requireString(doc.workflow, "workflow", issues);
1568
- requireString(doc.created_at, "created_at", issues);
1569
- requireString(doc.updated_at, "updated_at", issues);
1570
- requireEnum(doc.status, STAGE_STATUSES, "status", issues);
1571
- requireArray(doc.repos, "repos", issues);
1572
- let cursor = null;
1573
- if (isRecord(doc.cursor)) {
1574
- requireKeys(doc.cursor, ["phase", "stage", "task"], "cursor", issues);
1575
- cursor = {
1576
- phase: String(doc.cursor.phase ?? ""),
1577
- stage: String(doc.cursor.stage ?? ""),
1578
- task: typeof doc.cursor.task === "string" ? doc.cursor.task : null
1579
- };
1580
- } else if (doc.cursor !== undefined) {
1581
- issues.push({ path: "cursor", message: "expected a mapping" });
1582
- }
1583
- if (isRecord(doc.budget)) {
1584
- requireKeys(doc.budget, ["ceiling_usd", "spent_usd", "per_agent_max_usd"], "budget", issues);
1585
- requireNumber(doc.budget.ceiling_usd, "budget.ceiling_usd", issues);
1586
- requireNumber(doc.budget.spent_usd, "budget.spent_usd", issues);
1587
- requireNumber(doc.budget.per_agent_max_usd, "budget.per_agent_max_usd", issues);
1588
- } else if (doc.budget !== undefined) {
1589
- issues.push({ path: "budget", message: "expected a mapping" });
1590
- }
1591
- if (doc.build !== undefined) {
1592
- if (isRecord(doc.build)) {
1593
- requireKeys(doc.build, ["epic_branch"], "build", issues);
1594
- if (requireArray(doc.build.epic_branch, "build.epic_branch", issues)) {
1595
- doc.build.epic_branch.forEach((branch, i) => {
1596
- requireString(branch, `build.epic_branch[${i}]`, issues);
1597
- });
1598
- }
1599
- if (doc.build.branch_model !== undefined && !isBranchModelKind(doc.build.branch_model)) {
1600
- issues.push({
1601
- path: "build.branch_model",
1602
- message: `expected one of ${BRANCH_MODELS.join(" | ")}`
1603
- });
1604
- }
1605
- } else {
1606
- issues.push({ path: "build", message: "expected a mapping" });
1607
- }
1608
- }
1609
- if (doc.cancelled !== undefined) {
1610
- if (isRecord(doc.cancelled)) {
1611
- requireKeys(doc.cancelled, ["by", "at", "note"], "cancelled", issues);
1612
- requireString(doc.cancelled.by, "cancelled.by", issues);
1613
- requireString(doc.cancelled.at, "cancelled.at", issues);
1614
- requireString(doc.cancelled.note, "cancelled.note", issues);
1615
- } else {
1616
- issues.push({ path: "cancelled", message: "expected a mapping" });
1617
- }
1618
- }
1619
- if (doc.triage !== undefined) {
1620
- if (isRecord(doc.triage)) {
1621
- requireKeys(doc.triage, ["split", "depends_on"], "triage", issues);
1622
- requireString(doc.triage.split, "triage.split", issues);
1623
- if (requireArray(doc.triage.depends_on, "triage.depends_on", issues)) {
1624
- doc.triage.depends_on.forEach((slug, i) => {
1625
- requireString(slug, `triage.depends_on[${i}]`, issues);
1626
- });
1627
- }
1628
- } else {
1629
- issues.push({ path: "triage", message: "expected a mapping" });
1630
- }
1631
- }
1632
- if (doc.attended_by !== undefined)
1633
- requireEnum(doc.attended_by, ATTENDED_BY, "attended_by", issues);
1634
- if (doc.keep_worktrees !== undefined && typeof doc.keep_worktrees !== "boolean") {
1635
- issues.push({ path: "keep_worktrees", message: `expected a boolean, got ${typeof doc.keep_worktrees}` });
1636
- }
1637
- if (!requireArray(doc.phases, "phases", issues))
1638
- return result(issues, deprecations);
1639
- const phases = doc.phases;
1640
- if (phases.length > MAX_PHASES2) {
1641
- issues.push({ path: "phases", message: `${phases.length} phases exceeds the ${MAX_PHASES2} cap` });
1642
- }
1643
- const declaredStageIds = [];
1644
- for (const phase of phases) {
1645
- if (!isRecord(phase) || !Array.isArray(phase.stages))
1646
- continue;
1647
- for (const stage of phase.stages) {
1648
- if (isRecord(stage) && typeof stage.id === "string")
1649
- declaredStageIds.push(stage.id);
1650
- }
1651
- }
1652
- validateGatesPolicy(doc.gates_policy, declaredStageIds, issues);
1653
- let stageCount = 0;
1654
- let taskCount = 0;
1655
- let running = 0;
1656
- let cursorResolves = false;
1657
- let spentFromTasks = 0;
1658
- const phaseIds = new Set;
1659
- phases.forEach((phase, i) => {
1660
- const base = `phases[${i}]`;
1661
- if (!isRecord(phase)) {
1662
- issues.push({ path: base, message: "expected a mapping" });
1663
- return;
1664
- }
1665
- requireKeys(phase, ["id", "status", "stages"], base, issues);
1666
- const phaseId = typeof phase.id === "string" ? phase.id : "";
1667
- if (!PHASE_ID_RE.test(phaseId)) {
1668
- issues.push({ path: `${base}.id`, message: "phase id must match ^0[1-5]-[a-z]+$" });
1669
- }
1670
- if (phaseIds.has(phaseId))
1671
- issues.push({ path: `${base}.id`, message: `duplicate phase id ${phaseId}` });
1672
- phaseIds.add(phaseId);
1673
- requireEnum(phase.status, STAGE_STATUSES, `${base}.status`, issues);
1674
- if (!requireArray(phase.stages, `${base}.stages`, issues))
1675
- return;
1676
- const stageIds = new Set;
1677
- phase.stages.forEach((stage, j) => {
1678
- const path = `${base}.stages[${j}]`;
1679
- stageCount++;
1680
- if (!isRecord(stage)) {
1681
- issues.push({ path, message: "expected a mapping" });
1682
- return;
1683
- }
1684
- requireKeys(stage, ["id", "status", "expert", "model", "budget_usd", "cost_usd", "started_at", "ended_at", "inputs", "outputs", "gate", "tasks"], path, issues);
1685
- const stageId = typeof stage.id === "string" ? stage.id : "";
1686
- if (stageIds.has(stageId))
1687
- issues.push({ path: `${path}.id`, message: `duplicate stage id ${stageId}` });
1688
- stageIds.add(stageId);
1689
- requireEnum(stage.status, STAGE_STATUSES, `${path}.status`, issues);
1690
- if (stage.status === "running")
1691
- running++;
1692
- requireNumber(stage.budget_usd, `${path}.budget_usd`, issues);
1693
- requireNumber(stage.cost_usd, `${path}.cost_usd`, issues);
1694
- requireArray(stage.inputs, `${path}.inputs`, issues);
1695
- requireArray(stage.outputs, `${path}.outputs`, issues);
1696
- if (stage.stale !== undefined && typeof stage.stale !== "boolean") {
1697
- issues.push({ path: `${path}.stale`, message: "expected true or false" });
1698
- }
1699
- checkOrder(stage.started_at, stage.ended_at, path, issues);
1700
- if (cursor !== null && cursor.phase === phaseId && cursor.stage === stageId)
1701
- cursorResolves = true;
1702
- if (isRecord(stage.gate)) {
1703
- const gate = stage.gate;
1704
- requireKeys(gate, ["type", "status", "by", "at", "note"], `${path}.gate`, issues);
1705
- requireEnum(gate.type, GATE_TYPES, `${path}.gate.type`, issues);
1706
- requireEnum(gate.status, GATE_STATUSES, `${path}.gate.status`, issues);
1707
- if (gate.status === "approved" && (typeof gate.by !== "string" || typeof gate.at !== "string")) {
1708
- issues.push({ path: `${path}.gate`, message: "an approved gate needs both `by` and `at`" });
1709
- }
1710
- validateGateEvidence(gate.evidence, `${path}.gate.evidence`, issues);
1711
- } else if (stage.gate !== undefined) {
1712
- issues.push({ path: `${path}.gate`, message: "expected a mapping" });
1713
- }
1714
- if (requireArray(stage.tasks, `${path}.tasks`, issues)) {
1715
- const taskIds = new Set;
1716
- stage.tasks.forEach((task, k) => {
1717
- const tp = `${path}.tasks[${k}]`;
1718
- taskCount++;
1719
- if (!isRecord(task)) {
1720
- issues.push({ path: tp, message: "expected a mapping" });
1721
- return;
1722
- }
1723
- requireKeys(task, ["id", "status", "cost_usd", "error", "session_id"], tp, issues);
1724
- const taskId = typeof task.id === "string" ? task.id : "";
1725
- if (!/^t\d+$/.test(taskId))
1726
- issues.push({ path: `${tp}.id`, message: "task id must match ^t\\d+$" });
1727
- if (taskIds.has(taskId))
1728
- issues.push({ path: `${tp}.id`, message: `duplicate task id ${taskId}` });
1729
- taskIds.add(taskId);
1730
- requireEnum(task.status, STAGE_STATUSES, `${tp}.status`, issues);
1731
- if (task.cost_usd !== null)
1732
- requireNumber(task.cost_usd, `${tp}.cost_usd`, issues);
1733
- if (task.metered !== undefined && typeof task.metered !== "boolean") {
1734
- issues.push({ path: `${tp}.metered`, message: "expected true or false" });
1735
- }
1736
- if (task.cost_usd === null && task.metered !== false) {
1737
- issues.push({ path: `${tp}.metered`, message: "a null cost_usd must be marked `metered: false`" });
1738
- }
1739
- if (typeof task.cost_usd === "number")
1740
- spentFromTasks += task.cost_usd;
1741
- checkOrder(task.started_at, task.ended_at, tp, issues);
1742
- });
1743
- }
1744
- });
1745
- });
1746
- if (stageCount > MAX_STAGES)
1747
- issues.push({ path: "phases", message: `${stageCount} stages exceeds the ${MAX_STAGES} cap` });
1748
- if (taskCount > MAX_TASKS)
1749
- issues.push({ path: "phases", message: `${taskCount} tasks exceeds the ${MAX_TASKS} cap` });
1750
- if (running > 1)
1751
- issues.push({ path: "phases", message: `${running} stages are running; tldrx is single-writer` });
1752
- if (cursor !== null && !cursorResolves) {
1753
- issues.push({ path: "cursor", message: `cursor ${cursor.phase}/${cursor.stage} does not resolve to a stage` });
1754
- }
1755
- if (isRecord(doc.budget) && typeof doc.budget.spent_usd === "number") {
1756
- if (Math.abs(doc.budget.spent_usd - spentFromTasks) > 0.01) {
1757
- issues.push({
1758
- path: "budget.spent_usd",
1759
- message: `${doc.budget.spent_usd} does not match the task total ${round5(spentFromTasks)} (tolerance 0.01)`
1760
- });
1761
- }
1762
- }
1763
- return result(issues, deprecations);
1764
- }
1765
- function checkOrder(started, ended, path, issues) {
1766
- if (typeof started === "string" && typeof ended === "string" && ended < started) {
1767
- issues.push({ path: `${path}.ended_at`, message: "ended_at is before started_at" });
1768
- }
1769
- }
1770
- function round5(n) {
1771
- return Math.round(n * 100) / 100;
1772
- }
1773
- function asRunFile(input) {
1774
- return input;
1775
- }
1776
- var GATE_EVIDENCE_KEYS = [
1777
- "path",
1778
- "role",
1779
- "verdict",
1780
- "sampled",
1781
- "of",
1782
- "resolved",
1783
- "refuted",
1784
- "outside_surface"
1785
- ];
1786
- function validateGateEvidence(value, base, issues) {
1787
- if (value === undefined || value === null)
1788
- return;
1789
- if (!isRecord(value)) {
1790
- issues.push({ path: base, message: "expected a mapping" });
1791
- return;
1792
- }
1793
- requireKeys(value, GATE_EVIDENCE_KEYS, base, issues);
1794
- requireString(value.path, `${base}.path`, issues);
1795
- requireEnum(value.role, EVIDENCE_ROLES, `${base}.role`, issues);
1796
- requireEnum(value.verdict, EVIDENCE_VERDICTS, `${base}.verdict`, issues);
1797
- for (const key of ["sampled", "of", "resolved", "refuted", "outside_surface"]) {
1798
- requireNumber(value[key], `${base}.${key}`, issues);
1799
- }
1800
- }
1801
-
1802
1261
  // src/core/budget/budgetView.ts
1803
1262
  function raiseCommand(runId, phaseId, amountUsd) {
1804
1263
  return `tldrx budget raise ${phaseId} ${amountUsd.toFixed(2)} --run ${runId}`;
@@ -1807,4 +1266,4 @@ function shortBy(estimate, remaining) {
1807
1266
  return Math.max(0.01, Math.ceil((estimate - remaining) * 100) / 100);
1808
1267
  }
1809
1268
 
1810
- export { loadRunView, isAttendedByHostView, runSpend, hostTokensIn, renderRunEconomies, cursorStage, openRunViews, newestActiveRun, currentActor, nowRfc3339, DEFAULT_ON_HOST_TOKENS_EXCEED, DEFAULT_ECONOMY, economyFor, isHostTokens, validateRunBudget, asRunBudget, expertsDir, loadExperts, readExpertDomain, pathsIntersect, parseEvidence, evidencePath, gateEvidencePath, stackExpertNames, remainingWork, wouldExceed, wouldExceedHostTokens, isTerminal2 as isTerminal, isFinished, isAttendedByHost, flatten, stageAt, deriveRunStatus, derivePhaseStatus, validateRunFile, asRunFile, raiseCommand, shortBy };
1269
+ export { loadRunView, isAttendedByHostView, runSpend, hostTokensIn, renderRunEconomies, cursorStage, openRunViews, newestActiveRun, currentActor, nowRfc3339, DEFAULT_ON_HOST_TOKENS_EXCEED, DEFAULT_ECONOMY, economyFor, isHostTokens, validateRunBudget, asRunBudget, evidencePath, gateEvidencePath, expertsDir, loadExperts, readExpertDomain, pathsIntersect, stackExpertNames, remainingWork, wouldExceed, wouldExceedHostTokens, raiseCommand, shortBy };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseSrcToken
3
- } from "./chunk-a6rpj2cp.js";
3
+ } from "./chunk-9w64mxdh.js";
4
4
 
5
5
  // src/core/text/questions.ts
6
6
  var REQUIRED_METADATA_KEYS = ["id", "status", "area", "asked_by", "asked_at"];