@coreyuan/vector-mind 1.0.53 → 1.1.0
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 +99 -72
- package/admin-panel/README.md +77 -0
- package/admin-panel/dist/client/assets/index-Bzyjdhh2.css +1 -0
- package/admin-panel/dist/client/assets/index-Ph73ewzt.js +9 -0
- package/admin-panel/dist/client/index.html +14 -0
- package/admin-panel/package.json +25 -0
- package/admin-panel/server/index.mjs +1424 -0
- package/admin-panel/server/security.mjs +117 -0
- package/admin-panel/server/security.test.mjs +113 -0
- package/admin-panel/server/storage.mjs +49 -0
- package/admin-panel/server/storage.test.mjs +36 -0
- package/dist/builtin-conventions.js +1 -1
- package/dist/builtin-conventions.js.map +1 -1
- package/dist/builtin-instructions.d.ts +1 -1
- package/dist/builtin-instructions.js +1 -1
- package/dist/builtin-instructions.js.map +1 -1
- package/dist/checkpoint-snapshot.d.ts +56 -0
- package/dist/checkpoint-snapshot.js +411 -0
- package/dist/checkpoint-snapshot.js.map +1 -0
- package/dist/config.d.ts +4 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/context-governance.d.ts +56 -0
- package/dist/context-governance.js +176 -0
- package/dist/context-governance.js.map +1 -0
- package/dist/database-runtime.d.ts +6 -1
- package/dist/database-runtime.js +555 -127
- package/dist/database-runtime.js.map +1 -1
- package/dist/development-warnings.d.ts +2 -1
- package/dist/development-warnings.js +92 -22
- package/dist/development-warnings.js.map +1 -1
- package/dist/file-indexing.d.ts +2 -1
- package/dist/file-indexing.js +127 -47
- package/dist/file-indexing.js.map +1 -1
- package/dist/grep.js +4 -0
- package/dist/grep.js.map +1 -1
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/dist/large-file-split.d.ts +53 -8
- package/dist/large-file-split.js +294 -75
- package/dist/large-file-split.js.map +1 -1
- package/dist/memory-maintenance/compaction.d.ts +7 -0
- package/dist/memory-maintenance/compaction.js +133 -0
- package/dist/memory-maintenance/compaction.js.map +1 -0
- package/dist/memory-maintenance/context.d.ts +37 -0
- package/dist/memory-maintenance/context.js +52 -0
- package/dist/memory-maintenance/context.js.map +1 -0
- package/dist/memory-maintenance/index-prune.d.ts +22 -0
- package/dist/memory-maintenance/index-prune.js +243 -0
- package/dist/memory-maintenance/index-prune.js.map +1 -0
- package/dist/memory-maintenance/metrics.d.ts +5 -0
- package/dist/memory-maintenance/metrics.js +30 -0
- package/dist/memory-maintenance/metrics.js.map +1 -0
- package/dist/memory-maintenance/pending.d.ts +4 -0
- package/dist/memory-maintenance/pending.js +74 -0
- package/dist/memory-maintenance/pending.js.map +1 -0
- package/dist/memory-maintenance/purge.d.ts +7 -0
- package/dist/memory-maintenance/purge.js +115 -0
- package/dist/memory-maintenance/purge.js.map +1 -0
- package/dist/memory-maintenance/runner.d.ts +5 -0
- package/dist/memory-maintenance/runner.js +182 -0
- package/dist/memory-maintenance/runner.js.map +1 -0
- package/dist/memory-maintenance/sqlite.d.ts +10 -0
- package/dist/memory-maintenance/sqlite.js +67 -0
- package/dist/memory-maintenance/sqlite.js.map +1 -0
- package/dist/memory-maintenance/types.d.ts +87 -0
- package/dist/memory-maintenance/types.js +2 -0
- package/dist/memory-maintenance/types.js.map +1 -0
- package/dist/memory-maintenance.d.ts +5 -80
- package/dist/memory-maintenance.js +3 -536
- package/dist/memory-maintenance.js.map +1 -1
- package/dist/memory-mutations.d.ts +3 -3
- package/dist/memory-mutations.js +109 -43
- package/dist/memory-mutations.js.map +1 -1
- package/dist/memory-recall.d.ts +3 -1
- package/dist/memory-recall.js +33 -1
- package/dist/memory-recall.js.map +1 -1
- package/dist/operation-scope.js +42 -12
- package/dist/operation-scope.js.map +1 -1
- package/dist/path-containment.d.ts +5 -0
- package/dist/path-containment.js +64 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/path-filters.d.ts +1 -1
- package/dist/path-filters.js +7 -6
- package/dist/path-filters.js.map +1 -1
- package/dist/path-rules.d.ts +1 -0
- package/dist/path-rules.js +23 -1
- package/dist/path-rules.js.map +1 -1
- package/dist/pending-changes.d.ts +5 -0
- package/dist/pending-changes.js +30 -16
- package/dist/pending-changes.js.map +1 -1
- package/dist/project-files.d.ts +1 -0
- package/dist/project-files.js +19 -10
- package/dist/project-files.js.map +1 -1
- package/dist/root.d.ts +4 -1
- package/dist/root.js +34 -4
- package/dist/root.js.map +1 -1
- package/dist/rtk-integrity.d.ts +5 -0
- package/dist/rtk-integrity.js +35 -0
- package/dist/rtk-integrity.js.map +1 -0
- package/dist/rtk-shim.js +22 -9
- package/dist/rtk-shim.js.map +1 -1
- package/dist/rtk-tools.d.ts +8 -0
- package/dist/rtk-tools.js +59 -42
- package/dist/rtk-tools.js.map +1 -1
- package/dist/server-instructions.js +8 -88
- package/dist/server-instructions.js.map +1 -1
- package/dist/tool-catalog.d.ts +2 -0
- package/dist/tool-catalog.js +77 -24
- package/dist/tool-catalog.js.map +1 -1
- package/dist/tool-handlers/context-recovery.js +79 -54
- package/dist/tool-handlers/context-recovery.js.map +1 -1
- package/dist/tool-handlers/large-files.js +615 -41
- package/dist/tool-handlers/large-files.js.map +1 -1
- package/dist/tool-handlers/memory-diagnostics.js +240 -45
- package/dist/tool-handlers/memory-diagnostics.js.map +1 -1
- package/dist/tool-handlers/memory.js +167 -22
- package/dist/tool-handlers/memory.js.map +1 -1
- package/dist/tool-handlers/operations.js +28 -8
- package/dist/tool-handlers/operations.js.map +1 -1
- package/dist/tool-handlers/requirement-status.d.ts +5 -0
- package/dist/tool-handlers/requirement-status.js +267 -0
- package/dist/tool-handlers/requirement-status.js.map +1 -0
- package/dist/tool-handlers/requirements.js +851 -115
- package/dist/tool-handlers/requirements.js.map +1 -1
- package/dist/tool-handlers.d.ts +4 -0
- package/dist/tool-handlers.js +439 -6
- package/dist/tool-handlers.js.map +1 -1
- package/dist/tool-output.d.ts +71 -2
- package/dist/tool-output.js +56 -13
- package/dist/tool-output.js.map +1 -1
- package/dist/tool-schemas.d.ts +243 -0
- package/dist/tool-schemas.js +112 -26
- package/dist/tool-schemas.js.map +1 -1
- package/dist/types.d.ts +10 -1
- package/docs/capability-matrix.md +23 -17
- package/package.json +21 -5
- package/skills/vector-mind-autopilot/SKILL.md +82 -23
- package/skills/vector-mind-autopilot/references/claude-project-instructions.md +14 -11
- package/skills/vector-mind-autopilot/references/universal-system-prompt.md +24 -16
|
@@ -1,18 +1,139 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { DEVELOPMENT_BLOCK_FILE_LINES, DEVELOPMENT_HUGE_FILE_LINES } from "../config.js";
|
|
3
4
|
import { PlanLargeFileSplitArgsSchema, RecordLargeFileSplitArgsSchema } from "../tool-schemas.js";
|
|
4
|
-
import {
|
|
5
|
-
import { buildLargeFileSplitPlan } from "../large-file-split.js";
|
|
5
|
+
import { isLikelySourceImplementationFile } from "../development-warnings.js";
|
|
6
|
+
import { buildLargeFileSplitPlan, countFileLinesStreaming, hashFileContentStreaming, hasOrdinalModuleName, LARGE_FILE_SPLIT_PLANNER_VERSION, } from "../large-file-split.js";
|
|
6
7
|
import { toolCompactOrJson } from "../token-savings.js";
|
|
7
8
|
import { flushPendingChangeBuffer } from "../file-indexing.js";
|
|
8
9
|
import { logActivity } from "../activity-log.js";
|
|
9
10
|
import { resolveProjectPathUnderRoot, resolveReadPathUnderProjectRoot } from "../project-files.js";
|
|
10
11
|
import { compactLargeFileSplitPlanText, safeJson, toolJson } from "../tool-output.js";
|
|
12
|
+
import { parseMetadataJson } from "../memory-recall.js";
|
|
13
|
+
import { looksLikeGeneratedFile, shouldIgnoreDbFilePath } from "../path-rules.js";
|
|
14
|
+
function readFileHead(absPath, maxBytes = 16_384) {
|
|
15
|
+
const fd = fs.openSync(absPath, "r");
|
|
16
|
+
try {
|
|
17
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
18
|
+
const bytesRead = fs.readSync(fd, buffer, 0, maxBytes, 0);
|
|
19
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
fs.closeSync(fd);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function filePathIdentity(input) {
|
|
26
|
+
const normalized = input.replace(/\\/g, "/");
|
|
27
|
+
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
28
|
+
}
|
|
29
|
+
function validSplitPlanStatus(status) {
|
|
30
|
+
return ["planned", "in_progress", "partial", "resolved", "needs_refinement"].includes(String(status));
|
|
31
|
+
}
|
|
32
|
+
function parsePersistedPlan(content) {
|
|
33
|
+
try {
|
|
34
|
+
const payload = JSON.parse(content);
|
|
35
|
+
return payload?.plan && typeof payload.plan === "object" && !Array.isArray(payload.plan)
|
|
36
|
+
? payload.plan
|
|
37
|
+
: null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function compactPlanMetadata(args) {
|
|
44
|
+
return {
|
|
45
|
+
type: "large_file_split_plan",
|
|
46
|
+
status: args.status,
|
|
47
|
+
file: args.file,
|
|
48
|
+
source_state_hash: args.sourceStateHash,
|
|
49
|
+
source_content_hash: args.sourceStateHash,
|
|
50
|
+
planner_fingerprint: args.plannerFingerprint,
|
|
51
|
+
planner_version: LARGE_FILE_SPLIT_PLANNER_VERSION,
|
|
52
|
+
current_state_hash: args.currentStateHash ?? args.sourceStateHash,
|
|
53
|
+
requirement_id: args.requirement.id,
|
|
54
|
+
goal_key: args.requirement.goal_key ?? null,
|
|
55
|
+
plan_ok: args.plan.ok,
|
|
56
|
+
coverage_complete: args.plan.coverage.complete,
|
|
57
|
+
module_constraints_satisfied: args.plan.module_constraints?.satisfied === true,
|
|
58
|
+
target_paths: args.plan.modules.map((module) => module.target_path),
|
|
59
|
+
analysis_mode: args.plan.analysis_mode,
|
|
60
|
+
confidence: args.plan.confidence,
|
|
61
|
+
verification: args.verification ?? [],
|
|
62
|
+
verification_gaps: args.verificationGaps ?? args.plan.warnings,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function supersedePlanRow(context, row, replacementPlanId, reason) {
|
|
66
|
+
const metadata = parseMetadataJson(row.metadata_json);
|
|
67
|
+
let content = row.content;
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(row.content);
|
|
70
|
+
content = safeJson({
|
|
71
|
+
...parsed,
|
|
72
|
+
status: "superseded",
|
|
73
|
+
superseded: true,
|
|
74
|
+
superseded_reason: reason,
|
|
75
|
+
superseded_by_plan_id: replacementPlanId,
|
|
76
|
+
}) ?? row.content;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Keep legacy content intact while making metadata authoritative.
|
|
80
|
+
}
|
|
81
|
+
context.getDb().prepare(`UPDATE memory_items
|
|
82
|
+
SET content = ?, metadata_json = ?, content_hash = ?, updated_at = CURRENT_TIMESTAMP
|
|
83
|
+
WHERE id = ? AND kind = 'large_file_split_plan'`).run(content, safeJson({
|
|
84
|
+
...metadata,
|
|
85
|
+
status: "superseded",
|
|
86
|
+
superseded: true,
|
|
87
|
+
superseded_reason: reason,
|
|
88
|
+
superseded_by_plan_id: replacementPlanId,
|
|
89
|
+
superseded_at: new Date().toISOString(),
|
|
90
|
+
}), context.sha256Hex(content), row.id);
|
|
91
|
+
}
|
|
92
|
+
function resolveSplitRequirement(context, args) {
|
|
93
|
+
const { getActiveRequirementByIdStmt, getActiveRequirementByGoalKeyStmt, listActiveRequirementsStmt, } = context.getStatements();
|
|
94
|
+
if (args.req_id) {
|
|
95
|
+
const requirement = getActiveRequirementByIdStmt.get(args.req_id);
|
|
96
|
+
return { requirement, ambiguous: false, active_count: requirement ? 1 : 0 };
|
|
97
|
+
}
|
|
98
|
+
if (args.goal_key?.trim()) {
|
|
99
|
+
const requirement = getActiveRequirementByGoalKeyStmt.get(args.goal_key.trim());
|
|
100
|
+
return { requirement, ambiguous: false, active_count: requirement ? 1 : 0 };
|
|
101
|
+
}
|
|
102
|
+
const active = listActiveRequirementsStmt.all(2);
|
|
103
|
+
return {
|
|
104
|
+
requirement: active.length === 1 ? active[0] : undefined,
|
|
105
|
+
ambiguous: active.length > 1,
|
|
106
|
+
active_count: active.length,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function splitRequirementError(resolution) {
|
|
110
|
+
if (resolution.requirement)
|
|
111
|
+
return null;
|
|
112
|
+
return {
|
|
113
|
+
isError: true,
|
|
114
|
+
content: [{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: toolJson({
|
|
117
|
+
ok: false,
|
|
118
|
+
error: resolution.ambiguous
|
|
119
|
+
? "Multiple active requirements exist. Pass req_id or goal_key for the large-file split workflow."
|
|
120
|
+
: "No matching active requirement. Start or resume the requirement before planning or recording a large-file split.",
|
|
121
|
+
active_count: resolution.active_count,
|
|
122
|
+
}),
|
|
123
|
+
}],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
11
126
|
export async function handlePlanLargeFileSplit(rawArgs, context) {
|
|
12
127
|
const projectRoot = context.getProjectRoot();
|
|
13
128
|
const normalizeToDbPath = context.normalizeToDbPath;
|
|
129
|
+
const { insertMemoryItemStmt } = context.getStatements();
|
|
14
130
|
const args = PlanLargeFileSplitArgsSchema.parse(rawArgs);
|
|
15
131
|
flushPendingChangeBuffer();
|
|
132
|
+
const requirementResolution = resolveSplitRequirement(context, args);
|
|
133
|
+
const requirementError = splitRequirementError(requirementResolution);
|
|
134
|
+
if (requirementError)
|
|
135
|
+
return requirementError;
|
|
136
|
+
const active = requirementResolution.requirement;
|
|
16
137
|
const resolved = resolveReadPathUnderProjectRoot(projectRoot, normalizeToDbPath, args.file);
|
|
17
138
|
let stat;
|
|
18
139
|
try {
|
|
@@ -42,37 +163,241 @@ export async function handlePlanLargeFileSplit(rawArgs, context) {
|
|
|
42
163
|
],
|
|
43
164
|
};
|
|
44
165
|
}
|
|
45
|
-
|
|
46
|
-
const lineCount = lineInfo?.lines ?? 0;
|
|
47
|
-
if (lineCount < DEVELOPMENT_HUGE_FILE_LINES) {
|
|
166
|
+
if (shouldIgnoreDbFilePath(resolved.dbFilePath) || looksLikeGeneratedFile(readFileHead(resolved.absPath))) {
|
|
48
167
|
return {
|
|
49
|
-
|
|
50
|
-
|
|
168
|
+
isError: true,
|
|
169
|
+
content: [{
|
|
51
170
|
type: "text",
|
|
52
171
|
text: toolJson({
|
|
53
172
|
ok: false,
|
|
173
|
+
error: "Generated, vendored, dependency, or build-output source files must be regenerated or excluded, not mechanically modularized.",
|
|
54
174
|
file_path: resolved.dbFilePath,
|
|
55
|
-
line_count: lineInfo?.truncated ? `${lineCount}+` : lineCount,
|
|
56
|
-
huge_threshold_lines: DEVELOPMENT_HUGE_FILE_LINES,
|
|
57
|
-
recommended_action: "This file is not above the huge-file threshold. Use normal focused modularity rules unless the user explicitly asked for refactoring.",
|
|
58
175
|
}),
|
|
59
|
-
},
|
|
60
|
-
],
|
|
176
|
+
}],
|
|
61
177
|
};
|
|
62
178
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
179
|
+
const fileBaseName = path.basename(resolved.dbFilePath, path.extname(resolved.dbFilePath));
|
|
180
|
+
const fileParentDir = path.dirname(resolved.dbFilePath).replace(/\\/g, "/");
|
|
181
|
+
const defaultTargetDir = fileParentDir === "." ? fileBaseName : `${fileParentDir}/${fileBaseName}`;
|
|
182
|
+
let targetDir;
|
|
183
|
+
try {
|
|
184
|
+
const resolvedTargetDir = resolveProjectPathUnderRoot(projectRoot, normalizeToDbPath, args.target_dir ?? defaultTargetDir, { allowRoot: true });
|
|
185
|
+
targetDir = resolvedTargetDir.dbFilePath;
|
|
186
|
+
if (filePathIdentity(targetDir) === filePathIdentity(resolved.dbFilePath)) {
|
|
187
|
+
throw new Error("Large-file split target_dir must not be the source file.");
|
|
188
|
+
}
|
|
189
|
+
if (fs.existsSync(resolvedTargetDir.absPath) && !fs.statSync(resolvedTargetDir.absPath).isDirectory()) {
|
|
190
|
+
throw new Error(`Large-file split target_dir must be a directory boundary: ${targetDir}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
return {
|
|
195
|
+
isError: true,
|
|
196
|
+
content: [{ type: "text", text: toolJson({ ok: false, error: String(err) }) }],
|
|
197
|
+
};
|
|
66
198
|
}
|
|
67
|
-
|
|
199
|
+
let moduleOverrides;
|
|
200
|
+
try {
|
|
201
|
+
const seenModules = new Set();
|
|
202
|
+
const seenTargets = new Set();
|
|
203
|
+
moduleOverrides = args.module_overrides?.map((override) => {
|
|
204
|
+
const moduleName = override.module.trim();
|
|
205
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(moduleName) || hasOrdinalModuleName(moduleName)) {
|
|
206
|
+
throw new Error(`Invalid semantic module name: ${override.module}`);
|
|
207
|
+
}
|
|
208
|
+
const moduleKey = moduleName.toLowerCase();
|
|
209
|
+
if (seenModules.has(moduleKey))
|
|
210
|
+
throw new Error(`Duplicate module override: ${moduleName}`);
|
|
211
|
+
seenModules.add(moduleKey);
|
|
212
|
+
const targetPath = resolveProjectPathUnderRoot(projectRoot, normalizeToDbPath, override.target_path).dbFilePath;
|
|
213
|
+
const targetKey = filePathIdentity(targetPath);
|
|
214
|
+
if (seenTargets.has(targetKey))
|
|
215
|
+
throw new Error(`Duplicate module target_path: ${targetPath}`);
|
|
216
|
+
seenTargets.add(targetKey);
|
|
217
|
+
const relativeToTargetDir = path.posix.relative(targetDir.replace(/\\/g, "/"), targetPath.replace(/\\/g, "/"));
|
|
218
|
+
if (relativeToTargetDir.startsWith("../") || relativeToTargetDir === ".." || path.posix.isAbsolute(relativeToTargetDir)) {
|
|
219
|
+
throw new Error(`module_overrides target_path must stay under target_dir ${targetDir}: ${targetPath}`);
|
|
220
|
+
}
|
|
221
|
+
const normalizedTarget = targetPath.toLowerCase();
|
|
222
|
+
const targetBase = path.basename(normalizedTarget);
|
|
223
|
+
const sourceExtension = path.extname(resolved.dbFilePath).toLowerCase();
|
|
224
|
+
const targetExtension = path.extname(targetPath).toLowerCase();
|
|
225
|
+
const targetAbsPath = path.join(projectRoot, targetPath);
|
|
226
|
+
if (targetKey === filePathIdentity(resolved.dbFilePath) ||
|
|
227
|
+
!sourceExtension || targetExtension !== sourceExtension ||
|
|
228
|
+
fs.existsSync(targetAbsPath) && fs.statSync(targetAbsPath).isDirectory() ||
|
|
229
|
+
hasOrdinalModuleName(targetPath) ||
|
|
230
|
+
shouldIgnoreDbFilePath(targetPath) ||
|
|
231
|
+
normalizedTarget.includes(".parts/") ||
|
|
232
|
+
targetBase.includes(".generated.") ||
|
|
233
|
+
/(?:^|[_-])part\d*(?:\.|$)/.test(targetBase)) {
|
|
234
|
+
throw new Error(`Invalid module override target_path: ${targetPath}`);
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
module: moduleName,
|
|
238
|
+
target_path: targetPath,
|
|
239
|
+
declaration_names: override.declaration_names
|
|
240
|
+
? [...new Set(override.declaration_names.map((name) => name.trim()))].sort()
|
|
241
|
+
: undefined,
|
|
242
|
+
line_ranges: override.line_ranges
|
|
243
|
+
? [...override.line_ranges].sort((a, b) => a.start - b.start || a.end - b.end)
|
|
244
|
+
: undefined,
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
return {
|
|
250
|
+
isError: true,
|
|
251
|
+
content: [{ type: "text", text: toolJson({ ok: false, error: String(err) }) }],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
const effectiveMaxDeclarations = Math.min(200, args.max_declarations_per_module);
|
|
255
|
+
const effectiveMaxLines = Math.min(DEVELOPMENT_BLOCK_FILE_LINES, args.max_lines_per_module);
|
|
256
|
+
const fingerprintPayload = {
|
|
257
|
+
planner_version: LARGE_FILE_SPLIT_PLANNER_VERSION,
|
|
258
|
+
target_dir: targetDir,
|
|
259
|
+
max_modules: args.max_modules,
|
|
260
|
+
max_declarations_per_module: effectiveMaxDeclarations,
|
|
261
|
+
max_lines_per_module: effectiveMaxLines,
|
|
262
|
+
module_overrides: moduleOverrides ?? [],
|
|
263
|
+
};
|
|
264
|
+
const plannerFingerprint = context.sha256Hex(safeJson(fingerprintPayload) ?? "{}");
|
|
265
|
+
const plan = await buildLargeFileSplitPlan({
|
|
68
266
|
filePath: resolved.dbFilePath,
|
|
69
267
|
absPath: resolved.absPath,
|
|
70
268
|
intent: args.intent,
|
|
71
269
|
targetDir,
|
|
72
270
|
maxModules: args.max_modules,
|
|
73
271
|
hugeThresholdLines: DEVELOPMENT_HUGE_FILE_LINES,
|
|
272
|
+
maxDeclarationsPerModule: effectiveMaxDeclarations,
|
|
273
|
+
maxEstimatedLinesPerModule: effectiveMaxLines,
|
|
274
|
+
moduleOverrides,
|
|
275
|
+
plannerFingerprint,
|
|
276
|
+
});
|
|
277
|
+
if (plan.line_count < DEVELOPMENT_HUGE_FILE_LINES) {
|
|
278
|
+
return {
|
|
279
|
+
content: [{
|
|
280
|
+
type: "text",
|
|
281
|
+
text: toolJson({
|
|
282
|
+
ok: false,
|
|
283
|
+
file_path: resolved.dbFilePath,
|
|
284
|
+
line_count: plan.line_count,
|
|
285
|
+
huge_threshold_lines: DEVELOPMENT_HUGE_FILE_LINES,
|
|
286
|
+
recommended_action: "This file is not above the huge-file threshold. Use normal focused modularity rules unless the user explicitly asked for refactoring.",
|
|
287
|
+
}),
|
|
288
|
+
}],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const sourceStateHash = plan.source_content_hash;
|
|
292
|
+
const existingRows = context.getDb().prepare(`SELECT id, kind, content, req_id, file_path, metadata_json
|
|
293
|
+
FROM memory_items
|
|
294
|
+
WHERE kind = 'large_file_split_plan' AND req_id = ? AND file_path = ?
|
|
295
|
+
ORDER BY updated_at DESC, id DESC
|
|
296
|
+
LIMIT 20`).all(active.id, resolved.dbFilePath);
|
|
297
|
+
const activeStatuses = new Set(["planned", "in_progress", "partial", "needs_refinement"]);
|
|
298
|
+
const progressingRows = existingRows.filter((row) => {
|
|
299
|
+
const status = String(parseMetadataJson(row.metadata_json).status);
|
|
300
|
+
return status === "in_progress" || status === "partial";
|
|
301
|
+
});
|
|
302
|
+
const reusable = existingRows.find((row) => {
|
|
303
|
+
const metadata = parseMetadataJson(row.metadata_json);
|
|
304
|
+
const status = String(metadata.status);
|
|
305
|
+
const expectedState = (status === "in_progress" || status === "partial") && typeof metadata.current_state_hash === "string"
|
|
306
|
+
? metadata.current_state_hash
|
|
307
|
+
: typeof metadata.source_content_hash === "string"
|
|
308
|
+
? metadata.source_content_hash
|
|
309
|
+
: metadata.source_state_hash;
|
|
310
|
+
return activeStatuses.has(String(metadata.status)) &&
|
|
311
|
+
expectedState === sourceStateHash &&
|
|
312
|
+
metadata.planner_fingerprint === plannerFingerprint &&
|
|
313
|
+
!!parsePersistedPlan(row.content);
|
|
314
|
+
});
|
|
315
|
+
if (progressingRows.length && (progressingRows.length !== 1 || reusable?.id !== progressingRows[0].id)) {
|
|
316
|
+
return {
|
|
317
|
+
isError: true,
|
|
318
|
+
content: [{
|
|
319
|
+
type: "text",
|
|
320
|
+
text: toolJson({
|
|
321
|
+
ok: false,
|
|
322
|
+
error: "An in-progress or partial split plan already owns this requirement/file. Resume it with record_large_file_split or complete/defer the requirement before replanning.",
|
|
323
|
+
active_progress_plans: progressingRows.map((row) => ({
|
|
324
|
+
plan_id: row.id,
|
|
325
|
+
status: parseMetadataJson(row.metadata_json).status,
|
|
326
|
+
})),
|
|
327
|
+
}),
|
|
328
|
+
}],
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (reusable) {
|
|
332
|
+
const reusedPlan = parsePersistedPlan(reusable.content);
|
|
333
|
+
for (const duplicate of existingRows) {
|
|
334
|
+
if (duplicate.id === reusable.id)
|
|
335
|
+
continue;
|
|
336
|
+
const metadata = parseMetadataJson(duplicate.metadata_json);
|
|
337
|
+
if (!activeStatuses.has(String(metadata.status)))
|
|
338
|
+
continue;
|
|
339
|
+
if (metadata.status === "planned" || metadata.status === "needs_refinement") {
|
|
340
|
+
supersedePlanRow(context, duplicate, reusable.id, "duplicate_plan_reused");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
logActivity("plan_large_file_split", {
|
|
344
|
+
plan_id: reusable.id,
|
|
345
|
+
req_id: active.id,
|
|
346
|
+
file_path: reusedPlan.file_path,
|
|
347
|
+
reused: true,
|
|
348
|
+
});
|
|
349
|
+
return {
|
|
350
|
+
content: [{
|
|
351
|
+
type: "text",
|
|
352
|
+
text: toolCompactOrJson("plan_large_file_split", {
|
|
353
|
+
...reusedPlan,
|
|
354
|
+
plan_id: reusable.id,
|
|
355
|
+
plan_status: parseMetadataJson(reusable.metadata_json).status,
|
|
356
|
+
source_state_hash: sourceStateHash,
|
|
357
|
+
reused: true,
|
|
358
|
+
requirement: { id: active.id, title: active.title, goal_key: active.goal_key ?? null },
|
|
359
|
+
}, compactLargeFileSplitPlanText({
|
|
360
|
+
...reusedPlan,
|
|
361
|
+
plan_id: reusable.id,
|
|
362
|
+
plan_status: String(parseMetadataJson(reusable.metadata_json).status ?? "planned"),
|
|
363
|
+
requirement: { id: active.id, title: active.title },
|
|
364
|
+
}), args.format),
|
|
365
|
+
}],
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
const planStatus = plan.ok ? "planned" : "needs_refinement";
|
|
369
|
+
const planMetadata = compactPlanMetadata({
|
|
370
|
+
status: planStatus,
|
|
371
|
+
file: resolved.dbFilePath,
|
|
372
|
+
sourceStateHash,
|
|
373
|
+
plannerFingerprint,
|
|
374
|
+
requirement: active,
|
|
375
|
+
plan,
|
|
74
376
|
});
|
|
377
|
+
const planContent = safeJson({
|
|
378
|
+
type: "large_file_split_plan",
|
|
379
|
+
status: planStatus,
|
|
380
|
+
file: resolved.dbFilePath,
|
|
381
|
+
requirement: { id: active.id, title: active.title, goal_key: active.goal_key ?? null },
|
|
382
|
+
source_state_hash: sourceStateHash,
|
|
383
|
+
source_content_hash: sourceStateHash,
|
|
384
|
+
planner_fingerprint: plannerFingerprint,
|
|
385
|
+
plan,
|
|
386
|
+
});
|
|
387
|
+
const planInfo = insertMemoryItemStmt.run("large_file_split_plan", `large-file-split:${resolved.dbFilePath}`, planContent, resolved.dbFilePath, null, null, active.id, safeJson(planMetadata), context.sha256Hex(`${resolved.dbFilePath}\n${sourceStateHash ?? "missing"}\n${safeJson(plan)}`));
|
|
388
|
+
const planId = Number(planInfo.lastInsertRowid);
|
|
389
|
+
for (const stale of existingRows) {
|
|
390
|
+
const metadata = parseMetadataJson(stale.metadata_json);
|
|
391
|
+
if (metadata.status !== "planned" && metadata.status !== "needs_refinement")
|
|
392
|
+
continue;
|
|
393
|
+
const reason = metadata.source_content_hash === sourceStateHash || metadata.source_state_hash === sourceStateHash
|
|
394
|
+
? "planner_configuration_changed"
|
|
395
|
+
: "source_content_changed";
|
|
396
|
+
supersedePlanRow(context, stale, planId, reason);
|
|
397
|
+
}
|
|
75
398
|
logActivity("plan_large_file_split", {
|
|
399
|
+
plan_id: planId,
|
|
400
|
+
req_id: active.id,
|
|
76
401
|
file_path: plan.file_path,
|
|
77
402
|
line_count: plan.line_count,
|
|
78
403
|
target_dir: plan.target_dir,
|
|
@@ -82,43 +407,288 @@ export async function handlePlanLargeFileSplit(rawArgs, context) {
|
|
|
82
407
|
content: [
|
|
83
408
|
{
|
|
84
409
|
type: "text",
|
|
85
|
-
text: toolCompactOrJson("plan_large_file_split",
|
|
410
|
+
text: toolCompactOrJson("plan_large_file_split", {
|
|
411
|
+
...plan,
|
|
412
|
+
plan_id: planId,
|
|
413
|
+
plan_status: planStatus,
|
|
414
|
+
source_state_hash: sourceStateHash,
|
|
415
|
+
requirement: { id: active.id, title: active.title, goal_key: active.goal_key ?? null },
|
|
416
|
+
}, compactLargeFileSplitPlanText({
|
|
417
|
+
...plan,
|
|
418
|
+
plan_id: planId,
|
|
419
|
+
plan_status: planStatus,
|
|
420
|
+
requirement: { id: active.id, title: active.title },
|
|
421
|
+
}), args.format),
|
|
86
422
|
},
|
|
87
423
|
],
|
|
88
424
|
};
|
|
89
425
|
}
|
|
90
426
|
export async function handleRecordLargeFileSplit(rawArgs, context) {
|
|
427
|
+
const projectRoot = context.getProjectRoot();
|
|
91
428
|
const normalizeToDbPath = context.normalizeToDbPath;
|
|
92
429
|
const sha256Hex = context.sha256Hex;
|
|
93
|
-
const {
|
|
430
|
+
const { getMemoryItemByIdStmt } = context.getStatements();
|
|
94
431
|
const args = RecordLargeFileSplitArgsSchema.parse(rawArgs);
|
|
95
432
|
flushPendingChangeBuffer();
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
433
|
+
const requirementResolution = resolveSplitRequirement(context, args);
|
|
434
|
+
const requirementError = splitRequirementError(requirementResolution);
|
|
435
|
+
if (requirementError)
|
|
436
|
+
return requirementError;
|
|
437
|
+
const active = requirementResolution.requirement;
|
|
438
|
+
const resolvedFile = resolveProjectPathUnderRoot(projectRoot, normalizeToDbPath, args.file);
|
|
439
|
+
const normalizedFile = resolvedFile.dbFilePath;
|
|
440
|
+
const planRow = getMemoryItemByIdStmt.get(args.plan_id);
|
|
441
|
+
const planMetadata = parseMetadataJson(planRow?.metadata_json);
|
|
442
|
+
const legacyPlan = planMetadata.plan && typeof planMetadata.plan === "object" && !Array.isArray(planMetadata.plan)
|
|
443
|
+
? planMetadata.plan
|
|
444
|
+
: null;
|
|
445
|
+
const persistedPlan = planRow ? (parsePersistedPlan(planRow.content) ?? legacyPlan) : null;
|
|
446
|
+
const storedStatus = planMetadata.status;
|
|
447
|
+
if (!planRow ||
|
|
448
|
+
planRow.kind !== "large_file_split_plan" ||
|
|
449
|
+
planMetadata.type !== "large_file_split_plan" ||
|
|
450
|
+
planRow.req_id !== active.id ||
|
|
451
|
+
planMetadata.file !== normalizedFile ||
|
|
452
|
+
!persistedPlan ||
|
|
453
|
+
!validSplitPlanStatus(storedStatus)) {
|
|
454
|
+
return {
|
|
455
|
+
isError: true,
|
|
456
|
+
content: [{
|
|
457
|
+
type: "text",
|
|
458
|
+
text: toolJson({
|
|
459
|
+
ok: false,
|
|
460
|
+
error: "plan_id does not identify a compatible large-file split plan for this requirement and file.",
|
|
461
|
+
plan_id: args.plan_id,
|
|
462
|
+
req_id: active.id,
|
|
463
|
+
file_path: normalizedFile,
|
|
464
|
+
}),
|
|
465
|
+
}],
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (storedStatus === "needs_refinement") {
|
|
469
|
+
return {
|
|
470
|
+
isError: true,
|
|
471
|
+
content: [{
|
|
472
|
+
type: "text",
|
|
473
|
+
text: toolJson({
|
|
474
|
+
ok: false,
|
|
475
|
+
error: "This split plan is incomplete and must be refined before progress can be recorded.",
|
|
476
|
+
plan_id: args.plan_id,
|
|
477
|
+
}),
|
|
478
|
+
}],
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const modules = (args.modules ?? []).map((modulePath) => resolveProjectPathUnderRoot(projectRoot, normalizeToDbPath, modulePath).dbFilePath);
|
|
482
|
+
const ordinalModules = modules.filter(hasOrdinalModuleName);
|
|
483
|
+
const ignoredModules = modules.filter(shouldIgnoreDbFilePath);
|
|
484
|
+
const fakeSplitModules = modules.filter((modulePath) => {
|
|
485
|
+
const normalized = modulePath.replace(/\\/g, "/").toLowerCase();
|
|
486
|
+
const base = path.basename(normalized);
|
|
487
|
+
return normalized.includes(".parts/") || base.includes(".generated.") || /(?:^|[_-])part\d*(?:\.|$)/.test(base);
|
|
488
|
+
});
|
|
489
|
+
if (ordinalModules.length || ignoredModules.length || fakeSplitModules.length) {
|
|
490
|
+
return {
|
|
491
|
+
isError: true,
|
|
492
|
+
content: [
|
|
493
|
+
{
|
|
494
|
+
type: "text",
|
|
495
|
+
text: toolJson({
|
|
496
|
+
ok: false,
|
|
497
|
+
error: "Ordinal-prefixed, generated, parts, or ignored module paths are not allowed for mechanical modularization.",
|
|
498
|
+
file_path: normalizedFile,
|
|
499
|
+
invalid_modules: [...new Set([...ordinalModules, ...ignoredModules, ...fakeSplitModules])],
|
|
500
|
+
required_naming: "Use stable semantic names such as config.ts, api.ts, service.ts, storage.ts, ui.ts, or maintenance.ts; do not use 1_xxx, 2_xxx, 03-xxx, or other ordering prefixes.",
|
|
501
|
+
}),
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
const allowedTransitions = {
|
|
507
|
+
planned: ["in_progress", "partial", "resolved"],
|
|
508
|
+
in_progress: ["partial", "resolved"],
|
|
509
|
+
partial: ["in_progress", "resolved"],
|
|
510
|
+
resolved: ["resolved"],
|
|
511
|
+
};
|
|
512
|
+
if (!allowedTransitions[storedStatus].includes(args.status)) {
|
|
513
|
+
return {
|
|
514
|
+
isError: true,
|
|
515
|
+
content: [{
|
|
516
|
+
type: "text",
|
|
517
|
+
text: toolJson({
|
|
518
|
+
ok: false,
|
|
519
|
+
error: `Invalid split plan status transition: ${storedStatus} -> ${args.status}.`,
|
|
520
|
+
allowed_statuses: allowedTransitions[storedStatus],
|
|
521
|
+
}),
|
|
522
|
+
}],
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
const storedPlanModules = persistedPlan.modules.map((module) => module.target_path);
|
|
526
|
+
const unexpectedModules = modules.filter((modulePath) => !storedPlanModules.includes(modulePath));
|
|
527
|
+
if (args.status === "planned" && modules.length) {
|
|
528
|
+
return {
|
|
529
|
+
isError: true,
|
|
530
|
+
content: [{
|
|
531
|
+
type: "text",
|
|
532
|
+
text: toolJson({
|
|
533
|
+
ok: false,
|
|
534
|
+
error: "Planned status uses the persisted plan modules and does not accept caller module overrides.",
|
|
535
|
+
plan_id: args.plan_id,
|
|
536
|
+
}),
|
|
537
|
+
}],
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
if (unexpectedModules.length) {
|
|
541
|
+
return {
|
|
542
|
+
isError: true,
|
|
543
|
+
content: [{
|
|
544
|
+
type: "text",
|
|
545
|
+
text: toolJson({
|
|
546
|
+
ok: false,
|
|
547
|
+
error: "Progress modules must be a subset of the persisted split plan.",
|
|
548
|
+
unexpected_modules: unexpectedModules,
|
|
549
|
+
}),
|
|
550
|
+
}],
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
const missingModules = modules.filter((modulePath) => {
|
|
554
|
+
try {
|
|
555
|
+
return !fs.statSync(path.join(projectRoot, modulePath)).isFile();
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
return true;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
const missingPlannedModules = storedPlanModules.filter((modulePath) => !modules.includes(modulePath));
|
|
562
|
+
if ((args.status === "partial" || args.status === "resolved") && (!modules.length || missingModules.length) ||
|
|
563
|
+
args.status === "in_progress" && modules.length > 0 && missingModules.length > 0) {
|
|
564
|
+
return {
|
|
565
|
+
isError: true,
|
|
566
|
+
content: [{
|
|
567
|
+
type: "text",
|
|
568
|
+
text: toolJson({
|
|
569
|
+
ok: false,
|
|
570
|
+
error: "Submitted in-progress, partial, or resolved module paths must exist under project_root; partial/resolved also require at least one module.",
|
|
571
|
+
missing_modules: missingModules,
|
|
572
|
+
}),
|
|
573
|
+
}],
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
if (args.status === "resolved" && missingPlannedModules.length) {
|
|
577
|
+
return {
|
|
578
|
+
isError: true,
|
|
579
|
+
content: [{
|
|
580
|
+
type: "text",
|
|
581
|
+
text: toolJson({
|
|
582
|
+
ok: false,
|
|
583
|
+
error: "Resolved status must account for every module in the persisted split plan.",
|
|
584
|
+
missing_planned_modules: missingPlannedModules,
|
|
585
|
+
}),
|
|
586
|
+
}],
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
const actualRemainingLines = fs.existsSync(resolvedFile.absPath)
|
|
590
|
+
? await countFileLinesStreaming(resolvedFile.absPath)
|
|
591
|
+
: 0;
|
|
592
|
+
const verification = args.verification ?? [];
|
|
593
|
+
const verificationGaps = args.verification_gaps ?? [];
|
|
594
|
+
const oversizedResolvedModules = [];
|
|
595
|
+
if (args.status === "resolved") {
|
|
596
|
+
for (const modulePath of modules) {
|
|
597
|
+
const lines = await countFileLinesStreaming(path.join(projectRoot, modulePath));
|
|
598
|
+
if (lines >= DEVELOPMENT_BLOCK_FILE_LINES)
|
|
599
|
+
oversizedResolvedModules.push({ file: modulePath, lines });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (args.remaining_lines != null && args.remaining_lines !== actualRemainingLines) {
|
|
603
|
+
return {
|
|
604
|
+
isError: true,
|
|
605
|
+
content: [{
|
|
606
|
+
type: "text",
|
|
607
|
+
text: toolJson({
|
|
608
|
+
ok: false,
|
|
609
|
+
error: "remaining_lines does not match the current source file.",
|
|
610
|
+
provided: args.remaining_lines,
|
|
611
|
+
actual: actualRemainingLines,
|
|
612
|
+
}),
|
|
613
|
+
}],
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
if (args.status === "resolved" &&
|
|
617
|
+
(actualRemainingLines >= DEVELOPMENT_BLOCK_FILE_LINES ||
|
|
618
|
+
oversizedResolvedModules.length > 0 ||
|
|
619
|
+
verification.length === 0 ||
|
|
620
|
+
verificationGaps.length > 0)) {
|
|
621
|
+
return {
|
|
622
|
+
isError: true,
|
|
623
|
+
content: [{
|
|
624
|
+
type: "text",
|
|
625
|
+
text: toolJson({
|
|
626
|
+
ok: false,
|
|
627
|
+
error: "Resolved status requires the source and every target module below the very-large threshold, verification evidence, and no verification gaps.",
|
|
628
|
+
remaining_lines: actualRemainingLines,
|
|
629
|
+
resolved_line_threshold: DEVELOPMENT_BLOCK_FILE_LINES,
|
|
630
|
+
oversized_modules: oversizedResolvedModules,
|
|
631
|
+
verification,
|
|
632
|
+
verification_gaps: verificationGaps,
|
|
633
|
+
}),
|
|
634
|
+
}],
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
const currentStateHash = fs.existsSync(resolvedFile.absPath)
|
|
638
|
+
? await hashFileContentStreaming(resolvedFile.absPath)
|
|
639
|
+
: context.sha256Hex("missing");
|
|
640
|
+
const originalSourceStateHash = typeof planMetadata.source_content_hash === "string"
|
|
641
|
+
? planMetadata.source_content_hash
|
|
642
|
+
: typeof planMetadata.source_state_hash === "string"
|
|
643
|
+
? planMetadata.source_state_hash
|
|
644
|
+
: null;
|
|
106
645
|
const meta = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
646
|
+
...compactPlanMetadata({
|
|
647
|
+
status: args.status,
|
|
648
|
+
file: normalizedFile,
|
|
649
|
+
sourceStateHash: originalSourceStateHash,
|
|
650
|
+
plannerFingerprint: typeof planMetadata.planner_fingerprint === "string"
|
|
651
|
+
? planMetadata.planner_fingerprint
|
|
652
|
+
: persistedPlan.planner_fingerprint,
|
|
653
|
+
currentStateHash,
|
|
654
|
+
requirement: active,
|
|
655
|
+
plan: persistedPlan,
|
|
656
|
+
verification,
|
|
657
|
+
verificationGaps,
|
|
658
|
+
}),
|
|
659
|
+
progress_modules: modules,
|
|
660
|
+
remaining_lines: actualRemainingLines,
|
|
661
|
+
updated_at: new Date().toISOString(),
|
|
113
662
|
};
|
|
114
|
-
const
|
|
115
|
-
|
|
663
|
+
const content = safeJson({
|
|
664
|
+
type: "large_file_split_plan",
|
|
665
|
+
summary: args.summary,
|
|
666
|
+
status: args.status,
|
|
667
|
+
file: normalizedFile,
|
|
668
|
+
requirement: { id: active.id, title: active.title, goal_key: active.goal_key ?? null },
|
|
669
|
+
source_state_hash: originalSourceStateHash,
|
|
670
|
+
source_content_hash: originalSourceStateHash,
|
|
671
|
+
planner_fingerprint: persistedPlan.planner_fingerprint,
|
|
672
|
+
current_state_hash: currentStateHash,
|
|
673
|
+
plan: persistedPlan,
|
|
674
|
+
progress: {
|
|
675
|
+
modules,
|
|
676
|
+
remaining_lines: actualRemainingLines,
|
|
677
|
+
verification,
|
|
678
|
+
verification_gaps: verificationGaps,
|
|
679
|
+
updated_at: meta.updated_at,
|
|
680
|
+
},
|
|
681
|
+
}) ?? "{}";
|
|
682
|
+
context.getDb().prepare(`UPDATE memory_items
|
|
683
|
+
SET content = ?, metadata_json = ?, content_hash = ?, updated_at = CURRENT_TIMESTAMP
|
|
684
|
+
WHERE id = ? AND kind = 'large_file_split_plan'`).run(content, safeJson(meta), sha256Hex(content), args.plan_id);
|
|
116
685
|
logActivity("record_large_file_split", {
|
|
117
|
-
|
|
686
|
+
plan_id: args.plan_id,
|
|
687
|
+
req_id: active.id,
|
|
118
688
|
file_path: normalizedFile,
|
|
119
689
|
status: args.status,
|
|
120
690
|
modules: modules.slice(0, 20),
|
|
121
|
-
remaining_lines:
|
|
691
|
+
remaining_lines: actualRemainingLines,
|
|
122
692
|
});
|
|
123
693
|
return {
|
|
124
694
|
content: [
|
|
@@ -126,11 +696,15 @@ export async function handleRecordLargeFileSplit(rawArgs, context) {
|
|
|
126
696
|
type: "text",
|
|
127
697
|
text: toolJson({
|
|
128
698
|
ok: true,
|
|
129
|
-
|
|
699
|
+
plan: { id: args.plan_id },
|
|
700
|
+
note: { id: args.plan_id },
|
|
701
|
+
linked_to_requirement: { id: active.id, title: active.title, goal_key: active.goal_key ?? null },
|
|
130
702
|
file_path: normalizedFile,
|
|
131
703
|
status: args.status,
|
|
132
704
|
modules,
|
|
133
|
-
remaining_lines:
|
|
705
|
+
remaining_lines: actualRemainingLines,
|
|
706
|
+
verification,
|
|
707
|
+
verification_gaps: verificationGaps,
|
|
134
708
|
}),
|
|
135
709
|
},
|
|
136
710
|
],
|