@cleocode/contracts 2026.7.2 → 2026.8.2

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.
@@ -0,0 +1,149 @@
1
+ /**
2
+ * CAAMP instruction-block marker grammar — the single source of truth.
3
+ *
4
+ * CAAMP delimits the region it owns inside an agent instruction file
5
+ * (`AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, …) with a pair of HTML comments:
6
+ *
7
+ * ```markdown
8
+ * <!-- CAAMP:START -->
9
+ * @~/.cleo/templates/CLEO-INJECTION.md
10
+ * <!-- CAAMP:END -->
11
+ * ```
12
+ *
13
+ * Before T12051 this grammar was re-declared as inline string and regex
14
+ * literals in at least five places — `caamp/core/instructions/injector.ts`,
15
+ * `caamp/core/harness/pi.ts`, `core/injection.ts`, `core/bootstrap.ts` and
16
+ * `core/validation/doctor/checks.ts`. Each copy recognised a slightly
17
+ * different dialect, so a file that one module considered healthy another
18
+ * considered corrupt.
19
+ *
20
+ * The grammar lives here, in the leaf package, because `@cleocode/core` and
21
+ * `@cleocode/caamp` depend on each other and therefore cannot import the
22
+ * grammar from one another without a cycle.
23
+ *
24
+ * This module is **const data only** — no runtime helpers — so it satisfies
25
+ * the contracts-purity gate (`scripts/lint-no-runtime-in-contracts.mjs`).
26
+ * The engine that consumes these patterns lives in
27
+ * `packages/caamp/src/core/instructions/markers.ts`.
28
+ *
29
+ * @task T12051
30
+ * @see {@link https://github.com/kryptobaseddev/cleo} ADR-064 (CAAMP↔Adapters boundary)
31
+ */
32
+
33
+ /** Canonical opening marker of a CAAMP-managed block. */
34
+ export const CAAMP_MARKER_START = '<!-- CAAMP:START -->';
35
+
36
+ /** Canonical closing marker of a CAAMP-managed block. */
37
+ export const CAAMP_MARKER_END = '<!-- CAAMP:END -->';
38
+
39
+ /**
40
+ * Source of the strict, canonical block pattern.
41
+ *
42
+ * Capture group 1 is the block's inner content. Build a fresh `RegExp` from
43
+ * this string at every use site — a module-level `RegExp` carrying the `g`
44
+ * flag keeps a mutable `lastIndex`, which silently skips matches when the
45
+ * same object is reused across `.test()` / `.exec()` calls.
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const pattern = new RegExp(CAAMP_BLOCK_PATTERN_SOURCE, 'g');
50
+ * ```
51
+ */
52
+ export const CAAMP_BLOCK_PATTERN_SOURCE = '<!-- CAAMP:START -->([\\s\\S]*?)<!-- CAAMP:END -->';
53
+
54
+ /**
55
+ * Horizontal-whitespace class used by the damage-tolerant marker patterns.
56
+ *
57
+ * Deliberately excludes `\n` so the patterns stay anchored to a single line
58
+ * and cannot swallow surrounding content.
59
+ */
60
+ const H = '[ \\t\\r]';
61
+
62
+ /**
63
+ * Build the source of a damage-tolerant, whole-line marker pattern.
64
+ *
65
+ * Recognises the canonical marker plus the near-miss forms produced when a
66
+ * delimiter character is lost or mangled — a truncated write, an editor that
67
+ * reflows HTML comments, a shell heredoc, a careless hand edit.
68
+ *
69
+ * Two constraints keep this from over-matching, both learned from an
70
+ * adversarial review that demonstrated content loss with a looser pattern:
71
+ *
72
+ * 1. The `CAAMP:<KEYWORD>` token must be the *entire* line, modulo comment
73
+ * punctuation and horizontal whitespace. Prose such as "the CAAMP:START
74
+ * marker is written by …" is therefore never matched.
75
+ * 2. Comment punctuation must be present on **both** sides — at least one of
76
+ * `< ! -` before the token and at least one of `- >` after it. A bare
77
+ * `CAAMP:START` line is deliberately NOT treated as a damaged marker: it is
78
+ * not a plausible outcome of losing one delimiter character, and accepting
79
+ * it caused a documentation fence that merely *mentioned* CAAMP to be
80
+ * rewritten into a real marker, silently swallowing the fenced body.
81
+ */
82
+ const damagedMarkerSource = (keyword: 'START' | 'END'): string =>
83
+ `^${H}*[<!-]{1,4}${H}*CAAMP${H}*:${H}*${keyword}${H}*[->]{1,3}${H}*$`;
84
+
85
+ /**
86
+ * Source of the damage-tolerant pattern matching an opening marker line.
87
+ *
88
+ * Use with the `gmi` flags. Matches all of:
89
+ *
90
+ * | Form | Damage |
91
+ * | ------------------------ | -------------------------- |
92
+ * | `<!-- CAAMP:START -->` | none (canonical) |
93
+ * | `!-- CAAMP:START -->` | leading `<` lost |
94
+ * | `<!-- CAAMP:START --` | trailing `>` lost |
95
+ * | `<!--CAAMP:START-->` | spaces collapsed |
96
+ * | `<!-- caamp:start -->` | case folded |
97
+ *
98
+ * A bare `CAAMP:START` with no comment punctuation at all is deliberately NOT
99
+ * matched — see {@link damagedMarkerSource}.
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const damaged = new RegExp(CAAMP_DAMAGED_START_PATTERN_SOURCE, 'gmi');
104
+ * const healed = content.replace(damaged, CAAMP_MARKER_START);
105
+ * ```
106
+ */
107
+ export const CAAMP_DAMAGED_START_PATTERN_SOURCE = damagedMarkerSource('START');
108
+
109
+ /**
110
+ * Source of the damage-tolerant pattern matching a closing marker line.
111
+ *
112
+ * Mirrors {@link CAAMP_DAMAGED_START_PATTERN_SOURCE} for `CAAMP:END`. Use
113
+ * with the `gmi` flags.
114
+ */
115
+ export const CAAMP_DAMAGED_END_PATTERN_SOURCE = damagedMarkerSource('END');
116
+
117
+ /**
118
+ * The outcome of writing a CAAMP block into an instruction file.
119
+ *
120
+ * Previously re-declared as an inline union in eight separate locations.
121
+ *
122
+ * - `created` — the file did not exist and was written from scratch.
123
+ * - `added` — the file existed with no CAAMP block; one was prepended.
124
+ * - `repaired` — at least one damaged marker was healed back to canonical form.
125
+ * - `consolidated` — several blocks were merged down to exactly one.
126
+ * - `updated` — a single block existed and its content changed.
127
+ * - `intact` — the file already had exactly the desired content; no write.
128
+ */
129
+ export type CaampInjectionAction =
130
+ | 'created'
131
+ | 'added'
132
+ | 'repaired'
133
+ | 'consolidated'
134
+ | 'updated'
135
+ | 'intact';
136
+
137
+ /**
138
+ * Every {@link CaampInjectionAction} value, ordered from most to least
139
+ * invasive. Useful for reporting the single most significant action taken
140
+ * across a batch of files.
141
+ */
142
+ export const CAAMP_INJECTION_ACTIONS = [
143
+ 'created',
144
+ 'added',
145
+ 'repaired',
146
+ 'consolidated',
147
+ 'updated',
148
+ 'intact',
149
+ ] as const;
@@ -33,6 +33,52 @@ import { z } from 'zod';
33
33
 
34
34
  import type { VerificationGate } from './task.js';
35
35
 
36
+ // ---------------------------------------------------------------------------
37
+ // Known atom kinds — shared typed constant for boundary detection + error
38
+ // messages. Prefer this list over duplicated regex strings.
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * Every evidence atom kind recognized by the parser.
43
+ *
44
+ * Defines the exhaustive set of `kind:` prefixes accepted by
45
+ * {@link parseEvidenceString}. Derived from {@link EvidenceAtomSchema}'s
46
+ * discriminated union. Shared so boundary-splitting regexes, documentation,
47
+ * and error messages always agree without duplicating the literal list.
48
+ *
49
+ * Note: `state` is a recognized parser prefix (modifier for `pr:`) but is
50
+ * NOT a standalone {@link EvidenceAtom} kind — it is consumed in-place by the
51
+ * parser and never emitted as an atom. The boundary regex MUST include it so
52
+ * `pr:357;state:MERGED` splits correctly.
53
+ *
54
+ * @task T12030
55
+ */
56
+ export const EVIDENCE_ATOM_KINDS = [
57
+ 'commit',
58
+ 'files',
59
+ 'test-run',
60
+ 'tool',
61
+ 'url',
62
+ 'note',
63
+ 'decision',
64
+ 'pr',
65
+ 'loc-drop',
66
+ 'callsite-coverage',
67
+ 'satisfies',
68
+ ] as const satisfies ReadonlyArray<EvidenceAtom['kind']>;
69
+
70
+ /** Parser-level prefixes — {@link EVIDENCE_ATOM_KINDS} + `state` modifier. */
71
+ const PARSER_PREFIXES = [...EVIDENCE_ATOM_KINDS, 'state'] as const;
72
+
73
+ /**
74
+ * Regex matching a `;` ONLY when the next non-whitespace token is a known
75
+ * atom kind prefix followed by `:`. Used instead of a naive `split(';')` so
76
+ * semicolons inside `note:` payloads are preserved.
77
+ *
78
+ * @task T12030
79
+ */
80
+ const KIND_BOUNDARY_REGEX = new RegExp(`;(?=\\s*(?:${PARSER_PREFIXES.join('|')})\\s*:)`, 'g');
81
+
36
82
  // ---------------------------------------------------------------------------
37
83
  // Per-atom Zod schemas
38
84
  // ---------------------------------------------------------------------------
@@ -714,8 +760,9 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
714
760
  );
715
761
  }
716
762
  const chunks = raw
717
- .split(';')
763
+ .split(KIND_BOUNDARY_REGEX)
718
764
  .map((s) => s.trim())
765
+ .map((s) => s.replace(/^;+/, '')) // strip leading separator artifacts (;;commit: → commit:)
719
766
  .filter(Boolean);
720
767
  if (chunks.length === 0) {
721
768
  throw new EvidenceParseError(
@@ -734,7 +781,11 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
734
781
  );
735
782
  }
736
783
  const kind = chunk.slice(0, colon).trim();
737
- const payload = chunk.slice(colon + 1).trim();
784
+ const rawPayload = chunk.slice(colon + 1).trim();
785
+ // Strip trailing separator semicolons from non-note atoms (the old naive
786
+ // split consumed them as empty chunks). Note payloads preserve every
787
+ // character, including trailing ';', so they act as real punctuation.
788
+ const payload = kind === 'note' ? rawPayload : rawPayload.replace(/;+$/, '');
738
789
  switch (kind) {
739
790
  case 'commit':
740
791
  atoms.push({ kind: 'commit', sha: payload });
@@ -930,7 +981,7 @@ export function parseEvidenceString(raw: string): EvidenceAtom[] {
930
981
  default:
931
982
  throw new EvidenceParseError(
932
983
  `Unknown evidence kind: "${kind}" in atom "${chunk}"`,
933
- 'Valid kinds: commit, files, test-run, tool, url, note, loc-drop, callsite-coverage, decision, pr, satisfies, state',
984
+ `Valid kinds: ${PARSER_PREFIXES.join(', ')}`,
934
985
  );
935
986
  }
936
987
  }
package/src/index.ts CHANGED
@@ -544,6 +544,7 @@ export {
544
544
  callsiteCoverageAtomSchema,
545
545
  commitAtomSchema,
546
546
  decisionAtomSchema,
547
+ EVIDENCE_ATOM_KINDS,
547
548
  EvidenceAtomSchema,
548
549
  EvidenceParseError,
549
550
  filesAtomSchema,
@@ -2652,4 +2653,16 @@ export {
2652
2653
  workGraphSliceParamsSchema,
2653
2654
  workGraphSliceSchema,
2654
2655
  } from './workgraph.js';
2656
+
2655
2657
  // === WASM SDK (Rust crate bindings) ===
2658
+
2659
+ export type { CaampInjectionAction } from './caamp-markers.js';
2660
+ // === CAAMP instruction-block marker grammar (T12051) ===
2661
+ export {
2662
+ CAAMP_BLOCK_PATTERN_SOURCE,
2663
+ CAAMP_DAMAGED_END_PATTERN_SOURCE,
2664
+ CAAMP_DAMAGED_START_PATTERN_SOURCE,
2665
+ CAAMP_INJECTION_ACTIONS,
2666
+ CAAMP_MARKER_END,
2667
+ CAAMP_MARKER_START,
2668
+ } from './caamp-markers.js';
@@ -55,6 +55,14 @@ export type FileNamingConvention = 'kebab-case' | 'snake_case' | 'camelCase' | '
55
55
  /** Module import style. */
56
56
  export type ImportStyle = 'esm' | 'commonjs' | 'mixed';
57
57
 
58
+ /** Release and evidence behavior configured per project. */
59
+ export interface ProjectReleaseContext {
60
+ /** Branches whose reachable commits may satisfy `commit:` evidence atoms. */
61
+ integrationBranches?: string[];
62
+ /** Required successful checks for `pr:` evidence atoms. */
63
+ prRequiredWorkflows?: string[];
64
+ }
65
+
58
66
  /** Schema-compliant project context for LLM agent consumption. */
59
67
  export interface ProjectContext {
60
68
  schemaVersion: string;
@@ -91,6 +99,28 @@ export interface ProjectContext {
91
99
  commonPatterns?: string[];
92
100
  avoidPatterns?: string[];
93
101
  };
102
+ /** Lint command override (T12026). */
103
+ lint?: {
104
+ /** Command to run static analysis / lint checks. */
105
+ command?: string;
106
+ };
107
+ /** Type-check command override (T12026). */
108
+ typecheck?: {
109
+ /** Command to run type checking. */
110
+ command?: string;
111
+ };
112
+ /** Audit command override (T12026). */
113
+ audit?: {
114
+ /** Command to run dependency/module audit checks. */
115
+ command?: string;
116
+ };
117
+ /** Security-scan command override (T12026). JSON key is `security-scan`. */
118
+ 'security-scan'?: {
119
+ /** Command to run security vulnerability scanning. */
120
+ command?: string;
121
+ };
122
+ /** Release and evidence behavior for this project. */
123
+ release?: ProjectReleaseContext;
94
124
  }
95
125
 
96
126
  /**