@cleocode/cant 2026.3.76

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CLEO Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,7 @@
1
+ export type { LAFSEnvelope, LAFSError, LAFSMeta, MVILevel, } from '@cleocode/lafs';
2
+ export type { ConvertedFile, MigrationOptions, MigrationResult, UnconvertedSection, } from './migrate/index';
3
+ export { migrateMarkdown, serializeCantDocument, showDiff, showSummary } from './migrate/index';
4
+ export { initWasm, isNativeAvailable, isWasmAvailable } from './native-loader';
5
+ export type { ParsedCANTMessage } from './parse';
6
+ export { initCantParser, parseCANTMessage } from './parse';
7
+ export type { DirectiveType } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCANTMessage = exports.initCantParser = exports.isWasmAvailable = exports.isNativeAvailable = exports.initWasm = exports.showSummary = exports.showDiff = exports.serializeCantDocument = exports.migrateMarkdown = void 0;
4
+ // Migration engine
5
+ var index_1 = require("./migrate/index");
6
+ Object.defineProperty(exports, "migrateMarkdown", { enumerable: true, get: function () { return index_1.migrateMarkdown; } });
7
+ Object.defineProperty(exports, "serializeCantDocument", { enumerable: true, get: function () { return index_1.serializeCantDocument; } });
8
+ Object.defineProperty(exports, "showDiff", { enumerable: true, get: function () { return index_1.showDiff; } });
9
+ Object.defineProperty(exports, "showSummary", { enumerable: true, get: function () { return index_1.showSummary; } });
10
+ // Native loader (replaces wasm-loader)
11
+ var native_loader_1 = require("./native-loader");
12
+ Object.defineProperty(exports, "initWasm", { enumerable: true, get: function () { return native_loader_1.initWasm; } });
13
+ Object.defineProperty(exports, "isNativeAvailable", { enumerable: true, get: function () { return native_loader_1.isNativeAvailable; } });
14
+ Object.defineProperty(exports, "isWasmAvailable", { enumerable: true, get: function () { return native_loader_1.isWasmAvailable; } });
15
+ // Parser
16
+ var parse_1 = require("./parse");
17
+ Object.defineProperty(exports, "initCantParser", { enumerable: true, get: function () { return parse_1.initCantParser; } });
18
+ Object.defineProperty(exports, "parseCANTMessage", { enumerable: true, get: function () { return parse_1.parseCANTMessage; } });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Markdown-to-CANT conversion engine.
3
+ *
4
+ * Converts identified markdown sections into CANT document IR.
5
+ * Conservative by design: uncertain sections are flagged with
6
+ * TODO comments rather than guessed.
7
+ */
8
+ import type { MigrationOptions, MigrationResult } from './types';
9
+ /**
10
+ * Migrate a markdown file to CANT format.
11
+ *
12
+ * Parses the markdown into sections, classifies each section,
13
+ * converts recognized patterns to .cant files, and flags
14
+ * everything else as unconverted with TODO comments.
15
+ *
16
+ * @param content - Raw markdown file content
17
+ * @param inputFile - Path to the input file (for diagnostics)
18
+ * @param options - Migration options (write, verbose, outputDir)
19
+ * @returns Migration result with converted files and unconverted sections
20
+ */
21
+ export declare function migrateMarkdown(content: string, inputFile: string, options: MigrationOptions): MigrationResult;
@@ -0,0 +1,390 @@
1
+ "use strict";
2
+ /**
3
+ * Markdown-to-CANT conversion engine.
4
+ *
5
+ * Converts identified markdown sections into CANT document IR.
6
+ * Conservative by design: uncertain sections are flagged with
7
+ * TODO comments rather than guessed.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.migrateMarkdown = migrateMarkdown;
11
+ const markdown_parser_1 = require("./markdown-parser");
12
+ const serializer_1 = require("./serializer");
13
+ /**
14
+ * Migrate a markdown file to CANT format.
15
+ *
16
+ * Parses the markdown into sections, classifies each section,
17
+ * converts recognized patterns to .cant files, and flags
18
+ * everything else as unconverted with TODO comments.
19
+ *
20
+ * @param content - Raw markdown file content
21
+ * @param inputFile - Path to the input file (for diagnostics)
22
+ * @param options - Migration options (write, verbose, outputDir)
23
+ * @returns Migration result with converted files and unconverted sections
24
+ */
25
+ function migrateMarkdown(content, inputFile, options) {
26
+ const sections = (0, markdown_parser_1.parseMarkdownSections)(content);
27
+ const outputDir = options.outputDir ?? '.cleo/agents';
28
+ const outputFiles = [];
29
+ const unconverted = [];
30
+ for (const section of sections) {
31
+ switch (section.classification) {
32
+ case 'agent':
33
+ convertAgentSection(section, outputDir, outputFiles);
34
+ break;
35
+ case 'hook':
36
+ convertHookSection(section, outputDir, outputFiles, unconverted);
37
+ break;
38
+ case 'permissions':
39
+ // Permissions are typically embedded in agent sections.
40
+ // Standalone permission sections are flagged as unconverted
41
+ // since they need a parent agent context.
42
+ unconverted.push({
43
+ lineStart: section.lineStart,
44
+ lineEnd: section.lineEnd,
45
+ reason: 'Standalone permissions section needs parent agent context',
46
+ content: formatSectionContent(section),
47
+ });
48
+ break;
49
+ case 'skill':
50
+ convertSkillSection(section, outputDir, outputFiles, unconverted);
51
+ break;
52
+ case 'workflow':
53
+ convertWorkflowSection(section, outputDir, outputFiles, unconverted);
54
+ break;
55
+ case 'unknown':
56
+ unconverted.push({
57
+ lineStart: section.lineStart,
58
+ lineEnd: section.lineEnd,
59
+ reason: 'Could not classify section for automatic conversion',
60
+ content: formatSectionContent(section),
61
+ });
62
+ break;
63
+ }
64
+ }
65
+ const summary = buildSummary(outputFiles, unconverted);
66
+ return {
67
+ inputFile,
68
+ outputFiles,
69
+ unconverted,
70
+ summary,
71
+ };
72
+ }
73
+ /**
74
+ * Convert an agent-classified section to a .cant file.
75
+ */
76
+ function convertAgentSection(section, outputDir, outputFiles) {
77
+ const identifier = (0, markdown_parser_1.headingToIdentifier)(section.heading);
78
+ if (!identifier)
79
+ return;
80
+ const properties = (0, markdown_parser_1.extractProperties)(section.bodyLines);
81
+ const cantProperties = (0, serializer_1.propertiesToIR)(properties);
82
+ // Check for inline permissions within the agent section
83
+ const permissions = extractInlinePermissions(section.bodyLines);
84
+ const block = {
85
+ type: 'agent',
86
+ name: identifier,
87
+ properties: cantProperties,
88
+ permissions,
89
+ children: [],
90
+ };
91
+ const doc = {
92
+ kind: 'agent',
93
+ version: 1,
94
+ block,
95
+ };
96
+ const cantContent = (0, serializer_1.serializeCantDocument)(doc);
97
+ const path = `${outputDir}/${identifier}.cant`;
98
+ outputFiles.push({
99
+ path,
100
+ kind: 'agent',
101
+ content: cantContent,
102
+ });
103
+ }
104
+ /**
105
+ * Extract permissions that appear inline within an agent body section.
106
+ *
107
+ * Looks for sub-sections or indented lists under "Permissions:" within
108
+ * the body of an agent section.
109
+ */
110
+ function extractInlinePermissions(bodyLines) {
111
+ let inPermissions = false;
112
+ const permissionLines = [];
113
+ for (const line of bodyLines) {
114
+ if (/^\*?\*?permissions?\*?\*?\s*:/i.test(line.trim())) {
115
+ inPermissions = true;
116
+ continue;
117
+ }
118
+ if (inPermissions) {
119
+ if (/^[-*]\s+/.test(line.trim()) || /^\s+[-*]\s+/.test(line)) {
120
+ permissionLines.push(line.trim());
121
+ }
122
+ else if (line.trim() === '') {
123
+ }
124
+ else {
125
+ // Non-list line ends the permissions block
126
+ inPermissions = false;
127
+ }
128
+ }
129
+ }
130
+ return (0, markdown_parser_1.extractPermissions)(permissionLines);
131
+ }
132
+ /**
133
+ * Convert a hook-classified section to a .cant file.
134
+ */
135
+ function convertHookSection(section, outputDir, outputFiles, unconverted) {
136
+ const eventName = (0, markdown_parser_1.headingToEventName)(section.heading);
137
+ if (!eventName) {
138
+ // Cannot determine the specific event -- flag as unconverted
139
+ unconverted.push({
140
+ lineStart: section.lineStart,
141
+ lineEnd: section.lineEnd,
142
+ reason: `Hook heading "${section.heading}" does not match a known CAAMP event`,
143
+ content: formatSectionContent(section),
144
+ });
145
+ return;
146
+ }
147
+ // Convert body to directive-like lines
148
+ const hookBodyLines = convertHookBody(section.bodyLines);
149
+ const identifier = eventName
150
+ .replace(/([A-Z])/g, '-$1')
151
+ .toLowerCase()
152
+ .replace(/^-/, '');
153
+ const block = {
154
+ type: 'on',
155
+ name: eventName,
156
+ properties: [],
157
+ permissions: [],
158
+ children: [],
159
+ bodyLines: hookBodyLines,
160
+ };
161
+ const doc = {
162
+ kind: 'hook',
163
+ version: 1,
164
+ block,
165
+ };
166
+ const cantContent = (0, serializer_1.serializeCantDocument)(doc);
167
+ const path = `${outputDir}/${identifier}.cant`;
168
+ outputFiles.push({
169
+ path,
170
+ kind: 'hook',
171
+ content: cantContent,
172
+ });
173
+ }
174
+ /**
175
+ * Convert hook body markdown (numbered/bulleted lists) into CANT body lines.
176
+ *
177
+ * Strips numbering and bullet prefixes. Preserves directive lines (/verb).
178
+ * Wraps prose instructions as session prompts or TODO comments.
179
+ */
180
+ function convertHookBody(bodyLines) {
181
+ const result = [];
182
+ for (const line of bodyLines) {
183
+ const trimmed = line.trim();
184
+ if (!trimmed)
185
+ continue;
186
+ // Strip numbered list prefix: "1. " -> ""
187
+ const stripped = trimmed.replace(/^\d+\.\s+/, '').replace(/^[-*]\s+/, '');
188
+ // Already a directive line
189
+ if (stripped.startsWith('/')) {
190
+ result.push(stripped);
191
+ continue;
192
+ }
193
+ // Check for context-like references
194
+ const contextMatch = stripped.match(/^(?:load\s+)?context\s*:\s*(.+)/i);
195
+ if (contextMatch) {
196
+ const items = (contextMatch[1] ?? '')
197
+ .split(/[,\s]+/)
198
+ .map((s) => s.trim())
199
+ .filter(Boolean);
200
+ result.push(`# context: [${items.join(', ')}]`);
201
+ continue;
202
+ }
203
+ // Wrap as TODO comment for prose content
204
+ result.push(`# TODO: manual conversion needed -- ${stripped}`);
205
+ }
206
+ return result;
207
+ }
208
+ /**
209
+ * Convert a skill-classified section to a .cant file.
210
+ */
211
+ function convertSkillSection(section, outputDir, outputFiles, unconverted) {
212
+ const identifier = (0, markdown_parser_1.headingToIdentifier)(section.heading);
213
+ if (!identifier || identifier === 'skill' || identifier === 'skills') {
214
+ // Generic "Skills" heading -- just a list, not a skill definition
215
+ unconverted.push({
216
+ lineStart: section.lineStart,
217
+ lineEnd: section.lineEnd,
218
+ reason: 'Generic skills list cannot be converted to a skill definition',
219
+ content: formatSectionContent(section),
220
+ });
221
+ return;
222
+ }
223
+ const properties = (0, markdown_parser_1.extractProperties)(section.bodyLines);
224
+ const cantProperties = (0, serializer_1.propertiesToIR)(properties);
225
+ const block = {
226
+ type: 'skill',
227
+ name: identifier,
228
+ properties: cantProperties,
229
+ permissions: [],
230
+ children: [],
231
+ };
232
+ const doc = {
233
+ kind: 'skill',
234
+ version: 1,
235
+ block,
236
+ };
237
+ const cantContent = (0, serializer_1.serializeCantDocument)(doc);
238
+ const path = `${outputDir}/${identifier}.cant`;
239
+ outputFiles.push({
240
+ path,
241
+ kind: 'skill',
242
+ content: cantContent,
243
+ });
244
+ }
245
+ /**
246
+ * Convert a workflow-classified section to a .cant file.
247
+ *
248
+ * Workflows are the hardest to convert automatically. We attempt
249
+ * basic pipeline conversion for numbered procedure lists, but
250
+ * flag complex workflows as TODO.
251
+ */
252
+ function convertWorkflowSection(section, outputDir, outputFiles, unconverted) {
253
+ const identifier = (0, markdown_parser_1.headingToIdentifier)(section.heading);
254
+ if (!identifier)
255
+ return;
256
+ // Check if the body is a simple numbered step list (deploy procedure pattern)
257
+ const steps = extractPipelineSteps(section.bodyLines);
258
+ if (steps.length === 0) {
259
+ unconverted.push({
260
+ lineStart: section.lineStart,
261
+ lineEnd: section.lineEnd,
262
+ reason: 'Workflow section too complex for automatic conversion',
263
+ content: formatSectionContent(section),
264
+ });
265
+ return;
266
+ }
267
+ // Build pipeline steps as children
268
+ const stepBlocks = steps.map((step, i) => ({
269
+ type: 'step',
270
+ name: step.name || `step-${i + 1}`,
271
+ properties: step.properties,
272
+ permissions: [],
273
+ children: [],
274
+ }));
275
+ const pipelineBlock = {
276
+ type: 'pipeline',
277
+ name: `${identifier}-pipeline`,
278
+ properties: [],
279
+ permissions: [],
280
+ children: stepBlocks,
281
+ };
282
+ const workflowBlock = {
283
+ type: 'workflow',
284
+ name: identifier,
285
+ properties: [],
286
+ permissions: [],
287
+ children: [pipelineBlock],
288
+ };
289
+ const doc = {
290
+ kind: 'workflow',
291
+ version: 1,
292
+ block: workflowBlock,
293
+ };
294
+ const cantContent = (0, serializer_1.serializeCantDocument)(doc);
295
+ const path = `${outputDir}/${identifier}.cant`;
296
+ outputFiles.push({
297
+ path,
298
+ kind: 'workflow',
299
+ content: cantContent,
300
+ });
301
+ }
302
+ /**
303
+ * Extract pipeline steps from a numbered list of commands.
304
+ *
305
+ * Recognizes patterns like:
306
+ * - `1. Run \`command here\``
307
+ * - `2. Execute \`another command\``
308
+ *
309
+ * @param lines - Body lines to extract from
310
+ * @returns Array of extracted steps, empty if content is not step-like
311
+ */
312
+ function extractPipelineSteps(lines) {
313
+ const steps = [];
314
+ for (const line of lines) {
315
+ const trimmed = line.trim();
316
+ if (!trimmed)
317
+ continue;
318
+ // Match: "1. Run `command`" or "- Run `command`"
319
+ const cmdMatch = trimmed.match(/^(?:\d+\.|[-*])\s+(?:run\s+)?`([^`]+)`/i);
320
+ if (cmdMatch) {
321
+ const fullCmd = cmdMatch[1] ?? '';
322
+ const parts = fullCmd.split(/\s+/);
323
+ const command = parts[0] ?? fullCmd;
324
+ const args = parts.slice(1);
325
+ const properties = [{ key: 'command', value: command }];
326
+ if (args.length > 0) {
327
+ properties.push({ key: 'args', value: args });
328
+ }
329
+ const stepName = deriveStepName(fullCmd);
330
+ steps.push({ name: stepName, properties });
331
+ continue;
332
+ }
333
+ // Non-command lines break the pipeline pattern
334
+ // unless they're conditional prose (which we skip for now)
335
+ if (/^(?:\d+\.|[-*])\s+(?:if|then|else|ask|wait)/i.test(trimmed)) {
336
+ // Conditional step -- too complex, bail out
337
+ // Return empty to signal the whole section should be unconverted
338
+ return [];
339
+ }
340
+ }
341
+ return steps;
342
+ }
343
+ /**
344
+ * Derive a step name from a command string.
345
+ *
346
+ * "pnpm run build" -> "build"
347
+ * "pnpm test" -> "test"
348
+ * "gh pr diff" -> "gh-pr-diff"
349
+ */
350
+ function deriveStepName(cmd) {
351
+ const parts = cmd.split(/\s+/);
352
+ // Common patterns: "pnpm run X" -> X, "npm run X" -> X
353
+ if (parts.length >= 3 && (parts[0] === 'pnpm' || parts[0] === 'npm') && parts[1] === 'run') {
354
+ return (parts[2] ?? 'step').replace(/[^a-z0-9-]/g, '-');
355
+ }
356
+ // "pnpm test" -> "test"
357
+ if (parts.length >= 2 && (parts[0] === 'pnpm' || parts[0] === 'npm')) {
358
+ return (parts[1] ?? 'step').replace(/[^a-z0-9-]/g, '-');
359
+ }
360
+ // Generic: take last meaningful segment
361
+ const lastPart = parts[parts.length - 1] ?? 'step';
362
+ return lastPart.replace(/[^a-z0-9-]/g, '-').replace(/^-|-$/g, '') || 'step';
363
+ }
364
+ /**
365
+ * Format a section's content as a single string for unconverted output.
366
+ */
367
+ function formatSectionContent(section) {
368
+ const headingPrefix = '#'.repeat(section.level);
369
+ const heading = `${headingPrefix} ${section.heading}`;
370
+ return [heading, ...section.bodyLines].join('\n');
371
+ }
372
+ /**
373
+ * Build a human-readable migration summary.
374
+ */
375
+ function buildSummary(outputFiles, unconverted) {
376
+ const converted = outputFiles.length;
377
+ const remaining = unconverted.length;
378
+ const parts = [];
379
+ parts.push(`${converted} section(s) converted`);
380
+ parts.push(`${remaining} section(s) left as TODO`);
381
+ if (outputFiles.length > 0) {
382
+ const kinds = new Map();
383
+ for (const file of outputFiles) {
384
+ kinds.set(file.kind, (kinds.get(file.kind) ?? 0) + 1);
385
+ }
386
+ const kindSummary = [...kinds.entries()].map(([kind, count]) => `${count} ${kind}`).join(', ');
387
+ parts.push(`(${kindSummary})`);
388
+ }
389
+ return parts.join(', ');
390
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Simple diff display for CANT migration preview.
3
+ *
4
+ * Provides a color-coded before/after view showing what the
5
+ * migration engine would produce. Uses ANSI escape codes
6
+ * for terminal output.
7
+ */
8
+ import type { MigrationResult } from './types';
9
+ /**
10
+ * Show a color-coded diff of the migration result.
11
+ *
12
+ * Displays:
13
+ * - A summary header with conversion stats
14
+ * - Each converted file with green-highlighted content
15
+ * - Each unconverted section with yellow-highlighted warnings
16
+ *
17
+ * @param result - The migration result to display
18
+ * @param useColor - Whether to use ANSI color codes (default: true)
19
+ * @returns The formatted diff string
20
+ */
21
+ export declare function showDiff(result: MigrationResult, useColor?: boolean): string;
22
+ /**
23
+ * Generate a simple text-only summary (no color).
24
+ *
25
+ * Suitable for logging or non-terminal output.
26
+ *
27
+ * @param result - The migration result to summarize
28
+ * @returns Plain text summary
29
+ */
30
+ export declare function showSummary(result: MigrationResult): string;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ /**
3
+ * Simple diff display for CANT migration preview.
4
+ *
5
+ * Provides a color-coded before/after view showing what the
6
+ * migration engine would produce. Uses ANSI escape codes
7
+ * for terminal output.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.showDiff = showDiff;
11
+ exports.showSummary = showSummary;
12
+ /** ANSI color codes for terminal diff output. */
13
+ const ANSI = {
14
+ reset: '\x1b[0m',
15
+ red: '\x1b[31m',
16
+ green: '\x1b[32m',
17
+ yellow: '\x1b[33m',
18
+ cyan: '\x1b[36m',
19
+ dim: '\x1b[2m',
20
+ bold: '\x1b[1m',
21
+ };
22
+ /**
23
+ * Show a color-coded diff of the migration result.
24
+ *
25
+ * Displays:
26
+ * - A summary header with conversion stats
27
+ * - Each converted file with green-highlighted content
28
+ * - Each unconverted section with yellow-highlighted warnings
29
+ *
30
+ * @param result - The migration result to display
31
+ * @param useColor - Whether to use ANSI color codes (default: true)
32
+ * @returns The formatted diff string
33
+ */
34
+ function showDiff(result, useColor = true) {
35
+ const c = useColor
36
+ ? ANSI
37
+ : {
38
+ reset: '',
39
+ red: '',
40
+ green: '',
41
+ yellow: '',
42
+ cyan: '',
43
+ dim: '',
44
+ bold: '',
45
+ };
46
+ const lines = [];
47
+ // Header
48
+ lines.push(`${c.bold}Migration Preview: ${result.inputFile}${c.reset}`);
49
+ lines.push(`${c.dim}${'='.repeat(60)}${c.reset}`);
50
+ lines.push(result.summary);
51
+ lines.push('');
52
+ // Converted files
53
+ if (result.outputFiles.length > 0) {
54
+ lines.push(`${c.bold}${c.green}Converted files:${c.reset}`);
55
+ lines.push('');
56
+ for (const file of result.outputFiles) {
57
+ lines.push(`${c.cyan}--- ${file.path} (${file.kind})${c.reset}`);
58
+ const contentLines = file.content.split('\n');
59
+ for (const contentLine of contentLines) {
60
+ lines.push(`${c.green}+ ${contentLine}${c.reset}`);
61
+ }
62
+ lines.push('');
63
+ }
64
+ }
65
+ // Unconverted sections
66
+ if (result.unconverted.length > 0) {
67
+ lines.push(`${c.bold}${c.yellow}Unconverted sections (manual review needed):${c.reset}`);
68
+ lines.push('');
69
+ for (const section of result.unconverted) {
70
+ lines.push(`${c.yellow}! Lines ${section.lineStart}-${section.lineEnd}: ${section.reason}${c.reset}`);
71
+ const sectionLines = section.content.split('\n');
72
+ for (const sectionLine of sectionLines) {
73
+ lines.push(`${c.red}- ${sectionLine}${c.reset}`);
74
+ }
75
+ lines.push('');
76
+ }
77
+ }
78
+ return lines.join('\n');
79
+ }
80
+ /**
81
+ * Generate a simple text-only summary (no color).
82
+ *
83
+ * Suitable for logging or non-terminal output.
84
+ *
85
+ * @param result - The migration result to summarize
86
+ * @returns Plain text summary
87
+ */
88
+ function showSummary(result) {
89
+ const lines = [];
90
+ lines.push(`Migration: ${result.inputFile}`);
91
+ lines.push(result.summary);
92
+ if (result.outputFiles.length > 0) {
93
+ lines.push('');
94
+ lines.push('Would create:');
95
+ for (const file of result.outputFiles) {
96
+ lines.push(` ${file.path} (${file.kind})`);
97
+ }
98
+ }
99
+ if (result.unconverted.length > 0) {
100
+ lines.push('');
101
+ lines.push('Needs manual conversion:');
102
+ for (const section of result.unconverted) {
103
+ lines.push(` Lines ${section.lineStart}-${section.lineEnd}: ${section.reason}`);
104
+ }
105
+ }
106
+ return lines.join('\n');
107
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * CANT migration engine -- markdown-to-CANT conversion tooling.
3
+ *
4
+ * Entry point for the `cant migrate` command and programmatic
5
+ * migration of AGENTS.md files to .cant format.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { migrateMarkdown, showDiff } from '@cleocode/cant/migrate';
10
+ *
11
+ * const result = migrateMarkdown(markdownContent, 'AGENTS.md', {
12
+ * write: false,
13
+ * verbose: false,
14
+ * });
15
+ *
16
+ * console.log(showDiff(result));
17
+ * ```
18
+ */
19
+ export { migrateMarkdown } from './converter';
20
+ export { showDiff, showSummary } from './diff';
21
+ export { serializeCantDocument } from './serializer';
22
+ export type { ConvertedFile, MigrationOptions, MigrationResult, UnconvertedSection, } from './types';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * CANT migration engine -- markdown-to-CANT conversion tooling.
4
+ *
5
+ * Entry point for the `cant migrate` command and programmatic
6
+ * migration of AGENTS.md files to .cant format.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { migrateMarkdown, showDiff } from '@cleocode/cant/migrate';
11
+ *
12
+ * const result = migrateMarkdown(markdownContent, 'AGENTS.md', {
13
+ * write: false,
14
+ * verbose: false,
15
+ * });
16
+ *
17
+ * console.log(showDiff(result));
18
+ * ```
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.serializeCantDocument = exports.showSummary = exports.showDiff = exports.migrateMarkdown = void 0;
22
+ var converter_1 = require("./converter");
23
+ Object.defineProperty(exports, "migrateMarkdown", { enumerable: true, get: function () { return converter_1.migrateMarkdown; } });
24
+ var diff_1 = require("./diff");
25
+ Object.defineProperty(exports, "showDiff", { enumerable: true, get: function () { return diff_1.showDiff; } });
26
+ Object.defineProperty(exports, "showSummary", { enumerable: true, get: function () { return diff_1.showSummary; } });
27
+ var serializer_1 = require("./serializer");
28
+ Object.defineProperty(exports, "serializeCantDocument", { enumerable: true, get: function () { return serializer_1.serializeCantDocument; } });