tldr-experts 0.3.1 → 0.5.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 (46) hide show
  1. package/CHANGELOG.md +2396 -0
  2. package/README.md +133 -17
  3. package/dist/hooks/answer-capture.js +7 -8
  4. package/dist/hooks/budget-gate.js +83 -16
  5. package/dist/hooks/{chunk-zdxgragg.js → chunk-14zn51kh.js} +171 -16
  6. package/dist/hooks/{chunk-xpxe94qe.js → chunk-3w55tp71.js} +75 -27
  7. package/dist/hooks/{chunk-afamdvyn.js → chunk-5556vjt5.js} +1 -1
  8. package/dist/hooks/{chunk-ytvmc5ns.js → chunk-889tybxc.js} +26 -28
  9. package/dist/hooks/{chunk-9gb21660.js → chunk-a5dq2dcp.js} +54 -14
  10. package/dist/hooks/{chunk-ztczwtj0.js → chunk-a6rpj2cp.js} +835 -16
  11. package/dist/hooks/{chunk-3g61yg59.js → chunk-bvm6vjrt.js} +1 -1
  12. package/dist/hooks/{chunk-0z27twdk.js → chunk-hcrbr430.js} +10 -5
  13. package/dist/hooks/{chunk-3t91gvpp.js → chunk-nadqsr3w.js} +8 -2
  14. package/dist/hooks/{chunk-458wgg9j.js → chunk-q8d3sff9.js} +86 -11
  15. package/dist/hooks/{chunk-9kkm6q0t.js → chunk-qw73rdbr.js} +35 -3
  16. package/dist/hooks/{chunk-ybacnpxd.js → chunk-sae7sqty.js} +5 -0
  17. package/dist/hooks/{chunk-s5qsb4k6.js → chunk-v1c1hpb8.js} +1 -1
  18. package/dist/hooks/claim-sources.js +32 -21
  19. package/dist/hooks/dod-gate.js +7 -6
  20. package/dist/hooks/no-reask.js +9 -9
  21. package/dist/hooks/session-start.js +46 -21
  22. package/dist/hooks/statusline.js +8 -9
  23. package/dist/tldrx.js +19667 -10978
  24. package/package.json +4 -2
  25. package/plugin/.claude-plugin/plugin.json +2 -2
  26. package/plugin/skills/tldrx/SKILL.md +1 -1
  27. package/stages/build/stage.yml +2 -1
  28. package/stages/plan/stage.md +11 -0
  29. package/stages/watch/stage.md +4 -0
  30. package/templates/expert.md +13 -1
  31. package/templates/watcher.md +8 -1
  32. package/workflows/bugfix.yml +8 -4
  33. package/workflows/docs.yml +8 -4
  34. package/workflows/feature.yml +8 -4
  35. package/workflows/hotfix.yml +8 -4
  36. package/workflows/integration.yml +8 -4
  37. package/workflows/migration.yml +8 -4
  38. package/workflows/performance.yml +8 -4
  39. package/workflows/prototype.yml +8 -4
  40. package/workflows/refactor.yml +8 -4
  41. package/workflows/security-patch.yml +8 -4
  42. package/workflows/spike.yml +8 -4
  43. package/workflows/upgrade.yml +8 -4
  44. package/dist/hooks/chunk-sznsenee.js +0 -503
  45. package/templates/epic.md +0 -38
  46. package/templates/story.md +0 -55
@@ -1,8 +1,9 @@
1
1
  import {
2
2
  emitFactsYaml,
3
3
  withWorkspaceLock,
4
- workspaceRootOfFactsPath
5
- } from "./chunk-9kkm6q0t.js";
4
+ workspaceRootOfFactsPath,
5
+ writeAtomic
6
+ } from "./chunk-qw73rdbr.js";
6
7
  import {
7
8
  FACT_CONFIDENCES,
8
9
  FACT_KINDS,
@@ -10,9 +11,10 @@ import {
10
11
  MAX_FACT_CHARS,
11
12
  factNumber,
12
13
  formatFactId,
14
+ isLive,
13
15
  isRetired,
14
16
  noteDeprecations
15
- } from "./chunk-3t91gvpp.js";
17
+ } from "./chunk-nadqsr3w.js";
16
18
  import {
17
19
  asDocument,
18
20
  isRecord,
@@ -24,16 +26,17 @@ import {
24
26
  result
25
27
  } from "./chunk-39zh2e44.js";
26
28
  import {
27
- parseYaml
28
- } from "./chunk-ztczwtj0.js";
29
+ SRC_PATTERNS,
30
+ parseYaml,
31
+ readableSource
32
+ } from "./chunk-a6rpj2cp.js";
29
33
 
30
34
  // src/core/facts/FactsStore.ts
31
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
32
- import { dirname } from "node:path";
35
+ import { existsSync, readFileSync } from "node:fs";
33
36
 
34
37
  // src/core/facts/validateFactsFile.ts
35
- var ID_RE = /^F\d{3,6}$/;
36
- var Q_RE = /^Q\d{1,6}$/;
38
+ var ID_RE = SRC_PATTERNS.fact;
39
+ var Q_RE = SRC_PATTERNS.answer;
37
40
  function validateFactsFile(input) {
38
41
  const issues = [];
39
42
  const deprecations = [];
@@ -59,7 +62,7 @@ function validateFactsFile(input) {
59
62
  requireKeys(row, ["id", "fact", "area", "repos", "kind", "confidence", "source", "supersedes", "superseded_by", "retired"], path, issues);
60
63
  const id = typeof row.id === "string" ? row.id : "";
61
64
  if (!ID_RE.test(id))
62
- issues.push({ path: `${path}.id`, message: "id must match ^F\\d{3,6}$" });
65
+ issues.push({ path: `${path}.id`, message: `id must match ${readableSource(ID_RE)}` });
63
66
  else if (byId.has(id))
64
67
  issues.push({ path: `${path}.id`, message: `duplicate fact id ${id}` });
65
68
  else {
@@ -81,7 +84,7 @@ function validateFactsFile(input) {
81
84
  requireKeys(row.source, ["who", "when", "run", "q"], `${path}.source`, issues);
82
85
  const q = row.source.q;
83
86
  if (typeof q === "string" && !Q_RE.test(q)) {
84
- issues.push({ path: `${path}.source.q`, message: "expected ^Q\\d+$ or null" });
87
+ issues.push({ path: `${path}.source.q`, message: `expected ${readableSource(Q_RE)} or null` });
85
88
  }
86
89
  } else if (row.source !== undefined) {
87
90
  issues.push({ path: `${path}.source`, message: "expected a mapping" });
@@ -153,7 +156,7 @@ function findDuplicate(question, area, facts, threshold = DEFAULT_JACCARD_THRESH
153
156
  for (const fact of facts) {
154
157
  if (fact.area !== area)
155
158
  continue;
156
- if (isRetired(fact))
159
+ if (!isLive(fact))
157
160
  continue;
158
161
  const score = jaccard(asked, tokenize(fact.fact));
159
162
  if (score < threshold)
@@ -199,7 +202,7 @@ class FactsStore {
199
202
  return this.rows;
200
203
  }
201
204
  get active() {
202
- return this.rows.filter((f) => !isRetired(f));
205
+ return this.rows.filter(isLive);
203
206
  }
204
207
  get(id) {
205
208
  return this.rows.find((f) => f.id === id);
@@ -226,6 +229,15 @@ class FactsStore {
226
229
  this.rows.push(fact);
227
230
  return fact;
228
231
  }
232
+ headOf(id) {
233
+ let fact = this.get(id);
234
+ for (let step = 0;fact !== undefined && fact.superseded_by !== null; step++) {
235
+ if (step >= this.rows.length)
236
+ return;
237
+ fact = this.get(fact.superseded_by);
238
+ }
239
+ return fact;
240
+ }
229
241
  supersede(oldId, input) {
230
242
  const index = this.rows.findIndex((f) => f.id === oldId);
231
243
  if (index === -1)
@@ -269,7 +281,7 @@ class FactsStore {
269
281
  throw new Error(`refusing to write an invalid facts.yml: ${first?.path ?? ""} ${first?.message ?? ""}`);
270
282
  }
271
283
  withWorkspaceLock(workspaceRootOfFactsPath(path), () => {
272
- writeFactsAtomic(path, text);
284
+ writeAtomic(path, text);
273
285
  });
274
286
  }
275
287
  static update(path, fn) {
@@ -281,19 +293,5 @@ class FactsStore {
281
293
  });
282
294
  }
283
295
  }
284
- function writeFactsAtomic(path, text) {
285
- const temp = `${path}.tmp-${String(process.pid)}`;
286
- mkdirSync(dirname(path), { recursive: true });
287
- try {
288
- writeFileSync(temp, text, "utf8");
289
- renameSync(temp, path);
290
- } catch (error) {
291
- try {
292
- if (existsSync(temp))
293
- rmSync(temp, { force: true });
294
- } catch {}
295
- throw error;
296
- }
297
- }
298
296
 
299
297
  export { FactsStore };
@@ -1,27 +1,67 @@
1
+ import {
2
+ BULLET_RULE,
3
+ EMPTY_SECTION_RULE,
4
+ noneBullet
5
+ } from "./chunk-q8d3sff9.js";
6
+ import {
7
+ srcRule
8
+ } from "./chunk-a6rpj2cp.js";
9
+
1
10
  // src/hooks/lib/messages.ts
2
11
  var GRAMMAR = "[src: <repo:path:line> | https://… | Q<n> | F<n> | $ <cmd> → exit <n> | graph:<node> | absent:<path>]";
3
12
  function usd(amount) {
4
13
  return `$${amount.toFixed(2)}`;
5
14
  }
6
- function claimSourcesDeny(relPath, lines) {
7
- const list = lines.map((n) => `L${n}`).join(", ");
8
- return `[tldrx] claim-sources: ${lines.length} unsourced bullet(s) in ${relPath} — ${list}.
9
- ` + `Every bullet under Findings/Decisions/Unknowns/Evidence ledger must end with ${GRAMMAR}. ` + "Add the source or delete the claim.";
15
+ var MAX_QUOTED_CHARS = 200;
16
+ function quoteLine(text, line) {
17
+ if (text === "" || line < 1)
18
+ return null;
19
+ const found = text.split(`
20
+ `)[line - 1];
21
+ if (found === undefined || found.trim() === "")
22
+ return null;
23
+ const trimmed = found.trim();
24
+ return trimmed.length <= MAX_QUOTED_CHARS ? trimmed : `${trimmed.slice(0, MAX_QUOTED_CHARS)}…`;
25
+ }
26
+ function issueBlock(issue, text) {
27
+ const out = [`L${String(issue.line)}: ${issue.message}`];
28
+ const quoted = quoteLine(text, issue.line);
29
+ if (quoted !== null)
30
+ out.push(` you wrote: ${quoted}`);
31
+ if (issue.rule !== undefined) {
32
+ const rule = srcRule(issue.rule);
33
+ out.push(issue.message.includes(rule.rule) ? ` rule: \`${rule.id}\`` : ` rule \`${rule.id}\`: ${rule.rule}`);
34
+ out.push(` corrected: ${rule.good}`);
35
+ }
36
+ return out.join(`
37
+ `);
38
+ }
39
+ function claimSourcesDeny(relPath, lines, text = "") {
40
+ const list = lines.map((n) => `L${String(n)}`).join(", ");
41
+ const quotes = lines.map((n) => ({ n, quoted: quoteLine(text, n) })).filter((row) => row.quoted !== null).map((row) => `L${String(row.n)}: ${row.quoted}`);
42
+ return `[tldrx] claim-sources: ${String(lines.length)} unsourced bullet(s) in ${relPath} — ${list}.
43
+ ` + (quotes.length === 0 ? "" : `${quotes.join(`
44
+ `)}
45
+ `) + `Rule: ${BULLET_RULE}. The token goes at the END of the line: ${GRAMMAR}.
46
+ ` + "Add the source or delete the claim.";
10
47
  }
11
48
  function claimSourcesEmptySectionDeny(relPath, sections) {
12
- const list = sections.map((s) => `"${s.name}" (L${s.line})`).join(", ");
13
- return `[tldrx] claim-sources: ${sections.length} checked section(s) in ${relPath} contain no list ` + `items — ${list}.
14
- ` + "Findings/Decisions/Unknowns/Evidence ledger must each hold at least one sourced item; prose alone " + "is not a claim anything can check. If there is genuinely nothing, say so as an item: " + "`- none [src: absent:<what you looked at>]`.";
49
+ const list = sections.map((s) => `"${s.name}" (L${String(s.line)})`).join(", ");
50
+ return `[tldrx] claim-sources: ${String(sections.length)} checked section(s) in ${relPath} contain no list ` + `items — ${list}.
51
+ ` + `Rule: ${EMPTY_SECTION_RULE}.
52
+ ` + "If there is genuinely nothing, say so as an item: " + `\`${noneBullet("<what you looked at>")}\`.`;
15
53
  }
16
- function claimSourcesMalformedDeny(relPath, issues) {
17
- const list = issues.map((i) => `L${i.line}`).join(", ");
18
- return `[tldrx] claim-sources: ${issues.length} malformed citation(s) in ${relPath} — ${list}.
19
- ` + "The bullet cites a source but the token could not be read. The `[src: …]` token must be the LAST " + "thing on the line: remove the backticks around it and any words after it. A closing quote, bracket " + "or a final `.` after the `]` is fine.";
54
+ function claimSourcesMalformedDeny(relPath, issues, text = "") {
55
+ const list = issues.map((i) => `L${String(i.line)}`).join(", ");
56
+ return `[tldrx] claim-sources: ${String(issues.length)} malformed citation(s) in ${relPath} — ${list}.
57
+ ` + `${issues.map((issue) => issueBlock(issue, text)).join(`
58
+ `)}
59
+ ` + "The bullet cites a source but the token could not be read. Fix the citation or delete the claim.";
20
60
  }
21
- function claimSourcesUnresolvedDeny(relPath, issues) {
22
- const detail = issues.map((i) => `L${i.line}: ${i.message}`).join(`
61
+ function claimSourcesUnresolvedDeny(relPath, issues, text = "") {
62
+ const detail = issues.map((issue) => issueBlock(issue, text)).join(`
23
63
  `);
24
- return `[tldrx] claim-sources: ${issues.length} unresolvable source(s) in ${relPath}.
64
+ return `[tldrx] claim-sources: ${String(issues.length)} unresolvable source(s) in ${relPath}.
25
65
  ` + `${detail}
26
66
  ` + "A cited file must exist with the line in range, a command must be one of workspace.yml's, and " + "`$ … → exit n` belongs only in the Evidence ledger. Fix the citation or delete the claim.";
27
67
  }