tribunal-kit 5.7.0 → 5.8.1

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 (59) hide show
  1. package/.agent/ARCHITECTURE.md +6 -7
  2. package/.agent/agents/frontend-reviewer.md +13 -0
  3. package/.agent/agents/frontend-specialist.md +14 -0
  4. package/.agent/agents/logic-reviewer.md +11 -0
  5. package/.agent/agents/orchestrator.md +15 -0
  6. package/.agent/agents/project-planner.md +5 -0
  7. package/.agent/agents/security-auditor.md +13 -0
  8. package/.agent/agents/ui-ux-auditor.md +7 -31
  9. package/.agent/history/memory/.memory.idx +1693 -0
  10. package/.agent/history/memory/MEMORY.md +123 -0
  11. package/.agent/routing_index.json +694 -714
  12. package/.agent/rules/GEMINI.md +88 -13
  13. package/.agent/scripts/_colors.js +131 -89
  14. package/.agent/scripts/_utils.js +163 -128
  15. package/.agent/scripts/auto_preview.js +207 -197
  16. package/.agent/scripts/bundle_analyzer.js +227 -192
  17. package/.agent/scripts/case_law_manager.js +991 -689
  18. package/.agent/scripts/checklist.js +233 -190
  19. package/.agent/scripts/context_broker.js +930 -605
  20. package/.agent/scripts/dependency_analyzer.js +275 -184
  21. package/.agent/scripts/graph_builder.js +412 -341
  22. package/.agent/scripts/graph_visualizer.js +392 -390
  23. package/.agent/scripts/graph_zoom.js +198 -156
  24. package/.agent/scripts/inner_loop_validator.js +523 -445
  25. package/.agent/scripts/lint_runner.js +199 -157
  26. package/.agent/scripts/marathon_harness.js +819 -661
  27. package/.agent/scripts/minify_context.js +115 -100
  28. package/.agent/scripts/mutation_runner.js +321 -280
  29. package/.agent/scripts/prompt_compiler.js +62 -42
  30. package/.agent/scripts/schema_validator.js +373 -280
  31. package/.agent/scripts/security_scan.js +333 -190
  32. package/.agent/scripts/session_manager.js +306 -270
  33. package/.agent/scripts/skill_evolution.js +810 -637
  34. package/.agent/scripts/skill_integrator.js +327 -307
  35. package/.agent/scripts/strengthen_skills.js +203 -193
  36. package/.agent/scripts/swarm_dispatcher.js +558 -457
  37. package/.agent/scripts/test_runner.js +178 -152
  38. package/.agent/scripts/verify_all.js +200 -168
  39. package/.agent/skills/fabel-protocol/SKILL.md +271 -0
  40. package/.agent/skills/thinking-protocol/SKILL.md +27 -0
  41. package/.agent/workflows/generate.md +2 -1
  42. package/.agent/workflows/tribunal-full.md +4 -3
  43. package/.agent/workflows/tribunal-speed.md +1 -1
  44. package/README.md +184 -58
  45. package/bin/mcp-server.js +496 -173
  46. package/bin/tribunal-kit.js +1245 -987
  47. package/bin/wrapper.js +108 -74
  48. package/dist/cli.js +44 -0
  49. package/dist/commands/align.js +201 -0
  50. package/dist/commands/case.js +23 -0
  51. package/dist/commands/compile.js +84 -0
  52. package/dist/commands/init.js +42 -0
  53. package/dist/commands/learn.js +57 -0
  54. package/dist/commands/memory.js +456 -0
  55. package/package.json +22 -10
  56. package/scripts/benchmark.js +162 -125
  57. package/scripts/changelog.js +196 -168
  58. package/scripts/sync-version.js +94 -81
  59. package/scripts/validate-payload.js +85 -78
@@ -1,168 +1,196 @@
1
- #!/usr/bin/env node
2
- /**
3
- * changelog.js — Auto-generate CHANGELOG from git history
4
- *
5
- * Categorizes commits by conventional commit type:
6
- * feat: → ✨ Features
7
- * fix: → 🐛 Bug Fixes
8
- * perf: → ⚡ Performance
9
- * docs: → 📝 Documentation
10
- * test: → ✅ Tests
11
- * refactor: → ♻️ Refactors
12
- * chore: → 🔧 Chores
13
- * BREAKING: → 💥 Breaking Changes
14
- *
15
- * Usage:
16
- * node scripts/changelog.js → Generate full changelog
17
- * node scripts/changelog.js --preview → Preview unreleased changes
18
- * node scripts/changelog.js --since v4.1.0 → Changes since a specific tag
19
- */
20
-
21
- 'use strict';
22
-
23
- const { execSync } = require('child_process');
24
- const fs = require('fs');
25
- const path = require('path');
26
-
27
- const PKG = require(path.resolve(__dirname, '..', 'package.json'));
28
- const CHANGELOG_PATH = path.resolve(__dirname, '..', 'CHANGELOG.md');
29
-
30
- // ── Commit Categories ────────────────────────────────────
31
- const CATEGORIES = {
32
- feat: { emoji: '', title: 'Features' },
33
- fix: { emoji: '🐛', title: 'Bug Fixes' },
34
- perf: { emoji: '', title: 'Performance' },
35
- docs: { emoji: '📝', title: 'Documentation' },
36
- test: { emoji: '', title: 'Tests' },
37
- refactor: { emoji: '♻️', title: 'Refactors' },
38
- chore: { emoji: '🔧', title: 'Chores' },
39
- ci: { emoji: '🏗️', title: 'CI/CD' },
40
- style: { emoji: '🎨', title: 'Style' },
41
- breaking: { emoji: '💥', title: 'Breaking Changes' },
42
- };
43
-
44
- // ── Git Helpers ──────────────────────────────────────────
45
- function git(cmd) {
46
- try {
47
- return execSync(`git ${cmd}`, { encoding: 'utf8', timeout: 10000 }).trim();
48
- } catch {
49
- return '';
50
- }
51
- }
52
-
53
- function getLatestTag() {
54
- return git('describe --tags --abbrev=0 2>nul') || git('describe --tags --abbrev=0 2>/dev/null') || '';
55
- }
56
-
57
- function getCommits(since) {
58
- const range = since ? `${since}..HEAD` : 'HEAD';
59
- const MAX_COMMITS = 500;
60
- const format = '--format=%H||%s||%an||%ai';
61
- const raw = git(`log ${range} ${format} --no-merges -n ${MAX_COMMITS}`);
62
- if (!raw) return [];
63
-
64
- return raw.split('\n').filter(Boolean).map(line => {
65
- const [hash, subject, author, date] = line.split('||');
66
- return { hash: hash?.replace(/^"/, '').slice(0, 7), subject, author, date: date?.replace(/"$/, '').slice(0, 10) };
67
- });
68
- }
69
-
70
- function categorize(subject) {
71
- const lower = subject.toLowerCase();
72
-
73
- // Check for BREAKING CHANGE
74
- if (lower.includes('breaking') || lower.includes('!:')) {
75
- return 'breaking';
76
- }
77
-
78
- // Match conventional commit prefix
79
- const match = subject.match(/^(\w+)(?:\(.+?\))?:\s*/);
80
- if (match) {
81
- const type = match[1].toLowerCase();
82
- if (CATEGORIES[type]) return type;
83
- }
84
-
85
- // Heuristic fallback
86
- if (lower.includes('fix') || lower.includes('bug')) return 'fix';
87
- if (lower.includes('add') || lower.includes('new') || lower.includes('feat')) return 'feat';
88
- if (lower.includes('doc') || lower.includes('readme')) return 'docs';
89
- if (lower.includes('test')) return 'test';
90
- if (lower.includes('refactor') || lower.includes('clean')) return 'refactor';
91
- if (lower.includes('perf') || lower.includes('optim')) return 'perf';
92
- if (lower.includes('ci') || lower.includes('workflow')) return 'ci';
93
-
94
- return 'chore';
95
- }
96
-
97
- // ── Changelog Generation ─────────────────────────────────
98
- function generateChangelog(commits, version, date) {
99
- const grouped = {};
100
- for (const commit of commits) {
101
- const cat = categorize(commit.subject);
102
- if (!grouped[cat]) grouped[cat] = [];
103
- // Strip conventional prefix for cleaner display
104
- const clean = commit.subject.replace(/^\w+(\(.+?\))?:\s*/, '');
105
- grouped[cat].push({ ...commit, clean });
106
- }
107
-
108
- let md = `## [${version}] — ${date}\n\n`;
109
-
110
- // Breaking changes first
111
- const order = ['breaking', 'feat', 'fix', 'perf', 'refactor', 'docs', 'test', 'ci', 'style', 'chore'];
112
- for (const cat of order) {
113
- if (!grouped[cat] || grouped[cat].length === 0) continue;
114
- const { emoji, title } = CATEGORIES[cat];
115
- md += `### ${emoji} ${title}\n\n`;
116
- for (const c of grouped[cat]) {
117
- md += `- ${c.clean} (\`${c.hash}\`)\n`;
118
- }
119
- md += '\n';
120
- }
121
-
122
- return md;
123
- }
124
-
125
- // ── Main ─────────────────────────────────────────────────
126
- function main() {
127
- const args = process.argv.slice(2);
128
- const isPreview = args.includes('--preview');
129
- const sinceIdx = args.indexOf('--since');
130
- const sinceTag = sinceIdx !== -1 ? args[sinceIdx + 1] : null;
131
-
132
- const since = sinceTag || getLatestTag();
133
- const commits = getCommits(since);
134
-
135
- if (commits.length === 0) {
136
- console.log(' ℹ️ No new commits found since', since || 'beginning');
137
- process.exit(0);
138
- }
139
-
140
- const today = new Date().toISOString().slice(0, 10);
141
- const version = isPreview ? 'Unreleased' : PKG.version;
142
-
143
- const changelog = generateChangelog(commits, version, today);
144
-
145
- if (isPreview) {
146
- console.log('\n 📋 Changelog Preview\n ' + '─'.repeat(40) + '\n');
147
- console.log(changelog);
148
- console.log(` 📊 ${commits.length} commits since ${since || 'initial commit'}`);
149
- return;
150
- }
151
-
152
- // Write or prepend to CHANGELOG.md
153
- const header = `# Changelog\n\nAll notable changes to Tribunal Kit are documented here.\nFormat follows [Keep a Changelog](https://keepachangelog.com/).\n\n`;
154
-
155
- let existing = '';
156
- if (fs.existsSync(CHANGELOG_PATH)) {
157
- existing = fs.readFileSync(CHANGELOG_PATH, 'utf8');
158
- // Remove existing header
159
- existing = existing.replace(/^# Changelog[\s\S]*?(?=## )/, '');
160
- }
161
-
162
- const full = header + changelog + existing;
163
- fs.writeFileSync(CHANGELOG_PATH, full, 'utf8');
164
-
165
- console.log(` CHANGELOG.md updated v${version} (${commits.length} commits)`);
166
- }
167
-
168
- main();
1
+ #!/usr/bin/env node
2
+ /**
3
+ * changelog.js — Auto-generate CHANGELOG from git history
4
+ *
5
+ * Categorizes commits by conventional commit type:
6
+ * feat: → ✨ Features
7
+ * fix: → 🐛 Bug Fixes
8
+ * perf: → ⚡ Performance
9
+ * docs: → 📝 Documentation
10
+ * test: → ✅ Tests
11
+ * refactor: → ♻️ Refactors
12
+ * chore: → 🔧 Chores
13
+ * BREAKING: → 💥 Breaking Changes
14
+ *
15
+ * Usage:
16
+ * node scripts/changelog.js → Generate full changelog
17
+ * node scripts/changelog.js --preview → Preview unreleased changes
18
+ * node scripts/changelog.js --since v4.1.0 → Changes since a specific tag
19
+ */
20
+
21
+ "use strict";
22
+
23
+ const { execSync } = require("child_process");
24
+ const fs = require("fs");
25
+ const path = require("path");
26
+
27
+ const PKG = require(path.resolve(__dirname, "..", "package.json"));
28
+ const CHANGELOG_PATH = path.resolve(__dirname, "..", "CHANGELOG.md");
29
+
30
+ // ── Commit Categories ────────────────────────────────────
31
+ const CATEGORIES = {
32
+ feat: { emoji: "", title: "Features" },
33
+ fix: { emoji: "🐛", title: "Bug Fixes" },
34
+ perf: { emoji: "", title: "Performance" },
35
+ docs: { emoji: "📝", title: "Documentation" },
36
+ test: { emoji: "", title: "Tests" },
37
+ refactor: { emoji: "♻️", title: "Refactors" },
38
+ chore: { emoji: "🔧", title: "Chores" },
39
+ ci: { emoji: "🏗️", title: "CI/CD" },
40
+ style: { emoji: "🎨", title: "Style" },
41
+ breaking: { emoji: "💥", title: "Breaking Changes" },
42
+ };
43
+
44
+ // ── Git Helpers ──────────────────────────────────────────
45
+ function git(cmd) {
46
+ try {
47
+ return execSync(`git ${cmd}`, { encoding: "utf8", timeout: 10000 }).trim();
48
+ } catch {
49
+ return "";
50
+ }
51
+ }
52
+
53
+ function getLatestTag() {
54
+ return (
55
+ git("describe --tags --abbrev=0 2>nul") ||
56
+ git("describe --tags --abbrev=0 2>/dev/null") ||
57
+ ""
58
+ );
59
+ }
60
+
61
+ function getCommits(since) {
62
+ const range = since ? `${since}..HEAD` : "HEAD";
63
+ const MAX_COMMITS = 500;
64
+ const format = "--format=%H||%s||%an||%ai";
65
+ const raw = git(`log ${range} ${format} --no-merges -n ${MAX_COMMITS}`);
66
+ if (!raw) return [];
67
+
68
+ return raw
69
+ .split("\n")
70
+ .filter(Boolean)
71
+ .map((line) => {
72
+ const [hash, subject, author, date] = line.split("||");
73
+ return {
74
+ hash: hash?.replace(/^"/, "").slice(0, 7),
75
+ subject,
76
+ author,
77
+ date: date?.replace(/"$/, "").slice(0, 10),
78
+ };
79
+ });
80
+ }
81
+
82
+ function categorize(subject) {
83
+ const lower = subject.toLowerCase();
84
+
85
+ // Check for BREAKING CHANGE
86
+ if (lower.includes("breaking") || lower.includes("!:")) {
87
+ return "breaking";
88
+ }
89
+
90
+ // Match conventional commit prefix
91
+ const match = subject.match(/^(\w+)(?:\(.+?\))?:\s*/);
92
+ if (match) {
93
+ const type = match[1].toLowerCase();
94
+ if (CATEGORIES[type]) return type;
95
+ }
96
+
97
+ // Heuristic fallback
98
+ if (lower.includes("fix") || lower.includes("bug")) return "fix";
99
+ if (lower.includes("add") || lower.includes("new") || lower.includes("feat"))
100
+ return "feat";
101
+ if (lower.includes("doc") || lower.includes("readme")) return "docs";
102
+ if (lower.includes("test")) return "test";
103
+ if (lower.includes("refactor") || lower.includes("clean")) return "refactor";
104
+ if (lower.includes("perf") || lower.includes("optim")) return "perf";
105
+ if (lower.includes("ci") || lower.includes("workflow")) return "ci";
106
+
107
+ return "chore";
108
+ }
109
+
110
+ // ── Changelog Generation ─────────────────────────────────
111
+ function generateChangelog(commits, version, date) {
112
+ const grouped = {};
113
+ for (const commit of commits) {
114
+ const cat = categorize(commit.subject);
115
+ if (!grouped[cat]) grouped[cat] = [];
116
+ // Strip conventional prefix for cleaner display
117
+ const clean = commit.subject.replace(/^\w+(\(.+?\))?:\s*/, "");
118
+ grouped[cat].push({ ...commit, clean });
119
+ }
120
+
121
+ let md = `## [${version}] — ${date}\n\n`;
122
+
123
+ // Breaking changes first
124
+ const order = [
125
+ "breaking",
126
+ "feat",
127
+ "fix",
128
+ "perf",
129
+ "refactor",
130
+ "docs",
131
+ "test",
132
+ "ci",
133
+ "style",
134
+ "chore",
135
+ ];
136
+ for (const cat of order) {
137
+ if (!grouped[cat] || grouped[cat].length === 0) continue;
138
+ const { emoji, title } = CATEGORIES[cat];
139
+ md += `### ${emoji} ${title}\n\n`;
140
+ for (const c of grouped[cat]) {
141
+ md += `- ${c.clean} (\`${c.hash}\`)\n`;
142
+ }
143
+ md += "\n";
144
+ }
145
+
146
+ return md;
147
+ }
148
+
149
+ // ── Main ─────────────────────────────────────────────────
150
+ function main() {
151
+ const args = process.argv.slice(2);
152
+ const isPreview = args.includes("--preview");
153
+ const sinceIdx = args.indexOf("--since");
154
+ const sinceTag = sinceIdx !== -1 ? args[sinceIdx + 1] : null;
155
+
156
+ const since = sinceTag || getLatestTag();
157
+ const commits = getCommits(since);
158
+
159
+ if (commits.length === 0) {
160
+ console.log(" ℹ️ No new commits found since", since || "beginning");
161
+ process.exit(0);
162
+ }
163
+
164
+ const today = new Date().toISOString().slice(0, 10);
165
+ const version = isPreview ? "Unreleased" : PKG.version;
166
+
167
+ const changelog = generateChangelog(commits, version, today);
168
+
169
+ if (isPreview) {
170
+ console.log("\n 📋 Changelog Preview\n " + "─".repeat(40) + "\n");
171
+ console.log(changelog);
172
+ console.log(
173
+ ` 📊 ${commits.length} commits since ${since || "initial commit"}`,
174
+ );
175
+ return;
176
+ }
177
+
178
+ // Write or prepend to CHANGELOG.md
179
+ const header = `# Changelog\n\nAll notable changes to Tribunal Kit are documented here.\nFormat follows [Keep a Changelog](https://keepachangelog.com/).\n\n`;
180
+
181
+ let existing = "";
182
+ if (fs.existsSync(CHANGELOG_PATH)) {
183
+ existing = fs.readFileSync(CHANGELOG_PATH, "utf8");
184
+ // Remove existing header
185
+ existing = existing.replace(/^# Changelog[\s\S]*?(?=## )/, "");
186
+ }
187
+
188
+ const full = header + changelog + existing;
189
+ fs.writeFileSync(CHANGELOG_PATH, full, "utf8");
190
+
191
+ console.log(
192
+ ` ✔ CHANGELOG.md updated — v${version} (${commits.length} commits)`,
193
+ );
194
+ }
195
+
196
+ main();
@@ -1,81 +1,94 @@
1
- #!/usr/bin/env node
2
- /**
3
- * sync-version.js — Version Sync for Tribunal Kit
4
- *
5
- * Reads the version and counts from package.json and the .agent/ directory,
6
- * then updates all stale references across documentation files.
7
- *
8
- * Run manually or as a preversion npm script:
9
- * node scripts/sync-version.js
10
- */
11
-
12
- const fs = require('fs');
13
- const path = require('path');
14
-
15
- const ROOT = path.resolve(__dirname, '..');
16
- const PKG = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
17
-
18
- // Count actual installed items
19
- function countItems(dir) {
20
- const fullPath = path.join(ROOT, '.agent', dir);
21
- if (!fs.existsSync(fullPath)) return '?';
22
- return fs.readdirSync(fullPath).filter(f => !f.startsWith('.')).length;
23
- }
24
-
25
- const version = PKG.version;
26
- const agents = countItems('agents');
27
- const skills = countItems('skills');
28
- const workflows = countItems('workflows');
29
- const scripts = countItems('scripts');
30
-
31
- console.log(`\n 📊 Tribunal Kit v${version} — Actual Counts`);
32
- console.log(` ──────────────────────────────────────`);
33
- console.log(` Agents: ${agents}`);
34
- console.log(` Skills: ${skills}`);
35
- console.log(` Workflows: ${workflows}`);
36
- console.log(` Scripts: ${scripts}`);
37
- console.log();
38
-
39
- // Files to check for stale numbers
40
- const FILES_TO_CHECK = [
41
- 'README.md',
42
- 'AGENT_FLOW.md',
43
- '.agent/ARCHITECTURE.md',
44
- ];
45
-
46
- let staleFound = 0;
47
-
48
- for (const relPath of FILES_TO_CHECK) {
49
- const filePath = path.join(ROOT, relPath);
50
- if (!fs.existsSync(filePath)) continue;
51
-
52
- const content = fs.readFileSync(filePath, 'utf8');
53
-
54
- // Check for common stale patterns
55
- const checks = [
56
- { regex: /(\d+)\s*(specialist\s+)?agents/gi, expected: agents, label: 'agents' },
57
- { regex: /(\d+)\s*skill\s*modules/gi, expected: skills, label: 'skills' },
58
- { regex: /(\d+)\s*slash\s*command/gi, expected: workflows, label: 'workflows' },
59
- ];
60
-
61
- for (const check of checks) {
62
- let match;
63
- while ((match = check.regex.exec(content)) !== null) {
64
- const found = parseInt(match[1]);
65
- if (found !== check.expected && found > 5) { // ignore tiny numbers
66
- staleFound++;
67
- const line = content.substring(0, match.index).split('\n').length;
68
- console.log(` ⚠️ ${relPath}:${line} — says ${found} ${check.label}, actual is ${check.expected}`);
69
- }
70
- }
71
- }
72
- }
73
-
74
- if (staleFound === 0) {
75
- console.log(` ✅ All counts are in sync across ${FILES_TO_CHECK.length} files.`);
76
- } else {
77
- console.log(`\n ❌ Found ${staleFound} stale reference(s). Update manually or run the sync tool.`);
78
- process.exit(1);
79
- }
80
-
81
- console.log();
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sync-version.js — Version Sync for Tribunal Kit
4
+ *
5
+ * Reads the version and counts from package.json and the .agent/ directory,
6
+ * then updates all stale references across documentation files.
7
+ *
8
+ * Run manually or as a preversion npm script:
9
+ * node scripts/sync-version.js
10
+ */
11
+
12
+ const fs = require("fs");
13
+ const path = require("path");
14
+
15
+ const ROOT = path.resolve(__dirname, "..");
16
+ const PKG = JSON.parse(
17
+ fs.readFileSync(path.join(ROOT, "package.json"), "utf8"),
18
+ );
19
+
20
+ // Count actual installed items
21
+ function countItems(dir) {
22
+ const fullPath = path.join(ROOT, ".agent", dir);
23
+ if (!fs.existsSync(fullPath)) return "?";
24
+ return fs.readdirSync(fullPath).filter((f) => !f.startsWith(".")).length;
25
+ }
26
+
27
+ const version = PKG.version;
28
+ const agents = countItems("agents");
29
+ const skills = countItems("skills");
30
+ const workflows = countItems("workflows");
31
+ const scripts = countItems("scripts");
32
+
33
+ console.log(`\n 📊 Tribunal Kit v${version} — Actual Counts`);
34
+ console.log(` ──────────────────────────────────────`);
35
+ console.log(` Agents: ${agents}`);
36
+ console.log(` Skills: ${skills}`);
37
+ console.log(` Workflows: ${workflows}`);
38
+ console.log(` Scripts: ${scripts}`);
39
+ console.log();
40
+
41
+ // Files to check for stale numbers
42
+ const FILES_TO_CHECK = ["README.md", "AGENT_FLOW.md", ".agent/ARCHITECTURE.md"];
43
+
44
+ let staleFound = 0;
45
+
46
+ for (const relPath of FILES_TO_CHECK) {
47
+ const filePath = path.join(ROOT, relPath);
48
+ if (!fs.existsSync(filePath)) continue;
49
+
50
+ const content = fs.readFileSync(filePath, "utf8");
51
+
52
+ // Check for common stale patterns
53
+ const checks = [
54
+ {
55
+ regex: /(\d+)\s*(specialist\s+)?agents/gi,
56
+ expected: agents,
57
+ label: "agents",
58
+ },
59
+ { regex: /(\d+)\s*skill\s*modules/gi, expected: skills, label: "skills" },
60
+ {
61
+ regex: /(\d+)\s*slash\s*command/gi,
62
+ expected: workflows,
63
+ label: "workflows",
64
+ },
65
+ ];
66
+
67
+ for (const check of checks) {
68
+ let match;
69
+ while ((match = check.regex.exec(content)) !== null) {
70
+ const found = parseInt(match[1]);
71
+ if (found !== check.expected && found > 5) {
72
+ // ignore tiny numbers
73
+ staleFound++;
74
+ const line = content.substring(0, match.index).split("\n").length;
75
+ console.log(
76
+ ` ⚠️ ${relPath}:${line} says ${found} ${check.label}, actual is ${check.expected}`,
77
+ );
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ if (staleFound === 0) {
84
+ console.log(
85
+ ` ✅ All counts are in sync across ${FILES_TO_CHECK.length} files.`,
86
+ );
87
+ } else {
88
+ console.log(
89
+ `\n ❌ Found ${staleFound} stale reference(s). Update manually or run the sync tool.`,
90
+ );
91
+ process.exit(1);
92
+ }
93
+
94
+ console.log();