@cleocode/cant 2026.4.84 → 2026.4.86

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/bundle.d.ts CHANGED
@@ -22,6 +22,7 @@
22
22
  * ```
23
23
  */
24
24
  import type { CantDocumentResult } from './document.js';
25
+ import type { CantAgentV3 } from './types.js';
25
26
  /**
26
27
  * A single parsed `.cant` document with its source path and diagnostics.
27
28
  *
@@ -80,6 +81,21 @@ export interface AgentEntry {
80
81
  /** Simplified agent properties (role, tier, prompt, skills, etc.). */
81
82
  properties: Record<string, unknown>;
82
83
  }
84
+ /**
85
+ * Agent entry extended with a fully-typed {@link CantAgentV3} projection.
86
+ *
87
+ * @remarks
88
+ * T889 Wave 1 (W1-2) surface. `typed` is `null` when the entry could not be
89
+ * mapped to the v3 shape (for example, the AST kind is not `agent` or required
90
+ * fields are missing). When `typed` is present, the entry satisfies the
91
+ * {@link isCantAgentV3} structural guard and carries v1/v2-backward-compatible
92
+ * defaults (`tier: 'mid'`, `contextSources: []`, etc.) merged on top of any
93
+ * values discovered in the `.cant` source.
94
+ */
95
+ export interface TypedAgentEntry extends AgentEntry {
96
+ /** Typed v3 projection of this agent entry, or `null` when mapping failed. */
97
+ typed: CantAgentV3 | null;
98
+ }
83
99
  /**
84
100
  * A team declaration extracted from a compiled `.cant` file.
85
101
  *
@@ -123,8 +139,17 @@ export interface ToolEntry {
123
139
  export interface CompiledBundle {
124
140
  /** All successfully parsed documents, keyed by source path. */
125
141
  documents: Map<string, ParsedCantDocument>;
126
- /** Agents found across all documents. */
127
- agents: AgentEntry[];
142
+ /**
143
+ * Agents found across all documents.
144
+ *
145
+ * @remarks
146
+ * Each entry includes a `typed: CantAgentV3 | null` field that carries
147
+ * the fully-typed v3 projection (populated via {@link toCantAgentV3}).
148
+ * Older consumers that only read `name`, `sourcePath`, and `properties`
149
+ * remain source-compatible because {@link TypedAgentEntry} extends
150
+ * {@link AgentEntry}.
151
+ */
152
+ agents: TypedAgentEntry[];
128
153
  /** Teams found across all documents. */
129
154
  teams: TeamEntry[];
130
155
  /** Tools found across all documents. */
@@ -136,6 +161,30 @@ export interface CompiledBundle {
136
161
  /** Render the compiled bundle as a system prompt addendum. */
137
162
  renderSystemPrompt(): string;
138
163
  }
164
+ /**
165
+ * Project an {@link AgentEntry} into the fully-typed {@link CantAgentV3}
166
+ * surface, filling v1/v2-backward-compatible defaults for fields the source
167
+ * `.cant` file omits.
168
+ *
169
+ * @remarks
170
+ * Defaults applied when the source file does not declare them:
171
+ *
172
+ * - `tier`: `'mid'`
173
+ * - `contextSources`: `[]`
174
+ * - `onOverflow`: `'escalate_tier'`
175
+ * - `mentalModelRef`: `null`
176
+ * - `contracts`: `{ requires: [], ensures: [] }`
177
+ *
178
+ * Returns `null` when `entry.name` is empty (the AST kind wasn't `agent` or
179
+ * the name field was missing), so callers can distinguish "mapped" from
180
+ * "not an agent".
181
+ *
182
+ * @param entry - The {@link AgentEntry} extracted by {@link compileBundle}.
183
+ * @param sourcePath - Absolute path to the source `.cant` file (used to
184
+ * populate {@link CantAgentV3.sourcePath}).
185
+ * @returns A {@link CantAgentV3} projection, or `null` when mapping fails.
186
+ */
187
+ export declare function toCantAgentV3(entry: AgentEntry, sourcePath: string): CantAgentV3 | null;
139
188
  /**
140
189
  * Compile a list of `.cant` files into a unified {@link CompiledBundle}.
141
190
  *
package/dist/bundle.js CHANGED
@@ -23,6 +23,7 @@
23
23
  * ```
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.toCantAgentV3 = toCantAgentV3;
26
27
  exports.compileBundle = compileBundle;
27
28
  const document_js_1 = require("./document.js");
28
29
  /**
@@ -121,6 +122,253 @@ function extractAgents(doc, sourcePath) {
121
122
  }
122
123
  return agents;
123
124
  }
125
+ /** Valid {@link CantTier} string values. */
126
+ const VALID_TIERS = ['low', 'mid', 'high'];
127
+ /** Valid {@link CantOverflowStrategy} string values. */
128
+ const VALID_OVERFLOW = ['escalate_tier', 'fail'];
129
+ /**
130
+ * Coerce a simplified AST value to a plain string, or `undefined` if the value
131
+ * cannot be safely represented as one. Accepts raw strings, numbers, and the
132
+ * stringified form of booleans; other shapes (objects, arrays) return
133
+ * `undefined`.
134
+ */
135
+ function coerceString(value) {
136
+ if (typeof value === 'string')
137
+ return value;
138
+ if (typeof value === 'number' || typeof value === 'boolean')
139
+ return String(value);
140
+ return undefined;
141
+ }
142
+ /**
143
+ * Coerce a simplified AST value to an array of strings. Returns an empty array
144
+ * for non-array inputs. Array members are coerced via {@link coerceString} and
145
+ * dropped if they cannot be represented as strings.
146
+ */
147
+ function coerceStringArray(value) {
148
+ if (!Array.isArray(value))
149
+ return [];
150
+ const out = [];
151
+ for (const item of value) {
152
+ const s = coerceString(item);
153
+ if (typeof s === 'string')
154
+ out.push(s);
155
+ }
156
+ return out;
157
+ }
158
+ /**
159
+ * Map the raw `permissions` property (either a domain-access record or the
160
+ * tool-permissions scalar map) to the flat `Record<string, string>` required
161
+ * by {@link CantAgentV3}. Array access lists are joined with `, ` so downstream
162
+ * consumers keep the v1/v2 "tasks: read, write" wire format.
163
+ */
164
+ function extractV3Permissions(value) {
165
+ const result = {};
166
+ if (typeof value !== 'object' || value === null)
167
+ return result;
168
+ const rec = value;
169
+ for (const [domain, access] of Object.entries(rec)) {
170
+ if (Array.isArray(access)) {
171
+ const parts = coerceStringArray(access);
172
+ if (parts.length > 0)
173
+ result[domain] = parts.join(', ');
174
+ }
175
+ else {
176
+ const s = coerceString(access);
177
+ if (typeof s === 'string')
178
+ result[domain] = s;
179
+ }
180
+ }
181
+ return result;
182
+ }
183
+ /**
184
+ * Map the raw `contracts` property to a {@link CantContractBlock}.
185
+ *
186
+ * @remarks
187
+ * The Wave 0 grammar does not yet recognize `contracts:` blocks, so today
188
+ * this helper returns `null` unless the property is already shaped as
189
+ * `{ requires: string[], ensures: string[] }`. When the grammar adds the
190
+ * block, this helper will round-trip the richer shape without breaking
191
+ * existing callers.
192
+ */
193
+ function extractContracts(value) {
194
+ const empty = { requires: [], ensures: [] };
195
+ if (typeof value !== 'object' || value === null)
196
+ return empty;
197
+ const rec = value;
198
+ const toClauses = (raw) => {
199
+ if (!Array.isArray(raw))
200
+ return [];
201
+ const out = [];
202
+ for (const item of raw) {
203
+ const text = coerceString(item);
204
+ if (typeof text === 'string' && text.length > 0)
205
+ out.push({ text });
206
+ }
207
+ return out;
208
+ };
209
+ return {
210
+ requires: toClauses(rec['requires']),
211
+ ensures: toClauses(rec['ensures']),
212
+ };
213
+ }
214
+ /**
215
+ * Map the raw `context_sources` property value to an array of
216
+ * {@link CantContextSourceDef}. Supports the list form
217
+ * `[{source, query, max_entries}]` today; dict form (`patterns: {...}`) is
218
+ * flattened by the Wave 0 parser into sibling properties and therefore
219
+ * requires a follow-up grammar pass to reconstruct. Returns an empty array
220
+ * when the value is absent, malformed, or dict-flattened.
221
+ */
222
+ function extractContextSources(value) {
223
+ if (!Array.isArray(value))
224
+ return [];
225
+ const out = [];
226
+ for (const item of value) {
227
+ if (typeof item !== 'object' || item === null)
228
+ continue;
229
+ const rec = item;
230
+ const source = coerceString(rec['source']);
231
+ const query = coerceString(rec['query']);
232
+ const maxRaw = rec['maxEntries'] ?? rec['max_entries'] ?? rec['max'];
233
+ const maxEntries = typeof maxRaw === 'number'
234
+ ? maxRaw
235
+ : typeof maxRaw === 'string'
236
+ ? Number.parseInt(maxRaw, 10)
237
+ : Number.NaN;
238
+ if (typeof source === 'string' &&
239
+ typeof query === 'string' &&
240
+ Number.isFinite(maxEntries) &&
241
+ maxEntries > 0) {
242
+ out.push({ source, query, maxEntries });
243
+ }
244
+ }
245
+ return out;
246
+ }
247
+ /**
248
+ * Project an {@link AgentEntry} into the fully-typed {@link CantAgentV3}
249
+ * surface, filling v1/v2-backward-compatible defaults for fields the source
250
+ * `.cant` file omits.
251
+ *
252
+ * @remarks
253
+ * Defaults applied when the source file does not declare them:
254
+ *
255
+ * - `tier`: `'mid'`
256
+ * - `contextSources`: `[]`
257
+ * - `onOverflow`: `'escalate_tier'`
258
+ * - `mentalModelRef`: `null`
259
+ * - `contracts`: `{ requires: [], ensures: [] }`
260
+ *
261
+ * Returns `null` when `entry.name` is empty (the AST kind wasn't `agent` or
262
+ * the name field was missing), so callers can distinguish "mapped" from
263
+ * "not an agent".
264
+ *
265
+ * @param entry - The {@link AgentEntry} extracted by {@link compileBundle}.
266
+ * @param sourcePath - Absolute path to the source `.cant` file (used to
267
+ * populate {@link CantAgentV3.sourcePath}).
268
+ * @returns A {@link CantAgentV3} projection, or `null` when mapping fails.
269
+ */
270
+ function toCantAgentV3(entry, sourcePath) {
271
+ if (typeof entry.name !== 'string' || entry.name.length === 0)
272
+ return null;
273
+ const props = entry.properties;
274
+ const tierRaw = coerceString(props['tier']);
275
+ const tier = tierRaw !== undefined && VALID_TIERS.includes(tierRaw)
276
+ ? tierRaw
277
+ : 'mid';
278
+ const overflowRaw = coerceString(props['on_overflow'] ?? props['onOverflow']);
279
+ const onOverflow = overflowRaw !== undefined && VALID_OVERFLOW.includes(overflowRaw)
280
+ ? overflowRaw
281
+ : 'escalate_tier';
282
+ const mentalModelRef = null;
283
+ const version = coerceString(props['version']) ?? '1';
284
+ const role = coerceString(props['role']) ?? '';
285
+ const description = coerceString(props['description']) ?? '';
286
+ const prompt = coerceString(props['prompt']) ?? '';
287
+ const skills = coerceStringArray(props['skills']);
288
+ const permissions = extractV3Permissions(props['permissions']);
289
+ const contextSources = extractContextSources(props['context_sources'] ?? props['contextSources']);
290
+ const contracts = extractContracts(props['contracts']);
291
+ const model = coerceString(props['model']);
292
+ const persistRaw = props['persist'];
293
+ const persist = typeof persistRaw === 'boolean'
294
+ ? persistRaw
295
+ : typeof persistRaw === 'string'
296
+ ? persistRaw
297
+ : undefined;
298
+ const parent = coerceString(props['parent']);
299
+ const consultWhen = coerceString(props['consult-when'] ?? props['consultWhen']);
300
+ const workers = coerceStringArray(props['workers']);
301
+ const stages = coerceStringArray(props['stages']);
302
+ const deprecatedRaw = props['deprecated'];
303
+ const deprecated = typeof deprecatedRaw === 'boolean' ? deprecatedRaw : undefined;
304
+ const supersededBy = coerceString(props['superseded_by'] ?? props['supersededBy']);
305
+ const typed = {
306
+ name: entry.name,
307
+ sourcePath,
308
+ version,
309
+ role,
310
+ description,
311
+ prompt,
312
+ skills,
313
+ permissions,
314
+ tier,
315
+ contextSources,
316
+ onOverflow,
317
+ mentalModelRef,
318
+ contracts,
319
+ };
320
+ if (model !== undefined)
321
+ typed.model = model;
322
+ if (persist !== undefined)
323
+ typed.persist = persist;
324
+ if (parent !== undefined)
325
+ typed.parent = parent;
326
+ if (consultWhen !== undefined)
327
+ typed.consultWhen = consultWhen;
328
+ if (workers.length > 0)
329
+ typed.workers = workers;
330
+ if (stages.length > 0)
331
+ typed.stages = stages;
332
+ if (deprecated !== undefined)
333
+ typed.deprecated = deprecated;
334
+ if (supersededBy !== undefined)
335
+ typed.supersededBy = supersededBy;
336
+ return typed;
337
+ }
338
+ /**
339
+ * Detect placeholder `TODO` stubs in an agent's behavioral fields.
340
+ *
341
+ * @remarks
342
+ * T889 Wave 1 (W1-4) linter. Emits one {@link BundleDiagnostic} per offending
343
+ * field at severity `'error'` with `ruleId: 'S-TODO-001'`. An agent cannot be
344
+ * spawned while any of its `prompt`, `tone`, or `enforcement` values contain
345
+ * the literal substring `TODO` because the composer would forward placeholder
346
+ * content to the live model. The enclosing `valid` flag on
347
+ * {@link CompiledBundle} flips to `false` when any such diagnostic is raised.
348
+ *
349
+ * @param entry - The {@link AgentEntry} whose raw properties are inspected
350
+ * (not the {@link CantAgentV3} projection, because `tone` and `enforcement`
351
+ * are not part of the typed v3 surface).
352
+ * @param sourcePath - Absolute path to the source `.cant` file for attribution.
353
+ * @returns Zero or more S-TODO-001 diagnostics.
354
+ */
355
+ function detectTodoStubs(entry, sourcePath) {
356
+ const stubs = [];
357
+ const fields = ['prompt', 'tone', 'enforcement'];
358
+ for (const field of fields) {
359
+ const raw = entry.properties[field];
360
+ const value = coerceString(raw);
361
+ if (value?.includes('TODO')) {
362
+ stubs.push({
363
+ ruleId: 'S-TODO-001',
364
+ message: `Field '${field}' on agent '${entry.name}' contains TODO stub; agent cannot be spawned with placeholder content`,
365
+ severity: 'error',
366
+ sourcePath,
367
+ });
368
+ }
369
+ }
370
+ return stubs;
371
+ }
124
372
  /**
125
373
  * Extract teams from a parsed document AST.
126
374
  *
@@ -396,7 +644,18 @@ async function compileBundle(filePaths) {
396
644
  const agents = extractAgents(parseResult.document, filePath);
397
645
  const teams = extractTeams(parseResult.document, filePath);
398
646
  const tools = extractTools(parseResult.document, filePath);
399
- allAgents.push(...agents);
647
+ for (const agent of agents) {
648
+ const typed = toCantAgentV3(agent, filePath);
649
+ const typedEntry = { ...agent, typed };
650
+ const todoStubs = detectTodoStubs(agent, filePath);
651
+ if (todoStubs.length > 0) {
652
+ for (const stub of todoStubs) {
653
+ fileDiagnostics.push(stub);
654
+ }
655
+ allValid = false;
656
+ }
657
+ allAgents.push(typedEntry);
658
+ }
400
659
  allTeams.push(...teams);
401
660
  allTools.push(...tools);
402
661
  }
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export type { LAFSEnvelope, LAFSError, LAFSMeta, MVILevel } from '@cleocode/lafs';
2
- export type { AgentEntry, BundleDiagnostic, CompiledBundle, ParsedCantDocument, TeamEntry, ToolEntry, } from './bundle';
3
- export { compileBundle } from './bundle';
2
+ export type { AgentEntry, BundleDiagnostic, CompiledBundle, ParsedCantDocument, TeamEntry, ToolEntry, TypedAgentEntry, } from './bundle';
3
+ export { compileBundle, toCantAgentV3 } from './bundle';
4
4
  export type { AgentDefinition, ContextProvider, ContextSlice, MentalModelSlice, PathPermissions, SpawnPayload, Tier, } from './composer.js';
5
5
  export { composeSpawnPayload, escalateTier, estimateTokens, TIER_CAPS } from './composer.js';
6
6
  export { brainContextProvider } from './context-provider-brain.js';
@@ -16,6 +16,7 @@ export type { NativeDiagnostic, NativeParseDocumentResult, NativeParseError, Nat
16
16
  export { cantClassifyDirectiveNative, cantExecutePipelineNative, cantExtractAgentProfilesNative, cantParseDocumentNative, cantParseNative, cantValidateDocumentNative, initWasm, isNativeAvailable, isWasmAvailable, } from './native-loader';
17
17
  export type { ParsedCANTMessage } from './parse';
18
18
  export { initCantParser, parseCANTMessage } from './parse';
19
- export type { DirectiveType } from './types';
19
+ export type { CantAgentV3, CantContextSourceDef, CantContractBlock, CantContractClause, CantMentalModelRef, CantOverflowStrategy, CantPathPermissions, CantTier, DirectiveType, } from './types';
20
+ export { isCantAgentV3 } from './types';
20
21
  export type { MergeResult, WorktreeConfig, WorktreeEntry, WorktreeHandle, WorktreeRequest, } from './worktree.js';
21
22
  export { createWorktree, listWorktrees, mergeWorktree, resolveWorktreeRoot, } from './worktree.js';
package/dist/index.js CHANGED
@@ -2,10 +2,11 @@
2
2
  // JIT Agent Composer (ULTRAPLAN Wave 5)
3
3
  // Wave 7a: BRAIN-backed ContextProvider (T432)
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.resolveWorktreeRoot = exports.mergeWorktree = exports.listWorktrees = exports.createWorktree = exports.parseCANTMessage = exports.initCantParser = exports.isWasmAvailable = exports.isNativeAvailable = exports.initWasm = exports.cantValidateDocumentNative = exports.cantParseNative = exports.cantParseDocumentNative = exports.cantExtractAgentProfilesNative = exports.cantExecutePipelineNative = exports.cantClassifyDirectiveNative = exports.showSummary = exports.showDiff = exports.serializeCantDocument = exports.migrateMarkdown = exports.renderMentalModel = exports.harvestObservations = exports.createEmptyModel = exports.consolidate = exports.validateSpawnRequest = exports.ORCHESTRATOR_FORBIDDEN_TOOLS = exports.LEAD_FORBIDDEN_TOOLS = exports.filterToolsForRole = exports.validateDocument = exports.parseDocument = exports.listSections = exports.executePipeline = exports.brainContextProvider = exports.TIER_CAPS = exports.estimateTokens = exports.escalateTier = exports.composeSpawnPayload = exports.compileBundle = void 0;
5
+ exports.resolveWorktreeRoot = exports.mergeWorktree = exports.listWorktrees = exports.createWorktree = exports.isCantAgentV3 = exports.parseCANTMessage = exports.initCantParser = exports.isWasmAvailable = exports.isNativeAvailable = exports.initWasm = exports.cantValidateDocumentNative = exports.cantParseNative = exports.cantParseDocumentNative = exports.cantExtractAgentProfilesNative = exports.cantExecutePipelineNative = exports.cantClassifyDirectiveNative = exports.showSummary = exports.showDiff = exports.serializeCantDocument = exports.migrateMarkdown = exports.renderMentalModel = exports.harvestObservations = exports.createEmptyModel = exports.consolidate = exports.validateSpawnRequest = exports.ORCHESTRATOR_FORBIDDEN_TOOLS = exports.LEAD_FORBIDDEN_TOOLS = exports.filterToolsForRole = exports.validateDocument = exports.parseDocument = exports.listSections = exports.executePipeline = exports.brainContextProvider = exports.TIER_CAPS = exports.estimateTokens = exports.escalateTier = exports.composeSpawnPayload = exports.toCantAgentV3 = exports.compileBundle = void 0;
6
6
  // Bundle compiler
7
7
  var bundle_1 = require("./bundle");
8
8
  Object.defineProperty(exports, "compileBundle", { enumerable: true, get: function () { return bundle_1.compileBundle; } });
9
+ Object.defineProperty(exports, "toCantAgentV3", { enumerable: true, get: function () { return bundle_1.toCantAgentV3; } });
9
10
  var composer_js_1 = require("./composer.js");
10
11
  Object.defineProperty(exports, "composeSpawnPayload", { enumerable: true, get: function () { return composer_js_1.composeSpawnPayload; } });
11
12
  Object.defineProperty(exports, "escalateTier", { enumerable: true, get: function () { return composer_js_1.escalateTier; } });
@@ -52,6 +53,8 @@ Object.defineProperty(exports, "isWasmAvailable", { enumerable: true, get: funct
52
53
  var parse_1 = require("./parse");
53
54
  Object.defineProperty(exports, "initCantParser", { enumerable: true, get: function () { return parse_1.initCantParser; } });
54
55
  Object.defineProperty(exports, "parseCANTMessage", { enumerable: true, get: function () { return parse_1.parseCANTMessage; } });
56
+ var types_1 = require("./types");
57
+ Object.defineProperty(exports, "isCantAgentV3", { enumerable: true, get: function () { return types_1.isCantAgentV3; } });
55
58
  // Worktree isolation (ULTRAPLAN Wave 9)
56
59
  var worktree_js_1 = require("./worktree.js");
57
60
  Object.defineProperty(exports, "createWorktree", { enumerable: true, get: function () { return worktree_js_1.createWorktree; } });
package/dist/types.d.ts CHANGED
@@ -8,3 +8,103 @@ export interface ParsedCANTMessage {
8
8
  header_raw: string;
9
9
  body: string;
10
10
  }
11
+ /**
12
+ * A single contract clause — a precondition (requires) or postcondition (ensures)
13
+ * on agent behavior, expressed as OpenProse semiformal text.
14
+ *
15
+ * Syntax in .cant file:
16
+ * contracts:
17
+ * requires:
18
+ * - "Task is in started state before work begins"
19
+ * ensures:
20
+ * - "Output file written to OUTPUT_PATH before return"
21
+ */
22
+ export interface CantContractClause {
23
+ text: string;
24
+ enforcement?: 'hard' | 'soft';
25
+ }
26
+ /**
27
+ * Paired precondition/postcondition contract block attached to a CANT agent.
28
+ * `requires` clauses MUST hold before agent execution; `ensures` clauses MUST
29
+ * hold on successful return.
30
+ */
31
+ export interface CantContractBlock {
32
+ requires: CantContractClause[];
33
+ ensures: CantContractClause[];
34
+ }
35
+ /**
36
+ * Reference to a mental model slice to load into agent context.
37
+ * `scope` selects project-local vs global mental model store; `maxTokens`
38
+ * bounds the rendered slice; `validateOnLoad` enforces schema checks.
39
+ */
40
+ export interface CantMentalModelRef {
41
+ scope: 'project' | 'global';
42
+ maxTokens: number;
43
+ validateOnLoad: boolean;
44
+ }
45
+ /**
46
+ * Declarative context source binding: a CantContextSourceDef instructs the
47
+ * composer to query `source` with `query` and inject up to `maxEntries`
48
+ * results into the agent's spawn payload context.
49
+ */
50
+ export interface CantContextSourceDef {
51
+ source: string;
52
+ query: string;
53
+ maxEntries: number;
54
+ }
55
+ /**
56
+ * Strategy the composer applies when the rendered agent payload exceeds the
57
+ * tier token cap. `escalate_tier` promotes to the next tier; `fail` aborts.
58
+ */
59
+ export type CantOverflowStrategy = 'escalate_tier' | 'fail';
60
+ /**
61
+ * Agent capability tier. Tier selection drives token budgets, tool
62
+ * allowlists, and hierarchical spawn permissions.
63
+ */
64
+ export type CantTier = 'low' | 'mid' | 'high';
65
+ /**
66
+ * Filesystem permission ACL for an agent — separate read, write, and execute
67
+ * globs. Paths are evaluated against the spawning project root.
68
+ */
69
+ export interface CantPathPermissions {
70
+ read?: string[];
71
+ write?: string[];
72
+ execute?: string[];
73
+ }
74
+ /**
75
+ * Fully-typed CANT agent DSL v3 record. Represents the complete parsed shape
76
+ * of an agent declared in a `.cant` file with `kind: agent`. Consumed by the
77
+ * bundle compiler, composer, and spawn pipeline.
78
+ */
79
+ export interface CantAgentV3 {
80
+ name: string;
81
+ sourcePath: string;
82
+ version: string;
83
+ role: string;
84
+ description: string;
85
+ prompt: string;
86
+ skills: string[];
87
+ permissions: Record<string, string>;
88
+ model?: string;
89
+ persist?: boolean | string;
90
+ parent?: string;
91
+ filePermissions?: CantPathPermissions;
92
+ tier: CantTier;
93
+ contextSources: CantContextSourceDef[];
94
+ onOverflow: CantOverflowStrategy;
95
+ mentalModelRef: CantMentalModelRef | null;
96
+ contracts: CantContractBlock;
97
+ consultWhen?: string;
98
+ workers?: string[];
99
+ stages?: string[];
100
+ tools?: Record<string, string[]>;
101
+ deprecated?: boolean;
102
+ supersededBy?: string;
103
+ }
104
+ /**
105
+ * Structural type guard for `CantAgentV3`. Validates the required surface
106
+ * (name, sourcePath, version, role, description, prompt, skills,
107
+ * permissions, tier, contextSources, contracts). Returns `true` when `x`
108
+ * safely narrows to `CantAgentV3`.
109
+ */
110
+ export declare function isCantAgentV3(x: unknown): x is CantAgentV3;
package/dist/types.js CHANGED
@@ -1,2 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isCantAgentV3 = isCantAgentV3;
4
+ /**
5
+ * Structural type guard for `CantAgentV3`. Validates the required surface
6
+ * (name, sourcePath, version, role, description, prompt, skills,
7
+ * permissions, tier, contextSources, contracts). Returns `true` when `x`
8
+ * safely narrows to `CantAgentV3`.
9
+ */
10
+ function isCantAgentV3(x) {
11
+ if (typeof x !== 'object' || x === null)
12
+ return false;
13
+ const o = x;
14
+ return (typeof o.name === 'string' &&
15
+ typeof o.sourcePath === 'string' &&
16
+ typeof o.version === 'string' &&
17
+ typeof o.role === 'string' &&
18
+ typeof o.description === 'string' &&
19
+ typeof o.prompt === 'string' &&
20
+ Array.isArray(o.skills) &&
21
+ typeof o.permissions === 'object' &&
22
+ o.permissions !== null &&
23
+ typeof o.tier === 'string' &&
24
+ ['low', 'mid', 'high'].includes(o.tier) &&
25
+ Array.isArray(o.contextSources) &&
26
+ typeof o.contracts === 'object' &&
27
+ o.contracts !== null);
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/cant",
3
- "version": "2026.4.84",
3
+ "version": "2026.4.86",
4
4
  "description": "CANT protocol parser and runtime for CLEO — wraps cant-core via napi-rs",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,9 +9,9 @@
9
9
  "napi/"
10
10
  ],
11
11
  "dependencies": {
12
- "@cleocode/contracts": "2026.4.84",
13
- "@cleocode/core": "2026.4.84",
14
- "@cleocode/lafs": "2026.4.84"
12
+ "@cleocode/contracts": "2026.4.86",
13
+ "@cleocode/core": "2026.4.86",
14
+ "@cleocode/lafs": "2026.4.86"
15
15
  },
16
16
  "devDependencies": {
17
17
  "typescript": "^6.0.2",