flamerjs 1.0.0 → 1.1.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,141 @@
1
+ # Changelog
2
+
3
+ All notable changes to flamer are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [1.0.0] - 2026-10-24
8
+
9
+ ### Added
10
+
11
+ - Rust and C++ native workers under `native/core` for coordinated native
12
+ processing.
13
+ - Versioned JSONL worker protocol with TypeScript integration through
14
+ `--engine all|typescript|rust|cpp`.
15
+ - Dedicated native Rust and C++ build/test workflows.
16
+ - Architecture documentation covering runtime layers, protocol, and fallback
17
+ behavior.
18
+
19
+ ### Fixed
20
+
21
+ - Native execution failures are explicit for targeted workers and safely fall
22
+ back to TypeScript in the default `all` mode.
23
+
24
+ ## [1.1.1] - 2026-10-24
25
+
26
+ ### Fixed
27
+
28
+ - Align the main npm package and native worker package versions.
29
+ - Publish all supported Linux, macOS, and Windows x64/arm64 native packages.
30
+ - Include the changelog and architecture documentation in the npm tarball.
31
+
32
+ ## [1.1.0] - 2026-10-24
33
+
34
+ ### Added
35
+
36
+ - Optional platform-specific npm packages containing both native workers.
37
+ - Release automation for Linux x64, macOS x64/arm64, and Windows x64 native
38
+ packages.
39
+ - A native package builder and explicit Rust/C++ build-phase output.
40
+ - `scripts/install.sh` with npm installation and guarded native release-asset
41
+ installation paths.
42
+ - The default `flamer .` execution coordinates TypeScript, Rust, and C++
43
+ together, with pure TypeScript fallback when native workers are unavailable.
44
+ - Rust owns shared file metrics and Rust-specific checks; C++ owns C/C++-specific
45
+ checks, so the workers no longer perform the same analysis.
46
+
47
+ ### Fixed
48
+
49
+ - CI now builds before tests that execute the bundled CLI.
50
+ - Node 18 builds use a compatible pinned tsup release.
51
+ - Conventional Commit validation accepts the `ci` type.
52
+
53
+ - Dedicated Codecov coverage reporting and CodeQL analysis workflows.
54
+
55
+ ## [0.1.6] - 2026-10-24
56
+
57
+ ### Fixed
58
+
59
+ - Updated the README Quick Start to use the published `flamerjs` package with
60
+ `npx`, local installation, and global installation examples.
61
+ - Updated CLI examples to use the published `flamerjs` command while retaining
62
+ the source checkout workflow for contributors.
63
+
64
+ ## [0.1.5] - 2026-10-24
65
+
66
+ ### Added
67
+
68
+ - Human-readable terminal output is now the default CLI mode, with severity
69
+ grouping, clean scan summaries, and TTY-aware ANSI colors.
70
+ - `--color` and `--no-color` flags for controlling terminal colors.
71
+ - High-confidence detection for explicit not-implemented errors and empty
72
+ error handlers.
73
+ - npm packaging metadata, a `flamerjs` command alias, and automated publishing
74
+ to npm and GitHub Packages on version tags.
75
+
76
+ ### Fixed
77
+
78
+ - README and CI test-count checks now follow the current TypeScript test suite.
79
+ - CLI, contributor, and agent documentation now matches the `src/` and `dist/`
80
+ project layout.
81
+
82
+ ### Removed
83
+
84
+ - Bun as a project development dependency and lockfile.
85
+ - JSON as the default interactive output mode; JSON remains available with
86
+ `--format json`.
87
+
88
+ ## [0.1.0] - 2026-10-02
89
+
90
+ First public release.
91
+
92
+ ### Added
93
+
94
+ - Single-pass tree scanner covering 25 languages across 70 file extensions:
95
+ JavaScript, TypeScript, Python, Rust, Go, C, C++, C#, Java, Kotlin, Scala,
96
+ Terraform, HCL, Docker, SQL, Ruby, PHP, Shell, PowerShell, Haskell, Elixir,
97
+ Lua, R, Solidity, Swift, Dart, Zig, and Jupyter notebooks.
98
+ - Eight analyzers, each isolated so one throwing does not stop the rest:
99
+ `universal`, `notebooks`, `scaffolding`, `testing`, `git`, `node`, `python`,
100
+ `systems`.
101
+ - Redacted credential detection for 14 credential shapes, with test and fixture
102
+ paths exempt so a repository's own fake secrets are not reported.
103
+ - Adaptive bloat detection with per-language baselines, so a large generated
104
+ file is not scored as though a human wrote every line.
105
+ - Per-language test coverage ratios, so a mostly-Terraform repository is not
106
+ graded as though it were all application code.
107
+ - The roast persona in `SKILL.md`, including a confirmation gate, an audit mode
108
+ that drops the character, and an instruction to defer deep security analysis
109
+ to the `poison` skill when it is installed.
110
+ - JSON output by default with a human-readable `--format summary`.
111
+
112
+ ### Performance
113
+
114
+ Measured on a 3.0 GB monorepo of 98,161 files, 71,150 of them source:
115
+ **3.60s before, 2.10s after**, with byte-identical findings.
116
+
117
+ - Newline discovery uses `indexOf` rather than a per-character `charCodeAt`
118
+ loop. Measured 3.7x faster on a 525 MB corpus; `split` measured slower than
119
+ both while also allocating an array of every line.
120
+ - The credential scanner gates 13 of its 14 patterns behind a literal substring
121
+ test, so the regex engine never runs against a file that cannot contain that
122
+ shape of credential. The assignment pattern is intentionally left ungated
123
+ because its names match case insensitively and an incomplete gate would be a
124
+ silently dropped rule.
125
+ - Line numbers for a credential finding are resolved by binary search over a
126
+ lazily built offset table. The previous approach sliced and split the text
127
+ once per match, which is up to 280 times on a single file.
128
+
129
+ ### Notes
130
+
131
+ - Zero runtime dependencies. Node 18 or newer, no install step.
132
+ - `poison` is a separate project. Flamer does not import it, and the handoff is
133
+ a runtime capability check in `SKILL.md` rather than a code dependency.
134
+
135
+ [Unreleased]: https://github.com/dummy3ye/flamer/compare/v1.1.1...HEAD
136
+ [1.1.1]: https://github.com/dummy3ye/flamer/releases/tag/v1.1.1
137
+ [1.1.0]: https://github.com/dummy3ye/flamer/releases/tag/v1.1.0
138
+ [1.0.0]: https://github.com/dummy3ye/flamer/releases/tag/v1.0.0
139
+ [0.1.6]: https://github.com/dummy3ye/flamer/releases/tag/v0.1.6
140
+ [0.1.5]: https://github.com/dummy3ye/flamer/releases/tag/v0.1.5
141
+ [0.1.0]: https://github.com/OWNER/flamer/releases/tag/v0.1.0
package/README.md CHANGED
@@ -51,18 +51,22 @@ can be used locally, in pull requests, or in CI.
51
51
  - **Language-aware checks:** JavaScript/TypeScript, Python, Rust, Go, C/C++,
52
52
  C#, Zig, SQL, notebooks, and additional language profiles.
53
53
  - **Stable JSON output:** suitable for CI annotations and other automation.
54
- - **Single-pass scanning:** files are walked once and content is read once.
55
- - **Optional native workers:** Rust and C++ workers for isolated file metrics,
56
- with TypeScript remaining the default engine.
54
+ - **Single-pass TypeScript scanning:** the repository is walked once and
55
+ content is collected once before analyzers run.
56
+ - **Coordinated native workers:** Rust handles shared file metrics and
57
+ Rust-specific checks, while C++ handles C/C++ checks; TypeScript remains the
58
+ source of truth and fallback path.
57
59
 
58
60
  ## Requirements
59
61
 
60
62
  - Node.js 18 or newer
61
63
  - npm
62
64
 
63
- The default TypeScript engine needs only Node.js and npm. Native mode additionally
64
- requires Rust/Cargo for the Rust worker or a C++17 compiler and CMake for the
65
- C++ worker.
65
+ The default TypeScript engine needs only Node.js and npm. The published package
66
+ also declares optional, platform-specific native packages containing both
67
+ workers, so a compatible npm install gets full mode automatically. Building
68
+ workers from this repository requires Rust/Cargo and a C++17 compiler with
69
+ CMake.
66
70
 
67
71
  ## Quick start
68
72
 
@@ -107,24 +111,24 @@ flamerjs /path/to/repository --format summary
107
111
  flamerjs [path] [options]
108
112
  ```
109
113
 
110
- | Option | Description |
111
- | ------------------------------------- | ----------------------------------------------------------------------------- |
112
- | `--package, --scope <name>` | Analyze only a named sub-project |
113
- | `--path <dir>` | Set the repository root; defaults to the current directory |
114
- | `--format <json\|summary>` | Select JSON or human-readable output; summary is the default |
115
- | `--out <file>` | Also write the JSON report to a file |
116
- | `--fail-on <SEVERITY>` | Exit with status 1 at or above `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, or `INFO` |
117
- | `--max-files <n>` | Limit the number of files scanned |
118
- | `--commit-limit <n>` | Limit commits inspected by Git checks |
119
- | `--no-git` | Skip Git analysis |
120
- | `--no-lockfile-warning` | Suppress the missing-lockfile suggestion |
121
- | `--ignore-dir <name>` | Ignore an additional directory name; repeatable |
122
- | `--quiet` | Suppress the human summary |
123
- | `--color` | Force ANSI colors |
124
- | `--no-color` | Disable ANSI colors |
125
- | `--engine <typescript\|native\|auto>` | Select the scan engine; TypeScript is the default |
126
- | `--list-languages` | List recognized language profiles |
127
- | `--help` | Show command help |
114
+ | Option | Description |
115
+ | --------------------------------------- | ----------------------------------------------------------------------------- |
116
+ | `--package, --scope <name>` | Analyze only a named sub-project |
117
+ | `--path <dir>` | Set the repository root; defaults to the current directory |
118
+ | `--format <json\|summary>` | Select JSON or human-readable output; summary is the default |
119
+ | `--out <file>` | Also write the JSON report to a file |
120
+ | `--fail-on <SEVERITY>` | Exit with status 1 at or above `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, or `INFO` |
121
+ | `--max-files <n>` | Limit the number of files scanned |
122
+ | `--commit-limit <n>` | Limit commits inspected by Git checks |
123
+ | `--no-git` | Skip Git analysis |
124
+ | `--no-lockfile-warning` | Suppress the missing-lockfile suggestion |
125
+ | `--ignore-dir <name>` | Ignore an additional directory name; repeatable |
126
+ | `--quiet` | Suppress the human summary |
127
+ | `--color` | Force ANSI colors |
128
+ | `--no-color` | Disable ANSI colors |
129
+ | `--engine <all\|typescript\|rust\|cpp>` | Run all engines (default) or select one engine |
130
+ | `--list-languages` | List recognized language profiles |
131
+ | `--help` | Show command help |
128
132
 
129
133
  Exit status `0` means the scan completed; findings may still exist. Status `1`
130
134
  means `--fail-on` was reached, and status `2` indicates invalid usage or a
@@ -177,30 +181,57 @@ The project uses Node.js and npm as its supported development toolchain.
177
181
  `package-lock.json` is the canonical dependency lockfile.
178
182
  The current test suite contains **175 tests**.
179
183
 
180
- ### Optional native engine
184
+ ### Native engine
181
185
 
182
- Flamer includes an experimental Rust worker for native file metrics. It is
183
- disabled by default and does not change findings:
186
+ Flamer runs all three components by default: TypeScript analyzers plus the Rust
187
+ and C++ native workers. They do different work and run concurrently:
188
+
189
+ - TypeScript walks the repository, runs the generic analyzers, and produces the
190
+ report.
191
+ - Rust computes shared metrics for all selected files and checks Rust files for
192
+ `.unwrap()` usage.
193
+ - C++ checks only C and C++ files for native-language issues such as
194
+ `#include <bits/stdc++.h>`.
184
195
 
185
196
  ```bash
186
- npm run native:build
187
- node dist/cli.js . --engine native
197
+ npm run native:build:all
198
+ node dist/cli.js .
188
199
  ```
189
200
 
190
- Use `--engine auto` to fall back to TypeScript when the worker is unavailable.
201
+ Native findings are merged into the TypeScript report only after all requested
202
+ native jobs succeed. If a native binary is unavailable or fails, the default
203
+ `all` mode falls back to a pure TypeScript report. Use `--engine typescript` for
204
+ a native-free scan, or `--engine rust` / `--engine cpp` to require one specific
205
+ worker; explicit native modes return an error when their worker fails.
191
206
  The native protocol and worker details are documented in
192
207
  [`native/core/README.md`](native/core/README.md).
193
208
 
194
209
  See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the runtime layers,
195
210
  worker protocol, engine selection, and native development workflow.
196
211
 
212
+ For the source installation path:
213
+
214
+ ```bash
215
+ curl -fsSL https://raw.githubusercontent.com/dummy3ye/flamer/master/scripts/install.sh | sh
216
+ ```
217
+
218
+ Platform-specific native release binaries are not bundled in the current npm
219
+ package yet. The npm package uses optional native packages when they are
220
+ published for the current platform; if npm skips them, TypeScript fallback is
221
+ automatic. The installer's `native` path is reserved for standalone releases
222
+ that attach those assets.
223
+
197
224
  ## How it works
198
225
 
199
226
  1. Detect the repository layout, languages, and ecosystems.
200
227
  2. Walk the tree once and collect file metadata and content-derived signals.
201
228
  3. Run isolated analyzers against the collected records.
202
- 4. Optionally collect native file metrics through the versioned worker protocol.
203
- 5. Sort findings and emit JSON or a concise summary.
229
+ 4. Run the requested native jobs through the versioned JSONL protocol. Native
230
+ jobs receive non-overlapping responsibilities and return metrics or
231
+ language-specific findings.
232
+ 5. Merge successful native findings, sort everything, and emit JSON or a
233
+ concise summary. In default `all` mode, any native failure returns to the
234
+ TypeScript-only result.
204
235
 
205
236
  Analyzers are intentionally factual and conservative. A finding should identify
206
237
  something actionable rather than merely express a preference.
package/dist/cli.js CHANGED
@@ -353,7 +353,8 @@ var CATEGORIES = [
353
353
  "structure",
354
354
  "git",
355
355
  "error",
356
- "unfinished"
356
+ "unfinished",
357
+ "native"
357
358
  ];
358
359
  function rank(severity) {
359
360
  const r = RANK[String(severity).toUpperCase()];
@@ -2902,39 +2903,86 @@ var ANALYZERS = [
2902
2903
  ];
2903
2904
 
2904
2905
  // src/native.ts
2905
- import { spawnSync } from "child_process";
2906
+ import { spawn } from "child_process";
2906
2907
  import { existsSync as existsSync3 } from "fs";
2907
2908
  import { join as join6 } from "path";
2908
- function nativeWorkerPath(root) {
2909
- const configured = process.env.FLAMER_CORE;
2910
- if (configured) return configured;
2911
- return join6(root, "native", "core", "rust", "target", "release", "flamer-core");
2912
- }
2913
- function runNativeMetrics(root, files) {
2914
- const worker = nativeWorkerPath(root);
2915
- if (!existsSync3(worker)) {
2916
- throw new Error(`native worker not found at ${worker}; run npm run native:build`);
2917
- }
2918
- const request = JSON.stringify({ protocol: 1, operation: "file_metrics", root, files });
2919
- const result = spawnSync(worker, { input: `${request}
2920
- `, encoding: "utf8", timeout: 3e4 });
2921
- if (result.error) throw new Error(`native worker failed: ${result.error.message}`);
2922
- if (result.status !== 0) {
2923
- throw new Error(`native worker exited with status ${result.status}: ${result.stderr.trim()}`);
2924
- }
2925
- let response;
2909
+ import { createRequire } from "module";
2910
+ var require2 = createRequire(import.meta.url);
2911
+ function nativePackageName() {
2912
+ const platform = `${process.platform}-${process.arch}`;
2913
+ const supported = /* @__PURE__ */ new Set([
2914
+ "linux-x64",
2915
+ "linux-arm64",
2916
+ "darwin-x64",
2917
+ "darwin-arm64",
2918
+ "win32-x64",
2919
+ "win32-arm64"
2920
+ ]);
2921
+ return supported.has(platform) ? `@dummy3ye/flamer-native-${platform}` : null;
2922
+ }
2923
+ function packagedWorkerPath(worker) {
2924
+ const packageName = nativePackageName();
2925
+ if (!packageName) return null;
2926
2926
  try {
2927
- response = JSON.parse(result.stdout);
2927
+ return require2.resolve(`${packageName}/${worker}`);
2928
2928
  } catch {
2929
- throw new Error("native worker returned invalid JSON");
2930
- }
2931
- if (response.protocol !== 1 || response.operation !== "file_metrics") {
2932
- throw new Error("native worker returned an incompatible response");
2929
+ return null;
2933
2930
  }
2934
- if (!response.ok) {
2935
- throw new Error(response.errors.map((error) => `${error.path}: ${error.message}`).join("; "));
2931
+ }
2932
+ function nativeWorkerPath(root, worker) {
2933
+ const configured = worker === "rust" ? process.env.FLAMER_RUST_CORE : process.env.FLAMER_CPP_CORE;
2934
+ if (configured) return configured;
2935
+ if (process.env.FLAMER_CORE && worker === "rust") return process.env.FLAMER_CORE;
2936
+ const packaged = packagedWorkerPath(worker);
2937
+ if (packaged) return packaged;
2938
+ return worker === "rust" ? join6(root, "native", "core", "rust", "target", "release", "flamer-core") : join6(root, "native", "core", "cpp", "build", "flamer-core-cpp");
2939
+ }
2940
+ function runNativeWorker(root, files, worker, operation = "file_metrics") {
2941
+ const executable = nativeWorkerPath(root, worker);
2942
+ if (!existsSync3(executable)) {
2943
+ return Promise.reject(
2944
+ new Error(`${worker} worker not found at ${executable}; build the native workers first`)
2945
+ );
2936
2946
  }
2937
- return response;
2947
+ const request = JSON.stringify({ protocol: 1, operation, root, files });
2948
+ return new Promise((resolve2, reject) => {
2949
+ const child = spawn(executable, { stdio: ["pipe", "pipe", "pipe"] });
2950
+ let stdout = "";
2951
+ let stderr = "";
2952
+ const timeout = setTimeout(() => {
2953
+ child.kill();
2954
+ reject(new Error(`${worker} worker timed out`));
2955
+ }, 3e4);
2956
+ child.stdout.on("data", (chunk) => stdout += chunk);
2957
+ child.stderr.on("data", (chunk) => stderr += chunk);
2958
+ child.on("error", (error) => {
2959
+ clearTimeout(timeout);
2960
+ reject(new Error(`${worker} worker failed: ${error.message}`));
2961
+ });
2962
+ child.on("close", (status) => {
2963
+ clearTimeout(timeout);
2964
+ if (status !== 0) {
2965
+ reject(new Error(`${worker} worker exited with status ${status}: ${stderr.trim()}`));
2966
+ return;
2967
+ }
2968
+ try {
2969
+ const response = JSON.parse(stdout);
2970
+ if (response.protocol !== 1 || response.operation !== operation) {
2971
+ throw new Error(`${worker} worker returned an incompatible response`);
2972
+ }
2973
+ if (!response.ok) {
2974
+ throw new Error(
2975
+ response.errors.map((error) => `${error.path}: ${error.message}`).join("; ")
2976
+ );
2977
+ }
2978
+ resolve2(response);
2979
+ } catch (error) {
2980
+ reject(error instanceof Error ? error : new Error(String(error)));
2981
+ }
2982
+ });
2983
+ child.stdin.end(`${request}
2984
+ `);
2985
+ });
2938
2986
  }
2939
2987
 
2940
2988
  // src/cli.ts
@@ -2954,7 +3002,7 @@ function parseArgs(argv) {
2954
3002
  listLanguages: false,
2955
3003
  help: false,
2956
3004
  color: null,
2957
- engine: "typescript",
3005
+ engine: "all",
2958
3006
  ignoreDirs: []
2959
3007
  };
2960
3008
  for (let i = 0; i < argv.length; i++) {
@@ -3001,8 +3049,8 @@ function parseArgs(argv) {
3001
3049
  opts.color = false;
3002
3050
  break;
3003
3051
  case "--engine":
3004
- opts.engine = argv[++i] || "typescript";
3005
- if (!["typescript", "native", "auto"].includes(opts.engine)) {
3052
+ opts.engine = argv[++i] || "all";
3053
+ if (!["all", "typescript", "rust", "cpp"].includes(opts.engine)) {
3006
3054
  process.stderr.write(`invalid engine: ${opts.engine}
3007
3055
  `);
3008
3056
  process.exit(2);
@@ -3049,8 +3097,8 @@ Options:
3049
3097
  --quiet suppress the human summary
3050
3098
  --color force ANSI colors
3051
3099
  --no-color disable ANSI colors
3052
- --engine <typescript|native|auto>
3053
- choose the scan engine (default: typescript)
3100
+ --engine <all|typescript|rust|cpp>
3101
+ all runs TypeScript, Rust, and C++ (default: all)
3054
3102
  --list-languages print the supported language table
3055
3103
  -h, --help show this help
3056
3104
  `);
@@ -3067,7 +3115,7 @@ function printLanguages() {
3067
3115
  ${allExtensions().length} file extensions recognized.
3068
3116
  `);
3069
3117
  }
3070
- function run2(opts) {
3118
+ async function run2(opts) {
3071
3119
  const started = Date.now();
3072
3120
  const root = opts.root;
3073
3121
  if (!existsSync4(root) || !isDir(root)) {
@@ -3209,35 +3257,52 @@ function run2(opts) {
3209
3257
  }
3210
3258
  };
3211
3259
  if (opts.engine !== "typescript") {
3212
- try {
3213
- const native = runNativeMetrics(
3214
- root,
3215
- scopedFiles.map((file) => file.rel)
3260
+ const allFiles = scopedFiles.map((file) => file.rel);
3261
+ const cppFiles = scopedFiles.filter((file) => ["c", "cpp"].includes(file.lang)).map((file) => file.rel);
3262
+ const rustFiles = scopedFiles.filter((file) => file.lang === "rust").map((file) => file.rel);
3263
+ const jobs = opts.engine === "all" ? [
3264
+ ["rust", "file_metrics", allFiles],
3265
+ ["rust", "rust_analysis", rustFiles],
3266
+ ["cpp", "cpp_analysis", cppFiles]
3267
+ ] : opts.engine === "rust" ? [
3268
+ ["rust", "file_metrics", allFiles],
3269
+ ["rust", "rust_analysis", rustFiles]
3270
+ ] : [["cpp", "cpp_analysis", cppFiles]];
3271
+ const nativeResults = {};
3272
+ const failures = [];
3273
+ const nativeFindings = [];
3274
+ await Promise.all(
3275
+ jobs.map(async ([worker, operation, files2]) => {
3276
+ if (!files2.length && operation !== "file_metrics") return;
3277
+ try {
3278
+ const native = await runNativeWorker(root, files2, worker, operation);
3279
+ nativeResults[`${worker}:${operation}`] = {
3280
+ files: native.metrics.files,
3281
+ bytes: native.metrics.bytes,
3282
+ lines: native.metrics.lines,
3283
+ durationMs: native.duration_ms
3284
+ };
3285
+ nativeFindings.push(...native.findings || []);
3286
+ } catch (error) {
3287
+ failures.push(error?.message || String(error));
3288
+ }
3289
+ })
3290
+ );
3291
+ if (failures.length && opts.engine !== "all") {
3292
+ findings.push(
3293
+ finding({
3294
+ category: "error",
3295
+ severity: "CRITICAL",
3296
+ title: "Native engine failed",
3297
+ detail: failures.join("; ")
3298
+ })
3216
3299
  );
3217
- report.metadata.native = {
3218
- engine: "rust",
3219
- files: native.metrics.files,
3220
- bytes: native.metrics.bytes,
3221
- lines: native.metrics.lines,
3222
- durationMs: native.duration_ms
3223
- };
3224
- } catch (error) {
3225
- const message = error?.message || String(error);
3226
- if (opts.engine === "native") {
3227
- findings.push(
3228
- finding({
3229
- category: "error",
3230
- severity: "CRITICAL",
3231
- title: "Native engine failed",
3232
- detail: message
3233
- })
3234
- );
3235
- report.findings = sortFindings(findings);
3236
- report.metadata.native = { engine: "rust", error: message };
3237
- return { report, exitCode: 2 };
3238
- }
3239
- report.metadata.native = { engine: "typescript", fallback: message };
3300
+ report.findings = sortFindings(findings);
3301
+ report.metadata.native = { engine: opts.engine, errors: failures };
3302
+ return { report, exitCode: 2 };
3240
3303
  }
3304
+ if (!failures.length) findings.push(...nativeFindings);
3305
+ report.metadata.native = failures.length ? { engine: "typescript", fallback: failures, workers: nativeResults } : { engine: opts.engine, workers: nativeResults };
3241
3306
  }
3242
3307
  let exitCode = 0;
3243
3308
  if (opts.failOn && report.metadata.worst && rank(report.metadata.worst) <= rank(opts.failOn)) {
@@ -3362,11 +3427,11 @@ for (const stream of [process.stdout, process.stderr]) {
3362
3427
  throw err;
3363
3428
  });
3364
3429
  }
3365
- function main() {
3430
+ async function main() {
3366
3431
  const opts = parseArgs(process.argv.slice(2));
3367
3432
  if (opts.help) return printHelp();
3368
3433
  if (opts.listLanguages) return printLanguages();
3369
- const { report, exitCode } = run2(opts);
3434
+ const { report, exitCode } = await run2(opts);
3370
3435
  const json = JSON.stringify(report, null, 2);
3371
3436
  if (opts.out) {
3372
3437
  try {
@@ -3383,4 +3448,4 @@ function main() {
3383
3448
  }
3384
3449
  process.exitCode = exitCode;
3385
3450
  }
3386
- main();
3451
+ void main();
@@ -0,0 +1,175 @@
1
+ # Flamer architecture
2
+
3
+ Flamer is a Node.js CLI with a coordinated native execution layer. TypeScript
4
+ owns the public interface, repository traversal, generic analyzers, findings,
5
+ fallback behavior, and report format. Rust and C++ run together as specialized
6
+ workers with non-overlapping responsibilities. Native findings are validated
7
+ and merged into the TypeScript report; they are not alternative scanners.
8
+
9
+ ## Runtime layers
10
+
11
+ ```text
12
+ CLI (src/cli.ts)
13
+ ├─ argument parsing and terminal/JSON output
14
+ ├─ repository detection and scope selection
15
+ ├─ TypeScript scanner and analyzers
16
+ └─ native worker adapter (src/native.ts)
17
+ ├─ Rust worker: native/core/rust
18
+ │ ├─ shared file metrics
19
+ │ └─ Rust-specific analysis
20
+ └─ C++ worker: native/core/cpp
21
+ └─ C/C++-specific analysis
22
+ ```
23
+
24
+ ### TypeScript layer
25
+
26
+ The TypeScript CLI is the default and source of truth. `src/scan.ts` walks and
27
+ reads files once, `src/detect.ts` builds project profiles, and registered
28
+ analyzers turn the collected context into findings. This path works with the
29
+ published npm package and does not require Rust, C++, Cargo, or CMake.
30
+
31
+ ### Native layer
32
+
33
+ The native layer is intentionally separate from generic analyzer logic. Rust
34
+ receives all selected files for shared byte/newline metrics and receives Rust
35
+ files for Rust-specific checks. C++ receives only C and C++ files for
36
+ C/C++-specific checks. These jobs run concurrently in `all` mode, so no worker
37
+ repeats another worker's language analysis.
38
+
39
+ Rust is built from `native/core/rust/Cargo.toml` and exposes a reusable library
40
+ plus a thin JSONL binary. C++ is built with CMake from
41
+ `native/core/cpp/CMakeLists.txt` and exposes a public header, analyzer
42
+ implementation, and protocol executable.
43
+
44
+ The current native checks are deliberately small but real: Rust reports
45
+ `.unwrap()` usage and C++ reports the non-portable
46
+ `#include <bits/stdc++.h>` include. Additional expensive, language-specific
47
+ work can be added without moving report ownership out of TypeScript.
48
+
49
+ ## Engine selection
50
+
51
+ The CLI accepts:
52
+
53
+ | Engine | Behavior |
54
+ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
55
+ | `all` | Default; runs TypeScript, Rust, and C++ together. Falls back to the TypeScript-only report if any native job is unavailable or fails. |
56
+ | `typescript` | Runs only the TypeScript implementation. |
57
+ | `rust` | Runs TypeScript plus Rust metrics and Rust analysis; fails if Rust is unavailable. |
58
+ | `cpp` | Runs TypeScript plus C++ analysis; fails if C++ is unavailable. |
59
+
60
+ The workers can be overridden with `FLAMER_RUST_CORE` and `FLAMER_CPP_CORE`:
61
+
62
+ ```bash
63
+ FLAMER_RUST_CORE="$PWD/native/core/rust/target/release/flamer-core" \
64
+ FLAMER_CPP_CORE="$PWD/native/core/cpp/build/flamer-core-cpp" \
65
+ node dist/cli.js .
66
+ ```
67
+
68
+ ## Worker protocol
69
+
70
+ Workers communicate over newline-delimited JSON (JSONL):
71
+
72
+ ```json
73
+ { "protocol": 1, "operation": "file_metrics", "root": ".", "files": ["README.md"] }
74
+ ```
75
+
76
+ Each request produces exactly one response on stdout. The operation determines
77
+ the worker contract:
78
+
79
+ | Worker | Operation | Input files | Responsibility |
80
+ | ------ | --------------- | ------------------ | ---------------------------------------- |
81
+ | Rust | `file_metrics` | all selected files | byte, newline, and readable-file metrics |
82
+ | Rust | `rust_analysis` | Rust files | Rust-specific findings |
83
+ | C++ | `cpp_analysis` | C/C++ files | C/C++-specific findings |
84
+
85
+ Example response:
86
+
87
+ ```json
88
+ {
89
+ "protocol": 1,
90
+ "ok": true,
91
+ "operation": "file_metrics",
92
+ "metrics": { "files": 1, "bytes": 1234, "lines": 42 },
93
+ "errors": [],
94
+ "duration_ms": 1
95
+ }
96
+ ```
97
+
98
+ Stdout is reserved for protocol messages. Diagnostics belong on stderr.
99
+ Unknown protocol versions, operations, malformed responses, non-zero exits, and
100
+ timeouts are errors. The TypeScript adapter validates the response before using
101
+ it.
102
+
103
+ ## Build and test
104
+
105
+ ```bash
106
+ npm test
107
+ npm run native:build
108
+ npm run native:test
109
+ ```
110
+
111
+ The individual projects can also be built directly:
112
+
113
+ ```bash
114
+ cargo build --release --manifest-path native/core/rust/Cargo.toml
115
+ cmake -S native/core/cpp -B native/core/cpp/build -DCMAKE_BUILD_TYPE=Release
116
+ cmake --build native/core/cpp/build --config Release
117
+ ```
118
+
119
+ The npm native scripts validate that Cargo and CMake are installed and return
120
+ an actionable error when either toolchain is missing. The C++ CMake project
121
+ also exports `compile_commands.json` and contains a protocol smoke test.
122
+
123
+ Native builds are checked independently in the Native Rust and Native C++
124
+ GitHub Actions workflows. Compiled output is ignored and is not committed.
125
+
126
+ ## Release and installation roadmap
127
+
128
+ The npm package remains the required installation:
129
+
130
+ ```bash
131
+ npm install --global flamerjs
132
+ ```
133
+
134
+ `flamerjs` declares platform-specific native packages under the publisher scope
135
+ as optional
136
+ dependencies. npm installs only the package matching the host OS and CPU:
137
+
138
+ ```text
139
+ flamerjs
140
+ └── @dummy3ye/flamer-native-linux-x64
141
+ ├── bin/flamer-core
142
+ └── bin/flamer-core-cpp
143
+ ```
144
+
145
+ At runtime `src/native.ts` resolves both workers from that package. If npm
146
+ skips the optional package, the package is unavailable for the platform, or a
147
+ worker cannot start, default `all` mode falls back to TypeScript. The source
148
+ build command creates the same package shape:
149
+
150
+ ```bash
151
+ npm run native:build:all
152
+ npm run native:package -- linux-x64
153
+ ```
154
+
155
+ The release workflow must build and publish both workers for each supported
156
+ OS/architecture package before that platform is advertised as native-enabled.
157
+ The standalone installer and release archives can use the same worker
158
+ artifacts.
159
+
160
+ The eventual bundled release keeps the TypeScript CLI as the orchestration
161
+ layer and packages both Rust and C++ workers. They are not alternatives:
162
+ standard execution starts both, while the explicit engine flags are for
163
+ debugging, benchmarking, and degraded environments.
164
+
165
+ ## Design constraints
166
+
167
+ - Keep npm installation functional with Node.js alone; native packages remain
168
+ optional.
169
+ - Keep findings and report semantics in TypeScript.
170
+ - Keep workers deterministic and communicate only through the versioned
171
+ protocol; stdout is protocol-only and diagnostics use stderr.
172
+ - Keep native execution fallback-safe until platform packaging and release
173
+ binaries are mature.
174
+ - Do not duplicate generic analyzer work in native workers.
175
+ - Add a benchmark before moving more work out of the TypeScript scanner.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flamerjs",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Language-agnostic code health auditor that walks a repository once and reports what is wrong with it",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,13 +20,28 @@
20
20
  "flamerjs": "./dist/cli.js"
21
21
  },
22
22
  "files": [
23
- "dist"
23
+ "dist",
24
+ "README.md",
25
+ "LICENSE",
26
+ "CHANGELOG.md",
27
+ "docs/ARCHITECTURE.md"
24
28
  ],
29
+ "optionalDependencies": {
30
+ "@dummy3ye/flamer-native-darwin-arm64": "1.1.1",
31
+ "@dummy3ye/flamer-native-darwin-x64": "1.1.1",
32
+ "@dummy3ye/flamer-native-linux-arm64": "1.1.1",
33
+ "@dummy3ye/flamer-native-linux-x64": "1.1.1",
34
+ "@dummy3ye/flamer-native-win32-arm64": "1.1.1",
35
+ "@dummy3ye/flamer-native-win32-x64": "1.1.1"
36
+ },
25
37
  "scripts": {
26
38
  "build": "tsup",
27
39
  "dev": "tsup --watch",
28
- "native:build": "cargo build --release --manifest-path native/core/rust/Cargo.toml",
29
- "native:test": "cargo test --manifest-path native/core/rust/Cargo.toml",
40
+ "native:build": "sh scripts/native-build.sh",
41
+ "native:test": "sh scripts/native-test.sh",
42
+ "native:build:cpp": "sh scripts/native-build-cpp.sh",
43
+ "native:build:all": "sh scripts/native-build.sh",
44
+ "native:package": "node scripts/package-native.mjs",
30
45
  "prepublishOnly": "npm run lint && npm run build && npm test",
31
46
  "lint": "eslint .",
32
47
  "lint:fix": "eslint . --fix",