tldr-experts 0.3.0 → 0.4.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 (36) hide show
  1. package/CHANGELOG.md +1957 -0
  2. package/README.md +99 -12
  3. package/dist/hooks/answer-capture.js +21 -12
  4. package/dist/hooks/budget-gate.js +95 -11
  5. package/dist/hooks/{chunk-0bt6yb2q.js → chunk-39zh2e44.js} +10 -1
  6. package/dist/hooks/chunk-4cp363kv.js +1766 -0
  7. package/dist/hooks/{chunk-t8tdv11p.js → chunk-7y2dq0pj.js} +1 -1
  8. package/dist/hooks/chunk-9zsqxr6y.js +213 -0
  9. package/dist/hooks/{chunk-a8p2rc94.js → chunk-b8kxzna2.js} +1 -1
  10. package/dist/hooks/{chunk-j234zf0t.js → chunk-c6t5nx0r.js} +37 -166
  11. package/dist/hooks/{chunk-g395gk7e.js → chunk-m3mewgnw.js} +36 -17
  12. package/dist/hooks/chunk-phmdk72a.js +164 -0
  13. package/dist/hooks/chunk-rpcxsqh3.js +199 -0
  14. package/dist/hooks/{chunk-azctppjh.js → chunk-rz541e2b.js} +7 -1
  15. package/dist/hooks/{chunk-y0jdr3et.js → chunk-s1c5h7yx.js} +71 -29
  16. package/dist/hooks/{chunk-p274ckxv.js → chunk-sq44k6g2.js} +155 -12
  17. package/dist/hooks/{chunk-x98qs959.js → chunk-t1ywrfr4.js} +35 -42
  18. package/dist/hooks/{chunk-1zwcxd3f.js → chunk-tzzwddct.js} +1 -1
  19. package/dist/hooks/claim-sources.js +31 -23
  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 +22798 -12309
  25. package/package.json +3 -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/stages/build/stage.yml +2 -1
  30. package/stages/plan/stage.md +11 -0
  31. package/templates/budget.yml +5 -0
  32. package/templates/epic.md +5 -3
  33. package/dist/hooks/chunk-ae6bkfs5.js +0 -0
  34. package/dist/hooks/chunk-kw4tffzf.js +0 -139
  35. package/dist/hooks/chunk-sdjnnmzz.js +0 -497
  36. package/dist/hooks/chunk-t56k6146.js +0 -14
@@ -1,36 +1,42 @@
1
1
  #!/usr/bin/env node
2
- import"./chunk-kw4tffzf.js";
3
- import"./chunk-x98qs959.js";
2
+ import"./chunk-t1ywrfr4.js";
4
3
  import {
5
4
  allow,
6
5
  readPayload,
7
6
  runHook,
8
7
  sessionContext
9
- } from "./chunk-1zwcxd3f.js";
10
- import"./chunk-a8p2rc94.js";
8
+ } from "./chunk-tzzwddct.js";
9
+ import"./chunk-b8kxzna2.js";
11
10
  import {
12
11
  RunStore,
13
12
  isMovable,
14
13
  openQuestions,
15
14
  runSnapshot,
16
15
  whatIsWaiting
17
- } from "./chunk-y0jdr3et.js";
16
+ } from "./chunk-s1c5h7yx.js";
18
17
  import {
19
- openRunViews
20
- } from "./chunk-sdjnnmzz.js";
21
- import"./chunk-azctppjh.js";
22
- import"./chunk-ae6bkfs5.js";
18
+ evidencePath,
19
+ expertsDir,
20
+ gateEvidencePath,
21
+ loadExperts,
22
+ openRunViews,
23
+ parseEvidence,
24
+ pathsIntersect,
25
+ readExpertDomain,
26
+ stackExpertNames
27
+ } from "./chunk-4cp363kv.js";
28
+ import"./chunk-rz541e2b.js";
29
+ import"./chunk-9zsqxr6y.js";
30
+ import"./chunk-phmdk72a.js";
31
+ import"./chunk-c6t5nx0r.js";
23
32
  import {
24
33
  openBlocks,
25
34
  parseQuestions
26
- } from "./chunk-j234zf0t.js";
27
- import"./chunk-t56k6146.js";
28
- import {
29
- classifySrc
30
- } from "./chunk-g395gk7e.js";
35
+ } from "./chunk-rpcxsqh3.js";
36
+ import"./chunk-m3mewgnw.js";
31
37
  import {
32
38
  isRecord
33
- } from "./chunk-0bt6yb2q.js";
39
+ } from "./chunk-39zh2e44.js";
34
40
  import {
35
41
  PROJECT_FRAMEWORK_DIR,
36
42
  PROJECT_WORK_DIR,
@@ -39,7 +45,7 @@ import {
39
45
  findWorkspaceRoot,
40
46
  listRunDirs,
41
47
  parseYaml
42
- } from "./chunk-p274ckxv.js";
48
+ } from "./chunk-sq44k6g2.js";
43
49
 
44
50
  // src/core/status/PendingItem.ts
45
51
  function isPending(item) {
@@ -97,39 +103,6 @@ function statusLineOf(text) {
97
103
  }
98
104
  var DOCUMENT_EXTENSIONS = new Set([".md", ".txt", ".markdown", ".rst", ".adoc", ".pdf", ".doc", ".docx"]);
99
105
 
100
- // src/core/detect/walk.ts
101
- var SKIPPED_DIRS = new Set([
102
- ".git",
103
- "node_modules",
104
- "dist",
105
- "build",
106
- "out",
107
- "bin",
108
- "obj",
109
- "target",
110
- ".venv",
111
- "venv",
112
- "__pycache__",
113
- ".next",
114
- ".expo",
115
- ".gradle",
116
- "vendor",
117
- "coverage",
118
- "graphify-out",
119
- ".tldrx",
120
- "tldrx-work",
121
- ".idea",
122
- ".vscode",
123
- "Pods",
124
- "DerivedData",
125
- ".terraform",
126
- ".turbo",
127
- ".cache",
128
- "storybook-static",
129
- "playwright-report",
130
- "test-results"
131
- ]);
132
-
133
106
  // src/core/seed/collectSeed.ts
134
107
  var MAX_SEED_BYTES = 2 * 1024 * 1024;
135
108
 
@@ -454,6 +427,125 @@ function chainsOf(inputs, runs) {
454
427
  return chains;
455
428
  }
456
429
 
430
+ // src/core/run/decisionCards.ts
431
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
432
+ import { join as join3 } from "node:path";
433
+ function questionsCard(ctx) {
434
+ const blocks = openQuestionBlocks(join3(ctx.runDir, ctx.phaseId, "questions.md"));
435
+ if (blocks.length === 0)
436
+ return null;
437
+ const recommendations = readRecommendations(ctx);
438
+ return {
439
+ kind: "questions",
440
+ run: ctx.runId,
441
+ gate: `${ctx.phaseId}/${ctx.stageId}`,
442
+ headline: "",
443
+ questions: blocks.map((block) => toQuestion(block, ctx.runId, recommendations)),
444
+ detail: [],
445
+ commands: []
446
+ };
447
+ }
448
+ function toQuestion(block, runId, recommendations) {
449
+ const recommended = recommendations.get(block.id);
450
+ return {
451
+ id: block.id,
452
+ title: block.title,
453
+ whyAsked: block.whyAsked,
454
+ options: block.options.map(firstLine),
455
+ recommendation: recommended === undefined ? null : toRecommendation(recommended),
456
+ answerCommand: `tldrx answer ${block.id} "…" --run ${runId}`
457
+ };
458
+ }
459
+ function firstLine(option) {
460
+ return { letter: option.letter, text: option.text.split(`
461
+ `)[0] ?? "" };
462
+ }
463
+ function toRecommendation(entry) {
464
+ return { option: entry.option, why: entry.why, src: entry.src };
465
+ }
466
+ function openQuestionBlocks(path) {
467
+ if (!existsSync3(path))
468
+ return [];
469
+ try {
470
+ return openBlocks(parseQuestions(readFileSync3(path, "utf8")).blocks);
471
+ } catch {
472
+ return [];
473
+ }
474
+ }
475
+ function readRecommendations(ctx) {
476
+ const out = new Map;
477
+ const paths = [
478
+ evidencePath(ctx.runDir, ctx.stageId),
479
+ gateEvidencePath(ctx.runDir, ctx.phaseId, ctx.stageId)
480
+ ];
481
+ for (const path of paths) {
482
+ const text = readOrNull(path);
483
+ if (text === null)
484
+ continue;
485
+ const front = parseEvidence(text).front;
486
+ if (front === null)
487
+ continue;
488
+ for (const entry of front.recommend) {
489
+ if (!out.has(entry.q))
490
+ out.set(entry.q, entry);
491
+ }
492
+ if (out.size > 0)
493
+ return out;
494
+ }
495
+ return out;
496
+ }
497
+ function readOrNull(path) {
498
+ if (!existsSync3(path))
499
+ return null;
500
+ try {
501
+ return readFileSync3(path, "utf8");
502
+ } catch {
503
+ return null;
504
+ }
505
+ }
506
+
507
+ // src/core/ui/decisionCard.ts
508
+ function decisionHeader(card) {
509
+ return `DECISION — ${card.run} · ${card.gate}`;
510
+ }
511
+ function renderDecisionCard(card) {
512
+ const lines = [decisionHeader(card)];
513
+ if (card.kind === "questions") {
514
+ card.questions.forEach((question, index) => {
515
+ if (index > 0)
516
+ lines.push("");
517
+ lines.push(...renderQuestion(question));
518
+ });
519
+ return lines;
520
+ }
521
+ if (card.headline !== "")
522
+ lines.push(card.headline);
523
+ for (const line of card.detail)
524
+ lines.push(` ${line}`);
525
+ for (const command of card.commands)
526
+ lines.push(` ${command}`);
527
+ return lines;
528
+ }
529
+ function renderQuestion(question) {
530
+ const lines = [`${question.id} · ${question.title}`];
531
+ if (question.whyAsked !== null)
532
+ lines.push(` Why asked: ${question.whyAsked}`);
533
+ for (const option of question.options)
534
+ lines.push(` ${option.letter}) ${option.text}`);
535
+ const recommendation = renderRecommendation(question.recommendation);
536
+ if (recommendation !== null)
537
+ lines.push(recommendation);
538
+ lines.push(` ${question.answerCommand}`);
539
+ return lines;
540
+ }
541
+ function renderRecommendation(recommendation) {
542
+ if (recommendation === null)
543
+ return null;
544
+ const why = recommendation.why.trim();
545
+ const head = `Recommends ${recommendation.option}${why === "" ? "" : ` — ${why}`}`;
546
+ return recommendation.src.trim() === "" ? head : `${head} [src: ${recommendation.src.trim()}]`;
547
+ }
548
+
457
549
  // src/core/status/runItems.ts
458
550
  var NEXT_MARK = "← next";
459
551
  function dependencyInputs(root) {
@@ -508,8 +600,17 @@ function runItems(root) {
508
600
  if (waiting.kind === "gate") {
509
601
  details.push(`disagree instead: \`tldrx reject --run ${run.run} --note "<why>"\``);
510
602
  }
511
- if (waiting.kind === "answer" && waiting.questions.length > 1) {
512
- details.push(`open questions: ${waiting.questions.join(", ")}`);
603
+ if (waiting.kind === "answer") {
604
+ const card = questionsCard({
605
+ runDir: store.runDir,
606
+ runId: run.run,
607
+ phaseId: run.cursor.phase,
608
+ stageId: run.cursor.stage
609
+ });
610
+ if (card === null)
611
+ details.push(`open questions: ${waiting.questions.join(", ")}`);
612
+ else
613
+ details.push(...renderDecisionCard(card));
513
614
  }
514
615
  if (waiting.kind === "failed")
515
616
  details.push(waiting.message);
@@ -593,356 +694,12 @@ function commandFor(run, waiting) {
593
694
  return "";
594
695
  }
595
696
  }
596
- // src/core/experts/loadExperts.ts
597
- import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
598
- import { join as join3 } from "node:path";
599
-
600
- // src/core/init/competencyLevel.ts
601
- var EVIDENCE_KINDS = ["code", "run", "test", "doc", "answer"];
602
- var ALLOWED_KINDS = EVIDENCE_KINDS.join(", ");
603
- function isEvidenceKind(value) {
604
- return EVIDENCE_KINDS.includes(value);
605
- }
606
- var EVIDENCE_CONFIDENCES = ["measured", "inferred", "assumed"];
607
- function isEvidenceConfidence(value) {
608
- return EVIDENCE_CONFIDENCES.includes(value);
609
- }
610
- var WEIGHTS = { code: 1, run: 1, test: 1, doc: 0.5, answer: 0.8 };
611
- var CROSS_MULTIPLIER = 2;
612
- var ASSUMED_MULTIPLIER = 0.5;
613
- var THRESHOLDS = [0.5, 1.5, 3, 6, 20];
614
- var RECENCY_FLOOR = 0.25;
615
- var RECENCY_HALFLIFE_DAYS = 365;
616
- var NO_RUN_CAP = 3;
617
- var MIN_KINDS_FOR_TOP = 2;
618
- var DAY_MS = 86400000;
619
- function competencyLevel(evidence, now = new Date) {
620
- if (evidence.length === 0)
621
- return 0;
622
- let weighted = 0;
623
- const sources = new Set;
624
- const kinds = new Set;
625
- for (const item of evidence) {
626
- const ageDays = daysBetween(item.at, now);
627
- if (ageDays === null)
628
- continue;
629
- weighted += recency(ageDays) * weightOf(item);
630
- sources.add(item.src);
631
- kinds.add(item.kind);
632
- }
633
- let level = THRESHOLDS.filter((threshold) => weighted >= threshold).length;
634
- if (!kinds.has("run"))
635
- level = Math.min(level, NO_RUN_CAP);
636
- if (level === 5 && kinds.size < MIN_KINDS_FOR_TOP)
637
- level = 4;
638
- return Math.min(level, sources.size);
639
- }
640
- function weightOf(item) {
641
- const base = WEIGHTS[item.kind];
642
- const cross = item.cross === true ? CROSS_MULTIPLIER : 1;
643
- const confidence = item.confidence === "assumed" ? ASSUMED_MULTIPLIER : 1;
644
- return base * cross * confidence;
645
- }
646
- function recency(ageDays) {
647
- return Math.max(RECENCY_FLOOR, 1 - ageDays / RECENCY_HALFLIFE_DAYS);
648
- }
649
- function daysBetween(at, now) {
650
- const then = Date.parse(at);
651
- if (Number.isNaN(then))
652
- return null;
653
- return Math.max(0, (now.getTime() - then) / DAY_MS);
654
- }
655
-
656
- // src/core/experts/evidenceSrc.ts
657
- function checkEvidenceSrc(kind, src) {
658
- const parsed = classifySrc(src);
659
- if ("message" in parsed)
660
- return { reason: "malformed-src" };
661
- return matchesKind(kind, parsed) ? null : { reason: "kind-mismatch" };
662
- }
663
- function matchesKind(kind, ref) {
664
- switch (kind) {
665
- case "code":
666
- return ref.kind === "file";
667
- case "run":
668
- return ref.kind === "cmd" || isRunArtifact(ref);
669
- case "test":
670
- return ref.kind === "file" || ref.kind === "cmd";
671
- case "doc":
672
- return ref.kind === "doc";
673
- case "answer":
674
- return ref.kind === "fact";
675
- }
676
- }
677
- function isRunArtifact(ref) {
678
- return ref.kind === "file" && ref.repo === null && ref.path.startsWith("tldrx-work/");
679
- }
680
-
681
- // src/core/experts/readEvidenceRows.ts
682
- function readEvidenceRows(input) {
683
- if (!Array.isArray(input))
684
- return { evidence: [], ignored: [] };
685
- const evidence = [];
686
- const counts = new Map;
687
- const refuse = (reason, kind, src) => {
688
- const key = `${reason} ${reason === "unknown-kind" ? kind : `${kind} ${src}`}`;
689
- const seen = counts.get(key);
690
- counts.set(key, seen === undefined ? { reason, kind, src, count: 1 } : { ...seen, count: seen.count + 1 });
691
- };
692
- for (const raw of input) {
693
- if (typeof raw !== "object" || raw === null || Array.isArray(raw))
694
- continue;
695
- const row = raw;
696
- const kind = str(row.kind);
697
- const src = str(row.src);
698
- const at = str(row.at);
699
- if (src === "" || at === "")
700
- continue;
701
- if (!isEvidenceKind(kind)) {
702
- refuse("unknown-kind", kind, "");
703
- continue;
704
- }
705
- const problem = checkEvidenceSrc(kind, src);
706
- if (problem !== null) {
707
- refuse(problem.reason, kind, src);
708
- continue;
709
- }
710
- const confidence = str(row.confidence);
711
- evidence.push({
712
- kind,
713
- src,
714
- at,
715
- ...row.cross === true ? { cross: true } : {},
716
- ...isEvidenceConfidence(confidence) ? { confidence } : {}
717
- });
718
- }
719
- return { evidence, ignored: [...counts.values()] };
720
- }
721
- function str(value) {
722
- return typeof value === "string" ? value : "";
723
- }
724
-
725
- // src/core/experts/loadExperts.ts
726
- var EXPERTS_DIRNAME = "experts";
727
- var COMPETENCIES_FILE = "competencies.yml";
728
- var EXPERT_FILE = "expert.md";
729
- function expertsDir(root) {
730
- return join3(root, PROJECT_FRAMEWORK_DIR, EXPERTS_DIRNAME);
731
- }
732
- function expertDir(root, name) {
733
- return join3(expertsDir(root), name);
734
- }
735
- function loadExperts(root, now = new Date) {
736
- const dir = expertsDir(root);
737
- if (!existsSync3(dir))
738
- return [];
739
- const names = readdirSync2(dir).filter((entry) => statSync2(join3(dir, entry)).isDirectory()).sort();
740
- return names.map((name) => loadExpert(root, name, now));
741
- }
742
- function loadExpert(root, name, now = new Date) {
743
- const dir = expertDir(root, name);
744
- const path = join3(dir, COMPETENCIES_FILE);
745
- const empty = { name, dir, status: "unknown", lastTrained: null, areas: [], drifted: [] };
746
- if (!existsSync3(path))
747
- return { ...empty, error: `${COMPETENCIES_FILE} is missing` };
748
- let doc;
749
- try {
750
- const parsed = parseYaml(readFileSync3(path, "utf8"));
751
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
752
- return { ...empty, error: `${COMPETENCIES_FILE} is not a mapping` };
753
- }
754
- doc = parsed;
755
- } catch (error) {
756
- return { ...empty, error: error instanceof Error ? error.message : String(error) };
757
- }
758
- const areas = Array.isArray(doc.areas) ? doc.areas.map((area) => toArea(name, area, now)).filter((area) => area !== null) : [];
759
- return {
760
- name: typeof doc.expert === "string" && doc.expert !== "" ? doc.expert : name,
761
- dir,
762
- status: typeof doc.status === "string" ? doc.status : "unknown",
763
- lastTrained: typeof doc.last_trained === "string" ? doc.last_trained : null,
764
- areas,
765
- drifted: areas.filter((area) => area.storedLevel !== null && area.storedLevel !== area.level),
766
- error: null
767
- };
768
- }
769
- function toArea(expert, input, now) {
770
- if (typeof input !== "object" || input === null || Array.isArray(input))
771
- return null;
772
- const raw = input;
773
- const id = str2(raw.id) !== "" ? str2(raw.id) : str2(raw.area);
774
- if (id === "")
775
- return null;
776
- const { evidence, ignored } = readEvidenceRows(raw.evidence);
777
- return {
778
- id,
779
- title: str2(raw.title) !== "" ? str2(raw.title) : id,
780
- storedLevel: typeof raw.level === "number" ? raw.level : null,
781
- level: competencyLevel(evidence, now),
782
- trainPrompt: str2(raw.train_prompt) !== "" ? str2(raw.train_prompt) : `tldrx expert train ${expert} --area ${id} --mode light`,
783
- evidence,
784
- ignored,
785
- newestEvidence: newestDate(evidence)
786
- };
787
- }
788
- function newestDate(evidence) {
789
- let newest = null;
790
- for (const item of evidence) {
791
- if (newest === null || item.at > newest)
792
- newest = item.at;
793
- }
794
- return newest;
795
- }
796
- function str2(value) {
797
- return typeof value === "string" ? value : "";
798
- }
799
697
  // src/core/experts/stageCoverage.ts
800
- import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
801
- import { join as join6 } from "node:path";
802
-
803
- // src/core/experts/stackExperts.ts
804
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
698
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
805
699
  import { join as join4 } from "node:path";
806
- function stackExpertNames(root, repos) {
807
- const path = join4(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
808
- if (!existsSync4(path))
809
- return [];
810
- let doc;
811
- try {
812
- doc = parseYaml(readFileSync4(path, "utf8"));
813
- } catch {
814
- return [];
815
- }
816
- const list = doc?.repos;
817
- if (!Array.isArray(list))
818
- return [];
819
- const names = [];
820
- for (const row of list) {
821
- if (typeof row?.name !== "string" || !repos.includes(row.name))
822
- continue;
823
- const stack = Array.isArray(row.stack) ? row.stack : [];
824
- for (const language of stack) {
825
- if (typeof language !== "string" || language === "")
826
- continue;
827
- const expert = `${language}-stack`;
828
- if (!names.includes(expert))
829
- names.push(expert);
830
- }
831
- }
832
- return names;
833
- }
834
-
835
- // src/core/experts/selectExperts.ts
836
- import { existsSync as existsSync6, readdirSync as readdirSync3, statSync as statSync3 } from "node:fs";
837
-
838
- // src/core/experts/expertDocument.ts
839
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
840
- import { join as join5 } from "node:path";
841
- var H2_RE = /^##\s+(.+?)\s*$/;
842
- function readExpertDocument(root, name) {
843
- const path = join5(expertDir(root, name), EXPERT_FILE);
844
- if (!existsSync5(path)) {
845
- return { path, exists: false, frontMatter: new Map, body: "" };
846
- }
847
- const text = readFileSync5(path, "utf8");
848
- const { frontMatter, body } = splitFrontMatter(text);
849
- return { path, exists: true, frontMatter, body };
850
- }
851
- function splitFrontMatter(text) {
852
- const frontMatter = new Map;
853
- if (!text.startsWith(`---
854
- `))
855
- return { frontMatter, body: text };
856
- const end = text.indexOf(`
857
- ---`, 3);
858
- if (end === -1)
859
- return { frontMatter, body: text };
860
- for (const line of text.slice(4, end).split(`
861
- `)) {
862
- const colon = line.indexOf(":");
863
- if (colon <= 0 || line.trimStart().startsWith("#"))
864
- continue;
865
- const key = line.slice(0, colon).trim();
866
- const value = line.slice(colon + 1).replace(/#.*$/, "").trim().replace(/^"(.*)"$/, "$1");
867
- if (key !== "")
868
- frontMatter.set(key, value);
869
- }
870
- const newline = text.indexOf(`
871
- `, end + 1);
872
- return { frontMatter, body: newline === -1 ? "" : text.slice(newline + 1) };
873
- }
874
- function section(body, heading) {
875
- const lines = body.split(`
876
- `);
877
- const collected = [];
878
- let inside = false;
879
- for (const line of lines) {
880
- const match = H2_RE.exec(line);
881
- if (match !== null) {
882
- if (inside)
883
- break;
884
- inside = match[1] === heading;
885
- continue;
886
- }
887
- if (inside)
888
- collected.push(line);
889
- }
890
- return collected.join(`
891
- `).trim();
892
- }
893
-
894
- // src/core/experts/expertDomain.ts
895
- var PLACEHOLDER_PATHS = new Set(["path/to/module", "path/to/other"]);
896
- function readExpertDomain(root, name) {
897
- const doc = readExpertDocument(root, name);
898
- if (!doc.exists)
899
- return { name, exists: false, kind: "", repos: [], paths: [] };
900
- return {
901
- name,
902
- exists: true,
903
- kind: doc.frontMatter.get("kind") ?? "",
904
- repos: parseList(doc.frontMatter.get("repos") ?? ""),
905
- paths: domainPaths(section(doc.body, "Domain"))
906
- };
907
- }
908
- function parseList(value) {
909
- const inner = value.trim().replace(/^\[/, "").replace(/\]$/, "");
910
- return inner.split(",").map((item) => item.trim().replace(/^["']|["']$/g, "")).filter((item) => item !== "");
911
- }
912
- function domainPaths(body) {
913
- const paths = [];
914
- for (const line of body.split(`
915
- `)) {
916
- const bullet = /^\s*[-*]\s+(.*)$/.exec(line);
917
- if (bullet === null)
918
- continue;
919
- const text = (bullet[1] ?? "").trim();
920
- if (/^repo\s/i.test(text))
921
- continue;
922
- const code = /`([^`]+)`/.exec(text);
923
- const raw = (code?.[1] ?? text.split(/\s+—\s+/)[0] ?? "").trim();
924
- const path = normalisePath(raw);
925
- if (path === "" || PLACEHOLDER_PATHS.has(path) || !path.includes("/"))
926
- continue;
927
- if (!paths.includes(path))
928
- paths.push(path);
929
- }
930
- return paths;
931
- }
932
- function normalisePath(raw) {
933
- return raw.trim().replace(/^[a-z0-9-]+:(?=[^/])/i, "").replace(/^\.\//, "").replace(/\/+$/, "").replace(/\\/g, "/");
934
- }
935
- function pathsIntersect(a, b) {
936
- const left = normalisePath(a);
937
- const right = normalisePath(b);
938
- if (left === "" || right === "")
939
- return false;
940
- if (left === right)
941
- return true;
942
- return left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
943
- }
944
700
 
945
701
  // src/core/experts/selectExperts.ts
702
+ import { existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
946
703
  var MAX_DOMAIN_SELECTED = 8;
947
704
  var LEGACY_STAGE_EXPERTS = ["domain", "stack"];
948
705
  function selectExperts(input) {
@@ -982,18 +739,18 @@ function selectExperts(input) {
982
739
  }
983
740
  function expertNames(root) {
984
741
  const dir = expertsDir(root);
985
- if (!existsSync6(dir))
742
+ if (!existsSync4(dir))
986
743
  return [];
987
- return readdirSync3(dir).filter((entry) => {
744
+ return readdirSync2(dir).filter((entry) => {
988
745
  try {
989
- return statSync3(`${dir}/${entry}`).isDirectory();
746
+ return statSync2(`${dir}/${entry}`).isDirectory();
990
747
  } catch {
991
748
  return false;
992
749
  }
993
750
  }).sort();
994
751
  }
995
752
  function hasExpert(root, name) {
996
- return existsSync6(`${expertsDir(root)}/${name}`);
753
+ return existsSync4(`${expertsDir(root)}/${name}`);
997
754
  }
998
755
  var DIRECT_WEIGHT = 10;
999
756
  var GRAPH_HOPS = 2;
@@ -1083,18 +840,18 @@ function stagesLoadingExperts(root) {
1083
840
  }
1084
841
  function stageIds(root) {
1085
842
  const ids = new Set([
1086
- ...dirsIn(join6(root, PROJECT_FRAMEWORK_DIR, "stages")),
843
+ ...dirsIn(join4(root, PROJECT_FRAMEWORK_DIR, "stages")),
1087
844
  ...dirsIn(STAGES_DIR)
1088
845
  ]);
1089
846
  return [...ids].sort();
1090
847
  }
1091
848
  function dirsIn(dir) {
1092
- if (!existsSync7(dir))
849
+ if (!existsSync5(dir))
1093
850
  return [];
1094
851
  try {
1095
- return readdirSync4(dir).filter((entry) => {
852
+ return readdirSync3(dir).filter((entry) => {
1096
853
  try {
1097
- return statSync4(join6(dir, entry)).isDirectory() && existsSync7(join6(dir, entry, "stage.yml"));
854
+ return statSync3(join4(dir, entry)).isDirectory() && existsSync5(join4(dir, entry, "stage.yml"));
1098
855
  } catch {
1099
856
  return false;
1100
857
  }
@@ -1104,23 +861,23 @@ function dirsIn(dir) {
1104
861
  }
1105
862
  }
1106
863
  function readStage(root, id) {
1107
- const local = join6(root, PROJECT_FRAMEWORK_DIR, "stages", id, "stage.yml");
1108
- const path = existsSync7(local) ? local : join6(STAGES_DIR, id, "stage.yml");
1109
- if (!existsSync7(path))
864
+ const local = join4(root, PROJECT_FRAMEWORK_DIR, "stages", id, "stage.yml");
865
+ const path = existsSync5(local) ? local : join4(STAGES_DIR, id, "stage.yml");
866
+ if (!existsSync5(path))
1110
867
  return null;
1111
868
  try {
1112
- const doc = parseYaml(readFileSync6(path, "utf8"));
869
+ const doc = parseYaml(readFileSync4(path, "utf8"));
1113
870
  return isRecord(doc) ? doc : null;
1114
871
  } catch {
1115
872
  return null;
1116
873
  }
1117
874
  }
1118
875
  function workspaceRepos(root) {
1119
- const path = join6(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
1120
- if (!existsSync7(path))
876
+ const path = join4(root, PROJECT_FRAMEWORK_DIR, "workspace.yml");
877
+ if (!existsSync5(path))
1121
878
  return [];
1122
879
  try {
1123
- const doc = parseYaml(readFileSync6(path, "utf8"));
880
+ const doc = parseYaml(readFileSync4(path, "utf8"));
1124
881
  const list = isRecord(doc) && Array.isArray(doc.repos) ? doc.repos : [];
1125
882
  return list.map((row) => isRecord(row) && typeof row.name === "string" ? row.name : "").filter((name) => name !== "");
1126
883
  } catch {
@@ -1135,17 +892,14 @@ function strings(value) {
1135
892
  function evidenceCount(expert) {
1136
893
  return expert.areas.reduce((total, area) => total + area.evidence.length, 0);
1137
894
  }
1138
- // src/core/experts/expertKnowledge.ts
1139
- var DEFAULT_KNOWLEDGE_MAX_BYTES = 48 * 1024;
1140
-
1141
895
  // src/core/experts/roleExperts.ts
1142
- import { join as join7 } from "node:path";
1143
- var ROLE_TEMPLATES_DIR = join7(TEMPLATES_DIR, "experts");
896
+ import { join as join5 } from "node:path";
897
+ var ROLE_TEMPLATES_DIR = join5(TEMPLATES_DIR, "experts");
1144
898
  // src/core/experts/domainRank.ts
1145
899
  var MAX_GRAPH_BYTES = 64 * 1024 * 1024;
1146
900
  // src/core/training/mineRuns.ts
1147
- import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
1148
- import { join as join8, relative as relative2 } from "node:path";
901
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync4, statSync as statSync4 } from "node:fs";
902
+ import { join as join6, relative as relative2 } from "node:path";
1149
903
  var MAX_RUN_BYTES = 64 * 1024;
1150
904
  var MINED_NAMES = ["handoff.md", "retro.md"];
1151
905
  var MAX_DEPTH = 4;
@@ -1161,7 +915,7 @@ function findMined(dir, depth) {
1161
915
  return [];
1162
916
  let entries;
1163
917
  try {
1164
- entries = readdirSync5(dir);
918
+ entries = readdirSync4(dir);
1165
919
  } catch {
1166
920
  return [];
1167
921
  }
@@ -1169,10 +923,10 @@ function findMined(dir, depth) {
1169
923
  for (const entry of [...entries].sort()) {
1170
924
  if (entry.startsWith("."))
1171
925
  continue;
1172
- const abs = join8(dir, entry);
926
+ const abs = join6(dir, entry);
1173
927
  let isDir = false;
1174
928
  try {
1175
- isDir = statSync5(abs).isDirectory();
929
+ isDir = statSync4(abs).isDirectory();
1176
930
  } catch {
1177
931
  continue;
1178
932
  }