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,52 @@
|
|
|
1
|
+
async function resolveLatestRecoverableBatchSummary(projectPath, resumeStrategy = 'pending', dependencies = {}) {
|
|
2
|
+
const {
|
|
3
|
+
readCloseLoopBatchSummaryEntries,
|
|
4
|
+
loadCloseLoopBatchSummaryPayload,
|
|
5
|
+
buildCloseLoopBatchGoalsFromSummaryPayload
|
|
6
|
+
} = dependencies;
|
|
7
|
+
const entries = await readCloseLoopBatchSummaryEntries(projectPath);
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
if (!entry || !entry.file) {
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
let loaded = null;
|
|
13
|
+
try {
|
|
14
|
+
loaded = await loadCloseLoopBatchSummaryPayload(projectPath, entry.file);
|
|
15
|
+
const goalsResult = await buildCloseLoopBatchGoalsFromSummaryPayload(
|
|
16
|
+
loaded.payload,
|
|
17
|
+
loaded.file,
|
|
18
|
+
projectPath,
|
|
19
|
+
'auto',
|
|
20
|
+
resumeStrategy
|
|
21
|
+
);
|
|
22
|
+
if (Array.isArray(goalsResult.goals) && goalsResult.goals.length > 0) {
|
|
23
|
+
return loaded;
|
|
24
|
+
}
|
|
25
|
+
} catch (_error) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function resolveLatestPendingControllerSession(projectPath, dependencies = {}) {
|
|
33
|
+
const {
|
|
34
|
+
readCloseLoopControllerSessionEntries,
|
|
35
|
+
loadCloseLoopControllerSessionPayload
|
|
36
|
+
} = dependencies;
|
|
37
|
+
const sessions = await readCloseLoopControllerSessionEntries(projectPath);
|
|
38
|
+
const pendingSession = sessions.find((session) => Number(session && session.pending_goals) > 0);
|
|
39
|
+
if (!pendingSession || !pendingSession.file) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return await loadCloseLoopControllerSessionPayload(projectPath, pendingSession.file);
|
|
44
|
+
} catch (_error) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
resolveLatestRecoverableBatchSummary,
|
|
51
|
+
resolveLatestPendingControllerSession
|
|
52
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
function normalizeKeep(keepCandidate) {
|
|
2
|
+
if (keepCandidate === undefined || keepCandidate === null) {
|
|
3
|
+
return 20;
|
|
4
|
+
}
|
|
5
|
+
const parsed = Number(keepCandidate);
|
|
6
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 1000) {
|
|
7
|
+
throw new Error('--keep must be an integer between 0 and 1000.');
|
|
8
|
+
}
|
|
9
|
+
return parsed;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeSpecKeep(keepCandidate, fallback = 200) {
|
|
13
|
+
if (keepCandidate === undefined || keepCandidate === null) {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
const parsed = Number(keepCandidate);
|
|
17
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 5000) {
|
|
18
|
+
throw new Error('--keep must be an integer between 0 and 5000.');
|
|
19
|
+
}
|
|
20
|
+
return parsed;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeOlderThanDays(daysCandidate) {
|
|
24
|
+
if (daysCandidate === undefined || daysCandidate === null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const parsed = Number(daysCandidate);
|
|
28
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 36500) {
|
|
29
|
+
throw new Error('--older-than-days must be an integer between 0 and 36500.');
|
|
30
|
+
}
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function normalizeSpecSessionProtectWindowDays(daysCandidate) {
|
|
35
|
+
if (daysCandidate === undefined || daysCandidate === null) {
|
|
36
|
+
return 7;
|
|
37
|
+
}
|
|
38
|
+
const parsed = Number(daysCandidate);
|
|
39
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 36500) {
|
|
40
|
+
throw new Error('--spec-session-protect-window-days must be an integer between 0 and 36500.');
|
|
41
|
+
}
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeSpecSessionMaxTotal(maxTotalCandidate) {
|
|
46
|
+
if (maxTotalCandidate === undefined || maxTotalCandidate === null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const parsed = Number(maxTotalCandidate);
|
|
50
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 500000) {
|
|
51
|
+
throw new Error('--spec-session-max-total must be an integer between 1 and 500000.');
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeSpecSessionMaxCreated(maxCreatedCandidate) {
|
|
57
|
+
if (maxCreatedCandidate === undefined || maxCreatedCandidate === null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const parsed = Number(maxCreatedCandidate);
|
|
61
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 500000) {
|
|
62
|
+
throw new Error('--spec-session-max-created must be an integer between 0 and 500000.');
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeSpecSessionMaxCreatedPerGoal(maxCreatedPerGoalCandidate) {
|
|
68
|
+
if (maxCreatedPerGoalCandidate === undefined || maxCreatedPerGoalCandidate === null) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
const parsed = Number(maxCreatedPerGoalCandidate);
|
|
72
|
+
if (Number.isNaN(parsed) || parsed < 0 || parsed > 1000) {
|
|
73
|
+
throw new Error('--spec-session-max-created-per-goal must be a number between 0 and 1000.');
|
|
74
|
+
}
|
|
75
|
+
return Number(parsed.toFixed(2));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeSpecSessionMaxDuplicateGoals(maxDuplicateGoalsCandidate) {
|
|
79
|
+
if (maxDuplicateGoalsCandidate === undefined || maxDuplicateGoalsCandidate === null) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const parsed = Number(maxDuplicateGoalsCandidate);
|
|
83
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 500000) {
|
|
84
|
+
throw new Error('--spec-session-max-duplicate-goals must be an integer between 0 and 500000.');
|
|
85
|
+
}
|
|
86
|
+
return parsed;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = {
|
|
90
|
+
normalizeKeep,
|
|
91
|
+
normalizeSpecKeep,
|
|
92
|
+
normalizeOlderThanDays,
|
|
93
|
+
normalizeSpecSessionProtectWindowDays,
|
|
94
|
+
normalizeSpecSessionMaxTotal,
|
|
95
|
+
normalizeSpecSessionMaxCreated,
|
|
96
|
+
normalizeSpecSessionMaxCreatedPerGoal,
|
|
97
|
+
normalizeSpecSessionMaxDuplicateGoals
|
|
98
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
async function maybePersistCloseLoopControllerSummary(summary, options, projectPath, dependencies = {}) {
|
|
2
|
+
const {
|
|
3
|
+
normalizeControllerSessionKeep,
|
|
4
|
+
normalizeControllerSessionOlderThanDays,
|
|
5
|
+
sanitizeBatchSessionId,
|
|
6
|
+
createControllerSessionId,
|
|
7
|
+
getCloseLoopControllerSessionDir,
|
|
8
|
+
pruneCloseLoopControllerSessions,
|
|
9
|
+
schemaVersion,
|
|
10
|
+
fs,
|
|
11
|
+
now = () => new Date()
|
|
12
|
+
} = dependencies;
|
|
13
|
+
if (options.controllerSession === false) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const keep = normalizeControllerSessionKeep(options.controllerSessionKeep);
|
|
18
|
+
const olderThanDays = normalizeControllerSessionOlderThanDays(options.controllerSessionOlderThanDays);
|
|
19
|
+
const requestedId = typeof options.controllerSessionId === 'string' && options.controllerSessionId.trim()
|
|
20
|
+
? sanitizeBatchSessionId(options.controllerSessionId.trim())
|
|
21
|
+
: null;
|
|
22
|
+
const sessionId = requestedId || createControllerSessionId();
|
|
23
|
+
if (!sessionId) {
|
|
24
|
+
throw new Error('--controller-session-id is invalid after sanitization.');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const sessionDir = getCloseLoopControllerSessionDir(projectPath);
|
|
28
|
+
const sessionFile = require('path').join(sessionDir, `${sessionId}.json`);
|
|
29
|
+
summary.controller_session = { id: sessionId, file: sessionFile };
|
|
30
|
+
summary.schema_version = schemaVersion;
|
|
31
|
+
|
|
32
|
+
await fs.ensureDir(sessionDir);
|
|
33
|
+
const nowValue = now();
|
|
34
|
+
const updatedAt = nowValue instanceof Date ? nowValue.toISOString() : new Date(nowValue).toISOString();
|
|
35
|
+
await fs.writeJson(sessionFile, {
|
|
36
|
+
...summary,
|
|
37
|
+
schema_version: schemaVersion,
|
|
38
|
+
controller_session: { id: sessionId, file: sessionFile },
|
|
39
|
+
updated_at: updatedAt
|
|
40
|
+
}, { spaces: 2 });
|
|
41
|
+
|
|
42
|
+
if (keep !== null || olderThanDays !== null) {
|
|
43
|
+
summary.controller_session_prune = await pruneCloseLoopControllerSessions(projectPath, {
|
|
44
|
+
keep: keep === null ? null : keep,
|
|
45
|
+
olderThanDays,
|
|
46
|
+
currentFile: sessionFile,
|
|
47
|
+
dryRun: false
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function maybePersistCloseLoopBatchSummary(summary, options, projectPath, dependencies = {}) {
|
|
53
|
+
const {
|
|
54
|
+
normalizeBatchSessionKeep,
|
|
55
|
+
normalizeBatchSessionOlderThanDays,
|
|
56
|
+
sanitizeBatchSessionId,
|
|
57
|
+
createBatchSessionId,
|
|
58
|
+
getCloseLoopBatchSummaryDir,
|
|
59
|
+
pruneCloseLoopBatchSummarySessions,
|
|
60
|
+
schemaVersion,
|
|
61
|
+
fs,
|
|
62
|
+
now = () => new Date()
|
|
63
|
+
} = dependencies;
|
|
64
|
+
if (options.batchSession === false) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const keep = normalizeBatchSessionKeep(options.batchSessionKeep);
|
|
69
|
+
const olderThanDays = normalizeBatchSessionOlderThanDays(options.batchSessionOlderThanDays);
|
|
70
|
+
const requestedId = typeof options.batchSessionId === 'string' && options.batchSessionId.trim()
|
|
71
|
+
? sanitizeBatchSessionId(options.batchSessionId.trim())
|
|
72
|
+
: null;
|
|
73
|
+
const sessionId = requestedId || createBatchSessionId();
|
|
74
|
+
if (!sessionId) {
|
|
75
|
+
throw new Error('--batch-session-id is invalid after sanitization.');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const summaryDir = getCloseLoopBatchSummaryDir(projectPath);
|
|
79
|
+
const summaryFile = require('path').join(summaryDir, `${sessionId}.json`);
|
|
80
|
+
summary.batch_session = { id: sessionId, file: summaryFile };
|
|
81
|
+
summary.schema_version = schemaVersion;
|
|
82
|
+
|
|
83
|
+
await fs.ensureDir(summaryDir);
|
|
84
|
+
const nowValue = now();
|
|
85
|
+
const updatedAt = nowValue instanceof Date ? nowValue.toISOString() : new Date(nowValue).toISOString();
|
|
86
|
+
await fs.writeJson(summaryFile, {
|
|
87
|
+
...summary,
|
|
88
|
+
schema_version: schemaVersion,
|
|
89
|
+
batch_session: { id: sessionId, file: summaryFile },
|
|
90
|
+
updated_at: updatedAt
|
|
91
|
+
}, { spaces: 2 });
|
|
92
|
+
|
|
93
|
+
if (keep !== null || olderThanDays !== null) {
|
|
94
|
+
summary.batch_session_prune = await pruneCloseLoopBatchSummarySessions(projectPath, {
|
|
95
|
+
keep: keep === null ? null : keep,
|
|
96
|
+
olderThanDays,
|
|
97
|
+
currentFile: summaryFile,
|
|
98
|
+
dryRun: false
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
maybePersistCloseLoopControllerSummary,
|
|
105
|
+
maybePersistCloseLoopBatchSummary
|
|
106
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function presentCloseLoopSessionList(projectPath, sessions, statusFilter, limit, buildStatusCounts, getCloseLoopSessionDir) {
|
|
2
|
+
const filteredSessions = Array.isArray(sessions) ? sessions : [];
|
|
3
|
+
return {
|
|
4
|
+
mode: 'auto-session-list',
|
|
5
|
+
session_dir: getCloseLoopSessionDir(projectPath),
|
|
6
|
+
total: filteredSessions.length,
|
|
7
|
+
status_filter: statusFilter,
|
|
8
|
+
status_counts: buildStatusCounts(filteredSessions),
|
|
9
|
+
sessions: filteredSessions.slice(0, limit)
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function presentCloseLoopSessionStats(sessionDir, filteredSessions, statusFilter, days, cutoffMs, buildStatusCounts, buildMasterSpecCounts, isFailedStatus) {
|
|
14
|
+
// Backward compatibility: older callers omitted cutoffMs and passed builders directly.
|
|
15
|
+
if (typeof cutoffMs === 'function') {
|
|
16
|
+
isFailedStatus = buildMasterSpecCounts;
|
|
17
|
+
buildMasterSpecCounts = buildStatusCounts;
|
|
18
|
+
buildStatusCounts = cutoffMs;
|
|
19
|
+
cutoffMs = null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let completedSessions = 0;
|
|
23
|
+
let failedSessions = 0;
|
|
24
|
+
let subSpecCountSum = 0;
|
|
25
|
+
let sessionsWithSubSpecs = 0;
|
|
26
|
+
for (const session of filteredSessions) {
|
|
27
|
+
const status = String(session && session.status || 'unknown').trim().toLowerCase();
|
|
28
|
+
if (status === 'completed') {
|
|
29
|
+
completedSessions += 1;
|
|
30
|
+
}
|
|
31
|
+
if (isFailedStatus(status)) {
|
|
32
|
+
failedSessions += 1;
|
|
33
|
+
}
|
|
34
|
+
const subSpecCount = Number(session && session.sub_spec_count);
|
|
35
|
+
if (Number.isFinite(subSpecCount)) {
|
|
36
|
+
subSpecCountSum += subSpecCount;
|
|
37
|
+
sessionsWithSubSpecs += 1;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const totalSessions = filteredSessions.length;
|
|
41
|
+
const completionRate = totalSessions > 0 ? Number(((completedSessions / totalSessions) * 100).toFixed(2)) : 0;
|
|
42
|
+
const failureRate = totalSessions > 0 ? Number(((failedSessions / totalSessions) * 100).toFixed(2)) : 0;
|
|
43
|
+
const masterSpecCounts = buildMasterSpecCounts(filteredSessions);
|
|
44
|
+
const latestSession = totalSessions > 0 ? filteredSessions[0] : null;
|
|
45
|
+
const oldestSession = totalSessions > 0 ? filteredSessions[totalSessions - 1] : null;
|
|
46
|
+
return {
|
|
47
|
+
mode: 'auto-session-stats',
|
|
48
|
+
session_dir: sessionDir,
|
|
49
|
+
criteria: {
|
|
50
|
+
days,
|
|
51
|
+
status_filter: statusFilter,
|
|
52
|
+
since: cutoffMs === null ? null : new Date(cutoffMs).toISOString()
|
|
53
|
+
},
|
|
54
|
+
total_sessions: totalSessions,
|
|
55
|
+
completed_sessions: completedSessions,
|
|
56
|
+
failed_sessions: failedSessions,
|
|
57
|
+
completion_rate_percent: completionRate,
|
|
58
|
+
failure_rate_percent: failureRate,
|
|
59
|
+
sub_spec_count_sum: subSpecCountSum,
|
|
60
|
+
average_sub_specs_per_session: sessionsWithSubSpecs > 0 ? Number((subSpecCountSum / sessionsWithSubSpecs).toFixed(2)) : 0,
|
|
61
|
+
unique_master_spec_count: Object.keys(masterSpecCounts).length,
|
|
62
|
+
master_spec_counts: masterSpecCounts,
|
|
63
|
+
status_counts: buildStatusCounts(filteredSessions),
|
|
64
|
+
latest_updated_at: latestSession ? latestSession.updated_at : null,
|
|
65
|
+
oldest_updated_at: oldestSession ? oldestSession.updated_at : null,
|
|
66
|
+
latest_sessions: filteredSessions.slice(0, 10).map((item) => ({
|
|
67
|
+
id: item.id,
|
|
68
|
+
status: item.status,
|
|
69
|
+
goal: item.goal,
|
|
70
|
+
master_spec: item.master_spec,
|
|
71
|
+
sub_spec_count: item.sub_spec_count,
|
|
72
|
+
updated_at: item.updated_at,
|
|
73
|
+
parse_error: item.parse_error
|
|
74
|
+
}))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function presentControllerSessionList(projectPath, filteredSessions, statusFilter, limit, buildStatusCounts, getCloseLoopControllerSessionDir) {
|
|
79
|
+
return {
|
|
80
|
+
mode: 'auto-controller-session-list',
|
|
81
|
+
session_dir: getCloseLoopControllerSessionDir(projectPath),
|
|
82
|
+
total: filteredSessions.length,
|
|
83
|
+
status_filter: statusFilter,
|
|
84
|
+
status_counts: buildStatusCounts(filteredSessions),
|
|
85
|
+
sessions: filteredSessions.slice(0, limit).map((item) => ({
|
|
86
|
+
id: item.id,
|
|
87
|
+
file: item.file,
|
|
88
|
+
status: item.status,
|
|
89
|
+
queue_file: item.queue_file,
|
|
90
|
+
queue_format: item.queue_format,
|
|
91
|
+
processed_goals: item.processed_goals,
|
|
92
|
+
pending_goals: item.pending_goals,
|
|
93
|
+
updated_at: item.updated_at,
|
|
94
|
+
parse_error: item.parse_error
|
|
95
|
+
}))
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
presentCloseLoopSessionList,
|
|
101
|
+
presentCloseLoopSessionStats,
|
|
102
|
+
presentControllerSessionList
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
async function pruneCloseLoopBatchSummarySessions(projectPath, policy = {}, dependencies = {}) {
|
|
2
|
+
const { readCloseLoopBatchSummaryEntries, getCloseLoopBatchSummaryDir, fs, now = () => Date.now() } = dependencies;
|
|
3
|
+
const keep = policy.keep;
|
|
4
|
+
const olderThanDays = policy.olderThanDays;
|
|
5
|
+
const currentFile = policy.currentFile || null;
|
|
6
|
+
const dryRun = Boolean(policy.dryRun);
|
|
7
|
+
const sessions = await readCloseLoopBatchSummaryEntries(projectPath);
|
|
8
|
+
const nowMs = typeof now === 'function' ? Number(now()) : Date.now();
|
|
9
|
+
const cutoffMs = olderThanDays === null ? null : nowMs - (olderThanDays * 24 * 60 * 60 * 1000);
|
|
10
|
+
|
|
11
|
+
const keepLimit = Number.isInteger(keep) ? keep : Number.POSITIVE_INFINITY;
|
|
12
|
+
const deletable = [];
|
|
13
|
+
for (let index = 0; index < sessions.length; index += 1) {
|
|
14
|
+
const session = sessions[index];
|
|
15
|
+
if (session.file === currentFile) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const beyondKeep = Number.isFinite(keepLimit) ? index >= keepLimit : true;
|
|
19
|
+
const beyondAge = cutoffMs === null || session.mtime_ms < cutoffMs;
|
|
20
|
+
if (beyondKeep && beyondAge) {
|
|
21
|
+
deletable.push(session);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const deleted = [];
|
|
26
|
+
const errors = [];
|
|
27
|
+
if (!dryRun) {
|
|
28
|
+
for (const session of deletable) {
|
|
29
|
+
try {
|
|
30
|
+
await fs.remove(session.file);
|
|
31
|
+
deleted.push(session);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
errors.push({ id: session.id, file: session.file, error: error.message });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
enabled: true,
|
|
40
|
+
session_dir: getCloseLoopBatchSummaryDir(projectPath),
|
|
41
|
+
dry_run: dryRun,
|
|
42
|
+
criteria: {
|
|
43
|
+
keep: Number.isFinite(keepLimit) ? keepLimit : null,
|
|
44
|
+
older_than_days: olderThanDays
|
|
45
|
+
},
|
|
46
|
+
total_sessions: sessions.length,
|
|
47
|
+
kept_sessions: sessions.length - deletable.length,
|
|
48
|
+
deleted_count: dryRun ? deletable.length : deleted.length,
|
|
49
|
+
candidates: deletable.map((item) => ({ id: item.id, file: item.file, status: item.status, updated_at: item.updated_at })),
|
|
50
|
+
errors
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function pruneCloseLoopControllerSessions(projectPath, policy = {}, dependencies = {}) {
|
|
55
|
+
const { readCloseLoopControllerSessionEntries, getCloseLoopControllerSessionDir, fs, now = () => Date.now() } = dependencies;
|
|
56
|
+
const keep = policy.keep;
|
|
57
|
+
const olderThanDays = policy.olderThanDays;
|
|
58
|
+
const currentFile = policy.currentFile || null;
|
|
59
|
+
const dryRun = Boolean(policy.dryRun);
|
|
60
|
+
const sessions = await readCloseLoopControllerSessionEntries(projectPath);
|
|
61
|
+
const nowMs = typeof now === 'function' ? Number(now()) : Date.now();
|
|
62
|
+
const cutoffMs = olderThanDays === null ? null : nowMs - (olderThanDays * 24 * 60 * 60 * 1000);
|
|
63
|
+
|
|
64
|
+
const keepLimit = Number.isInteger(keep) ? keep : Number.POSITIVE_INFINITY;
|
|
65
|
+
const deletable = [];
|
|
66
|
+
for (let index = 0; index < sessions.length; index += 1) {
|
|
67
|
+
const session = sessions[index];
|
|
68
|
+
if (session.file === currentFile) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const beyondKeep = Number.isFinite(keepLimit) ? index >= keepLimit : true;
|
|
72
|
+
const beyondAge = cutoffMs === null || session.mtime_ms < cutoffMs;
|
|
73
|
+
if (beyondKeep && beyondAge) {
|
|
74
|
+
deletable.push(session);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const deleted = [];
|
|
79
|
+
const errors = [];
|
|
80
|
+
if (!dryRun) {
|
|
81
|
+
for (const session of deletable) {
|
|
82
|
+
try {
|
|
83
|
+
await fs.remove(session.file);
|
|
84
|
+
deleted.push(session);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
errors.push({ id: session.id, file: session.file, error: error.message });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
enabled: true,
|
|
93
|
+
session_dir: getCloseLoopControllerSessionDir(projectPath),
|
|
94
|
+
dry_run: dryRun,
|
|
95
|
+
criteria: {
|
|
96
|
+
keep: Number.isFinite(keepLimit) ? keepLimit : null,
|
|
97
|
+
older_than_days: olderThanDays
|
|
98
|
+
},
|
|
99
|
+
total_sessions: sessions.length,
|
|
100
|
+
kept_sessions: sessions.length - deletable.length,
|
|
101
|
+
deleted_count: dryRun ? deletable.length : deleted.length,
|
|
102
|
+
candidates: deletable.map((item) => ({ id: item.id, file: item.file, status: item.status, updated_at: item.updated_at })),
|
|
103
|
+
errors
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function pruneCloseLoopSessions(projectPath, options = {}, dependencies = {}) {
|
|
108
|
+
const { normalizeKeep, normalizeOlderThanDays, readCloseLoopSessionEntries, getCloseLoopSessionDir, fs, now = () => Date.now() } = dependencies;
|
|
109
|
+
const keep = normalizeKeep(options.keep);
|
|
110
|
+
const olderThanDays = normalizeOlderThanDays(options.olderThanDays);
|
|
111
|
+
const dryRun = Boolean(options.dryRun);
|
|
112
|
+
const sessions = await readCloseLoopSessionEntries(projectPath);
|
|
113
|
+
const nowMs = typeof now === 'function' ? Number(now()) : Date.now();
|
|
114
|
+
const cutoffMs = olderThanDays === null ? null : nowMs - (olderThanDays * 24 * 60 * 60 * 1000);
|
|
115
|
+
|
|
116
|
+
const keepSet = new Set(sessions.slice(0, keep).map((session) => session.file));
|
|
117
|
+
const deletable = sessions.filter((session) => {
|
|
118
|
+
if (keepSet.has(session.file)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
if (cutoffMs === null) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return session.mtime_ms < cutoffMs;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const deleted = [];
|
|
128
|
+
const errors = [];
|
|
129
|
+
if (!dryRun) {
|
|
130
|
+
for (const session of deletable) {
|
|
131
|
+
try {
|
|
132
|
+
await fs.remove(session.file);
|
|
133
|
+
deleted.push(session);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
errors.push({ id: session.id, file: session.file, error: error.message });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
mode: 'auto-session-prune',
|
|
142
|
+
session_dir: getCloseLoopSessionDir(projectPath),
|
|
143
|
+
dry_run: dryRun,
|
|
144
|
+
criteria: {
|
|
145
|
+
keep,
|
|
146
|
+
older_than_days: olderThanDays
|
|
147
|
+
},
|
|
148
|
+
total_sessions: sessions.length,
|
|
149
|
+
kept_sessions: sessions.length - deletable.length,
|
|
150
|
+
deleted_count: dryRun ? deletable.length : deleted.length,
|
|
151
|
+
candidates: deletable.map((item) => ({ id: item.id, file: item.file, status: item.status, updated_at: item.updated_at })),
|
|
152
|
+
errors
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function pruneCloseLoopBatchSummarySessionsCli(projectPath, options = {}, dependencies = {}) {
|
|
157
|
+
const { normalizeKeep, normalizeOlderThanDays, pruneCloseLoopBatchSummarySessions } = dependencies;
|
|
158
|
+
const keep = normalizeKeep(options.keep);
|
|
159
|
+
const olderThanDays = normalizeOlderThanDays(options.olderThanDays);
|
|
160
|
+
const dryRun = Boolean(options.dryRun);
|
|
161
|
+
const result = await pruneCloseLoopBatchSummarySessions(projectPath, {
|
|
162
|
+
keep,
|
|
163
|
+
olderThanDays,
|
|
164
|
+
currentFile: null,
|
|
165
|
+
dryRun
|
|
166
|
+
});
|
|
167
|
+
return { mode: 'auto-batch-session-prune', ...result };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function pruneCloseLoopControllerSessionsCli(projectPath, options = {}, dependencies = {}) {
|
|
171
|
+
const { normalizeKeep, normalizeOlderThanDays, pruneCloseLoopControllerSessions } = dependencies;
|
|
172
|
+
const keep = normalizeKeep(options.keep);
|
|
173
|
+
const olderThanDays = normalizeOlderThanDays(options.olderThanDays);
|
|
174
|
+
const dryRun = Boolean(options.dryRun);
|
|
175
|
+
const result = await pruneCloseLoopControllerSessions(projectPath, {
|
|
176
|
+
keep,
|
|
177
|
+
olderThanDays,
|
|
178
|
+
currentFile: null,
|
|
179
|
+
dryRun
|
|
180
|
+
});
|
|
181
|
+
return { mode: 'auto-controller-session-prune', ...result };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
pruneCloseLoopBatchSummarySessions,
|
|
186
|
+
pruneCloseLoopControllerSessions,
|
|
187
|
+
pruneCloseLoopSessions,
|
|
188
|
+
pruneCloseLoopBatchSummarySessionsCli,
|
|
189
|
+
pruneCloseLoopControllerSessionsCli
|
|
190
|
+
};
|