@cleocode/caamp 2026.8.1 → 2026.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { CaampInjectionAction } from '@cleocode/contracts/caamp-markers';
1
2
  import { WorktreeHandle } from '@cleocode/cant';
2
3
  import { PlatformPaths, SystemInfo } from '@cleocode/paths';
3
4
  export { PlatformPaths, SystemInfo } from '@cleocode/paths';
@@ -1467,7 +1468,7 @@ interface InstructionUpdateSummary {
1467
1468
  /** Detailed action log per instruction file. */
1468
1469
  actions: Array<{
1469
1470
  file: string;
1470
- action: 'created' | 'added' | 'consolidated' | 'updated' | 'intact';
1471
+ action: CaampInjectionAction;
1471
1472
  providers: string[];
1472
1473
  configFormats: ConfigFormat[];
1473
1474
  }>;
@@ -2999,6 +3000,112 @@ declare function writeConfig(filePath: string, format: ConfigFormat, key: string
2999
3000
  */
3000
3001
  declare function removeConfig(filePath: string, format: ConfigFormat, key: string, serverName: string): Promise<boolean>;
3001
3002
 
3003
+ /**
3004
+ * Atomic file writes and cross-process file locking.
3005
+ *
3006
+ * CAAMP mutates files that are shared by *every* project on the machine — most
3007
+ * critically `~/.agents/AGENTS.md`, which every `cleo init`, `cleo upgrade` and
3008
+ * `cleo doctor` run rewrites regardless of which project it was invoked from.
3009
+ * A plain `writeFile` on such a path is `open(O_TRUNC)` followed by one or more
3010
+ * `write(2)` calls: two processes interleaving there can leave a caller reading
3011
+ * a half-written file, and a reader racing a writer can observe a truncated
3012
+ * one.
3013
+ *
3014
+ * Two primitives remove that class of failure:
3015
+ *
3016
+ * - `writeFileAtomic` — the canonical tmp-then-rename primitive from
3017
+ * `@cleocode/core/tools/fs.js`. `rename(2)` within a filesystem is atomic, so
3018
+ * a concurrent reader sees either the whole old file or the whole new one,
3019
+ * never a mixture. It is re-exported here for callers already importing this
3020
+ * module; the definition lives in core, per the tools-vs-skills boundary.
3021
+ * - {@link withFileLock} — serialise a whole read-modify-write cycle across
3022
+ * processes via an `O_EXCL` guard file, so two writers cannot both read the
3023
+ * pre-state and then clobber each other's result. Generalised from the
3024
+ * bespoke copy that lived inline in `lock-utils.ts` (now a caller).
3025
+ *
3026
+ * @task T12051
3027
+ */
3028
+ /**
3029
+ * Options controlling {@link withFileLock}.
3030
+ *
3031
+ * @public
3032
+ */
3033
+ interface FileLockOptions {
3034
+ /**
3035
+ * Number of acquisition attempts before throwing.
3036
+ *
3037
+ * @defaultValue 400
3038
+ */
3039
+ retries?: number;
3040
+ /**
3041
+ * Delay between attempts, in milliseconds.
3042
+ *
3043
+ * @defaultValue 25
3044
+ */
3045
+ delayMs?: number;
3046
+ /**
3047
+ * Age at which an existing guard file is treated as abandoned and removed.
3048
+ *
3049
+ * @defaultValue 30000
3050
+ */
3051
+ staleMs?: number;
3052
+ }
3053
+ /**
3054
+ * Run `fn` while holding an exclusive cross-process lock on `targetPath`.
3055
+ *
3056
+ * The lock is a `<targetPath>.lock` guard file created with `O_EXCL`, which is
3057
+ * atomic on POSIX and on Windows. A guard left behind by a crashed process is
3058
+ * reclaimed once it exceeds `staleMs`.
3059
+ *
3060
+ * The guard is always released, including when `fn` throws.
3061
+ *
3062
+ * @param targetPath - Path being protected (the guard is a sibling of it)
3063
+ * @param fn - Work to perform while holding the lock
3064
+ * @param options - Retry, delay and staleness tuning
3065
+ * @returns Whatever `fn` returns
3066
+ * @throws Error if the lock cannot be acquired within `retries` attempts
3067
+ *
3068
+ * @example
3069
+ * ```typescript
3070
+ * const action = await withFileLock(agentsMd, async () => {
3071
+ * const before = await readFile(agentsMd, "utf-8");
3072
+ * await writeFileAtomic(agentsMd, transform(before));
3073
+ * return "updated";
3074
+ * });
3075
+ * ```
3076
+ *
3077
+ * @public
3078
+ */
3079
+ declare function withFileLock<T>(targetPath: string, fn: () => Promise<T>, options?: FileLockOptions): Promise<T>;
3080
+ /**
3081
+ * Throw if a read looks like it landed inside another process's
3082
+ * truncate-then-write window.
3083
+ *
3084
+ * `writeFileAtomic` makes *our* writes indivisible, but callers outside this
3085
+ * package still rewrite instruction files with a plain `writeFile`, which is
3086
+ * `open(O_TRUNC)` followed by `write(2)`. A read landing between those two
3087
+ * returns an empty string for a file that is not empty on disk. Reconciling
3088
+ * from that observation would replace every byte of the user's content with a
3089
+ * lone CAAMP block.
3090
+ *
3091
+ * Failing closed is the right trade: the caller retries or reports, and the
3092
+ * file is left exactly as it was.
3093
+ *
3094
+ * @param filePath - Path that was read, for the error message
3095
+ * @param content - What the read returned
3096
+ * @param sizeOnDisk - `stat().size` for the same path
3097
+ * @throws Error when `content` is empty but `sizeOnDisk` is greater than zero
3098
+ *
3099
+ * @example
3100
+ * ```typescript
3101
+ * const text = await readFile(p, "utf-8");
3102
+ * if (text.length === 0) assertNotTornRead(p, text, (await stat(p)).size);
3103
+ * ```
3104
+ *
3105
+ * @public
3106
+ */
3107
+ declare function assertNotTornRead(filePath: string, content: string, sizeOnDisk: number): void;
3108
+
3002
3109
  /**
3003
3110
  * Skill installer - canonical + symlink model
3004
3111
  *
@@ -4483,13 +4590,36 @@ declare function resolveNativeEvent(nativeName: string): Array<{
4483
4590
  declare function getHookMappingsVersion(): string;
4484
4591
 
4485
4592
  /**
4486
- * Marker-based instruction file injection
4593
+ * CAAMP marker engine — parsing, damage repair, and canonical block rendering.
4487
4594
  *
4488
- * Injects content blocks between CAAMP markers in instruction files
4489
- * (CLAUDE.md, AGENTS.md, GEMINI.md) and agent-definition files
4490
- * (cleo-subagent.md, seed agent profiles) per provider's native folder.
4595
+ * The grammar itself lives in `@cleocode/contracts/caamp-markers` (const data
4596
+ * in the leaf package, because `@cleocode/core` and `@cleocode/caamp` depend on
4597
+ * each other and cannot share a module directly). This file is the only place
4598
+ * that turns that grammar into behaviour.
4599
+ *
4600
+ * ## Why damage repair exists
4601
+ *
4602
+ * A CAAMP block is delimited by two HTML comments. Losing a single character
4603
+ * from an opening marker — `<!-- CAAMP:START -->` becoming `!-- CAAMP:START -->`
4604
+ * — used to be unrecoverable *and* self-amplifying:
4605
+ *
4606
+ * 1. The strict pattern no longer matched the block.
4607
+ * 2. `inject()` concluded the file had no CAAMP block at all and **prepended a
4608
+ * fresh one**, rather than replacing the damaged one.
4609
+ * 3. The file now contained two blocks. The protocol text they reference was
4610
+ * loaded into every agent's context twice.
4611
+ * 4. `cleo doctor` reported "markers unbalanced" and prescribed `cleo upgrade`
4612
+ * — which ran `inject()` again and could only make it worse.
4613
+ *
4614
+ * That ratchet was observed in the wild on `~/.agents/AGENTS.md`, which had
4615
+ * accumulated three blocks from two separate single-byte losses.
4616
+ *
4617
+ * {@link normalizeMarkers} breaks the loop by healing near-miss markers back to
4618
+ * canonical form *before* any decision is made about the file, so a damaged
4619
+ * block is recognised and replaced instead of duplicated.
4620
+ *
4621
+ * @task T12051
4491
4622
  */
4492
-
4493
4623
  /**
4494
4624
  * A single parsed CAAMP block extracted from a file.
4495
4625
  *
@@ -4505,6 +4635,209 @@ interface CaampBlock {
4505
4635
  /** Zero-based character offset immediately after the block in the file. */
4506
4636
  endIndex: number;
4507
4637
  }
4638
+ /**
4639
+ * Result of healing damaged markers in a string.
4640
+ *
4641
+ * @public
4642
+ */
4643
+ interface NormalizeResult {
4644
+ /** Content with every recognised marker rewritten to canonical form. */
4645
+ content: string;
4646
+ /** How many marker lines were rewritten. `0` means the input was already canonical. */
4647
+ repaired: number;
4648
+ }
4649
+ /**
4650
+ * Build a fresh global pattern matching a complete canonical CAAMP block.
4651
+ *
4652
+ * A new `RegExp` is returned on every call deliberately. A shared module-level
4653
+ * `RegExp` carrying the `g` flag holds a mutable `lastIndex`, so reusing one
4654
+ * across `.test()` or `.exec()` calls silently skips matches — a defect that
4655
+ * previously existed in `removeInjection`.
4656
+ *
4657
+ * @returns A new `RegExp` with the `g` flag; capture group 1 is the block body
4658
+ *
4659
+ * @example
4660
+ * ```typescript
4661
+ * for (const m of content.matchAll(blockPattern())) {
4662
+ * console.log(m[1]);
4663
+ * }
4664
+ * ```
4665
+ *
4666
+ * @public
4667
+ */
4668
+ declare function blockPattern(): RegExp;
4669
+ /**
4670
+ * Rewrite every damaged CAAMP marker line back to its canonical form.
4671
+ *
4672
+ * Only whole lines are considered, so prose that merely mentions a marker is
4673
+ * left alone. Lines that are already canonical are matched but rewritten to an
4674
+ * identical string, and therefore are not counted as repairs.
4675
+ *
4676
+ * @param content - Raw file contents
4677
+ * @returns The healed content and the number of marker lines actually changed
4678
+ *
4679
+ * @example
4680
+ * ```typescript
4681
+ * const { content, repaired } = normalizeMarkers(await readFile(p, "utf-8"));
4682
+ * if (repaired > 0) console.log(`healed ${repaired} damaged marker(s)`);
4683
+ * ```
4684
+ *
4685
+ * @public
4686
+ */
4687
+ declare function normalizeMarkers(content: string): NormalizeResult;
4688
+ /**
4689
+ * Parse every canonical CAAMP block out of a file's contents.
4690
+ *
4691
+ * Blocks are returned in order of appearance. An opening marker with no
4692
+ * matching closing marker is skipped rather than throwing, so a corrupted file
4693
+ * can still be inspected.
4694
+ *
4695
+ * Call {@link normalizeMarkers} first if the input may contain damaged markers
4696
+ * — this function is deliberately strict.
4697
+ *
4698
+ * @param fileContent - Raw text content of the file
4699
+ * @returns Array of parsed CAAMP blocks
4700
+ *
4701
+ * @example
4702
+ * ```typescript
4703
+ * const blocks = parseBlocks(await readFile(agentsMd, "utf-8"));
4704
+ * console.log(`${blocks.length} block(s)`);
4705
+ * ```
4706
+ *
4707
+ * @public
4708
+ */
4709
+ declare function parseBlocks(fileContent: string): CaampBlock[];
4710
+ /**
4711
+ * Wrap content in canonical CAAMP markers.
4712
+ *
4713
+ * @param content - Body of the block
4714
+ * @returns The full block, markers included
4715
+ *
4716
+ * @example
4717
+ * ```typescript
4718
+ * buildBlock("@AGENTS.md");
4719
+ * // "<!-- CAAMP:START -->\n@AGENTS.md\n<!-- CAAMP:END -->"
4720
+ * ```
4721
+ *
4722
+ * @public
4723
+ */
4724
+ declare function buildBlock(content: string): string;
4725
+ /**
4726
+ * Where {@link reconcile} places the block when the file has none yet.
4727
+ *
4728
+ * Only applies to a file that has no CAAMP block at all — an existing block is
4729
+ * always replaced where it already is, never moved.
4730
+ *
4731
+ * @public
4732
+ */
4733
+ type BlockInsertPosition = 'prepend' | 'append';
4734
+ /**
4735
+ * Outcome of reconciling a file's contents against the desired CAAMP block.
4736
+ *
4737
+ * @public
4738
+ */
4739
+ interface ReconcileResult {
4740
+ /** The file contents that should be on disk. */
4741
+ content: string;
4742
+ /** Number of blocks found before reconciliation. */
4743
+ blocksBefore: number;
4744
+ /** Number of damaged marker lines healed. */
4745
+ repaired: number;
4746
+ }
4747
+ /**
4748
+ * Reconcile a file's contents so it contains exactly one canonical CAAMP block
4749
+ * carrying `desiredContent`.
4750
+ *
4751
+ * The rules, in order:
4752
+ *
4753
+ * 1. Damaged markers are healed first, so a corrupted block is recognised as a
4754
+ * block rather than treated as absent.
4755
+ * 2. If the file has no block, one is inserted — at the top by default, or at
4756
+ * the bottom when `insert` is `'append'` (which is what the Pi harness has
4757
+ * always done for its own `AGENTS.md`).
4758
+ * 3. If the file has one or more blocks, the **first** is replaced in place and
4759
+ * any others are removed. Replacing in place matters: prepending instead
4760
+ * would walk the block up the file on every run, and would separate it from
4761
+ * 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.
4767
+ *
4768
+ * This function is pure — it performs no I/O, which is what makes the
4769
+ * behaviour straightforward to test exhaustively.
4770
+ *
4771
+ * @param existing - Current file contents
4772
+ * @param desiredContent - Body the single surviving block should carry
4773
+ * @param insert - Placement when the file has no block yet
4774
+ * @returns The reconciled content plus what was found on the way
4775
+ *
4776
+ * @example
4777
+ * ```typescript
4778
+ * const { content, blocksBefore, repaired } = reconcile(onDisk, "@AGENTS.md");
4779
+ * if (content !== onDisk) await writeFileAtomic(path, content);
4780
+ * ```
4781
+ *
4782
+ * @public
4783
+ */
4784
+ declare function reconcile(existing: string, desiredContent: string, insert?: BlockInsertPosition): ReconcileResult;
4785
+ /**
4786
+ * Merge the bodies of several CAAMP blocks into one, preserving order and
4787
+ * dropping exact duplicate lines.
4788
+ *
4789
+ * Used by repair, which — unlike injection — does not know what the block
4790
+ * *should* contain. Keeping the union rather than picking a winner means no
4791
+ * reference is silently dropped when two blocks legitimately differ (a project
4792
+ * block carrying `@AGENTS.md` and a global one carrying
4793
+ * `@~/.agents/AGENTS.md`, for instance).
4794
+ *
4795
+ * @param blocks - Blocks to merge, in file order
4796
+ * @returns The merged body
4797
+ *
4798
+ * @example
4799
+ * ```typescript
4800
+ * mergeBlockBodies(parseBlocks(content));
4801
+ * // "@AGENTS.md\n@~/.agents/AGENTS.md"
4802
+ * ```
4803
+ *
4804
+ * @public
4805
+ */
4806
+ declare function mergeBlockBodies(blocks: readonly CaampBlock[]): string;
4807
+ /**
4808
+ * Restore a file to exactly one well-formed CAAMP block without needing to
4809
+ * know what that block should contain.
4810
+ *
4811
+ * This is what `cleo caamp repair` and `cleo doctor` use. Injection knows the
4812
+ * desired body and calls {@link reconcile}; repair does not, so it derives the
4813
+ * surviving body from what is already there via {@link mergeBlockBodies}.
4814
+ *
4815
+ * Deriving rather than deduplicating matters: the previous repair removed only
4816
+ * blocks with *identical* bodies, so a file with two blocks carrying different
4817
+ * references was reported as "2 blocks (expected 1)" by the health check and
4818
+ * then left untouched by the repair the health check prescribed — an
4819
+ * unfixable warning loop.
4820
+ *
4821
+ * @param existing - Current file contents
4822
+ * @returns The repaired content plus what was found on the way
4823
+ *
4824
+ * @example
4825
+ * ```typescript
4826
+ * const { content, blocksBefore, repaired } = repairContent(onDisk);
4827
+ * ```
4828
+ *
4829
+ * @public
4830
+ */
4831
+ declare function repairContent(existing: string): ReconcileResult;
4832
+
4833
+ /**
4834
+ * Marker-based instruction file injection
4835
+ *
4836
+ * Injects content blocks between CAAMP markers in instruction files
4837
+ * (CLAUDE.md, AGENTS.md, GEMINI.md) and agent-definition files
4838
+ * (cleo-subagent.md, seed agent profiles) per provider's native folder.
4839
+ */
4840
+
4508
4841
  /**
4509
4842
  * Parse all CAAMP blocks from a file's content string.
4510
4843
  *
@@ -4515,6 +4848,15 @@ interface CaampBlock {
4515
4848
  * @param fileContent - Raw text content of the file
4516
4849
  * @returns Array of parsed CAAMP blocks
4517
4850
  *
4851
+ * @remarks
4852
+ * Strict: a block whose marker has been damaged (for example a lost `<`) is
4853
+ * not seen. Run {@link normalizeMarkers} first when the input may be corrupt.
4854
+ *
4855
+ * @example
4856
+ * ```typescript
4857
+ * const blocks = parseCaampBlocks(await readFile(agentsMd, "utf-8"));
4858
+ * ```
4859
+ *
4518
4860
  * @public
4519
4861
  */
4520
4862
  declare function parseCaampBlocks(fileContent: string): CaampBlock[];
@@ -4532,6 +4874,13 @@ interface DedupeResult {
4532
4874
  kept: number;
4533
4875
  /** `true` if the file was modified on disk; `false` if it was already clean. */
4534
4876
  modified: boolean;
4877
+ /**
4878
+ * Number of damaged marker lines healed back to canonical form.
4879
+ *
4880
+ * Non-zero means the file had corruption that the strict block pattern could
4881
+ * not see — the condition that used to make duplicates accumulate invisibly.
4882
+ */
4883
+ repaired: number;
4535
4884
  }
4536
4885
  /**
4537
4886
  * Deduplicate CAAMP blocks in a file by content.
@@ -4585,6 +4934,75 @@ declare function dedupeFile(filePath: string): Promise<DedupeResult>;
4585
4934
  * @public
4586
4935
  */
4587
4936
  declare function dedupeFiles(filePaths: string[]): Promise<DedupeResult[]>;
4937
+ /**
4938
+ * Every instruction file CAAMP may have written to, for a given project.
4939
+ *
4940
+ * Covers three tiers, because corruption in any one of them affects every
4941
+ * agent session:
4942
+ *
4943
+ * 1. The **global hub** `~/.agents/AGENTS.md` — the highest-risk file in the
4944
+ * system. Every `cleo init`, `cleo upgrade` and `cleo doctor` run rewrites
4945
+ * it regardless of which project invoked them, and until T12051 no health
4946
+ * check looked at it at all.
4947
+ * 2. The project's own `AGENTS.md`, `CLAUDE.md` and `GEMINI.md`.
4948
+ * 3. Each detected provider's global instruction file.
4949
+ *
4950
+ * Paths are de-duplicated and returned whether or not they exist; callers skip
4951
+ * missing ones.
4952
+ *
4953
+ * @param projectDir - Absolute path to the project directory
4954
+ * @param providers - Detected providers whose global files should be included
4955
+ * @returns De-duplicated absolute paths, global hub first
4956
+ *
4957
+ * @example
4958
+ * ```typescript
4959
+ * const paths = instructionFileCascade("/project", getInstalledProviders());
4960
+ * const results = await dedupeFiles(paths);
4961
+ * ```
4962
+ *
4963
+ * @public
4964
+ */
4965
+ declare function instructionFileCascade(projectDir: string, providers: Provider[]): string[];
4966
+ /**
4967
+ * Summary of a repair sweep across instruction files.
4968
+ *
4969
+ * @public
4970
+ */
4971
+ interface RepairResult {
4972
+ /** Per-file outcomes, in cascade order. Files that do not exist are omitted. */
4973
+ files: DedupeResult[];
4974
+ /** Total damaged marker lines healed across all files. */
4975
+ repaired: number;
4976
+ /** Total duplicate blocks removed across all files. */
4977
+ removed: number;
4978
+ /** How many files were actually rewritten. */
4979
+ filesModified: number;
4980
+ }
4981
+ /**
4982
+ * Heal damaged CAAMP markers and collapse duplicate blocks across a project's
4983
+ * whole instruction-file cascade.
4984
+ *
4985
+ * This is the repair that `cleo doctor` prescribes. It is deliberately
4986
+ * content-agnostic — it does not need to know what *should* be inside the
4987
+ * block, so it can restore a file to a well-formed single-block state without
4988
+ * a provider registry lookup or a template refresh.
4989
+ *
4990
+ * Safe to run repeatedly: a healthy cascade reports `repaired: 0`,
4991
+ * `removed: 0`, `filesModified: 0` and performs no writes.
4992
+ *
4993
+ * @param projectDir - Absolute path to the project directory
4994
+ * @param providers - Detected providers whose global files should be included
4995
+ * @returns Aggregate repair summary
4996
+ *
4997
+ * @example
4998
+ * ```typescript
4999
+ * const result = await repairInstructionFiles("/project", getInstalledProviders());
5000
+ * console.log(`healed ${result.repaired} marker(s), removed ${result.removed} duplicate(s)`);
5001
+ * ```
5002
+ *
5003
+ * @public
5004
+ */
5005
+ declare function repairInstructionFiles(projectDir: string, providers: Provider[]): Promise<RepairResult>;
4588
5006
  /**
4589
5007
  * Check the status of a CAAMP injection block in an instruction file.
4590
5008
  *
@@ -4618,7 +5036,8 @@ declare function checkInjection(filePath: string, expectedContent?: string): Pro
4618
5036
  * Behavior depends on the file state:
4619
5037
  * - File does not exist: creates the file with the injection block → `"created"`
4620
5038
  * - File exists without markers: prepends the injection block → `"added"`
4621
- * - File exists with multiple markers (duplicates): consolidates into single block → `"consolidated"`
5039
+ * - File exists with a damaged marker: heals it and replaces in place → `"repaired"`
5040
+ * - File exists with multiple markers (duplicates): consolidates into a single block → `"consolidated"`
4622
5041
  * - File exists with markers, content differs: replaces the block → `"updated"`
4623
5042
  * - File exists with markers, content matches: no-op → `"intact"`
4624
5043
  *
@@ -4627,11 +5046,21 @@ declare function checkInjection(filePath: string, expectedContent?: string): Pro
4627
5046
  *
4628
5047
  * @param filePath - Absolute path to the instruction file
4629
5048
  * @param content - Content to inject between CAAMP markers
4630
- * @returns Action taken: `"created"`, `"added"`, `"consolidated"`, `"updated"`, or `"intact"`
5049
+ * @returns The {@link CaampInjectionAction} describing what was done
4631
5050
  *
4632
5051
  * @remarks
4633
- * Handles duplicate marker consolidation automatically. When multiple CAAMP
4634
- * blocks are detected (from manual edits or bugs), they are merged into one.
5052
+ * Damaged markers are healed *before* the file is classified. This is what
5053
+ * stops a single lost character from ratcheting into duplicate blocks: prior
5054
+ * to T12051 a marker that lost its leading `<` was invisible to the block
5055
+ * pattern, so this function concluded the file had no block and prepended a
5056
+ * second one — permanently doubling the injected protocol text, and doubling
5057
+ * again on the next mishap.
5058
+ *
5059
+ * The whole read-modify-write cycle runs under a cross-process lock and the
5060
+ * write itself is atomic, because the busiest target — `~/.agents/AGENTS.md` —
5061
+ * is rewritten by every project on the machine.
5062
+ *
5063
+ * All text outside the CAAMP markers is preserved verbatim.
4635
5064
  *
4636
5065
  * @example
4637
5066
  * ```typescript
@@ -4641,7 +5070,7 @@ declare function checkInjection(filePath: string, expectedContent?: string): Pro
4641
5070
  *
4642
5071
  * @public
4643
5072
  */
4644
- declare function inject(filePath: string, content: string): Promise<'created' | 'added' | 'consolidated' | 'updated' | 'intact'>;
5073
+ declare function inject(filePath: string, content: string): Promise<CaampInjectionAction>;
4645
5074
  /**
4646
5075
  * Remove the CAAMP injection block from an instruction file.
4647
5076
  *
@@ -4654,6 +5083,9 @@ declare function inject(filePath: string, content: string): Promise<'created' |
4654
5083
  * Cleans up any leftover blank lines after removing the block. If the file
4655
5084
  * would be entirely empty after removal, the file itself is deleted.
4656
5085
  *
5086
+ * Blocks whose markers are damaged are healed first, so uninstall removes them
5087
+ * too rather than leaving orphaned fragments behind.
5088
+ *
4657
5089
  * @example
4658
5090
  * ```typescript
4659
5091
  * const removed = await removeInjection("/project/CLAUDE.md");
@@ -4712,7 +5144,7 @@ declare function checkAllInjections(providers: Provider[], projectDir: string, s
4712
5144
  *
4713
5145
  * @public
4714
5146
  */
4715
- declare function injectAll(providers: Provider[], projectDir: string, scope: 'project' | 'global', content: string): Promise<Map<string, 'created' | 'added' | 'consolidated' | 'updated' | 'intact'>>;
5147
+ declare function injectAll(providers: Provider[], projectDir: string, scope: 'project' | 'global', content: string): Promise<Map<string, CaampInjectionAction>>;
4716
5148
  /**
4717
5149
  * Options for ensuring a provider instruction file.
4718
5150
  *
@@ -4745,7 +5177,7 @@ interface EnsureProviderInstructionFileResult {
4745
5177
  /** Instruction file name from the provider registry. */
4746
5178
  instructFile: string;
4747
5179
  /** Action taken. */
4748
- action: 'created' | 'added' | 'consolidated' | 'updated' | 'intact';
5180
+ action: CaampInjectionAction;
4749
5181
  /** Provider ID. */
4750
5182
  providerId: string;
4751
5183
  }
@@ -4859,7 +5291,7 @@ interface WriteAgentFileResult {
4859
5291
  /** Absolute path to the written agent-definition file. */
4860
5292
  filePath: string;
4861
5293
  /** Action taken. */
4862
- action: 'created' | 'added' | 'consolidated' | 'updated' | 'intact';
5294
+ action: CaampInjectionAction;
4863
5295
  }
4864
5296
  /**
4865
5297
  * Options for writing agent-definition files to provider agent folders.
@@ -8449,4 +8881,4 @@ declare function parseSource(input: string): ParsedSource;
8449
8881
  */
8450
8882
  declare function isMarketplaceScoped(input: string): boolean;
8451
8883
 
8452
- export { type AuditFinding, type AuditResult, type AuditRule, type AuditSeverity, type BatchInstallOptions, type BatchInstallResult, 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 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, 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, _resetPlatformPathsCache, 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, 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, scanDirectory, scanFile, scoreSkillRecommendation, searchSkills, selectProvidersByMinimumPriority, setExclusivityMode, setQuiet, setVerbose, shouldOverrideSkill, supportsHook, toCanonical, toNative, toNativeBatch, toSarif, tokenizeCriteriaValue, translateToAll, updateInstructionsSingleOperation, validateInstructionIntegrity, validateRecommendationCriteria, validateSkill, writeAgentFileToAllProviders, writeConfig };
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 };
package/dist/index.js CHANGED
@@ -71,8 +71,11 @@ import {
71
71
  validateRecommendationCriteria,
72
72
  validateSkill,
73
73
  writeConfig
74
- } from "./chunk-QUAT7JH6.js";
74
+ } from "./chunk-6KBUDJZI.js";
75
75
  import {
76
+ assertNotTornRead,
77
+ blockPattern,
78
+ buildBlock,
76
79
  buildInjectionContent,
77
80
  buildSkillsMap,
78
81
  checkAllInjections,
@@ -104,14 +107,22 @@ import {
104
107
  groupByInstructFile,
105
108
  inject,
106
109
  injectAll,
110
+ instructionFileCascade,
111
+ mergeBlockBodies,
112
+ normalizeMarkers,
113
+ parseBlocks,
107
114
  parseCaampBlocks,
108
115
  parseInjectionContent,
109
116
  providerSupports,
110
117
  providerSupportsById,
118
+ reconcile,
111
119
  removeInjection,
120
+ repairContent,
121
+ repairInstructionFiles,
112
122
  resolveAlias,
123
+ withFileLock,
113
124
  writeAgentFileToAllProviders
114
- } from "./chunk-EH5U4PRC.js";
125
+ } from "./chunk-3IQUGSIV.js";
115
126
  import {
116
127
  CANONICAL_HOOK_EVENTS,
117
128
  HOOK_CATEGORIES,
@@ -274,7 +285,7 @@ function shouldOverrideSkill(skillName, incomingSource, existingEntry) {
274
285
  return true;
275
286
  }
276
287
  async function validateInstructionIntegrity(providers, projectDir, scope, expectedContent) {
277
- const { checkAllInjections: checkAllInjections2 } = await import("./injector-3TOVIDBO.js");
288
+ const { checkAllInjections: checkAllInjections2 } = await import("./injector-6YGSK3B6.js");
278
289
  const results = await checkAllInjections2(providers, projectDir, scope, expectedContent);
279
290
  const issues = [];
280
291
  for (const result of results) {
@@ -310,6 +321,9 @@ export {
310
321
  PiRequiredError,
311
322
  RECOMMENDATION_ERROR_CODES,
312
323
  _resetPlatformPathsCache,
324
+ assertNotTornRead,
325
+ blockPattern,
326
+ buildBlock,
313
327
  buildHookMatrix,
314
328
  buildInjectionContent,
315
329
  buildLibraryFromFiles,
@@ -397,6 +411,7 @@ export {
397
411
  installBatchWithRollback,
398
412
  installMcpServer,
399
413
  installSkill,
414
+ instructionFileCascade,
400
415
  isCaampOwnedSkill,
401
416
  isExclusivityMode,
402
417
  isMarketplaceScoped,
@@ -406,7 +421,10 @@ export {
406
421
  listCanonicalSkills,
407
422
  listMcpServers,
408
423
  loadLibraryFromModule,
424
+ mergeBlockBodies,
425
+ normalizeMarkers,
409
426
  normalizeRecommendationCriteria,
427
+ parseBlocks,
410
428
  parseCaampBlocks,
411
429
  parseInjectionContent,
412
430
  parseSkillFile,
@@ -416,6 +434,7 @@ export {
416
434
  rankSkills,
417
435
  readConfig,
418
436
  recommendSkills,
437
+ reconcile,
419
438
  recordSkillInstall,
420
439
  registerSkillLibrary,
421
440
  registerSkillLibraryFromPath,
@@ -425,6 +444,8 @@ export {
425
444
  removeMcpServerFromAll,
426
445
  removeSkill,
427
446
  removeSkillFromLock,
447
+ repairContent,
448
+ repairInstructionFiles,
428
449
  resetDetectionCache,
429
450
  resetExclusivityModeOverride,
430
451
  resolveAlias,
@@ -453,6 +474,7 @@ export {
453
474
  validateInstructionIntegrity,
454
475
  validateRecommendationCriteria,
455
476
  validateSkill,
477
+ withFileLock,
456
478
  writeAgentFileToAllProviders,
457
479
  writeConfig
458
480
  };