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.
- package/CHANGELOG.md +2396 -0
- package/README.md +133 -17
- package/dist/hooks/answer-capture.js +7 -8
- package/dist/hooks/budget-gate.js +83 -16
- package/dist/hooks/{chunk-zdxgragg.js → chunk-14zn51kh.js} +171 -16
- package/dist/hooks/{chunk-xpxe94qe.js → chunk-3w55tp71.js} +75 -27
- package/dist/hooks/{chunk-afamdvyn.js → chunk-5556vjt5.js} +1 -1
- package/dist/hooks/{chunk-ytvmc5ns.js → chunk-889tybxc.js} +26 -28
- package/dist/hooks/{chunk-9gb21660.js → chunk-a5dq2dcp.js} +54 -14
- package/dist/hooks/{chunk-ztczwtj0.js → chunk-a6rpj2cp.js} +835 -16
- package/dist/hooks/{chunk-3g61yg59.js → chunk-bvm6vjrt.js} +1 -1
- package/dist/hooks/{chunk-0z27twdk.js → chunk-hcrbr430.js} +10 -5
- package/dist/hooks/{chunk-3t91gvpp.js → chunk-nadqsr3w.js} +8 -2
- package/dist/hooks/{chunk-458wgg9j.js → chunk-q8d3sff9.js} +86 -11
- package/dist/hooks/{chunk-9kkm6q0t.js → chunk-qw73rdbr.js} +35 -3
- package/dist/hooks/{chunk-ybacnpxd.js → chunk-sae7sqty.js} +5 -0
- package/dist/hooks/{chunk-s5qsb4k6.js → chunk-v1c1hpb8.js} +1 -1
- package/dist/hooks/claim-sources.js +32 -21
- package/dist/hooks/dod-gate.js +7 -6
- package/dist/hooks/no-reask.js +9 -9
- package/dist/hooks/session-start.js +46 -21
- package/dist/hooks/statusline.js +8 -9
- package/dist/tldrx.js +19667 -10978
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/skills/tldrx/SKILL.md +1 -1
- package/stages/build/stage.yml +2 -1
- package/stages/plan/stage.md +11 -0
- package/stages/watch/stage.md +4 -0
- package/templates/expert.md +13 -1
- package/templates/watcher.md +8 -1
- package/workflows/bugfix.yml +8 -4
- package/workflows/docs.yml +8 -4
- package/workflows/feature.yml +8 -4
- package/workflows/hotfix.yml +8 -4
- package/workflows/integration.yml +8 -4
- package/workflows/migration.yml +8 -4
- package/workflows/performance.yml +8 -4
- package/workflows/prototype.yml +8 -4
- package/workflows/refactor.yml +8 -4
- package/workflows/security-patch.yml +8 -4
- package/workflows/spike.yml +8 -4
- package/workflows/upgrade.yml +8 -4
- package/dist/hooks/chunk-sznsenee.js +0 -503
- package/templates/epic.md +0 -38
- package/templates/story.md +0 -55
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
emitFactsYaml,
|
|
3
3
|
withWorkspaceLock,
|
|
4
|
-
workspaceRootOfFactsPath
|
|
5
|
-
|
|
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-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
+
SRC_PATTERNS,
|
|
30
|
+
parseYaml,
|
|
31
|
+
readableSource
|
|
32
|
+
} from "./chunk-a6rpj2cp.js";
|
|
29
33
|
|
|
30
34
|
// src/core/facts/FactsStore.ts
|
|
31
|
-
import { existsSync,
|
|
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 =
|
|
36
|
-
var Q_RE =
|
|
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:
|
|
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:
|
|
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 (
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
` +
|
|
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
|
-
` +
|
|
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((
|
|
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
|
}
|