@cleocode/caamp 2026.5.84 → 2026.5.87

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/index.d.ts CHANGED
@@ -1,7 +1,339 @@
1
+ import { Command } from 'commander';
1
2
  import { WorktreeHandle } from '@cleocode/cant';
2
3
  import { PlatformPaths, SystemInfo } from '@cleocode/paths';
3
4
  export { PlatformPaths, SystemInfo } from '@cleocode/paths';
4
5
 
6
+ /**
7
+ * `skills doctor adopt-orphans` — interactive orphan audit + adoption.
8
+ *
9
+ * @remarks
10
+ * An "orphan" is a skill directory that exists under any tracked path
11
+ * (`~/.cleo/skills/`, legacy `~/.local/share/agents/skills/`,
12
+ * `~/.agents/skills/` as a real dir) but has NO row in the per-user
13
+ * `skills.db` registry described in
14
+ * `docs/architecture/SG-CLEO-SKILLS-architecture-v3.md` §4.
15
+ *
16
+ * The handler offers four per-orphan dispositions:
17
+ *
18
+ * - **canonical-adopt** — REFUSED on a user machine. Canonical writes
19
+ * must flow via PR to `packages/skills/skills/` (architecture-v3 §6
20
+ * invariant). The handler emits a refusal explaining the PR flow.
21
+ * - **user-adopt** — inserts a row into `skills.db` with
22
+ * `source_type='user'`, `lifecycle_state='active'`, `installedAt=now`.
23
+ * - **delete** — archives the directory to
24
+ * `~/.cleo/skills/.archive/<name>-<ts>/` before unlinking from the
25
+ * original location.
26
+ * - **skip** — no action; the orphan is logged but otherwise ignored.
27
+ *
28
+ * All decisions are recorded to a structured JSON audit log at
29
+ * `~/.cleo/skills/.audit-log/adopt-<ISO-ts>.json` regardless of mode.
30
+ *
31
+ * ## Chokepoint compliance (ADR-068)
32
+ *
33
+ * This module emits PURE DATA. The skills.db reads and writes are deferred
34
+ * to caller-supplied callbacks (`loadRegisteredNames`, `recordRow`). The
35
+ * `cleo` dispatch layer in `packages/cleo/src/cli/commands/skills.ts` plugs
36
+ * the canonical `openCleoDb('skills')`/`upsertSkillRow` helpers from
37
+ * `@cleocode/core/store/skills-db`. caamp cannot depend on `@cleocode/core`
38
+ * directly (would invert the dep direction: core dynamically imports caamp).
39
+ *
40
+ * Three execution modes:
41
+ *
42
+ * - default (TTY) — interactive prompt per orphan.
43
+ * - `--non-interactive` — list orphans and exit without action
44
+ * (read-only audit).
45
+ * - `--auto-user-adopt` — bulk user-adopt all orphans without prompting
46
+ * (safe default for `cleo-init`-style scripts).
47
+ *
48
+ * @task T9657
49
+ * @epic T9571
50
+ * @saga T9560
51
+ * @architecture docs/architecture/SG-CLEO-SKILLS-architecture-v3.md §1, §6
52
+ */
53
+
54
+ /**
55
+ * One orphan disposition decision.
56
+ *
57
+ * @public
58
+ */
59
+ type OrphanDecision = 'canonical-adopt' | 'user-adopt' | 'delete' | 'skip';
60
+ /**
61
+ * Reason an action was refused (canonical-adopt on user machine, etc.).
62
+ *
63
+ * @public
64
+ */
65
+ interface OrphanRefusal {
66
+ /** Stable code for programmatic handling. */
67
+ code: 'E_CANONICAL_ADOPT_REFUSED';
68
+ /** Human-readable explanation. */
69
+ message: string;
70
+ /** Suggested next step (e.g. PR flow). */
71
+ remediation: string;
72
+ }
73
+ /**
74
+ * A single orphan skill directory discovered on disk.
75
+ *
76
+ * @public
77
+ */
78
+ interface OrphanRecord {
79
+ /** Skill name (basename of the orphan directory). */
80
+ name: string;
81
+ /** Absolute path to the orphan directory on disk. */
82
+ path: string;
83
+ /** Which tracked root this orphan was discovered under. */
84
+ discoveredVia: 'cleo' | 'legacy-agents' | 'home-agents';
85
+ /** Whether a `SKILL.md` sentinel exists at the root. */
86
+ hasSkillMd: boolean;
87
+ /** Size of the directory in bytes (best-effort; 0 on stat failure). */
88
+ sizeBytes: number;
89
+ }
90
+ /**
91
+ * Outcome of acting on a single orphan.
92
+ *
93
+ * @public
94
+ */
95
+ interface OrphanActionResult {
96
+ /** The orphan that was acted upon. */
97
+ orphan: OrphanRecord;
98
+ /** Decision the user (or flag) made. */
99
+ decision: OrphanDecision;
100
+ /** Whether the action completed successfully. */
101
+ applied: boolean;
102
+ /** Refusal payload when `applied=false` due to a policy block. */
103
+ refusal: OrphanRefusal | null;
104
+ /** Where the directory was archived to (delete only). */
105
+ archivedTo: string | null;
106
+ /** ISO-8601 timestamp when the action was taken. */
107
+ decidedAt: string;
108
+ }
109
+ /**
110
+ * Top-level result returned in the LAFS envelope.
111
+ *
112
+ * @public
113
+ */
114
+ interface DoctorAdoptResult {
115
+ /** Total orphans discovered. */
116
+ totalOrphans: number;
117
+ /** Per-orphan action results. */
118
+ results: OrphanActionResult[];
119
+ /** Audit log file path (always written). */
120
+ auditLogPath: string;
121
+ /** Execution mode used. */
122
+ mode: 'interactive' | 'non-interactive' | 'auto-user-adopt';
123
+ }
124
+ /**
125
+ * Pure-data payload emitted when a `user-adopt` decision is applied.
126
+ *
127
+ * @remarks
128
+ * The dispatch layer translates this into an `upsertSkillRow` call via the
129
+ * `openCleoDb('skills')` chokepoint. Keeping it as pure data means caamp
130
+ * never has to touch sqlite directly.
131
+ *
132
+ * @public
133
+ */
134
+ interface AdoptedSkillRowData {
135
+ /** Skill name (PK in skills.db). */
136
+ name: string;
137
+ /** Absolute install path on disk. */
138
+ installPath: string;
139
+ /** Wall-clock timestamp the adoption occurred. */
140
+ installedAt: string;
141
+ /** Always `'user'` for the adopt-orphans flow (canonical is refused). */
142
+ sourceType: 'user';
143
+ /** Always `'active'` post-adoption. */
144
+ lifecycleState: 'active';
145
+ }
146
+ /**
147
+ * Discover orphan skills across all tracked roots.
148
+ *
149
+ * @remarks
150
+ * Visits the three tracked roots in priority order and de-duplicates by
151
+ * basename — the first occurrence of `<name>` wins (so a `~/.cleo/skills/`
152
+ * entry shadows the same-named legacy entry). Symlinks under
153
+ * `~/.agents/skills/` that resolve back into `~/.cleo/skills/` are dropped
154
+ * because those are the bridge symlinks, not real orphans.
155
+ *
156
+ * Read-side IO (the set of names already known to `skills.db`) is supplied
157
+ * via the `registeredNames` callback so this module never opens sqlite
158
+ * directly — see ADR-068 chokepoint compliance in the file header.
159
+ *
160
+ * @param registeredNames - Pre-computed set of skill names known to the
161
+ * registry. Callers in production wire this through `openCleoDb('skills')`;
162
+ * tests wire it through a sandboxed `DatabaseSync` open.
163
+ * @returns Sorted-by-name list of `OrphanRecord`s.
164
+ *
165
+ * @public
166
+ */
167
+ declare function discoverOrphans(registeredNames: ReadonlySet<string>): OrphanRecord[];
168
+ /**
169
+ * Callback signature for persisting a `user-adopt` decision.
170
+ *
171
+ * @remarks
172
+ * Production wiring lives in `packages/cleo/src/cli/commands/skills.ts` and
173
+ * funnels into `upsertSkillRow` from `@cleocode/core/store/skills-db`,
174
+ * which routes through the canonical `openCleoDb('skills')` chokepoint.
175
+ * Test code passes a sandboxed sqlite write. May be synchronous or async.
176
+ *
177
+ * @public
178
+ */
179
+ type RecordRowFn = (data: AdoptedSkillRowData) => void | Promise<void>;
180
+ /**
181
+ * Apply a single decision to an orphan, returning a structured outcome.
182
+ *
183
+ * @remarks
184
+ * This is the policy chokepoint — `canonical-adopt` is unconditionally
185
+ * refused, `user-adopt` invokes `recordRow` with the canonical
186
+ * {@link AdoptedSkillRowData} payload, `delete` archives-then-rms, and
187
+ * `skip` records the intent without side effects. Errors during
188
+ * `user-adopt` or `delete` produce an `applied=false` result with a
189
+ * synthesised refusal payload rather than throwing, so the bulk loop can
190
+ * proceed across all orphans.
191
+ *
192
+ * @param orphan - Orphan to act on.
193
+ * @param decision - Decision to apply.
194
+ * @param now - ISO-8601 timestamp to record on the result.
195
+ * @param recordRow - Callback invoked to persist a successful `user-adopt`.
196
+ * Tests pass a sandbox write; production passes the cleo-dispatch wrapper.
197
+ * @returns A populated `OrphanActionResult`.
198
+ *
199
+ * @public
200
+ */
201
+ declare function applyDecision(orphan: OrphanRecord, decision: OrphanDecision, now: string, recordRow: RecordRowFn): Promise<OrphanActionResult>;
202
+ /**
203
+ * Write the audit log to `~/.cleo/skills/.audit-log/adopt-<ts>.json`.
204
+ *
205
+ * @remarks
206
+ * The log is written atomically (tmp-then-rename) so a SIGINT mid-write
207
+ * cannot leave a half-written file. The payload is a structured object
208
+ * containing the run timestamp, mode, full per-orphan results, and a
209
+ * stable `runId` UUID for cross-referencing in other CLEO audit streams
210
+ * (e.g. release-ship logs).
211
+ *
212
+ * @param result - The doctor-adopt result to persist.
213
+ * @returns Absolute path the audit log was written to.
214
+ *
215
+ * @public
216
+ */
217
+ declare function writeAuditLog(result: DoctorAdoptResult): string;
218
+ /**
219
+ * Options controlling a `runDoctorAdopt` invocation.
220
+ *
221
+ * @public
222
+ */
223
+ interface DoctorAdoptOptions {
224
+ /** Skip prompting and write nothing — list-only audit mode. */
225
+ nonInteractive?: boolean;
226
+ /** Skip prompting and bulk-adopt every orphan as `source_type='user'`. */
227
+ autoUserAdopt?: boolean;
228
+ /**
229
+ * Loads the set of skill names already known to the registry.
230
+ *
231
+ * Production wiring opens `skills.db` via `openCleoDb('skills')`; tests
232
+ * inject a sandbox-scoped reader.
233
+ */
234
+ loadRegisteredNames: () => ReadonlySet<string> | Promise<ReadonlySet<string>>;
235
+ /**
236
+ * Persists a single `user-adopt` decision to `skills.db`.
237
+ *
238
+ * Production wiring calls `upsertSkillRow` from `@cleocode/core/store`;
239
+ * tests inject a sandbox writer.
240
+ */
241
+ recordRow: RecordRowFn;
242
+ /** Test-only injection of a readline-compatible prompt. */
243
+ prompt?: (orphan: OrphanRecord) => Promise<OrphanDecision>;
244
+ /** Test-only opt-out of writing the audit log to disk. */
245
+ skipAuditLog?: boolean;
246
+ /** Test-only override for the discovery step. */
247
+ discoverFn?: (registeredNames: ReadonlySet<string>) => OrphanRecord[];
248
+ }
249
+ /**
250
+ * Execute the doctor-adopt workflow and return a structured result.
251
+ *
252
+ * @remarks
253
+ * Designed for both CLI invocation (from {@link registerSkillsDoctorAdopt})
254
+ * and direct testing — every side effect is overridable via
255
+ * {@link DoctorAdoptOptions}. The function never throws on per-orphan
256
+ * failures; instead each failure produces an `applied=false` entry with a
257
+ * refusal payload, so the caller gets a complete report even on partial
258
+ * failure.
259
+ *
260
+ * @param options - Mode flags + dependency-injected callbacks. The
261
+ * `loadRegisteredNames` and `recordRow` callbacks are MANDATORY so the
262
+ * caller (cleo dispatch or test harness) owns the sqlite open via the
263
+ * chokepoint.
264
+ * @returns The populated `DoctorAdoptResult`.
265
+ *
266
+ * @public
267
+ */
268
+ declare function runDoctorAdopt(options: DoctorAdoptOptions): Promise<DoctorAdoptResult>;
269
+ /**
270
+ * Default skill-name loader bound at CLI dispatch time.
271
+ *
272
+ * @remarks
273
+ * Re-exported so the cleo dispatch layer can construct it once and inject
274
+ * the same instance into {@link runDoctorAdopt}.
275
+ *
276
+ * @public
277
+ */
278
+ type RegisteredNamesLoader = () => ReadonlySet<string> | Promise<ReadonlySet<string>>;
279
+ /**
280
+ * Adapter callbacks the CLI registrar needs to satisfy
281
+ * {@link DoctorAdoptOptions}'s mandatory deps.
282
+ *
283
+ * @remarks
284
+ * The caamp CLI (`caamp skills doctor adopt-orphans`) supplies a no-op pair
285
+ * — caamp is the registry-author-tool and never touches a live skills.db.
286
+ * The cleo CLI overrides both with chokepoint-routed implementations.
287
+ *
288
+ * @public
289
+ */
290
+ interface DoctorAdoptCliAdapters {
291
+ loadRegisteredNames: RegisteredNamesLoader;
292
+ recordRow: RecordRowFn;
293
+ }
294
+ /**
295
+ * Standalone-`caamp` defaults — no DB access, every directory is an orphan.
296
+ *
297
+ * @remarks
298
+ * In the standalone caamp CLI, there's no skills.db (caamp is the
299
+ * registry-author tool, not the user-runtime). We treat every directory as
300
+ * an orphan and refuse all user-adopt writes by surfacing an explanatory
301
+ * error through `recordRow`. The cleo CLI overrides this with the real
302
+ * chokepoint-routed adapters.
303
+ *
304
+ * @public
305
+ */
306
+ declare const caampStandaloneAdapters: DoctorAdoptCliAdapters;
307
+ /**
308
+ * Register the `skills doctor adopt-orphans` subcommand on the parent
309
+ * `skills` Commander group.
310
+ *
311
+ * @remarks
312
+ * The handler creates the `doctor` subgroup if it doesn't already exist on
313
+ * the parent, so registration order is tolerant of co-registration with
314
+ * `registerSkillsDoctor` (T9655 bridge). Adapters default to
315
+ * {@link caampStandaloneAdapters} which surface a clear error — pass the
316
+ * cleo-chokepoint adapters when wiring under `packages/cleo/`.
317
+ *
318
+ * Default output is LAFS JSON. Pass `--human` for the colorised summary.
319
+ *
320
+ * @param parent - The parent `skills` Command from
321
+ * {@link registerSkillsCommands}.
322
+ * @param adapters - DB read/write hooks. Defaults to
323
+ * {@link caampStandaloneAdapters} so the standalone caamp CLI surfaces a
324
+ * helpful error rather than silently no-op'ing.
325
+ *
326
+ * @example
327
+ * ```bash
328
+ * cleo skills doctor adopt-orphans # interactive
329
+ * cleo skills doctor adopt-orphans --non-interactive
330
+ * cleo skills doctor adopt-orphans --auto-user-adopt --json
331
+ * ```
332
+ *
333
+ * @public
334
+ */
335
+ declare function registerSkillsDoctorAdopt(parent: Command, adapters?: DoctorAdoptCliAdapters): void;
336
+
5
337
  /**
6
338
  * `cleo skills doctor bridge` — single bridge symlink + per-skill symlink removal.
7
339
  *
@@ -8720,4 +9052,4 @@ declare function parseSource(input: string): ParsedSource;
8720
9052
  */
8721
9053
  declare function isMarketplaceScoped(input: string): boolean;
8722
9054
 
8723
- export { AgentsSkillsRealDirError, type AuditFinding, type AuditResult, type AuditRule, type AuditSeverity, type BatchInstallOptions, type BatchInstallResult, type BridgeSymlinkRecord, CANONICAL_HOOK_EVENTS, type CaampBlock, type CaampLockFile, type CanonicalEventDefinition, type CanonicalHookEvent, type CantProfileCounts, type CantProfileEntry, type CantValidationDiagnostic, type ConfigFormat, type CrossProviderMatrix, type CtDispatchMatrix, type CtManifest, type CtManifestSkill, type CtProfileDefinition, type CtSkillEntry, type CtValidationIssue, type CtValidationResult, DEFAULT_EXCLUSIVITY_MODE, type DedupeResult, type DetectionCacheOptions, type DetectionResult, type DoctorBridgeOptions, type DoctorBridgeResult, EXCLUSIVITY_MODE_ENV_VAR, type EnsureProviderInstructionFileOptions, type EnsureProviderInstructionFileResult, type ExclusivityMode, type GlobalOptions, HOOK_CATEGORIES, type Harness, type HarnessScope, type HookCategory, type HookEvent, type HookHandlerType, type HookMapping, type HookSupportResult, type HookSystemType, type InjectionCheckResult, type InjectionStatus, type InjectionTemplate, type InstallMcpServerOptions, type InstallMcpServerResult, type InstallSkillOptions, type InstructionUpdateSummary, type KnownProviderAgentFolderId, type LockEntry, MarketplaceClient, type MarketplaceResult, type MarketplaceSearchResult, type MarketplaceSkill, type McpConfigFormat, type McpDetectionEntry, type McpScope, type McpServerConfig, type McpServerEntriesByProvider, type McpServerEntry, type McpTransportType, type NormalizedHookEvent, type NormalizedRecommendationCriteria, type ParsedSource, type PerSkillSymlinkRemoval, PiHarness, PiRequiredError, type Provider, type ProviderCapabilities, type ProviderHarnessCapability, type ProviderHookProfile, type ProviderHookSummary, type ProviderHooksCapability, type ProviderMcpCapability, type ProviderPriority, type ProviderSkillsCapability, type ProviderSpawnCapability, type ProviderStatus, RECOMMENDATION_ERROR_CODES, type RankedSkillRecommendation, type RecommendSkillsResult, type RecommendationCriteriaInput, type RecommendationErrorCode, type RecommendationOptions, type RecommendationReason, type RecommendationReasonCode, type RecommendationScoreBreakdown, type RecommendationValidationIssue, type RecommendationValidationResult, type RecommendationWeights, type RegistryHarnessKind, type RegistryHookCatalog, type RegistryHookFormat, type RemoveMcpServerOptions, type RemoveMcpServerResult, type ResolveDefaultTargetProvidersOptions, type SkillBatchOperation, type SkillEntry, type SkillInstallResult, type SkillIntegrityResult, type SkillIntegrityStatus, type SkillLibrary, type SkillLibraryDispatchMatrix, type SkillLibraryEntry, type SkillLibraryManifest, type SkillLibraryManifestSkill, type SkillLibraryProfile, type SkillLibraryValidationIssue, type SkillLibraryValidationResult, type SkillMetadata, type SkillRowData, type SkillRowSourceType, type SkillsPrecedence, type SourceType, type SpawnAdapter, type SpawnMechanism, type SpawnOptions, type SpawnResult, type SubagentHandle, type SubagentResult, type SubagentTask, type TransportType, type ValidateCantProfileResult, type ValidationIssue, type ValidationResult, type WriteAgentFileOptions, type WriteAgentFileResult, _resetLegacySkillsWarning, _resetPlatformPathsCache, buildBackupTimestamp, buildHookMatrix, buildInjectionContent, buildLibraryFromFiles, buildSkillsMap, catalog, checkAllInjections, checkAllSkillIntegrity, checkAllSkillUpdates, checkInjection, checkSkillIntegrity, checkSkillUpdate, clearRegisteredLibrary, dedupeFile, dedupeFiles, deepMerge, detectAllProviders, detectMcpInstallations, detectProjectProviders, detectProvider, discoverSkill, discoverSkills, ensureAllProviderInstructionFiles, ensureDir, ensureProviderInstructionFile, formatSkillRecommendations, generateInjectionContent, generateSkillsSection, getAgentsConfigPath, getAgentsHome, getAgentsInstructFile, getAgentsLinksDir, getAgentsMcpDir, getAgentsMcpServersPath, getAgentsSpecDir, getAgentsWikiDir, getAllCanonicalEvents, getAllHarnesses, getAllProviders, getCanonicalEvent, getCanonicalEventsByCategory, getCanonicalSkillsDir, getCanonicalSkillsRoot, getCommonEvents, getCommonHookEvents, getEffectiveSkillsPaths, getExclusivityMode, getHarnessFor, getHookConfigPath, getHookMappingsVersion, getHookSupport, getHookSystemType, getInstalledProviders, getInstructionFiles, getLockFilePath, getMappedProviderIds, getNestedValue, getPlatformLocations, getPlatformPaths, getPrimaryHarness, getPrimaryProvider, getProjectAgentsDir, getProvider, getProviderAgentFolder, getProviderCapabilities, getProviderCount, getProviderHookProfile, getProviderInstructionReferences, getProviderOnlyEvents, getProviderSummary, getProvidersByHookEvent, getProvidersByInstructFile, getProvidersByPriority, getProvidersBySkillsPrecedence, getProvidersBySpawnCapability, getProvidersByStatus, getProvidersForEvent, getRegistryVersion, getSpawnCapableProviders, getSupportedEvents, getSystemInfo, getTrackedSkills, getUnsupportedEvents, groupByInstructFile, inferSkillSourceType, inject, injectAll, installBatchWithRollback, installMcpServer, installSkill, isCaampOwnedSkill, isExclusivityMode, isMarketplaceScoped, isQuiet, isVerbose, listAllMcpServers, listCanonicalSkills, listMcpServers, loadLibraryFromModule, normalizeRecommendationCriteria, parseCaampBlocks, parseInjectionContent, parseSkillFile, parseSource, providerSupports, providerSupportsById, rankSkills, readConfig, recommendSkills, recordSkillInstall, registerSkillLibrary, registerSkillLibraryFromPath, removeConfig, removeInjection, removeMcpServer, removeMcpServerFromAll, removeSkill, removeSkillFromLock, resetDetectionCache, resetExclusivityModeOverride, resolveAlias, resolveDefaultTargetProviders, resolveMcpConfigPath, resolveNativeEvent, resolveProviderSkillsDirs, resolveRegistryTemplatePath, runDoctorBridge, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setExclusivityMode, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validateRecommendationCriteria, validateSkill, writeAgentFileToAllProviders, writeConfig };
9055
+ export { type AdoptedSkillRowData, AgentsSkillsRealDirError, type AuditFinding, type AuditResult, type AuditRule, type AuditSeverity, type BatchInstallOptions, type BatchInstallResult, type BridgeSymlinkRecord, CANONICAL_HOOK_EVENTS, type CaampBlock, type CaampLockFile, type CanonicalEventDefinition, type CanonicalHookEvent, type CantProfileCounts, type CantProfileEntry, type CantValidationDiagnostic, type ConfigFormat, type CrossProviderMatrix, type CtDispatchMatrix, type CtManifest, type CtManifestSkill, type CtProfileDefinition, type CtSkillEntry, type CtValidationIssue, type CtValidationResult, DEFAULT_EXCLUSIVITY_MODE, type DedupeResult, type DetectionCacheOptions, type DetectionResult, type DoctorAdoptCliAdapters, type DoctorAdoptOptions, type DoctorAdoptResult, type DoctorBridgeOptions, type DoctorBridgeResult, EXCLUSIVITY_MODE_ENV_VAR, type EnsureProviderInstructionFileOptions, type EnsureProviderInstructionFileResult, type ExclusivityMode, type GlobalOptions, HOOK_CATEGORIES, type Harness, type HarnessScope, type HookCategory, type HookEvent, type HookHandlerType, type HookMapping, type HookSupportResult, type HookSystemType, type InjectionCheckResult, type InjectionStatus, type InjectionTemplate, type InstallMcpServerOptions, type InstallMcpServerResult, type InstallSkillOptions, type InstructionUpdateSummary, type KnownProviderAgentFolderId, type LockEntry, MarketplaceClient, type MarketplaceResult, type MarketplaceSearchResult, type MarketplaceSkill, type McpConfigFormat, type McpDetectionEntry, type McpScope, type McpServerConfig, type McpServerEntriesByProvider, type McpServerEntry, type McpTransportType, type NormalizedHookEvent, type NormalizedRecommendationCriteria, type OrphanActionResult, type OrphanDecision, type OrphanRecord, type OrphanRefusal, type ParsedSource, type PerSkillSymlinkRemoval, PiHarness, PiRequiredError, type Provider, type ProviderCapabilities, type ProviderHarnessCapability, type ProviderHookProfile, type ProviderHookSummary, type ProviderHooksCapability, type ProviderMcpCapability, type ProviderPriority, type ProviderSkillsCapability, type ProviderSpawnCapability, type ProviderStatus, RECOMMENDATION_ERROR_CODES, type RankedSkillRecommendation, type RecommendSkillsResult, type RecommendationCriteriaInput, type RecommendationErrorCode, type RecommendationOptions, type RecommendationReason, type RecommendationReasonCode, type RecommendationScoreBreakdown, type RecommendationValidationIssue, type RecommendationValidationResult, type RecommendationWeights, type RecordRowFn, type RegisteredNamesLoader, type RegistryHarnessKind, type RegistryHookCatalog, type RegistryHookFormat, type RemoveMcpServerOptions, type RemoveMcpServerResult, type ResolveDefaultTargetProvidersOptions, type SkillBatchOperation, type SkillEntry, type SkillInstallResult, type SkillIntegrityResult, type SkillIntegrityStatus, type SkillLibrary, type SkillLibraryDispatchMatrix, type SkillLibraryEntry, type SkillLibraryManifest, type SkillLibraryManifestSkill, type SkillLibraryProfile, type SkillLibraryValidationIssue, type SkillLibraryValidationResult, type SkillMetadata, type SkillRowData, type SkillRowSourceType, type SkillsPrecedence, type SourceType, type SpawnAdapter, type SpawnMechanism, type SpawnOptions, type SpawnResult, type SubagentHandle, type SubagentResult, type SubagentTask, type TransportType, type ValidateCantProfileResult, type ValidationIssue, type ValidationResult, type WriteAgentFileOptions, type WriteAgentFileResult, _resetLegacySkillsWarning, _resetPlatformPathsCache, applyDecision, buildBackupTimestamp, buildHookMatrix, buildInjectionContent, buildLibraryFromFiles, buildSkillsMap, caampStandaloneAdapters, catalog, checkAllInjections, checkAllSkillIntegrity, checkAllSkillUpdates, checkInjection, checkSkillIntegrity, checkSkillUpdate, clearRegisteredLibrary, dedupeFile, dedupeFiles, deepMerge, detectAllProviders, detectMcpInstallations, detectProjectProviders, detectProvider, discoverOrphans, discoverSkill, discoverSkills, ensureAllProviderInstructionFiles, ensureDir, ensureProviderInstructionFile, formatSkillRecommendations, generateInjectionContent, generateSkillsSection, getAgentsConfigPath, getAgentsHome, getAgentsInstructFile, getAgentsLinksDir, getAgentsMcpDir, getAgentsMcpServersPath, getAgentsSpecDir, getAgentsWikiDir, getAllCanonicalEvents, getAllHarnesses, getAllProviders, getCanonicalEvent, getCanonicalEventsByCategory, getCanonicalSkillsDir, getCanonicalSkillsRoot, getCommonEvents, getCommonHookEvents, getEffectiveSkillsPaths, getExclusivityMode, getHarnessFor, getHookConfigPath, getHookMappingsVersion, getHookSupport, getHookSystemType, getInstalledProviders, getInstructionFiles, getLockFilePath, getMappedProviderIds, getNestedValue, getPlatformLocations, getPlatformPaths, getPrimaryHarness, getPrimaryProvider, getProjectAgentsDir, getProvider, getProviderAgentFolder, getProviderCapabilities, getProviderCount, getProviderHookProfile, getProviderInstructionReferences, getProviderOnlyEvents, getProviderSummary, getProvidersByHookEvent, getProvidersByInstructFile, getProvidersByPriority, getProvidersBySkillsPrecedence, getProvidersBySpawnCapability, getProvidersByStatus, getProvidersForEvent, getRegistryVersion, getSpawnCapableProviders, getSupportedEvents, getSystemInfo, getTrackedSkills, getUnsupportedEvents, groupByInstructFile, inferSkillSourceType, inject, injectAll, installBatchWithRollback, installMcpServer, installSkill, isCaampOwnedSkill, isExclusivityMode, isMarketplaceScoped, isQuiet, isVerbose, listAllMcpServers, listCanonicalSkills, listMcpServers, loadLibraryFromModule, normalizeRecommendationCriteria, parseCaampBlocks, parseInjectionContent, parseSkillFile, parseSource, providerSupports, providerSupportsById, rankSkills, readConfig, recommendSkills, recordSkillInstall, registerSkillLibrary, registerSkillLibraryFromPath, registerSkillsDoctorAdopt, removeConfig, removeInjection, removeMcpServer, removeMcpServerFromAll, removeSkill, removeSkillFromLock, resetDetectionCache, resetExclusivityModeOverride, resolveAlias, resolveDefaultTargetProviders, resolveMcpConfigPath, resolveNativeEvent, resolveProviderSkillsDirs, resolveRegistryTemplatePath, runDoctorAdopt, runDoctorBridge, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setExclusivityMode, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validateRecommendationCriteria, validateSkill, writeAgentFileToAllProviders, writeAuditLog, writeConfig };
package/dist/index.js CHANGED
@@ -6,8 +6,10 @@ import {
6
6
  PiHarness,
7
7
  PiRequiredError,
8
8
  RECOMMENDATION_ERROR_CODES,
9
+ applyDecision,
9
10
  buildBackupTimestamp,
10
11
  buildLibraryFromFiles,
12
+ caampStandaloneAdapters,
11
13
  catalog_exports,
12
14
  checkAllSkillUpdates,
13
15
  checkSkillUpdate,
@@ -17,6 +19,7 @@ import {
17
19
  detectMcpInstallations,
18
20
  detectProjectProviders,
19
21
  detectProvider,
22
+ discoverOrphans,
20
23
  discoverSkill,
21
24
  discoverSkills,
22
25
  ensureDir,
@@ -50,6 +53,7 @@ import {
50
53
  recordSkillInstall,
51
54
  registerSkillLibrary,
52
55
  registerSkillLibraryFromPath,
56
+ registerSkillsDoctorAdopt,
53
57
  removeConfig,
54
58
  removeMcpServer,
55
59
  removeMcpServerFromAll,
@@ -59,6 +63,7 @@ import {
59
63
  resetExclusivityModeOverride,
60
64
  resolveDefaultTargetProviders,
61
65
  resolveMcpConfigPath,
66
+ runDoctorAdopt,
62
67
  runDoctorBridge,
63
68
  scanDirectory,
64
69
  scanFile,
@@ -73,8 +78,9 @@ import {
73
78
  updateInstructionsSingleOperation,
74
79
  validateRecommendationCriteria,
75
80
  validateSkill,
81
+ writeAuditLog,
76
82
  writeConfig
77
- } from "./chunk-QSJSM57K.js";
83
+ } from "./chunk-WXL7RPS4.js";
78
84
  import {
79
85
  buildInjectionContent,
80
86
  buildSkillsMap,
@@ -317,11 +323,13 @@ export {
317
323
  RECOMMENDATION_ERROR_CODES,
318
324
  _resetLegacySkillsWarning,
319
325
  _resetPlatformPathsCache,
326
+ applyDecision,
320
327
  buildBackupTimestamp,
321
328
  buildHookMatrix,
322
329
  buildInjectionContent,
323
330
  buildLibraryFromFiles,
324
331
  buildSkillsMap,
332
+ caampStandaloneAdapters,
325
333
  catalog_exports as catalog,
326
334
  checkAllInjections,
327
335
  checkAllSkillIntegrity,
@@ -337,6 +345,7 @@ export {
337
345
  detectMcpInstallations,
338
346
  detectProjectProviders,
339
347
  detectProvider,
348
+ discoverOrphans,
340
349
  discoverSkill,
341
350
  discoverSkills,
342
351
  ensureAllProviderInstructionFiles,
@@ -429,6 +438,7 @@ export {
429
438
  recordSkillInstall,
430
439
  registerSkillLibrary,
431
440
  registerSkillLibraryFromPath,
441
+ registerSkillsDoctorAdopt,
432
442
  removeConfig,
433
443
  removeInjection,
434
444
  removeMcpServer,
@@ -443,6 +453,7 @@ export {
443
453
  resolveNativeEvent,
444
454
  resolveProviderSkillsDirs,
445
455
  resolveRegistryTemplatePath,
456
+ runDoctorAdopt,
446
457
  runDoctorBridge,
447
458
  scanDirectory,
448
459
  scanFile,
@@ -465,6 +476,7 @@ export {
465
476
  validateRecommendationCriteria,
466
477
  validateSkill,
467
478
  writeAgentFileToAllProviders,
479
+ writeAuditLog,
468
480
  writeConfig
469
481
  };
470
482
  //# sourceMappingURL=index.js.map
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/caamp",
3
- "version": "2026.5.84",
3
+ "version": "2026.5.87",
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": {
@@ -50,10 +50,10 @@
50
50
  "jsonc-parser": "^3.3.1",
51
51
  "picocolors": "^1.1.1",
52
52
  "simple-git": "3.33.0",
53
- "@cleocode/lafs": "2026.5.84",
54
- "@cleocode/cant": "2026.5.84",
55
- "@cleocode/contracts": "2026.5.84",
56
- "@cleocode/paths": "2026.5.84"
53
+ "@cleocode/contracts": "2026.5.87",
54
+ "@cleocode/paths": "2026.5.87",
55
+ "@cleocode/cant": "2026.5.87",
56
+ "@cleocode/lafs": "2026.5.87"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@biomejs/biome": "2.4.11",