flamerjs 0.1.6 → 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 +75 -19
  2. package/dist/cli.js +143 -7
  3. package/package.json +12 -1
package/README.md CHANGED
@@ -51,13 +51,23 @@ 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.
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.
55
59
 
56
60
  ## Requirements
57
61
 
58
62
  - Node.js 18 or newer
59
63
  - npm
60
64
 
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.
70
+
61
71
  ## Quick start
62
72
 
63
73
  Run the published CLI without installing it permanently:
@@ -101,23 +111,24 @@ flamerjs /path/to/repository --format summary
101
111
  flamerjs [path] [options]
102
112
  ```
103
113
 
104
- | Option | Description |
105
- | --------------------------- | ----------------------------------------------------------------------------- |
106
- | `--package, --scope <name>` | Analyze only a named sub-project |
107
- | `--path <dir>` | Set the repository root; defaults to the current directory |
108
- | `--format <json\|summary>` | Select JSON or human-readable output; summary is the default |
109
- | `--out <file>` | Also write the JSON report to a file |
110
- | `--fail-on <SEVERITY>` | Exit with status 1 at or above `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, or `INFO` |
111
- | `--max-files <n>` | Limit the number of files scanned |
112
- | `--commit-limit <n>` | Limit commits inspected by Git checks |
113
- | `--no-git` | Skip Git analysis |
114
- | `--no-lockfile-warning` | Suppress the missing-lockfile suggestion |
115
- | `--ignore-dir <name>` | Ignore an additional directory name; repeatable |
116
- | `--quiet` | Suppress the human summary |
117
- | `--color` | Force ANSI colors |
118
- | `--no-color` | Disable ANSI colors |
119
- | `--list-languages` | List recognized language profiles |
120
- | `--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 |
121
132
 
122
133
  Exit status `0` means the scan completed; findings may still exist. Status `1`
123
134
  means `--fail-on` was reached, and status `2` indicates invalid usage or a
@@ -170,12 +181,57 @@ The project uses Node.js and npm as its supported development toolchain.
170
181
  `package-lock.json` is the canonical dependency lockfile.
171
182
  The current test suite contains **175 tests**.
172
183
 
184
+ ### Native engine
185
+
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>`.
195
+
196
+ ```bash
197
+ npm run native:build:all
198
+ node dist/cli.js .
199
+ ```
200
+
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.
206
+ The native protocol and worker details are documented in
207
+ [`native/core/README.md`](native/core/README.md).
208
+
209
+ See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the runtime layers,
210
+ worker protocol, engine selection, and native development workflow.
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
+
173
224
  ## How it works
174
225
 
175
226
  1. Detect the repository layout, languages, and ecosystems.
176
227
  2. Walk the tree once and collect file metadata and content-derived signals.
177
228
  3. Run isolated analyzers against the collected records.
178
- 4. 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.
179
235
 
180
236
  Analyzers are intentionally factual and conservative. A finding should identify
181
237
  something actionable rather than merely express a preference.
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { writeFileSync, existsSync as existsSync3 } from "fs";
4
+ import { writeFileSync, existsSync as existsSync4 } from "fs";
5
5
  import { resolve, basename } from "path";
6
6
 
7
7
  // src/util/fs.ts
@@ -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()];
@@ -2901,6 +2902,82 @@ var ANALYZERS = [
2901
2902
  { id: "systems", label: "Rust / Go / C / C++ / C# / Zig", fn: analyze6 }
2902
2903
  ];
2903
2904
 
2905
+ // src/native.ts
2906
+ import { spawn } from "child_process";
2907
+ import { existsSync as existsSync3 } from "fs";
2908
+ import { join as join6 } from "path";
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;
2919
+ try {
2920
+ return require2.resolve(`${packageName}/${worker}`);
2921
+ } catch {
2922
+ return null;
2923
+ }
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
+ );
2939
+ }
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
+ });
2979
+ }
2980
+
2904
2981
  // src/cli.ts
2905
2982
  var SPEC_VERSION = 1;
2906
2983
  function parseArgs(argv) {
@@ -2918,6 +2995,7 @@ function parseArgs(argv) {
2918
2995
  listLanguages: false,
2919
2996
  help: false,
2920
2997
  color: null,
2998
+ engine: "all",
2921
2999
  ignoreDirs: []
2922
3000
  };
2923
3001
  for (let i = 0; i < argv.length; i++) {
@@ -2963,6 +3041,14 @@ function parseArgs(argv) {
2963
3041
  case "--no-color":
2964
3042
  opts.color = false;
2965
3043
  break;
3044
+ case "--engine":
3045
+ opts.engine = argv[++i] || "all";
3046
+ if (!["all", "typescript", "rust", "cpp"].includes(opts.engine)) {
3047
+ process.stderr.write(`invalid engine: ${opts.engine}
3048
+ `);
3049
+ process.exit(2);
3050
+ }
3051
+ break;
2966
3052
  case "--list-languages":
2967
3053
  opts.listLanguages = true;
2968
3054
  break;
@@ -3004,6 +3090,8 @@ Options:
3004
3090
  --quiet suppress the human summary
3005
3091
  --color force ANSI colors
3006
3092
  --no-color disable ANSI colors
3093
+ --engine <all|typescript|rust|cpp>
3094
+ all runs TypeScript, Rust, and C++ (default: all)
3007
3095
  --list-languages print the supported language table
3008
3096
  -h, --help show this help
3009
3097
  `);
@@ -3020,10 +3108,10 @@ function printLanguages() {
3020
3108
  ${allExtensions().length} file extensions recognized.
3021
3109
  `);
3022
3110
  }
3023
- function run2(opts) {
3111
+ async function run2(opts) {
3024
3112
  const started = Date.now();
3025
3113
  const root = opts.root;
3026
- if (!existsSync3(root) || !isDir(root)) {
3114
+ if (!existsSync4(root) || !isDir(root)) {
3027
3115
  const err = {
3028
3116
  specVersion: SPEC_VERSION,
3029
3117
  tool: "flamer",
@@ -3161,6 +3249,54 @@ function run2(opts) {
3161
3249
  analyzers: analyzerResults
3162
3250
  }
3163
3251
  };
3252
+ if (opts.engine !== "typescript") {
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
+ })
3292
+ );
3293
+ report.findings = sortFindings(findings);
3294
+ report.metadata.native = { engine: opts.engine, errors: failures };
3295
+ return { report, exitCode: 2 };
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 };
3299
+ }
3164
3300
  let exitCode = 0;
3165
3301
  if (opts.failOn && report.metadata.worst && rank(report.metadata.worst) <= rank(opts.failOn)) {
3166
3302
  exitCode = 1;
@@ -3284,11 +3420,11 @@ for (const stream of [process.stdout, process.stderr]) {
3284
3420
  throw err;
3285
3421
  });
3286
3422
  }
3287
- function main() {
3423
+ async function main() {
3288
3424
  const opts = parseArgs(process.argv.slice(2));
3289
3425
  if (opts.help) return printHelp();
3290
3426
  if (opts.listLanguages) return printLanguages();
3291
- const { report, exitCode } = run2(opts);
3427
+ const { report, exitCode } = await run2(opts);
3292
3428
  const json = JSON.stringify(report, null, 2);
3293
3429
  if (opts.out) {
3294
3430
  try {
@@ -3305,4 +3441,4 @@ function main() {
3305
3441
  }
3306
3442
  process.exitCode = exitCode;
3307
3443
  }
3308
- main();
3444
+ void main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flamerjs",
3
- "version": "0.1.6",
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,9 +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",
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",
28
39
  "prepublishOnly": "npm run lint && npm run build && npm test",
29
40
  "lint": "eslint .",
30
41
  "lint:fix": "eslint . --fix",