@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,216 @@
1
+ import { installHooks, verifyHooks, removeHooks } from '../../lib/kairos/hook-installer.js';
2
+ import { handlePreHook, handleDynamicHook, handlePostHook, handlePreCompact } from '../../lib/kairos/hook-handlers.js';
3
+ import { titledBox } from '../../lib/ui/box.js';
4
+ import { style } from '../../lib/ui/colors.js';
5
+
6
+ const HELP = `
7
+ nemesis kairos — Hooks & contexte agent
8
+
9
+ Usage: nemesis kairos <subcommand>
10
+
11
+ Subcommands:
12
+ hooks setup Installe les hooks Claude Code
13
+ hooks status Verifie les hooks
14
+ hooks remove Desinstalle les hooks
15
+ push Envoyer un evenement a un agent
16
+ events Lister les evenements en attente
17
+ clear Vider la queue d'un agent
18
+ -h, --help Aide
19
+ `;
20
+
21
+ export async function handler({ args, flags, config }) {
22
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
23
+ console.log(HELP);
24
+ return;
25
+ }
26
+
27
+ const sub = args[0];
28
+ const sub2 = args[1];
29
+
30
+ // Public hooks subcommands
31
+ if (sub === 'hooks') {
32
+ switch (sub2) {
33
+ case 'setup': return handleHooksSetup(config);
34
+ case 'status': return handleHooksStatus(config);
35
+ case 'remove': return handleHooksRemove(config);
36
+ default:
37
+ console.log(' Usage: nemesis kairos hooks <setup|status|remove>');
38
+ return;
39
+ }
40
+ }
41
+
42
+ // Event bus subcommands
43
+ if (sub === 'push') return handlePush(args.slice(1), flags, config);
44
+ if (sub === 'events') return handleEvents(args.slice(1), flags, config);
45
+ if (sub === 'clear') return handleClear(args.slice(1), flags, config);
46
+
47
+ // Internal hook handlers (called by Claude Code hooks via stdin)
48
+ switch (sub) {
49
+ case 'pre-hook': return handlePreHook(config.cwd);
50
+ case 'dynamic-hook': return handleDynamicHook(config.cwd);
51
+ case 'post-hook': return handlePostHook(config.cwd);
52
+ case 'pre-compact': return handlePreCompact(config.cwd);
53
+ }
54
+
55
+ // Default
56
+ console.log(' Usage:');
57
+ console.log(' nemesis kairos hooks setup — Installe les hooks Claude Code');
58
+ console.log(' nemesis kairos hooks status — Verifie les hooks');
59
+ console.log(' nemesis kairos hooks remove — Desinstalle les hooks');
60
+ console.log(' nemesis kairos push — Envoyer un evenement');
61
+ console.log(' nemesis kairos events — Lister les evenements');
62
+ console.log(' nemesis kairos clear — Vider une queue');
63
+ }
64
+
65
+ async function handleHooksSetup(config) {
66
+ const result = installHooks(config.cwd);
67
+
68
+ console.log(titledBox('Kairos Hooks — Installation', [
69
+ `Fichier : ${result.path}`,
70
+ result.merged
71
+ ? 'Hooks merges avec la configuration existante.'
72
+ : 'Configuration creee.',
73
+ '',
74
+ 'Hooks installes :',
75
+ ' SessionStart → nemesis kairos pre-hook',
76
+ ' UserPromptSubmit → nemesis kairos dynamic-hook',
77
+ ' Stop → nemesis kairos post-hook',
78
+ ' PreCompact → nemesis kairos pre-compact',
79
+ ], { border: style.arkaRed }));
80
+ }
81
+
82
+ async function handleHooksStatus(config) {
83
+ const result = verifyHooks(config.cwd);
84
+
85
+ const icon = (ok) => ok ? style.green('✓') : style.red('✗');
86
+ const lines = [
87
+ `settings.json : ${result.settingsExists ? 'present' : style.red('ABSENT')}`,
88
+ '',
89
+ 'Hooks :',
90
+ ` ${icon(result.hooks.SessionStart)} SessionStart → pre-hook`,
91
+ ` ${icon(result.hooks.UserPromptSubmit)} UserPromptSubmit → dynamic-hook`,
92
+ ` ${icon(result.hooks.Stop)} Stop → post-hook`,
93
+ ` ${icon(result.hooks.PreCompact)} PreCompact → pre-compact`,
94
+ '',
95
+ result.ok
96
+ ? style.green('Tous les hooks sont installes.')
97
+ : style.yellow('ATTENTION : hooks manquants. Lancez nemesis kairos hooks setup.'),
98
+ ];
99
+
100
+ console.log(titledBox('Kairos Hooks — Status', lines, { border: style.arkaRed }));
101
+ }
102
+
103
+ async function handleHooksRemove(config) {
104
+ const result = removeHooks(config.cwd);
105
+
106
+ if (result.removed) {
107
+ console.log(' Hooks Kairos desinstalles.');
108
+ } else {
109
+ console.log(` Desinstallation impossible : ${result.reason}`);
110
+ }
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Event Bus subcommands
115
+ // ---------------------------------------------------------------------------
116
+
117
+ function getFlag(args, flag) {
118
+ const idx = args.indexOf(flag);
119
+ if (idx === -1 || idx + 1 >= args.length) return null;
120
+ return args[idx + 1];
121
+ }
122
+
123
+ async function handlePush(args, flags, config) {
124
+ const to = getFlag(args, '--to');
125
+ const type = getFlag(args, '--type');
126
+ const priority = getFlag(args, '--priority') || 'MEDIUM';
127
+ const message = getFlag(args, '--message') || '';
128
+
129
+ if (!to || !type) {
130
+ console.log(' Usage: nemesis kairos push --to <agent> --type <event_type> [--priority HIGH|MEDIUM|LOW] [--message <text>]');
131
+ return;
132
+ }
133
+
134
+ const { EVENT_TYPES } = await import('../../lib/kairos/event-bus.js');
135
+ const validTypes = Object.values(EVENT_TYPES);
136
+ if (!validTypes.includes(type)) {
137
+ console.log(` Type invalide: ${type}`);
138
+ console.log(` Types valides: ${validTypes.join(', ')}`);
139
+ return;
140
+ }
141
+
142
+ const validPriorities = ['HIGH', 'MEDIUM', 'LOW'];
143
+ if (!validPriorities.includes(priority.toUpperCase())) {
144
+ console.log(` Priorite invalide: ${priority}`);
145
+ console.log(` Priorites valides: ${validPriorities.join(', ')}`);
146
+ return;
147
+ }
148
+
149
+ const { routeEvent } = await import('../../lib/kairos/event-router.js');
150
+ const result = await routeEvent(config.cwd, {
151
+ target_agent_id: to,
152
+ event_type: type,
153
+ source_agent_id: 'cli',
154
+ priority: priority.toUpperCase(),
155
+ payload: message ? { message } : {},
156
+ });
157
+
158
+ if (result.delivered) {
159
+ console.log(` ${style.green('✓')} Evenement ${type} envoye a ${to} (${result.method})`);
160
+ console.log(` ${style.dim(result.detail)}`);
161
+ } else {
162
+ console.log(` ${style.red('✗')} Echec: ${result.detail}`);
163
+ }
164
+ }
165
+
166
+ async function handleEvents(args, flags, config) {
167
+ const agentFilter = getFlag(args, '--agent');
168
+
169
+ const { pollEvents, listQueues } = await import('../../lib/kairos/event-bus.js');
170
+
171
+ if (agentFilter) {
172
+ const events = pollEvents(config.cwd, agentFilter);
173
+ if (events.length === 0) {
174
+ console.log(`\n ${style.dim('Aucun evenement pour')} ${agentFilter}\n`);
175
+ return;
176
+ }
177
+ const lines = events.map(({ event }) =>
178
+ ` [${event.priority}] ${event.event_type} de ${event.source_agent_id} (${event.created_at})`
179
+ );
180
+ console.log(titledBox(`Events — ${agentFilter}`, lines, { border: style.arkaRed }));
181
+ } else {
182
+ const queues = listQueues(config.cwd);
183
+ if (queues.length === 0) {
184
+ console.log(`\n ${style.dim('Aucune queue active.')}\n`);
185
+ return;
186
+ }
187
+ const lines = queues.map(q =>
188
+ ` ${q.agentId} ${style.bold(String(q.count))} evt ${q.oldest ? style.dim(`depuis ${q.oldest}`) : ''}`
189
+ );
190
+ console.log(titledBox('Event Bus — Queues', lines, { border: style.arkaRed }));
191
+ }
192
+ }
193
+
194
+ async function handleClear(args, flags, config) {
195
+ const agent = getFlag(args, '--agent');
196
+ const all = args.includes('--all');
197
+
198
+ if (!agent && !all) {
199
+ console.log(' Usage: nemesis kairos clear --agent <name> [--all]');
200
+ return;
201
+ }
202
+
203
+ const { clearQueue, listQueues } = await import('../../lib/kairos/event-bus.js');
204
+
205
+ if (all) {
206
+ const queues = listQueues(config.cwd);
207
+ let total = 0;
208
+ for (const q of queues) {
209
+ total += clearQueue(config.cwd, q.agentId);
210
+ }
211
+ console.log(` ${style.green('✓')} ${total} evenement(s) purge(s) sur ${queues.length} queue(s).`);
212
+ } else {
213
+ const count = clearQueue(config.cwd, agent);
214
+ console.log(` ${style.green('✓')} ${count} evenement(s) purge(s) pour ${agent}.`);
215
+ }
216
+ }
@@ -0,0 +1,134 @@
1
+ import { createHcmClient } from '../../lib/sync/hcm-client.js';
2
+ import { interactiveMenu } from '../../lib/ui/menu.js';
3
+ import { createSpinner } from '../../lib/ui/spinner.js';
4
+ import { renderTable } from '../../lib/ui/table.js';
5
+ import { formatOutput } from '../../lib/ui/format.js';
6
+ import { style } from '../../lib/ui/colors.js';
7
+ import { getFlag, BACK_ITEM, HOME_ITEM } from './_helpers.js';
8
+
9
+ const HELP = `
10
+ nemesis kars — Recherche profils Kairos
11
+
12
+ Usage: nemesis kars search [--sector <secteur>]
13
+
14
+ Subcommands:
15
+ search Recherche de profils par secteur
16
+
17
+ Options:
18
+ --sector <s> Filtrer par secteur
19
+ -h, --help Aide
20
+ `;
21
+
22
+ export async function handler({ args, flags, _config }) {
23
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
24
+ console.log(HELP);
25
+ return;
26
+ }
27
+
28
+ let sub = args[0];
29
+ let subArgs = args.slice(1);
30
+
31
+ if (!sub) {
32
+ sub = await interactiveMenu([
33
+ { label: 'search', value: 'search', description: 'Recherche de profils par secteur' },
34
+ BACK_ITEM,
35
+ HOME_ITEM,
36
+ ], { title: 'nemesis kars' });
37
+ if (!sub || sub === '__back__') return '__back__';
38
+ if (sub === '__home__') return '__home__';
39
+ subArgs = [];
40
+ }
41
+
42
+ switch (sub) {
43
+ case 'search':
44
+ return handleSearch(subArgs, flags);
45
+ default:
46
+ console.error(`Sous-commande inconnue : ${sub}`);
47
+ console.log(HELP);
48
+ }
49
+ }
50
+
51
+ async function handleSearch(args, flags) {
52
+ const client = createHcmClient();
53
+ let sector = getFlag(args, '--sector');
54
+
55
+ // Interactive: fetch all profiles to extract sectors, then let user pick
56
+ if (!sector) {
57
+ const spinner = createSpinner('Chargement des secteurs...');
58
+ spinner.start();
59
+
60
+ try {
61
+ // q is required by the API — use 'a' to match all profiles
62
+ const all = await client.searchProfiles('a');
63
+ const profiles = Array.isArray(all) ? all : [];
64
+ const sectorSet = new Set();
65
+ for (const p of profiles) {
66
+ const sectors = Array.isArray(p.sectors) ? p.sectors : [];
67
+ for (const s of sectors) {
68
+ if (s) sectorSet.add(s);
69
+ }
70
+ }
71
+ const sectors = [...sectorSet].sort((a, b) => a.localeCompare(b, 'fr'));
72
+ spinner.stop(`${sectors.length} secteurs disponibles`);
73
+
74
+ if (sectors.length === 0) {
75
+ console.log(`\n ${style.yellow('⚠')} Aucun secteur disponible.\n`);
76
+ return;
77
+ }
78
+
79
+ const items = sectors.map(s => ({ label: s, value: s }));
80
+ sector = await interactiveMenu(items, {
81
+ title: 'Secteur',
82
+ maxVisible: 15,
83
+ });
84
+ if (!sector) return;
85
+ } catch (err) {
86
+ spinner.fail('Erreur HCM');
87
+ console.error(` ${err.message}\n`);
88
+ return;
89
+ }
90
+ }
91
+
92
+ // Fetch profiles for selected sector
93
+ const spinner = createSpinner(`Recherche secteur : ${sector}...`);
94
+ spinner.start();
95
+
96
+ try {
97
+ const results = await client.searchProfiles(null, sector);
98
+ const profiles = Array.isArray(results) ? results : [];
99
+ spinner.stop(`${profiles.length} profils trouves`);
100
+
101
+ if (flags.format === 'json') {
102
+ console.log(formatOutput(profiles, 'json'));
103
+ return;
104
+ }
105
+
106
+ if (profiles.length === 0) {
107
+ console.log(`\n Aucun profil trouve pour le secteur "${sector}".\n`);
108
+ return;
109
+ }
110
+
111
+ const columns = [
112
+ { key: 'id', label: 'ID' },
113
+ { key: 'role', label: 'Role' },
114
+ { key: 'keywords', label: 'Keywords' },
115
+ { key: 'sectors', label: 'Sectors' },
116
+ { key: 'score', label: 'Score' },
117
+ ];
118
+
119
+ const rows = profiles.map(p => ({
120
+ id: p.id || '',
121
+ role: p.role || '',
122
+ keywords: Array.isArray(p.keywords) ? p.keywords.slice(0, 3).join(', ') : '',
123
+ sectors: Array.isArray(p.sectors) ? p.sectors.join(', ') : '',
124
+ score: p.score != null ? String(p.score) : '',
125
+ }));
126
+
127
+ console.log('');
128
+ console.log(renderTable(columns, rows));
129
+ console.log('');
130
+ } catch (err) {
131
+ spinner.fail('Erreur HCM');
132
+ console.error(` ${err.message}\n`);
133
+ }
134
+ }
@@ -0,0 +1,275 @@
1
+ import { initMission, listMissions, inspectMission, validateMission } from '../../lib/core/mission.js';
2
+ import { readRegistry, getLanes } from '../../lib/core/registry.js';
3
+ import { askText, askConfirm } from '../../lib/ui/prompt.js';
4
+ import { interactiveMenu } from '../../lib/ui/menu.js';
5
+ import { renderTable } from '../../lib/ui/table.js';
6
+ import { formatOutput } from '../../lib/ui/format.js';
7
+ import { style } from '../../lib/ui/colors.js';
8
+ import { ensureProject, pickFromList, nextId, BACK_ITEM, HOME_ITEM } from './_helpers.js';
9
+ import { getMissionFlowmapInfo } from '../../lib/core/flowmap/cli-helpers.js';
10
+ import { getCurrentState } from '../../lib/core/flowmap/api.js';
11
+
12
+ const HELP = `
13
+ nemesis mission — Mission Contracts
14
+
15
+ Usage: nemesis mission <subcommand> [options]
16
+
17
+ Subcommands:
18
+ init [id] Cree un Mission Contract
19
+ list Liste les Mission Contracts
20
+ inspect [id] Affiche le contenu complet d'un MCT
21
+ validate [id] Valide un MC (DRAFT → VALIDATED)
22
+
23
+ Options:
24
+ -h, --help Aide
25
+ `;
26
+
27
+ export async function handler({ args, flags, config }) {
28
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
29
+ console.log(HELP);
30
+ return;
31
+ }
32
+
33
+ let sub = args[0];
34
+ let subArgs = args.slice(1);
35
+
36
+ if (!sub) {
37
+ sub = await interactiveMenu([
38
+ { label: 'list', value: 'list', description: 'Liste les Mission Contracts' },
39
+ { label: 'init', value: 'init', description: 'Cree un Mission Contract' },
40
+ { label: 'inspect', value: 'inspect', description: 'Affiche le contenu d\'un MCT' },
41
+ { label: 'validate', value: 'validate', description: 'Valide un MC (DRAFT → VALIDATED)' },
42
+ BACK_ITEM,
43
+ HOME_ITEM,
44
+ ], { title: 'nemesis mission' });
45
+ if (!sub || sub === '__back__') return '__back__';
46
+ if (sub === '__home__') return '__home__';
47
+ subArgs = [];
48
+ }
49
+
50
+ switch (sub) {
51
+ case 'init':
52
+ return handleInit(subArgs, flags, config);
53
+ case 'list':
54
+ return handleList(flags, config);
55
+ case 'inspect':
56
+ return handleInspect(subArgs, flags, config);
57
+ case 'validate':
58
+ return handleValidate(subArgs, flags, config);
59
+ default:
60
+ console.error(`Sous-commande inconnue : ${sub}`);
61
+ console.log(HELP);
62
+ }
63
+ }
64
+
65
+ async function handleInit(args, flags, config) {
66
+ const project = await ensureProject(config, flags);
67
+ if (!project) return;
68
+
69
+ // Auto-generate ID if not provided
70
+ let id = args[0];
71
+ if (!id) {
72
+ const existing = listMissions(project.hcm_dir);
73
+ const prefix = `MCT-${project.id}`;
74
+ const suggested = nextId(prefix, existing.map(m => m.id));
75
+ id = await askText('ID du Mission Contract', suggested);
76
+ }
77
+ if (!id) return;
78
+
79
+ const title = await askText('Titre');
80
+ const summary = await askText('Resume (optionnel)', '');
81
+
82
+ const result = initMission(id, {
83
+ title, summary, root: config.cwd, projectId: project.id,
84
+ });
85
+
86
+ console.log(`\n ${style.green('\u2713')} Mission Contract cree : ${style.bold(id)}`);
87
+ console.log(` ${style.dim('\u2192')} ${result.filepath.replace(config.cwd + '/', '')}`);
88
+
89
+ // Next steps
90
+ const registry = readRegistry(project.hcm_dir);
91
+ const lanes = registry ? getLanes(registry) : [];
92
+
93
+ console.log(`\n ${style.bold('Prochaines etapes')} :`);
94
+ console.log(' 1. Completez le workflow (gates, task_registry)');
95
+ console.log(` 2. Redigez les OdMs associes : ${style.dim('nemesis odm init')}`);
96
+ if (lanes.length === 0) {
97
+ console.log(` 3. ${style.yellow('Equipe vide')} — ajoutez un agent : ${style.dim('nemesis team add')}`);
98
+ } else {
99
+ console.log(` 3. Equipe : ${lanes.length} agent(s) disponible(s)`);
100
+ }
101
+ console.log('');
102
+
103
+ // Loop — propose to create another
104
+ const another = await askConfirm('Creer un autre Mission Contract ?', false);
105
+ if (another) return handleInit([], flags, config);
106
+ }
107
+
108
+ async function handleList(flags, config) {
109
+ const project = await ensureProject(config, flags);
110
+ if (!project) return;
111
+
112
+ const missions = listMissions(project.hcm_dir);
113
+
114
+ if (flags.format === 'json') {
115
+ console.log(formatOutput(missions, 'json'));
116
+ return;
117
+ }
118
+
119
+ if (missions.length === 0) {
120
+ console.log(`\n ${style.dim('Aucun Mission Contract.')}`);
121
+ const doCreate = await askConfirm('Creer un Mission Contract maintenant ?', true);
122
+ if (doCreate) {
123
+ return handleInit([], flags, config);
124
+ }
125
+ console.log('');
126
+ return;
127
+ }
128
+
129
+ // Enrich with FLOWMAP state
130
+ const enriched = missions.map(m => {
131
+ const missionId = m.id;
132
+ let flowmap = '-';
133
+ try {
134
+ const fmState = getCurrentState(missionId, { root: config.cwd });
135
+ if (fmState) flowmap = fmState.value;
136
+ } catch { /* no-op */ }
137
+ return { ...m, flowmap };
138
+ });
139
+
140
+ console.log('');
141
+ console.log(renderTable(
142
+ [
143
+ { key: 'id', label: 'ID' },
144
+ { key: 'title', label: 'Titre' },
145
+ { key: 'status', label: 'Status' },
146
+ { key: 'flowmap', label: 'FLOWMAP' },
147
+ ],
148
+ enriched,
149
+ ));
150
+ console.log('');
151
+ }
152
+
153
+ async function handleInspect(args, flags, config) {
154
+ const project = await ensureProject(config, flags);
155
+ if (!project) return;
156
+
157
+ let id = args[0];
158
+
159
+ // If no ID → picker from existing missions
160
+ if (!id) {
161
+ const missions = listMissions(project.hcm_dir);
162
+ if (missions.length === 0) {
163
+ console.log(`\n ${style.dim('Aucun Mission Contract a inspecter.')}`);
164
+ const doCreate = await askConfirm('Creer un Mission Contract maintenant ?', true);
165
+ if (doCreate) return handleInit([], flags, config);
166
+ return;
167
+ }
168
+ id = await pickFromList(missions, {
169
+ title: 'Quel Mission Contract inspecter ?',
170
+ labelFn: (m) => `${m.id} — ${m.title || style.dim('(sans titre)')} [${m.status}]`,
171
+ });
172
+ if (!id) return;
173
+ }
174
+
175
+ const data = inspectMission(id, project.hcm_dir);
176
+
177
+ if (flags.format === 'json') {
178
+ console.log(formatOutput(data, 'json'));
179
+ return;
180
+ }
181
+
182
+ // Formatted view — consistent with odm inspect
183
+ const meta = data.contract_meta || {};
184
+ const payload = data.contract_payload || {};
185
+ const cadrage = payload.cadrage || {};
186
+ const workflow = payload.workflow || {};
187
+ const taskReg = payload.task_registry || {};
188
+ const tasks = Array.isArray(taskReg.tasks) ? taskReg.tasks : [];
189
+
190
+ console.log(`\n ${style.bold('Mission Contract')} : ${style.nemesisAccent(meta.id || id)}`);
191
+ console.log(` Status : ${meta.status || '?'}`);
192
+ console.log(` Owner : ${meta.owner || '?'}`);
193
+ console.log(` Projet : ${meta.project_id || '?'}`);
194
+ console.log(` Titre : ${cadrage.title || '?'}`);
195
+ if (cadrage.summary) {
196
+ console.log(` Resume : ${cadrage.summary}`);
197
+ }
198
+ console.log(` Cree le : ${meta.created_at ? meta.created_at.split('T')[0] : '?'}`);
199
+
200
+ // Workflow
201
+ const gates = workflow.gates || {};
202
+ const gateCount = Object.keys(gates).length;
203
+ if (gateCount > 0) {
204
+ console.log(`\n Workflow : ${gateCount} gate(s)`);
205
+ for (const [name, gate] of Object.entries(gates)) {
206
+ console.log(` ${style.dim('\u2192')} ${name} : ${gate.description || gate.status || '?'}`);
207
+ }
208
+ } else {
209
+ console.log(`\n Workflow : ${style.dim('(a completer)')}`);
210
+ }
211
+
212
+ // Task registry
213
+ if (tasks.length > 0) {
214
+ console.log(`\n Tasks : ${tasks.length} tache(s)`);
215
+ console.log(renderTable(
216
+ [
217
+ { key: 'id', label: 'ID' },
218
+ { key: 'title', label: 'Tache' },
219
+ { key: 'status', label: 'Status' },
220
+ ],
221
+ tasks,
222
+ ));
223
+ } else {
224
+ console.log(` Tasks : ${style.dim('(aucune)')}`);
225
+ }
226
+
227
+ // FLOWMAP
228
+ const missionId = meta.mission_id || meta.id || id;
229
+ const fmInfo = getMissionFlowmapInfo(missionId, config.cwd);
230
+ if (fmInfo) {
231
+ console.log(`\n FLOWMAP : ${fmInfo.state} [${fmInfo.phase}] ${fmInfo.label}`);
232
+ if (fmInfo.history.length > 0) {
233
+ console.log('\n Historique recent :');
234
+ for (const h of fmInfo.history.slice(-5)) {
235
+ const ts = h.timestamp ? h.timestamp.replace('T', ' ').substring(0, 16) : '';
236
+ console.log(` - ${h.from || '?'} \u2192 ${h.to || '?'} ${h.event} ${style.dim(ts)}`);
237
+ }
238
+ }
239
+ } else {
240
+ console.log(`\n FLOWMAP : ${style.dim('(non initialise)')}`);
241
+ }
242
+
243
+ console.log('');
244
+ }
245
+
246
+ async function handleValidate(args, flags, config) {
247
+ const project = await ensureProject(config, flags);
248
+ if (!project) return;
249
+
250
+ let id = args[0];
251
+
252
+ if (!id) {
253
+ const missions = listMissions(project.hcm_dir);
254
+ const drafts = missions.filter(m => m.status === 'DRAFT');
255
+ if (drafts.length === 0) {
256
+ console.log(`\n ${style.dim('Aucun Mission Contract en DRAFT a valider.')}\n`);
257
+ return;
258
+ }
259
+ id = await pickFromList(drafts, {
260
+ title: 'Mission Contract a valider',
261
+ labelFn: (m) => `${m.id} — ${m.title || style.dim('(sans titre)')} [${m.status}]`,
262
+ });
263
+ if (!id) return;
264
+ }
265
+
266
+ try {
267
+ const result = validateMission(id, project.hcm_dir);
268
+ console.log(`\n ${style.green('\u2713')} Mission Contract ${style.bold(result.id)} → ${style.green('VALIDATED')}`);
269
+ console.log(`\n ${style.bold('Prochaines etapes')} :`);
270
+ console.log(` 1. Creer les OdMs associes : ${style.dim('nemesis odm init')}`);
271
+ console.log(` 2. Dispatcher aux agents : ${style.dim('nemesis odm set-status <ID> READY_TO_DISPATCH + nemesis orch')}\n`);
272
+ } catch (err) {
273
+ console.log(`\n ${style.red('\u2717')} ${err.message}\n`);
274
+ }
275
+ }