@pratik7368patil/anchor-core 0.1.6 → 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
@@ -334,10 +334,55 @@ CREATE VIRTUAL TABLE IF NOT EXISTS wisdom_units_fts USING fts5(
334
334
  category
335
335
  );
336
336
 
337
+ CREATE TABLE IF NOT EXISTS code_files (
338
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
339
+ repo_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
340
+ path TEXT NOT NULL,
341
+ language TEXT,
342
+ size_bytes INTEGER NOT NULL,
343
+ content_hash TEXT NOT NULL,
344
+ updated_at TEXT NOT NULL,
345
+ UNIQUE(repo_id, path)
346
+ );
347
+
348
+ CREATE TABLE IF NOT EXISTS code_chunks (
349
+ id TEXT PRIMARY KEY,
350
+ repo_id INTEGER NOT NULL REFERENCES repositories(id) ON DELETE CASCADE,
351
+ file_id INTEGER NOT NULL REFERENCES code_files(id) ON DELETE CASCADE,
352
+ repo TEXT NOT NULL,
353
+ file_path TEXT NOT NULL,
354
+ language TEXT,
355
+ start_line INTEGER NOT NULL,
356
+ end_line INTEGER NOT NULL,
357
+ sanitized_text TEXT NOT NULL,
358
+ symbols_json TEXT NOT NULL,
359
+ content_hash TEXT NOT NULL,
360
+ updated_at TEXT NOT NULL
361
+ );
362
+
363
+ CREATE VIRTUAL TABLE IF NOT EXISTS code_chunks_fts USING fts5(
364
+ chunkId UNINDEXED,
365
+ sanitizedText,
366
+ filePath,
367
+ symbols,
368
+ language
369
+ );
370
+
371
+ CREATE TABLE IF NOT EXISTS code_index_state (
372
+ repo TEXT PRIMARY KEY,
373
+ last_indexed_at TEXT NOT NULL,
374
+ indexed_files INTEGER NOT NULL,
375
+ code_chunks INTEGER NOT NULL,
376
+ skipped_files INTEGER NOT NULL
377
+ );
378
+
337
379
  CREATE TABLE IF NOT EXISTS sync_state (
338
380
  repo TEXT PRIMARY KEY,
339
381
  last_sync_at TEXT,
340
382
  last_indexed_pr INTEGER,
383
+ history_coverage TEXT,
384
+ history_limit INTEGER,
385
+ history_since TEXT,
341
386
  updated_at TEXT NOT NULL
342
387
  );
343
388
 
@@ -346,14 +391,382 @@ CREATE INDEX IF NOT EXISTS idx_pr_files_path ON pr_files(path);
346
391
  CREATE INDEX IF NOT EXISTS idx_pr_comments_source ON pr_comments(source_type);
347
392
  CREATE INDEX IF NOT EXISTS idx_wisdom_units_category ON wisdom_units(category);
348
393
  CREATE INDEX IF NOT EXISTS idx_wisdom_units_pr ON wisdom_units(pr_id);
394
+ CREATE INDEX IF NOT EXISTS idx_code_files_path ON code_files(path);
395
+ CREATE INDEX IF NOT EXISTS idx_code_chunks_file_path ON code_chunks(file_path);
396
+ `;
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)}
349
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
+ }
350
763
 
351
764
  // src/db/database.ts
352
765
  function defaultDatabasePath(cwd) {
353
- return path2.join(cwd, ".anchor", "index.sqlite");
766
+ return path3.join(cwd, ".anchor", "index.sqlite");
354
767
  }
355
768
  function openAnchorDatabase(cwd, databasePath = defaultDatabasePath(cwd)) {
356
- fs2.mkdirSync(path2.dirname(databasePath), { recursive: true });
769
+ fs3.mkdirSync(path3.dirname(databasePath), { recursive: true });
357
770
  const db = new Database(databasePath);
358
771
  db.pragma("journal_mode = WAL");
359
772
  db.pragma("foreign_keys = ON");
@@ -361,12 +774,23 @@ function openAnchorDatabase(cwd, databasePath = defaultDatabasePath(cwd)) {
361
774
  }
362
775
  function initializeSchema(db) {
363
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
+ }
364
786
  }
365
787
  function checkSchema(db) {
366
788
  try {
367
789
  const tables = db.prepare("SELECT name FROM sqlite_master WHERE type IN ('table', 'virtual') AND name = ?").all("wisdom_units_fts");
790
+ const codeTables = db.prepare("SELECT name FROM sqlite_master WHERE type IN ('table', 'virtual') AND name = ?").all("code_chunks_fts");
368
791
  const wisdom = db.prepare("SELECT name FROM sqlite_master WHERE name = ?").all("wisdom_units");
369
- return tables.length > 0 && wisdom.length > 0;
792
+ const code = db.prepare("SELECT name FROM sqlite_master WHERE name = ?").all("code_chunks");
793
+ return tables.length > 0 && wisdom.length > 0 && codeTables.length > 0 && code.length > 0;
370
794
  } catch {
371
795
  return false;
372
796
  }
@@ -386,16 +810,28 @@ function getLastSyncTime(db, repo) {
386
810
  const row = db.prepare("SELECT last_sync_at FROM sync_state WHERE repo = ?").get(repo);
387
811
  return row?.last_sync_at ?? void 0;
388
812
  }
389
- function updateSyncState(db, repo, lastIndexedPr) {
813
+ function updateSyncState(db, repo, lastIndexedPr, metadata = {}) {
390
814
  const now = (/* @__PURE__ */ new Date()).toISOString();
391
815
  db.prepare(
392
- `INSERT INTO sync_state (repo, last_sync_at, last_indexed_pr, updated_at)
393
- VALUES (?, ?, ?, ?)
816
+ `INSERT INTO sync_state
817
+ (repo, last_sync_at, last_indexed_pr, history_coverage, history_limit, history_since, updated_at)
818
+ VALUES (?, ?, ?, ?, ?, ?, ?)
394
819
  ON CONFLICT(repo) DO UPDATE SET
395
820
  last_sync_at = excluded.last_sync_at,
396
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,
397
825
  updated_at = excluded.updated_at`
398
- ).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
+ );
399
835
  }
400
836
  function deleteExistingPrData(db, prId) {
401
837
  const unitRows = db.prepare("SELECT id FROM wisdom_units WHERE pr_id = ?").all(prId);
@@ -543,35 +979,136 @@ function upsertPullRequest(db, pr, wisdomUnits) {
543
979
  const comments = (pr.reviews?.length ?? 0) + (pr.reviewComments?.length ?? 0) + (pr.issueComments?.length ?? 0);
544
980
  return { files: pr.files.length, comments, wisdom: wisdomUnits.length };
545
981
  }
982
+ function replaceCodeIndex(db, repo, codeFiles, codeChunks, skippedFiles, cwd) {
983
+ initializeSchema(db);
984
+ const repoId = ensureRepository(db, repo);
985
+ const now = (/* @__PURE__ */ new Date()).toISOString();
986
+ const transaction = db.transaction(() => {
987
+ const existingChunks = db.prepare("SELECT id FROM code_chunks WHERE repo_id = ?").all(repoId);
988
+ const deleteFts = db.prepare("DELETE FROM code_chunks_fts WHERE chunkId = ?");
989
+ for (const row of existingChunks) deleteFts.run(row.id);
990
+ db.prepare("DELETE FROM code_chunks WHERE repo_id = ?").run(repoId);
991
+ db.prepare("DELETE FROM code_files WHERE repo_id = ?").run(repoId);
992
+ const insertFile = db.prepare(
993
+ `INSERT INTO code_files
994
+ (repo_id, path, language, size_bytes, content_hash, updated_at)
995
+ VALUES (?, ?, ?, ?, ?, ?)`
996
+ );
997
+ for (const file of codeFiles) {
998
+ insertFile.run(
999
+ repoId,
1000
+ file.path,
1001
+ file.language ?? null,
1002
+ file.sizeBytes,
1003
+ file.contentHash,
1004
+ file.updatedAt
1005
+ );
1006
+ }
1007
+ const fileRows = db.prepare("SELECT id, path FROM code_files WHERE repo_id = ?").all(repoId);
1008
+ const fileIds = new Map(fileRows.map((row) => [row.path, row.id]));
1009
+ const insertChunk = db.prepare(
1010
+ `INSERT INTO code_chunks
1011
+ (id, repo_id, file_id, repo, file_path, language, start_line, end_line, sanitized_text,
1012
+ symbols_json, content_hash, updated_at)
1013
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
1014
+ );
1015
+ const insertFts = db.prepare(
1016
+ `INSERT INTO code_chunks_fts
1017
+ (chunkId, sanitizedText, filePath, symbols, language)
1018
+ VALUES (?, ?, ?, ?, ?)`
1019
+ );
1020
+ for (const chunk of codeChunks) {
1021
+ const fileId = fileIds.get(chunk.filePath);
1022
+ if (!fileId) continue;
1023
+ insertChunk.run(
1024
+ chunk.id,
1025
+ repoId,
1026
+ fileId,
1027
+ chunk.repo,
1028
+ chunk.filePath,
1029
+ chunk.language ?? null,
1030
+ chunk.startLine,
1031
+ chunk.endLine,
1032
+ chunk.sanitizedText,
1033
+ JSON.stringify(chunk.symbols),
1034
+ chunk.contentHash,
1035
+ chunk.updatedAt
1036
+ );
1037
+ insertFts.run(
1038
+ chunk.id,
1039
+ chunk.sanitizedText,
1040
+ chunk.filePath,
1041
+ chunk.symbols.join(" "),
1042
+ chunk.language ?? ""
1043
+ );
1044
+ }
1045
+ db.prepare(
1046
+ `INSERT INTO code_index_state (repo, last_indexed_at, indexed_files, code_chunks, skipped_files)
1047
+ VALUES (?, ?, ?, ?, ?)
1048
+ ON CONFLICT(repo) DO UPDATE SET
1049
+ last_indexed_at = excluded.last_indexed_at,
1050
+ indexed_files = excluded.indexed_files,
1051
+ code_chunks = excluded.code_chunks,
1052
+ skipped_files = excluded.skipped_files`
1053
+ ).run(repo, now, codeFiles.length, codeChunks.length, skippedFiles);
1054
+ });
1055
+ transaction();
1056
+ return {
1057
+ indexedFiles: codeFiles.length,
1058
+ codeChunksCreated: codeChunks.length,
1059
+ skippedFiles,
1060
+ databasePath: defaultDatabasePath(cwd)
1061
+ };
1062
+ }
546
1063
  function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken({ cwd }).token), databasePath = defaultDatabasePath(cwd)) {
547
- if (!fs2.existsSync(databasePath)) {
1064
+ if (!fs3.existsSync(databasePath)) {
1065
+ const rules = countValidTeamRules(cwd);
548
1066
  return {
549
1067
  databasePath,
550
1068
  prCount: 0,
551
1069
  fileCount: 0,
552
1070
  commentCount: 0,
553
1071
  wisdomUnitCount: 0,
1072
+ codeFileCount: 0,
1073
+ codeChunkCount: 0,
1074
+ historyCoverage: "unknown",
1075
+ staleEvidenceCount: 0,
1076
+ teamRuleCount: rules.count,
1077
+ lastRuleIndexTime: rules.lastRuleIndexTime,
554
1078
  githubTokenConfigured,
555
1079
  health: "missing_database"
556
1080
  };
557
1081
  }
558
1082
  const db = openAnchorDatabase(cwd, databasePath);
559
1083
  try {
1084
+ initializeSchema(db);
560
1085
  if (!checkSchema(db)) {
1086
+ const rules2 = countValidTeamRules(cwd);
561
1087
  return {
562
1088
  databasePath,
563
1089
  prCount: 0,
564
1090
  fileCount: 0,
565
1091
  commentCount: 0,
566
1092
  wisdomUnitCount: 0,
1093
+ codeFileCount: 0,
1094
+ codeChunkCount: 0,
1095
+ historyCoverage: "unknown",
1096
+ staleEvidenceCount: 0,
1097
+ teamRuleCount: rules2.count,
1098
+ lastRuleIndexTime: rules2.lastRuleIndexTime,
567
1099
  githubTokenConfigured,
568
1100
  health: "schema_invalid"
569
1101
  };
570
1102
  }
571
1103
  const count = (table) => db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count;
572
1104
  const repoRow = db.prepare("SELECT full_name FROM repositories ORDER BY id LIMIT 1").get();
573
- 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();
1108
+ const codeIndexRow = db.prepare("SELECT last_indexed_at FROM code_index_state ORDER BY last_indexed_at DESC LIMIT 1").get();
574
1109
  const wisdomUnitCount = count("wisdom_units");
1110
+ const codeChunkCount = count("code_chunks");
1111
+ const rules = countValidTeamRules(cwd);
575
1112
  return {
576
1113
  repo: repoRow?.full_name,
577
1114
  databasePath,
@@ -579,14 +1116,43 @@ function getIndexStatus(cwd, githubTokenConfigured = Boolean(resolveGitHubToken(
579
1116
  fileCount: count("pr_files"),
580
1117
  commentCount: count("pr_comments"),
581
1118
  wisdomUnitCount,
1119
+ codeFileCount: count("code_files"),
1120
+ codeChunkCount,
1121
+ historyCoverage: syncRow?.history_coverage ?? "unknown",
1122
+ historyLimit: syncRow?.history_limit ?? void 0,
1123
+ staleEvidenceCount: countStaleEvidence(db),
1124
+ teamRuleCount: rules.count,
582
1125
  lastSyncTime: syncRow?.last_sync_at ?? void 0,
1126
+ lastCodeIndexTime: codeIndexRow?.last_indexed_at ?? void 0,
1127
+ lastRuleIndexTime: rules.lastRuleIndexTime,
583
1128
  githubTokenConfigured,
584
- health: wisdomUnitCount > 0 ? "ok" : "empty_index"
1129
+ health: wisdomUnitCount > 0 || codeChunkCount > 0 ? "ok" : "empty_index"
585
1130
  };
586
1131
  } finally {
587
1132
  db.close();
588
1133
  }
589
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
+ }
590
1156
 
591
1157
  // src/indexer/chunker.ts
592
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;
@@ -618,9 +1184,260 @@ function chunkHistoricalText(text, maxChunkLength = 700) {
618
1184
  return expanded.filter((chunk) => chunk.length >= 12 && hasHighSignalLanguage(chunk));
619
1185
  }
620
1186
 
621
- // src/indexer/wisdom-extractor.ts
1187
+ // src/indexer/code-chunker.ts
622
1188
  import crypto from "crypto";
623
- import path3 from "path";
1189
+ import path4 from "path";
1190
+ var DEFAULT_CHUNK_LINES = 80;
1191
+ var DEFAULT_OVERLAP_LINES = 8;
1192
+ var FUNCTION_CALL_STOP_WORDS = /* @__PURE__ */ new Set([
1193
+ "catch",
1194
+ "describe",
1195
+ "for",
1196
+ "if",
1197
+ "it",
1198
+ "return",
1199
+ "switch",
1200
+ "test",
1201
+ "while"
1202
+ ]);
1203
+ function stableCodeChunkId(file, startLine, endLine) {
1204
+ const hash = crypto.createHash("sha256").update([file.repo, file.path, file.contentHash, startLine, endLine].join("\0")).digest("hex").slice(0, 24);
1205
+ return `cc_${hash}`;
1206
+ }
1207
+ function extractCodeSymbols(text, filePath) {
1208
+ const symbols = [];
1209
+ const declarations = text.matchAll(
1210
+ /\b(?:export\s+)?(?:async\s+)?(?:class|function|interface|type|enum|const|let|var)\s+([A-Za-z_$][\w$]*)/g
1211
+ );
1212
+ for (const match of declarations) symbols.push(match[1] ?? "");
1213
+ const objectMethods = text.matchAll(
1214
+ /\b([A-Za-z_$][\w$]{2,})\s*[:=]\s*(?:async\s*)?\([^)]*\)\s*=>/g
1215
+ );
1216
+ for (const match of objectMethods) symbols.push(match[1] ?? "");
1217
+ const calls = text.matchAll(/\b([A-Za-z_$][\w$]{2,})\s*\(/g);
1218
+ for (const match of calls) {
1219
+ const candidate = match[1] ?? "";
1220
+ if (!FUNCTION_CALL_STOP_WORDS.has(candidate)) symbols.push(candidate);
1221
+ }
1222
+ const basename = path4.basename(filePath).replace(/\.[^.]+$/, "");
1223
+ if (/^[A-Za-z_$][\w$-]*$/.test(basename)) symbols.push(basename);
1224
+ return uniqueStrings(symbols).slice(0, 40);
1225
+ }
1226
+ function chunkCodeFile(file, options = {}) {
1227
+ const chunkLines = options.chunkLines ?? DEFAULT_CHUNK_LINES;
1228
+ const overlapLines = Math.max(
1229
+ 0,
1230
+ Math.min(options.overlapLines ?? DEFAULT_OVERLAP_LINES, chunkLines - 1)
1231
+ );
1232
+ const lines = file.content.replace(/\r\n/g, "\n").split("\n");
1233
+ const chunks = [];
1234
+ for (let startIndex = 0; startIndex < lines.length; ) {
1235
+ const endIndex = Math.min(lines.length, startIndex + chunkLines);
1236
+ const rawText = lines.slice(startIndex, endIndex).join("\n");
1237
+ const sanitizedText = sanitizeHistoricalText(rawText);
1238
+ if (sanitizedText) {
1239
+ chunks.push({
1240
+ id: stableCodeChunkId(file, startIndex + 1, endIndex),
1241
+ repo: file.repo,
1242
+ filePath: file.path,
1243
+ language: file.language,
1244
+ startLine: startIndex + 1,
1245
+ endLine: endIndex,
1246
+ sanitizedText,
1247
+ symbols: extractCodeSymbols(sanitizedText, file.path),
1248
+ contentHash: file.contentHash,
1249
+ updatedAt: file.updatedAt
1250
+ });
1251
+ }
1252
+ if (endIndex >= lines.length) break;
1253
+ startIndex = Math.max(startIndex + 1, endIndex - overlapLines);
1254
+ }
1255
+ return chunks;
1256
+ }
1257
+
1258
+ // src/indexer/code-file-discovery.ts
1259
+ import { execFileSync as execFileSync3 } from "child_process";
1260
+ import crypto2 from "crypto";
1261
+ import fs4 from "fs";
1262
+ import path5 from "path";
1263
+ var DEFAULT_MAX_CODE_FILE_BYTES = 512 * 1024;
1264
+ var HARD_EXCLUDED_SEGMENTS = /* @__PURE__ */ new Set([
1265
+ ".git",
1266
+ ".anchor",
1267
+ ".cursor",
1268
+ ".codex",
1269
+ ".aws",
1270
+ ".ssh",
1271
+ "node_modules",
1272
+ ".nuxt",
1273
+ ".next",
1274
+ "dist",
1275
+ "build",
1276
+ "coverage",
1277
+ ".turbo"
1278
+ ]);
1279
+ var LANGUAGE_BY_EXTENSION = {
1280
+ ".cjs": "javascript",
1281
+ ".css": "css",
1282
+ ".go": "go",
1283
+ ".html": "html",
1284
+ ".java": "java",
1285
+ ".js": "javascript",
1286
+ ".json": "json",
1287
+ ".jsx": "javascript",
1288
+ ".md": "markdown",
1289
+ ".mjs": "javascript",
1290
+ ".py": "python",
1291
+ ".rb": "ruby",
1292
+ ".rs": "rust",
1293
+ ".scss": "scss",
1294
+ ".sh": "shell",
1295
+ ".sql": "sql",
1296
+ ".svelte": "svelte",
1297
+ ".ts": "typescript",
1298
+ ".tsx": "typescript",
1299
+ ".vue": "vue",
1300
+ ".yaml": "yaml",
1301
+ ".yml": "yaml"
1302
+ };
1303
+ function normalizeGitPath(value) {
1304
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "");
1305
+ }
1306
+ function isHardExcludedCodePath(filePath) {
1307
+ const normalized = normalizeGitPath(filePath);
1308
+ const segments = normalized.split("/");
1309
+ if (segments.some((segment) => HARD_EXCLUDED_SEGMENTS.has(segment))) return true;
1310
+ const basename = path5.posix.basename(normalized).toLowerCase();
1311
+ if ([".netrc", ".npmrc", ".pypirc", ".yarnrc"].includes(basename)) return true;
1312
+ if (basename === ".env" || basename.startsWith(".env.")) return true;
1313
+ if (basename === "id_rsa" || basename === "id_rsa.pub" || basename === "id_dsa" || basename === "id_ecdsa" || basename === "id_ed25519") {
1314
+ return true;
1315
+ }
1316
+ if (/\.(pem|key|p12|pfx)$/i.test(basename)) return true;
1317
+ return false;
1318
+ }
1319
+ function languageForPath(filePath) {
1320
+ const extension = path5.extname(filePath).toLowerCase();
1321
+ return LANGUAGE_BY_EXTENSION[extension];
1322
+ }
1323
+ function isProbablyBinary(buffer) {
1324
+ if (buffer.includes(0)) return true;
1325
+ if (buffer.length === 0) return false;
1326
+ let suspicious = 0;
1327
+ for (const byte of buffer) {
1328
+ const isAllowedControl = byte === 9 || byte === 10 || byte === 13;
1329
+ if (byte < 32 && !isAllowedControl) suspicious += 1;
1330
+ }
1331
+ return suspicious / buffer.length > 0.01;
1332
+ }
1333
+ function discoverGitFiles(cwd) {
1334
+ const output = execFileSync3("git", ["ls-files", "--cached", "--others", "--exclude-standard"], {
1335
+ cwd,
1336
+ encoding: "utf8",
1337
+ stdio: ["ignore", "pipe", "pipe"]
1338
+ });
1339
+ return output.split("\n").map((line) => normalizeGitPath(line.trim())).filter(Boolean);
1340
+ }
1341
+ function discoverCodeFiles(cwd, repo, options = {}) {
1342
+ const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_CODE_FILE_BYTES;
1343
+ const rootPath = path5.resolve(cwd);
1344
+ const files = [];
1345
+ let skippedFiles = 0;
1346
+ for (const filePath of discoverGitFiles(cwd)) {
1347
+ if (isHardExcludedCodePath(filePath)) {
1348
+ skippedFiles += 1;
1349
+ continue;
1350
+ }
1351
+ const absolutePath = path5.resolve(cwd, filePath);
1352
+ const relativeToRoot = path5.relative(rootPath, absolutePath);
1353
+ if (relativeToRoot.startsWith("..") || path5.isAbsolute(relativeToRoot)) {
1354
+ skippedFiles += 1;
1355
+ continue;
1356
+ }
1357
+ let stat;
1358
+ try {
1359
+ stat = fs4.statSync(absolutePath);
1360
+ } catch {
1361
+ skippedFiles += 1;
1362
+ continue;
1363
+ }
1364
+ if (!stat.isFile() || stat.size > maxFileBytes) {
1365
+ skippedFiles += 1;
1366
+ continue;
1367
+ }
1368
+ const buffer = fs4.readFileSync(absolutePath);
1369
+ if (isProbablyBinary(buffer)) {
1370
+ skippedFiles += 1;
1371
+ continue;
1372
+ }
1373
+ const content = buffer.toString("utf8");
1374
+ files.push({
1375
+ repo,
1376
+ path: filePath,
1377
+ language: languageForPath(filePath),
1378
+ sizeBytes: stat.size,
1379
+ contentHash: crypto2.createHash("sha256").update(buffer).digest("hex"),
1380
+ updatedAt: stat.mtime.toISOString(),
1381
+ absolutePath,
1382
+ content
1383
+ });
1384
+ }
1385
+ return { files, skippedFiles };
1386
+ }
1387
+
1388
+ // src/indexer/code-indexer.ts
1389
+ function indexCodebase(db, options) {
1390
+ options.onProgress?.({ stage: "discovering_code_files", repo: options.repo });
1391
+ const discovery = discoverCodeFiles(options.cwd, options.repo, {
1392
+ maxFileBytes: options.maxFileBytes
1393
+ });
1394
+ options.onProgress?.({
1395
+ stage: "discovered_code_files",
1396
+ repo: options.repo,
1397
+ files: discovery.files.length,
1398
+ skippedFiles: discovery.skippedFiles
1399
+ });
1400
+ const chunks = [];
1401
+ for (const [index, file] of discovery.files.entries()) {
1402
+ options.onProgress?.({
1403
+ stage: "indexing_code_file",
1404
+ repo: options.repo,
1405
+ current: index + 1,
1406
+ total: discovery.files.length,
1407
+ filePath: file.path
1408
+ });
1409
+ const fileChunks = chunkCodeFile(file);
1410
+ chunks.push(...fileChunks);
1411
+ options.onProgress?.({
1412
+ stage: "indexed_code_file",
1413
+ repo: options.repo,
1414
+ current: index + 1,
1415
+ total: discovery.files.length,
1416
+ filePath: file.path,
1417
+ chunks: fileChunks.length
1418
+ });
1419
+ }
1420
+ return replaceCodeIndex(
1421
+ db,
1422
+ options.repo,
1423
+ discovery.files.map(({ content: _content, absolutePath: _absolutePath, ...file }) => file),
1424
+ chunks,
1425
+ discovery.skippedFiles,
1426
+ options.cwd
1427
+ );
1428
+ }
1429
+ function emptyCodeIndexSummary(cwd) {
1430
+ return {
1431
+ indexedFiles: 0,
1432
+ codeChunksCreated: 0,
1433
+ skippedFiles: 0,
1434
+ databasePath: defaultDatabasePath(cwd)
1435
+ };
1436
+ }
1437
+
1438
+ // src/indexer/wisdom-extractor.ts
1439
+ import crypto3 from "crypto";
1440
+ import path6 from "path";
624
1441
  var CATEGORY_KEYWORDS = [
625
1442
  ["security_note", /\b(security|secret|token|bearer|oauth|credential|xss|csrf|injection|sanitize|redact)\b/i],
626
1443
  ["architecture_decision", /\b(architecture decision|architectural|we intentionally|design decision)\b/i],
@@ -652,7 +1469,7 @@ function extractSymbols(text, filePaths) {
652
1469
  }
653
1470
  }
654
1471
  for (const filePath of filePaths) {
655
- const basename = path3.basename(filePath).replace(/\.[^.]+$/, "");
1472
+ const basename = path6.basename(filePath).replace(/\.[^.]+$/, "");
656
1473
  if (/^[A-Za-z_$][\w$]*$/.test(basename)) symbols.push(basename);
657
1474
  }
658
1475
  return uniqueStrings(symbols).slice(0, 30);
@@ -676,7 +1493,7 @@ function confidenceFor(entry, text, category, duplicateCount) {
676
1493
  return Math.max(0, Math.min(1, Number(confidence.toFixed(2))));
677
1494
  }
678
1495
  function stableWisdomId(pr, sourceType, text, filePaths, createdAt, authors) {
679
- const hash = crypto.createHash("sha256").update(
1496
+ const hash = crypto3.createHash("sha256").update(
680
1497
  [pr.repo, pr.number, sourceType, canonicalizeText(text), filePaths.join("|"), createdAt, authors.join("|")].join(
681
1498
  "\0"
682
1499
  )
@@ -854,7 +1671,11 @@ function indexPullRequests(db, pullRequests, options) {
854
1671
  });
855
1672
  }
856
1673
  if (options.updateSyncStateAfter !== false) {
857
- updateSyncState(db, options.repo, lastPr);
1674
+ updateSyncState(db, options.repo, lastPr, {
1675
+ historyCoverage: options.historyCoverage,
1676
+ historyLimit: options.historyLimit,
1677
+ historySince: options.historySince
1678
+ });
858
1679
  }
859
1680
  return {
860
1681
  indexedPrs: pullRequests.length - skippedItems,
@@ -872,7 +1693,7 @@ function shouldSyncSince(db, repo, fallbackSince) {
872
1693
  }
873
1694
 
874
1695
  // src/retrieval/query-builder.ts
875
- import path4 from "path";
1696
+ import path7 from "path";
876
1697
  var CATEGORY_HINTS = [
877
1698
  "security",
878
1699
  "regression",
@@ -897,8 +1718,8 @@ function buildFtsQuery(input) {
897
1718
  const baseText = "task" in input ? input.task : input.query;
898
1719
  const fileTerms = files.flatMap((file) => [
899
1720
  file,
900
- path4.basename(file),
901
- ...path4.dirname(file).split(/[\\/]/).filter(Boolean)
1721
+ path7.basename(file),
1722
+ ...path7.dirname(file).split(/[\\/]/).filter(Boolean)
902
1723
  ]);
903
1724
  const tokens = uniqueStrings([
904
1725
  ...tokenizeSearchText(baseText, 24),
@@ -917,8 +1738,8 @@ function clampMaxResults(value, defaultValue) {
917
1738
  }
918
1739
 
919
1740
  // src/retrieval/ranker.ts
920
- import path5 from "path";
921
- function parseJsonArray(value) {
1741
+ import path8 from "path";
1742
+ function parseJsonArray2(value) {
922
1743
  try {
923
1744
  const parsed = JSON.parse(value);
924
1745
  return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
@@ -936,9 +1757,9 @@ function rowToWisdomUnit(row) {
936
1757
  category: row.category,
937
1758
  text: row.text,
938
1759
  sanitizedText: row.sanitized_text,
939
- filePaths: parseJsonArray(row.file_paths_json),
940
- symbols: parseJsonArray(row.symbols_json),
941
- authors: parseJsonArray(row.authors_json),
1760
+ filePaths: parseJsonArray2(row.file_paths_json),
1761
+ symbols: parseJsonArray2(row.symbols_json),
1762
+ authors: parseJsonArray2(row.authors_json),
942
1763
  createdAt: row.created_at,
943
1764
  mergedAt: row.merged_at ?? void 0,
944
1765
  confidence: row.confidence,
@@ -964,17 +1785,18 @@ function filePathMatch(unitPaths, queryFiles) {
964
1785
  if (queryFiles.length === 0 || unitPaths.length === 0) return 0;
965
1786
  let best = 0;
966
1787
  for (const queryFile of queryFiles) {
967
- const queryBase = path5.basename(queryFile).toLowerCase();
968
- const queryDir = path5.dirname(queryFile).toLowerCase();
1788
+ const queryBase = path8.basename(queryFile).toLowerCase();
1789
+ const queryDir = path8.dirname(queryFile).toLowerCase();
969
1790
  for (const unitPath of unitPaths) {
970
- const unitBase = path5.basename(unitPath).toLowerCase();
971
- const unitDir = path5.dirname(unitPath).toLowerCase();
1791
+ const unitBase = path8.basename(unitPath).toLowerCase();
1792
+ const unitDir = path8.dirname(unitPath).toLowerCase();
972
1793
  const q = queryFile.toLowerCase();
973
1794
  const u = unitPath.toLowerCase();
974
1795
  if (q === u) best = Math.max(best, 1);
975
1796
  else if (queryBase === unitBase) best = Math.max(best, 0.68);
976
1797
  else if (queryDir === unitDir) best = Math.max(best, 0.62);
977
- 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);
978
1800
  else if (queryBase && unitBase && queryBase.split(".")[0] === unitBase.split(".")[0]) {
979
1801
  best = Math.max(best, 0.48);
980
1802
  }
@@ -982,7 +1804,7 @@ function filePathMatch(unitPaths, queryFiles) {
982
1804
  }
983
1805
  return best;
984
1806
  }
985
- function symbolMatch(unit, querySymbols) {
1807
+ function symbolMatch2(unit, querySymbols) {
986
1808
  if (querySymbols.length === 0) return 0;
987
1809
  const unitSymbols = unit.symbols.map((symbol) => symbol.toLowerCase());
988
1810
  const text = unit.sanitizedText.toLowerCase();
@@ -991,14 +1813,15 @@ function symbolMatch(unit, querySymbols) {
991
1813
  const lower = symbol.toLowerCase();
992
1814
  if (unitSymbols.includes(lower)) best = Math.max(best, 1);
993
1815
  else if (text.includes(`\`${lower}\``)) best = Math.max(best, 1);
994
- 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);
995
1818
  else if (unitSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
996
1819
  best = Math.max(best, 0.35);
997
1820
  }
998
1821
  }
999
1822
  return best;
1000
1823
  }
1001
- function textMatch(unit, inputText) {
1824
+ function textMatch2(unit, inputText) {
1002
1825
  const queryTokens = tokenizeSearchText(inputText, 32);
1003
1826
  if (queryTokens.length === 0) return unit.bm25 === void 0 ? 0 : 0.45;
1004
1827
  const haystack = `${unit.sanitizedText} ${unit.filePaths.join(" ")} ${unit.symbols.join(" ")}`.toLowerCase();
@@ -1022,25 +1845,38 @@ function recencyScore(unit) {
1022
1845
  if (ageDays < 1460) return 0.45;
1023
1846
  return 0.25;
1024
1847
  }
1025
- 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) {
1026
1854
  const queryFiles = input.files ?? [];
1027
1855
  const querySymbols = "symbols" in input ? input.symbols ?? [] : [];
1028
1856
  const inputText = "task" in input ? `${input.task} ${input.diff ?? ""} ${input.currentCode ?? ""}` : input.query;
1029
- 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);
1030
1859
  const parts = {
1031
1860
  filePathMatch: filePathMatch(unit.filePaths, queryFiles),
1032
- symbolMatch: symbolMatch(unit, querySymbols),
1033
- textMatch: textMatch(unit, inputText),
1861
+ symbolMatch: symbolMatch2(unit, querySymbols),
1862
+ textMatch: textMatch2(unit, inputText),
1034
1863
  reviewerOrAuthorSignal: reviewerOrAuthorSignal(unit),
1035
1864
  recencyOrRepetition: Math.max(recencyScore(unit), repetition),
1036
1865
  categoryPriority: categoryPriority(unit.category)
1037
1866
  };
1038
- 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);
1039
1868
  return {
1040
1869
  ...unit,
1041
1870
  score: Number(score.toFixed(4)),
1042
1871
  scoreParts: parts,
1043
- 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)
1044
1880
  };
1045
1881
  }
1046
1882
  function escapeRegExp(value) {
@@ -1070,20 +1906,48 @@ function loadCandidates(db, input) {
1070
1906
  ).all(...categories);
1071
1907
  return rows.map(rowToWisdomUnit);
1072
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
+ }
1073
1929
  function rankWisdomUnits(db, input) {
1074
1930
  const candidates = loadCandidates(db, input);
1931
+ const codeSnapshot = loadCurrentCodeSnapshot(db);
1932
+ const repetitionCounts = loadClaimRepetitionCounts(db);
1075
1933
  const duplicates = /* @__PURE__ */ new Map();
1076
1934
  for (const unit of candidates) {
1077
- const key = `${unit.category}:${canonicalizeText(unit.sanitizedText).slice(0, 180)}`;
1935
+ const key = claimKeyFor(unit.category, unit.sanitizedText);
1078
1936
  duplicates.set(key, (duplicates.get(key) ?? 0) + 1);
1079
1937
  }
1080
1938
  const ranked = candidates.map((unit) => {
1081
- const key = `${unit.category}:${canonicalizeText(unit.sanitizedText).slice(0, 180)}`;
1082
- return scoreUnit(unit, input, duplicates.get(key) ?? 1);
1083
- }).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);
1084
1948
  const grouped = /* @__PURE__ */ new Map();
1085
1949
  for (const unit of ranked) {
1086
- const key = `${unit.category}:${canonicalizeText(unit.sanitizedText).slice(0, 180)}`;
1950
+ const key = unit.claimKey;
1087
1951
  const existing = grouped.get(key);
1088
1952
  if (!existing || unit.score > existing.score) {
1089
1953
  grouped.set(key, {
@@ -1091,7 +1955,11 @@ function rankWisdomUnits(db, input) {
1091
1955
  filePaths: uniqueStrings([...existing?.filePaths ?? [], ...unit.filePaths]),
1092
1956
  symbols: uniqueStrings([...existing?.symbols ?? [], ...unit.symbols]),
1093
1957
  authors: uniqueStrings([...existing?.authors ?? [], ...unit.authors]),
1094
- 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
+ )
1095
1963
  });
1096
1964
  }
1097
1965
  }
@@ -1099,14 +1967,174 @@ function rankWisdomUnits(db, input) {
1099
1967
  return [...grouped.values()].sort((a, b) => b.score - a.score || b.confidence - a.confidence).slice(0, limit);
1100
1968
  }
1101
1969
 
1970
+ // src/retrieval/code-ranker.ts
1971
+ import path9 from "path";
1972
+ function parseJsonArray3(value) {
1973
+ try {
1974
+ const parsed = JSON.parse(value);
1975
+ return Array.isArray(parsed) ? parsed.filter((item) => typeof item === "string") : [];
1976
+ } catch {
1977
+ return [];
1978
+ }
1979
+ }
1980
+ function rowToCodeChunk(row) {
1981
+ return {
1982
+ id: row.id,
1983
+ repo: row.repo,
1984
+ filePath: row.file_path,
1985
+ language: row.language ?? void 0,
1986
+ startLine: row.start_line,
1987
+ endLine: row.end_line,
1988
+ sanitizedText: row.sanitized_text,
1989
+ symbols: parseJsonArray3(row.symbols_json),
1990
+ contentHash: row.content_hash,
1991
+ updatedAt: row.updated_at,
1992
+ bm25: row.bm25 ?? void 0
1993
+ };
1994
+ }
1995
+ function filePathMatch2(filePath, queryFiles) {
1996
+ if (queryFiles.length === 0) return 0;
1997
+ let best = 0;
1998
+ const unitBase = path9.basename(filePath).toLowerCase();
1999
+ const unitDir = path9.dirname(filePath).toLowerCase();
2000
+ const unit = filePath.toLowerCase();
2001
+ for (const queryFile of queryFiles) {
2002
+ const query = queryFile.toLowerCase();
2003
+ const queryBase = path9.basename(queryFile).toLowerCase();
2004
+ const queryDir = path9.dirname(queryFile).toLowerCase();
2005
+ if (query === unit) best = Math.max(best, 1);
2006
+ else if (queryBase === unitBase) best = Math.max(best, 0.72);
2007
+ else if (queryDir === unitDir) best = Math.max(best, 0.62);
2008
+ else if (unitDir.startsWith(queryDir) || queryDir.startsWith(unitDir))
2009
+ best = Math.max(best, 0.38);
2010
+ else if (queryBase && unitBase && queryBase.split(".")[0] === unitBase.split(".")[0]) {
2011
+ best = Math.max(best, 0.48);
2012
+ }
2013
+ }
2014
+ return best;
2015
+ }
2016
+ function symbolMatch3(chunk, querySymbols) {
2017
+ if (querySymbols.length === 0) return 0;
2018
+ const chunkSymbols = chunk.symbols.map((symbol) => symbol.toLowerCase());
2019
+ const text = chunk.sanitizedText.toLowerCase();
2020
+ let best = 0;
2021
+ for (const symbol of querySymbols) {
2022
+ const lower = symbol.toLowerCase();
2023
+ if (chunkSymbols.includes(lower)) best = Math.max(best, 1);
2024
+ else if (new RegExp(`\\b${escapeRegExp2(lower)}\\b`, "i").test(text)) best = Math.max(best, 0.7);
2025
+ else if (chunkSymbols.some((candidate) => candidate.includes(lower) || lower.includes(candidate))) {
2026
+ best = Math.max(best, 0.42);
2027
+ }
2028
+ }
2029
+ return best;
2030
+ }
2031
+ function textMatch3(chunk, input) {
2032
+ const tokens = tokenizeSearchText(
2033
+ `${input.task} ${input.diff ?? ""} ${input.currentCode ?? ""}`,
2034
+ 40
2035
+ );
2036
+ const haystack = `${chunk.sanitizedText} ${chunk.filePath} ${chunk.symbols.join(" ")}`.toLowerCase();
2037
+ const overlap = tokens.length ? tokens.filter((token) => haystack.includes(token.toLowerCase())).length / tokens.length : 0;
2038
+ const bm25Signal = chunk.bm25 === void 0 ? 0 : Math.max(0.25, Math.min(1, 1 / (1 + Math.abs(chunk.bm25))));
2039
+ return Math.max(overlap, bm25Signal);
2040
+ }
2041
+ function recencyScore2(chunk) {
2042
+ const timestamp = Date.parse(chunk.updatedAt);
2043
+ if (Number.isNaN(timestamp)) return 0.25;
2044
+ const ageDays = Math.max(0, (Date.now() - timestamp) / (1e3 * 60 * 60 * 24));
2045
+ if (ageDays < 30) return 1;
2046
+ if (ageDays < 180) return 0.75;
2047
+ if (ageDays < 730) return 0.45;
2048
+ return 0.25;
2049
+ }
2050
+ function escapeRegExp2(value) {
2051
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2052
+ }
2053
+ function escapeLike(value) {
2054
+ return value.replace(/[\\%_]/g, (match) => `\\${match}`);
2055
+ }
2056
+ function loadCodeCandidates(db, input) {
2057
+ const candidates = /* @__PURE__ */ new Map();
2058
+ const ftsQuery = buildFtsQuery(input);
2059
+ if (ftsQuery) {
2060
+ const rows = db.prepare(
2061
+ `SELECT cc.*, bm25(code_chunks_fts) AS bm25
2062
+ FROM code_chunks_fts
2063
+ JOIN code_chunks cc ON cc.id = code_chunks_fts.chunkId
2064
+ WHERE code_chunks_fts MATCH ?
2065
+ ORDER BY bm25(code_chunks_fts)
2066
+ LIMIT 150`
2067
+ ).all(ftsQuery);
2068
+ for (const row of rows) {
2069
+ const chunk = rowToCodeChunk(row);
2070
+ candidates.set(chunk.id, chunk);
2071
+ }
2072
+ }
2073
+ for (const file of input.files ?? []) {
2074
+ const basename = path9.basename(file);
2075
+ const rows = db.prepare(
2076
+ `SELECT cc.*, NULL AS bm25
2077
+ FROM code_chunks cc
2078
+ WHERE cc.file_path = ?
2079
+ OR cc.file_path LIKE ? ESCAPE '\\'
2080
+ LIMIT 80`
2081
+ ).all(file, `%/${escapeLike(basename)}`);
2082
+ for (const row of rows) {
2083
+ const chunk = rowToCodeChunk(row);
2084
+ candidates.set(chunk.id, { ...chunk, bm25: candidates.get(chunk.id)?.bm25 ?? chunk.bm25 });
2085
+ }
2086
+ }
2087
+ if (candidates.size === 0) {
2088
+ const rows = db.prepare(
2089
+ `SELECT cc.*, NULL AS bm25
2090
+ FROM code_chunks cc
2091
+ ORDER BY updated_at DESC
2092
+ LIMIT 80`
2093
+ ).all();
2094
+ for (const row of rows) {
2095
+ const chunk = rowToCodeChunk(row);
2096
+ candidates.set(chunk.id, chunk);
2097
+ }
2098
+ }
2099
+ return [...candidates.values()];
2100
+ }
2101
+ function rankCodeChunks(db, input) {
2102
+ const queryFiles = input.files ?? [];
2103
+ const querySymbols = input.symbols ?? [];
2104
+ const ranked = loadCodeCandidates(db, input).map((chunk) => {
2105
+ const parts = {
2106
+ filePathMatch: filePathMatch2(chunk.filePath, queryFiles),
2107
+ symbolMatch: symbolMatch3(chunk, querySymbols),
2108
+ textMatch: textMatch3(chunk, input),
2109
+ recency: recencyScore2(chunk)
2110
+ };
2111
+ const score = 0.4 * parts.filePathMatch + 0.25 * parts.symbolMatch + 0.25 * parts.textMatch + 0.1 * parts.recency;
2112
+ return {
2113
+ ...chunk,
2114
+ symbols: uniqueStrings(chunk.symbols),
2115
+ score: Number(score.toFixed(4)),
2116
+ scoreParts: parts
2117
+ };
2118
+ }).sort((a, b) => b.score - a.score || b.startLine - a.startLine);
2119
+ const limit = Math.min(5, clampMaxResults(input.maxResults, 5));
2120
+ return ranked.slice(0, limit);
2121
+ }
2122
+
1102
2123
  // src/retrieval/formatter.ts
1103
2124
  function evidenceLine(unit) {
1104
2125
  const author = unit.authors[0] ? ` by @${unit.authors[0]}` : "";
1105
2126
  const file = unit.filePaths[0] ? `, ${unit.filePaths[0]}` : "";
1106
2127
  return `PR #${unit.prNumber}${author}, ${unit.sourceType}${file}`;
1107
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
+ }
1108
2136
  function whyItMatters(unit, input) {
1109
- const prefix = unit.confidence < 0.7 ? "Historical evidence suggests " : "";
2137
+ const prefix = unit.confidenceLevel === "weak" ? "Historical evidence suggests " : "";
1110
2138
  const target = input.files?.[0] ? ` when editing ${input.files[0]}` : " for this change";
1111
2139
  const categoryReasons = {
1112
2140
  security_note: `${prefix}there is a security-sensitive constraint to preserve${target}.`,
@@ -1125,27 +2153,69 @@ function whyItMatters(unit, input) {
1125
2153
  function riskLines(units) {
1126
2154
  const risks = /* @__PURE__ */ new Set();
1127
2155
  for (const unit of units) {
1128
- if (unit.category === "security_note") risks.add("Avoid logging, exposing, or weakening security-sensitive values.");
1129
- if (unit.category === "bug_regression") risks.add("Check for regressions similar to the cited PR history.");
1130
- if (unit.category === "api_contract") risks.add("Preserve documented API and backward-compatibility contracts.");
1131
- if (unit.category === "constraint") risks.add("Do not remove constraints without verifying the original reason no longer applies.");
2156
+ if (unit.category === "security_note")
2157
+ risks.add("Avoid logging, exposing, or weakening security-sensitive values.");
2158
+ if (unit.category === "bug_regression")
2159
+ risks.add("Check for regressions similar to the cited PR history.");
2160
+ if (unit.category === "api_contract")
2161
+ risks.add("Preserve documented API and backward-compatibility contracts.");
2162
+ if (unit.category === "constraint")
2163
+ risks.add(
2164
+ "Do not remove constraints without verifying the original reason no longer applies."
2165
+ );
1132
2166
  }
1133
2167
  return [...risks].slice(0, 4);
1134
2168
  }
1135
- function formatAnchorContext(units, input) {
1136
- 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", "");
1137
2190
  if (units.length === 0) {
1138
- 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
+ );
1139
2195
  } else {
1140
2196
  units.forEach((unit, index) => {
1141
- 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);
1142
2198
  lines.push(`${index + 1}. [${unit.category}] ${statement}`);
1143
2199
  lines.push(` Evidence: ${evidenceLine(unit)}`);
2200
+ lines.push(` Confidence: ${confidenceLine(unit)}`);
2201
+ lines.push(` Current code check: ${currentCodeCheckLine(unit)}`);
1144
2202
  lines.push(` Why it matters: ${whyItMatters(unit, input)}`);
1145
2203
  lines.push(` Link: ${unit.prUrl}`);
1146
2204
  lines.push("");
1147
2205
  });
1148
2206
  }
2207
+ lines.push("## Codebase Evidence", "");
2208
+ if (codeChunks.length === 0) {
2209
+ lines.push("No directly relevant indexed codebase context found.", "");
2210
+ } else {
2211
+ codeChunks.forEach((chunk, index) => {
2212
+ const symbols = chunk.symbols.length ? `; symbols: ${chunk.symbols.slice(0, 6).join(", ")}` : "";
2213
+ lines.push(`${index + 1}. ${chunk.filePath}:${chunk.startLine}-${chunk.endLine}${symbols}`);
2214
+ lines.push(` Why it matters: Current code near this match may affect the requested edit.`);
2215
+ lines.push(` Snippet: ${clipSentence(chunk.sanitizedText, 260)}`);
2216
+ lines.push("");
2217
+ });
2218
+ }
1149
2219
  lines.push("## Risks", "");
1150
2220
  const risks = riskLines(units);
1151
2221
  if (risks.length === 0) {
@@ -1165,6 +2235,13 @@ function formatAnchorContext(units, input) {
1165
2235
  id: unit.id,
1166
2236
  score: unit.score,
1167
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,
1168
2245
  category: unit.category,
1169
2246
  prNumber: unit.prNumber,
1170
2247
  prUrl: unit.prUrl,
@@ -1172,6 +2249,27 @@ function formatAnchorContext(units, input) {
1172
2249
  filePaths: unit.filePaths,
1173
2250
  symbols: unit.symbols,
1174
2251
  duplicateCount: unit.duplicateCount
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
+ })),
2265
+ codeEvidence: codeChunks.map((chunk) => ({
2266
+ id: chunk.id,
2267
+ score: chunk.score,
2268
+ filePath: chunk.filePath,
2269
+ language: chunk.language,
2270
+ startLine: chunk.startLine,
2271
+ endLine: chunk.endLine,
2272
+ symbols: chunk.symbols
1175
2273
  }))
1176
2274
  }
1177
2275
  };
@@ -1220,7 +2318,15 @@ function formatIndexStatus(status) {
1220
2318
  `- Files: ${status.fileCount}`,
1221
2319
  `- Comments: ${status.commentCount}`,
1222
2320
  `- Wisdom units: ${status.wisdomUnitCount}`,
2321
+ `- Code files: ${status.codeFileCount}`,
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}`,
1223
2327
  `- Last sync: ${status.lastSyncTime ?? "never"}`,
2328
+ `- Last code index: ${status.lastCodeIndexTime ?? "never"}`,
2329
+ `- Last rule index: ${status.lastRuleIndexTime ?? "never"}`,
1224
2330
  `- GitHub token configured: ${status.githubTokenConfigured ? "yes" : "no"}`,
1225
2331
  `- Health: ${status.health}`
1226
2332
  ];
@@ -1416,8 +2522,8 @@ async function fetchMergedPullRequests(options) {
1416
2522
  }
1417
2523
 
1418
2524
  // src/doctor.ts
1419
- import fs3 from "fs";
1420
- import path6 from "path";
2525
+ import fs5 from "fs";
2526
+ import path10 from "path";
1421
2527
  function check(name, ok, message, fix) {
1422
2528
  return { name, ok, message, fix: ok ? void 0 : fix };
1423
2529
  }
@@ -1478,12 +2584,12 @@ async function runDoctor(options) {
1478
2584
  )
1479
2585
  );
1480
2586
  }
1481
- const cursorConfigPath = path6.join(gitRoot ?? cwd, ".cursor", "mcp.json");
2587
+ const cursorConfigPath = path10.join(gitRoot ?? cwd, ".cursor", "mcp.json");
1482
2588
  let cursorConfig;
1483
2589
  let cursorConfigValid = false;
1484
- if (fs3.existsSync(cursorConfigPath)) {
2590
+ if (fs5.existsSync(cursorConfigPath)) {
1485
2591
  try {
1486
- cursorConfig = JSON.parse(fs3.readFileSync(cursorConfigPath, "utf8"));
2592
+ cursorConfig = JSON.parse(fs5.readFileSync(cursorConfigPath, "utf8"));
1487
2593
  cursorConfigValid = true;
1488
2594
  } catch {
1489
2595
  cursorConfigValid = false;
@@ -1492,7 +2598,7 @@ async function runDoctor(options) {
1492
2598
  checks.push(
1493
2599
  check(
1494
2600
  ".cursor/mcp.json valid",
1495
- fs3.existsSync(cursorConfigPath) && cursorConfigValid,
2601
+ fs5.existsSync(cursorConfigPath) && cursorConfigValid,
1496
2602
  cursorConfigValid ? ".cursor/mcp.json exists and is valid JSON." : ".cursor/mcp.json is missing or invalid.",
1497
2603
  "Run anchor init. If the file is malformed, fix the JSON and rerun anchor init."
1498
2604
  )
@@ -1509,7 +2615,7 @@ async function runDoctor(options) {
1509
2615
  )
1510
2616
  );
1511
2617
  const dbPath = defaultDatabasePath(gitRoot ?? cwd);
1512
- const dbExists = fs3.existsSync(dbPath);
2618
+ const dbExists = fs5.existsSync(dbPath);
1513
2619
  checks.push(
1514
2620
  check(
1515
2621
  ".anchor/index.sqlite exists",
@@ -1553,12 +2659,12 @@ async function runDoctor(options) {
1553
2659
  "Run pnpm build, then try anchor serve from the repository."
1554
2660
  )
1555
2661
  );
1556
- const rulePath = path6.join(gitRoot ?? cwd, ".cursor", "rules", "anchor.mdc");
2662
+ const rulePath = path10.join(gitRoot ?? cwd, ".cursor", "rules", "anchor.mdc");
1557
2663
  checks.push(
1558
2664
  check(
1559
2665
  "Cursor rule file exists",
1560
- fs3.existsSync(rulePath),
1561
- fs3.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.",
1562
2668
  "Run anchor init to create .cursor/rules/anchor.mdc."
1563
2669
  )
1564
2670
  );
@@ -1566,23 +2672,38 @@ async function runDoctor(options) {
1566
2672
  }
1567
2673
  export {
1568
2674
  ANCHOR_CURSOR_RULE,
2675
+ DEFAULT_MAX_CODE_FILE_BYTES,
1569
2676
  SCHEMA_SQL,
2677
+ TEAM_RULES_FILE,
1570
2678
  anchorMcpEntry,
1571
2679
  buildFtsQuery,
1572
2680
  canonicalizeText,
1573
2681
  categorizeWisdom,
1574
2682
  checkSchema,
2683
+ chunkCodeFile,
1575
2684
  chunkHistoricalText,
2685
+ claimKeyFor,
1576
2686
  clampMaxResults,
1577
2687
  clipSentence,
2688
+ confidenceAtLeast,
2689
+ confidenceLevelFor,
2690
+ confidenceRank,
2691
+ confidenceReasonsFor,
2692
+ countValidTeamRules,
1578
2693
  createGitHubClient,
1579
2694
  defaultDatabasePath,
1580
2695
  detectGitHubRepo,
1581
2696
  detectGitRoot,
2697
+ discoverCodeFiles,
2698
+ emptyCodeIndexSummary,
1582
2699
  ensureAnchorGitExclude,
1583
2700
  ensureCursorConfig,
1584
2701
  ensureCursorRule,
1585
2702
  ensureRepository,
2703
+ ensureTeamRulesFile,
2704
+ evaluateFreshness,
2705
+ evidenceForWisdom,
2706
+ extractCodeSymbols,
1586
2707
  extractSymbols,
1587
2708
  extractWisdomUnits,
1588
2709
  fetchMergedPullRequests,
@@ -1594,26 +2715,35 @@ export {
1594
2715
  getLastSyncTime,
1595
2716
  githubAuthFixMessage,
1596
2717
  hasHighSignalLanguage,
2718
+ indexCodebase,
1597
2719
  indexPullRequests,
1598
2720
  initializeSchema,
2721
+ isHardExcludedCodePath,
2722
+ loadCurrentCodeSnapshot,
2723
+ loadTeamRulesFile,
1599
2724
  mergeAnchorMcpConfig,
1600
2725
  normalizePullRequest,
1601
2726
  openAnchorDatabase,
1602
2727
  parseGitHubRemote,
2728
+ rankCodeChunks,
2729
+ rankTeamRules,
1603
2730
  rankWisdomUnits,
1604
2731
  redactSecrets,
1605
2732
  redactedHistoricalText,
2733
+ replaceCodeIndex,
1606
2734
  resolveGitHubToken,
1607
2735
  resolvePullRequestDetailConcurrency,
1608
2736
  resolvePullRequestFetchLimit,
1609
2737
  runDoctor,
1610
2738
  sanitizeHistoricalText,
1611
2739
  shouldSyncSince,
2740
+ sourceTypeLabel,
1612
2741
  stripPromptInjection,
1613
2742
  tokenizeSearchText,
1614
2743
  truncateText,
1615
2744
  uniqueStrings,
1616
2745
  updateSyncState,
1617
- upsertPullRequest
2746
+ upsertPullRequest,
2747
+ validateTeamRulesFile
1618
2748
  };
1619
2749
  //# sourceMappingURL=index.js.map