@arka-labs/nemesis 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +668 -0
- package/lib/core/agent-launcher.js +193 -0
- package/lib/core/audit.js +210 -0
- package/lib/core/connexions.js +80 -0
- package/lib/core/flowmap/api.js +111 -0
- package/lib/core/flowmap/cli-helpers.js +80 -0
- package/lib/core/flowmap/machine.js +281 -0
- package/lib/core/flowmap/persistence.js +83 -0
- package/lib/core/generators.js +183 -0
- package/lib/core/inbox.js +275 -0
- package/lib/core/logger.js +20 -0
- package/lib/core/mission.js +109 -0
- package/lib/core/notewriter/config.js +36 -0
- package/lib/core/notewriter/cr.js +237 -0
- package/lib/core/notewriter/log.js +112 -0
- package/lib/core/notewriter/notes.js +168 -0
- package/lib/core/notewriter/paths.js +45 -0
- package/lib/core/notewriter/reader.js +121 -0
- package/lib/core/notewriter/registry.js +80 -0
- package/lib/core/odm.js +191 -0
- package/lib/core/profile-picker.js +323 -0
- package/lib/core/project.js +287 -0
- package/lib/core/registry.js +129 -0
- package/lib/core/secrets.js +137 -0
- package/lib/core/services.js +45 -0
- package/lib/core/team.js +287 -0
- package/lib/core/templates.js +80 -0
- package/lib/kairos/agent-runner.js +261 -0
- package/lib/kairos/claude-invoker.js +90 -0
- package/lib/kairos/context-injector.js +331 -0
- package/lib/kairos/context-loader.js +108 -0
- package/lib/kairos/context-writer.js +45 -0
- package/lib/kairos/dispatcher-router.js +173 -0
- package/lib/kairos/dispatcher.js +139 -0
- package/lib/kairos/event-bus.js +287 -0
- package/lib/kairos/event-router.js +131 -0
- package/lib/kairos/flowmap-bridge.js +120 -0
- package/lib/kairos/hook-handlers.js +351 -0
- package/lib/kairos/hook-installer.js +207 -0
- package/lib/kairos/hook-prompts.js +54 -0
- package/lib/kairos/leader-rules.js +94 -0
- package/lib/kairos/pid-checker.js +108 -0
- package/lib/kairos/situation-detector.js +123 -0
- package/lib/sync/fallback-engine.js +97 -0
- package/lib/sync/hcm-client.js +170 -0
- package/lib/sync/health.js +47 -0
- package/lib/sync/llm-client.js +387 -0
- package/lib/sync/nemesis-client.js +379 -0
- package/lib/sync/service-session.js +74 -0
- package/lib/sync/sync-engine.js +178 -0
- package/lib/ui/box.js +104 -0
- package/lib/ui/brand.js +42 -0
- package/lib/ui/colors.js +57 -0
- package/lib/ui/dashboard.js +580 -0
- package/lib/ui/error-hints.js +49 -0
- package/lib/ui/format.js +61 -0
- package/lib/ui/menu.js +306 -0
- package/lib/ui/note-card.js +198 -0
- package/lib/ui/note-colors.js +26 -0
- package/lib/ui/note-detail.js +297 -0
- package/lib/ui/note-filters.js +252 -0
- package/lib/ui/note-views.js +283 -0
- package/lib/ui/prompt.js +81 -0
- package/lib/ui/spinner.js +139 -0
- package/lib/ui/streambox.js +46 -0
- package/lib/ui/table.js +42 -0
- package/lib/ui/tree.js +33 -0
- package/package.json +53 -0
- package/src/cli.js +457 -0
- package/src/commands/_helpers.js +119 -0
- package/src/commands/audit.js +187 -0
- package/src/commands/auth.js +316 -0
- package/src/commands/doctor.js +243 -0
- package/src/commands/hcm.js +147 -0
- package/src/commands/inbox.js +333 -0
- package/src/commands/init.js +160 -0
- package/src/commands/kairos.js +216 -0
- package/src/commands/kars.js +134 -0
- package/src/commands/mission.js +275 -0
- package/src/commands/notes.js +316 -0
- package/src/commands/notewriter.js +296 -0
- package/src/commands/odm.js +329 -0
- package/src/commands/orch.js +68 -0
- package/src/commands/project.js +123 -0
- package/src/commands/run.js +123 -0
- package/src/commands/services.js +705 -0
- package/src/commands/status.js +231 -0
- package/src/commands/team.js +572 -0
- package/src/config.js +84 -0
- package/src/index.js +5 -0
- package/templates/project-context.json +10 -0
- package/templates/template_CONTRIB-NAME.json +22 -0
- package/templates/template_CR-ODM-NAME-000.exemple.json +32 -0
- package/templates/template_DEC-NAME-000.json +18 -0
- package/templates/template_INTV-NAME-000.json +15 -0
- package/templates/template_MISSION_CONTRACT.json +46 -0
- package/templates/template_ODM-NAME-000.json +89 -0
- package/templates/template_REGISTRY-PROJECT.json +26 -0
- package/templates/template_TXN-NAME-000.json +24 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { setup, assign } from 'xstate';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* STEP_METADATA — lookup table derived from FLOWMAP.json
|
|
5
|
+
* Maps each XState state key to its FLOWMAP metadata.
|
|
6
|
+
*/
|
|
7
|
+
export const STEP_METADATA = {
|
|
8
|
+
F00: { step_id: 'F00', phase: 'CADRAGE', who: 'Leader + PM', label: 'Initialisation — Mission Contract à rédiger' },
|
|
9
|
+
F01: { step_id: 'F01', phase: 'CADRAGE', who: 'AGP ou PM', label: 'Mission Contract en attente de validation' },
|
|
10
|
+
F02: { step_id: 'F02', phase: 'CADRAGE', who: 'Leader', label: 'Mission Contract validé — OdM à rédiger' },
|
|
11
|
+
F03: { step_id: 'F03', phase: 'DISPATCH', who: 'Leader', label: 'OdM validé par PM — à dispatcher' },
|
|
12
|
+
F04: { step_id: 'F04', phase: 'EXECUTION', who: 'Expert', label: 'Expert reçoit l\'OdM — prise en charge' },
|
|
13
|
+
F05: { step_id: 'F05', phase: 'EXECUTION', who: 'Expert', label: 'Expert en cours d\'implémentation' },
|
|
14
|
+
F06: { step_id: 'F06', phase: 'EXECUTION', who: 'Expert', label: 'Expert termine — livraison au Leader' },
|
|
15
|
+
F07: { step_id: 'F07', phase: 'CONTROLE_EVIDENCE', who: 'Leader', label: 'Leader reçoit la livraison Expert — contrôle administratif' },
|
|
16
|
+
F08: { step_id: 'F08', phase: 'QA', who: 'QA', label: 'QA reçoit l\'OdM — exécution SALVA' },
|
|
17
|
+
F09: { step_id: 'F09', phase: 'QA', who: 'Leader', label: 'Lecture du verdict QA' },
|
|
18
|
+
F10: { step_id: 'F10', phase: 'VALIDATION_FINALE', who: 'Leader', label: 'Leader prépare la clôture — artefacts finaux' },
|
|
19
|
+
F10_reserve: { step_id: 'F10_reserve', phase: 'VALIDATION_FINALE', who: 'Leader + AGP', label: 'Réserve active — qualification AGP ou escalade PM' },
|
|
20
|
+
F11: { step_id: 'F11', phase: 'VALIDATION_FINALE', who: 'PM', label: 'Livraison au PM — mission close' },
|
|
21
|
+
FX_ESCALADE: { step_id: 'FX-ESCALADE', phase: 'EXCEPTION', who: 'Leader', label: 'Escalade PM — cas non couvert ou blocage' },
|
|
22
|
+
FX_RENVOI: { step_id: 'FX-RENVOI', phase: 'EXCEPTION', who: 'Leader', label: 'OdM renvoyé à Expert pour correction' },
|
|
23
|
+
FX_AMENDMENT: { step_id: 'FX-AMENDMENT', phase: 'EXCEPTION', who: 'Leader', label: 'OdM gelé — amendement requis' },
|
|
24
|
+
FIN: { step_id: 'FIN', phase: 'TERMINEE', who: '-', label: 'Mission terminée' },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* addHistoryEntry — assign action that appends a transition to context.history
|
|
29
|
+
*/
|
|
30
|
+
const addHistoryEntry = assign({
|
|
31
|
+
history: ({ context, event }) => [
|
|
32
|
+
...context.history,
|
|
33
|
+
{
|
|
34
|
+
from: context.currentStepId,
|
|
35
|
+
to: null, // filled by entry action of target state
|
|
36
|
+
event: event.type,
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
actor: event.actor || null,
|
|
39
|
+
note: event.note || null,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
updatedAt: () => new Date().toISOString(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* makeEntryAction — creates an assign action that updates currentStepId/phase/who
|
|
47
|
+
* and patches the last history entry with the correct "to" value.
|
|
48
|
+
*/
|
|
49
|
+
function makeEntryAction(stateKey) {
|
|
50
|
+
const meta = STEP_METADATA[stateKey];
|
|
51
|
+
return assign({
|
|
52
|
+
currentStepId: meta.step_id,
|
|
53
|
+
phase: meta.phase,
|
|
54
|
+
who: meta.who,
|
|
55
|
+
history: ({ context }) => {
|
|
56
|
+
if (context.history.length === 0) return context.history;
|
|
57
|
+
const updated = [...context.history];
|
|
58
|
+
const last = { ...updated[updated.length - 1], to: meta.step_id };
|
|
59
|
+
updated[updated.length - 1] = last;
|
|
60
|
+
return updated;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* ESCALATE event — spread into every non-final state.
|
|
67
|
+
*/
|
|
68
|
+
const ESCALATE_EVENT = {
|
|
69
|
+
ESCALATE: {
|
|
70
|
+
target: 'FX_ESCALADE',
|
|
71
|
+
actions: addHistoryEntry,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* REQUEST_AMENDMENT event — spread into states where OdM is frozen (post-dispatch F04+).
|
|
77
|
+
* Per FLOWMAP: "L'OdM est gelé mais un changement est nécessaire."
|
|
78
|
+
*/
|
|
79
|
+
const AMENDMENT_EVENT = {
|
|
80
|
+
REQUEST_AMENDMENT: {
|
|
81
|
+
target: 'FX_AMENDMENT',
|
|
82
|
+
actions: addHistoryEntry,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* flowmapMachine — XState v5 state machine for the Nemesis FLOWMAP workflow.
|
|
88
|
+
*/
|
|
89
|
+
export const flowmapMachine = setup({
|
|
90
|
+
guards: {
|
|
91
|
+
isControlOk: ({ event }) => event.result === 'OK',
|
|
92
|
+
isControlFail: ({ event }) => event.result === 'RENVOI',
|
|
93
|
+
isVerdictPass: ({ event }) => event.verdict === 'PASS',
|
|
94
|
+
isVerdictFail: ({ event }) => event.verdict === 'FAIL',
|
|
95
|
+
isVerdictReserve: ({ event }) => event.verdict === 'RESERVE',
|
|
96
|
+
isVerdictNotObservable: ({ event }) => event.verdict === 'NOT_OBSERVABLE',
|
|
97
|
+
isReserveNoImpact: ({ event }) => event.impact === 'aucun',
|
|
98
|
+
isReserveBlocking: ({ event }) => event.impact !== 'aucun',
|
|
99
|
+
isPmTargetF00: ({ event }) => event.targetStep === 'F00',
|
|
100
|
+
isPmTargetF02: ({ event }) => event.targetStep === 'F02',
|
|
101
|
+
isPmTargetF05: ({ event }) => event.targetStep === 'F05',
|
|
102
|
+
isPmTargetF10: ({ event }) => event.targetStep === 'F10',
|
|
103
|
+
},
|
|
104
|
+
actions: {
|
|
105
|
+
addHistoryEntry,
|
|
106
|
+
entryF00: makeEntryAction('F00'),
|
|
107
|
+
entryF01: makeEntryAction('F01'),
|
|
108
|
+
entryF02: makeEntryAction('F02'),
|
|
109
|
+
entryF03: makeEntryAction('F03'),
|
|
110
|
+
entryF04: makeEntryAction('F04'),
|
|
111
|
+
entryF05: makeEntryAction('F05'),
|
|
112
|
+
entryF06: makeEntryAction('F06'),
|
|
113
|
+
entryF07: makeEntryAction('F07'),
|
|
114
|
+
entryF08: makeEntryAction('F08'),
|
|
115
|
+
entryF09: makeEntryAction('F09'),
|
|
116
|
+
entryF10: makeEntryAction('F10'),
|
|
117
|
+
entryF10_reserve: makeEntryAction('F10_reserve'),
|
|
118
|
+
entryF11: makeEntryAction('F11'),
|
|
119
|
+
entryFX_ESCALADE: makeEntryAction('FX_ESCALADE'),
|
|
120
|
+
entryFX_RENVOI: makeEntryAction('FX_RENVOI'),
|
|
121
|
+
entryFX_AMENDMENT: makeEntryAction('FX_AMENDMENT'),
|
|
122
|
+
entryFIN: makeEntryAction('FIN'),
|
|
123
|
+
},
|
|
124
|
+
}).createMachine({
|
|
125
|
+
id: 'flowmap',
|
|
126
|
+
initial: 'F00',
|
|
127
|
+
context: ({ input }) => ({
|
|
128
|
+
missionId: input?.missionId || null,
|
|
129
|
+
odmId: input?.odmId || null,
|
|
130
|
+
projectId: input?.projectId || null,
|
|
131
|
+
currentStepId: 'F00',
|
|
132
|
+
phase: 'CADRAGE',
|
|
133
|
+
who: 'Leader + PM',
|
|
134
|
+
history: [],
|
|
135
|
+
createdAt: new Date().toISOString(),
|
|
136
|
+
updatedAt: new Date().toISOString(),
|
|
137
|
+
metadata: input?.metadata || {},
|
|
138
|
+
}),
|
|
139
|
+
states: {
|
|
140
|
+
F00: {
|
|
141
|
+
entry: 'entryF00',
|
|
142
|
+
on: {
|
|
143
|
+
SUBMIT_MC: { target: 'F01', actions: 'addHistoryEntry' },
|
|
144
|
+
...ESCALATE_EVENT,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
F01: {
|
|
148
|
+
entry: 'entryF01',
|
|
149
|
+
on: {
|
|
150
|
+
VALIDATE_MC: { target: 'F02', actions: 'addHistoryEntry' },
|
|
151
|
+
REJECT_MC: { target: 'F00', actions: 'addHistoryEntry' },
|
|
152
|
+
...ESCALATE_EVENT,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
F02: {
|
|
156
|
+
entry: 'entryF02',
|
|
157
|
+
on: {
|
|
158
|
+
SUBMIT_ODM: { target: 'F03', actions: 'addHistoryEntry' },
|
|
159
|
+
...ESCALATE_EVENT,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
F03: {
|
|
163
|
+
entry: 'entryF03',
|
|
164
|
+
on: {
|
|
165
|
+
DISPATCH_ODM: { target: 'F04', actions: 'addHistoryEntry' },
|
|
166
|
+
...ESCALATE_EVENT,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
F04: {
|
|
170
|
+
entry: 'entryF04',
|
|
171
|
+
on: {
|
|
172
|
+
ACKNOWLEDGE_ODM: { target: 'F05', actions: 'addHistoryEntry' },
|
|
173
|
+
...ESCALATE_EVENT,
|
|
174
|
+
...AMENDMENT_EVENT,
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
F05: {
|
|
178
|
+
entry: 'entryF05',
|
|
179
|
+
on: {
|
|
180
|
+
COMPLETE_IMPLEMENTATION: { target: 'F06', actions: 'addHistoryEntry' },
|
|
181
|
+
...ESCALATE_EVENT,
|
|
182
|
+
...AMENDMENT_EVENT,
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
F06: {
|
|
186
|
+
entry: 'entryF06',
|
|
187
|
+
on: {
|
|
188
|
+
DELIVER_ODM: { target: 'F07', actions: 'addHistoryEntry' },
|
|
189
|
+
...ESCALATE_EVENT,
|
|
190
|
+
...AMENDMENT_EVENT,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
F07: {
|
|
194
|
+
entry: 'entryF07',
|
|
195
|
+
on: {
|
|
196
|
+
CONTROL_VERDICT: [
|
|
197
|
+
{ guard: 'isControlOk', target: 'F08', actions: 'addHistoryEntry' },
|
|
198
|
+
{ guard: 'isControlFail', target: 'FX_RENVOI', actions: 'addHistoryEntry' },
|
|
199
|
+
],
|
|
200
|
+
...ESCALATE_EVENT,
|
|
201
|
+
...AMENDMENT_EVENT,
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
F08: {
|
|
205
|
+
entry: 'entryF08',
|
|
206
|
+
on: {
|
|
207
|
+
QA_COMPLETE: { target: 'F09', actions: 'addHistoryEntry' },
|
|
208
|
+
...ESCALATE_EVENT,
|
|
209
|
+
...AMENDMENT_EVENT,
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
F09: {
|
|
213
|
+
entry: 'entryF09',
|
|
214
|
+
on: {
|
|
215
|
+
QA_VERDICT: [
|
|
216
|
+
{ guard: 'isVerdictPass', target: 'F10', actions: 'addHistoryEntry' },
|
|
217
|
+
{ guard: 'isVerdictFail', target: 'FX_RENVOI', actions: 'addHistoryEntry' },
|
|
218
|
+
{ guard: 'isVerdictReserve', target: 'F10_reserve', actions: 'addHistoryEntry' },
|
|
219
|
+
{ guard: 'isVerdictNotObservable', target: 'F10', actions: 'addHistoryEntry' },
|
|
220
|
+
],
|
|
221
|
+
...ESCALATE_EVENT,
|
|
222
|
+
...AMENDMENT_EVENT,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
F10: {
|
|
226
|
+
entry: 'entryF10',
|
|
227
|
+
on: {
|
|
228
|
+
PREPARE_CLOSING: { target: 'F11', actions: 'addHistoryEntry' },
|
|
229
|
+
...ESCALATE_EVENT,
|
|
230
|
+
...AMENDMENT_EVENT,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
F10_reserve: {
|
|
234
|
+
entry: 'entryF10_reserve',
|
|
235
|
+
on: {
|
|
236
|
+
RESERVE_VERDICT: [
|
|
237
|
+
{ guard: 'isReserveNoImpact', target: 'F10', actions: 'addHistoryEntry' },
|
|
238
|
+
{ guard: 'isReserveBlocking', target: 'FX_ESCALADE', actions: 'addHistoryEntry' },
|
|
239
|
+
],
|
|
240
|
+
...ESCALATE_EVENT,
|
|
241
|
+
...AMENDMENT_EVENT,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
F11: {
|
|
245
|
+
entry: 'entryF11',
|
|
246
|
+
on: {
|
|
247
|
+
CLOSE_MISSION: { target: 'FIN', actions: 'addHistoryEntry' },
|
|
248
|
+
...ESCALATE_EVENT,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
FX_ESCALADE: {
|
|
252
|
+
entry: 'entryFX_ESCALADE',
|
|
253
|
+
on: {
|
|
254
|
+
PM_DECISION: [
|
|
255
|
+
{ guard: 'isPmTargetF00', target: 'F00', actions: 'addHistoryEntry' },
|
|
256
|
+
{ guard: 'isPmTargetF02', target: 'F02', actions: 'addHistoryEntry' },
|
|
257
|
+
{ guard: 'isPmTargetF05', target: 'F05', actions: 'addHistoryEntry' },
|
|
258
|
+
{ guard: 'isPmTargetF10', target: 'F10', actions: 'addHistoryEntry' },
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
FX_RENVOI: {
|
|
263
|
+
entry: 'entryFX_RENVOI',
|
|
264
|
+
on: {
|
|
265
|
+
RENVOI_TO_EXPERT: { target: 'F05', actions: 'addHistoryEntry' },
|
|
266
|
+
...ESCALATE_EVENT,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
FX_AMENDMENT: {
|
|
270
|
+
entry: 'entryFX_AMENDMENT',
|
|
271
|
+
on: {
|
|
272
|
+
CREATE_AMENDMENT: { target: 'F02', actions: 'addHistoryEntry' },
|
|
273
|
+
...ESCALATE_EVENT,
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
FIN: {
|
|
277
|
+
entry: 'entryFIN',
|
|
278
|
+
type: 'final',
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const STATE_DIR = 'state';
|
|
5
|
+
const SCHEMA = 'nemesis.flowmap_snapshot.v1';
|
|
6
|
+
|
|
7
|
+
function stateDir(root) {
|
|
8
|
+
return join(root, '.nemesis', STATE_DIR);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function snapshotPath(root, missionId) {
|
|
12
|
+
return join(stateDir(root), `${missionId}.json`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ensure .nemesis/state/ exists.
|
|
17
|
+
*/
|
|
18
|
+
export function ensureStateDir(root) {
|
|
19
|
+
const dir = stateDir(root);
|
|
20
|
+
if (!existsSync(dir)) {
|
|
21
|
+
mkdirSync(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
return dir;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Save an XState snapshot to disk.
|
|
28
|
+
*/
|
|
29
|
+
export function saveSnapshot(root, missionId, snapshot) {
|
|
30
|
+
ensureStateDir(root);
|
|
31
|
+
const data = {
|
|
32
|
+
_schema: SCHEMA,
|
|
33
|
+
mission_id: missionId,
|
|
34
|
+
saved_at: new Date().toISOString(),
|
|
35
|
+
snapshot,
|
|
36
|
+
};
|
|
37
|
+
writeFileSync(snapshotPath(root, missionId), JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Load an XState snapshot from disk. Returns null if absent or corrupted.
|
|
42
|
+
*/
|
|
43
|
+
export function loadSnapshot(root, missionId) {
|
|
44
|
+
const filepath = snapshotPath(root, missionId);
|
|
45
|
+
if (!existsSync(filepath)) return null;
|
|
46
|
+
try {
|
|
47
|
+
const raw = readFileSync(filepath, 'utf-8');
|
|
48
|
+
const data = JSON.parse(raw);
|
|
49
|
+
if (!data.snapshot) return null;
|
|
50
|
+
return data.snapshot;
|
|
51
|
+
} catch (_e) {
|
|
52
|
+
/* fallback: corrupted snapshot */
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if a snapshot exists for the given mission.
|
|
59
|
+
*/
|
|
60
|
+
export function hasSnapshot(root, missionId) {
|
|
61
|
+
return existsSync(snapshotPath(root, missionId));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* List all mission IDs that have snapshots.
|
|
66
|
+
*/
|
|
67
|
+
export function listSnapshots(root) {
|
|
68
|
+
const dir = stateDir(root);
|
|
69
|
+
if (!existsSync(dir)) return [];
|
|
70
|
+
return readdirSync(dir)
|
|
71
|
+
.filter(f => f.endsWith('.json'))
|
|
72
|
+
.map(f => f.replace('.json', ''));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Delete a snapshot for the given mission.
|
|
77
|
+
*/
|
|
78
|
+
export function deleteSnapshot(root, missionId) {
|
|
79
|
+
const filepath = snapshotPath(root, missionId);
|
|
80
|
+
if (existsSync(filepath)) {
|
|
81
|
+
unlinkSync(filepath);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate memory.md content for a new agent.
|
|
5
|
+
*/
|
|
6
|
+
export function generateMemory(agentName, role, project) {
|
|
7
|
+
return `> Session name: ${agentName}
|
|
8
|
+
> Agent ID: ${agentName.replace('Agent_', '')}
|
|
9
|
+
|
|
10
|
+
# Memory — ${agentName}
|
|
11
|
+
|
|
12
|
+
> Memoire persistante de l'agent ${role.toLowerCase()}.
|
|
13
|
+
> Mise a jour reguliere au fil des sessions.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Profil
|
|
18
|
+
|
|
19
|
+
- **Agent ID** : \`${agentName.replace('Agent_', '')}\`
|
|
20
|
+
- **Role** : ${role}
|
|
21
|
+
- **Mission** : A definir
|
|
22
|
+
- **Mode** : ${role}
|
|
23
|
+
- **Langue** : Francais pour les echanges, anglais pour le code
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Onboarding
|
|
28
|
+
|
|
29
|
+
- **Date** : ${new Date().toISOString().split('T')[0]}
|
|
30
|
+
- **Contexte** : Projet ${project.name || project.id}
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Regles specifiques
|
|
35
|
+
|
|
36
|
+
- A definir par le PM
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Etat du projet
|
|
41
|
+
|
|
42
|
+
_(a remplir au fil des sessions)_
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Sessions
|
|
47
|
+
|
|
48
|
+
_(vide — premiere session)_
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate onboarding prompt for a new agent.
|
|
54
|
+
* Pure JSON — blocking checklist, agent validates or reports NON READY.
|
|
55
|
+
*/
|
|
56
|
+
export function generateOnboardingPrompt(opts) {
|
|
57
|
+
const { agentName, role, profileId, reportsTo, project, odmIds, leader } = opts;
|
|
58
|
+
|
|
59
|
+
const steps = [
|
|
60
|
+
{
|
|
61
|
+
id: 1,
|
|
62
|
+
action: 'Lire ta memoire',
|
|
63
|
+
blocked: true,
|
|
64
|
+
instruction: `Lis le fichier .nemesis/claude/${agentName}/memory.md`,
|
|
65
|
+
validation: 'Cite ton Agent ID et ton role depuis le fichier',
|
|
66
|
+
on_fail: 'Status: NON READY — Cause: memoire introuvable ou illisible'
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 2,
|
|
70
|
+
action: 'Verifier acces HCM',
|
|
71
|
+
blocked: true,
|
|
72
|
+
instruction: 'Appelle hcm_status pour verifier que le HCM est accessible',
|
|
73
|
+
validation: 'Le HCM repond avec un status ok',
|
|
74
|
+
on_fail: 'Status: NON READY — Cause: HCM non disponible (serveur MCP HCM absent ou injoignable)'
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
if (profileId) {
|
|
79
|
+
steps.push(
|
|
80
|
+
{
|
|
81
|
+
id: 3,
|
|
82
|
+
action: 'Lire ton profil Kairos',
|
|
83
|
+
blocked: true,
|
|
84
|
+
instruction: `Appelle hcm_get_profile_atoms(profileId: "${profileId}")`,
|
|
85
|
+
validation: 'Confirme role, mission et mindset depuis les atoms NUCLEUS',
|
|
86
|
+
on_fail: `Status: NON READY — Cause: profil ${profileId} introuvable sur le HCM`
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 4,
|
|
90
|
+
action: 'Lire les atoms de gouvernance',
|
|
91
|
+
blocked: true,
|
|
92
|
+
instruction: 'Appelle hcm_get_governance_atoms',
|
|
93
|
+
validation: 'Integre les regles transversales a ton comportement',
|
|
94
|
+
on_fail: 'Status: NON READY — Cause: atoms de gouvernance indisponibles'
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const prompt = {
|
|
100
|
+
onboarding: {
|
|
101
|
+
agent: agentName,
|
|
102
|
+
role,
|
|
103
|
+
projet: project.name || project.id,
|
|
104
|
+
reporte_a: reportsTo || 'PM',
|
|
105
|
+
profil_kairos: profileId || null,
|
|
106
|
+
odms: (odmIds && odmIds.length > 0) ? odmIds : [],
|
|
107
|
+
leader: leader || false,
|
|
108
|
+
},
|
|
109
|
+
checklist: {
|
|
110
|
+
description: 'Execute chaque etape dans l\'ordre. Chaque etape est bloquante. Tu ne decides PAS de ce qui est bloquant.',
|
|
111
|
+
steps
|
|
112
|
+
},
|
|
113
|
+
on_success: {
|
|
114
|
+
status: 'READY',
|
|
115
|
+
format: {
|
|
116
|
+
agent: agentName,
|
|
117
|
+
role,
|
|
118
|
+
profil: profileId || 'aucun',
|
|
119
|
+
hcm: 'connecte',
|
|
120
|
+
memoire: 'lue'
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
on_failure: {
|
|
124
|
+
status: 'NON READY',
|
|
125
|
+
format: 'Cause: <liste des etapes en echec>'
|
|
126
|
+
},
|
|
127
|
+
regles: [
|
|
128
|
+
'Pas de decision d\'architecture sans validation PM',
|
|
129
|
+
'Pas de push sans validation PM',
|
|
130
|
+
'Depots dans .nemesis/HCM/',
|
|
131
|
+
'GitNexus avant toute modification de code',
|
|
132
|
+
'Francais pour les echanges, anglais pour le code',
|
|
133
|
+
...(leader ? [
|
|
134
|
+
'Tu coordonnes les agents qui te reportent en tant que leader.',
|
|
135
|
+
'Tu distribues les taches via des OdMs et synthetises les avancees pour le PM.',
|
|
136
|
+
] : []),
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
return JSON.stringify(prompt, null, 2);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Update ~/.claude/CLAUDE.md — add agent to table and workflow.
|
|
145
|
+
*/
|
|
146
|
+
export function updateClaudeMd(claudeMdPath, opts) {
|
|
147
|
+
const { agentName, repo, role, memoryPath } = opts;
|
|
148
|
+
let content = readFileSync(claudeMdPath, 'utf-8');
|
|
149
|
+
|
|
150
|
+
if (content.includes(agentName)) return;
|
|
151
|
+
|
|
152
|
+
// Add to agents table
|
|
153
|
+
const tableRow = `| **${agentName}** | ${repo} | ${role} | \`${memoryPath}\` |`;
|
|
154
|
+
const externesIdx = content.indexOf('## Agents externes');
|
|
155
|
+
if (externesIdx !== -1) {
|
|
156
|
+
const beforeExternes = content.lastIndexOf('\n\n', externesIdx);
|
|
157
|
+
if (beforeExternes !== -1) {
|
|
158
|
+
content = content.slice(0, beforeExternes) + '\n' + tableRow + content.slice(beforeExternes);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add to workflow tree
|
|
163
|
+
const workflowLine = ` ├── ${agentName} : ${role}`;
|
|
164
|
+
const workflowBlockStart = content.indexOf('## Workflow inter-agents');
|
|
165
|
+
if (workflowBlockStart !== -1) {
|
|
166
|
+
const codeBlockStart = content.indexOf('```\n', workflowBlockStart);
|
|
167
|
+
if (codeBlockStart !== -1) {
|
|
168
|
+
const codeBlockEnd = content.indexOf('\n```', codeBlockStart + 4);
|
|
169
|
+
if (codeBlockEnd !== -1) {
|
|
170
|
+
content = content.slice(0, codeBlockEnd) + '\n' + workflowLine + content.slice(codeBlockEnd);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
writeFileSync(claudeMdPath, content, 'utf-8');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Extract repo name from root path.
|
|
180
|
+
*/
|
|
181
|
+
export function getRepoName(root) {
|
|
182
|
+
return root.split('/').pop() || 'repo';
|
|
183
|
+
}
|