@cleocode/cant 2026.4.11 → 2026.4.12
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 +158 -0
- package/dist/bundle.js +423 -0
- package/dist/composer.d.ts +165 -0
- package/dist/composer.js +160 -0
- package/dist/hierarchy.d.ts +95 -0
- package/dist/hierarchy.js +103 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +28 -1
- package/dist/mental-model.d.ts +142 -0
- package/dist/mental-model.js +194 -0
- package/dist/worktree.d.ts +106 -0
- package/dist/worktree.js +179 -0
- package/package.json +3 -3
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiled bundle API for `.cant` files.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Provides {@link compileBundle} which takes a list of `.cant` file paths,
|
|
6
|
+
* parses and validates each one via the existing cant-napi bridge, then
|
|
7
|
+
* collects the results into a single {@link CompiledBundle}. The bundle
|
|
8
|
+
* exposes extracted agents, teams, tools, and diagnostics, plus a
|
|
9
|
+
* {@link CompiledBundle.renderSystemPrompt | renderSystemPrompt()} method
|
|
10
|
+
* that produces a markdown-formatted system prompt addendum suitable for
|
|
11
|
+
* appending to a Pi system prompt.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* import { compileBundle } from '@cleocode/cant';
|
|
16
|
+
*
|
|
17
|
+
* const bundle = await compileBundle(['.cleo/cant/my-agent.cant']);
|
|
18
|
+
* if (bundle.valid) {
|
|
19
|
+
* const prompt = bundle.renderSystemPrompt();
|
|
20
|
+
* console.log(prompt);
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
import type { CantDocumentResult } from './document.js';
|
|
25
|
+
/**
|
|
26
|
+
* A single parsed `.cant` document with its source path and diagnostics.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* The `document` field holds the raw AST as returned by
|
|
30
|
+
* {@link parseDocument}. Callers that need typed access should narrow the
|
|
31
|
+
* shape per the cant-core grammar (sections keyed by `Agent`, `Workflow`,
|
|
32
|
+
* `Pipeline`, etc.).
|
|
33
|
+
*/
|
|
34
|
+
export interface ParsedCantDocument {
|
|
35
|
+
/** Absolute path to the source `.cant` file. */
|
|
36
|
+
sourcePath: string;
|
|
37
|
+
/** The document kind from frontmatter (`"Agent"`, `"Workflow"`, etc.), or `null`. */
|
|
38
|
+
kind: string | null;
|
|
39
|
+
/** The raw AST from `parseDocument`. `null` when parsing failed. */
|
|
40
|
+
document: CantDocumentResult['document'];
|
|
41
|
+
/** Validation diagnostics for this document. */
|
|
42
|
+
diagnostics: BundleDiagnostic[];
|
|
43
|
+
}
|
|
44
|
+
/** A normalized diagnostic combining parse errors and validation diagnostics. */
|
|
45
|
+
export interface BundleDiagnostic {
|
|
46
|
+
/** The rule ID (e.g., `"S01"`, `"parse"`). */
|
|
47
|
+
ruleId: string;
|
|
48
|
+
/** Human-readable diagnostic message. */
|
|
49
|
+
message: string;
|
|
50
|
+
/** Severity: `"error"`, `"warning"`, `"info"`, or `"hint"`. */
|
|
51
|
+
severity: string;
|
|
52
|
+
/** Source file path. */
|
|
53
|
+
sourcePath: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* An agent declaration extracted from a compiled `.cant` file.
|
|
57
|
+
*
|
|
58
|
+
* @remarks
|
|
59
|
+
* Properties are stored as a flat `Record` with string keys. Values are
|
|
60
|
+
* simplified from the raw AST value wrapper (e.g., `{ Identifier: "worker" }`
|
|
61
|
+
* becomes `"worker"`).
|
|
62
|
+
*/
|
|
63
|
+
export interface AgentEntry {
|
|
64
|
+
/** The agent name as declared in the `.cant` file. */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Absolute path to the source `.cant` file. */
|
|
67
|
+
sourcePath: string;
|
|
68
|
+
/** Simplified agent properties (role, tier, prompt, skills, etc.). */
|
|
69
|
+
properties: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A team declaration extracted from a compiled `.cant` file.
|
|
73
|
+
*
|
|
74
|
+
* @remarks
|
|
75
|
+
* The current cant-core parser does not support `team` as a top-level
|
|
76
|
+
* section. This interface exists for forward-compatibility; the bundle
|
|
77
|
+
* will populate it once the grammar is extended.
|
|
78
|
+
*/
|
|
79
|
+
export interface TeamEntry {
|
|
80
|
+
/** The team name as declared in the `.cant` file. */
|
|
81
|
+
name: string;
|
|
82
|
+
/** Absolute path to the source `.cant` file. */
|
|
83
|
+
sourcePath: string;
|
|
84
|
+
/** Simplified team properties. */
|
|
85
|
+
properties: Record<string, unknown>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A tool declaration extracted from a compiled `.cant` file.
|
|
89
|
+
*
|
|
90
|
+
* @remarks
|
|
91
|
+
* The current cant-core parser does not support `tool` as a top-level
|
|
92
|
+
* section. This interface exists for forward-compatibility; the bundle
|
|
93
|
+
* will populate it once the grammar is extended.
|
|
94
|
+
*/
|
|
95
|
+
export interface ToolEntry {
|
|
96
|
+
/** The tool name as declared in the `.cant` file. */
|
|
97
|
+
name: string;
|
|
98
|
+
/** Absolute path to the source `.cant` file. */
|
|
99
|
+
sourcePath: string;
|
|
100
|
+
/** Simplified tool properties. */
|
|
101
|
+
properties: Record<string, unknown>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The result of compiling one or more `.cant` files into a unified bundle.
|
|
105
|
+
*
|
|
106
|
+
* @remarks
|
|
107
|
+
* Contains all successfully parsed documents, extracted entity entries
|
|
108
|
+
* (agents, teams, tools), cross-file diagnostics, and a
|
|
109
|
+
* {@link renderSystemPrompt} helper for Pi system prompt injection.
|
|
110
|
+
*/
|
|
111
|
+
export interface CompiledBundle {
|
|
112
|
+
/** All successfully parsed documents, keyed by source path. */
|
|
113
|
+
documents: Map<string, ParsedCantDocument>;
|
|
114
|
+
/** Agents found across all documents. */
|
|
115
|
+
agents: AgentEntry[];
|
|
116
|
+
/** Teams found across all documents. */
|
|
117
|
+
teams: TeamEntry[];
|
|
118
|
+
/** Tools found across all documents. */
|
|
119
|
+
tools: ToolEntry[];
|
|
120
|
+
/** Validation diagnostics across all documents. */
|
|
121
|
+
diagnostics: BundleDiagnostic[];
|
|
122
|
+
/** Whether all documents parsed and validated without errors. */
|
|
123
|
+
valid: boolean;
|
|
124
|
+
/** Render the compiled bundle as a system prompt addendum. */
|
|
125
|
+
renderSystemPrompt(): string;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Compile a list of `.cant` files into a unified {@link CompiledBundle}.
|
|
129
|
+
*
|
|
130
|
+
* @remarks
|
|
131
|
+
* For each file path, reads the file, parses it via {@link parseDocument},
|
|
132
|
+
* validates it via {@link validateDocument}, then extracts agents, teams,
|
|
133
|
+
* and tools from the AST. Diagnostics from both parse errors and validation
|
|
134
|
+
* are collected into the bundle's {@link CompiledBundle.diagnostics} array.
|
|
135
|
+
*
|
|
136
|
+
* Files that fail to parse are still included in the bundle (with their
|
|
137
|
+
* diagnostics) but do not contribute entities. The bundle's `valid` flag
|
|
138
|
+
* is `true` only when every file parsed successfully and validated with
|
|
139
|
+
* zero error-severity diagnostics.
|
|
140
|
+
*
|
|
141
|
+
* @param filePaths - Absolute paths to `.cant` files to compile.
|
|
142
|
+
* @returns A {@link CompiledBundle} with all extracted entities and diagnostics.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* import { compileBundle } from '@cleocode/cant';
|
|
147
|
+
*
|
|
148
|
+
* const bundle = await compileBundle([
|
|
149
|
+
* '/project/.cleo/cant/backend-dev.cant',
|
|
150
|
+
* '/project/.cleo/cant/frontend-dev.cant',
|
|
151
|
+
* ]);
|
|
152
|
+
*
|
|
153
|
+
* console.log(`Found ${bundle.agents.length} agents`);
|
|
154
|
+
* console.log(`Valid: ${bundle.valid}`);
|
|
155
|
+
* console.log(bundle.renderSystemPrompt());
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export declare function compileBundle(filePaths: string[]): Promise<CompiledBundle>;
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Compiled bundle API for `.cant` files.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Provides {@link compileBundle} which takes a list of `.cant` file paths,
|
|
7
|
+
* parses and validates each one via the existing cant-napi bridge, then
|
|
8
|
+
* collects the results into a single {@link CompiledBundle}. The bundle
|
|
9
|
+
* exposes extracted agents, teams, tools, and diagnostics, plus a
|
|
10
|
+
* {@link CompiledBundle.renderSystemPrompt | renderSystemPrompt()} method
|
|
11
|
+
* that produces a markdown-formatted system prompt addendum suitable for
|
|
12
|
+
* appending to a Pi system prompt.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { compileBundle } from '@cleocode/cant';
|
|
17
|
+
*
|
|
18
|
+
* const bundle = await compileBundle(['.cleo/cant/my-agent.cant']);
|
|
19
|
+
* if (bundle.valid) {
|
|
20
|
+
* const prompt = bundle.renderSystemPrompt();
|
|
21
|
+
* console.log(prompt);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.compileBundle = compileBundle;
|
|
27
|
+
const document_js_1 = require("./document.js");
|
|
28
|
+
/**
|
|
29
|
+
* Simplify a cant-core AST value wrapper to a plain JS value.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* The AST wraps values like `{ Identifier: "worker" }`,
|
|
33
|
+
* `{ String: { raw: "..." } }`, `{ Number: 2 }`, `{ Boolean: true }`,
|
|
34
|
+
* `{ Array: [...] }`, `{ ProseBlock: { lines: [...] } }`. This function
|
|
35
|
+
* extracts the inner payload for human-readable property maps.
|
|
36
|
+
*/
|
|
37
|
+
function simplifyValue(wrapper) {
|
|
38
|
+
if ('Identifier' in wrapper)
|
|
39
|
+
return wrapper['Identifier'];
|
|
40
|
+
if ('String' in wrapper) {
|
|
41
|
+
const inner = wrapper['String'];
|
|
42
|
+
if (typeof inner === 'object' && inner !== null && 'raw' in inner) {
|
|
43
|
+
return inner.raw;
|
|
44
|
+
}
|
|
45
|
+
return inner;
|
|
46
|
+
}
|
|
47
|
+
if ('Number' in wrapper)
|
|
48
|
+
return wrapper['Number'];
|
|
49
|
+
if ('Boolean' in wrapper)
|
|
50
|
+
return wrapper['Boolean'];
|
|
51
|
+
if ('ProseBlock' in wrapper) {
|
|
52
|
+
const block = wrapper['ProseBlock'];
|
|
53
|
+
if (typeof block === 'object' && block !== null && 'lines' in block) {
|
|
54
|
+
return block.lines.join('\n');
|
|
55
|
+
}
|
|
56
|
+
return block;
|
|
57
|
+
}
|
|
58
|
+
if ('Array' in wrapper) {
|
|
59
|
+
const arr = wrapper['Array'];
|
|
60
|
+
if (Array.isArray(arr)) {
|
|
61
|
+
return arr.map((item) => simplifyValue(item));
|
|
62
|
+
}
|
|
63
|
+
return arr;
|
|
64
|
+
}
|
|
65
|
+
// Fallback: return the wrapper as-is for unknown shapes
|
|
66
|
+
return wrapper;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract a flat properties map from raw AST properties.
|
|
70
|
+
*
|
|
71
|
+
* @param rawProps - The raw AST property array from a section.
|
|
72
|
+
* @returns A simplified `Record<string, unknown>` map.
|
|
73
|
+
*/
|
|
74
|
+
function extractProperties(rawProps) {
|
|
75
|
+
const result = {};
|
|
76
|
+
for (const prop of rawProps) {
|
|
77
|
+
const key = prop.key?.value;
|
|
78
|
+
if (typeof key !== 'string')
|
|
79
|
+
continue;
|
|
80
|
+
result[key] = simplifyValue(prop.value);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extract agents from a parsed document AST.
|
|
86
|
+
*
|
|
87
|
+
* @param doc - The raw AST document object from `parseDocument`.
|
|
88
|
+
* @param sourcePath - The source file path for attribution.
|
|
89
|
+
* @returns An array of {@link AgentEntry} extracted from `Agent` sections.
|
|
90
|
+
*/
|
|
91
|
+
function extractAgents(doc, sourcePath) {
|
|
92
|
+
if (typeof doc !== 'object' || doc === null)
|
|
93
|
+
return [];
|
|
94
|
+
const docObj = doc;
|
|
95
|
+
const sections = docObj['sections'];
|
|
96
|
+
if (!Array.isArray(sections))
|
|
97
|
+
return [];
|
|
98
|
+
const agents = [];
|
|
99
|
+
for (const section of sections) {
|
|
100
|
+
if (typeof section !== 'object' || section === null)
|
|
101
|
+
continue;
|
|
102
|
+
const wrapper = section;
|
|
103
|
+
const agentData = wrapper['Agent'];
|
|
104
|
+
if (!agentData)
|
|
105
|
+
continue;
|
|
106
|
+
const nameValue = agentData.name?.value;
|
|
107
|
+
if (typeof nameValue !== 'string')
|
|
108
|
+
continue;
|
|
109
|
+
const properties = extractProperties(agentData.properties ?? []);
|
|
110
|
+
// Include permissions as a property for visibility
|
|
111
|
+
if (Array.isArray(agentData.permissions) && agentData.permissions.length > 0) {
|
|
112
|
+
const permMap = {};
|
|
113
|
+
for (const perm of agentData.permissions) {
|
|
114
|
+
if (typeof perm.domain === 'string' && Array.isArray(perm.access)) {
|
|
115
|
+
permMap[perm.domain] = perm.access;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
properties['permissions'] = permMap;
|
|
119
|
+
}
|
|
120
|
+
agents.push({ name: nameValue, sourcePath, properties });
|
|
121
|
+
}
|
|
122
|
+
return agents;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Extract teams from a parsed document AST.
|
|
126
|
+
*
|
|
127
|
+
* @remarks
|
|
128
|
+
* The current cant-core parser does not support `team` sections. This
|
|
129
|
+
* function is a forward-compatible stub that will extract teams once the
|
|
130
|
+
* grammar adds `team` as a recognized top-level construct.
|
|
131
|
+
*
|
|
132
|
+
* @param doc - The raw AST document object from `parseDocument`.
|
|
133
|
+
* @param sourcePath - The source file path for attribution.
|
|
134
|
+
* @returns An array of {@link TeamEntry} (currently always empty).
|
|
135
|
+
*/
|
|
136
|
+
function extractTeams(doc, sourcePath) {
|
|
137
|
+
if (typeof doc !== 'object' || doc === null)
|
|
138
|
+
return [];
|
|
139
|
+
const docObj = doc;
|
|
140
|
+
const sections = docObj['sections'];
|
|
141
|
+
if (!Array.isArray(sections))
|
|
142
|
+
return [];
|
|
143
|
+
const teams = [];
|
|
144
|
+
for (const section of sections) {
|
|
145
|
+
if (typeof section !== 'object' || section === null)
|
|
146
|
+
continue;
|
|
147
|
+
const wrapper = section;
|
|
148
|
+
const teamData = wrapper['Team'];
|
|
149
|
+
if (!teamData)
|
|
150
|
+
continue;
|
|
151
|
+
const nameValue = teamData.name?.value;
|
|
152
|
+
if (typeof nameValue !== 'string')
|
|
153
|
+
continue;
|
|
154
|
+
teams.push({
|
|
155
|
+
name: nameValue,
|
|
156
|
+
sourcePath,
|
|
157
|
+
properties: extractProperties(teamData.properties ?? []),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return teams;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Extract tools from a parsed document AST.
|
|
164
|
+
*
|
|
165
|
+
* @remarks
|
|
166
|
+
* The current cant-core parser does not support `tool` sections. This
|
|
167
|
+
* function is a forward-compatible stub that will extract tools once the
|
|
168
|
+
* grammar adds `tool` as a recognized top-level construct.
|
|
169
|
+
*
|
|
170
|
+
* @param doc - The raw AST document object from `parseDocument`.
|
|
171
|
+
* @param sourcePath - The source file path for attribution.
|
|
172
|
+
* @returns An array of {@link ToolEntry} (currently always empty).
|
|
173
|
+
*/
|
|
174
|
+
function extractTools(doc, sourcePath) {
|
|
175
|
+
if (typeof doc !== 'object' || doc === null)
|
|
176
|
+
return [];
|
|
177
|
+
const docObj = doc;
|
|
178
|
+
const sections = docObj['sections'];
|
|
179
|
+
if (!Array.isArray(sections))
|
|
180
|
+
return [];
|
|
181
|
+
const tools = [];
|
|
182
|
+
for (const section of sections) {
|
|
183
|
+
if (typeof section !== 'object' || section === null)
|
|
184
|
+
continue;
|
|
185
|
+
const wrapper = section;
|
|
186
|
+
const toolData = wrapper['Tool'];
|
|
187
|
+
if (!toolData)
|
|
188
|
+
continue;
|
|
189
|
+
const nameValue = toolData.name?.value;
|
|
190
|
+
if (typeof nameValue !== 'string')
|
|
191
|
+
continue;
|
|
192
|
+
tools.push({
|
|
193
|
+
name: nameValue,
|
|
194
|
+
sourcePath,
|
|
195
|
+
properties: extractProperties(toolData.properties ?? []),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return tools;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Render a system prompt addendum from the compiled bundle contents.
|
|
202
|
+
*
|
|
203
|
+
* @remarks
|
|
204
|
+
* Produces markdown suitable for appending to a Pi system prompt. Lists
|
|
205
|
+
* all declared agents with their roles, tiers, and descriptions; all
|
|
206
|
+
* teams with orchestrators and members; and all tools with descriptions.
|
|
207
|
+
*
|
|
208
|
+
* @param bundle - The compiled bundle to render.
|
|
209
|
+
* @returns A markdown-formatted string, or an empty string if the bundle is empty.
|
|
210
|
+
*/
|
|
211
|
+
function renderBundleSystemPrompt(bundle) {
|
|
212
|
+
const lines = [];
|
|
213
|
+
if (bundle.agents.length === 0 && bundle.teams.length === 0 && bundle.tools.length === 0) {
|
|
214
|
+
return '';
|
|
215
|
+
}
|
|
216
|
+
lines.push('## CANT Bundle — Loaded Declarations');
|
|
217
|
+
lines.push('');
|
|
218
|
+
if (bundle.agents.length > 0) {
|
|
219
|
+
lines.push('### Agents');
|
|
220
|
+
lines.push('');
|
|
221
|
+
for (const agent of bundle.agents) {
|
|
222
|
+
const role = typeof agent.properties['role'] === 'string' ? agent.properties['role'] : 'unspecified';
|
|
223
|
+
const tier = typeof agent.properties['tier'] === 'string' ? agent.properties['tier'] : 'unspecified';
|
|
224
|
+
const prompt = typeof agent.properties['prompt'] === 'string' ? agent.properties['prompt'] : '';
|
|
225
|
+
const description = prompt.length > 0 ? prompt.split('\n')[0] : '';
|
|
226
|
+
lines.push(`- **${agent.name}** (role: ${role}, tier: ${tier})`);
|
|
227
|
+
if (description.length > 0) {
|
|
228
|
+
lines.push(` ${description.trim()}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
lines.push('');
|
|
232
|
+
}
|
|
233
|
+
if (bundle.teams.length > 0) {
|
|
234
|
+
lines.push('### Teams');
|
|
235
|
+
lines.push('');
|
|
236
|
+
for (const team of bundle.teams) {
|
|
237
|
+
const orchestrator = typeof team.properties['orchestrator'] === 'string'
|
|
238
|
+
? team.properties['orchestrator']
|
|
239
|
+
: 'unspecified';
|
|
240
|
+
const description = typeof team.properties['description'] === 'string'
|
|
241
|
+
? team.properties['description']
|
|
242
|
+
: '';
|
|
243
|
+
lines.push(`- **${team.name}** (orchestrator: ${orchestrator})`);
|
|
244
|
+
if (description.length > 0) {
|
|
245
|
+
lines.push(` ${description.trim()}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
lines.push('');
|
|
249
|
+
}
|
|
250
|
+
if (bundle.tools.length > 0) {
|
|
251
|
+
lines.push('### Tools');
|
|
252
|
+
lines.push('');
|
|
253
|
+
for (const tool of bundle.tools) {
|
|
254
|
+
const description = typeof tool.properties['description'] === 'string'
|
|
255
|
+
? tool.properties['description']
|
|
256
|
+
: '';
|
|
257
|
+
lines.push(`- **${tool.name}**`);
|
|
258
|
+
if (description.length > 0) {
|
|
259
|
+
lines.push(` ${description.trim()}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
lines.push('');
|
|
263
|
+
}
|
|
264
|
+
if (!bundle.valid && bundle.diagnostics.length > 0) {
|
|
265
|
+
const errorCount = bundle.diagnostics.filter(d => d.severity === 'error').length;
|
|
266
|
+
if (errorCount > 0) {
|
|
267
|
+
lines.push(`> **Warning**: ${errorCount} validation error(s) found across .cant files.`);
|
|
268
|
+
lines.push('');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return lines.join('\n');
|
|
272
|
+
}
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
// Public API
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
/**
|
|
277
|
+
* Compile a list of `.cant` files into a unified {@link CompiledBundle}.
|
|
278
|
+
*
|
|
279
|
+
* @remarks
|
|
280
|
+
* For each file path, reads the file, parses it via {@link parseDocument},
|
|
281
|
+
* validates it via {@link validateDocument}, then extracts agents, teams,
|
|
282
|
+
* and tools from the AST. Diagnostics from both parse errors and validation
|
|
283
|
+
* are collected into the bundle's {@link CompiledBundle.diagnostics} array.
|
|
284
|
+
*
|
|
285
|
+
* Files that fail to parse are still included in the bundle (with their
|
|
286
|
+
* diagnostics) but do not contribute entities. The bundle's `valid` flag
|
|
287
|
+
* is `true` only when every file parsed successfully and validated with
|
|
288
|
+
* zero error-severity diagnostics.
|
|
289
|
+
*
|
|
290
|
+
* @param filePaths - Absolute paths to `.cant` files to compile.
|
|
291
|
+
* @returns A {@link CompiledBundle} with all extracted entities and diagnostics.
|
|
292
|
+
*
|
|
293
|
+
* @example
|
|
294
|
+
* ```typescript
|
|
295
|
+
* import { compileBundle } from '@cleocode/cant';
|
|
296
|
+
*
|
|
297
|
+
* const bundle = await compileBundle([
|
|
298
|
+
* '/project/.cleo/cant/backend-dev.cant',
|
|
299
|
+
* '/project/.cleo/cant/frontend-dev.cant',
|
|
300
|
+
* ]);
|
|
301
|
+
*
|
|
302
|
+
* console.log(`Found ${bundle.agents.length} agents`);
|
|
303
|
+
* console.log(`Valid: ${bundle.valid}`);
|
|
304
|
+
* console.log(bundle.renderSystemPrompt());
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
307
|
+
async function compileBundle(filePaths) {
|
|
308
|
+
const documents = new Map();
|
|
309
|
+
const allAgents = [];
|
|
310
|
+
const allTeams = [];
|
|
311
|
+
const allTools = [];
|
|
312
|
+
const allDiagnostics = [];
|
|
313
|
+
let allValid = true;
|
|
314
|
+
for (const filePath of filePaths) {
|
|
315
|
+
const fileDiagnostics = [];
|
|
316
|
+
// Parse the document
|
|
317
|
+
let parseResult;
|
|
318
|
+
try {
|
|
319
|
+
parseResult = await (0, document_js_1.parseDocument)(filePath);
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
323
|
+
fileDiagnostics.push({
|
|
324
|
+
ruleId: 'parse',
|
|
325
|
+
message: `Failed to read or parse file: ${message}`,
|
|
326
|
+
severity: 'error',
|
|
327
|
+
sourcePath: filePath,
|
|
328
|
+
});
|
|
329
|
+
allDiagnostics.push(...fileDiagnostics);
|
|
330
|
+
documents.set(filePath, {
|
|
331
|
+
sourcePath: filePath,
|
|
332
|
+
kind: null,
|
|
333
|
+
document: null,
|
|
334
|
+
diagnostics: fileDiagnostics,
|
|
335
|
+
});
|
|
336
|
+
allValid = false;
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
// Convert parse errors to bundle diagnostics
|
|
340
|
+
if (!parseResult.success) {
|
|
341
|
+
for (const err of parseResult.errors) {
|
|
342
|
+
fileDiagnostics.push({
|
|
343
|
+
ruleId: 'parse',
|
|
344
|
+
message: err.message,
|
|
345
|
+
severity: err.severity,
|
|
346
|
+
sourcePath: filePath,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
allValid = false;
|
|
350
|
+
}
|
|
351
|
+
// Extract document kind from AST
|
|
352
|
+
let kind = null;
|
|
353
|
+
if (parseResult.document !== null && typeof parseResult.document === 'object') {
|
|
354
|
+
const docObj = parseResult.document;
|
|
355
|
+
if (typeof docObj['kind'] === 'string') {
|
|
356
|
+
kind = docObj['kind'];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Validate if parsing succeeded
|
|
360
|
+
if (parseResult.success) {
|
|
361
|
+
let validationResult;
|
|
362
|
+
try {
|
|
363
|
+
validationResult = await (0, document_js_1.validateDocument)(filePath);
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
367
|
+
fileDiagnostics.push({
|
|
368
|
+
ruleId: 'validate',
|
|
369
|
+
message: `Validation failed: ${message}`,
|
|
370
|
+
severity: 'error',
|
|
371
|
+
sourcePath: filePath,
|
|
372
|
+
});
|
|
373
|
+
allDiagnostics.push(...fileDiagnostics);
|
|
374
|
+
documents.set(filePath, {
|
|
375
|
+
sourcePath: filePath,
|
|
376
|
+
kind,
|
|
377
|
+
document: parseResult.document,
|
|
378
|
+
diagnostics: fileDiagnostics,
|
|
379
|
+
});
|
|
380
|
+
allValid = false;
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
// Convert validation diagnostics
|
|
384
|
+
for (const diag of validationResult.diagnostics) {
|
|
385
|
+
fileDiagnostics.push({
|
|
386
|
+
ruleId: diag.ruleId,
|
|
387
|
+
message: diag.message,
|
|
388
|
+
severity: diag.severity,
|
|
389
|
+
sourcePath: filePath,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
if (!validationResult.valid) {
|
|
393
|
+
allValid = false;
|
|
394
|
+
}
|
|
395
|
+
// Extract entities from successfully parsed documents
|
|
396
|
+
const agents = extractAgents(parseResult.document, filePath);
|
|
397
|
+
const teams = extractTeams(parseResult.document, filePath);
|
|
398
|
+
const tools = extractTools(parseResult.document, filePath);
|
|
399
|
+
allAgents.push(...agents);
|
|
400
|
+
allTeams.push(...teams);
|
|
401
|
+
allTools.push(...tools);
|
|
402
|
+
}
|
|
403
|
+
allDiagnostics.push(...fileDiagnostics);
|
|
404
|
+
documents.set(filePath, {
|
|
405
|
+
sourcePath: filePath,
|
|
406
|
+
kind,
|
|
407
|
+
document: parseResult.document,
|
|
408
|
+
diagnostics: fileDiagnostics,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
const bundle = {
|
|
412
|
+
documents,
|
|
413
|
+
agents: allAgents,
|
|
414
|
+
teams: allTeams,
|
|
415
|
+
tools: allTools,
|
|
416
|
+
diagnostics: allDiagnostics,
|
|
417
|
+
valid: allValid,
|
|
418
|
+
renderSystemPrompt() {
|
|
419
|
+
return renderBundleSystemPrompt(this);
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
return bundle;
|
|
423
|
+
}
|