shakers-cli 0.0.0-stage → 0.14.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.
- package/LICENSE +59 -0
- package/README.md +1233 -2
- package/bin/add-agent.js +88 -0
- package/bin/add-project.js +88 -0
- package/bin/add-role.js +61 -0
- package/bin/add-skill.js +88 -0
- package/bin/ai-usage.js +99 -0
- package/bin/alma.js +99 -0
- package/bin/applications.js +69 -0
- package/bin/availability.js +70 -0
- package/bin/certifications.js +69 -0
- package/bin/certify.js +73 -0
- package/bin/change-role.js +61 -0
- package/bin/config.js +124 -0
- package/bin/experiences.js +69 -0
- package/bin/find-projects.js +89 -0
- package/bin/invitations.js +69 -0
- package/bin/lang.js +70 -0
- package/bin/login.js +216 -0
- package/bin/mcp.js +124 -0
- package/bin/mcpb.js +5 -0
- package/bin/me.js +69 -0
- package/bin/onboarding.js +134 -0
- package/bin/portfolios.js +69 -0
- package/bin/rate.js +70 -0
- package/bin/register.js +337 -0
- package/bin/report-html.js +134 -0
- package/bin/save.js +77 -0
- package/bin/shakers.js +363 -0
- package/bin/share.js +72 -0
- package/bin/show-project.js +72 -0
- package/bin/socials.js +70 -0
- package/bin/superadmin.js +351 -0
- package/bin/update-check-worker.js +70 -0
- package/bin/update.js +111 -0
- package/package.json +40 -4
- package/src/agent-category.js +13 -0
- package/src/agent-evaluation.js +177 -0
- package/src/agent-org-chart.js +355 -0
- package/src/agent-synthesis.js +132 -0
- package/src/agent-usage.js +118 -0
- package/src/agents-client.js +165 -0
- package/src/ai-fluency-cell.js +72 -0
- package/src/ai-profile-client.js +45 -0
- package/src/ai-profile-preview.js +154 -0
- package/src/ai.js +66 -0
- package/src/alma-client.js +123 -0
- package/src/alma-flow.js +32 -0
- package/src/alma-repo-context.js +47 -0
- package/src/alma-shell.js +161 -0
- package/src/ansi.js +62 -0
- package/src/applications-client.js +79 -0
- package/src/applications-flow.js +58 -0
- package/src/auth-client.js +144 -0
- package/src/auth-device-client.js +118 -0
- package/src/auth-method-picker.js +44 -0
- package/src/auth-methods.js +35 -0
- package/src/auth-session-store.js +215 -0
- package/src/authorship-mismatch.js +62 -0
- package/src/authorship.js +204 -0
- package/src/automations-detector.js +170 -0
- package/src/availability-client.js +93 -0
- package/src/availability-flow.js +124 -0
- package/src/backend-request.js +191 -0
- package/src/blind-approval-extract.js +48 -0
- package/src/brand-ansi.js +14 -0
- package/src/brand-svg.js +9 -0
- package/src/browser-tools-detector.js +21 -0
- package/src/build-next-level.js +56 -0
- package/src/certifications-client.js +89 -0
- package/src/certifications-flow.js +75 -0
- package/src/certify-dimension-client.js +163 -0
- package/src/certify-dimension-flow.js +135 -0
- package/src/certify-disclaimer.js +51 -0
- package/src/certify-remediation-prompt.js +23 -0
- package/src/cli-args.js +83 -0
- package/src/command-audit.js +105 -0
- package/src/config-dir.js +120 -0
- package/src/config.js +877 -0
- package/src/consent-flow.js +115 -0
- package/src/consent-skip.js +27 -0
- package/src/cv-file.js +62 -0
- package/src/cv-text.js +92 -0
- package/src/decisions-extract.js +43 -0
- package/src/detect-repos.js +145 -0
- package/src/detected-tools.js +15 -0
- package/src/detection-evidence.js +105 -0
- package/src/detection-noise.js +132 -0
- package/src/detectors.js +221 -0
- package/src/device-auth-store.js +100 -0
- package/src/device-login.js +120 -0
- package/src/email-verification.js +152 -0
- package/src/env-paths.js +30 -0
- package/src/find-projects-cache.js +89 -0
- package/src/find-projects-client.js +148 -0
- package/src/find-projects-flow.js +164 -0
- package/src/git-activity.js +124 -0
- package/src/git-log-prefetch.js +53 -0
- package/src/graph-certs.js +58 -0
- package/src/graph-generator.js +230 -0
- package/src/graph-scan.js +239 -0
- package/src/i18n.js +3341 -0
- package/src/inter-fonts.js +6 -0
- package/src/interactive-select.js +183 -0
- package/src/interview-client.js +226 -0
- package/src/interview-transcript.js +56 -0
- package/src/inventory-client.js +90 -0
- package/src/invitations-client.js +66 -0
- package/src/invitations-flow.js +50 -0
- package/src/lang-client.js +147 -0
- package/src/lang-codes.js +14 -0
- package/src/lang-flow.js +85 -0
- package/src/legal-notice.js +53 -0
- package/src/locale.js +72 -0
- package/src/markdown-ansi.js +50 -0
- package/src/maturity.js +111 -0
- package/src/mcp-ai-usage-result-tool.js +111 -0
- package/src/mcp-ai-usage-tool.js +401 -0
- package/src/mcp-alma-tools.js +95 -0
- package/src/mcp-applications-tools.js +44 -0
- package/src/mcp-auth-form.js +480 -0
- package/src/mcp-availability-tools.js +45 -0
- package/src/mcp-certifications-tools.js +44 -0
- package/src/mcp-certify-dimension-tools.js +66 -0
- package/src/mcp-choice.js +95 -0
- package/src/mcp-detector.js +223 -0
- package/src/mcp-device-login-tools.js +177 -0
- package/src/mcp-find-projects-tools.js +69 -0
- package/src/mcp-install.js +275 -0
- package/src/mcp-instructions.js +27 -0
- package/src/mcp-invitations-tools.js +44 -0
- package/src/mcp-lang-tools.js +41 -0
- package/src/mcp-onboarding-tools.js +379 -0
- package/src/mcp-portfolios-tools.js +50 -0
- package/src/mcp-profile-tools.js +48 -0
- package/src/mcp-prompts.js +73 -0
- package/src/mcp-rate-tools.js +44 -0
- package/src/mcp-register-tools.js +441 -0
- package/src/mcp-report-tools.js +105 -0
- package/src/mcp-role-tools.js +372 -0
- package/src/mcp-saved-positions-tools.js +59 -0
- package/src/mcp-server.js +290 -0
- package/src/mcp-show-project-tools.js +52 -0
- package/src/mcp-signup-client.js +118 -0
- package/src/mcp-signup-tools.js +1171 -0
- package/src/mcp-socials-tools.js +41 -0
- package/src/memory-structure-detector.js +108 -0
- package/src/model-call-record.js +198 -0
- package/src/onboarding-availability.js +26 -0
- package/src/onboarding-client.js +374 -0
- package/src/onboarding-flow.js +1162 -0
- package/src/onboarding-funnel.js +272 -0
- package/src/onboarding-livekit-client.js +102 -0
- package/src/onboarding-status-client.js +57 -0
- package/src/open-file.js +32 -0
- package/src/osc-link.js +16 -0
- package/src/portfolio-client.js +164 -0
- package/src/portfolio-git-info.js +61 -0
- package/src/portfolios-client.js +85 -0
- package/src/portfolios-flow.js +79 -0
- package/src/profile-client.js +133 -0
- package/src/profile-flow.js +115 -0
- package/src/profile-switch.js +46 -0
- package/src/prompt-select.js +66 -0
- package/src/rate-client.js +81 -0
- package/src/rate-flow.js +137 -0
- package/src/register-context.js +145 -0
- package/src/render-ai-fluency.js +266 -0
- package/src/render-certification.js +177 -0
- package/src/render-html.js +140 -0
- package/src/render-report-text.js +27 -0
- package/src/render-sheet.js +261 -0
- package/src/render-terminal.js +109 -0
- package/src/render-usage-report-text.js +155 -0
- package/src/repo-context.js +215 -0
- package/src/report-gating.js +49 -0
- package/src/report-store.js +226 -0
- package/src/report-theme.js +177 -0
- package/src/report-view-client.js +50 -0
- package/src/reveal-output.js +29 -0
- package/src/roadmap-content.js +453 -0
- package/src/roadmap-personalization.js +132 -0
- package/src/roadmap-prompt.js +154 -0
- package/src/roles-client.js +112 -0
- package/src/roles-flow.js +192 -0
- package/src/sanitize-network-text.js +36 -0
- package/src/saved-positions-client.js +71 -0
- package/src/saved-positions-flow.js +50 -0
- package/src/scan-exclusions.js +50 -0
- package/src/scanner.js +624 -0
- package/src/scope-signals.js +148 -0
- package/src/secret-prompt.js +72 -0
- package/src/session-gate.js +30 -0
- package/src/session-normalizers.js +174 -0
- package/src/session-refresh.js +51 -0
- package/src/session-scan.js +475 -0
- package/src/session-signals.js +36 -0
- package/src/shakers-wordmark.js +17 -0
- package/src/share-card.js +362 -0
- package/src/share-certification.js +111 -0
- package/src/share-consent-state.js +157 -0
- package/src/share-payload.js +300 -0
- package/src/share.js +185 -0
- package/src/show-project-client.js +58 -0
- package/src/show-project-flow.js +119 -0
- package/src/signup-client.js +103 -0
- package/src/signup-copy.js +450 -0
- package/src/signup-language.js +21 -0
- package/src/skills-client.js +102 -0
- package/src/socials-client.js +91 -0
- package/src/socials-flow.js +83 -0
- package/src/specificity-extract.js +41 -0
- package/src/start-add-agents.js +306 -0
- package/src/start-add-portfolio.js +388 -0
- package/src/start-add-skills.js +260 -0
- package/src/stdin-ask.js +129 -0
- package/src/steering-extract.js +43 -0
- package/src/strip-internal-comments.js +45 -0
- package/src/tech-detector.js +295 -0
- package/src/templates/report-sheet.html +572 -0
- package/src/terminal-agents.js +207 -0
- package/src/terminal-format.js +85 -0
- package/src/terminal-progress.js +106 -0
- package/src/terminal-sections.js +219 -0
- package/src/tier-analysis.js +126 -0
- package/src/tier-engine.js +114 -0
- package/src/tier-projection.js +117 -0
- package/src/transcript-turns.js +49 -0
- package/src/translations-client.js +84 -0
- package/src/typewriter.js +154 -0
- package/src/update-flow.js +223 -0
- package/src/update-notifier.js +113 -0
- package/src/usage-model-calls.js +119 -0
- package/src/usage-oneshot-commands.js +180 -0
- package/src/usage-repo-scope.js +162 -0
- package/src/usage-run-steps.js +354 -0
- package/src/work-streams.js +79 -0
package/src/stdin-ask.js
ADDED
|
@@ -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 };
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
EXCLUDED_DIRS,
|
|
8
|
+
MAX_WALK_DEPTH,
|
|
9
|
+
isRefusedWalkRoot,
|
|
10
|
+
isExcludedWalkPath,
|
|
11
|
+
} = require('./scan-exclusions');
|
|
12
|
+
|
|
13
|
+
// Monorepo support (skill-code-certification, issue 008): manifests are discovered RECURSIVELY under `root`, not just at the top level.
|
|
14
|
+
|
|
15
|
+
// Exact dependency-name -> canonical framework/library name (npm + pip).
|
|
16
|
+
const EXACT_FRAMEWORK_MAP = {
|
|
17
|
+
// JS/TS — frontend
|
|
18
|
+
react: 'React',
|
|
19
|
+
'react-dom': 'React',
|
|
20
|
+
next: 'Next.js',
|
|
21
|
+
vue: 'Vue',
|
|
22
|
+
nuxt: 'Nuxt',
|
|
23
|
+
'@angular/core': 'Angular',
|
|
24
|
+
svelte: 'Svelte',
|
|
25
|
+
'@sveltejs/kit': 'SvelteKit',
|
|
26
|
+
'solid-js': 'SolidJS',
|
|
27
|
+
// JS/TS — backend
|
|
28
|
+
express: 'Express',
|
|
29
|
+
fastify: 'Fastify',
|
|
30
|
+
koa: 'Koa',
|
|
31
|
+
'@nestjs/core': 'NestJS',
|
|
32
|
+
hapi: 'Hapi',
|
|
33
|
+
'@hapi/hapi': 'Hapi',
|
|
34
|
+
// JS/TS — meta-frameworks
|
|
35
|
+
astro: 'Astro',
|
|
36
|
+
remix: 'Remix',
|
|
37
|
+
'@remix-run/react': 'Remix',
|
|
38
|
+
'@remix-run/node': 'Remix',
|
|
39
|
+
'@remix-run/server-runtime': 'Remix',
|
|
40
|
+
// JS/TS — state management
|
|
41
|
+
zustand: 'Zustand',
|
|
42
|
+
redux: 'Redux',
|
|
43
|
+
'react-redux': 'Redux',
|
|
44
|
+
'@reduxjs/toolkit': 'Redux Toolkit',
|
|
45
|
+
// JS/TS — TanStack family (each product is its own Skill)
|
|
46
|
+
'@tanstack/react-query': 'TanStack Query',
|
|
47
|
+
'@tanstack/vue-query': 'TanStack Query',
|
|
48
|
+
'@tanstack/solid-query': 'TanStack Query',
|
|
49
|
+
'@tanstack/svelte-query': 'TanStack Query',
|
|
50
|
+
'react-query': 'TanStack Query', // pre-rename package, same Skill
|
|
51
|
+
'@tanstack/react-router': 'TanStack Router',
|
|
52
|
+
'@tanstack/router': 'TanStack Router',
|
|
53
|
+
'@tanstack/react-table': 'TanStack Table',
|
|
54
|
+
'@tanstack/vue-table': 'TanStack Table',
|
|
55
|
+
'@tanstack/table-core': 'TanStack Table',
|
|
56
|
+
'@tanstack/react-form': 'TanStack Form',
|
|
57
|
+
'@tanstack/form-core': 'TanStack Form',
|
|
58
|
+
// JS/TS — data / API layer
|
|
59
|
+
prisma: 'Prisma',
|
|
60
|
+
'@prisma/client': 'Prisma',
|
|
61
|
+
graphql: 'GraphQL',
|
|
62
|
+
'@apollo/client': 'Apollo',
|
|
63
|
+
'@apollo/server': 'Apollo',
|
|
64
|
+
'apollo-server': 'Apollo',
|
|
65
|
+
'@trpc/server': 'tRPC',
|
|
66
|
+
'@trpc/client': 'tRPC',
|
|
67
|
+
zod: 'Zod',
|
|
68
|
+
// JS/TS — styling. Code-certified via its CONFIG file surface
|
|
69
|
+
// (tailwind.config.*) — see tech-extensions.js.
|
|
70
|
+
tailwindcss: 'Tailwind CSS',
|
|
71
|
+
// JS/TS — build tools & test runners.
|
|
72
|
+
vite: 'Vite',
|
|
73
|
+
webpack: 'Webpack',
|
|
74
|
+
jest: 'Jest',
|
|
75
|
+
vitest: 'Vitest',
|
|
76
|
+
// Python
|
|
77
|
+
django: 'Django',
|
|
78
|
+
flask: 'Flask',
|
|
79
|
+
fastapi: 'FastAPI',
|
|
80
|
+
pyramid: 'Pyramid',
|
|
81
|
+
tornado: 'Tornado',
|
|
82
|
+
sqlalchemy: 'SQLAlchemy',
|
|
83
|
+
pydantic: 'Pydantic',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Go module paths recognized by prefix (module paths carry version suffixes
|
|
87
|
+
// like "/v4" and full import sub-paths, so exact match isn't reliable).
|
|
88
|
+
const GO_FRAMEWORK_PREFIXES = [
|
|
89
|
+
{ prefix: 'github.com/gin-gonic/gin', name: 'Gin' },
|
|
90
|
+
{ prefix: 'github.com/labstack/echo', name: 'Echo' },
|
|
91
|
+
{ prefix: 'github.com/gofiber/fiber', name: 'Fiber' },
|
|
92
|
+
{ prefix: 'github.com/gorilla/mux', name: 'Gorilla Mux' },
|
|
93
|
+
{ prefix: 'github.com/beego/beego', name: 'Beego' },
|
|
94
|
+
{ prefix: 'gorm.io/gorm', name: 'GORM' },
|
|
95
|
+
{ prefix: 'github.com/jinzhu/gorm', name: 'GORM' }, // pre-move import path, same Skill
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
// Maps a raw dependency/module name to its canonical framework/library display name, or null if unrecognized.
|
|
99
|
+
function canonicalFrameworkName(rawName) {
|
|
100
|
+
if (typeof rawName !== 'string' || !rawName) return null;
|
|
101
|
+
if (Object.prototype.hasOwnProperty.call(EXACT_FRAMEWORK_MAP, rawName)) {
|
|
102
|
+
return EXACT_FRAMEWORK_MAP[rawName];
|
|
103
|
+
}
|
|
104
|
+
for (const { prefix, name } of GO_FRAMEWORK_PREFIXES) {
|
|
105
|
+
if (rawName === prefix || rawName.startsWith(`${prefix}/`)) {
|
|
106
|
+
return name;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function readFileSafe(p) {
|
|
113
|
+
try {
|
|
114
|
+
return fs.readFileSync(p, 'utf8');
|
|
115
|
+
} catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function fromPackageJsonFile(filePath) {
|
|
121
|
+
const raw = readFileSafe(filePath);
|
|
122
|
+
if (!raw) return [];
|
|
123
|
+
let pkg;
|
|
124
|
+
try {
|
|
125
|
+
pkg = JSON.parse(raw);
|
|
126
|
+
} catch {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
const groups = ['dependencies', 'devDependencies', 'peerDependencies', 'optionalDependencies'];
|
|
130
|
+
const names = [];
|
|
131
|
+
for (const group of groups) {
|
|
132
|
+
if (pkg[group] && typeof pkg[group] === 'object') {
|
|
133
|
+
names.push(...Object.keys(pkg[group]));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return names;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function fromRequirementsTxtFile(filePath) {
|
|
140
|
+
const raw = readFileSafe(filePath);
|
|
141
|
+
if (!raw) return [];
|
|
142
|
+
const names = [];
|
|
143
|
+
for (const rawLine of raw.split(/\r?\n/)) {
|
|
144
|
+
const line = rawLine.trim();
|
|
145
|
+
if (!line || line.startsWith('#') || line.startsWith('-')) continue;
|
|
146
|
+
const match = line.match(/^([A-Za-z0-9_.-]+)/);
|
|
147
|
+
if (match) names.push(match[1].toLowerCase());
|
|
148
|
+
}
|
|
149
|
+
return names;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function fromGoModFile(filePath) {
|
|
153
|
+
const raw = readFileSafe(filePath);
|
|
154
|
+
if (!raw) return [];
|
|
155
|
+
const names = [];
|
|
156
|
+
const blockMatch = raw.match(/require\s*\(([\s\S]*?)\)/);
|
|
157
|
+
if (blockMatch) {
|
|
158
|
+
for (const rawLine of blockMatch[1].split(/\r?\n/)) {
|
|
159
|
+
const line = rawLine.trim();
|
|
160
|
+
if (!line || line.startsWith('//')) continue;
|
|
161
|
+
const match = line.match(/^(\S+)/);
|
|
162
|
+
if (match) names.push(match[1]);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const singleLineRe = /^require\s+(\S+)\s+\S+/gm;
|
|
166
|
+
let m;
|
|
167
|
+
while ((m = singleLineRe.exec(raw)) !== null) {
|
|
168
|
+
if (m[1] === '(') continue; // the block-opening "require (" line, not a single-line require
|
|
169
|
+
names.push(m[1]);
|
|
170
|
+
}
|
|
171
|
+
return names;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function fromPyprojectTomlFile(filePath) {
|
|
175
|
+
const raw = readFileSafe(filePath);
|
|
176
|
+
if (!raw) return [];
|
|
177
|
+
const names = [];
|
|
178
|
+
|
|
179
|
+
const sectionRe = /^\[tool\.poetry\.(?:dependencies|dev-dependencies|group\.[^\]]+\.dependencies)\]\s*$/gm;
|
|
180
|
+
const lines = raw.split(/\r?\n/);
|
|
181
|
+
let inDepsSection = false;
|
|
182
|
+
for (const line of lines) {
|
|
183
|
+
if (/^\[.*\]\s*$/.test(line)) {
|
|
184
|
+
inDepsSection = sectionRe.test(line);
|
|
185
|
+
sectionRe.lastIndex = 0;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!inDepsSection) continue;
|
|
189
|
+
const kv = line.match(/^([A-Za-z0-9_.-]+)\s*=/);
|
|
190
|
+
if (kv && kv[1].toLowerCase() !== 'python') names.push(kv[1].toLowerCase());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const depsArrayMatch = raw.match(/\bdependencies\s*=\s*\[([\s\S]*?)\]/);
|
|
194
|
+
if (depsArrayMatch) {
|
|
195
|
+
const itemRe = /["']([A-Za-z0-9_.-]+)/g;
|
|
196
|
+
let m;
|
|
197
|
+
while ((m = itemRe.exec(depsArrayMatch[1])) !== null) {
|
|
198
|
+
names.push(m[1].toLowerCase());
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return names;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Manifest filename -> reader (skill-code-certification, issue 008).
|
|
206
|
+
const MANIFEST_READERS = {
|
|
207
|
+
'package.json': fromPackageJsonFile,
|
|
208
|
+
'requirements.txt': fromRequirementsTxtFile,
|
|
209
|
+
'go.mod': fromGoModFile,
|
|
210
|
+
'pyproject.toml': fromPyprojectTomlFile,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
function findManifestFiles(root) {
|
|
214
|
+
const found = [];
|
|
215
|
+
if (isRefusedWalkRoot(root)) return found;
|
|
216
|
+
|
|
217
|
+
function walk(dir, depth) {
|
|
218
|
+
if (depth > MAX_WALK_DEPTH) return;
|
|
219
|
+
let entries;
|
|
220
|
+
try {
|
|
221
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
222
|
+
} catch {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
const abs = path.join(dir, entry.name);
|
|
227
|
+
if (entry.isDirectory()) {
|
|
228
|
+
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
229
|
+
if (isExcludedWalkPath(abs)) continue;
|
|
230
|
+
walk(abs, depth + 1);
|
|
231
|
+
} else if (entry.isFile() && Object.prototype.hasOwnProperty.call(MANIFEST_READERS, entry.name)) {
|
|
232
|
+
found.push(abs);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
walk(root, 0);
|
|
238
|
+
return found.sort();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Merges every discovered manifest's output, deduped, sorted, WITHOUT filtering through the canonical framework map.
|
|
242
|
+
function detectRawDependencyNames(root) {
|
|
243
|
+
const rawNames = [];
|
|
244
|
+
for (const filePath of findManifestFiles(root)) {
|
|
245
|
+
const reader = MANIFEST_READERS[path.basename(filePath)];
|
|
246
|
+
if (reader) rawNames.push(...reader(filePath));
|
|
247
|
+
}
|
|
248
|
+
return [...new Set(rawNames)].sort();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Maps the merged raw dependency names through canonicalFrameworkName, keeps only recognized names, dedupes, sorts.
|
|
252
|
+
function detectTechnologies(root) {
|
|
253
|
+
const rawNames = detectRawDependencyNames(root);
|
|
254
|
+
const canonical = new Set();
|
|
255
|
+
for (const rawName of rawNames) {
|
|
256
|
+
const name = canonicalFrameworkName(rawName);
|
|
257
|
+
if (name) canonical.add(name);
|
|
258
|
+
}
|
|
259
|
+
return [...canonical].sort();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function detectTechnologyManifests(root) {
|
|
263
|
+
const byTech = new Map();
|
|
264
|
+
for (const filePath of findManifestFiles(root)) {
|
|
265
|
+
const reader = MANIFEST_READERS[path.basename(filePath)];
|
|
266
|
+
if (!reader) continue;
|
|
267
|
+
const rel = path.relative(root, filePath).split(path.sep).join('/');
|
|
268
|
+
const seenInFile = new Set();
|
|
269
|
+
for (const rawName of reader(filePath)) {
|
|
270
|
+
const tech = canonicalFrameworkName(rawName);
|
|
271
|
+
if (!tech || seenInFile.has(tech)) continue;
|
|
272
|
+
seenInFile.add(tech);
|
|
273
|
+
let set = byTech.get(tech);
|
|
274
|
+
if (!set) {
|
|
275
|
+
set = new Set();
|
|
276
|
+
byTech.set(tech, set);
|
|
277
|
+
}
|
|
278
|
+
set.add(rel);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return [...byTech.entries()]
|
|
282
|
+
.map(([tech, set]) => ({ tech, manifestPaths: [...set].sort() }))
|
|
283
|
+
.sort((a, b) => a.tech.localeCompare(b.tech));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
module.exports = {
|
|
287
|
+
detectTechnologies,
|
|
288
|
+
detectTechnologyManifests,
|
|
289
|
+
detectRawDependencyNames,
|
|
290
|
+
canonicalFrameworkName,
|
|
291
|
+
findManifestFiles,
|
|
292
|
+
// Exported as the SOURCE OF TRUTH for the complete set of canonical technologies the detector can ever emit.
|
|
293
|
+
EXACT_FRAMEWORK_MAP,
|
|
294
|
+
GO_FRAMEWORK_PREFIXES,
|
|
295
|
+
};
|