scene-capability-engine 3.6.32 → 3.6.36
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/CHANGELOG.md +86 -1
- package/README.md +119 -122
- package/README.zh.md +123 -121
- package/bin/scene-capability-engine.js +11 -0
- package/docs/README.md +21 -32
- package/docs/auto-refactor-index.md +384 -0
- package/docs/command-reference.md +94 -2
- package/docs/magicball-adaptation-task-checklist-v1.md +385 -0
- package/docs/magicball-app-bundle-sqlite-and-command-draft.md +539 -0
- package/docs/magicball-capability-iteration-api.md +2 -0
- package/docs/magicball-capability-iteration-ui.md +2 -0
- package/docs/magicball-capability-library.md +2 -0
- package/docs/magicball-cli-invocation-examples.md +336 -0
- package/docs/magicball-frontend-state-and-command-mapping.md +244 -0
- package/docs/magicball-integration-doc-index.md +137 -0
- package/docs/magicball-integration-issue-tracker.md +218 -0
- package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +249 -0
- package/docs/magicball-sce-adaptation-guide.md +203 -0
- package/docs/magicball-three-mode-alignment-plan.md +551 -0
- package/docs/magicball-ui-surface-checklist.md +126 -0
- package/docs/magicball-write-auth-adaptation-guide.md +328 -0
- package/docs/refactor-completion-roadmap.md +116 -0
- package/docs/zh/README.md +27 -30
- package/docs/zh/refactor-completion-roadmap.md +116 -0
- package/lib/app/registry-config.js +73 -0
- package/lib/app/registry-sync-service.js +228 -0
- package/lib/auto/archive-schema-service.js +276 -0
- package/lib/auto/archive-summary.js +60 -0
- package/lib/auto/batch-goal-input-service.js +543 -0
- package/lib/auto/batch-output.js +201 -0
- package/lib/auto/batch-summary-storage-service.js +110 -0
- package/lib/auto/close-loop-batch-service.js +116 -0
- package/lib/auto/close-loop-controller-service.js +287 -0
- package/lib/auto/close-loop-program-service.js +283 -0
- package/lib/auto/close-loop-recovery-service.js +191 -0
- package/lib/auto/close-loop-session-storage-service.js +50 -0
- package/lib/auto/controller-lock-service.js +55 -0
- package/lib/auto/controller-output.js +32 -0
- package/lib/auto/controller-queue-service.js +127 -0
- package/lib/auto/controller-session-storage-service.js +105 -0
- package/lib/auto/governance-advisory-service.js +208 -0
- package/lib/auto/governance-close-loop-service.js +411 -0
- package/lib/auto/governance-maintenance-presenter.js +162 -0
- package/lib/auto/governance-maintenance-service.js +112 -0
- package/lib/auto/governance-session-presenter.js +70 -0
- package/lib/auto/governance-session-storage-service.js +198 -0
- package/lib/auto/governance-signals.js +139 -0
- package/lib/auto/governance-stats-presenter.js +337 -0
- package/lib/auto/governance-stats-service.js +115 -0
- package/lib/auto/governance-summary.js +703 -0
- package/lib/auto/handoff-capability-matrix-service.js +281 -0
- package/lib/auto/handoff-evidence-review-service.js +251 -0
- package/lib/auto/handoff-release-evidence-service.js +190 -0
- package/lib/auto/handoff-release-gate-history-loaders-service.js +502 -0
- package/lib/auto/handoff-release-gate-history-service.js +257 -0
- package/lib/auto/handoff-reporting-service.js +1407 -0
- package/lib/auto/handoff-run-service.js +486 -0
- package/lib/auto/handoff-snapshots-service.js +645 -0
- package/lib/auto/observability-service.js +132 -0
- package/lib/auto/output-writer.js +34 -0
- package/lib/auto/program-auto-remediation-service.js +130 -0
- package/lib/auto/program-diagnostics.js +138 -0
- package/lib/auto/program-governance-helpers.js +306 -0
- package/lib/auto/program-governance-loop-service.js +413 -0
- package/lib/auto/program-output.js +106 -0
- package/lib/auto/program-summary.js +183 -0
- package/lib/auto/recovery-memory-service.js +684 -0
- package/lib/auto/recovery-selection-service.js +52 -0
- package/lib/auto/retention-policy.js +98 -0
- package/lib/auto/session-persistence-service.js +106 -0
- package/lib/auto/session-presenter.js +105 -0
- package/lib/auto/session-prune-service.js +190 -0
- package/lib/auto/session-query-service.js +249 -0
- package/lib/auto/spec-protection.js +141 -0
- package/lib/commands/app.js +911 -0
- package/lib/commands/assurance.js +212 -0
- package/lib/commands/auto.js +1091 -11063
- package/lib/commands/mode.js +321 -0
- package/lib/commands/ontology.js +415 -0
- package/lib/commands/pm.js +422 -0
- package/lib/ontology/seed-profiles.js +160 -0
- package/lib/state/sce-state-store.js +3369 -1200
- package/package.json +1 -1
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
function getCloseLoopRecoveryMemoryFile(projectPath, dependencies = {}) {
|
|
4
|
+
const pathModule = dependencies.path || path;
|
|
5
|
+
return pathModule.join(projectPath, '.sce', 'auto', 'close-loop-recovery-memory.json');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function loadCloseLoopRecoveryMemory(projectPath, dependencies = {}) {
|
|
9
|
+
const { fs, getCloseLoopRecoveryMemoryFile } = dependencies;
|
|
10
|
+
const memoryFile = getCloseLoopRecoveryMemoryFile(projectPath);
|
|
11
|
+
const fallbackPayload = {
|
|
12
|
+
version: 1,
|
|
13
|
+
signatures: {}
|
|
14
|
+
};
|
|
15
|
+
if (!(await fs.pathExists(memoryFile))) {
|
|
16
|
+
return {
|
|
17
|
+
file: memoryFile,
|
|
18
|
+
payload: fallbackPayload
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let payload = null;
|
|
23
|
+
try {
|
|
24
|
+
payload = await fs.readJson(memoryFile);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
return {
|
|
27
|
+
file: memoryFile,
|
|
28
|
+
payload: fallbackPayload
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!payload || typeof payload !== 'object') {
|
|
33
|
+
return {
|
|
34
|
+
file: memoryFile,
|
|
35
|
+
payload: fallbackPayload
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
file: memoryFile,
|
|
41
|
+
payload: {
|
|
42
|
+
version: Number(payload.version) || 1,
|
|
43
|
+
signatures: payload.signatures && typeof payload.signatures === 'object'
|
|
44
|
+
? payload.signatures
|
|
45
|
+
: {}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function normalizeRecoveryMemoryToken(value) {
|
|
51
|
+
return `${value || ''}`
|
|
52
|
+
.toLowerCase()
|
|
53
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
54
|
+
.replace(/^-+|-+$/g, '')
|
|
55
|
+
.slice(0, 120);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function resolveRecoveryMemoryScope(projectPath, scopeCandidate, dependencies = {}) {
|
|
59
|
+
const { path: pathModule = path, normalizeRecoveryMemoryToken, resolveGitBranchToken } = dependencies;
|
|
60
|
+
const explicitScope = `${scopeCandidate || ''}`.trim();
|
|
61
|
+
if (explicitScope && explicitScope.toLowerCase() !== 'auto') {
|
|
62
|
+
return normalizeRecoveryMemoryToken(explicitScope) || 'default-scope';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const projectToken = normalizeRecoveryMemoryToken(pathModule.basename(pathModule.resolve(projectPath || '.'))) || 'project';
|
|
66
|
+
const branchToken = await resolveGitBranchToken(projectPath);
|
|
67
|
+
return `${projectToken}|${branchToken || 'default'}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function resolveGitBranchToken(projectPath, dependencies = {}) {
|
|
71
|
+
const { path: pathModule = path, fs, normalizeRecoveryMemoryToken } = dependencies;
|
|
72
|
+
try {
|
|
73
|
+
const gitMetadataPath = pathModule.join(projectPath, '.git');
|
|
74
|
+
if (!(await fs.pathExists(gitMetadataPath))) {
|
|
75
|
+
return 'no-git';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let gitDir = gitMetadataPath;
|
|
79
|
+
const gitStat = await fs.stat(gitMetadataPath);
|
|
80
|
+
if (gitStat.isFile()) {
|
|
81
|
+
const pointer = await fs.readFile(gitMetadataPath, 'utf8');
|
|
82
|
+
const match = pointer.match(/gitdir:\s*(.+)/i);
|
|
83
|
+
if (match && match[1]) {
|
|
84
|
+
gitDir = pathModule.resolve(projectPath, match[1].trim());
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const headFile = pathModule.join(gitDir, 'HEAD');
|
|
89
|
+
if (!(await fs.pathExists(headFile))) {
|
|
90
|
+
return 'no-head';
|
|
91
|
+
}
|
|
92
|
+
const headContent = `${await fs.readFile(headFile, 'utf8')}`.trim();
|
|
93
|
+
const refMatch = headContent.match(/^ref:\s+refs\/heads\/(.+)$/i);
|
|
94
|
+
if (refMatch && refMatch[1]) {
|
|
95
|
+
return normalizeRecoveryMemoryToken(refMatch[1]) || 'unknown-branch';
|
|
96
|
+
}
|
|
97
|
+
if (/^[a-f0-9]{7,40}$/i.test(headContent)) {
|
|
98
|
+
return `detached-${headContent.slice(0, 8).toLowerCase()}`;
|
|
99
|
+
}
|
|
100
|
+
return 'unknown-branch';
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return 'unknown-branch';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildRecoveryMemorySignature(summaryPayload, context = {}, dependencies = {}) {
|
|
107
|
+
const { buildProgramDiagnostics } = dependencies;
|
|
108
|
+
const safeSummary = summaryPayload && typeof summaryPayload === 'object'
|
|
109
|
+
? summaryPayload
|
|
110
|
+
: {};
|
|
111
|
+
const diagnostics = safeSummary.program_diagnostics && typeof safeSummary.program_diagnostics === 'object'
|
|
112
|
+
? safeSummary.program_diagnostics
|
|
113
|
+
: buildProgramDiagnostics(safeSummary);
|
|
114
|
+
const clusters = Array.isArray(diagnostics.failure_clusters)
|
|
115
|
+
? diagnostics.failure_clusters
|
|
116
|
+
: [];
|
|
117
|
+
const clusterSignature = clusters
|
|
118
|
+
.slice(0, 3)
|
|
119
|
+
.map(cluster => normalizeRecoveryMemoryToken(cluster && cluster.signature))
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
.join('|');
|
|
122
|
+
const scopeToken = normalizeRecoveryMemoryToken(context.scope || 'default-scope') || 'default-scope';
|
|
123
|
+
const modeToken = normalizeRecoveryMemoryToken(safeSummary.mode || 'unknown-mode');
|
|
124
|
+
const failedCount = Number(safeSummary.failed_goals) || 0;
|
|
125
|
+
const seed = clusterSignature || 'no-failure-cluster';
|
|
126
|
+
return `scope-${scopeToken}|${modeToken}|failed-${failedCount}|${seed}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getRecoveryActionMemoryKey(action, index) {
|
|
130
|
+
const actionToken = normalizeRecoveryMemoryToken(action && action.action);
|
|
131
|
+
const commandToken = normalizeRecoveryMemoryToken(action && action.suggested_command);
|
|
132
|
+
const fallback = `action-${index}`;
|
|
133
|
+
return actionToken || commandToken
|
|
134
|
+
? `${fallback}|${actionToken || 'none'}|${commandToken || 'none'}`
|
|
135
|
+
: fallback;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function selectRecoveryActionFromMemory(availableActions, recoveryMemoryEntry) {
|
|
139
|
+
if (
|
|
140
|
+
!recoveryMemoryEntry ||
|
|
141
|
+
typeof recoveryMemoryEntry !== 'object' ||
|
|
142
|
+
!recoveryMemoryEntry.actions ||
|
|
143
|
+
typeof recoveryMemoryEntry.actions !== 'object'
|
|
144
|
+
) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const candidates = [];
|
|
149
|
+
for (let index = 1; index <= availableActions.length; index += 1) {
|
|
150
|
+
const action = availableActions[index - 1];
|
|
151
|
+
const key = getRecoveryActionMemoryKey(action, index);
|
|
152
|
+
const stats = recoveryMemoryEntry.actions[key];
|
|
153
|
+
if (!stats || typeof stats !== 'object') {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const attempts = Number(stats.attempts) || 0;
|
|
157
|
+
const successes = Number(stats.successes) || 0;
|
|
158
|
+
if (attempts <= 0) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const successRate = successes / attempts;
|
|
162
|
+
const score = (successRate * 100) + Math.min(25, attempts);
|
|
163
|
+
candidates.push({
|
|
164
|
+
index,
|
|
165
|
+
key,
|
|
166
|
+
score,
|
|
167
|
+
attempts,
|
|
168
|
+
successes,
|
|
169
|
+
failures: Number(stats.failures) || 0,
|
|
170
|
+
success_rate_percent: Number((successRate * 100).toFixed(2))
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (candidates.length === 0) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
candidates.sort((left, right) => {
|
|
178
|
+
if (right.score !== left.score) {
|
|
179
|
+
return right.score - left.score;
|
|
180
|
+
}
|
|
181
|
+
if (right.attempts !== left.attempts) {
|
|
182
|
+
return right.attempts - left.attempts;
|
|
183
|
+
}
|
|
184
|
+
return left.index - right.index;
|
|
185
|
+
});
|
|
186
|
+
const best = candidates[0];
|
|
187
|
+
return {
|
|
188
|
+
...best,
|
|
189
|
+
selection_reason: 'highest memory score: success_rate_percent + bounded_attempt_bonus',
|
|
190
|
+
top_candidates: candidates.slice(0, 5)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function getRecoveryMemoryEntry(recoveryMemoryPayload, signature) {
|
|
195
|
+
if (
|
|
196
|
+
!recoveryMemoryPayload ||
|
|
197
|
+
typeof recoveryMemoryPayload !== 'object' ||
|
|
198
|
+
!recoveryMemoryPayload.signatures ||
|
|
199
|
+
typeof recoveryMemoryPayload.signatures !== 'object'
|
|
200
|
+
) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const signatureKey = `${signature || ''}`.trim();
|
|
204
|
+
if (!signatureKey) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const entry = recoveryMemoryPayload.signatures[signatureKey];
|
|
208
|
+
return entry && typeof entry === 'object' ? entry : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function updateCloseLoopRecoveryMemory(
|
|
212
|
+
projectPath,
|
|
213
|
+
recoveryMemory,
|
|
214
|
+
signature,
|
|
215
|
+
selectedIndex,
|
|
216
|
+
selectedAction,
|
|
217
|
+
finalStatus,
|
|
218
|
+
metadata = {},
|
|
219
|
+
dependencies = {}
|
|
220
|
+
) {
|
|
221
|
+
const {
|
|
222
|
+
fs,
|
|
223
|
+
path: pathModule = path,
|
|
224
|
+
getCloseLoopRecoveryMemoryFile,
|
|
225
|
+
getRecoveryActionMemoryKey,
|
|
226
|
+
normalizeRecoveryMemoryToken
|
|
227
|
+
} = dependencies;
|
|
228
|
+
const memoryFile = recoveryMemory && typeof recoveryMemory.file === 'string'
|
|
229
|
+
? recoveryMemory.file
|
|
230
|
+
: getCloseLoopRecoveryMemoryFile(projectPath);
|
|
231
|
+
const memoryPayload = recoveryMemory && recoveryMemory.payload && typeof recoveryMemory.payload === 'object'
|
|
232
|
+
? recoveryMemory.payload
|
|
233
|
+
: {
|
|
234
|
+
version: 1,
|
|
235
|
+
signatures: {}
|
|
236
|
+
};
|
|
237
|
+
if (!memoryPayload.signatures || typeof memoryPayload.signatures !== 'object') {
|
|
238
|
+
memoryPayload.signatures = {};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const signatureKey = `${signature || ''}`.trim() || 'unknown-signature';
|
|
242
|
+
const selected = Number.isInteger(selectedIndex) && selectedIndex > 0 ? selectedIndex : 1;
|
|
243
|
+
const actionKey = getRecoveryActionMemoryKey(selectedAction || {}, selected);
|
|
244
|
+
const now = new Date().toISOString();
|
|
245
|
+
const scope = normalizeRecoveryMemoryToken(metadata.scope || '') || null;
|
|
246
|
+
|
|
247
|
+
if (!memoryPayload.signatures[signatureKey] || typeof memoryPayload.signatures[signatureKey] !== 'object') {
|
|
248
|
+
memoryPayload.signatures[signatureKey] = {
|
|
249
|
+
attempts: 0,
|
|
250
|
+
successes: 0,
|
|
251
|
+
failures: 0,
|
|
252
|
+
scope,
|
|
253
|
+
last_used_at: null,
|
|
254
|
+
last_selected_index: null,
|
|
255
|
+
actions: {}
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const signatureEntry = memoryPayload.signatures[signatureKey];
|
|
260
|
+
if (!signatureEntry.actions || typeof signatureEntry.actions !== 'object') {
|
|
261
|
+
signatureEntry.actions = {};
|
|
262
|
+
}
|
|
263
|
+
if (!signatureEntry.actions[actionKey] || typeof signatureEntry.actions[actionKey] !== 'object') {
|
|
264
|
+
signatureEntry.actions[actionKey] = {
|
|
265
|
+
attempts: 0,
|
|
266
|
+
successes: 0,
|
|
267
|
+
failures: 0,
|
|
268
|
+
last_status: null,
|
|
269
|
+
last_used_at: null,
|
|
270
|
+
last_selected_index: selected
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const actionEntry = signatureEntry.actions[actionKey];
|
|
275
|
+
const succeeded = `${finalStatus || ''}`.trim().toLowerCase() === 'completed';
|
|
276
|
+
|
|
277
|
+
signatureEntry.attempts = (Number(signatureEntry.attempts) || 0) + 1;
|
|
278
|
+
signatureEntry.successes = (Number(signatureEntry.successes) || 0) + (succeeded ? 1 : 0);
|
|
279
|
+
signatureEntry.failures = (Number(signatureEntry.failures) || 0) + (succeeded ? 0 : 1);
|
|
280
|
+
signatureEntry.scope = signatureEntry.scope || scope;
|
|
281
|
+
signatureEntry.last_used_at = now;
|
|
282
|
+
signatureEntry.last_selected_index = selected;
|
|
283
|
+
|
|
284
|
+
actionEntry.attempts = (Number(actionEntry.attempts) || 0) + 1;
|
|
285
|
+
actionEntry.successes = (Number(actionEntry.successes) || 0) + (succeeded ? 1 : 0);
|
|
286
|
+
actionEntry.failures = (Number(actionEntry.failures) || 0) + (succeeded ? 0 : 1);
|
|
287
|
+
actionEntry.last_status = `${finalStatus || 'unknown'}`;
|
|
288
|
+
actionEntry.last_used_at = now;
|
|
289
|
+
actionEntry.last_selected_index = selected;
|
|
290
|
+
|
|
291
|
+
await fs.ensureDir(pathModule.dirname(memoryFile));
|
|
292
|
+
await fs.writeJson(memoryFile, memoryPayload, { spaces: 2 });
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
file: memoryFile,
|
|
296
|
+
signature: signatureKey,
|
|
297
|
+
action_key: actionKey,
|
|
298
|
+
scope: signatureEntry.scope || scope,
|
|
299
|
+
entry: actionEntry
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function summarizeRecoveryMemory(memoryPayload) {
|
|
304
|
+
const signatures = memoryPayload && memoryPayload.signatures && typeof memoryPayload.signatures === 'object'
|
|
305
|
+
? memoryPayload.signatures
|
|
306
|
+
: {};
|
|
307
|
+
const signatureKeys = Object.keys(signatures);
|
|
308
|
+
let actionCount = 0;
|
|
309
|
+
const scopeCounts = {};
|
|
310
|
+
for (const key of signatureKeys) {
|
|
311
|
+
const entry = signatures[key];
|
|
312
|
+
if (entry && entry.actions && typeof entry.actions === 'object') {
|
|
313
|
+
actionCount += Object.keys(entry.actions).length;
|
|
314
|
+
}
|
|
315
|
+
const scope = normalizeRecoveryMemoryToken(entry && entry.scope ? entry.scope : 'default-scope') || 'default-scope';
|
|
316
|
+
scopeCounts[scope] = (Number(scopeCounts[scope]) || 0) + 1;
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
signature_count: signatureKeys.length,
|
|
320
|
+
action_count: actionCount,
|
|
321
|
+
scope_count: Object.keys(scopeCounts).length,
|
|
322
|
+
scopes: scopeCounts
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function filterRecoveryMemoryByScope(memoryPayload, scopeCandidate) {
|
|
327
|
+
const normalizedScope = normalizeRecoveryMemoryToken(scopeCandidate);
|
|
328
|
+
if (!normalizedScope) {
|
|
329
|
+
return {
|
|
330
|
+
scope: null,
|
|
331
|
+
payload: memoryPayload
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const source = memoryPayload && typeof memoryPayload === 'object'
|
|
336
|
+
? memoryPayload
|
|
337
|
+
: { version: 1, signatures: {} };
|
|
338
|
+
const signatures = source.signatures && typeof source.signatures === 'object'
|
|
339
|
+
? source.signatures
|
|
340
|
+
: {};
|
|
341
|
+
const filteredSignatures = {};
|
|
342
|
+
for (const [signature, entryRaw] of Object.entries(signatures)) {
|
|
343
|
+
const entry = entryRaw && typeof entryRaw === 'object' ? entryRaw : null;
|
|
344
|
+
if (!entry) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
const entryScope = normalizeRecoveryMemoryToken(entry.scope || 'default-scope') || 'default-scope';
|
|
348
|
+
if (entryScope !== normalizedScope) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
filteredSignatures[signature] = entry;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
scope: normalizedScope,
|
|
356
|
+
payload: {
|
|
357
|
+
version: Number(source.version) || 1,
|
|
358
|
+
signatures: filteredSignatures
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function buildRecoveryMemoryScopeStats(memoryPayload) {
|
|
364
|
+
const signatures = memoryPayload && memoryPayload.signatures && typeof memoryPayload.signatures === 'object'
|
|
365
|
+
? memoryPayload.signatures
|
|
366
|
+
: {};
|
|
367
|
+
const aggregates = new Map();
|
|
368
|
+
for (const entryRaw of Object.values(signatures)) {
|
|
369
|
+
const entry = entryRaw && typeof entryRaw === 'object' ? entryRaw : null;
|
|
370
|
+
if (!entry) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const scope = normalizeRecoveryMemoryToken(entry.scope || 'default-scope') || 'default-scope';
|
|
374
|
+
if (!aggregates.has(scope)) {
|
|
375
|
+
aggregates.set(scope, {
|
|
376
|
+
scope,
|
|
377
|
+
signature_count: 0,
|
|
378
|
+
action_count: 0,
|
|
379
|
+
attempts: 0,
|
|
380
|
+
successes: 0,
|
|
381
|
+
failures: 0
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const aggregate = aggregates.get(scope);
|
|
385
|
+
aggregate.signature_count += 1;
|
|
386
|
+
aggregate.attempts += Number(entry.attempts) || 0;
|
|
387
|
+
aggregate.successes += Number(entry.successes) || 0;
|
|
388
|
+
aggregate.failures += Number(entry.failures) || 0;
|
|
389
|
+
if (entry.actions && typeof entry.actions === 'object') {
|
|
390
|
+
aggregate.action_count += Object.keys(entry.actions).length;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return [...aggregates.values()]
|
|
395
|
+
.map(item => ({
|
|
396
|
+
...item,
|
|
397
|
+
success_rate_percent: item.attempts > 0
|
|
398
|
+
? Number(((item.successes / item.attempts) * 100).toFixed(2))
|
|
399
|
+
: 0
|
|
400
|
+
}))
|
|
401
|
+
.sort((left, right) => {
|
|
402
|
+
if (right.signature_count !== left.signature_count) {
|
|
403
|
+
return right.signature_count - left.signature_count;
|
|
404
|
+
}
|
|
405
|
+
return `${left.scope}`.localeCompare(`${right.scope}`);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function isIsoTimestampOlderThan(timestamp, cutoffMs) {
|
|
410
|
+
if (cutoffMs === null) {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
if (typeof timestamp !== 'string' || !timestamp.trim()) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
const parsed = Date.parse(timestamp);
|
|
417
|
+
if (Number.isNaN(parsed)) {
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
return parsed < cutoffMs;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async function showCloseLoopRecoveryMemory(projectPath, options = {}, dependencies = {}) {
|
|
424
|
+
const { loadCloseLoopRecoveryMemory, filterRecoveryMemoryByScope, summarizeRecoveryMemory } = dependencies;
|
|
425
|
+
const recoveryMemory = await loadCloseLoopRecoveryMemory(projectPath);
|
|
426
|
+
const filtered = filterRecoveryMemoryByScope(recoveryMemory.payload, options.scope);
|
|
427
|
+
return {
|
|
428
|
+
mode: 'auto-recovery-memory-show',
|
|
429
|
+
file: recoveryMemory.file,
|
|
430
|
+
scope: filtered.scope,
|
|
431
|
+
stats: summarizeRecoveryMemory(filtered.payload),
|
|
432
|
+
payload: filtered.payload
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function showCloseLoopRecoveryMemoryScopes(projectPath, dependencies = {}) {
|
|
437
|
+
const { loadCloseLoopRecoveryMemory, buildRecoveryMemoryScopeStats } = dependencies;
|
|
438
|
+
const recoveryMemory = await loadCloseLoopRecoveryMemory(projectPath);
|
|
439
|
+
const scopes = buildRecoveryMemoryScopeStats(recoveryMemory.payload);
|
|
440
|
+
return {
|
|
441
|
+
mode: 'auto-recovery-memory-scopes',
|
|
442
|
+
file: recoveryMemory.file,
|
|
443
|
+
total_scopes: scopes.length,
|
|
444
|
+
scopes
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function pruneCloseLoopRecoveryMemory(projectPath, options = {}, dependencies = {}) {
|
|
449
|
+
const {
|
|
450
|
+
fs,
|
|
451
|
+
path: pathModule = path,
|
|
452
|
+
Date: DateCtor = Date,
|
|
453
|
+
normalizeRecoveryMemoryTtlDays,
|
|
454
|
+
loadCloseLoopRecoveryMemory,
|
|
455
|
+
filterRecoveryMemoryByScope,
|
|
456
|
+
summarizeRecoveryMemory
|
|
457
|
+
} = dependencies;
|
|
458
|
+
const olderThanDays = normalizeRecoveryMemoryTtlDays(options.olderThanDays === undefined ? 30 : options.olderThanDays);
|
|
459
|
+
const scope = normalizeRecoveryMemoryToken(options.scope || '') || null;
|
|
460
|
+
const dryRun = Boolean(options.dryRun);
|
|
461
|
+
const recoveryMemory = await loadCloseLoopRecoveryMemory(projectPath);
|
|
462
|
+
const memoryPayload = recoveryMemory.payload && typeof recoveryMemory.payload === 'object'
|
|
463
|
+
? recoveryMemory.payload
|
|
464
|
+
: { version: 1, signatures: {} };
|
|
465
|
+
if (!memoryPayload.signatures || typeof memoryPayload.signatures !== 'object') {
|
|
466
|
+
memoryPayload.signatures = {};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const cutoffMs = olderThanDays === null
|
|
470
|
+
? null
|
|
471
|
+
: DateCtor.now() - (olderThanDays * 24 * 60 * 60 * 1000);
|
|
472
|
+
const filteredBeforePayload = scope
|
|
473
|
+
? filterRecoveryMemoryByScope(memoryPayload, scope).payload
|
|
474
|
+
: memoryPayload;
|
|
475
|
+
const signaturesBefore = summarizeRecoveryMemory(filteredBeforePayload).signature_count;
|
|
476
|
+
const actionBefore = summarizeRecoveryMemory(filteredBeforePayload).action_count;
|
|
477
|
+
|
|
478
|
+
const retainedSignatures = {};
|
|
479
|
+
for (const [signature, entryRaw] of Object.entries(memoryPayload.signatures)) {
|
|
480
|
+
const entry = entryRaw && typeof entryRaw === 'object' ? { ...entryRaw } : null;
|
|
481
|
+
if (!entry) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const entryScope = normalizeRecoveryMemoryToken(entry.scope || 'default-scope') || 'default-scope';
|
|
485
|
+
if (scope && entryScope !== scope) {
|
|
486
|
+
retainedSignatures[signature] = entry;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
const actions = entry.actions && typeof entry.actions === 'object' ? entry.actions : {};
|
|
490
|
+
const retainedActions = {};
|
|
491
|
+
for (const [actionKey, actionStatsRaw] of Object.entries(actions)) {
|
|
492
|
+
const actionStats = actionStatsRaw && typeof actionStatsRaw === 'object' ? actionStatsRaw : null;
|
|
493
|
+
if (!actionStats) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (!isIsoTimestampOlderThan(actionStats.last_used_at, cutoffMs)) {
|
|
497
|
+
retainedActions[actionKey] = actionStats;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (Object.keys(retainedActions).length > 0 || !isIsoTimestampOlderThan(entry.last_used_at, cutoffMs)) {
|
|
502
|
+
retainedSignatures[signature] = {
|
|
503
|
+
...entry,
|
|
504
|
+
actions: retainedActions
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const nextPayload = {
|
|
510
|
+
version: Number(memoryPayload.version) || 1,
|
|
511
|
+
signatures: retainedSignatures
|
|
512
|
+
};
|
|
513
|
+
const filteredAfterPayload = scope
|
|
514
|
+
? filterRecoveryMemoryByScope(nextPayload, scope).payload
|
|
515
|
+
: nextPayload;
|
|
516
|
+
const signaturesAfter = summarizeRecoveryMemory(filteredAfterPayload).signature_count;
|
|
517
|
+
const actionAfter = summarizeRecoveryMemory(filteredAfterPayload).action_count;
|
|
518
|
+
if (!dryRun) {
|
|
519
|
+
await fs.ensureDir(pathModule.dirname(recoveryMemory.file));
|
|
520
|
+
await fs.writeJson(recoveryMemory.file, nextPayload, { spaces: 2 });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return {
|
|
524
|
+
mode: 'auto-recovery-memory-prune',
|
|
525
|
+
file: recoveryMemory.file,
|
|
526
|
+
scope,
|
|
527
|
+
dry_run: dryRun,
|
|
528
|
+
older_than_days: olderThanDays,
|
|
529
|
+
signatures_before: signaturesBefore,
|
|
530
|
+
signatures_after: signaturesAfter,
|
|
531
|
+
actions_before: actionBefore,
|
|
532
|
+
actions_after: actionAfter,
|
|
533
|
+
signatures_removed: Math.max(0, signaturesBefore - signaturesAfter),
|
|
534
|
+
actions_removed: Math.max(0, actionBefore - actionAfter)
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async function clearCloseLoopRecoveryMemory(projectPath, dependencies = {}) {
|
|
539
|
+
const { fs, getCloseLoopRecoveryMemoryFile } = dependencies;
|
|
540
|
+
const recoveryMemoryFile = getCloseLoopRecoveryMemoryFile(projectPath);
|
|
541
|
+
const existed = await fs.pathExists(recoveryMemoryFile);
|
|
542
|
+
if (existed) {
|
|
543
|
+
await fs.remove(recoveryMemoryFile);
|
|
544
|
+
}
|
|
545
|
+
return {
|
|
546
|
+
mode: 'auto-recovery-memory-clear',
|
|
547
|
+
file: recoveryMemoryFile,
|
|
548
|
+
existed,
|
|
549
|
+
cleared: true
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function resolveRecoveryActionSelection(summaryPayload, actionCandidate, context = {}, dependencies = {}) {
|
|
554
|
+
const {
|
|
555
|
+
buildProgramDiagnostics,
|
|
556
|
+
buildProgramRemediationActions,
|
|
557
|
+
normalizeRecoveryActionIndex,
|
|
558
|
+
selectRecoveryActionFromMemory
|
|
559
|
+
} = dependencies;
|
|
560
|
+
const diagnostics = summaryPayload && summaryPayload.program_diagnostics && typeof summaryPayload.program_diagnostics === 'object'
|
|
561
|
+
? summaryPayload.program_diagnostics
|
|
562
|
+
: buildProgramDiagnostics(summaryPayload || {});
|
|
563
|
+
const availableActions = Array.isArray(diagnostics.remediation_actions) && diagnostics.remediation_actions.length > 0
|
|
564
|
+
? diagnostics.remediation_actions
|
|
565
|
+
: buildProgramRemediationActions(summaryPayload || {}, []);
|
|
566
|
+
const optionLabel = typeof context.optionLabel === 'string' && context.optionLabel.trim()
|
|
567
|
+
? context.optionLabel.trim()
|
|
568
|
+
: '--use-action';
|
|
569
|
+
let selectedIndex = null;
|
|
570
|
+
let selectionSource = 'default';
|
|
571
|
+
let memorySelection = null;
|
|
572
|
+
let selectionExplain = null;
|
|
573
|
+
if (actionCandidate !== undefined && actionCandidate !== null) {
|
|
574
|
+
selectedIndex = normalizeRecoveryActionIndex(actionCandidate, availableActions.length, optionLabel);
|
|
575
|
+
selectionSource = 'explicit';
|
|
576
|
+
selectionExplain = {
|
|
577
|
+
mode: 'explicit',
|
|
578
|
+
reason: `${optionLabel} provided`,
|
|
579
|
+
selected_index: selectedIndex
|
|
580
|
+
};
|
|
581
|
+
} else {
|
|
582
|
+
memorySelection = selectRecoveryActionFromMemory(availableActions, context.recoveryMemoryEntry);
|
|
583
|
+
if (memorySelection) {
|
|
584
|
+
selectedIndex = memorySelection.index;
|
|
585
|
+
selectionSource = 'memory';
|
|
586
|
+
selectionExplain = {
|
|
587
|
+
mode: 'memory',
|
|
588
|
+
reason: memorySelection.selection_reason,
|
|
589
|
+
selected_index: selectedIndex,
|
|
590
|
+
candidate_count: memorySelection.top_candidates.length,
|
|
591
|
+
top_candidates: memorySelection.top_candidates
|
|
592
|
+
};
|
|
593
|
+
} else {
|
|
594
|
+
selectedIndex = normalizeRecoveryActionIndex(undefined, availableActions.length);
|
|
595
|
+
selectionExplain = {
|
|
596
|
+
mode: 'default',
|
|
597
|
+
reason: 'no matching memory entry found for current signature',
|
|
598
|
+
selected_index: selectedIndex
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const selectedAction = availableActions[selectedIndex - 1] || null;
|
|
603
|
+
const appliedPatch = selectedAction && selectedAction.strategy_patch && typeof selectedAction.strategy_patch === 'object'
|
|
604
|
+
? { ...selectedAction.strategy_patch }
|
|
605
|
+
: {};
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
selectedIndex,
|
|
609
|
+
selectedAction,
|
|
610
|
+
availableActions,
|
|
611
|
+
appliedPatch,
|
|
612
|
+
selectionSource,
|
|
613
|
+
memorySelection,
|
|
614
|
+
selectionExplain
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function applyRecoveryActionPatch(options, selectedAction) {
|
|
619
|
+
const baseOptions = { ...options };
|
|
620
|
+
const patch = selectedAction && selectedAction.strategy_patch && typeof selectedAction.strategy_patch === 'object'
|
|
621
|
+
? selectedAction.strategy_patch
|
|
622
|
+
: {};
|
|
623
|
+
const merged = { ...baseOptions };
|
|
624
|
+
|
|
625
|
+
if (patch.batchAutonomous !== undefined) {
|
|
626
|
+
merged.batchAutonomous = Boolean(patch.batchAutonomous);
|
|
627
|
+
}
|
|
628
|
+
if (patch.continueOnError !== undefined && merged.continueOnError === undefined) {
|
|
629
|
+
merged.continueOnError = Boolean(patch.continueOnError);
|
|
630
|
+
}
|
|
631
|
+
if (patch.batchParallel !== undefined && (merged.batchParallel === undefined || merged.batchParallel === null)) {
|
|
632
|
+
merged.batchParallel = Number(patch.batchParallel);
|
|
633
|
+
}
|
|
634
|
+
if (patch.batchAgentBudget !== undefined && (merged.batchAgentBudget === undefined || merged.batchAgentBudget === null)) {
|
|
635
|
+
merged.batchAgentBudget = Number(patch.batchAgentBudget);
|
|
636
|
+
}
|
|
637
|
+
if (patch.batchPriority !== undefined && (!merged.batchPriority || `${merged.batchPriority}`.trim().toLowerCase() === 'fifo')) {
|
|
638
|
+
merged.batchPriority = patch.batchPriority;
|
|
639
|
+
}
|
|
640
|
+
if (patch.batchAgingFactor !== undefined && (merged.batchAgingFactor === undefined || merged.batchAgingFactor === null)) {
|
|
641
|
+
merged.batchAgingFactor = Number(patch.batchAgingFactor);
|
|
642
|
+
}
|
|
643
|
+
if (patch.batchRetryRounds !== undefined && (merged.batchRetryRounds === undefined || merged.batchRetryRounds === null)) {
|
|
644
|
+
merged.batchRetryRounds = Number(patch.batchRetryRounds);
|
|
645
|
+
}
|
|
646
|
+
if (patch.batchRetryUntilComplete !== undefined && merged.batchRetryUntilComplete === undefined) {
|
|
647
|
+
merged.batchRetryUntilComplete = Boolean(patch.batchRetryUntilComplete);
|
|
648
|
+
}
|
|
649
|
+
if (patch.batchRetryMaxRounds !== undefined && (merged.batchRetryMaxRounds === undefined || merged.batchRetryMaxRounds === null)) {
|
|
650
|
+
merged.batchRetryMaxRounds = Number(patch.batchRetryMaxRounds);
|
|
651
|
+
}
|
|
652
|
+
if (patch.dodTests !== undefined && !merged.dodTests) {
|
|
653
|
+
merged.dodTests = patch.dodTests;
|
|
654
|
+
}
|
|
655
|
+
if (patch.dodTasksClosed !== undefined && merged.dodTasksClosed === undefined) {
|
|
656
|
+
merged.dodTasksClosed = Boolean(patch.dodTasksClosed);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
return merged;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
module.exports = {
|
|
665
|
+
getCloseLoopRecoveryMemoryFile,
|
|
666
|
+
loadCloseLoopRecoveryMemory,
|
|
667
|
+
normalizeRecoveryMemoryToken,
|
|
668
|
+
resolveRecoveryMemoryScope,
|
|
669
|
+
resolveGitBranchToken,
|
|
670
|
+
buildRecoveryMemorySignature,
|
|
671
|
+
getRecoveryActionMemoryKey,
|
|
672
|
+
selectRecoveryActionFromMemory,
|
|
673
|
+
getRecoveryMemoryEntry,
|
|
674
|
+
updateCloseLoopRecoveryMemory,
|
|
675
|
+
summarizeRecoveryMemory,
|
|
676
|
+
filterRecoveryMemoryByScope,
|
|
677
|
+
buildRecoveryMemoryScopeStats,
|
|
678
|
+
showCloseLoopRecoveryMemory,
|
|
679
|
+
showCloseLoopRecoveryMemoryScopes,
|
|
680
|
+
pruneCloseLoopRecoveryMemory,
|
|
681
|
+
clearCloseLoopRecoveryMemory,
|
|
682
|
+
resolveRecoveryActionSelection,
|
|
683
|
+
applyRecoveryActionPatch
|
|
684
|
+
};
|