@cleocode/caamp 2026.4.0 → 2026.4.3
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/dist/{chunk-3WKBFXLE.js → chunk-6NBM4CAF.js} +796 -1611
- package/dist/chunk-6NBM4CAF.js.map +1 -0
- package/dist/{chunk-5UUABVWS.js → chunk-CRU25LRL.js} +2 -2
- package/dist/{chunk-OH6Z2N3G.js → chunk-FLBRAXDW.js} +2 -2
- package/dist/{chunk-3IEKCREL.js → chunk-XWQ5WPHC.js} +1 -8
- package/dist/{chunk-3IEKCREL.js.map → chunk-XWQ5WPHC.js.map} +1 -1
- package/dist/cli.js +364 -1906
- package/dist/cli.js.map +1 -1
- package/dist/{hooks-Q7KO2SGK.js → hooks-VLIP52LY.js} +3 -3
- package/dist/index.d.ts +6 -1043
- package/dist/index.js +5 -60
- package/dist/index.js.map +1 -1
- package/dist/{injector-XCWEBXWK.js → injector-ALLOKC54.js} +3 -3
- package/package.json +2 -2
- package/dist/chunk-3WKBFXLE.js.map +0 -1
- /package/dist/{chunk-5UUABVWS.js.map → chunk-CRU25LRL.js.map} +0 -0
- /package/dist/{chunk-OH6Z2N3G.js.map → chunk-FLBRAXDW.js.map} +0 -0
- /package/dist/{hooks-Q7KO2SGK.js.map → hooks-VLIP52LY.js.map} +0 -0
- /package/dist/{injector-XCWEBXWK.js.map → injector-ALLOKC54.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,70 +1,43 @@
|
|
|
1
1
|
import {
|
|
2
2
|
MarketplaceClient,
|
|
3
3
|
RECOMMENDATION_ERROR_CODES,
|
|
4
|
-
applyMcpInstallWithPolicy,
|
|
5
|
-
buildCleoProfile,
|
|
6
4
|
buildLibraryFromFiles,
|
|
7
|
-
buildServerConfig,
|
|
8
5
|
catalog_exports,
|
|
9
6
|
checkAllSkillUpdates,
|
|
10
|
-
checkCommandReachability,
|
|
11
7
|
checkSkillUpdate,
|
|
12
8
|
clearRegisteredLibrary,
|
|
13
|
-
configureProviderGlobalAndProject,
|
|
14
9
|
deepMerge,
|
|
15
10
|
detectAllProviders,
|
|
16
|
-
detectMcpConfigConflicts,
|
|
17
11
|
detectProjectProviders,
|
|
18
12
|
detectProvider,
|
|
19
13
|
discoverSkill,
|
|
20
14
|
discoverSkills,
|
|
21
15
|
ensureDir,
|
|
22
|
-
extractVersionTag,
|
|
23
16
|
formatSkillRecommendations,
|
|
24
17
|
getInstalledProviders,
|
|
25
|
-
getLastSelectedAgents,
|
|
26
18
|
getNestedValue,
|
|
27
|
-
getTrackedMcpServers,
|
|
28
19
|
getTrackedSkills,
|
|
29
|
-
getTransform,
|
|
30
|
-
inferCleoLockData,
|
|
31
20
|
installBatchWithRollback,
|
|
32
|
-
installMcpServer,
|
|
33
|
-
installMcpServerToAll,
|
|
34
21
|
installSkill,
|
|
35
|
-
isCleoSource,
|
|
36
22
|
isMarketplaceScoped,
|
|
37
23
|
isQuiet,
|
|
38
24
|
isVerbose,
|
|
39
|
-
listAgentsMcpServers,
|
|
40
|
-
listAllMcpServers,
|
|
41
25
|
listCanonicalSkills,
|
|
42
|
-
listMcpServers,
|
|
43
26
|
loadLibraryFromModule,
|
|
44
|
-
normalizeCleoChannel,
|
|
45
27
|
normalizeRecommendationCriteria,
|
|
46
|
-
parseEnvAssignments,
|
|
47
28
|
parseSkillFile,
|
|
48
29
|
parseSource,
|
|
49
30
|
rankSkills,
|
|
50
31
|
readConfig,
|
|
51
32
|
readLockFile,
|
|
52
33
|
recommendSkills,
|
|
53
|
-
reconcileCleoLock,
|
|
54
|
-
recordMcpInstall,
|
|
55
34
|
recordSkillInstall,
|
|
56
35
|
registerSkillLibrary,
|
|
57
36
|
registerSkillLibraryFromPath,
|
|
58
37
|
removeConfig,
|
|
59
|
-
removeMcpFromLock,
|
|
60
|
-
removeMcpServer,
|
|
61
38
|
removeSkill,
|
|
62
39
|
removeSkillFromLock,
|
|
63
40
|
resetDetectionCache,
|
|
64
|
-
resolveChannelFromServerName,
|
|
65
|
-
resolveCleoServerName,
|
|
66
|
-
resolveConfigPath,
|
|
67
|
-
saveLastSelectedAgents,
|
|
68
41
|
scanDirectory,
|
|
69
42
|
scanFile,
|
|
70
43
|
scoreSkillRecommendation,
|
|
@@ -78,7 +51,7 @@ import {
|
|
|
78
51
|
validateRecommendationCriteria,
|
|
79
52
|
validateSkill,
|
|
80
53
|
writeConfig
|
|
81
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-6NBM4CAF.js";
|
|
82
55
|
import {
|
|
83
56
|
buildInjectionContent,
|
|
84
57
|
buildSkillsMap,
|
|
@@ -111,7 +84,7 @@ import {
|
|
|
111
84
|
providerSupportsById,
|
|
112
85
|
removeInjection,
|
|
113
86
|
resolveAlias
|
|
114
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-CRU25LRL.js";
|
|
115
88
|
import {
|
|
116
89
|
CANONICAL_HOOK_EVENTS,
|
|
117
90
|
HOOK_CATEGORIES,
|
|
@@ -137,7 +110,7 @@ import {
|
|
|
137
110
|
toNative,
|
|
138
111
|
toNativeBatch,
|
|
139
112
|
translateToAll
|
|
140
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-FLBRAXDW.js";
|
|
141
114
|
import {
|
|
142
115
|
_resetPlatformPathsCache,
|
|
143
116
|
getAgentsConfigPath,
|
|
@@ -156,7 +129,7 @@ import {
|
|
|
156
129
|
getSystemInfo,
|
|
157
130
|
resolveProviderSkillsDirs,
|
|
158
131
|
resolveRegistryTemplatePath
|
|
159
|
-
} from "./chunk-
|
|
132
|
+
} from "./chunk-XWQ5WPHC.js";
|
|
160
133
|
|
|
161
134
|
// src/core/skills/integrity.ts
|
|
162
135
|
import { existsSync, lstatSync, readlinkSync } from "fs";
|
|
@@ -274,7 +247,7 @@ function shouldOverrideSkill(skillName, incomingSource, existingEntry) {
|
|
|
274
247
|
return true;
|
|
275
248
|
}
|
|
276
249
|
async function validateInstructionIntegrity(providers, projectDir, scope, expectedContent) {
|
|
277
|
-
const { checkAllInjections: checkAllInjections2 } = await import("./injector-
|
|
250
|
+
const { checkAllInjections: checkAllInjections2 } = await import("./injector-ALLOKC54.js");
|
|
278
251
|
const results = await checkAllInjections2(providers, projectDir, scope, expectedContent);
|
|
279
252
|
const issues = [];
|
|
280
253
|
for (const result of results) {
|
|
@@ -306,26 +279,20 @@ export {
|
|
|
306
279
|
MarketplaceClient,
|
|
307
280
|
RECOMMENDATION_ERROR_CODES,
|
|
308
281
|
_resetPlatformPathsCache,
|
|
309
|
-
applyMcpInstallWithPolicy,
|
|
310
|
-
buildCleoProfile,
|
|
311
282
|
buildHookMatrix,
|
|
312
283
|
buildInjectionContent,
|
|
313
284
|
buildLibraryFromFiles,
|
|
314
|
-
buildServerConfig,
|
|
315
285
|
buildSkillsMap,
|
|
316
286
|
catalog_exports as catalog,
|
|
317
287
|
checkAllInjections,
|
|
318
288
|
checkAllSkillIntegrity,
|
|
319
289
|
checkAllSkillUpdates,
|
|
320
|
-
checkCommandReachability,
|
|
321
290
|
checkInjection,
|
|
322
291
|
checkSkillIntegrity,
|
|
323
292
|
checkSkillUpdate,
|
|
324
293
|
clearRegisteredLibrary,
|
|
325
|
-
configureProviderGlobalAndProject,
|
|
326
294
|
deepMerge,
|
|
327
295
|
detectAllProviders,
|
|
328
|
-
detectMcpConfigConflicts,
|
|
329
296
|
detectProjectProviders,
|
|
330
297
|
detectProvider,
|
|
331
298
|
discoverSkill,
|
|
@@ -333,7 +300,6 @@ export {
|
|
|
333
300
|
ensureAllProviderInstructionFiles,
|
|
334
301
|
ensureDir,
|
|
335
302
|
ensureProviderInstructionFile,
|
|
336
|
-
extractVersionTag,
|
|
337
303
|
formatSkillRecommendations,
|
|
338
304
|
generateInjectionContent,
|
|
339
305
|
generateSkillsSection,
|
|
@@ -359,7 +325,6 @@ export {
|
|
|
359
325
|
getHookSystemType,
|
|
360
326
|
getInstalledProviders,
|
|
361
327
|
getInstructionFiles,
|
|
362
|
-
getLastSelectedAgents,
|
|
363
328
|
getLockFilePath,
|
|
364
329
|
getMappedProviderIds,
|
|
365
330
|
getNestedValue,
|
|
@@ -383,31 +348,20 @@ export {
|
|
|
383
348
|
getSpawnCapableProviders,
|
|
384
349
|
getSupportedEvents,
|
|
385
350
|
getSystemInfo,
|
|
386
|
-
getTrackedMcpServers,
|
|
387
351
|
getTrackedSkills,
|
|
388
|
-
getTransform,
|
|
389
352
|
getUnsupportedEvents,
|
|
390
353
|
groupByInstructFile,
|
|
391
|
-
inferCleoLockData,
|
|
392
354
|
inject,
|
|
393
355
|
injectAll,
|
|
394
356
|
installBatchWithRollback,
|
|
395
|
-
installMcpServer,
|
|
396
|
-
installMcpServerToAll,
|
|
397
357
|
installSkill,
|
|
398
358
|
isCaampOwnedSkill,
|
|
399
|
-
isCleoSource,
|
|
400
359
|
isMarketplaceScoped,
|
|
401
360
|
isQuiet,
|
|
402
361
|
isVerbose,
|
|
403
|
-
listAgentsMcpServers,
|
|
404
|
-
listAllMcpServers,
|
|
405
362
|
listCanonicalSkills,
|
|
406
|
-
listMcpServers,
|
|
407
363
|
loadLibraryFromModule,
|
|
408
|
-
normalizeCleoChannel,
|
|
409
364
|
normalizeRecommendationCriteria,
|
|
410
|
-
parseEnvAssignments,
|
|
411
365
|
parseInjectionContent,
|
|
412
366
|
parseSkillFile,
|
|
413
367
|
parseSource,
|
|
@@ -415,28 +369,19 @@ export {
|
|
|
415
369
|
providerSupportsById,
|
|
416
370
|
rankSkills,
|
|
417
371
|
readConfig,
|
|
418
|
-
readLockFile,
|
|
419
372
|
recommendSkills,
|
|
420
|
-
reconcileCleoLock,
|
|
421
|
-
recordMcpInstall,
|
|
422
373
|
recordSkillInstall,
|
|
423
374
|
registerSkillLibrary,
|
|
424
375
|
registerSkillLibraryFromPath,
|
|
425
376
|
removeConfig,
|
|
426
377
|
removeInjection,
|
|
427
|
-
removeMcpFromLock,
|
|
428
|
-
removeMcpServer,
|
|
429
378
|
removeSkill,
|
|
430
379
|
removeSkillFromLock,
|
|
431
380
|
resetDetectionCache,
|
|
432
381
|
resolveAlias,
|
|
433
|
-
resolveChannelFromServerName,
|
|
434
|
-
resolveCleoServerName,
|
|
435
|
-
resolveConfigPath,
|
|
436
382
|
resolveNativeEvent,
|
|
437
383
|
resolveProviderSkillsDirs,
|
|
438
384
|
resolveRegistryTemplatePath,
|
|
439
|
-
saveLastSelectedAgents,
|
|
440
385
|
scanDirectory,
|
|
441
386
|
scanFile,
|
|
442
387
|
scoreSkillRecommendation,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/skills/integrity.ts"],"sourcesContent":["/**\n * Skill integrity checking\n *\n * Validates that installed skills have intact symlinks, correct canonical paths,\n * and enforces ct-* prefix priority for CAAMP-shipped skills.\n */\n\nimport { existsSync, lstatSync, readlinkSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { LockEntry, Provider } from '../../types.js';\nimport { readLockFile } from '../lock-utils.js';\nimport { getCanonicalSkillsDir, resolveProviderSkillsDirs } from '../paths/standard.js';\n\n/** CAAMP-reserved skill prefix. Skills with this prefix are owned by CAAMP. */\nconst CAAMP_SKILL_PREFIX = 'ct-';\n\n/**\n * Status of a single skill's integrity check.\n *\n * @public\n */\nexport type SkillIntegrityStatus =\n | 'intact'\n | 'broken-symlink'\n | 'missing-canonical'\n | 'missing-link'\n | 'not-tracked'\n | 'tampered';\n\n/**\n * Result of checking a single skill's integrity.\n *\n * @public\n */\nexport interface SkillIntegrityResult {\n /** Skill name. */\n name: string;\n /** Overall integrity status. */\n status: SkillIntegrityStatus;\n /** Whether the canonical directory exists. */\n canonicalExists: boolean;\n /** Expected canonical path from lock file. */\n canonicalPath: string | null;\n /** Provider link statuses — which agents have valid symlinks. */\n linkStatuses: Array<{\n providerId: string;\n linkPath: string;\n exists: boolean;\n isSymlink: boolean;\n pointsToCanonical: boolean;\n }>;\n /** Whether this is a CAAMP-reserved (ct-*) skill. */\n isCaampOwned: boolean;\n /** Human-readable issue description, if any. */\n issue?: string;\n}\n\n/**\n * Check whether a skill name is reserved by CAAMP (ct-* prefix).\n *\n * @remarks\n * Skills with the `ct-` prefix are considered CAAMP-owned and receive\n * special treatment during installation conflict resolution.\n *\n * @param skillName - Skill name to check\n * @returns `true` if the skill name starts with `ct-`\n *\n * @example\n * ```typescript\n * isCaampOwnedSkill(\"ct-research-agent\"); // true\n * isCaampOwnedSkill(\"my-custom-skill\"); // false\n * ```\n *\n * @public\n */\nexport function isCaampOwnedSkill(skillName: string): boolean {\n return skillName.startsWith(CAAMP_SKILL_PREFIX);\n}\n\n/**\n * Check the integrity of a single installed skill.\n *\n * @remarks\n * Validates that the canonical directory exists on disk, the lock file entry\n * matches the actual state, and symlinks from provider skill directories\n * point to the canonical path.\n *\n * @param skillName - Name of the skill to check\n * @param providers - Providers to check symlinks for\n * @param scope - Whether to check global or project links\n * @param projectDir - Project directory (for project scope)\n * @returns Integrity check result\n *\n * @example\n * ```typescript\n * const result = await checkSkillIntegrity(\"ct-research-agent\", providers, \"global\");\n * if (result.status !== \"intact\") {\n * console.log(`Issue: ${result.issue}`);\n * }\n * ```\n *\n * @public\n */\nexport async function checkSkillIntegrity(\n skillName: string,\n providers: Provider[],\n scope: 'global' | 'project' = 'global',\n projectDir?: string,\n): Promise<SkillIntegrityResult> {\n const lock = await readLockFile();\n const entry = lock.skills[skillName];\n const isCaampOwned = isCaampOwnedSkill(skillName);\n\n // Not tracked in lock file\n if (!entry) {\n const canonicalPath = join(getCanonicalSkillsDir(), skillName);\n return {\n name: skillName,\n status: 'not-tracked',\n canonicalExists: existsSync(canonicalPath),\n canonicalPath: null,\n linkStatuses: [],\n isCaampOwned,\n issue: 'Skill is not tracked in the CAAMP lock file',\n };\n }\n\n const canonicalPath = entry.canonicalPath;\n const canonicalExists = existsSync(canonicalPath);\n\n // Check symlinks for each provider\n const linkStatuses: SkillIntegrityResult['linkStatuses'] = [];\n\n for (const provider of providers) {\n const targetDirs = resolveProviderSkillsDirs(provider, scope, projectDir);\n for (const skillsDir of targetDirs) {\n if (!skillsDir) continue;\n\n const linkPath = join(skillsDir, skillName);\n const exists = existsSync(linkPath);\n let isSymlink = false;\n let pointsToCanonical = false;\n\n if (exists) {\n try {\n const stat = lstatSync(linkPath);\n isSymlink = stat.isSymbolicLink();\n if (isSymlink) {\n const target = resolve(readlinkSync(linkPath));\n pointsToCanonical = target === resolve(canonicalPath);\n }\n } catch {\n // Can't stat — treat as broken\n }\n }\n\n linkStatuses.push({\n providerId: provider.id,\n linkPath,\n exists,\n isSymlink,\n pointsToCanonical,\n });\n }\n }\n\n // Determine overall status\n if (!canonicalExists) {\n return {\n name: skillName,\n status: 'missing-canonical',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n issue: `Canonical directory missing: ${canonicalPath}`,\n };\n }\n\n const brokenLinks = linkStatuses.filter((l) => !l.exists);\n const tamperedLinks = linkStatuses.filter((l) => l.exists && !l.pointsToCanonical);\n\n if (tamperedLinks.length > 0) {\n return {\n name: skillName,\n status: 'tampered',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n issue: `${tamperedLinks.length} link(s) do not point to canonical path`,\n };\n }\n\n if (brokenLinks.length > 0) {\n return {\n name: skillName,\n status: 'broken-symlink',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n issue: `${brokenLinks.length} symlink(s) missing`,\n };\n }\n\n return {\n name: skillName,\n status: 'intact',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n };\n}\n\n/**\n * Check integrity of all tracked skills.\n *\n * @remarks\n * Iterates over every skill in the lock file and runs\n * {@link checkSkillIntegrity} on each.\n *\n * @param providers - Providers to check symlinks for\n * @param scope - Whether to check global or project links\n * @param projectDir - Project directory (for project scope)\n * @returns Map of skill name to integrity result\n *\n * @example\n * ```typescript\n * const results = await checkAllSkillIntegrity(providers);\n * for (const [name, result] of results) {\n * console.log(`${name}: ${result.status}`);\n * }\n * ```\n *\n * @public\n */\nexport async function checkAllSkillIntegrity(\n providers: Provider[],\n scope: 'global' | 'project' = 'global',\n projectDir?: string,\n): Promise<Map<string, SkillIntegrityResult>> {\n const lock = await readLockFile();\n const results = new Map<string, SkillIntegrityResult>();\n\n for (const skillName of Object.keys(lock.skills)) {\n const result = await checkSkillIntegrity(skillName, providers, scope, projectDir);\n results.set(skillName, result);\n }\n\n return results;\n}\n\n/**\n * Resolve a skill name conflict where a user-installed skill collides\n * with a CAAMP-owned (ct-*) skill.\n *\n * @remarks\n * CAAMP-owned skills always win. Returns `true` if the incoming skill\n * should take precedence over the existing installation.\n *\n * @param skillName - Skill name to check\n * @param incomingSource - Source of the incoming skill installation\n * @param existingEntry - Existing lock entry, if any\n * @returns `true` if the incoming installation should proceed\n *\n * @example\n * ```typescript\n * const proceed = shouldOverrideSkill(\"ct-research-agent\", \"library\", existingEntry);\n * if (proceed) {\n * // Safe to install/override\n * }\n * ```\n *\n * @public\n */\nexport function shouldOverrideSkill(\n skillName: string,\n incomingSource: string,\n existingEntry: LockEntry | undefined,\n): boolean {\n // No existing entry — always allow\n if (!existingEntry) return true;\n\n // For ct-* skills, CAAMP package source always wins\n if (isCaampOwnedSkill(skillName)) {\n // If incoming is from CAAMP package (library source), it always wins\n if (existingEntry.sourceType === 'library') return true;\n // If existing is from CAAMP but incoming is user, CAAMP wins (block user)\n return true;\n }\n\n // Non-ct-* skills: user always wins\n return true;\n}\n\n/**\n * Validate instruction file injection status across all providers.\n *\n * @remarks\n * Checks that CAAMP blocks exist and are current in all relevant\n * instruction files (CLAUDE.md, AGENTS.md, GEMINI.md).\n *\n * @param providers - Providers to check\n * @param projectDir - Project directory\n * @param scope - Whether to check global or project files\n * @param expectedContent - Expected CAAMP block content\n * @returns Array of file paths with issues\n *\n * @example\n * ```typescript\n * const issues = await validateInstructionIntegrity(providers, process.cwd(), \"project\");\n * for (const issue of issues) {\n * console.log(`${issue.providerId}: ${issue.issue} (${issue.file})`);\n * }\n * ```\n *\n * @public\n */\nexport async function validateInstructionIntegrity(\n providers: Provider[],\n projectDir: string,\n scope: 'project' | 'global',\n expectedContent?: string,\n): Promise<Array<{ file: string; providerId: string; issue: string }>> {\n const { checkAllInjections } = await import('../instructions/injector.js');\n const results = await checkAllInjections(providers, projectDir, scope, expectedContent);\n const issues: Array<{ file: string; providerId: string; issue: string }> = [];\n\n for (const result of results) {\n if (result.status === 'missing') {\n issues.push({\n file: result.file,\n providerId: result.provider,\n issue: 'Instruction file does not exist',\n });\n } else if (result.status === 'none') {\n issues.push({\n file: result.file,\n providerId: result.provider,\n issue: 'No CAAMP injection block found',\n });\n } else if (result.status === 'outdated') {\n issues.push({\n file: result.file,\n providerId: result.provider,\n issue: 'CAAMP injection block is outdated',\n });\n }\n }\n\n return issues;\n}\n"],"mappings":"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","names":["canonicalPath","checkAllInjections"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/skills/integrity.ts"],"sourcesContent":["/**\n * Skill integrity checking\n *\n * Validates that installed skills have intact symlinks, correct canonical paths,\n * and enforces ct-* prefix priority for CAAMP-shipped skills.\n */\n\nimport { existsSync, lstatSync, readlinkSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { LockEntry, Provider } from '../../types.js';\nimport { readLockFile } from '../lock-utils.js';\nimport { getCanonicalSkillsDir, resolveProviderSkillsDirs } from '../paths/standard.js';\n\n/** CAAMP-reserved skill prefix. Skills with this prefix are owned by CAAMP. */\nconst CAAMP_SKILL_PREFIX = 'ct-';\n\n/**\n * Status of a single skill's integrity check.\n *\n * @public\n */\nexport type SkillIntegrityStatus =\n | 'intact'\n | 'broken-symlink'\n | 'missing-canonical'\n | 'missing-link'\n | 'not-tracked'\n | 'tampered';\n\n/**\n * Result of checking a single skill's integrity.\n *\n * @public\n */\nexport interface SkillIntegrityResult {\n /** Skill name. */\n name: string;\n /** Overall integrity status. */\n status: SkillIntegrityStatus;\n /** Whether the canonical directory exists. */\n canonicalExists: boolean;\n /** Expected canonical path from lock file. */\n canonicalPath: string | null;\n /** Provider link statuses — which agents have valid symlinks. */\n linkStatuses: Array<{\n providerId: string;\n linkPath: string;\n exists: boolean;\n isSymlink: boolean;\n pointsToCanonical: boolean;\n }>;\n /** Whether this is a CAAMP-reserved (ct-*) skill. */\n isCaampOwned: boolean;\n /** Human-readable issue description, if any. */\n issue?: string;\n}\n\n/**\n * Check whether a skill name is reserved by CAAMP (ct-* prefix).\n *\n * @remarks\n * Skills with the `ct-` prefix are considered CAAMP-owned and receive\n * special treatment during installation conflict resolution.\n *\n * @param skillName - Skill name to check\n * @returns `true` if the skill name starts with `ct-`\n *\n * @example\n * ```typescript\n * isCaampOwnedSkill(\"ct-research-agent\"); // true\n * isCaampOwnedSkill(\"my-custom-skill\"); // false\n * ```\n *\n * @public\n */\nexport function isCaampOwnedSkill(skillName: string): boolean {\n return skillName.startsWith(CAAMP_SKILL_PREFIX);\n}\n\n/**\n * Check the integrity of a single installed skill.\n *\n * @remarks\n * Validates that the canonical directory exists on disk, the lock file entry\n * matches the actual state, and symlinks from provider skill directories\n * point to the canonical path.\n *\n * @param skillName - Name of the skill to check\n * @param providers - Providers to check symlinks for\n * @param scope - Whether to check global or project links\n * @param projectDir - Project directory (for project scope)\n * @returns Integrity check result\n *\n * @example\n * ```typescript\n * const result = await checkSkillIntegrity(\"ct-research-agent\", providers, \"global\");\n * if (result.status !== \"intact\") {\n * console.log(`Issue: ${result.issue}`);\n * }\n * ```\n *\n * @public\n */\nexport async function checkSkillIntegrity(\n skillName: string,\n providers: Provider[],\n scope: 'global' | 'project' = 'global',\n projectDir?: string,\n): Promise<SkillIntegrityResult> {\n const lock = await readLockFile();\n const entry = lock.skills[skillName];\n const isCaampOwned = isCaampOwnedSkill(skillName);\n\n // Not tracked in lock file\n if (!entry) {\n const canonicalPath = join(getCanonicalSkillsDir(), skillName);\n return {\n name: skillName,\n status: 'not-tracked',\n canonicalExists: existsSync(canonicalPath),\n canonicalPath: null,\n linkStatuses: [],\n isCaampOwned,\n issue: 'Skill is not tracked in the CAAMP lock file',\n };\n }\n\n const canonicalPath = entry.canonicalPath;\n const canonicalExists = existsSync(canonicalPath);\n\n // Check symlinks for each provider\n const linkStatuses: SkillIntegrityResult['linkStatuses'] = [];\n\n for (const provider of providers) {\n const targetDirs = resolveProviderSkillsDirs(provider, scope, projectDir);\n for (const skillsDir of targetDirs) {\n if (!skillsDir) continue;\n\n const linkPath = join(skillsDir, skillName);\n const exists = existsSync(linkPath);\n let isSymlink = false;\n let pointsToCanonical = false;\n\n if (exists) {\n try {\n const stat = lstatSync(linkPath);\n isSymlink = stat.isSymbolicLink();\n if (isSymlink) {\n const target = resolve(readlinkSync(linkPath));\n pointsToCanonical = target === resolve(canonicalPath);\n }\n } catch {\n // Can't stat — treat as broken\n }\n }\n\n linkStatuses.push({\n providerId: provider.id,\n linkPath,\n exists,\n isSymlink,\n pointsToCanonical,\n });\n }\n }\n\n // Determine overall status\n if (!canonicalExists) {\n return {\n name: skillName,\n status: 'missing-canonical',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n issue: `Canonical directory missing: ${canonicalPath}`,\n };\n }\n\n const brokenLinks = linkStatuses.filter((l) => !l.exists);\n const tamperedLinks = linkStatuses.filter((l) => l.exists && !l.pointsToCanonical);\n\n if (tamperedLinks.length > 0) {\n return {\n name: skillName,\n status: 'tampered',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n issue: `${tamperedLinks.length} link(s) do not point to canonical path`,\n };\n }\n\n if (brokenLinks.length > 0) {\n return {\n name: skillName,\n status: 'broken-symlink',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n issue: `${brokenLinks.length} symlink(s) missing`,\n };\n }\n\n return {\n name: skillName,\n status: 'intact',\n canonicalExists,\n canonicalPath,\n linkStatuses,\n isCaampOwned,\n };\n}\n\n/**\n * Check integrity of all tracked skills.\n *\n * @remarks\n * Iterates over every skill in the lock file and runs\n * {@link checkSkillIntegrity} on each.\n *\n * @param providers - Providers to check symlinks for\n * @param scope - Whether to check global or project links\n * @param projectDir - Project directory (for project scope)\n * @returns Map of skill name to integrity result\n *\n * @example\n * ```typescript\n * const results = await checkAllSkillIntegrity(providers);\n * for (const [name, result] of results) {\n * console.log(`${name}: ${result.status}`);\n * }\n * ```\n *\n * @public\n */\nexport async function checkAllSkillIntegrity(\n providers: Provider[],\n scope: 'global' | 'project' = 'global',\n projectDir?: string,\n): Promise<Map<string, SkillIntegrityResult>> {\n const lock = await readLockFile();\n const results = new Map<string, SkillIntegrityResult>();\n\n for (const skillName of Object.keys(lock.skills)) {\n const result = await checkSkillIntegrity(skillName, providers, scope, projectDir);\n results.set(skillName, result);\n }\n\n return results;\n}\n\n/**\n * Resolve a skill name conflict where a user-installed skill collides\n * with a CAAMP-owned (ct-*) skill.\n *\n * @remarks\n * CAAMP-owned skills always win. Returns `true` if the incoming skill\n * should take precedence over the existing installation.\n *\n * @param skillName - Skill name to check\n * @param incomingSource - Source of the incoming skill installation\n * @param existingEntry - Existing lock entry, if any\n * @returns `true` if the incoming installation should proceed\n *\n * @example\n * ```typescript\n * const proceed = shouldOverrideSkill(\"ct-research-agent\", \"library\", existingEntry);\n * if (proceed) {\n * // Safe to install/override\n * }\n * ```\n *\n * @public\n */\nexport function shouldOverrideSkill(\n skillName: string,\n incomingSource: string,\n existingEntry: LockEntry | undefined,\n): boolean {\n // No existing entry — always allow\n if (!existingEntry) return true;\n\n // For ct-* skills, CAAMP package source always wins\n if (isCaampOwnedSkill(skillName)) {\n // If incoming is from CAAMP package (library source), it always wins\n if (existingEntry.sourceType === 'library') return true;\n // If existing is from CAAMP but incoming is user, CAAMP wins (block user)\n return true;\n }\n\n // Non-ct-* skills: user always wins\n return true;\n}\n\n/**\n * Validate instruction file injection status across all providers.\n *\n * @remarks\n * Checks that CAAMP blocks exist and are current in all relevant\n * instruction files (CLAUDE.md, AGENTS.md, GEMINI.md).\n *\n * @param providers - Providers to check\n * @param projectDir - Project directory\n * @param scope - Whether to check global or project files\n * @param expectedContent - Expected CAAMP block content\n * @returns Array of file paths with issues\n *\n * @example\n * ```typescript\n * const issues = await validateInstructionIntegrity(providers, process.cwd(), \"project\");\n * for (const issue of issues) {\n * console.log(`${issue.providerId}: ${issue.issue} (${issue.file})`);\n * }\n * ```\n *\n * @public\n */\nexport async function validateInstructionIntegrity(\n providers: Provider[],\n projectDir: string,\n scope: 'project' | 'global',\n expectedContent?: string,\n): Promise<Array<{ file: string; providerId: string; issue: string }>> {\n const { checkAllInjections } = await import('../instructions/injector.js');\n const results = await checkAllInjections(providers, projectDir, scope, expectedContent);\n const issues: Array<{ file: string; providerId: string; issue: string }> = [];\n\n for (const result of results) {\n if (result.status === 'missing') {\n issues.push({\n file: result.file,\n providerId: result.provider,\n issue: 'Instruction file does not exist',\n });\n } else if (result.status === 'none') {\n issues.push({\n file: result.file,\n providerId: result.provider,\n issue: 'No CAAMP injection block found',\n });\n } else if (result.status === 'outdated') {\n issues.push({\n file: result.file,\n providerId: result.provider,\n issue: 'CAAMP injection block is outdated',\n });\n }\n }\n\n return issues;\n}\n"],"mappings":"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","names":["canonicalPath","checkAllInjections"]}
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
inject,
|
|
7
7
|
injectAll,
|
|
8
8
|
removeInjection
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-CRU25LRL.js";
|
|
10
|
+
import "./chunk-XWQ5WPHC.js";
|
|
11
11
|
export {
|
|
12
12
|
checkAllInjections,
|
|
13
13
|
checkInjection,
|
|
@@ -17,4 +17,4 @@ export {
|
|
|
17
17
|
injectAll,
|
|
18
18
|
removeInjection
|
|
19
19
|
};
|
|
20
|
-
//# sourceMappingURL=injector-
|
|
20
|
+
//# sourceMappingURL=injector-ALLOKC54.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/caamp",
|
|
3
|
-
"version": "2026.4.
|
|
3
|
+
"version": "2026.4.3",
|
|
4
4
|
"description": "Central AI Agent Managed Packages - unified provider registry and package manager for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"jsonc-parser": "^3.3.1",
|
|
53
53
|
"picocolors": "^1.1.1",
|
|
54
54
|
"simple-git": "3.33.0",
|
|
55
|
-
"@cleocode/lafs": "2026.4.
|
|
55
|
+
"@cleocode/lafs": "2026.4.3"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@biomejs/biome": "2.4.8",
|