flamerjs 1.0.0 → 1.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.
Files changed (3) hide show
  1. package/README.md +63 -32
  2. package/dist/cli.js +122 -64
  3. package/package.json +12 -3
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,79 @@ 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(["linux-x64", "darwin-x64", "darwin-arm64", "win32-x64"]);
2914
+ return supported.has(platform) ? `@dummy3ye/flamer-native-${platform}` : null;
2915
+ }
2916
+ function packagedWorkerPath(worker) {
2917
+ const packageName = nativePackageName();
2918
+ if (!packageName) return null;
2926
2919
  try {
2927
- response = JSON.parse(result.stdout);
2920
+ return require2.resolve(`${packageName}/${worker}`);
2928
2921
  } 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");
2922
+ return null;
2933
2923
  }
2934
- if (!response.ok) {
2935
- throw new Error(response.errors.map((error) => `${error.path}: ${error.message}`).join("; "));
2924
+ }
2925
+ function nativeWorkerPath(root, worker) {
2926
+ const configured = worker === "rust" ? process.env.FLAMER_RUST_CORE : process.env.FLAMER_CPP_CORE;
2927
+ if (configured) return configured;
2928
+ if (process.env.FLAMER_CORE && worker === "rust") return process.env.FLAMER_CORE;
2929
+ const packaged = packagedWorkerPath(worker);
2930
+ if (packaged) return packaged;
2931
+ return worker === "rust" ? join6(root, "native", "core", "rust", "target", "release", "flamer-core") : join6(root, "native", "core", "cpp", "build", "flamer-core-cpp");
2932
+ }
2933
+ function runNativeWorker(root, files, worker, operation = "file_metrics") {
2934
+ const executable = nativeWorkerPath(root, worker);
2935
+ if (!existsSync3(executable)) {
2936
+ return Promise.reject(
2937
+ new Error(`${worker} worker not found at ${executable}; build the native workers first`)
2938
+ );
2936
2939
  }
2937
- return response;
2940
+ const request = JSON.stringify({ protocol: 1, operation, root, files });
2941
+ return new Promise((resolve2, reject) => {
2942
+ const child = spawn(executable, { stdio: ["pipe", "pipe", "pipe"] });
2943
+ let stdout = "";
2944
+ let stderr = "";
2945
+ const timeout = setTimeout(() => {
2946
+ child.kill();
2947
+ reject(new Error(`${worker} worker timed out`));
2948
+ }, 3e4);
2949
+ child.stdout.on("data", (chunk) => stdout += chunk);
2950
+ child.stderr.on("data", (chunk) => stderr += chunk);
2951
+ child.on("error", (error) => {
2952
+ clearTimeout(timeout);
2953
+ reject(new Error(`${worker} worker failed: ${error.message}`));
2954
+ });
2955
+ child.on("close", (status) => {
2956
+ clearTimeout(timeout);
2957
+ if (status !== 0) {
2958
+ reject(new Error(`${worker} worker exited with status ${status}: ${stderr.trim()}`));
2959
+ return;
2960
+ }
2961
+ try {
2962
+ const response = JSON.parse(stdout);
2963
+ if (response.protocol !== 1 || response.operation !== operation) {
2964
+ throw new Error(`${worker} worker returned an incompatible response`);
2965
+ }
2966
+ if (!response.ok) {
2967
+ throw new Error(
2968
+ response.errors.map((error) => `${error.path}: ${error.message}`).join("; ")
2969
+ );
2970
+ }
2971
+ resolve2(response);
2972
+ } catch (error) {
2973
+ reject(error instanceof Error ? error : new Error(String(error)));
2974
+ }
2975
+ });
2976
+ child.stdin.end(`${request}
2977
+ `);
2978
+ });
2938
2979
  }
2939
2980
 
2940
2981
  // src/cli.ts
@@ -2954,7 +2995,7 @@ function parseArgs(argv) {
2954
2995
  listLanguages: false,
2955
2996
  help: false,
2956
2997
  color: null,
2957
- engine: "typescript",
2998
+ engine: "all",
2958
2999
  ignoreDirs: []
2959
3000
  };
2960
3001
  for (let i = 0; i < argv.length; i++) {
@@ -3001,8 +3042,8 @@ function parseArgs(argv) {
3001
3042
  opts.color = false;
3002
3043
  break;
3003
3044
  case "--engine":
3004
- opts.engine = argv[++i] || "typescript";
3005
- if (!["typescript", "native", "auto"].includes(opts.engine)) {
3045
+ opts.engine = argv[++i] || "all";
3046
+ if (!["all", "typescript", "rust", "cpp"].includes(opts.engine)) {
3006
3047
  process.stderr.write(`invalid engine: ${opts.engine}
3007
3048
  `);
3008
3049
  process.exit(2);
@@ -3049,8 +3090,8 @@ Options:
3049
3090
  --quiet suppress the human summary
3050
3091
  --color force ANSI colors
3051
3092
  --no-color disable ANSI colors
3052
- --engine <typescript|native|auto>
3053
- choose the scan engine (default: typescript)
3093
+ --engine <all|typescript|rust|cpp>
3094
+ all runs TypeScript, Rust, and C++ (default: all)
3054
3095
  --list-languages print the supported language table
3055
3096
  -h, --help show this help
3056
3097
  `);
@@ -3067,7 +3108,7 @@ function printLanguages() {
3067
3108
  ${allExtensions().length} file extensions recognized.
3068
3109
  `);
3069
3110
  }
3070
- function run2(opts) {
3111
+ async function run2(opts) {
3071
3112
  const started = Date.now();
3072
3113
  const root = opts.root;
3073
3114
  if (!existsSync4(root) || !isDir(root)) {
@@ -3209,35 +3250,52 @@ function run2(opts) {
3209
3250
  }
3210
3251
  };
3211
3252
  if (opts.engine !== "typescript") {
3212
- try {
3213
- const native = runNativeMetrics(
3214
- root,
3215
- scopedFiles.map((file) => file.rel)
3253
+ const allFiles = scopedFiles.map((file) => file.rel);
3254
+ const cppFiles = scopedFiles.filter((file) => ["c", "cpp"].includes(file.lang)).map((file) => file.rel);
3255
+ const rustFiles = scopedFiles.filter((file) => file.lang === "rust").map((file) => file.rel);
3256
+ const jobs = opts.engine === "all" ? [
3257
+ ["rust", "file_metrics", allFiles],
3258
+ ["rust", "rust_analysis", rustFiles],
3259
+ ["cpp", "cpp_analysis", cppFiles]
3260
+ ] : opts.engine === "rust" ? [
3261
+ ["rust", "file_metrics", allFiles],
3262
+ ["rust", "rust_analysis", rustFiles]
3263
+ ] : [["cpp", "cpp_analysis", cppFiles]];
3264
+ const nativeResults = {};
3265
+ const failures = [];
3266
+ const nativeFindings = [];
3267
+ await Promise.all(
3268
+ jobs.map(async ([worker, operation, files2]) => {
3269
+ if (!files2.length && operation !== "file_metrics") return;
3270
+ try {
3271
+ const native = await runNativeWorker(root, files2, worker, operation);
3272
+ nativeResults[`${worker}:${operation}`] = {
3273
+ files: native.metrics.files,
3274
+ bytes: native.metrics.bytes,
3275
+ lines: native.metrics.lines,
3276
+ durationMs: native.duration_ms
3277
+ };
3278
+ nativeFindings.push(...native.findings || []);
3279
+ } catch (error) {
3280
+ failures.push(error?.message || String(error));
3281
+ }
3282
+ })
3283
+ );
3284
+ if (failures.length && opts.engine !== "all") {
3285
+ findings.push(
3286
+ finding({
3287
+ category: "error",
3288
+ severity: "CRITICAL",
3289
+ title: "Native engine failed",
3290
+ detail: failures.join("; ")
3291
+ })
3216
3292
  );
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 };
3293
+ report.findings = sortFindings(findings);
3294
+ report.metadata.native = { engine: opts.engine, errors: failures };
3295
+ return { report, exitCode: 2 };
3240
3296
  }
3297
+ if (!failures.length) findings.push(...nativeFindings);
3298
+ report.metadata.native = failures.length ? { engine: "typescript", fallback: failures, workers: nativeResults } : { engine: opts.engine, workers: nativeResults };
3241
3299
  }
3242
3300
  let exitCode = 0;
3243
3301
  if (opts.failOn && report.metadata.worst && rank(report.metadata.worst) <= rank(opts.failOn)) {
@@ -3362,11 +3420,11 @@ for (const stream of [process.stdout, process.stderr]) {
3362
3420
  throw err;
3363
3421
  });
3364
3422
  }
3365
- function main() {
3423
+ async function main() {
3366
3424
  const opts = parseArgs(process.argv.slice(2));
3367
3425
  if (opts.help) return printHelp();
3368
3426
  if (opts.listLanguages) return printLanguages();
3369
- const { report, exitCode } = run2(opts);
3427
+ const { report, exitCode } = await run2(opts);
3370
3428
  const json = JSON.stringify(report, null, 2);
3371
3429
  if (opts.out) {
3372
3430
  try {
@@ -3383,4 +3441,4 @@ function main() {
3383
3441
  }
3384
3442
  process.exitCode = exitCode;
3385
3443
  }
3386
- main();
3444
+ void main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flamerjs",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
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",
@@ -22,11 +22,20 @@
22
22
  "files": [
23
23
  "dist"
24
24
  ],
25
+ "optionalDependencies": {
26
+ "@dummy3ye/flamer-native-darwin-arm64": "1.1.1",
27
+ "@dummy3ye/flamer-native-darwin-x64": "1.1.1",
28
+ "@dummy3ye/flamer-native-linux-x64": "1.1.1",
29
+ "@dummy3ye/flamer-native-win32-x64": "1.1.1"
30
+ },
25
31
  "scripts": {
26
32
  "build": "tsup",
27
33
  "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",
34
+ "native:build": "sh scripts/native-build.sh",
35
+ "native:test": "sh scripts/native-test.sh",
36
+ "native:build:cpp": "sh scripts/native-build-cpp.sh",
37
+ "native:build:all": "sh scripts/native-build.sh",
38
+ "native:package": "node scripts/package-native.mjs",
30
39
  "prepublishOnly": "npm run lint && npm run build && npm test",
31
40
  "lint": "eslint .",
32
41
  "lint:fix": "eslint . --fix",