@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.d.ts +95 -3
- package/dist/index.js +631 -81
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/db/schema.sql +3 -0
package/dist/index.js
CHANGED
|
@@ -252,8 +252,8 @@ function redactedHistoricalText(text) {
|
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
// src/db/database.ts
|
|
255
|
-
import
|
|
256
|
-
import
|
|
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
|
|
766
|
+
return path3.join(cwd, ".anchor", "index.sqlite");
|
|
398
767
|
}
|
|
399
768
|
function openAnchorDatabase(cwd, databasePath = defaultDatabasePath(cwd)) {
|
|
400
|
-
|
|
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
|
|
439
|
-
|
|
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(
|
|
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 (!
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
833
|
-
import
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
923
|
-
const relativeToRoot =
|
|
924
|
-
if (relativeToRoot.startsWith("..") ||
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
1289
|
-
...
|
|
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
|
|
1309
|
-
function
|
|
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:
|
|
1328
|
-
symbols:
|
|
1329
|
-
authors:
|
|
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 =
|
|
1356
|
-
const queryDir =
|
|
1788
|
+
const queryBase = path8.basename(queryFile).toLowerCase();
|
|
1789
|
+
const queryDir = path8.dirname(queryFile).toLowerCase();
|
|
1357
1790
|
for (const unitPath of unitPaths) {
|
|
1358
|
-
const unitBase =
|
|
1359
|
-
const unitDir =
|
|
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))
|
|
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
|
|
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))
|
|
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
|
|
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
|
|
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:
|
|
1421
|
-
textMatch:
|
|
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 =
|
|
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 =
|
|
1470
|
-
return scoreUnit(
|
|
1471
|
-
|
|
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 =
|
|
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
|
|
1492
|
-
function
|
|
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:
|
|
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 =
|
|
1519
|
-
const unitDir =
|
|
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 =
|
|
1524
|
-
const queryDir =
|
|
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
|
|
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
|
|
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 =
|
|
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:
|
|
1628
|
-
textMatch:
|
|
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.
|
|
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", ""
|
|
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(
|
|
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.
|
|
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
|
|
1991
|
-
import
|
|
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 =
|
|
2587
|
+
const cursorConfigPath = path10.join(gitRoot ?? cwd, ".cursor", "mcp.json");
|
|
2053
2588
|
let cursorConfig;
|
|
2054
2589
|
let cursorConfigValid = false;
|
|
2055
|
-
if (
|
|
2590
|
+
if (fs5.existsSync(cursorConfigPath)) {
|
|
2056
2591
|
try {
|
|
2057
|
-
cursorConfig = JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
2132
|
-
|
|
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
|