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.
- package/CHANGELOG.md +792 -0
- package/README.md +19 -5
- package/dist/hooks/answer-capture.js +136 -12
- package/dist/hooks/budget-gate.js +9 -9
- package/dist/hooks/{chunk-889tybxc.js → chunk-17gv74sr.js} +3 -3
- package/dist/hooks/{chunk-3w55tp71.js → chunk-5k4dggq0.js} +51 -17
- package/dist/hooks/{chunk-14zn51kh.js → chunk-7eqhfddw.js} +28 -569
- package/dist/hooks/{chunk-nadqsr3w.js → chunk-97ncnegd.js} +1 -1
- package/dist/hooks/{chunk-a6rpj2cp.js → chunk-9w64mxdh.js} +345 -22
- package/dist/hooks/{chunk-a5dq2dcp.js → chunk-dhbxzjfs.js} +2 -4
- package/dist/hooks/{chunk-hcrbr430.js → chunk-g4db8rbs.js} +2 -30
- package/dist/hooks/chunk-h7151g8w.js +803 -0
- package/dist/hooks/{chunk-5556vjt5.js → chunk-ka6bkb64.js} +1 -1
- package/dist/hooks/chunk-rrkdfk7s.js +30 -0
- package/dist/hooks/{chunk-qw73rdbr.js → chunk-v4ay50nq.js} +1 -1
- package/dist/hooks/{chunk-v1c1hpb8.js → chunk-vrhczzxs.js} +1 -1
- package/dist/hooks/{chunk-bvm6vjrt.js → chunk-x6n8b5fp.js} +1 -1
- package/dist/hooks/claim-sources.js +8 -10
- package/dist/hooks/dod-gate.js +7 -7
- package/dist/hooks/no-reask.js +8 -9
- package/dist/hooks/session-start.js +23 -18
- package/dist/hooks/statusline.js +10 -10
- package/dist/tldrx.js +2539 -1006
- package/env.yml +9 -2
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/skills/tldrx/SKILL.md +8 -1
- package/templates/questions.md +8 -0
- package/dist/hooks/chunk-q8d3sff9.js +0 -220
- package/dist/hooks/chunk-sae7sqty.js +0 -206
|
@@ -7708,6 +7708,13 @@ var SRC_RULES = [
|
|
|
7708
7708
|
bad: "- no retention policy is recorded [src: absent:]",
|
|
7709
7709
|
good: "- no retention policy is recorded [src: absent:docs/retention.md]"
|
|
7710
7710
|
},
|
|
7711
|
+
{
|
|
7712
|
+
id: "absent-needle",
|
|
7713
|
+
rule: "`absent:<path>#<needle>` carries the words you searched for — `#` with nothing after it " + "says you searched for nothing",
|
|
7714
|
+
enforcedBy: ["absent:", "#"],
|
|
7715
|
+
bad: "- no retention policy is recorded [src: absent:docs/retention.md#]",
|
|
7716
|
+
good: "- no retention policy is recorded [src: absent:docs/retention.md#retention]"
|
|
7717
|
+
},
|
|
7711
7718
|
{
|
|
7712
7719
|
id: "aidlc-shape",
|
|
7713
7720
|
rule: "an `aidlc` source is `aidlc:<file>:<line>` or `aidlc:<file>#Q<n>`",
|
|
@@ -7814,10 +7821,15 @@ function classifySrc(src, repos) {
|
|
|
7814
7821
|
if (raw.startsWith("aidlc:"))
|
|
7815
7822
|
return parseAidlcSrc(raw);
|
|
7816
7823
|
if (raw.startsWith("absent:")) {
|
|
7817
|
-
const
|
|
7824
|
+
const rest = raw.slice("absent:".length);
|
|
7825
|
+
const hash = rest.indexOf("#");
|
|
7826
|
+
const path = hash === -1 ? rest : rest.slice(0, hash);
|
|
7827
|
+
const needle = hash === -1 ? null : rest.slice(hash + 1);
|
|
7818
7828
|
if (path === "")
|
|
7819
7829
|
return refuse(raw, "absent-path");
|
|
7820
|
-
|
|
7830
|
+
if (needle !== null && needle.trim() === "")
|
|
7831
|
+
return refuse(raw, "absent-needle");
|
|
7832
|
+
return { kind: "absent", raw, path, needle };
|
|
7821
7833
|
}
|
|
7822
7834
|
if (ANSWER_RE.test(raw))
|
|
7823
7835
|
return { kind: "answer", raw, q: raw };
|
|
@@ -7894,13 +7906,16 @@ function refused(message, resolved) {
|
|
|
7894
7906
|
function unverified(message) {
|
|
7895
7907
|
return { ok: true, outcome: "unverified", message };
|
|
7896
7908
|
}
|
|
7909
|
+
function noted(message) {
|
|
7910
|
+
return { ok: true, outcome: "noted", message };
|
|
7911
|
+
}
|
|
7897
7912
|
function resolveSrc(ref, ctx, section, claim = "") {
|
|
7898
7913
|
switch (ref.kind) {
|
|
7899
7914
|
case "file": {
|
|
7900
7915
|
if (ref.repo !== null && !ctx.repos.has(ref.repo)) {
|
|
7901
7916
|
return refused(`unknown repo \`${ref.repo}\` (not in workspace.yml)`);
|
|
7902
7917
|
}
|
|
7903
|
-
const bases =
|
|
7918
|
+
const bases = pathBases(ref.repo, ref.path, ctx);
|
|
7904
7919
|
let short = null;
|
|
7905
7920
|
for (const base of bases) {
|
|
7906
7921
|
if (!existsSync3(base.abs) || !statSync3(base.abs).isFile())
|
|
@@ -7939,7 +7954,7 @@ function resolveSrc(ref, ctx, section, claim = "") {
|
|
|
7939
7954
|
case "doc":
|
|
7940
7955
|
return resolveDoc(ref.url, ctx);
|
|
7941
7956
|
case "absent":
|
|
7942
|
-
return resolveAbsent(ref
|
|
7957
|
+
return resolveAbsent(ref, ctx, section, claim);
|
|
7943
7958
|
case "aidlc":
|
|
7944
7959
|
return OK;
|
|
7945
7960
|
}
|
|
@@ -7993,45 +8008,134 @@ function resolveDoc(url, ctx) {
|
|
|
7993
8008
|
return OK;
|
|
7994
8009
|
return unverified(`${url} is not fetched offline and no input, map or knowledge file cites it`);
|
|
7995
8010
|
}
|
|
7996
|
-
function resolveAbsent(
|
|
8011
|
+
function resolveAbsent(ref, ctx, section, claim) {
|
|
8012
|
+
const { path, needle } = ref;
|
|
7997
8013
|
if (section !== "Unknowns" && claim !== "" && !isNegativeClaim(claim)) {
|
|
7998
8014
|
return refused(`absent:${path} sources a positive claim — an absence can only support a negative one ` + "(say what is NOT there: `no`, `not`, `never`, `none`, `absent`), or move it under Unknowns");
|
|
7999
8015
|
}
|
|
8000
|
-
const
|
|
8001
|
-
if (
|
|
8016
|
+
const found = firstExisting(absentBases(path, ctx));
|
|
8017
|
+
if (found === null)
|
|
8018
|
+
return OK;
|
|
8019
|
+
if (needle !== null)
|
|
8020
|
+
return searchForNeedle(found, needle, path);
|
|
8021
|
+
const held = whatIsThere(found);
|
|
8022
|
+
if (held === null)
|
|
8023
|
+
return OK;
|
|
8024
|
+
return noted(`${path} EXISTS (${held}) — "it is not in there" is a claim about its CONTENT, unchecked here. ` + `Cite \`absent:${path}#<what you searched for>\` to have it checked, or cite the line that proves it.`);
|
|
8025
|
+
}
|
|
8026
|
+
var NEEDLE_MAX_BYTES = 512 * 1024;
|
|
8027
|
+
var EMPTY_PROBE_MAX_BYTES = 64 * 1024;
|
|
8028
|
+
function searchForNeedle(abs, needle, path) {
|
|
8029
|
+
let stat;
|
|
8030
|
+
try {
|
|
8031
|
+
stat = statSync3(abs);
|
|
8032
|
+
} catch {
|
|
8002
8033
|
return OK;
|
|
8003
|
-
|
|
8034
|
+
}
|
|
8035
|
+
if (stat.isDirectory()) {
|
|
8036
|
+
return refused(`absent:${path}#${needle} searches a DIRECTORY — a needle is searched in one file. ` + `Cite the file you read (\`absent:${path}/<file>#${needle}\`), or drop the needle.`);
|
|
8037
|
+
}
|
|
8038
|
+
if (stat.size > NEEDLE_MAX_BYTES) {
|
|
8039
|
+
return noted(`${path} is ${String(stat.size)} bytes — too large to search here for \`${needle}\``);
|
|
8040
|
+
}
|
|
8041
|
+
let text;
|
|
8042
|
+
try {
|
|
8043
|
+
text = readFileSync2(abs, "utf8");
|
|
8044
|
+
} catch {
|
|
8045
|
+
return noted(`${path} could not be read here to search for \`${needle}\``);
|
|
8046
|
+
}
|
|
8047
|
+
const wanted = needle.toLowerCase();
|
|
8048
|
+
const lines = text.split(`
|
|
8049
|
+
`);
|
|
8050
|
+
for (let i = 0;i < lines.length; i++) {
|
|
8051
|
+
if ((lines[i] ?? "").toLowerCase().includes(wanted)) {
|
|
8052
|
+
return refused(`\`${needle}\` IS at ${path}:${String(i + 1)} — that is a presence, not an absence`, abs);
|
|
8053
|
+
}
|
|
8054
|
+
}
|
|
8055
|
+
return OK;
|
|
8056
|
+
}
|
|
8057
|
+
function whatIsThere(abs) {
|
|
8058
|
+
let stat;
|
|
8059
|
+
try {
|
|
8060
|
+
stat = statSync3(abs);
|
|
8061
|
+
} catch {
|
|
8062
|
+
return null;
|
|
8063
|
+
}
|
|
8064
|
+
if (stat.isDirectory()) {
|
|
8065
|
+
let entries;
|
|
8066
|
+
try {
|
|
8067
|
+
entries = readdirSync2(abs);
|
|
8068
|
+
} catch {
|
|
8069
|
+
return "a directory this check could not read";
|
|
8070
|
+
}
|
|
8071
|
+
if (entries.length === 0)
|
|
8072
|
+
return null;
|
|
8073
|
+
return `${String(entries.length)} entr${entries.length === 1 ? "y" : "ies"}`;
|
|
8074
|
+
}
|
|
8075
|
+
if (!stat.isFile())
|
|
8076
|
+
return "a path that is neither a file nor a directory";
|
|
8077
|
+
if (stat.size === 0)
|
|
8078
|
+
return null;
|
|
8079
|
+
if (stat.size > EMPTY_PROBE_MAX_BYTES)
|
|
8080
|
+
return `${String(stat.size)} bytes`;
|
|
8081
|
+
let text;
|
|
8082
|
+
try {
|
|
8083
|
+
text = readFileSync2(abs, "utf8");
|
|
8084
|
+
} catch {
|
|
8085
|
+
return `${String(stat.size)} bytes`;
|
|
8086
|
+
}
|
|
8087
|
+
if (text.trim() === "")
|
|
8088
|
+
return null;
|
|
8089
|
+
const lines = text.split(`
|
|
8090
|
+
`).length;
|
|
8091
|
+
return `${String(lines)} line${lines === 1 ? "" : "s"}`;
|
|
8092
|
+
}
|
|
8093
|
+
function absentBases(path, ctx) {
|
|
8094
|
+
const colon = path.indexOf(":");
|
|
8095
|
+
if (colon > 0) {
|
|
8096
|
+
const repo = path.slice(0, colon);
|
|
8097
|
+
if (ctx.repos.has(repo))
|
|
8098
|
+
return pathBases(repo, path.slice(colon + 1), ctx);
|
|
8099
|
+
}
|
|
8100
|
+
return pathBases(null, path, ctx);
|
|
8101
|
+
}
|
|
8102
|
+
function firstExisting(bases) {
|
|
8103
|
+
for (const base of bases) {
|
|
8104
|
+
if (existsSync3(base.abs))
|
|
8105
|
+
return base.abs;
|
|
8106
|
+
}
|
|
8107
|
+
return null;
|
|
8004
8108
|
}
|
|
8005
8109
|
var NEGATIVE_RE = /\b(no|not|none|never|nothing|nobody|nowhere|neither|nor|absent|absence|missing|lacks?|lacking|without|unknown|undocumented|unrecorded|untracked|zero|n\/a)\b|n't\b/i;
|
|
8006
8110
|
function isNegativeClaim(text) {
|
|
8007
8111
|
return NEGATIVE_RE.test(text);
|
|
8008
8112
|
}
|
|
8009
|
-
function
|
|
8010
|
-
if (isAbsolute2(
|
|
8011
|
-
return [{ label: "the absolute path as written", abs: normalize(
|
|
8113
|
+
function pathBases(repo, path, ctx) {
|
|
8114
|
+
if (isAbsolute2(path)) {
|
|
8115
|
+
return [{ label: "the absolute path as written", abs: normalize(path) }];
|
|
8012
8116
|
}
|
|
8013
|
-
if (
|
|
8014
|
-
const rel2 = ctx.repos.get(
|
|
8117
|
+
if (repo !== null) {
|
|
8118
|
+
const rel2 = ctx.repos.get(repo) ?? "";
|
|
8015
8119
|
return [
|
|
8016
|
-
...epicBases(ctx,
|
|
8017
|
-
{ label: repoLabel(
|
|
8120
|
+
...epicBases(ctx, repo, path),
|
|
8121
|
+
{ label: repoLabel(repo, rel2), abs: normalize(join3(ctx.root, rel2, path)) }
|
|
8018
8122
|
];
|
|
8019
8123
|
}
|
|
8020
8124
|
const bases = [];
|
|
8021
|
-
const slash =
|
|
8022
|
-
const named = slash > 0 ?
|
|
8125
|
+
const slash = path.indexOf("/");
|
|
8126
|
+
const named = slash > 0 ? path.slice(0, slash) : "";
|
|
8023
8127
|
const rel = named === "" ? undefined : ctx.repos.get(named);
|
|
8024
8128
|
if (rel !== undefined)
|
|
8025
|
-
bases.push(...epicBases(ctx, named,
|
|
8026
|
-
bases.push({ label: "workspace root", abs: normalize(join3(ctx.root,
|
|
8129
|
+
bases.push(...epicBases(ctx, named, path.slice(slash + 1)));
|
|
8130
|
+
bases.push({ label: "workspace root", abs: normalize(join3(ctx.root, path)) });
|
|
8027
8131
|
const runDir = ctx.runDir ?? null;
|
|
8028
8132
|
if (runDir !== null && runDir !== "") {
|
|
8029
|
-
bases.push({ label: `run dir ${displayPath(ctx.root, runDir)}`, abs: normalize(join3(runDir,
|
|
8133
|
+
bases.push({ label: `run dir ${displayPath(ctx.root, runDir)}`, abs: normalize(join3(runDir, path)) });
|
|
8030
8134
|
}
|
|
8031
8135
|
if (rel !== undefined) {
|
|
8032
8136
|
bases.push({
|
|
8033
8137
|
label: repoLabel(named, rel),
|
|
8034
|
-
abs: normalize(join3(ctx.root, rel,
|
|
8138
|
+
abs: normalize(join3(ctx.root, rel, path.slice(slash + 1)))
|
|
8035
8139
|
});
|
|
8036
8140
|
}
|
|
8037
8141
|
return bases;
|
|
@@ -8256,4 +8360,223 @@ function readCapped(path) {
|
|
|
8256
8360
|
}
|
|
8257
8361
|
}
|
|
8258
8362
|
|
|
8259
|
-
|
|
8363
|
+
// src/core/text/handoff.ts
|
|
8364
|
+
var HANDOFF_SECTIONS = ["Findings", "Decisions", "Unknowns", "Evidence ledger"];
|
|
8365
|
+
var H2_RE = /^##\s+(.+?)\s*$/;
|
|
8366
|
+
var BULLET_RE = /^(?: {0,3}-|\d{1,9}[.)])\s+(\S.*)$/;
|
|
8367
|
+
var CONTINUATION_RE = /^\s+\S/;
|
|
8368
|
+
function parseHandoff(text) {
|
|
8369
|
+
const lines = text.split(`
|
|
8370
|
+
`);
|
|
8371
|
+
const sections = [];
|
|
8372
|
+
const headings = [];
|
|
8373
|
+
let current = null;
|
|
8374
|
+
let pending = null;
|
|
8375
|
+
const flushBullet = () => {
|
|
8376
|
+
if (pending === null)
|
|
8377
|
+
return;
|
|
8378
|
+
if (current !== null) {
|
|
8379
|
+
const joined = pending.parts.join(" ");
|
|
8380
|
+
current.bullets.push({ line: pending.line, text: joined, token: parseSrcToken(joined) });
|
|
8381
|
+
}
|
|
8382
|
+
pending = null;
|
|
8383
|
+
};
|
|
8384
|
+
const flush = () => {
|
|
8385
|
+
flushBullet();
|
|
8386
|
+
if (current !== null)
|
|
8387
|
+
sections.push(current);
|
|
8388
|
+
current = null;
|
|
8389
|
+
};
|
|
8390
|
+
for (let i = 0;i < lines.length; i++) {
|
|
8391
|
+
const line = lines[i] ?? "";
|
|
8392
|
+
const heading = H2_RE.exec(line);
|
|
8393
|
+
if (heading !== null && heading[1] !== undefined) {
|
|
8394
|
+
flush();
|
|
8395
|
+
headings.push(heading[1]);
|
|
8396
|
+
current = { name: heading[1], headingLine: i + 1, bullets: [] };
|
|
8397
|
+
continue;
|
|
8398
|
+
}
|
|
8399
|
+
if (line.startsWith("# ")) {
|
|
8400
|
+
flush();
|
|
8401
|
+
continue;
|
|
8402
|
+
}
|
|
8403
|
+
if (current === null)
|
|
8404
|
+
continue;
|
|
8405
|
+
const bullet = BULLET_RE.exec(line);
|
|
8406
|
+
if (bullet !== null && bullet[1] !== undefined) {
|
|
8407
|
+
flushBullet();
|
|
8408
|
+
pending = { line: i + 1, parts: [bullet[1]] };
|
|
8409
|
+
continue;
|
|
8410
|
+
}
|
|
8411
|
+
if (pending !== null && CONTINUATION_RE.test(line)) {
|
|
8412
|
+
pending.parts.push(line.trim());
|
|
8413
|
+
continue;
|
|
8414
|
+
}
|
|
8415
|
+
flushBullet();
|
|
8416
|
+
}
|
|
8417
|
+
flush();
|
|
8418
|
+
return { sections, headings };
|
|
8419
|
+
}
|
|
8420
|
+
function parseItems(text) {
|
|
8421
|
+
const items = [];
|
|
8422
|
+
let section = "";
|
|
8423
|
+
let pending = null;
|
|
8424
|
+
const flush = () => {
|
|
8425
|
+
if (pending === null)
|
|
8426
|
+
return;
|
|
8427
|
+
const joined = pending.parts.join(" ");
|
|
8428
|
+
items.push({ line: pending.line, text: joined, section: pending.section, token: parseSrcToken(joined) });
|
|
8429
|
+
pending = null;
|
|
8430
|
+
};
|
|
8431
|
+
const lines = text.split(`
|
|
8432
|
+
`);
|
|
8433
|
+
for (let i = 0;i < lines.length; i++) {
|
|
8434
|
+
const line = lines[i] ?? "";
|
|
8435
|
+
const heading = H2_RE.exec(line);
|
|
8436
|
+
if (heading !== null && heading[1] !== undefined) {
|
|
8437
|
+
flush();
|
|
8438
|
+
section = heading[1];
|
|
8439
|
+
continue;
|
|
8440
|
+
}
|
|
8441
|
+
const bullet = BULLET_RE.exec(line);
|
|
8442
|
+
if (bullet !== null && bullet[1] !== undefined) {
|
|
8443
|
+
flush();
|
|
8444
|
+
pending = { line: i + 1, parts: [bullet[1]], section };
|
|
8445
|
+
continue;
|
|
8446
|
+
}
|
|
8447
|
+
if (pending !== null && CONTINUATION_RE.test(line)) {
|
|
8448
|
+
pending.parts.push(line.trim());
|
|
8449
|
+
continue;
|
|
8450
|
+
}
|
|
8451
|
+
flush();
|
|
8452
|
+
}
|
|
8453
|
+
flush();
|
|
8454
|
+
return items;
|
|
8455
|
+
}
|
|
8456
|
+
function isHandoff(text) {
|
|
8457
|
+
return missingSections(parseHandoff(text)).length === 0;
|
|
8458
|
+
}
|
|
8459
|
+
function missingSections(handoff, required = HANDOFF_SECTIONS) {
|
|
8460
|
+
const present = handoff.headings;
|
|
8461
|
+
const missing = [];
|
|
8462
|
+
let cursor = 0;
|
|
8463
|
+
for (const name of required) {
|
|
8464
|
+
const at = present.indexOf(name, cursor);
|
|
8465
|
+
if (at === -1)
|
|
8466
|
+
missing.push(name);
|
|
8467
|
+
else
|
|
8468
|
+
cursor = at + 1;
|
|
8469
|
+
}
|
|
8470
|
+
return missing;
|
|
8471
|
+
}
|
|
8472
|
+
var BULLET_RULE = `every list item under ${HANDOFF_SECTIONS.join(" / ")} ends with a \`[src: …]\` token`;
|
|
8473
|
+
var EMPTY_SECTION_RULE = `each of ${HANDOFF_SECTIONS.join(" / ")} holds at least one list item — prose alone is not a claim`;
|
|
8474
|
+
var MAX_BULLETS = 200;
|
|
8475
|
+
var BULLET_CAP_RULE = `a handoff carries at most ${String(MAX_BULLETS)} list items across the four sections — ` + "beyond that cap it is a document, not a handoff";
|
|
8476
|
+
function citationIssue(line, text) {
|
|
8477
|
+
const failure = diagnoseSrcToken(text);
|
|
8478
|
+
if (failure === null) {
|
|
8479
|
+
return { line, message: "malformed citation — the `[src: …]` token could not be read" };
|
|
8480
|
+
}
|
|
8481
|
+
return { line, message: `malformed citation — ${failure.rule.rule}`, rule: failure.rule.id };
|
|
8482
|
+
}
|
|
8483
|
+
function validateSections(handoff, required, ctx, lineOffset = 0) {
|
|
8484
|
+
const emptySections = [];
|
|
8485
|
+
const unsourced = [];
|
|
8486
|
+
const malformed = [];
|
|
8487
|
+
const unresolved = [];
|
|
8488
|
+
const unverified2 = [];
|
|
8489
|
+
const noted2 = [];
|
|
8490
|
+
let bulletCount = 0;
|
|
8491
|
+
const wanted = new Set(required);
|
|
8492
|
+
for (const section of handoff.sections) {
|
|
8493
|
+
if (!wanted.has(section.name))
|
|
8494
|
+
continue;
|
|
8495
|
+
if (section.bullets.length === 0) {
|
|
8496
|
+
emptySections.push({ name: section.name, line: section.headingLine + lineOffset });
|
|
8497
|
+
}
|
|
8498
|
+
for (const bullet of section.bullets) {
|
|
8499
|
+
bulletCount++;
|
|
8500
|
+
const line = bullet.line + lineOffset;
|
|
8501
|
+
if (bullet.token === null) {
|
|
8502
|
+
if (hasSrcMarker(bullet.text))
|
|
8503
|
+
malformed.push(citationIssue(line, bullet.text));
|
|
8504
|
+
else
|
|
8505
|
+
unsourced.push(line);
|
|
8506
|
+
continue;
|
|
8507
|
+
}
|
|
8508
|
+
for (const error of bullet.token.errors) {
|
|
8509
|
+
unresolved.push({ line, message: `[src: ${error.raw}] — ${error.message}`, rule: error.rule });
|
|
8510
|
+
}
|
|
8511
|
+
const claim = bullet.text.replace(bullet.token.raw, " ").trim();
|
|
8512
|
+
for (const ref of bullet.token.refs) {
|
|
8513
|
+
const resolution = resolveSrc(ref, ctx, section.name, claim);
|
|
8514
|
+
const issue = { line, message: `[src: ${ref.raw}] — ${resolution.message ?? "unresolvable"}` };
|
|
8515
|
+
if (resolution.outcome === "refused")
|
|
8516
|
+
unresolved.push(issue);
|
|
8517
|
+
else if (resolution.outcome === "unverified")
|
|
8518
|
+
unverified2.push(issue);
|
|
8519
|
+
else if (resolution.outcome === "noted")
|
|
8520
|
+
noted2.push({ ...issue, src: ref.raw });
|
|
8521
|
+
}
|
|
8522
|
+
}
|
|
8523
|
+
}
|
|
8524
|
+
return { emptySections, unsourced, malformed, unresolved, unverified: unverified2, noted: noted2, bulletCount };
|
|
8525
|
+
}
|
|
8526
|
+
function validateHandoff(text, ctx) {
|
|
8527
|
+
const handoff = parseHandoff(text);
|
|
8528
|
+
const missing = missingSections(handoff);
|
|
8529
|
+
const report = validateSections(handoff, HANDOFF_SECTIONS, ctx);
|
|
8530
|
+
const unresolved = [...report.unresolved];
|
|
8531
|
+
if (report.bulletCount > MAX_BULLETS) {
|
|
8532
|
+
unresolved.push({ line: 0, message: `${String(report.bulletCount)} bullets — ${BULLET_CAP_RULE}` });
|
|
8533
|
+
}
|
|
8534
|
+
return {
|
|
8535
|
+
ok: missing.length === 0 && report.emptySections.length === 0 && report.unsourced.length === 0 && report.malformed.length === 0 && unresolved.length === 0,
|
|
8536
|
+
missingSections: missing,
|
|
8537
|
+
emptySections: report.emptySections,
|
|
8538
|
+
unsourced: report.unsourced,
|
|
8539
|
+
malformed: report.malformed,
|
|
8540
|
+
unresolved,
|
|
8541
|
+
unverified: report.unverified,
|
|
8542
|
+
noted: report.noted,
|
|
8543
|
+
bulletCount: report.bulletCount
|
|
8544
|
+
};
|
|
8545
|
+
}
|
|
8546
|
+
function validateCitations(text, ctx) {
|
|
8547
|
+
const malformed = [];
|
|
8548
|
+
const unresolved = [];
|
|
8549
|
+
const unverified2 = [];
|
|
8550
|
+
const noted2 = [];
|
|
8551
|
+
let cited = 0;
|
|
8552
|
+
for (const item of parseItems(text)) {
|
|
8553
|
+
if (item.token === null) {
|
|
8554
|
+
if (!hasSrcMarker(item.text))
|
|
8555
|
+
continue;
|
|
8556
|
+
cited++;
|
|
8557
|
+
malformed.push(citationIssue(item.line, item.text));
|
|
8558
|
+
continue;
|
|
8559
|
+
}
|
|
8560
|
+
cited++;
|
|
8561
|
+
for (const error of item.token.errors) {
|
|
8562
|
+
unresolved.push({ line: item.line, message: `[src: ${error.raw}] — ${error.message}`, rule: error.rule });
|
|
8563
|
+
}
|
|
8564
|
+
const claim = item.text.replace(item.token.raw, " ").trim();
|
|
8565
|
+
for (const ref of item.token.refs) {
|
|
8566
|
+
const resolution = resolveSrc(ref, ctx, item.section, claim);
|
|
8567
|
+
const issue = { line: item.line, message: `[src: ${ref.raw}] — ${resolution.message ?? "unresolvable"}` };
|
|
8568
|
+
if (resolution.outcome === "refused")
|
|
8569
|
+
unresolved.push(issue);
|
|
8570
|
+
else if (resolution.outcome === "unverified")
|
|
8571
|
+
unverified2.push(issue);
|
|
8572
|
+
else if (resolution.outcome === "noted")
|
|
8573
|
+
noted2.push({ ...issue, src: ref.raw });
|
|
8574
|
+
}
|
|
8575
|
+
}
|
|
8576
|
+
return { malformed, unresolved, unverified: unverified2, noted: noted2, cited };
|
|
8577
|
+
}
|
|
8578
|
+
function noneBullet(lookedAt) {
|
|
8579
|
+
return `- none [src: absent:${lookedAt}]`;
|
|
8580
|
+
}
|
|
8581
|
+
|
|
8582
|
+
export { runtime, parseYaml, parseYamlRepairing, STAGES_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, readableSource, SRC_PATTERNS, srcRule, parseSrcToken, classifySrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs, parseHandoff, isHandoff, BULLET_RULE, EMPTY_SECTION_RULE, validateHandoff, validateCitations, noneBullet };
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BULLET_RULE,
|
|
3
3
|
EMPTY_SECTION_RULE,
|
|
4
|
-
noneBullet
|
|
5
|
-
} from "./chunk-q8d3sff9.js";
|
|
6
|
-
import {
|
|
4
|
+
noneBullet,
|
|
7
5
|
srcRule
|
|
8
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-9w64mxdh.js";
|
|
9
7
|
|
|
10
8
|
// src/hooks/lib/messages.ts
|
|
11
9
|
var GRAMMAR = "[src: <repo:path:line> | https://… | Q<n> | F<n> | $ <cmd> → exit <n> | graph:<node> | absent:<path>]";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-39zh2e44.js";
|
|
4
4
|
import {
|
|
5
5
|
parseYaml
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-9w64mxdh.js";
|
|
7
7
|
|
|
8
8
|
// src/core/schemas/planCommon.ts
|
|
9
9
|
var STORY_ID_RE = /^S\d{1,4}$/;
|
|
@@ -132,33 +132,5 @@ function message(error) {
|
|
|
132
132
|
return error instanceof Error ? error.message.split(`
|
|
133
133
|
`)[0] ?? "parse error" : String(error);
|
|
134
134
|
}
|
|
135
|
-
// src/core/schemas/story.ts
|
|
136
|
-
var DOD_FENCE_OPEN_RE = /^\s*```+\s*dod\s*$/i;
|
|
137
|
-
var DOD_FENCE_CLOSE_RE = /^\s*```+\s*$/;
|
|
138
|
-
var FENCE_OPEN_RE = DOD_FENCE_OPEN_RE;
|
|
139
|
-
var FENCE_CLOSE_RE = DOD_FENCE_CLOSE_RE;
|
|
140
|
-
function parseDodBlock(text) {
|
|
141
|
-
const commands = [];
|
|
142
|
-
let present = false;
|
|
143
|
-
let inside = false;
|
|
144
|
-
for (const line of text.split(`
|
|
145
|
-
`)) {
|
|
146
|
-
if (inside) {
|
|
147
|
-
if (FENCE_CLOSE_RE.test(line)) {
|
|
148
|
-
inside = false;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
const command = line.trim();
|
|
152
|
-
if (command !== "" && !command.startsWith("#"))
|
|
153
|
-
commands.push(command);
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (FENCE_OPEN_RE.test(line)) {
|
|
157
|
-
present = true;
|
|
158
|
-
inside = true;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return { present, commands };
|
|
162
|
-
}
|
|
163
135
|
|
|
164
|
-
export { STORY_ID_RE, WAVE_ID_RE, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter
|
|
136
|
+
export { STORY_ID_RE, WAVE_ID_RE, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter };
|