@hashicorp/kits 0.1.5 → 0.1.8
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 +51 -682
- package/dist/adapters/claude-code/detection.d.ts +1 -1
- package/dist/adapters/claude-code/detection.d.ts.map +1 -1
- package/dist/adapters/claude-code/detection.js +3 -2
- package/dist/adapters/claude-code/detection.js.map +1 -1
- package/dist/adapters/claude-code/index.d.ts +1 -1
- package/dist/adapters/claude-code/index.d.ts.map +1 -1
- package/dist/adapters/claude-code/index.js +25 -9
- package/dist/adapters/claude-code/index.js.map +1 -1
- package/dist/adapters/claude-code/installer.d.ts +1 -4
- package/dist/adapters/claude-code/installer.d.ts.map +1 -1
- package/dist/adapters/claude-code/installer.js +21 -24
- package/dist/adapters/claude-code/installer.js.map +1 -1
- package/dist/adapters/gemini-cli/detection.d.ts +1 -1
- package/dist/adapters/gemini-cli/detection.d.ts.map +1 -1
- package/dist/adapters/gemini-cli/detection.js +3 -2
- package/dist/adapters/gemini-cli/detection.js.map +1 -1
- package/dist/adapters/gemini-cli/index.d.ts +1 -1
- package/dist/adapters/gemini-cli/index.d.ts.map +1 -1
- package/dist/adapters/gemini-cli/index.js +3 -1
- package/dist/adapters/gemini-cli/index.js.map +1 -1
- package/dist/adapters/gemini-cli/installer.d.ts +1 -4
- package/dist/adapters/gemini-cli/installer.d.ts.map +1 -1
- package/dist/adapters/gemini-cli/installer.js +20 -24
- package/dist/adapters/gemini-cli/installer.js.map +1 -1
- package/dist/adapters/github-copilot/detection.d.ts +1 -1
- package/dist/adapters/github-copilot/detection.d.ts.map +1 -1
- package/dist/adapters/github-copilot/detection.js +3 -2
- package/dist/adapters/github-copilot/detection.js.map +1 -1
- package/dist/adapters/github-copilot/index.d.ts +1 -1
- package/dist/adapters/github-copilot/index.d.ts.map +1 -1
- package/dist/adapters/github-copilot/index.js +2 -1
- package/dist/adapters/github-copilot/index.js.map +1 -1
- package/dist/adapters/github-copilot/installer.d.ts.map +1 -1
- package/dist/adapters/github-copilot/installer.js +6 -3
- package/dist/adapters/github-copilot/installer.js.map +1 -1
- package/dist/adapters/opencode/detection.d.ts.map +1 -1
- package/dist/adapters/opencode/detection.js +3 -2
- package/dist/adapters/opencode/detection.js.map +1 -1
- package/dist/adapters/opencode/index.d.ts.map +1 -1
- package/dist/adapters/opencode/index.js +43 -20
- package/dist/adapters/opencode/index.js.map +1 -1
- package/dist/adapters/opencode/installer.d.ts.map +1 -1
- package/dist/adapters/opencode/installer.js +57 -32
- package/dist/adapters/opencode/installer.js.map +1 -1
- package/dist/adapters/types.d.ts +8 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/adapters/types.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/install.js +238 -176
- package/dist/cli/install.js.map +1 -1
- package/dist/cli/list.d.ts.map +1 -1
- package/dist/cli/list.js +18 -10
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/uninstall.d.ts.map +1 -1
- package/dist/cli/uninstall.js +93 -88
- package/dist/cli/uninstall.js.map +1 -1
- package/dist/cli/upgrade.d.ts.map +1 -1
- package/dist/cli/upgrade.js +180 -31
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/core/debug.d.ts +1 -1
- package/dist/core/debug.js +2 -2
- package/dist/core/debug.js.map +1 -1
- package/dist/core/hook-arguments.d.ts +4 -0
- package/dist/core/hook-arguments.d.ts.map +1 -0
- package/dist/core/hook-arguments.js +56 -0
- package/dist/core/hook-arguments.js.map +1 -0
- package/dist/core/hook-instance.d.ts +2 -2
- package/dist/core/hook-instance.d.ts.map +1 -1
- package/dist/core/hook-instance.js +7 -11
- package/dist/core/hook-instance.js.map +1 -1
- package/dist/core/hook-store.d.ts +5 -0
- package/dist/core/hook-store.d.ts.map +1 -0
- package/dist/core/hook-store.js +6 -0
- package/dist/core/hook-store.js.map +1 -0
- package/dist/core/types.d.ts +12 -11
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/upgrade-executor.d.ts +3 -1
- package/dist/core/upgrade-executor.d.ts.map +1 -1
- package/dist/core/upgrade-executor.js +292 -25
- package/dist/core/upgrade-executor.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lockfile/index.d.ts +5 -5
- package/dist/lockfile/index.d.ts.map +1 -1
- package/dist/lockfile/index.js +35 -57
- package/dist/lockfile/index.js.map +1 -1
- package/dist/lockfile/read.js +1 -1
- package/dist/lockfile/read.js.map +1 -1
- package/dist/lockfile/types.d.ts +39 -39
- package/dist/lockfile/types.d.ts.map +1 -1
- package/dist/lockfile/types.js +1 -1
- package/dist/lockfile/types.js.map +1 -1
- package/dist/lockfile/upgrade-check.d.ts.map +1 -1
- package/dist/lockfile/upgrade-check.js +16 -19
- package/dist/lockfile/upgrade-check.js.map +1 -1
- package/dist/tui/types.d.ts +2 -0
- package/dist/tui/types.d.ts.map +1 -1
- package/dist/tui/upgrade-select.d.ts.map +1 -1
- package/dist/tui/upgrade-select.js +92 -14
- package/dist/tui/upgrade-select.js.map +1 -1
- package/dist/validation/validate-hooks.d.ts.map +1 -1
- package/dist/validation/validate-hooks.js +2 -1
- package/dist/validation/validate-hooks.js.map +1 -1
- package/package.json +2 -1
- package/schemas/examples/hook-binding-valid.json +1 -2
- package/schemas/hook-binding.schema.json +15 -15
- package/schemas/hook-program.schema.json +39 -7
- package/schemas/kit.schema.json +5 -4
- package/schemas/kits-lock.schema.json +161 -105
package/dist/cli/install.js
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
import { ExitCode } from "./types.js";
|
|
8
8
|
import * as clack from "@clack/prompts";
|
|
9
9
|
import fs from "node:fs/promises";
|
|
10
|
+
import os from "node:os";
|
|
10
11
|
import path from "node:path";
|
|
11
12
|
import pc from "picocolors";
|
|
12
13
|
import { fetchSource, scanKits, filterKitsByName as filterDiscoveryKitsByName, getMissingKitNames as getDiscoveryMissingKitNames, NoFetcherError, SourceParseError, } from "../discovery/index.js";
|
|
13
14
|
import { resolvePrimitiveReferences, PrimitivesRegistryLoader, validateCliEnvFlags, mergeEnvDefs, resolveEnvVarsFromConfig, } from "../resolution/index.js";
|
|
14
15
|
import { hashMcpConfig } from "../core/mcp-instance.js";
|
|
15
|
-
import {
|
|
16
|
+
import { hashHookProgram } from "../core/hook-instance.js";
|
|
16
17
|
import { readLockfile } from "../lockfile/read.js";
|
|
17
18
|
import { writeLockfile } from "../lockfile/write.js";
|
|
18
19
|
import { createEmptyLockfile, } from "../lockfile/types.js";
|
|
@@ -131,16 +132,18 @@ async function resolveManifestTarget(source) {
|
|
|
131
132
|
}
|
|
132
133
|
function buildLockedVersionMap(lockfile) {
|
|
133
134
|
const locked = new Map();
|
|
134
|
-
for (const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
for (const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
locked.
|
|
135
|
+
for (const kit of Object.values(lockfile.kits)) {
|
|
136
|
+
for (const harnessEntry of Object.values(kit.harnesses)) {
|
|
137
|
+
if (!harnessEntry)
|
|
138
|
+
continue;
|
|
139
|
+
for (const [type, primitives] of Object.entries(harnessEntry.primitives)) {
|
|
140
|
+
for (const primitive of primitives ?? []) {
|
|
141
|
+
if (!primitive.version)
|
|
142
|
+
continue;
|
|
143
|
+
const key = `${type}:${primitive.name}`;
|
|
144
|
+
if (!locked.has(key)) {
|
|
145
|
+
locked.set(key, primitive.version);
|
|
146
|
+
}
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
}
|
|
@@ -282,6 +285,20 @@ export async function runInstall(source, options) {
|
|
|
282
285
|
}
|
|
283
286
|
return { success: false, exitCode: ExitCode.ScopeRequired, error: message };
|
|
284
287
|
}
|
|
288
|
+
if (scope === "project") {
|
|
289
|
+
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
290
|
+
const homeDir = path.resolve(os.homedir());
|
|
291
|
+
if (resolvedProjectRoot === homeDir) {
|
|
292
|
+
const message = "Project scope installs are not allowed in the home directory.";
|
|
293
|
+
if (options.json) {
|
|
294
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
console.error(`Error: ${message}`);
|
|
298
|
+
}
|
|
299
|
+
return { success: false, exitCode: ExitCode.InvalidArguments, error: message };
|
|
300
|
+
}
|
|
301
|
+
}
|
|
285
302
|
try {
|
|
286
303
|
// Start interactive UI if applicable
|
|
287
304
|
if (isInteractive) {
|
|
@@ -706,25 +723,19 @@ export async function runInstall(source, options) {
|
|
|
706
723
|
: undefined;
|
|
707
724
|
const outputSource = manifestInstall && manifestPath ? manifestPath : source;
|
|
708
725
|
const overwriteTargets = new Map();
|
|
709
|
-
const existingInstances = new Set();
|
|
710
|
-
for (const harness of selectedHarnesses) {
|
|
711
|
-
const harnessEntry = lockfile.harnesses[harness.name];
|
|
712
|
-
if (!harnessEntry)
|
|
713
|
-
continue;
|
|
714
|
-
for (const instanceName of Object.keys(harnessEntry.kits)) {
|
|
715
|
-
existingInstances.add(instanceName);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
726
|
+
const existingInstances = new Set(Object.keys(lockfile.kits));
|
|
718
727
|
const usedInstanceNames = new Set(selectedKits.map((kit) => kit.installAs));
|
|
719
728
|
for (const kit of selectedKits) {
|
|
720
729
|
const collidingHarnesses = [];
|
|
721
730
|
const existingKits = [];
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
731
|
+
const existingKit = lockfile.kits[kit.installAs];
|
|
732
|
+
if (existingKit) {
|
|
733
|
+
for (const harness of selectedHarnesses) {
|
|
734
|
+
if (existingKit.harnesses[harness.name]) {
|
|
735
|
+
collidingHarnesses.push(harness.name);
|
|
736
|
+
existingKits.push(existingKit);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
728
739
|
}
|
|
729
740
|
if (collidingHarnesses.length === 0) {
|
|
730
741
|
continue;
|
|
@@ -1297,14 +1308,17 @@ async function buildHookPrimitiveInfoBySourcePath(resolvedByKit) {
|
|
|
1297
1308
|
continue;
|
|
1298
1309
|
if (!primitive.hookProgram || !primitive.hookBinding)
|
|
1299
1310
|
continue;
|
|
1311
|
+
if (!primitive.hookEntryPath)
|
|
1312
|
+
continue;
|
|
1300
1313
|
const key = buildPrimitiveKey(kitName, primitive.sourcePath);
|
|
1301
1314
|
if (infoBySourcePath.has(key))
|
|
1302
1315
|
continue;
|
|
1303
1316
|
const instanceName = resolveHookInstanceName(primitive.hookProgram.name ?? primitive.name, primitive.instanceName, kitName);
|
|
1304
|
-
const
|
|
1317
|
+
const entryContents = await fs.readFile(primitive.hookEntryPath, "utf-8");
|
|
1318
|
+
const checksum = hashHookProgram(primitive.hookProgram, entryContents);
|
|
1305
1319
|
infoBySourcePath.set(key, {
|
|
1306
1320
|
instanceName,
|
|
1307
|
-
|
|
1321
|
+
checksum,
|
|
1308
1322
|
program: primitive.hookProgram,
|
|
1309
1323
|
binding: primitive.hookBinding,
|
|
1310
1324
|
primitiveName: primitive.name,
|
|
@@ -1344,8 +1358,8 @@ function getMcpConfigPath(adapter, scope, projectRoot) {
|
|
|
1344
1358
|
return expandPath(mcpPath);
|
|
1345
1359
|
}
|
|
1346
1360
|
function getHookConfigPath(adapter, scope, projectRoot) {
|
|
1347
|
-
const
|
|
1348
|
-
const hookPath =
|
|
1361
|
+
const configLocations = adapter.getConfigLocations(scope);
|
|
1362
|
+
const hookPath = configLocations.hooks ?? configLocations.primary;
|
|
1349
1363
|
if (scope === "project" && projectRoot) {
|
|
1350
1364
|
return path.join(projectRoot, hookPath.replace(/^\.\//, ""));
|
|
1351
1365
|
}
|
|
@@ -1386,69 +1400,92 @@ async function promptForMcpInstanceName(instanceName, usedNames) {
|
|
|
1386
1400
|
return trimmed;
|
|
1387
1401
|
}
|
|
1388
1402
|
}
|
|
1403
|
+
function getChecksumSuffix(checksum) {
|
|
1404
|
+
const delimiterIndex = checksum.indexOf(":");
|
|
1405
|
+
return delimiterIndex === -1 ? checksum : checksum.slice(delimiterIndex + 1);
|
|
1406
|
+
}
|
|
1407
|
+
function buildHookInstanceAlias(instanceName, checksum, existing) {
|
|
1408
|
+
const suffix = getChecksumSuffix(checksum);
|
|
1409
|
+
const lengths = [8, 12, 16, suffix.length];
|
|
1410
|
+
for (const length of lengths) {
|
|
1411
|
+
const alias = `${instanceName}@${suffix.slice(0, length)}`;
|
|
1412
|
+
const entry = existing?.[alias];
|
|
1413
|
+
if (!entry || entry.checksum === checksum) {
|
|
1414
|
+
return alias;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return `${instanceName}@${suffix}`;
|
|
1418
|
+
}
|
|
1389
1419
|
async function removeOverwrittenKitsForHarness(harness, kitsToOverwrite, lockfile, scope, projectRoot, options) {
|
|
1390
|
-
|
|
1391
|
-
if (!harnessEntry || kitsToOverwrite.size === 0) {
|
|
1420
|
+
if (kitsToOverwrite.size === 0) {
|
|
1392
1421
|
return;
|
|
1393
1422
|
}
|
|
1394
|
-
const mcpInstances =
|
|
1395
|
-
const hookInstances =
|
|
1423
|
+
const mcpInstances = lockfile.mcpInstances;
|
|
1424
|
+
const hookInstances = lockfile.hookInstances;
|
|
1396
1425
|
const mcpCandidates = new Set();
|
|
1397
1426
|
const hookCandidates = new Set();
|
|
1398
1427
|
for (const kitName of kitsToOverwrite) {
|
|
1399
|
-
const kit =
|
|
1428
|
+
const kit = lockfile.kits[kitName];
|
|
1400
1429
|
if (!kit)
|
|
1401
1430
|
continue;
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
const instanceName = primitive.instanceName ?? primitive.namespacedName;
|
|
1412
|
-
if (instanceName) {
|
|
1413
|
-
hookCandidates.add(instanceName);
|
|
1414
|
-
}
|
|
1415
|
-
continue;
|
|
1416
|
-
}
|
|
1417
|
-
const filePath = expandPath(primitive.installedPath);
|
|
1418
|
-
try {
|
|
1419
|
-
await remove(filePath);
|
|
1420
|
-
if (options.verbose) {
|
|
1421
|
-
if (options.isInteractive) {
|
|
1422
|
-
clack.log.info(` ✓ Removed ${primitive.namespacedName}`);
|
|
1431
|
+
const harnessEntry = kit.harnesses[harness.name];
|
|
1432
|
+
if (!harnessEntry)
|
|
1433
|
+
continue;
|
|
1434
|
+
for (const [type, entries] of Object.entries(harnessEntry.primitives)) {
|
|
1435
|
+
for (const primitive of entries ?? []) {
|
|
1436
|
+
const displayName = `${kitName}.${primitive.name}`;
|
|
1437
|
+
if (type === "mcp") {
|
|
1438
|
+
if ("instanceRef" in primitive && primitive.instanceRef) {
|
|
1439
|
+
mcpCandidates.add(primitive.instanceRef);
|
|
1423
1440
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (type === "hooks") {
|
|
1444
|
+
if ("instanceRef" in primitive && primitive.instanceRef) {
|
|
1445
|
+
hookCandidates.add(primitive.instanceRef);
|
|
1426
1446
|
}
|
|
1447
|
+
continue;
|
|
1427
1448
|
}
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1449
|
+
const filePath = expandPath(primitive.installedPath);
|
|
1450
|
+
try {
|
|
1451
|
+
await remove(filePath);
|
|
1452
|
+
if (options.verbose) {
|
|
1453
|
+
if (options.isInteractive) {
|
|
1454
|
+
clack.log.info(` ✓ Removed ${displayName}`);
|
|
1455
|
+
}
|
|
1456
|
+
else {
|
|
1457
|
+
console.error(` Removed: ${filePath}`);
|
|
1458
|
+
}
|
|
1434
1459
|
}
|
|
1435
|
-
|
|
1436
|
-
|
|
1460
|
+
}
|
|
1461
|
+
catch (error) {
|
|
1462
|
+
if (options.verbose) {
|
|
1463
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
1464
|
+
if (options.isInteractive) {
|
|
1465
|
+
clack.log.warn(` ⚠ Could not remove ${displayName}: ${msg}`);
|
|
1466
|
+
}
|
|
1467
|
+
else {
|
|
1468
|
+
console.error(` Warning: Could not remove ${filePath}: ${msg}`);
|
|
1469
|
+
}
|
|
1437
1470
|
}
|
|
1438
1471
|
}
|
|
1439
1472
|
}
|
|
1440
1473
|
}
|
|
1474
|
+
delete kit.harnesses[harness.name];
|
|
1475
|
+
if (Object.keys(kit.harnesses).length === 0) {
|
|
1476
|
+
delete lockfile.kits[kitName];
|
|
1477
|
+
}
|
|
1441
1478
|
}
|
|
1442
1479
|
const mcpInstancesToRemove = [];
|
|
1443
1480
|
if (mcpInstances) {
|
|
1444
|
-
for (const
|
|
1445
|
-
const entry = mcpInstances[
|
|
1481
|
+
for (const instanceRef of mcpCandidates) {
|
|
1482
|
+
const entry = mcpInstances[instanceRef];
|
|
1446
1483
|
if (!entry)
|
|
1447
1484
|
continue;
|
|
1448
1485
|
entry.usedBy = entry.usedBy.filter((name) => !kitsToOverwrite.has(name));
|
|
1449
1486
|
if (entry.usedBy.length === 0) {
|
|
1450
|
-
delete mcpInstances[
|
|
1451
|
-
mcpInstancesToRemove.push(instanceName);
|
|
1487
|
+
delete mcpInstances[instanceRef];
|
|
1488
|
+
mcpInstancesToRemove.push(entry.instanceName);
|
|
1452
1489
|
}
|
|
1453
1490
|
}
|
|
1454
1491
|
}
|
|
@@ -1468,14 +1505,14 @@ async function removeOverwrittenKitsForHarness(harness, kitsToOverwrite, lockfil
|
|
|
1468
1505
|
}
|
|
1469
1506
|
const hookInstancesToRemove = [];
|
|
1470
1507
|
if (hookInstances) {
|
|
1471
|
-
for (const
|
|
1472
|
-
const entry = hookInstances[
|
|
1508
|
+
for (const instanceRef of hookCandidates) {
|
|
1509
|
+
const entry = hookInstances[instanceRef];
|
|
1473
1510
|
if (!entry)
|
|
1474
1511
|
continue;
|
|
1475
1512
|
entry.usedBy = entry.usedBy.filter((name) => !kitsToOverwrite.has(name));
|
|
1476
1513
|
if (entry.usedBy.length === 0) {
|
|
1477
|
-
delete hookInstances[
|
|
1478
|
-
hookInstancesToRemove.push(
|
|
1514
|
+
delete hookInstances[instanceRef];
|
|
1515
|
+
hookInstancesToRemove.push(instanceRef);
|
|
1479
1516
|
}
|
|
1480
1517
|
}
|
|
1481
1518
|
}
|
|
@@ -1493,9 +1530,7 @@ async function removeOverwrittenKitsForHarness(harness, kitsToOverwrite, lockfil
|
|
|
1493
1530
|
}
|
|
1494
1531
|
}
|
|
1495
1532
|
}
|
|
1496
|
-
|
|
1497
|
-
delete harnessEntry.kits[kitName];
|
|
1498
|
-
}
|
|
1533
|
+
// kits are removed above when their harness entries are cleared
|
|
1499
1534
|
}
|
|
1500
1535
|
/**
|
|
1501
1536
|
* Execute the actual installation of kits to harnesses.
|
|
@@ -1701,11 +1736,12 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
1701
1736
|
};
|
|
1702
1737
|
}
|
|
1703
1738
|
}
|
|
1704
|
-
const existingInstances = lockfile.
|
|
1739
|
+
const existingInstances = lockfile.mcpInstances ?? {};
|
|
1705
1740
|
const overwriteKits = filterOverwriteKits(overwriteKitsByHarness.get(harness.name), kitsToCheck);
|
|
1706
1741
|
const usedInstanceNames = new Set(Object.entries(existingInstances)
|
|
1707
|
-
.filter(([, entry]) => entry.
|
|
1708
|
-
.
|
|
1742
|
+
.filter(([, entry]) => entry.harness === harness.name &&
|
|
1743
|
+
entry.usedBy.some((name) => !overwriteKits.has(name)))
|
|
1744
|
+
.map(([, entry]) => entry.instanceName));
|
|
1709
1745
|
const assignments = new Map();
|
|
1710
1746
|
const forkedInstances = new Map();
|
|
1711
1747
|
const instancesToInstall = new Set();
|
|
@@ -1714,7 +1750,8 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
1714
1750
|
let finalName = instanceName;
|
|
1715
1751
|
let finalHash = candidate.info.configHash;
|
|
1716
1752
|
let action = "install";
|
|
1717
|
-
const
|
|
1753
|
+
const instanceKey = `${harness.name}:${instanceName}`;
|
|
1754
|
+
const existing = existingInstances[instanceKey];
|
|
1718
1755
|
const existingUsers = existing?.usedBy.filter((name) => !overwriteKits.has(name)) ?? [];
|
|
1719
1756
|
if (existing && existingUsers.length > 0) {
|
|
1720
1757
|
if (existing.configHash === candidate.info.configHash) {
|
|
@@ -1822,60 +1859,67 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
1822
1859
|
const info = hookInfoByPrimitiveKey.get(key);
|
|
1823
1860
|
if (!info)
|
|
1824
1861
|
continue;
|
|
1825
|
-
const
|
|
1826
|
-
const entry =
|
|
1862
|
+
const byChecksum = desiredByName.get(info.instanceName) ?? new Map();
|
|
1863
|
+
const entry = byChecksum.get(info.checksum) ?? {
|
|
1827
1864
|
primitives: [],
|
|
1828
1865
|
kits: new Set(),
|
|
1829
1866
|
info,
|
|
1830
1867
|
};
|
|
1831
1868
|
entry.primitives.push({ primitive, kitName: kit.installAs });
|
|
1832
1869
|
entry.kits.add(kit.installAs);
|
|
1833
|
-
|
|
1834
|
-
desiredByName.set(info.instanceName,
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
for (const [instanceName, byHash] of desiredByName) {
|
|
1838
|
-
if (byHash.size > 1) {
|
|
1839
|
-
const message = `Conflicting hook configs detected for instance "${instanceName}". ` +
|
|
1840
|
-
"Resolve the conflict by renaming one of the hook instances.";
|
|
1841
|
-
return {
|
|
1842
|
-
success: false,
|
|
1843
|
-
exitCode: ExitCode.InstallationFailed,
|
|
1844
|
-
error: message,
|
|
1845
|
-
};
|
|
1870
|
+
byChecksum.set(info.checksum, entry);
|
|
1871
|
+
desiredByName.set(info.instanceName, byChecksum);
|
|
1846
1872
|
}
|
|
1847
1873
|
}
|
|
1848
|
-
const existingInstances = lockfile.
|
|
1874
|
+
const existingInstances = lockfile.hookInstances ?? {};
|
|
1849
1875
|
const overwriteKits = filterOverwriteKits(overwriteKitsByHarness.get(harness.name), kitsToCheck);
|
|
1850
1876
|
const assignments = new Map();
|
|
1851
1877
|
const instancesToInstall = new Set();
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1878
|
+
const plannedBaseChecksums = new Map();
|
|
1879
|
+
for (const [instanceName, byChecksum] of desiredByName) {
|
|
1880
|
+
for (const candidate of byChecksum.values()) {
|
|
1881
|
+
let instanceRef = instanceName;
|
|
1882
|
+
let action = "install";
|
|
1883
|
+
const checksum = candidate.info.checksum;
|
|
1884
|
+
const existingBase = existingInstances[instanceName];
|
|
1885
|
+
const existingBaseUsers = existingBase?.usedBy.filter((name) => !overwriteKits.has(name)) ?? [];
|
|
1886
|
+
const baseInUse = Boolean(existingBase && existingBaseUsers.length > 0);
|
|
1887
|
+
if (baseInUse && existingBase?.checksum && existingBase.checksum !== checksum) {
|
|
1888
|
+
instanceRef = buildHookInstanceAlias(instanceName, checksum, existingInstances);
|
|
1889
|
+
}
|
|
1890
|
+
const plannedChecksum = plannedBaseChecksums.get(instanceName);
|
|
1891
|
+
if (instanceRef === instanceName && plannedChecksum && plannedChecksum !== checksum) {
|
|
1892
|
+
instanceRef = buildHookInstanceAlias(instanceName, checksum, existingInstances);
|
|
1893
|
+
}
|
|
1894
|
+
if (instanceRef === instanceName) {
|
|
1895
|
+
plannedBaseChecksums.set(instanceName, checksum);
|
|
1896
|
+
}
|
|
1897
|
+
const existing = existingInstances[instanceRef];
|
|
1898
|
+
const existingUsers = existing?.usedBy.filter((name) => !overwriteKits.has(name)) ?? [];
|
|
1899
|
+
if (existing && existingUsers.length > 0) {
|
|
1900
|
+
if (existing.checksum === checksum) {
|
|
1901
|
+
action = "reuse";
|
|
1902
|
+
}
|
|
1903
|
+
else {
|
|
1904
|
+
instanceRef = buildHookInstanceAlias(instanceName, checksum, existingInstances);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
const aliasEntry = existingInstances[instanceRef];
|
|
1908
|
+
const aliasUsers = aliasEntry?.usedBy.filter((name) => !overwriteKits.has(name)) ?? [];
|
|
1909
|
+
if (aliasEntry && aliasUsers.length > 0 && aliasEntry.checksum === checksum) {
|
|
1910
|
+
action = "reuse";
|
|
1911
|
+
}
|
|
1912
|
+
if (action === "install") {
|
|
1913
|
+
instancesToInstall.add(instanceRef);
|
|
1914
|
+
}
|
|
1915
|
+
for (const { primitive, kitName } of candidate.primitives) {
|
|
1916
|
+
const key = buildPrimitiveKey(kitName, primitive.sourcePath);
|
|
1917
|
+
assignments.set(key, {
|
|
1918
|
+
instanceRef,
|
|
1919
|
+
checksum,
|
|
1920
|
+
action,
|
|
1921
|
+
});
|
|
1865
1922
|
}
|
|
1866
|
-
action = "reuse";
|
|
1867
|
-
finalHash = existing.configHash;
|
|
1868
|
-
}
|
|
1869
|
-
if (action === "install") {
|
|
1870
|
-
instancesToInstall.add(instanceName);
|
|
1871
|
-
}
|
|
1872
|
-
for (const { primitive, kitName } of candidate.primitives) {
|
|
1873
|
-
const key = buildPrimitiveKey(kitName, primitive.sourcePath);
|
|
1874
|
-
assignments.set(key, {
|
|
1875
|
-
instanceName,
|
|
1876
|
-
configHash: finalHash,
|
|
1877
|
-
action,
|
|
1878
|
-
});
|
|
1879
1923
|
}
|
|
1880
1924
|
}
|
|
1881
1925
|
if (assignments.size > 0) {
|
|
@@ -1961,15 +2005,10 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
1961
2005
|
primitivesForAdapter.push(primitive);
|
|
1962
2006
|
continue;
|
|
1963
2007
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
if (!shouldInstall) {
|
|
1968
|
-
skippedHookPrimitives.push(primitive);
|
|
1969
|
-
continue;
|
|
2008
|
+
primitivesForAdapter.push({ ...primitive, instanceName: assignment.instanceRef });
|
|
2009
|
+
if (assignment.action === "install") {
|
|
2010
|
+
installedHookInstanceNames.add(assignment.instanceRef);
|
|
1970
2011
|
}
|
|
1971
|
-
primitivesForAdapter.push({ ...primitive, instanceName: assignment.instanceName });
|
|
1972
|
-
installedHookInstanceNames.add(assignment.instanceName);
|
|
1973
2012
|
continue;
|
|
1974
2013
|
}
|
|
1975
2014
|
primitivesForAdapter.push(primitive);
|
|
@@ -2048,66 +2087,83 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
2048
2087
|
}
|
|
2049
2088
|
}
|
|
2050
2089
|
// Record in lockfile
|
|
2051
|
-
if (!lockfile.harnesses[harness.name]) {
|
|
2052
|
-
lockfile.harnesses[harness.name] = { kits: {} };
|
|
2053
|
-
}
|
|
2054
2090
|
const statusByKey = new Map();
|
|
2055
2091
|
for (const status of installResult.installedPrimitives) {
|
|
2056
2092
|
statusByKey.set(`${status.type}:${status.name}`, status);
|
|
2057
2093
|
}
|
|
2058
|
-
const
|
|
2094
|
+
const kitEntry = (lockfile.kits[kit.installAs] ??= {
|
|
2095
|
+
name: kit.name,
|
|
2096
|
+
installAs: kit.installAs,
|
|
2097
|
+
version: kit.manifest.version,
|
|
2098
|
+
installedAt: new Date().toISOString(),
|
|
2099
|
+
source: kit.source,
|
|
2100
|
+
harnesses: {},
|
|
2101
|
+
});
|
|
2102
|
+
kitEntry.version = kit.manifest.version;
|
|
2103
|
+
kitEntry.installedAt = new Date().toISOString();
|
|
2104
|
+
kitEntry.source = kit.source;
|
|
2105
|
+
const primitivesByType = {};
|
|
2106
|
+
for (const primitive of resolvedPrimitives) {
|
|
2059
2107
|
const key = `${primitive.type}:${primitive.name}`;
|
|
2060
2108
|
const status = statusByKey.get(key);
|
|
2061
|
-
const namespacedName = status?.namespacedName
|
|
2062
|
-
?? harness.adapter.getNamespacedName(kit.installAs, primitive.name);
|
|
2063
2109
|
const installedPath = status?.destination
|
|
2064
2110
|
?? (primitive.type === "mcp" ? mcpConfigPath : "");
|
|
2065
|
-
const
|
|
2111
|
+
const entry = {
|
|
2066
2112
|
name: primitive.name,
|
|
2067
|
-
|
|
2068
|
-
namespacedName,
|
|
2069
|
-
isInline: !primitive.ref,
|
|
2113
|
+
version: primitive.resolvedVersion ?? kit.manifest.version,
|
|
2070
2114
|
installedPath,
|
|
2071
2115
|
};
|
|
2072
|
-
if (primitive.
|
|
2073
|
-
|
|
2116
|
+
if (!primitive.ref) {
|
|
2117
|
+
entry.isInline = true;
|
|
2074
2118
|
}
|
|
2075
|
-
|
|
2076
|
-
// Extract version spec from ref (e.g., "tf-plan@^1.0.0" -> "^1.0.0")
|
|
2119
|
+
else {
|
|
2077
2120
|
const atIndex = primitive.ref.indexOf("@");
|
|
2078
2121
|
if (atIndex !== -1) {
|
|
2079
|
-
|
|
2122
|
+
entry.versionSpec = primitive.ref.substring(atIndex + 1);
|
|
2080
2123
|
}
|
|
2081
2124
|
}
|
|
2082
2125
|
if (primitive.type === "mcp") {
|
|
2083
2126
|
const assignment = mcpAssignments?.get(buildPrimitiveKey(kit.installAs, primitive.sourcePath));
|
|
2084
2127
|
if (assignment) {
|
|
2085
|
-
|
|
2086
|
-
|
|
2128
|
+
const instanceRef = `${harness.name}:${assignment.instanceName}`;
|
|
2129
|
+
const mcpEntry = {
|
|
2130
|
+
...entry,
|
|
2131
|
+
configPath: mcpConfigPath,
|
|
2132
|
+
instanceRef,
|
|
2133
|
+
};
|
|
2134
|
+
(primitivesByType.mcp ??= []).push(mcpEntry);
|
|
2087
2135
|
}
|
|
2136
|
+
continue;
|
|
2088
2137
|
}
|
|
2089
2138
|
if (primitive.type === "hooks") {
|
|
2090
2139
|
const assignment = hookAssignments?.get(buildPrimitiveKey(kit.installAs, primitive.sourcePath));
|
|
2091
2140
|
if (assignment) {
|
|
2092
|
-
|
|
2093
|
-
|
|
2141
|
+
if (!primitive.hookBinding) {
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
2144
|
+
const destinationDir = status?.destination ?? "";
|
|
2145
|
+
const programPath = primitive.hookEntryPath
|
|
2146
|
+
? path.join(destinationDir, path.basename(primitive.hookEntryPath))
|
|
2147
|
+
: destinationDir;
|
|
2148
|
+
const hookEntry = {
|
|
2149
|
+
...entry,
|
|
2150
|
+
installedPath: programPath,
|
|
2151
|
+
configPath: hookConfigPath,
|
|
2152
|
+
instanceRef: assignment.instanceRef,
|
|
2153
|
+
binding: primitive.hookBinding,
|
|
2154
|
+
};
|
|
2155
|
+
(primitivesByType.hooks ??= []).push(hookEntry);
|
|
2094
2156
|
}
|
|
2157
|
+
continue;
|
|
2095
2158
|
}
|
|
2096
|
-
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
installAs: kit.installAs,
|
|
2101
|
-
version: kit.manifest.version,
|
|
2102
|
-
installedAt: new Date().toISOString(),
|
|
2103
|
-
source: kit.source,
|
|
2104
|
-
primitives: installedPrimitives,
|
|
2159
|
+
(primitivesByType[primitive.type] ??= []).push(entry);
|
|
2160
|
+
}
|
|
2161
|
+
kitEntry.harnesses[harness.name] = {
|
|
2162
|
+
primitives: primitivesByType,
|
|
2105
2163
|
};
|
|
2106
|
-
lockfile.harnesses[harness.name].kits[kit.installAs] = installedKit;
|
|
2107
2164
|
if (mcpAssignments && mcpInfoByPrimitiveKey.size > 0) {
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
harnessEntry.mcpInstances = {};
|
|
2165
|
+
if (!lockfile.mcpInstances) {
|
|
2166
|
+
lockfile.mcpInstances = {};
|
|
2111
2167
|
}
|
|
2112
2168
|
for (const primitive of resolvedPrimitives) {
|
|
2113
2169
|
if (primitive.type !== "mcp")
|
|
@@ -2115,8 +2171,8 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
2115
2171
|
const assignment = mcpAssignments.get(buildPrimitiveKey(kit.installAs, primitive.sourcePath));
|
|
2116
2172
|
if (!assignment)
|
|
2117
2173
|
continue;
|
|
2118
|
-
const
|
|
2119
|
-
const existingInstance =
|
|
2174
|
+
const instanceKey = `${harness.name}:${assignment.instanceName}`;
|
|
2175
|
+
const existingInstance = lockfile.mcpInstances[instanceKey];
|
|
2120
2176
|
if (existingInstance) {
|
|
2121
2177
|
if (!existingInstance.usedBy.includes(kit.installAs)) {
|
|
2122
2178
|
existingInstance.usedBy.push(kit.installAs);
|
|
@@ -2126,9 +2182,12 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
2126
2182
|
const info = mcpInfoByPrimitiveKey.get(buildPrimitiveKey(kit.installAs, primitive.sourcePath));
|
|
2127
2183
|
if (!info)
|
|
2128
2184
|
continue;
|
|
2129
|
-
|
|
2130
|
-
|
|
2185
|
+
lockfile.mcpInstances[instanceKey] = {
|
|
2186
|
+
harness: harness.name,
|
|
2187
|
+
instanceName: assignment.instanceName,
|
|
2188
|
+
config: info.config,
|
|
2131
2189
|
configHash: assignment.configHash,
|
|
2190
|
+
version: info.version,
|
|
2132
2191
|
usedBy: [kit.installAs],
|
|
2133
2192
|
sourcePrimitive: info.primitiveName,
|
|
2134
2193
|
installedAt: new Date().toISOString(),
|
|
@@ -2136,9 +2195,8 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
2136
2195
|
}
|
|
2137
2196
|
}
|
|
2138
2197
|
if (hookAssignments && hookInfoByPrimitiveKey.size > 0) {
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
harnessEntry.hookInstances = {};
|
|
2198
|
+
if (!lockfile.hookInstances) {
|
|
2199
|
+
lockfile.hookInstances = {};
|
|
2142
2200
|
}
|
|
2143
2201
|
for (const primitive of resolvedPrimitives) {
|
|
2144
2202
|
if (primitive.type !== "hooks")
|
|
@@ -2146,8 +2204,7 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
2146
2204
|
const assignment = hookAssignments.get(buildPrimitiveKey(kit.installAs, primitive.sourcePath));
|
|
2147
2205
|
if (!assignment)
|
|
2148
2206
|
continue;
|
|
2149
|
-
const
|
|
2150
|
-
const existingInstance = harnessEntry.hookInstances[instanceName];
|
|
2207
|
+
const existingInstance = lockfile.hookInstances[assignment.instanceRef];
|
|
2151
2208
|
if (existingInstance) {
|
|
2152
2209
|
if (!existingInstance.usedBy.includes(kit.installAs)) {
|
|
2153
2210
|
existingInstance.usedBy.push(kit.installAs);
|
|
@@ -2157,12 +2214,17 @@ async function executeInstallation(kits, harnesses, registryBySource, options) {
|
|
|
2157
2214
|
const info = hookInfoByPrimitiveKey.get(buildPrimitiveKey(kit.installAs, primitive.sourcePath));
|
|
2158
2215
|
if (!info)
|
|
2159
2216
|
continue;
|
|
2160
|
-
|
|
2217
|
+
const status = statusByKey.get(`hooks:${primitive.name}`);
|
|
2218
|
+
const destinationDir = status?.destination ?? "";
|
|
2219
|
+
const programPath = primitive.hookEntryPath
|
|
2220
|
+
? path.join(destinationDir, path.basename(primitive.hookEntryPath))
|
|
2221
|
+
: destinationDir;
|
|
2222
|
+
lockfile.hookInstances[assignment.instanceRef] = {
|
|
2223
|
+
instanceName: assignment.instanceRef,
|
|
2224
|
+
programPath,
|
|
2225
|
+
checksum: assignment.checksum,
|
|
2161
2226
|
version: info.version,
|
|
2162
|
-
configHash: assignment.configHash,
|
|
2163
2227
|
usedBy: [kit.installAs],
|
|
2164
|
-
sourcePrimitive: info.primitiveName,
|
|
2165
|
-
installedAt: new Date().toISOString(),
|
|
2166
2228
|
};
|
|
2167
2229
|
}
|
|
2168
2230
|
}
|