@chimix/agentscan 0.2.0 → 0.3.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 +11 -13
  2. package/dist/cli.js +53 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -88,7 +88,8 @@ Run it against any project. It reads that project, writes nothing, and never
88
88
  leaves your machine:
89
89
 
90
90
  ```bash
91
- npx @chimix/agentscan@latest check ~/path/to/your-project
91
+ cd ~/your-project
92
+ npx @chimix/agentscan@latest
92
93
  ```
93
94
 
94
95
  Works on Node 20.11+ or Bun — the published bin is a single bundled file with
@@ -175,21 +176,18 @@ Flags for `check`:
175
176
  ## Sample output
176
177
 
177
178
  ```text
178
- agentscan v0.1.0 — touchagency
179
+ agentscan v0.3.0 — touchagency
179
180
 
180
- Stack: 46 deps · 54 skills · 1 mcp · 2 agents · packageManager=bun
181
+ Scanned: 46 deps · 54 skills · 1 mcp · 2 agents · packageManager=bun
181
182
 
182
- WARN hook:PreToolUse:.claude/hooks/guard-destructive-bash.js
183
- rule:hook.missing-script
184
- PreToolUse hook points at a script that does not exist: .claude/hooks/guard-destructive-bash.js
185
- evidence: hook PreToolUse @ …/.claude/settings.json · script .claude/hooks/guard-destructive-bash.js
186
-
187
- WARN hook:PreToolUse:.claude/hooks/protect-artists-json.js
188
- rule:hook.missing-script
189
- PreToolUse hook points at a script that does not exist: .claude/hooks/protect-artists-json.js
190
- evidence: hook PreToolUse @ …/.claude/settings.json · script .claude/hooks/protect-artists-json.js
183
+ WARN rule:hook.missing-script ×6
184
+ PreToolUse hook points at a script that does not exist
185
+ PostToolUse @ .claude/settings.json · .claude/hooks/notify-related-tests.js
186
+ PreToolUse @ .claude/settings.json · .claude/hooks/guard-destructive-bash.js
187
+ PreToolUse @ .claude/settings.json · .claude/hooks/protect-artists-json.js
188
+ PreToolUse @ .claude/settings.json · .claude/hooks/protect-env.js
191
189
 
192
- Summary: 6 warn · 4 info hidden (--verbose) · score 40/100
190
+ Summary: 6 warn · 4 info hidden (--verbose) · score 40/100 broken
193
191
  ```
194
192
 
195
193
  The `Stack:` line is orientation only — which project, how big. The summary lists
package/dist/cli.js CHANGED
@@ -12534,6 +12534,21 @@ function score(findings) {
12534
12534
  }
12535
12535
  return Math.max(0, 100 - deduction);
12536
12536
  }
12537
+ function scoreLabel(points) {
12538
+ if (points === 100) {
12539
+ return "clean";
12540
+ }
12541
+ if (points >= 90) {
12542
+ return "good";
12543
+ }
12544
+ if (points >= 70) {
12545
+ return "needs work";
12546
+ }
12547
+ if (points >= 50) {
12548
+ return "poor";
12549
+ }
12550
+ return "broken";
12551
+ }
12537
12552
 
12538
12553
  // src/report/exit-code.ts
12539
12554
  function exitCode(findings, failOn, failUnder) {
@@ -12685,10 +12700,10 @@ function renderText(args) {
12685
12700
  lines.push(` no package.json in ${safe(args.resolvedFrom)} — scanned ${safe(facts.root)}`);
12686
12701
  }
12687
12702
  lines.push("");
12688
- lines.push(`Stack: ${formatStack(facts)}`);
12703
+ lines.push(`Scanned: ${formatStack(facts)}`);
12689
12704
  lines.push("");
12690
- for (const f of visible) {
12691
- lines.push(...formatFinding(f));
12705
+ for (const group of groupByRule(visible)) {
12706
+ lines.push(...formatGroup(group, facts.root));
12692
12707
  lines.push("");
12693
12708
  }
12694
12709
  lines.push(summary);
@@ -12712,17 +12727,38 @@ function formatStack(facts) {
12712
12727
  `packageManager=${facts.packageManager}`
12713
12728
  ].join(" · ");
12714
12729
  }
12715
- function formatFinding(f) {
12730
+ function groupByRule(findings) {
12731
+ const byRule = new Map;
12732
+ for (const f of findings) {
12733
+ const bucket = byRule.get(f.ruleId);
12734
+ if (bucket === undefined) {
12735
+ byRule.set(f.ruleId, [f]);
12736
+ } else {
12737
+ bucket.push(f);
12738
+ }
12739
+ }
12740
+ return [...byRule.values()];
12741
+ }
12742
+ function shortPath(value, root) {
12743
+ return value.split(`${root}/`).join("");
12744
+ }
12745
+ function formatGroup(group, root) {
12746
+ const first = group[0];
12747
+ const count = group.length;
12716
12748
  const out = [];
12717
- out.push(`${actionColumn(f.action)} ${safe(f.subject)}`);
12718
- out.push(` rule:${safe(f.ruleId)}`);
12719
- out.push(` ${safe(f.message)}`);
12720
- if (f.evidence.length > 0) {
12721
- const ev = f.evidence.map((e) => `${safe(e.kind)} ${safe(e.value)}`).join(" · ");
12722
- out.push(` evidence: ${ev}`);
12749
+ out.push(`${actionColumn(first.action)} rule:${safe(first.ruleId)}${count > 1 ? ` ×${count}` : ""}`);
12750
+ out.push(` ${safe(count === 1 ? first.message : ruleHeadline(first))}`);
12751
+ for (const f of group) {
12752
+ const where = f.evidence.filter((e) => e.kind !== "count" && e.kind !== "threshold").map((e) => shortPath(e.value, root));
12753
+ const location = where.length > 0 ? where.join(" · ") : f.subject;
12754
+ out.push(` ${safe(shortPath(location, root))}`);
12723
12755
  }
12724
12756
  return out;
12725
12757
  }
12758
+ function ruleHeadline(f) {
12759
+ const cut = f.message.indexOf(": ");
12760
+ return cut === -1 ? f.message : f.message.slice(0, cut);
12761
+ }
12726
12762
  function formatSummary(findings, verbose) {
12727
12763
  const counts = {
12728
12764
  keep: 0,
@@ -12746,14 +12782,15 @@ function formatSummary(findings, verbose) {
12746
12782
  parts.push(`${infoHidden} info hidden (--verbose)`);
12747
12783
  }
12748
12784
  const points = score(findings);
12785
+ const scored = `score ${points}/100 ${scoreLabel(points)}`;
12749
12786
  if (parts.length === 0) {
12750
- return `Summary: no findings · score ${points}/100`;
12787
+ return `Summary: no findings · ${scored}`;
12751
12788
  }
12752
- return `Summary: ${parts.join(" · ")} · score ${points}/100`;
12789
+ return `Summary: ${parts.join(" · ")} · ${scored}`;
12753
12790
  }
12754
12791
 
12755
12792
  // src/version.ts
12756
- var VERSION = "0.2.0";
12793
+ var VERSION = "0.3.0";
12757
12794
 
12758
12795
  // src/commands/check.ts
12759
12796
  async function runCheck(options) {
@@ -12936,6 +12973,7 @@ function printHelp() {
12936
12973
  const text = `agentscan v${VERSION}
12937
12974
 
12938
12975
  Usage:
12976
+ agentscan scan the current directory
12939
12977
  agentscan check [dir] [options]
12940
12978
  agentscan explain <findingId> [dir]
12941
12979
  agentscan rules [dir]
@@ -13004,15 +13042,11 @@ async function main(argv) {
13004
13042
  `);
13005
13043
  return 0;
13006
13044
  }
13007
- if (values.help || positionals.length === 0) {
13008
- printHelp();
13009
- return 0;
13010
- }
13011
- const command = positionals[0];
13012
- if (command === undefined) {
13045
+ if (values.help) {
13013
13046
  printHelp();
13014
13047
  return 0;
13015
13048
  }
13049
+ const command = positionals[0] ?? "check";
13016
13050
  try {
13017
13051
  switch (command) {
13018
13052
  case "check": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chimix/agentscan",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Find broken agent configuration in a project — hooks whose scripts are gone, MCP servers that cannot start, skills that disagree with their lockfile",
5
5
  "type": "module",
6
6
  "bin": {