@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.d.ts +189 -3
- package/dist/index.js +1195 -65
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/db/schema.sql +47 -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
|
|
@@ -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
|
|
766
|
+
return path3.join(cwd, ".anchor", "index.sqlite");
|
|
354
767
|
}
|
|
355
768
|
function openAnchorDatabase(cwd, databasePath = defaultDatabasePath(cwd)) {
|
|
356
|
-
|
|
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
|
-
|
|
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
|
|
393
|
-
|
|
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(
|
|
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 (!
|
|
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(
|
|
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/
|
|
1187
|
+
// src/indexer/code-chunker.ts
|
|
622
1188
|
import crypto from "crypto";
|
|
623
|
-
import
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
901
|
-
...
|
|
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
|
|
921
|
-
function
|
|
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:
|
|
940
|
-
symbols:
|
|
941
|
-
authors:
|
|
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 =
|
|
968
|
-
const queryDir =
|
|
1788
|
+
const queryBase = path8.basename(queryFile).toLowerCase();
|
|
1789
|
+
const queryDir = path8.dirname(queryFile).toLowerCase();
|
|
969
1790
|
for (const unitPath of unitPaths) {
|
|
970
|
-
const unitBase =
|
|
971
|
-
const unitDir =
|
|
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))
|
|
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
|
|
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))
|
|
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
|
|
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
|
|
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:
|
|
1033
|
-
textMatch:
|
|
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 =
|
|
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 =
|
|
1082
|
-
return scoreUnit(
|
|
1083
|
-
|
|
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 =
|
|
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.
|
|
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")
|
|
1129
|
-
|
|
1130
|
-
if (unit.category === "
|
|
1131
|
-
|
|
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", ""
|
|
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(
|
|
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.
|
|
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
|
|
1420
|
-
import
|
|
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 =
|
|
2587
|
+
const cursorConfigPath = path10.join(gitRoot ?? cwd, ".cursor", "mcp.json");
|
|
1482
2588
|
let cursorConfig;
|
|
1483
2589
|
let cursorConfigValid = false;
|
|
1484
|
-
if (
|
|
2590
|
+
if (fs5.existsSync(cursorConfigPath)) {
|
|
1485
2591
|
try {
|
|
1486
|
-
cursorConfig = JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
1561
|
-
|
|
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
|