@sphior/scan 0.1.2 → 0.1.3

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/dist/cli.js CHANGED
@@ -17094,16 +17094,58 @@ function buildFixContext(f) {
17094
17094
  remediation_steps: remediationStepsFor(f),
17095
17095
  references: f.references ?? [],
17096
17096
  agent_instructions: agentInstructions(f),
17097
- data_boundary_note: DATA_BOUNDARY_NOTE
17097
+ data_boundary_note: DATA_BOUNDARY_NOTE,
17098
+ also: []
17098
17099
  };
17099
17100
  }
17101
+ var SEV_ORDER = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
17100
17102
  function buildFixContexts(findings) {
17101
- const order = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
17102
- return findings.map(buildFixContext).sort((a, b) => order[a.severity] - order[b.severity]);
17103
+ const byLocation = /* @__PURE__ */ new Map();
17104
+ for (const f of findings) {
17105
+ const ctx = buildFixContext(f);
17106
+ const key = `${ctx.location.path}:${ctx.location.start_line}:${ctx.location.end_line}`;
17107
+ const bucket = byLocation.get(key);
17108
+ if (bucket) bucket.push(ctx);
17109
+ else byLocation.set(key, [ctx]);
17110
+ }
17111
+ const merged = [];
17112
+ for (const group of byLocation.values()) {
17113
+ group.sort((a, b) => SEV_ORDER[a.severity] - SEV_ORDER[b.severity]);
17114
+ const [primary, ...rest] = group;
17115
+ if (rest.length === 0) {
17116
+ merged.push(primary);
17117
+ continue;
17118
+ }
17119
+ const seen = new Set(primary.remediation_steps);
17120
+ const steps = [...primary.remediation_steps];
17121
+ for (const r of rest) {
17122
+ for (const step of r.remediation_steps) {
17123
+ if (seen.has(step)) continue;
17124
+ seen.add(step);
17125
+ steps.push(step);
17126
+ }
17127
+ }
17128
+ const alsoTitles = rest.map((r) => `${r.title}${r.cwe ? ` (${r.cwe})` : ""}`).join("; ");
17129
+ merged.push({
17130
+ ...primary,
17131
+ // まとめた結果を指示文にも反映する。1 箇所に複数の検出がある事実を伝えないと、
17132
+ // エージェントは主の 1 件だけ直して残りを見落とす
17133
+ agent_instructions: `${primary.agent_instructions} This location has ${group.length} findings \u2014 your single fix must satisfy all of them, not just the first: ${alsoTitles}.`,
17134
+ remediation_steps: steps,
17135
+ references: [.../* @__PURE__ */ new Set([...primary.references, ...rest.flatMap((r) => r.references)])],
17136
+ also: rest.map((r) => ({
17137
+ rule_id: r.rule_id,
17138
+ severity: r.severity,
17139
+ cwe: r.cwe,
17140
+ title: r.title
17141
+ }))
17142
+ });
17143
+ }
17144
+ return merged.sort((a, b) => SEV_ORDER[a.severity] - SEV_ORDER[b.severity]);
17103
17145
  }
17104
17146
 
17105
17147
  // src/engine/cli.ts
17106
- var SEV_ORDER = { info: 0, low: 1, medium: 2, high: 3, critical: 4 };
17148
+ var SEV_ORDER2 = { info: 0, low: 1, medium: 2, high: 3, critical: 4 };
17107
17149
  function parseFlags(rest) {
17108
17150
  const flags2 = {};
17109
17151
  for (let i2 = 0; i2 < rest.length; i2++) {
@@ -17140,7 +17182,7 @@ function runCli(argv, ctx = {}) {
17140
17182
  if (flags2["fix-context"]) writeFile(flags2["fix-context"], canonicalJson(buildFixContexts(result.findings)));
17141
17183
  if (!flags2.sarif && !flags2.json && !flags2["fix-context"]) {
17142
17184
  if (ctx.isTty) {
17143
- const rows = [...result.findings].sort((a, b) => SEV_ORDER[b.severity] - SEV_ORDER[a.severity]).map((f) => ({
17185
+ const rows = [...result.findings].sort((a, b) => SEV_ORDER2[b.severity] - SEV_ORDER2[a.severity]).map((f) => ({
17144
17186
  sev: f.severity,
17145
17187
  loc: `${f.location.path}:${f.location.start_line}`,
17146
17188
  text: `${f.title}${f.cwe ? ` (${f.cwe})` : ""}`
@@ -17170,9 +17212,9 @@ function runCli(argv, ctx = {}) {
17170
17212
  }
17171
17213
  let exitCode = 0;
17172
17214
  const failOn = flags2["fail-on"];
17173
- if (failOn && SEV_ORDER[failOn] !== void 0) {
17174
- const threshold = SEV_ORDER[failOn];
17175
- if (result.findings.some((f) => SEV_ORDER[f.severity] >= threshold)) exitCode = 1;
17215
+ if (failOn && SEV_ORDER2[failOn] !== void 0) {
17216
+ const threshold = SEV_ORDER2[failOn];
17217
+ if (result.findings.some((f) => SEV_ORDER2[f.severity] >= threshold)) exitCode = 1;
17176
17218
  }
17177
17219
  return { exitCode, result, sarifJson };
17178
17220
  }
package/dist/server.cjs CHANGED
@@ -26098,7 +26098,8 @@ function buildFixContext(f) {
26098
26098
  remediation_steps: remediationStepsFor(f),
26099
26099
  references: f.references ?? [],
26100
26100
  agent_instructions: agentInstructions(f),
26101
- data_boundary_note: DATA_BOUNDARY_NOTE
26101
+ data_boundary_note: DATA_BOUNDARY_NOTE,
26102
+ also: []
26102
26103
  };
26103
26104
  }
26104
26105
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sphior/scan",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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": {