iranti 0.3.0 → 0.3.2
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/README.md +843 -839
- package/bin/iranti.js +1 -1
- package/dist/scripts/claude-code-memory-hook.js +41 -153
- package/dist/scripts/iranti-cli.js +86 -9
- package/dist/scripts/iranti-mcp.js +141 -69
- package/dist/scripts/seed.js +1 -1
- package/dist/src/api/middleware/validation.d.ts.map +1 -1
- package/dist/src/api/middleware/validation.js +13 -1
- package/dist/src/api/middleware/validation.js.map +1 -1
- package/dist/src/api/routes/knowledge.d.ts.map +1 -1
- package/dist/src/api/routes/knowledge.js +3 -0
- package/dist/src/api/routes/knowledge.js.map +1 -1
- package/dist/src/api/routes/memory.d.ts.map +1 -1
- package/dist/src/api/routes/memory.js +3 -0
- package/dist/src/api/routes/memory.js.map +1 -1
- package/dist/src/api/server.js +1 -1
- package/dist/src/archivist/index.js +9 -9
- package/dist/src/attendant/AttendantInstance.d.ts +42 -1
- package/dist/src/attendant/AttendantInstance.d.ts.map +1 -1
- package/dist/src/attendant/AttendantInstance.js +496 -90
- package/dist/src/attendant/AttendantInstance.js.map +1 -1
- package/dist/src/attendant/index.d.ts +1 -1
- package/dist/src/attendant/index.d.ts.map +1 -1
- package/dist/src/attendant/index.js.map +1 -1
- package/dist/src/chat/index.d.ts +2 -0
- package/dist/src/chat/index.d.ts.map +1 -1
- package/dist/src/chat/index.js +56 -22
- package/dist/src/chat/index.js.map +1 -1
- package/dist/src/lib/assistantCheckpoint.d.ts +21 -0
- package/dist/src/lib/assistantCheckpoint.d.ts.map +1 -0
- package/dist/src/lib/assistantCheckpoint.js +143 -0
- package/dist/src/lib/assistantCheckpoint.js.map +1 -0
- package/dist/src/lib/cliHelpCatalog.js +2 -2
- package/dist/src/lib/cliHelpCatalog.js.map +1 -1
- package/dist/src/lib/hostMemoryFormatting.d.ts +25 -0
- package/dist/src/lib/hostMemoryFormatting.d.ts.map +1 -0
- package/dist/src/lib/hostMemoryFormatting.js +55 -0
- package/dist/src/lib/hostMemoryFormatting.js.map +1 -0
- package/dist/src/lib/projectLearning.d.ts +21 -0
- package/dist/src/lib/projectLearning.d.ts.map +1 -0
- package/dist/src/lib/projectLearning.js +357 -0
- package/dist/src/lib/projectLearning.js.map +1 -0
- package/dist/src/lib/protocolEnforcement.d.ts +3 -1
- package/dist/src/lib/protocolEnforcement.d.ts.map +1 -1
- package/dist/src/lib/protocolEnforcement.js +28 -2
- package/dist/src/lib/protocolEnforcement.js.map +1 -1
- package/dist/src/lib/sessionLedger.d.ts +18 -0
- package/dist/src/lib/sessionLedger.d.ts.map +1 -1
- package/dist/src/lib/sessionLedger.js +78 -0
- package/dist/src/lib/sessionLedger.js.map +1 -1
- package/dist/src/librarian/index.d.ts.map +1 -1
- package/dist/src/librarian/index.js +102 -51
- package/dist/src/librarian/index.js.map +1 -1
- package/dist/src/library/queries.js +56 -56
- package/dist/src/sdk/index.d.ts +2 -0
- package/dist/src/sdk/index.d.ts.map +1 -1
- package/dist/src/sdk/index.js +39 -2
- package/dist/src/sdk/index.js.map +1 -1
- package/package.json +9 -5
package/bin/iranti.js
CHANGED
|
@@ -11,6 +11,8 @@ const runtimeEnv_1 = require("../src/lib/runtimeEnv");
|
|
|
11
11
|
const staffEventRegistry_1 = require("../src/lib/staffEventRegistry");
|
|
12
12
|
const client_1 = require("../src/library/client");
|
|
13
13
|
const autoRemember_1 = require("../src/lib/autoRemember");
|
|
14
|
+
const assistantCheckpoint_1 = require("../src/lib/assistantCheckpoint");
|
|
15
|
+
const hostMemoryFormatting_1 = require("../src/lib/hostMemoryFormatting");
|
|
14
16
|
const MEMORY_NEED_POSITIVE_PATTERNS = [
|
|
15
17
|
/\bwhat(?:'s| is| was)?\s+my\b/i,
|
|
16
18
|
/\bdo you remember\b/i,
|
|
@@ -41,7 +43,7 @@ function printHelp() {
|
|
|
41
43
|
'',
|
|
42
44
|
'Reads Claude Code hook JSON from stdin and returns hookSpecificOutput.additionalContext on stdout.',
|
|
43
45
|
'This helper retrieves working memory; durable KB writes still require explicit iranti_write/ingest calls.',
|
|
44
|
-
'Set IRANTI_AUTO_REMEMBER=true to auto-save
|
|
46
|
+
'Set IRANTI_AUTO_REMEMBER=true to auto-save prompt-side personal/project facts. Assistant-response continuity facts and shared checkpoints are captured on Stop regardless.',
|
|
45
47
|
].join('\n'));
|
|
46
48
|
}
|
|
47
49
|
function parseArgs(argv) {
|
|
@@ -229,19 +231,28 @@ function getMaxFacts() {
|
|
|
229
231
|
return Math.min(12, Math.trunc(raw));
|
|
230
232
|
}
|
|
231
233
|
function formatSessionContext(facts, cwd) {
|
|
232
|
-
const limited = facts.slice(0, getMaxFacts())
|
|
234
|
+
const limited = (0, hostMemoryFormatting_1.assignStructuredFactIds)(facts.slice(0, getMaxFacts()).map((fact) => ({
|
|
235
|
+
...fact,
|
|
236
|
+
entityKey: `${fact.entity}/${fact.key}`,
|
|
237
|
+
})));
|
|
233
238
|
const lines = [
|
|
234
239
|
'[Iranti Session Memory]',
|
|
235
240
|
`Project: ${path_1.default.basename(cwd)}`,
|
|
236
241
|
'REQUIRED: Call mcp__iranti__iranti_handshake before responding to the first user message.',
|
|
237
|
-
'REQUIRED: Call mcp__iranti__iranti_attend before every
|
|
242
|
+
'REQUIRED: Call mcp__iranti__iranti_attend(phase=\'pre-response\') before every reply and before factual discovery.',
|
|
243
|
+
'REQUIRED: After every response, call mcp__iranti__iranti_attend(phase=\'post-response\').',
|
|
244
|
+
'REQUIRED: Prefer injected Iranti facts before re-inferring project state.',
|
|
238
245
|
];
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
const block = (0, hostMemoryFormatting_1.formatStructuredFactBlock)(limited, {
|
|
247
|
+
title: 'Iranti Session Facts',
|
|
248
|
+
introLines: [
|
|
249
|
+
'Use these loaded facts as the starting working-memory frame for this session.',
|
|
250
|
+
'Prefer them before re-inferring project state.',
|
|
251
|
+
'Fact IDs are stable only within this block.',
|
|
252
|
+
],
|
|
253
|
+
});
|
|
254
|
+
if (block)
|
|
255
|
+
lines.push(block);
|
|
245
256
|
return lines.join('\n');
|
|
246
257
|
}
|
|
247
258
|
function formatPreCompactContext() {
|
|
@@ -270,154 +281,22 @@ function extractSelfMemoryQueryKey(prompt) {
|
|
|
270
281
|
function formatPromptContext(facts, prompt) {
|
|
271
282
|
if (facts.length === 0)
|
|
272
283
|
return '';
|
|
273
|
-
const
|
|
284
|
+
const structuredFacts = (0, hostMemoryFormatting_1.assignStructuredFactIds)(facts.map((fact) => ({
|
|
285
|
+
...fact,
|
|
286
|
+
entityKey: `${fact.entity}/${fact.key}`,
|
|
287
|
+
})));
|
|
288
|
+
const lines = [];
|
|
274
289
|
const targetKey = prompt ? extractSelfMemoryQueryKey(prompt) : null;
|
|
275
290
|
if (targetKey) {
|
|
276
|
-
const answerCandidate =
|
|
291
|
+
const answerCandidate = structuredFacts.find((fact) => (0, autoRemember_1.canonicalizeMemoryKey)(fact.entityKey.split('/').slice(2).join('/')) === targetKey);
|
|
277
292
|
if (answerCandidate) {
|
|
278
|
-
lines.push(`Direct
|
|
279
|
-
lines.push(
|
|
293
|
+
lines.push(`[Iranti Direct Answer]`);
|
|
294
|
+
lines.push(`Use ${answerCandidate.factId} directly if it fully answers the user question: ${answerCandidate.summary}.`);
|
|
280
295
|
}
|
|
281
296
|
}
|
|
282
|
-
|
|
283
|
-
lines.push(`- ${fact.entity}/${fact.key}: ${fact.summary}`);
|
|
284
|
-
}
|
|
297
|
+
lines.push((0, hostMemoryFormatting_1.formatStructuredFactBlock)(structuredFacts, { title: 'Iranti Retrieved Memory' }));
|
|
285
298
|
return lines.join('\n');
|
|
286
299
|
}
|
|
287
|
-
function readTextField(value, preferredKey) {
|
|
288
|
-
if (typeof value !== 'object' || value === null)
|
|
289
|
-
return undefined;
|
|
290
|
-
const record = value;
|
|
291
|
-
const raw = record[preferredKey];
|
|
292
|
-
return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
|
|
293
|
-
}
|
|
294
|
-
function readItems(value) {
|
|
295
|
-
if (typeof value !== 'object' || value === null)
|
|
296
|
-
return [];
|
|
297
|
-
const record = value;
|
|
298
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
299
|
-
return items.map((item) => String(item ?? '').trim()).filter(Boolean);
|
|
300
|
-
}
|
|
301
|
-
function readFileChangeOutputs(value) {
|
|
302
|
-
if (typeof value !== 'object' || value === null)
|
|
303
|
-
return [];
|
|
304
|
-
const record = value;
|
|
305
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
306
|
-
return items.map((item) => {
|
|
307
|
-
if (typeof item !== 'object' || item === null)
|
|
308
|
-
return '';
|
|
309
|
-
const change = item;
|
|
310
|
-
const action = String(change.action ?? 'updated').trim();
|
|
311
|
-
const targetPath = String(change.path ?? '').trim();
|
|
312
|
-
const toPath = String(change.toPath ?? '').trim();
|
|
313
|
-
if (!targetPath)
|
|
314
|
-
return '';
|
|
315
|
-
return toPath ? `${action} ${targetPath} -> ${toPath}` : `${action} ${targetPath}`;
|
|
316
|
-
}).filter(Boolean);
|
|
317
|
-
}
|
|
318
|
-
function readCheckpointActions(value) {
|
|
319
|
-
if (typeof value !== 'object' || value === null)
|
|
320
|
-
return [];
|
|
321
|
-
const record = value;
|
|
322
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
323
|
-
return items
|
|
324
|
-
.map((item) => {
|
|
325
|
-
if (typeof item !== 'object' || item === null)
|
|
326
|
-
return null;
|
|
327
|
-
const action = item;
|
|
328
|
-
const summary = String(action.summary ?? '').trim();
|
|
329
|
-
if (!summary)
|
|
330
|
-
return null;
|
|
331
|
-
const kind = String(action.kind ?? 'action').trim() || 'action';
|
|
332
|
-
return {
|
|
333
|
-
kind,
|
|
334
|
-
summary,
|
|
335
|
-
...(typeof action.status === 'string' && action.status.trim() ? { status: action.status.trim() } : {}),
|
|
336
|
-
...(typeof action.target === 'string' && action.target.trim() ? { target: action.target.trim() } : {}),
|
|
337
|
-
...(typeof action.detail === 'string' && action.detail.trim() ? { detail: action.detail.trim() } : {}),
|
|
338
|
-
};
|
|
339
|
-
})
|
|
340
|
-
.filter((item) => Boolean(item));
|
|
341
|
-
}
|
|
342
|
-
function readActionOutputs(value) {
|
|
343
|
-
if (typeof value !== 'object' || value === null)
|
|
344
|
-
return [];
|
|
345
|
-
const record = value;
|
|
346
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
347
|
-
return items.map((item) => {
|
|
348
|
-
if (typeof item !== 'object' || item === null)
|
|
349
|
-
return '';
|
|
350
|
-
const action = item;
|
|
351
|
-
const kind = String(action.kind ?? 'action').trim() || 'action';
|
|
352
|
-
const summary = String(action.summary ?? '').trim();
|
|
353
|
-
const status = String(action.status ?? '').trim();
|
|
354
|
-
if (!summary)
|
|
355
|
-
return '';
|
|
356
|
-
return status ? `[${status}] ${kind}: ${summary}` : `${kind}: ${summary}`;
|
|
357
|
-
}).filter(Boolean);
|
|
358
|
-
}
|
|
359
|
-
function extractHookCheckpointPayload(response) {
|
|
360
|
-
const facts = (0, autoRemember_1.extractExplicitAssistantMemory)(response).filter((fact) => fact.scope === 'project');
|
|
361
|
-
if (facts.length === 0) {
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
const checkpoint = {};
|
|
365
|
-
const outputs = [];
|
|
366
|
-
for (const fact of facts) {
|
|
367
|
-
if (fact.key === 'current_step') {
|
|
368
|
-
checkpoint.currentStep = readTextField(fact.value, 'text');
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
if (fact.key === 'next_step') {
|
|
372
|
-
checkpoint.nextStep = readTextField(fact.value, 'instruction') ?? readTextField(fact.value, 'text');
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
if (fact.key === 'open_risks') {
|
|
376
|
-
checkpoint.openRisks = readItems(fact.value);
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
if (fact.key === 'important_artifacts') {
|
|
380
|
-
outputs.push(...readItems(fact.value));
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (fact.key === 'recent_file_changes') {
|
|
384
|
-
const fileChanges = typeof fact.value === 'object' && fact.value !== null && Array.isArray(fact.value.items)
|
|
385
|
-
? fact.value.items
|
|
386
|
-
.filter((item) => item && typeof item === 'object')
|
|
387
|
-
.map((item) => ({
|
|
388
|
-
action: String(item.action ?? 'updated').trim() || 'updated',
|
|
389
|
-
path: String(item.path ?? '').trim(),
|
|
390
|
-
...(typeof item.toPath === 'string' && item.toPath.trim() ? { toPath: String(item.toPath).trim() } : {}),
|
|
391
|
-
...(typeof item.purpose === 'string' && item.purpose.trim() ? { purpose: String(item.purpose).trim() } : {}),
|
|
392
|
-
}))
|
|
393
|
-
.filter((item) => item.path)
|
|
394
|
-
: [];
|
|
395
|
-
if (fileChanges.length > 0) {
|
|
396
|
-
checkpoint.fileChanges = [...(checkpoint.fileChanges ?? []), ...fileChanges];
|
|
397
|
-
}
|
|
398
|
-
outputs.push(...readFileChangeOutputs(fact.value));
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
if (fact.key === 'recent_actions') {
|
|
402
|
-
const actions = readCheckpointActions(fact.value);
|
|
403
|
-
if (actions.length > 0) {
|
|
404
|
-
checkpoint.actions = [...(checkpoint.actions ?? []), ...actions];
|
|
405
|
-
}
|
|
406
|
-
outputs.push(...readActionOutputs(fact.value));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
if (outputs.length > 0) {
|
|
410
|
-
checkpoint.recentOutputs = outputs;
|
|
411
|
-
}
|
|
412
|
-
return checkpoint.currentStep
|
|
413
|
-
|| checkpoint.nextStep
|
|
414
|
-
|| (checkpoint.openRisks && checkpoint.openRisks.length > 0)
|
|
415
|
-
|| (checkpoint.recentOutputs && checkpoint.recentOutputs.length > 0)
|
|
416
|
-
|| (checkpoint.actions && checkpoint.actions.length > 0)
|
|
417
|
-
|| (checkpoint.fileChanges && checkpoint.fileChanges.length > 0)
|
|
418
|
-
? checkpoint
|
|
419
|
-
: null;
|
|
420
|
-
}
|
|
421
300
|
function emitHookContext(event, additionalContext) {
|
|
422
301
|
const payload = {
|
|
423
302
|
hookSpecificOutput: {
|
|
@@ -523,8 +402,8 @@ async function buildHookAdditionalContext(options) {
|
|
|
523
402
|
}
|
|
524
403
|
if (event === 'Stop') {
|
|
525
404
|
const response = getLastAssistantMessage(payload);
|
|
526
|
-
if (response
|
|
527
|
-
await (0, autoRemember_1.
|
|
405
|
+
if (response) {
|
|
406
|
+
await (0, autoRemember_1.rememberAssistantResponseFacts)({
|
|
528
407
|
iranti,
|
|
529
408
|
response,
|
|
530
409
|
agent,
|
|
@@ -534,7 +413,7 @@ async function buildHookAdditionalContext(options) {
|
|
|
534
413
|
host: 'claude_code',
|
|
535
414
|
},
|
|
536
415
|
});
|
|
537
|
-
const checkpoint =
|
|
416
|
+
const checkpoint = (0, assistantCheckpoint_1.extractAssistantCheckpointPayload)(response);
|
|
538
417
|
const projectEntity = (0, autoRemember_1.getProjectMemoryEntity)();
|
|
539
418
|
if (checkpoint && projectEntity && typeof iranti.checkpoint === 'function') {
|
|
540
419
|
await iranti.checkpoint({
|
|
@@ -547,6 +426,14 @@ async function buildHookAdditionalContext(options) {
|
|
|
547
426
|
},
|
|
548
427
|
});
|
|
549
428
|
}
|
|
429
|
+
await iranti.attend({
|
|
430
|
+
agent,
|
|
431
|
+
latestMessage: response,
|
|
432
|
+
currentContext: response,
|
|
433
|
+
entityHints,
|
|
434
|
+
maxFacts: getMaxFacts(),
|
|
435
|
+
phase: 'post-response',
|
|
436
|
+
});
|
|
550
437
|
}
|
|
551
438
|
return '';
|
|
552
439
|
}
|
|
@@ -575,6 +462,7 @@ async function buildHookAdditionalContext(options) {
|
|
|
575
462
|
currentContext: buildCurrentContext(payload, prompt),
|
|
576
463
|
entityHints,
|
|
577
464
|
maxFacts: getMaxFacts(),
|
|
465
|
+
phase: 'pre-response',
|
|
578
466
|
});
|
|
579
467
|
const facts = attend.facts.map((fact) => ({
|
|
580
468
|
entity: fact.entityKey.split('/').slice(0, 2).join('/'),
|
|
@@ -34,6 +34,7 @@ const autoRemember_1 = require("../src/lib/autoRemember");
|
|
|
34
34
|
const semanticFactTags_1 = require("../src/lib/semanticFactTags");
|
|
35
35
|
const staffEventRegistry_1 = require("../src/lib/staffEventRegistry");
|
|
36
36
|
const scaffoldCloseout_1 = require("../src/lib/scaffoldCloseout");
|
|
37
|
+
const projectLearning_1 = require("../src/lib/projectLearning");
|
|
37
38
|
class CliError extends Error {
|
|
38
39
|
constructor(code, message, hints = [], details) {
|
|
39
40
|
super(message);
|
|
@@ -1104,10 +1105,14 @@ async function writeProjectBinding(projectPath, updates) {
|
|
|
1104
1105
|
const previousBinding = fs_1.default.existsSync(outFile)
|
|
1105
1106
|
? await readEnvFile(outFile).catch(() => ({}))
|
|
1106
1107
|
: {};
|
|
1108
|
+
const normalizedUpdates = {
|
|
1109
|
+
...updates,
|
|
1110
|
+
IRANTI_CODEBASE_ENTITY: updates.IRANTI_CODEBASE_ENTITY ?? previousBinding.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath),
|
|
1111
|
+
};
|
|
1107
1112
|
if (!fs_1.default.existsSync(outFile)) {
|
|
1108
1113
|
await writeText(outFile, '# Iranti project binding\n');
|
|
1109
1114
|
}
|
|
1110
|
-
await upsertEnvFile(outFile,
|
|
1115
|
+
await upsertEnvFile(outFile, normalizedUpdates);
|
|
1111
1116
|
await ensureProjectGitignore(projectPath);
|
|
1112
1117
|
const writtenBinding = await readEnvFile(outFile).catch(() => ({}));
|
|
1113
1118
|
await syncProjectBindingRegistry(projectPath, writtenBinding, previousBinding);
|
|
@@ -2132,6 +2137,11 @@ function printAttendResult(target, latestMessage, result) {
|
|
|
2132
2137
|
console.log(` confidence ${result.decision.confidence}`);
|
|
2133
2138
|
console.log(` explanation ${result.decision.explanation}`);
|
|
2134
2139
|
console.log(` facts ${result.facts.length}`);
|
|
2140
|
+
if ((result.memoryAttributions?.length ?? 0) > 0) {
|
|
2141
|
+
const firstAttribution = result.memoryAttributions[0];
|
|
2142
|
+
console.log(` injection id ${firstAttribution.injectionId}`);
|
|
2143
|
+
console.log(` attribution ${firstAttribution.status} surfaced=${firstAttribution.surfaced} used=${firstAttribution.used} helpful=${firstAttribution.helpful}`);
|
|
2144
|
+
}
|
|
2135
2145
|
if (result.facts.length === 0) {
|
|
2136
2146
|
console.log('');
|
|
2137
2147
|
console.log('No facts selected for injection.');
|
|
@@ -2506,6 +2516,10 @@ async function isPortAvailable(port, host = '0.0.0.0') {
|
|
|
2506
2516
|
});
|
|
2507
2517
|
}
|
|
2508
2518
|
function listPublishedDockerHostPorts() {
|
|
2519
|
+
const injected = process.env.IRANTI_FAKE_DOCKER_PORTS?.trim();
|
|
2520
|
+
if (injected) {
|
|
2521
|
+
return (0, dockerCliParsing_1.parsePublishedDockerHostPorts)(injected);
|
|
2522
|
+
}
|
|
2509
2523
|
const docker = inspectDockerAvailability();
|
|
2510
2524
|
if (!docker.daemonReachable)
|
|
2511
2525
|
return new Set();
|
|
@@ -2756,12 +2770,21 @@ async function executeSetupPlan(plan) {
|
|
|
2756
2770
|
IRANTI_INSTANCE: plan.instanceName,
|
|
2757
2771
|
IRANTI_INSTANCE_ENV: configured.envFile,
|
|
2758
2772
|
});
|
|
2773
|
+
const bindingEnv = await readEnvFile(written);
|
|
2774
|
+
const learningStatus = await (0, projectLearning_1.writeProjectLearningSnapshot)({
|
|
2775
|
+
projectPath,
|
|
2776
|
+
projectEnvFile: written,
|
|
2777
|
+
binding: bindingEnv,
|
|
2778
|
+
agentId: project.agentId,
|
|
2779
|
+
});
|
|
2759
2780
|
bindings.push({
|
|
2760
2781
|
projectPath,
|
|
2761
2782
|
envFile: written,
|
|
2762
2783
|
agentId: project.agentId,
|
|
2763
2784
|
projectMode: project.projectMode,
|
|
2764
2785
|
autoRemember: project.autoRemember,
|
|
2786
|
+
codebaseEntity: bindingEnv.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath),
|
|
2787
|
+
learningStatus,
|
|
2765
2788
|
});
|
|
2766
2789
|
if (project.claudeCode) {
|
|
2767
2790
|
await writeClaudeCodeProjectFiles(projectPath);
|
|
@@ -2790,7 +2813,7 @@ async function executeSetupPlan(plan) {
|
|
|
2790
2813
|
bindings,
|
|
2791
2814
|
};
|
|
2792
2815
|
}
|
|
2793
|
-
function parseSetupConfig(filePath) {
|
|
2816
|
+
async function parseSetupConfig(filePath) {
|
|
2794
2817
|
const resolved = path_1.default.resolve(filePath);
|
|
2795
2818
|
if (!fs_1.default.existsSync(resolved)) {
|
|
2796
2819
|
throw new Error(`Setup config file not found: ${resolved}`);
|
|
@@ -2812,7 +2835,7 @@ function parseSetupConfig(filePath) {
|
|
|
2812
2835
|
: databaseModeRaw === 'existing' || databaseModeRaw === 'local' || databaseModeRaw.length === 0
|
|
2813
2836
|
? 'local'
|
|
2814
2837
|
: (() => { throw new Error(`Unsupported databaseMode in setup config: ${databaseModeRaw}`); })();
|
|
2815
|
-
const databaseUrl =
|
|
2838
|
+
const databaseUrl = await resolveSetupDatabaseUrl(databaseMode, instanceName, String(raw?.databaseUrl ?? raw?.dbUrl ?? '').trim());
|
|
2816
2839
|
const databaseIntentStrategy = parseDatabaseIntentStrategyFlag(String(raw?.databaseIntent ?? raw?.dbIntent ?? '').trim(), 'databaseIntent');
|
|
2817
2840
|
const provider = normalizeProvider(String(raw?.provider ?? 'mock')) ?? 'mock';
|
|
2818
2841
|
if (!isSupportedProvider(provider)) {
|
|
@@ -2874,7 +2897,7 @@ function parseSetupConfig(filePath) {
|
|
|
2874
2897
|
}),
|
|
2875
2898
|
};
|
|
2876
2899
|
}
|
|
2877
|
-
function defaultsSetupPlan(args) {
|
|
2900
|
+
async function defaultsSetupPlan(args) {
|
|
2878
2901
|
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
2879
2902
|
const root = path_1.default.resolve(getFlag(args, 'root') ?? resolveInstallRoot(args, scope));
|
|
2880
2903
|
const mode = getFlag(args, 'mode') === 'shared' ? 'shared' : 'isolated';
|
|
@@ -2901,7 +2924,7 @@ function defaultsSetupPlan(args) {
|
|
|
2901
2924
|
const explicitDatabaseUrl = (getFlag(args, 'db-url')
|
|
2902
2925
|
?? (databaseMode === 'managed' ? process.env.DATABASE_URL : '')
|
|
2903
2926
|
?? '').trim();
|
|
2904
|
-
const databaseUrl =
|
|
2927
|
+
const databaseUrl = await resolveSetupDatabaseUrl(databaseMode, instanceName, explicitDatabaseUrl);
|
|
2905
2928
|
const databaseIntentStrategy = parseDatabaseIntentStrategyFlag(getFlag(args, 'db-intent'), '--db-intent');
|
|
2906
2929
|
const provider = normalizeProvider(getFlag(args, 'provider') ?? process.env.LLM_PROVIDER ?? 'mock') ?? 'mock';
|
|
2907
2930
|
if (!isSupportedProvider(provider)) {
|
|
@@ -3491,6 +3514,23 @@ function deriveDatabaseUrlForMode(mode, instanceName, explicitDatabaseUrl) {
|
|
|
3491
3514
|
}
|
|
3492
3515
|
return `postgresql://${user}:${password}@${localDatabaseHost}:5432/iranti_${instanceName}`;
|
|
3493
3516
|
}
|
|
3517
|
+
async function resolveSetupDatabaseUrl(mode, instanceName, explicitDatabaseUrl) {
|
|
3518
|
+
if (mode !== 'docker') {
|
|
3519
|
+
return deriveDatabaseUrlForMode(mode, instanceName, explicitDatabaseUrl);
|
|
3520
|
+
}
|
|
3521
|
+
const explicit = explicitDatabaseUrl?.trim() ?? '';
|
|
3522
|
+
if (explicit && !detectPlaceholder(explicit)) {
|
|
3523
|
+
return explicit;
|
|
3524
|
+
}
|
|
3525
|
+
const preferredPort = 5432;
|
|
3526
|
+
const dockerPublishedPorts = listPublishedDockerHostPorts();
|
|
3527
|
+
const selectedPort = await isPortUsable(preferredPort, '0.0.0.0', dockerPublishedPorts)
|
|
3528
|
+
? preferredPort
|
|
3529
|
+
: await findNextAvailablePort(preferredPort + 1, '0.0.0.0', 50, dockerPublishedPorts);
|
|
3530
|
+
const user = encodeURIComponent((process.env.POSTGRES_USER ?? 'postgres').trim() || 'postgres');
|
|
3531
|
+
const password = encodeURIComponent((process.env.POSTGRES_PASSWORD ?? 'postgres').trim() || 'postgres');
|
|
3532
|
+
return `postgresql://${user}:${password}@${preferredLocalDatabaseHost()}:${selectedPort}/iranti_${instanceName}`;
|
|
3533
|
+
}
|
|
3494
3534
|
function preferredLocalDatabaseHost() {
|
|
3495
3535
|
return process.platform === 'win32' ? '127.0.0.1' : 'localhost';
|
|
3496
3536
|
}
|
|
@@ -5002,7 +5042,7 @@ async function setupCommand(args) {
|
|
|
5002
5042
|
const dependencyChecks = await collectDependencyChecks();
|
|
5003
5043
|
printDependencyChecks(dependencyChecks);
|
|
5004
5044
|
console.log('');
|
|
5005
|
-
const plan = configPath ? parseSetupConfig(configPath) : defaultsSetupPlan(args);
|
|
5045
|
+
const plan = configPath ? await parseSetupConfig(configPath) : await defaultsSetupPlan(args);
|
|
5006
5046
|
const result = await executeSetupPlan(plan);
|
|
5007
5047
|
console.log(sectionTitle('Setup Complete'));
|
|
5008
5048
|
console.log(` runtime root ${result.root}`);
|
|
@@ -5019,7 +5059,10 @@ async function setupCommand(args) {
|
|
|
5019
5059
|
else {
|
|
5020
5060
|
console.log(' projects');
|
|
5021
5061
|
for (const binding of result.bindings) {
|
|
5022
|
-
|
|
5062
|
+
const learningSuffix = binding.learningStatus.status === 'written'
|
|
5063
|
+
? `, learned=${binding.codebaseEntity}`
|
|
5064
|
+
: `, project-learning=${binding.learningStatus.status}`;
|
|
5065
|
+
console.log(` - ${binding.projectPath} (${binding.agentId}, ${binding.projectMode}${learningSuffix})`);
|
|
5023
5066
|
}
|
|
5024
5067
|
}
|
|
5025
5068
|
printNextSteps([
|
|
@@ -5382,7 +5425,10 @@ async function setupCommand(args) {
|
|
|
5382
5425
|
else {
|
|
5383
5426
|
console.log(' projects');
|
|
5384
5427
|
for (const binding of finalResult.bindings) {
|
|
5385
|
-
|
|
5428
|
+
const learningSuffix = binding.learningStatus.status === 'written'
|
|
5429
|
+
? `, codebase=${binding.codebaseEntity}`
|
|
5430
|
+
: `, project-learning=${binding.learningStatus.status}`;
|
|
5431
|
+
console.log(` - ${binding.projectPath} (${binding.agentId}, ${binding.projectMode}, auto-remember=${binding.autoRemember ? 'true' : 'false'}${learningSuffix})`);
|
|
5386
5432
|
}
|
|
5387
5433
|
}
|
|
5388
5434
|
const nextSteps = [
|
|
@@ -6557,10 +6603,21 @@ async function projectInitCommand(args) {
|
|
|
6557
6603
|
IRANTI_INSTANCE: instanceName,
|
|
6558
6604
|
IRANTI_INSTANCE_ENV: envFile,
|
|
6559
6605
|
});
|
|
6606
|
+
const writtenBinding = await readEnvFile(outFile);
|
|
6607
|
+
const learningStatus = await (0, projectLearning_1.writeProjectLearningSnapshot)({
|
|
6608
|
+
projectPath,
|
|
6609
|
+
projectEnvFile: outFile,
|
|
6610
|
+
binding: writtenBinding,
|
|
6611
|
+
agentId,
|
|
6612
|
+
});
|
|
6560
6613
|
console.log(sectionTitle('Project Initialized'));
|
|
6561
6614
|
console.log(` status ${okLabel()}`);
|
|
6562
6615
|
console.log(` wrote ${outFile}`);
|
|
6563
6616
|
console.log(` mode ${projectMode}`);
|
|
6617
|
+
console.log(` codebase ${writtenBinding.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath)}`);
|
|
6618
|
+
if (learningStatus.status !== 'written') {
|
|
6619
|
+
console.log(` learn ${paint(learningStatus.status, learningStatus.status === 'failed' ? 'red' : 'yellow')} (${learningStatus.detail})`);
|
|
6620
|
+
}
|
|
6564
6621
|
printNextSteps([
|
|
6565
6622
|
`iranti doctor --instance ${instanceName}`,
|
|
6566
6623
|
'iranti chat',
|
|
@@ -6859,6 +6916,13 @@ async function configureProjectCommand(args) {
|
|
|
6859
6916
|
throw new Error('Unable to determine IRANTI_API_KEY. Provide --api-key <token> or configure the instance first.');
|
|
6860
6917
|
}
|
|
6861
6918
|
const written = await writeProjectBinding(projectPath, updates);
|
|
6919
|
+
const writtenBinding = await readEnvFile(written);
|
|
6920
|
+
const learningStatus = await (0, projectLearning_1.writeProjectLearningSnapshot)({
|
|
6921
|
+
projectPath,
|
|
6922
|
+
projectEnvFile: written,
|
|
6923
|
+
binding: writtenBinding,
|
|
6924
|
+
agentId: updates.IRANTI_AGENT_ID,
|
|
6925
|
+
});
|
|
6862
6926
|
const json = hasFlag(args, 'json');
|
|
6863
6927
|
const result = {
|
|
6864
6928
|
projectPath,
|
|
@@ -6868,6 +6932,8 @@ async function configureProjectCommand(args) {
|
|
|
6868
6932
|
autoRemember: updates.IRANTI_AUTO_REMEMBER === 'true',
|
|
6869
6933
|
projectMode: updates.IRANTI_PROJECT_MODE,
|
|
6870
6934
|
instance: updates.IRANTI_INSTANCE ?? null,
|
|
6935
|
+
codebaseEntity: writtenBinding.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath),
|
|
6936
|
+
projectLearning: learningStatus,
|
|
6871
6937
|
};
|
|
6872
6938
|
if (json) {
|
|
6873
6939
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -6881,9 +6947,13 @@ async function configureProjectCommand(args) {
|
|
|
6881
6947
|
console.log(` agent ${updates.IRANTI_AGENT_ID}`);
|
|
6882
6948
|
console.log(` remember ${updates.IRANTI_AUTO_REMEMBER}`);
|
|
6883
6949
|
console.log(` mode ${updates.IRANTI_PROJECT_MODE}`);
|
|
6950
|
+
console.log(` codebase ${writtenBinding.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath)}`);
|
|
6884
6951
|
if (updates.IRANTI_INSTANCE) {
|
|
6885
6952
|
console.log(` instance ${updates.IRANTI_INSTANCE}`);
|
|
6886
6953
|
}
|
|
6954
|
+
if (learningStatus.status !== 'written') {
|
|
6955
|
+
console.log(` learn ${paint(learningStatus.status, learningStatus.status === 'failed' ? 'red' : 'yellow')} (${learningStatus.detail})`);
|
|
6956
|
+
}
|
|
6887
6957
|
printNextSteps([
|
|
6888
6958
|
`iranti doctor${updates.IRANTI_INSTANCE ? ` --instance ${updates.IRANTI_INSTANCE}` : ''}`,
|
|
6889
6959
|
]);
|
|
@@ -6922,7 +6992,7 @@ async function authCreateKeyCommand(args) {
|
|
|
6922
6992
|
const resolvedProjectPath = path_1.default.resolve(projectPath);
|
|
6923
6993
|
const existingBindingFile = path_1.default.join(resolvedProjectPath, '.env.iranti');
|
|
6924
6994
|
const existingBinding = fs_1.default.existsSync(existingBindingFile) ? await readEnvFile(existingBindingFile) : {};
|
|
6925
|
-
await writeProjectBinding(resolvedProjectPath, {
|
|
6995
|
+
const written = await writeProjectBinding(resolvedProjectPath, {
|
|
6926
6996
|
IRANTI_URL: `http://localhost:${env.IRANTI_PORT ?? '3001'}`,
|
|
6927
6997
|
IRANTI_API_KEY: created.token,
|
|
6928
6998
|
IRANTI_AGENT_ID: agentId ?? existingBinding.IRANTI_AGENT_ID ?? 'my_agent',
|
|
@@ -6932,6 +7002,13 @@ async function authCreateKeyCommand(args) {
|
|
|
6932
7002
|
IRANTI_INSTANCE: instanceName,
|
|
6933
7003
|
IRANTI_INSTANCE_ENV: envFile,
|
|
6934
7004
|
});
|
|
7005
|
+
const binding = await readEnvFile(written);
|
|
7006
|
+
await (0, projectLearning_1.writeProjectLearningSnapshot)({
|
|
7007
|
+
projectPath: resolvedProjectPath,
|
|
7008
|
+
projectEnvFile: written,
|
|
7009
|
+
binding,
|
|
7010
|
+
agentId: agentId ?? binding.IRANTI_AGENT_ID ?? 'my_agent',
|
|
7011
|
+
});
|
|
6935
7012
|
}
|
|
6936
7013
|
if (hasFlag(args, 'json')) {
|
|
6937
7014
|
console.log(JSON.stringify({
|