@pratik7368patil/anchor-core 0.1.7 → 0.1.8

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/dist/index.js CHANGED
@@ -252,8 +252,8 @@ function redactedHistoricalText(text) {
252
252
  }
253
253
 
254
254
  // src/db/database.ts
255
- import fs2 from "fs";
256
- import path2 from "path";
255
+ import fs3 from "fs";
256
+ import path3 from "path";
257
257
  import Database from "better-sqlite3";
258
258
 
259
259
  // src/db/migrations.ts
@@ -380,6 +380,9 @@ CREATE TABLE IF NOT EXISTS sync_state (
380
380
  repo TEXT PRIMARY KEY,
381
381
  last_sync_at TEXT,
382
382
  last_indexed_pr INTEGER,
383
+ history_coverage TEXT,
384
+ history_limit INTEGER,
385
+ history_since TEXT,
383
386
  updated_at TEXT NOT NULL
384
387
  );
385
388
 
@@ -392,12 +395,378 @@ CREATE INDEX IF NOT EXISTS idx_code_files_path ON code_files(path);
392
395
  CREATE INDEX IF NOT EXISTS idx_code_chunks_file_path ON code_chunks(file_path);
393
396
  `;
394
397
 
398
+ // src/rules/team-rules.ts
399
+ import fs2 from "fs";
400
+ import path2 from "path";
401
+ import { z } from "zod";
402
+
403
+ // src/retrieval/evidence.ts
404
+ function claimKeyFor(category, sanitizedText) {
405
+ return `${category}:${canonicalizeText(sanitizedText).slice(0, 180)}`;
406
+ }
407
+ function confidenceLevelFor(confidence) {
408
+ if (confidence >= 0.75) return "strong";
409
+ if (confidence >= 0.55) return "moderate";
410
+ return "weak";
411
+ }
412
+ function confidenceRank(level) {
413
+ const ranks = {
414
+ weak: 1,
415
+ moderate: 2,
416
+ strong: 3
417
+ };
418
+ return ranks[level];
419
+ }
420
+ function confidenceAtLeast(level, minimum) {
421
+ return confidenceRank(level) >= confidenceRank(minimum);
422
+ }
423
+ function evidenceForWisdom(unit) {
424
+ return {
425
+ prNumber: unit.prNumber,
426
+ prUrl: unit.prUrl,
427
+ sourceType: unit.sourceType,
428
+ author: unit.authors[0],
429
+ filePath: unit.filePaths[0]
430
+ };
431
+ }
432
+ function confidenceReasonsFor(unit, repeatedEvidenceCount) {
433
+ const reasons = [];
434
+ if (unit.sourceType === "review_comment" || unit.sourceType === "review_summary") {
435
+ reasons.push("reviewer evidence");
436
+ } else if (unit.sourceType === "pr_body") {
437
+ reasons.push("PR description evidence");
438
+ } else if (unit.sourceType === "commit_message") {
439
+ reasons.push("commit message evidence");
440
+ } else {
441
+ reasons.push(sourceTypeLabel(unit.sourceType));
442
+ }
443
+ if (unit.filePaths.length > 0) reasons.push("file-associated");
444
+ if (unit.symbols.length > 0) reasons.push("symbol-associated");
445
+ if (/\b(regression|this broke|broke|root cause)\b/i.test(unit.sanitizedText)) {
446
+ reasons.push("regression language");
447
+ }
448
+ if (/\b(do not|don't|must|should not|avoid|invariant|contract)\b/i.test(unit.sanitizedText)) {
449
+ reasons.push("constraint language");
450
+ }
451
+ if (repeatedEvidenceCount > 1) {
452
+ reasons.push(`repeated across ${repeatedEvidenceCount} PRs`);
453
+ }
454
+ return reasons;
455
+ }
456
+ function sourceTypeLabel(sourceType) {
457
+ return sourceType.replace(/_/g, " ");
458
+ }
459
+ function parseJsonArray(value) {
460
+ try {
461
+ const parsed = JSON.parse(value);
462
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
463
+ } catch {
464
+ return [];
465
+ }
466
+ }
467
+ function loadCurrentCodeSnapshot(db) {
468
+ const fileRows = db.prepare("SELECT path FROM code_files").all();
469
+ const chunkRows = db.prepare("SELECT file_path, symbols_json FROM code_chunks").all();
470
+ const filePaths = new Set(fileRows.map((row) => row.path));
471
+ const symbolsByFile = /* @__PURE__ */ new Map();
472
+ const allSymbols = /* @__PURE__ */ new Set();
473
+ for (const row of chunkRows) {
474
+ const symbols = parseJsonArray(row.symbols_json).map((symbol) => symbol.toLowerCase());
475
+ const fileSymbols = symbolsByFile.get(row.file_path) ?? /* @__PURE__ */ new Set();
476
+ for (const symbol of symbols) {
477
+ fileSymbols.add(symbol);
478
+ allSymbols.add(symbol);
479
+ }
480
+ symbolsByFile.set(row.file_path, fileSymbols);
481
+ }
482
+ return {
483
+ hasCodeIndex: fileRows.length > 0 || chunkRows.length > 0,
484
+ filePaths,
485
+ symbolsByFile,
486
+ allSymbols
487
+ };
488
+ }
489
+ function evaluateFreshness(subject, snapshot) {
490
+ if (!snapshot.hasCodeIndex) {
491
+ return {
492
+ status: "possibly_stale",
493
+ reason: "No current code index is available to verify this evidence."
494
+ };
495
+ }
496
+ const filePaths = subject.filePaths.filter(Boolean);
497
+ const symbols = subject.symbols.map((symbol) => symbol.toLowerCase()).filter(Boolean);
498
+ if (filePaths.length > 0) {
499
+ const existingFiles = filePaths.filter((filePath) => snapshot.filePaths.has(filePath));
500
+ if (existingFiles.length === 0) {
501
+ return {
502
+ status: "stale",
503
+ reason: "None of the historical file paths exist in the current code index."
504
+ };
505
+ }
506
+ if (symbols.length === 0) {
507
+ return {
508
+ status: "current",
509
+ reason: "At least one historical file path exists in the current code index."
510
+ };
511
+ }
512
+ for (const filePath of existingFiles) {
513
+ const fileSymbols = snapshot.symbolsByFile.get(filePath) ?? /* @__PURE__ */ new Set();
514
+ if (symbols.some((symbol) => fileSymbols.has(symbol))) {
515
+ return {
516
+ status: "current",
517
+ reason: "Historical file and symbol are present in the current code index."
518
+ };
519
+ }
520
+ }
521
+ if (symbols.some((symbol) => snapshot.allSymbols.has(symbol))) {
522
+ return {
523
+ status: "possibly_stale",
524
+ reason: "The historical file exists, but the referenced symbol appears elsewhere or moved."
525
+ };
526
+ }
527
+ return {
528
+ status: "possibly_stale",
529
+ reason: "The historical file exists, but referenced symbols were not found there."
530
+ };
531
+ }
532
+ if (symbols.length > 0 && symbols.some((symbol) => snapshot.allSymbols.has(symbol))) {
533
+ return {
534
+ status: "current",
535
+ reason: "Referenced symbol exists in the current code index."
536
+ };
537
+ }
538
+ return {
539
+ status: "possibly_stale",
540
+ reason: "Evidence has no exact current file path to verify."
541
+ };
542
+ }
543
+
544
+ // src/rules/team-rules.ts
545
+ var TEAM_RULES_FILE = "anchor.rules.json";
546
+ var SourceTypeSchema = z.enum([
547
+ "pr_body",
548
+ "review_comment",
549
+ "issue_comment",
550
+ "review_summary",
551
+ "commit_message",
552
+ "diff_context"
553
+ ]);
554
+ var WisdomCategorySchema = z.enum([
555
+ "architecture_decision",
556
+ "constraint",
557
+ "rejected_approach",
558
+ "bug_regression",
559
+ "testing_rule",
560
+ "api_contract",
561
+ "performance_note",
562
+ "security_note",
563
+ "style_convention",
564
+ "unknown"
565
+ ]);
566
+ var ConfidenceLevelSchema = z.enum(["strong", "moderate", "weak"]);
567
+ var EvidenceRefSchema = z.object({
568
+ prNumber: z.number().int().positive(),
569
+ prUrl: z.string().url(),
570
+ sourceType: SourceTypeSchema,
571
+ author: z.string().min(1).optional(),
572
+ filePath: z.string().min(1).optional(),
573
+ note: z.string().min(1).max(500).optional()
574
+ });
575
+ var TeamRuleSchema = z.object({
576
+ id: z.string().min(1).max(120).regex(/^[a-z0-9][a-z0-9._-]*$/i),
577
+ category: WisdomCategorySchema,
578
+ text: z.string().min(1).max(1e3),
579
+ filePaths: z.array(z.string().min(1)).max(50).default([]),
580
+ symbols: z.array(z.string().min(1)).max(100).default([]),
581
+ evidence: z.array(EvidenceRefSchema).min(1),
582
+ confidenceLevel: ConfidenceLevelSchema.default("strong")
583
+ });
584
+ var TeamRulesFileSchema = z.object({
585
+ version: z.literal(1),
586
+ rules: z.array(TeamRuleSchema).default([])
587
+ });
588
+ function rulesPath(cwd) {
589
+ return path2.join(detectGitRoot(cwd) ?? cwd, TEAM_RULES_FILE);
590
+ }
591
+ function defaultRulesFile() {
592
+ return `${JSON.stringify({ version: 1, rules: [] }, null, 2)}
593
+ `;
594
+ }
595
+ function ensureTeamRulesFile(cwd) {
596
+ const filePath = rulesPath(cwd);
597
+ if (fs2.existsSync(filePath)) return { path: filePath, created: false };
598
+ fs2.writeFileSync(filePath, defaultRulesFile());
599
+ return { path: filePath, created: true };
600
+ }
601
+ function sanitizeEvidence(evidence) {
602
+ return evidence.map((item) => ({
603
+ ...item,
604
+ note: item.note ? sanitizeHistoricalText(item.note) : void 0
605
+ }));
606
+ }
607
+ function loadTeamRulesFile(cwd) {
608
+ const filePath = rulesPath(cwd);
609
+ if (!fs2.existsSync(filePath)) {
610
+ return { ok: true, exists: false, path: filePath, errors: [], rules: [] };
611
+ }
612
+ let parsedJson;
613
+ try {
614
+ parsedJson = JSON.parse(fs2.readFileSync(filePath, "utf8"));
615
+ } catch (error) {
616
+ return {
617
+ ok: false,
618
+ exists: true,
619
+ path: filePath,
620
+ errors: [`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`],
621
+ rules: []
622
+ };
623
+ }
624
+ const parsed = TeamRulesFileSchema.safeParse(parsedJson);
625
+ if (!parsed.success) {
626
+ return {
627
+ ok: false,
628
+ exists: true,
629
+ path: filePath,
630
+ errors: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`),
631
+ rules: []
632
+ };
633
+ }
634
+ const seenIds = /* @__PURE__ */ new Set();
635
+ const duplicateIds = parsed.data.rules.map((rule) => rule.id).filter((id) => {
636
+ if (seenIds.has(id)) return true;
637
+ seenIds.add(id);
638
+ return false;
639
+ });
640
+ if (duplicateIds.length > 0) {
641
+ return {
642
+ ok: false,
643
+ exists: true,
644
+ path: filePath,
645
+ errors: [`Duplicate rule ids: ${uniqueStrings(duplicateIds).join(", ")}`],
646
+ rules: []
647
+ };
648
+ }
649
+ const rules = parsed.data.rules.map((rule) => {
650
+ const sanitizedText = sanitizeHistoricalText(rule.text);
651
+ return {
652
+ id: rule.id,
653
+ category: rule.category,
654
+ text: sanitizedText,
655
+ sanitizedText,
656
+ filePaths: uniqueStrings(rule.filePaths),
657
+ symbols: uniqueStrings(rule.symbols),
658
+ evidence: sanitizeEvidence(rule.evidence),
659
+ confidenceLevel: rule.confidenceLevel
660
+ };
661
+ });
662
+ return { ok: true, exists: true, path: filePath, errors: [], rules };
663
+ }
664
+ function validateTeamRulesFile(cwd) {
665
+ const loaded = loadTeamRulesFile(cwd);
666
+ if (!loaded.exists) {
667
+ return {
668
+ ok: false,
669
+ path: loaded.path,
670
+ errors: [`${TEAM_RULES_FILE} does not exist. Run anchor rules init.`],
671
+ rules: []
672
+ };
673
+ }
674
+ return {
675
+ ok: loaded.ok,
676
+ path: loaded.path,
677
+ errors: loaded.errors,
678
+ rules: loaded.rules
679
+ };
680
+ }
681
+ function pathMatch(rulePaths, queryFiles) {
682
+ if (rulePaths.length === 0 || queryFiles.length === 0) return 0;
683
+ let best = 0;
684
+ for (const rulePath of rulePaths) {
685
+ const ruleBase = path2.basename(rulePath).toLowerCase();
686
+ const ruleDir = path2.dirname(rulePath).toLowerCase();
687
+ for (const queryFile of queryFiles) {
688
+ const queryBase = path2.basename(queryFile).toLowerCase();
689
+ const queryDir = path2.dirname(queryFile).toLowerCase();
690
+ if (rulePath.toLowerCase() === queryFile.toLowerCase()) best = Math.max(best, 1);
691
+ else if (ruleBase === queryBase) best = Math.max(best, 0.72);
692
+ else if (ruleDir === queryDir) best = Math.max(best, 0.6);
693
+ else if (ruleDir.startsWith(queryDir) || queryDir.startsWith(ruleDir)) {
694
+ best = Math.max(best, 0.35);
695
+ }
696
+ }
697
+ }
698
+ return best;
699
+ }
700
+ function symbolMatch(rule, querySymbols) {
701
+ if (rule.symbols.length === 0 || querySymbols.length === 0) return 0;
702
+ const ruleSymbols = rule.symbols.map((symbol) => symbol.toLowerCase());
703
+ let best = 0;
704
+ for (const symbol of querySymbols) {
705
+ const lower = symbol.toLowerCase();
706
+ if (ruleSymbols.includes(lower)) best = Math.max(best, 1);
707
+ else if (ruleSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
708
+ best = Math.max(best, 0.45);
709
+ }
710
+ }
711
+ return best;
712
+ }
713
+ function textMatch(rule, input) {
714
+ const tokens = tokenizeSearchText(
715
+ `${input.task} ${input.diff ?? ""} ${input.currentCode ?? ""}`,
716
+ 32
717
+ );
718
+ if (tokens.length === 0) return 0;
719
+ const haystack = `${rule.sanitizedText} ${rule.filePaths.join(" ")} ${rule.symbols.join(" ")}`.toLowerCase();
720
+ return tokens.filter((token) => haystack.includes(token.toLowerCase())).length / tokens.length;
721
+ }
722
+ function confidenceScore(level) {
723
+ if (level === "strong") return 1;
724
+ if (level === "moderate") return 0.7;
725
+ return 0.4;
726
+ }
727
+ function confidenceReasons(rule) {
728
+ const firstEvidence = rule.evidence[0];
729
+ return [
730
+ "team-approved rule",
731
+ firstEvidence ? `${sourceTypeLabel(firstEvidence.sourceType)} evidence` : "source evidence",
732
+ ...rule.filePaths.length > 0 ? ["file-associated"] : [],
733
+ ...rule.symbols.length > 0 ? ["symbol-associated"] : []
734
+ ];
735
+ }
736
+ function passesStrictMode(rule, input) {
737
+ if (!input.strict) return true;
738
+ if (rule.freshnessStatus === "stale") return false;
739
+ return confidenceAtLeast(rule.confidenceLevel, input.minConfidence ?? "strong");
740
+ }
741
+ function rankTeamRules(db, cwd, input) {
742
+ const loaded = loadTeamRulesFile(cwd);
743
+ if (!loaded.ok || loaded.rules.length === 0) return [];
744
+ const codeSnapshot = loadCurrentCodeSnapshot(db);
745
+ return loaded.rules.map((rule) => {
746
+ const freshness = evaluateFreshness(rule, codeSnapshot);
747
+ const score = 1 + 0.35 * pathMatch(rule.filePaths, input.files ?? []) + 0.25 * symbolMatch(rule, input.symbols ?? []) + 0.25 * textMatch(rule, input) + 0.15 * confidenceScore(rule.confidenceLevel);
748
+ return {
749
+ ...rule,
750
+ score: Number(score.toFixed(4)),
751
+ freshnessStatus: freshness.status,
752
+ freshnessReason: freshness.reason,
753
+ confidenceReasons: confidenceReasons(rule)
754
+ };
755
+ }).filter((rule) => passesStrictMode(rule, input)).sort((a, b) => b.score - a.score).slice(0, 4);
756
+ }
757
+ function countValidTeamRules(cwd) {
758
+ const loaded = loadTeamRulesFile(cwd);
759
+ if (!loaded.exists || !loaded.ok) return { count: 0 };
760
+ const stat = fs2.statSync(loaded.path);
761
+ return { count: loaded.rules.length, lastRuleIndexTime: stat.mtime.toISOString() };
762
+ }
763
+
395
764
  // src/db/database.ts
396
765
  function defaultDatabasePath(cwd) {
397
- return path2.join(cwd, ".anchor", "index.sqlite");
766
+ return path3.join(cwd, ".anchor", "index.sqlite");
398
767
  }
399
768
  function openAnchorDatabase(cwd, databasePath = defaultDatabasePath(cwd)) {
400
- fs2.mkdirSync(path2.dirname(databasePath), { recursive: true });
769
+ fs3.mkdirSync(path3.dirname(databasePath), { recursive: true });
401
770
  const db = new Database(databasePath);
402
771
  db.pragma("journal_mode = WAL");
403
772
  db.pragma("foreign_keys = ON");
@@ -405,6 +774,15 @@ function openAnchorDatabase(cwd, databasePath = defaultDatabasePath(cwd)) {
405
774
  }
406
775
  function initializeSchema(db) {
407
776
  db.exec(SCHEMA_SQL);
777
+ ensureColumn(db, "sync_state", "history_coverage", "TEXT");
778
+ ensureColumn(db, "sync_state", "history_limit", "INTEGER");
779
+ ensureColumn(db, "sync_state", "history_since", "TEXT");
780
+ }
781
+ function ensureColumn(db, tableName, columnName, definition) {
782
+ const columns = db.prepare(`PRAGMA table_info(${tableName})`).all();
783
+ if (!columns.some((column) => column.name === columnName)) {
784
+ db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${definition}`);
785
+ }
408
786
  }
409
787
  function checkSchema(db) {
410
788
  try {
@@ -432,16 +810,28 @@ function getLastSyncTime(db, repo) {
432
810
  const row = db.prepare("SELECT last_sync_at FROM sync_state WHERE repo = ?").get(repo);
433
811
  return row?.last_sync_at ?? void 0;
434
812
  }
435
- function updateSyncState(db, repo, lastIndexedPr) {
813
+ function updateSyncState(db, repo, lastIndexedPr, metadata = {}) {
436
814
  const now = (/* @__PURE__ */ new Date()).toISOString();
437
815
  db.prepare(
438
- `INSERT INTO sync_state (repo, last_sync_at, last_indexed_pr, updated_at)
439
- VALUES (?, ?, ?, ?)
816
+ `INSERT INTO sync_state
817
+ (repo, last_sync_at, last_indexed_pr, history_coverage, history_limit, history_since, updated_at)
818
+ VALUES (?, ?, ?, ?, ?, ?, ?)
440
819
  ON CONFLICT(repo) DO UPDATE SET
441
820
  last_sync_at = excluded.last_sync_at,
442
821
  last_indexed_pr = excluded.last_indexed_pr,
822
+ history_coverage = excluded.history_coverage,
823
+ history_limit = excluded.history_limit,
824
+ history_since = excluded.history_since,
443
825
  updated_at = excluded.updated_at`
444
- ).run(repo, now, lastIndexedPr ?? null, now);
826
+ ).run(
827
+ repo,
828
+ now,
829
+ lastIndexedPr ?? null,
830
+ metadata.historyCoverage ?? "unknown",
831
+ metadata.historyLimit ?? null,
832
+ metadata.historySince ?? null,
833
+ now
834
+ );
445
835
  }
446
836
  function deleteExistingPrData(db, prId) {
447
837
  const unitRows = db.prepare("SELECT id FROM wisdom_units WHERE pr_id = ?").all(prId);
@@ -671,7 +1061,8 @@ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd) {
671
1061
  };
672
1062
  }
673
1063
  function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken({ cwd }).token), databasePath = defaultDatabasePath(cwd)) {
674
- if (!fs2.existsSync(databasePath)) {
1064
+ if (!fs3.existsSync(databasePath)) {
1065
+ const rules = countValidTeamRules(cwd);
675
1066
  return {
676
1067
  databasePath,
677
1068
  prCount: 0,
@@ -680,6 +1071,10 @@ function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken(
680
1071
  wisdomUnitCount: 0,
681
1072
  codeFileCount: 0,
682
1073
  codeChunkCount: 0,
1074
+ historyCoverage: "unknown",
1075
+ staleEvidenceCount: 0,
1076
+ teamRuleCount: rules.count,
1077
+ lastRuleIndexTime: rules.lastRuleIndexTime,
683
1078
  githubTokenConfigured,
684
1079
  health: "missing_database"
685
1080
  };
@@ -688,6 +1083,7 @@ function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken(
688
1083
  try {
689
1084
  initializeSchema(db);
690
1085
  if (!checkSchema(db)) {
1086
+ const rules2 = countValidTeamRules(cwd);
691
1087
  return {
692
1088
  databasePath,
693
1089
  prCount: 0,
@@ -696,16 +1092,23 @@ function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken(
696
1092
  wisdomUnitCount: 0,
697
1093
  codeFileCount: 0,
698
1094
  codeChunkCount: 0,
1095
+ historyCoverage: "unknown",
1096
+ staleEvidenceCount: 0,
1097
+ teamRuleCount: rules2.count,
1098
+ lastRuleIndexTime: rules2.lastRuleIndexTime,
699
1099
  githubTokenConfigured,
700
1100
  health: "schema_invalid"
701
1101
  };
702
1102
  }
703
1103
  const count = (table) => db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count;
704
1104
  const repoRow = db.prepare("SELECT full_name FROM repositories ORDER BY id LIMIT 1").get();
705
- const syncRow = db.prepare("SELECT last_sync_at FROM sync_state ORDER BY updated_at DESC LIMIT 1").get();
1105
+ const syncRow = db.prepare(
1106
+ "SELECT last_sync_at, history_coverage, history_limit FROM sync_state ORDER BY updated_at DESC LIMIT 1"
1107
+ ).get();
706
1108
  const codeIndexRow = db.prepare("SELECT last_indexed_at FROM code_index_state ORDER BY last_indexed_at DESC LIMIT 1").get();
707
1109
  const wisdomUnitCount = count("wisdom_units");
708
1110
  const codeChunkCount = count("code_chunks");
1111
+ const rules = countValidTeamRules(cwd);
709
1112
  return {
710
1113
  repo: repoRow?.full_name,
711
1114
  databasePath,
@@ -715,8 +1118,13 @@ function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken(
715
1118
  wisdomUnitCount,
716
1119
  codeFileCount: count("code_files"),
717
1120
  codeChunkCount,
1121
+ historyCoverage: syncRow?.history_coverage ?? "unknown",
1122
+ historyLimit: syncRow?.history_limit ?? void 0,
1123
+ staleEvidenceCount: countStaleEvidence(db),
1124
+ teamRuleCount: rules.count,
718
1125
  lastSyncTime: syncRow?.last_sync_at ?? void 0,
719
1126
  lastCodeIndexTime: codeIndexRow?.last_indexed_at ?? void 0,
1127
+ lastRuleIndexTime: rules.lastRuleIndexTime,
720
1128
  githubTokenConfigured,
721
1129
  health: wisdomUnitCount > 0 || codeChunkCount > 0 ? "ok" : "empty_index"
722
1130
  };
@@ -724,6 +1132,27 @@ function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken(
724
1132
  db.close();
725
1133
  }
726
1134
  }
1135
+ function countStaleEvidence(db) {
1136
+ const codeFiles = new Set(
1137
+ db.prepare("SELECT path FROM code_files").all().map(
1138
+ (row) => row.path
1139
+ )
1140
+ );
1141
+ if (codeFiles.size === 0) return 0;
1142
+ const rows = db.prepare("SELECT file_paths_json FROM wisdom_units").all();
1143
+ let stale = 0;
1144
+ for (const row of rows) {
1145
+ let paths = [];
1146
+ try {
1147
+ const parsed = JSON.parse(row.file_paths_json);
1148
+ paths = Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
1149
+ } catch {
1150
+ paths = [];
1151
+ }
1152
+ if (paths.length > 0 && !paths.some((filePath) => codeFiles.has(filePath))) stale += 1;
1153
+ }
1154
+ return stale;
1155
+ }
727
1156
 
728
1157
  // src/indexer/chunker.ts
729
1158
  var HIGH_SIGNAL_PATTERN = /\b(because|we intentionally|do not|don't|must|should not|avoid|rejected|regression|breaking|contract|invariant|performance|security|secret|token|migration|compatibility|lazy|eager|thread-safe|race|deadlock|deprecated|backward compatible|do not change|this broke|root cause|architecture decision)\b/i;
@@ -757,7 +1186,7 @@ function chunkHistoricalText(text, maxChunkLength = 700) {
757
1186
 
758
1187
  // src/indexer/code-chunker.ts
759
1188
  import crypto from "crypto";
760
- import path3 from "path";
1189
+ import path4 from "path";
761
1190
  var DEFAULT_CHUNK_LINES = 80;
762
1191
  var DEFAULT_OVERLAP_LINES = 8;
763
1192
  var FUNCTION_CALL_STOP_WORDS = /* @__PURE__ */ new Set([
@@ -790,7 +1219,7 @@ function extractCodeSymbols(text, filePath) {
790
1219
  const candidate = match[1] ?? "";
791
1220
  if (!FUNCTION_CALL_STOP_WORDS.has(candidate)) symbols.push(candidate);
792
1221
  }
793
- const basename = path3.basename(filePath).replace(/\.[^.]+$/, "");
1222
+ const basename = path4.basename(filePath).replace(/\.[^.]+$/, "");
794
1223
  if (/^[A-Za-z_$][\w$-]*$/.test(basename)) symbols.push(basename);
795
1224
  return uniqueStrings(symbols).slice(0, 40);
796
1225
  }
@@ -829,8 +1258,8 @@ function chunkCodeFile(file, options = {}) {
829
1258
  // src/indexer/code-file-discovery.ts
830
1259
  import { execFileSync as execFileSync3 } from "child_process";
831
1260
  import crypto2 from "crypto";
832
- import fs3 from "fs";
833
- import path4 from "path";
1261
+ import fs4 from "fs";
1262
+ import path5 from "path";
834
1263
  var DEFAULT_MAX_CODE_FILE_BYTES = 512 * 1024;
835
1264
  var HARD_EXCLUDED_SEGMENTS = /* @__PURE__ */ new Set([
836
1265
  ".git",
@@ -878,7 +1307,7 @@ function isHardExcludedCodePath(filePath) {
878
1307
  const normalized = normalizeGitPath(filePath);
879
1308
  const segments = normalized.split("/");
880
1309
  if (segments.some((segment) => HARD_EXCLUDED_SEGMENTS.has(segment))) return true;
881
- const basename = path4.posix.basename(normalized).toLowerCase();
1310
+ const basename = path5.posix.basename(normalized).toLowerCase();
882
1311
  if ([".netrc", ".npmrc", ".pypirc", ".yarnrc"].includes(basename)) return true;
883
1312
  if (basename === ".env" || basename.startsWith(".env.")) return true;
884
1313
  if (basename === "id_rsa" || basename === "id_rsa.pub" || basename === "id_dsa" || basename === "id_ecdsa" || basename === "id_ed25519") {
@@ -888,7 +1317,7 @@ function isHardExcludedCodePath(filePath) {
888
1317
  return false;
889
1318
  }
890
1319
  function languageForPath(filePath) {
891
- const extension = path4.extname(filePath).toLowerCase();
1320
+ const extension = path5.extname(filePath).toLowerCase();
892
1321
  return LANGUAGE_BY_EXTENSION[extension];
893
1322
  }
894
1323
  function isProbablyBinary(buffer) {
@@ -911,7 +1340,7 @@ function discoverGitFiles(cwd) {
911
1340
  }
912
1341
  function discoverCodeFiles(cwd, repo, options = {}) {
913
1342
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_CODE_FILE_BYTES;
914
- const rootPath = path4.resolve(cwd);
1343
+ const rootPath = path5.resolve(cwd);
915
1344
  const files = [];
916
1345
  let skippedFiles = 0;
917
1346
  for (const filePath of discoverGitFiles(cwd)) {
@@ -919,15 +1348,15 @@ function discoverCodeFiles(cwd, repo, options = {}) {
919
1348
  skippedFiles += 1;
920
1349
  continue;
921
1350
  }
922
- const absolutePath = path4.resolve(cwd, filePath);
923
- const relativeToRoot = path4.relative(rootPath, absolutePath);
924
- if (relativeToRoot.startsWith("..") || path4.isAbsolute(relativeToRoot)) {
1351
+ const absolutePath = path5.resolve(cwd, filePath);
1352
+ const relativeToRoot = path5.relative(rootPath, absolutePath);
1353
+ if (relativeToRoot.startsWith("..") || path5.isAbsolute(relativeToRoot)) {
925
1354
  skippedFiles += 1;
926
1355
  continue;
927
1356
  }
928
1357
  let stat;
929
1358
  try {
930
- stat = fs3.statSync(absolutePath);
1359
+ stat = fs4.statSync(absolutePath);
931
1360
  } catch {
932
1361
  skippedFiles += 1;
933
1362
  continue;
@@ -936,7 +1365,7 @@ function discoverCodeFiles(cwd, repo, options = {}) {
936
1365
  skippedFiles += 1;
937
1366
  continue;
938
1367
  }
939
- const buffer = fs3.readFileSync(absolutePath);
1368
+ const buffer = fs4.readFileSync(absolutePath);
940
1369
  if (isProbablyBinary(buffer)) {
941
1370
  skippedFiles += 1;
942
1371
  continue;
@@ -1008,7 +1437,7 @@ function emptyCodeIndexSummary(cwd) {
1008
1437
 
1009
1438
  // src/indexer/wisdom-extractor.ts
1010
1439
  import crypto3 from "crypto";
1011
- import path5 from "path";
1440
+ import path6 from "path";
1012
1441
  var CATEGORY_KEYWORDS = [
1013
1442
  ["security_note", /\b(security|secret|token|bearer|oauth|credential|xss|csrf|injection|sanitize|redact)\b/i],
1014
1443
  ["architecture_decision", /\b(architecture decision|architectural|we intentionally|design decision)\b/i],
@@ -1040,7 +1469,7 @@ function extractSymbols(text, filePaths) {
1040
1469
  }
1041
1470
  }
1042
1471
  for (const filePath of filePaths) {
1043
- const basename = path5.basename(filePath).replace(/\.[^.]+$/, "");
1472
+ const basename = path6.basename(filePath).replace(/\.[^.]+$/, "");
1044
1473
  if (/^[A-Za-z_$][\w$]*$/.test(basename)) symbols.push(basename);
1045
1474
  }
1046
1475
  return uniqueStrings(symbols).slice(0, 30);
@@ -1242,7 +1671,11 @@ function indexPullRequests(db, pullRequests, options) {
1242
1671
  });
1243
1672
  }
1244
1673
  if (options.updateSyncStateAfter !== false) {
1245
- updateSyncState(db, options.repo, lastPr);
1674
+ updateSyncState(db, options.repo, lastPr, {
1675
+ historyCoverage: options.historyCoverage,
1676
+ historyLimit: options.historyLimit,
1677
+ historySince: options.historySince
1678
+ });
1246
1679
  }
1247
1680
  return {
1248
1681
  indexedPrs: pullRequests.length - skippedItems,
@@ -1260,7 +1693,7 @@ function shouldSyncSince(db, repo, fallbackSince) {
1260
1693
  }
1261
1694
 
1262
1695
  // src/retrieval/query-builder.ts
1263
- import path6 from "path";
1696
+ import path7 from "path";
1264
1697
  var CATEGORY_HINTS = [
1265
1698
  "security",
1266
1699
  "regression",
@@ -1285,8 +1718,8 @@ function buildFtsQuery(input) {
1285
1718
  const baseText = "task" in input ? input.task : input.query;
1286
1719
  const fileTerms = files.flatMap((file) => [
1287
1720
  file,
1288
- path6.basename(file),
1289
- ...path6.dirname(file).split(/[\\/]/).filter(Boolean)
1721
+ path7.basename(file),
1722
+ ...path7.dirname(file).split(/[\\/]/).filter(Boolean)
1290
1723
  ]);
1291
1724
  const tokens = uniqueStrings([
1292
1725
  ...tokenizeSearchText(baseText, 24),
@@ -1305,8 +1738,8 @@ function clampMaxResults(value, defaultValue) {
1305
1738
  }
1306
1739
 
1307
1740
  // src/retrieval/ranker.ts
1308
- import path7 from "path";
1309
- function parseJsonArray(value) {
1741
+ import path8 from "path";
1742
+ function parseJsonArray2(value) {
1310
1743
  try {
1311
1744
  const parsed = JSON.parse(value);
1312
1745
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
@@ -1324,9 +1757,9 @@ function rowToWisdomUnit(row) {
1324
1757
  category: row.category,
1325
1758
  text: row.text,
1326
1759
  sanitizedText: row.sanitized_text,
1327
- filePaths: parseJsonArray(row.file_paths_json),
1328
- symbols: parseJsonArray(row.symbols_json),
1329
- authors: parseJsonArray(row.authors_json),
1760
+ filePaths: parseJsonArray2(row.file_paths_json),
1761
+ symbols: parseJsonArray2(row.symbols_json),
1762
+ authors: parseJsonArray2(row.authors_json),
1330
1763
  createdAt: row.created_at,
1331
1764
  mergedAt: row.merged_at ?? void 0,
1332
1765
  confidence: row.confidence,
@@ -1352,17 +1785,18 @@ function filePathMatch(unitPaths, queryFiles) {
1352
1785
  if (queryFiles.length === 0 || unitPaths.length === 0) return 0;
1353
1786
  let best = 0;
1354
1787
  for (const queryFile of queryFiles) {
1355
- const queryBase = path7.basename(queryFile).toLowerCase();
1356
- const queryDir = path7.dirname(queryFile).toLowerCase();
1788
+ const queryBase = path8.basename(queryFile).toLowerCase();
1789
+ const queryDir = path8.dirname(queryFile).toLowerCase();
1357
1790
  for (const unitPath of unitPaths) {
1358
- const unitBase = path7.basename(unitPath).toLowerCase();
1359
- const unitDir = path7.dirname(unitPath).toLowerCase();
1791
+ const unitBase = path8.basename(unitPath).toLowerCase();
1792
+ const unitDir = path8.dirname(unitPath).toLowerCase();
1360
1793
  const q = queryFile.toLowerCase();
1361
1794
  const u = unitPath.toLowerCase();
1362
1795
  if (q === u) best = Math.max(best, 1);
1363
1796
  else if (queryBase === unitBase) best = Math.max(best, 0.68);
1364
1797
  else if (queryDir === unitDir) best = Math.max(best, 0.62);
1365
- else if (unitDir.startsWith(queryDir) || queryDir.startsWith(unitDir)) best = Math.max(best, 0.38);
1798
+ else if (unitDir.startsWith(queryDir) || queryDir.startsWith(unitDir))
1799
+ best = Math.max(best, 0.38);
1366
1800
  else if (queryBase && unitBase && queryBase.split(".")[0] === unitBase.split(".")[0]) {
1367
1801
  best = Math.max(best, 0.48);
1368
1802
  }
@@ -1370,7 +1804,7 @@ function filePathMatch(unitPaths, queryFiles) {
1370
1804
  }
1371
1805
  return best;
1372
1806
  }
1373
- function symbolMatch(unit, querySymbols) {
1807
+ function symbolMatch2(unit, querySymbols) {
1374
1808
  if (querySymbols.length === 0) return 0;
1375
1809
  const unitSymbols = unit.symbols.map((symbol) => symbol.toLowerCase());
1376
1810
  const text = unit.sanitizedText.toLowerCase();
@@ -1379,14 +1813,15 @@ function symbolMatch(unit, querySymbols) {
1379
1813
  const lower = symbol.toLowerCase();
1380
1814
  if (unitSymbols.includes(lower)) best = Math.max(best, 1);
1381
1815
  else if (text.includes(`\`${lower}\``)) best = Math.max(best, 1);
1382
- else if (new RegExp(`\\b${escapeRegExp(lower)}\\b`, "i").test(text)) best = Math.max(best, 0.66);
1816
+ else if (new RegExp(`\\b${escapeRegExp(lower)}\\b`, "i").test(text))
1817
+ best = Math.max(best, 0.66);
1383
1818
  else if (unitSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
1384
1819
  best = Math.max(best, 0.35);
1385
1820
  }
1386
1821
  }
1387
1822
  return best;
1388
1823
  }
1389
- function textMatch(unit, inputText) {
1824
+ function textMatch2(unit, inputText) {
1390
1825
  const queryTokens = tokenizeSearchText(inputText, 32);
1391
1826
  if (queryTokens.length === 0) return unit.bm25 === void 0 ? 0 : 0.45;
1392
1827
  const haystack = `${unit.sanitizedText} ${unit.filePaths.join(" ")} ${unit.symbols.join(" ")}`.toLowerCase();
@@ -1410,25 +1845,38 @@ function recencyScore(unit) {
1410
1845
  if (ageDays < 1460) return 0.45;
1411
1846
  return 0.25;
1412
1847
  }
1413
- function scoreUnit(unit, input, duplicateCount) {
1848
+ function freshnessMultiplier(status) {
1849
+ if (status === "current") return 1;
1850
+ if (status === "possibly_stale") return 0.85;
1851
+ return 0.55;
1852
+ }
1853
+ function scoreUnit(unit, input, duplicateCount, repeatedEvidenceCount, freshness) {
1414
1854
  const queryFiles = input.files ?? [];
1415
1855
  const querySymbols = "symbols" in input ? input.symbols ?? [] : [];
1416
1856
  const inputText = "task" in input ? `${input.task} ${input.diff ?? ""} ${input.currentCode ?? ""}` : input.query;
1417
- const repetition = Math.min(1, duplicateCount / 3);
1857
+ const repetition = Math.min(1, Math.max(duplicateCount, repeatedEvidenceCount) / 3);
1858
+ const claimKey = claimKeyFor(unit.category, unit.sanitizedText);
1418
1859
  const parts = {
1419
1860
  filePathMatch: filePathMatch(unit.filePaths, queryFiles),
1420
- symbolMatch: symbolMatch(unit, querySymbols),
1421
- textMatch: textMatch(unit, inputText),
1861
+ symbolMatch: symbolMatch2(unit, querySymbols),
1862
+ textMatch: textMatch2(unit, inputText),
1422
1863
  reviewerOrAuthorSignal: reviewerOrAuthorSignal(unit),
1423
1864
  recencyOrRepetition: Math.max(recencyScore(unit), repetition),
1424
1865
  categoryPriority: categoryPriority(unit.category)
1425
1866
  };
1426
- const score = 0.35 * parts.filePathMatch + 0.2 * parts.symbolMatch + 0.2 * parts.textMatch + 0.1 * parts.reviewerOrAuthorSignal + 0.1 * parts.recencyOrRepetition + 0.05 * parts.categoryPriority;
1867
+ const score = (0.35 * parts.filePathMatch + 0.2 * parts.symbolMatch + 0.2 * parts.textMatch + 0.1 * parts.reviewerOrAuthorSignal + 0.1 * parts.recencyOrRepetition + 0.05 * parts.categoryPriority) * freshnessMultiplier(freshness.status);
1427
1868
  return {
1428
1869
  ...unit,
1429
1870
  score: Number(score.toFixed(4)),
1430
1871
  scoreParts: parts,
1431
- duplicateCount
1872
+ duplicateCount,
1873
+ claimKey,
1874
+ repeatedEvidenceCount,
1875
+ confidenceLevel: confidenceLevelFor(unit.confidence),
1876
+ confidenceReasons: confidenceReasonsFor(unit, repeatedEvidenceCount),
1877
+ freshnessStatus: freshness.status,
1878
+ freshnessReason: freshness.reason,
1879
+ evidence: evidenceForWisdom(unit)
1432
1880
  };
1433
1881
  }
1434
1882
  function escapeRegExp(value) {
@@ -1458,20 +1906,48 @@ function loadCandidates(db, input) {
1458
1906
  ).all(...categories);
1459
1907
  return rows.map(rowToWisdomUnit);
1460
1908
  }
1909
+ function loadClaimRepetitionCounts(db) {
1910
+ const rows = db.prepare("SELECT category, sanitized_text, pr_number FROM wisdom_units").all();
1911
+ const grouped = /* @__PURE__ */ new Map();
1912
+ for (const row of rows) {
1913
+ const key = claimKeyFor(row.category, row.sanitized_text);
1914
+ const prs = grouped.get(key) ?? /* @__PURE__ */ new Set();
1915
+ prs.add(row.pr_number);
1916
+ grouped.set(key, prs);
1917
+ }
1918
+ return new Map([...grouped.entries()].map(([key, prs]) => [key, prs.size]));
1919
+ }
1920
+ function minConfidence(input) {
1921
+ if ("minConfidence" in input && input.minConfidence) return input.minConfidence;
1922
+ return "strong";
1923
+ }
1924
+ function passesStrictMode2(unit, input) {
1925
+ if (!("strict" in input) || !input.strict) return true;
1926
+ if (unit.freshnessStatus === "stale") return false;
1927
+ return confidenceAtLeast(unit.confidenceLevel, minConfidence(input));
1928
+ }
1461
1929
  function rankWisdomUnits(db, input) {
1462
1930
  const candidates = loadCandidates(db, input);
1931
+ const codeSnapshot = loadCurrentCodeSnapshot(db);
1932
+ const repetitionCounts = loadClaimRepetitionCounts(db);
1463
1933
  const duplicates = /* @__PURE__ */ new Map();
1464
1934
  for (const unit of candidates) {
1465
- const key = `${unit.category}:${canonicalizeText(unit.sanitizedText).slice(0, 180)}`;
1935
+ const key = claimKeyFor(unit.category, unit.sanitizedText);
1466
1936
  duplicates.set(key, (duplicates.get(key) ?? 0) + 1);
1467
1937
  }
1468
1938
  const ranked = candidates.map((unit) => {
1469
- const key = `${unit.category}:${canonicalizeText(unit.sanitizedText).slice(0, 180)}`;
1470
- return scoreUnit(unit, input, duplicates.get(key) ?? 1);
1471
- }).sort((a, b) => b.score - a.score || b.confidence - a.confidence);
1939
+ const key = claimKeyFor(unit.category, unit.sanitizedText);
1940
+ return scoreUnit(
1941
+ unit,
1942
+ input,
1943
+ duplicates.get(key) ?? 1,
1944
+ repetitionCounts.get(key) ?? 1,
1945
+ evaluateFreshness(unit, codeSnapshot)
1946
+ );
1947
+ }).filter((unit) => passesStrictMode2(unit, input)).sort((a, b) => b.score - a.score || b.confidence - a.confidence);
1472
1948
  const grouped = /* @__PURE__ */ new Map();
1473
1949
  for (const unit of ranked) {
1474
- const key = `${unit.category}:${canonicalizeText(unit.sanitizedText).slice(0, 180)}`;
1950
+ const key = unit.claimKey;
1475
1951
  const existing = grouped.get(key);
1476
1952
  if (!existing || unit.score > existing.score) {
1477
1953
  grouped.set(key, {
@@ -1479,7 +1955,11 @@ function rankWisdomUnits(db, input) {
1479
1955
  filePaths: uniqueStrings([...existing?.filePaths ?? [], ...unit.filePaths]),
1480
1956
  symbols: uniqueStrings([...existing?.symbols ?? [], ...unit.symbols]),
1481
1957
  authors: uniqueStrings([...existing?.authors ?? [], ...unit.authors]),
1482
- duplicateCount: Math.max(unit.duplicateCount, existing?.duplicateCount ?? 1)
1958
+ duplicateCount: Math.max(unit.duplicateCount, existing?.duplicateCount ?? 1),
1959
+ repeatedEvidenceCount: Math.max(
1960
+ unit.repeatedEvidenceCount,
1961
+ existing?.repeatedEvidenceCount ?? 1
1962
+ )
1483
1963
  });
1484
1964
  }
1485
1965
  }
@@ -1488,8 +1968,8 @@ function rankWisdomUnits(db, input) {
1488
1968
  }
1489
1969
 
1490
1970
  // src/retrieval/code-ranker.ts
1491
- import path8 from "path";
1492
- function parseJsonArray2(value) {
1971
+ import path9 from "path";
1972
+ function parseJsonArray3(value) {
1493
1973
  try {
1494
1974
  const parsed = JSON.parse(value);
1495
1975
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
@@ -1506,7 +1986,7 @@ function rowToCodeChunk(row) {
1506
1986
  startLine: row.start_line,
1507
1987
  endLine: row.end_line,
1508
1988
  sanitizedText: row.sanitized_text,
1509
- symbols: parseJsonArray2(row.symbols_json),
1989
+ symbols: parseJsonArray3(row.symbols_json),
1510
1990
  contentHash: row.content_hash,
1511
1991
  updatedAt: row.updated_at,
1512
1992
  bm25: row.bm25 ?? void 0
@@ -1515,13 +1995,13 @@ function rowToCodeChunk(row) {
1515
1995
  function filePathMatch2(filePath, queryFiles) {
1516
1996
  if (queryFiles.length === 0) return 0;
1517
1997
  let best = 0;
1518
- const unitBase = path8.basename(filePath).toLowerCase();
1519
- const unitDir = path8.dirname(filePath).toLowerCase();
1998
+ const unitBase = path9.basename(filePath).toLowerCase();
1999
+ const unitDir = path9.dirname(filePath).toLowerCase();
1520
2000
  const unit = filePath.toLowerCase();
1521
2001
  for (const queryFile of queryFiles) {
1522
2002
  const query = queryFile.toLowerCase();
1523
- const queryBase = path8.basename(queryFile).toLowerCase();
1524
- const queryDir = path8.dirname(queryFile).toLowerCase();
2003
+ const queryBase = path9.basename(queryFile).toLowerCase();
2004
+ const queryDir = path9.dirname(queryFile).toLowerCase();
1525
2005
  if (query === unit) best = Math.max(best, 1);
1526
2006
  else if (queryBase === unitBase) best = Math.max(best, 0.72);
1527
2007
  else if (queryDir === unitDir) best = Math.max(best, 0.62);
@@ -1533,7 +2013,7 @@ function filePathMatch2(filePath, queryFiles) {
1533
2013
  }
1534
2014
  return best;
1535
2015
  }
1536
- function symbolMatch2(chunk, querySymbols) {
2016
+ function symbolMatch3(chunk, querySymbols) {
1537
2017
  if (querySymbols.length === 0) return 0;
1538
2018
  const chunkSymbols = chunk.symbols.map((symbol) => symbol.toLowerCase());
1539
2019
  const text = chunk.sanitizedText.toLowerCase();
@@ -1548,7 +2028,7 @@ function symbolMatch2(chunk, querySymbols) {
1548
2028
  }
1549
2029
  return best;
1550
2030
  }
1551
- function textMatch2(chunk, input) {
2031
+ function textMatch3(chunk, input) {
1552
2032
  const tokens = tokenizeSearchText(
1553
2033
  `${input.task} ${input.diff ?? ""} ${input.currentCode ?? ""}`,
1554
2034
  40
@@ -1591,7 +2071,7 @@ function loadCodeCandidates(db, input) {
1591
2071
  }
1592
2072
  }
1593
2073
  for (const file of input.files ?? []) {
1594
- const basename = path8.basename(file);
2074
+ const basename = path9.basename(file);
1595
2075
  const rows = db.prepare(
1596
2076
  `SELECT cc.*, NULL AS bm25
1597
2077
  FROM code_chunks cc
@@ -1624,8 +2104,8 @@ function rankCodeChunks(db, input) {
1624
2104
  const ranked = loadCodeCandidates(db, input).map((chunk) => {
1625
2105
  const parts = {
1626
2106
  filePathMatch: filePathMatch2(chunk.filePath, queryFiles),
1627
- symbolMatch: symbolMatch2(chunk, querySymbols),
1628
- textMatch: textMatch2(chunk, input),
2107
+ symbolMatch: symbolMatch3(chunk, querySymbols),
2108
+ textMatch: textMatch3(chunk, input),
1629
2109
  recency: recencyScore2(chunk)
1630
2110
  };
1631
2111
  const score = 0.4 * parts.filePathMatch + 0.25 * parts.symbolMatch + 0.25 * parts.textMatch + 0.1 * parts.recency;
@@ -1646,8 +2126,15 @@ function evidenceLine(unit) {
1646
2126
  const file = unit.filePaths[0] ? `, ${unit.filePaths[0]}` : "";
1647
2127
  return `PR #${unit.prNumber}${author}, ${unit.sourceType}${file}`;
1648
2128
  }
2129
+ function confidenceLine(unit) {
2130
+ const reasons = unit.confidenceReasons.length ? ` (${unit.confidenceReasons.join(", ")})` : "";
2131
+ return `${unit.confidenceLevel}${reasons}`;
2132
+ }
2133
+ function currentCodeCheckLine(unit) {
2134
+ return `${unit.freshnessStatus.replace(/_/g, " ")} - ${unit.freshnessReason}`;
2135
+ }
1649
2136
  function whyItMatters(unit, input) {
1650
- const prefix = unit.confidence < 0.7 ? "Historical evidence suggests " : "";
2137
+ const prefix = unit.confidenceLevel === "weak" ? "Historical evidence suggests " : "";
1651
2138
  const target = input.files?.[0] ? ` when editing ${input.files[0]}` : " for this change";
1652
2139
  const categoryReasons = {
1653
2140
  security_note: `${prefix}there is a security-sensitive constraint to preserve${target}.`,
@@ -1679,15 +2166,39 @@ function riskLines(units) {
1679
2166
  }
1680
2167
  return [...risks].slice(0, 4);
1681
2168
  }
1682
- function formatAnchorContext(units, input, codeChunks = []) {
1683
- const lines = ["# Anchor Context", "", "## Must know", ""];
2169
+ function formatAnchorContext(units, input, codeChunks = [], teamRules = [], warnings = []) {
2170
+ const lines = ["# Anchor Context", ""];
2171
+ if (warnings.length > 0) {
2172
+ lines.push("## Warnings", "");
2173
+ for (const warning of warnings) lines.push(`- ${warning}`);
2174
+ lines.push("");
2175
+ }
2176
+ if (teamRules.length > 0) {
2177
+ lines.push("## Team-approved rules", "");
2178
+ teamRules.forEach((rule, index) => {
2179
+ const evidence = rule.evidence[0];
2180
+ const evidenceText = evidence ? `PR #${evidence.prNumber}, ${evidence.sourceType}${evidence.filePath ? `, ${evidence.filePath}` : ""}` : "No evidence";
2181
+ lines.push(`${index + 1}. [${rule.category}] ${clipSentence(rule.sanitizedText)}`);
2182
+ lines.push(` Evidence: ${evidenceText}`);
2183
+ lines.push(` Confidence: ${confidenceLine(rule)}`);
2184
+ lines.push(` Current code check: ${currentCodeCheckLine(rule)}`);
2185
+ if (evidence?.prUrl) lines.push(` Link: ${evidence.prUrl}`);
2186
+ lines.push("");
2187
+ });
2188
+ }
2189
+ lines.push("## Must know", "");
1684
2190
  if (units.length === 0) {
1685
- lines.push("No directly relevant indexed PR history found.", "");
2191
+ lines.push(
2192
+ input.strict ? "No reliable historical evidence found." : "No directly relevant indexed PR history found.",
2193
+ ""
2194
+ );
1686
2195
  } else {
1687
2196
  units.forEach((unit, index) => {
1688
- const statement = unit.confidence < 0.7 ? `Historical evidence suggests ${clipSentence(unit.sanitizedText)}` : clipSentence(unit.sanitizedText);
2197
+ const statement = unit.confidenceLevel === "weak" ? `Historical evidence suggests ${clipSentence(unit.sanitizedText)}` : clipSentence(unit.sanitizedText);
1689
2198
  lines.push(`${index + 1}. [${unit.category}] ${statement}`);
1690
2199
  lines.push(` Evidence: ${evidenceLine(unit)}`);
2200
+ lines.push(` Confidence: ${confidenceLine(unit)}`);
2201
+ lines.push(` Current code check: ${currentCodeCheckLine(unit)}`);
1691
2202
  lines.push(` Why it matters: ${whyItMatters(unit, input)}`);
1692
2203
  lines.push(` Link: ${unit.prUrl}`);
1693
2204
  lines.push("");
@@ -1724,6 +2235,13 @@ function formatAnchorContext(units, input, codeChunks = []) {
1724
2235
  id: unit.id,
1725
2236
  score: unit.score,
1726
2237
  confidence: unit.confidence,
2238
+ confidenceLevel: unit.confidenceLevel,
2239
+ confidenceReasons: unit.confidenceReasons,
2240
+ freshnessStatus: unit.freshnessStatus,
2241
+ freshnessReason: unit.freshnessReason,
2242
+ evidence: unit.evidence,
2243
+ claimKey: unit.claimKey,
2244
+ repeatedEvidenceCount: unit.repeatedEvidenceCount,
1727
2245
  category: unit.category,
1728
2246
  prNumber: unit.prNumber,
1729
2247
  prUrl: unit.prUrl,
@@ -1732,6 +2250,18 @@ function formatAnchorContext(units, input, codeChunks = []) {
1732
2250
  symbols: unit.symbols,
1733
2251
  duplicateCount: unit.duplicateCount
1734
2252
  })),
2253
+ teamRules: teamRules.map((rule) => ({
2254
+ id: rule.id,
2255
+ score: rule.score,
2256
+ confidenceLevel: rule.confidenceLevel,
2257
+ confidenceReasons: rule.confidenceReasons,
2258
+ freshnessStatus: rule.freshnessStatus,
2259
+ freshnessReason: rule.freshnessReason,
2260
+ category: rule.category,
2261
+ filePaths: rule.filePaths,
2262
+ symbols: rule.symbols,
2263
+ evidence: rule.evidence
2264
+ })),
1735
2265
  codeEvidence: codeChunks.map((chunk) => ({
1736
2266
  id: chunk.id,
1737
2267
  score: chunk.score,
@@ -1790,8 +2320,13 @@ function formatIndexStatus(status) {
1790
2320
  `- Wisdom units: ${status.wisdomUnitCount}`,
1791
2321
  `- Code files: ${status.codeFileCount}`,
1792
2322
  `- Code chunks: ${status.codeChunkCount}`,
2323
+ `- History coverage: ${status.historyCoverage ?? "unknown"}`,
2324
+ `- History limit: ${status.historyLimit ?? "n/a"}`,
2325
+ `- Stale evidence: ${status.staleEvidenceCount}`,
2326
+ `- Team rules: ${status.teamRuleCount}`,
1793
2327
  `- Last sync: ${status.lastSyncTime ?? "never"}`,
1794
2328
  `- Last code index: ${status.lastCodeIndexTime ?? "never"}`,
2329
+ `- Last rule index: ${status.lastRuleIndexTime ?? "never"}`,
1795
2330
  `- GitHub token configured: ${status.githubTokenConfigured ? "yes" : "no"}`,
1796
2331
  `- Health: ${status.health}`
1797
2332
  ];
@@ -1987,8 +2522,8 @@ async function fetchMergedPullRequests(options) {
1987
2522
  }
1988
2523
 
1989
2524
  // src/doctor.ts
1990
- import fs4 from "fs";
1991
- import path9 from "path";
2525
+ import fs5 from "fs";
2526
+ import path10 from "path";
1992
2527
  function check(name, ok, message, fix) {
1993
2528
  return { name, ok, message, fix: ok ? void 0 : fix };
1994
2529
  }
@@ -2049,12 +2584,12 @@ async function runDoctor(options) {
2049
2584
  )
2050
2585
  );
2051
2586
  }
2052
- const cursorConfigPath = path9.join(gitRoot ?? cwd, ".cursor", "mcp.json");
2587
+ const cursorConfigPath = path10.join(gitRoot ?? cwd, ".cursor", "mcp.json");
2053
2588
  let cursorConfig;
2054
2589
  let cursorConfigValid = false;
2055
- if (fs4.existsSync(cursorConfigPath)) {
2590
+ if (fs5.existsSync(cursorConfigPath)) {
2056
2591
  try {
2057
- cursorConfig = JSON.parse(fs4.readFileSync(cursorConfigPath, "utf8"));
2592
+ cursorConfig = JSON.parse(fs5.readFileSync(cursorConfigPath, "utf8"));
2058
2593
  cursorConfigValid = true;
2059
2594
  } catch {
2060
2595
  cursorConfigValid = false;
@@ -2063,7 +2598,7 @@ async function runDoctor(options) {
2063
2598
  checks.push(
2064
2599
  check(
2065
2600
  ".cursor/mcp.json valid",
2066
- fs4.existsSync(cursorConfigPath) && cursorConfigValid,
2601
+ fs5.existsSync(cursorConfigPath) && cursorConfigValid,
2067
2602
  cursorConfigValid ? ".cursor/mcp.json exists and is valid JSON." : ".cursor/mcp.json is missing or invalid.",
2068
2603
  "Run anchor init. If the file is malformed, fix the JSON and rerun anchor init."
2069
2604
  )
@@ -2080,7 +2615,7 @@ async function runDoctor(options) {
2080
2615
  )
2081
2616
  );
2082
2617
  const dbPath = defaultDatabasePath(gitRoot ?? cwd);
2083
- const dbExists = fs4.existsSync(dbPath);
2618
+ const dbExists = fs5.existsSync(dbPath);
2084
2619
  checks.push(
2085
2620
  check(
2086
2621
  ".anchor/index.sqlite exists",
@@ -2124,12 +2659,12 @@ async function runDoctor(options) {
2124
2659
  "Run pnpm build, then try anchor serve from the repository."
2125
2660
  )
2126
2661
  );
2127
- const rulePath = path9.join(gitRoot ?? cwd, ".cursor", "rules", "anchor.mdc");
2662
+ const rulePath = path10.join(gitRoot ?? cwd, ".cursor", "rules", "anchor.mdc");
2128
2663
  checks.push(
2129
2664
  check(
2130
2665
  "Cursor rule file exists",
2131
- fs4.existsSync(rulePath),
2132
- fs4.existsSync(rulePath) ? "Cursor rule file exists." : "Cursor rule file is missing.",
2666
+ fs5.existsSync(rulePath),
2667
+ fs5.existsSync(rulePath) ? "Cursor rule file exists." : "Cursor rule file is missing.",
2133
2668
  "Run anchor init to create .cursor/rules/anchor.mdc."
2134
2669
  )
2135
2670
  );
@@ -2139,6 +2674,7 @@ export {
2139
2674
  ANCHOR_CURSOR_RULE,
2140
2675
  DEFAULT_MAX_CODE_FILE_BYTES,
2141
2676
  SCHEMA_SQL,
2677
+ TEAM_RULES_FILE,
2142
2678
  anchorMcpEntry,
2143
2679
  buildFtsQuery,
2144
2680
  canonicalizeText,
@@ -2146,8 +2682,14 @@ export {
2146
2682
  checkSchema,
2147
2683
  chunkCodeFile,
2148
2684
  chunkHistoricalText,
2685
+ claimKeyFor,
2149
2686
  clampMaxResults,
2150
2687
  clipSentence,
2688
+ confidenceAtLeast,
2689
+ confidenceLevelFor,
2690
+ confidenceRank,
2691
+ confidenceReasonsFor,
2692
+ countValidTeamRules,
2151
2693
  createGitHubClient,
2152
2694
  defaultDatabasePath,
2153
2695
  detectGitHubRepo,
@@ -2158,6 +2700,9 @@ export {
2158
2700
  ensureCursorConfig,
2159
2701
  ensureCursorRule,
2160
2702
  ensureRepository,
2703
+ ensureTeamRulesFile,
2704
+ evaluateFreshness,
2705
+ evidenceForWisdom,
2161
2706
  extractCodeSymbols,
2162
2707
  extractSymbols,
2163
2708
  extractWisdomUnits,
@@ -2174,11 +2719,14 @@ export {
2174
2719
  indexPullRequests,
2175
2720
  initializeSchema,
2176
2721
  isHardExcludedCodePath,
2722
+ loadCurrentCodeSnapshot,
2723
+ loadTeamRulesFile,
2177
2724
  mergeAnchorMcpConfig,
2178
2725
  normalizePullRequest,
2179
2726
  openAnchorDatabase,
2180
2727
  parseGitHubRemote,
2181
2728
  rankCodeChunks,
2729
+ rankTeamRules,
2182
2730
  rankWisdomUnits,
2183
2731
  redactSecrets,
2184
2732
  redactedHistoricalText,
@@ -2189,11 +2737,13 @@ export {
2189
2737
  runDoctor,
2190
2738
  sanitizeHistoricalText,
2191
2739
  shouldSyncSince,
2740
+ sourceTypeLabel,
2192
2741
  stripPromptInjection,
2193
2742
  tokenizeSearchText,
2194
2743
  truncateText,
2195
2744
  uniqueStrings,
2196
2745
  updateSyncState,
2197
- upsertPullRequest
2746
+ upsertPullRequest,
2747
+ validateTeamRulesFile
2198
2748
  };
2199
2749
  //# sourceMappingURL=index.js.map