tldr-experts 0.3.0 → 0.3.1

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 (32) hide show
  1. package/CHANGELOG.md +994 -0
  2. package/README.md +26 -7
  3. package/dist/hooks/answer-capture.js +20 -11
  4. package/dist/hooks/budget-gate.js +28 -8
  5. package/dist/hooks/chunk-0z27twdk.js +159 -0
  6. package/dist/hooks/{chunk-0bt6yb2q.js → chunk-39zh2e44.js} +10 -1
  7. package/dist/hooks/{chunk-1zwcxd3f.js → chunk-3g61yg59.js} +1 -1
  8. package/dist/hooks/chunk-3t91gvpp.js +193 -0
  9. package/dist/hooks/{chunk-kw4tffzf.js → chunk-458wgg9j.js} +30 -24
  10. package/dist/hooks/{chunk-j234zf0t.js → chunk-9kkm6q0t.js} +4 -165
  11. package/dist/hooks/{chunk-t8tdv11p.js → chunk-afamdvyn.js} +1 -1
  12. package/dist/hooks/{chunk-a8p2rc94.js → chunk-s5qsb4k6.js} +1 -1
  13. package/dist/hooks/{chunk-g395gk7e.js → chunk-sznsenee.js} +1 -1
  14. package/dist/hooks/{chunk-y0jdr3et.js → chunk-xpxe94qe.js} +23 -10
  15. package/dist/hooks/{chunk-azctppjh.js → chunk-ybacnpxd.js} +4 -1
  16. package/dist/hooks/{chunk-x98qs959.js → chunk-ytvmc5ns.js} +19 -22
  17. package/dist/hooks/chunk-zdxgragg.js +1655 -0
  18. package/dist/hooks/{chunk-p274ckxv.js → chunk-ztczwtj0.js} +6 -1
  19. package/dist/hooks/claim-sources.js +9 -9
  20. package/dist/hooks/dod-gate.js +12 -37
  21. package/dist/hooks/no-reask.js +9 -9
  22. package/dist/hooks/session-start.js +176 -422
  23. package/dist/hooks/statusline.js +14 -11
  24. package/dist/tldrx.js +12029 -6330
  25. package/package.json +1 -1
  26. package/plugin/.claude-plugin/plugin.json +1 -1
  27. package/plugin/skills/tldrx/SKILL.md +16 -3
  28. package/stages/build/stage.md +5 -0
  29. package/templates/budget.yml +5 -0
  30. package/dist/hooks/chunk-ae6bkfs5.js +0 -0
  31. package/dist/hooks/chunk-sdjnnmzz.js +0 -497
  32. package/dist/hooks/chunk-t56k6146.js +0 -14
@@ -2,7 +2,7 @@ import {
2
2
  hasSrcMarker,
3
3
  parseSrcToken,
4
4
  resolveSrc
5
- } from "./chunk-g395gk7e.js";
5
+ } from "./chunk-sznsenee.js";
6
6
 
7
7
  // src/core/text/handoff.ts
8
8
  var HANDOFF_SECTIONS = ["Findings", "Decisions", "Unknowns", "Evidence ledger"];
@@ -64,56 +64,55 @@ function parseHandoff(text) {
64
64
  function isHandoff(text) {
65
65
  return missingSections(parseHandoff(text)).length === 0;
66
66
  }
67
- function missingSections(handoff) {
67
+ function missingSections(handoff, required = HANDOFF_SECTIONS) {
68
68
  const present = handoff.headings;
69
69
  const missing = [];
70
70
  let cursor = 0;
71
- for (const required of HANDOFF_SECTIONS) {
72
- const at = present.indexOf(required, cursor);
71
+ for (const name of required) {
72
+ const at = present.indexOf(name, cursor);
73
73
  if (at === -1)
74
- missing.push(required);
74
+ missing.push(name);
75
75
  else
76
76
  cursor = at + 1;
77
77
  }
78
78
  return missing;
79
79
  }
80
80
  var MAX_BULLETS = 200;
81
- function validateHandoff(text, ctx) {
82
- const handoff = parseHandoff(text);
83
- const missing = missingSections(handoff);
81
+ function validateSections(handoff, required, ctx, lineOffset = 0) {
84
82
  const emptySections = [];
85
83
  const unsourced = [];
86
84
  const malformed = [];
87
85
  const unresolved = [];
88
86
  const unverified = [];
89
87
  let bulletCount = 0;
90
- const required = new Set(HANDOFF_SECTIONS);
88
+ const wanted = new Set(required);
91
89
  for (const section of handoff.sections) {
92
- if (!required.has(section.name))
90
+ if (!wanted.has(section.name))
93
91
  continue;
94
92
  if (section.bullets.length === 0) {
95
- emptySections.push({ name: section.name, line: section.headingLine });
93
+ emptySections.push({ name: section.name, line: section.headingLine + lineOffset });
96
94
  }
97
95
  for (const bullet of section.bullets) {
98
96
  bulletCount++;
97
+ const line = bullet.line + lineOffset;
99
98
  if (bullet.token === null) {
100
99
  if (hasSrcMarker(bullet.text)) {
101
100
  malformed.push({
102
- line: bullet.line,
101
+ line,
103
102
  message: "malformed citation — the `[src: …]` token must be the last thing on the line " + "(closing quotes, brackets and a final `.` are allowed after it, words are not)"
104
103
  });
105
104
  } else {
106
- unsourced.push(bullet.line);
105
+ unsourced.push(line);
107
106
  }
108
107
  continue;
109
108
  }
110
109
  for (const error of bullet.token.errors) {
111
- unresolved.push({ line: bullet.line, message: `[src: ${error.raw}] — ${error.message}` });
110
+ unresolved.push({ line, message: `[src: ${error.raw}] — ${error.message}` });
112
111
  }
113
112
  const claim = bullet.text.replace(bullet.token.raw, " ").trim();
114
113
  for (const ref of bullet.token.refs) {
115
114
  const resolution = resolveSrc(ref, ctx, section.name, claim);
116
- const issue = { line: bullet.line, message: `[src: ${ref.raw}] — ${resolution.message ?? "unresolvable"}` };
115
+ const issue = { line, message: `[src: ${ref.raw}] — ${resolution.message ?? "unresolvable"}` };
117
116
  if (resolution.outcome === "refused")
118
117
  unresolved.push(issue);
119
118
  else if (resolution.outcome === "unverified")
@@ -121,19 +120,26 @@ function validateHandoff(text, ctx) {
121
120
  }
122
121
  }
123
122
  }
124
- if (bulletCount > MAX_BULLETS) {
125
- unresolved.push({ line: 0, message: `${bulletCount} bullets exceeds the ${MAX_BULLETS} cap` });
123
+ return { emptySections, unsourced, malformed, unresolved, unverified, bulletCount };
124
+ }
125
+ function validateHandoff(text, ctx) {
126
+ const handoff = parseHandoff(text);
127
+ const missing = missingSections(handoff);
128
+ const report = validateSections(handoff, HANDOFF_SECTIONS, ctx);
129
+ const unresolved = [...report.unresolved];
130
+ if (report.bulletCount > MAX_BULLETS) {
131
+ unresolved.push({ line: 0, message: `${report.bulletCount} bullets exceeds the ${MAX_BULLETS} cap` });
126
132
  }
127
133
  return {
128
- ok: missing.length === 0 && emptySections.length === 0 && unsourced.length === 0 && malformed.length === 0 && unresolved.length === 0,
134
+ ok: missing.length === 0 && report.emptySections.length === 0 && report.unsourced.length === 0 && report.malformed.length === 0 && unresolved.length === 0,
129
135
  missingSections: missing,
130
- emptySections,
131
- unsourced,
132
- malformed,
136
+ emptySections: report.emptySections,
137
+ unsourced: report.unsourced,
138
+ malformed: report.malformed,
133
139
  unresolved,
134
- unverified,
135
- bulletCount
140
+ unverified: report.unverified,
141
+ bulletCount: report.bulletCount
136
142
  };
137
143
  }
138
144
 
139
- export { isHandoff, validateHandoff };
145
+ export { parseHandoff, isHandoff, validateHandoff };
@@ -1,170 +1,7 @@
1
- import {
2
- parseSrcToken
3
- } from "./chunk-g395gk7e.js";
4
1
  import {
5
2
  PROJECT_FRAMEWORK_DIR,
6
3
  PROJECT_WORK_DIR
7
- } from "./chunk-p274ckxv.js";
8
-
9
- // src/core/text/questions.ts
10
- var REQUIRED_METADATA_KEYS = ["id", "status", "area", "asked_by", "asked_at"];
11
- var HEADING_RE = /^##\s+(Q\d{1,6})\s+·\s+(.+?)\s*$/;
12
- var METADATA_RE = /^<!--\s*(.*?)\s*-->$/;
13
- var WHY_RE = /^Why asked:\s*(.*)$/;
14
- var OPTION_RE = /^-\s+([A-E])\)\s*(.*)$/;
15
- var ANSWER_RE = /^\[Answer\]:[ \t]*(\S.*)$/;
16
- var ANSWER_SLOT_RE = /^\[Answer\]:/;
17
- var FOOTER_KEYS = ["answered_by", "answered_at", "fact"];
18
- function parseQuestions(text) {
19
- const trailingNewline = text.endsWith(`
20
- `);
21
- const body = trailingNewline ? text.slice(0, -1) : text;
22
- const lines = body === "" ? [] : body.split(`
23
- `);
24
- const preamble = [];
25
- const blocks = [];
26
- let pending = null;
27
- const close = () => {
28
- if (pending === null)
29
- return;
30
- blocks.push(buildBlock(pending.start + 1, pending.lines));
31
- pending = null;
32
- };
33
- for (let i = 0;i < lines.length; i++) {
34
- const line = lines[i] ?? "";
35
- if (HEADING_RE.test(line)) {
36
- close();
37
- pending = { start: i, lines: [line] };
38
- continue;
39
- }
40
- if (pending === null)
41
- preamble.push(line);
42
- else
43
- pending.lines.push(line);
44
- }
45
- close();
46
- return { preamble, blocks, trailingNewline };
47
- }
48
- function buildBlock(startLine, lines) {
49
- const heading = HEADING_RE.exec(lines[0] ?? "");
50
- const id = heading?.[1] ?? "";
51
- const title = heading?.[2] ?? "";
52
- let metadata = null;
53
- let metadataIndex = -1;
54
- let whyAsked = null;
55
- let whySrc = null;
56
- const options = [];
57
- let answer = "";
58
- let answerIndex = -1;
59
- let footer = null;
60
- for (let i = 1;i < lines.length; i++) {
61
- const line = lines[i] ?? "";
62
- const comment = METADATA_RE.exec(line);
63
- if (comment !== null && comment[1] !== undefined) {
64
- const pairs = parsePipeComment(comment[1]);
65
- if (metadata === null && pairs.some(([k]) => k === "id")) {
66
- metadata = toMetadata(pairs);
67
- metadataIndex = i;
68
- } else if (footer === null && pairs.some(([k]) => k === "answered_by")) {
69
- footer = toFooter(pairs);
70
- }
71
- continue;
72
- }
73
- const why = WHY_RE.exec(line);
74
- if (why !== null && whyAsked === null) {
75
- whyAsked = why[1] ?? "";
76
- whySrc = parseSrcToken(line);
77
- continue;
78
- }
79
- const option = OPTION_RE.exec(line);
80
- if (option !== null && option[1] !== undefined && option[2] !== undefined) {
81
- options.push({ letter: option[1], text: option[2] });
82
- continue;
83
- }
84
- if (ANSWER_SLOT_RE.test(line) && answerIndex === -1) {
85
- answerIndex = i;
86
- const captured = ANSWER_RE.exec(line);
87
- answer = (captured?.[1] ?? "").trim();
88
- }
89
- }
90
- return {
91
- id,
92
- title,
93
- metadata,
94
- metadataIndex,
95
- whyAsked,
96
- whySrc,
97
- options,
98
- answer,
99
- answerIndex,
100
- footer,
101
- startLine,
102
- lines: [...lines]
103
- };
104
- }
105
- function parsePipeComment(inner) {
106
- const pairs = [];
107
- for (const part of inner.split("|")) {
108
- const colon = part.indexOf(":");
109
- if (colon === -1)
110
- continue;
111
- pairs.push([part.slice(0, colon).trim(), part.slice(colon + 1).trim()]);
112
- }
113
- return pairs;
114
- }
115
- function toMetadata(pairs) {
116
- const map = new Map(pairs);
117
- const extra = pairs.filter(([k]) => !REQUIRED_METADATA_KEYS.includes(k));
118
- return {
119
- id: map.get("id") ?? "",
120
- status: map.get("status") ?? "",
121
- area: map.get("area") ?? "",
122
- asked_by: map.get("asked_by") ?? "",
123
- asked_at: map.get("asked_at") ?? "",
124
- extra
125
- };
126
- }
127
- function toFooter(pairs) {
128
- const map = new Map(pairs);
129
- return {
130
- answered_by: map.get("answered_by") ?? "",
131
- answered_at: map.get("answered_at") ?? "",
132
- fact: map.get("fact") ?? ""
133
- };
134
- }
135
- function serializeQuestions(doc) {
136
- const out = [...doc.preamble];
137
- for (const block of doc.blocks)
138
- out.push(...block.lines);
139
- const text = out.join(`
140
- `);
141
- return doc.trailingNewline ? `${text}
142
- ` : text;
143
- }
144
- function detectAnswered(blocks) {
145
- return blocks.filter((b) => b.metadata?.status === "open" && b.answer !== "");
146
- }
147
- function openBlocks(blocks) {
148
- return blocks.filter((b) => b.metadata?.status === "open");
149
- }
150
- function recordAnswer(block, footer) {
151
- const lines = [...block.lines];
152
- if (block.metadataIndex >= 0) {
153
- const line = lines[block.metadataIndex] ?? "";
154
- lines[block.metadataIndex] = line.replace(/(\|\s*status:\s*)open(\s*\|)/, "$1answered$2");
155
- }
156
- const footerLine = `<!-- ${FOOTER_KEYS.map((k) => `${k}: ${footer[k]}`).join(" | ")} -->`;
157
- const insertAt = block.answerIndex >= 0 ? block.answerIndex + 1 : lines.length;
158
- lines.splice(insertAt, 0, footerLine);
159
- const metadata = block.metadata === null ? null : { ...block.metadata, status: "answered" };
160
- return { ...block, lines, metadata, footer };
161
- }
162
- function replaceBlock(doc, block) {
163
- return {
164
- ...doc,
165
- blocks: doc.blocks.map((b) => b.id === block.id ? block : b)
166
- };
167
- }
4
+ } from "./chunk-ztczwtj0.js";
168
5
 
169
6
  // src/core/lock/workspaceLock.ts
170
7
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, rmSync as rmSync2, writeSync, closeSync } from "node:fs";
@@ -315,6 +152,8 @@ function emitFact(fact, indent = " ") {
315
152
  `${inner}supersedes: ${yamlScalar(fact.supersedes)}`,
316
153
  `${inner}superseded_by: ${yamlScalar(fact.superseded_by)}`
317
154
  ];
155
+ if (fact.truncated === true)
156
+ lines.push(`${inner}truncated: true`);
318
157
  if (fact.retired === null) {
319
158
  lines.push(`${inner}retired: null`);
320
159
  } else {
@@ -336,4 +175,4 @@ function emitFactsYaml(file, header) {
336
175
  `;
337
176
  }
338
177
 
339
- export { readLock, isAlive, workspaceRootOfRunDir, workspaceRootOfFactsPath, withWorkspaceLock, yamlScalar, emitFactsYaml, parseQuestions, serializeQuestions, detectAnswered, openBlocks, recordAnswer, replaceBlock };
178
+ export { readLock, isAlive, workspaceRootOfRunDir, workspaceRootOfFactsPath, withWorkspaceLock, yamlScalar, emitFactsYaml };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  toolInput
3
- } from "./chunk-1zwcxd3f.js";
3
+ } from "./chunk-3g61yg59.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-p274ckxv.js";
3
+ } from "./chunk-ztczwtj0.js";
4
4
 
5
5
  // src/core/hooks/passthrough.ts
6
6
  async function readStdin() {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  PROJECT_FRAMEWORK_DIR,
3
3
  parseYaml
4
- } from "./chunk-p274ckxv.js";
4
+ } from "./chunk-ztczwtj0.js";
5
5
 
6
6
  // src/core/text/srcToken.ts
7
7
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
@@ -1,10 +1,12 @@
1
1
  import {
2
+ DEFAULT_ECONOMY,
2
3
  asRunBudget,
3
4
  asRunFile,
4
5
  cursorStage,
5
6
  derivePhaseStatus,
6
7
  deriveRunStatus,
7
8
  flatten,
9
+ isAttendedByHost,
8
10
  isFinished,
9
11
  isTerminal,
10
12
  nowRfc3339,
@@ -12,26 +14,26 @@ import {
12
14
  stageAt,
13
15
  validateRunBudget,
14
16
  validateRunFile
15
- } from "./chunk-sdjnnmzz.js";
17
+ } from "./chunk-zdxgragg.js";
16
18
  import {
17
19
  EventLog
18
- } from "./chunk-azctppjh.js";
20
+ } from "./chunk-ybacnpxd.js";
19
21
  import {
20
22
  isAlive,
21
- openBlocks,
22
- parseQuestions,
23
23
  readLock,
24
24
  withWorkspaceLock,
25
25
  workspaceRootOfRunDir,
26
26
  yamlScalar
27
- } from "./chunk-j234zf0t.js";
27
+ } from "./chunk-9kkm6q0t.js";
28
28
  import {
29
- noteDeprecations
30
- } from "./chunk-t56k6146.js";
29
+ noteDeprecations,
30
+ openBlocks,
31
+ parseQuestions
32
+ } from "./chunk-3t91gvpp.js";
31
33
  import {
32
34
  listRunDirs,
33
35
  parseYaml
34
- } from "./chunk-p274ckxv.js";
36
+ } from "./chunk-ztczwtj0.js";
35
37
 
36
38
  // src/core/statusline/runSnapshot.ts
37
39
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
@@ -52,8 +54,12 @@ function gatesPolicy(policy) {
52
54
  const entries = Object.entries(policy).map(([id, value]) => `${yamlScalar(id)}: ${yamlScalar(value)}`);
53
55
  return `{${entries.join(", ")}}`;
54
56
  }
57
+ function gateEvidence(e) {
58
+ return `{path: ${yamlScalar(e.path)}, role: ${yamlScalar(e.role)}, verdict: ${yamlScalar(e.verdict)}, ` + `sampled: ${String(e.sampled)}, of: ${String(e.of)}, resolved: ${String(e.resolved)}, ` + `refuted: ${String(e.refuted)}, outside_surface: ${String(e.outside_surface)}}`;
59
+ }
55
60
  function gate(g) {
56
- return `{type: ${yamlScalar(g.type)}, status: ${yamlScalar(g.status)}, by: ${yamlScalar(g.by)}, ` + `at: ${yamlScalar(g.at)}, note: ${yamlScalar(g.note)}}`;
61
+ const evidence = g.evidence === undefined ? "" : `, evidence: ${gateEvidence(g.evidence)}`;
62
+ return `{type: ${yamlScalar(g.type)}, status: ${yamlScalar(g.status)}, by: ${yamlScalar(g.by)}, ` + `at: ${yamlScalar(g.at)}, note: ${yamlScalar(g.note)}${evidence}}`;
57
63
  }
58
64
  function task(t, indent) {
59
65
  const inner = `${indent} `;
@@ -120,6 +126,9 @@ function emitRunYaml(run) {
120
126
  if (run.cancelled !== undefined) {
121
127
  lines.push(`cancelled: {by: ${yamlScalar(run.cancelled.by)}, at: ${yamlScalar(run.cancelled.at)}, ` + `note: ${yamlScalar(run.cancelled.note)}}`);
122
128
  }
129
+ if (run.attended_by !== undefined) {
130
+ lines.push(`attended_by: ${yamlScalar(run.attended_by)}`);
131
+ }
123
132
  if (run.gates_policy !== undefined && Object.keys(run.gates_policy).length > 0) {
124
133
  lines.push(`gates_policy: ${gatesPolicy(run.gates_policy)}`);
125
134
  }
@@ -149,10 +158,12 @@ function emitBudgetYaml(budget) {
149
158
  `per_agent_max_usd: ${money(budget.per_agent_max_usd)}`,
150
159
  `warn_at_pct: ${budget.warn_at_pct}`,
151
160
  `on_exceed: ${yamlScalar(budget.on_exceed)}`,
161
+ ...budget.economy === DEFAULT_ECONOMY ? [] : [`economy: ${yamlScalar(budget.economy)}`],
152
162
  "phases:"
153
163
  ];
154
164
  for (const phase of budget.phases) {
155
- lines.push(` - {id: ${yamlScalar(phase.id)}, ceiling_usd: ${money(phase.ceiling_usd)}, ` + `spent_usd: ${money(phase.spent_usd)}}`);
165
+ const economy = phase.economy === null ? "" : `, economy: ${yamlScalar(phase.economy)}`;
166
+ lines.push(` - {id: ${yamlScalar(phase.id)}, ceiling_usd: ${money(phase.ceiling_usd)}, ` + `spent_usd: ${money(phase.spent_usd)}${economy}}`);
156
167
  }
157
168
  return `${lines.join(`
158
169
  `)}
@@ -418,6 +429,7 @@ function fromStore(root) {
418
429
  openCount: open.length,
419
430
  autoGates: stages.filter((s) => s.gate.status === "approved" && s.gate.by === AUTO).length,
420
431
  staleStages: stages.filter((s) => s.stale === true).length,
432
+ attendedByHost: isAttendedByHost(run),
421
433
  source: "run-store"
422
434
  };
423
435
  }
@@ -446,6 +458,7 @@ function fromTolerantRead(root) {
446
458
  openCount: open.length,
447
459
  autoGates: 0,
448
460
  staleStages: 0,
461
+ attendedByHost: false,
449
462
  source: "tolerant"
450
463
  };
451
464
  }
@@ -5,7 +5,7 @@ import {
5
5
  requireNumber,
6
6
  requireString,
7
7
  result
8
- } from "./chunk-0bt6yb2q.js";
8
+ } from "./chunk-39zh2e44.js";
9
9
 
10
10
  // src/core/events/EventLog.ts
11
11
  import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
@@ -17,6 +17,7 @@ var EVENT_TYPES = [
17
17
  "run.closed",
18
18
  "run.unlocked",
19
19
  "run.cancelled",
20
+ "run.attended",
20
21
  "phase.started",
21
22
  "phase.done",
22
23
  "stage.started",
@@ -33,6 +34,8 @@ var EVENT_TYPES = [
33
34
  "gate.approved",
34
35
  "gate.rejected",
35
36
  "gate.revoked",
37
+ "story.reopened",
38
+ "story.base_fastforwarded",
36
39
  "check.passed",
37
40
  "check.failed",
38
41
  "budget.warned",
@@ -2,10 +2,17 @@ import {
2
2
  emitFactsYaml,
3
3
  withWorkspaceLock,
4
4
  workspaceRootOfFactsPath
5
- } from "./chunk-j234zf0t.js";
5
+ } from "./chunk-9kkm6q0t.js";
6
6
  import {
7
+ FACT_CONFIDENCES,
8
+ FACT_KINDS,
9
+ MAX_FACTS,
10
+ MAX_FACT_CHARS,
11
+ factNumber,
12
+ formatFactId,
13
+ isRetired,
7
14
  noteDeprecations
8
- } from "./chunk-t56k6146.js";
15
+ } from "./chunk-3t91gvpp.js";
9
16
  import {
10
17
  asDocument,
11
18
  isRecord,
@@ -15,30 +22,15 @@ import {
15
22
  requireString,
16
23
  requireVersion,
17
24
  result
18
- } from "./chunk-0bt6yb2q.js";
25
+ } from "./chunk-39zh2e44.js";
19
26
  import {
20
27
  parseYaml
21
- } from "./chunk-p274ckxv.js";
28
+ } from "./chunk-ztczwtj0.js";
22
29
 
23
30
  // src/core/facts/FactsStore.ts
24
31
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
25
32
  import { dirname } from "node:path";
26
33
 
27
- // src/core/facts/Fact.ts
28
- var FACT_KINDS = ["answer", "observed", "derived"];
29
- var FACT_CONFIDENCES = ["measured", "inferred", "stated"];
30
- var MAX_FACTS = 5000;
31
- var MAX_FACT_CHARS = 300;
32
- function isRetired(fact) {
33
- return fact.retired !== null && fact.retired.at !== null;
34
- }
35
- function factNumber(id) {
36
- return Number.parseInt(id.slice(1), 10);
37
- }
38
- function formatFactId(n) {
39
- return `F${String(n).padStart(3, "0")}`;
40
- }
41
-
42
34
  // src/core/facts/validateFactsFile.ts
43
35
  var ID_RE = /^F\d{3,6}$/;
44
36
  var Q_RE = /^Q\d{1,6}$/;
@@ -94,6 +86,9 @@ function validateFactsFile(input) {
94
86
  } else if (row.source !== undefined) {
95
87
  issues.push({ path: `${path}.source`, message: "expected a mapping" });
96
88
  }
89
+ if (row.truncated !== undefined && typeof row.truncated !== "boolean") {
90
+ issues.push({ path: `${path}.truncated`, message: "expected true, false or absent" });
91
+ }
97
92
  const superseded = typeof row.superseded_by === "string";
98
93
  const retired = isRecord(row.retired) && row.retired.at !== null && row.retired.at !== undefined;
99
94
  if (superseded && retired) {
@@ -214,9 +209,10 @@ class FactsStore {
214
209
  return formatFactId(highest + 1);
215
210
  }
216
211
  append(input) {
212
+ const cut = input.fact.length > MAX_FACT_CHARS;
217
213
  const fact = {
218
214
  id: this.nextId(),
219
- fact: input.fact.length > MAX_FACT_CHARS ? `${input.fact.slice(0, MAX_FACT_CHARS - 1)}…` : input.fact,
215
+ fact: cut ? `${input.fact.slice(0, MAX_FACT_CHARS - 1)}…` : input.fact,
220
216
  area: input.area,
221
217
  repos: [...input.repos],
222
218
  kind: input.kind,
@@ -224,7 +220,8 @@ class FactsStore {
224
220
  source: { ...input.source },
225
221
  supersedes: input.supersedes ?? null,
226
222
  superseded_by: null,
227
- retired: input.retired ?? null
223
+ retired: input.retired ?? null,
224
+ ...cut || input.truncated === true ? { truncated: true } : {}
228
225
  };
229
226
  this.rows.push(fact);
230
227
  return fact;
@@ -299,4 +296,4 @@ function writeFactsAtomic(path, text) {
299
296
  }
300
297
  }
301
298
 
302
- export { MAX_FACT_CHARS, FactsStore };
299
+ export { FactsStore };