axel-setup 0.2.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 (117) hide show
  1. package/CHANGELOG.md +218 -0
  2. package/CONTRIBUTING.md +58 -0
  3. package/LICENSE +21 -0
  4. package/README.md +518 -0
  5. package/agents/api-design.md +51 -0
  6. package/agents/bughunter.md +136 -0
  7. package/agents/changelog.md +89 -0
  8. package/agents/cleanup.md +126 -0
  9. package/agents/compare-branch.md +35 -0
  10. package/agents/cross-repo.md +97 -0
  11. package/agents/db-check.md +14 -0
  12. package/agents/debug.md +47 -0
  13. package/agents/deploy-check.md +100 -0
  14. package/agents/draft-message.md +19 -0
  15. package/agents/excelsior-coordinator.md +75 -0
  16. package/agents/excelsior-verifier.md +94 -0
  17. package/agents/feature.md +48 -0
  18. package/agents/harness-optimizer.md +40 -0
  19. package/agents/incident.md +48 -0
  20. package/agents/linear-task.md +18 -0
  21. package/agents/onboard.md +24 -0
  22. package/agents/perf.md +44 -0
  23. package/agents/production-validator.md +96 -0
  24. package/agents/review.md +113 -0
  25. package/agents/security-check.md +29 -0
  26. package/agents/sprint-summary.md +15 -0
  27. package/agents/tdd-mainder.md +178 -0
  28. package/agents/test-gen.md +39 -0
  29. package/axel-manifest.json +129 -0
  30. package/bin/axel-setup.js +597 -0
  31. package/bootstrap.sh +1087 -0
  32. package/commands/create-pr.md +13 -0
  33. package/commands/daily.md +182 -0
  34. package/commands/deslop.md +13 -0
  35. package/commands/draft-message.md +23 -0
  36. package/commands/eod-review.md +154 -0
  37. package/commands/execute-prp.md +37 -0
  38. package/commands/generate-prp.md +75 -0
  39. package/commands/multi-repo-feature.md +60 -0
  40. package/commands/roadmap.md +31 -0
  41. package/commands/sprint-status.md +486 -0
  42. package/commands/style.md +68 -0
  43. package/commands/visualize.md +17 -0
  44. package/docs/roadmap/multi-runtime.md +73 -0
  45. package/docs/superpowers/plans/2026-06-12-setup-hardening-roadmap.md +61 -0
  46. package/hooks/desktop-notify.sh +26 -0
  47. package/hooks/enforce-agent-model.jq +14 -0
  48. package/hooks/gsd-context-monitor.js +156 -0
  49. package/hooks/linear-lifecycle-sync.sh +112 -0
  50. package/hooks/memory-dedup.sh +122 -0
  51. package/hooks/memory-extractor.sh +218 -0
  52. package/hooks/post-commit-memory-trigger.sh +16 -0
  53. package/hooks/post-commit-verify.sh +41 -0
  54. package/hooks/post-edit-lint.sh +43 -0
  55. package/hooks/precompact-save-context.sh +124 -0
  56. package/hooks/priority-map-staleness.sh +29 -0
  57. package/hooks/proactive-resolver.sh +104 -0
  58. package/hooks/session-auto-title.sh +165 -0
  59. package/hooks/session-checkpoint.sh +97 -0
  60. package/hooks/session-cost-log.sh +77 -0
  61. package/hooks/session-log-action.sh +36 -0
  62. package/hooks/session-log-prompt.sh +25 -0
  63. package/hooks/session-restore.sh +45 -0
  64. package/hooks/session-save.sh +81 -0
  65. package/hooks/session-summarize.sh +154 -0
  66. package/hooks/validate-commit-format.sh +38 -0
  67. package/hooks/weekly-priority-map-review.sh +143 -0
  68. package/install.sh +46 -0
  69. package/package.json +67 -0
  70. package/scripts/ci/bootstrap-dry-run.sh +40 -0
  71. package/scripts/ci/check.sh +65 -0
  72. package/scripts/posthog-snapshot-loader.sh +112 -0
  73. package/skills/context-budget/SKILL.md +86 -0
  74. package/skills/memory-review/SKILL.md +100 -0
  75. package/skills/model-routing/SKILL.md +70 -0
  76. package/skills/posthog-weekly/SKILL.md +271 -0
  77. package/skills/ui-ux-pro-max/SKILL.md +377 -0
  78. package/skills/ui-ux-pro-max/data/charts.csv +26 -0
  79. package/skills/ui-ux-pro-max/data/colors.csv +97 -0
  80. package/skills/ui-ux-pro-max/data/icons.csv +101 -0
  81. package/skills/ui-ux-pro-max/data/landing.csv +31 -0
  82. package/skills/ui-ux-pro-max/data/products.csv +97 -0
  83. package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  84. package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  85. package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  86. package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  87. package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  88. package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  89. package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  90. package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  91. package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  92. package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  93. package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  94. package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  95. package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  96. package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  97. package/skills/ui-ux-pro-max/data/styles.csv +68 -0
  98. package/skills/ui-ux-pro-max/data/typography.csv +58 -0
  99. package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  100. package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  101. package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
  102. package/skills/ui-ux-pro-max/scripts/core.py +253 -0
  103. package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
  104. package/skills/ui-ux-pro-max/scripts/search.py +114 -0
  105. package/templates/AGENTS.runtime.md +17 -0
  106. package/templates/CLAUDE.md +252 -0
  107. package/templates/claude-monitor.plist +35 -0
  108. package/templates/keybindings.json +13 -0
  109. package/templates/merge-settings.jq +53 -0
  110. package/templates/review-upgrades.md +44 -0
  111. package/templates/settings.json +255 -0
  112. package/templates/statusline-command.sh +182 -0
  113. package/tests/fixtures/hooks/events.json +32 -0
  114. package/tools/session-costs-view.sh +128 -0
  115. package/tools/session-dashboard-gen.sh +369 -0
  116. package/tools/session-live.sh +173 -0
  117. package/tools/session-server.js +441 -0
@@ -0,0 +1,597 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { spawnSync } = require("node:child_process");
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+
8
+ const root = path.resolve(__dirname, "..");
9
+ const bootstrap = path.join(root, "bootstrap.sh");
10
+ const args = process.argv.slice(2);
11
+
12
+ function printHelp() {
13
+ console.log(`Usage:
14
+ axel-setup [bootstrap options]
15
+ axel-setup doctor [--target claude|codex|generic] [--home PATH] [--codex-home PATH] [--output PATH]
16
+ axel-setup diff [--target claude|codex|generic] [--home PATH] [--codex-home PATH] [--output PATH]
17
+ axel-setup review-upgrades [--target claude|codex|generic] [--home PATH] [--codex-home PATH] [--output PATH]
18
+ axel-setup metrics [--json]
19
+ axel-setup uninstall [--target claude|codex|generic] [--home PATH] [--codex-home PATH] [--output PATH] [--apply]
20
+
21
+ Bootstrap examples:
22
+ axel-setup --user-name "Your Name"
23
+ axel-setup --profile team-safe --skip-gsd --no-launchd
24
+ axel-setup --target codex --profile minimal
25
+ axel-setup --target generic --output ./axel-runtime
26
+
27
+ Doctor examples:
28
+ axel-setup doctor
29
+ axel-setup doctor --home /tmp/axel-home
30
+ axel-setup doctor --target codex --codex-home /tmp/codex-home
31
+ axel-setup doctor --target generic --output ./axel-runtime
32
+
33
+ Maintenance examples:
34
+ axel-setup diff --target codex --codex-home /tmp/codex-home
35
+ axel-setup review-upgrades --home /tmp/axel-home
36
+ axel-setup metrics
37
+ axel-setup metrics --json
38
+ axel-setup uninstall --target generic --output ./axel-runtime
39
+ axel-setup uninstall --target generic --output ./axel-runtime --apply`);
40
+ }
41
+
42
+ function parseRuntimeArgs(argv, command) {
43
+ let home = os.homedir();
44
+ let target = "claude";
45
+ let codexHome = process.env.CODEX_HOME || "";
46
+ let output = "";
47
+ let apply = false;
48
+
49
+ for (let index = 0; index < argv.length; index += 1) {
50
+ const arg = argv[index];
51
+ if (arg === "--home") {
52
+ requireValue(argv, index, arg);
53
+ home = argv[index + 1];
54
+ index += 1;
55
+ } else if (arg === "--target") {
56
+ requireValue(argv, index, arg);
57
+ target = argv[index + 1];
58
+ index += 1;
59
+ } else if (arg === "--codex-home") {
60
+ requireValue(argv, index, arg);
61
+ codexHome = argv[index + 1];
62
+ index += 1;
63
+ } else if (arg === "--output") {
64
+ requireValue(argv, index, arg);
65
+ output = argv[index + 1];
66
+ index += 1;
67
+ } else if (arg === "--apply" && command === "uninstall") {
68
+ apply = true;
69
+ } else if (arg === "-h" || arg === "--help") {
70
+ printHelp();
71
+ process.exit(0);
72
+ } else {
73
+ console.error(`Unknown ${command} option: ${arg}`);
74
+ process.exit(1);
75
+ }
76
+ }
77
+
78
+ if (!["claude", "codex", "generic"].includes(target)) {
79
+ console.error(`Unknown ${command} target: ${target}`);
80
+ process.exit(1);
81
+ }
82
+
83
+ if (target === "generic" && !output) {
84
+ console.error(`--output is required when using ${command} --target generic`);
85
+ process.exit(1);
86
+ }
87
+
88
+ return { apply, codexHome, home, output, target };
89
+ }
90
+
91
+ function requireValue(argv, index, arg) {
92
+ if (index + 1 >= argv.length || argv[index + 1].startsWith("--")) {
93
+ console.error(`${arg} requires a value`);
94
+ process.exit(1);
95
+ }
96
+ }
97
+
98
+ function readJson(filePath) {
99
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
100
+ }
101
+
102
+ function readText(filePath) {
103
+ return fs.readFileSync(filePath, "utf8");
104
+ }
105
+
106
+ function resolveRuntime(argv, command) {
107
+ const options = parseRuntimeArgs(argv, command);
108
+ const { codexHome, home, output, target } = options;
109
+ const installRoot =
110
+ target === "claude"
111
+ ? path.join(home, ".claude")
112
+ : target === "codex"
113
+ ? codexHome || path.join(home, ".codex")
114
+ : path.resolve(output);
115
+ const manifestPath = path.join(installRoot, "axel-manifest.json");
116
+
117
+ return { ...options, installRoot, manifestPath };
118
+ }
119
+
120
+ function readInstalledManifest(manifestPath, target) {
121
+ if (!fs.existsSync(manifestPath)) {
122
+ return null;
123
+ }
124
+
125
+ const manifest = readJson(manifestPath);
126
+ const manifestTarget = manifest.target || "claude";
127
+ if (manifestTarget !== target) {
128
+ console.error(`Manifest target mismatch: expected ${target}, found ${manifestTarget}`);
129
+ process.exit(1);
130
+ }
131
+
132
+ return manifest;
133
+ }
134
+
135
+ function runDoctor(argv) {
136
+ const { home, installRoot, manifestPath, target } = resolveRuntime(argv, "doctor");
137
+ let failures = 0;
138
+
139
+ console.log("AXEL Doctor");
140
+ console.log(`Target: ${target}`);
141
+ console.log(`Home: ${home}`);
142
+ console.log(`Install root: ${installRoot}`);
143
+
144
+ const manifest = readInstalledManifest(manifestPath, target);
145
+ if (!manifest) {
146
+ console.log(`MISSING ${path.relative(installRoot, manifestPath)}`);
147
+ process.exit(1);
148
+ }
149
+
150
+ const profile = manifest.profile || "personal";
151
+ const manifestTarget = manifest.target || "claude";
152
+ console.log(`Profile: ${profile}`);
153
+ console.log(`Manifest target: ${manifestTarget}`);
154
+ console.log(`PASS ${path.relative(installRoot, manifestPath)}`);
155
+
156
+ const requiredPaths = manifest.requiredPaths || [];
157
+ for (const entry of requiredPaths) {
158
+ const targets = entry.targets || ["claude"];
159
+ if (!targets.includes(target)) {
160
+ continue;
161
+ }
162
+
163
+ const profiles = entry.profiles || [];
164
+ const appliesToProfile = profiles.length === 0 || profiles.includes(profile);
165
+ const skipped = entry.skipFlag && manifest.skipped && manifest.skipped[entry.skipFlag] === true;
166
+ const optionalDisabled =
167
+ entry.optionalFlag && (!manifest.enabled || manifest.enabled[entry.optionalFlag] !== true);
168
+
169
+ if (!appliesToProfile || skipped || optionalDisabled) {
170
+ console.log(`SKIP ${entry.path}`);
171
+ continue;
172
+ }
173
+
174
+ const absolutePath = path.join(installRoot, entry.path);
175
+ if (fs.existsSync(absolutePath)) {
176
+ console.log(`PASS ${entry.path}`);
177
+ } else {
178
+ console.log(`MISSING ${entry.path}`);
179
+ failures += 1;
180
+ }
181
+ }
182
+
183
+ process.exit(failures === 0 ? 0 : 1);
184
+ }
185
+
186
+ function listFilesRecursive(startDir) {
187
+ if (!fs.existsSync(startDir)) {
188
+ return [];
189
+ }
190
+
191
+ const files = [];
192
+ const entries = fs.readdirSync(startDir, { withFileTypes: true }).sort((left, right) =>
193
+ left.name.localeCompare(right.name),
194
+ );
195
+
196
+ for (const entry of entries) {
197
+ const fullPath = path.join(startDir, entry.name);
198
+ if (entry.isDirectory()) {
199
+ if (entry.name === "__pycache__") {
200
+ continue;
201
+ }
202
+ files.push(...listFilesRecursive(fullPath));
203
+ } else if (entry.isFile() && !entry.name.endsWith(".pyc")) {
204
+ files.push(fullPath);
205
+ }
206
+ }
207
+
208
+ return files;
209
+ }
210
+
211
+ function listTopLevelScripts() {
212
+ const scriptsDir = path.join(root, "scripts");
213
+ if (!fs.existsSync(scriptsDir)) {
214
+ return [];
215
+ }
216
+
217
+ return fs
218
+ .readdirSync(scriptsDir, { withFileTypes: true })
219
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".sh"))
220
+ .map((entry) => path.join(scriptsDir, entry.name))
221
+ .sort();
222
+ }
223
+
224
+ function addTreeCandidates(candidates, sourceDir, targetDir, options = {}) {
225
+ for (const sourcePath of listFilesRecursive(path.join(root, sourceDir))) {
226
+ const relativePath = path.relative(path.join(root, sourceDir), sourcePath);
227
+ candidates.push({
228
+ ...options,
229
+ sourcePath,
230
+ targetPath: path.join(targetDir, relativePath),
231
+ });
232
+ }
233
+ }
234
+
235
+ function addScriptCandidates(candidates, targetDir, options = {}) {
236
+ for (const sourcePath of listTopLevelScripts()) {
237
+ candidates.push({
238
+ ...options,
239
+ sourcePath,
240
+ targetPath: path.join(targetDir, path.basename(sourcePath)),
241
+ });
242
+ }
243
+ }
244
+
245
+ function addFileCandidate(candidates, sourcePath, targetPath, options = {}) {
246
+ candidates.push({
247
+ ...options,
248
+ sourcePath: path.join(root, sourcePath),
249
+ targetPath,
250
+ });
251
+ }
252
+
253
+ function addPresenceCandidate(candidates, targetPath, note) {
254
+ candidates.push({
255
+ note,
256
+ sourcePath: null,
257
+ targetPath,
258
+ });
259
+ }
260
+
261
+ function isPosthogCandidate(candidate) {
262
+ return candidate.targetPath.includes("posthog-weekly") || candidate.targetPath.includes("posthog-snapshot-loader.sh");
263
+ }
264
+
265
+ function isSkippedCandidate(candidate, manifest) {
266
+ const skipped = manifest.skipped || {};
267
+ const enabled = manifest.enabled || {};
268
+
269
+ if (candidate.skipFlag && skipped[candidate.skipFlag] === true) {
270
+ return true;
271
+ }
272
+
273
+ if (isPosthogCandidate(candidate) && enabled["enable-posthog"] !== true) {
274
+ return true;
275
+ }
276
+
277
+ return false;
278
+ }
279
+
280
+ function buildCandidates(target, manifest) {
281
+ const candidates = [];
282
+
283
+ if (target === "claude") {
284
+ addTreeCandidates(candidates, "hooks", "hooks");
285
+ addTreeCandidates(candidates, "commands", "commands");
286
+ addTreeCandidates(candidates, "agents", "agents");
287
+ addTreeCandidates(candidates, "skills", "skills");
288
+ addScriptCandidates(candidates, "scripts");
289
+ addTreeCandidates(candidates, "tools", "tools", { skipFlag: "skip-monitor" });
290
+ addFileCandidate(candidates, "templates/statusline-command.sh", "statusline-command.sh");
291
+ addFileCandidate(candidates, "templates/keybindings.json", "keybindings.json", { skipFlag: "skip-keybindings" });
292
+ addPresenceCandidate(candidates, "settings.json", "merge-managed");
293
+ } else {
294
+ addFileCandidate(candidates, "templates/AGENTS.runtime.md", "AGENTS.md");
295
+ addTreeCandidates(candidates, "commands", "commands");
296
+ addTreeCandidates(candidates, "agents", "agents");
297
+ addTreeCandidates(candidates, "skills", "skills");
298
+ addScriptCandidates(candidates, "scripts");
299
+ }
300
+
301
+ addPresenceCandidate(candidates, "axel-manifest.json", "manifest");
302
+ return candidates.filter((candidate) => !isSkippedCandidate(candidate, manifest));
303
+ }
304
+
305
+ function compareCandidate(installRoot, candidate) {
306
+ const installedPath = path.join(installRoot, candidate.targetPath);
307
+
308
+ if (!fs.existsSync(installedPath)) {
309
+ return "MISSING";
310
+ }
311
+
312
+ if (!candidate.sourcePath) {
313
+ return "PRESENT";
314
+ }
315
+
316
+ const source = fs.readFileSync(candidate.sourcePath);
317
+ const installed = fs.readFileSync(installedPath);
318
+ return Buffer.compare(source, installed) === 0 ? "MATCH" : "DIFF";
319
+ }
320
+
321
+ function runDiff(argv) {
322
+ const { installRoot, manifestPath, target } = resolveRuntime(argv, "diff");
323
+ const manifest = readInstalledManifest(manifestPath, target);
324
+
325
+ if (!manifest) {
326
+ console.error(`Missing AXEL manifest at ${manifestPath}`);
327
+ process.exit(1);
328
+ }
329
+
330
+ console.log("AXEL Diff");
331
+ console.log(`Target: ${target}`);
332
+ console.log(`Install root: ${installRoot}`);
333
+
334
+ for (const candidate of buildCandidates(target, manifest)) {
335
+ const status = compareCandidate(installRoot, candidate);
336
+ const suffix = candidate.note ? ` (${candidate.note})` : "";
337
+ console.log(`${status} ${candidate.targetPath}${suffix}`);
338
+ }
339
+ }
340
+
341
+ function runReviewUpgrades(argv) {
342
+ const { installRoot, manifestPath, target } = resolveRuntime(argv, "review-upgrades");
343
+ readInstalledManifest(manifestPath, target);
344
+
345
+ const upgradesDir = path.join(installRoot, "axel-upgrades");
346
+ const reviewPath = path.join(upgradesDir, "REVIEW.md");
347
+ const upgradeManifestPath = path.join(upgradesDir, "MANIFEST.md");
348
+
349
+ console.log("AXEL Upgrade Review");
350
+ console.log(`Target: ${target}`);
351
+ console.log(`Install root: ${installRoot}`);
352
+
353
+ if (!fs.existsSync(upgradeManifestPath)) {
354
+ console.log(`No upgrade proposals found at ${upgradesDir}`);
355
+ return;
356
+ }
357
+
358
+ if (fs.existsSync(reviewPath)) {
359
+ console.log(`Instructions: ${reviewPath}`);
360
+ }
361
+ console.log(`Manifest: ${upgradeManifestPath}`);
362
+ console.log("");
363
+ console.log(fs.readFileSync(upgradeManifestPath, "utf8").trimEnd());
364
+ }
365
+
366
+ function countMatches(text, regex) {
367
+ return (text.match(regex) || []).length;
368
+ }
369
+
370
+ function extractSection(text, startHeading, endHeading) {
371
+ const start = text.indexOf(startHeading);
372
+ if (start === -1) {
373
+ return "";
374
+ }
375
+
376
+ const end = text.indexOf(endHeading, start + startHeading.length);
377
+ return end === -1 ? text.slice(start) : text.slice(start, end);
378
+ }
379
+
380
+ function parseMetricsArgs(argv) {
381
+ let json = false;
382
+
383
+ for (const arg of argv) {
384
+ if (arg === "--json") {
385
+ json = true;
386
+ } else if (arg === "-h" || arg === "--help") {
387
+ printHelp();
388
+ process.exit(0);
389
+ } else {
390
+ console.error(`Unknown metrics option: ${arg}`);
391
+ process.exit(1);
392
+ }
393
+ }
394
+
395
+ return { json };
396
+ }
397
+
398
+ function buildMetricsReport() {
399
+ const contextBudgetSkill = readText(path.join(root, "skills/context-budget/SKILL.md"));
400
+ const phaseOne = extractSection(contextBudgetSkill, "### Phase 1", "### Phase 2");
401
+ const phaseThree = extractSection(contextBudgetSkill, "### Phase 3", "### Phase 4");
402
+
403
+ const contextBudget = {
404
+ avoidedFailures: [
405
+ "context window exhaustion before handoff",
406
+ "stale or duplicated agent and skill surface",
407
+ "overloaded MCP tool context",
408
+ ],
409
+ name: "context-budget",
410
+ protectedWorkflows: ["setup overhead audit", "token headroom review"],
411
+ signals: {
412
+ inventoryChecks: countMatches(phaseOne, /^- \*\*/gm),
413
+ issueDetectors: countMatches(phaseThree, /^- /gm),
414
+ reportTemplate: contextBudgetSkill.includes("Context Budget Report") ? 1 : 0,
415
+ },
416
+ source: "skills/context-budget/SKILL.md",
417
+ };
418
+
419
+ const costLogHook = readText(path.join(root, "hooks/session-cost-log.sh"));
420
+ const contextMonitor = readText(path.join(root, "hooks/gsd-context-monitor.js"));
421
+ const csvHeader = costLogHook.match(/echo "([^"]*session_id[^"]*)" > "\$LOG_FILE"/)?.[1] || "";
422
+ const usageMonitor = {
423
+ avoidedFailures: [
424
+ "silent cost growth",
425
+ "unnoticed context pressure",
426
+ "rate-limit surprises during long sessions",
427
+ ],
428
+ name: "usage-monitor",
429
+ protectedWorkflows: ["session cost review", "live context warning", "dashboard inspection"],
430
+ signals: {
431
+ costLogFields: csvHeader ? csvHeader.split(",").length : 0,
432
+ dashboardTools: ["session-live.sh", "session-costs-view.sh", "session-dashboard-gen.sh", "session-server.js"].filter(
433
+ (fileName) => fs.existsSync(path.join(root, "tools", fileName)),
434
+ ).length,
435
+ warningThresholdRemainingPct: Number(contextMonitor.match(/WARNING_THRESHOLD = (\d+)/)?.[1] || 0),
436
+ criticalThresholdRemainingPct: Number(contextMonitor.match(/CRITICAL_THRESHOLD = (\d+)/)?.[1] || 0),
437
+ debounceToolCalls: Number(contextMonitor.match(/DEBOUNCE_CALLS = (\d+)/)?.[1] || 0),
438
+ },
439
+ source: "hooks/session-cost-log.sh + hooks/gsd-context-monitor.js + tools/session-*",
440
+ };
441
+
442
+ const hookFixtures = readJson(path.join(root, "tests/fixtures/hooks/events.json"));
443
+ const hookEvents = Object.values(hookFixtures).map((event) => event.hook_event_name).filter(Boolean);
444
+ const hookHarness = {
445
+ avoidedFailures: [
446
+ "subagents inheriting the most expensive session model",
447
+ "lost edit/action history before session persistence",
448
+ "Stop hook regressions that break next-session context",
449
+ ],
450
+ name: "hook-harness",
451
+ protectedWorkflows: ["Agent model routing", "tool action logging", "session persistence"],
452
+ signals: {
453
+ fixtures: Object.keys(hookFixtures).length,
454
+ hookPhases: new Set(hookEvents).size,
455
+ regressionAssertions: 6,
456
+ },
457
+ source: "tests/fixtures/hooks/events.json + tests/hook-harness.sh",
458
+ };
459
+
460
+ const areas = [contextBudget, usageMonitor, hookHarness].map((area) => ({
461
+ ...area,
462
+ comparable: {
463
+ avoidedFailures: area.avoidedFailures.length,
464
+ protectedWorkflows: area.protectedWorkflows.length,
465
+ signalKinds: Object.keys(area.signals).length,
466
+ },
467
+ }));
468
+
469
+ return {
470
+ areas,
471
+ generatedFrom: "package assets and checked-in fixtures",
472
+ privacy: "No private local session logs, prompts, costs, tokens, or repository paths are read.",
473
+ };
474
+ }
475
+
476
+ function runMetrics(argv) {
477
+ const { json } = parseMetricsArgs(argv);
478
+ const report = buildMetricsReport();
479
+
480
+ if (json) {
481
+ console.log(JSON.stringify(report, null, 2));
482
+ return;
483
+ }
484
+
485
+ console.log("AXEL Metrics");
486
+ console.log(`Generated from: ${report.generatedFrom}`);
487
+ console.log(`Privacy: ${report.privacy}`);
488
+
489
+ for (const area of report.areas) {
490
+ console.log("");
491
+ console.log(`${area.name}`);
492
+ console.log(` Source: ${area.source}`);
493
+ console.log(
494
+ ` Comparable: ${area.comparable.signalKinds} signal kinds, ${area.comparable.protectedWorkflows} protected workflows, ${area.comparable.avoidedFailures} avoided failures`,
495
+ );
496
+ console.log(` Signals: ${JSON.stringify(area.signals)}`);
497
+ console.log(` Protected workflows: ${area.protectedWorkflows.join("; ")}`);
498
+ console.log(` Avoided failures: ${area.avoidedFailures.join("; ")}`);
499
+ }
500
+ }
501
+
502
+ function pruneEmptyParents(startDir, stopDir) {
503
+ let current = startDir;
504
+ while (current.startsWith(stopDir) && current !== stopDir) {
505
+ try {
506
+ fs.rmdirSync(current);
507
+ } catch {
508
+ return;
509
+ }
510
+ current = path.dirname(current);
511
+ }
512
+ }
513
+
514
+ function runUninstall(argv) {
515
+ const { apply, installRoot, manifestPath, target } = resolveRuntime(argv, "uninstall");
516
+ const manifest = readInstalledManifest(manifestPath, target);
517
+
518
+ if (!manifest) {
519
+ console.error(`Missing AXEL manifest at ${manifestPath}`);
520
+ process.exit(1);
521
+ }
522
+
523
+ console.log("AXEL Uninstall");
524
+ console.log(`Target: ${target}`);
525
+ console.log(`Install root: ${installRoot}`);
526
+ console.log(`Mode: ${apply ? "apply" : "dry-run"}`);
527
+
528
+ for (const candidate of buildCandidates(target, manifest)) {
529
+ const installedPath = path.join(installRoot, candidate.targetPath);
530
+ const status = compareCandidate(installRoot, candidate);
531
+
532
+ if (candidate.note === "merge-managed") {
533
+ console.log(`KEEP ${candidate.targetPath} (${candidate.note})`);
534
+ continue;
535
+ }
536
+
537
+ if (candidate.note === "manifest") {
538
+ console.log(`${apply ? "REMOVE" : "WOULD REMOVE"} ${candidate.targetPath}`);
539
+ if (apply && fs.existsSync(installedPath)) {
540
+ fs.rmSync(installedPath);
541
+ }
542
+ continue;
543
+ }
544
+
545
+ if (status === "MATCH") {
546
+ console.log(`${apply ? "REMOVE" : "WOULD REMOVE"} ${candidate.targetPath}`);
547
+ if (apply) {
548
+ fs.rmSync(installedPath);
549
+ pruneEmptyParents(path.dirname(installedPath), installRoot);
550
+ }
551
+ } else if (status === "DIFF") {
552
+ console.log(`KEEP ${candidate.targetPath} (modified)`);
553
+ } else {
554
+ console.log(`SKIP ${candidate.targetPath} (missing)`);
555
+ }
556
+ }
557
+ }
558
+
559
+ if (args[0] === "-h" || args[0] === "--help") {
560
+ printHelp();
561
+ process.exit(0);
562
+ }
563
+
564
+ if (args[0] === "doctor") {
565
+ runDoctor(args.slice(1));
566
+ }
567
+
568
+ if (args[0] === "diff") {
569
+ runDiff(args.slice(1));
570
+ process.exit(0);
571
+ }
572
+
573
+ if (args[0] === "review-upgrades") {
574
+ runReviewUpgrades(args.slice(1));
575
+ process.exit(0);
576
+ }
577
+
578
+ if (args[0] === "metrics") {
579
+ runMetrics(args.slice(1));
580
+ process.exit(0);
581
+ }
582
+
583
+ if (args[0] === "uninstall") {
584
+ runUninstall(args.slice(1));
585
+ process.exit(0);
586
+ }
587
+
588
+ const result = spawnSync("bash", [bootstrap, ...args], {
589
+ stdio: "inherit",
590
+ });
591
+
592
+ if (result.error) {
593
+ console.error(`Failed to run AXEL bootstrap: ${result.error.message}`);
594
+ process.exit(1);
595
+ }
596
+
597
+ process.exit(result.status ?? 1);