@coreyuan/vector-mind 1.0.54 → 1.1.1
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 +107 -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 +6 -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 +1 -1
- package/dist/config.js +1 -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 +3 -1
- package/dist/database-runtime.js +232 -127
- package/dist/database-runtime.js.map +1 -1
- package/dist/development-warnings.d.ts +2 -1
- package/dist/development-warnings.js +90 -20
- 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 +5 -2
- package/dist/index.js.map +1 -1
- package/dist/large-file-split.d.ts +52 -8
- package/dist/large-file-split.js +290 -76
- package/dist/large-file-split.js.map +1 -1
- package/dist/memory-maintenance/compaction.js +3 -2
- package/dist/memory-maintenance/compaction.js.map +1 -1
- package/dist/memory-maintenance/metrics.d.ts +1 -1
- package/dist/memory-maintenance/metrics.js +11 -3
- package/dist/memory-maintenance/metrics.js.map +1 -1
- package/dist/memory-maintenance/purge.js +1 -1
- package/dist/memory-maintenance/purge.js.map +1 -1
- package/dist/memory-maintenance/runner.d.ts +1 -0
- package/dist/memory-maintenance/runner.js +15 -3
- package/dist/memory-maintenance/runner.js.map +1 -1
- package/dist/memory-maintenance/types.d.ts +1 -0
- 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.js +16 -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/pending-changes.d.ts +5 -0
- package/dist/pending-changes.js +26 -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 +9 -88
- package/dist/server-instructions.js.map +1 -1
- package/dist/tool-catalog.d.ts +2 -0
- package/dist/tool-catalog.js +76 -23
- 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 +600 -44
- 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 +786 -97
- package/dist/tool-handlers/requirements.js.map +1 -1
- package/dist/tool-handlers.d.ts +4 -0
- package/dist/tool-handlers.js +183 -10
- package/dist/tool-handlers.js.map +1 -1
- package/dist/tool-output.d.ts +32 -1
- package/dist/tool-output.js +31 -12
- package/dist/tool-output.js.map +1 -1
- package/dist/tool-schemas.d.ts +235 -0
- package/dist/tool-schemas.js +98 -26
- package/dist/tool-schemas.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/docs/capability-matrix.md +24 -17
- package/package.json +19 -6
- 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,224 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
|
-
import { CompleteRequirementArgsSchema, GetPendingChangesArgsSchema,
|
|
3
|
-
import { buildDevelopmentWarnings, buildRequirementMappingWarnings, buildRequirementScopeContract, buildRequirementStartWarnings, buildScopeDriftWarnings, getRequirementItems, getRequirementScopeContract, isDevelopmentWarningBlockingForChangeMode, mergeScopeContracts, normalizeRequirementItems } from "../development-warnings.js";
|
|
3
|
+
import { CompleteRequirementArgsSchema, GetPendingChangesArgsSchema, PreflightChangeScopeArgsSchema, StartRequirementArgsSchema, SyncChangeIntentArgsSchema } from "../tool-schemas.js";
|
|
4
|
+
import { buildDevelopmentWarnings, buildRequirementMappingWarnings, buildRequirementScopeContract, buildRequirementStartWarnings, buildScopeDriftWarnings, getRequirementItems, getRequirementScopeContract, isDevelopmentWarningBlockingForChangeMode, mergeScopeContracts, normalizeRequirementItems, scopeContractHasRules } from "../development-warnings.js";
|
|
4
5
|
import { mergePendingWithGit } from "../pending-changes.js";
|
|
5
6
|
import { toolCompactOrJson } from "../token-savings.js";
|
|
6
7
|
import { flushPendingChangeBuffer, indexFile } from "../file-indexing.js";
|
|
7
8
|
import { buildFixPatternContent, buildFixPatternMetadata, buildFixPatternQualitySignals, collectRelevantFixPatterns, normalizeFixPattern } from "../fix-patterns.js";
|
|
8
9
|
import { completeAllActiveRequirementMemoryItems, completeRequirementMemoryItemsByReqId } from "../memory-mutations.js";
|
|
9
|
-
import { makePreviewText } from "../memory-recall.js";
|
|
10
|
+
import { makePreviewText, parseMetadataJson } from "../memory-recall.js";
|
|
10
11
|
import { logActivity } from "../activity-log.js";
|
|
11
12
|
import { compactPreflightChangeScopeText, safeJson, toolJson } from "../tool-output.js";
|
|
13
|
+
import { normalizeRequirementGoalIdentity, sameRequirement } from "../context-governance.js";
|
|
14
|
+
import { DEVELOPMENT_HUGE_FILE_LINES } from "../config.js";
|
|
15
|
+
import { hashFileContentStreaming } from "../large-file-split.js";
|
|
16
|
+
import { resolvePathWithinRoot } from "../path-containment.js";
|
|
17
|
+
const UNFINISHED_SPLIT_PLAN_STATUSES = new Set(["planned", "in_progress", "partial", "needs_refinement"]);
|
|
18
|
+
const MAX_SYNC_RESPONSE_ITEMS = 100;
|
|
19
|
+
const MAX_SYNC_RESPONSE_ARRAY_CHARS = 12_000;
|
|
20
|
+
const preflightResultCache = new Map();
|
|
21
|
+
function normalizeIdentityText(value) {
|
|
22
|
+
return (value ?? "").normalize("NFKC").trim().replace(/\s+/g, " ").toLocaleLowerCase();
|
|
23
|
+
}
|
|
24
|
+
function canonicalizeIdentity(value) {
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return value.map(canonicalizeIdentity);
|
|
27
|
+
if (!value || typeof value !== "object")
|
|
28
|
+
return value;
|
|
29
|
+
return Object.fromEntries(Object.entries(value)
|
|
30
|
+
.filter(([, item]) => item !== undefined)
|
|
31
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
32
|
+
.map(([key, item]) => [key, canonicalizeIdentity(item)]));
|
|
33
|
+
}
|
|
34
|
+
function sortedUniqueStrings(values) {
|
|
35
|
+
return [...new Set(values.map((value) => normalizeIdentityText(value)).filter(Boolean))].sort();
|
|
36
|
+
}
|
|
37
|
+
function createSyncResponseBudget() {
|
|
38
|
+
let remainingChars = MAX_SYNC_RESPONSE_ARRAY_CHARS;
|
|
39
|
+
return function boundItems(items) {
|
|
40
|
+
const page = [];
|
|
41
|
+
for (const item of items) {
|
|
42
|
+
if (page.length >= MAX_SYNC_RESPONSE_ITEMS)
|
|
43
|
+
break;
|
|
44
|
+
const itemChars = JSON.stringify(item).length + (page.length ? 1 : 0);
|
|
45
|
+
if (itemChars > remainingChars)
|
|
46
|
+
break;
|
|
47
|
+
page.push(item);
|
|
48
|
+
remainingChars -= itemChars;
|
|
49
|
+
}
|
|
50
|
+
return { page, total: items.length, truncated: page.length < items.length };
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function rememberPreflight(key, value) {
|
|
54
|
+
preflightResultCache.set(key, value);
|
|
55
|
+
if (preflightResultCache.size <= 200)
|
|
56
|
+
return;
|
|
57
|
+
const oldest = preflightResultCache.keys().next().value;
|
|
58
|
+
if (oldest)
|
|
59
|
+
preflightResultCache.delete(oldest);
|
|
60
|
+
}
|
|
61
|
+
function renderPreflight(value, format) {
|
|
62
|
+
return {
|
|
63
|
+
content: [{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: toolCompactOrJson("preflight_change_scope", value, compactPreflightChangeScopeText(value), format),
|
|
66
|
+
}],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function fileReachesLineThreshold(absPath, threshold) {
|
|
70
|
+
const fd = fs.openSync(absPath, "r");
|
|
71
|
+
try {
|
|
72
|
+
const buffer = Buffer.allocUnsafe(256 * 1024);
|
|
73
|
+
let lines = 0;
|
|
74
|
+
let bytesRead = 0;
|
|
75
|
+
let sawBytes = false;
|
|
76
|
+
let lastByte = 0;
|
|
77
|
+
do {
|
|
78
|
+
bytesRead = fs.readSync(fd, buffer, 0, buffer.length, null);
|
|
79
|
+
if (bytesRead <= 0)
|
|
80
|
+
break;
|
|
81
|
+
sawBytes = true;
|
|
82
|
+
lastByte = buffer[bytesRead - 1];
|
|
83
|
+
for (let index = 0; index < bytesRead; index += 1) {
|
|
84
|
+
if (buffer[index] === 10)
|
|
85
|
+
lines += 1;
|
|
86
|
+
}
|
|
87
|
+
if (lines >= threshold)
|
|
88
|
+
return true;
|
|
89
|
+
} while (bytesRead > 0);
|
|
90
|
+
return lines + (sawBytes && lastByte !== 10 ? 1 : 0) >= threshold;
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
fs.closeSync(fd);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function hasUnfinishedLargeFileSplitPlan(context, reqId, filePath) {
|
|
97
|
+
const rows = context.getDb().prepare(`SELECT metadata_json
|
|
98
|
+
FROM memory_items
|
|
99
|
+
WHERE kind = 'large_file_split_plan' AND req_id = ? AND file_path = ?
|
|
100
|
+
ORDER BY updated_at DESC, id DESC
|
|
101
|
+
LIMIT 20`).all(reqId, filePath);
|
|
102
|
+
return rows.some((row) => UNFINISHED_SPLIT_PLAN_STATUSES.has(String(parseMetadataJson(row.metadata_json).status)));
|
|
103
|
+
}
|
|
104
|
+
function resolveActiveRequirement(context, args) {
|
|
105
|
+
const { listActiveRequirementsStmt } = context.getStatements();
|
|
106
|
+
const activeCount = Number(context.getDb().prepare(`SELECT COUNT(*) AS count FROM requirements WHERE status = 'active'`).get()?.count ?? 0);
|
|
107
|
+
if (args.req_id) {
|
|
108
|
+
const requirement = context.getDb().prepare(`SELECT id, title, status, context_data, goal_key, created_at
|
|
109
|
+
FROM requirements WHERE id = ? LIMIT 1`).get(args.req_id);
|
|
110
|
+
return {
|
|
111
|
+
requirement: requirement?.status === "superseded" ? undefined : requirement,
|
|
112
|
+
ambiguous: false,
|
|
113
|
+
active_count: activeCount,
|
|
114
|
+
requested_status: requirement?.status ?? null,
|
|
115
|
+
found: !!requirement,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (args.goal_key?.trim()) {
|
|
119
|
+
const requirement = context.getDb().prepare(`SELECT id, title, status, context_data, goal_key, created_at
|
|
120
|
+
FROM requirements WHERE goal_key = ?
|
|
121
|
+
ORDER BY CASE status WHEN 'active' THEN 0 WHEN 'completed' THEN 1 ELSE 2 END,
|
|
122
|
+
updated_at DESC, id DESC
|
|
123
|
+
LIMIT 1`).get(args.goal_key.trim());
|
|
124
|
+
return {
|
|
125
|
+
requirement: requirement?.status === "superseded" ? undefined : requirement,
|
|
126
|
+
ambiguous: false,
|
|
127
|
+
active_count: activeCount,
|
|
128
|
+
requested_status: requirement?.status ?? null,
|
|
129
|
+
found: !!requirement,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const active = listActiveRequirementsStmt.all(2);
|
|
133
|
+
return {
|
|
134
|
+
requirement: active.length === 1 ? active[0] : undefined,
|
|
135
|
+
ambiguous: activeCount > 1,
|
|
136
|
+
active_count: activeCount,
|
|
137
|
+
requested_status: active.length === 1 ? active[0].status : null,
|
|
138
|
+
found: active.length === 1,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function validateLargeFileSplitPlans(context, args, requirement, hugeFiles) {
|
|
142
|
+
const normalizeToDbPath = context.normalizeToDbPath;
|
|
143
|
+
const requiredFiles = [...new Set(hugeFiles.map(normalizeToDbPath))];
|
|
144
|
+
const ids = [...new Set([...(args.split_plan_ids ?? []), ...(args.split_plan_id ? [args.split_plan_id] : [])])];
|
|
145
|
+
const validByFile = new Map();
|
|
146
|
+
const invalid = [];
|
|
147
|
+
const currentHashes = new Map();
|
|
148
|
+
for (const planId of ids) {
|
|
149
|
+
const row = context.getStatements().getMemoryItemByIdStmt.get(planId);
|
|
150
|
+
if (!row || row.kind !== "large_file_split_plan") {
|
|
151
|
+
invalid.push({ plan_id: planId, reason: "not_a_large_file_split_plan" });
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const metadata = parseMetadataJson(row.metadata_json);
|
|
155
|
+
const legacyPlan = metadata.plan && typeof metadata.plan === "object" && !Array.isArray(metadata.plan)
|
|
156
|
+
? metadata.plan
|
|
157
|
+
: {};
|
|
158
|
+
const legacyCoverage = legacyPlan.coverage && typeof legacyPlan.coverage === "object" && !Array.isArray(legacyPlan.coverage)
|
|
159
|
+
? legacyPlan.coverage
|
|
160
|
+
: {};
|
|
161
|
+
const legacyModuleConstraints = legacyPlan.module_constraints && typeof legacyPlan.module_constraints === "object" && !Array.isArray(legacyPlan.module_constraints)
|
|
162
|
+
? legacyPlan.module_constraints
|
|
163
|
+
: {};
|
|
164
|
+
const file = typeof metadata.file === "string" ? normalizeToDbPath(metadata.file) : "";
|
|
165
|
+
const status = typeof metadata.status === "string" ? metadata.status : "";
|
|
166
|
+
const expectedHash = typeof metadata.current_state_hash === "string"
|
|
167
|
+
? metadata.current_state_hash
|
|
168
|
+
: typeof metadata.source_state_hash === "string"
|
|
169
|
+
? metadata.source_state_hash
|
|
170
|
+
: null;
|
|
171
|
+
let currentHash = null;
|
|
172
|
+
if (file) {
|
|
173
|
+
currentHash = currentHashes.get(file) ?? null;
|
|
174
|
+
if (!currentHash) {
|
|
175
|
+
const absPath = path.join(context.getProjectRoot(), file);
|
|
176
|
+
try {
|
|
177
|
+
currentHash = fs.statSync(absPath).isFile()
|
|
178
|
+
? await hashFileContentStreaming(absPath)
|
|
179
|
+
: context.sha256Hex("missing");
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
currentHash = context.sha256Hex("missing");
|
|
183
|
+
}
|
|
184
|
+
currentHashes.set(file, currentHash);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
let reason = "";
|
|
188
|
+
if (!requirement || row.req_id !== requirement.id)
|
|
189
|
+
reason = "requirement_mismatch";
|
|
190
|
+
else if (!file || !requiredFiles.includes(file))
|
|
191
|
+
reason = "file_mismatch";
|
|
192
|
+
else if (!["planned", "in_progress", "partial"].includes(status))
|
|
193
|
+
reason = `invalid_status:${status || "missing"}`;
|
|
194
|
+
else if ((metadata.plan_ok ?? legacyPlan.ok) !== true)
|
|
195
|
+
reason = "plan_requires_refinement";
|
|
196
|
+
else if ((metadata.coverage_complete ?? legacyCoverage.complete) !== true)
|
|
197
|
+
reason = "incomplete_declaration_coverage";
|
|
198
|
+
else if ((metadata.module_constraints_satisfied ?? legacyModuleConstraints.satisfied ?? false) !== true)
|
|
199
|
+
reason = "oversized_target_module";
|
|
200
|
+
else if (!expectedHash || expectedHash !== currentHash)
|
|
201
|
+
reason = "source_state_changed_since_plan";
|
|
202
|
+
if (reason) {
|
|
203
|
+
invalid.push({ plan_id: planId, reason });
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
validByFile.set(file, planId);
|
|
207
|
+
}
|
|
208
|
+
const missingFiles = requiredFiles.filter((file) => !validByFile.has(file));
|
|
209
|
+
return {
|
|
210
|
+
valid: requiredFiles.length > 0 && missingFiles.length === 0,
|
|
211
|
+
required_files: requiredFiles,
|
|
212
|
+
valid_plan_ids: [...validByFile.values()],
|
|
213
|
+
invalid,
|
|
214
|
+
missing_files: missingFiles,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
12
217
|
export async function handleStartRequirement(rawArgs, context) {
|
|
13
218
|
const sha256Hex = context.sha256Hex;
|
|
14
|
-
const { insertRequirementStmt,
|
|
219
|
+
const { insertRequirementStmt, insertMemoryItemStmt, getActiveRequirementByGoalKeyStmt, listActiveRequirementsStmt, getRequirementMemoryItemIdStmt, } = context.getStatements();
|
|
15
220
|
const args = StartRequirementArgsSchema.parse(rawArgs);
|
|
221
|
+
const shouldClosePrevious = args.close_previous || args.previous_req_id != null;
|
|
16
222
|
flushPendingChangeBuffer();
|
|
17
223
|
const scope_contract = buildRequirementScopeContract({
|
|
18
224
|
title: args.title,
|
|
@@ -26,27 +232,153 @@ export async function handleStartRequirement(rawArgs, context) {
|
|
|
26
232
|
const development_warnings = buildRequirementStartWarnings({
|
|
27
233
|
title: args.title,
|
|
28
234
|
background: args.background,
|
|
29
|
-
close_previous:
|
|
235
|
+
close_previous: shouldClosePrevious,
|
|
30
236
|
});
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
completeAllActiveRequirementMemoryItems();
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
console.error("[vectormind] failed to close previous active requirements:", err);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const info = insertRequirementStmt.run(args.title, args.background || null);
|
|
41
|
-
const id = Number(info.lastInsertRowid);
|
|
237
|
+
const explicitGoalKey = args.goal_key?.trim() ?? "";
|
|
238
|
+
const goalKey = explicitGoalKey ||
|
|
239
|
+
`auto:${sha256Hex(normalizeRequirementGoalIdentity(args.title, args.background)).slice(0, 24)}`;
|
|
42
240
|
const background = args.background?.trim() ?? "";
|
|
43
241
|
const content = background ? `${args.title}\n\n${background}` : args.title;
|
|
44
|
-
|
|
45
|
-
|
|
242
|
+
let writeOutcome;
|
|
243
|
+
try {
|
|
244
|
+
const writeTransaction = context.getDb().transaction(() => {
|
|
245
|
+
const currentByGoalKey = getActiveRequirementByGoalKeyStmt.get(goalKey);
|
|
246
|
+
const currentActiveCandidates = listActiveRequirementsStmt.all(50);
|
|
247
|
+
const currentExactActive = !explicitGoalKey
|
|
248
|
+
? currentActiveCandidates.find((candidate) => sameRequirement(candidate, { ...args, goal_key: goalKey }))
|
|
249
|
+
: undefined;
|
|
250
|
+
const currentActive = currentByGoalKey ?? currentExactActive;
|
|
251
|
+
const currentReuseReason = currentByGoalKey
|
|
252
|
+
? "goal_key"
|
|
253
|
+
: currentExactActive
|
|
254
|
+
? "same_requirement"
|
|
255
|
+
: null;
|
|
256
|
+
if (args.reuse_active && currentActive && currentReuseReason) {
|
|
257
|
+
return {
|
|
258
|
+
kind: "reused",
|
|
259
|
+
requirement: currentActive,
|
|
260
|
+
reuseReason: currentReuseReason,
|
|
261
|
+
closedPrevious: false,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (shouldClosePrevious && !args.previous_req_id && currentActiveCandidates.length > 1) {
|
|
265
|
+
return {
|
|
266
|
+
kind: "error",
|
|
267
|
+
error: "Multiple active requirements exist. Pass previous_req_id to close one, or pass an existing goal_key to resume it.",
|
|
268
|
+
activeRequirements: currentActiveCandidates.slice(0, 10),
|
|
269
|
+
activeCount: currentActiveCandidates.length,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
let closedPrevious = false;
|
|
273
|
+
const previousRequirementId = args.previous_req_id ??
|
|
274
|
+
(currentActiveCandidates.length === 1 ? currentActiveCandidates[0].id : null);
|
|
275
|
+
if (shouldClosePrevious && previousRequirementId) {
|
|
276
|
+
const completed = completeRequirementMemoryItemsByReqId(previousRequirementId);
|
|
277
|
+
if (!completed) {
|
|
278
|
+
const raced = getActiveRequirementByGoalKeyStmt.get(goalKey);
|
|
279
|
+
if (raced) {
|
|
280
|
+
return {
|
|
281
|
+
kind: "reused",
|
|
282
|
+
requirement: raced,
|
|
283
|
+
reuseReason: "concurrent_goal_key_insert",
|
|
284
|
+
closedPrevious: false,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
return {
|
|
288
|
+
kind: "error",
|
|
289
|
+
error: `previous_req_id ${previousRequirementId} is not active and cannot be closed.`,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
closedPrevious = true;
|
|
293
|
+
}
|
|
294
|
+
let id;
|
|
295
|
+
try {
|
|
296
|
+
const info = insertRequirementStmt.run(args.title, args.background || null, goalKey);
|
|
297
|
+
id = Number(info.lastInsertRowid);
|
|
298
|
+
}
|
|
299
|
+
catch (err) {
|
|
300
|
+
const raced = getActiveRequirementByGoalKeyStmt.get(goalKey);
|
|
301
|
+
if (!raced)
|
|
302
|
+
throw err;
|
|
303
|
+
return {
|
|
304
|
+
kind: "reused",
|
|
305
|
+
requirement: raced,
|
|
306
|
+
reuseReason: "concurrent_goal_key_insert",
|
|
307
|
+
closedPrevious,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const memoryInfo = insertMemoryItemStmt.run("requirement", args.title, content, null, null, null, id, safeJson({ status: "active", goal_key: goalKey, scope_contract, requirement_items }), sha256Hex(content));
|
|
311
|
+
return {
|
|
312
|
+
kind: "created",
|
|
313
|
+
id,
|
|
314
|
+
memoryId: Number(memoryInfo.lastInsertRowid),
|
|
315
|
+
closedPrevious,
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
writeOutcome = writeTransaction.immediate();
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
return {
|
|
322
|
+
isError: true,
|
|
323
|
+
content: [{
|
|
324
|
+
type: "text",
|
|
325
|
+
text: toolJson({ ok: false, error: `Failed to start requirement atomically: ${String(err)}` }),
|
|
326
|
+
}],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (writeOutcome.kind === "error") {
|
|
330
|
+
return {
|
|
331
|
+
isError: true,
|
|
332
|
+
content: [{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: toolJson({
|
|
335
|
+
ok: false,
|
|
336
|
+
error: writeOutcome.error,
|
|
337
|
+
...(writeOutcome.activeRequirements
|
|
338
|
+
? {
|
|
339
|
+
active_count: writeOutcome.activeCount ?? writeOutcome.activeRequirements.length,
|
|
340
|
+
active_requirements: writeOutcome.activeRequirements.map((candidate) => ({
|
|
341
|
+
id: candidate.id,
|
|
342
|
+
title: candidate.title,
|
|
343
|
+
goal_key: candidate.goal_key,
|
|
344
|
+
})),
|
|
345
|
+
}
|
|
346
|
+
: {}),
|
|
347
|
+
}),
|
|
348
|
+
}],
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
if (writeOutcome.kind === "reused") {
|
|
352
|
+
const memoryId = getRequirementMemoryItemIdStmt.get(writeOutcome.requirement.id)?.id ?? null;
|
|
353
|
+
const activeScopeContract = getRequirementScopeContract(writeOutcome.requirement.id);
|
|
354
|
+
const activeRequirementItems = getRequirementItems(writeOutcome.requirement.id);
|
|
355
|
+
logActivity("start_requirement", {
|
|
356
|
+
req_id: writeOutcome.requirement.id,
|
|
357
|
+
title: writeOutcome.requirement.title,
|
|
358
|
+
reused_active: true,
|
|
359
|
+
});
|
|
360
|
+
return {
|
|
361
|
+
content: [{
|
|
362
|
+
type: "text",
|
|
363
|
+
text: toolJson({
|
|
364
|
+
ok: true,
|
|
365
|
+
requirement: { id: writeOutcome.requirement.id, title: writeOutcome.requirement.title },
|
|
366
|
+
goal_key: writeOutcome.requirement.goal_key ?? goalKey,
|
|
367
|
+
memory_item: { id: memoryId },
|
|
368
|
+
reused: true,
|
|
369
|
+
reuse_reason: writeOutcome.reuseReason,
|
|
370
|
+
closed_previous: writeOutcome.closedPrevious,
|
|
371
|
+
scope_contract: activeScopeContract,
|
|
372
|
+
requirement_items: activeRequirementItems,
|
|
373
|
+
development_warnings: [],
|
|
374
|
+
}),
|
|
375
|
+
}],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
46
378
|
logActivity("start_requirement", {
|
|
47
|
-
req_id: id,
|
|
379
|
+
req_id: writeOutcome.id,
|
|
48
380
|
title: args.title,
|
|
49
|
-
closed_previous:
|
|
381
|
+
closed_previous: writeOutcome.closedPrevious,
|
|
50
382
|
scope_contract,
|
|
51
383
|
requirement_items,
|
|
52
384
|
development_warnings: development_warnings.length,
|
|
@@ -57,9 +389,11 @@ export async function handleStartRequirement(rawArgs, context) {
|
|
|
57
389
|
type: "text",
|
|
58
390
|
text: toolJson({
|
|
59
391
|
ok: true,
|
|
60
|
-
requirement: { id, title: args.title },
|
|
61
|
-
|
|
62
|
-
|
|
392
|
+
requirement: { id: writeOutcome.id, title: args.title },
|
|
393
|
+
goal_key: goalKey,
|
|
394
|
+
memory_item: { id: writeOutcome.memoryId },
|
|
395
|
+
closed_previous: writeOutcome.closedPrevious,
|
|
396
|
+
close_previous_ignored: false,
|
|
63
397
|
scope_contract,
|
|
64
398
|
requirement_items,
|
|
65
399
|
development_warnings,
|
|
@@ -70,12 +404,13 @@ export async function handleStartRequirement(rawArgs, context) {
|
|
|
70
404
|
}
|
|
71
405
|
export async function handlePreflightChangeScope(rawArgs, context) {
|
|
72
406
|
const normalizeToDbPath = context.normalizeToDbPath;
|
|
73
|
-
const { getActiveRequirementStmt } = context.getStatements();
|
|
74
407
|
const args = PreflightChangeScopeArgsSchema.parse(rawArgs);
|
|
75
408
|
const changeMode = args.change_mode;
|
|
76
409
|
flushPendingChangeBuffer();
|
|
77
410
|
const files = (args.files ?? args.planned_files ?? []).filter((f) => typeof f === "string" && f.length > 0);
|
|
78
|
-
const
|
|
411
|
+
const resolution = resolveActiveRequirement(context, args);
|
|
412
|
+
const active = resolution.requirement;
|
|
413
|
+
const normalizedFiles = files.map(normalizeToDbPath);
|
|
79
414
|
const explicitContract = buildRequirementScopeContract({
|
|
80
415
|
title: active?.title ?? "",
|
|
81
416
|
background: active?.context_data ?? "",
|
|
@@ -84,6 +419,20 @@ export async function handlePreflightChangeScope(rawArgs, context) {
|
|
|
84
419
|
allowed_paths: args.allowed_paths,
|
|
85
420
|
denied_paths: args.denied_paths,
|
|
86
421
|
});
|
|
422
|
+
const persistedContract = active ? getRequirementScopeContract(active.id) : null;
|
|
423
|
+
const mergedContract = mergeScopeContracts(persistedContract, explicitContract);
|
|
424
|
+
const temporaryExactFileContract = !scopeContractHasRules(mergedContract) &&
|
|
425
|
+
args.files === undefined &&
|
|
426
|
+
(args.planned_files?.length ?? 0) > 0
|
|
427
|
+
? {
|
|
428
|
+
allow_terms: [],
|
|
429
|
+
deny_terms: [],
|
|
430
|
+
allowed_paths: Array.from(new Set(normalizedFiles)),
|
|
431
|
+
denied_paths: [],
|
|
432
|
+
inferred_from: ["preflight.planned_files"],
|
|
433
|
+
}
|
|
434
|
+
: null;
|
|
435
|
+
const scope_contract = mergeScopeContracts(mergedContract, temporaryExactFileContract);
|
|
87
436
|
const explicitRequirementItems = normalizeRequirementItems(args.requirement_items);
|
|
88
437
|
const requirementItems = explicitRequirementItems.length
|
|
89
438
|
? explicitRequirementItems
|
|
@@ -95,7 +444,7 @@ export async function handlePreflightChangeScope(rawArgs, context) {
|
|
|
95
444
|
...buildDevelopmentWarnings(fileInputs, { includeUnspecified: fileInputs.length === 0 }),
|
|
96
445
|
...buildScopeDriftWarnings({
|
|
97
446
|
requirement: active,
|
|
98
|
-
contract:
|
|
447
|
+
contract: scope_contract,
|
|
99
448
|
intent: args.intent,
|
|
100
449
|
files: fileInputs,
|
|
101
450
|
includeMissingContractHint: true,
|
|
@@ -108,22 +457,22 @@ export async function handlePreflightChangeScope(rawArgs, context) {
|
|
|
108
457
|
change_mode: changeMode,
|
|
109
458
|
}),
|
|
110
459
|
];
|
|
111
|
-
const scope_contract = mergeScopeContracts(active ? getRequirementScopeContract(active.id) : null, explicitContract);
|
|
112
|
-
logActivity("preflight_change_scope", {
|
|
113
|
-
req_id: active?.id ?? null,
|
|
114
|
-
intent_preview: makePreviewText(args.intent, 200),
|
|
115
|
-
change_mode: changeMode,
|
|
116
|
-
files: files.slice(0, 25),
|
|
117
|
-
files_total: files.length,
|
|
118
|
-
development_warnings: development_warnings.length,
|
|
119
|
-
});
|
|
120
460
|
const hasTargetFiles = fileInputs.length > 0;
|
|
121
461
|
const hugeWarnings = development_warnings.filter((w) => w.code === "huge_file_modularization_required");
|
|
122
462
|
const hasHugeFile = hugeWarnings.length > 0;
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
463
|
+
const splitPlanValidation = await validateLargeFileSplitPlans(context, args, active, hugeWarnings.flatMap((warning) => warning.files ?? []));
|
|
464
|
+
const deferReason = args.defer_split_reason?.trim() ?? "";
|
|
465
|
+
const minimalBugfixAllowed = changeMode === "bugfix" &&
|
|
466
|
+
args.adds_responsibility === false &&
|
|
467
|
+
deferReason.length > 0;
|
|
468
|
+
const emergencyHotfixAllowed = changeMode === "emergency_hotfix" && deferReason.length > 0;
|
|
469
|
+
const mechanicalPlanAllowed = changeMode === "mechanical_modularization" && splitPlanValidation.valid;
|
|
470
|
+
const hugeGateBlocked = hasHugeFile && !minimalBugfixAllowed && !emergencyHotfixAllowed && !mechanicalPlanAllowed;
|
|
471
|
+
const blockingWarnings = development_warnings.filter((warning) => warning.code !== "huge_file_modularization_required" &&
|
|
472
|
+
isDevelopmentWarningBlockingForChangeMode(warning, changeMode));
|
|
473
|
+
const hasBlockingWarnings = blockingWarnings.length > 0 || hugeGateBlocked;
|
|
474
|
+
const hasActiveRequirement = !!active;
|
|
475
|
+
const safeToEdit = hasActiveRequirement && hasTargetFiles && !hasBlockingWarnings;
|
|
127
476
|
const relevantFixPatterns = collectRelevantFixPatterns(context, {
|
|
128
477
|
intent: args.intent,
|
|
129
478
|
files: normalizedFiles,
|
|
@@ -132,31 +481,62 @@ export async function handlePreflightChangeScope(rawArgs, context) {
|
|
|
132
481
|
limit: 3,
|
|
133
482
|
});
|
|
134
483
|
const quality_signals = buildFixPatternQualitySignals(relevantFixPatterns);
|
|
135
|
-
const recommendedAction =
|
|
136
|
-
? "
|
|
137
|
-
:
|
|
138
|
-
? "
|
|
139
|
-
:
|
|
140
|
-
? "
|
|
141
|
-
: hasHugeFile
|
|
142
|
-
? "
|
|
143
|
-
:
|
|
144
|
-
? "
|
|
145
|
-
:
|
|
146
|
-
|
|
147
|
-
|
|
484
|
+
const recommendedAction = resolution.ambiguous
|
|
485
|
+
? "Multiple active requirements exist. Pass req_id or goal_key so this preflight cannot attach to another task."
|
|
486
|
+
: !hasActiveRequirement
|
|
487
|
+
? "Start or resume the current code-change requirement before editing, or pass its req_id/goal_key."
|
|
488
|
+
: !hasTargetFiles
|
|
489
|
+
? "Identify the intended target files/modules and rerun preflight_change_scope before editing."
|
|
490
|
+
: hasHugeFile && changeMode === "mechanical_modularization" && !splitPlanValidation.valid
|
|
491
|
+
? "Mechanical modularization requires a valid split_plan_id for every huge target file, with complete declaration coverage and an unchanged source state."
|
|
492
|
+
: hasHugeFile && changeMode === "mechanical_modularization" && safeToEdit
|
|
493
|
+
? "Proceed with the persisted split plan, move whole declarations into real named modules/directories, validate, then update that plan with record_large_file_split."
|
|
494
|
+
: hasHugeFile && changeMode === "bugfix" && minimalBugfixAllowed && safeToEdit
|
|
495
|
+
? "Proceed only with the declared minimal bugfix, add no responsibilities, and keep the split deferral reason in the final change intent."
|
|
496
|
+
: hasHugeFile && changeMode === "emergency_hotfix" && safeToEdit
|
|
497
|
+
? "Proceed only with the smallest urgent fix, add no responsibilities, record why modularization was deferred, and schedule the split next."
|
|
498
|
+
: hasHugeFile
|
|
499
|
+
? "Stop normal feature editing. Call plan_large_file_split and perform mechanical modularization first, then rerun preflight_change_scope with change_mode='mechanical_modularization'."
|
|
500
|
+
: hasBlockingWarnings
|
|
501
|
+
? "Stop before editing. Narrow the planned files or explicitly expand the current requirement/scope contract."
|
|
502
|
+
: "Planned files are within the current generic scope checks.";
|
|
503
|
+
const requiredAction = hasHugeFile && hugeGateBlocked
|
|
504
|
+
? changeMode === "mechanical_modularization"
|
|
505
|
+
? "valid_split_plan"
|
|
506
|
+
: "mechanical_modularization"
|
|
148
507
|
: undefined;
|
|
149
508
|
const allowedChangeModes = hasHugeFile
|
|
150
|
-
? ["mechanical_modularization", "emergency_hotfix"]
|
|
509
|
+
? ["mechanical_modularization", "bugfix", "emergency_hotfix"]
|
|
151
510
|
: undefined;
|
|
152
|
-
const
|
|
511
|
+
const baseOutputValue = {
|
|
153
512
|
ok: safeToEdit,
|
|
154
513
|
safe_to_edit: safeToEdit,
|
|
514
|
+
advisory_only: !hasHugeFile,
|
|
515
|
+
workflow_gate: hasHugeFile
|
|
516
|
+
? {
|
|
517
|
+
code: "huge_file_modularization_required",
|
|
518
|
+
active: hasBlockingWarnings,
|
|
519
|
+
satisfied: !hasBlockingWarnings,
|
|
520
|
+
required_action: requiredAction ?? changeMode,
|
|
521
|
+
host_runtime_enforced: false,
|
|
522
|
+
minimal_bugfix_allowed: minimalBugfixAllowed,
|
|
523
|
+
emergency_hotfix_allowed: emergencyHotfixAllowed,
|
|
524
|
+
}
|
|
525
|
+
: null,
|
|
155
526
|
change_mode: changeMode,
|
|
156
527
|
recommended_action: recommendedAction,
|
|
157
528
|
required_action: requiredAction,
|
|
158
529
|
allowed_change_modes: allowedChangeModes,
|
|
530
|
+
split_plan_validation: splitPlanValidation,
|
|
531
|
+
adds_responsibility: args.adds_responsibility ?? null,
|
|
532
|
+
defer_split_reason: deferReason || null,
|
|
159
533
|
active_requirement: active ? { id: active.id, title: active.title } : null,
|
|
534
|
+
requirement_resolution: {
|
|
535
|
+
requested_req_id: args.req_id ?? null,
|
|
536
|
+
requested_goal_key: args.goal_key ?? null,
|
|
537
|
+
ambiguous: resolution.ambiguous,
|
|
538
|
+
active_count: resolution.active_count,
|
|
539
|
+
},
|
|
160
540
|
intent: args.intent,
|
|
161
541
|
files: normalizedFiles,
|
|
162
542
|
requirement_mapping: {
|
|
@@ -164,17 +544,37 @@ export async function handlePreflightChangeScope(rawArgs, context) {
|
|
|
164
544
|
planned_changes: args.planned_changes ?? [],
|
|
165
545
|
},
|
|
166
546
|
scope_contract,
|
|
547
|
+
scope_contract_persistence: temporaryExactFileContract
|
|
548
|
+
? "temporary_read_only"
|
|
549
|
+
: scopeContractHasRules(scope_contract)
|
|
550
|
+
? "persisted_or_explicit"
|
|
551
|
+
: "none",
|
|
167
552
|
development_warnings,
|
|
168
553
|
quality_signals,
|
|
169
554
|
};
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
555
|
+
const preflightIdempotencyKey = context.sha256Hex(JSON.stringify(canonicalizeIdentity(baseOutputValue)));
|
|
556
|
+
const cacheKey = `${context.getProjectRoot()}\n${preflightIdempotencyKey}`;
|
|
557
|
+
const cachedPreflight = preflightResultCache.get(cacheKey);
|
|
558
|
+
const reused = !!cachedPreflight;
|
|
559
|
+
const outputValue = {
|
|
560
|
+
...(cachedPreflight ?? baseOutputValue),
|
|
561
|
+
reused,
|
|
562
|
+
idempotency_key: preflightIdempotencyKey,
|
|
177
563
|
};
|
|
564
|
+
if (!cachedPreflight) {
|
|
565
|
+
rememberPreflight(cacheKey, outputValue);
|
|
566
|
+
}
|
|
567
|
+
logActivity("preflight_change_scope", {
|
|
568
|
+
req_id: active?.id ?? null,
|
|
569
|
+
intent_preview: makePreviewText(args.intent, 200),
|
|
570
|
+
change_mode: changeMode,
|
|
571
|
+
files: files.slice(0, 25),
|
|
572
|
+
files_total: files.length,
|
|
573
|
+
development_warnings: development_warnings.length,
|
|
574
|
+
reused,
|
|
575
|
+
idempotency_key: preflightIdempotencyKey,
|
|
576
|
+
});
|
|
577
|
+
return renderPreflight(outputValue, args.format);
|
|
178
578
|
}
|
|
179
579
|
export async function handleSyncChangeIntent(rawArgs, context) {
|
|
180
580
|
const db = context.getDb();
|
|
@@ -182,7 +582,7 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
182
582
|
const normalizeToDbPath = context.normalizeToDbPath;
|
|
183
583
|
const sha256Hex = context.sha256Hex;
|
|
184
584
|
const getFileStateHash = context.getFileStateHash;
|
|
185
|
-
const { insertChangeLogStmt, upsertSyncedFileStateStmt, insertMemoryItemStmt,
|
|
585
|
+
const { insertChangeLogStmt, upsertSyncedFileStateStmt, insertMemoryItemStmt, listPendingChangesStmt, deletePendingChangeStmt, } = context.getStatements();
|
|
186
586
|
const args = SyncChangeIntentArgsSchema.parse(rawArgs);
|
|
187
587
|
const explicitFixPattern = args.fix_pattern
|
|
188
588
|
? normalizeFixPattern({
|
|
@@ -193,10 +593,146 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
193
593
|
: args.verification_gaps,
|
|
194
594
|
})
|
|
195
595
|
: null;
|
|
596
|
+
const toContainedFile = (input, allowMissing) => {
|
|
597
|
+
const absolute = resolvePathWithinRoot(projectRoot, input, { allowMissing });
|
|
598
|
+
const relative = path.relative(projectRoot, absolute);
|
|
599
|
+
if (!relative || relative === ".") {
|
|
600
|
+
throw new Error(`sync_change_intent file must identify an entry below project_root: ${input}`);
|
|
601
|
+
}
|
|
602
|
+
return { absolute, dbFilePath: normalizeToDbPath(relative) };
|
|
603
|
+
};
|
|
604
|
+
const explicitFileInputs = [...(args.files ?? []), ...(args.affected_files ?? [])].filter((f) => typeof f === "string" && f.length > 0);
|
|
605
|
+
const explicitFiles = explicitFileInputs.map((rawFile) => {
|
|
606
|
+
const contained = toContainedFile(rawFile, true);
|
|
607
|
+
return { rawFile: contained.absolute, dbFilePath: contained.dbFilePath };
|
|
608
|
+
});
|
|
609
|
+
const largeFileSplitDeferrals = (args.large_file_split_deferrals ?? []).map((deferral) => {
|
|
610
|
+
const contained = toContainedFile(deferral.file, true);
|
|
611
|
+
return {
|
|
612
|
+
file: contained.dbFilePath,
|
|
613
|
+
reason: deferral.reason.trim(),
|
|
614
|
+
recorded_at: new Date().toISOString(),
|
|
615
|
+
};
|
|
616
|
+
});
|
|
617
|
+
const syncIdempotencyKey = args.idempotency_key?.trim() || null;
|
|
618
|
+
const requestFingerprint = syncIdempotencyKey
|
|
619
|
+
? sha256Hex(JSON.stringify(canonicalizeIdentity({
|
|
620
|
+
req_id: args.req_id ?? null,
|
|
621
|
+
goal_key: normalizeIdentityText(args.goal_key),
|
|
622
|
+
intent: normalizeIdentityText(args.intent),
|
|
623
|
+
files: explicitFiles.map((file) => file.dbFilePath).sort(),
|
|
624
|
+
verification: sortedUniqueStrings(args.verification ?? []),
|
|
625
|
+
verification_gaps: sortedUniqueStrings(args.verification_gaps ?? []),
|
|
626
|
+
fix_pattern: explicitFixPattern,
|
|
627
|
+
large_file_split_deferrals: largeFileSplitDeferrals
|
|
628
|
+
.map((deferral) => ({ file: deferral.file, reason: normalizeIdentityText(deferral.reason) }))
|
|
629
|
+
.sort((left, right) => left.file.localeCompare(right.file)),
|
|
630
|
+
pending_limit: args.pending_limit,
|
|
631
|
+
complete_requirement: args.complete_requirement,
|
|
632
|
+
})))
|
|
633
|
+
: null;
|
|
634
|
+
const findExistingSync = () => {
|
|
635
|
+
if (!syncIdempotencyKey)
|
|
636
|
+
return undefined;
|
|
637
|
+
return db.prepare(`SELECT mi.id, mi.req_id, r.title AS requirement_title, mi.metadata_json
|
|
638
|
+
FROM memory_items mi
|
|
639
|
+
JOIN requirements r ON r.id = mi.req_id
|
|
640
|
+
WHERE mi.kind = 'change_intent'
|
|
641
|
+
AND json_valid(COALESCE(mi.metadata_json, '{}'))
|
|
642
|
+
AND json_extract(mi.metadata_json, '$.idempotency_key') = ?
|
|
643
|
+
ORDER BY mi.updated_at DESC, mi.id DESC
|
|
644
|
+
LIMIT 1`).get(syncIdempotencyKey);
|
|
645
|
+
};
|
|
646
|
+
const replayExistingSync = (existing) => {
|
|
647
|
+
const metadata = parseMetadataJson(existing.metadata_json);
|
|
648
|
+
if (metadata.request_fingerprint !== requestFingerprint) {
|
|
649
|
+
return {
|
|
650
|
+
isError: true,
|
|
651
|
+
content: [{
|
|
652
|
+
type: "text",
|
|
653
|
+
text: toolJson({
|
|
654
|
+
ok: false,
|
|
655
|
+
error: "idempotency_key conflict: the key was already used with different sync_change_intent arguments",
|
|
656
|
+
idempotency_key: syncIdempotencyKey,
|
|
657
|
+
}),
|
|
658
|
+
}],
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
const storedFiles = Array.isArray(metadata.files)
|
|
662
|
+
? metadata.files
|
|
663
|
+
.filter((file) => !!file && typeof file === "object" && !Array.isArray(file))
|
|
664
|
+
.map((file) => ({
|
|
665
|
+
file_path: String(file.file_path ?? ""),
|
|
666
|
+
event: String(file.event ?? "manual"),
|
|
667
|
+
source: file.source === "args" || file.source === "pending" || file.source === "unspecified"
|
|
668
|
+
? file.source
|
|
669
|
+
: "unspecified",
|
|
670
|
+
}))
|
|
671
|
+
.filter((file) => file.file_path.length > 0)
|
|
672
|
+
: [];
|
|
673
|
+
const changeLogId = Number(metadata.change_log_id ?? 0);
|
|
674
|
+
const developmentWarnings = buildDevelopmentWarnings(storedFiles, {
|
|
675
|
+
includeUnspecified: storedFiles.some((file) => file.file_path === "(unspecified)"),
|
|
676
|
+
});
|
|
677
|
+
const boundResponseItems = createSyncResponseBudget();
|
|
678
|
+
const boundedStoredFiles = boundResponseItems(storedFiles);
|
|
679
|
+
const storedVerification = Array.isArray(metadata.verification) ? metadata.verification : [];
|
|
680
|
+
const storedVerificationGaps = Array.isArray(metadata.verification_gaps) ? metadata.verification_gaps : [];
|
|
681
|
+
const storedDeferrals = Array.isArray(metadata.large_file_split_deferrals)
|
|
682
|
+
? metadata.large_file_split_deferrals
|
|
683
|
+
: [];
|
|
684
|
+
const boundedVerification = boundResponseItems(storedVerification);
|
|
685
|
+
const boundedVerificationGaps = boundResponseItems(storedVerificationGaps);
|
|
686
|
+
const boundedDeferrals = boundResponseItems(storedDeferrals);
|
|
687
|
+
const boundedWarnings = boundResponseItems(developmentWarnings);
|
|
688
|
+
return {
|
|
689
|
+
content: [{
|
|
690
|
+
type: "text",
|
|
691
|
+
text: toolJson({
|
|
692
|
+
ok: true,
|
|
693
|
+
linked_to_requirement: { id: existing.req_id, title: existing.requirement_title },
|
|
694
|
+
synced_files: boundedStoredFiles.page,
|
|
695
|
+
synced_files_total: boundedStoredFiles.total,
|
|
696
|
+
synced_files_truncated: boundedStoredFiles.truncated,
|
|
697
|
+
created: [],
|
|
698
|
+
created_total: 0,
|
|
699
|
+
created_truncated: false,
|
|
700
|
+
created_change: {
|
|
701
|
+
change_log_id: changeLogId,
|
|
702
|
+
memory_item_id: existing.id,
|
|
703
|
+
file_count: Number(metadata.file_count ?? storedFiles.filter((file) => file.file_path !== "(unspecified)").length),
|
|
704
|
+
},
|
|
705
|
+
verification: boundedVerification.page,
|
|
706
|
+
verification_total: boundedVerification.total,
|
|
707
|
+
verification_truncated: boundedVerification.truncated,
|
|
708
|
+
verification_gaps: boundedVerificationGaps.page,
|
|
709
|
+
verification_gaps_total: boundedVerificationGaps.total,
|
|
710
|
+
verification_gaps_truncated: boundedVerificationGaps.truncated,
|
|
711
|
+
large_file_split_deferrals: boundedDeferrals.page,
|
|
712
|
+
large_file_split_deferrals_total: boundedDeferrals.total,
|
|
713
|
+
large_file_split_deferrals_truncated: boundedDeferrals.truncated,
|
|
714
|
+
created_fix_pattern: metadata.created_fix_pattern ?? null,
|
|
715
|
+
requirement_completed: metadata.complete_requirement === true,
|
|
716
|
+
pending_batch: metadata.pending_batch ?? null,
|
|
717
|
+
reused: true,
|
|
718
|
+
idempotency_key: syncIdempotencyKey,
|
|
719
|
+
development_warnings: boundedWarnings.page,
|
|
720
|
+
development_warnings_total: boundedWarnings.total,
|
|
721
|
+
development_warnings_truncated: boundedWarnings.truncated,
|
|
722
|
+
}),
|
|
723
|
+
}],
|
|
724
|
+
};
|
|
725
|
+
};
|
|
726
|
+
const existingBeforeResolution = findExistingSync();
|
|
727
|
+
if (existingBeforeResolution)
|
|
728
|
+
return replayExistingSync(existingBeforeResolution);
|
|
196
729
|
flushPendingChangeBuffer();
|
|
197
|
-
const
|
|
198
|
-
const active =
|
|
730
|
+
const resolution = resolveActiveRequirement(context, args);
|
|
731
|
+
const active = resolution.requirement;
|
|
199
732
|
if (!active) {
|
|
733
|
+
const existingAfterResolution = findExistingSync();
|
|
734
|
+
if (existingAfterResolution)
|
|
735
|
+
return replayExistingSync(existingAfterResolution);
|
|
200
736
|
return {
|
|
201
737
|
isError: true,
|
|
202
738
|
content: [
|
|
@@ -204,7 +740,35 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
204
740
|
type: "text",
|
|
205
741
|
text: toolJson({
|
|
206
742
|
ok: false,
|
|
207
|
-
|
|
743
|
+
code: resolution.ambiguous
|
|
744
|
+
? "MULTIPLE_ACTIVE_REQUIREMENTS"
|
|
745
|
+
: resolution.requested_status === "superseded"
|
|
746
|
+
? "REQUIREMENT_SUPERSEDED"
|
|
747
|
+
: resolution.found
|
|
748
|
+
? "REQUIREMENT_NOT_WRITABLE"
|
|
749
|
+
: "REQUIREMENT_NOT_ACTIVE_IN_PROJECT",
|
|
750
|
+
error: resolution.ambiguous
|
|
751
|
+
? "Multiple active requirements exist. Pass req_id or goal_key to sync_change_intent."
|
|
752
|
+
: resolution.requested_status === "superseded"
|
|
753
|
+
? "The requested requirement was superseded and cannot accept new change intent. Start a new requirement instead."
|
|
754
|
+
: "No matching active requirement exists under the resolved project root. Call start_requirement({ project_root, title, background }) for a new goal, or inspect/resume an existing requirement.",
|
|
755
|
+
project_root: context.getProjectRoot(),
|
|
756
|
+
requested: {
|
|
757
|
+
req_id: args.req_id ?? null,
|
|
758
|
+
goal_key: args.goal_key?.trim() || null,
|
|
759
|
+
},
|
|
760
|
+
active_count: resolution.active_count,
|
|
761
|
+
active_requirements: context.getStatements().listActiveRequirementsStmt.all(10)
|
|
762
|
+
.map((requirement) => ({ id: requirement.id, title: requirement.title, goal_key: requirement.goal_key })),
|
|
763
|
+
recovery: resolution.ambiguous
|
|
764
|
+
? { action: "pass_requirement_identity", tools: ["get_requirement_status"] }
|
|
765
|
+
: resolution.requested_status === "superseded"
|
|
766
|
+
? { action: "start_replacement_requirement", tools: ["get_requirement_status", "start_requirement"] }
|
|
767
|
+
: {
|
|
768
|
+
action: "inspect_or_resume_requirement",
|
|
769
|
+
tools: ["get_requirement_status", "resume_requirement", "start_requirement"],
|
|
770
|
+
hint: "Verify project_root matches the start_requirement call. Use project_root_mode=exact only for an intentionally independent nested project.",
|
|
771
|
+
},
|
|
208
772
|
}),
|
|
209
773
|
},
|
|
210
774
|
],
|
|
@@ -212,32 +776,48 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
212
776
|
}
|
|
213
777
|
const created = [];
|
|
214
778
|
let createdChange = null;
|
|
779
|
+
const reusedSync = false;
|
|
780
|
+
let pendingBatch = null;
|
|
215
781
|
const fixPatternMemoryItem = { value: null };
|
|
216
782
|
const synced_files = [];
|
|
783
|
+
let transactionReplay;
|
|
217
784
|
const insertTx = db.transaction(() => {
|
|
785
|
+
transactionReplay = findExistingSync();
|
|
786
|
+
if (transactionReplay)
|
|
787
|
+
return;
|
|
788
|
+
const pendingPathsToDelete = new Set();
|
|
218
789
|
const targets = [];
|
|
219
790
|
if (explicitFiles.length) {
|
|
220
|
-
for (const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
for (const t of targets) {
|
|
225
|
-
deletePendingChangeStmt.run(t.dbFilePath);
|
|
791
|
+
for (const file of explicitFiles) {
|
|
792
|
+
targets.push({ rawFile: file.rawFile, dbFilePath: file.dbFilePath, event: "manual", source: "args" });
|
|
793
|
+
pendingPathsToDelete.add(file.dbFilePath);
|
|
226
794
|
}
|
|
227
795
|
}
|
|
228
796
|
else {
|
|
229
797
|
const pendingAll = listPendingChangesStmt.all();
|
|
230
|
-
const merged = mergePendingWithGit(pendingAll, { offset: 0, limit:
|
|
798
|
+
const merged = mergePendingWithGit(pendingAll, { offset: 0, limit: args.pending_limit });
|
|
799
|
+
pendingBatch = {
|
|
800
|
+
limit: args.pending_limit,
|
|
801
|
+
total: merged.total,
|
|
802
|
+
processed: merged.page.length,
|
|
803
|
+
remaining: merged.remaining,
|
|
804
|
+
truncated: merged.truncated,
|
|
805
|
+
};
|
|
806
|
+
if (args.complete_requirement && merged.remaining > 0) {
|
|
807
|
+
throw new Error(`Cannot complete requirement while ${merged.remaining} pending change(s) remain after this bounded batch. ` +
|
|
808
|
+
"Run sync_change_intent with complete_requirement=false until pending_batch.remaining is 0, then complete it.");
|
|
809
|
+
}
|
|
231
810
|
if (merged.page.length) {
|
|
232
811
|
for (const p of merged.page) {
|
|
812
|
+
const contained = toContainedFile(p.file_path, p.last_event === "unlink");
|
|
233
813
|
targets.push({
|
|
234
|
-
rawFile:
|
|
235
|
-
dbFilePath:
|
|
814
|
+
rawFile: contained.absolute,
|
|
815
|
+
dbFilePath: contained.dbFilePath,
|
|
236
816
|
event: p.last_event,
|
|
237
|
-
source:
|
|
817
|
+
source: "pending",
|
|
238
818
|
});
|
|
819
|
+
pendingPathsToDelete.add(p.file_path);
|
|
239
820
|
}
|
|
240
|
-
deleteAllPendingChangesStmt.run();
|
|
241
821
|
}
|
|
242
822
|
else {
|
|
243
823
|
targets.push({
|
|
@@ -273,6 +853,33 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
273
853
|
};
|
|
274
854
|
});
|
|
275
855
|
const concreteFileStates = fileStates.filter((f) => !f.unspecified);
|
|
856
|
+
if (largeFileSplitDeferrals.length) {
|
|
857
|
+
const syncedPathKeys = new Set(concreteFileStates.map((file) => process.platform === "win32" ? file.file_path.toLowerCase() : file.file_path));
|
|
858
|
+
for (const deferral of largeFileSplitDeferrals) {
|
|
859
|
+
const deferralKey = process.platform === "win32" ? deferral.file.toLowerCase() : deferral.file;
|
|
860
|
+
if (!syncedPathKeys.has(deferralKey)) {
|
|
861
|
+
throw new Error(`Large-file split deferral must belong to this sync_change_intent file set: ${deferral.file}`);
|
|
862
|
+
}
|
|
863
|
+
const fileState = concreteFileStates.find((file) => (process.platform === "win32" ? file.file_path.toLowerCase() : file.file_path) === deferralKey);
|
|
864
|
+
let currentlyHuge = false;
|
|
865
|
+
if (fileState && fileState.event !== "unlink") {
|
|
866
|
+
const absPath = path.isAbsolute(fileState.rawFile)
|
|
867
|
+
? fileState.rawFile
|
|
868
|
+
: path.join(projectRoot, fileState.rawFile);
|
|
869
|
+
try {
|
|
870
|
+
currentlyHuge = fs.statSync(absPath).isFile() && fileReachesLineThreshold(absPath, DEVELOPMENT_HUGE_FILE_LINES);
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
currentlyHuge = false;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (!currentlyHuge && !hasUnfinishedLargeFileSplitPlan(context, active.id, deferral.file)) {
|
|
877
|
+
throw new Error(`Large-file split deferral requires a currently huge file or an unfinished split plan for this requirement: ${deferral.file}`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
for (const pendingPath of pendingPathsToDelete)
|
|
882
|
+
deletePendingChangeStmt.run(pendingPath);
|
|
276
883
|
const primaryFilePath = concreteFileStates.length === 1
|
|
277
884
|
? concreteFileStates[0].file_path
|
|
278
885
|
: concreteFileStates.length > 1
|
|
@@ -298,6 +905,11 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
298
905
|
file_count: fileCount,
|
|
299
906
|
verification: args.verification ?? [],
|
|
300
907
|
verification_gaps: args.verification_gaps ?? [],
|
|
908
|
+
large_file_split_deferrals: largeFileSplitDeferrals,
|
|
909
|
+
idempotency_key: syncIdempotencyKey,
|
|
910
|
+
request_fingerprint: requestFingerprint,
|
|
911
|
+
pending_batch: pendingBatch,
|
|
912
|
+
complete_requirement: args.complete_requirement,
|
|
301
913
|
});
|
|
302
914
|
const memoryInfo = insertMemoryItemStmt.run("change_intent", active.title, args.intent, concreteFileStates.length === 1 ? concreteFileStates[0].file_path : null, null, null, active.id, memoryMetadata, sha256Hex(`${args.intent}\n${concreteFileStates.map((f) => f.file_path).join("\n")}`));
|
|
303
915
|
const memory_item_id = Number(memoryInfo.lastInsertRowid);
|
|
@@ -336,8 +948,23 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
336
948
|
}), sha256Hex(content));
|
|
337
949
|
fixPatternMemoryItem.value = { id: Number(memoryInfo.lastInsertRowid), kind: "fix_pattern", title };
|
|
338
950
|
}
|
|
951
|
+
if (args.complete_requirement) {
|
|
952
|
+
if (!completeRequirementMemoryItemsByReqId(active.id)) {
|
|
953
|
+
throw new Error(`Requirement ${active.id} is no longer active and cannot be completed.`);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
339
956
|
});
|
|
340
|
-
|
|
957
|
+
try {
|
|
958
|
+
insertTx();
|
|
959
|
+
}
|
|
960
|
+
catch (err) {
|
|
961
|
+
return {
|
|
962
|
+
isError: true,
|
|
963
|
+
content: [{ type: "text", text: toolJson({ ok: false, error: `sync_change_intent transaction failed: ${String(err)}` }) }],
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
if (transactionReplay)
|
|
967
|
+
return replayExistingSync(transactionReplay);
|
|
341
968
|
const development_warnings = [
|
|
342
969
|
...buildDevelopmentWarnings(synced_files, {
|
|
343
970
|
includeUnspecified: synced_files.some((f) => f.file_path === "(unspecified)"),
|
|
@@ -348,6 +975,13 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
348
975
|
files: synced_files,
|
|
349
976
|
}),
|
|
350
977
|
];
|
|
978
|
+
const boundResponseItems = createSyncResponseBudget();
|
|
979
|
+
const boundedSyncedFiles = boundResponseItems(synced_files);
|
|
980
|
+
const boundedCreated = boundResponseItems(created);
|
|
981
|
+
const boundedVerification = boundResponseItems(args.verification ?? []);
|
|
982
|
+
const boundedVerificationGaps = boundResponseItems(args.verification_gaps ?? []);
|
|
983
|
+
const boundedDeferrals = boundResponseItems(largeFileSplitDeferrals);
|
|
984
|
+
const boundedWarnings = boundResponseItems(development_warnings);
|
|
351
985
|
logActivity("sync_change_intent", {
|
|
352
986
|
req_id: active.id,
|
|
353
987
|
title: active.title,
|
|
@@ -356,6 +990,10 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
356
990
|
files_total: synced_files.length,
|
|
357
991
|
fix_pattern_memory_id: fixPatternMemoryItem.value?.id ?? null,
|
|
358
992
|
development_warnings: development_warnings.length,
|
|
993
|
+
large_file_split_deferrals: largeFileSplitDeferrals.length,
|
|
994
|
+
pending_batch: pendingBatch,
|
|
995
|
+
reused: reusedSync,
|
|
996
|
+
idempotency_key: syncIdempotencyKey,
|
|
359
997
|
});
|
|
360
998
|
return {
|
|
361
999
|
content: [
|
|
@@ -364,20 +1002,37 @@ export async function handleSyncChangeIntent(rawArgs, context) {
|
|
|
364
1002
|
text: toolJson({
|
|
365
1003
|
ok: true,
|
|
366
1004
|
linked_to_requirement: { id: active.id, title: active.title },
|
|
367
|
-
synced_files,
|
|
368
|
-
|
|
1005
|
+
synced_files: boundedSyncedFiles.page,
|
|
1006
|
+
synced_files_total: boundedSyncedFiles.total,
|
|
1007
|
+
synced_files_truncated: boundedSyncedFiles.truncated,
|
|
1008
|
+
created: boundedCreated.page,
|
|
1009
|
+
created_total: boundedCreated.total,
|
|
1010
|
+
created_truncated: boundedCreated.truncated,
|
|
369
1011
|
created_change: createdChange,
|
|
370
|
-
verification:
|
|
371
|
-
|
|
1012
|
+
verification: boundedVerification.page,
|
|
1013
|
+
verification_total: boundedVerification.total,
|
|
1014
|
+
verification_truncated: boundedVerification.truncated,
|
|
1015
|
+
verification_gaps: boundedVerificationGaps.page,
|
|
1016
|
+
verification_gaps_total: boundedVerificationGaps.total,
|
|
1017
|
+
verification_gaps_truncated: boundedVerificationGaps.truncated,
|
|
1018
|
+
large_file_split_deferrals: boundedDeferrals.page,
|
|
1019
|
+
large_file_split_deferrals_total: boundedDeferrals.total,
|
|
1020
|
+
large_file_split_deferrals_truncated: boundedDeferrals.truncated,
|
|
372
1021
|
created_fix_pattern: fixPatternMemoryItem.value,
|
|
373
|
-
|
|
1022
|
+
requirement_completed: args.complete_requirement,
|
|
1023
|
+
pending_batch: pendingBatch,
|
|
1024
|
+
reused: reusedSync,
|
|
1025
|
+
idempotency_key: syncIdempotencyKey,
|
|
1026
|
+
development_warnings: boundedWarnings.page,
|
|
1027
|
+
development_warnings_total: boundedWarnings.total,
|
|
1028
|
+
development_warnings_truncated: boundedWarnings.truncated,
|
|
374
1029
|
}),
|
|
375
1030
|
},
|
|
376
1031
|
],
|
|
377
1032
|
};
|
|
378
1033
|
}
|
|
379
1034
|
export async function handleGetPendingChanges(rawArgs, context) {
|
|
380
|
-
const {
|
|
1035
|
+
const { listPendingChangesStmt } = context.getStatements();
|
|
381
1036
|
const args = GetPendingChangesArgsSchema.parse(rawArgs);
|
|
382
1037
|
flushPendingChangeBuffer();
|
|
383
1038
|
const offset = args.offset;
|
|
@@ -387,7 +1042,8 @@ export async function handleGetPendingChanges(rawArgs, context) {
|
|
|
387
1042
|
const total = mergedPending.total;
|
|
388
1043
|
const truncated = mergedPending.truncated;
|
|
389
1044
|
const pending = mergedPending.page;
|
|
390
|
-
const
|
|
1045
|
+
const resolution = resolveActiveRequirement(context, args);
|
|
1046
|
+
const activeForScope = resolution.requirement;
|
|
391
1047
|
const development_warnings = [
|
|
392
1048
|
...buildDevelopmentWarnings(pending),
|
|
393
1049
|
...(activeForScope ? buildScopeDriftWarnings({ requirement: activeForScope, files: pending }) : []),
|
|
@@ -404,41 +1060,74 @@ export async function handleGetPendingChanges(rawArgs, context) {
|
|
|
404
1060
|
content: [
|
|
405
1061
|
{
|
|
406
1062
|
type: "text",
|
|
407
|
-
text: toolJson({
|
|
1063
|
+
text: toolJson({
|
|
1064
|
+
ok: true,
|
|
1065
|
+
total,
|
|
1066
|
+
offset,
|
|
1067
|
+
limit,
|
|
1068
|
+
truncated,
|
|
1069
|
+
pending,
|
|
1070
|
+
requirement_resolution: {
|
|
1071
|
+
requirement_id: activeForScope?.id ?? null,
|
|
1072
|
+
ambiguous: resolution.ambiguous,
|
|
1073
|
+
active_count: resolution.active_count,
|
|
1074
|
+
},
|
|
1075
|
+
development_warnings,
|
|
1076
|
+
}),
|
|
408
1077
|
},
|
|
409
1078
|
],
|
|
410
1079
|
};
|
|
411
1080
|
}
|
|
412
1081
|
export async function handleCompleteRequirement(rawArgs, context) {
|
|
413
|
-
const db = context.getDb();
|
|
414
|
-
const { completeAllActiveRequirementsStmt, getActiveRequirementStmt, completeRequirementByIdStmt } = context.getStatements();
|
|
415
1082
|
const args = CompleteRequirementArgsSchema.parse(rawArgs);
|
|
416
1083
|
flushPendingChangeBuffer();
|
|
417
1084
|
const updated = [];
|
|
418
1085
|
if (args.all_active) {
|
|
419
|
-
const activeRows = db.prepare(`SELECT id FROM requirements WHERE status = 'active' ORDER BY created_at DESC, id DESC`).all().slice(0, 200);
|
|
420
1086
|
try {
|
|
421
|
-
|
|
422
|
-
|
|
1087
|
+
const completedIds = completeAllActiveRequirementMemoryItems();
|
|
1088
|
+
for (const id of completedIds)
|
|
1089
|
+
updated.push({ id });
|
|
423
1090
|
}
|
|
424
1091
|
catch (err) {
|
|
425
|
-
|
|
1092
|
+
return {
|
|
1093
|
+
isError: true,
|
|
1094
|
+
content: [{ type: "text", text: toolJson({ ok: false, error: `Failed to complete active requirements atomically: ${String(err)}` }) }],
|
|
1095
|
+
};
|
|
426
1096
|
}
|
|
427
|
-
for (const r of activeRows)
|
|
428
|
-
updated.push({ id: r.id });
|
|
429
1097
|
logActivity("complete_requirement", { all_active: true, completed: updated.map((u) => u.id) });
|
|
430
1098
|
return { content: [{ type: "text", text: toolJson({ ok: true, completed: updated }) }] };
|
|
431
1099
|
}
|
|
432
|
-
const
|
|
1100
|
+
const resolution = resolveActiveRequirement(context, args);
|
|
1101
|
+
if (resolution.ambiguous) {
|
|
1102
|
+
return {
|
|
1103
|
+
isError: true,
|
|
1104
|
+
content: [{
|
|
1105
|
+
type: "text",
|
|
1106
|
+
text: toolJson({
|
|
1107
|
+
ok: false,
|
|
1108
|
+
error: "Multiple active requirements exist. Pass req_id or goal_key to complete_requirement.",
|
|
1109
|
+
active_count: resolution.active_count,
|
|
1110
|
+
}),
|
|
1111
|
+
}],
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
const targetId = resolution.requirement?.id ?? null;
|
|
433
1115
|
if (!targetId) {
|
|
434
1116
|
return { content: [{ type: "text", text: toolJson({ ok: true, completed: [] }) }] };
|
|
435
1117
|
}
|
|
436
1118
|
try {
|
|
437
|
-
|
|
438
|
-
|
|
1119
|
+
if (!completeRequirementMemoryItemsByReqId(targetId)) {
|
|
1120
|
+
return {
|
|
1121
|
+
isError: true,
|
|
1122
|
+
content: [{ type: "text", text: toolJson({ ok: false, error: `Requirement ${targetId} is no longer active.` }) }],
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
439
1125
|
}
|
|
440
1126
|
catch (err) {
|
|
441
|
-
|
|
1127
|
+
return {
|
|
1128
|
+
isError: true,
|
|
1129
|
+
content: [{ type: "text", text: toolJson({ ok: false, error: `Failed to complete requirement atomically: ${String(err)}` }) }],
|
|
1130
|
+
};
|
|
442
1131
|
}
|
|
443
1132
|
logActivity("complete_requirement", { req_id: targetId });
|
|
444
1133
|
return { content: [{ type: "text", text: toolJson({ ok: true, completed: [{ id: targetId }] }) }] };
|