iranti 0.2.51 → 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 +30 -17
- package/dist/scripts/api-key-create.js +1 -1
- package/dist/scripts/api-key-list.js +1 -1
- package/dist/scripts/api-key-revoke.js +1 -1
- package/dist/scripts/claude-code-memory-hook.js +116 -30
- package/dist/scripts/codex-setup.js +86 -4
- package/dist/scripts/iranti-cli.js +1359 -57
- package/dist/scripts/iranti-mcp.js +578 -75
- package/dist/scripts/seed.js +11 -6
- package/dist/scripts/setup.js +1 -1
- package/dist/src/api/healthChecks.d.ts +29 -0
- package/dist/src/api/healthChecks.d.ts.map +1 -0
- package/dist/src/api/healthChecks.js +72 -0
- package/dist/src/api/healthChecks.js.map +1 -0
- package/dist/src/api/middleware/validation.d.ts +22 -0
- package/dist/src/api/middleware/validation.d.ts.map +1 -1
- package/dist/src/api/middleware/validation.js +93 -3
- 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 +53 -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 +73 -9
- package/dist/src/api/routes/memory.js.map +1 -1
- package/dist/src/api/server.js +38 -43
- package/dist/src/api/server.js.map +1 -1
- package/dist/src/attendant/AttendantInstance.d.ts +135 -2
- package/dist/src/attendant/AttendantInstance.d.ts.map +1 -1
- package/dist/src/attendant/AttendantInstance.js +1836 -93
- 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 +1 -1
- package/dist/src/attendant/index.js.map +1 -1
- package/dist/src/attendant/registry.d.ts.map +1 -1
- package/dist/src/attendant/registry.js +2 -0
- package/dist/src/attendant/registry.js.map +1 -1
- package/dist/src/chat/index.d.ts +23 -0
- package/dist/src/chat/index.d.ts.map +1 -1
- package/dist/src/chat/index.js +111 -22
- package/dist/src/chat/index.js.map +1 -1
- package/dist/src/generated/prisma/browser.d.ts +5 -0
- package/dist/src/generated/prisma/browser.d.ts.map +1 -1
- package/dist/src/generated/prisma/client.d.ts +5 -0
- package/dist/src/generated/prisma/client.d.ts.map +1 -1
- package/dist/src/generated/prisma/commonInputTypes.d.ts +48 -0
- package/dist/src/generated/prisma/commonInputTypes.d.ts.map +1 -1
- package/dist/src/generated/prisma/internal/class.d.ts +11 -0
- package/dist/src/generated/prisma/internal/class.d.ts.map +1 -1
- package/dist/src/generated/prisma/internal/class.js +4 -4
- package/dist/src/generated/prisma/internal/class.js.map +1 -1
- package/dist/src/generated/prisma/internal/prismaNamespace.d.ts +92 -1
- package/dist/src/generated/prisma/internal/prismaNamespace.d.ts.map +1 -1
- package/dist/src/generated/prisma/internal/prismaNamespace.js +17 -2
- package/dist/src/generated/prisma/internal/prismaNamespace.js.map +1 -1
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts +16 -0
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -1
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js +17 -2
- package/dist/src/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -1
- package/dist/src/generated/prisma/models/StaffEvent.d.ts +1184 -0
- package/dist/src/generated/prisma/models/StaffEvent.d.ts.map +1 -0
- package/dist/src/generated/prisma/models/StaffEvent.js +3 -0
- package/dist/src/generated/prisma/models/StaffEvent.js.map +1 -0
- package/dist/src/generated/prisma/models.d.ts +1 -0
- package/dist/src/generated/prisma/models.d.ts.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/autoRemember.d.ts +15 -0
- package/dist/src/lib/autoRemember.d.ts.map +1 -1
- package/dist/src/lib/autoRemember.js +433 -71
- package/dist/src/lib/autoRemember.js.map +1 -1
- package/dist/src/lib/cliHelpCatalog.d.ts.map +1 -1
- package/dist/src/lib/cliHelpCatalog.js +23 -11
- package/dist/src/lib/cliHelpCatalog.js.map +1 -1
- package/dist/src/lib/cliHelpRenderer.d.ts +1 -0
- package/dist/src/lib/cliHelpRenderer.d.ts.map +1 -1
- package/dist/src/lib/cliHelpRenderer.js +4 -0
- package/dist/src/lib/cliHelpRenderer.js.map +1 -1
- package/dist/src/lib/commandErrors.d.ts +5 -1
- package/dist/src/lib/commandErrors.d.ts.map +1 -1
- package/dist/src/lib/commandErrors.js +250 -17
- package/dist/src/lib/commandErrors.js.map +1 -1
- package/dist/src/lib/createFirstPartyIranti.d.ts.map +1 -1
- package/dist/src/lib/createFirstPartyIranti.js +1 -0
- package/dist/src/lib/createFirstPartyIranti.js.map +1 -1
- package/dist/src/lib/dbStaffEventEmitter.d.ts +2 -0
- package/dist/src/lib/dbStaffEventEmitter.d.ts.map +1 -1
- package/dist/src/lib/dbStaffEventEmitter.js +15 -0
- package/dist/src/lib/dbStaffEventEmitter.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/issueFacts.d.ts +37 -0
- package/dist/src/lib/issueFacts.d.ts.map +1 -0
- package/dist/src/lib/issueFacts.js +72 -0
- package/dist/src/lib/issueFacts.js.map +1 -0
- package/dist/src/lib/llm.d.ts +8 -0
- package/dist/src/lib/llm.d.ts.map +1 -1
- package/dist/src/lib/llm.js +33 -0
- package/dist/src/lib/llm.js.map +1 -1
- package/dist/src/lib/packageRoot.d.ts +2 -0
- package/dist/src/lib/packageRoot.d.ts.map +1 -0
- package/dist/src/lib/packageRoot.js +22 -0
- package/dist/src/lib/packageRoot.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 +29 -0
- package/dist/src/lib/protocolEnforcement.d.ts.map +1 -0
- package/dist/src/lib/protocolEnforcement.js +124 -0
- package/dist/src/lib/protocolEnforcement.js.map +1 -0
- package/dist/src/lib/providers/claude.js +1 -1
- package/dist/src/lib/providers/claude.js.map +1 -1
- package/dist/src/lib/router.js +1 -1
- package/dist/src/lib/router.js.map +1 -1
- package/dist/src/lib/runtimeEnv.d.ts.map +1 -1
- package/dist/src/lib/runtimeEnv.js +8 -3
- package/dist/src/lib/runtimeEnv.js.map +1 -1
- package/dist/src/lib/scaffoldCloseout.d.ts +27 -0
- package/dist/src/lib/scaffoldCloseout.d.ts.map +1 -0
- package/dist/src/lib/scaffoldCloseout.js +139 -0
- package/dist/src/lib/scaffoldCloseout.js.map +1 -0
- package/dist/src/lib/semanticFactTags.d.ts +10 -0
- package/dist/src/lib/semanticFactTags.d.ts.map +1 -0
- package/dist/src/lib/semanticFactTags.js +166 -0
- package/dist/src/lib/semanticFactTags.js.map +1 -0
- package/dist/src/lib/sessionLedger.d.ts +94 -0
- package/dist/src/lib/sessionLedger.d.ts.map +1 -0
- package/dist/src/lib/sessionLedger.js +997 -0
- package/dist/src/lib/sessionLedger.js.map +1 -0
- package/dist/src/lib/sharedStateInvalidation.d.ts +10 -0
- package/dist/src/lib/sharedStateInvalidation.d.ts.map +1 -0
- package/dist/src/lib/sharedStateInvalidation.js +184 -0
- package/dist/src/lib/sharedStateInvalidation.js.map +1 -0
- package/dist/src/lib/staffEventsTable.d.ts +3 -0
- package/dist/src/lib/staffEventsTable.d.ts.map +1 -0
- package/dist/src/lib/staffEventsTable.js +58 -0
- package/dist/src/lib/staffEventsTable.js.map +1 -0
- package/dist/src/librarian/index.d.ts.map +1 -1
- package/dist/src/librarian/index.js +113 -2
- package/dist/src/librarian/index.js.map +1 -1
- package/dist/src/library/client.d.ts +6 -1
- package/dist/src/library/client.d.ts.map +1 -1
- package/dist/src/library/client.js +21 -7
- package/dist/src/library/client.js.map +1 -1
- package/dist/src/library/embeddings.d.ts +9 -1
- package/dist/src/library/embeddings.d.ts.map +1 -1
- package/dist/src/library/embeddings.js +28 -3
- package/dist/src/library/embeddings.js.map +1 -1
- package/dist/src/library/queries.d.ts.map +1 -1
- package/dist/src/library/queries.js +263 -46
- package/dist/src/library/queries.js.map +1 -1
- package/dist/src/sdk/index.d.ts +52 -1
- package/dist/src/sdk/index.d.ts.map +1 -1
- package/dist/src/sdk/index.js +546 -98
- package/dist/src/sdk/index.js.map +1 -1
- package/package.json +24 -3
- package/prisma/migrations/20260331101500_add_staff_events_ledger/migration.sql +24 -0
- package/prisma/schema.prisma +22 -0
|
@@ -13,6 +13,7 @@ const https_1 = __importDefault(require("https"));
|
|
|
13
13
|
const promises_2 = __importDefault(require("readline/promises"));
|
|
14
14
|
const stream_1 = require("stream");
|
|
15
15
|
const net_1 = __importDefault(require("net"));
|
|
16
|
+
const crypto_1 = require("crypto");
|
|
16
17
|
const client_1 = require("../src/library/client");
|
|
17
18
|
const apiKeys_1 = require("../src/security/apiKeys");
|
|
18
19
|
const cliHelpRenderer_1 = require("../src/lib/cliHelpRenderer");
|
|
@@ -30,7 +31,10 @@ const backends_1 = require("../src/library/backends");
|
|
|
30
31
|
const runtimeLifecycle_1 = require("../src/lib/runtimeLifecycle");
|
|
31
32
|
const queries_1 = require("../src/library/queries");
|
|
32
33
|
const autoRemember_1 = require("../src/lib/autoRemember");
|
|
34
|
+
const semanticFactTags_1 = require("../src/lib/semanticFactTags");
|
|
33
35
|
const staffEventRegistry_1 = require("../src/lib/staffEventRegistry");
|
|
36
|
+
const scaffoldCloseout_1 = require("../src/lib/scaffoldCloseout");
|
|
37
|
+
const projectLearning_1 = require("../src/lib/projectLearning");
|
|
34
38
|
class CliError extends Error {
|
|
35
39
|
constructor(code, message, hints = [], details) {
|
|
36
40
|
super(message);
|
|
@@ -64,6 +68,7 @@ const ANSI = {
|
|
|
64
68
|
};
|
|
65
69
|
let CLI_DEBUG = process.argv.includes('--debug') || process.env.IRANTI_DEBUG === '1';
|
|
66
70
|
let CLI_VERBOSE = CLI_DEBUG || process.argv.includes('--verbose') || process.env.IRANTI_VERBOSE === '1';
|
|
71
|
+
let ACTIVE_PARSED_ARGS = null;
|
|
67
72
|
// H-7: Cleanup/rollback stack — LIFO handlers run on SIGINT/SIGTERM to undo partial multi-step operations
|
|
68
73
|
const _cleanupStack = [];
|
|
69
74
|
function pushCleanup(fn) {
|
|
@@ -146,6 +151,15 @@ function verboseLog(message, details) {
|
|
|
146
151
|
function cliError(code, message, hints = [], details) {
|
|
147
152
|
return new CliError(code, message, hints, details);
|
|
148
153
|
}
|
|
154
|
+
function wantsJsonErrorEnvelope(args) {
|
|
155
|
+
return Boolean(args && hasFlag(args, 'json'));
|
|
156
|
+
}
|
|
157
|
+
function normalizeCliFailure(err) {
|
|
158
|
+
if (err instanceof CliError) {
|
|
159
|
+
return err;
|
|
160
|
+
}
|
|
161
|
+
return (0, commandErrors_1.rewriteCommandError)('iranti', err);
|
|
162
|
+
}
|
|
149
163
|
function parseArgs(argv) {
|
|
150
164
|
const flags = new Map();
|
|
151
165
|
const positionals = [];
|
|
@@ -282,6 +296,25 @@ function findClosestAncestorRuntimeRoot(startDir) {
|
|
|
282
296
|
}
|
|
283
297
|
return null;
|
|
284
298
|
}
|
|
299
|
+
function findClosestDoctorEnvTarget(startDir) {
|
|
300
|
+
for (const dir of walkAncestorPaths(startDir)) {
|
|
301
|
+
const repoEnv = path_1.default.join(dir, '.env');
|
|
302
|
+
if (fs_1.default.existsSync(repoEnv) && fs_1.default.statSync(repoEnv).isFile()) {
|
|
303
|
+
return {
|
|
304
|
+
envFile: repoEnv,
|
|
305
|
+
envSource: 'repo',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const projectEnv = path_1.default.join(dir, '.env.iranti');
|
|
309
|
+
if (fs_1.default.existsSync(projectEnv) && fs_1.default.statSync(projectEnv).isFile()) {
|
|
310
|
+
return {
|
|
311
|
+
envFile: projectEnv,
|
|
312
|
+
envSource: 'project-binding',
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { envFile: null, envSource: 'repo' };
|
|
317
|
+
}
|
|
285
318
|
function resolveInstallRootDetails(args, scope) {
|
|
286
319
|
const explicitFlag = getFlag(args, 'root');
|
|
287
320
|
const explicit = explicitFlag ? stripWrappingQuotes(explicitFlag) : (process.env.IRANTI_HOME ? stripWrappingQuotes(process.env.IRANTI_HOME) : undefined);
|
|
@@ -621,6 +654,16 @@ async function readEnvFile(filePath) {
|
|
|
621
654
|
}
|
|
622
655
|
return out;
|
|
623
656
|
}
|
|
657
|
+
async function readEnvFileIfExists(filePath) {
|
|
658
|
+
if (!filePath || !fs_1.default.existsSync(filePath))
|
|
659
|
+
return null;
|
|
660
|
+
try {
|
|
661
|
+
return await readEnvFile(filePath);
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
624
667
|
async function readInstanceMetaFile(metaFile) {
|
|
625
668
|
if (!fs_1.default.existsSync(metaFile))
|
|
626
669
|
return null;
|
|
@@ -632,7 +675,11 @@ async function readInstanceMetaFile(metaFile) {
|
|
|
632
675
|
return null;
|
|
633
676
|
}
|
|
634
677
|
}
|
|
635
|
-
function
|
|
678
|
+
function resolveInstanceApiKeyPepper(existingPepper) {
|
|
679
|
+
const trimmed = existingPepper?.trim() ?? '';
|
|
680
|
+
return trimmed || (0, crypto_1.randomBytes)(32).toString('hex');
|
|
681
|
+
}
|
|
682
|
+
function makeInstanceEnv(name, port, dbUrl, apiKey, instanceDir, apiKeyPepper) {
|
|
636
683
|
const lines = [
|
|
637
684
|
'# Iranti instance env',
|
|
638
685
|
`IRANTI_INSTANCE_NAME=${name}`,
|
|
@@ -645,6 +692,7 @@ function makeInstanceEnv(name, port, dbUrl, apiKey, instanceDir) {
|
|
|
645
692
|
'IRANTI_ARCHIVIST_DEBOUNCE_MS=60000',
|
|
646
693
|
'IRANTI_ARCHIVIST_INTERVAL_MS=0',
|
|
647
694
|
`IRANTI_API_KEY=${apiKey ?? 'replace_me_with_api_key'}`,
|
|
695
|
+
`IRANTI_API_KEY_PEPPER=${apiKeyPepper}`,
|
|
648
696
|
'',
|
|
649
697
|
];
|
|
650
698
|
return lines.join('\n');
|
|
@@ -1054,13 +1102,28 @@ async function ensureProjectGitignore(projectPath) {
|
|
|
1054
1102
|
async function writeProjectBinding(projectPath, updates) {
|
|
1055
1103
|
await ensureDir(projectPath);
|
|
1056
1104
|
const outFile = path_1.default.join(projectPath, '.env.iranti');
|
|
1105
|
+
const previousBinding = fs_1.default.existsSync(outFile)
|
|
1106
|
+
? await readEnvFile(outFile).catch(() => ({}))
|
|
1107
|
+
: {};
|
|
1108
|
+
const normalizedUpdates = {
|
|
1109
|
+
...updates,
|
|
1110
|
+
IRANTI_CODEBASE_ENTITY: updates.IRANTI_CODEBASE_ENTITY ?? previousBinding.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath),
|
|
1111
|
+
};
|
|
1057
1112
|
if (!fs_1.default.existsSync(outFile)) {
|
|
1058
1113
|
await writeText(outFile, '# Iranti project binding\n');
|
|
1059
1114
|
}
|
|
1060
|
-
await upsertEnvFile(outFile,
|
|
1115
|
+
await upsertEnvFile(outFile, normalizedUpdates);
|
|
1061
1116
|
await ensureProjectGitignore(projectPath);
|
|
1117
|
+
const writtenBinding = await readEnvFile(outFile).catch(() => ({}));
|
|
1118
|
+
await syncProjectBindingRegistry(projectPath, writtenBinding, previousBinding);
|
|
1062
1119
|
return outFile;
|
|
1063
1120
|
}
|
|
1121
|
+
function normalizeProjectRegistryPath(projectPath) {
|
|
1122
|
+
const normalized = path_1.default.resolve(projectPath);
|
|
1123
|
+
return process.platform === 'win32'
|
|
1124
|
+
? normalized.toLowerCase()
|
|
1125
|
+
: normalized;
|
|
1126
|
+
}
|
|
1064
1127
|
async function removeProjectPathFromRegistry(runtimeRoot, instanceName, projectPath) {
|
|
1065
1128
|
const registryPath = path_1.default.join(runtimeRoot, 'instances', instanceName, 'projects.json');
|
|
1066
1129
|
if (!fs_1.default.existsSync(registryPath))
|
|
@@ -1068,16 +1131,54 @@ async function removeProjectPathFromRegistry(runtimeRoot, instanceName, projectP
|
|
|
1068
1131
|
const parsed = readJsonFile(registryPath);
|
|
1069
1132
|
if (!parsed || !Array.isArray(parsed.projects))
|
|
1070
1133
|
return false;
|
|
1134
|
+
const expectedProjectPath = normalizeProjectRegistryPath(projectPath);
|
|
1071
1135
|
const nextProjects = parsed.projects.filter((entry) => {
|
|
1072
1136
|
if (!entry || typeof entry !== 'object')
|
|
1073
1137
|
return false;
|
|
1074
|
-
return String(entry.projectPath ?? '') !==
|
|
1138
|
+
return normalizeProjectRegistryPath(String(entry.projectPath ?? '')) !== expectedProjectPath;
|
|
1075
1139
|
});
|
|
1076
1140
|
if (nextProjects.length === parsed.projects.length)
|
|
1077
1141
|
return false;
|
|
1078
1142
|
await writeText(registryPath, `${JSON.stringify({ projects: nextProjects }, null, 2)}\n`);
|
|
1079
1143
|
return true;
|
|
1080
1144
|
}
|
|
1145
|
+
async function syncProjectBindingRegistry(projectPath, binding, previousBinding = {}) {
|
|
1146
|
+
const nextRuntimeRoot = runtimeRootFromInstanceEnv(binding.IRANTI_INSTANCE_ENV ?? '');
|
|
1147
|
+
const nextInstanceName = binding.IRANTI_INSTANCE?.trim() || null;
|
|
1148
|
+
const previousRuntimeRoot = runtimeRootFromInstanceEnv(previousBinding.IRANTI_INSTANCE_ENV ?? '');
|
|
1149
|
+
const previousInstanceName = previousBinding.IRANTI_INSTANCE?.trim() || null;
|
|
1150
|
+
if (previousRuntimeRoot && previousInstanceName && (previousRuntimeRoot !== nextRuntimeRoot
|
|
1151
|
+
|| previousInstanceName !== nextInstanceName)) {
|
|
1152
|
+
await removeProjectPathFromRegistry(previousRuntimeRoot, previousInstanceName, projectPath);
|
|
1153
|
+
}
|
|
1154
|
+
if (!nextRuntimeRoot || !nextInstanceName)
|
|
1155
|
+
return;
|
|
1156
|
+
const registryPath = path_1.default.join(nextRuntimeRoot, 'instances', nextInstanceName, 'projects.json');
|
|
1157
|
+
const existingRegistry = fs_1.default.existsSync(registryPath)
|
|
1158
|
+
? readJsonFile(registryPath)
|
|
1159
|
+
: null;
|
|
1160
|
+
const existingProjects = Array.isArray(existingRegistry?.projects) ? existingRegistry.projects : [];
|
|
1161
|
+
const normalizedProjectPath = normalizeProjectRegistryPath(projectPath);
|
|
1162
|
+
const previousEntry = existingProjects.find((entry) => entry
|
|
1163
|
+
&& typeof entry === 'object'
|
|
1164
|
+
&& normalizeProjectRegistryPath(String(entry.projectPath ?? '')) === normalizedProjectPath);
|
|
1165
|
+
const boundAt = typeof previousEntry?.boundAt === 'string' && previousEntry.boundAt.trim().length > 0
|
|
1166
|
+
? previousEntry.boundAt
|
|
1167
|
+
: new Date().toISOString();
|
|
1168
|
+
const nextProjects = existingProjects
|
|
1169
|
+
.filter((entry) => !entry
|
|
1170
|
+
|| typeof entry !== 'object'
|
|
1171
|
+
|| normalizeProjectRegistryPath(String(entry.projectPath ?? '')) !== normalizedProjectPath)
|
|
1172
|
+
.concat({
|
|
1173
|
+
projectPath,
|
|
1174
|
+
agentId: binding.IRANTI_AGENT_ID?.trim() || 'project_main',
|
|
1175
|
+
memoryEntity: binding.IRANTI_MEMORY_ENTITY?.trim() || 'user/main',
|
|
1176
|
+
mode: binding.IRANTI_PROJECT_MODE?.trim() || 'isolated',
|
|
1177
|
+
boundAt,
|
|
1178
|
+
})
|
|
1179
|
+
.sort((a, b) => String(a.projectPath ?? '').localeCompare(String(b.projectPath ?? '')));
|
|
1180
|
+
await writeText(registryPath, `${JSON.stringify({ projects: nextProjects }, null, 2)}\n`);
|
|
1181
|
+
}
|
|
1081
1182
|
async function cleanupProjectBindingRegistry(projectPath, binding) {
|
|
1082
1183
|
const removedFrom = [];
|
|
1083
1184
|
const runtimeRoot = runtimeRootFromInstanceEnv(binding.IRANTI_INSTANCE_ENV ?? '');
|
|
@@ -1089,6 +1190,24 @@ async function cleanupProjectBindingRegistry(projectPath, binding) {
|
|
|
1089
1190
|
}
|
|
1090
1191
|
return removedFrom;
|
|
1091
1192
|
}
|
|
1193
|
+
function readInstanceProjectRegistry(root, instanceName) {
|
|
1194
|
+
const registryPath = path_1.default.join(root, 'instances', instanceName, 'projects.json');
|
|
1195
|
+
if (!fs_1.default.existsSync(registryPath))
|
|
1196
|
+
return [];
|
|
1197
|
+
const parsed = readJsonFile(registryPath);
|
|
1198
|
+
if (!parsed || !Array.isArray(parsed.projects))
|
|
1199
|
+
return [];
|
|
1200
|
+
return parsed.projects
|
|
1201
|
+
.filter((entry) => entry && typeof entry === 'object' && typeof entry.projectPath === 'string' && entry.projectPath.trim().length > 0)
|
|
1202
|
+
.map((entry) => ({
|
|
1203
|
+
projectPath: String(entry.projectPath),
|
|
1204
|
+
agentId: typeof entry.agentId === 'string' && entry.agentId.trim().length > 0 ? entry.agentId : 'project_main',
|
|
1205
|
+
memoryEntity: typeof entry.memoryEntity === 'string' && entry.memoryEntity.trim().length > 0 ? entry.memoryEntity : 'user/main',
|
|
1206
|
+
mode: typeof entry.mode === 'string' && entry.mode.trim().length > 0 ? entry.mode : 'isolated',
|
|
1207
|
+
boundAt: typeof entry.boundAt === 'string' && entry.boundAt.trim().length > 0 ? entry.boundAt : null,
|
|
1208
|
+
}))
|
|
1209
|
+
.sort((a, b) => a.projectPath.localeCompare(b.projectPath));
|
|
1210
|
+
}
|
|
1092
1211
|
async function withPromptSession(run) {
|
|
1093
1212
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
1094
1213
|
throw new Error('--interactive requires a real terminal session.');
|
|
@@ -1202,6 +1321,59 @@ function postgresDatabaseName(databaseUrl) {
|
|
|
1202
1321
|
}
|
|
1203
1322
|
return database;
|
|
1204
1323
|
}
|
|
1324
|
+
function summarizeDatabaseTarget(databaseUrl) {
|
|
1325
|
+
const trimmed = databaseUrl?.trim() ?? '';
|
|
1326
|
+
if (!trimmed || detectPlaceholder(trimmed))
|
|
1327
|
+
return null;
|
|
1328
|
+
try {
|
|
1329
|
+
const parsed = parsePostgresConnectionString(trimmed);
|
|
1330
|
+
const database = decodeURIComponent(parsed.pathname.replace(/^\/+/, ''));
|
|
1331
|
+
if (!database)
|
|
1332
|
+
return parsed.host || trimmed;
|
|
1333
|
+
return `${parsed.host}/${database}`;
|
|
1334
|
+
}
|
|
1335
|
+
catch {
|
|
1336
|
+
return trimmed;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
function summarizeActiveAuthority(envSource, bindingFile, boundInstanceEnvFile) {
|
|
1340
|
+
if (bindingFile && boundInstanceEnvFile)
|
|
1341
|
+
return 'project binding -> bound instance env';
|
|
1342
|
+
if (bindingFile)
|
|
1343
|
+
return 'project binding';
|
|
1344
|
+
if (envSource.startsWith('instance:'))
|
|
1345
|
+
return 'named instance env';
|
|
1346
|
+
if (envSource === 'explicit-env')
|
|
1347
|
+
return 'explicit env';
|
|
1348
|
+
if (envSource === 'repo')
|
|
1349
|
+
return 'repo env';
|
|
1350
|
+
if (envSource === 'environment')
|
|
1351
|
+
return 'environment';
|
|
1352
|
+
return envSource;
|
|
1353
|
+
}
|
|
1354
|
+
async function buildOperatorAuthoritySummary(options) {
|
|
1355
|
+
const repoEnv = await readEnvFileIfExists(options.repoEnvFile ?? null);
|
|
1356
|
+
const boundDatabaseUrl = options.boundInstanceEnv?.DATABASE_URL?.trim() || null;
|
|
1357
|
+
const nearbyBoundDatabaseUrl = options.nearbyBoundInstanceEnv?.DATABASE_URL?.trim() || null;
|
|
1358
|
+
const selectedDatabaseUrl = options.env?.DATABASE_URL?.trim() || null;
|
|
1359
|
+
const activeDatabaseUrl = boundDatabaseUrl || selectedDatabaseUrl;
|
|
1360
|
+
const repoDatabaseUrl = repoEnv?.DATABASE_URL?.trim() || null;
|
|
1361
|
+
return {
|
|
1362
|
+
activeAuthority: summarizeActiveAuthority(options.envSource, options.bindingFile ?? null, options.boundInstanceEnvFile ?? null),
|
|
1363
|
+
activeBindingSource: options.bindingFile ?? null,
|
|
1364
|
+
activeBoundInstanceEnv: options.boundInstanceEnvFile ?? null,
|
|
1365
|
+
activeDatabaseUrl,
|
|
1366
|
+
activeDatabaseTarget: summarizeDatabaseTarget(activeDatabaseUrl),
|
|
1367
|
+
repoDatabaseUrl,
|
|
1368
|
+
repoDatabaseTarget: summarizeDatabaseTarget(repoDatabaseUrl),
|
|
1369
|
+
repoDatabaseDiffers: Boolean(repoDatabaseUrl && activeDatabaseUrl && repoDatabaseUrl !== activeDatabaseUrl),
|
|
1370
|
+
nearbyBindingSource: options.nearbyBindingFile ?? null,
|
|
1371
|
+
nearbyBoundInstanceEnv: options.nearbyBoundInstanceEnvFile ?? null,
|
|
1372
|
+
nearbyBindingDatabaseUrl: nearbyBoundDatabaseUrl,
|
|
1373
|
+
nearbyBindingDatabaseTarget: summarizeDatabaseTarget(nearbyBoundDatabaseUrl),
|
|
1374
|
+
nearbyBindingDiffers: Boolean(nearbyBoundDatabaseUrl && activeDatabaseUrl && nearbyBoundDatabaseUrl !== activeDatabaseUrl),
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1205
1377
|
function isLocalPostgresHost(hostname) {
|
|
1206
1378
|
const normalized = hostname.trim().toLowerCase();
|
|
1207
1379
|
return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
|
|
@@ -1513,13 +1685,17 @@ async function ensureRuntimeInstalled(root, scope) {
|
|
|
1513
1685
|
async function ensureInstanceConfigured(root, name, config) {
|
|
1514
1686
|
const { instanceDir, envFile, metaFile } = instancePaths(root, name);
|
|
1515
1687
|
const created = !fs_1.default.existsSync(envFile);
|
|
1688
|
+
const existingEnv = fs_1.default.existsSync(envFile)
|
|
1689
|
+
? await readEnvFile(envFile).catch(() => ({}))
|
|
1690
|
+
: {};
|
|
1691
|
+
const apiKeyPepper = resolveInstanceApiKeyPepper(existingEnv.IRANTI_API_KEY_PEPPER);
|
|
1516
1692
|
if (created) {
|
|
1517
1693
|
await ensureDir(instanceDir);
|
|
1518
1694
|
await ensureDir(path_1.default.join(instanceDir, 'logs'));
|
|
1519
1695
|
await ensureDir(path_1.default.join(instanceDir, 'escalation', 'active'));
|
|
1520
1696
|
await ensureDir(path_1.default.join(instanceDir, 'escalation', 'resolved'));
|
|
1521
1697
|
await ensureDir(path_1.default.join(instanceDir, 'escalation', 'archived'));
|
|
1522
|
-
await writeText(envFile, makeInstanceEnv(name, config.port, config.dbUrl, config.apiKey, instanceDir));
|
|
1698
|
+
await writeText(envFile, makeInstanceEnv(name, config.port, config.dbUrl, config.apiKey, instanceDir, apiKeyPepper));
|
|
1523
1699
|
const meta = {
|
|
1524
1700
|
name,
|
|
1525
1701
|
createdAt: new Date().toISOString(),
|
|
@@ -1535,6 +1711,7 @@ async function ensureInstanceConfigured(root, name, config) {
|
|
|
1535
1711
|
IRANTI_PORT: String(config.port),
|
|
1536
1712
|
DATABASE_URL: config.dbUrl,
|
|
1537
1713
|
IRANTI_API_KEY: config.apiKey,
|
|
1714
|
+
IRANTI_API_KEY_PEPPER: apiKeyPepper,
|
|
1538
1715
|
LLM_PROVIDER: config.provider,
|
|
1539
1716
|
...config.providerKeys,
|
|
1540
1717
|
});
|
|
@@ -1545,22 +1722,37 @@ async function ensureInstanceConfigured(root, name, config) {
|
|
|
1545
1722
|
return { envFile, instanceDir, created };
|
|
1546
1723
|
}
|
|
1547
1724
|
function makeIrantiMcpServerConfig(projectEnvPath) {
|
|
1725
|
+
const env = {
|
|
1726
|
+
IRANTI_MCP_HOST: 'codex_cli',
|
|
1727
|
+
};
|
|
1728
|
+
if (projectEnvPath) {
|
|
1729
|
+
env.IRANTI_PROJECT_ENV = projectEnvPath;
|
|
1730
|
+
}
|
|
1548
1731
|
return {
|
|
1549
1732
|
command: 'iranti',
|
|
1550
1733
|
args: ['mcp'],
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1734
|
+
env,
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
function makeClaudeLocalMcpServerConfig(projectEnvPath) {
|
|
1738
|
+
const env = {
|
|
1739
|
+
IRANTI_MCP_HOST: 'claude_code',
|
|
1740
|
+
};
|
|
1741
|
+
if (projectEnvPath) {
|
|
1742
|
+
env.IRANTI_PROJECT_ENV = projectEnvPath;
|
|
1743
|
+
}
|
|
1744
|
+
return {
|
|
1745
|
+
command: 'iranti',
|
|
1746
|
+
args: ['mcp'],
|
|
1747
|
+
env,
|
|
1558
1748
|
};
|
|
1559
1749
|
}
|
|
1560
1750
|
function makeVsCodeIrantiMcpServerConfig(projectPath, projectEnvPath) {
|
|
1561
1751
|
const resolvedProjectEnvPath = projectEnvPath ? path_1.default.resolve(projectEnvPath) : undefined;
|
|
1562
1752
|
const localProjectEnvPath = path_1.default.join(projectPath, '.env.iranti');
|
|
1563
|
-
const env = {
|
|
1753
|
+
const env = {
|
|
1754
|
+
IRANTI_MCP_HOST: 'codex_vscode',
|
|
1755
|
+
};
|
|
1564
1756
|
if (resolvedProjectEnvPath && path_1.default.resolve(localProjectEnvPath) !== resolvedProjectEnvPath) {
|
|
1565
1757
|
env.IRANTI_PROJECT_ENV = resolvedProjectEnvPath;
|
|
1566
1758
|
}
|
|
@@ -1619,6 +1811,13 @@ async function resolveAttendantCliTarget(args) {
|
|
|
1619
1811
|
cwd,
|
|
1620
1812
|
projectEnvFile: explicitProjectEnv ? path_1.default.resolve(explicitProjectEnv) : undefined,
|
|
1621
1813
|
});
|
|
1814
|
+
debugLog('Attendant CLI runtime env resolved.', {
|
|
1815
|
+
cwd,
|
|
1816
|
+
authorityMode: loaded.authorityMode,
|
|
1817
|
+
projectEnvFile: loaded.projectEnvFile ?? null,
|
|
1818
|
+
instanceEnvFile: loaded.instanceEnvFile ?? null,
|
|
1819
|
+
loadedFiles: loaded.loadedFiles.join(' | ') || '(none)',
|
|
1820
|
+
});
|
|
1622
1821
|
envSource = loaded.projectEnvFile ? 'project-binding' : 'environment';
|
|
1623
1822
|
envFile = loaded.projectEnvFile ?? loaded.instanceEnvFile ?? null;
|
|
1624
1823
|
projectEnvFile = loaded.projectEnvFile;
|
|
@@ -1638,7 +1837,11 @@ async function resolveAttendantCliTarget(args) {
|
|
|
1638
1837
|
projectEnvFile,
|
|
1639
1838
|
instanceEnvFile,
|
|
1640
1839
|
agentId,
|
|
1641
|
-
iranti: (0, createFirstPartyIranti_1.createFirstPartyIranti)({
|
|
1840
|
+
iranti: (0, createFirstPartyIranti_1.createFirstPartyIranti)({
|
|
1841
|
+
connectionString,
|
|
1842
|
+
sessionLedgerSource: 'cli',
|
|
1843
|
+
sessionLedgerHost: 'plain_cli',
|
|
1844
|
+
}),
|
|
1642
1845
|
};
|
|
1643
1846
|
}
|
|
1644
1847
|
function truncateText(value, limit) {
|
|
@@ -1713,6 +1916,135 @@ function resolveTaskEntity(args) {
|
|
|
1713
1916
|
}
|
|
1714
1917
|
return entity;
|
|
1715
1918
|
}
|
|
1919
|
+
function resolveIssueEntity(args) {
|
|
1920
|
+
const explicit = getFlag(args, 'entity')?.trim();
|
|
1921
|
+
if (explicit) {
|
|
1922
|
+
if (!explicit.includes('/')) {
|
|
1923
|
+
throw new Error('issue entity must use entityType/entityId format.');
|
|
1924
|
+
}
|
|
1925
|
+
return explicit;
|
|
1926
|
+
}
|
|
1927
|
+
const fromEnv = process.env.IRANTI_MEMORY_ENTITY?.trim();
|
|
1928
|
+
if (fromEnv?.includes('/')) {
|
|
1929
|
+
return fromEnv;
|
|
1930
|
+
}
|
|
1931
|
+
throw new Error('Missing issue entity. Pass --entity <entityType/entityId> or run from a bound project with IRANTI_MEMORY_ENTITY.');
|
|
1932
|
+
}
|
|
1933
|
+
function resolveIssueStatusFilter(args) {
|
|
1934
|
+
const raw = getFlag(args, 'status')?.trim().toLowerCase();
|
|
1935
|
+
if (!raw)
|
|
1936
|
+
return null;
|
|
1937
|
+
if (raw === 'open' || raw === 'resolved') {
|
|
1938
|
+
return raw;
|
|
1939
|
+
}
|
|
1940
|
+
throw new Error(`Invalid --status '${raw}'. Use open or resolved.`);
|
|
1941
|
+
}
|
|
1942
|
+
function parseIssueListFact(entry) {
|
|
1943
|
+
if (!entry.key.startsWith('issue_')) {
|
|
1944
|
+
return { item: null, invalid: null };
|
|
1945
|
+
}
|
|
1946
|
+
const value = typeof entry.value === 'object' && entry.value ? entry.value : null;
|
|
1947
|
+
if (!value) {
|
|
1948
|
+
return {
|
|
1949
|
+
item: null,
|
|
1950
|
+
invalid: {
|
|
1951
|
+
key: entry.key,
|
|
1952
|
+
summary: entry.summary,
|
|
1953
|
+
confidence: entry.confidence,
|
|
1954
|
+
source: entry.source,
|
|
1955
|
+
reason: 'issue_* fact value is not an object',
|
|
1956
|
+
},
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
const rawStatus = typeof value?.status === 'string' ? value.status.toLowerCase() : '';
|
|
1960
|
+
if (rawStatus !== 'open' && rawStatus !== 'resolved') {
|
|
1961
|
+
return {
|
|
1962
|
+
item: null,
|
|
1963
|
+
invalid: {
|
|
1964
|
+
key: entry.key,
|
|
1965
|
+
summary: entry.summary,
|
|
1966
|
+
confidence: entry.confidence,
|
|
1967
|
+
source: entry.source,
|
|
1968
|
+
reason: 'issue_* fact is missing canonical open/resolved status',
|
|
1969
|
+
},
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
return {
|
|
1973
|
+
item: {
|
|
1974
|
+
key: entry.key,
|
|
1975
|
+
issueId: typeof value?.issueId === 'string' && value.issueId.trim() ? value.issueId.trim() : entry.key.replace(/^issue_/, ''),
|
|
1976
|
+
title: typeof value?.title === 'string' && value.title.trim() ? value.title.trim() : entry.summary,
|
|
1977
|
+
status: rawStatus,
|
|
1978
|
+
severity: typeof value?.severity === 'string' && value.severity.trim() ? value.severity.trim() : 'medium',
|
|
1979
|
+
summary: entry.summary,
|
|
1980
|
+
confidence: entry.confidence,
|
|
1981
|
+
source: entry.source,
|
|
1982
|
+
discoveredAt: typeof value?.discoveredAt === 'string' && value.discoveredAt.trim() ? value.discoveredAt.trim() : null,
|
|
1983
|
+
resolvedAt: typeof value?.resolvedAt === 'string' && value.resolvedAt.trim() ? value.resolvedAt.trim() : null,
|
|
1984
|
+
resolution: typeof value?.resolution === 'string' && value.resolution.trim() ? value.resolution.trim() : null,
|
|
1985
|
+
tags: Array.isArray(value?.tags)
|
|
1986
|
+
? value.tags.map((tag) => String(tag).trim()).filter(Boolean)
|
|
1987
|
+
: [],
|
|
1988
|
+
},
|
|
1989
|
+
invalid: null,
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
function compareIssueListItems(a, b) {
|
|
1993
|
+
if (a.status !== b.status) {
|
|
1994
|
+
return a.status === 'open' ? -1 : 1;
|
|
1995
|
+
}
|
|
1996
|
+
const severityRank = new Map([
|
|
1997
|
+
['critical', 0],
|
|
1998
|
+
['high', 1],
|
|
1999
|
+
['medium', 2],
|
|
2000
|
+
['low', 3],
|
|
2001
|
+
]);
|
|
2002
|
+
const severityDelta = (severityRank.get(a.severity) ?? 99) - (severityRank.get(b.severity) ?? 99);
|
|
2003
|
+
if (severityDelta !== 0)
|
|
2004
|
+
return severityDelta;
|
|
2005
|
+
return a.issueId.localeCompare(b.issueId);
|
|
2006
|
+
}
|
|
2007
|
+
function printIssuesResult(entity, items, statusFilter, inventoryCounts, invalidFacts) {
|
|
2008
|
+
console.log(sectionTitle('Issues Command'));
|
|
2009
|
+
console.log(`Entity: ${entity}`);
|
|
2010
|
+
console.log(`Filter: ${statusFilter ?? 'all'}`);
|
|
2011
|
+
console.log(`Total: ${items.length}`);
|
|
2012
|
+
console.log(`Canonical inventory: ${inventoryCounts.canonicalTotal} (${inventoryCounts.open} open, ${inventoryCounts.resolved} resolved)`);
|
|
2013
|
+
console.log(`Invalid issue_* facts: ${inventoryCounts.invalid}`);
|
|
2014
|
+
console.log('');
|
|
2015
|
+
if (invalidFacts.length > 0) {
|
|
2016
|
+
console.log('Warnings:');
|
|
2017
|
+
for (const invalid of invalidFacts) {
|
|
2018
|
+
console.log(` - ${invalid.key}: ${invalid.reason} [source=${invalid.source}, confidence=${invalid.confidence}]`);
|
|
2019
|
+
}
|
|
2020
|
+
console.log('');
|
|
2021
|
+
}
|
|
2022
|
+
if (items.length === 0) {
|
|
2023
|
+
console.log('No issue facts found.');
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
for (const item of items) {
|
|
2027
|
+
const header = `[${item.status.toUpperCase()}] ${item.issueId} (${item.severity})`;
|
|
2028
|
+
console.log(header);
|
|
2029
|
+
console.log(` Title: ${item.title}`);
|
|
2030
|
+
console.log(` Summary: ${item.summary}`);
|
|
2031
|
+
console.log(` Source: ${item.source}`);
|
|
2032
|
+
console.log(` Confidence: ${item.confidence}`);
|
|
2033
|
+
if (item.discoveredAt) {
|
|
2034
|
+
console.log(` Discovered: ${item.discoveredAt}`);
|
|
2035
|
+
}
|
|
2036
|
+
if (item.resolvedAt) {
|
|
2037
|
+
console.log(` Resolved: ${item.resolvedAt}`);
|
|
2038
|
+
}
|
|
2039
|
+
if (item.resolution) {
|
|
2040
|
+
console.log(` Resolution: ${item.resolution}`);
|
|
2041
|
+
}
|
|
2042
|
+
if (item.tags.length > 0) {
|
|
2043
|
+
console.log(` Tags: ${item.tags.join(', ')}`);
|
|
2044
|
+
}
|
|
2045
|
+
console.log('');
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
1716
2048
|
function buildHandoffSummary(key, value) {
|
|
1717
2049
|
switch (key) {
|
|
1718
2050
|
case 'status': {
|
|
@@ -1805,6 +2137,11 @@ function printAttendResult(target, latestMessage, result) {
|
|
|
1805
2137
|
console.log(` confidence ${result.decision.confidence}`);
|
|
1806
2138
|
console.log(` explanation ${result.decision.explanation}`);
|
|
1807
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
|
+
}
|
|
1808
2145
|
if (result.facts.length === 0) {
|
|
1809
2146
|
console.log('');
|
|
1810
2147
|
console.log('No facts selected for injection.');
|
|
@@ -1839,6 +2176,21 @@ function makeClaudeHookEntry(event, projectEnvPath) {
|
|
|
1839
2176
|
],
|
|
1840
2177
|
};
|
|
1841
2178
|
}
|
|
2179
|
+
const IRANTI_CLAUDE_ALLOWED_TOOLS = [
|
|
2180
|
+
'mcp__iranti__iranti_handshake',
|
|
2181
|
+
'mcp__iranti__iranti_attend',
|
|
2182
|
+
'mcp__iranti__iranti_observe',
|
|
2183
|
+
'mcp__iranti__iranti_checkpoint',
|
|
2184
|
+
'mcp__iranti__iranti_query',
|
|
2185
|
+
'mcp__iranti__iranti_search',
|
|
2186
|
+
'mcp__iranti__iranti_write',
|
|
2187
|
+
'mcp__iranti__iranti_remember_response',
|
|
2188
|
+
'mcp__iranti__iranti_ingest',
|
|
2189
|
+
'mcp__iranti__iranti_relate',
|
|
2190
|
+
'mcp__iranti__iranti_related',
|
|
2191
|
+
'mcp__iranti__iranti_related_deep',
|
|
2192
|
+
'mcp__iranti__iranti_who_knows',
|
|
2193
|
+
];
|
|
1842
2194
|
function isClaudeHooksObject(value) {
|
|
1843
2195
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
1844
2196
|
}
|
|
@@ -1871,12 +2223,46 @@ function needsClaudeHookSettingsUpgrade(value) {
|
|
|
1871
2223
|
}
|
|
1872
2224
|
return false;
|
|
1873
2225
|
}
|
|
2226
|
+
function mergeClaudePermissionAllowList(existing) {
|
|
2227
|
+
const currentPermissions = existing && typeof existing.permissions === 'object' && existing.permissions !== null && !Array.isArray(existing.permissions)
|
|
2228
|
+
? { ...existing.permissions }
|
|
2229
|
+
: {};
|
|
2230
|
+
const allow = Array.isArray(currentPermissions.allow)
|
|
2231
|
+
? [...currentPermissions.allow.map((value) => String(value))]
|
|
2232
|
+
: [];
|
|
2233
|
+
for (const toolName of IRANTI_CLAUDE_ALLOWED_TOOLS) {
|
|
2234
|
+
if (!allow.includes(toolName)) {
|
|
2235
|
+
allow.push(toolName);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
return {
|
|
2239
|
+
...currentPermissions,
|
|
2240
|
+
allow,
|
|
2241
|
+
};
|
|
2242
|
+
}
|
|
1874
2243
|
function makeClaudeHookSettings(projectEnvPath, existing) {
|
|
1875
2244
|
const existingHooks = existing && isClaudeHooksObject(existing.hooks)
|
|
1876
2245
|
? existing.hooks
|
|
1877
2246
|
: {};
|
|
2247
|
+
const existingMcpServers = existing && existing.mcpServers && typeof existing.mcpServers === 'object' && !Array.isArray(existing.mcpServers)
|
|
2248
|
+
? existing.mcpServers
|
|
2249
|
+
: {};
|
|
2250
|
+
const existingEnabledMcpjsonServers = Array.isArray(existing?.enabledMcpjsonServers)
|
|
2251
|
+
? existing.enabledMcpjsonServers
|
|
2252
|
+
.map((value) => String(value))
|
|
2253
|
+
.filter((value) => value !== 'iranti')
|
|
2254
|
+
: undefined;
|
|
1878
2255
|
return {
|
|
1879
2256
|
...(existing ?? {}),
|
|
2257
|
+
enableAllProjectMcpServers: false,
|
|
2258
|
+
permissions: mergeClaudePermissionAllowList(existing),
|
|
2259
|
+
mcpServers: {
|
|
2260
|
+
...existingMcpServers,
|
|
2261
|
+
iranti: makeClaudeLocalMcpServerConfig(projectEnvPath),
|
|
2262
|
+
},
|
|
2263
|
+
...(existingEnabledMcpjsonServers
|
|
2264
|
+
? { enabledMcpjsonServers: existingEnabledMcpjsonServers }
|
|
2265
|
+
: {}),
|
|
1880
2266
|
hooks: {
|
|
1881
2267
|
...existingHooks,
|
|
1882
2268
|
SessionStart: [makeClaudeHookEntry('SessionStart', projectEnvPath)],
|
|
@@ -1994,12 +2380,87 @@ async function writeClaudeCodeProjectFiles(projectPath, projectEnvPath, force =
|
|
|
1994
2380
|
settingsStatus = 'updated';
|
|
1995
2381
|
}
|
|
1996
2382
|
}
|
|
2383
|
+
const claudeMdFile = path_1.default.join(projectPath, 'CLAUDE.md');
|
|
2384
|
+
let claudeMdStatus = 'unchanged';
|
|
2385
|
+
const irantiMdBlock = buildIrantiClaudeMdBlock();
|
|
2386
|
+
if (!fs_1.default.existsSync(claudeMdFile)) {
|
|
2387
|
+
await writeText(claudeMdFile, irantiMdBlock);
|
|
2388
|
+
claudeMdStatus = 'created';
|
|
2389
|
+
}
|
|
2390
|
+
else {
|
|
2391
|
+
const existing = fs_1.default.readFileSync(claudeMdFile, 'utf8');
|
|
2392
|
+
if (!existing.includes('<!-- iranti-rules -->')) {
|
|
2393
|
+
await writeText(claudeMdFile, `${existing.trimEnd()}\n\n${irantiMdBlock}`);
|
|
2394
|
+
claudeMdStatus = 'updated';
|
|
2395
|
+
}
|
|
2396
|
+
else {
|
|
2397
|
+
const replaced = existing.replace(/<!-- iranti-rules -->[\s\S]*?<!-- \/iranti-rules -->/, irantiMdBlock.trim());
|
|
2398
|
+
if (replaced !== existing) {
|
|
2399
|
+
await writeText(claudeMdFile, replaced);
|
|
2400
|
+
claudeMdStatus = 'updated';
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
const closeout = await (0, scaffoldCloseout_1.writeProjectScaffoldCloseout)({
|
|
2405
|
+
tool: 'claude',
|
|
2406
|
+
projectPath,
|
|
2407
|
+
projectEnvFile: resolvedProjectEnvPath ?? projectEnvPath ?? null,
|
|
2408
|
+
files: [
|
|
2409
|
+
{ path: mcpFile, status: mcpStatus },
|
|
2410
|
+
{ path: vscodeMcpFile, status: vscodeMcpStatus },
|
|
2411
|
+
{ path: settingsFile, status: settingsStatus },
|
|
2412
|
+
{ path: claudeMdFile, status: claudeMdStatus },
|
|
2413
|
+
],
|
|
2414
|
+
agentId: 'iranti_cli_scaffold',
|
|
2415
|
+
});
|
|
1997
2416
|
return {
|
|
1998
2417
|
mcp: mcpStatus,
|
|
1999
2418
|
vscodeMcp: vscodeMcpStatus,
|
|
2000
2419
|
settings: settingsStatus,
|
|
2420
|
+
claudeMd: claudeMdStatus,
|
|
2421
|
+
closeout,
|
|
2001
2422
|
};
|
|
2002
2423
|
}
|
|
2424
|
+
function buildIrantiClaudeMdBlock() {
|
|
2425
|
+
return [
|
|
2426
|
+
'<!-- iranti-rules -->',
|
|
2427
|
+
'# Iranti Memory Protocol',
|
|
2428
|
+
'',
|
|
2429
|
+
'IMPORTANT: These rules OVERRIDE any default behavior and MUST be followed exactly. No work should begin until the session-start acknowledgment is stated.',
|
|
2430
|
+
'',
|
|
2431
|
+
'## Every turn',
|
|
2432
|
+
'1. Call `mcp__iranti__iranti_attend` before responding to the user message.',
|
|
2433
|
+
'2. Call `mcp__iranti__iranti_attend` before using any knowledge discovery tool — Read, Grep, Glob, WebSearch, WebFetch, and Bash commands used as factual basis for a decision.',
|
|
2434
|
+
'3. Call `mcp__iranti__iranti_attend` after knowledge discovery to check new findings against stored memory and decide what to inject, write, or checkpoint.',
|
|
2435
|
+
'4. Call `mcp__iranti__iranti_attend` after delivering any substantive response — plans, decisions, analysis, or structured recommendations are durable findings that must be assessed before the next turn.',
|
|
2436
|
+
'5. Call `mcp__iranti__iranti_write` after confirmed durable findings — decisions, blockers, next steps, validated environment details, what worked, what failed, what changed, and what happens next.',
|
|
2437
|
+
'6. Call `mcp__iranti__iranti_write` after every Edit or Write tool call — file changes are always durable. Record what changed, why, and what the file now does.',
|
|
2438
|
+
'7. Call `mcp__iranti__iranti_write` after any Bash command that reveals system state — build results, test outcomes, container state, environment facts, errors.',
|
|
2439
|
+
'8. Call `mcp__iranti__iranti_write` after any WebSearch or WebFetch that surfaces confirmed external facts.',
|
|
2440
|
+
'9. Call `mcp__iranti__iranti_write` after any Agent (subagent) tool completes — subagent findings are invisible to the hive mind otherwise.',
|
|
2441
|
+
'10. If a recall-style lookup returns no facts, try at least one alternative retrieval angle before concluding the fact is not stored.',
|
|
2442
|
+
'11. Call `mcp__iranti__iranti_attend` again when new knowledge should change what is loaded for the next step.',
|
|
2443
|
+
'',
|
|
2444
|
+
'## Session start',
|
|
2445
|
+
'- Call `mcp__iranti__iranti_handshake` with the current task before responding to the first user message.',
|
|
2446
|
+
'- After the handshake completes, you MUST explicitly state: "I acknowledge that Iranti is a hive mind. I have read the Iranti operating rules and the protocols for iranti_handshake, iranti_attend, iranti_write, iranti_checkpoint, and iranti_remember_response, and I pledge to use them to persist what I find, what works, what fails, what changes, and what happens next so later sessions do not have to rediscover it."',
|
|
2447
|
+
'- This acknowledgment is strictly mandatory. No work should begin until it is stated.',
|
|
2448
|
+
'',
|
|
2449
|
+
'## After context compaction',
|
|
2450
|
+
'- Call `mcp__iranti__iranti_handshake` before responding to the next user message.',
|
|
2451
|
+
'',
|
|
2452
|
+
'## Checkpointing',
|
|
2453
|
+
'- Call `mcp__iranti__iranti_checkpoint` when completing a task, when shifting to a new task mid-session, at any natural pause point, and before stepping away from long or interrupted work.',
|
|
2454
|
+
'- Record key actions in the checkpoint `actions` field so later sessions can see important commands, tests, searches, validations, and decisions without rerunning them blindly.',
|
|
2455
|
+
'- Do not rely on `mcp__iranti__iranti_write` alone — facts and checkpoints are separate stores. A checkpoint not written means the next handshake recovers from stale data.',
|
|
2456
|
+
'- Under-logged runs are non-compliant. Leave structured breadcrumbs for what you found, what worked, what failed, what changed, and what happens next instead of only a broad summary.',
|
|
2457
|
+
'',
|
|
2458
|
+
'## Host setup check',
|
|
2459
|
+
'- If this file was not present at session start, run `iranti claude-setup .` to complete integration.',
|
|
2460
|
+
'<!-- /iranti-rules -->',
|
|
2461
|
+
'',
|
|
2462
|
+
].join('\n');
|
|
2463
|
+
}
|
|
2003
2464
|
function hasCodexInstalled() {
|
|
2004
2465
|
try {
|
|
2005
2466
|
const proc = (0, commandInvocation_1.spawnSyncResolved)('codex', ['--version'], { stdio: 'ignore' });
|
|
@@ -2055,6 +2516,10 @@ async function isPortAvailable(port, host = '0.0.0.0') {
|
|
|
2055
2516
|
});
|
|
2056
2517
|
}
|
|
2057
2518
|
function listPublishedDockerHostPorts() {
|
|
2519
|
+
const injected = process.env.IRANTI_FAKE_DOCKER_PORTS?.trim();
|
|
2520
|
+
if (injected) {
|
|
2521
|
+
return (0, dockerCliParsing_1.parsePublishedDockerHostPorts)(injected);
|
|
2522
|
+
}
|
|
2058
2523
|
const docker = inspectDockerAvailability();
|
|
2059
2524
|
if (!docker.daemonReachable)
|
|
2060
2525
|
return new Set();
|
|
@@ -2305,12 +2770,21 @@ async function executeSetupPlan(plan) {
|
|
|
2305
2770
|
IRANTI_INSTANCE: plan.instanceName,
|
|
2306
2771
|
IRANTI_INSTANCE_ENV: configured.envFile,
|
|
2307
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
|
+
});
|
|
2308
2780
|
bindings.push({
|
|
2309
2781
|
projectPath,
|
|
2310
2782
|
envFile: written,
|
|
2311
2783
|
agentId: project.agentId,
|
|
2312
2784
|
projectMode: project.projectMode,
|
|
2313
2785
|
autoRemember: project.autoRemember,
|
|
2786
|
+
codebaseEntity: bindingEnv.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath),
|
|
2787
|
+
learningStatus,
|
|
2314
2788
|
});
|
|
2315
2789
|
if (project.claudeCode) {
|
|
2316
2790
|
await writeClaudeCodeProjectFiles(projectPath);
|
|
@@ -2339,7 +2813,7 @@ async function executeSetupPlan(plan) {
|
|
|
2339
2813
|
bindings,
|
|
2340
2814
|
};
|
|
2341
2815
|
}
|
|
2342
|
-
function parseSetupConfig(filePath) {
|
|
2816
|
+
async function parseSetupConfig(filePath) {
|
|
2343
2817
|
const resolved = path_1.default.resolve(filePath);
|
|
2344
2818
|
if (!fs_1.default.existsSync(resolved)) {
|
|
2345
2819
|
throw new Error(`Setup config file not found: ${resolved}`);
|
|
@@ -2361,7 +2835,7 @@ function parseSetupConfig(filePath) {
|
|
|
2361
2835
|
: databaseModeRaw === 'existing' || databaseModeRaw === 'local' || databaseModeRaw.length === 0
|
|
2362
2836
|
? 'local'
|
|
2363
2837
|
: (() => { throw new Error(`Unsupported databaseMode in setup config: ${databaseModeRaw}`); })();
|
|
2364
|
-
const databaseUrl =
|
|
2838
|
+
const databaseUrl = await resolveSetupDatabaseUrl(databaseMode, instanceName, String(raw?.databaseUrl ?? raw?.dbUrl ?? '').trim());
|
|
2365
2839
|
const databaseIntentStrategy = parseDatabaseIntentStrategyFlag(String(raw?.databaseIntent ?? raw?.dbIntent ?? '').trim(), 'databaseIntent');
|
|
2366
2840
|
const provider = normalizeProvider(String(raw?.provider ?? 'mock')) ?? 'mock';
|
|
2367
2841
|
if (!isSupportedProvider(provider)) {
|
|
@@ -2423,7 +2897,7 @@ function parseSetupConfig(filePath) {
|
|
|
2423
2897
|
}),
|
|
2424
2898
|
};
|
|
2425
2899
|
}
|
|
2426
|
-
function defaultsSetupPlan(args) {
|
|
2900
|
+
async function defaultsSetupPlan(args) {
|
|
2427
2901
|
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
2428
2902
|
const root = path_1.default.resolve(getFlag(args, 'root') ?? resolveInstallRoot(args, scope));
|
|
2429
2903
|
const mode = getFlag(args, 'mode') === 'shared' ? 'shared' : 'isolated';
|
|
@@ -2450,7 +2924,7 @@ function defaultsSetupPlan(args) {
|
|
|
2450
2924
|
const explicitDatabaseUrl = (getFlag(args, 'db-url')
|
|
2451
2925
|
?? (databaseMode === 'managed' ? process.env.DATABASE_URL : '')
|
|
2452
2926
|
?? '').trim();
|
|
2453
|
-
const databaseUrl =
|
|
2927
|
+
const databaseUrl = await resolveSetupDatabaseUrl(databaseMode, instanceName, explicitDatabaseUrl);
|
|
2454
2928
|
const databaseIntentStrategy = parseDatabaseIntentStrategyFlag(getFlag(args, 'db-intent'), '--db-intent');
|
|
2455
2929
|
const provider = normalizeProvider(getFlag(args, 'provider') ?? process.env.LLM_PROVIDER ?? 'mock') ?? 'mock';
|
|
2456
2930
|
if (!isSupportedProvider(provider)) {
|
|
@@ -2609,6 +3083,22 @@ function summarizeStatus(checks) {
|
|
|
2609
3083
|
return 'warn';
|
|
2610
3084
|
return 'pass';
|
|
2611
3085
|
}
|
|
3086
|
+
async function resolveDatabaseAuthorityInfo(repoEnvFile, boundInstanceEnvFile) {
|
|
3087
|
+
const normalizedRepoEnvFile = repoEnvFile && fs_1.default.existsSync(repoEnvFile) ? repoEnvFile : null;
|
|
3088
|
+
const normalizedBoundEnvFile = boundInstanceEnvFile && fs_1.default.existsSync(boundInstanceEnvFile) ? boundInstanceEnvFile : null;
|
|
3089
|
+
const repoEnv = normalizedRepoEnvFile ? await readEnvFile(normalizedRepoEnvFile) : null;
|
|
3090
|
+
const boundEnv = normalizedBoundEnvFile ? await readEnvFile(normalizedBoundEnvFile) : null;
|
|
3091
|
+
const repoEnvDatabaseUrl = repoEnv?.DATABASE_URL?.trim() || null;
|
|
3092
|
+
const boundInstanceDatabaseUrl = boundEnv?.DATABASE_URL?.trim() || null;
|
|
3093
|
+
return {
|
|
3094
|
+
repoEnvFile: normalizedRepoEnvFile,
|
|
3095
|
+
repoEnvDatabaseUrl,
|
|
3096
|
+
boundInstanceDatabaseUrl,
|
|
3097
|
+
mismatch: Boolean(repoEnvDatabaseUrl
|
|
3098
|
+
&& boundInstanceDatabaseUrl
|
|
3099
|
+
&& repoEnvDatabaseUrl !== boundInstanceDatabaseUrl),
|
|
3100
|
+
};
|
|
3101
|
+
}
|
|
2612
3102
|
function detectVsCodeMcpWorkspaceCheck(projectEnvPath) {
|
|
2613
3103
|
const vscodeMcpPath = path_1.default.join(path_1.default.dirname(projectEnvPath), '.vscode', 'mcp.json');
|
|
2614
3104
|
if (!fs_1.default.existsSync(vscodeMcpPath)) {
|
|
@@ -2680,6 +3170,9 @@ function collectDoctorRemediations(checks, envSource, envFile) {
|
|
|
2680
3170
|
if (check.name === 'bound instance env' && check.status !== 'pass') {
|
|
2681
3171
|
add('Run `iranti configure project` to refresh the project binding, or set IRANTI_INSTANCE_ENV in `.env.iranti` so doctor can inspect the bound local instance.');
|
|
2682
3172
|
}
|
|
3173
|
+
if (check.name === 'runtime root selection' && check.status !== 'pass') {
|
|
3174
|
+
add('Rerun `iranti doctor` with `--root <runtime-root>` that matches the bound project instance, or refresh `.env.iranti` with `iranti configure project` if the binding is stale.');
|
|
3175
|
+
}
|
|
2683
3176
|
if (check.name === 'vscode mcp workspace' && check.status !== 'pass') {
|
|
2684
3177
|
add('Run `iranti codex-setup` from the project root to scaffold `.vscode/mcp.json`, or add an `iranti` server entry there manually for VS Code MCP clients.');
|
|
2685
3178
|
}
|
|
@@ -2723,15 +3216,14 @@ function resolveDoctorEnvTarget(args) {
|
|
|
2723
3216
|
envSource: `instance:${instanceName}`,
|
|
2724
3217
|
};
|
|
2725
3218
|
}
|
|
2726
|
-
const
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
return { envFile: repoEnv, envSource: 'repo' };
|
|
3219
|
+
const discovered = findClosestDoctorEnvTarget(cwd);
|
|
3220
|
+
if (discovered.envFile && discovered.envSource === 'repo') {
|
|
3221
|
+
debugLog('Doctor target resolved from repo env.', { envFile: discovered.envFile });
|
|
3222
|
+
return discovered;
|
|
2731
3223
|
}
|
|
2732
|
-
if (
|
|
2733
|
-
debugLog('Doctor target resolved from project binding.', { envFile:
|
|
2734
|
-
return
|
|
3224
|
+
if (discovered.envFile && discovered.envSource === 'project-binding') {
|
|
3225
|
+
debugLog('Doctor target resolved from project binding.', { envFile: discovered.envFile });
|
|
3226
|
+
return discovered;
|
|
2735
3227
|
}
|
|
2736
3228
|
debugLog('Doctor target resolution found no env file.', { cwd });
|
|
2737
3229
|
return { envFile: null, envSource: 'repo' };
|
|
@@ -3022,6 +3514,23 @@ function deriveDatabaseUrlForMode(mode, instanceName, explicitDatabaseUrl) {
|
|
|
3022
3514
|
}
|
|
3023
3515
|
return `postgresql://${user}:${password}@${localDatabaseHost}:5432/iranti_${instanceName}`;
|
|
3024
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
|
+
}
|
|
3025
3534
|
function preferredLocalDatabaseHost() {
|
|
3026
3535
|
return process.platform === 'win32' ? '127.0.0.1' : 'localhost';
|
|
3027
3536
|
}
|
|
@@ -3906,6 +4415,15 @@ function removeIrantiClaudeHooksFromValue(value) {
|
|
|
3906
4415
|
}
|
|
3907
4416
|
return Object.keys(next).length === 0 ? null : next;
|
|
3908
4417
|
}
|
|
4418
|
+
function removeIrantiAgentsBlockFromText(value) {
|
|
4419
|
+
if (!value.includes('<!-- iranti-rules -->'))
|
|
4420
|
+
return value;
|
|
4421
|
+
const next = value
|
|
4422
|
+
.replace(/<!-- iranti-rules -->[\s\S]*?<!-- \/iranti-rules -->/g, '')
|
|
4423
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
4424
|
+
.trim();
|
|
4425
|
+
return next.length === 0 ? null : `${next}\n`;
|
|
4426
|
+
}
|
|
3909
4427
|
async function cleanupProjectBindingIntegrations(projectPath) {
|
|
3910
4428
|
const result = { removed: [], updated: [], warnings: [] };
|
|
3911
4429
|
const candidates = [
|
|
@@ -3952,6 +4470,26 @@ async function cleanupProjectBindingIntegrations(projectPath) {
|
|
|
3952
4470
|
}
|
|
3953
4471
|
}
|
|
3954
4472
|
}
|
|
4473
|
+
const agentsFile = path_1.default.join(projectPath, 'AGENTS.md');
|
|
4474
|
+
if (fs_1.default.existsSync(agentsFile)) {
|
|
4475
|
+
try {
|
|
4476
|
+
const existing = await promises_1.default.readFile(agentsFile, 'utf8');
|
|
4477
|
+
const next = removeIrantiAgentsBlockFromText(existing);
|
|
4478
|
+
if (next !== existing) {
|
|
4479
|
+
if (!next) {
|
|
4480
|
+
await promises_1.default.rm(agentsFile, { force: true });
|
|
4481
|
+
result.removed.push(agentsFile);
|
|
4482
|
+
}
|
|
4483
|
+
else {
|
|
4484
|
+
await writeText(agentsFile, next);
|
|
4485
|
+
result.updated.push(agentsFile);
|
|
4486
|
+
}
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
catch {
|
|
4490
|
+
result.warnings.push(`Skipped unreadable Codex agents file ${agentsFile}`);
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
3955
4493
|
return result;
|
|
3956
4494
|
}
|
|
3957
4495
|
async function cleanupProjectArtifacts(artifacts) {
|
|
@@ -4023,6 +4561,37 @@ async function cleanupProjectArtifacts(artifacts) {
|
|
|
4023
4561
|
});
|
|
4024
4562
|
}
|
|
4025
4563
|
}
|
|
4564
|
+
if (artifact.agentsFile && fs_1.default.existsSync(artifact.agentsFile)) {
|
|
4565
|
+
try {
|
|
4566
|
+
const existing = await promises_1.default.readFile(artifact.agentsFile, 'utf8');
|
|
4567
|
+
const next = removeIrantiAgentsBlockFromText(existing);
|
|
4568
|
+
if (next !== existing) {
|
|
4569
|
+
if (!next) {
|
|
4570
|
+
await promises_1.default.rm(artifact.agentsFile, { force: true });
|
|
4571
|
+
results.push({
|
|
4572
|
+
label: 'project-agents',
|
|
4573
|
+
status: 'pass',
|
|
4574
|
+
detail: `Removed ${artifact.agentsFile}`,
|
|
4575
|
+
});
|
|
4576
|
+
}
|
|
4577
|
+
else {
|
|
4578
|
+
await writeText(artifact.agentsFile, next);
|
|
4579
|
+
results.push({
|
|
4580
|
+
label: 'project-agents',
|
|
4581
|
+
status: 'pass',
|
|
4582
|
+
detail: `Removed Iranti Codex block from ${artifact.agentsFile}`,
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
catch {
|
|
4588
|
+
results.push({
|
|
4589
|
+
label: 'project-agents',
|
|
4590
|
+
status: 'warn',
|
|
4591
|
+
detail: `Skipped unreadable text file ${artifact.agentsFile}`,
|
|
4592
|
+
});
|
|
4593
|
+
}
|
|
4594
|
+
}
|
|
4026
4595
|
}
|
|
4027
4596
|
return results;
|
|
4028
4597
|
}
|
|
@@ -4161,7 +4730,7 @@ async function uninstallCommand(args) {
|
|
|
4161
4730
|
&& !dryRun;
|
|
4162
4731
|
if (execute && !dryRun) {
|
|
4163
4732
|
if (requiresDetachedWindowsSelfUninstall) {
|
|
4164
|
-
const artifactFiles = projectArtifacts.flatMap((artifact) => [artifact.bindingFile, artifact.mcpFile, artifact.claudeSettingsFile]
|
|
4733
|
+
const artifactFiles = projectArtifacts.flatMap((artifact) => [artifact.bindingFile, artifact.mcpFile, artifact.claudeSettingsFile, artifact.agentsFile]
|
|
4165
4734
|
.filter((value) => Boolean(value)));
|
|
4166
4735
|
const script = buildDetachedWindowsUninstallScript({
|
|
4167
4736
|
parentPid: process.pid,
|
|
@@ -4473,7 +5042,7 @@ async function setupCommand(args) {
|
|
|
4473
5042
|
const dependencyChecks = await collectDependencyChecks();
|
|
4474
5043
|
printDependencyChecks(dependencyChecks);
|
|
4475
5044
|
console.log('');
|
|
4476
|
-
const plan = configPath ? parseSetupConfig(configPath) : defaultsSetupPlan(args);
|
|
5045
|
+
const plan = configPath ? await parseSetupConfig(configPath) : await defaultsSetupPlan(args);
|
|
4477
5046
|
const result = await executeSetupPlan(plan);
|
|
4478
5047
|
console.log(sectionTitle('Setup Complete'));
|
|
4479
5048
|
console.log(` runtime root ${result.root}`);
|
|
@@ -4490,7 +5059,10 @@ async function setupCommand(args) {
|
|
|
4490
5059
|
else {
|
|
4491
5060
|
console.log(' projects');
|
|
4492
5061
|
for (const binding of result.bindings) {
|
|
4493
|
-
|
|
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})`);
|
|
4494
5066
|
}
|
|
4495
5067
|
}
|
|
4496
5068
|
printNextSteps([
|
|
@@ -4853,7 +5425,10 @@ async function setupCommand(args) {
|
|
|
4853
5425
|
else {
|
|
4854
5426
|
console.log(' projects');
|
|
4855
5427
|
for (const binding of finalResult.bindings) {
|
|
4856
|
-
|
|
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})`);
|
|
4857
5432
|
}
|
|
4858
5433
|
}
|
|
4859
5434
|
const nextSteps = [
|
|
@@ -4868,8 +5443,37 @@ async function setupCommand(args) {
|
|
|
4868
5443
|
}
|
|
4869
5444
|
async function doctorCommand(args) {
|
|
4870
5445
|
const json = hasFlag(args, 'json');
|
|
5446
|
+
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
4871
5447
|
const { envFile, envSource } = resolveDoctorEnvTarget(args);
|
|
5448
|
+
const repoEnvFile = findClosestAncestorFile(process.cwd(), '.env');
|
|
5449
|
+
const resolution = resolveInstallRootDetails(args, scope);
|
|
5450
|
+
const discovery = {
|
|
5451
|
+
selectedRuntimeRoot: resolution.root,
|
|
5452
|
+
selectionSource: resolution.source,
|
|
5453
|
+
selectionReason: describeRuntimeRootSource(resolution.source),
|
|
5454
|
+
boundRuntimeRoot: null,
|
|
5455
|
+
boundInstanceEnv: null,
|
|
5456
|
+
projectBindingFile: null,
|
|
5457
|
+
projectBindingSource: null,
|
|
5458
|
+
rootMismatch: false,
|
|
5459
|
+
otherRuntimeRoots: [],
|
|
5460
|
+
};
|
|
4872
5461
|
const checks = [];
|
|
5462
|
+
let authority = {
|
|
5463
|
+
activeAuthority: summarizeActiveAuthority(envSource, null, null),
|
|
5464
|
+
activeBindingSource: null,
|
|
5465
|
+
activeBoundInstanceEnv: null,
|
|
5466
|
+
activeDatabaseUrl: null,
|
|
5467
|
+
activeDatabaseTarget: null,
|
|
5468
|
+
repoDatabaseUrl: null,
|
|
5469
|
+
repoDatabaseTarget: null,
|
|
5470
|
+
repoDatabaseDiffers: false,
|
|
5471
|
+
nearbyBindingSource: null,
|
|
5472
|
+
nearbyBoundInstanceEnv: null,
|
|
5473
|
+
nearbyBindingDatabaseUrl: null,
|
|
5474
|
+
nearbyBindingDatabaseTarget: null,
|
|
5475
|
+
nearbyBindingDiffers: false,
|
|
5476
|
+
};
|
|
4873
5477
|
const version = getPackageVersion();
|
|
4874
5478
|
const pushEnvironmentChecks = async (env, prefix = '') => {
|
|
4875
5479
|
const databaseUrl = env.DATABASE_URL;
|
|
@@ -4902,7 +5506,7 @@ async function doctorCommand(args) {
|
|
|
4902
5506
|
});
|
|
4903
5507
|
try {
|
|
4904
5508
|
if (!detectPlaceholder(databaseUrl)) {
|
|
4905
|
-
(0, client_1.initDb)(databaseUrl);
|
|
5509
|
+
(0, client_1.initDb)(databaseUrl, { applicationName: 'iranti:cli:doctor' });
|
|
4906
5510
|
databaseInitializedForDoctor = true;
|
|
4907
5511
|
}
|
|
4908
5512
|
const backendName = (0, backends_1.resolveVectorBackendName)({
|
|
@@ -4992,12 +5596,33 @@ async function doctorCommand(args) {
|
|
|
4992
5596
|
const treatAsProjectBinding = envSource === 'project-binding'
|
|
4993
5597
|
|| path_1.default.basename(envFile).toLowerCase() === '.env.iranti'
|
|
4994
5598
|
|| (Boolean(env.IRANTI_URL?.trim()) && detectPlaceholder(env.DATABASE_URL));
|
|
5599
|
+
let linkedInstanceEnvFile = null;
|
|
5600
|
+
let linkedEnv = null;
|
|
4995
5601
|
checks.push({
|
|
4996
5602
|
name: 'environment file',
|
|
4997
5603
|
status: 'pass',
|
|
4998
5604
|
detail: `${envSource} env loaded from ${envFile}`,
|
|
4999
5605
|
});
|
|
5000
5606
|
if (treatAsProjectBinding) {
|
|
5607
|
+
const binding = await inspectProjectBinding(envFile);
|
|
5608
|
+
const boundRuntimeRoot = binding.runtimeRoot ? path_1.default.resolve(binding.runtimeRoot) : null;
|
|
5609
|
+
const selectedRuntimeRoot = path_1.default.resolve(resolution.root);
|
|
5610
|
+
const otherRuntimeRoots = Array.from(new Set([boundRuntimeRoot]
|
|
5611
|
+
.filter((candidate) => Boolean(candidate))
|
|
5612
|
+
.filter((candidate) => candidate !== selectedRuntimeRoot && fs_1.default.existsSync(candidate))));
|
|
5613
|
+
discovery.boundRuntimeRoot = boundRuntimeRoot;
|
|
5614
|
+
discovery.boundInstanceEnv = binding.instanceEnvFile;
|
|
5615
|
+
discovery.projectBindingFile = binding.bindingFile;
|
|
5616
|
+
discovery.projectBindingSource = 'doctor-target';
|
|
5617
|
+
discovery.rootMismatch = Boolean(boundRuntimeRoot && boundRuntimeRoot !== selectedRuntimeRoot);
|
|
5618
|
+
discovery.otherRuntimeRoots = otherRuntimeRoots;
|
|
5619
|
+
checks.push({
|
|
5620
|
+
name: 'runtime root selection',
|
|
5621
|
+
status: discovery.rootMismatch ? 'warn' : 'pass',
|
|
5622
|
+
detail: discovery.rootMismatch
|
|
5623
|
+
? `Doctor selected runtime root ${selectedRuntimeRoot} (${describeRuntimeRootSource(resolution.source)}), but the project binding points at ${boundRuntimeRoot}.`
|
|
5624
|
+
: `Doctor selected runtime root ${selectedRuntimeRoot} (${describeRuntimeRootSource(resolution.source)}).`,
|
|
5625
|
+
});
|
|
5001
5626
|
checks.push(detectPlaceholder(env.IRANTI_URL)
|
|
5002
5627
|
? {
|
|
5003
5628
|
name: 'project binding url',
|
|
@@ -5023,28 +5648,28 @@ async function doctorCommand(args) {
|
|
|
5023
5648
|
status: 'pass',
|
|
5024
5649
|
detail: 'IRANTI_API_KEY is present in .env.iranti.',
|
|
5025
5650
|
});
|
|
5026
|
-
|
|
5027
|
-
if (!
|
|
5651
|
+
linkedInstanceEnvFile = env.IRANTI_INSTANCE_ENV?.trim() || null;
|
|
5652
|
+
if (!linkedInstanceEnvFile) {
|
|
5028
5653
|
checks.push({
|
|
5029
5654
|
name: 'bound instance env',
|
|
5030
5655
|
status: 'warn',
|
|
5031
5656
|
detail: 'IRANTI_INSTANCE_ENV is not set in .env.iranti. Skipping database and provider checks for the bound instance.',
|
|
5032
5657
|
});
|
|
5033
5658
|
}
|
|
5034
|
-
else if (!fs_1.default.existsSync(
|
|
5659
|
+
else if (!fs_1.default.existsSync(linkedInstanceEnvFile)) {
|
|
5035
5660
|
checks.push({
|
|
5036
5661
|
name: 'bound instance env',
|
|
5037
5662
|
status: 'warn',
|
|
5038
|
-
detail: `Linked instance env not found: ${
|
|
5663
|
+
detail: `Linked instance env not found: ${linkedInstanceEnvFile}. Skipping database and provider checks for the bound instance.`,
|
|
5039
5664
|
});
|
|
5040
5665
|
}
|
|
5041
5666
|
else {
|
|
5042
5667
|
checks.push({
|
|
5043
5668
|
name: 'bound instance env',
|
|
5044
5669
|
status: 'pass',
|
|
5045
|
-
detail: `Using ${
|
|
5670
|
+
detail: `Using ${linkedInstanceEnvFile} for bound instance diagnostics.`,
|
|
5046
5671
|
});
|
|
5047
|
-
|
|
5672
|
+
linkedEnv = await readEnvFile(linkedInstanceEnvFile);
|
|
5048
5673
|
await pushEnvironmentChecks(linkedEnv, 'bound instance ');
|
|
5049
5674
|
}
|
|
5050
5675
|
}
|
|
@@ -5062,11 +5687,55 @@ async function doctorCommand(args) {
|
|
|
5062
5687
|
detail: 'IRANTI_API_KEY is present.',
|
|
5063
5688
|
});
|
|
5064
5689
|
}
|
|
5690
|
+
const nearbyProjectBindingFile = treatAsProjectBinding
|
|
5691
|
+
? null
|
|
5692
|
+
: findClosestAncestorFile(path_1.default.dirname(envFile), '.env.iranti');
|
|
5693
|
+
const nearbyBindingEnv = await readEnvFileIfExists(nearbyProjectBindingFile);
|
|
5694
|
+
const nearbyBoundInstanceEnvFile = nearbyBindingEnv?.IRANTI_INSTANCE_ENV?.trim() || null;
|
|
5695
|
+
const nearbyBoundInstanceEnv = await readEnvFileIfExists(nearbyBoundInstanceEnvFile);
|
|
5696
|
+
authority = await buildOperatorAuthoritySummary({
|
|
5697
|
+
envSource,
|
|
5698
|
+
envFile,
|
|
5699
|
+
env,
|
|
5700
|
+
bindingFile: treatAsProjectBinding ? envFile : null,
|
|
5701
|
+
boundInstanceEnvFile: linkedInstanceEnvFile,
|
|
5702
|
+
boundInstanceEnv: linkedEnv,
|
|
5703
|
+
repoEnvFile,
|
|
5704
|
+
nearbyBindingFile: nearbyProjectBindingFile,
|
|
5705
|
+
nearbyBoundInstanceEnvFile,
|
|
5706
|
+
nearbyBoundInstanceEnv,
|
|
5707
|
+
});
|
|
5708
|
+
if (!treatAsProjectBinding && authority.nearbyBindingSource && authority.nearbyBindingDiffers) {
|
|
5709
|
+
checks.push({
|
|
5710
|
+
name: 'nearby project binding authority',
|
|
5711
|
+
status: 'warn',
|
|
5712
|
+
detail: `Repo env is the active doctor target, but nearby project binding ${authority.nearbyBindingSource} points at ${authority.nearbyBindingDatabaseTarget ?? 'an unknown database target'} via ${authority.nearbyBoundInstanceEnv ?? 'an unknown bound env'}. Direct DB checks against repo .env may miss facts stored in the bound instance DB.`,
|
|
5713
|
+
});
|
|
5714
|
+
}
|
|
5065
5715
|
}
|
|
5716
|
+
debugLog('Doctor authority summary resolved.', {
|
|
5717
|
+
activeAuthority: authority.activeAuthority,
|
|
5718
|
+
activeBindingSource: authority.activeBindingSource ?? null,
|
|
5719
|
+
activeBoundInstanceEnv: authority.activeBoundInstanceEnv ?? null,
|
|
5720
|
+
activeDatabaseTarget: authority.activeDatabaseTarget ?? null,
|
|
5721
|
+
repoDatabaseTarget: authority.repoDatabaseTarget ?? null,
|
|
5722
|
+
repoDatabaseDiffers: authority.repoDatabaseDiffers,
|
|
5723
|
+
nearbyBindingSource: authority.nearbyBindingSource ?? null,
|
|
5724
|
+
nearbyBindingDatabaseTarget: authority.nearbyBindingDatabaseTarget ?? null,
|
|
5725
|
+
nearbyBindingDiffers: authority.nearbyBindingDiffers,
|
|
5726
|
+
});
|
|
5066
5727
|
const result = {
|
|
5067
5728
|
version,
|
|
5068
5729
|
envSource,
|
|
5069
5730
|
envFile,
|
|
5731
|
+
authority,
|
|
5732
|
+
selectedRuntimeRoot: discovery.selectedRuntimeRoot,
|
|
5733
|
+
selectedRuntimeRootSource: discovery.selectionSource,
|
|
5734
|
+
boundRuntimeRoot: discovery.boundRuntimeRoot,
|
|
5735
|
+
boundInstanceEnv: discovery.boundInstanceEnv,
|
|
5736
|
+
rootMismatch: discovery.rootMismatch,
|
|
5737
|
+
otherRuntimeRoots: discovery.otherRuntimeRoots,
|
|
5738
|
+
discovery,
|
|
5070
5739
|
status: summarizeStatus(checks),
|
|
5071
5740
|
checks,
|
|
5072
5741
|
remediations: collectDoctorRemediations(checks, envSource, envFile),
|
|
@@ -5087,6 +5756,26 @@ async function doctorCommand(args) {
|
|
|
5087
5756
|
: paint(result.status.toUpperCase(), 'red')}`);
|
|
5088
5757
|
if (envFile)
|
|
5089
5758
|
console.log(` env : ${envFile}`);
|
|
5759
|
+
console.log(` authority : ${authority.activeAuthority}`);
|
|
5760
|
+
if (authority.activeBindingSource)
|
|
5761
|
+
console.log(` binding : ${authority.activeBindingSource}`);
|
|
5762
|
+
if (authority.activeBoundInstanceEnv)
|
|
5763
|
+
console.log(` bound env : ${authority.activeBoundInstanceEnv}`);
|
|
5764
|
+
if (authority.activeDatabaseTarget)
|
|
5765
|
+
console.log(` active db : ${authority.activeDatabaseTarget}`);
|
|
5766
|
+
if (authority.activeDatabaseUrl)
|
|
5767
|
+
console.log(` active url: ${authority.activeDatabaseUrl}`);
|
|
5768
|
+
if (authority.repoDatabaseDiffers && authority.repoDatabaseTarget) {
|
|
5769
|
+
console.log(` repo db : ${authority.repoDatabaseTarget} (repo .env differs)`);
|
|
5770
|
+
}
|
|
5771
|
+
if (authority.nearbyBindingDiffers) {
|
|
5772
|
+
if (authority.nearbyBindingSource)
|
|
5773
|
+
console.log(` nearby binding : ${authority.nearbyBindingSource}`);
|
|
5774
|
+
if (authority.nearbyBoundInstanceEnv)
|
|
5775
|
+
console.log(` nearby bound env: ${authority.nearbyBoundInstanceEnv}`);
|
|
5776
|
+
if (authority.nearbyBindingDatabaseTarget)
|
|
5777
|
+
console.log(` nearby db : ${authority.nearbyBindingDatabaseTarget} (binding differs)`);
|
|
5778
|
+
}
|
|
5090
5779
|
console.log('');
|
|
5091
5780
|
for (const check of checks) {
|
|
5092
5781
|
const marker = check.status === 'pass'
|
|
@@ -5120,6 +5809,24 @@ async function statusCommand(args) {
|
|
|
5120
5809
|
const binding = projectEnv && fs_1.default.existsSync(projectEnv) ? await inspectProjectBinding(projectEnv) : null;
|
|
5121
5810
|
const boundRuntimeRoot = binding?.runtimeRoot ?? null;
|
|
5122
5811
|
const boundInstanceEnv = binding?.instanceEnvFile ?? null;
|
|
5812
|
+
const projectBindingEnv = await readEnvFileIfExists(projectEnv);
|
|
5813
|
+
const boundInstanceEnvMap = await readEnvFileIfExists(boundInstanceEnv);
|
|
5814
|
+
const activeStatusEnv = projectEnv && fs_1.default.existsSync(projectEnv)
|
|
5815
|
+
? projectBindingEnv
|
|
5816
|
+
: await readEnvFileIfExists(repoEnv);
|
|
5817
|
+
const authority = await buildOperatorAuthoritySummary({
|
|
5818
|
+
envSource: projectEnv && fs_1.default.existsSync(projectEnv) ? 'project-binding' : repoEnv && fs_1.default.existsSync(repoEnv) ? 'repo' : 'environment',
|
|
5819
|
+
envFile: projectEnv && fs_1.default.existsSync(projectEnv)
|
|
5820
|
+
? projectEnv
|
|
5821
|
+
: repoEnv && fs_1.default.existsSync(repoEnv)
|
|
5822
|
+
? repoEnv
|
|
5823
|
+
: null,
|
|
5824
|
+
env: activeStatusEnv,
|
|
5825
|
+
bindingFile: projectEnv && fs_1.default.existsSync(projectEnv) ? projectEnv : null,
|
|
5826
|
+
boundInstanceEnvFile: boundInstanceEnv,
|
|
5827
|
+
boundInstanceEnv: boundInstanceEnvMap,
|
|
5828
|
+
repoEnvFile: repoEnv,
|
|
5829
|
+
});
|
|
5123
5830
|
const rootMismatch = Boolean(boundRuntimeRoot && path_1.default.resolve(boundRuntimeRoot) !== path_1.default.resolve(root));
|
|
5124
5831
|
const userInstallRuntimeRoot = fs_1.default.existsSync(path_1.default.join(resolution.userRoot, 'install.json')) ? resolution.userRoot : null;
|
|
5125
5832
|
const systemInstallRuntimeRoot = fs_1.default.existsSync(path_1.default.join(resolution.systemRoot, 'install.json')) ? resolution.systemRoot : null;
|
|
@@ -5132,6 +5839,18 @@ async function statusCommand(args) {
|
|
|
5132
5839
|
rows.push({ label: 'scope', value: scope });
|
|
5133
5840
|
rows.push({ label: 'runtime_root', value: root });
|
|
5134
5841
|
rows.push({ label: 'root_source', value: describeRuntimeRootSource(resolution.source) });
|
|
5842
|
+
rows.push({ label: 'authority', value: authority.activeAuthority });
|
|
5843
|
+
if (authority.activeBindingSource)
|
|
5844
|
+
rows.push({ label: 'binding_source', value: authority.activeBindingSource });
|
|
5845
|
+
if (authority.activeBoundInstanceEnv)
|
|
5846
|
+
rows.push({ label: 'bound_instance_env', value: authority.activeBoundInstanceEnv });
|
|
5847
|
+
if (authority.activeDatabaseTarget)
|
|
5848
|
+
rows.push({ label: 'active_db_target', value: authority.activeDatabaseTarget });
|
|
5849
|
+
if (authority.activeDatabaseUrl)
|
|
5850
|
+
rows.push({ label: 'active_db_url', value: authority.activeDatabaseUrl });
|
|
5851
|
+
if (authority.repoDatabaseDiffers && authority.repoDatabaseTarget) {
|
|
5852
|
+
rows.push({ label: 'repo_db_target', value: `${authority.repoDatabaseTarget} (repo .env differs)` });
|
|
5853
|
+
}
|
|
5135
5854
|
if (boundRuntimeRoot)
|
|
5136
5855
|
rows.push({ label: 'bound_root', value: boundRuntimeRoot });
|
|
5137
5856
|
rows.push({ label: 'repo_env', value: repoEnv && fs_1.default.existsSync(repoEnv) ? repoEnv : '(missing)' });
|
|
@@ -5141,12 +5860,21 @@ async function statusCommand(args) {
|
|
|
5141
5860
|
rows.push({ label: 'root_mismatch', value: 'project binding points at a different runtime root' });
|
|
5142
5861
|
const instances = await collectRuntimeInstanceSummaries(root);
|
|
5143
5862
|
const recommendedActions = Array.from(new Set(instances.flatMap((instance) => instance.repairHints)));
|
|
5863
|
+
debugLog('Status authority summary resolved.', {
|
|
5864
|
+
activeAuthority: authority.activeAuthority,
|
|
5865
|
+
activeBindingSource: authority.activeBindingSource ?? null,
|
|
5866
|
+
activeBoundInstanceEnv: authority.activeBoundInstanceEnv ?? null,
|
|
5867
|
+
activeDatabaseTarget: authority.activeDatabaseTarget ?? null,
|
|
5868
|
+
repoDatabaseTarget: authority.repoDatabaseTarget ?? null,
|
|
5869
|
+
repoDatabaseDiffers: authority.repoDatabaseDiffers,
|
|
5870
|
+
});
|
|
5144
5871
|
if (json) {
|
|
5145
5872
|
console.log(JSON.stringify({
|
|
5146
5873
|
version: getPackageVersion(),
|
|
5147
5874
|
scope,
|
|
5148
5875
|
runtimeRoot: root,
|
|
5149
5876
|
runtimeRootSource: resolution.source,
|
|
5877
|
+
authority,
|
|
5150
5878
|
discovery: {
|
|
5151
5879
|
selectedRuntimeRoot: root,
|
|
5152
5880
|
selectionSource: resolution.source,
|
|
@@ -5196,6 +5924,15 @@ async function statusCommand(args) {
|
|
|
5196
5924
|
console.log(` meta: ${instance.metaFile}`);
|
|
5197
5925
|
console.log(` config: ${describeInstanceConfig(instance.config)}`);
|
|
5198
5926
|
console.log(` runtime: ${describeInstanceRuntime(instance.runtime)}`);
|
|
5927
|
+
if (instance.projectCount === 0) {
|
|
5928
|
+
console.log(' projects: none bound');
|
|
5929
|
+
}
|
|
5930
|
+
else {
|
|
5931
|
+
console.log(` projects: ${instance.projectCount}`);
|
|
5932
|
+
for (const project of instance.boundProjects) {
|
|
5933
|
+
console.log(` - ${project.projectPath} (${project.agentId}, ${project.mode})`);
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5199
5936
|
if (instance.repairHints.length > 0) {
|
|
5200
5937
|
console.log(' hints:');
|
|
5201
5938
|
for (const hint of instance.repairHints) {
|
|
@@ -5215,6 +5952,108 @@ async function statusCommand(args) {
|
|
|
5215
5952
|
}
|
|
5216
5953
|
}
|
|
5217
5954
|
}
|
|
5955
|
+
function handoffWriteProperties(key) {
|
|
5956
|
+
switch (key) {
|
|
5957
|
+
case 'status':
|
|
5958
|
+
return {
|
|
5959
|
+
memoryScope: 'project',
|
|
5960
|
+
capturePhase: 'manual',
|
|
5961
|
+
durableClass: 'handoff_status',
|
|
5962
|
+
canonicalKey: 'status',
|
|
5963
|
+
mergeStrategy: 'replace',
|
|
5964
|
+
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
5965
|
+
memoryScope: 'project',
|
|
5966
|
+
durableClass: 'handoff_status',
|
|
5967
|
+
mergeStrategy: 'replace',
|
|
5968
|
+
extraTags: ['handoff', 'task_memory'],
|
|
5969
|
+
}),
|
|
5970
|
+
};
|
|
5971
|
+
case 'next_step':
|
|
5972
|
+
return {
|
|
5973
|
+
memoryScope: 'project',
|
|
5974
|
+
capturePhase: 'manual',
|
|
5975
|
+
durableClass: 'next_step',
|
|
5976
|
+
canonicalKey: 'next_step',
|
|
5977
|
+
mergeStrategy: 'replace',
|
|
5978
|
+
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
5979
|
+
memoryScope: 'project',
|
|
5980
|
+
durableClass: 'next_step',
|
|
5981
|
+
mergeStrategy: 'replace',
|
|
5982
|
+
extraTags: ['handoff', 'task_memory'],
|
|
5983
|
+
}),
|
|
5984
|
+
};
|
|
5985
|
+
case 'current_owner':
|
|
5986
|
+
return {
|
|
5987
|
+
memoryScope: 'project',
|
|
5988
|
+
capturePhase: 'manual',
|
|
5989
|
+
durableClass: 'owner',
|
|
5990
|
+
canonicalKey: 'current_owner',
|
|
5991
|
+
mergeStrategy: 'replace',
|
|
5992
|
+
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
5993
|
+
memoryScope: 'project',
|
|
5994
|
+
durableClass: 'owner',
|
|
5995
|
+
mergeStrategy: 'replace',
|
|
5996
|
+
extraTags: ['handoff', 'task_memory'],
|
|
5997
|
+
}),
|
|
5998
|
+
};
|
|
5999
|
+
case 'blockers':
|
|
6000
|
+
return {
|
|
6001
|
+
memoryScope: 'project',
|
|
6002
|
+
capturePhase: 'manual',
|
|
6003
|
+
durableClass: 'open_risks',
|
|
6004
|
+
canonicalKey: 'blockers',
|
|
6005
|
+
mergeStrategy: 'replace',
|
|
6006
|
+
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
6007
|
+
memoryScope: 'project',
|
|
6008
|
+
durableClass: 'open_risks',
|
|
6009
|
+
mergeStrategy: 'replace',
|
|
6010
|
+
extraTags: ['handoff', 'task_memory', 'blockers'],
|
|
6011
|
+
}),
|
|
6012
|
+
};
|
|
6013
|
+
case 'artifacts':
|
|
6014
|
+
return {
|
|
6015
|
+
memoryScope: 'project',
|
|
6016
|
+
capturePhase: 'manual',
|
|
6017
|
+
durableClass: 'artifact',
|
|
6018
|
+
canonicalKey: 'artifacts',
|
|
6019
|
+
mergeStrategy: 'replace',
|
|
6020
|
+
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
6021
|
+
memoryScope: 'project',
|
|
6022
|
+
durableClass: 'artifact',
|
|
6023
|
+
mergeStrategy: 'replace',
|
|
6024
|
+
extraTags: ['handoff', 'task_memory'],
|
|
6025
|
+
}),
|
|
6026
|
+
};
|
|
6027
|
+
case 'active_handoff_task':
|
|
6028
|
+
return {
|
|
6029
|
+
memoryScope: 'project',
|
|
6030
|
+
capturePhase: 'manual',
|
|
6031
|
+
durableClass: 'handoff_task',
|
|
6032
|
+
canonicalKey: 'active_handoff_task',
|
|
6033
|
+
mergeStrategy: 'replace',
|
|
6034
|
+
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
6035
|
+
memoryScope: 'project',
|
|
6036
|
+
durableClass: 'handoff_task',
|
|
6037
|
+
mergeStrategy: 'replace',
|
|
6038
|
+
extraTags: ['handoff', 'project_memory'],
|
|
6039
|
+
}),
|
|
6040
|
+
};
|
|
6041
|
+
default:
|
|
6042
|
+
return {
|
|
6043
|
+
memoryScope: 'project',
|
|
6044
|
+
capturePhase: 'manual',
|
|
6045
|
+
durableClass: 'decision',
|
|
6046
|
+
canonicalKey: key,
|
|
6047
|
+
mergeStrategy: 'replace',
|
|
6048
|
+
...(0, semanticFactTags_1.buildSemanticFactTags)({
|
|
6049
|
+
memoryScope: 'project',
|
|
6050
|
+
durableClass: 'decision',
|
|
6051
|
+
mergeStrategy: 'replace',
|
|
6052
|
+
extraTags: ['handoff', 'task_memory'],
|
|
6053
|
+
}),
|
|
6054
|
+
};
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
5218
6057
|
async function collectRuntimeInstanceSummaries(root) {
|
|
5219
6058
|
const instancesDir = path_1.default.join(root, 'instances');
|
|
5220
6059
|
const instances = [];
|
|
@@ -5225,6 +6064,7 @@ async function collectRuntimeInstanceSummaries(root) {
|
|
|
5225
6064
|
for (const entry of entries.filter((value) => value.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5226
6065
|
const { envFile, metaFile } = instancePaths(root, entry.name);
|
|
5227
6066
|
const config = await inspectInstanceConfig(root, entry.name);
|
|
6067
|
+
const boundProjects = readInstanceProjectRegistry(root, entry.name);
|
|
5228
6068
|
let port = '(unknown)';
|
|
5229
6069
|
if (config.state.envPresent && config.state.envReadable) {
|
|
5230
6070
|
try {
|
|
@@ -5241,6 +6081,8 @@ async function collectRuntimeInstanceSummaries(root) {
|
|
|
5241
6081
|
port,
|
|
5242
6082
|
envFile: config.state.envPresent ? envFile : '(missing)',
|
|
5243
6083
|
metaFile: config.state.metaPresent ? metaFile : '(missing)',
|
|
6084
|
+
boundProjects,
|
|
6085
|
+
projectCount: boundProjects.length,
|
|
5244
6086
|
config,
|
|
5245
6087
|
runtime,
|
|
5246
6088
|
repairHints: buildInstanceRepairHints(entry.name, config, runtime),
|
|
@@ -5514,6 +6356,10 @@ async function createInstanceCommand(args) {
|
|
|
5514
6356
|
}
|
|
5515
6357
|
const { instanceDir, envFile, metaFile } = instancePaths(root, name);
|
|
5516
6358
|
const instanceAlreadyExisted = fs_1.default.existsSync(instanceDir);
|
|
6359
|
+
const existingEnv = fs_1.default.existsSync(envFile)
|
|
6360
|
+
? await readEnvFile(envFile).catch(() => ({}))
|
|
6361
|
+
: {};
|
|
6362
|
+
const apiKeyPepper = resolveInstanceApiKeyPepper(existingEnv.IRANTI_API_KEY_PEPPER);
|
|
5517
6363
|
if (instanceAlreadyExisted && !hasFlag(args, 'force')) {
|
|
5518
6364
|
throw new Error(`Instance '${name}' already exists at ${instanceDir}. Use --force to overwrite.`);
|
|
5519
6365
|
}
|
|
@@ -5538,17 +6384,25 @@ async function createInstanceCommand(args) {
|
|
|
5538
6384
|
await ensureDir(path_1.default.join(instanceDir, 'escalation', 'active'));
|
|
5539
6385
|
await ensureDir(path_1.default.join(instanceDir, 'escalation', 'resolved'));
|
|
5540
6386
|
await ensureDir(path_1.default.join(instanceDir, 'escalation', 'archived'));
|
|
5541
|
-
await writeText(envFile, makeInstanceEnv(name, port, dbUrl, apiKey, instanceDir));
|
|
6387
|
+
await writeText(envFile, makeInstanceEnv(name, port, dbUrl, apiKey, instanceDir, apiKeyPepper));
|
|
5542
6388
|
await upsertEnvFile(envFile, {
|
|
6389
|
+
IRANTI_API_KEY_PEPPER: apiKeyPepper,
|
|
5543
6390
|
LLM_PROVIDER: provider,
|
|
5544
6391
|
...(providerKey && providerKeyName ? { [providerKeyName]: providerKey } : {}),
|
|
5545
6392
|
});
|
|
6393
|
+
const resolvedDatabaseIntent = resolveInstanceDatabaseIntent({
|
|
6394
|
+
instanceName: name,
|
|
6395
|
+
env: { DATABASE_URL: dbUrl },
|
|
6396
|
+
meta: null,
|
|
6397
|
+
dependencies: [],
|
|
6398
|
+
}).intent;
|
|
5546
6399
|
const meta = {
|
|
5547
6400
|
name,
|
|
5548
6401
|
createdAt: new Date().toISOString(),
|
|
5549
6402
|
port,
|
|
5550
6403
|
envFile,
|
|
5551
6404
|
instanceDir,
|
|
6405
|
+
...(resolvedDatabaseIntent ? { databaseIntent: resolvedDatabaseIntent } : {}),
|
|
5552
6406
|
};
|
|
5553
6407
|
await writeJson(metaFile, meta);
|
|
5554
6408
|
// Instance fully created — pop the rollback so it doesn't run on normal exit
|
|
@@ -5617,6 +6471,7 @@ async function showInstanceCommand(args) {
|
|
|
5617
6471
|
: {};
|
|
5618
6472
|
const meta = await readInstanceMetaFile(instancePaths(root, name).metaFile);
|
|
5619
6473
|
const dependencies = (0, runtimeDependencies_1.parseInstanceDependencies)(meta?.dependencies).dependencies;
|
|
6474
|
+
const boundProjects = readInstanceProjectRegistry(root, name);
|
|
5620
6475
|
const databaseIntent = resolveInstanceDatabaseIntent({
|
|
5621
6476
|
instanceName: name,
|
|
5622
6477
|
env,
|
|
@@ -5637,6 +6492,12 @@ async function showInstanceCommand(args) {
|
|
|
5637
6492
|
if (dependencies.length > 0) {
|
|
5638
6493
|
console.log(` deps: ${dependencies.map((dependency) => (0, runtimeDependencies_1.describeInstanceDependency)(dependency)).join(', ')}`);
|
|
5639
6494
|
}
|
|
6495
|
+
if (boundProjects.length > 0) {
|
|
6496
|
+
console.log(' projects:');
|
|
6497
|
+
for (const project of boundProjects) {
|
|
6498
|
+
console.log(` - ${project.projectPath} (${project.agentId}, ${project.mode})`);
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
5640
6501
|
console.log(` runtime: ${describeInstanceRuntime(runtime)}`);
|
|
5641
6502
|
if (runtime.state?.healthUrl) {
|
|
5642
6503
|
console.log(` health: ${runtime.state.healthUrl}`);
|
|
@@ -5742,10 +6603,21 @@ async function projectInitCommand(args) {
|
|
|
5742
6603
|
IRANTI_INSTANCE: instanceName,
|
|
5743
6604
|
IRANTI_INSTANCE_ENV: envFile,
|
|
5744
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
|
+
});
|
|
5745
6613
|
console.log(sectionTitle('Project Initialized'));
|
|
5746
6614
|
console.log(` status ${okLabel()}`);
|
|
5747
6615
|
console.log(` wrote ${outFile}`);
|
|
5748
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
|
+
}
|
|
5749
6621
|
printNextSteps([
|
|
5750
6622
|
`iranti doctor --instance ${instanceName}`,
|
|
5751
6623
|
'iranti chat',
|
|
@@ -5879,6 +6751,9 @@ async function configureInstanceCommand(args) {
|
|
|
5879
6751
|
}
|
|
5880
6752
|
updates[envKey] = undefined;
|
|
5881
6753
|
}
|
|
6754
|
+
if (!env.IRANTI_API_KEY_PEPPER?.trim()) {
|
|
6755
|
+
updates.IRANTI_API_KEY_PEPPER = resolveInstanceApiKeyPepper();
|
|
6756
|
+
}
|
|
5882
6757
|
let nextDependencies = currentDependencies;
|
|
5883
6758
|
if (clearDockerContainer) {
|
|
5884
6759
|
nextDependencies = currentDependencies.filter((dependency) => dependency.kind !== 'docker-container');
|
|
@@ -6031,7 +6906,7 @@ async function configureProjectCommand(args) {
|
|
|
6031
6906
|
IRANTI_PERSONAL_MEMORY_ENTITY: explicitPersonalMemoryEntity ?? existing.IRANTI_PERSONAL_MEMORY_ENTITY ?? 'user/main',
|
|
6032
6907
|
IRANTI_AUTO_REMEMBER: String(explicitAutoRemember ?? envFlagEnabled(existing.IRANTI_AUTO_REMEMBER)),
|
|
6033
6908
|
IRANTI_PROJECT_MODE: normalizeProjectMode(explicitProjectMode, normalizeProjectMode(existing.IRANTI_PROJECT_MODE, inferProjectMode(projectPath, instanceEnvFile))),
|
|
6034
|
-
IRANTI_INSTANCE: instanceName,
|
|
6909
|
+
IRANTI_INSTANCE: instanceName ?? existing.IRANTI_INSTANCE,
|
|
6035
6910
|
IRANTI_INSTANCE_ENV: instanceEnvFile,
|
|
6036
6911
|
};
|
|
6037
6912
|
if (!updates.IRANTI_URL) {
|
|
@@ -6041,6 +6916,13 @@ async function configureProjectCommand(args) {
|
|
|
6041
6916
|
throw new Error('Unable to determine IRANTI_API_KEY. Provide --api-key <token> or configure the instance first.');
|
|
6042
6917
|
}
|
|
6043
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
|
+
});
|
|
6044
6926
|
const json = hasFlag(args, 'json');
|
|
6045
6927
|
const result = {
|
|
6046
6928
|
projectPath,
|
|
@@ -6050,6 +6932,8 @@ async function configureProjectCommand(args) {
|
|
|
6050
6932
|
autoRemember: updates.IRANTI_AUTO_REMEMBER === 'true',
|
|
6051
6933
|
projectMode: updates.IRANTI_PROJECT_MODE,
|
|
6052
6934
|
instance: updates.IRANTI_INSTANCE ?? null,
|
|
6935
|
+
codebaseEntity: writtenBinding.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath),
|
|
6936
|
+
projectLearning: learningStatus,
|
|
6053
6937
|
};
|
|
6054
6938
|
if (json) {
|
|
6055
6939
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -6063,9 +6947,13 @@ async function configureProjectCommand(args) {
|
|
|
6063
6947
|
console.log(` agent ${updates.IRANTI_AGENT_ID}`);
|
|
6064
6948
|
console.log(` remember ${updates.IRANTI_AUTO_REMEMBER}`);
|
|
6065
6949
|
console.log(` mode ${updates.IRANTI_PROJECT_MODE}`);
|
|
6950
|
+
console.log(` codebase ${writtenBinding.IRANTI_CODEBASE_ENTITY ?? (0, projectLearning_1.deriveProjectCodebaseEntity)(projectPath)}`);
|
|
6066
6951
|
if (updates.IRANTI_INSTANCE) {
|
|
6067
6952
|
console.log(` instance ${updates.IRANTI_INSTANCE}`);
|
|
6068
6953
|
}
|
|
6954
|
+
if (learningStatus.status !== 'written') {
|
|
6955
|
+
console.log(` learn ${paint(learningStatus.status, learningStatus.status === 'failed' ? 'red' : 'yellow')} (${learningStatus.detail})`);
|
|
6956
|
+
}
|
|
6069
6957
|
printNextSteps([
|
|
6070
6958
|
`iranti doctor${updates.IRANTI_INSTANCE ? ` --instance ${updates.IRANTI_INSTANCE}` : ''}`,
|
|
6071
6959
|
]);
|
|
@@ -6090,7 +6978,7 @@ async function authCreateKeyCommand(args) {
|
|
|
6090
6978
|
throw new Error(`Instance '${instanceName}' still has a placeholder DATABASE_URL. Update ${envFile} first.`);
|
|
6091
6979
|
}
|
|
6092
6980
|
const scopes = scopesRaw.split(',').map((value) => value.trim()).filter(Boolean);
|
|
6093
|
-
(0, client_1.initDb)(env.DATABASE_URL);
|
|
6981
|
+
(0, client_1.initDb)(env.DATABASE_URL, { applicationName: 'iranti:cli:auth_create' });
|
|
6094
6982
|
const created = await (0, apiKeys_1.createOrRotateApiKey)({
|
|
6095
6983
|
keyId,
|
|
6096
6984
|
owner,
|
|
@@ -6104,7 +6992,7 @@ async function authCreateKeyCommand(args) {
|
|
|
6104
6992
|
const resolvedProjectPath = path_1.default.resolve(projectPath);
|
|
6105
6993
|
const existingBindingFile = path_1.default.join(resolvedProjectPath, '.env.iranti');
|
|
6106
6994
|
const existingBinding = fs_1.default.existsSync(existingBindingFile) ? await readEnvFile(existingBindingFile) : {};
|
|
6107
|
-
await writeProjectBinding(resolvedProjectPath, {
|
|
6995
|
+
const written = await writeProjectBinding(resolvedProjectPath, {
|
|
6108
6996
|
IRANTI_URL: `http://localhost:${env.IRANTI_PORT ?? '3001'}`,
|
|
6109
6997
|
IRANTI_API_KEY: created.token,
|
|
6110
6998
|
IRANTI_AGENT_ID: agentId ?? existingBinding.IRANTI_AGENT_ID ?? 'my_agent',
|
|
@@ -6114,6 +7002,13 @@ async function authCreateKeyCommand(args) {
|
|
|
6114
7002
|
IRANTI_INSTANCE: instanceName,
|
|
6115
7003
|
IRANTI_INSTANCE_ENV: envFile,
|
|
6116
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
|
+
});
|
|
6117
7012
|
}
|
|
6118
7013
|
if (hasFlag(args, 'json')) {
|
|
6119
7014
|
console.log(JSON.stringify({
|
|
@@ -6154,7 +7049,7 @@ async function authListKeysCommand(args) {
|
|
|
6154
7049
|
if (detectPlaceholder(env.DATABASE_URL)) {
|
|
6155
7050
|
throw new Error(`Instance '${instanceName}' still has a placeholder DATABASE_URL. Update ${envFile} first.`);
|
|
6156
7051
|
}
|
|
6157
|
-
(0, client_1.initDb)(env.DATABASE_URL);
|
|
7052
|
+
(0, client_1.initDb)(env.DATABASE_URL, { applicationName: 'iranti:cli:auth_list' });
|
|
6158
7053
|
const keys = await (0, apiKeys_1.listApiKeys)();
|
|
6159
7054
|
if (hasFlag(args, 'json')) {
|
|
6160
7055
|
console.log(JSON.stringify({ instance: instanceName, keys }, null, 2));
|
|
@@ -6182,7 +7077,7 @@ async function authRevokeKeyCommand(args) {
|
|
|
6182
7077
|
if (detectPlaceholder(env.DATABASE_URL)) {
|
|
6183
7078
|
throw new Error(`Instance '${instanceName}' still has a placeholder DATABASE_URL. Update ${envFile} first.`);
|
|
6184
7079
|
}
|
|
6185
|
-
(0, client_1.initDb)(env.DATABASE_URL);
|
|
7080
|
+
(0, client_1.initDb)(env.DATABASE_URL, { applicationName: 'iranti:cli:auth_revoke' });
|
|
6186
7081
|
const revoked = await (0, apiKeys_1.revokeApiKey)(keyId);
|
|
6187
7082
|
if (!revoked) {
|
|
6188
7083
|
throw new Error(`API key not found: ${keyId}`);
|
|
@@ -6315,6 +7210,52 @@ async function attendCommand(args) {
|
|
|
6315
7210
|
await (0, client_1.disconnectDb)().catch(() => undefined);
|
|
6316
7211
|
}
|
|
6317
7212
|
}
|
|
7213
|
+
async function issuesCommand(args) {
|
|
7214
|
+
try {
|
|
7215
|
+
const json = hasFlag(args, 'json');
|
|
7216
|
+
const target = await resolveAttendantCliTarget(args);
|
|
7217
|
+
const entity = resolveIssueEntity(args);
|
|
7218
|
+
const statusFilter = resolveIssueStatusFilter(args);
|
|
7219
|
+
const allFacts = await target.iranti.queryAll(entity);
|
|
7220
|
+
const parsedIssueFacts = allFacts
|
|
7221
|
+
.map(parseIssueListFact)
|
|
7222
|
+
.filter((result) => result.item || result.invalid);
|
|
7223
|
+
const canonicalItems = parsedIssueFacts
|
|
7224
|
+
.map((result) => result.item)
|
|
7225
|
+
.filter((item) => Boolean(item));
|
|
7226
|
+
const invalidIssueLikeFacts = parsedIssueFacts
|
|
7227
|
+
.map((result) => result.invalid)
|
|
7228
|
+
.filter((item) => Boolean(item))
|
|
7229
|
+
.sort((a, b) => a.key.localeCompare(b.key));
|
|
7230
|
+
const inventoryCounts = {
|
|
7231
|
+
open: canonicalItems.filter((item) => item.status === 'open').length,
|
|
7232
|
+
resolved: canonicalItems.filter((item) => item.status === 'resolved').length,
|
|
7233
|
+
invalid: invalidIssueLikeFacts.length,
|
|
7234
|
+
canonicalTotal: canonicalItems.length,
|
|
7235
|
+
issueLikeTotal: canonicalItems.length + invalidIssueLikeFacts.length,
|
|
7236
|
+
};
|
|
7237
|
+
const items = canonicalItems
|
|
7238
|
+
.filter((item) => !statusFilter || item.status === statusFilter)
|
|
7239
|
+
.sort(compareIssueListItems);
|
|
7240
|
+
await (0, staffEventRegistry_1.flushStaffEventEmitter)().catch(() => undefined);
|
|
7241
|
+
if (json) {
|
|
7242
|
+
console.log(JSON.stringify({
|
|
7243
|
+
entity,
|
|
7244
|
+
status: statusFilter,
|
|
7245
|
+
total: items.length,
|
|
7246
|
+
counts: inventoryCounts,
|
|
7247
|
+
invalidIssueLikeFacts,
|
|
7248
|
+
items,
|
|
7249
|
+
}, null, 2));
|
|
7250
|
+
return;
|
|
7251
|
+
}
|
|
7252
|
+
printIssuesResult(entity, items, statusFilter, inventoryCounts, invalidIssueLikeFacts);
|
|
7253
|
+
}
|
|
7254
|
+
finally {
|
|
7255
|
+
await (0, staffEventRegistry_1.flushStaffEventEmitter)().catch(() => undefined);
|
|
7256
|
+
await (0, client_1.disconnectDb)().catch(() => undefined);
|
|
7257
|
+
}
|
|
7258
|
+
}
|
|
6318
7259
|
async function handoffCommand(args) {
|
|
6319
7260
|
try {
|
|
6320
7261
|
const json = hasFlag(args, 'json');
|
|
@@ -6405,6 +7346,7 @@ async function handoffCommand(args) {
|
|
|
6405
7346
|
confidence,
|
|
6406
7347
|
source,
|
|
6407
7348
|
agent: target.agentId,
|
|
7349
|
+
properties: handoffWriteProperties(write.key),
|
|
6408
7350
|
});
|
|
6409
7351
|
}
|
|
6410
7352
|
await (0, staffEventRegistry_1.flushStaffEventEmitter)().catch(() => undefined);
|
|
@@ -6431,7 +7373,7 @@ async function handoffCommand(args) {
|
|
|
6431
7373
|
function printClaudeSetupHelp() {
|
|
6432
7374
|
console.log([
|
|
6433
7375
|
'Scaffold Claude Code MCP and hook files for the current project.',
|
|
6434
|
-
'Use this when a bound repo should be ready for Claude Code without hand-editing `.mcp.json`, `.vscode/mcp.json`,
|
|
7376
|
+
'Use this when a bound repo should be ready for Claude Code without hand-editing `.mcp.json`, `.vscode/mcp.json`, `.claude/settings.local.json`, or `CLAUDE.md`.',
|
|
6435
7377
|
'',
|
|
6436
7378
|
'Usage:',
|
|
6437
7379
|
' iranti claude-setup [path] [--project-env <path>] [--force]',
|
|
@@ -6445,9 +7387,10 @@ function printClaudeSetupHelp() {
|
|
|
6445
7387
|
'',
|
|
6446
7388
|
'Notes:',
|
|
6447
7389
|
' - Expects a project binding at .env.iranti unless --project-env is supplied.',
|
|
6448
|
-
' - Writes .mcp.json, .vscode/mcp.json,
|
|
7390
|
+
' - Writes .mcp.json, .vscode/mcp.json, .claude/settings.local.json, and a local `CLAUDE.md` Iranti protocol block.',
|
|
6449
7391
|
' - Adds the Iranti MCP server to existing .mcp.json / .vscode/mcp.json files without removing other servers.',
|
|
6450
7392
|
' - Leaves existing Claude hook files untouched unless --force is supplied.',
|
|
7393
|
+
' - The generated protocol block explicitly requires handshake at session start, attend before reply and before/after discovery, checkpointing at natural pauses/interrupted work, and durable writes after confirmed findings.',
|
|
6451
7394
|
'',
|
|
6452
7395
|
'Scan mode (--scan):',
|
|
6453
7396
|
' - Scans immediate subdirectories of the given dir by default.',
|
|
@@ -6570,6 +7513,7 @@ async function discoverProjectArtifacts(scanRoots) {
|
|
|
6570
7513
|
const bindingFile = path_1.default.join(current, '.env.iranti');
|
|
6571
7514
|
const mcpFile = path_1.default.join(current, '.mcp.json');
|
|
6572
7515
|
const claudeSettingsFile = path_1.default.join(current, '.claude', 'settings.local.json');
|
|
7516
|
+
const agentsFile = path_1.default.join(current, 'AGENTS.md');
|
|
6573
7517
|
const artifact = { projectPath: current };
|
|
6574
7518
|
if (fs_1.default.existsSync(bindingFile))
|
|
6575
7519
|
artifact.bindingFile = bindingFile;
|
|
@@ -6579,7 +7523,18 @@ async function discoverProjectArtifacts(scanRoots) {
|
|
|
6579
7523
|
if (fs_1.default.existsSync(claudeSettingsFile) && hasIrantiClaudeHookSettings(readJsonFile(claudeSettingsFile))) {
|
|
6580
7524
|
artifact.claudeSettingsFile = claudeSettingsFile;
|
|
6581
7525
|
}
|
|
6582
|
-
if (
|
|
7526
|
+
if (fs_1.default.existsSync(agentsFile)) {
|
|
7527
|
+
try {
|
|
7528
|
+
const agentsText = await promises_1.default.readFile(agentsFile, 'utf8');
|
|
7529
|
+
if (agentsText.includes('<!-- iranti-rules -->')) {
|
|
7530
|
+
artifact.agentsFile = agentsFile;
|
|
7531
|
+
}
|
|
7532
|
+
}
|
|
7533
|
+
catch {
|
|
7534
|
+
// Ignore unreadable AGENTS files during discovery; cleanup will warn if selected.
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
if (artifact.bindingFile || artifact.mcpFile || artifact.claudeSettingsFile || artifact.agentsFile) {
|
|
6583
7538
|
projects.set(current, artifact);
|
|
6584
7539
|
}
|
|
6585
7540
|
for (const entry of entries) {
|
|
@@ -6674,9 +7629,12 @@ async function claudeSetupCommand(args) {
|
|
|
6674
7629
|
console.log(` mcp ${path_1.default.join(projectPath, '.mcp.json')}`);
|
|
6675
7630
|
console.log(` vscode ${path_1.default.join(projectPath, '.vscode', 'mcp.json')}`);
|
|
6676
7631
|
console.log(` settings ${path_1.default.join(projectPath, '.claude', 'settings.local.json')}`);
|
|
6677
|
-
console.log(`
|
|
6678
|
-
console.log(`
|
|
6679
|
-
console.log(`
|
|
7632
|
+
console.log(` claude.md ${path_1.default.join(projectPath, 'CLAUDE.md')}`);
|
|
7633
|
+
console.log(` mcp status ${result.mcp}`);
|
|
7634
|
+
console.log(` vscode status ${result.vscodeMcp}`);
|
|
7635
|
+
console.log(` settings status ${result.settings}`);
|
|
7636
|
+
console.log(` claude.md status ${result.claudeMd}`);
|
|
7637
|
+
console.log(` memory closeout ${result.closeout.status} (${result.closeout.detail})`);
|
|
6680
7638
|
console.log(`${infoLabel()} Next: open Claude Code in this project and verify Iranti tools are available.`);
|
|
6681
7639
|
}
|
|
6682
7640
|
async function chatCommand(args) {
|
|
@@ -6760,6 +7718,9 @@ function printHandshakeHelp() {
|
|
|
6760
7718
|
function printAttendHelp() {
|
|
6761
7719
|
(0, cliHelpRenderer_1.printAttendHelp)({ sectionTitle, commandText });
|
|
6762
7720
|
}
|
|
7721
|
+
function printIssuesHelp() {
|
|
7722
|
+
(0, cliHelpRenderer_1.printIssuesHelp)({ sectionTitle, commandText });
|
|
7723
|
+
}
|
|
6763
7724
|
function printHandoffHelp() {
|
|
6764
7725
|
(0, cliHelpRenderer_1.printHandoffHelp)({ sectionTitle, commandText });
|
|
6765
7726
|
}
|
|
@@ -6772,8 +7733,305 @@ function printResolveHelp() {
|
|
|
6772
7733
|
function printProviderKeyHelp() {
|
|
6773
7734
|
(0, cliHelpRenderer_1.printProviderKeyHelp)({ sectionTitle, commandText });
|
|
6774
7735
|
}
|
|
7736
|
+
function printMcpHelp() {
|
|
7737
|
+
console.log([
|
|
7738
|
+
'MCP server and maintenance commands.',
|
|
7739
|
+
'Use this when the stdio MCP server should be started directly, or when you need to inspect and clean stale MCP wrapper/server pairs.',
|
|
7740
|
+
'',
|
|
7741
|
+
'Usage:',
|
|
7742
|
+
' iranti mcp',
|
|
7743
|
+
' iranti mcp cleanup [--dry-run] [--json]',
|
|
7744
|
+
'',
|
|
7745
|
+
'Notes:',
|
|
7746
|
+
' - `iranti mcp` starts the stdio MCP server for Claude, Codex, or another MCP client.',
|
|
7747
|
+
' - `iranti mcp cleanup` only removes stale launcher/server pairs that no longer have a live host ancestor.',
|
|
7748
|
+
' - Active chains still rooted in `claude.exe` or `codex.exe` are reported but not killed.',
|
|
7749
|
+
' - Current implementation is tuned for Windows process trees.',
|
|
7750
|
+
].join('\n'));
|
|
7751
|
+
}
|
|
7752
|
+
function isMcpLauncherCommand(commandLine) {
|
|
7753
|
+
if (!commandLine)
|
|
7754
|
+
return false;
|
|
7755
|
+
const lower = commandLine.toLowerCase();
|
|
7756
|
+
return lower.includes('\\node_modules\\iranti\\bin\\iranti.js')
|
|
7757
|
+
&& /\bmcp\b/.test(lower);
|
|
7758
|
+
}
|
|
7759
|
+
function isMcpChildCommand(commandLine) {
|
|
7760
|
+
if (!commandLine)
|
|
7761
|
+
return false;
|
|
7762
|
+
return commandLine.toLowerCase().includes('\\dist\\scripts\\iranti-mcp.js');
|
|
7763
|
+
}
|
|
7764
|
+
function collectWindowsProcessSnapshot() {
|
|
7765
|
+
const probe = runCommandCapture('powershell', [
|
|
7766
|
+
'-NoProfile',
|
|
7767
|
+
'-Command',
|
|
7768
|
+
'Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId, Name, CommandLine | ConvertTo-Json -Compress',
|
|
7769
|
+
]);
|
|
7770
|
+
if (probe.status !== 0) {
|
|
7771
|
+
throw cliError('IRANTI_MCP_CLEANUP_PROBE_FAILED', 'Failed to inspect Windows process state for MCP cleanup.', ['Retry with `--debug` to inspect the PowerShell probe output.'], { stderr: probe.stderr.trim() || null });
|
|
7772
|
+
}
|
|
7773
|
+
const payload = JSON.parse(probe.stdout);
|
|
7774
|
+
const rows = Array.isArray(payload) ? payload : [payload];
|
|
7775
|
+
return rows.map((row) => ({
|
|
7776
|
+
pid: Number(row.ProcessId ?? 0),
|
|
7777
|
+
parentPid: row.ParentProcessId === null || row.ParentProcessId === undefined
|
|
7778
|
+
? null
|
|
7779
|
+
: Number(row.ParentProcessId),
|
|
7780
|
+
name: String(row.Name ?? ''),
|
|
7781
|
+
commandLine: String(row.CommandLine ?? ''),
|
|
7782
|
+
})).filter((row) => Number.isFinite(row.pid) && row.pid > 0);
|
|
7783
|
+
}
|
|
7784
|
+
function summarizeMcpCleanupCounts(candidates) {
|
|
7785
|
+
return {
|
|
7786
|
+
stale_no_host_ancestor: candidates.filter((candidate) => candidate.status === 'stale_no_host_ancestor').length,
|
|
7787
|
+
stale_shell_no_host: candidates.filter((candidate) => candidate.status === 'stale_shell_no_host').length,
|
|
7788
|
+
stale_child_parent_missing: candidates.filter((candidate) => candidate.status === 'stale_child_parent_missing').length,
|
|
7789
|
+
stale_launcher_only: candidates.filter((candidate) => candidate.status === 'stale_launcher_only').length,
|
|
7790
|
+
attached_claude: candidates.filter((candidate) => candidate.status === 'attached_claude').length,
|
|
7791
|
+
attached_codex: candidates.filter((candidate) => candidate.status === 'attached_codex').length,
|
|
7792
|
+
attached_or_uncertain: candidates.filter((candidate) => candidate.status === 'attached_or_uncertain').length,
|
|
7793
|
+
};
|
|
7794
|
+
}
|
|
7795
|
+
function buildMcpCleanupReport(rows) {
|
|
7796
|
+
const protectedPids = currentProcessFamilyPids();
|
|
7797
|
+
const index = new Map();
|
|
7798
|
+
for (const row of rows) {
|
|
7799
|
+
index.set(row.pid, row);
|
|
7800
|
+
}
|
|
7801
|
+
const warnings = [];
|
|
7802
|
+
const candidates = [];
|
|
7803
|
+
const matchedLaunchers = new Set();
|
|
7804
|
+
const childByLauncher = new Map();
|
|
7805
|
+
for (const row of rows) {
|
|
7806
|
+
if (row.name.toLowerCase() !== 'node.exe')
|
|
7807
|
+
continue;
|
|
7808
|
+
if (!isMcpChildCommand(row.commandLine))
|
|
7809
|
+
continue;
|
|
7810
|
+
if (protectedPids.has(row.pid))
|
|
7811
|
+
continue;
|
|
7812
|
+
const parent = row.parentPid ? index.get(row.parentPid) : undefined;
|
|
7813
|
+
if (!parent) {
|
|
7814
|
+
candidates.push({
|
|
7815
|
+
status: 'stale_child_parent_missing',
|
|
7816
|
+
childPid: row.pid,
|
|
7817
|
+
reason: 'MCP child process has no live launcher parent.',
|
|
7818
|
+
});
|
|
7819
|
+
continue;
|
|
7820
|
+
}
|
|
7821
|
+
if (!isMcpLauncherCommand(parent.commandLine)) {
|
|
7822
|
+
candidates.push({
|
|
7823
|
+
status: 'attached_or_uncertain',
|
|
7824
|
+
childPid: row.pid,
|
|
7825
|
+
reason: 'MCP child process is attached to a parent that is not a recognized Iranti MCP launcher.',
|
|
7826
|
+
});
|
|
7827
|
+
continue;
|
|
7828
|
+
}
|
|
7829
|
+
matchedLaunchers.add(parent.pid);
|
|
7830
|
+
childByLauncher.set(parent.pid, row);
|
|
7831
|
+
if (protectedPids.has(parent.pid)) {
|
|
7832
|
+
candidates.push({
|
|
7833
|
+
status: 'attached_or_uncertain',
|
|
7834
|
+
launcherPid: parent.pid,
|
|
7835
|
+
childPid: row.pid,
|
|
7836
|
+
reason: 'Skipping the current CLI process family.',
|
|
7837
|
+
});
|
|
7838
|
+
continue;
|
|
7839
|
+
}
|
|
7840
|
+
const grand = parent.parentPid ? index.get(parent.parentPid) : undefined;
|
|
7841
|
+
const great = grand?.parentPid ? index.get(grand.parentPid) : undefined;
|
|
7842
|
+
const grandName = grand?.name.toLowerCase() ?? '';
|
|
7843
|
+
const greatName = great?.name.toLowerCase() ?? '';
|
|
7844
|
+
if (!grand) {
|
|
7845
|
+
candidates.push({
|
|
7846
|
+
status: 'stale_no_host_ancestor',
|
|
7847
|
+
launcherPid: parent.pid,
|
|
7848
|
+
childPid: row.pid,
|
|
7849
|
+
reason: 'Launcher parent exists, but no live host ancestor remains.',
|
|
7850
|
+
});
|
|
7851
|
+
continue;
|
|
7852
|
+
}
|
|
7853
|
+
if (grandName === 'cmd.exe' && !great) {
|
|
7854
|
+
candidates.push({
|
|
7855
|
+
status: 'stale_shell_no_host',
|
|
7856
|
+
launcherPid: parent.pid,
|
|
7857
|
+
childPid: row.pid,
|
|
7858
|
+
reason: 'Launcher is still wrapped by cmd.exe, but the host process above cmd.exe is gone.',
|
|
7859
|
+
});
|
|
7860
|
+
continue;
|
|
7861
|
+
}
|
|
7862
|
+
if (grandName === 'cmd.exe' && greatName === 'claude.exe') {
|
|
7863
|
+
candidates.push({
|
|
7864
|
+
status: 'attached_claude',
|
|
7865
|
+
launcherPid: parent.pid,
|
|
7866
|
+
childPid: row.pid,
|
|
7867
|
+
host: 'claude',
|
|
7868
|
+
reason: 'Chain is still rooted in claude.exe.',
|
|
7869
|
+
});
|
|
7870
|
+
continue;
|
|
7871
|
+
}
|
|
7872
|
+
if (grandName === 'cmd.exe' && greatName === 'codex.exe') {
|
|
7873
|
+
candidates.push({
|
|
7874
|
+
status: 'attached_codex',
|
|
7875
|
+
launcherPid: parent.pid,
|
|
7876
|
+
childPid: row.pid,
|
|
7877
|
+
host: 'codex',
|
|
7878
|
+
reason: 'Chain is still rooted in codex.exe.',
|
|
7879
|
+
});
|
|
7880
|
+
continue;
|
|
7881
|
+
}
|
|
7882
|
+
candidates.push({
|
|
7883
|
+
status: 'attached_or_uncertain',
|
|
7884
|
+
launcherPid: parent.pid,
|
|
7885
|
+
childPid: row.pid,
|
|
7886
|
+
reason: 'Launcher/server pair still has a live ancestor that is not a known stale shape.',
|
|
7887
|
+
});
|
|
7888
|
+
}
|
|
7889
|
+
for (const row of rows) {
|
|
7890
|
+
if (row.name.toLowerCase() !== 'node.exe')
|
|
7891
|
+
continue;
|
|
7892
|
+
if (!isMcpLauncherCommand(row.commandLine))
|
|
7893
|
+
continue;
|
|
7894
|
+
if (matchedLaunchers.has(row.pid) || protectedPids.has(row.pid))
|
|
7895
|
+
continue;
|
|
7896
|
+
const grand = row.parentPid ? index.get(row.parentPid) : undefined;
|
|
7897
|
+
const great = grand?.parentPid ? index.get(grand.parentPid) : undefined;
|
|
7898
|
+
const grandName = grand?.name.toLowerCase() ?? '';
|
|
7899
|
+
const greatName = great?.name.toLowerCase() ?? '';
|
|
7900
|
+
if (!grand) {
|
|
7901
|
+
candidates.push({
|
|
7902
|
+
status: 'stale_launcher_only',
|
|
7903
|
+
launcherPid: row.pid,
|
|
7904
|
+
reason: 'Launcher remains alive without a child or live host ancestor.',
|
|
7905
|
+
});
|
|
7906
|
+
}
|
|
7907
|
+
else if (grandName === 'cmd.exe' && !great) {
|
|
7908
|
+
candidates.push({
|
|
7909
|
+
status: 'stale_launcher_only',
|
|
7910
|
+
launcherPid: row.pid,
|
|
7911
|
+
reason: 'Launcher is still wrapped by cmd.exe, but the host process above cmd.exe is gone.',
|
|
7912
|
+
});
|
|
7913
|
+
}
|
|
7914
|
+
}
|
|
7915
|
+
const safeStatuses = new Set([
|
|
7916
|
+
'stale_no_host_ancestor',
|
|
7917
|
+
'stale_shell_no_host',
|
|
7918
|
+
'stale_child_parent_missing',
|
|
7919
|
+
'stale_launcher_only',
|
|
7920
|
+
]);
|
|
7921
|
+
const safeCandidates = candidates.filter((candidate) => safeStatuses.has(candidate.status));
|
|
7922
|
+
const skippedCandidates = candidates.filter((candidate) => !safeStatuses.has(candidate.status));
|
|
7923
|
+
if (safeCandidates.length === 0) {
|
|
7924
|
+
warnings.push('No stale MCP launcher/server pairs were found with the current safe cleanup rule.');
|
|
7925
|
+
}
|
|
7926
|
+
return {
|
|
7927
|
+
platform: process.platform,
|
|
7928
|
+
supported: process.platform === 'win32',
|
|
7929
|
+
dryRun: true,
|
|
7930
|
+
counts: summarizeMcpCleanupCounts(candidates),
|
|
7931
|
+
safeCandidates,
|
|
7932
|
+
skippedCandidates,
|
|
7933
|
+
cleaned: [],
|
|
7934
|
+
warnings,
|
|
7935
|
+
};
|
|
7936
|
+
}
|
|
7937
|
+
function printMcpCleanupReport(report) {
|
|
7938
|
+
console.log(sectionTitle('MCP Cleanup'));
|
|
7939
|
+
if (!report.supported) {
|
|
7940
|
+
console.log(`${warnLabel()} MCP cleanup is currently supported on Windows only.`);
|
|
7941
|
+
return;
|
|
7942
|
+
}
|
|
7943
|
+
console.log(` safe stale candidates ${report.safeCandidates.length}`);
|
|
7944
|
+
console.log(` attached claude ${report.counts.attached_claude}`);
|
|
7945
|
+
console.log(` attached codex ${report.counts.attached_codex}`);
|
|
7946
|
+
console.log(` other live / uncertain ${report.counts.attached_or_uncertain}`);
|
|
7947
|
+
console.log(` cleaned entries ${report.cleaned.length}`);
|
|
7948
|
+
console.log(` mode ${report.dryRun ? 'dry-run' : 'execute'}`);
|
|
7949
|
+
console.log('');
|
|
7950
|
+
if (report.safeCandidates.length > 0) {
|
|
7951
|
+
console.log(sectionTitle('Safe Targets'));
|
|
7952
|
+
for (const candidate of report.safeCandidates) {
|
|
7953
|
+
const ids = [
|
|
7954
|
+
candidate.launcherPid ? `launcher=${candidate.launcherPid}` : null,
|
|
7955
|
+
candidate.childPid ? `child=${candidate.childPid}` : null,
|
|
7956
|
+
].filter((value) => Boolean(value));
|
|
7957
|
+
console.log(` ${commandText(candidate.status)} ${ids.join(' ')} - ${candidate.reason}`);
|
|
7958
|
+
}
|
|
7959
|
+
console.log('');
|
|
7960
|
+
}
|
|
7961
|
+
if (report.skippedCandidates.length > 0) {
|
|
7962
|
+
console.log(sectionTitle('Skipped Targets'));
|
|
7963
|
+
for (const candidate of report.skippedCandidates) {
|
|
7964
|
+
const ids = [
|
|
7965
|
+
candidate.launcherPid ? `launcher=${candidate.launcherPid}` : null,
|
|
7966
|
+
candidate.childPid ? `child=${candidate.childPid}` : null,
|
|
7967
|
+
].filter((value) => Boolean(value));
|
|
7968
|
+
console.log(` ${commandText(candidate.status)} ${ids.join(' ')} - ${candidate.reason}`);
|
|
7969
|
+
}
|
|
7970
|
+
console.log('');
|
|
7971
|
+
}
|
|
7972
|
+
for (const warning of report.warnings) {
|
|
7973
|
+
console.log(`${warnLabel()} ${warning}`);
|
|
7974
|
+
}
|
|
7975
|
+
}
|
|
7976
|
+
async function mcpCleanupCommand(args) {
|
|
7977
|
+
const json = hasFlag(args, 'json');
|
|
7978
|
+
const dryRun = hasFlag(args, 'dry-run');
|
|
7979
|
+
if (process.platform !== 'win32') {
|
|
7980
|
+
const report = {
|
|
7981
|
+
platform: process.platform,
|
|
7982
|
+
supported: false,
|
|
7983
|
+
dryRun,
|
|
7984
|
+
counts: {
|
|
7985
|
+
stale_no_host_ancestor: 0,
|
|
7986
|
+
stale_shell_no_host: 0,
|
|
7987
|
+
stale_child_parent_missing: 0,
|
|
7988
|
+
stale_launcher_only: 0,
|
|
7989
|
+
attached_claude: 0,
|
|
7990
|
+
attached_codex: 0,
|
|
7991
|
+
attached_or_uncertain: 0,
|
|
7992
|
+
},
|
|
7993
|
+
safeCandidates: [],
|
|
7994
|
+
skippedCandidates: [],
|
|
7995
|
+
cleaned: [],
|
|
7996
|
+
warnings: ['MCP cleanup is currently implemented for Windows process trees only.'],
|
|
7997
|
+
};
|
|
7998
|
+
if (json) {
|
|
7999
|
+
console.log(JSON.stringify(report, null, 2));
|
|
8000
|
+
return;
|
|
8001
|
+
}
|
|
8002
|
+
printMcpCleanupReport(report);
|
|
8003
|
+
return;
|
|
8004
|
+
}
|
|
8005
|
+
const report = buildMcpCleanupReport(collectWindowsProcessSnapshot());
|
|
8006
|
+
report.dryRun = dryRun;
|
|
8007
|
+
if (!dryRun) {
|
|
8008
|
+
const cleaned = new Set();
|
|
8009
|
+
for (const candidate of report.safeCandidates) {
|
|
8010
|
+
if (candidate.childPid && !cleaned.has(`child:${candidate.childPid}`)) {
|
|
8011
|
+
const proc = runCommandCapture('powershell', ['-NoProfile', '-Command', `Stop-Process -Id ${candidate.childPid} -Force -ErrorAction SilentlyContinue`]);
|
|
8012
|
+
if (proc.status === 0) {
|
|
8013
|
+
report.cleaned.push({ pid: candidate.childPid, role: 'child' });
|
|
8014
|
+
cleaned.add(`child:${candidate.childPid}`);
|
|
8015
|
+
}
|
|
8016
|
+
}
|
|
8017
|
+
if (candidate.launcherPid && !cleaned.has(`launcher:${candidate.launcherPid}`)) {
|
|
8018
|
+
const proc = runCommandCapture('powershell', ['-NoProfile', '-Command', `Stop-Process -Id ${candidate.launcherPid} -Force -ErrorAction SilentlyContinue`]);
|
|
8019
|
+
if (proc.status === 0) {
|
|
8020
|
+
report.cleaned.push({ pid: candidate.launcherPid, role: 'launcher' });
|
|
8021
|
+
cleaned.add(`launcher:${candidate.launcherPid}`);
|
|
8022
|
+
}
|
|
8023
|
+
}
|
|
8024
|
+
}
|
|
8025
|
+
}
|
|
8026
|
+
if (json) {
|
|
8027
|
+
console.log(JSON.stringify(report, null, 2));
|
|
8028
|
+
return;
|
|
8029
|
+
}
|
|
8030
|
+
printMcpCleanupReport(report);
|
|
8031
|
+
}
|
|
6775
8032
|
async function main() {
|
|
6776
8033
|
const args = parseArgs(process.argv.slice(2));
|
|
8034
|
+
ACTIVE_PARSED_ARGS = args;
|
|
6777
8035
|
setCliDebugFlags(args);
|
|
6778
8036
|
debugLog('CLI invocation started.', {
|
|
6779
8037
|
command: args.command,
|
|
@@ -7001,6 +8259,14 @@ async function main() {
|
|
|
7001
8259
|
await attendCommand(args);
|
|
7002
8260
|
return;
|
|
7003
8261
|
}
|
|
8262
|
+
if (args.command === 'issues') {
|
|
8263
|
+
if (hasFlag(args, 'help')) {
|
|
8264
|
+
printIssuesHelp();
|
|
8265
|
+
return;
|
|
8266
|
+
}
|
|
8267
|
+
await issuesCommand(args);
|
|
8268
|
+
return;
|
|
8269
|
+
}
|
|
7004
8270
|
if (args.command === 'handoff') {
|
|
7005
8271
|
if (hasFlag(args, 'help')) {
|
|
7006
8272
|
printHandoffHelp();
|
|
@@ -7026,6 +8292,26 @@ async function main() {
|
|
|
7026
8292
|
return;
|
|
7027
8293
|
}
|
|
7028
8294
|
if (args.command === 'mcp') {
|
|
8295
|
+
if (!args.subcommand || args.subcommand === 'server') {
|
|
8296
|
+
if (hasFlag(args, 'help')) {
|
|
8297
|
+
printMcpHelp();
|
|
8298
|
+
return;
|
|
8299
|
+
}
|
|
8300
|
+
await handoffToScript('iranti-mcp', args.subcommand === 'server' ? process.argv.slice(4) : process.argv.slice(3));
|
|
8301
|
+
return;
|
|
8302
|
+
}
|
|
8303
|
+
if (args.subcommand === 'cleanup') {
|
|
8304
|
+
if (hasFlag(args, 'help')) {
|
|
8305
|
+
printMcpHelp();
|
|
8306
|
+
return;
|
|
8307
|
+
}
|
|
8308
|
+
await mcpCleanupCommand(args);
|
|
8309
|
+
return;
|
|
8310
|
+
}
|
|
8311
|
+
if (args.subcommand === 'help' || args.subcommand === '--help') {
|
|
8312
|
+
printMcpHelp();
|
|
8313
|
+
return;
|
|
8314
|
+
}
|
|
7029
8315
|
await handoffToScript('iranti-mcp', process.argv.slice(3));
|
|
7030
8316
|
return;
|
|
7031
8317
|
}
|
|
@@ -7065,24 +8351,40 @@ main().then(async () => {
|
|
|
7065
8351
|
await (0, staffEventRegistry_1.flushStaffEventEmitter)();
|
|
7066
8352
|
}
|
|
7067
8353
|
catch { }
|
|
7068
|
-
const
|
|
7069
|
-
const
|
|
7070
|
-
const
|
|
8354
|
+
const failure = normalizeCliFailure(err);
|
|
8355
|
+
const code = failure.code ?? 'IRANTI_COMMAND_FAILED';
|
|
8356
|
+
const message = failure.message;
|
|
8357
|
+
const hints = failure instanceof CliError ? failure.hints : (failure.hints ?? []);
|
|
8358
|
+
const details = failure instanceof CliError ? failure.details : undefined;
|
|
8359
|
+
if (wantsJsonErrorEnvelope(ACTIVE_PARSED_ARGS)) {
|
|
8360
|
+
const payload = {
|
|
8361
|
+
ok: false,
|
|
8362
|
+
error: {
|
|
8363
|
+
code,
|
|
8364
|
+
message,
|
|
8365
|
+
hints,
|
|
8366
|
+
...(details && Object.keys(details).length > 0 ? { details } : {}),
|
|
8367
|
+
...(CLI_DEBUG && failure.stack ? { stack: failure.stack } : {}),
|
|
8368
|
+
},
|
|
8369
|
+
};
|
|
8370
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
8371
|
+
process.exit(1);
|
|
8372
|
+
}
|
|
7071
8373
|
console.error(`${failLabel('ERROR')}${code ? ` [${code}]` : ''} ${message}`);
|
|
7072
|
-
if (
|
|
8374
|
+
if (hints.length > 0) {
|
|
7073
8375
|
console.error('');
|
|
7074
8376
|
console.error('Possible fixes:');
|
|
7075
|
-
for (const hint of
|
|
8377
|
+
for (const hint of hints) {
|
|
7076
8378
|
console.error(` - ${hint}`);
|
|
7077
8379
|
}
|
|
7078
8380
|
}
|
|
7079
|
-
if (CLI_DEBUG &&
|
|
8381
|
+
if (CLI_DEBUG && details && Object.keys(details).length > 0) {
|
|
7080
8382
|
console.error('');
|
|
7081
|
-
console.error(`${paint('[DEBUG]', 'gray')} ${JSON.stringify(
|
|
8383
|
+
console.error(`${paint('[DEBUG]', 'gray')} ${JSON.stringify(details, null, 2)}`);
|
|
7082
8384
|
}
|
|
7083
|
-
if (CLI_DEBUG &&
|
|
8385
|
+
if (CLI_DEBUG && failure.stack) {
|
|
7084
8386
|
console.error('');
|
|
7085
|
-
console.error(
|
|
8387
|
+
console.error(failure.stack);
|
|
7086
8388
|
}
|
|
7087
8389
|
process.exit(1);
|
|
7088
8390
|
});
|