@cleocode/caamp 2026.9.8 → 2026.9.10

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,4 +1,5 @@
1
- import { CaampInjectionAction } from '@cleocode/contracts/caamp-markers';
1
+ import { InstructionDeliveryFinding, CaampInjectionAction, InstructionDelivery } from '@cleocode/contracts/caamp-markers';
2
+ import { PackageArtifactBudgets, PackageArtifactRequirement, PackageArtifactFile, PackageFilesEntry, PackageArtifactInventory, PackageArtifactPolicy, PackageArtifactValidation } from '@cleocode/contracts/package-artifact';
2
3
  import { WorktreeHandle } from '@cleocode/cant';
3
4
  import { PlatformPaths, SystemInfo } from '@cleocode/paths';
4
5
  export { PlatformPaths, SystemInfo } from '@cleocode/paths';
@@ -1228,6 +1229,10 @@ type InjectionStatus = 'current' | 'outdated' | 'missing' | 'none';
1228
1229
  * @public
1229
1230
  */
1230
1231
  interface InjectionCheckResult {
1232
+ /** Deterministic delivery defects, independently of marker integrity. */
1233
+ deliveryFindings?: InstructionDeliveryFinding[];
1234
+ /** File inspection cannot establish live provider behavior. */
1235
+ liveEvaluation?: 'unverified';
1231
1236
  /** Absolute path to the instruction file. */
1232
1237
  file: string;
1233
1238
  /** Provider ID that owns this instruction file. */
@@ -1502,6 +1507,56 @@ interface InstructionUpdateSummary {
1502
1507
  */
1503
1508
  declare function updateInstructionsSingleOperation(providers: Provider[], content: string, scope?: Scope, projectDir?: string): Promise<InstructionUpdateSummary>;
1504
1509
 
1510
+ /**
1511
+ * Pure semantic validation of npm-produced package inventories.
1512
+ *
1513
+ * Code placed in `packages/caamp/` per Package-Boundary Check — verified
1514
+ * against AGENTS.md. No filesystem, process, model, or runtime-store access.
1515
+ */
1516
+
1517
+ /** Existing CLI upper budgets: 20 MiB packed, 36 MiB unpacked, and 800 files. */
1518
+ declare const CLEO_ARTIFACT_BUDGETS: Readonly<PackageArtifactBudgets>;
1519
+ /** Required CLI and adapter-node resources, independent of historical byte/file-count floors. */
1520
+ declare const CLEO_ARTIFACT_REQUIREMENTS: readonly PackageArtifactRequirement[];
1521
+ /**
1522
+ * Classify a manifest files entry without evaluating exclusions as disk paths.
1523
+ *
1524
+ * @param entry - Original npm manifest entry.
1525
+ * @returns Classification only; actual selected content must come from the packer.
1526
+ * @example
1527
+ * ```ts
1528
+ * classifyPackageFilesEntry('!dist/index.js.map').kind; // 'exclusion'
1529
+ * ```
1530
+ */
1531
+ declare function classifyPackageFilesEntry(entry: string): PackageFilesEntry;
1532
+ /**
1533
+ * Validate nonempty semantic resources, inventory identity, expected hashes and upper budgets.
1534
+ *
1535
+ * @param inventory - Packer-produced evidence or an explicitly identified fixture.
1536
+ * @param policy - Independently specified resources, expected files and cost ceilings.
1537
+ * @returns A copied evidence receipt; valid means only the supplied checks passed.
1538
+ * @example
1539
+ * ```ts
1540
+ * const result = validatePackageArtifact(inventory, {
1541
+ * requirements: CLEO_ARTIFACT_REQUIREMENTS,
1542
+ * budgets: CLEO_ARTIFACT_BUDGETS,
1543
+ * });
1544
+ * // result.runtime remains 'not-assessed', even when result.valid is true.
1545
+ * ```
1546
+ */
1547
+ declare function validatePackageArtifact(inventory: PackageArtifactInventory, policy: PackageArtifactPolicy): PackageArtifactValidation;
1548
+ /**
1549
+ * Reject declaration and stray JavaScript output absent from the published CLI bundle.
1550
+ *
1551
+ * @param files - Actual selected package files, not a development directory listing.
1552
+ * @returns Concrete build-shape failures; this does not load the bundle.
1553
+ * @example
1554
+ * ```ts
1555
+ * assertCleoShippedBuildShape([{ path: 'dist/cli/index.js', size: 1 }]); // []
1556
+ * ```
1557
+ */
1558
+ declare function assertCleoShippedBuildShape(files: readonly PackageArtifactFile[]): string[];
1559
+
1505
1560
  /**
1506
1561
  * Three-tier scope helper for Pi harness operations.
1507
1562
  *
@@ -4759,11 +4814,11 @@ interface ReconcileResult {
4759
4814
  * any others are removed. Replacing in place matters: prepending instead
4760
4815
  * would walk the block up the file on every run, and would separate it from
4761
4816
  * any heading a user wrote above it.
4762
- * 4. All text outside CAAMP blocks is preserved. CAAMP owns the region between
4763
- * its markers and nothing else in the file. The only change made outside
4764
- * them is whitespace tidying runs of three or more newlines collapse to
4765
- * two, and the file ends with exactly one newline. No non-blank line is
4766
- * ever removed, reordered or rewritten.
4817
+ * 4. Every byte outside CAAMP blocks is preserved, including indentation,
4818
+ * CRLF, BOM, blank lines and trailing whitespace. Only newly inserted block
4819
+ * separators are added; existing user text is never trimmed or tidied.
4820
+ * 5. Nested or unmatched markers are ambiguous ownership and reject mutation.
4821
+ * No guessed region is removed or expanded to consume user content.
4767
4822
  *
4768
4823
  * This function is pure — it performs no I/O, which is what makes the
4769
4824
  * behaviour straightforward to test exhaustively.
@@ -4772,6 +4827,7 @@ interface ReconcileResult {
4772
4827
  * @param desiredContent - Body the single surviving block should carry
4773
4828
  * @param insert - Placement when the file has no block yet
4774
4829
  * @returns The reconciled content plus what was found on the way
4830
+ * @throws When existing or desired markers have ambiguous ownership.
4775
4831
  *
4776
4832
  * @example
4777
4833
  * ```typescript
@@ -4820,6 +4876,7 @@ declare function mergeBlockBodies(blocks: readonly CaampBlock[]): string;
4820
4876
  *
4821
4877
  * @param existing - Current file contents
4822
4878
  * @returns The repaired content plus what was found on the way
4879
+ * @throws When marker ownership is ambiguous; callers must preserve the original file.
4823
4880
  *
4824
4881
  * @example
4825
4882
  * ```typescript
@@ -5080,8 +5137,9 @@ declare function inject(filePath: string, content: string): Promise<CaampInjecti
5080
5137
  * @returns `true` if a CAAMP block was found and removed, `false` otherwise
5081
5138
  *
5082
5139
  * @remarks
5083
- * Cleans up any leftover blank lines after removing the block. If the file
5084
- * would be entirely empty after removal, the file itself is deleted.
5140
+ * Retains every byte outside the marker spans, including whitespace-only
5141
+ * content. Deletes the file only when no outside bytes remain. Ambiguous
5142
+ * marker ownership rejects removal before any file write.
5085
5143
  *
5086
5144
  * Blocks whose markers are damaged are healed first, so uninstall removes them
5087
5145
  * too rather than leaving orphaned fragments behind.
@@ -5165,6 +5223,8 @@ interface EnsureProviderInstructionFileOptions {
5165
5223
  content?: string[];
5166
5224
  /** Whether this is a global or project-level file. @defaultValue `"project"` */
5167
5225
  scope?: 'project' | 'global';
5226
+ /** Embed resolved sources; reference-only delivery requires an explicitly verified provider. */
5227
+ delivery?: 'self-contained' | 'verified-references';
5168
5228
  }
5169
5229
  /**
5170
5230
  * Result of ensuring a provider instruction file.
@@ -5481,6 +5541,24 @@ declare function generateSkillsSection(skillNames: string[]): string;
5481
5541
  * @public
5482
5542
  */
5483
5543
  declare function groupByInstructFile(providers: Provider[]): Map<string, Provider[]>;
5544
+ /**
5545
+ * Expand instruction references into bounded, self-contained managed content.
5546
+ *
5547
+ * Code placed in `packages/caamp/` per Package-Boundary Check — verified against AGENTS.md.
5548
+ * Missing files and cycles are explicit findings; callers must refuse incomplete writes.
5549
+ * Marker lines are removed from embedded files so nested managed blocks cannot corrupt
5550
+ * the destination. References inside fenced examples are preserved as examples.
5551
+ *
5552
+ * @param content - Managed content containing standalone reference lines.
5553
+ * @param baseDir - Directory against which the first references resolve.
5554
+ * @returns Resolved bootstrap and deterministic delivery diagnostics.
5555
+ * @example
5556
+ * ```typescript
5557
+ * const delivery = await resolveInstructionDelivery('@AGENTS.md', '/project');
5558
+ * ```
5559
+ * @public
5560
+ */
5561
+ declare function resolveInstructionDelivery(content: string, baseDir: string): Promise<InstructionDelivery>;
5484
5562
 
5485
5563
  /**
5486
5564
  * Simple logger with verbose/quiet mode support.
@@ -8881,4 +8959,4 @@ declare function parseSource(input: string): ParsedSource;
8881
8959
  */
8882
8960
  declare function isMarketplaceScoped(input: string): boolean;
8883
8961
 
8884
- export { type AuditFinding, type AuditResult, type AuditRule, type AuditSeverity, type BatchInstallOptions, type BatchInstallResult, type BlockInsertPosition, 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, EXCLUSIVITY_MODE_ENV_VAR, type EnsureProviderInstructionFileOptions, type EnsureProviderInstructionFileResult, type ExclusivityMode, type FileLockOptions, 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 NormalizeResult, type NormalizedHookEvent, type NormalizedRecommendationCriteria, type ParsedSource, 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 ReconcileResult, type RegistryHarnessKind, type RegistryHookCatalog, type RegistryHookFormat, type RemoveMcpServerOptions, type RemoveMcpServerResult, type RepairResult, 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, _resetPlatformPathsCache, assertNotTornRead, blockPattern, buildBlock, 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, 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, instructionFileCascade, isCaampOwnedSkill, isExclusivityMode, isMarketplaceScoped, isQuiet, isVerbose, listAllMcpServers, listCanonicalSkills, listMcpServers, loadLibraryFromModule, mergeBlockBodies, normalizeMarkers, normalizeRecommendationCriteria, parseBlocks, parseCaampBlocks, parseInjectionContent, parseSkillFile, parseSource, providerSupports, providerSupportsById, rankSkills, readConfig, recommendSkills, reconcile, recordSkillInstall, registerSkillLibrary, registerSkillLibraryFromPath, removeConfig, removeInjection, removeMcpServer, removeMcpServerFromAll, removeSkill, removeSkillFromLock, repairContent, repairInstructionFiles, resetDetectionCache, resetExclusivityModeOverride, resolveAlias, resolveDefaultTargetProviders, resolveMcpConfigPath, resolveNativeEvent, resolveProviderSkillsDirs, resolveRegistryTemplatePath, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setExclusivityMode, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validateRecommendationCriteria, validateSkill, withFileLock, writeAgentFileToAllProviders, writeConfig };
8962
+ export { type AuditFinding, type AuditResult, type AuditRule, type AuditSeverity, type BatchInstallOptions, type BatchInstallResult, type BlockInsertPosition, CANONICAL_HOOK_EVENTS, CLEO_ARTIFACT_BUDGETS, CLEO_ARTIFACT_REQUIREMENTS, 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, EXCLUSIVITY_MODE_ENV_VAR, type EnsureProviderInstructionFileOptions, type EnsureProviderInstructionFileResult, type ExclusivityMode, type FileLockOptions, 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 NormalizeResult, type NormalizedHookEvent, type NormalizedRecommendationCriteria, type ParsedSource, 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 ReconcileResult, type RegistryHarnessKind, type RegistryHookCatalog, type RegistryHookFormat, type RemoveMcpServerOptions, type RemoveMcpServerResult, type RepairResult, 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, _resetPlatformPathsCache, assertCleoShippedBuildShape, assertNotTornRead, blockPattern, buildBlock, buildHookMatrix, buildInjectionContent, buildLibraryFromFiles, buildSkillsMap, catalog, checkAllInjections, checkAllSkillIntegrity, checkAllSkillUpdates, checkInjection, checkSkillIntegrity, checkSkillUpdate, classifyPackageFilesEntry, 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, 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, instructionFileCascade, isCaampOwnedSkill, isExclusivityMode, isMarketplaceScoped, isQuiet, isVerbose, listAllMcpServers, listCanonicalSkills, listMcpServers, loadLibraryFromModule, mergeBlockBodies, normalizeMarkers, normalizeRecommendationCriteria, parseBlocks, parseCaampBlocks, parseInjectionContent, parseSkillFile, parseSource, providerSupports, providerSupportsById, rankSkills, readConfig, recommendSkills, reconcile, recordSkillInstall, registerSkillLibrary, registerSkillLibraryFromPath, removeConfig, removeInjection, removeMcpServer, removeMcpServerFromAll, removeSkill, removeSkillFromLock, repairContent, repairInstructionFiles, resetDetectionCache, resetExclusivityModeOverride, resolveAlias, resolveDefaultTargetProviders, resolveInstructionDelivery, resolveMcpConfigPath, resolveNativeEvent, resolveProviderSkillsDirs, resolveRegistryTemplatePath, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setExclusivityMode, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validatePackageArtifact, validateRecommendationCriteria, validateSkill, withFileLock, writeAgentFileToAllProviders, writeConfig };
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ import {
71
71
  validateRecommendationCriteria,
72
72
  validateSkill,
73
73
  writeConfig
74
- } from "./chunk-6KBUDJZI.js";
74
+ } from "./chunk-52ZWZXEN.js";
75
75
  import {
76
76
  assertNotTornRead,
77
77
  blockPattern,
@@ -120,9 +120,10 @@ import {
120
120
  repairContent,
121
121
  repairInstructionFiles,
122
122
  resolveAlias,
123
+ resolveInstructionDelivery,
123
124
  withFileLock,
124
125
  writeAgentFileToAllProviders
125
- } from "./chunk-3IQUGSIV.js";
126
+ } from "./chunk-G3CEXEW2.js";
126
127
  import {
127
128
  CANONICAL_HOOK_EVENTS,
128
129
  HOOK_CATEGORIES,
@@ -168,6 +169,219 @@ import {
168
169
  resolveRegistryTemplatePath
169
170
  } from "./chunk-HDZYZTR2.js";
170
171
 
172
+ // src/core/artifacts/validation.ts
173
+ import { posix } from "path";
174
+ var CLEO_ARTIFACT_BUDGETS = Object.freeze({
175
+ packedBytes: 20 * 1024 * 1024,
176
+ unpackedBytes: 36 * 1024 * 1024,
177
+ fileCount: 800
178
+ });
179
+ var CLEO_ARTIFACT_REQUIREMENTS = Object.freeze([
180
+ { id: "cli-entry", path: "dist/cli/index.js", match: "exact" },
181
+ { id: "studio-server-entry", path: "studio-dist/index.js", match: "exact" },
182
+ { id: "studio-handler", path: "studio-dist/handler.js", match: "exact" },
183
+ { id: "studio-server", path: "studio-dist/server/index.js", match: "exact" },
184
+ { id: "studio-manifest", path: "studio-dist/server/manifest.js", match: "exact" },
185
+ {
186
+ id: "studio-client-start",
187
+ path: "studio-dist/client/_app/immutable/entry/start.*.js",
188
+ match: "glob"
189
+ },
190
+ {
191
+ id: "studio-client-app",
192
+ path: "studio-dist/client/_app/immutable/entry/app.*.js",
193
+ match: "glob"
194
+ }
195
+ ]);
196
+ var SHA256 = /^[a-f0-9]{64}$/;
197
+ var GLOB = /[*?{}[\]]|[+@!]\(/;
198
+ function pathProblem(path) {
199
+ if (!path || path.trim() !== path) return "Path is empty or has surrounding whitespace.";
200
+ if (path.includes("\\") || /[\x00-\x1f\x7f]/.test(path))
201
+ return "Path is not a portable POSIX package path.";
202
+ if (posix.isAbsolute(path) || /^[A-Za-z]:/.test(path))
203
+ return "Path must be relative to the package.";
204
+ if (path.split("/").some((part) => !part || part === "." || part === "..")) {
205
+ return "Path must not contain empty, current-directory, or parent-directory segments.";
206
+ }
207
+ return null;
208
+ }
209
+ function classifyPackageFilesEntry(entry) {
210
+ const exclusion = entry.startsWith("!");
211
+ const pattern = (exclusion ? entry.slice(1) : entry).replace(/^\.\//, "").replace(/\/$/, "");
212
+ const reason = pattern.startsWith("!") ? "Repeated exclusion markers are not assessed." : pathProblem(pattern);
213
+ return {
214
+ entry,
215
+ pattern,
216
+ kind: reason ? "invalid" : exclusion ? "exclusion" : GLOB.test(pattern) ? "glob" : "literal",
217
+ reason
218
+ };
219
+ }
220
+ function fileProblems(file) {
221
+ const problems = [];
222
+ const pathError = pathProblem(file.path);
223
+ if (pathError) problems.push(pathError);
224
+ if (!Number.isSafeInteger(file.size) || file.size < 0)
225
+ problems.push("File size must be a nonnegative safe integer.");
226
+ if (file.sha256 !== void 0 && !SHA256.test(file.sha256))
227
+ problems.push("File SHA-256 must be 64 lowercase hexadecimal characters.");
228
+ return problems;
229
+ }
230
+ function validatePackageArtifact(inventory, policy) {
231
+ const issues = [];
232
+ const add = (code, subject, message) => {
233
+ issues.push({ code, subject, message });
234
+ };
235
+ if (!inventory.packageName.trim() || !inventory.version.trim()) {
236
+ add("invalid-inventory", "identity", "Package name and version must be recorded.");
237
+ }
238
+ if (!["npm-pack", "npm-pack-dry-run", "fixture"].includes(inventory.source)) {
239
+ add("invalid-inventory", "source", "Inventory provenance is not recognized.");
240
+ }
241
+ if (!Number.isSafeInteger(inventory.packedBytes) || inventory.packedBytes < 0) {
242
+ add("invalid-inventory", "packedBytes", "Packed bytes must be a nonnegative safe integer.");
243
+ }
244
+ if (inventory.tarballSha256 !== void 0 && !SHA256.test(inventory.tarballSha256)) {
245
+ add(
246
+ "invalid-inventory",
247
+ "tarballSha256",
248
+ "Tarball SHA-256 must be 64 lowercase hexadecimal characters."
249
+ );
250
+ }
251
+ if (inventory.source === "npm-pack-dry-run" && inventory.tarballSha256 !== void 0) {
252
+ add(
253
+ "invalid-inventory",
254
+ "tarballSha256",
255
+ "A dry run cannot establish a retained tarball hash."
256
+ );
257
+ }
258
+ const files = /* @__PURE__ */ new Map();
259
+ let unpackedBytes = 0;
260
+ for (const file of inventory.files) {
261
+ for (const problem of fileProblems(file)) add("invalid-inventory", file.path, problem);
262
+ if (files.has(file.path)) add("invalid-inventory", file.path, "Duplicate inventory path.");
263
+ files.set(file.path, file);
264
+ if (unpackedBytes !== null) {
265
+ const total = unpackedBytes + file.size;
266
+ unpackedBytes = Number.isSafeInteger(total) && file.size >= 0 ? total : null;
267
+ }
268
+ }
269
+ if (unpackedBytes === null)
270
+ add("invalid-inventory", "unpackedBytes", "File bytes cannot be safely totaled.");
271
+ const entries = (policy.filesEntries ?? []).map(classifyPackageFilesEntry);
272
+ for (const entry of entries) {
273
+ if (entry.reason) add("invalid-policy", entry.entry, entry.reason);
274
+ }
275
+ if (!policy.requirements.length)
276
+ add("invalid-policy", "requirements", "At least one semantic content requirement is required.");
277
+ const ids = /* @__PURE__ */ new Set();
278
+ const requirements = policy.requirements.map((requirement) => {
279
+ const invalid = pathProblem(requirement.path);
280
+ const duplicate = ids.has(requirement.id);
281
+ ids.add(requirement.id);
282
+ if (!requirement.id.trim() || duplicate || invalid || !["exact", "glob"].includes(requirement.match)) {
283
+ add(
284
+ "invalid-policy",
285
+ requirement.id,
286
+ invalid ?? "Requirement identity or match mode is invalid or duplicated."
287
+ );
288
+ return { id: requirement.id, matchedPaths: [], satisfied: false };
289
+ }
290
+ const matches = inventory.files.filter(
291
+ (file) => requirement.match === "exact" ? file.path === requirement.path : posix.matchesGlob(file.path, requirement.path)
292
+ );
293
+ if (!matches.length)
294
+ add(
295
+ "missing",
296
+ requirement.id,
297
+ `Required resource ${requirement.path} is absent from the selected inventory.`
298
+ );
299
+ for (const file of matches) {
300
+ if (file.size === 0) add("empty", file.path, `Required resource ${requirement.id} is empty.`);
301
+ }
302
+ return {
303
+ id: requirement.id,
304
+ matchedPaths: matches.map((file) => file.path),
305
+ satisfied: matches.length > 0 && matches.every((file) => file.size > 0)
306
+ };
307
+ });
308
+ for (const pattern of policy.forbiddenPatterns ?? []) {
309
+ const invalid = pathProblem(pattern);
310
+ if (invalid) {
311
+ add("invalid-policy", pattern, invalid);
312
+ continue;
313
+ }
314
+ for (const file of inventory.files) {
315
+ if (posix.matchesGlob(file.path, pattern))
316
+ add("forbidden", file.path, `Selected file matches forbidden pattern ${pattern}.`);
317
+ }
318
+ }
319
+ let hashComparison = "not-requested";
320
+ for (const expected of policy.expectedFiles ?? []) {
321
+ const problems = fileProblems(expected);
322
+ for (const problem of problems) add("invalid-policy", expected.path, problem);
323
+ const actual = files.get(expected.path);
324
+ if (!actual) add("missing", expected.path, "An independently expected file is missing.");
325
+ else if (actual.size !== expected.size)
326
+ add("mismatch", expected.path, "File bytes differ from independently expected inventory.");
327
+ if (expected.sha256 !== void 0) {
328
+ if (hashComparison !== "failed") hashComparison = "matched";
329
+ if (problems.length || actual?.sha256 !== expected.sha256) {
330
+ hashComparison = "failed";
331
+ add(
332
+ "mismatch",
333
+ expected.path,
334
+ "Actual hash is missing or differs from the independently expected SHA-256."
335
+ );
336
+ }
337
+ }
338
+ }
339
+ for (const [field, limit] of Object.entries(policy.budgets)) {
340
+ if (!Number.isSafeInteger(limit) || limit < 0)
341
+ add("invalid-policy", field, "Budget must be a nonnegative safe integer.");
342
+ }
343
+ if (inventory.packedBytes > policy.budgets.packedBytes)
344
+ add("budget", "packedBytes", "Packed bytes exceed the configured upper budget.");
345
+ if (unpackedBytes !== null && unpackedBytes > policy.budgets.unpackedBytes)
346
+ add("budget", "unpackedBytes", "Unpacked bytes exceed the configured upper budget.");
347
+ if (inventory.files.length > policy.budgets.fileCount)
348
+ add("budget", "fileCount", "Selected file count exceeds the configured upper budget.");
349
+ return {
350
+ valid: issues.length === 0,
351
+ inventory: { ...inventory, files: inventory.files.map((file) => ({ ...file })) },
352
+ unpackedBytes,
353
+ issues,
354
+ filesEntries: entries,
355
+ requirements,
356
+ hashComparison,
357
+ runtime: "not-assessed",
358
+ limitations: [
359
+ "Manifest entries are classified only; the supplied packer inventory is the content authority.",
360
+ "Hashes are recorded and compared to supplied expected evidence; this validator does not read or authenticate tarball bytes.",
361
+ "Installability, module/dependency resolution, local-reference closure and native/dynamic runtime behavior are not assessed."
362
+ ]
363
+ };
364
+ }
365
+ function assertCleoShippedBuildShape(files) {
366
+ const reasons = [];
367
+ const dist = files.filter((file) => file.path.startsWith("dist/"));
368
+ const declarations = dist.filter(
369
+ (file) => file.path.endsWith(".d.ts") || file.path.endsWith(".d.ts.map")
370
+ );
371
+ if (declarations.length)
372
+ reasons.push(
373
+ `${declarations.length} declaration file(s) under dist/ \u2014 the esbuild bundle emits none`
374
+ );
375
+ const stray = dist.filter(
376
+ (file) => file.path.endsWith(".js") && file.path !== "dist/cli/index.js"
377
+ );
378
+ if (stray.length)
379
+ reasons.push(
380
+ `${stray.length} .js file(s) under dist/ outside the declared entry dist/cli/index.js, e.g. ${stray[0]?.path}`
381
+ );
382
+ return reasons;
383
+ }
384
+
171
385
  // src/core/skills/integrity.ts
172
386
  import { existsSync, lstatSync, readlinkSync } from "fs";
173
387
  import { join, resolve } from "path";
@@ -285,7 +499,7 @@ function shouldOverrideSkill(skillName, incomingSource, existingEntry) {
285
499
  return true;
286
500
  }
287
501
  async function validateInstructionIntegrity(providers, projectDir, scope, expectedContent) {
288
- const { checkAllInjections: checkAllInjections2 } = await import("./injector-6YGSK3B6.js");
502
+ const { checkAllInjections: checkAllInjections2 } = await import("./injector-X5FN7WMR.js");
289
503
  const results = await checkAllInjections2(providers, projectDir, scope, expectedContent);
290
504
  const issues = [];
291
505
  for (const result of results) {
@@ -313,6 +527,8 @@ async function validateInstructionIntegrity(providers, projectDir, scope, expect
313
527
  }
314
528
  export {
315
529
  CANONICAL_HOOK_EVENTS,
530
+ CLEO_ARTIFACT_BUDGETS,
531
+ CLEO_ARTIFACT_REQUIREMENTS,
316
532
  DEFAULT_EXCLUSIVITY_MODE,
317
533
  EXCLUSIVITY_MODE_ENV_VAR,
318
534
  HOOK_CATEGORIES,
@@ -321,6 +537,7 @@ export {
321
537
  PiRequiredError,
322
538
  RECOMMENDATION_ERROR_CODES,
323
539
  _resetPlatformPathsCache,
540
+ assertCleoShippedBuildShape,
324
541
  assertNotTornRead,
325
542
  blockPattern,
326
543
  buildBlock,
@@ -335,6 +552,7 @@ export {
335
552
  checkInjection,
336
553
  checkSkillIntegrity,
337
554
  checkSkillUpdate,
555
+ classifyPackageFilesEntry,
338
556
  clearRegisteredLibrary,
339
557
  dedupeFile,
340
558
  dedupeFiles,
@@ -450,6 +668,7 @@ export {
450
668
  resetExclusivityModeOverride,
451
669
  resolveAlias,
452
670
  resolveDefaultTargetProviders,
671
+ resolveInstructionDelivery,
453
672
  resolveMcpConfigPath,
454
673
  resolveNativeEvent,
455
674
  resolveProviderSkillsDirs,
@@ -472,6 +691,7 @@ export {
472
691
  translateToAll,
473
692
  updateInstructionsSingleOperation,
474
693
  validateInstructionIntegrity,
694
+ validatePackageArtifact,
475
695
  validateRecommendationCriteria,
476
696
  validateSkill,
477
697
  withFileLock,
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 { resolveSkillsRoot } from '@cleocode/core/skills/skill-root.js';\nimport type { LockEntry, Provider } from '../../types.js';\nimport { readLockFile } from '../lock-utils.js';\nimport { 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(resolveSkillsRoot(), 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/artifacts/validation.ts","../src/core/skills/integrity.ts"],"sourcesContent":["/**\n * Pure semantic validation of npm-produced package inventories.\n *\n * Code placed in `packages/caamp/` per Package-Boundary Check — verified\n * against AGENTS.md. No filesystem, process, model, or runtime-store access.\n */\n\nimport { posix } from 'node:path';\nimport type {\n PackageArtifactBudgets,\n PackageArtifactFile,\n PackageArtifactInventory,\n PackageArtifactIssue,\n PackageArtifactPolicy,\n PackageArtifactRequirement,\n PackageArtifactValidation,\n PackageFilesEntry,\n} from '@cleocode/contracts/package-artifact';\n\n/** Existing CLI upper budgets: 20 MiB packed, 36 MiB unpacked, and 800 files. */\nexport const CLEO_ARTIFACT_BUDGETS: Readonly<PackageArtifactBudgets> = Object.freeze({\n packedBytes: 20 * 1024 * 1024,\n unpackedBytes: 36 * 1024 * 1024,\n fileCount: 800,\n});\n\n/** Required CLI and adapter-node resources, independent of historical byte/file-count floors. */\nexport const CLEO_ARTIFACT_REQUIREMENTS: readonly PackageArtifactRequirement[] = Object.freeze([\n { id: 'cli-entry', path: 'dist/cli/index.js', match: 'exact' },\n { id: 'studio-server-entry', path: 'studio-dist/index.js', match: 'exact' },\n { id: 'studio-handler', path: 'studio-dist/handler.js', match: 'exact' },\n { id: 'studio-server', path: 'studio-dist/server/index.js', match: 'exact' },\n { id: 'studio-manifest', path: 'studio-dist/server/manifest.js', match: 'exact' },\n {\n id: 'studio-client-start',\n path: 'studio-dist/client/_app/immutable/entry/start.*.js',\n match: 'glob',\n },\n {\n id: 'studio-client-app',\n path: 'studio-dist/client/_app/immutable/entry/app.*.js',\n match: 'glob',\n },\n]);\n\nconst SHA256 = /^[a-f0-9]{64}$/;\nconst GLOB = /[*?{}[\\]]|[+@!]\\(/;\n\nfunction pathProblem(path: string): string | null {\n if (!path || path.trim() !== path) return 'Path is empty or has surrounding whitespace.';\n if (path.includes('\\\\') || /[\\x00-\\x1f\\x7f]/.test(path))\n return 'Path is not a portable POSIX package path.';\n if (posix.isAbsolute(path) || /^[A-Za-z]:/.test(path))\n return 'Path must be relative to the package.';\n if (path.split('/').some((part) => !part || part === '.' || part === '..')) {\n return 'Path must not contain empty, current-directory, or parent-directory segments.';\n }\n return null;\n}\n\n/**\n * Classify a manifest files entry without evaluating exclusions as disk paths.\n *\n * @param entry - Original npm manifest entry.\n * @returns Classification only; actual selected content must come from the packer.\n * @example\n * ```ts\n * classifyPackageFilesEntry('!dist/index.js.map').kind; // 'exclusion'\n * ```\n */\nexport function classifyPackageFilesEntry(entry: string): PackageFilesEntry {\n const exclusion = entry.startsWith('!');\n const pattern = (exclusion ? entry.slice(1) : entry).replace(/^\\.\\//, '').replace(/\\/$/, '');\n const reason = pattern.startsWith('!')\n ? 'Repeated exclusion markers are not assessed.'\n : pathProblem(pattern);\n return {\n entry,\n pattern,\n kind: reason ? 'invalid' : exclusion ? 'exclusion' : GLOB.test(pattern) ? 'glob' : 'literal',\n reason,\n };\n}\n\nfunction fileProblems(file: PackageArtifactFile): string[] {\n const problems: string[] = [];\n const pathError = pathProblem(file.path);\n if (pathError) problems.push(pathError);\n if (!Number.isSafeInteger(file.size) || file.size < 0)\n problems.push('File size must be a nonnegative safe integer.');\n if (file.sha256 !== undefined && !SHA256.test(file.sha256))\n problems.push('File SHA-256 must be 64 lowercase hexadecimal characters.');\n return problems;\n}\n\n/**\n * Validate nonempty semantic resources, inventory identity, expected hashes and upper budgets.\n *\n * @param inventory - Packer-produced evidence or an explicitly identified fixture.\n * @param policy - Independently specified resources, expected files and cost ceilings.\n * @returns A copied evidence receipt; valid means only the supplied checks passed.\n * @example\n * ```ts\n * const result = validatePackageArtifact(inventory, {\n * requirements: CLEO_ARTIFACT_REQUIREMENTS,\n * budgets: CLEO_ARTIFACT_BUDGETS,\n * });\n * // result.runtime remains 'not-assessed', even when result.valid is true.\n * ```\n */\nexport function validatePackageArtifact(\n inventory: PackageArtifactInventory,\n policy: PackageArtifactPolicy,\n): PackageArtifactValidation {\n const issues: PackageArtifactIssue[] = [];\n const add = (code: PackageArtifactIssue['code'], subject: string, message: string): void => {\n issues.push({ code, subject, message });\n };\n if (!inventory.packageName.trim() || !inventory.version.trim()) {\n add('invalid-inventory', 'identity', 'Package name and version must be recorded.');\n }\n if (!['npm-pack', 'npm-pack-dry-run', 'fixture'].includes(inventory.source)) {\n add('invalid-inventory', 'source', 'Inventory provenance is not recognized.');\n }\n if (!Number.isSafeInteger(inventory.packedBytes) || inventory.packedBytes < 0) {\n add('invalid-inventory', 'packedBytes', 'Packed bytes must be a nonnegative safe integer.');\n }\n if (inventory.tarballSha256 !== undefined && !SHA256.test(inventory.tarballSha256)) {\n add(\n 'invalid-inventory',\n 'tarballSha256',\n 'Tarball SHA-256 must be 64 lowercase hexadecimal characters.',\n );\n }\n if (inventory.source === 'npm-pack-dry-run' && inventory.tarballSha256 !== undefined) {\n add(\n 'invalid-inventory',\n 'tarballSha256',\n 'A dry run cannot establish a retained tarball hash.',\n );\n }\n const files = new Map<string, PackageArtifactFile>();\n let unpackedBytes: number | null = 0;\n for (const file of inventory.files) {\n for (const problem of fileProblems(file)) add('invalid-inventory', file.path, problem);\n if (files.has(file.path)) add('invalid-inventory', file.path, 'Duplicate inventory path.');\n files.set(file.path, file);\n if (unpackedBytes !== null) {\n const total: number = unpackedBytes + file.size;\n unpackedBytes = Number.isSafeInteger(total) && file.size >= 0 ? total : null;\n }\n }\n if (unpackedBytes === null)\n add('invalid-inventory', 'unpackedBytes', 'File bytes cannot be safely totaled.');\n const entries = (policy.filesEntries ?? []).map(classifyPackageFilesEntry);\n for (const entry of entries) {\n if (entry.reason) add('invalid-policy', entry.entry, entry.reason);\n }\n if (!policy.requirements.length)\n add('invalid-policy', 'requirements', 'At least one semantic content requirement is required.');\n const ids = new Set<string>();\n const requirements = policy.requirements.map((requirement) => {\n const invalid = pathProblem(requirement.path);\n const duplicate = ids.has(requirement.id);\n ids.add(requirement.id);\n if (\n !requirement.id.trim() ||\n duplicate ||\n invalid ||\n !['exact', 'glob'].includes(requirement.match)\n ) {\n add(\n 'invalid-policy',\n requirement.id,\n invalid ?? 'Requirement identity or match mode is invalid or duplicated.',\n );\n return { id: requirement.id, matchedPaths: [], satisfied: false };\n }\n const matches = inventory.files.filter((file) =>\n requirement.match === 'exact'\n ? file.path === requirement.path\n : posix.matchesGlob(file.path, requirement.path),\n );\n if (!matches.length)\n add(\n 'missing',\n requirement.id,\n `Required resource ${requirement.path} is absent from the selected inventory.`,\n );\n for (const file of matches) {\n if (file.size === 0) add('empty', file.path, `Required resource ${requirement.id} is empty.`);\n }\n return {\n id: requirement.id,\n matchedPaths: matches.map((file) => file.path),\n satisfied: matches.length > 0 && matches.every((file) => file.size > 0),\n };\n });\n for (const pattern of policy.forbiddenPatterns ?? []) {\n const invalid = pathProblem(pattern);\n if (invalid) {\n add('invalid-policy', pattern, invalid);\n continue;\n }\n for (const file of inventory.files) {\n if (posix.matchesGlob(file.path, pattern))\n add('forbidden', file.path, `Selected file matches forbidden pattern ${pattern}.`);\n }\n }\n let hashComparison: PackageArtifactValidation['hashComparison'] = 'not-requested';\n for (const expected of policy.expectedFiles ?? []) {\n const problems = fileProblems(expected);\n for (const problem of problems) add('invalid-policy', expected.path, problem);\n const actual = files.get(expected.path);\n if (!actual) add('missing', expected.path, 'An independently expected file is missing.');\n else if (actual.size !== expected.size)\n add('mismatch', expected.path, 'File bytes differ from independently expected inventory.');\n if (expected.sha256 !== undefined) {\n if (hashComparison !== 'failed') hashComparison = 'matched';\n if (problems.length || actual?.sha256 !== expected.sha256) {\n hashComparison = 'failed';\n add(\n 'mismatch',\n expected.path,\n 'Actual hash is missing or differs from the independently expected SHA-256.',\n );\n }\n }\n }\n for (const [field, limit] of Object.entries(policy.budgets)) {\n if (!Number.isSafeInteger(limit) || limit < 0)\n add('invalid-policy', field, 'Budget must be a nonnegative safe integer.');\n }\n if (inventory.packedBytes > policy.budgets.packedBytes)\n add('budget', 'packedBytes', 'Packed bytes exceed the configured upper budget.');\n if (unpackedBytes !== null && unpackedBytes > policy.budgets.unpackedBytes)\n add('budget', 'unpackedBytes', 'Unpacked bytes exceed the configured upper budget.');\n if (inventory.files.length > policy.budgets.fileCount)\n add('budget', 'fileCount', 'Selected file count exceeds the configured upper budget.');\n return {\n valid: issues.length === 0,\n inventory: { ...inventory, files: inventory.files.map((file) => ({ ...file })) },\n unpackedBytes,\n issues,\n filesEntries: entries,\n requirements,\n hashComparison,\n runtime: 'not-assessed',\n limitations: [\n 'Manifest entries are classified only; the supplied packer inventory is the content authority.',\n 'Hashes are recorded and compared to supplied expected evidence; this validator does not read or authenticate tarball bytes.',\n 'Installability, module/dependency resolution, local-reference closure and native/dynamic runtime behavior are not assessed.',\n ],\n };\n}\n\n/**\n * Reject declaration and stray JavaScript output absent from the published CLI bundle.\n *\n * @param files - Actual selected package files, not a development directory listing.\n * @returns Concrete build-shape failures; this does not load the bundle.\n * @example\n * ```ts\n * assertCleoShippedBuildShape([{ path: 'dist/cli/index.js', size: 1 }]); // []\n * ```\n */\nexport function assertCleoShippedBuildShape(files: readonly PackageArtifactFile[]): string[] {\n const reasons: string[] = [];\n const dist = files.filter((file) => file.path.startsWith('dist/'));\n const declarations = dist.filter(\n (file) => file.path.endsWith('.d.ts') || file.path.endsWith('.d.ts.map'),\n );\n if (declarations.length)\n reasons.push(\n `${declarations.length} declaration file(s) under dist/ — the esbuild bundle emits none`,\n );\n const stray = dist.filter(\n (file) => file.path.endsWith('.js') && file.path !== 'dist/cli/index.js',\n );\n if (stray.length)\n reasons.push(\n `${stray.length} .js file(s) under dist/ outside the declared entry dist/cli/index.js, e.g. ${stray[0]?.path}`,\n );\n return reasons;\n}\n","/**\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 { resolveSkillsRoot } from '@cleocode/core/skills/skill-root.js';\nimport type { LockEntry, Provider } from '../../types.js';\nimport { readLockFile } from '../lock-utils.js';\nimport { 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(resolveSkillsRoot(), 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"]}
@@ -13,7 +13,7 @@ import {
13
13
  removeInjection,
14
14
  repairInstructionFiles,
15
15
  writeAgentFileToAllProviders
16
- } from "./chunk-3IQUGSIV.js";
16
+ } from "./chunk-G3CEXEW2.js";
17
17
  import "./chunk-HDZYZTR2.js";
18
18
  export {
19
19
  checkAllInjections,
@@ -31,4 +31,4 @@ export {
31
31
  repairInstructionFiles,
32
32
  writeAgentFileToAllProviders
33
33
  };
34
- //# sourceMappingURL=injector-6YGSK3B6.js.map
34
+ //# sourceMappingURL=injector-X5FN7WMR.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/caamp",
3
- "version": "2026.9.8",
3
+ "version": "2026.9.10",
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,14 +50,14 @@
50
50
  "jsonc-parser": "^3.3.1",
51
51
  "picocolors": "^1.1.1",
52
52
  "simple-git": "3.33.0",
53
- "@cleocode/contracts": "2026.9.8",
54
- "@cleocode/lafs": "2026.9.8",
55
- "@cleocode/core": "2026.9.8",
56
- "@cleocode/paths": "2026.9.8",
57
- "@cleocode/cant": "2026.9.8"
53
+ "@cleocode/cant": "2026.9.10",
54
+ "@cleocode/contracts": "2026.9.10",
55
+ "@cleocode/core": "2026.9.10",
56
+ "@cleocode/lafs": "2026.9.10",
57
+ "@cleocode/paths": "2026.9.10"
58
58
  },
59
59
  "peerDependencies": {
60
- "@cleocode/core": "2026.9.8"
60
+ "@cleocode/core": "2026.9.10"
61
61
  },
62
62
  "peerDependenciesMeta": {
63
63
  "@cleocode/core": {