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.
Files changed (30) hide show
  1. package/CHANGELOG.md +792 -0
  2. package/README.md +19 -5
  3. package/dist/hooks/answer-capture.js +136 -12
  4. package/dist/hooks/budget-gate.js +9 -9
  5. package/dist/hooks/{chunk-889tybxc.js → chunk-17gv74sr.js} +3 -3
  6. package/dist/hooks/{chunk-3w55tp71.js → chunk-5k4dggq0.js} +51 -17
  7. package/dist/hooks/{chunk-14zn51kh.js → chunk-7eqhfddw.js} +28 -569
  8. package/dist/hooks/{chunk-nadqsr3w.js → chunk-97ncnegd.js} +1 -1
  9. package/dist/hooks/{chunk-a6rpj2cp.js → chunk-9w64mxdh.js} +345 -22
  10. package/dist/hooks/{chunk-a5dq2dcp.js → chunk-dhbxzjfs.js} +2 -4
  11. package/dist/hooks/{chunk-hcrbr430.js → chunk-g4db8rbs.js} +2 -30
  12. package/dist/hooks/chunk-h7151g8w.js +803 -0
  13. package/dist/hooks/{chunk-5556vjt5.js → chunk-ka6bkb64.js} +1 -1
  14. package/dist/hooks/chunk-rrkdfk7s.js +30 -0
  15. package/dist/hooks/{chunk-qw73rdbr.js → chunk-v4ay50nq.js} +1 -1
  16. package/dist/hooks/{chunk-v1c1hpb8.js → chunk-vrhczzxs.js} +1 -1
  17. package/dist/hooks/{chunk-bvm6vjrt.js → chunk-x6n8b5fp.js} +1 -1
  18. package/dist/hooks/claim-sources.js +8 -10
  19. package/dist/hooks/dod-gate.js +7 -7
  20. package/dist/hooks/no-reask.js +8 -9
  21. package/dist/hooks/session-start.js +23 -18
  22. package/dist/hooks/statusline.js +10 -10
  23. package/dist/tldrx.js +2539 -1006
  24. package/env.yml +9 -2
  25. package/package.json +1 -1
  26. package/plugin/.claude-plugin/plugin.json +1 -1
  27. package/plugin/skills/tldrx/SKILL.md +8 -1
  28. package/templates/questions.md +8 -0
  29. package/dist/hooks/chunk-q8d3sff9.js +0 -220
  30. 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 path = raw.slice("absent:".length);
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
- return { kind: "absent", raw, path };
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 = fileBases(ref, ctx);
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.path, ctx, section, claim);
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(path, ctx, section, claim) {
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 abs = isAbsolute2(path) ? normalize(path) : normalize(join3(ctx.root, path));
8001
- if (!existsSync3(abs))
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
- return unverified(`${path} EXISTS — "found nothing in it" is a claim about its contents, unchecked here`);
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 fileBases(ref, ctx) {
8010
- if (isAbsolute2(ref.path)) {
8011
- return [{ label: "the absolute path as written", abs: normalize(ref.path) }];
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 (ref.repo !== null) {
8014
- const rel2 = ctx.repos.get(ref.repo) ?? "";
8117
+ if (repo !== null) {
8118
+ const rel2 = ctx.repos.get(repo) ?? "";
8015
8119
  return [
8016
- ...epicBases(ctx, ref.repo, ref.path),
8017
- { label: repoLabel(ref.repo, rel2), abs: normalize(join3(ctx.root, rel2, ref.path)) }
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 = ref.path.indexOf("/");
8022
- const named = slash > 0 ? ref.path.slice(0, slash) : "";
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, ref.path.slice(slash + 1)));
8026
- bases.push({ label: "workspace root", abs: normalize(join3(ctx.root, ref.path)) });
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, ref.path)) });
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, ref.path.slice(slash + 1)))
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
- export { runtime, parseYaml, parseYamlRepairing, STAGES_DIR, TEMPLATES_DIR, PROJECT_FRAMEWORK_DIR, PROJECT_WORK_DIR, readableSource, SRC_PATTERNS, srcRule, diagnoseSrcToken, parseSrcToken, hasSrcMarker, classifySrc, resolveSrc, locateWork, findWorkspaceRoot, loadWorkspace, toSrcContext, repoPath, factsPath, stageYamlPath, listRunDirs };
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-a6rpj2cp.js";
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-a6rpj2cp.js";
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, parseDodBlock };
136
+ export { STORY_ID_RE, WAVE_ID_RE, MAX_WAVES, MAX_STORIES_PER_WAVE, MAX_PLAN_STORIES, requireStringList, requirePattern, requireVersion1, parseFrontMatter };