@sphior/scan 0.1.1 → 0.1.2

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 (2) hide show
  1. package/dist/cli.js +38 -5
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -17013,7 +17013,7 @@ function collectSourceFiles(rootDir, opts = {}) {
17013
17013
  }
17014
17014
 
17015
17015
  // src/engine/cli.ts
17016
- import { writeFileSync } from "node:fs";
17016
+ import { existsSync, statSync as statSync2, writeFileSync } from "node:fs";
17017
17017
 
17018
17018
  // src/engine/mcp/fix-context.ts
17019
17019
  var DATA_BOUNDARY_NOTE = "SPHIOR does not transmit your source code. This payload contains only finding metadata and deterministic remediation guidance. Read the actual code from your local workspace.";
@@ -17119,27 +17119,55 @@ function parseFlags(rest) {
17119
17119
  function runCli(argv, ctx = {}) {
17120
17120
  const log = ctx.log ?? (() => {
17121
17121
  });
17122
+ const logErr = ctx.logErr ?? log;
17122
17123
  const writeFile = ctx.writeFile ?? ((p, d) => writeFileSync(p, d));
17123
17124
  const [cmd, dir, ...rest] = argv;
17124
17125
  if (cmd !== "run" || !dir) {
17125
- log("usage: sphior-scan run <dir> [--sarif out.sarif] [--json out.json] [--fix-context out.json] [--fail-on high]");
17126
+ logErr("usage: sphior-scan run <dir> [--sarif out.sarif] [--json out.json] [--fix-context out.json] [--fail-on high]");
17126
17127
  return { exitCode: 2, error: "bad_args" };
17127
17128
  }
17128
17129
  const flags2 = parseFlags(rest);
17130
+ if (!existsSync(dir) || !statSync2(dir).isDirectory()) {
17131
+ logErr(`error: \u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: ${dir}`);
17132
+ logErr(" \u8D70\u67FB\u3057\u3066\u3044\u306A\u3044\u305F\u3081\u300C\u554F\u984C\u306A\u3057\u300D\u3068\u306F\u5831\u544A\u3057\u307E\u305B\u3093\u3002\u30D1\u30B9\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002");
17133
+ return { exitCode: 2, error: "dir_not_found" };
17134
+ }
17129
17135
  const files = collectSourceFiles(dir);
17130
17136
  const result = runScan({ files });
17131
17137
  const sarifJson = canonicalJson(result.sarif);
17132
17138
  if (flags2.sarif) writeFile(flags2.sarif, sarifJson);
17133
17139
  if (flags2.json) writeFile(flags2.json, canonicalJson(result.findings));
17134
17140
  if (flags2["fix-context"]) writeFile(flags2["fix-context"], canonicalJson(buildFixContexts(result.findings)));
17135
- if (!flags2.sarif && !flags2.json && !flags2["fix-context"]) log(sarifJson);
17141
+ if (!flags2.sarif && !flags2.json && !flags2["fix-context"]) {
17142
+ if (ctx.isTty) {
17143
+ const rows = [...result.findings].sort((a, b) => SEV_ORDER[b.severity] - SEV_ORDER[a.severity]).map((f) => ({
17144
+ sev: f.severity,
17145
+ loc: `${f.location.path}:${f.location.start_line}`,
17146
+ text: `${f.title}${f.cwe ? ` (${f.cwe})` : ""}`
17147
+ }));
17148
+ const locW = Math.max(0, ...rows.map((r) => r.loc.length));
17149
+ for (const r of rows) logErr(` ${r.sev.padEnd(8)} ${r.loc.padEnd(locW)} ${r.text}`);
17150
+ if (rows.length > 0) logErr("");
17151
+ } else {
17152
+ log(sarifJson);
17153
+ }
17154
+ }
17155
+ if (files.length === 0) {
17156
+ logErr(`warning: ${dir} \u306B\u8D70\u67FB\u5BFE\u8C61\u306E\u30D5\u30A1\u30A4\u30EB\u304C\u3042\u308A\u307E\u305B\u3093\u3067\u3057\u305F\uFF080 \u4EF6\u8D70\u67FB\uFF09\u3002`);
17157
+ }
17136
17158
  const counts = result.findings.reduce((acc, f) => {
17137
17159
  acc[f.severity] = (acc[f.severity] ?? 0) + 1;
17138
17160
  return acc;
17139
17161
  }, {});
17140
- log(
17162
+ logErr(
17141
17163
  `SPHIOR CODE: ${result.findings.length} findings (critical=${counts.critical ?? 0} high=${counts.high ?? 0} medium=${counts.medium ?? 0} low=${counts.low ?? 0}) engine=${result.engine_version} ruleset=${result.ruleset_version}`
17142
17164
  );
17165
+ if (ctx.isTty && !flags2.sarif && !flags2.json && !flags2["fix-context"]) {
17166
+ logErr("");
17167
+ logErr(" --fix-context fc.json AI \u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u6E21\u3057\u3066\u4FEE\u6B63\u3055\u305B\u308B");
17168
+ logErr(" --sarif out.sarif GitHub code scanning / CI \u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9\u3078");
17169
+ logErr(" --fail-on high \u6307\u5B9A\u4EE5\u4E0A\u306E\u91CD\u5927\u5EA6\u304C\u3042\u308C\u3070 exit 1\uFF08CI \u7528\uFF09");
17170
+ }
17143
17171
  let exitCode = 0;
17144
17172
  const failOn = flags2["fail-on"];
17145
17173
  if (failOn && SEV_ORDER[failOn] !== void 0) {
@@ -23959,7 +23987,12 @@ async function main() {
23959
23987
  })));
23960
23988
  const res = runCli(process.argv.slice(2), {
23961
23989
  cwd: process.cwd(),
23962
- log: (m) => console.log(m)
23990
+ log: (m) => console.log(m),
23991
+ // データ(SARIF)
23992
+ logErr: (m) => console.error(m),
23993
+ // 診断(サマリ・警告・ヒント)
23994
+ // 端末なら人が読む形、パイプ/リダイレクトなら SARIF(`| jq` を壊さない)
23995
+ isTty: Boolean(process.stdout.isTTY)
23963
23996
  });
23964
23997
  process.exit(res.exitCode);
23965
23998
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphior/scan",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Deterministic source scanner — hardcoded secrets and insecure patterns (CWE) with AST/taint analysis. No AI, no network, no source upload. CLI + LSP for editors and CI.",
5
5
  "type": "module",
6
6
  "bin": {