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
@@ -1,37 +1,29 @@
1
- import {
2
- EVIDENCE_FILE,
3
- gateEvidenceRelPath
4
- } from "./chunk-fv66tdsm.js";
5
1
  import {
6
2
  MAX_PLAN_STORIES,
7
3
  MAX_STORIES_PER_WAVE,
8
4
  MAX_WAVES,
5
+ PROJECT_FRAMEWORK_DIR,
6
+ SRC_PATTERNS,
9
7
  STORY_ID_RE,
10
8
  WAVE_ID_RE,
11
- requirePattern,
12
- requireStringList,
13
- requireVersion1
14
- } from "./chunk-5w5eg00k.js";
15
- import {
16
9
  asDocument,
10
+ classifySrc,
17
11
  isRecord,
12
+ listRunDirs,
13
+ parseYaml,
14
+ readableSource,
18
15
  requireArray,
19
16
  requireEnum,
20
17
  requireKeys,
21
18
  requireNumber,
19
+ requirePattern,
22
20
  requireRecord,
23
21
  requireString,
22
+ requireStringList,
24
23
  requireVersion,
24
+ requireVersion1,
25
25
  result
26
- } from "./chunk-39zh2e44.js";
27
- import {
28
- PROJECT_FRAMEWORK_DIR,
29
- SRC_PATTERNS,
30
- classifySrc,
31
- listRunDirs,
32
- parseYaml,
33
- readableSource
34
- } from "./chunk-y3cxqvnx.js";
26
+ } from "./chunk-r1efatxk.js";
35
27
 
36
28
  // src/hooks/lib/runFile.ts
37
29
  import { existsSync, readFileSync } from "node:fs";
@@ -188,6 +180,8 @@ function nowRfc3339() {
188
180
  var ON_EXCEED = ["block", "warn"];
189
181
  var ON_HOST_TOKENS_EXCEED = ["warn", "block"];
190
182
  var DEFAULT_ON_HOST_TOKENS_EXCEED = "warn";
183
+ var ON_GRANT_EXCEED = ["warn", "block"];
184
+ var DEFAULT_ON_GRANT_EXCEED = "warn";
191
185
  var ECONOMIES = ["metered-usd", "host-tokens"];
192
186
  var DEFAULT_ECONOMY = "metered-usd";
193
187
  var DEFAULT_WARN_AT_PCT = 80;
@@ -205,6 +199,16 @@ function economyFor(budget, phaseId) {
205
199
  function isHostTokens(budget, phaseId) {
206
200
  return economyFor(budget, phaseId) === "host-tokens";
207
201
  }
202
+ function requirePositiveGrant(value, path, issues) {
203
+ if (typeof value !== "number")
204
+ return;
205
+ if (value > 0)
206
+ return;
207
+ issues.push({
208
+ path,
209
+ message: "expected a grant greater than 0 — absence, not 0, means no grant was recorded"
210
+ });
211
+ }
208
212
  function validateRunBudget(input) {
209
213
  const issues = [];
210
214
  const deprecations = [];
@@ -226,6 +230,19 @@ function validateRunBudget(input) {
226
230
  if (doc.ceiling_host_tokens !== undefined && doc.ceiling_host_tokens !== null) {
227
231
  requireNumber(doc.ceiling_host_tokens, "ceiling_host_tokens", issues);
228
232
  }
233
+ if (doc.authorized_usd !== undefined && doc.authorized_usd !== null) {
234
+ requireNumber(doc.authorized_usd, "authorized_usd", issues);
235
+ requirePositiveGrant(doc.authorized_usd, "authorized_usd", issues);
236
+ }
237
+ if (doc.authorized_by !== undefined && doc.authorized_by !== null) {
238
+ requireString(doc.authorized_by, "authorized_by", issues);
239
+ }
240
+ if (doc.authorized_at !== undefined && doc.authorized_at !== null) {
241
+ requireString(doc.authorized_at, "authorized_at", issues);
242
+ }
243
+ if (doc.on_grant_exceed !== undefined && doc.on_grant_exceed !== null) {
244
+ requireEnum(doc.on_grant_exceed, ON_GRANT_EXCEED, "on_grant_exceed", issues);
245
+ }
229
246
  if (doc.warn_at_pct !== undefined) {
230
247
  requireNumber(doc.warn_at_pct, "warn_at_pct", issues);
231
248
  const pct = doc.warn_at_pct;
@@ -258,6 +275,10 @@ function validateRunBudget(input) {
258
275
  if (phase.ceiling_host_tokens !== undefined && phase.ceiling_host_tokens !== null) {
259
276
  requireNumber(phase.ceiling_host_tokens, `${path}.ceiling_host_tokens`, issues);
260
277
  }
278
+ if (phase.authorized_usd !== undefined && phase.authorized_usd !== null) {
279
+ requireNumber(phase.authorized_usd, `${path}.authorized_usd`, issues);
280
+ requirePositiveGrant(phase.authorized_usd, `${path}.authorized_usd`, issues);
281
+ }
261
282
  const economy = ECONOMIES.includes(phase.economy) ? phase.economy : runEconomy;
262
283
  if (economy === "host-tokens") {
263
284
  const tokens = typeof phase.ceiling_host_tokens === "number" ? phase.ceiling_host_tokens : typeof phase.ceiling_usd === "number" ? phase.ceiling_usd : 0;
@@ -290,12 +311,17 @@ function asRunBudget(input) {
290
311
  economy: doc.economy ?? DEFAULT_ECONOMY,
291
312
  on_host_tokens_exceed: doc.on_host_tokens_exceed ?? DEFAULT_ON_HOST_TOKENS_EXCEED,
292
313
  ceiling_host_tokens: doc.ceiling_host_tokens ?? null,
314
+ authorized_usd: doc.authorized_usd ?? null,
315
+ authorized_by: doc.authorized_by ?? null,
316
+ authorized_at: doc.authorized_at ?? null,
317
+ on_grant_exceed: doc.on_grant_exceed ?? DEFAULT_ON_GRANT_EXCEED,
293
318
  phases: (doc.phases ?? []).map((phase) => ({
294
319
  id: phase.id,
295
320
  ceiling_usd: phase.ceiling_usd,
296
321
  spent_usd: phase.spent_usd,
297
322
  economy: phase.economy ?? null,
298
- ceiling_host_tokens: phase.ceiling_host_tokens ?? null
323
+ ceiling_host_tokens: phase.ceiling_host_tokens ?? null,
324
+ authorized_usd: phase.authorized_usd ?? null
299
325
  }))
300
326
  };
301
327
  }
@@ -312,11 +338,11 @@ function hostTokenCeiling(budget, phaseId) {
312
338
 
313
339
  // src/core/budget/remainingWork.ts
314
340
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
315
- import { join as join8 } from "node:path";
341
+ import { join as join7 } from "node:path";
316
342
 
317
343
  // src/core/run/buildProgress.ts
318
344
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
319
- import { join as join7 } from "node:path";
345
+ import { join as join6 } from "node:path";
320
346
 
321
347
  // src/core/schemas/waves.ts
322
348
  function validateWaves(input) {
@@ -424,25 +450,13 @@ var SKIPPED_DIRS = new Set([
424
450
  "test-results"
425
451
  ]);
426
452
 
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("/"));
437
- }
438
-
439
453
  // src/core/experts/expertDocument.ts
440
454
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
441
- import { join as join4 } from "node:path";
455
+ import { join as join3 } from "node:path";
442
456
 
443
457
  // src/core/experts/loadExperts.ts
444
458
  import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "node:fs";
445
- import { join as join3 } from "node:path";
459
+ import { join as join2 } from "node:path";
446
460
 
447
461
  // src/core/init/competencyLevel.ts
448
462
  var EVIDENCE_KINDS = ["code", "run", "test", "doc", "answer"];
@@ -574,21 +588,21 @@ var EXPERTS_DIRNAME = "experts";
574
588
  var COMPETENCIES_FILE = "competencies.yml";
575
589
  var EXPERT_FILE = "expert.md";
576
590
  function expertsDir(root) {
577
- return join3(root, PROJECT_FRAMEWORK_DIR, EXPERTS_DIRNAME);
591
+ return join2(root, PROJECT_FRAMEWORK_DIR, EXPERTS_DIRNAME);
578
592
  }
579
593
  function expertDir(root, name) {
580
- return join3(expertsDir(root), name);
594
+ return join2(expertsDir(root), name);
581
595
  }
582
596
  function loadExperts(root, now = new Date) {
583
597
  const dir = expertsDir(root);
584
598
  if (!existsSync2(dir))
585
599
  return [];
586
- const names = readdirSync(dir).filter((entry) => statSync(join3(dir, entry)).isDirectory()).sort();
600
+ const names = readdirSync(dir).filter((entry) => statSync(join2(dir, entry)).isDirectory()).sort();
587
601
  return names.map((name) => loadExpert(root, name, now));
588
602
  }
589
603
  function loadExpert(root, name, now = new Date) {
590
604
  const dir = expertDir(root, name);
591
- const path = join3(dir, COMPETENCIES_FILE);
605
+ const path = join2(dir, COMPETENCIES_FILE);
592
606
  const empty = { name, dir, status: "unknown", lastTrained: null, areas: [], drifted: [] };
593
607
  if (!existsSync2(path))
594
608
  return { ...empty, error: `${COMPETENCIES_FILE} is missing` };
@@ -647,7 +661,7 @@ function str3(value) {
647
661
  // src/core/experts/expertDocument.ts
648
662
  var H2_RE = /^##\s+(.+?)\s*$/;
649
663
  function readExpertDocument(root, name) {
650
- const path = join4(expertDir(root, name), EXPERT_FILE);
664
+ const path = join3(expertDir(root, name), EXPERT_FILE);
651
665
  if (!existsSync3(path)) {
652
666
  return { path, exists: false, frontMatter: new Map, body: "" };
653
667
  }
@@ -754,9 +768,9 @@ var DEFAULT_KNOWLEDGE_MAX_BYTES = 48 * 1024;
754
768
 
755
769
  // src/core/experts/workspaceRepos.ts
756
770
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
757
- import { join as join5 } from "node:path";
771
+ import { join as join4 } from "node:path";
758
772
  function readWorkspaceDocument(root) {
759
- const path = join5(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
773
+ const path = join4(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
760
774
  if (!existsSync4(path))
761
775
  return null;
762
776
  try {
@@ -827,7 +841,7 @@ var MAX_TOUCHED_BYTES = 64 * 1024;
827
841
 
828
842
  // src/core/build/plan.ts
829
843
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
830
- import { join as join6 } from "node:path";
844
+ import { join as join5 } from "node:path";
831
845
 
832
846
  // src/core/schemas/budget.ts
833
847
  function validateBudget(input) {
@@ -858,7 +872,7 @@ var PLAN_PHASE = "03-plan";
858
872
  var PLAN_BUDGET_FILE = "budget.yml";
859
873
  function loadPlanPrices(planDir, stories) {
860
874
  const empty = new Map;
861
- const path = join6(planDir, PLAN_BUDGET_FILE);
875
+ const path = join5(planDir, PLAN_BUDGET_FILE);
862
876
  if (!existsSync5(path))
863
877
  return { prices: empty, issue: null };
864
878
  let doc;
@@ -928,8 +942,8 @@ var PLAN_DIR = "03-plan";
928
942
  var BUILD_PHASE2 = "04-build";
929
943
  var STATUS_RE = /^status\s*:\s*(\w+)\s*$/m;
930
944
  function buildProgress(runDir) {
931
- const wavesPath = join7(runDir, PLAN_DIR, "waves.yml");
932
- const implicitPath = join7(runDir, BUILD_PHASE2, IMPLICIT_PLAN_FILE);
945
+ const wavesPath = join6(runDir, PLAN_DIR, "waves.yml");
946
+ const implicitPath = join6(runDir, BUILD_PHASE2, IMPLICIT_PLAN_FILE);
933
947
  const implicit = !existsSync6(wavesPath) && existsSync6(implicitPath);
934
948
  const path = implicit ? implicitPath : wavesPath;
935
949
  if (!existsSync6(path))
@@ -970,7 +984,7 @@ function implicitStatus(path) {
970
984
  }
971
985
  }
972
986
  function statusOf(runDir, id) {
973
- const path = join7(runDir, PLAN_DIR, "stories", `${id}.md`);
987
+ const path = join6(runDir, PLAN_DIR, "stories", `${id}.md`);
974
988
  if (!existsSync6(path))
975
989
  return "missing";
976
990
  try {
@@ -981,7 +995,7 @@ function statusOf(runDir, id) {
981
995
  }
982
996
  function costByStory(runDir) {
983
997
  const costs = new Map;
984
- const path = join7(runDir, "events.jsonl");
998
+ const path = join6(runDir, "events.jsonl");
985
999
  if (!existsSync6(path))
986
1000
  return costs;
987
1001
  let text;
@@ -1081,7 +1095,7 @@ function measure(input) {
1081
1095
  if (all.length === 0)
1082
1096
  return fallback;
1083
1097
  const ids = new Set(all.map((story) => story.id));
1084
- const prices = progress.implicit ? new Map : loadPlanPrices(join8(input.runDir, PLAN_DIR), ids).prices;
1098
+ const prices = progress.implicit ? new Map : loadPlanPrices(join7(input.runDir, PLAN_DIR), ids).prices;
1085
1099
  const verdicts = reviewVerdictsByStory(input.runDir);
1086
1100
  const caps = new CapMath(input, prices, all.length);
1087
1101
  const stories = [];
@@ -1186,7 +1200,7 @@ class CapMath {
1186
1200
  }
1187
1201
  function reviewVerdictsByStory(runDir) {
1188
1202
  const counts = new Map;
1189
- const path = join8(runDir, "events.jsonl");
1203
+ const path = join7(runDir, "events.jsonl");
1190
1204
  if (!existsSync7(path))
1191
1205
  return counts;
1192
1206
  let text;
@@ -1283,4 +1297,4 @@ function shortBy(estimate, remaining) {
1283
1297
  return Math.max(0.01, Math.ceil((estimate - remaining) * 100) / 100);
1284
1298
  }
1285
1299
 
1286
- 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 };
1300
+ export { loadRunView, isAttendedByHostView, runSpend, hostTokensIn, renderRunEconomies, cursorStage, openRunViews, newestActiveRun, currentActor, nowRfc3339, DEFAULT_ON_HOST_TOKENS_EXCEED, DEFAULT_ON_GRANT_EXCEED, DEFAULT_ECONOMY, economyFor, isHostTokens, validateRunBudget, asRunBudget, expertsDir, loadExperts, readExpertDomain, pathsIntersect, stackExpertNames, remainingWork, wouldExceed, wouldExceedHostTokens, raiseCommand, shortBy };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runtime
3
- } from "./chunk-y3cxqvnx.js";
3
+ } from "./chunk-r1efatxk.js";
4
4
 
5
5
  // src/core/hooks/passthrough.ts
6
6
  async function readStdin() {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  parseHookInput,
3
3
  readStdin
4
- } from "./chunk-x3zy7zrn.js";
4
+ } from "./chunk-6c84kjd7.js";
5
5
 
6
6
  // src/hooks/lib/decide.ts
7
7
  function deny(reason) {
@@ -1,9 +1,8 @@
1
- import {
2
- parseFrontMatter
3
- } from "./chunk-5w5eg00k.js";
4
1
  import {
5
2
  asDocument,
6
3
  isRecord,
4
+ parseFrontMatter,
5
+ parseHandoff,
7
6
  requireArray,
8
7
  requireEnum,
9
8
  requireKeys,
@@ -11,10 +10,7 @@ import {
11
10
  requireString,
12
11
  requireVersion,
13
12
  result
14
- } from "./chunk-39zh2e44.js";
15
- import {
16
- parseHandoff
17
- } from "./chunk-y3cxqvnx.js";
13
+ } from "./chunk-r1efatxk.js";
18
14
 
19
15
  // src/core/events/EventLog.ts
20
16
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
@@ -48,6 +44,7 @@ var EVENT_TYPES = [
48
44
  "story.base_fastforwarded",
49
45
  "story.review_retried",
50
46
  "story.work_rescued",
47
+ "story.touches_widened",
51
48
  "result.unreadable",
52
49
  "operator_note",
53
50
  "check.passed",
@@ -55,9 +52,11 @@ var EVENT_TYPES = [
55
52
  "budget.warned",
56
53
  "budget.blocked",
57
54
  "budget.raised",
55
+ "budget.granted",
58
56
  "fact.added",
59
57
  "fact.retired",
60
58
  "fact.superseded",
59
+ "fact.conflict_raised",
61
60
  "doc.superseded",
62
61
  "map.refreshed",
63
62
  "ticket.synced",
@@ -472,6 +471,7 @@ var MAX_TASKS = 200;
472
471
  var GATE_EXECUTOR_TYPES = ["human", "agent", "auto"];
473
472
  var GATE_AUTHORITY_TYPES = ["direct", "delegated"];
474
473
  var GATE_AUTHORITY_SOURCES = ["self", "run.created", "gate.policy_changed", "unrecorded"];
474
+ var TRIAGE_BUDGET_BASES = ["model-guess", "owner-grant", "preset"];
475
475
  function isTerminal(status) {
476
476
  return TERMINAL_STATUSES.includes(status);
477
477
  }
@@ -585,6 +585,9 @@ function validateRunFile(input) {
585
585
  requireString(slug, `triage.depends_on[${i}]`, issues);
586
586
  });
587
587
  }
588
+ if (doc.triage.budget_basis !== undefined) {
589
+ requireEnum(doc.triage.budget_basis, TRIAGE_BUDGET_BASES, "triage.budget_basis", issues);
590
+ }
588
591
  } else {
589
592
  issues.push({ path: "triage", message: "expected a mapping" });
590
593
  }
@@ -815,4 +818,16 @@ function validateGateEvidence(value, base, issues) {
815
818
  }
816
819
  }
817
820
 
818
- export { EventLog, EVIDENCE_FILE, gateEvidenceRelPath, parseEvidence, PHASE_ID_RE, isTerminal, isFinished, isAttendedByHost, flatten, stageAt, deriveRunStatus, derivePhaseStatus, validateRunFile, asRunFile };
821
+ // src/core/facilitator/paths.ts
822
+ import { join as join2, relative, sep } from "node:path";
823
+ function agentDir(runDir, stageId) {
824
+ return join2(runDir, ".agent", stageId);
825
+ }
826
+ function evidencePath(runDir, stageId) {
827
+ return join2(agentDir(runDir, stageId), EVIDENCE_FILE);
828
+ }
829
+ function gateEvidencePath(runDir, phaseId, stageId) {
830
+ return join2(runDir, ...gateEvidenceRelPath(phaseId, stageId).split("/"));
831
+ }
832
+
833
+ export { EventLog, parseEvidence, PHASE_ID_RE, isTerminal, isFinished, isAttendedByHost, flatten, stageAt, deriveRunStatus, derivePhaseStatus, validateRunFile, asRunFile, evidencePath, gateEvidencePath };
@@ -0,0 +1,103 @@
1
+ import {
2
+ findDuplicate
3
+ } from "./chunk-kqvbzyc0.js";
4
+ import {
5
+ evidencePath,
6
+ gateEvidencePath,
7
+ parseEvidence
8
+ } from "./chunk-9v45j71e.js";
9
+ import {
10
+ openBlocks,
11
+ parseQuestions
12
+ } from "./chunk-wzzywf6c.js";
13
+
14
+ // src/core/distill/distill.ts
15
+ var CONFLICT_THRESHOLD = 0.6;
16
+ function conflictOf(claim, facts) {
17
+ const hit = findDuplicate(claim.match, claim.area, facts, CONFLICT_THRESHOLD);
18
+ if (hit === null)
19
+ return null;
20
+ return hit.fact.fact.trim() === claim.text.trim() ? null : hit;
21
+ }
22
+
23
+ // src/core/run/decisionCards.ts
24
+ import { existsSync, readFileSync } from "node:fs";
25
+ import { join } from "node:path";
26
+ function questionsCard(ctx) {
27
+ const blocks = openQuestionBlocks(join(ctx.runDir, ctx.phaseId, "questions.md"));
28
+ if (blocks.length === 0)
29
+ return null;
30
+ const recommendations = readRecommendations(ctx);
31
+ return {
32
+ kind: "questions",
33
+ run: ctx.runId,
34
+ gate: `${ctx.phaseId}/${ctx.stageId}`,
35
+ headline: "",
36
+ questions: blocks.map((block) => toQuestion(block, ctx.runId, recommendations)),
37
+ detail: [],
38
+ commands: []
39
+ };
40
+ }
41
+ function answerCommand(questionId, runId) {
42
+ return `tldrx answer ${questionId} "…" --run ${runId}`;
43
+ }
44
+ function toQuestion(block, runId, recommendations) {
45
+ const recommended = recommendations.get(block.id);
46
+ return {
47
+ id: block.id,
48
+ title: block.title,
49
+ whyAsked: block.whyAsked,
50
+ options: block.options.map(firstLine),
51
+ recommendation: recommended === undefined ? null : toRecommendation(recommended),
52
+ answerCommand: answerCommand(block.id, runId)
53
+ };
54
+ }
55
+ function firstLine(option) {
56
+ return { letter: option.letter, text: option.text.split(`
57
+ `)[0] ?? "" };
58
+ }
59
+ function toRecommendation(entry) {
60
+ return { option: entry.option, why: entry.why, src: entry.src };
61
+ }
62
+ function openQuestionBlocks(path) {
63
+ if (!existsSync(path))
64
+ return [];
65
+ try {
66
+ return openBlocks(parseQuestions(readFileSync(path, "utf8")).blocks);
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+ function readRecommendations(ctx) {
72
+ const out = new Map;
73
+ const paths = [
74
+ evidencePath(ctx.runDir, ctx.stageId),
75
+ gateEvidencePath(ctx.runDir, ctx.phaseId, ctx.stageId)
76
+ ];
77
+ for (const path of paths) {
78
+ const text = readOrNull(path);
79
+ if (text === null)
80
+ continue;
81
+ const front = parseEvidence(text).front;
82
+ if (front === null)
83
+ continue;
84
+ for (const entry of front.recommend) {
85
+ if (!out.has(entry.q))
86
+ out.set(entry.q, entry);
87
+ }
88
+ if (out.size > 0)
89
+ return out;
90
+ }
91
+ return out;
92
+ }
93
+ function readOrNull(path) {
94
+ if (!existsSync(path))
95
+ return null;
96
+ try {
97
+ return readFileSync(path, "utf8");
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ export { questionsCard, conflictOf };
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  DEFAULT_ECONOMY,
3
+ DEFAULT_ON_GRANT_EXCEED,
3
4
  DEFAULT_ON_HOST_TOKENS_EXCEED,
4
5
  asRunBudget,
5
6
  cursorStage,
@@ -7,7 +8,7 @@ import {
7
8
  nowRfc3339,
8
9
  openRunViews,
9
10
  validateRunBudget
10
- } from "./chunk-hg43zexg.js";
11
+ } from "./chunk-1c43349s.js";
11
12
  import {
12
13
  EventLog,
13
14
  asRunFile,
@@ -19,7 +20,7 @@ import {
19
20
  isTerminal,
20
21
  stageAt,
21
22
  validateRunFile
22
- } from "./chunk-fv66tdsm.js";
23
+ } from "./chunk-9v45j71e.js";
23
24
  import {
24
25
  backupPathFor,
25
26
  isAlive,
@@ -28,17 +29,18 @@ import {
28
29
  workspaceRootOfRunDir,
29
30
  writeAtomic,
30
31
  yamlScalar
31
- } from "./chunk-8awygmqw.js";
32
+ } from "./chunk-tg7ppsxr.js";
32
33
  import {
34
+ isAdvisory,
33
35
  noteDeprecations,
34
36
  openBlocks,
35
37
  parseQuestions
36
- } from "./chunk-vyvjbbmb.js";
38
+ } from "./chunk-wzzywf6c.js";
37
39
  import {
38
40
  listRunDirs,
39
41
  parseYaml,
40
42
  parseYamlRepairing
41
- } from "./chunk-y3cxqvnx.js";
43
+ } from "./chunk-r1efatxk.js";
42
44
 
43
45
  // src/core/statusline/runSnapshot.ts
44
46
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
@@ -136,7 +138,8 @@ function emitRunYaml(run) {
136
138
  `budget: {ceiling_usd: ${money(run.budget.ceiling_usd)}, spent_usd: ${money(run.budget.spent_usd)}, ` + `per_agent_max_usd: ${money(run.budget.per_agent_max_usd)}}`
137
139
  ];
138
140
  if (run.triage !== undefined) {
139
- lines.push(`triage: {split: ${yamlScalar(run.triage.split)}, depends_on: ${inlineList(run.triage.depends_on)}}`);
141
+ const basis = run.triage.budget_basis === undefined ? "" : `, budget_basis: ${yamlScalar(run.triage.budget_basis)}`;
142
+ lines.push(`triage: {split: ${yamlScalar(run.triage.split)}, depends_on: ${inlineList(run.triage.depends_on)}${basis}}`);
140
143
  }
141
144
  if (run.build !== undefined && run.build.epic_branch.length > 0) {
142
145
  const model = run.build.branch_model === undefined ? "" : `, branch_model: ${yamlScalar(run.build.branch_model)}`;
@@ -183,12 +186,17 @@ function emitBudgetYaml(budget) {
183
186
  ...budget.economy === DEFAULT_ECONOMY ? [] : [`economy: ${yamlScalar(budget.economy)}`],
184
187
  ...budget.on_host_tokens_exceed === DEFAULT_ON_HOST_TOKENS_EXCEED ? [] : [`on_host_tokens_exceed: ${yamlScalar(budget.on_host_tokens_exceed)}`],
185
188
  ...budget.ceiling_host_tokens === null ? [] : [`ceiling_host_tokens: ${tokens(budget.ceiling_host_tokens)}`],
189
+ ...budget.authorized_usd === null ? [] : [`authorized_usd: ${money(budget.authorized_usd)}`],
190
+ ...budget.authorized_by === null ? [] : [`authorized_by: ${yamlScalar(budget.authorized_by)}`],
191
+ ...budget.authorized_at === null ? [] : [`authorized_at: ${yamlScalar(budget.authorized_at)}`],
192
+ ...budget.on_grant_exceed === DEFAULT_ON_GRANT_EXCEED ? [] : [`on_grant_exceed: ${yamlScalar(budget.on_grant_exceed)}`],
186
193
  "phases:"
187
194
  ];
188
195
  for (const phase of budget.phases) {
189
196
  const economy = phase.economy === null ? "" : `, economy: ${yamlScalar(phase.economy)}`;
190
197
  const hostTokens = phase.ceiling_host_tokens === null ? "" : `, ceiling_host_tokens: ${tokens(phase.ceiling_host_tokens)}`;
191
- lines.push(` - {id: ${yamlScalar(phase.id)}, ceiling_usd: ${money(phase.ceiling_usd)}, ` + `spent_usd: ${money(phase.spent_usd)}${economy}${hostTokens}}`);
198
+ const grant = phase.authorized_usd === null ? "" : `, authorized_usd: ${money(phase.authorized_usd)}`;
199
+ lines.push(` - {id: ${yamlScalar(phase.id)}, ceiling_usd: ${money(phase.ceiling_usd)}, ` + `spent_usd: ${money(phase.spent_usd)}${economy}${hostTokens}${grant}}`);
192
200
  }
193
201
  return `${lines.join(`
194
202
  `)}
@@ -357,7 +365,8 @@ class RunStore {
357
365
  ...mine,
358
366
  ceiling_usd: theirs.ceiling_usd,
359
367
  ceiling_host_tokens: theirs.ceiling_host_tokens,
360
- economy: theirs.economy
368
+ economy: theirs.economy,
369
+ authorized_usd: theirs.authorized_usd
361
370
  };
362
371
  })
363
372
  };
@@ -464,6 +473,15 @@ function openQuestionIds(path) {
464
473
  return [];
465
474
  }
466
475
  }
476
+ function blockingQuestionIds(path) {
477
+ if (!existsSync2(path))
478
+ return [];
479
+ try {
480
+ return openBlocks(parseQuestions(readFileSync2(path, "utf8")).blocks).filter((block) => !isAdvisory(block)).map((block) => block.id);
481
+ } catch {
482
+ return [];
483
+ }
484
+ }
467
485
 
468
486
  // src/core/statusline/runSnapshot.ts
469
487
  function runSnapshot(root) {
@@ -559,7 +577,6 @@ function numberAfter(text, key) {
559
577
  }
560
578
 
561
579
  // src/core/run/waiting.ts
562
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
563
580
  import { basename as basename2, join as join4 } from "node:path";
564
581
 
565
582
  // src/core/run/prepared.ts
@@ -634,7 +651,7 @@ function waitingFor(run, runDir) {
634
651
  questions: []
635
652
  };
636
653
  }
637
- const open = openQuestionIds2(join4(runDir, cursor.phase, "questions.md"));
654
+ const open = blockingQuestionIds(join4(runDir, cursor.phase, "questions.md"));
638
655
  switch (entry.stage.status) {
639
656
  case "awaiting_gate":
640
657
  return {
@@ -698,15 +715,6 @@ function cancelledMessage(cancelled) {
698
715
  const note = cancelled.note.trim();
699
716
  return `run cancelled by ${by}${when}${note === "" ? "" : ` — ${note}`}`;
700
717
  }
701
- function openQuestionIds2(path) {
702
- if (!existsSync5(path))
703
- return [];
704
- try {
705
- return openBlocks(parseQuestions(readFileSync4(path, "utf8")).blocks).map((b) => b.id);
706
- } catch {
707
- return [];
708
- }
709
- }
710
718
 
711
719
  // src/core/run/runStatus.ts
712
720
  var BAR_CELLS = 5;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  toolInput
3
- } from "./chunk-2y3kwfrx.js";
3
+ } from "./chunk-6ws778wq.js";
4
4
 
5
5
  // src/hooks/lib/wouldBe.ts
6
6
  import { existsSync, readFileSync } from "node:fs";