@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.
Files changed (100) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +668 -0
  3. package/lib/core/agent-launcher.js +193 -0
  4. package/lib/core/audit.js +210 -0
  5. package/lib/core/connexions.js +80 -0
  6. package/lib/core/flowmap/api.js +111 -0
  7. package/lib/core/flowmap/cli-helpers.js +80 -0
  8. package/lib/core/flowmap/machine.js +281 -0
  9. package/lib/core/flowmap/persistence.js +83 -0
  10. package/lib/core/generators.js +183 -0
  11. package/lib/core/inbox.js +275 -0
  12. package/lib/core/logger.js +20 -0
  13. package/lib/core/mission.js +109 -0
  14. package/lib/core/notewriter/config.js +36 -0
  15. package/lib/core/notewriter/cr.js +237 -0
  16. package/lib/core/notewriter/log.js +112 -0
  17. package/lib/core/notewriter/notes.js +168 -0
  18. package/lib/core/notewriter/paths.js +45 -0
  19. package/lib/core/notewriter/reader.js +121 -0
  20. package/lib/core/notewriter/registry.js +80 -0
  21. package/lib/core/odm.js +191 -0
  22. package/lib/core/profile-picker.js +323 -0
  23. package/lib/core/project.js +287 -0
  24. package/lib/core/registry.js +129 -0
  25. package/lib/core/secrets.js +137 -0
  26. package/lib/core/services.js +45 -0
  27. package/lib/core/team.js +287 -0
  28. package/lib/core/templates.js +80 -0
  29. package/lib/kairos/agent-runner.js +261 -0
  30. package/lib/kairos/claude-invoker.js +90 -0
  31. package/lib/kairos/context-injector.js +331 -0
  32. package/lib/kairos/context-loader.js +108 -0
  33. package/lib/kairos/context-writer.js +45 -0
  34. package/lib/kairos/dispatcher-router.js +173 -0
  35. package/lib/kairos/dispatcher.js +139 -0
  36. package/lib/kairos/event-bus.js +287 -0
  37. package/lib/kairos/event-router.js +131 -0
  38. package/lib/kairos/flowmap-bridge.js +120 -0
  39. package/lib/kairos/hook-handlers.js +351 -0
  40. package/lib/kairos/hook-installer.js +207 -0
  41. package/lib/kairos/hook-prompts.js +54 -0
  42. package/lib/kairos/leader-rules.js +94 -0
  43. package/lib/kairos/pid-checker.js +108 -0
  44. package/lib/kairos/situation-detector.js +123 -0
  45. package/lib/sync/fallback-engine.js +97 -0
  46. package/lib/sync/hcm-client.js +170 -0
  47. package/lib/sync/health.js +47 -0
  48. package/lib/sync/llm-client.js +387 -0
  49. package/lib/sync/nemesis-client.js +379 -0
  50. package/lib/sync/service-session.js +74 -0
  51. package/lib/sync/sync-engine.js +178 -0
  52. package/lib/ui/box.js +104 -0
  53. package/lib/ui/brand.js +42 -0
  54. package/lib/ui/colors.js +57 -0
  55. package/lib/ui/dashboard.js +580 -0
  56. package/lib/ui/error-hints.js +49 -0
  57. package/lib/ui/format.js +61 -0
  58. package/lib/ui/menu.js +306 -0
  59. package/lib/ui/note-card.js +198 -0
  60. package/lib/ui/note-colors.js +26 -0
  61. package/lib/ui/note-detail.js +297 -0
  62. package/lib/ui/note-filters.js +252 -0
  63. package/lib/ui/note-views.js +283 -0
  64. package/lib/ui/prompt.js +81 -0
  65. package/lib/ui/spinner.js +139 -0
  66. package/lib/ui/streambox.js +46 -0
  67. package/lib/ui/table.js +42 -0
  68. package/lib/ui/tree.js +33 -0
  69. package/package.json +53 -0
  70. package/src/cli.js +457 -0
  71. package/src/commands/_helpers.js +119 -0
  72. package/src/commands/audit.js +187 -0
  73. package/src/commands/auth.js +316 -0
  74. package/src/commands/doctor.js +243 -0
  75. package/src/commands/hcm.js +147 -0
  76. package/src/commands/inbox.js +333 -0
  77. package/src/commands/init.js +160 -0
  78. package/src/commands/kairos.js +216 -0
  79. package/src/commands/kars.js +134 -0
  80. package/src/commands/mission.js +275 -0
  81. package/src/commands/notes.js +316 -0
  82. package/src/commands/notewriter.js +296 -0
  83. package/src/commands/odm.js +329 -0
  84. package/src/commands/orch.js +68 -0
  85. package/src/commands/project.js +123 -0
  86. package/src/commands/run.js +123 -0
  87. package/src/commands/services.js +705 -0
  88. package/src/commands/status.js +231 -0
  89. package/src/commands/team.js +572 -0
  90. package/src/config.js +84 -0
  91. package/src/index.js +5 -0
  92. package/templates/project-context.json +10 -0
  93. package/templates/template_CONTRIB-NAME.json +22 -0
  94. package/templates/template_CR-ODM-NAME-000.exemple.json +32 -0
  95. package/templates/template_DEC-NAME-000.json +18 -0
  96. package/templates/template_INTV-NAME-000.json +15 -0
  97. package/templates/template_MISSION_CONTRACT.json +46 -0
  98. package/templates/template_ODM-NAME-000.json +89 -0
  99. package/templates/template_REGISTRY-PROJECT.json +26 -0
  100. 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
+ }