metame-cli 1.5.23 → 1.5.24
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/package.json +3 -2
- package/scripts/core/team-session-route.js +164 -0
- package/scripts/daemon-agent-commands.js +31 -42
- package/scripts/daemon-bridges.js +50 -9
- package/scripts/daemon-claude-engine.js +13 -1
- package/scripts/daemon-command-router.js +24 -6
- package/scripts/daemon-command-session-route.js +13 -38
- package/scripts/daemon-reactive-lifecycle.js +6 -6
- package/scripts/daemon-session-commands.js +33 -43
- package/scripts/daemon-session-store.js +5 -2
- package/scripts/daemon-warm-pool.js +65 -14
- package/scripts/ops-mission-queue.js +24 -1
- package/scripts/ops-reactive-bootstrap.js +46 -2
- package/scripts/core/handoff.test.js +0 -1074
- package/scripts/core/memory-model.test.js +0 -486
- package/scripts/core/reactive-paths.test.js +0 -35
- package/scripts/core/reactive-prompt.test.js +0 -88
- package/scripts/core/reactive-signal.test.js +0 -88
- package/scripts/core/thread-chat-id.test.js +0 -113
- package/scripts/sync-readme.js +0 -64
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { normalizeEngineName: _normalizeEngine } = require('./daemon-utils');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
resolveSessionRoute: _resolveSessionRoute,
|
|
6
|
+
resolveResumeRouteForTarget: _resolveResumeRouteForTarget,
|
|
7
|
+
applyResumeRouteState: _applyResumeRouteState,
|
|
8
|
+
} = require('./core/team-session-route');
|
|
5
9
|
|
|
6
10
|
function createSessionCommandHandler(deps) {
|
|
7
11
|
const {
|
|
@@ -49,45 +53,16 @@ function createSessionCommandHandler(deps) {
|
|
|
49
53
|
return available.length === 1 ? normalizeEngineName(available[0]) : getDefaultEngine();
|
|
50
54
|
}
|
|
51
55
|
|
|
52
|
-
function buildBoundSessionChatId(projectKey) {
|
|
53
|
-
const key = String(projectKey || '').trim();
|
|
54
|
-
return key ? `_bound_${key}` : '';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
56
|
function getSessionRoute(chatId) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
: null;
|
|
68
|
-
|
|
69
|
-
if (stickyMember) {
|
|
70
|
-
return {
|
|
71
|
-
sessionChatId: `_agent_${stickyMember.key}`,
|
|
72
|
-
cwd: stickyMember.cwd ? normalizeCwd(stickyMember.cwd) : (boundProj && boundProj.cwd ? normalizeCwd(boundProj.cwd) : null),
|
|
73
|
-
engine: normalizeEngineName(stickyMember.engine || (boundProj && boundProj.engine)),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (boundProj) {
|
|
78
|
-
return {
|
|
79
|
-
sessionChatId: buildBoundSessionChatId(boundKey),
|
|
80
|
-
cwd: boundProj.cwd ? normalizeCwd(boundProj.cwd) : null,
|
|
81
|
-
engine: normalizeEngineName(boundProj.engine),
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const rawSession = getSession(chatId);
|
|
86
|
-
return {
|
|
87
|
-
sessionChatId: String(chatId),
|
|
88
|
-
cwd: rawSession && rawSession.cwd ? normalizeCwd(rawSession.cwd) : null,
|
|
89
|
-
engine: inferStoredEngine(rawSession),
|
|
90
|
-
};
|
|
57
|
+
return _resolveSessionRoute({
|
|
58
|
+
chatId,
|
|
59
|
+
cfg: loadConfig(),
|
|
60
|
+
state: loadState(),
|
|
61
|
+
getSession,
|
|
62
|
+
normalizeCwd,
|
|
63
|
+
normalizeEngineName,
|
|
64
|
+
inferStoredEngine,
|
|
65
|
+
});
|
|
91
66
|
}
|
|
92
67
|
|
|
93
68
|
function getCurrentEngine(chatId) {
|
|
@@ -101,8 +76,8 @@ function createSessionCommandHandler(deps) {
|
|
|
101
76
|
}
|
|
102
77
|
|
|
103
78
|
// Write per-engine session slot, preserving cwd and other engine slots.
|
|
104
|
-
function attachEngineSession(state, chatId, engine, sessionId, cwd, meta = {}) {
|
|
105
|
-
const effectiveId = getSessionRoute(chatId).sessionChatId;
|
|
79
|
+
function attachEngineSession(state, chatId, engine, sessionId, cwd, meta = {}, options = {}) {
|
|
80
|
+
const effectiveId = options.sessionChatId || getSessionRoute(chatId).sessionChatId;
|
|
106
81
|
const existing = state.sessions[effectiveId] || {};
|
|
107
82
|
const existingEngines = existing.engines || {};
|
|
108
83
|
const nextSlot = {
|
|
@@ -134,6 +109,17 @@ function createSessionCommandHandler(deps) {
|
|
|
134
109
|
return null;
|
|
135
110
|
}
|
|
136
111
|
|
|
112
|
+
function resolveResumeRouteForTarget(chatId, targetCwd, state, cfg) {
|
|
113
|
+
return _resolveResumeRouteForTarget({
|
|
114
|
+
chatId,
|
|
115
|
+
targetCwd,
|
|
116
|
+
cfg,
|
|
117
|
+
state,
|
|
118
|
+
normalizeCwd,
|
|
119
|
+
fallbackSessionChatId: getSessionRoute(chatId).sessionChatId,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
137
123
|
function resolveAttachableSession(engine, cwd, options = {}) {
|
|
138
124
|
if (typeof findAttachableSession === 'function') {
|
|
139
125
|
return findAttachableSession({ engine, cwd, ...options });
|
|
@@ -147,6 +133,9 @@ function createSessionCommandHandler(deps) {
|
|
|
147
133
|
|
|
148
134
|
function attachResolvedTarget(state, chatId, engine, target, fallbackCwd) {
|
|
149
135
|
const targetCwd = target && target.projectPath ? target.projectPath : fallbackCwd;
|
|
136
|
+
const cfg = loadConfig();
|
|
137
|
+
const resumeRoute = resolveResumeRouteForTarget(chatId, targetCwd, state, cfg);
|
|
138
|
+
_applyResumeRouteState(state, chatId, resumeRoute);
|
|
150
139
|
if (target && target.pendingState) {
|
|
151
140
|
attachEngineSession(state, chatId, engine, null, targetCwd, {
|
|
152
141
|
started: false,
|
|
@@ -155,7 +144,7 @@ function createSessionCommandHandler(deps) {
|
|
|
155
144
|
...(target.sandboxMode ? { sandboxMode: target.sandboxMode } : {}),
|
|
156
145
|
...(target.approvalPolicy ? { approvalPolicy: target.approvalPolicy } : {}),
|
|
157
146
|
...(target.permissionMode ? { permissionMode: target.permissionMode } : {}),
|
|
158
|
-
});
|
|
147
|
+
}, { sessionChatId: resumeRoute.sessionChatId });
|
|
159
148
|
return {
|
|
160
149
|
cwd: targetCwd,
|
|
161
150
|
pendingState: true,
|
|
@@ -166,7 +155,7 @@ function createSessionCommandHandler(deps) {
|
|
|
166
155
|
started: true,
|
|
167
156
|
runtimeSessionObserved: true,
|
|
168
157
|
clearCompactContext: true,
|
|
169
|
-
});
|
|
158
|
+
}, { sessionChatId: resumeRoute.sessionChatId });
|
|
170
159
|
return {
|
|
171
160
|
cwd: targetCwd,
|
|
172
161
|
pendingState: false,
|
|
@@ -437,6 +426,7 @@ function createSessionCommandHandler(deps) {
|
|
|
437
426
|
const title = target.customTitle || target.summary || target.sessionId.slice(0, 8);
|
|
438
427
|
const lines = [`▶️ Resumed: ${title}`];
|
|
439
428
|
if (attached.cwd) lines.push(`📁 ${path.basename(attached.cwd)}`);
|
|
429
|
+
lines.push(`ID: ${target.sessionId}`);
|
|
440
430
|
if (Array.isArray(recentDialogue) && recentDialogue.length > 0) {
|
|
441
431
|
lines.push('');
|
|
442
432
|
lines.push('最近对话:');
|
|
@@ -960,6 +960,7 @@ function createSessionStore(deps) {
|
|
|
960
960
|
const proj = s.projectPath ? path.basename(s.projectPath) : '~';
|
|
961
961
|
const ago = getSessionRelativeTimeLabel(s);
|
|
962
962
|
const shortId = s.sessionId.slice(0, 8);
|
|
963
|
+
const visibleId = s.sessionId.slice(0, 18);
|
|
963
964
|
const tags = (sessionTags[s.sessionId] && sessionTags[s.sessionId].tags || []).slice(0, 3);
|
|
964
965
|
const engineLabel = (s.engine || 'claude') === 'codex' ? 'codex' : 'claude';
|
|
965
966
|
|
|
@@ -972,6 +973,7 @@ function createSessionStore(deps) {
|
|
|
972
973
|
if (firstSnippet) line += `\n 📝 ${firstSnippet}`;
|
|
973
974
|
if (lastUserSnippet && lastUserSnippet !== firstSnippet) line += `\n 💬 ${lastUserSnippet}`;
|
|
974
975
|
if (lastAiSnippet) line += `\n 🤖 ${lastAiSnippet}`;
|
|
976
|
+
line += `\n ID ${visibleId}`;
|
|
975
977
|
line += `\n /resume ${shortId}`;
|
|
976
978
|
return line;
|
|
977
979
|
}
|
|
@@ -984,11 +986,12 @@ function createSessionStore(deps) {
|
|
|
984
986
|
const title = sessionDisplayTitle(s, 60, sessionTags);
|
|
985
987
|
const proj = s.projectPath ? path.basename(s.projectPath) : '~';
|
|
986
988
|
const ago = getSessionRelativeTimeLabel(s);
|
|
987
|
-
const
|
|
989
|
+
const visibleId = s.sessionId.slice(0, 18);
|
|
988
990
|
const tags = (sessionTags[s.sessionId] && sessionTags[s.sessionId].tags || []).slice(0, 4);
|
|
989
991
|
const engineLabel = (s.engine || 'claude') === 'codex' ? 'codex' : 'claude';
|
|
990
992
|
|
|
991
993
|
let desc = `**${i + 1}. ${title}**\n📁${proj} · ${ago} · ${engineLabel}`;
|
|
994
|
+
desc += `\nID: ${visibleId}`;
|
|
992
995
|
if (tags.length) desc += `\n${tags.map(t => `\`${t}\``).join(' ')}`;
|
|
993
996
|
// Show first prompt, last user message, and last assistant reply
|
|
994
997
|
const firstSnippet = _cleanSnippet(s.firstPrompt, 50);
|
|
@@ -998,7 +1001,7 @@ function createSessionStore(deps) {
|
|
|
998
1001
|
if (lastUserSnippet && lastUserSnippet !== firstSnippet) desc += `\n💬 ${lastUserSnippet}`;
|
|
999
1002
|
if (lastAiSnippet) desc += `\n🤖 ${lastAiSnippet}`;
|
|
1000
1003
|
elements.push({ tag: 'div', text: { tag: 'lark_md', content: desc } });
|
|
1001
|
-
elements.push({ tag: 'action', actions: [{ tag: 'button', text: { tag: 'plain_text', content: `▶️
|
|
1004
|
+
elements.push({ tag: 'action', actions: [{ tag: 'button', text: { tag: 'plain_text', content: `▶️ Resume ${visibleId}` }, type: 'primary', value: { cmd: `/resume ${s.sessionId}` } }] });
|
|
1002
1005
|
});
|
|
1003
1006
|
return elements;
|
|
1004
1007
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* daemon-warm-pool.js
|
|
5
7
|
*
|
|
@@ -20,11 +22,15 @@
|
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
function createWarmPool(deps) {
|
|
23
|
-
const {
|
|
25
|
+
const {
|
|
26
|
+
log,
|
|
27
|
+
idleTimeoutMs = 5 * 60 * 1000,
|
|
28
|
+
hasBackgroundDescendants = defaultHasBackgroundDescendants,
|
|
29
|
+
} = deps;
|
|
24
30
|
|
|
25
31
|
// Pool: sessionKey -> { child, sessionId, cwd, idleTimer }
|
|
26
32
|
const pool = new Map();
|
|
27
|
-
const IDLE_TIMEOUT_MS =
|
|
33
|
+
const IDLE_TIMEOUT_MS = idleTimeoutMs;
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Acquire a warm process for the given session key.
|
|
@@ -67,17 +73,6 @@ function createWarmPool(deps) {
|
|
|
67
73
|
return;
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
// Set idle timeout
|
|
71
|
-
const idleTimer = setTimeout(() => {
|
|
72
|
-
const e = pool.get(sessionKey);
|
|
73
|
-
if (e && e.child === child) {
|
|
74
|
-
log('INFO', `[WarmPool] Idle timeout, killing warm process pid=${child.pid} for ${sessionKey}`);
|
|
75
|
-
_killEntry(e);
|
|
76
|
-
pool.delete(sessionKey);
|
|
77
|
-
}
|
|
78
|
-
}, IDLE_TIMEOUT_MS);
|
|
79
|
-
if (typeof idleTimer.unref === 'function') idleTimer.unref();
|
|
80
|
-
|
|
81
76
|
// Auto-cleanup on unexpected death
|
|
82
77
|
const onExit = () => {
|
|
83
78
|
const e = pool.get(sessionKey);
|
|
@@ -94,8 +89,9 @@ function createWarmPool(deps) {
|
|
|
94
89
|
child,
|
|
95
90
|
sessionId: meta.sessionId || '',
|
|
96
91
|
cwd: meta.cwd || '',
|
|
97
|
-
idleTimer,
|
|
92
|
+
idleTimer: null,
|
|
98
93
|
});
|
|
94
|
+
_armIdleTimer(sessionKey, child);
|
|
99
95
|
log('INFO', `[WarmPool] Stored warm process pid=${child.pid} for ${sessionKey} (pool size: ${pool.size})`);
|
|
100
96
|
}
|
|
101
97
|
|
|
@@ -137,6 +133,34 @@ function createWarmPool(deps) {
|
|
|
137
133
|
pool.delete(sessionKey);
|
|
138
134
|
}
|
|
139
135
|
|
|
136
|
+
function _armIdleTimer(sessionKey, child) {
|
|
137
|
+
const entry = pool.get(sessionKey);
|
|
138
|
+
if (!entry || entry.child !== child) return;
|
|
139
|
+
if (entry.idleTimer) clearTimeout(entry.idleTimer);
|
|
140
|
+
entry.idleTimer = setTimeout(() => {
|
|
141
|
+
const current = pool.get(sessionKey);
|
|
142
|
+
if (!current || current.child !== child) return;
|
|
143
|
+
if (_hasLiveBackgroundDescendants(child.pid)) {
|
|
144
|
+
log('INFO', `[WarmPool] Idle timeout skipped for ${sessionKey}: pid=${child.pid} still has background descendants`);
|
|
145
|
+
_armIdleTimer(sessionKey, child);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
log('INFO', `[WarmPool] Idle timeout, killing warm process pid=${child.pid} for ${sessionKey}`);
|
|
149
|
+
_killEntry(current);
|
|
150
|
+
pool.delete(sessionKey);
|
|
151
|
+
}, IDLE_TIMEOUT_MS);
|
|
152
|
+
if (typeof entry.idleTimer.unref === 'function') entry.idleTimer.unref();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function _hasLiveBackgroundDescendants(pid) {
|
|
156
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
157
|
+
try {
|
|
158
|
+
return !!hasBackgroundDescendants(pid);
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
140
164
|
/**
|
|
141
165
|
* Build the stream-json user message for stdin.
|
|
142
166
|
*/
|
|
@@ -159,4 +183,31 @@ function createWarmPool(deps) {
|
|
|
159
183
|
};
|
|
160
184
|
}
|
|
161
185
|
|
|
186
|
+
function defaultHasBackgroundDescendants(pid) {
|
|
187
|
+
if (!Number.isFinite(pid) || pid <= 0) return false;
|
|
188
|
+
|
|
189
|
+
if (process.platform === 'win32') {
|
|
190
|
+
try {
|
|
191
|
+
const output = execSync(
|
|
192
|
+
`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ParentProcessId=${pid}\\").Count"`,
|
|
193
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], timeout: 3000, windowsHide: true }
|
|
194
|
+
).trim();
|
|
195
|
+
return Number(output) > 0;
|
|
196
|
+
} catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
const output = execSync(`pgrep -P ${pid}`, {
|
|
203
|
+
encoding: 'utf8',
|
|
204
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
205
|
+
timeout: 3000,
|
|
206
|
+
}).trim();
|
|
207
|
+
return output.length > 0;
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
162
213
|
module.exports = { createWarmPool };
|
|
@@ -17,6 +17,7 @@ const MISSIONS_FILE = 'workspace/missions.md';
|
|
|
17
17
|
const SECTIONS = ['pending', 'active', 'completed', 'abandoned'];
|
|
18
18
|
const RECENT_LOG_LINES = 500;
|
|
19
19
|
const ERROR_THRESHOLD = 3;
|
|
20
|
+
const BOOTSTRAP_MISSION_ID = 'bootstrap-001';
|
|
20
21
|
|
|
21
22
|
function getMetameDir() {
|
|
22
23
|
return process.env.METAME_DIR || path.join(os.homedir(), '.metame');
|
|
@@ -211,6 +212,20 @@ function completeMission(cwd, id) {
|
|
|
211
212
|
return { success: true, topic: { id: mission.id, title: mission.title, status: 'completed' } };
|
|
212
213
|
}
|
|
213
214
|
|
|
215
|
+
function completeBootstrapMission(cwd) {
|
|
216
|
+
const sections = readSections(cwd);
|
|
217
|
+
const found = findMission(sections, BOOTSTRAP_MISSION_ID);
|
|
218
|
+
if (!found || found.section !== 'active') {
|
|
219
|
+
return { success: false, completed: false, reason: found ? `bootstrap_${found.section}` : 'bootstrap_missing' };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const mission = sections.active.splice(found.index, 1)[0];
|
|
223
|
+
mission.status = 'completed';
|
|
224
|
+
sections.completed.push(mission);
|
|
225
|
+
writeSections(cwd, sections);
|
|
226
|
+
return { success: true, completed: true, topic: { id: mission.id, title: mission.title, status: 'completed' } };
|
|
227
|
+
}
|
|
228
|
+
|
|
214
229
|
function listMissions(cwd) {
|
|
215
230
|
const sections = readSections(cwd);
|
|
216
231
|
const topics = [];
|
|
@@ -320,4 +335,12 @@ if (require.main === module) {
|
|
|
320
335
|
process.stdout.write(JSON.stringify(result) + '\n');
|
|
321
336
|
}
|
|
322
337
|
|
|
323
|
-
module.exports = {
|
|
338
|
+
module.exports = {
|
|
339
|
+
nextMission,
|
|
340
|
+
activateMission,
|
|
341
|
+
completeMission,
|
|
342
|
+
completeBootstrapMission,
|
|
343
|
+
listMissions,
|
|
344
|
+
pruneObsoleteMissions,
|
|
345
|
+
scanLogs,
|
|
346
|
+
};
|
|
@@ -6,7 +6,7 @@ const path = require('path');
|
|
|
6
6
|
const { execFileSync } = require('child_process');
|
|
7
7
|
|
|
8
8
|
const yaml = require('./resolve-yaml');
|
|
9
|
-
const { pruneObsoleteMissions, scanLogs } = require('./ops-mission-queue');
|
|
9
|
+
const { pruneObsoleteMissions, scanLogs, completeBootstrapMission } = require('./ops-mission-queue');
|
|
10
10
|
const { bootstrapReactiveProject } = require('./daemon-reactive-lifecycle');
|
|
11
11
|
|
|
12
12
|
const HOME = os.homedir();
|
|
@@ -52,6 +52,40 @@ function dispatchReactiveItem(item) {
|
|
|
52
52
|
return { success: true };
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function buildScanSummary(pruned, scanned, bootstrapCompleted, bootstrap) {
|
|
56
|
+
const findings = [];
|
|
57
|
+
|
|
58
|
+
if ((pruned?.pruned || 0) > 0) {
|
|
59
|
+
findings.push(`pruned ${pruned.pruned} obsolete mission${pruned.pruned === 1 ? '' : 's'}`);
|
|
60
|
+
}
|
|
61
|
+
if ((scanned?.new_missions || 0) > 0) {
|
|
62
|
+
findings.push(`detected ${scanned.new_missions} new repair mission${scanned.new_missions === 1 ? '' : 's'}`);
|
|
63
|
+
}
|
|
64
|
+
if (bootstrapCompleted?.completed) {
|
|
65
|
+
findings.push('completed legacy bootstrap-001');
|
|
66
|
+
}
|
|
67
|
+
if (bootstrap?.started) {
|
|
68
|
+
const missionLabel = [bootstrap.missionId, bootstrap.mission].filter(Boolean).join(' ');
|
|
69
|
+
findings.push(`started repair ${missionLabel}`.trim());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const quiet = findings.length === 0;
|
|
73
|
+
const action = bootstrap?.started
|
|
74
|
+
? 'repair_started'
|
|
75
|
+
: quiet
|
|
76
|
+
? 'quiet_scan'
|
|
77
|
+
: 'findings_only';
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
quiet,
|
|
81
|
+
action,
|
|
82
|
+
findings,
|
|
83
|
+
summary: quiet
|
|
84
|
+
? 'ops-scan completed: no new recurring issues, no repair started'
|
|
85
|
+
: `ops-scan completed: ${findings.join('; ')}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
55
89
|
function main() {
|
|
56
90
|
const config = loadConfig();
|
|
57
91
|
const project = config?.projects?.[PROJECT_KEY];
|
|
@@ -63,7 +97,7 @@ function main() {
|
|
|
63
97
|
const cwd = project.cwd.replace(/^~/, HOME);
|
|
64
98
|
const pruned = pruneObsoleteMissions(cwd);
|
|
65
99
|
const scanned = scanLogs(cwd);
|
|
66
|
-
|
|
100
|
+
const bootstrapCompleted = completeBootstrapMission(cwd);
|
|
67
101
|
const result = bootstrapReactiveProject(PROJECT_KEY, config, {
|
|
68
102
|
metameDir: METAME_DIR,
|
|
69
103
|
loadState,
|
|
@@ -73,14 +107,24 @@ function main() {
|
|
|
73
107
|
log: () => {},
|
|
74
108
|
notifyUser: () => {},
|
|
75
109
|
});
|
|
110
|
+
const summary = buildScanSummary(pruned, scanned, bootstrapCompleted, result);
|
|
76
111
|
|
|
77
112
|
process.stdout.write(JSON.stringify({
|
|
78
113
|
success: true,
|
|
79
114
|
pruned: pruned.pruned || 0,
|
|
80
115
|
new_missions: scanned.new_missions || 0,
|
|
81
116
|
total_pending: scanned.total_pending || 0,
|
|
117
|
+
bootstrap_completed: !!bootstrapCompleted.completed,
|
|
118
|
+
quiet: summary.quiet,
|
|
119
|
+
action: summary.action,
|
|
120
|
+
findings: summary.findings,
|
|
121
|
+
summary: summary.summary,
|
|
82
122
|
bootstrap: result,
|
|
83
123
|
}) + '\n');
|
|
84
124
|
}
|
|
85
125
|
|
|
86
126
|
if (require.main === module) main();
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
buildScanSummary,
|
|
130
|
+
};
|