@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,243 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ import { detectProject } from '../../lib/core/project.js';
5
+ import { readRegistry, getLanes } from '../../lib/core/registry.js';
6
+ import { diagnoseHcm } from '../../lib/sync/health.js';
7
+ import { readServices, SERVICE_IDS } from '../../lib/core/services.js';
8
+ import { listConnexions } from '../../lib/core/connexions.js';
9
+ import { verifyHooks, ALL_HOOKS } from '../../lib/kairos/hook-installer.js';
10
+ import { style } from '../../lib/ui/colors.js';
11
+
12
+ const HELP = `
13
+ nemesis doctor — Diagnostic complet
14
+
15
+ Usage: nemesis doctor
16
+
17
+ Options:
18
+ -h, --help Aide
19
+ `;
20
+
21
+ const HCM_EXPECTED_DIRS = [
22
+ 'contracts', 'odm', 'cr', 'transactions', 'note',
23
+ 'decisions', 'interventions', 'contributors', 'project', 'odm-cr-QA',
24
+ ];
25
+
26
+ const EXPECTED_TEMPLATES = [
27
+ 'template_ODM-NAME-000.json',
28
+ 'template_CR-ODM-NAME-000.exemple.json',
29
+ 'template_MISSION_CONTRACT.json',
30
+ 'template_REGISTRY-PROJECT.json',
31
+ 'template_DEC-NAME-000.json',
32
+ 'template_INTV-NAME-000.json',
33
+ 'template_CONTRIB-NAME.json',
34
+ 'template_TXN-NAME-000.json',
35
+ ];
36
+
37
+ const ok = (msg) => ` ${style.green('\u2713')} ${msg}`;
38
+ const ko = (msg) => ` ${style.red('\u2717')} ${msg}`;
39
+ const warn = (msg) => ` ${style.yellow('\u26A0')} ${msg}`;
40
+
41
+ export async function handler({ args, flags, config }) {
42
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
43
+ console.log(HELP);
44
+ return;
45
+ }
46
+
47
+ const project = detectProject(config.cwd);
48
+ let hasErrors = false;
49
+
50
+ // Check 1: Structure .nemesis/HCM/
51
+ console.log(`\n ${style.bold('Structure .nemesis/HCM/')} :`);
52
+ if (!project) {
53
+ console.log(ko('Aucun projet detecte'));
54
+ hasErrors = true;
55
+ } else {
56
+ for (const dir of HCM_EXPECTED_DIRS) {
57
+ const path = join(project.hcm_dir, dir);
58
+ if (existsSync(path)) {
59
+ console.log(ok(`${dir}/`));
60
+ } else {
61
+ console.log(ko(`${dir}/ MANQUANT`));
62
+ hasErrors = true;
63
+ }
64
+ }
65
+ const processFile = join(project.hcm_dir, 'PROCESS.md');
66
+ if (existsSync(processFile)) {
67
+ console.log(ok('PROCESS.md'));
68
+ } else {
69
+ console.log(ko('PROCESS.md MANQUANT'));
70
+ hasErrors = true;
71
+ }
72
+ }
73
+
74
+ // Check 2: Templates
75
+ console.log(`\n ${style.bold('Templates .nemesis/template/')} :`);
76
+ const templateDir = join(config.cwd, '.nemesis', 'template');
77
+ if (existsSync(templateDir)) {
78
+ const files = readdirSync(templateDir);
79
+ let found = 0;
80
+ for (const tmpl of EXPECTED_TEMPLATES) {
81
+ if (files.includes(tmpl)) found++;
82
+ }
83
+ if (found === EXPECTED_TEMPLATES.length) {
84
+ console.log(ok(`${found} templates presents`));
85
+ } else {
86
+ console.log(warn(`${found}/${EXPECTED_TEMPLATES.length} templates`));
87
+ }
88
+ } else {
89
+ console.log(ko('.nemesis/template/ MANQUANT'));
90
+ hasErrors = true;
91
+ }
92
+
93
+ // Check 3: Config
94
+ console.log(`\n ${style.bold('Config')} :`);
95
+ const configFile = join(homedir(), '.nemesis', 'config.json');
96
+ if (existsSync(configFile)) {
97
+ console.log(ok('~/.nemesis/config.json'));
98
+ } else {
99
+ console.log(warn('~/.nemesis/config.json absent (defaults utilises)'));
100
+ }
101
+ if (project) {
102
+ console.log(ok(`Projet detecte : ${project.id}`));
103
+ }
104
+
105
+ // Check 4: HCM
106
+ console.log(`\n ${style.bold('HCM')} :`);
107
+ try {
108
+ const hcmResult = await diagnoseHcm();
109
+ console.log(` URL : ${hcmResult.url}`);
110
+ for (const check of hcmResult.checks) {
111
+ console.log(check.ok ? ok(`${check.label} : ${check.detail}`) : ko(`${check.label} : ${check.detail}`));
112
+ }
113
+ if (!hcmResult.ok) hasErrors = true;
114
+ } catch (err) {
115
+ console.log(ko(`Erreur : ${err.message}`));
116
+ hasErrors = true;
117
+ }
118
+
119
+ // Check 5: Registry vs CLAUDE.md
120
+ console.log(`\n ${style.bold('Registre')} :`);
121
+ if (project) {
122
+ const registry = readRegistry(project.hcm_dir);
123
+ if (registry) {
124
+ const lanes = getLanes(registry);
125
+ console.log(ok(`REGISTRY (${lanes.length + 1} membres)`));
126
+
127
+ const claudeMdPath = join(homedir(), '.claude', 'CLAUDE.md');
128
+ if (existsSync(claudeMdPath)) {
129
+ const claudeContent = readFileSync(claudeMdPath, 'utf-8');
130
+ let syncCount = 0;
131
+ for (const lane of lanes) {
132
+ if (claudeContent.includes(lane.name || lane.id)) syncCount++;
133
+ }
134
+ if (syncCount === lanes.length) {
135
+ console.log(ok(`CLAUDE.md sync (${lanes.length} agents)`));
136
+ } else {
137
+ console.log(warn(`CLAUDE.md desync (${syncCount}/${lanes.length} agents)`));
138
+ }
139
+ }
140
+ } else {
141
+ console.log(ko('Registry introuvable'));
142
+ hasErrors = true;
143
+ }
144
+ }
145
+
146
+ // Check 6: Agent memory files
147
+ console.log(`\n ${style.bold('Agents')} :`);
148
+ if (project) {
149
+ const registry = readRegistry(project.hcm_dir);
150
+ if (registry) {
151
+ const lanes = getLanes(registry);
152
+ for (const lane of lanes) {
153
+ if (lane.memory_path) {
154
+ const fullPath = join(project.root, lane.memory_path);
155
+ if (existsSync(fullPath)) {
156
+ const stat = statSync(fullPath);
157
+ const size = (stat.size / 1024).toFixed(1);
158
+ console.log(ok(`${lane.name || lane.id} : memory.md present (${size} KB)`));
159
+ } else {
160
+ console.log(ko(`${lane.name || lane.id} : memory.md MANQUANT`));
161
+ hasErrors = true;
162
+ }
163
+ }
164
+ }
165
+ if (lanes.length === 0) {
166
+ console.log(' (aucun agent enregistre)');
167
+ }
168
+ }
169
+ }
170
+
171
+ // Check 7: Services LLM
172
+ console.log(`\n ${style.bold('Services')} :`);
173
+ if (project) {
174
+ const svcData = readServices(project.root);
175
+ const allConnexions = listConnexions();
176
+ const now = Date.now();
177
+ const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1000;
178
+
179
+ for (const sid of SERVICE_IDS) {
180
+ const svc = svcData.services[sid];
181
+ if (!svc) {
182
+ console.log(warn(`${sid} \u2014 non configure`));
183
+ continue;
184
+ }
185
+ const cx = allConnexions.find(c => c.id === svc.connexion_id);
186
+ if (!cx) {
187
+ console.log(ko(`${sid} \u2014 connexion manquante`));
188
+ hasErrors = true;
189
+ continue;
190
+ }
191
+ if (cx.status === 'connected') {
192
+ const latStr = cx.latency ? `, ${cx.latency}ms` : '';
193
+ console.log(ok(`${sid} \u2014 ${cx.name} (connected${latStr})`));
194
+ } else if (cx.status === 'error') {
195
+ console.log(ko(`${sid} \u2014 ${cx.name} (error)`));
196
+ hasErrors = true;
197
+ } else {
198
+ console.log(warn(`${sid} \u2014 ${cx.name} (non teste)`));
199
+ }
200
+ if (cx.last_tested && (now - new Date(cx.last_tested).getTime()) > SEVEN_DAYS) {
201
+ console.log(warn(`${sid} \u2014 ${cx.name} non teste depuis 7 jours`));
202
+ }
203
+ // Check fallbacks
204
+ for (const fb of (svc.fallback || [])) {
205
+ const fbCx = allConnexions.find(c => c.id === fb.connexion_id);
206
+ if (fbCx && fbCx.last_tested && (now - new Date(fbCx.last_tested).getTime()) > SEVEN_DAYS) {
207
+ console.log(warn(`${sid} \u2014 Fallback ${fbCx.name} non teste depuis 7 jours`));
208
+ }
209
+ }
210
+ }
211
+ } else {
212
+ console.log(warn('Pas de projet — services non verifies'));
213
+ }
214
+
215
+ // Check 8: Hooks Kairos
216
+ console.log(`\n ${style.bold('Hooks Kairos')} :`);
217
+ if (project) {
218
+ const hookResult = verifyHooks(project.root);
219
+ const hookCount = Object.keys(ALL_HOOKS).length;
220
+ const activeCount = Object.values(hookResult.hooks).filter(Boolean).length;
221
+ if (activeCount === hookCount) {
222
+ console.log(ok(`Tous les hooks installes (${hookCount}/${hookCount})`));
223
+ } else if (activeCount > 0) {
224
+ console.log(warn(`${activeCount}/${hookCount} hooks installes`));
225
+ } else {
226
+ console.log(warn('Hooks non installes (nemesis services hooks setup)'));
227
+ }
228
+ } else {
229
+ console.log(warn('Pas de projet — hooks non verifies'));
230
+ }
231
+
232
+ // Summary
233
+ console.log('');
234
+ if (hasErrors) {
235
+ console.log(` ${style.red('Des problemes ont ete detectes.')} Voir ci-dessus.`);
236
+ if (!project) {
237
+ console.log(` ${style.dim('\u2192 Lancez')} nemesis init ${style.dim('pour creer un projet.')}`);
238
+ }
239
+ console.log('');
240
+ } else {
241
+ console.log(` ${style.green('Tout est OK.')}\n`);
242
+ }
243
+ }
@@ -0,0 +1,147 @@
1
+ import { createHcmClient } from '../../lib/sync/hcm-client.js';
2
+ import { diagnoseHcm } from '../../lib/sync/health.js';
3
+ import { fullSync } from '../../lib/sync/sync-engine.js';
4
+ import { interactiveMenu } from '../../lib/ui/menu.js';
5
+ import { createSpinner } from '../../lib/ui/spinner.js';
6
+ import { formatOutput } from '../../lib/ui/format.js';
7
+ import { askText } from '../../lib/ui/prompt.js';
8
+ import { style } from '../../lib/ui/colors.js';
9
+ import { ensureProject, BACK_ITEM, HOME_ITEM } from './_helpers.js';
10
+
11
+ const HELP = `
12
+ nemesis hcm — Synchronisation HCM
13
+
14
+ Usage: nemesis hcm <subcommand> [options]
15
+
16
+ Subcommands:
17
+ sync Force la synchronisation vers HCM
18
+ status Affiche le status HCM (connexion + stats)
19
+ search [query] Recherche dans les documents HCM
20
+
21
+ Options:
22
+ -h, --help Aide
23
+ `;
24
+
25
+ export async function handler({ args, flags, config }) {
26
+ if (flags.help || args.includes('--help') || args.includes('-h')) {
27
+ console.log(HELP);
28
+ return;
29
+ }
30
+
31
+ let sub = args[0];
32
+ let subArgs = args.slice(1);
33
+
34
+ if (!sub) {
35
+ sub = await interactiveMenu([
36
+ { label: 'status', value: 'status', description: 'Status HCM (connexion + stats)' },
37
+ { label: 'sync', value: 'sync', description: 'Force la synchronisation' },
38
+ { label: 'search', value: 'search', description: 'Recherche dans les documents' },
39
+ BACK_ITEM,
40
+ HOME_ITEM,
41
+ ], { title: 'nemesis hcm' });
42
+ if (!sub || sub === '__back__') return '__back__';
43
+ if (sub === '__home__') return '__home__';
44
+ subArgs = [];
45
+ }
46
+
47
+ switch (sub) {
48
+ case 'sync':
49
+ return handleSync(flags, config);
50
+ case 'status':
51
+ return handleStatus(flags, config);
52
+ case 'search':
53
+ return handleSearch(subArgs, flags, config);
54
+ default:
55
+ console.error(`Sous-commande inconnue : ${sub}`);
56
+ console.log(HELP);
57
+ }
58
+ }
59
+
60
+ async function handleSync(flags, config) {
61
+ const project = await ensureProject(config, flags);
62
+ if (!project) return;
63
+
64
+ const spinner = createSpinner('Scanning .nemesis/HCM/...');
65
+ spinner.start();
66
+
67
+ const client = createHcmClient();
68
+
69
+ try {
70
+ const results = await fullSync(project.hcm_dir, client);
71
+ spinner.stop('Sync termine');
72
+
73
+ console.log('');
74
+ for (const detail of results.details) {
75
+ const symbol = detail.status === 'synced' ? '\u2713' : detail.status === 'error' ? '\u2717' : '\u2713';
76
+ const suffix = detail.status === 'synced' ? 'SYNCED' :
77
+ detail.status === 'error' ? `ERREUR (${detail.error})` : 'deja a jour';
78
+ console.log(` ${symbol} ${detail.file} \u2192 ${suffix}`);
79
+ }
80
+
81
+ console.log(`\n ${results.total} fichiers scannes, ${results.synced} synchronises, ${results.unchanged} inchanges.`);
82
+ if (results.errors > 0) console.log(` ${results.errors} erreurs.`);
83
+ console.log('');
84
+ } catch (err) {
85
+ spinner.fail('Erreur sync');
86
+ console.error(` ${err.message}\n`);
87
+ }
88
+ }
89
+
90
+ async function handleStatus(flags, config) {
91
+ const diag = await diagnoseHcm();
92
+
93
+ if (flags.format === 'json') {
94
+ console.log(formatOutput(diag, 'json'));
95
+ return;
96
+ }
97
+
98
+ console.log(`\n URL : ${diag.url}`);
99
+ for (const check of diag.checks) {
100
+ const symbol = check.ok ? '\u2713' : '\u2717';
101
+ console.log(` ${symbol} ${check.label} : ${check.detail}`);
102
+ }
103
+
104
+ const { detectProject } = await import('../../lib/core/project.js');
105
+ const project = detectProject(config.cwd);
106
+ if (project) {
107
+ console.log(`\n Projet ${style.bold(project.id)} detecte localement`);
108
+ }
109
+ console.log('');
110
+ }
111
+
112
+ async function handleSearch(args, flags, _config) {
113
+ let query = args.join(' ');
114
+
115
+ // If no query → ask interactively
116
+ if (!query) {
117
+ query = await askText('Recherche HCM');
118
+ if (!query) return;
119
+ }
120
+
121
+ const client = createHcmClient();
122
+
123
+ try {
124
+ const results = await client.search(query);
125
+
126
+ if (flags.format === 'json') {
127
+ console.log(formatOutput(results, 'json'));
128
+ return;
129
+ }
130
+
131
+ const items = results.results || results.documents || results || [];
132
+ if (!Array.isArray(items) || items.length === 0) {
133
+ console.log('\n Aucun resultat.\n');
134
+ return;
135
+ }
136
+
137
+ console.log(`\n ${items.length} resultats :\n`);
138
+ items.forEach((item, i) => {
139
+ const title = item.title || item.label || item.id || '(sans titre)';
140
+ const score = item.score ? ` (score: ${item.score.toFixed(1)})` : '';
141
+ console.log(` ${i + 1}. ${title}${score}`);
142
+ });
143
+ console.log('');
144
+ } catch (err) {
145
+ console.error(` Erreur HCM : ${err.message}\n`);
146
+ }
147
+ }