shakers-cli 0.15.0 → 0.17.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 (132) hide show
  1. package/README.md +56 -1203
  2. package/bin/ai-usage.js +32 -58
  3. package/bin/login.js +10 -10
  4. package/bin/mcp.js +17 -12
  5. package/bin/mcpb.js +1 -1
  6. package/bin/onboarding.js +3 -2
  7. package/bin/register.js +19 -5
  8. package/bin/report-html.js +2 -2
  9. package/bin/shakers-cli.js +352 -0
  10. package/bin/shakers.js +20 -344
  11. package/bin/share.js +1 -1
  12. package/bin/superadmin.js +5 -5
  13. package/bin/version.js +3 -0
  14. package/package.json +12 -3
  15. package/src/account-marker.js +40 -0
  16. package/src/account-trace.js +23 -0
  17. package/src/agent-category.js +1 -1
  18. package/src/agent-evaluation.js +3 -3
  19. package/src/agent-org-chart.js +5 -5
  20. package/src/agent-synthesis.js +3 -3
  21. package/src/agent-usage.js +1 -1
  22. package/src/agents-client.js +4 -4
  23. package/src/ai-usage-review.js +101 -0
  24. package/src/ai.js +1 -1
  25. package/src/ansi.js +1 -1
  26. package/src/auth-client.js +18 -4
  27. package/src/auth-method-picker.js +1 -1
  28. package/src/auth-methods.js +1 -1
  29. package/src/auth-session-store.js +18 -8
  30. package/src/authorship-mismatch.js +1 -1
  31. package/src/authorship.js +2 -2
  32. package/src/automations-detector.js +1 -1
  33. package/src/backend-request.js +5 -3
  34. package/src/brand-ansi.js +1 -1
  35. package/src/browser-tools-detector.js +1 -1
  36. package/src/certify-disclaimer.js +1 -1
  37. package/src/certify-remediation-prompt.js +1 -1
  38. package/src/cli-args.js +2 -2
  39. package/src/command-audit.js +8 -5
  40. package/src/config-dir.js +1 -1
  41. package/src/config.js +114 -48
  42. package/src/consent-flow.js +7 -6
  43. package/src/consent-skip.js +1 -1
  44. package/src/detection-evidence.js +2 -2
  45. package/src/detection-noise.js +1 -1
  46. package/src/detectors.js +1 -1
  47. package/src/device-login.js +12 -1
  48. package/src/email-verification.js +3 -3
  49. package/src/env-paths.js +1 -1
  50. package/src/find-projects-client.js +12 -0
  51. package/src/find-projects-flow.js +1 -13
  52. package/src/git-activity.js +1 -1
  53. package/src/graph-certs.js +3 -3
  54. package/src/graph-scan.js +9 -9
  55. package/src/i18n.js +148 -138
  56. package/src/interactive-select.js +1 -1
  57. package/src/interview-client.js +6 -1
  58. package/src/interview-transcript.js +1 -1
  59. package/src/legal-notice.js +1 -1
  60. package/src/maturity.js +6 -6
  61. package/src/mcp-ai-usage-result-tool.js +34 -12
  62. package/src/mcp-ai-usage-tool.js +282 -163
  63. package/src/mcp-auth-form.js +7 -3
  64. package/src/mcp-detector.js +3 -3
  65. package/src/mcp-device-login-tools.js +2 -1
  66. package/src/mcp-find-projects-tools.js +1 -2
  67. package/src/mcp-install.js +279 -44
  68. package/src/mcp-instructions.js +1 -1
  69. package/src/mcp-onboarding-tools.js +4 -4
  70. package/src/mcp-prompts.js +4 -4
  71. package/src/mcp-register-tools.js +1 -0
  72. package/src/mcp-role-tools.js +24 -1
  73. package/src/mcp-server.js +5 -1
  74. package/src/mcp-signup-client.js +2 -2
  75. package/src/mcp-signup-tools.js +39 -9
  76. package/src/memory-structure-detector.js +1 -1
  77. package/src/model-call-record.js +2 -2
  78. package/src/onboarding-flow.js +63 -42
  79. package/src/onboarding-funnel.js +153 -20
  80. package/src/open-file.js +1 -1
  81. package/src/osc-link.js +1 -1
  82. package/src/portfolio-client.js +11 -1
  83. package/src/portfolio-git-info.js +1 -1
  84. package/src/profile-flow.js +1 -1
  85. package/src/profile-switch.js +5 -12
  86. package/src/render-certification.js +10 -10
  87. package/src/render-html.js +6 -6
  88. package/src/render-report-text.js +1 -1
  89. package/src/render-sheet.js +10 -10
  90. package/src/render-terminal.js +6 -6
  91. package/src/repo-scope.js +132 -0
  92. package/src/report-gating.js +5 -3
  93. package/src/report-store.js +13 -8
  94. package/src/roadmap-content.js +5 -5
  95. package/src/roadmap-personalization.js +3 -3
  96. package/src/roadmap-prompt.js +2 -2
  97. package/src/roles-flow.js +8 -5
  98. package/src/scan-exclusions.js +1 -1
  99. package/src/scanner.js +14 -14
  100. package/src/secret-prompt.js +1 -1
  101. package/src/session-gate.js +1 -1
  102. package/src/session-refresh.js +33 -3
  103. package/src/session-scan.js +1 -1
  104. package/src/shakers-wordmark.js +1 -1
  105. package/src/share-card.js +2 -2
  106. package/src/share-certification.js +4 -4
  107. package/src/share-consent-state.js +9 -9
  108. package/src/share-payload.js +7 -7
  109. package/src/share.js +7 -7
  110. package/src/signup-client.js +1 -1
  111. package/src/signup-copy.js +177 -2
  112. package/src/skills-client.js +1 -1
  113. package/src/start-add-agents.js +2 -2
  114. package/src/start-add-portfolio.js +3 -13
  115. package/src/start-add-skills.js +2 -2
  116. package/src/stdin-ask.js +16 -4
  117. package/src/strip-internal-comments.js +1 -1
  118. package/src/tech-detector.js +2 -2
  119. package/src/templates/report-sheet.html +11 -13
  120. package/src/terminal-agents.js +8 -8
  121. package/src/terminal-format.js +3 -3
  122. package/src/terminal-sections.js +11 -11
  123. package/src/tier-engine.js +3 -3
  124. package/src/tier-projection.js +2 -2
  125. package/src/transcript-turns.js +1 -1
  126. package/src/typewriter.js +1 -1
  127. package/src/usage-model-calls.js +6 -6
  128. package/src/usage-oneshot-commands.js +5 -5
  129. package/src/usage-repo-scope.js +11 -129
  130. package/src/usage-run-steps.js +82 -181
  131. package/src/usage-scan.js +235 -0
  132. package/src/work-streams.js +1 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // The agents + activity blocks of the terminal report, extracted from src/render-terminal.js (structure refactor, issue 020).
3
+ // The agents + activity blocks of the terminal report, extracted from src/render-terminal.js.
4
4
 
5
5
  const { label } = require('./i18n');
6
6
  const { buildAgentCardTree } = require('./render-html');
@@ -11,7 +11,7 @@ const { DEFAULT_REPORT_GATE } = require('./report-gating');
11
11
  const { c, wrap, summarize } = require('./terminal-format');
12
12
  const { detectedToolNames } = require('./detected-tools');
13
13
 
14
- // agents (ADR-016: one line per agent) As SIMPLE as possible: ONE line per agent.
14
+ // agents: As SIMPLE as possible: ONE line per agent.
15
15
 
16
16
  // AI product (from the source, e.g. Claude Code) shown in place of the LLM model.
17
17
  function agentProductLabel(card, t) {
@@ -24,7 +24,7 @@ function agentLine(card, depth, t) {
24
24
  const indent = ' '.repeat(depth);
25
25
  // Root = a filled bullet; every deeper level = that many stacked down-arrows.
26
26
  const marker = depth === 0 ? `${c.success}●${c.reset}` : `${c.primary}${'↓'.repeat(depth)}${c.reset}`;
27
- // card.symbolicName is agent-synthesis (LLM, ADR-010) content; card.name is
27
+ // card.symbolicName is agent-synthesis (LLM) content; card.name is
28
28
  // the local, deterministic agent id — only the former needs stripping.
29
29
  const title = card.symbolicName
30
30
  ? `${sanitizeRenderText(card.symbolicName)} ${c.muted}(${card.name})${c.reset}`
@@ -67,7 +67,7 @@ function agentClassLine(card, depth, t) {
67
67
  const catLabel = (category && cc.categories[category]) || sanitizeRenderText(category);
68
68
  const levelLabel = (level && cc.levels[level]) || sanitizeRenderText(level);
69
69
  const bits = [];
70
- // The FLOOR category is dimmed, not accented (issue 120).
70
+ // The FLOOR category is dimmed, not accented.
71
71
  const floor = isFloorCategory(category);
72
72
  if (catLabel) {
73
73
  bits.push(floor ? `${c.dim}${catLabel}${c.reset}` : `${c.primary}${catLabel}${c.reset}`);
@@ -127,7 +127,7 @@ function printActivity(report, t, p) {
127
127
  ` ${c.success}●${c.reset} ${a.workStreams(w.streamCount, w.multiDayStreams, w.maxStreamSpanDays)}`,
128
128
  );
129
129
  }
130
- // Traction (ADR-039): recency-windowed signals from the local scan; agentsOnProfile
130
+ // Traction: recency-windowed signals from the local scan; agentsOnProfile
131
131
  // is hub-only, shown only when the ai-profile preview has already been fetched.
132
132
  const tr = tractionParts(report, s, a);
133
133
  if (tr.length) p(` ${c.success}●${c.reset} ${a.tractionLabel}: ${tr.join(' · ')}`);
@@ -157,7 +157,7 @@ function printAgents(report, t, p, gate = DEFAULT_REPORT_GATE) {
157
157
  p();
158
158
  return;
159
159
  }
160
- // THE ROOT THE AGENTS HANG FROM (issue 086 — restored, not invented).
160
+ // THE ROOT THE AGENTS HANG FROM (restored, not invented).
161
161
  p(` ${c.muted}${label(t.html.orchestratorLabel, 'Orchestrator')}${c.reset}`);
162
162
  const visited = new Set();
163
163
  const walk = (card, depth) => {
@@ -187,11 +187,11 @@ function printAgents(report, t, p, gate = DEFAULT_REPORT_GATE) {
187
187
  const names = [...omittedNames].map(sanitizeRenderText).join(', ');
188
188
  for (const line of wrap(t.classification.agentsEvalPartial(names, omittedNames.size))) p(` ${c.dim}${line}${c.reset}`);
189
189
  }
190
- // THE CASE THAT SAID NOTHING AT ALL (issue 116).
190
+ // THE CASE THAT SAID NOTHING AT ALL.
191
191
  if (callFailed(report, MODEL_CALL.EVALUATION)) {
192
192
  const detected = Array.isArray(report.agents) ? report.agents.length : 0;
193
193
  const cls = t.classification;
194
- // FOUR outcomes, because they ask the talent to do different things — and ADR-042 is what made three of them distinguishable at all.
194
+ // FOUR outcomes, because they ask the talent to do different things.
195
195
  const record = modelCall(report, MODEL_CALL.EVALUATION);
196
196
  const reason = record && record.reason;
197
197
  let notice;
@@ -21,7 +21,7 @@ function bar(score, width = 24) {
21
21
  return '█'.repeat(filled) + '░'.repeat(width - filled);
22
22
  }
23
23
 
24
- // ADR-016: a light visual break between the reordered sections — a dim rule
24
+ // A light visual break between the reordered sections — a dim rule
25
25
  // with air around it, keeping the branded ANSI look and zero deps.
26
26
  function sep(p) {
27
27
  p();
@@ -29,7 +29,7 @@ function sep(p) {
29
29
  p();
30
30
  }
31
31
 
32
- // WRAPPING, added for issue 087.
32
+ // WRAPPING.
33
33
  const WRAP_COLS = 76;
34
34
 
35
35
  function wrap(text, width = WRAP_COLS) {
@@ -71,7 +71,7 @@ function labelledBlock(p, label, value, indent = ' ') {
71
71
  for (const line of wrap(value, WRAP_COLS - indent.length)) p(`${indent}${c.muted}${line}${c.reset}`);
72
72
  }
73
73
 
74
- // Terminal-SUMMARIZE (user feedback, 2026-07-16): the earlier condense (commit 465badb) over-trimmed — it left the terminal as headings + copyable prompts with the prose stripped out.
74
+ // Terminal-SUMMARIZE: the earlier condense over-trimmed — it left the terminal as headings + copyable prompts with the prose stripped out.
75
75
  function summarize(text, max = 140) {
76
76
  const s = String(text || '').trim().replace(/\s+/g, ' ');
77
77
  if (s.length <= max) return s;
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // The MCP-services, tier-analysis, progression-ladder and tier-roadmap sections of the terminal report, extracted from src/render-terminal.js (structure refactor, issue 020).
3
+ // The MCP-services, tier-analysis, progression-ladder and tier-roadmap sections of the terminal report, extracted from src/render-terminal.js.
4
4
 
5
5
  const { getRoadmapEntry } = require('./roadmap-content');
6
6
  const { analyzeTier, buildLadder } = require('./tier-analysis');
@@ -27,7 +27,7 @@ function printMcpServices(report, t, p) {
27
27
  p(` ${c.primary}${shown.join(`${c.reset}${c.muted} · ${c.reset}${c.primary}`)}${c.reset}`);
28
28
  }
29
29
  // Stated, never omitted: the list has to account for every server the tier
30
- // counted, or the screen shows two different numbers for one set (issue 096).
30
+ // counted, or the screen shows two different numbers for one set.
31
31
  if (unidentified > 0) {
32
32
  for (const line of wrap(t.html.mcpUnidentified(unidentified))) p(` ${c.dim}${line}${c.reset}`);
33
33
  }
@@ -82,7 +82,7 @@ function printLadder(report, t, p) {
82
82
  p(` ${c.muted}${ld.setupIntro}${c.reset}`);
83
83
  p(` ${c.dim}${ld.legend(done, current, pending)}${c.reset}`);
84
84
  p();
85
- // NESTED (ADR-016): each SETUP LEVEL, then its tiers indented beneath.
85
+ // NESTED: each SETUP LEVEL, then its tiers indented beneath.
86
86
  for (const lvl of setupLevels) {
87
87
  const nameStyle = lvl.status === 'pending' ? c.muted : `${c.bold}${c.white}`;
88
88
  const keys = lvl.tierKeys.length ? ` ${c.muted}— [${lvl.tierKeys.join(', ')}]${c.reset}` : '';
@@ -138,8 +138,8 @@ function printRoadmap(report, maturity, t, lang, p) {
138
138
  }
139
139
 
140
140
  p(` ${c.bold}${c.warning}${t.html.roadmapHeading}${c.reset}`);
141
- // Issue 083: the register, stated before the content.
142
- // Wrapped like every other prose line in this block (issue 087): it was the one line still running past the 80th column, which is the shape the squad read as a dump.
141
+ // The register, stated before the content.
142
+ // Wrapped like every other prose line in this block: it was the one line still running past the 80th column, which is the shape the squad read as a dump.
143
143
  for (const line of wrap(t.html.roadmapSubheading)) p(` ${c.dim}${line}${c.reset}`);
144
144
 
145
145
  if (curatedEntry.contentUnavailable) {
@@ -152,15 +152,15 @@ function printRoadmap(report, maturity, t, lang, p) {
152
152
  const personalization = report && report.roadmapPersonalization;
153
153
  const entry = mergeRoadmapPersonalization(curatedEntry, personalization);
154
154
 
155
- // Issue 087: the jump title gets air and its own line, and everything below it hangs off it.
156
- // THE BEFORE AND THE AFTER (issue 084), above the steps and not below them.
155
+ // The jump title gets air and its own line, and everything below it hangs off it.
156
+ // THE BEFORE AND THE AFTER, above the steps and not below them.
157
157
  printProjection(report, t, p);
158
158
 
159
159
  p();
160
160
  p(` ${c.white}${c.bold}${sanitizeRenderText(entry.title)}${c.reset}`);
161
161
  p();
162
162
 
163
- // Issue 109: whether this roadmap is YOURS or the template, said out loud.
163
+ // Whether this roadmap is YOURS or the template, said out loud.
164
164
  if (personalization) {
165
165
  p(` ${c.dim}${t.html.roadmapPersonalizedNotice}${c.reset}`);
166
166
  p();
@@ -170,7 +170,7 @@ function printRoadmap(report, maturity, t, lang, p) {
170
170
  }
171
171
 
172
172
  if (entry.maxTier) {
173
- // ADR-008 (skill-code-certification): T7 is NOT a dead end.
173
+ // T7 is NOT a dead end.
174
174
  if (entry.whatRemains) {
175
175
  for (const line of wrap(sanitizeRenderText(entry.whatRemains))) p(` ${c.muted}${line}${c.reset}`);
176
176
  p();
@@ -198,7 +198,7 @@ function printRoadmap(report, maturity, t, lang, p) {
198
198
  {
199
199
  const promptText = buildImplementationPrompt(entry, report, maturity, lang);
200
200
  if (promptText) {
201
- // Issue 087: THE wall of text.
201
+ // THE wall of text.
202
202
  sep(p);
203
203
  p(` ${c.bold}${t.html.implementationPromptHeading}${c.reset}`);
204
204
  for (const line of wrap(t.html.implementationPromptHint)) p(` ${c.dim}${line}${c.reset}`);
@@ -210,7 +210,7 @@ function printRoadmap(report, maturity, t, lang, p) {
210
210
  }
211
211
  }
212
212
 
213
- // --build-next-level (issue 021) is now a SECONDARY, opt-in alternative to the prompt above — never when already at the terminal T7 entry (there is nothing left to build).
213
+ // --build-next-level is now a SECONDARY, opt-in alternative to the prompt above — never when already at the terminal T7 entry (there is nothing left to build).
214
214
  if (!entry.maxTier) p(` ${c.dim}${t.cli.buildNextLevelHint}${c.reset}`);
215
215
 
216
216
  p();
@@ -20,7 +20,7 @@ const AGENTIC_IDS = ['claude-code', 'aider', 'gemini-cli', 'codex-cli', 'amazon-
20
20
  // Band 0-4 derived from tier (level-model.md, single source of truth): index = tier -> band.
21
21
  const BAND_BY_TIER = [0, 1, 2, 3, 3, 4, 4, 4];
22
22
 
23
- // Setup Level (Talent Certification Framework, ADR-016): the 3-value rollup that REPLACES the retired 0-4 band on every display surface.
23
+ // Setup Level (Talent Certification Framework): the 3-value rollup that REPLACES the retired 0-4 band on every display surface.
24
24
  const SETUP_LEVELS = [
25
25
  { key: 'none', code: null, rank: 0, emoji: '○' },
26
26
  { key: 'S1', code: 'S1', rank: 1, emoji: '◔' },
@@ -60,7 +60,7 @@ function aggregateTierSignals(report) {
60
60
  ? report.agentCounts.agents
61
61
  : 0;
62
62
 
63
- // THE MCP SIGNAL IS THE DEDUPED SET THE REPORT SHOWS (issue 112).
63
+ // THE MCP SIGNAL IS THE DEDUPED SET THE REPORT SHOWS.
64
64
  const mcpTotal =
65
65
  report && report.mcp && typeof report.mcp.total === 'number' ? report.mcp.total : mcp;
66
66
 
@@ -93,7 +93,7 @@ function computeTierResult(report) {
93
93
  tierKey: meta.key,
94
94
  tierName: meta.name,
95
95
  band: bandForTier(tier),
96
- // Setup Level (ADR-016) — the shown rollup. `{key, code, rank, emoji}`;
96
+ // Setup Level — the shown rollup. `{key, code, rank, emoji}`;
97
97
  // the label is resolved from i18n by `key` at the render layer.
98
98
  setupLevel: setupLevelForTier(tier),
99
99
  signals,
@@ -3,7 +3,7 @@
3
3
  const { classify } = require('./maturity');
4
4
  const { computeTierResult, aggregateTierSignals, computeTier } = require('./tier-engine');
5
5
 
6
- // Where this roadmap leaves you, computed with the REAL engine (issue 084).
6
+ // Where this roadmap leaves you, computed with the REAL engine.
7
7
 
8
8
  // One entry per TARGET tier: the smallest report-shape changes that satisfy that tier's criterion.
9
9
  const AGENTIC_TOOL_ID = 'claude-code';
@@ -42,7 +42,7 @@ const bumpDepth = (field) => (r) => {
42
42
  tool.depth[field] = (tool.depth[field] || 0) + 1;
43
43
  };
44
44
 
45
- // "Configure one MCP server" needs its own delta, and the reason is a collision worth recording (issue 112 landing after issue 084).
45
+ // "Configure one MCP server" needs its own delta, and the reason is a collision worth recording.
46
46
  const addMcpServer = (r) => {
47
47
  const tool = ensureDetectedTool(r);
48
48
  tool.depth.mcpServers = (tool.depth.mcpServers || 0) + 1;
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { iterateSessionObjects } = require('./session-scan');
4
4
 
5
- // Human-turn reader for the steering/decisions extractors (hybrid evaluation, Slice 2).
5
+ // Human-turn reader for the steering/decisions extractors.
6
6
 
7
7
  function isHumanTurn(obj) {
8
8
  if (!obj || typeof obj !== 'object') return false;
package/src/typewriter.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { isInteractive } = require('./terminal-progress');
4
4
 
5
- // Progressive reveal of text that is ALREADY IN HAND (talents-ai-score, camino A).
5
+ // Progressive reveal of text that is ALREADY IN HAND.
6
6
 
7
7
  // 60fps. Fine enough that the reveal reads as continuous, coarse enough that a
8
8
  // 700ms budget is ~43 writes rather than one per character.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // The three ephemeral model-call orchestrators of the `ai-usage` flow, extracted from bin/ai-usage.js (structure refactor, issue 020).
3
+ // The three ephemeral model-call orchestrators of the `ai-usage` flow, extracted from bin/ai-usage.js.
4
4
 
5
5
  const { parseAgentDescriptions, parseAgentDefinitionsForRoots } = require('./agent-org-chart');
6
6
  const {
@@ -21,7 +21,7 @@ const { hasTraceContentConsent } = require('./share');
21
21
  const { getRoadmapEntry } = require('./roadmap-content');
22
22
  const { computeTierResult } = require('./tier-engine');
23
23
 
24
- // Agent-synthesis call (talents-ai-score, ADR-010/ADR-011, "ephemeral" — not persisted; ADR-051 amends WHEN it runs, not that).
24
+ // Agent-synthesis call ("ephemeral" — not persisted).
25
25
  async function maybeSynthesizeAgents(report, root) {
26
26
  const KEY = MODEL_CALL.SYNTHESIS;
27
27
  if (!Array.isArray(report.agents) || report.agents.length === 0) {
@@ -51,7 +51,7 @@ async function maybeSynthesizeAgents(report, root) {
51
51
  }
52
52
  }
53
53
 
54
- // Ephemeral agent-evaluation call (ADR-016): scores each agent's DEFINITION quality server-side (gemini-2.5-flash — see src/agent-evaluation.js).
54
+ // Ephemeral agent-evaluation call: scores each agent's DEFINITION quality server-side (gemini-2.5-flash — see src/agent-evaluation.js).
55
55
  async function maybeEvaluateAgents(report, roots, lang) {
56
56
  const KEY = MODEL_CALL.EVALUATION;
57
57
  if (!Array.isArray(report.agents) || report.agents.length === 0) {
@@ -66,7 +66,7 @@ async function maybeEvaluateAgents(report, roots, lang) {
66
66
 
67
67
  try {
68
68
  const definitions = parseAgentDefinitionsForRoots(roots);
69
- // ADR-026: pass the report language so the rationale + the one-line description come back translated (a Spanish-authored definition still yields report-language prose).
69
+ // Pass the report language so the rationale + the one-line description come back translated (a Spanish-authored definition still yields report-language prose).
70
70
  const requestBody = buildAgentEvaluationRequest(report.agents, definitions, lang, hasTraceContentConsent());
71
71
  return await requestAgentEvaluation(requestBody, {
72
72
  endpoint,
@@ -103,8 +103,8 @@ async function maybePersonalizeRoadmap(report, maturity, lang) {
103
103
 
104
104
  try {
105
105
  const tierResult = computeTierResult(report);
106
- // ADR-026: pass the report language so the personalized prose is localized.
107
- // ADR-028: fresh consent read, see maybeSynthesizeAgents above.
106
+ // Pass the report language so the personalized prose is localized.
107
+ // Fresh consent read, see maybeSynthesizeAgents above.
108
108
  const requestBody = buildRoadmapPersonalizationRequest(entry, tierResult, report, lang, hasTraceContentConsent());
109
109
  return await requestRoadmapPersonalization(requestBody, {
110
110
  endpoint,
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // One-shot command handlers extracted from bin/ai-usage.js (structure refactor, issue 020).
3
+ // One-shot command handlers extracted from bin/ai-usage.js.
4
4
 
5
5
  const {
6
6
  getConsentStatus,
@@ -17,7 +17,7 @@ const {
17
17
  } = require('./config');
18
18
  const { buildNextLevelStarter } = require('./build-next-level');
19
19
 
20
- // One-shot consent management commands (issue 007). Mirror the retired
20
+ // One-shot consent management commands. Mirror the retired
21
21
  // `--enroll` pattern: they act immediately and do NOT scan.
22
22
 
23
23
  function doConsentStatus(catalog) {
@@ -41,7 +41,7 @@ function doConsentRevoke(catalog) {
41
41
  process.stdout.write(`\n ${catalog.consent.revoked}\n\n`);
42
42
  }
43
43
 
44
- // skill-code-certification / ADR-003: clears the decision back to "no decision yet" so the consent question is asked again next run — distinct from revoke (which persists `denied`).
44
+ // Clears the decision back to "no decision yet" so the consent question is asked again next run — distinct from revoke (which persists `denied`).
45
45
  function doConsentReset(catalog) {
46
46
  resetConsent();
47
47
  process.stdout.write(`\n ${catalog.consent.reset}\n\n`);
@@ -102,14 +102,14 @@ function doShowEndpoint(catalog) {
102
102
  process.stdout.write(`\n ${e.showNone}\n\n`);
103
103
  }
104
104
 
105
- // ADR-042: there is no second hop any more.
105
+ // There is no second hop any more.
106
106
  if (hasRetiredFallbackConfig()) {
107
107
  process.stdout.write(` ${e.showFallbackRetired}\n\n`);
108
108
  }
109
109
 
110
110
  }
111
111
 
112
- // "Construir el siguiente nivel ahora" (issue 021): optional, explicit phase — never runs unless the talent asks for it via --build-next-level.
112
+ // "Construir el siguiente nivel ahora": optional, explicit phase — never runs unless the talent asks for it via --build-next-level.
113
113
  function doBuildNextLevel(root, maturity, force, catalog) {
114
114
  const b = catalog.buildNextLevel;
115
115
  const result = buildNextLevelStarter(root || process.cwd(), maturity.tierKey, { force });
@@ -1,69 +1,12 @@
1
1
  'use strict';
2
2
 
3
- // Repo-scoping for the `ai-usage` command, extracted from bin/ai-usage.js (structure refactor, issue 020).
3
+ // Repo-scoping for the `ai-usage` command, extracted from bin/ai-usage.js.
4
4
 
5
- const { isRefusedWalkRoot } = require('./scan-exclusions');
6
- const {
7
- detectRepos,
8
- repoIdentityForCwd,
9
- matchReposByFlag,
10
- scopeFromSelection,
11
- indexOfCurrentRepo,
12
- } = require('./detect-repos');
5
+ const core = require('./repo-scope');
13
6
  const { promptSelect } = require('./prompt-select');
14
7
  const { createStdinAsk } = require('./stdin-ask');
15
8
 
16
- const MAX_MACHINE_WIDE_REPOS = 5;
17
- const SCOPE_MACHINE = 'machine';
18
- const SCOPE_REPO = 'repo';
19
-
20
- function currentRepoToplevel(cwd, repoIdentityFn = repoIdentityForCwd) {
21
- try {
22
- return repoIdentityFn(cwd).toplevel || cwd;
23
- } catch {
24
- return cwd;
25
- }
26
- }
27
-
28
- // The real git toplevel of `cwd`, or null when `cwd` is not a repo (or is a refused system root like `/`, the MCP `cwd=/` case).
29
- function cwdRepoToplevel(cwd, repoIdentityFn = repoIdentityForCwd) {
30
- if (isRefusedWalkRoot(cwd)) return null;
31
- try {
32
- return repoIdentityFn(cwd).toplevel || null;
33
- } catch {
34
- return null;
35
- }
36
- }
37
-
38
- // Pure: builds the machine-wide root set.
39
- function machineWideToplevels(cwdRepoTop, detectedRepos, cap = MAX_MACHINE_WIDE_REPOS) {
40
- const ordered = [];
41
- const seen = new Set();
42
- const push = (t) => {
43
- if (typeof t !== 'string' || !t) return;
44
- if (isRefusedWalkRoot(t)) return;
45
- if (seen.has(t)) return;
46
- seen.add(t);
47
- ordered.push(t);
48
- };
49
- push(cwdRepoTop);
50
- for (const r of detectedRepos || []) push(r && r.toplevel);
51
- const capped = ordered.slice(0, cap);
52
- return { toplevels: capped, total: ordered.length, capped: ordered.length > capped.length };
53
- }
54
-
55
- function cwdOnlyScope(repos, cwdToplevel) {
56
- const idx = indexOfCurrentRepo(repos, cwdToplevel);
57
- if (idx === -1) {
58
- return { selectedCwds: new Set(), toplevels: [cwdToplevel], mode: 'cwd' };
59
- }
60
- const { selectedCwds, selectedToplevels } = scopeFromSelection(repos, [idx]);
61
- return {
62
- selectedCwds,
63
- toplevels: selectedToplevels.length ? selectedToplevels : [cwdToplevel],
64
- mode: 'cwd',
65
- };
66
- }
9
+ const { SCOPE_MACHINE, SCOPE_REPO, machineWideToplevels } = core;
67
10
 
68
11
  async function promptScope({ catalog, stdinIsTTY = true, mkAsk = createStdinAsk } = {}) {
69
12
  const c = (catalog && catalog.cli) || {};
@@ -88,75 +31,14 @@ async function promptScope({ catalog, stdinIsTTY = true, mkAsk = createStdinAsk
88
31
  }
89
32
  }
90
33
 
91
- // Resolve which repos feed the AI-usage evaluation.
92
- async function resolveRepoScope({
93
- opts,
94
- cwd,
95
- injectedAsk,
96
- catalog,
97
- detectReposFn = detectRepos,
98
- repoIdentityFn = repoIdentityForCwd,
99
- chooseScope = promptScope,
100
- isTTY = !!process.stdin.isTTY && !!process.stdout.isTTY,
101
- }) {
102
- const cwdToplevel = currentRepoToplevel(cwd, repoIdentityFn);
103
- const cwdRepoTop = cwdRepoToplevel(cwd, repoIdentityFn);
104
- const note = (s) => {
105
- if (!opts.json) process.stdout.write(`\n ${s}\n`);
106
- };
107
-
108
- const detect = () => {
109
- try {
110
- return detectReposFn(process.env);
111
- } catch {
112
- return { repos: [], unassignedSessionCount: 0 };
113
- }
114
- };
115
-
116
- // 1. Explicit repo list always wins.
117
- if (opts.repos) {
118
- const { repos, unassignedSessionCount } = detect();
119
- note(catalog.cli.reposDetectedNote(repos.length, unassignedSessionCount));
120
- const { indices, unmatched } = matchReposByFlag(repos, opts.repos);
121
- if (unmatched.length > 0) {
122
- process.stderr.write(` ${catalog.cli.reposFlagUnmatched(unmatched.join(', '))}\n`);
123
- }
124
- if (indices.length > 0) {
125
- const { selectedCwds, selectedToplevels } = scopeFromSelection(repos, indices);
126
- return { selectedCwds, toplevels: [...new Set(selectedToplevels)], mode: 'flag' };
127
- }
128
- return cwdOnlyScope(repos, cwdToplevel);
129
- }
130
-
131
- const wantsMachine = opts.allRepos || opts.machine || opts.scope === SCOPE_MACHINE;
132
- const wantsRepo = opts.repo || opts.scope === SCOPE_REPO;
133
-
134
- let choice = null;
135
- if (wantsMachine) choice = SCOPE_MACHINE;
136
- else if (wantsRepo) choice = SCOPE_REPO;
137
- else if (isTTY && !injectedAsk && !opts.json) {
138
- choice = await chooseScope({ catalog, stdinIsTTY: true });
139
- }
140
- if (choice === null) choice = SCOPE_REPO;
141
-
142
- if (choice === SCOPE_MACHINE) {
143
- const { repos, unassignedSessionCount } = detect();
144
- note(catalog.cli.reposDetectedNote(repos.length, unassignedSessionCount));
145
- const mw = machineWideToplevels(cwdRepoTop, repos);
146
- if (mw.capped) {
147
- process.stderr.write(`\n ${catalog.cli.reposScopeCapped(mw.toplevels.length, mw.total)}\n`);
148
- }
149
- note(catalog.cli.reposScopeAll);
150
- return {
151
- selectedCwds: null,
152
- toplevels: mw.toplevels.length ? mw.toplevels : [cwdToplevel],
153
- mode: 'all',
154
- };
155
- }
156
-
157
- // Current repo.
158
- const { repos } = detect();
159
- return cwdOnlyScope(repos, cwdToplevel);
34
+ function resolveRepoScope(args) {
35
+ return core.resolveRepoScope({
36
+ ...args,
37
+ chooseScope: args.chooseScope || promptScope,
38
+ note: (s) => {
39
+ if (!args.opts.json) process.stdout.write(`\n ${s}\n`);
40
+ },
41
+ });
160
42
  }
161
43
 
162
44
  module.exports = { resolveRepoScope, machineWideToplevels, promptScope };