@coreyuan/vector-mind 1.0.44 → 1.0.48

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.48";
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,42 @@ 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_WARN_FILE_BYTES = (() => {
87
+ const raw = process.env.VECTORMIND_WARN_FILE_BYTES?.trim();
88
+ if (!raw)
89
+ return 120_000;
90
+ const n = Number.parseInt(raw, 10);
91
+ if (!Number.isFinite(n) || n < 10_000)
92
+ return 120_000;
93
+ return Math.min(20_000_000, n);
94
+ })();
95
+ const DEVELOPMENT_WARN_PENDING_FILES = (() => {
96
+ const raw = process.env.VECTORMIND_WARN_PENDING_FILES?.trim();
97
+ if (!raw)
98
+ return 12;
99
+ const n = Number.parseInt(raw, 10);
100
+ if (!Number.isFinite(n) || n < 1)
101
+ return 12;
102
+ return Math.min(500, n);
103
+ })();
68
104
  const RIPGREP_RESOLVE_TIMEOUT_MS = 5_000;
69
105
  const RIPGREP_SEARCH_TIMEOUT_MS = 30_000;
70
106
  const RIPGREP_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
@@ -1384,6 +1420,416 @@ function isContentIndexableFile(filePath) {
1384
1420
  return false;
1385
1421
  return getContentChunkKind(filePath) !== null;
1386
1422
  }
1423
+ function isLikelySourceImplementationFile(filePath) {
1424
+ const ext = path.extname(filePath).toLowerCase();
1425
+ return new Set([
1426
+ ".ts",
1427
+ ".tsx",
1428
+ ".js",
1429
+ ".jsx",
1430
+ ".mjs",
1431
+ ".cjs",
1432
+ ".py",
1433
+ ".go",
1434
+ ".rs",
1435
+ ".java",
1436
+ ".kt",
1437
+ ".cs",
1438
+ ".c",
1439
+ ".cc",
1440
+ ".cpp",
1441
+ ".h",
1442
+ ".hpp",
1443
+ ".vue",
1444
+ ".svelte",
1445
+ ]).has(ext);
1446
+ }
1447
+ function countFileLinesBounded(absPath, maxBytes) {
1448
+ let stat;
1449
+ try {
1450
+ stat = fs.statSync(absPath);
1451
+ }
1452
+ catch {
1453
+ return null;
1454
+ }
1455
+ if (!stat.isFile())
1456
+ return null;
1457
+ const bytesToRead = Math.min(stat.size, maxBytes);
1458
+ const fd = fs.openSync(absPath, "r");
1459
+ try {
1460
+ const buffer = Buffer.alloc(bytesToRead);
1461
+ const read = fs.readSync(fd, buffer, 0, bytesToRead, 0);
1462
+ let lines = read > 0 ? 1 : 0;
1463
+ for (let i = 0; i < read; i++) {
1464
+ if (buffer[i] === 10)
1465
+ lines += 1;
1466
+ }
1467
+ return { lines, truncated: stat.size > bytesToRead };
1468
+ }
1469
+ finally {
1470
+ fs.closeSync(fd);
1471
+ }
1472
+ }
1473
+ function isPathInsideProjectRoot(absPath) {
1474
+ const root = path.resolve(projectRoot);
1475
+ const rel = path.relative(root, path.resolve(absPath));
1476
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
1477
+ }
1478
+ function checkPathScope(inputPath) {
1479
+ const normalizedInput = inputPath.trim() || ".";
1480
+ const absPath = path.resolve(path.isAbsolute(normalizedInput) ? normalizedInput : path.join(projectRoot, normalizedInput));
1481
+ return {
1482
+ input_path: inputPath,
1483
+ abs_path: absPath,
1484
+ in_project: isPathInsideProjectRoot(absPath),
1485
+ project_root: path.resolve(projectRoot),
1486
+ };
1487
+ }
1488
+ function buildCrossProjectPathWarnings(paths) {
1489
+ const checks = (paths ?? []).map((p) => checkPathScope(p)).filter((c) => !c.in_project);
1490
+ if (!checks.length)
1491
+ return [];
1492
+ return [
1493
+ {
1494
+ code: "cross_project_path",
1495
+ severity: "warning",
1496
+ 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.",
1497
+ files: checks.slice(0, 10).map((c) => c.input_path),
1498
+ details: {
1499
+ project_root: path.resolve(projectRoot),
1500
+ paths: checks.slice(0, 10),
1501
+ total_paths: checks.length,
1502
+ },
1503
+ },
1504
+ ];
1505
+ }
1506
+ function buildFileReadDevelopmentWarnings(filePath, absPath, stat) {
1507
+ const warnings = [];
1508
+ if (!isPathInsideProjectRoot(absPath)) {
1509
+ warnings.push(...buildCrossProjectPathWarnings([filePath]));
1510
+ return warnings;
1511
+ }
1512
+ if (!isLikelySourceImplementationFile(filePath))
1513
+ return warnings;
1514
+ let st = stat;
1515
+ try {
1516
+ st ??= fs.statSync(absPath);
1517
+ }
1518
+ catch {
1519
+ return warnings;
1520
+ }
1521
+ if (!st.isFile())
1522
+ return warnings;
1523
+ const lineInfo = countFileLinesBounded(absPath, 2_000_000);
1524
+ const lineCount = lineInfo?.lines ?? 0;
1525
+ const tooManyLines = lineCount >= DEVELOPMENT_BLOCK_FILE_LINES;
1526
+ const warnLines = lineCount >= DEVELOPMENT_WARN_FILE_LINES;
1527
+ const warnBytes = st.size >= DEVELOPMENT_WARN_FILE_BYTES;
1528
+ if (!tooManyLines && !warnLines && !warnBytes)
1529
+ return warnings;
1530
+ warnings.push({
1531
+ code: "large_file_read",
1532
+ severity: tooManyLines ? "blocker" : "warning",
1533
+ message: tooManyLines
1534
+ ? "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."
1535
+ : "You are reading a large implementation file. Keep the target narrow and prefer extracting focused modules before adding responsibilities.",
1536
+ files: [filePath],
1537
+ details: {
1538
+ lines: lineInfo?.truncated ? `${lineCount}+` : lineCount,
1539
+ bytes: st.size,
1540
+ warn_lines: DEVELOPMENT_WARN_FILE_LINES,
1541
+ block_lines: DEVELOPMENT_BLOCK_FILE_LINES,
1542
+ warn_bytes: DEVELOPMENT_WARN_FILE_BYTES,
1543
+ },
1544
+ });
1545
+ return warnings;
1546
+ }
1547
+ function buildMatchedFileDevelopmentWarnings(filePaths) {
1548
+ const seen = new Set();
1549
+ const warnings = [];
1550
+ for (const fp of filePaths) {
1551
+ if (!fp || seen.has(fp))
1552
+ continue;
1553
+ seen.add(fp);
1554
+ const abs = path.isAbsolute(fp) ? path.resolve(fp) : path.join(projectRoot, fp);
1555
+ warnings.push(...buildFileReadDevelopmentWarnings(normalizeToDbPath(fp), abs));
1556
+ if (warnings.length >= 8)
1557
+ break;
1558
+ }
1559
+ return warnings;
1560
+ }
1561
+ function buildRequirementStartWarnings(args) {
1562
+ const warnings = [];
1563
+ const activeReqs = listActiveRequirementsStmt.all(10);
1564
+ if (!args.close_previous && activeReqs.length > 0) {
1565
+ warnings.push({
1566
+ code: "multiple_active_requirements",
1567
+ severity: "warning",
1568
+ 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.",
1569
+ details: {
1570
+ active_requirements: activeReqs.slice(0, 5).map((r) => ({ id: r.id, title: r.title, status: r.status })),
1571
+ },
1572
+ });
1573
+ }
1574
+ const text = `${args.title}\n${args.background}`.toLowerCase();
1575
+ const broadTerms = [
1576
+ "顺便",
1577
+ "一起",
1578
+ "所有",
1579
+ "全部",
1580
+ "整体",
1581
+ "重构",
1582
+ "统一",
1583
+ "优化一下",
1584
+ "顺手",
1585
+ "相关的",
1586
+ "all ",
1587
+ "everything",
1588
+ "refactor",
1589
+ "cleanup",
1590
+ "clean up",
1591
+ ];
1592
+ const matched = broadTerms.filter((term) => text.includes(term));
1593
+ if (matched.length >= 2 || text.length > 1800) {
1594
+ warnings.push({
1595
+ code: "broad_requirement_scope",
1596
+ severity: "warning",
1597
+ 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.",
1598
+ details: { matched_terms: matched.slice(0, 10), text_length: text.length },
1599
+ });
1600
+ }
1601
+ return warnings;
1602
+ }
1603
+ function normalizeScopeTerms(values) {
1604
+ return Array.from(new Set((values ?? []).map((v) => v.trim()).filter(Boolean)));
1605
+ }
1606
+ function buildRequirementScopeContract(args) {
1607
+ const allowTerms = normalizeScopeTerms(args.scope_allow);
1608
+ const denyTerms = normalizeScopeTerms(args.scope_deny);
1609
+ const allowedPaths = normalizeScopeTerms(args.allowed_paths).map(normalizeToDbPath);
1610
+ const deniedPaths = normalizeScopeTerms(args.denied_paths).map((p) => p.replace(/\\/g, "/"));
1611
+ return {
1612
+ allow_terms: allowTerms,
1613
+ deny_terms: Array.from(new Set(denyTerms)),
1614
+ allowed_paths: Array.from(new Set(allowedPaths)),
1615
+ denied_paths: Array.from(new Set(deniedPaths)),
1616
+ inferred_from: [],
1617
+ };
1618
+ }
1619
+ function getRequirementScopeContract(reqId) {
1620
+ const memId = getRequirementMemoryItemIdStmt.get(reqId)?.id;
1621
+ if (memId == null)
1622
+ return null;
1623
+ const row = getMemoryItemByIdStmt.get(memId);
1624
+ const meta = parseMetadataJson(row?.metadata_json);
1625
+ const raw = meta.scope_contract;
1626
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
1627
+ return null;
1628
+ const obj = raw;
1629
+ return {
1630
+ allow_terms: Array.isArray(obj.allow_terms) ? obj.allow_terms.filter((v) => typeof v === "string") : [],
1631
+ deny_terms: Array.isArray(obj.deny_terms) ? obj.deny_terms.filter((v) => typeof v === "string") : [],
1632
+ allowed_paths: Array.isArray(obj.allowed_paths) ? obj.allowed_paths.filter((v) => typeof v === "string") : [],
1633
+ denied_paths: Array.isArray(obj.denied_paths) ? obj.denied_paths.filter((v) => typeof v === "string") : [],
1634
+ inferred_from: Array.isArray(obj.inferred_from) ? obj.inferred_from.filter((v) => typeof v === "string") : [],
1635
+ };
1636
+ }
1637
+ function wildcardToRegex(pattern) {
1638
+ const source = escapeRegExp(pattern).replace(/\\\*/g, ".*");
1639
+ return new RegExp(source, "i");
1640
+ }
1641
+ function pathMatchesAnyPattern(filePath, patterns) {
1642
+ const normalized = filePath.replace(/\\/g, "/");
1643
+ return patterns.filter((p) => wildcardToRegex(p.replace(/\\/g, "/")).test(normalized));
1644
+ }
1645
+ function fileContentHasDeniedTerms(filePath, terms) {
1646
+ if (!terms.length || filePath === "(unspecified)")
1647
+ return [];
1648
+ const abs = path.isAbsolute(filePath) ? path.resolve(filePath) : path.join(projectRoot, filePath);
1649
+ let st;
1650
+ try {
1651
+ st = fs.statSync(abs);
1652
+ }
1653
+ catch {
1654
+ return [];
1655
+ }
1656
+ if (!st.isFile() || st.size > 2_000_000)
1657
+ return [];
1658
+ let content = "";
1659
+ try {
1660
+ content = fs.readFileSync(abs, "utf8");
1661
+ }
1662
+ catch {
1663
+ return [];
1664
+ }
1665
+ const lower = `${filePath}\n${content.slice(0, 250_000)}`.toLowerCase();
1666
+ return terms.filter((term) => lower.includes(term.toLowerCase()));
1667
+ }
1668
+ function mergeScopeContracts(base, extra) {
1669
+ if (!base && !extra)
1670
+ return null;
1671
+ return {
1672
+ allow_terms: Array.from(new Set([...(base?.allow_terms ?? []), ...(extra?.allow_terms ?? [])])),
1673
+ deny_terms: Array.from(new Set([...(base?.deny_terms ?? []), ...(extra?.deny_terms ?? [])])),
1674
+ allowed_paths: Array.from(new Set([...(base?.allowed_paths ?? []), ...(extra?.allowed_paths ?? [])])),
1675
+ denied_paths: Array.from(new Set([...(base?.denied_paths ?? []), ...(extra?.denied_paths ?? [])])),
1676
+ inferred_from: Array.from(new Set([...(base?.inferred_from ?? []), ...(extra?.inferred_from ?? [])])),
1677
+ };
1678
+ }
1679
+ function buildScopeDriftWarnings(args) {
1680
+ const requirementContract = args.requirement ? getRequirementScopeContract(args.requirement.id) : null;
1681
+ const contract = mergeScopeContracts(requirementContract, args.contract);
1682
+ const hasScopeRules = !!contract && (contract.allow_terms.length > 0 ||
1683
+ contract.deny_terms.length > 0 ||
1684
+ contract.allowed_paths.length > 0 ||
1685
+ contract.denied_paths.length > 0);
1686
+ const warnings = [];
1687
+ if (!contract || !hasScopeRules) {
1688
+ if (args.includeMissingContractHint && args.files.length > 0) {
1689
+ warnings.push({
1690
+ code: "scope_contract_missing",
1691
+ severity: "warning",
1692
+ 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.",
1693
+ files: args.files.map((f) => normalizeToDbPath(f.file_path)).slice(0, 12),
1694
+ details: {
1695
+ requirement_id: args.requirement?.id ?? null,
1696
+ requirement_title: args.requirement?.title ?? null,
1697
+ },
1698
+ });
1699
+ }
1700
+ return warnings;
1701
+ }
1702
+ const allowTerms = contract.allow_terms;
1703
+ const denyTerms = contract.deny_terms;
1704
+ const allowedPaths = contract.allowed_paths;
1705
+ const deniedPaths = contract.denied_paths;
1706
+ const intentText = args.intent ?? "";
1707
+ const intentDenied = denyTerms.filter((term) => intentText.toLowerCase().includes(term.toLowerCase()));
1708
+ const suspicious = [];
1709
+ for (const f of args.files) {
1710
+ const fp = normalizeToDbPath(f.file_path);
1711
+ if (fp === "(unspecified)")
1712
+ continue;
1713
+ const matchedDeniedPaths = pathMatchesAnyPattern(fp, deniedPaths);
1714
+ const matchedDeniedTerms = [
1715
+ ...denyTerms.filter((term) => fp.toLowerCase().includes(term.toLowerCase())),
1716
+ ...fileContentHasDeniedTerms(fp, denyTerms),
1717
+ ];
1718
+ const isExplicitlyAllowed = pathMatchesAnyPattern(fp, allowedPaths).length > 0 ||
1719
+ allowTerms.some((term) => fp.toLowerCase().includes(term.toLowerCase()));
1720
+ const violatesAllowedPaths = allowedPaths.length > 0 && pathMatchesAnyPattern(fp, allowedPaths).length === 0;
1721
+ if ((matchedDeniedPaths.length || matchedDeniedTerms.length || intentDenied.length || violatesAllowedPaths) &&
1722
+ !isExplicitlyAllowed) {
1723
+ suspicious.push({
1724
+ file_path: fp,
1725
+ matched_terms: Array.from(new Set([...matchedDeniedTerms, ...intentDenied])).slice(0, 12),
1726
+ matched_paths: [
1727
+ ...matchedDeniedPaths.slice(0, 12),
1728
+ ...(violatesAllowedPaths ? [`outside allowed_paths: ${allowedPaths.slice(0, 5).join(", ")}`] : []),
1729
+ ],
1730
+ });
1731
+ }
1732
+ }
1733
+ if (suspicious.length) {
1734
+ warnings.push({
1735
+ code: "scope_drift",
1736
+ severity: "blocker",
1737
+ 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.",
1738
+ files: suspicious.slice(0, 12).map((s) => s.file_path),
1739
+ details: {
1740
+ requirement_id: args.requirement?.id ?? null,
1741
+ requirement_title: args.requirement?.title ?? null,
1742
+ inferred_from: contract.inferred_from,
1743
+ deny_terms: denyTerms.slice(0, 30),
1744
+ denied_paths: deniedPaths.slice(0, 30),
1745
+ suspicious: suspicious.slice(0, 12),
1746
+ },
1747
+ });
1748
+ }
1749
+ return warnings;
1750
+ }
1751
+ function buildDevelopmentWarnings(files, opts = {}) {
1752
+ const warnings = [];
1753
+ const uniqueFiles = Array.from(new Set(files
1754
+ .map((f) => f.file_path)
1755
+ .filter((f) => !!f && f !== "(unspecified)")
1756
+ .map((f) => normalizeToDbPath(f))));
1757
+ if (opts.includeUnspecified || files.some((f) => f.file_path === "(unspecified)")) {
1758
+ warnings.push({
1759
+ code: "unspecified_change_target",
1760
+ severity: "warning",
1761
+ message: "No changed file target was captured. For development work, sync concrete files so the current requirement owns only its real changes.",
1762
+ });
1763
+ }
1764
+ if (uniqueFiles.length >= DEVELOPMENT_WARN_PENDING_FILES) {
1765
+ warnings.push({
1766
+ code: "many_pending_files",
1767
+ severity: "warning",
1768
+ message: "This requirement touches many files. Re-check the user request and keep only files required by the current requirement.",
1769
+ files: uniqueFiles.slice(0, 20),
1770
+ details: { total_files: uniqueFiles.length, threshold: DEVELOPMENT_WARN_PENDING_FILES },
1771
+ });
1772
+ }
1773
+ const topDirs = new Set(uniqueFiles
1774
+ .map((f) => f.replace(/\\/g, "/").split("/").filter(Boolean)[0] ?? "")
1775
+ .filter(Boolean));
1776
+ if (uniqueFiles.length >= 6 && topDirs.size >= 4) {
1777
+ warnings.push({
1778
+ code: "broad_change_surface",
1779
+ severity: "warning",
1780
+ message: "Changed files span several top-level areas. Avoid modifying completed or merely related features unless the current requirement explicitly needs it.",
1781
+ files: uniqueFiles.slice(0, 20),
1782
+ details: { top_level_dirs: Array.from(topDirs).slice(0, 12), total_dirs: topDirs.size },
1783
+ });
1784
+ }
1785
+ for (const relPath of uniqueFiles) {
1786
+ if (!isLikelySourceImplementationFile(relPath))
1787
+ continue;
1788
+ const absPath = path.isAbsolute(relPath) ? relPath : path.join(projectRoot, relPath);
1789
+ let stat;
1790
+ try {
1791
+ stat = fs.statSync(absPath);
1792
+ }
1793
+ catch {
1794
+ continue;
1795
+ }
1796
+ if (!stat.isFile())
1797
+ continue;
1798
+ const lineInfo = countFileLinesBounded(absPath, 2_000_000);
1799
+ const lineCount = lineInfo?.lines ?? 0;
1800
+ const tooManyLines = lineCount >= DEVELOPMENT_BLOCK_FILE_LINES;
1801
+ const warnLines = lineCount >= DEVELOPMENT_WARN_FILE_LINES;
1802
+ const warnBytes = stat.size >= DEVELOPMENT_WARN_FILE_BYTES;
1803
+ if (!tooManyLines && !warnLines && !warnBytes)
1804
+ continue;
1805
+ warnings.push({
1806
+ code: tooManyLines ? "very_large_file" : "large_file",
1807
+ severity: tooManyLines ? "blocker" : "warning",
1808
+ message: tooManyLines
1809
+ ? "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."
1810
+ : "This implementation file is getting large. Prefer extracting focused modules instead of continuing to pile unrelated responsibilities into it.",
1811
+ files: [relPath],
1812
+ details: {
1813
+ lines: lineInfo?.truncated ? `${lineCount}+` : lineCount,
1814
+ bytes: stat.size,
1815
+ warn_lines: DEVELOPMENT_WARN_FILE_LINES,
1816
+ block_lines: DEVELOPMENT_BLOCK_FILE_LINES,
1817
+ warn_bytes: DEVELOPMENT_WARN_FILE_BYTES,
1818
+ },
1819
+ });
1820
+ }
1821
+ return warnings;
1822
+ }
1823
+ function compactDevelopmentWarningsText(warnings) {
1824
+ if (!warnings.length)
1825
+ return [];
1826
+ const lines = ["development warnings:"];
1827
+ for (const w of warnings.slice(0, 8)) {
1828
+ const files = w.files?.length ? ` files=${w.files.slice(0, 5).join(",")}` : "";
1829
+ lines.push(`- ${w.severity} ${w.code}: ${oneLine(w.message, 180)}${files}`);
1830
+ }
1831
+ return lines;
1832
+ }
1387
1833
  function extractSymbols(filePath, content) {
1388
1834
  const ext = path.extname(filePath).toLowerCase();
1389
1835
  if (ext === ".py")
@@ -1744,12 +2190,25 @@ const StartRequirementArgsSchema = ProjectRootArgSchema.merge(z.object({
1744
2190
  title: z.string().min(1),
1745
2191
  background: z.string().optional().default(""),
1746
2192
  close_previous: z.boolean().optional().default(true),
2193
+ scope_allow: z.array(z.string().min(1)).optional(),
2194
+ scope_deny: z.array(z.string().min(1)).optional(),
2195
+ allowed_paths: z.array(z.string().min(1)).optional(),
2196
+ denied_paths: z.array(z.string().min(1)).optional(),
1747
2197
  }));
1748
2198
  const SyncChangeIntentArgsSchema = ProjectRootArgSchema.merge(z.object({
1749
2199
  intent: z.string().min(1),
1750
2200
  files: z.array(z.string().min(1)).optional(),
1751
2201
  affected_files: z.array(z.string().min(1)).optional(),
1752
2202
  }));
2203
+ const PreflightChangeScopeArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
2204
+ intent: z.string().optional().default(""),
2205
+ files: z.array(z.string().min(1)).optional(),
2206
+ planned_files: z.array(z.string().min(1)).optional(),
2207
+ scope_allow: z.array(z.string().min(1)).optional(),
2208
+ scope_deny: z.array(z.string().min(1)).optional(),
2209
+ allowed_paths: z.array(z.string().min(1)).optional(),
2210
+ denied_paths: z.array(z.string().min(1)).optional(),
2211
+ }));
1753
2212
  const QueryCodebaseArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
1754
2213
  query: z.string().min(1),
1755
2214
  }));
@@ -2079,6 +2538,7 @@ function compactGrepText(data) {
2079
2538
  const lines = [
2080
2539
  `grep ${data.backend}${fallback} mode=${data.mode} matches=${data.matches.length}/${total} truncated=${data.truncated}${candidateText} q="${oneLine(data.query, 100)}"`,
2081
2540
  ];
2541
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings ?? []));
2082
2542
  if (data.ripgrep_error)
2083
2543
  lines.push(`ripgrep_error ${oneLine(data.ripgrep_error, 180)}`);
2084
2544
  for (const m of data.matches.slice(0, 80)) {
@@ -2108,15 +2568,18 @@ function compactReadTextFileText(data) {
2108
2568
  const offset = data.offset != null ? ` offset=${data.offset}` : "";
2109
2569
  const header = `file ${data.file_path}${offset} chars=${data.returned_chars}/${data.total_chars} truncated=${data.truncated}`;
2110
2570
  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}`;
2571
+ const warnings = compactDevelopmentWarningsText(data.development_warnings ?? []).join("\n");
2572
+ return `${header}${warnings ? `\n${warnings}` : ""}\n${data.text}${hint}`;
2112
2573
  }
2113
2574
  function compactReadFileLinesText(data) {
2114
2575
  const header = `lines ${data.file_path}:${data.from_line}-${data.to_line} returned=${data.returned} truncated=${data.truncated}`;
2115
2576
  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}`;
2577
+ const warnings = compactDevelopmentWarningsText(data.development_warnings ?? []).join("\n");
2578
+ return `${header}${warnings ? `\n${warnings}` : ""}\n${data.text}${hint}`;
2117
2579
  }
2118
2580
  function compactQueryCodebaseText(data) {
2119
2581
  const lines = [`query_codebase matches=${data.matches.length} q="${oneLine(data.query, 100)}"`];
2582
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings ?? []));
2120
2583
  for (const m of data.matches.slice(0, 50)) {
2121
2584
  lines.push(`${m.file_path}: ${m.type} ${m.name}${m.signature ? ` — ${oneLine(m.signature, 160)}` : ""}`);
2122
2585
  }
@@ -2151,6 +2614,20 @@ function compactMaintenanceText(data) {
2151
2614
  lines.push("hint: dry_run=false applies changes; vacuum=true reclaims sqlite file space after pruning");
2152
2615
  return lines.join("\n");
2153
2616
  }
2617
+ function compactPreflightChangeScopeText(data) {
2618
+ const req = data.active_requirement ? `#${data.active_requirement.id} ${data.active_requirement.title}` : "none";
2619
+ const lines = [
2620
+ `preflight_change_scope ok=${data.ok} safe_to_edit=${data.safe_to_edit} requirement=${req} files=${data.files.length} intent="${oneLine(data.intent, 120)}"`,
2621
+ `action: ${oneLine(data.recommended_action, 180)}`,
2622
+ ];
2623
+ if (data.scope_contract) {
2624
+ 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}`);
2625
+ }
2626
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings));
2627
+ if (!data.development_warnings.length)
2628
+ lines.push("- no development warnings");
2629
+ return lines.join("\n");
2630
+ }
2154
2631
  function compactBootstrapText(data) {
2155
2632
  const lines = [];
2156
2633
  lines.push(`ok ctx ${data.root_source} watcher=${data.watcher_enabled ? (data.watcher_ready ? "ready" : "starting") : "off"} root=${data.project_root}`);
@@ -2175,6 +2652,7 @@ function compactBootstrapText(data) {
2175
2652
  else {
2176
2653
  lines.push("pending 0");
2177
2654
  }
2655
+ lines.push(...compactDevelopmentWarningsText(data.development_warnings ?? []));
2178
2656
  if (data.items.length) {
2179
2657
  lines.push("requirements:");
2180
2658
  for (const item of data.items) {
@@ -4285,6 +4763,9 @@ function buildServerInstructions() {
4285
4763
  "Built-in architecture and code-organization quality policy:",
4286
4764
  BUILTIN_ARCHITECTURE_AND_CODE_ORGANIZATION_INSTRUCTIONS,
4287
4765
  "",
4766
+ "Built-in requirement boundary and modularity quality policy:",
4767
+ BUILTIN_REQUIREMENT_BOUNDARY_AND_MODULARITY_INSTRUCTIONS,
4768
+ "",
4288
4769
  "Built-in frontend output-purity quality policy:",
4289
4770
  BUILTIN_FRONTEND_OUTPUT_PURITY_INSTRUCTIONS,
4290
4771
  "",
@@ -4315,7 +4796,12 @@ function buildServerInstructions() {
4315
4796
  "- 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
4797
  "- 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
4798
  "- BEFORE editing code: call start_requirement(title, background) to set the active requirement.",
4799
+ " - 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.",
4800
+ "- 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.",
4801
+ "- 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.",
4802
+ "- 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.",
4318
4803
  "- 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.)",
4804
+ "- 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
4805
  "- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
4320
4806
  "- 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
4807
  "- 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 +5460,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4974
5460
  tools: [
4975
5461
  {
4976
5462
  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).",
5463
+ 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
5464
  inputSchema: toJsonSchemaCompat(StartRequirementArgsSchema),
4979
5465
  },
4980
5466
  {
4981
5467
  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.",
5468
+ 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
5469
  inputSchema: toJsonSchemaCompat(SyncChangeIntentArgsSchema),
4984
5470
  },
5471
+ {
5472
+ name: "preflight_change_scope",
5473
+ 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.",
5474
+ inputSchema: toJsonSchemaCompat(PreflightChangeScopeArgsSchema),
5475
+ },
4985
5476
  {
4986
5477
  name: "get_brain_dump",
4987
5478
  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 +5480,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4989
5480
  },
4990
5481
  {
4991
5482
  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.",
5483
+ 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
5484
  inputSchema: toJsonSchemaCompat(BootstrapContextArgsSchema),
4994
5485
  },
4995
5486
  {
4996
5487
  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).",
5488
+ 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
5489
  inputSchema: toJsonSchemaCompat(GetPendingChangesArgsSchema),
4999
5490
  },
5000
5491
  {
@@ -5039,7 +5530,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5039
5530
  },
5040
5531
  {
5041
5532
  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.",
5533
+ 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
5534
  inputSchema: toJsonSchemaCompat(GrepArgsSchema),
5044
5535
  },
5045
5536
  {
@@ -5054,7 +5545,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5054
5545
  },
5055
5546
  {
5056
5547
  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.",
5548
+ 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
5549
  inputSchema: toJsonSchemaCompat(ReadFileLinesArgsSchema),
5059
5550
  },
5060
5551
  {
@@ -5064,7 +5555,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5064
5555
  },
5065
5556
  {
5066
5557
  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.",
5558
+ 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
5559
  inputSchema: toJsonSchemaCompat(QueryCodebaseArgsSchema),
5069
5560
  },
5070
5561
  {
@@ -5118,6 +5609,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5118
5609
  if (toolName === "start_requirement") {
5119
5610
  const args = StartRequirementArgsSchema.parse(rawArgs);
5120
5611
  flushPendingChangeBuffer();
5612
+ const scope_contract = buildRequirementScopeContract({
5613
+ title: args.title,
5614
+ background: args.background,
5615
+ scope_allow: args.scope_allow,
5616
+ scope_deny: args.scope_deny,
5617
+ allowed_paths: args.allowed_paths,
5618
+ denied_paths: args.denied_paths,
5619
+ });
5620
+ const development_warnings = buildRequirementStartWarnings({
5621
+ title: args.title,
5622
+ background: args.background,
5623
+ close_previous: args.close_previous,
5624
+ });
5121
5625
  if (args.close_previous) {
5122
5626
  try {
5123
5627
  completeAllActiveRequirementsStmt.run();
@@ -5131,13 +5635,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5131
5635
  const id = Number(info.lastInsertRowid);
5132
5636
  const background = args.background?.trim() ?? "";
5133
5637
  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));
5638
+ const memoryInfo = insertMemoryItemStmt.run("requirement", args.title, content, null, null, null, id, safeJson({ status: "active", scope_contract }), sha256Hex(content));
5135
5639
  const memory_id = Number(memoryInfo.lastInsertRowid);
5136
5640
  enqueueEmbedding(memory_id);
5137
5641
  logActivity("start_requirement", {
5138
5642
  req_id: id,
5139
5643
  title: args.title,
5140
5644
  closed_previous: args.close_previous,
5645
+ scope_contract,
5646
+ development_warnings: development_warnings.length,
5141
5647
  });
5142
5648
  return {
5143
5649
  content: [
@@ -5148,6 +5654,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5148
5654
  requirement: { id, title: args.title },
5149
5655
  memory_item: { id: memory_id },
5150
5656
  closed_previous: args.close_previous,
5657
+ scope_contract,
5658
+ development_warnings,
5151
5659
  }),
5152
5660
  },
5153
5661
  ],
@@ -5302,6 +5810,65 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5302
5810
  ],
5303
5811
  };
5304
5812
  }
5813
+ if (toolName === "preflight_change_scope") {
5814
+ const args = PreflightChangeScopeArgsSchema.parse(rawArgs);
5815
+ flushPendingChangeBuffer();
5816
+ const files = (args.files ?? args.planned_files ?? []).filter((f) => typeof f === "string" && f.length > 0);
5817
+ const active = getActiveRequirementStmt.get();
5818
+ const explicitContract = buildRequirementScopeContract({
5819
+ title: active?.title ?? "",
5820
+ background: active?.context_data ?? "",
5821
+ scope_allow: args.scope_allow,
5822
+ scope_deny: args.scope_deny,
5823
+ allowed_paths: args.allowed_paths,
5824
+ denied_paths: args.denied_paths,
5825
+ });
5826
+ const fileInputs = files.map((file_path) => ({ file_path }));
5827
+ const development_warnings = [
5828
+ ...buildDevelopmentWarnings(fileInputs, { includeUnspecified: fileInputs.length === 0 }),
5829
+ ...buildScopeDriftWarnings({
5830
+ requirement: active,
5831
+ contract: explicitContract,
5832
+ intent: args.intent,
5833
+ files: fileInputs,
5834
+ includeMissingContractHint: true,
5835
+ }),
5836
+ ];
5837
+ const scope_contract = mergeScopeContracts(active ? getRequirementScopeContract(active.id) : null, explicitContract);
5838
+ logActivity("preflight_change_scope", {
5839
+ req_id: active?.id ?? null,
5840
+ intent_preview: makePreviewText(args.intent, 200),
5841
+ files: files.slice(0, 25),
5842
+ files_total: files.length,
5843
+ development_warnings: development_warnings.length,
5844
+ });
5845
+ const hasTargetFiles = fileInputs.length > 0;
5846
+ const hasBlockingWarnings = development_warnings.some((w) => w.severity === "blocker" || w.severity === "warning");
5847
+ const safeToEdit = hasTargetFiles && !hasBlockingWarnings;
5848
+ const recommendedAction = !hasTargetFiles
5849
+ ? "Identify the intended target files/modules and rerun preflight_change_scope before editing."
5850
+ : hasBlockingWarnings
5851
+ ? "Stop before editing. Narrow the planned files or explicitly expand the current requirement/scope contract."
5852
+ : "Planned files are within the current generic scope checks.";
5853
+ const outputValue = {
5854
+ ok: safeToEdit,
5855
+ safe_to_edit: safeToEdit,
5856
+ recommended_action: recommendedAction,
5857
+ active_requirement: active ? { id: active.id, title: active.title } : null,
5858
+ intent: args.intent,
5859
+ files: files.map(normalizeToDbPath),
5860
+ scope_contract,
5861
+ development_warnings,
5862
+ };
5863
+ return {
5864
+ content: [
5865
+ {
5866
+ type: "text",
5867
+ text: toolCompactOrJson("preflight_change_scope", outputValue, compactPreflightChangeScopeText(outputValue), args.format),
5868
+ },
5869
+ ],
5870
+ };
5871
+ }
5305
5872
  if (toolName === "sync_change_intent") {
5306
5873
  const args = SyncChangeIntentArgsSchema.parse(rawArgs);
5307
5874
  flushPendingChangeBuffer();
@@ -5386,12 +5953,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5386
5953
  }
5387
5954
  });
5388
5955
  insertTx();
5956
+ const development_warnings = [
5957
+ ...buildDevelopmentWarnings(synced_files, {
5958
+ includeUnspecified: synced_files.some((f) => f.file_path === "(unspecified)"),
5959
+ }),
5960
+ ...buildScopeDriftWarnings({
5961
+ requirement: active,
5962
+ intent: args.intent,
5963
+ files: synced_files,
5964
+ }),
5965
+ ];
5389
5966
  logActivity("sync_change_intent", {
5390
5967
  req_id: active.id,
5391
5968
  title: active.title,
5392
5969
  intent_preview: makePreviewText(args.intent, 200),
5393
5970
  files: synced_files.slice(0, 25),
5394
5971
  files_total: synced_files.length,
5972
+ development_warnings: development_warnings.length,
5395
5973
  });
5396
5974
  return {
5397
5975
  content: [
@@ -5402,6 +5980,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5402
5980
  linked_to_requirement: { id: active.id, title: active.title },
5403
5981
  synced_files,
5404
5982
  created,
5983
+ development_warnings,
5405
5984
  }),
5406
5985
  },
5407
5986
  ],
@@ -5442,6 +6021,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5442
6021
  const pending_total = mergedPending.total;
5443
6022
  const pending_truncated = mergedPending.truncated;
5444
6023
  const pending_changes = mergedPending.page;
6024
+ const activeForScope = getActiveRequirementStmt.get();
6025
+ const development_warnings = [
6026
+ ...buildDevelopmentWarnings(pending_changes),
6027
+ ...(activeForScope
6028
+ ? buildScopeDriftWarnings({ requirement: activeForScope, files: pending_changes })
6029
+ : []),
6030
+ ];
5445
6031
  const q = args.query?.trim() ?? "";
5446
6032
  const semanticKinds = args.kinds?.length ? args.kinds : BOOTSTRAP_DEFAULT_CONTEXT_KINDS;
5447
6033
  const semantic = q
@@ -5506,6 +6092,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5506
6092
  pending_limit,
5507
6093
  pending_truncated,
5508
6094
  pending_changes,
6095
+ development_warnings,
5509
6096
  items,
5510
6097
  semantic,
5511
6098
  };
@@ -5553,6 +6140,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5553
6140
  const pending_total = mergedPending.total;
5554
6141
  const pending_truncated = mergedPending.truncated;
5555
6142
  const pending_changes = mergedPending.page;
6143
+ const activeForScope = getActiveRequirementStmt.get();
6144
+ const development_warnings = [
6145
+ ...buildDevelopmentWarnings(pending_changes),
6146
+ ...(activeForScope
6147
+ ? buildScopeDriftWarnings({ requirement: activeForScope, files: pending_changes })
6148
+ : []),
6149
+ ];
5556
6150
  logActivity("get_brain_dump", {
5557
6151
  pending_total,
5558
6152
  pending_returned: pending_changes.length,
@@ -5597,6 +6191,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5597
6191
  pending_limit,
5598
6192
  pending_truncated,
5599
6193
  pending_changes,
6194
+ development_warnings,
5600
6195
  items,
5601
6196
  semantic: null,
5602
6197
  };
@@ -5619,18 +6214,24 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5619
6214
  const total = mergedPending.total;
5620
6215
  const truncated = mergedPending.truncated;
5621
6216
  const pending = mergedPending.page;
6217
+ const activeForScope = getActiveRequirementStmt.get();
6218
+ const development_warnings = [
6219
+ ...buildDevelopmentWarnings(pending),
6220
+ ...(activeForScope ? buildScopeDriftWarnings({ requirement: activeForScope, files: pending }) : []),
6221
+ ];
5622
6222
  logActivity("get_pending_changes", {
5623
6223
  total,
5624
6224
  offset,
5625
6225
  limit,
5626
6226
  returned: pending.length,
5627
6227
  truncated,
6228
+ development_warnings: development_warnings.length,
5628
6229
  });
5629
6230
  return {
5630
6231
  content: [
5631
6232
  {
5632
6233
  type: "text",
5633
- text: toolJson({ ok: true, total, offset, limit, truncated, pending }),
6234
+ text: toolJson({ ok: true, total, offset, limit, truncated, pending, development_warnings }),
5634
6235
  },
5635
6236
  ],
5636
6237
  };
@@ -5821,6 +6422,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5821
6422
  const includePaths = args.include_paths?.length ? args.include_paths : null;
5822
6423
  const excludePaths = args.exclude_paths?.length ? args.exclude_paths : null;
5823
6424
  const maxResults = args.max_results;
6425
+ const development_warnings = [
6426
+ ...buildCrossProjectPathWarnings(includePaths),
6427
+ ...buildCrossProjectPathWarnings(excludePaths),
6428
+ ];
5824
6429
  const caseSensitive = args.case_sensitive ?? (smartCase ? hasUppercaseAscii(q) : true);
5825
6430
  const ripgrepResult = runRipgrepSearch({
5826
6431
  query: q,
@@ -5832,6 +6437,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5832
6437
  maxResults,
5833
6438
  });
5834
6439
  if (ripgrepResult.ok) {
6440
+ const grepDevelopmentWarnings = [
6441
+ ...development_warnings,
6442
+ ...buildMatchedFileDevelopmentWarnings(ripgrepResult.matches.map((m) => m.file_path)),
6443
+ ];
5835
6444
  logActivity("grep", {
5836
6445
  backend: ripgrepResult.backend,
5837
6446
  rg_command: ripgrepResult.rg_command,
@@ -5844,6 +6453,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5844
6453
  matches: ripgrepResult.matches.length,
5845
6454
  total_matches: ripgrepResult.total_matches,
5846
6455
  truncated: ripgrepResult.truncated,
6456
+ development_warnings: grepDevelopmentWarnings.length,
5847
6457
  });
5848
6458
  const outputValue = {
5849
6459
  ok: true,
@@ -5858,6 +6468,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5858
6468
  matches: ripgrepResult.matches,
5859
6469
  total_matches: ripgrepResult.total_matches,
5860
6470
  truncated: ripgrepResult.truncated,
6471
+ development_warnings: grepDevelopmentWarnings,
5861
6472
  };
5862
6473
  return {
5863
6474
  content: [
@@ -5923,6 +6534,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5923
6534
  ],
5924
6535
  };
5925
6536
  }
6537
+ const grepDevelopmentWarnings = [
6538
+ ...development_warnings,
6539
+ ...buildMatchedFileDevelopmentWarnings(indexedResult.matches.map((m) => m.file_path)),
6540
+ ];
5926
6541
  logActivity("grep", {
5927
6542
  backend: indexedResult.backend,
5928
6543
  fallback_reason: "ripgrep_unavailable",
@@ -5939,6 +6554,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5939
6554
  candidates_scanned: indexedResult.candidates.scanned,
5940
6555
  matches: indexedResult.matches.length,
5941
6556
  truncated: indexedResult.truncated,
6557
+ development_warnings: grepDevelopmentWarnings.length,
5942
6558
  });
5943
6559
  const outputValue = {
5944
6560
  ok: true,
@@ -5957,6 +6573,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5957
6573
  candidates: indexedResult.candidates,
5958
6574
  matches: indexedResult.matches,
5959
6575
  truncated: indexedResult.truncated,
6576
+ development_warnings: grepDevelopmentWarnings,
5960
6577
  };
5961
6578
  return {
5962
6579
  content: [
@@ -6076,6 +6693,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6076
6693
  total_chars: result.totalChars,
6077
6694
  truncated: result.truncated,
6078
6695
  });
6696
+ const development_warnings = buildFileReadDevelopmentWarnings(resolved.dbFilePath, resolved.absPath, st);
6079
6697
  const outputValue = {
6080
6698
  ok: true,
6081
6699
  file_path: resolved.dbFilePath,
@@ -6083,6 +6701,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6083
6701
  returned_chars: result.returnedChars,
6084
6702
  total_chars: result.totalChars,
6085
6703
  truncated: result.truncated,
6704
+ development_warnings,
6086
6705
  text: result.text,
6087
6706
  };
6088
6707
  return {
@@ -6212,6 +6831,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6212
6831
  returned: result.returned,
6213
6832
  truncated: result.truncated,
6214
6833
  });
6834
+ const development_warnings = buildFileReadDevelopmentWarnings(resolved.dbFilePath, resolved.absPath, st);
6215
6835
  const outputValue = {
6216
6836
  ok: true,
6217
6837
  file_path: resolved.dbFilePath,
@@ -6219,6 +6839,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6219
6839
  to_line: toLine,
6220
6840
  returned: result.returned,
6221
6841
  truncated: result.truncated,
6842
+ development_warnings,
6222
6843
  text: result.text,
6223
6844
  };
6224
6845
  return {
@@ -6237,12 +6858,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
6237
6858
  const like = `%${escaped}%`;
6238
6859
  const rows = searchSymbolsStmt.all(like, like, q, like, 250);
6239
6860
  const filtered = rows.filter((r) => !shouldIgnoreDbFilePath(r.file_path)).slice(0, 50);
6861
+ const development_warnings = buildMatchedFileDevelopmentWarnings(filtered.map((m) => m.file_path));
6240
6862
  logActivity("query_codebase", {
6241
6863
  query: q,
6242
6864
  matches: filtered.length,
6865
+ development_warnings: development_warnings.length,
6243
6866
  sample: filtered.slice(0, 10).map((m) => ({ name: m.name, type: m.type, file_path: m.file_path })),
6244
6867
  });
6245
- const outputValue = { ok: true, query: q, matches: filtered };
6868
+ const outputValue = { ok: true, query: q, matches: filtered, development_warnings };
6246
6869
  return {
6247
6870
  content: [
6248
6871
  {