agent-cfml-check 0.1.0

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 Yap Wei Jun
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.
package/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # Agent CFML Check
2
+
3
+ `agent-cfml-check` is a deterministic, read-only checker for a deliberately bounded CFML structural subset. It is the independent implementation repository for the `agent-cfml-check` entry in the AI-Agent-Tools Hub.
4
+
5
+ ## Current status
6
+
7
+ Version `0.1.0` contains the first feasibility slice. The current implementation is locally verified for:
8
+
9
+ - paired CFML tag nesting;
10
+ - `cfelse` and `cfelseif` ownership and ordering;
11
+ - bodyless tags and self-closing bodyless spellings;
12
+ - nested CFML comments;
13
+ - quoted tag attributes, doubled-quote escapes, and comments inside tag expressions;
14
+ - embedded `cfscript` delimiter balance while respecting strings and comments;
15
+ - recognized pure-script `.cfc` files beginning with `component` or `interface`;
16
+ - UTF-8 source metadata, one-based source positions, and zero-based UTF-8 byte ranges;
17
+ - explicit-root path safety, source stability checks, input validation, output limits, nesting limits, finding limits, and time limits;
18
+ - deterministic JSON and text CLI output.
19
+
20
+ Verification at repository commit `1d5c768`:
21
+
22
+ - `npm test`: 17/17 passed;
23
+ - `npm run typecheck`: passed;
24
+ - `npm pack --dry-run --json`: passed;
25
+ - capabilities, valid-fixture, and misnested-fixture CLI checks: passed.
26
+
27
+ This is not yet a published package, a complete CFML parser, or evidence of Lucee/Adobe ColdFusion compatibility. Hub lifecycle and publication status remain outside this repository's locally verified implementation evidence.
28
+
29
+ ## Install and use
30
+
31
+ ```sh
32
+ npm install
33
+ npm test
34
+ npm run typecheck
35
+ npm run build
36
+ node dist/cli/index.js capabilities --json
37
+ node dist/cli/index.js check --root . fixtures/valid.cfm --json
38
+ node dist/cli/index.js check --root . fixtures/misnested.cfm --json
39
+ ```
40
+
41
+ The package requires Node.js `>=18.18.0`. It has no runtime npm dependencies; TypeScript and Node.js type definitions are development dependencies.
42
+
43
+ The CLI supports:
44
+
45
+ ```text
46
+ agent-cfml-check capabilities [--json] [--pretty]
47
+ agent-cfml-check check --root <directory> <file> [--json] [--pretty]
48
+ ```
49
+
50
+ Check limits can be overridden with `--max-source-bytes`, `--max-nesting`, `--max-findings`, `--max-output-bytes`, and `--time-limit-ms`, subject to the hard caps reported by `capabilities`.
51
+
52
+ The checker requires one explicit root and one `.cfm` or `.cfc` file. JSON mode emits one envelope on stdout; diagnostics stay on stderr. A completed check with structural violations still exits `0` and reports `data.verdict: "violations"`. Exit `3` means unsupported syntax, insufficient evidence, or a resource limit. Exit `4` means an explicit-root or access-policy rejection. See [SPEC.md](SPEC.md) for the complete contract.
53
+
54
+ ## Safety and boundaries
55
+
56
+ The checker never executes CFML or JavaScript from the inspected source, follows includes, reads directories as input, uses the network, installs packages, or modifies the inspected source tree. It reads exactly one regular UTF-8 source file under the explicit root, rejects symlink escapes, and fails closed when the bounded profile cannot establish a result.
57
+
58
+ The supported profile is `cfml-structure-v1`. Unknown/custom/imported tags, optional-body semantics outside the catalog, full expression and runtime semantics, SQL/HTML validity, CFML tag islands inside `cfscript`, and engine behavior are outside the profile or explicitly unverified.
59
+
60
+ ## Repository map
61
+
62
+ - `src/`: TypeScript implementation;
63
+ - `src/core/source-reader.ts`: root, path, file, encoding, and snapshot checks;
64
+ - `src/core/lexer.ts`: bounded CFML tag and CFScript scanning;
65
+ - `src/core/source-index.ts`: source positions and UTF-8 byte ranges;
66
+ - `src/core/checker.ts`: capabilities, checking, envelopes, and output fitting;
67
+ - `src/cli/index.ts`: CLI argument parsing and rendering;
68
+ - `schema/`: JSON Schema for result envelopes;
69
+ - `fixtures/`: valid and misnested examples;
70
+ - `test/`: implementation and CLI tests;
71
+ - `dist/`: generated package output;
72
+ - `DESIGN.md`, `EPIC.md`, `ROADMAP.md`, `TASK.md`, `GOAL.md`, `PROGRESS.md`, `GOAL_PROMPT.md`, `CHANGELOG.md`: repository design, planning, status, and maintenance documents.
73
+
74
+ `package.json` currently publishes `dist`, `README.md`, `SPEC.md`, `schema`, and `fixtures`; the design, planning, progress, goal, prompt, and changelog documents remain repository-maintainer documentation. Although `src/index.ts` contains TypeScript exports, installed-package library imports are not documented as supported until `main`/`exports` are added and tested.
package/SPEC.md ADDED
@@ -0,0 +1,116 @@
1
+ # Agent CFML Check Specification
2
+
3
+ **Status:** frozen feasibility contract for implementation baseline `1d5c768`
4
+ **Tool version:** `0.1.0`
5
+ **Profile:** `cfml-structure-v1`
6
+ **Envelope schema:** `schema/agent-cfml-check-result-v1.schema.json`
7
+
8
+ ## 1. Scope
9
+
10
+ The tool performs deterministic, read-only structural checks on exactly one explicitly selected local UTF-8 `.cfm` or `.cfc` file under an explicitly supplied root. It does not execute CFML, resolve includes, inspect a directory as source input, use the network, or modify the inspected source tree.
11
+
12
+ A completed result proves only the checks listed in that result. It does not prove Lucee or Adobe ColdFusion execution compatibility.
13
+
14
+ ## 2. Operations and interfaces
15
+
16
+ ### CLI operations
17
+
18
+ ```text
19
+ agent-cfml-check capabilities [--json] [--pretty]
20
+ agent-cfml-check check --root <directory> <file> [--json] [--pretty]
21
+ ```
22
+
23
+ `capabilities` reports the profile, operations, extensions, tag catalogue, effective default limits, and exclusions. The CLI does not accept a file, root, or check-limit override for this operation.
24
+
25
+ `check` requires exactly one explicit root and one `.cfm` or `.cfc` file. `--pretty` requires `--json`. `--help` prints usage and `--version` prints `0.1.0`; neither may be combined with other options.
26
+
27
+ ### TypeScript interface
28
+
29
+ `src/index.ts` exports `capabilities`, `checkFile`, `DEFAULT_LIMITS`, `HARD_LIMITS`, `normalizeLimits`, and schema types. The source exports exist, but package metadata currently lacks `main`/`exports`; installed-package library imports are not yet a verified contract.
30
+
31
+ The TypeScript `capabilities(limitsInput)` API may receive limit overrides, while the CLI `capabilities` operation rejects them.
32
+
33
+ ## 3. Supported structures
34
+
35
+ ### Paired bodies
36
+
37
+ `cfcomponent`, `cffunction`, `cfif`, `cfloop`, `cfoutput`, `cfquery`, `cfsavecontent`, `cfscript`, and `cfsilent` require matching closing tags and cannot be self-closing.
38
+
39
+ ### Branches
40
+
41
+ `cfelse` and `cfelseif` must belong to the directly open `cfif`. Only one `cfelse` is accepted, and `cfelseif` cannot follow `cfelse`.
42
+
43
+ ### Bodyless tags
44
+
45
+ `cfargument`, `cfinclude`, `cfqueryparam`, `cfreturn`, and `cfset` are standalone. Ordinary and self-closing spellings are accepted. Closing a bodyless tag is unsupported.
46
+
47
+ Tag names are case-insensitive. Unknown, custom, imported, and unlisted tags are unsupported.
48
+
49
+ ### Comments, attributes, and scripts
50
+
51
+ - Nested `<!--- ... --->` CFML comments are ignored for tag detection.
52
+ - Quoted tag attributes respect backslash escapes, doubled quote escapes, and CFML comments inside the tag expression.
53
+ - HTML comments do not suppress CFML tag detection.
54
+ - Embedded `cfscript` respects quoted strings, `//` line comments, and `/* ... */` block comments while checking `()`, `[]`, and `{}`.
55
+ - CFML tag islands inside `cfscript` are unsupported.
56
+ - A `.cfc` beginning with `component` or `interface` after an optional BOM receives the pure-script delimiter check. A `.cfc` without recognized CFML tags or a recognized pure-script entry is incomplete/unsupported.
57
+
58
+ ## 4. Findings
59
+
60
+ Completed structural findings use these codes:
61
+
62
+ - `UNEXPECTED_CLOSE`
63
+ - `MISMATCHED_CLOSE`
64
+ - `UNCLOSED_TAG`
65
+ - `INVALID_BRANCH`
66
+ - `UNTERMINATED_STRING`
67
+ - `UNTERMINATED_COMMENT`
68
+ - `UNBALANCED_DELIMITER`
69
+
70
+ Each finding contains a message, one-based line/column positions, a zero-based UTF-8 byte range `[start_byte, end_byte)`, and a related opening location or `null`. Findings are ordered by start byte and then code.
71
+
72
+ ## 5. Result envelope
73
+
74
+ Every JSON result contains `schema_version`, `tool`, `status`, `complete`, `data`, `errors`, `warnings`, and `meta`.
75
+
76
+ For `status: "ok"`, `complete` is `true`, `errors` is empty, and `data` is present. A completed structural violation remains `status: "ok"`, with `data.verdict: "violations"` and exit `0`.
77
+
78
+ For `status: "incomplete"` or `"error"`, `complete` is `false`, `data` is `null`, and at least one error is present. No partial success is returned when evidence or a resource limit is insufficient.
79
+
80
+ Check data contains the profile, source path/SHA-256/byte size/UTF-8/BOM metadata, verdict `pass` or `violations`, checks `tag-nesting`, `branch-structure`, and `cfscript-delimiters`, exclusions, and findings. Capabilities data contains the profile, operations, extensions, supported tags, limits, and exclusions.
81
+
82
+ ## 6. Limits
83
+
84
+ | Limit | Default | Hard cap |
85
+ |---|---:|---:|
86
+ | `max_source_bytes` | 2 MiB | 16 MiB |
87
+ | `max_nesting` | 256 | 1,024 |
88
+ | `max_findings` | 100 | 1,000 |
89
+ | `max_output_bytes` | 64 KiB | 1 MiB |
90
+ | `time_limit_ms` | 5,000 | 30,000 |
91
+
92
+ Requested limits must be positive safe integers and cannot exceed hard caps. Reaching a limit returns a fail-closed result rather than partial data.
93
+
94
+ ## 7. Input and safety behavior
95
+
96
+ The source reader requires `.cfm` or `.cfc`, an existing root and file, realpath containment, a regular file, valid UTF-8, no NUL byte, and an effective source-size limit. It checks size and modification time before and after reading and rejects a changed snapshot. Symlink escapes are rejected.
97
+
98
+ Known envelope error codes include `INVALID_ARGUMENT`, `INVALID_LIMIT`, `UNSUPPORTED_EXTENSION`, `ROOT_NOT_FOUND`, `FILE_NOT_FOUND`, `PATH_UNRESOLVED`, `FILE_OUTSIDE_ROOT`, `LIMIT_EXCEEDED`, `SOURCE_CHANGED`, `BINARY_INPUT`, `ENCODING_UNSUPPORTED`, `UNSUPPORTED_SYNTAX`, and `INTERNAL_ERROR`.
99
+
100
+ ## 8. Exit codes and output
101
+
102
+ | Code | Meaning |
103
+ |---:|---|
104
+ | `0` | Completed capabilities or check, including `violations` |
105
+ | `1` | Unexpected internal failure without a safe result |
106
+ | `2` | Invalid argument, limit, extension, or ordinary input rejection |
107
+ | `3` | Unsupported syntax, incomplete evidence, source change, or resource limit |
108
+ | `4` | Explicit-root, path-resolution, or access-policy rejection |
109
+
110
+ JSON mode emits one envelope on stdout; failure diagnostics are also written to stderr. Pretty JSON is intentionally multi-line but remains one JSON envelope.
111
+
112
+ ## 9. Evidence boundary
113
+
114
+ The profile does not validate full CFML grammar, expression/type/runtime semantics, HTML, SQL, include expansion, directory/project context, Lucee/Adobe execution, unknown/custom/imported tags, or CFML tag islands inside `cfscript`. Package publication, Hub lifecycle, cross-platform compatibility, and installed-package library imports are also outside current verified evidence.
115
+
116
+ The repository tests cover CF-01 through CF-13, pure-script `.cfc` handling, and three CLI behaviors. Current local evidence is 17/17 tests, typecheck pass, package dry-run pass, and Windows CLI smoke checks. JSON Schema validation, engine comparison, non-Windows verification, and publication remain pending.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(argv?: readonly string[]): number;
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ import { capabilities, checkFile } from "../core/checker.js";
3
+ import { DEFAULT_LIMITS } from "../core/limits.js";
4
+ import { ToolFailure } from "../schema/errors.js";
5
+ import { pathToFileURL } from "node:url";
6
+ import path from "node:path";
7
+ const HELP = `agent-cfml-check — deterministic, read-only structural checks for a bounded CFML subset
8
+
9
+ Usage:
10
+ agent-cfml-check capabilities [--json] [--pretty]
11
+ agent-cfml-check check --root <directory> <file> [--json] [--pretty]
12
+
13
+ The checker reads exactly one .cfm or .cfc file under the explicit root. It never
14
+ executes CFML, follows includes, uses the network, or modifies source files.
15
+ `;
16
+ function parseInteger(value, name) {
17
+ if (!/^\d+$/u.test(value))
18
+ throw new ToolFailure("INVALID_ARGUMENT", `${name} must be a positive integer`, 2);
19
+ const parsed = Number(value);
20
+ if (!Number.isSafeInteger(parsed) || parsed < 1)
21
+ throw new ToolFailure("INVALID_ARGUMENT", `${name} must be a positive integer`, 2);
22
+ return parsed;
23
+ }
24
+ function parseArgs(argv) {
25
+ let operation = null;
26
+ let file;
27
+ let root;
28
+ let json = false;
29
+ let pretty = false;
30
+ let help = false;
31
+ let version = false;
32
+ const limits = {};
33
+ const nextValue = (index, flag) => {
34
+ const value = argv[index + 1];
35
+ if (value === undefined || value.startsWith("--"))
36
+ throw new ToolFailure("INVALID_ARGUMENT", `${flag} requires a value`, 2);
37
+ return value;
38
+ };
39
+ for (let index = 0; index < argv.length; index += 1) {
40
+ const argument = argv[index];
41
+ if (argument === undefined)
42
+ continue;
43
+ if (argument === "--help" || argument === "-h") {
44
+ help = true;
45
+ continue;
46
+ }
47
+ if (argument === "--version") {
48
+ version = true;
49
+ continue;
50
+ }
51
+ if (argument === "--json") {
52
+ json = true;
53
+ continue;
54
+ }
55
+ if (argument === "--pretty") {
56
+ pretty = true;
57
+ continue;
58
+ }
59
+ if (argument === "--root") {
60
+ root = nextValue(index, argument);
61
+ index += 1;
62
+ continue;
63
+ }
64
+ const limitsMap = {
65
+ "--max-source-bytes": "max_source_bytes",
66
+ "--max-nesting": "max_nesting",
67
+ "--max-findings": "max_findings",
68
+ "--max-output-bytes": "max_output_bytes",
69
+ "--time-limit-ms": "time_limit_ms",
70
+ };
71
+ const limitName = limitsMap[argument];
72
+ if (limitName !== undefined) {
73
+ limits[limitName] = parseInteger(nextValue(index, argument), argument);
74
+ index += 1;
75
+ continue;
76
+ }
77
+ if (argument.startsWith("--"))
78
+ throw new ToolFailure("INVALID_ARGUMENT", `Unknown flag ${argument}`, 2);
79
+ if (operation === null && (argument === "capabilities" || argument === "check")) {
80
+ operation = argument;
81
+ continue;
82
+ }
83
+ if (operation === "check" && file === undefined) {
84
+ file = argument;
85
+ continue;
86
+ }
87
+ throw new ToolFailure("INVALID_ARGUMENT", "Unexpected positional argument", 2);
88
+ }
89
+ if (help && (version || operation !== null || file !== undefined || root !== undefined || json || pretty || Object.keys(limits).length > 0)) {
90
+ throw new ToolFailure("INVALID_ARGUMENT", "--help cannot be combined with other options", 2);
91
+ }
92
+ if (version && (help || operation !== null || file !== undefined || root !== undefined || json || pretty || Object.keys(limits).length > 0)) {
93
+ throw new ToolFailure("INVALID_ARGUMENT", "--version cannot be combined with other options", 2);
94
+ }
95
+ if (help || version)
96
+ return { operation: "capabilities", file: undefined, root: undefined, json, pretty, limits, help, version };
97
+ if (operation === null)
98
+ throw new ToolFailure("INVALID_ARGUMENT", "An operation is required: capabilities or check", 2);
99
+ if (operation === "capabilities" && (file !== undefined || root !== undefined || Object.keys(limits).length > 0))
100
+ throw new ToolFailure("INVALID_ARGUMENT", "capabilities does not accept a file, root, or check limits", 2);
101
+ if (operation === "check" && (file === undefined || root === undefined))
102
+ throw new ToolFailure("INVALID_ARGUMENT", "check requires --root and one source file", 2);
103
+ if (pretty && !json)
104
+ throw new ToolFailure("INVALID_ARGUMENT", "--pretty requires --json", 2);
105
+ return { operation, file, root, json, pretty, limits, help, version };
106
+ }
107
+ function renderText(envelope) {
108
+ if (envelope.status !== "ok" || envelope.data === null)
109
+ return `${envelope.status}: ${envelope.errors.map((error) => `${error.code}: ${error.message}`).join("; ")}\n`;
110
+ const data = envelope.data;
111
+ const lines = [`${envelope.tool.id} ${envelope.tool.version}`, `status: ${envelope.status}`, `verdict: ${data.verdict ?? "n/a"}`, `source: ${data.source?.path ?? "n/a"}`, `findings: ${data.findings?.length ?? 0}`];
112
+ for (const item of data.findings ?? [])
113
+ lines.push(` ${item.code} at ${item.location.start.line}:${item.location.start.column}: ${item.message}`);
114
+ return `${lines.join("\n")}\n`;
115
+ }
116
+ export function main(argv = process.argv.slice(2)) {
117
+ try {
118
+ const args = parseArgs(argv);
119
+ if (args.help) {
120
+ process.stdout.write(HELP);
121
+ return 0;
122
+ }
123
+ if (args.version) {
124
+ process.stdout.write(`${"0.1.0"}\n`);
125
+ return 0;
126
+ }
127
+ const result = args.operation === "capabilities"
128
+ ? { envelope: capabilities(args.limits), exit_code: 0 }
129
+ : checkFile({ root: args.root, file: args.file, limits: args.limits });
130
+ if (args.json)
131
+ process.stdout.write(`${JSON.stringify(result.envelope, null, args.pretty ? 2 : undefined)}\n`);
132
+ else
133
+ process.stdout.write(renderText(result.envelope));
134
+ if (result.envelope.status !== "ok")
135
+ for (const error of result.envelope.errors)
136
+ process.stderr.write(`error: ${error.code}: ${error.message}\n`);
137
+ return result.exit_code;
138
+ }
139
+ catch (error) {
140
+ const failure = error instanceof ToolFailure ? error : new ToolFailure("INTERNAL_ERROR", "The CLI failed without a safe result", 1);
141
+ const envelope = {
142
+ schema_version: "1.0.0",
143
+ tool: { id: "agent-cfml-check", version: "0.1.0" },
144
+ status: "error",
145
+ complete: false,
146
+ data: null,
147
+ errors: [{ code: failure.code, message: failure.message }],
148
+ warnings: [],
149
+ meta: { scope: "CLI invocation", limits: DEFAULT_LIMITS },
150
+ };
151
+ process.stdout.write(`${JSON.stringify(envelope)}\n`);
152
+ process.stderr.write(`error: ${failure.code}: ${failure.message}\n`);
153
+ return failure.exitCode;
154
+ }
155
+ }
156
+ if (process.argv[1] !== undefined && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url)
157
+ process.exitCode = main();
@@ -0,0 +1,6 @@
1
+ import type { CapabilitiesData, CheckData, CheckLimits, Envelope, CheckOptions } from "../schema/types.js";
2
+ export declare function capabilities(limitsInput?: Partial<CheckLimits>): Envelope<CapabilitiesData>;
3
+ export declare function checkFile(options: CheckOptions): {
4
+ envelope: Envelope<CheckData>;
5
+ exit_code: 0 | 1 | 2 | 3 | 4;
6
+ };
@@ -0,0 +1,94 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import { ToolFailure } from "../schema/errors.js";
4
+ import { PROFILE_ID, TOOL_ID, TOOL_VERSION } from "../schema/types.js";
5
+ import { normalizeLimits } from "./limits.js";
6
+ import { loadSource } from "./source-reader.js";
7
+ import { checkPureScriptCfc, PAIRED_TAGS, BRANCH_TAGS, BODYLESS_TAGS } from "./lexer.js";
8
+ function meta(limits) {
9
+ return { scope: "One explicitly selected local UTF-8 .cfm or .cfc file under an explicit root", limits };
10
+ }
11
+ function emptyEnvelope(status, limits, errors) {
12
+ return { schema_version: "1.0.0", tool: { id: TOOL_ID, version: TOOL_VERSION }, status, complete: false, data: null, errors, warnings: [], meta: meta(limits) };
13
+ }
14
+ function fitOutput(envelope, limits) {
15
+ const serialized = JSON.stringify(envelope);
16
+ if (Buffer.byteLength(serialized, "utf8") <= limits.max_output_bytes)
17
+ return envelope;
18
+ const fallback = emptyEnvelope("incomplete", limits, [{ code: "LIMIT_EXCEEDED", message: "The output limit was reached; result data was withheld" }]);
19
+ if (Buffer.byteLength(JSON.stringify(fallback), "utf8") <= limits.max_output_bytes)
20
+ return fallback;
21
+ return { schema_version: "1.0.0", tool: { id: TOOL_ID, version: TOOL_VERSION }, status: "incomplete", complete: false, data: null, errors: [{ code: "LIMIT_EXCEEDED", message: "Output limit exceeded" }], warnings: [], meta: meta(limits) };
22
+ }
23
+ export function capabilities(limitsInput) {
24
+ const limits = normalizeLimits(limitsInput);
25
+ return {
26
+ schema_version: "1.0.0",
27
+ tool: { id: TOOL_ID, version: TOOL_VERSION },
28
+ status: "ok",
29
+ complete: true,
30
+ data: {
31
+ profile: PROFILE_ID,
32
+ operations: ["capabilities", "check"],
33
+ extensions: [".cfm", ".cfc"],
34
+ supported_tags: {
35
+ paired_bodies: [...PAIRED_TAGS].sort(),
36
+ branches: [...BRANCH_TAGS].sort(),
37
+ bodyless: [...BODYLESS_TAGS].sort(),
38
+ },
39
+ limits,
40
+ exclusions: [
41
+ "Full CFML grammar and expression/type/runtime semantics",
42
+ "HTML validity, SQL correctness, include expansion, and Lucee execution",
43
+ "Unknown/custom/imported tag libraries and CFML tag islands inside cfscript",
44
+ ],
45
+ },
46
+ errors: [],
47
+ warnings: [],
48
+ meta: meta(limits),
49
+ };
50
+ }
51
+ export function checkFile(options) {
52
+ let limits;
53
+ try {
54
+ limits = normalizeLimits(options.limits);
55
+ }
56
+ catch (error) {
57
+ const fallback = normalizeLimits(undefined);
58
+ const failure = error instanceof ToolFailure ? error : new ToolFailure("INVALID_LIMIT", "Invalid limits", 2);
59
+ return { envelope: emptyEnvelope("error", fallback, [{ code: failure.code, message: failure.message }]), exit_code: failure.exitCode };
60
+ }
61
+ try {
62
+ const source = loadSource({ root: options.root, file: options.file, limits });
63
+ const extension = path.extname(source.relative_path);
64
+ const scanned = checkPureScriptCfc(source.source, extension, limits);
65
+ if (scanned.incomplete.length > 0) {
66
+ return { envelope: fitOutput(emptyEnvelope("incomplete", limits, scanned.incomplete), limits), exit_code: 3 };
67
+ }
68
+ const data = {
69
+ profile: PROFILE_ID,
70
+ source: {
71
+ path: source.relative_path,
72
+ sha256: createHash("sha256").update(source.bytes).digest("hex"),
73
+ byte_size: source.bytes.byteLength,
74
+ encoding: "utf-8",
75
+ bom: source.bom,
76
+ },
77
+ verdict: scanned.findings.length === 0 ? "pass" : "violations",
78
+ checks: ["tag-nesting", "branch-structure", "cfscript-delimiters"],
79
+ exclusions: [
80
+ "Expression, type, SQL, HTML, include, and runtime semantics are unverified",
81
+ "Unknown/custom/imported tags and CFML tag islands inside cfscript are unsupported",
82
+ ],
83
+ findings: scanned.findings,
84
+ };
85
+ return {
86
+ envelope: fitOutput({ schema_version: "1.0.0", tool: { id: TOOL_ID, version: TOOL_VERSION }, status: "ok", complete: true, data, errors: [], warnings: [], meta: meta(limits) }, limits),
87
+ exit_code: 0,
88
+ };
89
+ }
90
+ catch (error) {
91
+ const failure = error instanceof ToolFailure ? error : new ToolFailure("INTERNAL_ERROR", "The checker failed without a safe result", 1);
92
+ return { envelope: fitOutput(emptyEnvelope(failure.exitCode === 3 ? "incomplete" : "error", limits, [{ code: failure.code, message: failure.message }]), limits), exit_code: failure.exitCode };
93
+ }
94
+ }
@@ -0,0 +1,28 @@
1
+ import type { Finding } from "../schema/types.js";
2
+ export declare const PAIRED_TAGS: Set<string>;
3
+ export declare const BRANCH_TAGS: Set<string>;
4
+ export declare const BODYLESS_TAGS: Set<string>;
5
+ interface ScriptRegion {
6
+ content_start: number;
7
+ content_end: number;
8
+ }
9
+ interface ScanResult {
10
+ findings: Finding[];
11
+ incomplete: Array<{
12
+ code: string;
13
+ message: string;
14
+ }>;
15
+ script_regions: ScriptRegion[];
16
+ saw_cfml_tag: boolean;
17
+ }
18
+ export declare function scanCfml(source: string, limits: {
19
+ max_nesting: number;
20
+ max_findings: number;
21
+ time_limit_ms: number;
22
+ }): ScanResult;
23
+ export declare function checkPureScriptCfc(source: string, extension: string, limits: {
24
+ max_nesting: number;
25
+ max_findings: number;
26
+ time_limit_ms: number;
27
+ }): ScanResult;
28
+ export {};