@cleocode/cant 2026.4.5 → 2026.4.7

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,103 @@
1
+ /**
2
+ * High-level CANT document API.
3
+ *
4
+ * @remarks
5
+ * Replaces the standalone `cant-cli` Rust binary that was previously
6
+ * spawned by `cleo cant`. All operations now run in-process via the
7
+ * cant-napi binding, avoiding subprocess overhead and shared-binary
8
+ * lookup logic.
9
+ *
10
+ * The functions here return plain TypeScript values (no LAFS envelope)
11
+ * so that callers can wrap them in whatever response shape they need.
12
+ * The CLEO CLI uses `cliOutput` for envelope formatting.
13
+ */
14
+ import { type NativeDiagnostic, type NativeParseError, type NativePipelineResult } from './native-loader';
15
+ /** The kind of section to enumerate via {@link listSections}. */
16
+ export type SectionKind = 'agent' | 'pipeline' | 'workflow';
17
+ /** Result of {@link parseDocument}. */
18
+ export interface CantDocumentResult {
19
+ /** The file path that was parsed. */
20
+ file: string;
21
+ /** Whether parsing succeeded. */
22
+ success: boolean;
23
+ /** The parsed document AST as a JSON-compatible object (null on failure). */
24
+ document: unknown;
25
+ /** Parse errors (empty when `success` is true). */
26
+ errors: NativeParseError[];
27
+ }
28
+ /** Result of {@link validateDocument}. */
29
+ export interface CantValidationResult {
30
+ /** The file path that was validated. */
31
+ file: string;
32
+ /** Whether validation passed (no errors; warnings allowed). */
33
+ valid: boolean;
34
+ /** Total diagnostic count. */
35
+ total: number;
36
+ /** Number of error-severity diagnostics. */
37
+ errorCount: number;
38
+ /** Number of warning-severity diagnostics. */
39
+ warningCount: number;
40
+ /** All diagnostics emitted by the 42-rule validator. */
41
+ diagnostics: NativeDiagnostic[];
42
+ }
43
+ /** Result of {@link listSections}. */
44
+ export interface CantListResult {
45
+ /** The file path that was inspected. */
46
+ file: string;
47
+ /** The section filter that was applied. */
48
+ filter: SectionKind;
49
+ /** Number of matching sections. */
50
+ count: number;
51
+ /** Names of the matching sections (in source order). */
52
+ names: string[];
53
+ }
54
+ /** Result of {@link executePipeline}. */
55
+ export interface CantPipelineResult {
56
+ /** The file path that was executed. */
57
+ file: string;
58
+ /** The pipeline name that was requested. */
59
+ pipeline: string;
60
+ /** Whether the pipeline ran to completion with all steps succeeding. */
61
+ success: boolean;
62
+ /** Total wall-clock duration in milliseconds. */
63
+ durationMs: number;
64
+ /** Per-step results in execution order. */
65
+ steps: NativePipelineResult['steps'];
66
+ /** Optional error message describing why the pipeline did not run. */
67
+ error?: string | null;
68
+ }
69
+ /**
70
+ * Parse a `.cant` file and return its AST.
71
+ *
72
+ * @param filePath - Absolute path to the `.cant` file.
73
+ * @returns A {@link CantDocumentResult} with either the AST or parse errors.
74
+ */
75
+ export declare function parseDocument(filePath: string): Promise<CantDocumentResult>;
76
+ /**
77
+ * Validate a `.cant` file using the 42-rule validation engine.
78
+ *
79
+ * @param filePath - Absolute path to the `.cant` file.
80
+ * @returns A {@link CantValidationResult} with all diagnostics.
81
+ */
82
+ export declare function validateDocument(filePath: string): Promise<CantValidationResult>;
83
+ /**
84
+ * Enumerate the agents, pipelines, or workflows defined in a `.cant` file.
85
+ *
86
+ * @param filePath - Absolute path to the `.cant` file.
87
+ * @param kind - Which section type to enumerate (default: `"agent"`).
88
+ * @returns A {@link CantListResult} with the matching section names.
89
+ */
90
+ export declare function listSections(filePath: string, kind?: SectionKind): Promise<CantListResult>;
91
+ /**
92
+ * Execute a deterministic pipeline from a `.cant` file via the Rust runtime.
93
+ *
94
+ * @remarks
95
+ * Wraps the napi-rs `cantExecutePipeline` async export. Logical failures
96
+ * (file not found, parse errors, missing pipeline, runtime errors) are
97
+ * surfaced via the `success: false` + `error` fields rather than thrown.
98
+ *
99
+ * @param filePath - Absolute path to the `.cant` file.
100
+ * @param pipelineName - The name of the `pipeline { ... }` block to execute.
101
+ * @returns A {@link CantPipelineResult} describing the pipeline outcome.
102
+ */
103
+ export declare function executePipeline(filePath: string, pipelineName: string): Promise<CantPipelineResult>;
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ /**
3
+ * High-level CANT document API.
4
+ *
5
+ * @remarks
6
+ * Replaces the standalone `cant-cli` Rust binary that was previously
7
+ * spawned by `cleo cant`. All operations now run in-process via the
8
+ * cant-napi binding, avoiding subprocess overhead and shared-binary
9
+ * lookup logic.
10
+ *
11
+ * The functions here return plain TypeScript values (no LAFS envelope)
12
+ * so that callers can wrap them in whatever response shape they need.
13
+ * The CLEO CLI uses `cliOutput` for envelope formatting.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.parseDocument = parseDocument;
17
+ exports.validateDocument = validateDocument;
18
+ exports.listSections = listSections;
19
+ exports.executePipeline = executePipeline;
20
+ const promises_1 = require("node:fs/promises");
21
+ const native_loader_1 = require("./native-loader");
22
+ /**
23
+ * Parse a `.cant` file and return its AST.
24
+ *
25
+ * @param filePath - Absolute path to the `.cant` file.
26
+ * @returns A {@link CantDocumentResult} with either the AST or parse errors.
27
+ */
28
+ async function parseDocument(filePath) {
29
+ const content = await (0, promises_1.readFile)(filePath, 'utf-8');
30
+ const result = (0, native_loader_1.cantParseDocumentNative)(content);
31
+ return {
32
+ file: filePath,
33
+ success: result.success,
34
+ document: result.document ?? null,
35
+ errors: result.errors,
36
+ };
37
+ }
38
+ /**
39
+ * Validate a `.cant` file using the 42-rule validation engine.
40
+ *
41
+ * @param filePath - Absolute path to the `.cant` file.
42
+ * @returns A {@link CantValidationResult} with all diagnostics.
43
+ */
44
+ async function validateDocument(filePath) {
45
+ const content = await (0, promises_1.readFile)(filePath, 'utf-8');
46
+ const result = (0, native_loader_1.cantValidateDocumentNative)(content);
47
+ return {
48
+ file: filePath,
49
+ valid: result.valid,
50
+ total: result.total,
51
+ errorCount: result.errorCount,
52
+ warningCount: result.warningCount,
53
+ diagnostics: result.diagnostics,
54
+ };
55
+ }
56
+ /**
57
+ * Enumerate the agents, pipelines, or workflows defined in a `.cant` file.
58
+ *
59
+ * @param filePath - Absolute path to the `.cant` file.
60
+ * @param kind - Which section type to enumerate (default: `"agent"`).
61
+ * @returns A {@link CantListResult} with the matching section names.
62
+ */
63
+ async function listSections(filePath, kind = 'agent') {
64
+ const content = await (0, promises_1.readFile)(filePath, 'utf-8');
65
+ const parsed = (0, native_loader_1.cantParseDocumentNative)(content);
66
+ if (!parsed.success || parsed.document == null) {
67
+ return { file: filePath, filter: kind, count: 0, names: [] };
68
+ }
69
+ const doc = parsed.document;
70
+ const sections = Array.isArray(doc.sections) ? doc.sections : [];
71
+ const names = [];
72
+ for (const section of sections) {
73
+ if (typeof section !== 'object' || section === null)
74
+ continue;
75
+ const wrapper = section;
76
+ const inner = wrapper[capitalize(kind)];
77
+ if (!inner)
78
+ continue;
79
+ const nameField = inner['name'];
80
+ if (typeof nameField === 'object' && nameField !== null) {
81
+ const value = nameField['value'];
82
+ if (typeof value === 'string')
83
+ names.push(value);
84
+ }
85
+ else if (typeof nameField === 'string') {
86
+ names.push(nameField);
87
+ }
88
+ }
89
+ return { file: filePath, filter: kind, count: names.length, names };
90
+ }
91
+ /**
92
+ * Execute a deterministic pipeline from a `.cant` file via the Rust runtime.
93
+ *
94
+ * @remarks
95
+ * Wraps the napi-rs `cantExecutePipeline` async export. Logical failures
96
+ * (file not found, parse errors, missing pipeline, runtime errors) are
97
+ * surfaced via the `success: false` + `error` fields rather than thrown.
98
+ *
99
+ * @param filePath - Absolute path to the `.cant` file.
100
+ * @param pipelineName - The name of the `pipeline { ... }` block to execute.
101
+ * @returns A {@link CantPipelineResult} describing the pipeline outcome.
102
+ */
103
+ async function executePipeline(filePath, pipelineName) {
104
+ const result = await (0, native_loader_1.cantExecutePipelineNative)(filePath, pipelineName);
105
+ return {
106
+ file: filePath,
107
+ pipeline: pipelineName,
108
+ success: result.success,
109
+ durationMs: result.durationMs,
110
+ steps: result.steps,
111
+ error: result.error ?? null,
112
+ };
113
+ }
114
+ /** Capitalize the first character of a string (used for AST section keys). */
115
+ function capitalize(s) {
116
+ return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
117
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
- export type { LAFSEnvelope, LAFSError, LAFSMeta, MVILevel, } from '@cleocode/lafs';
1
+ export type { LAFSEnvelope, LAFSError, LAFSMeta, MVILevel } from '@cleocode/lafs';
2
+ export type { CantDocumentResult, CantListResult, CantPipelineResult, CantValidationResult, SectionKind, } from './document';
3
+ export { executePipeline, listSections, parseDocument, validateDocument, } from './document';
2
4
  export type { ConvertedFile, MigrationOptions, MigrationResult, UnconvertedSection, } from './migrate/index';
3
5
  export { migrateMarkdown, serializeCantDocument, showDiff, showSummary } from './migrate/index';
4
- export { initWasm, isNativeAvailable, isWasmAvailable } from './native-loader';
6
+ export type { NativeDiagnostic, NativeParseDocumentResult, NativeParseError, NativeParseResult, NativePipelineResult, NativePipelineStep, NativeValidateResult, } from './native-loader';
7
+ export { cantClassifyDirectiveNative, cantExecutePipelineNative, cantExtractAgentProfilesNative, cantParseDocumentNative, cantParseNative, cantValidateDocumentNative, initWasm, isNativeAvailable, isWasmAvailable, } from './native-loader';
5
8
  export type { ParsedCANTMessage } from './parse';
6
9
  export { initCantParser, parseCANTMessage } from './parse';
7
10
  export type { DirectiveType } from './types';
package/dist/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
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;
3
+ 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.validateDocument = exports.parseDocument = exports.listSections = exports.executePipeline = void 0;
4
+ // High-level API (replaces standalone cant-cli binary)
5
+ var document_1 = require("./document");
6
+ Object.defineProperty(exports, "executePipeline", { enumerable: true, get: function () { return document_1.executePipeline; } });
7
+ Object.defineProperty(exports, "listSections", { enumerable: true, get: function () { return document_1.listSections; } });
8
+ Object.defineProperty(exports, "parseDocument", { enumerable: true, get: function () { return document_1.parseDocument; } });
9
+ Object.defineProperty(exports, "validateDocument", { enumerable: true, get: function () { return document_1.validateDocument; } });
4
10
  // Migration engine
5
11
  var index_1 = require("./migrate/index");
6
12
  Object.defineProperty(exports, "migrateMarkdown", { enumerable: true, get: function () { return index_1.migrateMarkdown; } });
@@ -9,6 +15,12 @@ Object.defineProperty(exports, "showDiff", { enumerable: true, get: function ()
9
15
  Object.defineProperty(exports, "showSummary", { enumerable: true, get: function () { return index_1.showSummary; } });
10
16
  // Native loader (replaces wasm-loader)
11
17
  var native_loader_1 = require("./native-loader");
18
+ Object.defineProperty(exports, "cantClassifyDirectiveNative", { enumerable: true, get: function () { return native_loader_1.cantClassifyDirectiveNative; } });
19
+ Object.defineProperty(exports, "cantExecutePipelineNative", { enumerable: true, get: function () { return native_loader_1.cantExecutePipelineNative; } });
20
+ Object.defineProperty(exports, "cantExtractAgentProfilesNative", { enumerable: true, get: function () { return native_loader_1.cantExtractAgentProfilesNative; } });
21
+ Object.defineProperty(exports, "cantParseDocumentNative", { enumerable: true, get: function () { return native_loader_1.cantParseDocumentNative; } });
22
+ Object.defineProperty(exports, "cantParseNative", { enumerable: true, get: function () { return native_loader_1.cantParseNative; } });
23
+ Object.defineProperty(exports, "cantValidateDocumentNative", { enumerable: true, get: function () { return native_loader_1.cantValidateDocumentNative; } });
12
24
  Object.defineProperty(exports, "initWasm", { enumerable: true, get: function () { return native_loader_1.initWasm; } });
13
25
  Object.defineProperty(exports, "isNativeAvailable", { enumerable: true, get: function () { return native_loader_1.isNativeAvailable; } });
14
26
  Object.defineProperty(exports, "isWasmAvailable", { enumerable: true, get: function () { return native_loader_1.isWasmAvailable; } });
@@ -1,32 +1,160 @@
1
1
  /**
2
- * Native addon loader for cant-core via napi-rs
2
+ * Native addon loader for cant-core via napi-rs.
3
3
  *
4
- * Loads the napi-rs native addon synchronously. Falls back gracefully
5
- * if the native addon is not available (e.g., unsupported platform).
4
+ * @remarks
5
+ * Loads the napi-rs native addon synchronously on first use. Tries the
6
+ * package-local binary first (`packages/cant/napi/cant.linux-x64-gnu.node`),
7
+ * then falls back to the workspace `cant-napi` crate's `index.cjs` for
8
+ * dev-mode builds where the package binary may not be present yet.
6
9
  *
7
- * Replaces the previous wasm-loader.ts which used async WASM initialization.
10
+ * Replaces the previous WASM loader. Follows the same pattern as
11
+ * `packages/lafs/src/native-loader.ts`.
8
12
  */
9
- /**
10
- * Check if the native addon is available
11
- */
12
- export declare function isNativeAvailable(): boolean;
13
- /** Shape returned by the native Rust WASM cantParse function. */
13
+ /** Shape of a parsed CANT message returned by the native binding. */
14
14
  export interface NativeParseResult {
15
+ /** The directive verb if present (e.g., `"done"`), or `undefined`. */
15
16
  directive?: string;
17
+ /** The classification of the directive as a lowercase string. */
16
18
  directiveType?: string;
19
+ /** All `@`-addresses found in the message, without the `@` prefix. */
17
20
  addresses?: string[];
21
+ /** All task references found in the message, including the `T` prefix. */
18
22
  taskRefs?: string[];
23
+ /** All `#`-tags found in the message, without the `#` prefix. */
19
24
  tags?: string[];
25
+ /** The raw text of the first line (the header). */
20
26
  headerRaw?: string;
27
+ /** Everything after the first newline (the body). */
21
28
  body?: string;
22
29
  }
30
+ /** A parse error from document parsing, exposed by the native binding. */
31
+ export interface NativeParseError {
32
+ /** Human-readable error message. */
33
+ message: string;
34
+ /** Line number (1-based) where the error occurred. */
35
+ line: number;
36
+ /** Column number (1-based) where the error occurred. */
37
+ col: number;
38
+ /** Byte offset of the error start. */
39
+ start: number;
40
+ /** Byte offset of the error end. */
41
+ end: number;
42
+ /** Severity: "error" or "warning". */
43
+ severity: string;
44
+ }
45
+ /** Result of parsing a `.cant` document via the native binding. */
46
+ export interface NativeParseDocumentResult {
47
+ /** Whether parsing succeeded. */
48
+ success: boolean;
49
+ /** Parsed AST as a JSON-compatible object (null if parsing failed). */
50
+ document: unknown;
51
+ /** Parse errors (empty if parsing succeeded). */
52
+ errors: NativeParseError[];
53
+ }
54
+ /** A validation diagnostic from the 42-rule validation engine. */
55
+ export interface NativeDiagnostic {
56
+ /** The rule ID (e.g., "S01", "P06", "W08"). */
57
+ ruleId: string;
58
+ /** Human-readable diagnostic message. */
59
+ message: string;
60
+ /** Severity: "error", "warning", "info", or "hint". */
61
+ severity: string;
62
+ /** Line number (1-based). */
63
+ line: number;
64
+ /** Column number (1-based). */
65
+ col: number;
66
+ }
67
+ /** Result of validating a `.cant` document via the native binding. */
68
+ export interface NativeValidateResult {
69
+ /** Whether validation passed (no errors; warnings allowed). */
70
+ valid: boolean;
71
+ /** Total number of diagnostics. */
72
+ total: number;
73
+ /** Number of errors. */
74
+ errorCount: number;
75
+ /** Number of warnings. */
76
+ warningCount: number;
77
+ /** All diagnostics from the validation engine. */
78
+ diagnostics: NativeDiagnostic[];
79
+ }
80
+ /** A single step result from a pipeline run via the native binding. */
81
+ export interface NativePipelineStep {
82
+ /** The step name from the pipeline definition. */
83
+ name: string;
84
+ /** Subprocess exit code (0 = success). */
85
+ exitCode: number;
86
+ /** Length in bytes of captured stdout. */
87
+ stdoutLen: number;
88
+ /** Length in bytes of captured stderr. */
89
+ stderrLen: number;
90
+ /** Wall-clock duration of the step in milliseconds. */
91
+ durationMs: number;
92
+ /** Whether the step was skipped due to a condition. */
93
+ skipped: boolean;
94
+ }
95
+ /** The aggregate result of a pipeline run via the native binding. */
96
+ export interface NativePipelineResult {
97
+ /** The pipeline name. */
98
+ name: string;
99
+ /** Whether all steps completed with exit code 0. */
100
+ success: boolean;
101
+ /** Total wall-clock duration in milliseconds. */
102
+ durationMs: number;
103
+ /** Per-step results in execution order. */
104
+ steps: NativePipelineStep[];
105
+ /** Optional error message describing why the pipeline did not run. */
106
+ error?: string | null;
107
+ }
23
108
  /**
24
- * Parse a CANT message using the native addon
109
+ * Check if the native addon is available.
110
+ *
111
+ * @returns `true` if the native Rust binding loaded successfully.
112
+ */
113
+ export declare function isNativeAvailable(): boolean;
114
+ /**
115
+ * Parse a CANT message using the native addon.
116
+ *
117
+ * @param content - The CANT message content to parse.
25
118
  */
26
119
  export declare function cantParseNative(content: string): NativeParseResult;
27
120
  /**
28
- * Classify a directive using the native addon
121
+ * Classify a directive verb using the native addon.
122
+ *
123
+ * @param verb - The directive verb to classify.
29
124
  */
30
125
  export declare function cantClassifyDirectiveNative(verb: string): string;
126
+ /**
127
+ * Parse a `.cant` document via the native addon (Layer 2/3).
128
+ *
129
+ * @param content - The raw `.cant` file content to parse.
130
+ */
131
+ export declare function cantParseDocumentNative(content: string): NativeParseDocumentResult;
132
+ /**
133
+ * Parse and validate a `.cant` document via the native addon (42 rules).
134
+ *
135
+ * @param content - The raw `.cant` file content to parse and validate.
136
+ */
137
+ export declare function cantValidateDocumentNative(content: string): NativeValidateResult;
138
+ /**
139
+ * Extract agent profiles from a `.cant` document via the native addon.
140
+ *
141
+ * @param content - The raw `.cant` file content.
142
+ */
143
+ export declare function cantExtractAgentProfilesNative(content: string): unknown[];
144
+ /**
145
+ * Execute a deterministic pipeline from a `.cant` file via the native addon.
146
+ *
147
+ * @param filePath - Absolute or relative path to a `.cant` file.
148
+ * @param pipelineName - The name of the `pipeline { ... }` block to run.
149
+ */
150
+ export declare function cantExecutePipelineNative(filePath: string, pipelineName: string): Promise<NativePipelineResult>;
31
151
  export declare const isWasmAvailable: typeof isNativeAvailable;
152
+ /**
153
+ * Backward-compatible no-op initializer.
154
+ *
155
+ * @remarks
156
+ * The previous WASM loader required an async `init()` call. With napi-rs
157
+ * the binding loads synchronously, so this exists only to keep older
158
+ * callers (e.g. test fixtures) compiling without changes.
159
+ */
32
160
  export declare const initWasm: () => Promise<void>;
@@ -1,17 +1,27 @@
1
1
  "use strict";
2
2
  /**
3
- * Native addon loader for cant-core via napi-rs
3
+ * Native addon loader for cant-core via napi-rs.
4
4
  *
5
- * Loads the napi-rs native addon synchronously. Falls back gracefully
6
- * if the native addon is not available (e.g., unsupported platform).
5
+ * @remarks
6
+ * Loads the napi-rs native addon synchronously on first use. Tries the
7
+ * package-local binary first (`packages/cant/napi/cant.linux-x64-gnu.node`),
8
+ * then falls back to the workspace `cant-napi` crate's `index.cjs` for
9
+ * dev-mode builds where the package binary may not be present yet.
7
10
  *
8
- * Replaces the previous wasm-loader.ts which used async WASM initialization.
11
+ * Replaces the previous WASM loader. Follows the same pattern as
12
+ * `packages/lafs/src/native-loader.ts`.
9
13
  */
10
14
  Object.defineProperty(exports, "__esModule", { value: true });
11
15
  exports.initWasm = exports.isWasmAvailable = void 0;
12
16
  exports.isNativeAvailable = isNativeAvailable;
13
17
  exports.cantParseNative = cantParseNative;
14
18
  exports.cantClassifyDirectiveNative = cantClassifyDirectiveNative;
19
+ exports.cantParseDocumentNative = cantParseDocumentNative;
20
+ exports.cantValidateDocumentNative = cantValidateDocumentNative;
21
+ exports.cantExtractAgentProfilesNative = cantExtractAgentProfilesNative;
22
+ exports.cantExecutePipelineNative = cantExecutePipelineNative;
23
+ /* eslint-disable @typescript-eslint/no-require-imports */
24
+ const node_path_1 = require("node:path");
15
25
  let nativeModule = null;
16
26
  let loadAttempted = false;
17
27
  /**
@@ -22,51 +32,106 @@ function ensureLoaded() {
22
32
  if (loadAttempted)
23
33
  return;
24
34
  loadAttempted = true;
35
+ // The compiled file lives at packages/cant/dist/native-loader.js, so
36
+ // ../napi resolves to packages/cant/napi/cant.linux-x64-gnu.node.
37
+ const packageBinary = (0, node_path_1.join)(__dirname, '..', 'napi', 'cant.linux-x64-gnu.node');
25
38
  try {
26
- // Try loading the napi-rs native addon
27
- // The package name will be @cleocode/cant-native once published
28
- nativeModule = require('@cleocode/cant-native');
39
+ nativeModule = require(packageBinary);
40
+ return;
41
+ }
42
+ catch {
43
+ // Fall through to workspace dev fallback.
44
+ }
45
+ try {
46
+ // Development fallback: load via the cant-napi crate's index.cjs.
47
+ // From packages/cant/dist/ -> ../../../crates/cant-napi/index.cjs.
48
+ nativeModule = require('../../../crates/cant-napi/index.cjs');
29
49
  }
30
50
  catch {
31
- try {
32
- // Development fallback: try loading from the crate build output
33
- nativeModule = require('../../crates/cant-napi');
34
- }
35
- catch {
36
- // Native addon not available — JS fallback will be used
37
- nativeModule = null;
38
- }
51
+ nativeModule = null;
39
52
  }
40
53
  }
41
54
  /**
42
- * Check if the native addon is available
55
+ * Check if the native addon is available.
56
+ *
57
+ * @returns `true` if the native Rust binding loaded successfully.
43
58
  */
44
59
  function isNativeAvailable() {
45
60
  ensureLoaded();
46
61
  return nativeModule !== null;
47
62
  }
48
63
  /**
49
- * Parse a CANT message using the native addon
64
+ * Get the native module, throwing if it failed to load.
65
+ *
66
+ * @internal
67
+ * @throws Error when the native addon could not be loaded.
50
68
  */
51
- function cantParseNative(content) {
69
+ function requireNative() {
52
70
  ensureLoaded();
53
71
  if (!nativeModule) {
54
- throw new Error('Native addon not available.');
72
+ throw new Error('cant-napi native addon not available. Build it with: cargo build --release -p cant-napi');
55
73
  }
56
- return nativeModule.cantParse(content);
74
+ return nativeModule;
75
+ }
76
+ /**
77
+ * Parse a CANT message using the native addon.
78
+ *
79
+ * @param content - The CANT message content to parse.
80
+ */
81
+ function cantParseNative(content) {
82
+ return requireNative().cantParse(content);
57
83
  }
58
84
  /**
59
- * Classify a directive using the native addon
85
+ * Classify a directive verb using the native addon.
86
+ *
87
+ * @param verb - The directive verb to classify.
60
88
  */
61
89
  function cantClassifyDirectiveNative(verb) {
62
- ensureLoaded();
63
- if (!nativeModule) {
64
- throw new Error('Native addon not available.');
65
- }
66
- return nativeModule.cantClassifyDirective(verb);
90
+ return requireNative().cantClassifyDirective(verb);
91
+ }
92
+ /**
93
+ * Parse a `.cant` document via the native addon (Layer 2/3).
94
+ *
95
+ * @param content - The raw `.cant` file content to parse.
96
+ */
97
+ function cantParseDocumentNative(content) {
98
+ return requireNative().cantParseDocument(content);
99
+ }
100
+ /**
101
+ * Parse and validate a `.cant` document via the native addon (42 rules).
102
+ *
103
+ * @param content - The raw `.cant` file content to parse and validate.
104
+ */
105
+ function cantValidateDocumentNative(content) {
106
+ return requireNative().cantValidateDocument(content);
107
+ }
108
+ /**
109
+ * Extract agent profiles from a `.cant` document via the native addon.
110
+ *
111
+ * @param content - The raw `.cant` file content.
112
+ */
113
+ function cantExtractAgentProfilesNative(content) {
114
+ return requireNative().cantExtractAgentProfiles(content);
67
115
  }
68
- // Backward compatibility aliases
116
+ /**
117
+ * Execute a deterministic pipeline from a `.cant` file via the native addon.
118
+ *
119
+ * @param filePath - Absolute or relative path to a `.cant` file.
120
+ * @param pipelineName - The name of the `pipeline { ... }` block to run.
121
+ */
122
+ function cantExecutePipelineNative(filePath, pipelineName) {
123
+ return requireNative().cantExecutePipeline(filePath, pipelineName);
124
+ }
125
+ // Backward compatibility aliases (kept so existing callers compile).
69
126
  exports.isWasmAvailable = isNativeAvailable;
127
+ /**
128
+ * Backward-compatible no-op initializer.
129
+ *
130
+ * @remarks
131
+ * The previous WASM loader required an async `init()` call. With napi-rs
132
+ * the binding loads synchronously, so this exists only to keep older
133
+ * callers (e.g. test fixtures) compiling without changes.
134
+ */
70
135
  const initWasm = async () => {
71
136
  ensureLoaded();
72
137
  };
Binary file
package/package.json CHANGED
@@ -1,27 +1,39 @@
1
1
  {
2
2
  "name": "@cleocode/cant",
3
- "version": "2026.4.5",
4
- "description": "CANT protocol parser and interpreter for CLEO - wraps cant-core WASM",
3
+ "version": "2026.4.7",
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",
7
7
  "files": [
8
8
  "dist/",
9
- "wasm/"
9
+ "napi/"
10
10
  ],
11
11
  "dependencies": {
12
- "@cleocode/contracts": "2026.4.5",
13
- "@cleocode/lafs": "2026.4.5"
12
+ "@cleocode/contracts": "2026.4.7",
13
+ "@cleocode/lafs": "2026.4.7"
14
14
  },
15
15
  "devDependencies": {
16
16
  "typescript": "^5.0.0",
17
17
  "vitest": "^1.0.0"
18
18
  },
19
+ "napi": {
20
+ "name": "cant",
21
+ "package": {
22
+ "name": "@cleocode/cant"
23
+ },
24
+ "triples": {
25
+ "defaults": false,
26
+ "additional": [
27
+ "x86_64-unknown-linux-gnu"
28
+ ]
29
+ }
30
+ },
19
31
  "keywords": [
20
32
  "cleo",
21
33
  "cant",
22
34
  "agent",
23
35
  "parser",
24
- "wasm"
36
+ "napi-rs"
25
37
  ],
26
38
  "license": "MIT",
27
39
  "repository": {
@@ -34,7 +46,7 @@
34
46
  },
35
47
  "scripts": {
36
48
  "build": "tsc",
37
- "build:wasm": "cd ../../crates/cant-core && wasm-pack build --target web --features wasm --out-dir ../../packages/cant/wasm",
49
+ "build:napi": "cargo build --release -p cant-napi && mkdir -p napi && cp ../../target/release/libcant_napi.so napi/cant.linux-x64-gnu.node",
38
50
  "test": "vitest run",
39
51
  "test:watch": "vitest"
40
52
  }
@@ -1,27 +0,0 @@
1
- /**
2
- * WASM loader for cant-core
3
- *
4
- * Loads the WASM module and provides access to CANT parsing functions
5
- */
6
- /** Shape of the CANT WASM module exports. */
7
- type CantWasmModule = typeof import('../wasm/cant_core');
8
- declare let wasmModule: CantWasmModule | null;
9
- /**
10
- * Initialize the WASM module
11
- * Must be called before using any WASM functions
12
- */
13
- export declare function initWasm(): Promise<void>;
14
- /**
15
- * Check if WASM is available
16
- */
17
- export declare function isWasmAvailable(): boolean;
18
- /**
19
- * Parse a CANT message using WASM
20
- */
21
- export declare function cantParseWASM(content: string): unknown;
22
- /**
23
- * Classify a directive using WASM
24
- */
25
- export declare function cantClassifyDirectiveWASM(verb: string): string;
26
- export type { CantWasmModule };
27
- export { wasmModule };
@@ -1,100 +0,0 @@
1
- "use strict";
2
- /**
3
- * WASM loader for cant-core
4
- *
5
- * Loads the WASM module and provides access to CANT parsing functions
6
- */
7
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
- if (k2 === undefined) k2 = k;
9
- var desc = Object.getOwnPropertyDescriptor(m, k);
10
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
- desc = { enumerable: true, get: function() { return m[k]; } };
12
- }
13
- Object.defineProperty(o, k2, desc);
14
- }) : (function(o, m, k, k2) {
15
- if (k2 === undefined) k2 = k;
16
- o[k2] = m[k];
17
- }));
18
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
- Object.defineProperty(o, "default", { enumerable: true, value: v });
20
- }) : function(o, v) {
21
- o["default"] = v;
22
- });
23
- var __importStar = (this && this.__importStar) || (function () {
24
- var ownKeys = function(o) {
25
- ownKeys = Object.getOwnPropertyNames || function (o) {
26
- var ar = [];
27
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
- return ar;
29
- };
30
- return ownKeys(o);
31
- };
32
- return function (mod) {
33
- if (mod && mod.__esModule) return mod;
34
- var result = {};
35
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
- __setModuleDefault(result, mod);
37
- return result;
38
- };
39
- })();
40
- Object.defineProperty(exports, "__esModule", { value: true });
41
- exports.wasmModule = void 0;
42
- exports.initWasm = initWasm;
43
- exports.isWasmAvailable = isWasmAvailable;
44
- exports.cantParseWASM = cantParseWASM;
45
- exports.cantClassifyDirectiveWASM = cantClassifyDirectiveWASM;
46
- // Dynamic import of the WASM module
47
- let wasmModule = null;
48
- exports.wasmModule = wasmModule;
49
- let isInitializing = false;
50
- let initPromise = null;
51
- /**
52
- * Initialize the WASM module
53
- * Must be called before using any WASM functions
54
- */
55
- async function initWasm() {
56
- if (wasmModule)
57
- return;
58
- if (isInitializing) {
59
- return initPromise;
60
- }
61
- isInitializing = true;
62
- initPromise = (async () => {
63
- try {
64
- // Try to load the WASM module
65
- const wasm = await Promise.resolve().then(() => __importStar(require('../wasm/cant_core.js')));
66
- await wasm.default();
67
- exports.wasmModule = wasmModule = wasm;
68
- }
69
- catch (_error) {
70
- console.warn('WASM module not available, falling back to stub implementation');
71
- exports.wasmModule = wasmModule = null;
72
- }
73
- })();
74
- await initPromise;
75
- isInitializing = false;
76
- }
77
- /**
78
- * Check if WASM is available
79
- */
80
- function isWasmAvailable() {
81
- return wasmModule !== null;
82
- }
83
- /**
84
- * Parse a CANT message using WASM
85
- */
86
- function cantParseWASM(content) {
87
- if (!wasmModule) {
88
- throw new Error('WASM not initialized. Call initWasm() first.');
89
- }
90
- return wasmModule.cant_parse(content);
91
- }
92
- /**
93
- * Classify a directive using WASM
94
- */
95
- function cantClassifyDirectiveWASM(verb) {
96
- if (!wasmModule) {
97
- throw new Error('WASM not initialized. Call initWasm() first.');
98
- }
99
- return wasmModule.cant_classify_directive(verb);
100
- }