activate-agentmd 2.4.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 (44) hide show
  1. package/README.md +158 -0
  2. package/bin/agentmd.js +186 -0
  3. package/package.json +55 -0
  4. package/src/commands/agents.js +44 -0
  5. package/src/commands/analytics.js +128 -0
  6. package/src/commands/auth.js +144 -0
  7. package/src/commands/ci.js +296 -0
  8. package/src/commands/extract.js +254 -0
  9. package/src/commands/info.js +105 -0
  10. package/src/commands/init.js +166 -0
  11. package/src/commands/install.js +216 -0
  12. package/src/commands/link.js +206 -0
  13. package/src/commands/list.js +99 -0
  14. package/src/commands/outdated.js +92 -0
  15. package/src/commands/remove.js +72 -0
  16. package/src/commands/review.js +293 -0
  17. package/src/commands/search.js +110 -0
  18. package/src/commands/sync.js +245 -0
  19. package/src/commands/telemetry.js +82 -0
  20. package/src/commands/test.js +226 -0
  21. package/src/commands/update.js +115 -0
  22. package/src/commands/validate.js +125 -0
  23. package/src/config/license-public-keys.json +17 -0
  24. package/src/detect.json +452 -0
  25. package/src/lib/agents.js +301 -0
  26. package/src/lib/analytics.js +137 -0
  27. package/src/lib/coordinates.js +103 -0
  28. package/src/lib/credentials.js +115 -0
  29. package/src/lib/detect.js +352 -0
  30. package/src/lib/diff.js +142 -0
  31. package/src/lib/enterprise.js +140 -0
  32. package/src/lib/fetcher.js +174 -0
  33. package/src/lib/invocation.js +47 -0
  34. package/src/lib/license.js +234 -0
  35. package/src/lib/manifest.js +191 -0
  36. package/src/lib/patterns.js +135 -0
  37. package/src/lib/pro.js +149 -0
  38. package/src/lib/ranking.js +287 -0
  39. package/src/lib/registry.js +207 -0
  40. package/src/lib/star.js +105 -0
  41. package/src/lib/status.js +75 -0
  42. package/src/lib/telemetry.js +169 -0
  43. package/src/lib/versions.js +190 -0
  44. package/src/registry.json +33907 -0
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # activate-agentmd
2
+
3
+ > The package manager for AI coding-agent engineering standards.
4
+
5
+ ```bash
6
+ npx activate-agentmd init
7
+ ```
8
+
9
+ [![npm](https://img.shields.io/npm/v/activate-agentmd)](https://www.npmjs.com/package/activate-agentmd)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
11
+
12
+ Scans your project, shows what it found, and installs the standards that match.
13
+
14
+ ```
15
+ agentmd scanning project…
16
+
17
+ ✓ Docker Dockerfile
18
+ ✓ TypeScript tsconfig.json
19
+ ✓ Prisma prisma/
20
+ ✓ Next.js package.json (next)
21
+ ✓ PostgreSQL package.json (pg)
22
+
23
+ Recommended packages for claude:
24
+
25
+ Backend nextjs
26
+ Database prisma, orm, migration, schema-design, postgres, indexes
27
+ Frontend typescript, nextjs, server-components, routing, react
28
+ Security authentication, authorization, oauth, owasp
29
+ Testing unit, test-strategy, e2e, accessibility
30
+
31
+ 31 to install
32
+
33
+ Install these? [Y/n]
34
+ ```
35
+
36
+ Then wire them into whichever agent you use:
37
+
38
+ ```bash
39
+ npx activate-agentmd link
40
+ ```
41
+
42
+ That writes the installed package list into `CLAUDE.md`, `AGENTS.md`,
43
+ `.cursor/rules/agentmd.mdc`, `GEMINI.md` or
44
+ `.github/copilot-instructions.md` — inside a delimited block, so your own
45
+ content is never touched.
46
+
47
+ ---
48
+
49
+ ## Commands
50
+
51
+ | Command | What it does |
52
+ | --- | --- |
53
+ | `init` | Scan the project, install what matches |
54
+ | `link` | Wire installed packages into your agent config files |
55
+ | `search <query>` | Search names, categories, descriptions |
56
+ | `list [model/category]` | Browse the registry |
57
+ | `list --installed` | What this project has |
58
+ | `info <package>` | Details for one package |
59
+ | `install <package>` | Install a package, or a whole category |
60
+ | `remove <package>` | Uninstall |
61
+ | `outdated` | What's changed upstream, and what you've edited |
62
+ | `update` | Pull current versions |
63
+ | `validate` | Check registry + manifest integrity |
64
+ | `agents` | List supported agent config targets |
65
+
66
+ Package paths are `<model>/<category>/<package>`. A trailing slash installs the
67
+ whole category:
68
+
69
+ ```bash
70
+ agentmd install claude/Security/owasp # one
71
+ agentmd install claude/Testing/ # all 11
72
+ ```
73
+
74
+ Full reference:
75
+ [docs/cli.md](https://github.com/Aaditya1273/Agent.md/blob/main/docs/cli.md)
76
+
77
+ ---
78
+
79
+ ## Updates respect your edits
80
+
81
+ `agentmd update` never overwrites a package you've edited. It reports the
82
+ conflict and leaves it alone until you pass `--force`.
83
+
84
+ ```
85
+ $ agentmd outdated
86
+
87
+ ↑ claude/Testing/unit update available
88
+ ✎ claude/Security/owasp edited locally
89
+
90
+ 1 outdated, 1 modified, 24 current
91
+ ```
92
+
93
+ The distinction comes from a SHA-256 checksum recorded at install time.
94
+
95
+ ---
96
+
97
+ ## Where things go
98
+
99
+ | Path | What |
100
+ | --- | --- |
101
+ | `.agentmd/presets/` | Package content |
102
+ | `.agentmd/manifest.json` | What's installed, with version, checksum and source |
103
+
104
+ Commit both — that's how a team shares one set of standards, and how changes
105
+ to your agent's instructions show up in code review.
106
+
107
+ ---
108
+
109
+ ## Registry
110
+
111
+ 304 canonical standards, tuned for 11 model families (3,344 total):
112
+ `claude`, `deepseek`, `gemini`, `glm`, `grok`, `kimi`, `minimax`, `mistral`, `open-ai`, `qwen` and `sarvam-ai`.
113
+
114
+ ```bash
115
+ agentmd list claude # all categories
116
+ agentmd list claude/Security # packages, with descriptions
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Security
122
+
123
+ This CLI distributes content your agent reads as instructions, so:
124
+
125
+ - Package content is **never executed** — it's markdown written to disk.
126
+ - HTTPS only; a redirect to plaintext HTTP is refused.
127
+ - Writes are resolved against `.agentmd/presets/` and refuse to escape it.
128
+ - Fetches are bounded by timeout, redirect count and response size.
129
+ - No accounts, no telemetry, one runtime dependency (`picocolors`).
130
+
131
+ Packages are not signed — see
132
+ [SECURITY.md](https://github.com/Aaditya1273/Agent.md/blob/main/SECURITY.md)
133
+ for the full threat model, including what is *not* guaranteed.
134
+
135
+ ---
136
+
137
+ ## Environment
138
+
139
+ | Variable | Meaning |
140
+ | --- | --- |
141
+ | `AGENTMD_REGISTRY_BASE` | Override the registry base URL (mirrors, testing) |
142
+ | `AGENTMD_TIMEOUT_MS` | Per-request timeout, default 30000 |
143
+ | `AGENTMD_DEBUG` | Print a stack trace on error |
144
+
145
+ Requires Node 18+.
146
+
147
+ ---
148
+
149
+ ## Contributing
150
+
151
+ Adding a package is a directory and one markdown file — see
152
+ [CONTRIBUTING.md](https://github.com/Aaditya1273/Agent.md/blob/main/CONTRIBUTING.md).
153
+
154
+ ```bash
155
+ cd packages/cli && npm ci && npm test
156
+ ```
157
+
158
+ MIT licensed.
package/bin/agentmd.js ADDED
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * agentmd — the package manager for AI coding-agent engineering standards.
5
+ *
6
+ * Packages are markdown. `install` fetches them into .agentmd/presets/,
7
+ * `link` wires them into whichever agent config conventions your project
8
+ * uses, and `update`/`outdated` keep them current.
9
+ */
10
+
11
+ "use strict";
12
+
13
+ const pc = require("picocolors");
14
+ const pkg = require("../package.json");
15
+ const { cmd } = require("../src/lib/invocation");
16
+
17
+ /**
18
+ * How the user invoked us. `npx activate-agentmd` does not put `agentmd` on PATH,
19
+ * so every example below has to match the form they actually typed — otherwise
20
+ * the first thing they copy out of --help is `command not found`.
21
+ */
22
+ const A = cmd();
23
+
24
+ const HELP = `
25
+ ${pc.bold("agentmd")} ${pc.dim("v" + pkg.version)} — engineering standards for AI coding agents
26
+
27
+ ${pc.bold("USAGE")}
28
+ ${A} <command> [options]
29
+
30
+ ${pc.bold("GETTING STARTED")}
31
+ ${pc.cyan("init")} Scan this project and install what matches
32
+ ${pc.cyan("link")} Wire installed packages into your agent config files
33
+ ${pc.cyan("extract")} ${pc.yellow("PRO")} Derive this repo's unwritten conventions into a local standard
34
+ ${pc.cyan("review")} ${pc.white("[--base ref]")} Check a diff against the installed standards (--fast: offline patterns, --local: Ollama)
35
+ ${pc.cyan("test")} Benchmark whether the model actually obeys the installed standards
36
+
37
+ ${pc.bold("FINDING PACKAGES")}
38
+ ${pc.cyan("search")} ${pc.white("<query>")} Search names, categories and descriptions
39
+ ${pc.cyan("list")} ${pc.white("[model[/category]]")} Browse the registry
40
+ ${pc.cyan("list")} ${pc.white("--installed")} Show what this project has
41
+ ${pc.cyan("info")} ${pc.white("<package>")} Details for one package
42
+
43
+ ${pc.bold("MANAGING PACKAGES")}
44
+ ${pc.cyan("install")} ${pc.white("<package>…")} Install packages, or a whole category
45
+ ${pc.cyan("remove")} ${pc.white("<package>")} Uninstall a package
46
+ ${pc.cyan("outdated")} Show packages that have changed upstream
47
+ ${pc.cyan("update")} Pull the current version of each package
48
+ ${pc.cyan("validate")} Check registry and manifest integrity
49
+ ${pc.cyan("agents")} List supported agent config targets
50
+ ${pc.cyan("ci")} Write or check the CI workflow (${pc.yellow("--cloud")}: hosted, private preview)
51
+
52
+ ${pc.bold("ACCOUNT")}
53
+ ${pc.cyan("login")} ${pc.white("[--key <key>]")} Sign in with an Agent.md Pro license key
54
+ ${pc.cyan("logout")} Forget the stored key
55
+ ${pc.cyan("whoami")} Show the current plan
56
+ ${pc.cyan("sync")} ${pc.white("--private")} ${pc.yellow("PRO")} Push or pull your company's own standards
57
+ ${pc.cyan("analytics")} ${pc.yellow("PRO")} What your standards actually caught
58
+ ${pc.cyan("telemetry")} Anonymous usage counters (off by default)
59
+
60
+ ${pc.bold("PACKAGE PATHS")}
61
+ ${pc.white("<model>/<category>/<package>")} e.g. ${pc.white("claude/Security/owasp")}
62
+ ${pc.white("<model>/<category>/")} installs the whole category
63
+
64
+ ${pc.bold("OPTIONS")}
65
+ --force Overwrite existing files / local edits
66
+ --dry Show what would happen, change nothing
67
+ --yes, -y Skip the confirmation prompt (init)
68
+ --model=NAME Choose a model (default: claude)
69
+ --agent=NAME Choose link targets, comma-separated, or "all"
70
+ --help, -h Show this help
71
+ --version, -v Show the version
72
+
73
+ ${pc.bold("FREE AND PRO")}
74
+ ${pc.dim("Everything local is free, forever: init, link, install, update, list, search,")}
75
+ ${pc.dim("review --fast and review --local. No account, no key, no telemetry.")}
76
+ ${pc.dim("Pro adds extract, private sync, analytics and cloud CI — " + (process.env.AGENTMD_PRO_URL || "https://agent-dot-md.vercel.app/pro"))}
77
+ ${pc.dim("In CI, set AGENTMD_PRO_KEY instead of logging in.")}
78
+
79
+ ${pc.bold("EXAMPLES")}
80
+ ${pc.dim("# The one-minute path")}
81
+ ${A} init
82
+
83
+ ${pc.dim("# Find and install a specific standard")}
84
+ ${A} search authentication
85
+ ${A} install claude/Security/owasp
86
+ ${A} link
87
+
88
+ ${pc.dim("# Install every Testing standard, then keep it current")}
89
+ ${A} install claude/Testing/
90
+ ${A} outdated
91
+
92
+ ${pc.dim(" Files are written to .agentmd/presets/, tracked in .agentmd/manifest.json")}
93
+ ${pc.dim(" Docs: https://github.com/Aaditya1273/Agent.md")}
94
+ `;
95
+
96
+ /** Loaded lazily so `--help` and `--version` never parse the 348 KB index. */
97
+ const { requirePro } = require("../src/lib/pro");
98
+
99
+ const COMMANDS = {
100
+ init: () => require("../src/commands/init").init,
101
+ link: () => require("../src/commands/link").link,
102
+ install: () => require("../src/commands/install").install,
103
+ remove: () => require("../src/commands/remove").remove,
104
+ list: () => require("../src/commands/list").list,
105
+ search: () => require("../src/commands/search").search,
106
+ info: () => require("../src/commands/info").info,
107
+ outdated: () => require("../src/commands/outdated").outdated,
108
+ update: () => require("../src/commands/update").update,
109
+ validate: () => require("../src/commands/validate").validate,
110
+ agents: () => require("../src/commands/agents").agents,
111
+ review: () => require("../src/commands/review").review,
112
+ test: () => require("../src/commands/test").test,
113
+ ci: () => require("../src/commands/ci").ci,
114
+
115
+ // Account.
116
+ login: () => require("../src/commands/auth").login,
117
+ logout: () => require("../src/commands/auth").logout,
118
+ whoami: () => require("../src/commands/auth").whoami,
119
+ telemetry: () => require("../src/commands/telemetry").telemetry,
120
+
121
+ // Pro. The gate lives here, in one table, rather than inside each command —
122
+ // so what is paid for can be read at a glance and a command cannot
123
+ // accidentally half-gate itself. `ci --cloud` gates inside ci.js instead,
124
+ // because the command itself is free and only that one flag is not.
125
+ extract: () => requirePro("extract", require("../src/commands/extract").extract),
126
+ sync: () => requirePro("sync:private", require("../src/commands/sync").sync),
127
+ analytics: () => requirePro("analytics", require("../src/commands/analytics").analytics),
128
+ };
129
+
130
+ /** Short forms, plus the names other package managers train people to type. */
131
+ const ALIASES = {
132
+ i: "install",
133
+ add: "install",
134
+ rm: "remove",
135
+ uninstall: "remove",
136
+ ls: "list",
137
+ s: "search",
138
+ find: "search",
139
+ show: "info",
140
+ up: "update",
141
+ upgrade: "update",
142
+ check: "outdated",
143
+ lint: "validate",
144
+ };
145
+
146
+ async function main() {
147
+ const argv = process.argv.slice(2);
148
+
149
+ if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
150
+ console.log(HELP);
151
+ return;
152
+ }
153
+ if (argv.includes("--version") || argv.includes("-v")) {
154
+ console.log(pkg.version);
155
+ return;
156
+ }
157
+
158
+ const name = ALIASES[argv[0]] || argv[0];
159
+ const load = Object.prototype.hasOwnProperty.call(COMMANDS, name) ? COMMANDS[name] : null;
160
+
161
+ if (!load) {
162
+ console.error(pc.red(`Unknown command: "${argv[0]}"`));
163
+ console.error("");
164
+ console.error(pc.dim(" Available: " + Object.keys(COMMANDS).join(", ")));
165
+ console.error(pc.dim(` Run '${A} --help' for usage.`));
166
+ process.exit(1);
167
+ }
168
+
169
+ // Telemetry is off unless the user opted in; `timed` is a no-op until then.
170
+ const { timed } = require("../src/lib/telemetry");
171
+ await timed(name, () => load()(argv.slice(1)));
172
+
173
+ // One ask, in one place, after the command succeeded. Throwing above skips
174
+ // it — a star request stapled to a stack trace is the wrong read of the
175
+ // room. star.js decides whether this particular moment earned one.
176
+ require("../src/lib/star").maybeAsk(name);
177
+ }
178
+
179
+ main().catch((err) => {
180
+ console.error("");
181
+ console.error(pc.red("Error: ") + err.message);
182
+ if (process.env.AGENTMD_DEBUG) console.error(pc.dim(err.stack));
183
+ else console.error(pc.dim(" Re-run with AGENTMD_DEBUG=1 for a stack trace."));
184
+ console.error("");
185
+ process.exit(1);
186
+ });
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "activate-agentmd",
3
+ "version": "2.4.0",
4
+ "description": "The package manager for AI coding-agent engineering standards. Install, link and update rules across Claude Code, Cursor, Codex, Gemini and Copilot.",
5
+ "keywords": [
6
+ "ai",
7
+ "agent",
8
+ "agents-md",
9
+ "claude",
10
+ "claude-code",
11
+ "cursor",
12
+ "cursor-rules",
13
+ "codex",
14
+ "copilot",
15
+ "gemini",
16
+ "llm",
17
+ "prompts",
18
+ "presets",
19
+ "engineering-standards",
20
+ "ai-coding"
21
+ ],
22
+ "homepage": "https://github.com/Aaditya1273/Agent.md#readme",
23
+ "bugs": {
24
+ "url": "https://github.com/Aaditya1273/Agent.md/issues"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/Aaditya1273/Agent.md.git"
29
+ },
30
+ "license": "MIT",
31
+ "bin": {
32
+ "agentmd": "bin/agentmd.js"
33
+ },
34
+ "files": [
35
+ "bin/",
36
+ "src/",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "scripts": {
41
+ "test": "node --test test/*.test.js",
42
+ "smoke": "node bin/agentmd.js --help >/dev/null && node bin/agentmd.js validate",
43
+ "start": "node bin/agentmd.js",
44
+ "prepack": "node scripts/check-publish-safety.js",
45
+ "safety": "node scripts/check-publish-safety.js",
46
+ "test:pro": "node scripts/test-pro.js"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "dependencies": {
52
+ "@anthropic-ai/sdk": "^0.125.0",
53
+ "picocolors": "1.1.1"
54
+ }
55
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * agents.js
3
+ *
4
+ * Handles: agentmd agents
5
+ *
6
+ * Lists every agent config convention `agentmd link` can write, and whether
7
+ * this project already has it. Deliberately short: it lists what is
8
+ * implemented, not what is planned.
9
+ */
10
+
11
+ "use strict";
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const pc = require("picocolors");
16
+ const { TARGETS } = require("../lib/agents");
17
+ const { cmd } = require("../lib/invocation");
18
+
19
+ /** How the user invoked us — "npx activate-agentmd" or "agentmd". */
20
+ const A = cmd();
21
+
22
+ function agents() {
23
+ const cwd = process.cwd();
24
+
25
+ console.log(pc.bold("\nAgent config targets\n"));
26
+ console.log(pc.dim(" Packages are plain markdown, so any agent can read them. These are the"));
27
+ console.log(pc.dim(` config conventions '${A} link' writes automatically.\n`));
28
+
29
+ const width = Math.max(...Object.keys(TARGETS).map((k) => k.length));
30
+ for (const [name, target] of Object.entries(TARGETS)) {
31
+ const present = fs.existsSync(path.join(cwd, target.file));
32
+ const mark = present ? pc.green("✓") : pc.dim("·");
33
+ console.log(
34
+ ` ${mark} ${pc.cyan(name.padEnd(width))} ${pc.white(target.file.padEnd(34))} ${pc.dim(target.label)}`
35
+ );
36
+ }
37
+
38
+ console.log("");
39
+ console.log(pc.dim(" ✓ = already present in this project"));
40
+ console.log(pc.dim(` Write them: ${A} link --agent=claude,cursor`));
41
+ console.log(pc.dim(` Write all: ${A} link --agent=all\n`));
42
+ }
43
+
44
+ module.exports = { agents };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * analytics.js
3
+ *
4
+ * Handles: agentmd analytics [--since 30d] [--json] (Pro)
5
+ *
6
+ * Reads the local ledger `review` writes and answers the question that decides
7
+ * whether any of this was worth it: which standards actually caught something,
8
+ * and which have never fired.
9
+ *
10
+ * The second half matters more than the first. A rule nobody ever violates is
11
+ * costing tokens on every prompt and buying nothing, and this is the only
12
+ * place that says so with a number.
13
+ */
14
+
15
+ "use strict";
16
+
17
+ const pc = require("picocolors");
18
+ const { summarise, read } = require("../lib/analytics");
19
+ const { readManifest } = require("../lib/manifest");
20
+ const { cmd } = require("../lib/invocation");
21
+
22
+ const A = cmd();
23
+
24
+ /** `30d`, `12w`, `6m`, or `all`. */
25
+ function parseSince(value) {
26
+ if (!value || value === "all") return null;
27
+ const m = /^(\d+)\s*([dwm])$/i.exec(value.trim());
28
+ if (!m) return undefined; // signals "unparseable" so the caller can complain
29
+ const days = Number(m[1]) * ({ d: 1, w: 7, m: 30 })[m[2].toLowerCase()];
30
+ return new Date(Date.now() - days * 86400000);
31
+ }
32
+
33
+ const flagValue = (args, name) => {
34
+ const inline = (args || []).find((a) => a.startsWith(`--${name}=`));
35
+ if (inline) return inline.slice(name.length + 3);
36
+ const i = (args || []).indexOf(`--${name}`);
37
+ return i !== -1 && args[i + 1] && !args[i + 1].startsWith("--") ? args[i + 1] : null;
38
+ };
39
+
40
+ /** A fixed-width bar for a value against the largest in its column. */
41
+ const bar = (n, max, width = 18) =>
42
+ pc.dim("▁".repeat(0)) + "█".repeat(Math.max(1, Math.round((n / Math.max(1, max)) * width)));
43
+
44
+ function analytics(args) {
45
+ args = args || [];
46
+ const cwd = process.cwd();
47
+ const sinceRaw = flagValue(args, "since") || "all";
48
+ const since = parseSince(sinceRaw);
49
+
50
+ if (since === undefined) {
51
+ console.error(pc.red(`\n Could not read --since "${sinceRaw}". Use 30d, 12w, 6m or all.\n`));
52
+ process.exitCode = 1;
53
+ return;
54
+ }
55
+
56
+ const ledger = read(cwd);
57
+ if (ledger.runs.length === 0) {
58
+ console.log(`\n ${pc.dim("No review history in this project yet.")}`);
59
+ console.log(pc.dim(` Run '${A} review --fast' — it records every run, free and offline.\n`));
60
+ return;
61
+ }
62
+
63
+ const s = summarise(cwd, since);
64
+
65
+ if (args.includes("--json")) {
66
+ console.log(JSON.stringify(s, null, 2));
67
+ return;
68
+ }
69
+
70
+ const window = since ? `since ${since.toISOString().slice(0, 10)}` : "all time";
71
+ console.log(pc.bold(`\nWhat your standards caught ${pc.dim("— " + window)}\n`));
72
+
73
+ if (s.runs === 0) {
74
+ console.log(pc.dim(` No reviews in that window. ${ledger.runs.length} run(s) recorded overall.\n`));
75
+ return;
76
+ }
77
+
78
+ console.log(` ${pc.white(String(s.total).padStart(5))} ${pc.dim("findings across")} ${pc.white(String(s.runs))} ${pc.dim("review" + (s.runs === 1 ? "" : "s"))}`);
79
+ console.log(` ${pc.dim(" " + s.patterns + " caught offline by pattern rules — no model, no tokens")}`);
80
+ console.log("");
81
+ console.log(
82
+ ` ${pc.red(String(s.bySeverity.high || 0) + " high")} ` +
83
+ `${pc.yellow(String(s.bySeverity.medium || 0) + " medium")} ` +
84
+ `${pc.dim(String(s.bySeverity.low || 0) + " low")}`
85
+ );
86
+
87
+ if (s.byStandard.length) {
88
+ const max = s.byStandard[0][1];
89
+ console.log(pc.bold("\n Per standard\n"));
90
+ for (const [standard, n] of s.byStandard.slice(0, 12)) {
91
+ console.log(` ${pc.white(String(n).padStart(4))} ${pc.dim(bar(n, max))} ${standard}`);
92
+ }
93
+ }
94
+
95
+ if (s.byRule.length > 1) {
96
+ console.log(pc.bold("\n Most-broken rules\n"));
97
+ for (const [rule, n] of s.byRule.slice(0, 5)) {
98
+ console.log(` ${pc.white(String(n).padStart(4))} ${rule}`);
99
+ }
100
+ }
101
+
102
+ // The expensive half: what you are paying context for and getting nothing from.
103
+ try {
104
+ const installed = readManifest(cwd).presets.map((p) => `${p.category}/${p.preset}`);
105
+ const idle = installed.filter((id) => !s.idle.seen.has(id));
106
+ if (idle.length) {
107
+ console.log(pc.bold(`\n Never fired ${pc.dim(`(${idle.length} of ${installed.length} installed)`)}\n`));
108
+ for (const id of idle.slice(0, 10)) console.log(` ${pc.dim("·")} ${pc.dim(id)}`);
109
+ if (idle.length > 10) console.log(pc.dim(` … and ${idle.length - 10} more`));
110
+ console.log(pc.dim(`\n These cost context on every prompt. Drop one with '${A} remove <id>' if it is not`));
111
+ console.log(pc.dim(" guarding something you simply have not broken yet."));
112
+ }
113
+ } catch {
114
+ // no manifest here — the per-standard table above still stands on its own
115
+ }
116
+
117
+ if (s.byWeek.length > 1) {
118
+ const max = Math.max(...s.byWeek.map(([, n]) => n));
119
+ console.log(pc.bold("\n By week\n"));
120
+ for (const [week, n] of s.byWeek.slice(-8)) {
121
+ console.log(` ${pc.dim(week)} ${pc.white(String(n).padStart(4))} ${pc.dim(bar(n, max, 24))}`);
122
+ }
123
+ }
124
+
125
+ console.log(pc.dim(`\n Local only — this never leaves your machine. ${A} analytics --json for the raw numbers.\n`));
126
+ }
127
+
128
+ module.exports = { analytics, parseSince };