neurain 0.1.0-alpha.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 (89) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +57 -0
  3. package/README.md +205 -0
  4. package/SECURITY.md +22 -0
  5. package/bin/neurain.mjs +7 -0
  6. package/docs/comparison-mem0.en.md +22 -0
  7. package/docs/connect-claude.en.md +48 -0
  8. package/docs/connect-claude.kr.md +51 -0
  9. package/docs/connect-codex.en.md +38 -0
  10. package/docs/connect-codex.kr.md +40 -0
  11. package/docs/connect-gemini.en.md +71 -0
  12. package/docs/connect-gemini.kr.md +71 -0
  13. package/docs/connect-runtime.en.md +61 -0
  14. package/docs/connect-runtime.kr.md +61 -0
  15. package/docs/development-status.en.md +157 -0
  16. package/docs/development-status.kr.md +157 -0
  17. package/docs/knowledge-os.en.md +105 -0
  18. package/docs/knowledge-os.kr.md +106 -0
  19. package/docs/pricing.en.md +14 -0
  20. package/docs/privacy-and-data-flow.en.md +25 -0
  21. package/docs/public-saas-readiness.en.md +39 -0
  22. package/docs/quickstart.en.md +64 -0
  23. package/docs/quickstart.kr.md +64 -0
  24. package/docs/release-checklist.en.md +38 -0
  25. package/docs/safety.en.md +36 -0
  26. package/docs/self-improvement-90-roadmap.en.md +429 -0
  27. package/docs/self-improvement-90-roadmap.kr.md +429 -0
  28. package/docs/self-improving-workflows.en.md +163 -0
  29. package/docs/self-improving-workflows.kr.md +163 -0
  30. package/docs/support.en.md +17 -0
  31. package/docs/troubleshooting.en.md +35 -0
  32. package/package.json +36 -0
  33. package/src/cli.mjs +261 -0
  34. package/src/core/adopt.mjs +304 -0
  35. package/src/core/answer_eval.mjs +450 -0
  36. package/src/core/capabilities.mjs +217 -0
  37. package/src/core/capture_durable.mjs +181 -0
  38. package/src/core/classify.mjs +237 -0
  39. package/src/core/compile_desk.mjs +324 -0
  40. package/src/core/complete.mjs +108 -0
  41. package/src/core/config.mjs +142 -0
  42. package/src/core/connect.mjs +355 -0
  43. package/src/core/curator.mjs +351 -0
  44. package/src/core/daemon.mjs +536 -0
  45. package/src/core/digest.mjs +155 -0
  46. package/src/core/doctor.mjs +115 -0
  47. package/src/core/durable.mjs +96 -0
  48. package/src/core/envelope.mjs +97 -0
  49. package/src/core/flush.mjs +190 -0
  50. package/src/core/fs.mjs +121 -0
  51. package/src/core/init.mjs +194 -0
  52. package/src/core/journal.mjs +269 -0
  53. package/src/core/labels.mjs +117 -0
  54. package/src/core/lessons.mjs +793 -0
  55. package/src/core/lifecycle.mjs +1138 -0
  56. package/src/core/link_check.mjs +180 -0
  57. package/src/core/live_cases.mjs +221 -0
  58. package/src/core/onboard.mjs +175 -0
  59. package/src/core/plan_receipt.mjs +177 -0
  60. package/src/core/plan_writeback.mjs +176 -0
  61. package/src/core/queue.mjs +62 -0
  62. package/src/core/queue_archive.mjs +87 -0
  63. package/src/core/queue_model.mjs +161 -0
  64. package/src/core/queue_write.mjs +28 -0
  65. package/src/core/recall.mjs +1802 -0
  66. package/src/core/recall_bench.mjs +275 -0
  67. package/src/core/recall_corpus.mjs +152 -0
  68. package/src/core/recall_facts.mjs +233 -0
  69. package/src/core/recall_intel.mjs +233 -0
  70. package/src/core/recall_lexical.mjs +269 -0
  71. package/src/core/recap.mjs +78 -0
  72. package/src/core/review_queue.mjs +131 -0
  73. package/src/core/review_worker.mjs +284 -0
  74. package/src/core/route.mjs +73 -0
  75. package/src/core/safety.mjs +57 -0
  76. package/src/core/scheduler.mjs +697 -0
  77. package/src/core/search.mjs +54 -0
  78. package/src/core/secret_scan.mjs +143 -0
  79. package/src/core/semantic.mjs +187 -0
  80. package/src/core/source_digest.mjs +56 -0
  81. package/src/core/source_digest_gen.mjs +311 -0
  82. package/src/core/stage.mjs +105 -0
  83. package/src/core/status.mjs +175 -0
  84. package/src/core/vault_state.mjs +115 -0
  85. package/src/core/watch.mjs +282 -0
  86. package/src/core/wiki_log.mjs +29 -0
  87. package/src/core/wrap.mjs +62 -0
  88. package/src/mcp/server.mjs +865 -0
  89. package/templates/starter-vault/README.md +9 -0
@@ -0,0 +1,355 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { absPath } from './fs.mjs';
3
+
4
+ export async function connectCommand(args) {
5
+ const host = args._[0];
6
+ const root = absPath(args._[1] || args.root || process.cwd());
7
+ if (!['codex', 'claude', 'gemini', 'runtime'].includes(host)) throw new Error('connect supports: codex, claude, gemini, runtime');
8
+ const dryRun = Boolean(args['dry-run']);
9
+ const lifecycleHooks = Boolean(args['lifecycle-hooks'] || args.lifecycle);
10
+ if (lifecycleHooks) return lifecycleHooksConnect(host, root, { dryRun, json: Boolean(args.json), scope: args.scope || 'local' });
11
+ if (host === 'runtime') return runtimeConnect(root, { dryRun, json: Boolean(args.json) });
12
+ const command = connectShellCommand(host, root, args);
13
+ if (!dryRun) {
14
+ const [bin, ...cmdArgs] = command;
15
+ const result = spawnSync(bin, cmdArgs, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
16
+ if (result.status !== 0) {
17
+ throw new Error(`${host} MCP connect failed:\n${result.stderr || result.stdout || 'no output'}`);
18
+ }
19
+ }
20
+ const payload = { ok: true, host, root, dry_run: dryRun, command };
21
+ if (args.json) return { json: true, payload };
22
+ return {
23
+ text: [
24
+ `# Neurain connect ${host}${dryRun ? ' [dry-run]' : ''}`,
25
+ '',
26
+ `- Root: ${root}`,
27
+ `- Command: ${shellQuote(command)}`,
28
+ dryRun ? '- No host config changed.' : '- Host MCP config updated.',
29
+ ].join('\n'),
30
+ };
31
+ }
32
+
33
+ function connectShellCommand(host, root, args = {}) {
34
+ if (host === 'codex') return codexCommand(root);
35
+ if (host === 'claude') return claudeCommand(root, args.scope || 'local');
36
+ if (host === 'gemini') return geminiCommand(root, args.scope || 'project');
37
+ throw new Error(`Unsupported host: ${host}`);
38
+ }
39
+
40
+ export function codexCommand(root) {
41
+ return ['codex', 'mcp', 'add', 'neurain', '--env', `NEURAIN_ROOT=${root}`, '--', 'npx', '-y', 'neurain', 'mcp', '--root', root];
42
+ }
43
+
44
+ export function claudeCommand(root, scope = 'local') {
45
+ return ['claude', 'mcp', 'add', '--scope', scope, '-e', `NEURAIN_ROOT=${root}`, 'neurain', '--', 'npx', '-y', 'neurain', 'mcp', '--root', root];
46
+ }
47
+
48
+ export function geminiCommand(root, scope = 'project') {
49
+ return [
50
+ 'gemini',
51
+ 'mcp',
52
+ 'add',
53
+ '--scope',
54
+ scope,
55
+ '--env',
56
+ `NEURAIN_ROOT=${root}`,
57
+ '--include-tools',
58
+ geminiIncludedTools().join(','),
59
+ 'neurain',
60
+ 'npx',
61
+ '-y',
62
+ 'neurain',
63
+ 'mcp',
64
+ '--root',
65
+ root,
66
+ ];
67
+ }
68
+
69
+ export function geminiConfigSnippet(root) {
70
+ return `${JSON.stringify({
71
+ mcpServers: {
72
+ neurain: {
73
+ command: 'npx',
74
+ args: ['-y', 'neurain', 'mcp', '--root', root],
75
+ env: { NEURAIN_ROOT: root },
76
+ trust: false,
77
+ includeTools: geminiIncludedTools(),
78
+ },
79
+ },
80
+ }, null, 2)}\n`;
81
+ }
82
+
83
+ export function runtimeConfigSnippet(root) {
84
+ return [
85
+ 'mcp_servers:',
86
+ ' neurain:',
87
+ ' command: "npx"',
88
+ ` args: ["-y", "neurain", "mcp", "--root", ${JSON.stringify(root)}]`,
89
+ ' env:',
90
+ ` NEURAIN_ROOT: ${JSON.stringify(root)}`,
91
+ ' tools:',
92
+ ' include:',
93
+ ' - neurain_status',
94
+ ' - neurain_search',
95
+ ' - neurain_recall_search',
96
+ ' - neurain_recall_status',
97
+ ' - neurain_recall_verify',
98
+ ' - neurain_recall_cross_host_eval',
99
+ ' - neurain_answer_quality_eval',
100
+ ' - neurain_live_cases_scaffold',
101
+ ' - neurain_lifecycle_report',
102
+ ' - neurain_watch_report',
103
+ ' - neurain_review_worker',
104
+ ' - neurain_scheduler_tick',
105
+ ' - neurain_scheduler_eval',
106
+ ' - neurain_wrap_preview',
107
+ ' resources: false',
108
+ ' prompts: false',
109
+ ].join('\n');
110
+ }
111
+
112
+ export function geminiIncludedTools() {
113
+ return [
114
+ 'neurain_status',
115
+ 'neurain_search',
116
+ 'neurain_adopt_scan',
117
+ 'neurain_recap',
118
+ 'neurain_capabilities',
119
+ 'neurain_recall_status',
120
+ 'neurain_recall_search',
121
+ 'neurain_recall_semantic_search',
122
+ 'neurain_recall_hybrid_search',
123
+ 'neurain_recall_verify',
124
+ 'neurain_recall_cross_host_eval',
125
+ 'neurain_recall_semantic_eval',
126
+ 'neurain_answer_quality_eval',
127
+ 'neurain_live_cases_scaffold',
128
+ 'neurain_lifecycle_report',
129
+ 'neurain_lifecycle_eval',
130
+ 'neurain_lessons_list',
131
+ 'neurain_lessons_candidates',
132
+ 'neurain_lessons_eval',
133
+ 'neurain_watch_report',
134
+ 'neurain_review_worker',
135
+ 'neurain_scheduler_tick',
136
+ 'neurain_scheduler_eval',
137
+ 'neurain_wrap_preview',
138
+ ];
139
+ }
140
+
141
+ export function lifecycleHookCommand(host, root) {
142
+ return ['npx', '-y', 'neurain', 'lifecycle', 'hook', root, '--host', host, '--confirm', '1건 저장 진행', '--quiet'];
143
+ }
144
+
145
+ export function claudeLifecycleSettingsSnippet(root) {
146
+ const command = shellQuote(lifecycleHookCommand('claude', root));
147
+ return JSON.stringify({
148
+ hooks: {
149
+ SessionStart: [
150
+ {
151
+ matcher: 'startup|resume|clear|compact',
152
+ hooks: [{ type: 'command', command, timeout: 10 }],
153
+ },
154
+ ],
155
+ UserPromptSubmit: [
156
+ {
157
+ hooks: [{ type: 'command', command, timeout: 10 }],
158
+ },
159
+ ],
160
+ Stop: [
161
+ {
162
+ hooks: [{ type: 'command', command, timeout: 10 }],
163
+ },
164
+ ],
165
+ SessionEnd: [
166
+ {
167
+ hooks: [{ type: 'command', command, timeout: 10 }],
168
+ },
169
+ ],
170
+ },
171
+ }, null, 2);
172
+ }
173
+
174
+ export function codexLifecycleHooksSnippet(root) {
175
+ const command = shellQuote(lifecycleHookCommand('codex', root));
176
+ return JSON.stringify({
177
+ hooks: {
178
+ SessionStart: [
179
+ {
180
+ hooks: [
181
+ {
182
+ type: 'command',
183
+ command,
184
+ timeout: 10,
185
+ statusMessage: 'Neurain records session boundary metadata...',
186
+ },
187
+ ],
188
+ },
189
+ ],
190
+ PostToolUse: [
191
+ {
192
+ matcher: 'Edit|Write',
193
+ hooks: [
194
+ {
195
+ type: 'command',
196
+ command,
197
+ timeout: 10,
198
+ statusMessage: 'Neurain marks review due after durable edits...',
199
+ },
200
+ ],
201
+ },
202
+ ],
203
+ },
204
+ }, null, 2);
205
+ }
206
+
207
+ export function runtimeLifecycleProxySnippet(root) {
208
+ return JSON.stringify({
209
+ command: lifecycleHookCommand('runtime', root),
210
+ payload_contract: {
211
+ required: ['session_id', 'hook_event_name'],
212
+ supported_hook_event_name_values: [
213
+ 'session_start',
214
+ 'turn_start',
215
+ 'turn_end',
216
+ 'wrap_complete',
217
+ 'review_due',
218
+ 'review_complete',
219
+ 'compaction',
220
+ 'resume',
221
+ 'session_end',
222
+ ],
223
+ optional: ['turn_id', 'parent_session_id', 'source'],
224
+ forbidden_to_store: ['prompt', 'messages', 'transcript_path', 'stdout', 'stderr', 'tool_result'],
225
+ },
226
+ example_payload: {
227
+ session_id: 'runtime-session-1',
228
+ hook_event_name: 'turn_end',
229
+ turn_id: 'turn-1',
230
+ source: 'agent-loop-boundary',
231
+ },
232
+ }, null, 2);
233
+ }
234
+
235
+ function runtimeConnect(root, { dryRun, json }) {
236
+ if (!dryRun) {
237
+ throw new Error('Runtime connect alpha is config-preview only. Run with --dry-run, then adapt the snippet to the target host MCP config.');
238
+ }
239
+ const payload = {
240
+ ok: true,
241
+ host: 'runtime',
242
+ root,
243
+ dry_run: true,
244
+ config_path: 'host-managed MCP config',
245
+ reload_command: 'host-specific MCP reload',
246
+ config_snippet: runtimeConfigSnippet(root),
247
+ note: 'Runtime connector alpha emits a bounded Neurain MCP config snippet for an agent host that supports MCP. It does not edit any host config directly.',
248
+ };
249
+ if (json) return { json: true, payload };
250
+ return {
251
+ text: [
252
+ '# Neurain connect runtime [dry-run]',
253
+ '',
254
+ `- Root: ${root}`,
255
+ '- Config path: host-managed MCP config',
256
+ '- Reload: use the target host MCP reload flow',
257
+ '- No host config changed.',
258
+ '',
259
+ '```yaml',
260
+ payload.config_snippet,
261
+ '```',
262
+ ].join('\n'),
263
+ };
264
+ }
265
+
266
+ function lifecycleHooksConnect(host, root, { dryRun, json, scope }) {
267
+ if (!dryRun) {
268
+ throw new Error('Lifecycle hook connector alpha is preview-only. Run with --dry-run, inspect the snippet, and paste it into the host config yourself.');
269
+ }
270
+ const supported = ['claude', 'codex', 'runtime'].includes(host);
271
+ const payload = {
272
+ ok: true,
273
+ host,
274
+ root,
275
+ dry_run: true,
276
+ lifecycle_hooks: {
277
+ supported,
278
+ status: lifecycleHookStatus(host),
279
+ writes_host_config: false,
280
+ durable_neurain_write_when_hook_fires: supported,
281
+ stored_payload_body: false,
282
+ transcript_path_stored: false,
283
+ native_host_config_verified: supported && host !== 'runtime',
284
+ events: lifecycleHookEvents(host),
285
+ mapped_events: lifecycleHookMappedEvents(host),
286
+ },
287
+ manual_commands: manualLifecycleCommands(host, root),
288
+ };
289
+ if (host === 'claude') {
290
+ payload.config_path = scope === 'user' ? '~/.claude/settings.json' : scope === 'project' ? '.claude/settings.json' : '.claude/settings.local.json';
291
+ payload.settings_snippet = claudeLifecycleSettingsSnippet(root);
292
+ payload.note = 'Claude Code supports command hooks. This preview records only lifecycle boundary metadata and stores no prompt body, transcript path, or success stdout in model context.';
293
+ } else if (host === 'codex') {
294
+ payload.config_path = '.codex/hooks.json';
295
+ payload.settings_snippet = codexLifecycleHooksSnippet(root);
296
+ payload.note = 'Codex hook preview records SessionStart and marks review_due after Edit or Write PostToolUse. It stores no prompt body, transcript path, tool stdout, or tool stderr in model context.';
297
+ } else if (host === 'gemini') {
298
+ payload.config_path = '.gemini/settings.json';
299
+ payload.settings_snippet = geminiConfigSnippet(root);
300
+ payload.note = 'Gemini CLI MCP connection is supported, but lifecycle hooks are manual-only in this alpha. Use lifecycle emit commands when you need explicit Gemini boundary receipts.';
301
+ } else {
302
+ payload.config_path = 'host-managed runtime lifecycle bridge';
303
+ payload.settings_snippet = runtimeLifecycleProxySnippet(root);
304
+ payload.note = 'Runtime lifecycle bridge is a host-proxy contract, not a native config edit. An agent runtime can call the command with direct lifecycle boundary payloads while Neurain stores only metadata.';
305
+ }
306
+ if (json) return { json: true, payload };
307
+ const lines = [
308
+ `# Neurain connect ${host} lifecycle hooks [dry-run]`,
309
+ '',
310
+ `- Root: ${root}`,
311
+ `- Hook automation supported: ${supported ? 'yes' : 'no'}`,
312
+ '- No host config changed.',
313
+ `- Note: ${payload.note}`,
314
+ ];
315
+ if (payload.settings_snippet) {
316
+ lines.push('', `- Suggested config path: ${payload.config_path}`, '', '```json', payload.settings_snippet, '```');
317
+ }
318
+ lines.push('', '## Manual lifecycle commands', '', ...payload.manual_commands.map((command) => `- ${shellQuote(command)}`));
319
+ return { text: lines.join('\n') };
320
+ }
321
+
322
+ function lifecycleHookStatus(host) {
323
+ if (host === 'claude') return 'claude_code_settings_preview';
324
+ if (host === 'codex') return 'codex_hooks_json_preview';
325
+ if (host === 'runtime') return 'runtime_host_proxy_contract_preview';
326
+ if (host === 'gemini') return 'gemini_mcp_manual_lifecycle_only';
327
+ return 'manual_lifecycle_only';
328
+ }
329
+
330
+ function lifecycleHookEvents(host) {
331
+ if (host === 'claude') return ['SessionStart', 'UserPromptSubmit', 'Stop', 'SessionEnd'];
332
+ if (host === 'codex') return ['SessionStart', 'PostToolUse'];
333
+ if (host === 'runtime') return ['session_start', 'turn_start', 'turn_end', 'wrap_complete', 'review_due', 'review_complete', 'compaction', 'resume', 'session_end'];
334
+ return [];
335
+ }
336
+
337
+ function lifecycleHookMappedEvents(host) {
338
+ if (host === 'claude') return ['session_start', 'resume', 'compaction', 'turn_start', 'turn_end', 'session_end'];
339
+ if (host === 'codex') return ['session_start', 'resume', 'compaction', 'review_due'];
340
+ if (host === 'runtime') return lifecycleHookEvents(host);
341
+ return [];
342
+ }
343
+
344
+ function manualLifecycleCommands(host, root) {
345
+ const safeHost = ['codex', 'claude', 'gemini', 'runtime'].includes(host) ? host : 'generic';
346
+ return [
347
+ ['npx', '-y', 'neurain', 'lifecycle', 'emit', root, '--host', safeHost, '--event', 'session_start', '--session-id', `${safeHost}-session-1`, '--confirm', '1건 저장 진행'],
348
+ ['npx', '-y', 'neurain', 'lifecycle', 'emit', root, '--host', safeHost, '--event', 'turn_end', '--session-id', `${safeHost}-session-1`, '--turn-id', 'turn-1', '--confirm', '1건 저장 진행'],
349
+ ['npx', '-y', 'neurain', 'lifecycle', 'report', root, '--host', safeHost],
350
+ ];
351
+ }
352
+
353
+ function shellQuote(parts) {
354
+ return parts.map((part) => /^[A-Za-z0-9_./:=@-]+$/.test(part) ? part : JSON.stringify(part)).join(' ');
355
+ }