shakers-cli-beta 0.8.0-beta.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 (218) hide show
  1. package/LICENSE +59 -0
  2. package/README.md +1175 -0
  3. package/bin/add-agent.js +88 -0
  4. package/bin/add-project.js +88 -0
  5. package/bin/add-role.js +61 -0
  6. package/bin/add-skill.js +88 -0
  7. package/bin/ai-usage.js +81 -0
  8. package/bin/alma.js +99 -0
  9. package/bin/applications.js +69 -0
  10. package/bin/availability.js +70 -0
  11. package/bin/certifications.js +69 -0
  12. package/bin/certify.js +73 -0
  13. package/bin/change-role.js +61 -0
  14. package/bin/config.js +120 -0
  15. package/bin/experiences.js +69 -0
  16. package/bin/find-projects.js +89 -0
  17. package/bin/invitations.js +69 -0
  18. package/bin/lang.js +70 -0
  19. package/bin/login.js +283 -0
  20. package/bin/mcp.js +98 -0
  21. package/bin/me.js +69 -0
  22. package/bin/onboarding.js +134 -0
  23. package/bin/portfolios.js +69 -0
  24. package/bin/rate.js +70 -0
  25. package/bin/register.js +325 -0
  26. package/bin/report-html.js +134 -0
  27. package/bin/save.js +77 -0
  28. package/bin/shakers.js +344 -0
  29. package/bin/share.js +72 -0
  30. package/bin/show-project.js +72 -0
  31. package/bin/socials.js +70 -0
  32. package/bin/superadmin.js +351 -0
  33. package/package.json +40 -0
  34. package/src/agent-category.js +13 -0
  35. package/src/agent-evaluation.js +177 -0
  36. package/src/agent-org-chart.js +355 -0
  37. package/src/agent-synthesis.js +132 -0
  38. package/src/agent-usage.js +118 -0
  39. package/src/agents-client.js +165 -0
  40. package/src/ai-fluency-cell.js +72 -0
  41. package/src/ai-profile-client.js +45 -0
  42. package/src/ai-profile-preview.js +154 -0
  43. package/src/ai.js +66 -0
  44. package/src/alma-client.js +123 -0
  45. package/src/alma-flow.js +32 -0
  46. package/src/alma-repo-context.js +47 -0
  47. package/src/alma-shell.js +161 -0
  48. package/src/ansi.js +62 -0
  49. package/src/applications-client.js +79 -0
  50. package/src/applications-flow.js +58 -0
  51. package/src/auth-client.js +144 -0
  52. package/src/auth-device-client.js +118 -0
  53. package/src/auth-method-picker.js +44 -0
  54. package/src/auth-methods.js +35 -0
  55. package/src/auth-session-store.js +214 -0
  56. package/src/authorship.js +203 -0
  57. package/src/automations-detector.js +170 -0
  58. package/src/availability-client.js +92 -0
  59. package/src/availability-flow.js +105 -0
  60. package/src/backend-request.js +170 -0
  61. package/src/blind-approval-extract.js +48 -0
  62. package/src/brand-ansi.js +14 -0
  63. package/src/brand-svg.js +9 -0
  64. package/src/browser-tools-detector.js +21 -0
  65. package/src/build-next-level.js +56 -0
  66. package/src/certifications-client.js +89 -0
  67. package/src/certifications-flow.js +75 -0
  68. package/src/certify-dimension-client.js +217 -0
  69. package/src/certify-dimension-flow.js +193 -0
  70. package/src/certify-disclaimer.js +51 -0
  71. package/src/certify-remediation-prompt.js +23 -0
  72. package/src/cli-args.js +83 -0
  73. package/src/config-dir.js +120 -0
  74. package/src/config.js +778 -0
  75. package/src/consent-flow.js +115 -0
  76. package/src/consent-skip.js +27 -0
  77. package/src/decisions-extract.js +43 -0
  78. package/src/detect-repos.js +145 -0
  79. package/src/detected-tools.js +15 -0
  80. package/src/detection-evidence.js +105 -0
  81. package/src/detection-noise.js +132 -0
  82. package/src/detectors.js +221 -0
  83. package/src/device-auth-store.js +100 -0
  84. package/src/device-login.js +118 -0
  85. package/src/email-verification.js +152 -0
  86. package/src/env-paths.js +16 -0
  87. package/src/find-projects-cache.js +89 -0
  88. package/src/find-projects-client.js +138 -0
  89. package/src/find-projects-flow.js +164 -0
  90. package/src/git-activity.js +123 -0
  91. package/src/graph-certs.js +58 -0
  92. package/src/graph-generator.js +230 -0
  93. package/src/graph-scan.js +239 -0
  94. package/src/i18n.js +3264 -0
  95. package/src/inter-fonts.js +6 -0
  96. package/src/interactive-select.js +183 -0
  97. package/src/interview-client.js +226 -0
  98. package/src/interview-transcript.js +56 -0
  99. package/src/inventory-client.js +90 -0
  100. package/src/invitations-client.js +66 -0
  101. package/src/invitations-flow.js +50 -0
  102. package/src/lang-client.js +147 -0
  103. package/src/lang-codes.js +14 -0
  104. package/src/lang-flow.js +85 -0
  105. package/src/legal-notice.js +53 -0
  106. package/src/locale.js +51 -0
  107. package/src/markdown-ansi.js +50 -0
  108. package/src/maturity.js +111 -0
  109. package/src/mcp-ai-usage-result-tool.js +109 -0
  110. package/src/mcp-ai-usage-tool.js +333 -0
  111. package/src/mcp-alma-tools.js +91 -0
  112. package/src/mcp-applications-tools.js +42 -0
  113. package/src/mcp-ask-tools.js +50 -0
  114. package/src/mcp-auth-form.js +282 -0
  115. package/src/mcp-availability-tools.js +43 -0
  116. package/src/mcp-certifications-tools.js +42 -0
  117. package/src/mcp-certify-dimension-tools.js +58 -0
  118. package/src/mcp-detector.js +223 -0
  119. package/src/mcp-device-login-tools.js +173 -0
  120. package/src/mcp-find-projects-tools.js +67 -0
  121. package/src/mcp-install.js +245 -0
  122. package/src/mcp-invitations-tools.js +42 -0
  123. package/src/mcp-lang-tools.js +39 -0
  124. package/src/mcp-onboarding-tools.js +388 -0
  125. package/src/mcp-portfolios-tools.js +46 -0
  126. package/src/mcp-profile-tools.js +46 -0
  127. package/src/mcp-rate-tools.js +42 -0
  128. package/src/mcp-register-tools.js +517 -0
  129. package/src/mcp-report-tools.js +101 -0
  130. package/src/mcp-role-tools.js +112 -0
  131. package/src/mcp-saved-positions-tools.js +55 -0
  132. package/src/mcp-server.js +170 -0
  133. package/src/mcp-show-project-tools.js +50 -0
  134. package/src/mcp-socials-tools.js +39 -0
  135. package/src/memory-structure-detector.js +108 -0
  136. package/src/model-call-record.js +198 -0
  137. package/src/onboarding-availability.js +26 -0
  138. package/src/onboarding-client.js +364 -0
  139. package/src/onboarding-flow.js +1078 -0
  140. package/src/onboarding-livekit-client.js +102 -0
  141. package/src/onboarding-status-client.js +42 -0
  142. package/src/open-file.js +32 -0
  143. package/src/osc-link.js +16 -0
  144. package/src/portfolio-client.js +164 -0
  145. package/src/portfolio-git-info.js +61 -0
  146. package/src/portfolios-client.js +85 -0
  147. package/src/portfolios-flow.js +79 -0
  148. package/src/profile-client.js +125 -0
  149. package/src/profile-flow.js +115 -0
  150. package/src/profile-switch.js +46 -0
  151. package/src/prompt-select.js +29 -0
  152. package/src/rate-client.js +82 -0
  153. package/src/rate-flow.js +137 -0
  154. package/src/render-ai-fluency.js +266 -0
  155. package/src/render-certification.js +177 -0
  156. package/src/render-html.js +140 -0
  157. package/src/render-report-text.js +27 -0
  158. package/src/render-sheet.js +261 -0
  159. package/src/render-terminal.js +109 -0
  160. package/src/render-usage-report-text.js +155 -0
  161. package/src/repo-context.js +215 -0
  162. package/src/report-gating.js +49 -0
  163. package/src/report-store.js +226 -0
  164. package/src/report-theme.js +177 -0
  165. package/src/report-view-client.js +50 -0
  166. package/src/reveal-output.js +29 -0
  167. package/src/roadmap-content.js +453 -0
  168. package/src/roadmap-personalization.js +132 -0
  169. package/src/roadmap-prompt.js +154 -0
  170. package/src/roles-client.js +114 -0
  171. package/src/roles-flow.js +192 -0
  172. package/src/sanitize-network-text.js +36 -0
  173. package/src/saved-positions-client.js +71 -0
  174. package/src/saved-positions-flow.js +50 -0
  175. package/src/scan-exclusions.js +50 -0
  176. package/src/scanner.js +612 -0
  177. package/src/scope-signals.js +148 -0
  178. package/src/secret-prompt.js +72 -0
  179. package/src/session-gate.js +30 -0
  180. package/src/session-refresh.js +51 -0
  181. package/src/session-scan.js +336 -0
  182. package/src/session-signals.js +36 -0
  183. package/src/shakers-wordmark.js +17 -0
  184. package/src/share-card.js +362 -0
  185. package/src/share-certification.js +111 -0
  186. package/src/share-consent-state.js +157 -0
  187. package/src/share-payload.js +300 -0
  188. package/src/share.js +184 -0
  189. package/src/show-project-client.js +58 -0
  190. package/src/show-project-flow.js +119 -0
  191. package/src/signup-client.js +91 -0
  192. package/src/skills-client.js +102 -0
  193. package/src/socials-client.js +91 -0
  194. package/src/socials-flow.js +83 -0
  195. package/src/specificity-extract.js +41 -0
  196. package/src/start-add-agents.js +306 -0
  197. package/src/start-add-portfolio.js +388 -0
  198. package/src/start-add-skills.js +260 -0
  199. package/src/stdin-ask.js +129 -0
  200. package/src/steering-extract.js +43 -0
  201. package/src/strip-internal-comments.js +45 -0
  202. package/src/tech-detector.js +295 -0
  203. package/src/templates/report-sheet.html +572 -0
  204. package/src/terminal-agents.js +199 -0
  205. package/src/terminal-format.js +85 -0
  206. package/src/terminal-progress.js +106 -0
  207. package/src/terminal-sections.js +219 -0
  208. package/src/tier-analysis.js +126 -0
  209. package/src/tier-engine.js +114 -0
  210. package/src/tier-projection.js +117 -0
  211. package/src/transcript-turns.js +49 -0
  212. package/src/translations-client.js +84 -0
  213. package/src/typewriter.js +154 -0
  214. package/src/usage-model-calls.js +119 -0
  215. package/src/usage-oneshot-commands.js +180 -0
  216. package/src/usage-repo-scope.js +162 -0
  217. package/src/usage-run-steps.js +338 -0
  218. package/src/work-streams.js +74 -0
@@ -0,0 +1,260 @@
1
+ 'use strict';
2
+
3
+ const { getUsageDiscoveredInventoryEndpoint, getSkillsDeclareEndpoint, getPortfoliosListEndpoint, getPortfolioUpdateEndpoint } = require('./config');
4
+ const { requestDeclareSkill } = require('./skills-client');
5
+ const { requestDiscoveredInventory } = require('./inventory-client');
6
+ const { requestListPortfolios, requestUpdatePortfolioSkills } = require('./portfolio-client');
7
+ const { confirmDisclaimerAcceptance } = require('./certify-disclaimer');
8
+ const { runInteractiveMultiSelect } = require('./interactive-select');
9
+ const { loadAuthSession, sessionStatus } = require('./auth-session-store');
10
+ const { styleSuccess } = require('./ansi');
11
+
12
+ // `start`'s "Añadir skills" route (talents-ai-score, ADR-054/055).
13
+
14
+ // Maps a technical failure reason to the localized `start.errorReason` copy.
15
+ function reasonCopy(reason, c) {
16
+ if (typeof reason === 'string' && reason.startsWith('http-')) {
17
+ return c.errorReasonHttp(reason.slice('http-'.length));
18
+ }
19
+ return c.errorReason[reason] || c.errorReason.generic;
20
+ }
21
+
22
+ function parseMultiIndices(raw, count) {
23
+ const trimmed = String(raw == null ? '' : raw).trim();
24
+ if (!trimmed) return null;
25
+ const parts = trimmed.split(/[\s,]+/).filter(Boolean);
26
+ const idx = new Set();
27
+ for (const part of parts) {
28
+ if (!/^\d+$/.test(part)) return null;
29
+ const n = Number(part);
30
+ if (n < 1 || n > count) return null;
31
+ idx.add(n - 1);
32
+ }
33
+ return idx.size > 0 ? [...idx].sort((a, b) => a - b) : null;
34
+ }
35
+
36
+ // Numbered fallback prompt for the multi-select, mirroring
37
+ // bin/certify.js#askNumberedChoice's shape but for a LIST of indices.
38
+ async function askMultiIndices(ask, promptText, count) {
39
+ const raw = await ask(promptText);
40
+ return parseMultiIndices(raw, count);
41
+ }
42
+
43
+ async function pickSkills({ ask, stdinIsTTY, candidates, c, input, output }) {
44
+ if (stdinIsTTY && typeof ask.suspend === 'function') {
45
+ ask.suspend();
46
+ const picked = await runInteractiveMultiSelect({
47
+ items: candidates,
48
+ labelFor: (s) => `${s.skillName}${s.technology ? ` (${s.technology})` : ''}`,
49
+ header: c.addSkillsSelectHeading,
50
+ hint: c.addSkillsSelectHint,
51
+ ...(input ? { input } : {}),
52
+ ...(output ? { output } : {}),
53
+ });
54
+ ask.resume();
55
+ return picked || [];
56
+ }
57
+ process.stdout.write(`\n ${c.addSkillsSelectHeading}\n`);
58
+ candidates.forEach((s, i) => {
59
+ process.stdout.write(` ${i + 1}) ${s.skillName}${s.technology ? ` (${s.technology})` : ''}\n`);
60
+ });
61
+ const idxs = await askMultiIndices(ask, ` ${c.addSkillsSelectPrompt(candidates.length)}`, candidates.length);
62
+ return idxs ? idxs.map((i) => candidates[i]) : [];
63
+ }
64
+
65
+ // Declares every chosen skill against the CURRENT `hubAccessToken`.
66
+ async function declareAll(chosen, { accessToken, hubAccessToken }, endpoint, c) {
67
+ const pending = [];
68
+ const declared = [];
69
+ let hubSessionExpired = false;
70
+ let succeeded = 0;
71
+ for (const skill of chosen) {
72
+ if (hubSessionExpired) {
73
+ pending.push(skill);
74
+ continue;
75
+ }
76
+ const r = await requestDeclareSkill({ skillId: skill.skillId, accessToken, hubAccessToken }, { endpoint });
77
+ if (r.ok) {
78
+ process.stdout.write(` ${styleSuccess(c.addSkillsDeclaredOne(skill.skillName))}\n`);
79
+ succeeded += 1;
80
+ declared.push(skill);
81
+ } else if (r.reason === 'hub-session-expired') {
82
+ hubSessionExpired = true;
83
+ pending.push(skill);
84
+ } else {
85
+ process.stdout.write(` ${c.addSkillsDeclareFailed(skill.skillName, reasonCopy(r.reason, c))}\n`);
86
+ }
87
+ }
88
+ return { pending, hubSessionExpired, succeeded, declared };
89
+ }
90
+
91
+ async function pickPortfolios({ ask, stdinIsTTY, portfolios, heading, c, input, output }) {
92
+ if (stdinIsTTY && typeof ask.suspend === 'function') {
93
+ ask.suspend();
94
+ const picked = await runInteractiveMultiSelect({
95
+ items: portfolios,
96
+ labelFor: (p) => p.name,
97
+ header: heading,
98
+ hint: c.addSkillsRelateSelectHint,
99
+ ...(input ? { input } : {}),
100
+ ...(output ? { output } : {}),
101
+ });
102
+ ask.resume();
103
+ return picked || [];
104
+ }
105
+ process.stdout.write(`\n ${heading}\n`);
106
+ portfolios.forEach((p, i) => {
107
+ process.stdout.write(` ${i + 1}) ${p.name}\n`);
108
+ });
109
+ const idxs = await askMultiIndices(ask, ` ${c.addSkillsRelateSelectPrompt(portfolios.length)}`, portfolios.length);
110
+ return idxs ? idxs.map((i) => portfolios[i]) : [];
111
+ }
112
+
113
+ async function relateSkillToPortfolios({ ask, stdinIsTTY, skill, portfolios, hubAccessToken, updateEndpoint, c, input, output }) {
114
+ const wantsRelate = /^(y|yes|s|si|sí)$/i.test(String(await ask(`\n ${c.addSkillsRelateAsk(skill.skillName)} `)).trim());
115
+ if (!wantsRelate) {
116
+ process.stdout.write(` ${c.addSkillsRelateSkipped(skill.skillName)}\n`);
117
+ return;
118
+ }
119
+ const relateHeading = c.addSkillsRelateSelectHeading(skill.skillName);
120
+ const chosenPortfolios = await pickPortfolios({ ask, stdinIsTTY, portfolios, heading: relateHeading, c, input, output });
121
+ if (!chosenPortfolios || chosenPortfolios.length === 0) {
122
+ process.stdout.write(` ${c.addSkillsRelateSkipped(skill.skillName)}\n`);
123
+ return;
124
+ }
125
+ for (const portfolio of chosenPortfolios) {
126
+ const skillIds = Array.from(new Set([...(portfolio.skillIds || []), skill.skillId]));
127
+ const r = await requestUpdatePortfolioSkills({ portfolioId: portfolio.id, skillIds, hubAccessToken }, { endpoint: updateEndpoint });
128
+ if (r.ok) {
129
+ portfolio.skillIds = skillIds;
130
+ process.stdout.write(`${styleSuccess(c.addSkillsRelated(skill.skillName, portfolio.name))}\n`);
131
+ } else {
132
+ process.stdout.write(`${c.addSkillsRelateFailed(skill.skillName, portfolio.name, reasonCopy(r.reason, c))}\n`);
133
+ }
134
+ }
135
+ }
136
+
137
+ async function offerRelateToPortfolios({ ask, stdinIsTTY, declared, session, c, input, output }) {
138
+ if (!declared || declared.length === 0) return;
139
+ const listEndpoint = getPortfoliosListEndpoint();
140
+ const updateEndpoint = getPortfolioUpdateEndpoint();
141
+ if (!listEndpoint || !updateEndpoint) return;
142
+
143
+ const listed = await requestListPortfolios(
144
+ { accessToken: session.accessToken, hubAccessToken: session.hubAccessToken },
145
+ { endpoint: listEndpoint },
146
+ );
147
+ if (!listed.ok || listed.portfolios.length === 0) {
148
+ process.stdout.write(`\n ${c.addSkillsRelateNoPortfolios}\n`);
149
+ return;
150
+ }
151
+
152
+ for (const skill of declared) {
153
+ await relateSkillToPortfolios({
154
+ ask, stdinIsTTY, skill, portfolios: listed.portfolios, hubAccessToken: session.hubAccessToken, updateEndpoint, c, input, output,
155
+ });
156
+ }
157
+ }
158
+
159
+ // The whole route.
160
+ async function runAddSkills({
161
+ ask, catalog, root, session, stdinIsTTY, input, output, preAccepted = false, deps = {},
162
+ } = {}) {
163
+ const c = catalog.start;
164
+ const runLogin = deps.runLogin;
165
+
166
+ const inventoryEndpoint = getUsageDiscoveredInventoryEndpoint();
167
+ const declareEndpoint = getSkillsDeclareEndpoint();
168
+ if (!inventoryEndpoint || !declareEndpoint) {
169
+ process.stderr.write(`\n ${c.addSkillsErrorNoEndpoint}\n\n`);
170
+ return;
171
+ }
172
+
173
+ const acceptance = await confirmDisclaimerAcceptance({ ask, catalog, stdinIsTTY, preAccepted });
174
+ if (!acceptance.accepted) return; // confirmDisclaimerAcceptance already printed why
175
+
176
+ const inventory = await requestDiscoveredInventory(
177
+ { accessToken: session.accessToken },
178
+ { endpoint: inventoryEndpoint },
179
+ );
180
+ if (!inventory.ok) {
181
+ process.stderr.write(`\n ${c.addSkillsResolveErrorIntro} ${reasonCopy(inventory.reason, c)}\n\n`);
182
+ return;
183
+ }
184
+
185
+ const candidates = inventory.skills.map((skill) => ({
186
+ skillId: skill.skillId,
187
+ skillName: skill.skillName,
188
+ technology: skill.technologies.join(', '),
189
+ }));
190
+ if (candidates.length === 0) {
191
+ process.stdout.write(`\n ${c.addSkillsNoneMatched}\n\n`);
192
+ return;
193
+ }
194
+
195
+ const hubAccessToken = session.hubAccessToken;
196
+ if (!hubAccessToken) {
197
+ process.stderr.write(`\n ${c.addSkillsNoHubToken}\n\n`);
198
+ return;
199
+ }
200
+
201
+ const chosen = await pickSkills({ ask, stdinIsTTY, candidates, c, input, output });
202
+ if (!chosen || chosen.length === 0) {
203
+ process.stdout.write(`\n ${c.addSkillsNoneChosen}\n\n`);
204
+ return;
205
+ }
206
+
207
+ process.stdout.write('\n');
208
+ const { pending, hubSessionExpired, succeeded, declared } = await declareAll(
209
+ chosen, { accessToken: session.accessToken, hubAccessToken }, declareEndpoint, c,
210
+ );
211
+ if (!hubSessionExpired) {
212
+ printProfileBoost(succeeded, c);
213
+ await offerRelateToPortfolios({ ask, stdinIsTTY, declared, session, c, input, output });
214
+ process.stdout.write('\n');
215
+ return;
216
+ }
217
+
218
+ process.stdout.write(`\n ${c.addSkillsHubSessionExpired}\n`);
219
+ const relogged = await offerRelogin({ ask, c, runLogin });
220
+ if (!relogged) return;
221
+ process.stdout.write(` ${c.addSkillsRelaunchAfterRelogin}\n\n`);
222
+ const retried = await declareAll(pending, { accessToken: relogged.accessToken, hubAccessToken: relogged.hubAccessToken }, declareEndpoint, c);
223
+ printProfileBoost(succeeded + retried.succeeded, c);
224
+ await offerRelateToPortfolios({
225
+ ask, stdinIsTTY, declared: [...declared, ...retried.declared], session: relogged, c, input, output,
226
+ });
227
+ process.stdout.write('\n');
228
+ }
229
+
230
+ function printProfileBoost(succeeded, c) {
231
+ if (succeeded > 0) process.stdout.write(` ${c.addSkillsProfileBoost}\n`);
232
+ }
233
+
234
+ // Asks the Talent whether to re-login, right there, and runs it if they say yes.
235
+ async function offerRelogin({ ask, c, runLogin }) {
236
+ if (typeof runLogin !== 'function') return null;
237
+ const raw = String(await ask(`\n ${c.addSkillsOfferRelogin} `)).trim();
238
+ if (!/^(y|yes|s|si|sí)$/i.test(raw)) {
239
+ process.stdout.write(`\n ${c.addSkillsRelaunchDeclined}\n\n`);
240
+ return null;
241
+ }
242
+ await runLogin([], { ask });
243
+ const fresh = loadAuthSession();
244
+ if (sessionStatus(fresh) !== 'active' || !fresh.hubAccessToken) {
245
+ process.stdout.write(`\n ${c.addSkillsReloginFailed}\n\n`);
246
+ return null;
247
+ }
248
+ return fresh;
249
+ }
250
+
251
+ module.exports = {
252
+ runAddSkills,
253
+ parseMultiIndices,
254
+ reasonCopy,
255
+ declareAll,
256
+ printProfileBoost,
257
+ offerRelateToPortfolios,
258
+ // Exported for src/start-add-agents.js's own relate step (ADR-041): the multi-select over the Talent's portfolios, reused verbatim.
259
+ pickPortfolios,
260
+ };
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+
3
+ const readline = require('readline');
4
+ const { readSecretRaw, canPromptSecretly } = require('./secret-prompt');
5
+
6
+ // `ask(question) => Promise<string>` factory for real stdin, used by bin/report.js to drive src/consent-flow.js's interactive disclosure.
7
+
8
+ function createLineQueueAsk(writeQuestion) {
9
+ const queue = [];
10
+ let resolveNext = null;
11
+ let ended = false;
12
+
13
+ function pushLine(line) {
14
+ if (resolveNext) {
15
+ const resolve = resolveNext;
16
+ resolveNext = null;
17
+ resolve(line);
18
+ } else {
19
+ queue.push(line);
20
+ }
21
+ }
22
+
23
+ // Signals the stream has ended (EOF) with nothing further to give.
24
+ function markEnded() {
25
+ ended = true;
26
+ if (resolveNext) {
27
+ const resolve = resolveNext;
28
+ resolveNext = null;
29
+ resolve('');
30
+ }
31
+ }
32
+
33
+ // DELIBERATE EXCEPTION to this file's own rule ("answers arriving BEFORE ask() is called are still delivered in order" — see the test file's own name for that behaviour).
34
+ function drain() {
35
+ queue.length = 0;
36
+ }
37
+
38
+ function ask(question) {
39
+ writeQuestion(question);
40
+ return new Promise((resolve) => {
41
+ if (queue.length > 0) resolve(queue.shift());
42
+ else if (ended) resolve('');
43
+ else resolveNext = resolve;
44
+ });
45
+ }
46
+
47
+ return { ask, pushLine, markEnded, drain };
48
+ }
49
+
50
+ // `onInterrupt` (optional): a Ctrl-C (SIGINT) handler for a TTY.
51
+ function createStdinAsk({ onInterrupt } = {}) {
52
+ // `rl` is re-createable (issue 102), not a `const`.
53
+ let rl = null;
54
+ let ended = false;
55
+ let suspending = false;
56
+ let currentPrompt = '';
57
+
58
+ const { ask, pushLine, markEnded, drain } = createLineQueueAsk((question) => {
59
+ // Long-answer wrap bug (skill-code-certification, certify agents): in a TTY, `readline` runs in terminal mode and redraws the current line on every keystroke (wrap, cursor).
60
+ const text = ` ${question} `;
61
+ const lastNewline = text.lastIndexOf('\n');
62
+ if (lastNewline >= 0) {
63
+ process.stdout.write(text.slice(0, lastNewline + 1));
64
+ currentPrompt = text.slice(lastNewline + 1);
65
+ } else {
66
+ currentPrompt = text;
67
+ }
68
+ if (rl) { rl.setPrompt(currentPrompt); rl.prompt(); }
69
+ });
70
+ function attach() {
71
+ if (rl) return;
72
+ rl = readline.createInterface({ input: process.stdin, output: process.stdout });
73
+ // The issue-088 fix, re-applied on every attach: readline must own the prompt
74
+ // it repaints, and a new interface does not know the old one.
75
+ if (currentPrompt) rl.setPrompt(currentPrompt);
76
+ rl.on('line', pushLine);
77
+ rl.on('close', () => {
78
+ rl = null;
79
+ // A `suspend()` close is intentional (the secret prompt); a real EOF is not.
80
+ if (!suspending) { ended = true; markEnded(); }
81
+ });
82
+ rl.on('SIGINT', onInterrupt || (() => {
83
+ process.stdout.write('\n');
84
+ if (rl) rl.close();
85
+ process.exit(130);
86
+ }));
87
+ }
88
+
89
+ attach();
90
+
91
+ // Releases stdin so the raw-mode secret reader can own it, WITHOUT ending the
92
+ // FIFO queue: buffered lines survive. Mirrors `repl-stdin.js#suspend`.
93
+ function suspend() {
94
+ if (!rl) return;
95
+ suspending = true;
96
+ const r = rl;
97
+ rl = null;
98
+ r.close();
99
+ suspending = false;
100
+ }
101
+ // SECRETS DO NOT ECHO (issue 102).
102
+ ask.secret = async (question) => {
103
+ if (!canPromptSecretly(process.stdin)) return ask(question);
104
+ process.stdout.write(` ${question} `);
105
+ suspend();
106
+ try {
107
+ const value = await readSecretRaw({ input: process.stdin, output: process.stdout });
108
+ return value == null ? '' : value;
109
+ } finally {
110
+ // In a `finally` because a terminal left without echo is worse than the bug
111
+ // this replaces: re-attaching restores readline's cooked mode and its prompt.
112
+ attach();
113
+ }
114
+ };
115
+
116
+ ask.close = () => {
117
+ ended = true;
118
+ if (rl) { const r = rl; rl = null; r.close(); }
119
+ markEnded();
120
+ };
121
+ ask.suspend = suspend;
122
+ ask.resume = attach;
123
+ ask.isEnded = () => ended;
124
+ // See createLineQueueAsk's `drain` docblock: discards whatever is queued and unread, without ending the stream.
125
+ ask.drain = drain;
126
+ return ask;
127
+ }
128
+
129
+ module.exports = { createStdinAsk, createLineQueueAsk };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ const { eachHumanText } = require('./transcript-turns');
4
+ const { scrubSecrets } = require('./agent-synthesis');
5
+
6
+ const MAX_EXCERPTS = 20;
7
+ const MAX_EXCERPT_LEN = 200;
8
+
9
+ const STEERING_RE =
10
+ /\b(no|nope|not that|don'?t|do not|instead|en vez de|en lugar de|mejor|better|change|cambia|revert|revierte|vuelve|deshaz|undo|stop|para|wrong|incorrect|incorrecto|fix|arregla|corrige|use|usa|make it|hazlo|add|añade|remove|quita|ensure|asegúrate|should|deberías|must|debes)\b/i;
11
+
12
+ function makeSteeringAccumulator() {
13
+ let steeringTraceCount = 0;
14
+ const excerpts = [];
15
+ const seen = new Set();
16
+ return {
17
+ add(text) {
18
+ if (!STEERING_RE.test(text)) return;
19
+ steeringTraceCount += 1;
20
+ if (excerpts.length >= MAX_EXCERPTS) return;
21
+ const scrubbed = scrubSecrets(text)
22
+ .replace(/\s+/g, ' ')
23
+ .trim()
24
+ .slice(0, MAX_EXCERPT_LEN);
25
+ if (scrubbed && !seen.has(scrubbed)) {
26
+ seen.add(scrubbed);
27
+ excerpts.push(scrubbed);
28
+ }
29
+ },
30
+ result() {
31
+ if (steeringTraceCount === 0) return null;
32
+ return { steeringTraceCount, redactedSteeringExcerpts: excerpts };
33
+ },
34
+ };
35
+ }
36
+
37
+ function collectSteering(env = process.env, selectedCwds = null) {
38
+ const acc = makeSteeringAccumulator();
39
+ for (const text of eachHumanText(env, { selectedCwds })) acc.add(text);
40
+ return acc.result();
41
+ }
42
+
43
+ module.exports = { collectSteering, makeSteeringAccumulator };
@@ -0,0 +1,45 @@
1
+ 'use strict';
2
+
3
+ // Our comments do not travel in the talent's shareable report (issue 098).
4
+
5
+ // `<!-- … -->`, including multi-line.
6
+ const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
7
+
8
+ // `/* … */`, the CSS and JS block form. Same verification as above.
9
+ const BLOCK_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
10
+
11
+ // A whole line that is nothing but a `//` comment.
12
+ const LINE_COMMENT_RE = /^[ \t]*\/\/[^\n]*$/gm;
13
+
14
+ // Three or more consecutive newlines (what a removed comment block leaves
15
+ // behind) collapse to two. Never touches indentation or single blank lines.
16
+ const BLANK_RUN_RE = /\n{3,}/g;
17
+
18
+ function stripTemplateComments(html) {
19
+ if (typeof html !== 'string' || !html) return '';
20
+ return html
21
+ .replace(HTML_COMMENT_RE, '')
22
+ .replace(BLOCK_COMMENT_RE, '')
23
+ .replace(LINE_COMMENT_RE, '')
24
+ .replace(BLANK_RUN_RE, '\n\n');
25
+ }
26
+
27
+ // The patterns that make a comment an INTERNAL reference — exported so the test and this module agree on one definition instead of two.
28
+ const INTERNAL_REFERENCE_PATTERNS = Object.freeze([
29
+ { label: 'issue reference', re: /\bissues?\s*#?\d{2,4}\b/i },
30
+ { label: 'commit hash', re: /(?<![#\w])(?=[0-9a-f]{7,40}(?![\w]))(?=[0-9a-f]*[a-f])(?=[0-9a-f]*\d)[0-9a-f]{7,40}(?![\w])/ },
31
+ { label: 'src/ file path', re: /\bsrc\/[a-z0-9-]+\.(?:js|html|css)\b/i },
32
+ { label: 'ADR reference', re: /\bADR-\d{2,3}\b/ },
33
+ ]);
34
+
35
+ function findInternalReferences(text) {
36
+ const hay = typeof text === 'string' ? text : '';
37
+ const found = [];
38
+ for (const { label, re } of INTERNAL_REFERENCE_PATTERNS) {
39
+ const m = hay.match(new RegExp(re.source, re.flags.includes('g') ? re.flags : `${re.flags}g`));
40
+ if (m && m.length) found.push({ label, matches: [...new Set(m)] });
41
+ }
42
+ return found;
43
+ }
44
+
45
+ module.exports = { stripTemplateComments, findInternalReferences, INTERNAL_REFERENCE_PATTERNS };