@coreyuan/vector-mind 1.0.48 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextpro
16
16
  import { BUILTIN_CONVENTIONS } from "./builtin-conventions.js";
17
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.48";
19
+ const SERVER_VERSION = "1.0.49";
20
20
  const rootFromEnv = process.env.VECTORMIND_ROOT?.trim() ?? "";
21
21
  const prettyJsonOutput = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_PRETTY_JSON ?? "").trim().toLowerCase());
22
22
  const debugLogEnabled = ["1", "true", "on", "yes"].includes((process.env.VECTORMIND_DEBUG_LOG ?? "").trim().toLowerCase());
@@ -83,6 +83,16 @@ const DEVELOPMENT_BLOCK_FILE_LINES = (() => {
83
83
  return Math.max(1200, DEVELOPMENT_WARN_FILE_LINES);
84
84
  return Math.min(100_000, n);
85
85
  })();
86
+ const DEVELOPMENT_HUGE_FILE_LINES = (() => {
87
+ const raw = process.env.VECTORMIND_HUGE_FILE_LINES?.trim();
88
+ if (!raw)
89
+ return 3000;
90
+ const n = Number.parseInt(raw, 10);
91
+ if (!Number.isFinite(n) || n < DEVELOPMENT_BLOCK_FILE_LINES) {
92
+ return Math.max(3000, DEVELOPMENT_BLOCK_FILE_LINES);
93
+ }
94
+ return Math.min(200_000, n);
95
+ })();
86
96
  const DEVELOPMENT_WARN_FILE_BYTES = (() => {
87
97
  const raw = process.env.VECTORMIND_WARN_FILE_BYTES?.trim();
88
98
  if (!raw)
@@ -1503,6 +1513,54 @@ function buildCrossProjectPathWarnings(paths) {
1503
1513
  },
1504
1514
  ];
1505
1515
  }
1516
+ function buildLargeImplementationFileWarning(args) {
1517
+ const linesValue = args.lineCountTruncated ? `${args.lineCount}+` : args.lineCount;
1518
+ if (args.lineCount >= DEVELOPMENT_HUGE_FILE_LINES) {
1519
+ return {
1520
+ code: "huge_file_modularization_required",
1521
+ severity: "blocker",
1522
+ message: "This implementation file is huge. Before any normal feature work, perform mechanical modularization: move whole functions/types/impl blocks into real, clearly named modules/directories, avoid *.generated.* or *.parts files, preserve behavior, then run format/build/tests.",
1523
+ files: [args.filePath],
1524
+ details: {
1525
+ lines: linesValue,
1526
+ bytes: args.bytes,
1527
+ warn_lines: DEVELOPMENT_WARN_FILE_LINES,
1528
+ block_lines: DEVELOPMENT_BLOCK_FILE_LINES,
1529
+ huge_lines: DEVELOPMENT_HUGE_FILE_LINES,
1530
+ required_action: "mechanical_modularization",
1531
+ allowed_change_modes: ["mechanical_modularization", "emergency_hotfix"],
1532
+ forbidden_file_patterns: ["*.generated.*", "*.parts", "*.rs.parts", "*_part*"],
1533
+ mechanical_rules: [
1534
+ "move whole declarations only",
1535
+ "use real module names and clear directory boundaries",
1536
+ "preserve behavior and public semantics",
1537
+ "only add necessary mod/use/pub(crate)/re-export glue",
1538
+ "run formatter, build, and tests after each phase",
1539
+ ],
1540
+ reading: !!args.reading,
1541
+ },
1542
+ };
1543
+ }
1544
+ return {
1545
+ code: args.code,
1546
+ severity: args.code === "large_file" || (args.code === "large_file_read" && args.lineCount < DEVELOPMENT_BLOCK_FILE_LINES)
1547
+ ? "warning"
1548
+ : "blocker",
1549
+ message: args.code === "large_file"
1550
+ ? "This implementation file is getting large. Prefer extracting focused modules instead of continuing to pile unrelated responsibilities into it."
1551
+ : args.reading
1552
+ ? "You are reading a very large implementation file. Do not keep patching new feature code into it; identify a narrow function and split new behavior into focused modules unless this task is explicitly a planned extraction."
1553
+ : "This implementation file is already very large. Do not add new feature code here by default; split into a focused module/service/component and keep this file as a thin entry.",
1554
+ files: [args.filePath],
1555
+ details: {
1556
+ lines: linesValue,
1557
+ bytes: args.bytes,
1558
+ warn_lines: DEVELOPMENT_WARN_FILE_LINES,
1559
+ block_lines: DEVELOPMENT_BLOCK_FILE_LINES,
1560
+ huge_lines: DEVELOPMENT_HUGE_FILE_LINES,
1561
+ },
1562
+ };
1563
+ }
1506
1564
  function buildFileReadDevelopmentWarnings(filePath, absPath, stat) {
1507
1565
  const warnings = [];
1508
1566
  if (!isPathInsideProjectRoot(absPath)) {
@@ -1527,21 +1585,14 @@ function buildFileReadDevelopmentWarnings(filePath, absPath, stat) {
1527
1585
  const warnBytes = st.size >= DEVELOPMENT_WARN_FILE_BYTES;
1528
1586
  if (!tooManyLines && !warnLines && !warnBytes)
1529
1587
  return warnings;
1530
- warnings.push({
1588
+ warnings.push(buildLargeImplementationFileWarning({
1531
1589
  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
- });
1590
+ filePath,
1591
+ lineCount,
1592
+ lineCountTruncated: lineInfo?.truncated,
1593
+ bytes: st.size,
1594
+ reading: true,
1595
+ }));
1545
1596
  return warnings;
1546
1597
  }
1547
1598
  function buildMatchedFileDevelopmentWarnings(filePaths) {
@@ -1802,24 +1853,37 @@ function buildDevelopmentWarnings(files, opts = {}) {
1802
1853
  const warnBytes = stat.size >= DEVELOPMENT_WARN_FILE_BYTES;
1803
1854
  if (!tooManyLines && !warnLines && !warnBytes)
1804
1855
  continue;
1805
- warnings.push({
1856
+ warnings.push(buildLargeImplementationFileWarning({
1806
1857
  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
- });
1858
+ filePath: relPath,
1859
+ lineCount,
1860
+ lineCountTruncated: lineInfo?.truncated,
1861
+ bytes: stat.size,
1862
+ }));
1820
1863
  }
1821
1864
  return warnings;
1822
1865
  }
1866
+ function isLargeFileWarningCode(code) {
1867
+ return (code === "large_file" ||
1868
+ code === "very_large_file" ||
1869
+ code === "large_file_read" ||
1870
+ code === "huge_file_modularization_required");
1871
+ }
1872
+ function isDevelopmentWarningBlockingForChangeMode(warning, changeMode) {
1873
+ if (changeMode === "mechanical_modularization") {
1874
+ if (isLargeFileWarningCode(warning.code))
1875
+ return false;
1876
+ if (warning.code === "scope_contract_missing")
1877
+ return false;
1878
+ }
1879
+ if (changeMode === "emergency_hotfix") {
1880
+ if (isLargeFileWarningCode(warning.code))
1881
+ return false;
1882
+ if (warning.code === "scope_contract_missing")
1883
+ return false;
1884
+ }
1885
+ return warning.severity === "blocker" || warning.severity === "warning";
1886
+ }
1823
1887
  function compactDevelopmentWarningsText(warnings) {
1824
1888
  if (!warnings.length)
1825
1889
  return [];
@@ -1987,6 +2051,142 @@ function extractCLikeSymbols(content) {
1987
2051
  }
1988
2052
  return symbols;
1989
2053
  }
2054
+ function declarationRegexForExtension(ext) {
2055
+ switch (ext) {
2056
+ case ".rs":
2057
+ return /^\s*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:fn|struct|enum|trait|impl|mod|type|const|static)\s+([A-Za-z_][\w]*)?/;
2058
+ case ".go":
2059
+ return /^\s*(?:func|type|const|var)\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)?/;
2060
+ case ".py":
2061
+ return /^(?:class|async\s+def|def)\s+([A-Za-z_][\w]*)/;
2062
+ case ".ts":
2063
+ case ".tsx":
2064
+ case ".js":
2065
+ case ".jsx":
2066
+ case ".mjs":
2067
+ case ".cjs":
2068
+ return /^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|enum|const|let|var)\s+([A-Za-z_$][\w$]*)?/;
2069
+ default:
2070
+ return /^\s*(?:pub\s+)?(?:async\s+)?(?:fn|function|class|struct|enum|interface|type|const|static)\s+([A-Za-z_][\w]*)?/;
2071
+ }
2072
+ }
2073
+ function moduleNameFromDeclaration(name, signature) {
2074
+ const text = `${name} ${signature}`.toLowerCase();
2075
+ const rules = [
2076
+ [/(config|setting|env)/, "config"],
2077
+ [/(state|store|persist)/, "state"],
2078
+ [/(api|client|request|response|heartbeat|activate|sync)/, "api"],
2079
+ [/(service|daemon|install|start|stop)/, "service"],
2080
+ [/(log|logger|redact)/, "logging"],
2081
+ [/(gui|window|dialog|form|view|button|list)/, "ui"],
2082
+ [/(share|disk|smb|unc|folder|directory)/, "share"],
2083
+ [/(repair|cleanup|probe|health)/, "maintenance"],
2084
+ [/(path|sanitize|normalize|host|ip|util|helper)/, "util"],
2085
+ [/(test|mock|fixture)/, "tests"],
2086
+ ];
2087
+ for (const [pattern, moduleName] of rules) {
2088
+ if (pattern.test(text))
2089
+ return moduleName;
2090
+ }
2091
+ return "core";
2092
+ }
2093
+ function topLevelDeclarationsForPlan(content, ext, maxDecls = 160) {
2094
+ const decls = [];
2095
+ const lines = content.split(/\r?\n/);
2096
+ const regex = declarationRegexForExtension(ext);
2097
+ for (let i = 0; i < lines.length && decls.length < maxDecls; i++) {
2098
+ const raw = lines[i];
2099
+ const trimmed = raw.trim();
2100
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#"))
2101
+ continue;
2102
+ const m = raw.match(regex);
2103
+ if (!m)
2104
+ continue;
2105
+ const fallback = trimmed.split(/\s+/).slice(0, 3).join("_").replace(/[^\w$]+/g, "_");
2106
+ const name = (m[1] || fallback || `declaration_${i + 1}`).replace(/^[^A-Za-z_]+/, "") || `declaration_${i + 1}`;
2107
+ const kind = trimmed.split(/\s+/).find((part) => ["fn", "function", "class", "struct", "enum", "trait", "impl", "mod", "type", "const", "static", "interface"].includes(part.replace(/[({].*$/, ""))) ?? "declaration";
2108
+ decls.push({
2109
+ line: i + 1,
2110
+ kind,
2111
+ name,
2112
+ signature: oneLine(trimmed, 180),
2113
+ suggested_module: moduleNameFromDeclaration(name, trimmed),
2114
+ });
2115
+ }
2116
+ return decls;
2117
+ }
2118
+ function targetPathForModule(originalFilePath, targetDir, moduleName) {
2119
+ const ext = path.extname(originalFilePath) || ".txt";
2120
+ const normalizedTargetDir = targetDir.replace(/\\/g, "/").replace(/\/+$/, "");
2121
+ if (!normalizedTargetDir || normalizedTargetDir === ".")
2122
+ return `${moduleName}${ext}`;
2123
+ return `${normalizedTargetDir}/${moduleName}${ext}`;
2124
+ }
2125
+ function buildLargeFileSplitPlan(args) {
2126
+ const content = fs.readFileSync(args.absPath, "utf8");
2127
+ const st = fs.statSync(args.absPath);
2128
+ const lines = content.split(/\r?\n/).length;
2129
+ const ext = path.extname(args.absPath).toLowerCase();
2130
+ const baseName = path.basename(args.filePath, path.extname(args.filePath));
2131
+ const parentDir = path.dirname(args.filePath).replace(/\\/g, "/");
2132
+ const defaultTargetDir = parentDir === "." ? baseName : `${parentDir}/${baseName}`;
2133
+ const targetDir = normalizeToDbPath(args.targetDir ?? defaultTargetDir);
2134
+ const declarations = topLevelDeclarationsForPlan(content, ext);
2135
+ const grouped = new Map();
2136
+ for (const decl of declarations) {
2137
+ const key = grouped.size >= args.maxModules && !grouped.has(decl.suggested_module) ? "core" : decl.suggested_module;
2138
+ const list = grouped.get(key) ?? [];
2139
+ list.push(decl);
2140
+ grouped.set(key, list);
2141
+ }
2142
+ if (!grouped.size)
2143
+ grouped.set("core", []);
2144
+ const modules = Array.from(grouped.entries())
2145
+ .slice(0, args.maxModules)
2146
+ .map(([moduleName, decls]) => ({
2147
+ module: moduleName,
2148
+ target_path: targetPathForModule(args.filePath, targetDir, moduleName),
2149
+ declarations: decls.slice(0, 24).map((d) => `${d.kind} ${d.name} @L${d.line}`),
2150
+ reason: `Move whole ${moduleName}-related declarations together without changing behavior.`,
2151
+ }));
2152
+ return {
2153
+ ok: true,
2154
+ file_path: args.filePath,
2155
+ line_count: lines,
2156
+ bytes: st.size,
2157
+ huge_threshold_lines: DEVELOPMENT_HUGE_FILE_LINES,
2158
+ required_action: "mechanical_modularization",
2159
+ intent: args.intent,
2160
+ target_dir: targetDir,
2161
+ forbidden_patterns: ["*.generated.*", "*.parts", "*.rs.parts", "*_part*", "*Part*"],
2162
+ mechanical_rules: [
2163
+ "Move only complete declarations/impl blocks/functions/classes/types; do not split a declaration body.",
2164
+ "Use real module names and clear directory boundaries; do not create generated/parts/partN files.",
2165
+ "Preserve behavior, names, API semantics, data formats, side effects, and test expectations.",
2166
+ "Only add necessary module declarations, imports, pub(crate), and re-exports to make moved code compile.",
2167
+ "Run formatter, build/check, and relevant tests after each small phase.",
2168
+ ],
2169
+ modules,
2170
+ steps: [
2171
+ "Create a dedicated mechanical modularization requirement before touching the huge file.",
2172
+ "Add the target module directory and move one cohesive declaration group at a time.",
2173
+ "Keep the original file as a thin entry/mod orchestration file where possible.",
2174
+ "After each group, run formatter and the smallest available compile/test command.",
2175
+ "Record the split with record_large_file_split(status='partial' or 'resolved').",
2176
+ "Resume the original feature only after the target huge file is no longer the default place for new code.",
2177
+ ],
2178
+ validation: [
2179
+ "No *.generated.*, *.parts, *.rs.parts, or numbered part files were created.",
2180
+ "The original file line count decreased or contains only thin orchestration glue.",
2181
+ "Formatter passes.",
2182
+ "Build/check passes.",
2183
+ "Relevant tests pass.",
2184
+ ],
2185
+ notes: declarations.length
2186
+ ? [`Detected ${declarations.length} top-level declarations for mechanical grouping.`]
2187
+ : ["No top-level declarations were detected by lightweight scanning; split by obvious cohesive sections and verify after each move."],
2188
+ };
2189
+ }
1990
2190
  function chunkTextByLines(content, opts) {
1991
2191
  const lines = content.split(/\r?\n/);
1992
2192
  if (lines.length === 0)
@@ -2204,11 +2404,28 @@ const PreflightChangeScopeArgsSchema = ProjectRootArgSchema.merge(OutputFormatSc
2204
2404
  intent: z.string().optional().default(""),
2205
2405
  files: z.array(z.string().min(1)).optional(),
2206
2406
  planned_files: z.array(z.string().min(1)).optional(),
2407
+ change_mode: z
2408
+ .enum(["feature", "bugfix", "refactor", "mechanical_modularization", "emergency_hotfix"])
2409
+ .optional()
2410
+ .default("feature"),
2207
2411
  scope_allow: z.array(z.string().min(1)).optional(),
2208
2412
  scope_deny: z.array(z.string().min(1)).optional(),
2209
2413
  allowed_paths: z.array(z.string().min(1)).optional(),
2210
2414
  denied_paths: z.array(z.string().min(1)).optional(),
2211
2415
  }));
2416
+ const PlanLargeFileSplitArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
2417
+ file: z.string().min(1),
2418
+ intent: z.string().optional().default("mechanical modularization"),
2419
+ target_dir: z.string().optional(),
2420
+ max_modules: z.number().int().min(2).max(30).optional().default(12),
2421
+ }));
2422
+ const RecordLargeFileSplitArgsSchema = ProjectRootArgSchema.merge(z.object({
2423
+ file: z.string().min(1),
2424
+ status: z.enum(["planned", "in_progress", "partial", "resolved"]),
2425
+ summary: z.string().min(1),
2426
+ modules: z.array(z.string().min(1)).optional(),
2427
+ remaining_lines: z.number().int().min(0).optional(),
2428
+ }));
2212
2429
  const QueryCodebaseArgsSchema = ProjectRootArgSchema.merge(OutputFormatSchema).merge(z.object({
2213
2430
  query: z.string().min(1),
2214
2431
  }));
@@ -2617,9 +2834,13 @@ function compactMaintenanceText(data) {
2617
2834
  function compactPreflightChangeScopeText(data) {
2618
2835
  const req = data.active_requirement ? `#${data.active_requirement.id} ${data.active_requirement.title}` : "none";
2619
2836
  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)}"`,
2837
+ `preflight_change_scope ok=${data.ok} safe_to_edit=${data.safe_to_edit} mode=${data.change_mode} requirement=${req} files=${data.files.length} intent="${oneLine(data.intent, 120)}"`,
2621
2838
  `action: ${oneLine(data.recommended_action, 180)}`,
2622
2839
  ];
2840
+ if (data.required_action)
2841
+ lines.push(`required_action=${data.required_action}`);
2842
+ if (data.allowed_change_modes?.length)
2843
+ lines.push(`allowed_change_modes=${data.allowed_change_modes.join(",")}`);
2623
2844
  if (data.scope_contract) {
2624
2845
  lines.push(`scope allow_terms=${data.scope_contract.allow_terms.length} deny_terms=${data.scope_contract.deny_terms.length} allowed_paths=${data.scope_contract.allowed_paths.length} denied_paths=${data.scope_contract.denied_paths.length}`);
2625
2846
  }
@@ -2628,6 +2849,26 @@ function compactPreflightChangeScopeText(data) {
2628
2849
  lines.push("- no development warnings");
2629
2850
  return lines.join("\n");
2630
2851
  }
2852
+ function compactLargeFileSplitPlanText(data) {
2853
+ const lines = [
2854
+ `large_file_split ok=${data.ok} file=${data.file_path} lines=${data.line_count} threshold=${data.huge_threshold_lines} action=${data.required_action}`,
2855
+ `target_dir=${data.target_dir}`,
2856
+ `forbidden=${data.forbidden_patterns.join(",")}`,
2857
+ ];
2858
+ lines.push("modules:");
2859
+ for (const m of data.modules.slice(0, 20)) {
2860
+ const decls = m.declarations.length ? ` decls=${m.declarations.slice(0, 8).join("; ")}` : " decls=(manual sections)";
2861
+ lines.push(`- ${m.module} -> ${m.target_path}${decls}`);
2862
+ }
2863
+ lines.push("steps:");
2864
+ for (const step of data.steps.slice(0, 8))
2865
+ lines.push(`- ${step}`);
2866
+ lines.push("validation:");
2867
+ for (const v of data.validation.slice(0, 8))
2868
+ lines.push(`- ${v}`);
2869
+ lines.push("hint: use format=json for full declarations/rules");
2870
+ return lines.join("\n");
2871
+ }
2631
2872
  function compactBootstrapText(data) {
2632
2873
  const lines = [];
2633
2874
  lines.push(`ok ctx ${data.root_source} watcher=${data.watcher_enabled ? (data.watcher_ready ? "ready" : "starting") : "off"} root=${data.project_root}`);
@@ -4800,6 +5041,7 @@ function buildServerInstructions() {
4800
5041
  "- BEFORE editing once target files/modules are known: call preflight_change_scope(intent, files/planned_files, optional scope_allow/scope_deny/allowed_paths/denied_paths). If ok=false/safe_to_edit=false or it returns development_warnings, stop before editing and narrow the plan unless the user explicitly expands the requirement.",
4801
5042
  "- Treat the active requirement as the only change boundary. Do not add extra business behavior, new flows, new fields, new interfaces, or touch completed/related features unless the user explicitly asked or the change is strictly necessary.",
4802
5043
  "- Do not keep piling new feature code into a large single file. Prefer small modules/services/components; if an implementation file is already large, split it before adding more responsibilities.",
5044
+ "- If preflight_change_scope/read_file_lines/grep/query_codebase/get_pending_changes/sync_change_intent returns huge_file_modularization_required, do not continue normal feature work. Call plan_large_file_split, perform mechanical modularization with real module names/directories, never create generated/parts/partN files, then call record_large_file_split before resuming normal feature work. Only use preflight_change_scope(change_mode='mechanical_modularization') for the split itself; use change_mode='emergency_hotfix' only for the smallest urgent fix and still record why the split was deferred.",
4803
5045
  "- AFTER editing + saving: call get_pending_changes() to see unsynced files, then call sync_change_intent(intent, files). (You can omit files to auto-link all pending changes.)",
4804
5046
  "- If preflight_change_scope, read_file_lines, grep, query_codebase, get_pending_changes, or sync_change_intent returns development_warnings, address those warnings before continuing or explain why the current requirement truly needs that scope.",
4805
5047
  "- After major milestones/decisions: call upsert_project_summary(summary) and/or add_note(...) to persist durable context locally.",
@@ -5470,9 +5712,19 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
5470
5712
  },
5471
5713
  {
5472
5714
  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.",
5715
+ description: "MUST call BEFORE editing once you know the intended files/modules. Checks planned files against the active requirement and optional generic scope_allow/scope_deny/allowed_paths/denied_paths. If ok=false/safe_to_edit=false, stop before editing and narrow the plan or scope contract. For huge files, use change_mode='mechanical_modularization' only when the task is to split the file.",
5474
5716
  inputSchema: toJsonSchemaCompat(PreflightChangeScopeArgsSchema),
5475
5717
  },
5718
+ {
5719
+ name: "plan_large_file_split",
5720
+ description: "Plan a mechanical modularization split for a huge implementation file. Produces real module names/directories and explicitly forbids generated/parts/partN files. Use this before normal feature work when preflight_change_scope returns huge_file_modularization_required.",
5721
+ inputSchema: toJsonSchemaCompat(PlanLargeFileSplitArgsSchema),
5722
+ },
5723
+ {
5724
+ name: "record_large_file_split",
5725
+ description: "Record the planned/in-progress/partial/resolved status of a huge-file mechanical modularization split so future sessions know the file is being decomposed and where modules moved.",
5726
+ inputSchema: toJsonSchemaCompat(RecordLargeFileSplitArgsSchema),
5727
+ },
5476
5728
  {
5477
5729
  name: "get_brain_dump",
5478
5730
  description: "Restore recent requirements/changes/notes/summary/pending changes. Prefer bootstrap_context() at session start when you also want recall from the local memory store.",
@@ -5812,6 +6064,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5812
6064
  }
5813
6065
  if (toolName === "preflight_change_scope") {
5814
6066
  const args = PreflightChangeScopeArgsSchema.parse(rawArgs);
6067
+ const changeMode = args.change_mode;
5815
6068
  flushPendingChangeBuffer();
5816
6069
  const files = (args.files ?? args.planned_files ?? []).filter((f) => typeof f === "string" && f.length > 0);
5817
6070
  const active = getActiveRequirementStmt.get();
@@ -5838,22 +6091,41 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5838
6091
  logActivity("preflight_change_scope", {
5839
6092
  req_id: active?.id ?? null,
5840
6093
  intent_preview: makePreviewText(args.intent, 200),
6094
+ change_mode: changeMode,
5841
6095
  files: files.slice(0, 25),
5842
6096
  files_total: files.length,
5843
6097
  development_warnings: development_warnings.length,
5844
6098
  });
5845
6099
  const hasTargetFiles = fileInputs.length > 0;
5846
- const hasBlockingWarnings = development_warnings.some((w) => w.severity === "blocker" || w.severity === "warning");
6100
+ const hugeWarnings = development_warnings.filter((w) => w.code === "huge_file_modularization_required");
6101
+ const hasHugeFile = hugeWarnings.length > 0;
6102
+ const blockingWarnings = development_warnings.filter((w) => isDevelopmentWarningBlockingForChangeMode(w, changeMode));
6103
+ const hasBlockingWarnings = blockingWarnings.length > 0;
5847
6104
  const safeToEdit = hasTargetFiles && !hasBlockingWarnings;
5848
6105
  const recommendedAction = !hasTargetFiles
5849
6106
  ? "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.";
6107
+ : hasHugeFile && changeMode === "mechanical_modularization" && safeToEdit
6108
+ ? "Proceed only with mechanical modularization: call plan_large_file_split, move whole declarations into real named modules/directories, avoid generated/parts files, validate, then record_large_file_split."
6109
+ : hasHugeFile && changeMode === "emergency_hotfix" && safeToEdit
6110
+ ? "Proceed only with the smallest urgent fix, do not add new responsibilities, record why mechanical modularization was deferred, and plan/record the split next."
6111
+ : hasHugeFile
6112
+ ? "Stop normal feature work. Call plan_large_file_split and perform mechanical modularization first, or rerun preflight_change_scope with change_mode='mechanical_modularization' for the split itself."
6113
+ : hasBlockingWarnings
6114
+ ? "Stop before editing. Narrow the planned files or explicitly expand the current requirement/scope contract."
6115
+ : "Planned files are within the current generic scope checks.";
6116
+ const requiredAction = hasHugeFile && changeMode !== "mechanical_modularization"
6117
+ ? "mechanical_modularization"
6118
+ : undefined;
6119
+ const allowedChangeModes = hasHugeFile
6120
+ ? ["mechanical_modularization", "emergency_hotfix"]
6121
+ : undefined;
5853
6122
  const outputValue = {
5854
6123
  ok: safeToEdit,
5855
6124
  safe_to_edit: safeToEdit,
6125
+ change_mode: changeMode,
5856
6126
  recommended_action: recommendedAction,
6127
+ required_action: requiredAction,
6128
+ allowed_change_modes: allowedChangeModes,
5857
6129
  active_requirement: active ? { id: active.id, title: active.title } : null,
5858
6130
  intent: args.intent,
5859
6131
  files: files.map(normalizeToDbPath),
@@ -5869,6 +6141,129 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
5869
6141
  ],
5870
6142
  };
5871
6143
  }
6144
+ if (toolName === "plan_large_file_split") {
6145
+ const args = PlanLargeFileSplitArgsSchema.parse(rawArgs);
6146
+ flushPendingChangeBuffer();
6147
+ const resolved = resolveReadPathUnderProjectRoot(args.file);
6148
+ let stat;
6149
+ try {
6150
+ stat = fs.statSync(resolved.absPath);
6151
+ }
6152
+ catch (err) {
6153
+ return {
6154
+ isError: true,
6155
+ content: [{ type: "text", text: toolJson({ ok: false, error: `File not found: ${String(err)}` }) }],
6156
+ };
6157
+ }
6158
+ if (!stat.isFile()) {
6159
+ return { isError: true, content: [{ type: "text", text: toolJson({ ok: false, error: "Not a file" }) }] };
6160
+ }
6161
+ if (!isLikelySourceImplementationFile(resolved.dbFilePath)) {
6162
+ return {
6163
+ isError: true,
6164
+ content: [
6165
+ {
6166
+ type: "text",
6167
+ text: toolJson({
6168
+ ok: false,
6169
+ error: "Not a recognized source implementation file",
6170
+ file_path: resolved.dbFilePath,
6171
+ }),
6172
+ },
6173
+ ],
6174
+ };
6175
+ }
6176
+ const lineInfo = countFileLinesBounded(resolved.absPath, 8_000_000);
6177
+ const lineCount = lineInfo?.lines ?? 0;
6178
+ if (lineCount < DEVELOPMENT_HUGE_FILE_LINES) {
6179
+ return {
6180
+ content: [
6181
+ {
6182
+ type: "text",
6183
+ text: toolJson({
6184
+ ok: false,
6185
+ file_path: resolved.dbFilePath,
6186
+ line_count: lineInfo?.truncated ? `${lineCount}+` : lineCount,
6187
+ huge_threshold_lines: DEVELOPMENT_HUGE_FILE_LINES,
6188
+ recommended_action: "This file is not above the huge-file threshold. Use normal focused modularity rules unless the user explicitly asked for refactoring.",
6189
+ }),
6190
+ },
6191
+ ],
6192
+ };
6193
+ }
6194
+ let targetDir = args.target_dir;
6195
+ if (targetDir) {
6196
+ targetDir = resolveProjectPathUnderRoot(targetDir, { allowRoot: true }).dbFilePath;
6197
+ }
6198
+ const plan = buildLargeFileSplitPlan({
6199
+ filePath: resolved.dbFilePath,
6200
+ absPath: resolved.absPath,
6201
+ intent: args.intent,
6202
+ targetDir,
6203
+ maxModules: args.max_modules,
6204
+ });
6205
+ logActivity("plan_large_file_split", {
6206
+ file_path: plan.file_path,
6207
+ line_count: plan.line_count,
6208
+ target_dir: plan.target_dir,
6209
+ modules: plan.modules.map((m) => m.module),
6210
+ });
6211
+ return {
6212
+ content: [
6213
+ {
6214
+ type: "text",
6215
+ text: toolCompactOrJson("plan_large_file_split", plan, compactLargeFileSplitPlanText(plan), args.format),
6216
+ },
6217
+ ],
6218
+ };
6219
+ }
6220
+ if (toolName === "record_large_file_split") {
6221
+ const args = RecordLargeFileSplitArgsSchema.parse(rawArgs);
6222
+ flushPendingChangeBuffer();
6223
+ const normalizedFile = normalizeToDbPath(args.file);
6224
+ const active = getActiveRequirementStmt.get();
6225
+ const modules = (args.modules ?? []).map(normalizeToDbPath);
6226
+ const content = [
6227
+ `Huge-file mechanical modularization ${args.status}: ${normalizedFile}`,
6228
+ "",
6229
+ args.summary,
6230
+ modules.length ? `\nModules:\n${modules.map((m) => `- ${m}`).join("\n")}` : "",
6231
+ args.remaining_lines != null ? `\nRemaining lines: ${args.remaining_lines}` : "",
6232
+ ].filter(Boolean).join("\n");
6233
+ const meta = {
6234
+ tags: ["large-file-split", "mechanical-modularization"],
6235
+ file: normalizedFile,
6236
+ status: args.status,
6237
+ modules,
6238
+ remaining_lines: args.remaining_lines ?? null,
6239
+ active_requirement_id: active?.id ?? null,
6240
+ };
6241
+ const info = insertMemoryItemStmt.run("note", `large-file-split:${normalizedFile}:${args.status}`, content, normalizedFile, null, null, active?.id ?? null, safeJson(meta), sha256Hex(content));
6242
+ const id = Number(info.lastInsertRowid);
6243
+ enqueueEmbedding(id);
6244
+ logActivity("record_large_file_split", {
6245
+ memory_item_id: id,
6246
+ file_path: normalizedFile,
6247
+ status: args.status,
6248
+ modules: modules.slice(0, 20),
6249
+ remaining_lines: args.remaining_lines ?? null,
6250
+ });
6251
+ return {
6252
+ content: [
6253
+ {
6254
+ type: "text",
6255
+ text: toolJson({
6256
+ ok: true,
6257
+ note: { id },
6258
+ file_path: normalizedFile,
6259
+ status: args.status,
6260
+ modules,
6261
+ remaining_lines: args.remaining_lines ?? null,
6262
+ }),
6263
+ },
6264
+ ],
6265
+ };
6266
+ }
5872
6267
  if (toolName === "sync_change_intent") {
5873
6268
  const args = SyncChangeIntentArgsSchema.parse(rawArgs);
5874
6269
  flushPendingChangeBuffer();