@coreyuan/vector-mind 1.0.44 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,9 +14,9 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
14
14
  import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
15
15
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
16
16
  import { BUILTIN_CONVENTIONS } from "./builtin-conventions.js";
17
- import { BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS, BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS, BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS, BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS, BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS, BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS, BUILTIN_PLAN_LITE_INSTRUCTIONS, BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS, BUILTIN_WRITE_POLICY_INSTRUCTIONS, } from "./builtin-instructions.js";
17
+ import { BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS, BUILTIN_DESTRUCTIVE_OPERATION_GUARD_INSTRUCTIONS, BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS, BUILTIN_GIT_COMMIT_SUMMARY_INSTRUCTIONS, BUILTIN_LOW_OVERHEAD_WORKFLOW_INSTRUCTIONS, BUILTIN_PAYLOAD_GUARD_INSTRUCTIONS, BUILTIN_PLAN_LITE_INSTRUCTIONS, BUILTIN_REQUIREMENT_BOUNDARY_AND_MODULARITY_INSTRUCTIONS, BUILTIN_THREAD_HANDOFF_SWITCH_INSTRUCTIONS, BUILTIN_WRITE_POLICY_INSTRUCTIONS, } from "./builtin-instructions.js";
18
18
  const SERVER_NAME = "vector-mind";
19
- const SERVER_VERSION = "1.0.44";
19
+ const SERVER_VERSION = "1.0.49";
20
20
  const rootFromEnv = process.env.VECTORMIND_ROOT?.trim() ?? "";
21
21
  const prettyJsonOutput = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_PRETTY_JSON ?? "").trim().toLowerCase());
22
22
  const debugLogEnabled = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_DEBUG_LOG ?? "").trim().toLowerCase());
@@ -65,6 +65,52 @@ const PENDING_PRUNE_EVERY = (() => {
65
65
  return 500;
66
66
  return n;
67
67
  })();
68
+ const DEVELOPMENT_WARN_FILE_LINES = (() => {
69
+ const raw = process.env.VECTORMIND_WARN_FILE_LINES?.trim();
70
+ if (!raw)
71
+ return 800;
72
+ const n = Number.parseInt(raw, 10);
73
+ if (!Number.isFinite(n) || n < 100)
74
+ return 800;
75
+ return Math.min(50_000, n);
76
+ })();
77
+ const DEVELOPMENT_BLOCK_FILE_LINES = (() => {
78
+ const raw = process.env.VECTORMIND_BLOCK_FILE_LINES?.trim();
79
+ if (!raw)
80
+ return 1200;
81
+ const n = Number.parseInt(raw, 10);
82
+ if (!Number.isFinite(n) || n < DEVELOPMENT_WARN_FILE_LINES)
83
+ return Math.max(1200, DEVELOPMENT_WARN_FILE_LINES);
84
+ return Math.min(100_000, n);
85
+ })();
86
+ const DEVELOPMENT_HUGE_FILE_LINES = (() => {
87
+ const raw = process.env.VECTORMIND_HUGE_FILE_LINES?.trim();
88
+ if (!raw)
89
+ return 3000;
90
+ const n = Number.parseInt(raw, 10);
91
+ if (!Number.isFinite(n) || n < DEVELOPMENT_BLOCK_FILE_LINES) {
92
+ return Math.max(3000, DEVELOPMENT_BLOCK_FILE_LINES);
93
+ }
94
+ return Math.min(200_000, n);
95
+ })();
96
+ const DEVELOPMENT_WARN_FILE_BYTES = (() => {
97
+ const raw = process.env.VECTORMIND_WARN_FILE_BYTES?.trim();
98
+ if (!raw)
99
+ return 120_000;
100
+ const n = Number.parseInt(raw, 10);
101
+ if (!Number.isFinite(n) || n < 10_000)
102
+ return 120_000;
103
+ return Math.min(20_000_000, n);
104
+ })();
105
+ const DEVELOPMENT_WARN_PENDING_FILES = (() => {
106
+ const raw = process.env.VECTORMIND_WARN_PENDING_FILES?.trim();
107
+ if (!raw)
108
+ return 12;
109
+ const n = Number.parseInt(raw, 10);
110
+ if (!Number.isFinite(n) || n < 1)
111
+ return 12;
112
+ return Math.min(500, n);
113
+ })();
68
114
  const RIPGREP_RESOLVE_TIMEOUT_MS = 5_000;
69
115
  const RIPGREP_SEARCH_TIMEOUT_MS = 30_000;
70
116
  const RIPGREP_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
@@ -1384,6 +1430,470 @@ function isContentIndexableFile(filePath) {
1384
1430
  return false;
1385
1431
  return getContentChunkKind(filePath) !== null;
1386
1432
  }
1433
+ function isLikelySourceImplementationFile(filePath) {
1434
+ const ext = path.extname(filePath).toLowerCase();
1435
+ return new Set([
1436
+ ".ts",
1437
+ ".tsx",
1438
+ ".js",
1439
+ ".jsx",
1440
+ ".mjs",
1441
+ ".cjs",
1442
+ ".py",
1443
+ ".go",
1444
+ ".rs",
1445
+ ".java",
1446
+ ".kt",
1447
+ ".cs",
1448
+ ".c",
1449
+ ".cc",
1450
+ ".cpp",
1451
+ ".h",
1452
+ ".hpp",
1453
+ ".vue",
1454
+ ".svelte",
1455
+ ]).has(ext);
1456
+ }
1457
+ function countFileLinesBounded(absPath, maxBytes) {
1458
+ let stat;
1459
+ try {
1460
+ stat = fs.statSync(absPath);
1461
+ }
1462
+ catch {
1463
+ return null;
1464
+ }
1465
+ if (!stat.isFile())
1466
+ return null;
1467
+ const bytesToRead = Math.min(stat.size, maxBytes);
1468
+ const fd = fs.openSync(absPath, "r");
1469
+ try {
1470
+ const buffer = Buffer.alloc(bytesToRead);
1471
+ const read = fs.readSync(fd, buffer, 0, bytesToRead, 0);
1472
+ let lines = read > 0 ? 1 : 0;
1473
+ for (let i = 0; i < read; i++) {
1474
+ if (buffer[i] === 10)
1475
+ lines += 1;
1476
+ }
1477
+ return { lines, truncated: stat.size > bytesToRead };
1478
+ }
1479
+ finally {
1480
+ fs.closeSync(fd);
1481
+ }
1482
+ }
1483
+ function isPathInsideProjectRoot(absPath) {
1484
+ const root = path.resolve(projectRoot);
1485
+ const rel = path.relative(root, path.resolve(absPath));
1486
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
1487
+ }
1488
+ function checkPathScope(inputPath) {
1489
+ const normalizedInput = inputPath.trim() || ".";
1490
+ const absPath = path.resolve(path.isAbsolute(normalizedInput) ? normalizedInput : path.join(projectRoot, normalizedInput));
1491
+ return {
1492
+ input_path: inputPath,
1493
+ abs_path: absPath,
1494
+ in_project: isPathInsideProjectRoot(absPath),
1495
+ project_root: path.resolve(projectRoot),
1496
+ };
1497
+ }
1498
+ function buildCrossProjectPathWarnings(paths) {
1499
+ const checks = (paths ?? []).map((p) => checkPathScope(p)).filter((c) => !c.in_project);
1500
+ if (!checks.length)
1501
+ return [];
1502
+ return [
1503
+ {
1504
+ code: "cross_project_path",
1505
+ severity: "warning",
1506
+ message: "A path points outside the current project_root. Switch project_root intentionally before reading/searching another repo; do not mix unrelated project context into the current requirement.",
1507
+ files: checks.slice(0, 10).map((c) => c.input_path),
1508
+ details: {
1509
+ project_root: path.resolve(projectRoot),
1510
+ paths: checks.slice(0, 10),
1511
+ total_paths: checks.length,
1512
+ },
1513
+ },
1514
+ ];
1515
+ }
1516
+ function buildLargeImplementationFileWarning(args) {
1517
+ const linesValue = args.lineCountTruncated ? `${args.lineCount}+` : args.lineCount;
1518
+ if (args.lineCount >= DEVELOPMENT_HUGE_FILE_LINES) {
1519
+ return {
1520
+ code: "huge_file_modularization_required",
1521
+ severity: "blocker",
1522
+ message: "This implementation file is huge. Before any normal feature work, perform mechanical modularization: move whole functions/types/impl blocks into real, clearly named modules/directories, avoid *.generated.* or *.parts files, preserve behavior, then run format/build/tests.",
1523
+ files: [args.filePath],
1524
+ details: {
1525
+ lines: linesValue,
1526
+ bytes: args.bytes,
1527
+ warn_lines: DEVELOPMENT_WARN_FILE_LINES,
1528
+ block_lines: DEVELOPMENT_BLOCK_FILE_LINES,
1529
+ huge_lines: DEVELOPMENT_HUGE_FILE_LINES,
1530
+ required_action: "mechanical_modularization",
1531
+ allowed_change_modes: ["mechanical_modularization", "emergency_hotfix"],
1532
+ forbidden_file_patterns: ["*.generated.*", "*.parts", "*.rs.parts", "*_part*"],
1533
+ mechanical_rules: [
1534
+ "move whole declarations only",
1535
+ "use real module names and clear directory boundaries",
1536
+ "preserve behavior and public semantics",
1537
+ "only add necessary mod/use/pub(crate)/re-export glue",
1538
+ "run formatter, build, and tests after each phase",
1539
+ ],
1540
+ reading: !!args.reading,
1541
+ },
1542
+ };
1543
+ }
1544
+ return {
1545
+ code: args.code,
1546
+ severity: args.code === "large_file" || (args.code === "large_file_read" && args.lineCount < DEVELOPMENT_BLOCK_FILE_LINES)
1547
+ ? "warning"
1548
+ : "blocker",
1549
+ message: args.code === "large_file"
1550
+ ? "This implementation file is getting large. Prefer extracting focused modules instead of continuing to pile unrelated responsibilities into it."
1551
+ : args.reading
1552
+ ? "You are reading a very large implementation file. Do not keep patching new feature code into it; identify a narrow function and split new behavior into focused modules unless this task is explicitly a planned extraction."
1553
+ : "This implementation file is already very large. Do not add new feature code here by default; split into a focused module/service/component and keep this file as a thin entry.",
1554
+ files: [args.filePath],
1555
+ details: {
1556
+ lines: linesValue,
1557
+ bytes: args.bytes,
1558
+ warn_lines: DEVELOPMENT_WARN_FILE_LINES,
1559
+ block_lines: DEVELOPMENT_BLOCK_FILE_LINES,
1560
+ huge_lines: DEVELOPMENT_HUGE_FILE_LINES,
1561
+ },
1562
+ };
1563
+ }
1564
+ function buildFileReadDevelopmentWarnings(filePath, absPath, stat) {
1565
+ const warnings = [];
1566
+ if (!isPathInsideProjectRoot(absPath)) {
1567
+ warnings.push(...buildCrossProjectPathWarnings([filePath]));
1568
+ return warnings;
1569
+ }
1570
+ if (!isLikelySourceImplementationFile(filePath))
1571
+ return warnings;
1572
+ let st = stat;
1573
+ try {
1574
+ st ??= fs.statSync(absPath);
1575
+ }
1576
+ catch {
1577
+ return warnings;
1578
+ }
1579
+ if (!st.isFile())
1580
+ return warnings;
1581
+ const lineInfo = countFileLinesBounded(absPath, 2_000_000);
1582
+ const lineCount = lineInfo?.lines ?? 0;
1583
+ const tooManyLines = lineCount >= DEVELOPMENT_BLOCK_FILE_LINES;
1584
+ const warnLines = lineCount >= DEVELOPMENT_WARN_FILE_LINES;
1585
+ const warnBytes = st.size >= DEVELOPMENT_WARN_FILE_BYTES;
1586
+ if (!tooManyLines && !warnLines && !warnBytes)
1587
+ return warnings;
1588
+ warnings.push(buildLargeImplementationFileWarning({
1589
+ code: "large_file_read",
1590
+ filePath,
1591
+ lineCount,
1592
+ lineCountTruncated: lineInfo?.truncated,
1593
+ bytes: st.size,
1594
+ reading: true,
1595
+ }));
1596
+ return warnings;
1597
+ }
1598
+ function buildMatchedFileDevelopmentWarnings(filePaths) {
1599
+ const seen = new Set();
1600
+ const warnings = [];
1601
+ for (const fp of filePaths) {
1602
+ if (!fp || seen.has(fp))
1603
+ continue;
1604
+ seen.add(fp);
1605
+ const abs = path.isAbsolute(fp) ? path.resolve(fp) : path.join(projectRoot, fp);
1606
+ warnings.push(...buildFileReadDevelopmentWarnings(normalizeToDbPath(fp), abs));
1607
+ if (warnings.length >= 8)
1608
+ break;
1609
+ }
1610
+ return warnings;
1611
+ }
1612
+ function buildRequirementStartWarnings(args) {
1613
+ const warnings = [];
1614
+ const activeReqs = listActiveRequirementsStmt.all(10);
1615
+ if (!args.close_previous && activeReqs.length > 0) {
1616
+ warnings.push({
1617
+ code: "multiple_active_requirements",
1618
+ severity: "warning",
1619
+ message: "Starting a requirement without closing previous active requirements can mix unrelated context. Only keep multiple active requirements when the user explicitly asked for parallel work.",
1620
+ details: {
1621
+ active_requirements: activeReqs.slice(0, 5).map((r) => ({ id: r.id, title: r.title, status: r.status })),
1622
+ },
1623
+ });
1624
+ }
1625
+ const text = `${args.title}\n${args.background}`.toLowerCase();
1626
+ const broadTerms = [
1627
+ "顺便",
1628
+ "一起",
1629
+ "所有",
1630
+ "全部",
1631
+ "整体",
1632
+ "重构",
1633
+ "统一",
1634
+ "优化一下",
1635
+ "顺手",
1636
+ "相关的",
1637
+ "all ",
1638
+ "everything",
1639
+ "refactor",
1640
+ "cleanup",
1641
+ "clean up",
1642
+ ];
1643
+ const matched = broadTerms.filter((term) => text.includes(term));
1644
+ if (matched.length >= 2 || text.length > 1800) {
1645
+ warnings.push({
1646
+ code: "broad_requirement_scope",
1647
+ severity: "warning",
1648
+ message: "The requirement wording looks broad. Treat the current user request as the only boundary; do not add extra workflows, fields, pages, interfaces, or touch completed related features unless explicitly required.",
1649
+ details: { matched_terms: matched.slice(0, 10), text_length: text.length },
1650
+ });
1651
+ }
1652
+ return warnings;
1653
+ }
1654
+ function normalizeScopeTerms(values) {
1655
+ return Array.from(new Set((values ?? []).map((v) => v.trim()).filter(Boolean)));
1656
+ }
1657
+ function buildRequirementScopeContract(args) {
1658
+ const allowTerms = normalizeScopeTerms(args.scope_allow);
1659
+ const denyTerms = normalizeScopeTerms(args.scope_deny);
1660
+ const allowedPaths = normalizeScopeTerms(args.allowed_paths).map(normalizeToDbPath);
1661
+ const deniedPaths = normalizeScopeTerms(args.denied_paths).map((p) => p.replace(/\\/g, "/"));
1662
+ return {
1663
+ allow_terms: allowTerms,
1664
+ deny_terms: Array.from(new Set(denyTerms)),
1665
+ allowed_paths: Array.from(new Set(allowedPaths)),
1666
+ denied_paths: Array.from(new Set(deniedPaths)),
1667
+ inferred_from: [],
1668
+ };
1669
+ }
1670
+ function getRequirementScopeContract(reqId) {
1671
+ const memId = getRequirementMemoryItemIdStmt.get(reqId)?.id;
1672
+ if (memId == null)
1673
+ return null;
1674
+ const row = getMemoryItemByIdStmt.get(memId);
1675
+ const meta = parseMetadataJson(row?.metadata_json);
1676
+ const raw = meta.scope_contract;
1677
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1678
+ return null;
1679
+ const obj = raw;
1680
+ return {
1681
+ allow_terms: Array.isArray(obj.allow_terms) ? obj.allow_terms.filter((v) => typeof v === "string") : [],
1682
+ deny_terms: Array.isArray(obj.deny_terms) ? obj.deny_terms.filter((v) => typeof v === "string") : [],
1683
+ allowed_paths: Array.isArray(obj.allowed_paths) ? obj.allowed_paths.filter((v) => typeof v === "string") : [],
1684
+ denied_paths: Array.isArray(obj.denied_paths) ? obj.denied_paths.filter((v) => typeof v === "string") : [],
1685
+ inferred_from: Array.isArray(obj.inferred_from) ? obj.inferred_from.filter((v) => typeof v === "string") : [],
1686
+ };
1687
+ }
1688
+ function wildcardToRegex(pattern) {
1689
+ const source = escapeRegExp(pattern).replace(/\\\*/g, ".*");
1690
+ return new RegExp(source, "i");
1691
+ }
1692
+ function pathMatchesAnyPattern(filePath, patterns) {
1693
+ const normalized = filePath.replace(/\\/g, "/");
1694
+ return patterns.filter((p) => wildcardToRegex(p.replace(/\\/g, "/")).test(normalized));
1695
+ }
1696
+ function fileContentHasDeniedTerms(filePath, terms) {
1697
+ if (!terms.length || filePath === "(unspecified)")
1698
+ return [];
1699
+ const abs = path.isAbsolute(filePath) ? path.resolve(filePath) : path.join(projectRoot, filePath);
1700
+ let st;
1701
+ try {
1702
+ st = fs.statSync(abs);
1703
+ }
1704
+ catch {
1705
+ return [];
1706
+ }
1707
+ if (!st.isFile() || st.size > 2_000_000)
1708
+ return [];
1709
+ let content = "";
1710
+ try {
1711
+ content = fs.readFileSync(abs, "utf8");
1712
+ }
1713
+ catch {
1714
+ return [];
1715
+ }
1716
+ const lower = `${filePath}\n${content.slice(0, 250_000)}`.toLowerCase();
1717
+ return terms.filter((term) => lower.includes(term.toLowerCase()));
1718
+ }
1719
+ function mergeScopeContracts(base, extra) {
1720
+ if (!base && !extra)
1721
+ return null;
1722
+ return {
1723
+ allow_terms: Array.from(new Set([...(base?.allow_terms ?? []), ...(extra?.allow_terms ?? [])])),
1724
+ deny_terms: Array.from(new Set([...(base?.deny_terms ?? []), ...(extra?.deny_terms ?? [])])),
1725
+ allowed_paths: Array.from(new Set([...(base?.allowed_paths ?? []), ...(extra?.allowed_paths ?? [])])),
1726
+ denied_paths: Array.from(new Set([...(base?.denied_paths ?? []), ...(extra?.denied_paths ?? [])])),
1727
+ inferred_from: Array.from(new Set([...(base?.inferred_from ?? []), ...(extra?.inferred_from ?? [])])),
1728
+ };
1729
+ }
1730
+ function buildScopeDriftWarnings(args) {
1731
+ const requirementContract = args.requirement ? getRequirementScopeContract(args.requirement.id) : null;
1732
+ const contract = mergeScopeContracts(requirementContract, args.contract);
1733
+ const hasScopeRules = !!contract && (contract.allow_terms.length > 0 ||
1734
+ contract.deny_terms.length > 0 ||
1735
+ contract.allowed_paths.length > 0 ||
1736
+ contract.denied_paths.length > 0);
1737
+ const warnings = [];
1738
+ if (!contract || !hasScopeRules) {
1739
+ if (args.includeMissingContractHint && args.files.length > 0) {
1740
+ warnings.push({
1741
+ code: "scope_contract_missing",
1742
+ severity: "warning",
1743
+ message: "No explicit scope allow/deny contract is set for this requirement, so the planned files cannot be proven in-scope before editing. Define scope_allow/scope_deny or allowed_paths/denied_paths before editing.",
1744
+ files: args.files.map((f) => normalizeToDbPath(f.file_path)).slice(0, 12),
1745
+ details: {
1746
+ requirement_id: args.requirement?.id ?? null,
1747
+ requirement_title: args.requirement?.title ?? null,
1748
+ },
1749
+ });
1750
+ }
1751
+ return warnings;
1752
+ }
1753
+ const allowTerms = contract.allow_terms;
1754
+ const denyTerms = contract.deny_terms;
1755
+ const allowedPaths = contract.allowed_paths;
1756
+ const deniedPaths = contract.denied_paths;
1757
+ const intentText = args.intent ?? "";
1758
+ const intentDenied = denyTerms.filter((term) => intentText.toLowerCase().includes(term.toLowerCase()));
1759
+ const suspicious = [];
1760
+ for (const f of args.files) {
1761
+ const fp = normalizeToDbPath(f.file_path);
1762
+ if (fp === "(unspecified)")
1763
+ continue;
1764
+ const matchedDeniedPaths = pathMatchesAnyPattern(fp, deniedPaths);
1765
+ const matchedDeniedTerms = [
1766
+ ...denyTerms.filter((term) => fp.toLowerCase().includes(term.toLowerCase())),
1767
+ ...fileContentHasDeniedTerms(fp, denyTerms),
1768
+ ];
1769
+ const isExplicitlyAllowed = pathMatchesAnyPattern(fp, allowedPaths).length > 0 ||
1770
+ allowTerms.some((term) => fp.toLowerCase().includes(term.toLowerCase()));
1771
+ const violatesAllowedPaths = allowedPaths.length > 0 && pathMatchesAnyPattern(fp, allowedPaths).length === 0;
1772
+ if ((matchedDeniedPaths.length || matchedDeniedTerms.length || intentDenied.length || violatesAllowedPaths) &&
1773
+ !isExplicitlyAllowed) {
1774
+ suspicious.push({
1775
+ file_path: fp,
1776
+ matched_terms: Array.from(new Set([...matchedDeniedTerms, ...intentDenied])).slice(0, 12),
1777
+ matched_paths: [
1778
+ ...matchedDeniedPaths.slice(0, 12),
1779
+ ...(violatesAllowedPaths ? [`outside allowed_paths: ${allowedPaths.slice(0, 5).join(", ")}`] : []),
1780
+ ],
1781
+ });
1782
+ }
1783
+ }
1784
+ if (suspicious.length) {
1785
+ warnings.push({
1786
+ code: "scope_drift",
1787
+ severity: "blocker",
1788
+ message: "The current requirement appears to be touching a denied or out-of-scope domain. Stop and narrow the change unless the user explicitly expanded this requirement.",
1789
+ files: suspicious.slice(0, 12).map((s) => s.file_path),
1790
+ details: {
1791
+ requirement_id: args.requirement?.id ?? null,
1792
+ requirement_title: args.requirement?.title ?? null,
1793
+ inferred_from: contract.inferred_from,
1794
+ deny_terms: denyTerms.slice(0, 30),
1795
+ denied_paths: deniedPaths.slice(0, 30),
1796
+ suspicious: suspicious.slice(0, 12),
1797
+ },
1798
+ });
1799
+ }
1800
+ return warnings;
1801
+ }
1802
+ function buildDevelopmentWarnings(files, opts = {}) {
1803
+ const warnings = [];
1804
+ const uniqueFiles = Array.from(new Set(files
1805
+ .map((f) => f.file_path)
1806
+ .filter((f) => !!f && f !== "(unspecified)")
1807
+ .map((f) => normalizeToDbPath(f))));
1808
+ if (opts.includeUnspecified || files.some((f) => f.file_path === "(unspecified)")) {
1809
+ warnings.push({
1810
+ code: "unspecified_change_target",
1811
+ severity: "warning",
1812
+ message: "No changed file target was captured. For development work, sync concrete files so the current requirement owns only its real changes.",
1813
+ });
1814
+ }
1815
+ if (uniqueFiles.length >= DEVELOPMENT_WARN_PENDING_FILES) {
1816
+ warnings.push({
1817
+ code: "many_pending_files",
1818
+ severity: "warning",
1819
+ message: "This requirement touches many files. Re-check the user request and keep only files required by the current requirement.",
1820
+ files: uniqueFiles.slice(0, 20),
1821
+ details: { total_files: uniqueFiles.length, threshold: DEVELOPMENT_WARN_PENDING_FILES },
1822
+ });
1823
+ }
1824
+ const topDirs = new Set(uniqueFiles
1825
+ .map((f) => f.replace(/\\/g, "/").split("/").filter(Boolean)[0] ?? "")
1826
+ .filter(Boolean));
1827
+ if (uniqueFiles.length >= 6 && topDirs.size >= 4) {
1828
+ warnings.push({
1829
+ code: "broad_change_surface",
1830
+ severity: "warning",
1831
+ message: "Changed files span several top-level areas. Avoid modifying completed or merely related features unless the current requirement explicitly needs it.",
1832
+ files: uniqueFiles.slice(0, 20),
1833
+ details: { top_level_dirs: Array.from(topDirs).slice(0, 12), total_dirs: topDirs.size },
1834
+ });
1835
+ }
1836
+ for (const relPath of uniqueFiles) {
1837
+ if (!isLikelySourceImplementationFile(relPath))
1838
+ continue;
1839
+ const absPath = path.isAbsolute(relPath) ? relPath : path.join(projectRoot, relPath);
1840
+ let stat;
1841
+ try {
1842
+ stat = fs.statSync(absPath);
1843
+ }
1844
+ catch {
1845
+ continue;
1846
+ }
1847
+ if (!stat.isFile())
1848
+ continue;
1849
+ const lineInfo = countFileLinesBounded(absPath, 2_000_000);
1850
+ const lineCount = lineInfo?.lines ?? 0;
1851
+ const tooManyLines = lineCount >= DEVELOPMENT_BLOCK_FILE_LINES;
1852
+ const warnLines = lineCount >= DEVELOPMENT_WARN_FILE_LINES;
1853
+ const warnBytes = stat.size >= DEVELOPMENT_WARN_FILE_BYTES;
1854
+ if (!tooManyLines && !warnLines && !warnBytes)
1855
+ continue;
1856
+ warnings.push(buildLargeImplementationFileWarning({
1857
+ code: tooManyLines ? "very_large_file" : "large_file",
1858
+ filePath: relPath,
1859
+ lineCount,
1860
+ lineCountTruncated: lineInfo?.truncated,
1861
+ bytes: stat.size,
1862
+ }));
1863
+ }
1864
+ return warnings;
1865
+ }
1866
+ function isLargeFileWarningCode(code) {
1867
+ return (code === "large_file" ||
1868
+ code === "very_large_file" ||
1869
+ code === "large_file_read" ||
1870
+ code === "huge_file_modularization_required");
1871
+ }
1872
+ function isDevelopmentWarningBlockingForChangeMode(warning, changeMode) {
1873
+ if (changeMode === "mechanical_modularization") {
1874
+ if (isLargeFileWarningCode(warning.code))
1875
+ return false;
1876
+ if (warning.code === "scope_contract_missing")
1877
+ return false;
1878
+ }
1879
+ if (changeMode === "emergency_hotfix") {
1880
+ if (isLargeFileWarningCode(warning.code))
1881
+ return false;
1882
+ if (warning.code === "scope_contract_missing")
1883
+ return false;
1884
+ }
1885
+ return warning.severity === "blocker" || warning.severity === "warning";
1886
+ }
1887
+ function compactDevelopmentWarningsText(warnings) {
1888
+ if (!warnings.length)
1889
+ return [];
1890
+ const lines = ["development warnings:"];
1891
+ for (const w of warnings.slice(0, 8)) {
1892
+ const files = w.files?.length ? ` files=${w.files.slice(0, 5).join(",")}` : "";
1893
+ lines.push(`- ${w.severity} ${w.code}: ${oneLine(w.message, 180)}${files}`);
1894
+ }
1895
+ return lines;
1896
+ }
1387
1897
  function extractSymbols(filePath, content) {
1388
1898
  const ext = path.extname(filePath).toLowerCase();
1389
1899
  if (ext === ".py")
@@ -1541,6 +2051,142 @@ function extractCLikeSymbols(content) {
1541
2051
  }
1542
2052
  return symbols;
1543
2053
  }
2054
+ function declarationRegexForExtension(ext) {
2055
+ switch (ext) {
2056
+ case ".rs":
2057
+ return /^\s*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:fn|struct|enum|trait|impl|mod|type|const|static)\s+([A-Za-z_][\w]*)?/;
2058
+ case ".go":
2059
+ return /^\s*(?:func|type|const|var)\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)?/;
2060
+ case ".py":
2061
+ return /^(?:class|async\s+def|def)\s+([A-Za-z_][\w]*)/;
2062
+ case ".ts":
2063
+ case ".tsx":
2064
+ case ".js":
2065
+ case ".jsx":
2066
+ case ".mjs":
2067
+ case ".cjs":
2068
+ return /^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|enum|const|let|var)\s+([A-Za-z_$][\w$]*)?/;
2069
+ default:
2070
+ return /^\s*(?:pub\s+)?(?:async\s+)?(?:fn|function|class|struct|enum|interface|type|const|static)\s+([A-Za-z_][\w]*)?/;
2071
+ }
2072
+ }
2073
+ function moduleNameFromDeclaration(name, signature) {
2074
+ const text = `${name} ${signature}`.toLowerCase();
2075
+ const rules = [
2076
+ [/(config|setting|env)/, "config"],
2077
+ [/(state|store|persist)/, "state"],
2078
+ [/(api|client|request|response|heartbeat|activate|sync)/, "api"],
2079
+ [/(service|daemon|install|start|stop)/, "service"],
2080
+ [/(log|logger|redact)/, "logging"],
2081
+ [/(gui|window|dialog|form|view|button|list)/, "ui"],
2082
+ [/(share|disk|smb|unc|folder|directory)/, "share"],
2083
+ [/(repair|cleanup|probe|health)/, "maintenance"],
2084
+ [/(path|sanitize|normalize|host|ip|util|helper)/, "util"],
2085
+ [/(test|mock|fixture)/, "tests"],
2086
+ ];
2087
+ for (const [pattern, moduleName] of rules) {
2088
+ if (pattern.test(text))
2089
+ return moduleName;
2090
+ }
2091
+ return "core";
2092
+ }
2093
+ function topLevelDeclarationsForPlan(content, ext, maxDecls = 160) {
2094
+ const decls = [];
2095
+ const lines = content.split(/\r?\n/);
2096
+ const regex = declarationRegexForExtension(ext);
2097
+ for (let i = 0; i < lines.length && decls.length < maxDecls; i++) {
2098
+ const raw = lines[i];
2099
+ const trimmed = raw.trim();
2100
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#"))
2101
+ continue;
2102
+ const m = raw.match(regex);
2103
+ if (!m)
2104
+ continue;
2105
+ const fallback = trimmed.split(/\s+/).slice(0, 3).join("_").replace(/[^\w$]+/g, "_");
2106
+ const name = (m[1] || fallback || `declaration_${i + 1}`).replace(/^[^A-Za-z_]+/, "") || `declaration_${i + 1}`;
2107
+ const kind = trimmed.split(/\s+/).find((part) => ["fn", "function", "class", "struct", "enum", "trait", "impl", "mod", "type", "const", "static", "interface"].includes(part.replace(/[({].*$/, ""))) ?? "declaration";
2108
+ decls.push({
2109
+ line: i + 1,
2110
+ kind,
2111
+ name,
2112
+ signature: oneLine(trimmed, 180),
2113
+ suggested_module: moduleNameFromDeclaration(name, trimmed),
2114
+ });
2115
+ }
2116
+ return decls;
2117
+ }
2118
+ function targetPathForModule(originalFilePath, targetDir, moduleName) {
2119
+ const ext = path.extname(originalFilePath) || ".txt";
2120
+ const normalizedTargetDir = targetDir.replace(/\\/g, "/").replace(/\/+$/, "");
2121
+ if (!normalizedTargetDir || normalizedTargetDir === ".")
2122
+ return `${moduleName}${ext}`;
2123
+ return `${normalizedTargetDir}/${moduleName}${ext}`;
2124
+ }
2125
+ function buildLargeFileSplitPlan(args) {
2126
+ const content = fs.readFileSync(args.absPath, "utf8");
2127
+ const st = fs.statSync(args.absPath);
2128
+ const lines = content.split(/\r?\n/).length;
2129
+ const ext = path.extname(args.absPath).toLowerCase();
2130
+ const baseName = path.basename(args.filePath, path.extname(args.filePath));
2131
+ const parentDir = path.dirname(args.filePath).replace(/\\/g, "/");
2132
+ const defaultTargetDir = parentDir === "." ? baseName : `${parentDir}/${baseName}`;
2133
+ const targetDir = normalizeToDbPath(args.targetDir ?? defaultTargetDir);
2134
+ const declarations = topLevelDeclarationsForPlan(content, ext);
2135
+ const grouped = new Map();
2136
+ for (const decl of declarations) {
2137
+ const key = grouped.size >= args.maxModules && !grouped.has(decl.suggested_module) ? "core" : decl.suggested_module;
2138
+ const list = grouped.get(key) ?? [];
2139
+ list.push(decl);
2140
+ grouped.set(key, list);
2141
+ }
2142
+ if (!grouped.size)
2143
+ grouped.set("core", []);
2144
+ const modules = Array.from(grouped.entries())
2145
+ .slice(0, args.maxModules)
2146
+ .map(([moduleName, decls]) => ({
2147
+ module: moduleName,
2148
+ target_path: targetPathForModule(args.filePath, targetDir, moduleName),
2149
+ declarations: decls.slice(0, 24).map((d) => `${d.kind} ${d.name} @L${d.line}`),
2150
+ reason: `Move whole ${moduleName}-related declarations together without changing behavior.`,
2151
+ }));
2152
+ return {
2153
+ ok: true,
2154
+ file_path: args.filePath,
2155
+ line_count: lines,
2156
+ bytes: st.size,
2157
+ huge_threshold_lines: DEVELOPMENT_HUGE_FILE_LINES,
2158
+ required_action: "mechanical_modularization",
2159
+ intent: args.intent,
2160
+ target_dir: targetDir,
2161
+ forbidden_patterns: ["*.generated.*", "*.parts", "*.rs.parts", "*_part*", "*Part*"],
2162
+ mechanical_rules: [
2163
+ "Move only complete declarations/impl blocks/functions/classes/types; do not split a declaration body.",
2164
+ "Use real module names and clear directory boundaries; do not create generated/parts/partN files.",
2165
+ "Preserve behavior, names, API semantics, data formats, side effects, and test expectations.",
2166
+ "Only add necessary module declarations, imports, pub(crate), and re-exports to make moved code compile.",
2167
+ "Run formatter, build/check, and relevant tests after each small phase.",
2168
+ ],
2169
+ modules,
2170
+ steps: [
2171
+ "Create a dedicated mechanical modularization requirement before touching the huge file.",
2172
+ "Add the target module directory and move one cohesive declaration group at a time.",
2173
+ "Keep the original file as a thin entry/mod orchestration file where possible.",
2174
+ "After each group, run formatter and the smallest available compile/test command.",
2175
+ "Record the split with record_large_file_split(status='partial' or 'resolved').",
2176
+ "Resume the original feature only after the target huge file is no longer the default place for new code.",
2177
+ ],
2178
+ validation: [
2179
+ "No *.generated.*, *.parts, *.rs.parts, or numbered part files were created.",
2180
+ "The original file line count decreased or contains only thin orchestration glue.",
2181
+ "Formatter passes.",
2182
+ "Build/check passes.",
2183
+ "Relevant tests pass.",
2184
+ ],
2185
+ notes: declarations.length
2186
+ ? [`Detected ${declarations.length} top-level declarations for mechanical grouping.`]
2187
+ : ["No top-level declarations were detected by lightweight scanning; split by obvious cohesive sections and verify after each move."],
2188
+ };
2189
+ }
1544
2190
  function chunkTextByLines(content, opts) {
1545
2191
  const lines = content.split(/\r?\n/);
1546
2192
  if (lines.length === 0)
@@ -1744,12 +2390,42 @@ const StartRequirementArgsSchema = ProjectRootArgSchema.merge(z.object({
1744
2390
  title: z.string().min(1),
1745
2391
  background: z.string().optional().default(""),
1746
2392
  close_previous: z.boolean().optional().default(true),
2393
+ scope_allow: z.array(z.string().min(1)).optional(),
2394
+ scope_deny: z.array(z.string().min(1)).optional(),
2395
+ allowed_paths: z.array(z.string().min(1)).optional(),
2396
+ denied_paths: z.array(z.string().min(1)).optional(),
1747
2397
  }));
1748
2398
  const SyncChangeIntentArgsSchema = ProjectRootArgSchema.merge(z.object({
1749
2399
  intent: z.string().min(1),
1750
2400
  files: z.array(z.string().min(1)).optional(),
1751
2401
  affected_files: z.array(z.string().min(1)).optional(),
1752
2402
  }));
2403
+ const PreflightChangeScopeArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
2404
+ intent: z.string().optional().default(""),
2405
+ files: z.array(z.string().min(1)).optional(),
2406
+ planned_files: z.array(z.string().min(1)).optional(),
2407
+ change_mode: z
2408
+ .enum(["feature", "bugfix", "refactor", "mechanical_modularization", "emergency_hotfix"])
2409
+ .optional()
2410
+ .default("feature"),
2411
+ scope_allow: z.array(z.string().min(1)).optional(),
2412
+ scope_deny: z.array(z.string().min(1)).optional(),
2413
+ allowed_paths: z.array(z.string().min(1)).optional(),
2414
+ denied_paths: z.array(z.string().min(1)).optional(),
2415
+ }));
2416
+ const PlanLargeFileSplitArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
2417
+ file: z.string().min(1),
2418
+ intent: z.string().optional().default("mechanical modularization"),
2419
+ target_dir: z.string().optional(),
2420
+ max_modules: z.number().int().min(2).max(30).optional().default(12),
2421
+ }));
2422
+ const RecordLargeFileSplitArgsSchema = ProjectRootArgSchema.merge(z.object({
2423
+ file: z.string().min(1),
2424
+ status: z.enum(["planned", "in_progress", "partial", "resolved"]),
2425
+ summary: z.string().min(1),
2426
+ modules: z.array(z.string().min(1)).optional(),
2427
+ remaining_lines: z.number().int().min(0).optional(),
2428
+ }));
1753
2429
  const QueryCodebaseArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1754
2430
  query: z.string().min(1),
1755
2431
  }));
@@ -2079,6 +2755,7 @@ function compactGrepText(data) {
2079
2755
  const lines = [
2080
2756
  `grep ${data.backend}${fallback} mode=${data.mode} matches=${data.matches.length}/${total} truncated=${data.truncated}${candidateText} q="${oneLine(data.query, 100)}"`,
2081
2757
  ];
2758
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings ?? []));
2082
2759
  if (data.ripgrep_error)
2083
2760
  lines.push(`ripgrep_error ${oneLine(data.ripgrep_error, 180)}`);
2084
2761
  for (const m of data.matches.slice(0, 80)) {
@@ -2108,15 +2785,18 @@ function compactReadTextFileText(data) {
2108
2785
  const offset = data.offset != null ? ` offset=${data.offset}` : "";
2109
2786
  const header = `file ${data.file_path}${offset} chars=${data.returned_chars}/${data.total_chars} truncated=${data.truncated}`;
2110
2787
  const hint = data.truncated ? "\nhint: continue with offset or read_file_lines; use format=json for metadata fields" : "";
2111
- return `${header}\n${data.text}${hint}`;
2788
+ const warnings = compactDevelopmentWarningsText(data.development_warnings ?? []).join("\n");
2789
+ return `${header}${warnings ? `\n${warnings}` : ""}\n${data.text}${hint}`;
2112
2790
  }
2113
2791
  function compactReadFileLinesText(data) {
2114
2792
  const header = `lines ${data.file_path}:${data.from_line}-${data.to_line} returned=${data.returned} truncated=${data.truncated}`;
2115
2793
  const hint = data.truncated ? "\nhint: narrow range or raise max_lines/max_chars; use format=json for metadata fields" : "";
2116
- return `${header}\n${data.text}${hint}`;
2794
+ const warnings = compactDevelopmentWarningsText(data.development_warnings ?? []).join("\n");
2795
+ return `${header}${warnings ? `\n${warnings}` : ""}\n${data.text}${hint}`;
2117
2796
  }
2118
2797
  function compactQueryCodebaseText(data) {
2119
2798
  const lines = [`query_codebase matches=${data.matches.length} q="${oneLine(data.query, 100)}"`];
2799
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings ?? []));
2120
2800
  for (const m of data.matches.slice(0, 50)) {
2121
2801
  lines.push(`${m.file_path}: ${m.type} ${m.name}${m.signature ? ` — ${oneLine(m.signature, 160)}` : ""}`);
2122
2802
  }
@@ -2151,6 +2831,44 @@ function compactMaintenanceText(data) {
2151
2831
  lines.push("hint: dry_run=false applies changes; vacuum=true reclaims sqlite file space after pruning");
2152
2832
  return lines.join("\n");
2153
2833
  }
2834
+ function compactPreflightChangeScopeText(data) {
2835
+ const req = data.active_requirement ? `#${data.active_requirement.id} ${data.active_requirement.title}` : "none";
2836
+ const lines = [
2837
+ `preflight_change_scope ok=${data.ok} safe_to_edit=${data.safe_to_edit} mode=${data.change_mode} requirement=${req} files=${data.files.length} intent="${oneLine(data.intent, 120)}"`,
2838
+ `action: ${oneLine(data.recommended_action, 180)}`,
2839
+ ];
2840
+ if (data.required_action)
2841
+ lines.push(`required_action=${data.required_action}`);
2842
+ if (data.allowed_change_modes?.length)
2843
+ lines.push(`allowed_change_modes=${data.allowed_change_modes.join(",")}`);
2844
+ if (data.scope_contract) {
2845
+ lines.push(`scope allow_terms=${data.scope_contract.allow_terms.length} deny_terms=${data.scope_contract.deny_terms.length} allowed_paths=${data.scope_contract.allowed_paths.length} denied_paths=${data.scope_contract.denied_paths.length}`);
2846
+ }
2847
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings));
2848
+ if (!data.development_warnings.length)
2849
+ lines.push("- no development warnings");
2850
+ return lines.join("\n");
2851
+ }
2852
+ function compactLargeFileSplitPlanText(data) {
2853
+ const lines = [
2854
+ `large_file_split ok=${data.ok} file=${data.file_path} lines=${data.line_count} threshold=${data.huge_threshold_lines} action=${data.required_action}`,
2855
+ `target_dir=${data.target_dir}`,
2856
+ `forbidden=${data.forbidden_patterns.join(",")}`,
2857
+ ];
2858
+ lines.push("modules:");
2859
+ for (const m of data.modules.slice(0, 20)) {
2860
+ const decls = m.declarations.length ? ` decls=${m.declarations.slice(0, 8).join("; ")}` : " decls=(manual sections)";
2861
+ lines.push(`- ${m.module} -> ${m.target_path}${decls}`);
2862
+ }
2863
+ lines.push("steps:");
2864
+ for (const step of data.steps.slice(0, 8))
2865
+ lines.push(`- ${step}`);
2866
+ lines.push("validation:");
2867
+ for (const v of data.validation.slice(0, 8))
2868
+ lines.push(`- ${v}`);
2869
+ lines.push("hint: use format=json for full declarations/rules");
2870
+ return lines.join("\n");
2871
+ }
2154
2872
  function compactBootstrapText(data) {
2155
2873
  const lines = [];
2156
2874
  lines.push(`ok ctx ${data.root_source} watcher=${data.watcher_enabled ? (data.watcher_ready ? "ready" : "starting") : "off"} root=${data.project_root}`);
@@ -2175,6 +2893,7 @@ function compactBootstrapText(data) {
2175
2893
  else {
2176
2894
  lines.push("pending 0");
2177
2895
  }
2896
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings ?? []));
2178
2897
  if (data.items.length) {
2179
2898
  lines.push("requirements:");
2180
2899
  for (const item of data.items) {
@@ -4285,6 +5004,9 @@ function buildServerInstructions() {
4285
5004
  "Built-in architecture and code-organization quality policy:",
4286
5005
  BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS,
4287
5006
  "",
5007
+ "Built-in requirement boundary and modularity quality policy:",
5008
+ BUILTIN_REQUIREMENT_BOUNDARY_AND_MODULARITY_INSTRUCTIONS,
5009
+ "",
4288
5010
  "Built-in frontend output-purity quality policy:",
4289
5011
  BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS,
4290
5012
  "",
@@ -4315,7 +5037,13 @@ function buildServerInstructions() {
4315
5037
  "- For raw repo text search with exact file+line+col matches, prefer grep({ query: <pattern> }). It uses ripgrep against real project files when available, applies built-in noise filters, and only falls back to indexed search if ripgrep is unavailable.",
4316
5038
  "- To read a bounded segment of a file, prefer read_file_lines({ path: <file>, from_line/to_line or total_count }) over unbounded file reads.",
4317
5039
  "- BEFORE editing code: call start_requirement(title, background) to set the active requirement.",
5040
+ " - When the user names a narrow feature/domain, pass scope_allow/allowed_paths and scope_deny/denied_paths when useful. These are generic project-specific scope boundaries, not business-specific built-ins.",
5041
+ "- BEFORE editing once target files/modules are known: call preflight_change_scope(intent, files/planned_files, optional scope_allow/scope_deny/allowed_paths/denied_paths). If ok=false/safe_to_edit=false or it returns development_warnings, stop before editing and narrow the plan unless the user explicitly expands the requirement.",
5042
+ "- Treat the active requirement as the only change boundary. Do not add extra business behavior, new flows, new fields, new interfaces, or touch completed/related features unless the user explicitly asked or the change is strictly necessary.",
5043
+ "- Do not keep piling new feature code into a large single file. Prefer small modules/services/components; if an implementation file is already large, split it before adding more responsibilities.",
5044
+ "- If preflight_change_scope/read_file_lines/grep/query_codebase/get_pending_changes/sync_change_intent returns huge_file_modularization_required, do not continue normal feature work. Call plan_large_file_split, perform mechanical modularization with real module names/directories, never create generated/parts/partN files, then call record_large_file_split before resuming normal feature work. Only use preflight_change_scope(change_mode='mechanical_modularization') for the split itself; use change_mode='emergency_hotfix' only for the smallest urgent fix and still record why the split was deferred.",
4318
5045
  "- AFTER editing + saving: call get_pending_changes() to see unsynced files, then call sync_change_intent(intent, files). (You can omit files to auto-link all pending changes.)",
5046
+ "- If preflight_change_scope, read_file_lines, grep, query_codebase, get_pending_changes, or sync_change_intent returns development_warnings, address those warnings before continuing or explain why the current requirement truly needs that scope.",
4319
5047
  "- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
4320
5048
  "- When a requirement or user decision changes/reverses an older behavior, call upsert_decision(key, title, content, supersedes_req_ids?/supersedes_memory_ids?) and/or supersede_memory(...). Current decisions are shown in bootstrap_context/get_brain_dump and superseded memories are hidden from default semantic recall so stale requirements do not override newer facts.",
4321
5049
  "- If the user states a durable project convention (build commands, frameworks, naming rules, output paths): call upsert_convention(key, content, tags) so it is applied in future sessions.",
@@ -4974,14 +5702,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4974
5702
  tools: [
4975
5703
  {
4976
5704
  name: "start_requirement",
4977
- description: "MUST call BEFORE editing code. Starts/activates a requirement so all subsequent code changes can be linked to a concrete intent (do not edit code without an active requirement).",
5705
+ description: "MUST call BEFORE editing code. Starts/activates the concrete user requirement so subsequent changes stay inside that requirement boundary and do not accumulate unrelated work. Supports scope_allow/scope_deny and allowed_paths/denied_paths to prevent unrelated domain drift.",
4978
5706
  inputSchema: toJsonSchemaCompat(StartRequirementArgsSchema),
4979
5707
  },
4980
5708
  {
4981
5709
  name: "sync_change_intent",
4982
- description: "MUST call AFTER you edit code and save files. Archives the intent summary and links affected files to the current active requirement. If you omit files, the server will auto-link all pending changed files since the last sync.",
5710
+ description: "MUST call AFTER you edit code and save files. Archives the intent summary, links affected files to the current active requirement, and returns development_warnings for oversized files, broad change scope, or missing file targets.",
4983
5711
  inputSchema: toJsonSchemaCompat(SyncChangeIntentArgsSchema),
4984
5712
  },
5713
+ {
5714
+ name: "preflight_change_scope",
5715
+ description: "MUST call BEFORE editing once you know the intended files/modules. Checks planned files against the active requirement and optional generic scope_allow/scope_deny/allowed_paths/denied_paths. If ok=false/safe_to_edit=false, stop before editing and narrow the plan or scope contract. For huge files, use change_mode='mechanical_modularization' only when the task is to split the file.",
5716
+ inputSchema: toJsonSchemaCompat(PreflightChangeScopeArgsSchema),
5717
+ },
5718
+ {
5719
+ name: "plan_large_file_split",
5720
+ description: "Plan a mechanical modularization split for a huge implementation file. Produces real module names/directories and explicitly forbids generated/parts/partN files. Use this before normal feature work when preflight_change_scope returns huge_file_modularization_required.",
5721
+ inputSchema: toJsonSchemaCompat(PlanLargeFileSplitArgsSchema),
5722
+ },
5723
+ {
5724
+ name: "record_large_file_split",
5725
+ description: "Record the planned/in-progress/partial/resolved status of a huge-file mechanical modularization split so future sessions know the file is being decomposed and where modules moved.",
5726
+ inputSchema: toJsonSchemaCompat(RecordLargeFileSplitArgsSchema),
5727
+ },
4985
5728
  {
4986
5729
  name: "get_brain_dump",
4987
5730
  description: "Restore recent requirements/changes/notes/summary/pending changes. Prefer bootstrap_context() at session start when you also want recall from the local memory store.",
@@ -4989,12 +5732,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4989
5732
  },
4990
5733
  {
4991
5734
  name: "bootstrap_context",
4992
- description: "MUST call at the start of every new chat/session. Returns brain dump + pending changes, and (if you pass query) matches from the local memory store to avoid guessing.",
5735
+ description: "MUST call at the start of every new chat/session. Returns brain dump + pending changes + development_warnings, and (if you pass query) matches from the local memory store to avoid guessing.",
4993
5736
  inputSchema: toJsonSchemaCompat(BootstrapContextArgsSchema),
4994
5737
  },
4995
5738
  {
4996
5739
  name: "get_pending_changes",
4997
- description: "List files that changed locally but have not been acknowledged by sync_change_intent yet. Use this to see what needs syncing (or omit files in sync_change_intent to auto-link them).",
5740
+ description: "List files that changed locally but have not been acknowledged by sync_change_intent yet. Also returns development_warnings to catch god-file growth, broad change scope, and requirement-boundary drift.",
4998
5741
  inputSchema: toJsonSchemaCompat(GetPendingChangesArgsSchema),
4999
5742
  },
5000
5743
  {
@@ -5039,7 +5782,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5039
5782
  },
5040
5783
  {
5041
5784
  name: "grep",
5042
- description: "Repo text search with precise file/line/col matches, powered by ripgrep against real project files plus built-in noise filters. Falls back to indexed search only when ripgrep is unavailable.",
5785
+ description: "Repo text search with precise file/line/col matches, powered by ripgrep against real project files plus built-in noise filters. Falls back to indexed search only when ripgrep is unavailable. Returns development_warnings for cross-project paths or huge implementation-file matches.",
5043
5786
  inputSchema: toJsonSchemaCompat(GrepArgsSchema),
5044
5787
  },
5045
5788
  {
@@ -5054,7 +5797,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5054
5797
  },
5055
5798
  {
5056
5799
  name: "read_file_lines",
5057
- description: "Read a specific line range from a file under project_root (with strict size limits). Prefer this over Get-Content for deterministic reads.",
5800
+ description: "Read a specific line range from a file under project_root (with strict size limits). Prefer this over Get-Content for deterministic reads. Returns development_warnings when the target is a huge implementation file.",
5058
5801
  inputSchema: toJsonSchemaCompat(ReadFileLinesArgsSchema),
5059
5802
  },
5060
5803
  {
@@ -5064,7 +5807,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5064
5807
  },
5065
5808
  {
5066
5809
  name: "query_codebase",
5067
- description: "Search the symbol index for class/function/type names (or substrings) to locate definitions by file path and signature. Use this when you need to find codedo not guess locations.",
5810
+ description: "Search the symbol index for class/function/type names (or substrings) to locate definitions by file path and signature. Use this when you need to find code; do not guess locations. Returns development_warnings when matches point at huge implementation files.",
5068
5811
  inputSchema: toJsonSchemaCompat(QueryCodebaseArgsSchema),
5069
5812
  },
5070
5813
  {
@@ -5118,6 +5861,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5118
5861
  if (toolName === "start_requirement") {
5119
5862
  const args = StartRequirementArgsSchema.parse(rawArgs);
5120
5863
  flushPendingChangeBuffer();
5864
+ const scope_contract = buildRequirementScopeContract({
5865
+ title: args.title,
5866
+ background: args.background,
5867
+ scope_allow: args.scope_allow,
5868
+ scope_deny: args.scope_deny,
5869
+ allowed_paths: args.allowed_paths,
5870
+ denied_paths: args.denied_paths,
5871
+ });
5872
+ const development_warnings = buildRequirementStartWarnings({
5873
+ title: args.title,
5874
+ background: args.background,
5875
+ close_previous: args.close_previous,
5876
+ });
5121
5877
  if (args.close_previous) {
5122
5878
  try {
5123
5879
  completeAllActiveRequirementsStmt.run();
@@ -5131,13 +5887,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5131
5887
  const id = Number(info.lastInsertRowid);
5132
5888
  const background = args.background?.trim() ?? "";
5133
5889
  const content = background ? `${args.title}\n\n${background}` : args.title;
5134
- const memoryInfo = insertMemoryItemStmt.run("requirement", args.title, content, null, null, null, id, safeJson({ status: "active" }), sha256Hex(content));
5890
+ const memoryInfo = insertMemoryItemStmt.run("requirement", args.title, content, null, null, null, id, safeJson({ status: "active", scope_contract }), sha256Hex(content));
5135
5891
  const memory_id = Number(memoryInfo.lastInsertRowid);
5136
5892
  enqueueEmbedding(memory_id);
5137
5893
  logActivity("start_requirement", {
5138
5894
  req_id: id,
5139
5895
  title: args.title,
5140
5896
  closed_previous: args.close_previous,
5897
+ scope_contract,
5898
+ development_warnings: development_warnings.length,
5141
5899
  });
5142
5900
  return {
5143
5901
  content: [
@@ -5148,6 +5906,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5148
5906
  requirement: { id, title: args.title },
5149
5907
  memory_item: { id: memory_id },
5150
5908
  closed_previous: args.close_previous,
5909
+ scope_contract,
5910
+ development_warnings,
5151
5911
  }),
5152
5912
  },
5153
5913
  ],
@@ -5302,6 +6062,208 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5302
6062
  ],
5303
6063
  };
5304
6064
  }
6065
+ if (toolName === "preflight_change_scope") {
6066
+ const args = PreflightChangeScopeArgsSchema.parse(rawArgs);
6067
+ const changeMode = args.change_mode;
6068
+ flushPendingChangeBuffer();
6069
+ const files = (args.files ?? args.planned_files ?? []).filter((f) => typeof f === "string" && f.length > 0);
6070
+ const active = getActiveRequirementStmt.get();
6071
+ const explicitContract = buildRequirementScopeContract({
6072
+ title: active?.title ?? "",
6073
+ background: active?.context_data ?? "",
6074
+ scope_allow: args.scope_allow,
6075
+ scope_deny: args.scope_deny,
6076
+ allowed_paths: args.allowed_paths,
6077
+ denied_paths: args.denied_paths,
6078
+ });
6079
+ const fileInputs = files.map((file_path) => ({ file_path }));
6080
+ const development_warnings = [
6081
+ ...buildDevelopmentWarnings(fileInputs, { includeUnspecified: fileInputs.length === 0 }),
6082
+ ...buildScopeDriftWarnings({
6083
+ requirement: active,
6084
+ contract: explicitContract,
6085
+ intent: args.intent,
6086
+ files: fileInputs,
6087
+ includeMissingContractHint: true,
6088
+ }),
6089
+ ];
6090
+ const scope_contract = mergeScopeContracts(active ? getRequirementScopeContract(active.id) : null, explicitContract);
6091
+ logActivity("preflight_change_scope", {
6092
+ req_id: active?.id ?? null,
6093
+ intent_preview: makePreviewText(args.intent, 200),
6094
+ change_mode: changeMode,
6095
+ files: files.slice(0, 25),
6096
+ files_total: files.length,
6097
+ development_warnings: development_warnings.length,
6098
+ });
6099
+ const hasTargetFiles = fileInputs.length > 0;
6100
+ const hugeWarnings = development_warnings.filter((w) => w.code === "huge_file_modularization_required");
6101
+ const hasHugeFile = hugeWarnings.length > 0;
6102
+ const blockingWarnings = development_warnings.filter((w) => isDevelopmentWarningBlockingForChangeMode(w, changeMode));
6103
+ const hasBlockingWarnings = blockingWarnings.length > 0;
6104
+ const safeToEdit = hasTargetFiles && !hasBlockingWarnings;
6105
+ const recommendedAction = !hasTargetFiles
6106
+ ? "Identify the intended target files/modules and rerun preflight_change_scope before editing."
6107
+ : hasHugeFile && changeMode === "mechanical_modularization" && safeToEdit
6108
+ ? "Proceed only with mechanical modularization: call plan_large_file_split, move whole declarations into real named modules/directories, avoid generated/parts files, validate, then record_large_file_split."
6109
+ : hasHugeFile && changeMode === "emergency_hotfix" && safeToEdit
6110
+ ? "Proceed only with the smallest urgent fix, do not add new responsibilities, record why mechanical modularization was deferred, and plan/record the split next."
6111
+ : hasHugeFile
6112
+ ? "Stop normal feature work. Call plan_large_file_split and perform mechanical modularization first, or rerun preflight_change_scope with change_mode='mechanical_modularization' for the split itself."
6113
+ : hasBlockingWarnings
6114
+ ? "Stop before editing. Narrow the planned files or explicitly expand the current requirement/scope contract."
6115
+ : "Planned files are within the current generic scope checks.";
6116
+ const requiredAction = hasHugeFile && changeMode !== "mechanical_modularization"
6117
+ ? "mechanical_modularization"
6118
+ : undefined;
6119
+ const allowedChangeModes = hasHugeFile
6120
+ ? ["mechanical_modularization", "emergency_hotfix"]
6121
+ : undefined;
6122
+ const outputValue = {
6123
+ ok: safeToEdit,
6124
+ safe_to_edit: safeToEdit,
6125
+ change_mode: changeMode,
6126
+ recommended_action: recommendedAction,
6127
+ required_action: requiredAction,
6128
+ allowed_change_modes: allowedChangeModes,
6129
+ active_requirement: active ? { id: active.id, title: active.title } : null,
6130
+ intent: args.intent,
6131
+ files: files.map(normalizeToDbPath),
6132
+ scope_contract,
6133
+ development_warnings,
6134
+ };
6135
+ return {
6136
+ content: [
6137
+ {
6138
+ type: "text",
6139
+ text: toolCompactOrJson("preflight_change_scope", outputValue, compactPreflightChangeScopeText(outputValue), args.format),
6140
+ },
6141
+ ],
6142
+ };
6143
+ }
6144
+ if (toolName === "plan_large_file_split") {
6145
+ const args = PlanLargeFileSplitArgsSchema.parse(rawArgs);
6146
+ flushPendingChangeBuffer();
6147
+ const resolved = resolveReadPathUnderProjectRoot(args.file);
6148
+ let stat;
6149
+ try {
6150
+ stat = fs.statSync(resolved.absPath);
6151
+ }
6152
+ catch (err) {
6153
+ return {
6154
+ isError: true,
6155
+ content: [{ type: "text", text: toolJson({ ok: false, error: `File not found: ${String(err)}` }) }],
6156
+ };
6157
+ }
6158
+ if (!stat.isFile()) {
6159
+ return { isError: true, content: [{ type: "text", text: toolJson({ ok: false, error: "Not a file" }) }] };
6160
+ }
6161
+ if (!isLikelySourceImplementationFile(resolved.dbFilePath)) {
6162
+ return {
6163
+ isError: true,
6164
+ content: [
6165
+ {
6166
+ type: "text",
6167
+ text: toolJson({
6168
+ ok: false,
6169
+ error: "Not a recognized source implementation file",
6170
+ file_path: resolved.dbFilePath,
6171
+ }),
6172
+ },
6173
+ ],
6174
+ };
6175
+ }
6176
+ const lineInfo = countFileLinesBounded(resolved.absPath, 8_000_000);
6177
+ const lineCount = lineInfo?.lines ?? 0;
6178
+ if (lineCount < DEVELOPMENT_HUGE_FILE_LINES) {
6179
+ return {
6180
+ content: [
6181
+ {
6182
+ type: "text",
6183
+ text: toolJson({
6184
+ ok: false,
6185
+ file_path: resolved.dbFilePath,
6186
+ line_count: lineInfo?.truncated ? `${lineCount}+` : lineCount,
6187
+ huge_threshold_lines: DEVELOPMENT_HUGE_FILE_LINES,
6188
+ recommended_action: "This file is not above the huge-file threshold. Use normal focused modularity rules unless the user explicitly asked for refactoring.",
6189
+ }),
6190
+ },
6191
+ ],
6192
+ };
6193
+ }
6194
+ let targetDir = args.target_dir;
6195
+ if (targetDir) {
6196
+ targetDir = resolveProjectPathUnderRoot(targetDir, { allowRoot: true }).dbFilePath;
6197
+ }
6198
+ const plan = buildLargeFileSplitPlan({
6199
+ filePath: resolved.dbFilePath,
6200
+ absPath: resolved.absPath,
6201
+ intent: args.intent,
6202
+ targetDir,
6203
+ maxModules: args.max_modules,
6204
+ });
6205
+ logActivity("plan_large_file_split", {
6206
+ file_path: plan.file_path,
6207
+ line_count: plan.line_count,
6208
+ target_dir: plan.target_dir,
6209
+ modules: plan.modules.map((m) => m.module),
6210
+ });
6211
+ return {
6212
+ content: [
6213
+ {
6214
+ type: "text",
6215
+ text: toolCompactOrJson("plan_large_file_split", plan, compactLargeFileSplitPlanText(plan), args.format),
6216
+ },
6217
+ ],
6218
+ };
6219
+ }
6220
+ if (toolName === "record_large_file_split") {
6221
+ const args = RecordLargeFileSplitArgsSchema.parse(rawArgs);
6222
+ flushPendingChangeBuffer();
6223
+ const normalizedFile = normalizeToDbPath(args.file);
6224
+ const active = getActiveRequirementStmt.get();
6225
+ const modules = (args.modules ?? []).map(normalizeToDbPath);
6226
+ const content = [
6227
+ `Huge-file mechanical modularization ${args.status}: ${normalizedFile}`,
6228
+ "",
6229
+ args.summary,
6230
+ modules.length ? `\nModules:\n${modules.map((m) => `- ${m}`).join("\n")}` : "",
6231
+ args.remaining_lines != null ? `\nRemaining lines: ${args.remaining_lines}` : "",
6232
+ ].filter(Boolean).join("\n");
6233
+ const meta = {
6234
+ tags: ["large-file-split", "mechanical-modularization"],
6235
+ file: normalizedFile,
6236
+ status: args.status,
6237
+ modules,
6238
+ remaining_lines: args.remaining_lines ?? null,
6239
+ active_requirement_id: active?.id ?? null,
6240
+ };
6241
+ const info = insertMemoryItemStmt.run("note", `large-file-split:${normalizedFile}:${args.status}`, content, normalizedFile, null, null, active?.id ?? null, safeJson(meta), sha256Hex(content));
6242
+ const id = Number(info.lastInsertRowid);
6243
+ enqueueEmbedding(id);
6244
+ logActivity("record_large_file_split", {
6245
+ memory_item_id: id,
6246
+ file_path: normalizedFile,
6247
+ status: args.status,
6248
+ modules: modules.slice(0, 20),
6249
+ remaining_lines: args.remaining_lines ?? null,
6250
+ });
6251
+ return {
6252
+ content: [
6253
+ {
6254
+ type: "text",
6255
+ text: toolJson({
6256
+ ok: true,
6257
+ note: { id },
6258
+ file_path: normalizedFile,
6259
+ status: args.status,
6260
+ modules,
6261
+ remaining_lines: args.remaining_lines ?? null,
6262
+ }),
6263
+ },
6264
+ ],
6265
+ };
6266
+ }
5305
6267
  if (toolName === "sync_change_intent") {
5306
6268
  const args = SyncChangeIntentArgsSchema.parse(rawArgs);
5307
6269
  flushPendingChangeBuffer();
@@ -5386,12 +6348,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5386
6348
  }
5387
6349
  });
5388
6350
  insertTx();
6351
+ const development_warnings = [
6352
+ ...buildDevelopmentWarnings(synced_files, {
6353
+ includeUnspecified: synced_files.some((f) => f.file_path === "(unspecified)"),
6354
+ }),
6355
+ ...buildScopeDriftWarnings({
6356
+ requirement: active,
6357
+ intent: args.intent,
6358
+ files: synced_files,
6359
+ }),
6360
+ ];
5389
6361
  logActivity("sync_change_intent", {
5390
6362
  req_id: active.id,
5391
6363
  title: active.title,
5392
6364
  intent_preview: makePreviewText(args.intent, 200),
5393
6365
  files: synced_files.slice(0, 25),
5394
6366
  files_total: synced_files.length,
6367
+ development_warnings: development_warnings.length,
5395
6368
  });
5396
6369
  return {
5397
6370
  content: [
@@ -5402,6 +6375,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5402
6375
  linked_to_requirement: { id: active.id, title: active.title },
5403
6376
  synced_files,
5404
6377
  created,
6378
+ development_warnings,
5405
6379
  }),
5406
6380
  },
5407
6381
  ],
@@ -5442,6 +6416,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5442
6416
  const pending_total = mergedPending.total;
5443
6417
  const pending_truncated = mergedPending.truncated;
5444
6418
  const pending_changes = mergedPending.page;
6419
+ const activeForScope = getActiveRequirementStmt.get();
6420
+ const development_warnings = [
6421
+ ...buildDevelopmentWarnings(pending_changes),
6422
+ ...(activeForScope
6423
+ ? buildScopeDriftWarnings({ requirement: activeForScope, files: pending_changes })
6424
+ : []),
6425
+ ];
5445
6426
  const q = args.query?.trim() ?? "";
5446
6427
  const semanticKinds = args.kinds?.length ? args.kinds : BOOTSTRAP_DEFAULT_CONTEXT_KINDS;
5447
6428
  const semantic = q
@@ -5506,6 +6487,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5506
6487
  pending_limit,
5507
6488
  pending_truncated,
5508
6489
  pending_changes,
6490
+ development_warnings,
5509
6491
  items,
5510
6492
  semantic,
5511
6493
  };
@@ -5553,6 +6535,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5553
6535
  const pending_total = mergedPending.total;
5554
6536
  const pending_truncated = mergedPending.truncated;
5555
6537
  const pending_changes = mergedPending.page;
6538
+ const activeForScope = getActiveRequirementStmt.get();
6539
+ const development_warnings = [
6540
+ ...buildDevelopmentWarnings(pending_changes),
6541
+ ...(activeForScope
6542
+ ? buildScopeDriftWarnings({ requirement: activeForScope, files: pending_changes })
6543
+ : []),
6544
+ ];
5556
6545
  logActivity("get_brain_dump", {
5557
6546
  pending_total,
5558
6547
  pending_returned: pending_changes.length,
@@ -5597,6 +6586,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5597
6586
  pending_limit,
5598
6587
  pending_truncated,
5599
6588
  pending_changes,
6589
+ development_warnings,
5600
6590
  items,
5601
6591
  semantic: null,
5602
6592
  };
@@ -5619,18 +6609,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5619
6609
  const total = mergedPending.total;
5620
6610
  const truncated = mergedPending.truncated;
5621
6611
  const pending = mergedPending.page;
6612
+ const activeForScope = getActiveRequirementStmt.get();
6613
+ const development_warnings = [
6614
+ ...buildDevelopmentWarnings(pending),
6615
+ ...(activeForScope ? buildScopeDriftWarnings({ requirement: activeForScope, files: pending }) : []),
6616
+ ];
5622
6617
  logActivity("get_pending_changes", {
5623
6618
  total,
5624
6619
  offset,
5625
6620
  limit,
5626
6621
  returned: pending.length,
5627
6622
  truncated,
6623
+ development_warnings: development_warnings.length,
5628
6624
  });
5629
6625
  return {
5630
6626
  content: [
5631
6627
  {
5632
6628
  type: "text",
5633
- text: toolJson({ ok: true, total, offset, limit, truncated, pending }),
6629
+ text: toolJson({ ok: true, total, offset, limit, truncated, pending, development_warnings }),
5634
6630
  },
5635
6631
  ],
5636
6632
  };
@@ -5821,6 +6817,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5821
6817
  const includePaths = args.include_paths?.length ? args.include_paths : null;
5822
6818
  const excludePaths = args.exclude_paths?.length ? args.exclude_paths : null;
5823
6819
  const maxResults = args.max_results;
6820
+ const development_warnings = [
6821
+ ...buildCrossProjectPathWarnings(includePaths),
6822
+ ...buildCrossProjectPathWarnings(excludePaths),
6823
+ ];
5824
6824
  const caseSensitive = args.case_sensitive ?? (smartCase ? hasUppercaseAscii(q) : true);
5825
6825
  const ripgrepResult = runRipgrepSearch({
5826
6826
  query: q,
@@ -5832,6 +6832,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5832
6832
  maxResults,
5833
6833
  });
5834
6834
  if (ripgrepResult.ok) {
6835
+ const grepDevelopmentWarnings = [
6836
+ ...development_warnings,
6837
+ ...buildMatchedFileDevelopmentWarnings(ripgrepResult.matches.map((m) => m.file_path)),
6838
+ ];
5835
6839
  logActivity("grep", {
5836
6840
  backend: ripgrepResult.backend,
5837
6841
  rg_command: ripgrepResult.rg_command,
@@ -5844,6 +6848,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5844
6848
  matches: ripgrepResult.matches.length,
5845
6849
  total_matches: ripgrepResult.total_matches,
5846
6850
  truncated: ripgrepResult.truncated,
6851
+ development_warnings: grepDevelopmentWarnings.length,
5847
6852
  });
5848
6853
  const outputValue = {
5849
6854
  ok: true,
@@ -5858,6 +6863,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5858
6863
  matches: ripgrepResult.matches,
5859
6864
  total_matches: ripgrepResult.total_matches,
5860
6865
  truncated: ripgrepResult.truncated,
6866
+ development_warnings: grepDevelopmentWarnings,
5861
6867
  };
5862
6868
  return {
5863
6869
  content: [
@@ -5923,6 +6929,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5923
6929
  ],
5924
6930
  };
5925
6931
  }
6932
+ const grepDevelopmentWarnings = [
6933
+ ...development_warnings,
6934
+ ...buildMatchedFileDevelopmentWarnings(indexedResult.matches.map((m) => m.file_path)),
6935
+ ];
5926
6936
  logActivity("grep", {
5927
6937
  backend: indexedResult.backend,
5928
6938
  fallback_reason: "ripgrep_unavailable",
@@ -5939,6 +6949,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5939
6949
  candidates_scanned: indexedResult.candidates.scanned,
5940
6950
  matches: indexedResult.matches.length,
5941
6951
  truncated: indexedResult.truncated,
6952
+ development_warnings: grepDevelopmentWarnings.length,
5942
6953
  });
5943
6954
  const outputValue = {
5944
6955
  ok: true,
@@ -5957,6 +6968,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5957
6968
  candidates: indexedResult.candidates,
5958
6969
  matches: indexedResult.matches,
5959
6970
  truncated: indexedResult.truncated,
6971
+ development_warnings: grepDevelopmentWarnings,
5960
6972
  };
5961
6973
  return {
5962
6974
  content: [
@@ -6076,6 +7088,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6076
7088
  total_chars: result.totalChars,
6077
7089
  truncated: result.truncated,
6078
7090
  });
7091
+ const development_warnings = buildFileReadDevelopmentWarnings(resolved.dbFilePath, resolved.absPath, st);
6079
7092
  const outputValue = {
6080
7093
  ok: true,
6081
7094
  file_path: resolved.dbFilePath,
@@ -6083,6 +7096,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6083
7096
  returned_chars: result.returnedChars,
6084
7097
  total_chars: result.totalChars,
6085
7098
  truncated: result.truncated,
7099
+ development_warnings,
6086
7100
  text: result.text,
6087
7101
  };
6088
7102
  return {
@@ -6212,6 +7226,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6212
7226
  returned: result.returned,
6213
7227
  truncated: result.truncated,
6214
7228
  });
7229
+ const development_warnings = buildFileReadDevelopmentWarnings(resolved.dbFilePath, resolved.absPath, st);
6215
7230
  const outputValue = {
6216
7231
  ok: true,
6217
7232
  file_path: resolved.dbFilePath,
@@ -6219,6 +7234,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6219
7234
  to_line: toLine,
6220
7235
  returned: result.returned,
6221
7236
  truncated: result.truncated,
7237
+ development_warnings,
6222
7238
  text: result.text,
6223
7239
  };
6224
7240
  return {
@@ -6237,12 +7253,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6237
7253
  const like = `%${escaped}%`;
6238
7254
  const rows = searchSymbolsStmt.all(like, like, q, like, 250);
6239
7255
  const filtered = rows.filter((r) => !shouldIgnoreDbFilePath(r.file_path)).slice(0, 50);
7256
+ const development_warnings = buildMatchedFileDevelopmentWarnings(filtered.map((m) => m.file_path));
6240
7257
  logActivity("query_codebase", {
6241
7258
  query: q,
6242
7259
  matches: filtered.length,
7260
+ development_warnings: development_warnings.length,
6243
7261
  sample: filtered.slice(0, 10).map((m) => ({ name: m.name, type: m.type, file_path: m.file_path })),
6244
7262
  });
6245
- const outputValue = { ok: true, query: q, matches: filtered };
7263
+ const outputValue = { ok: true, query: q, matches: filtered, development_warnings };
6246
7264
  return {
6247
7265
  content: [
6248
7266
  {