@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/README.md +36 -18
- package/dist/builtin-conventions.js +16 -1
- package/dist/builtin-conventions.js.map +1 -1
- package/dist/builtin-instructions.d.ts +2 -1
- package/dist/builtin-instructions.js +2 -1
- package/dist/builtin-instructions.js.map +1 -1
- package/dist/index.js +637 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/builtin-policy.d.ts +0 -7
- package/dist/builtin-policy.js +0 -24
- package/dist/builtin-policy.js.map +0 -1
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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 code
|
|
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
|
{
|