@meyverick/agentic 5.0.2
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/AGENTS.md +234 -0
- package/CHANGELOG.md +236 -0
- package/README.md +50 -0
- package/install.ts +349 -0
- package/package.json +37 -0
- package/scripts/check-deps.mjs +587 -0
- package/scripts/git-dl.mjs +100 -0
- package/skills/check/SKILL.md +108 -0
- package/skills/check/evals/benchmark.json +40 -0
- package/skills/check/evals/evals.json +38 -0
- package/skills/check/references/diagnostic-matrix.md +170 -0
- package/skills/check/references/script-anatomy.md +154 -0
- package/skills/create-skill/SKILL.md +291 -0
- package/skills/create-skill/assets/templates/SKILL.md.template +118 -0
- package/skills/create-skill/assets/templates/evals.json.template +36 -0
- package/skills/create-skill/assets/templates/grading.json.template +26 -0
- package/skills/create-skill/evals/benchmark.json +41 -0
- package/skills/create-skill/evals/evals.json +50 -0
- package/skills/create-skill/evals/grading-template.json +36 -0
- package/skills/create-skill/evals/near-misses.json +35 -0
- package/skills/create-skill/evals/trigger-queries.json +80 -0
- package/skills/create-skill/references/antipatterns.md +123 -0
- package/skills/create-skill/references/component-decomposition.md +130 -0
- package/skills/create-skill/references/content-quality-criteria.md +61 -0
- package/skills/create-skill/references/description-optimization.md +90 -0
- package/skills/create-skill/references/eval-methodology.md +100 -0
- package/skills/create-skill/references/fragility-matching.md +88 -0
- package/skills/create-skill/references/gotchas-patterns.md +80 -0
- package/skills/create-skill/references/specification.md +77 -0
- package/skills/create-skill/scripts/audit-antipatterns.mjs +164 -0
- package/skills/create-skill/scripts/compute-benchmark.mjs +111 -0
- package/skills/create-skill/scripts/run-cold-eval.mjs +118 -0
- package/skills/create-skill/scripts/scaffold-skill.mjs +86 -0
- package/skills/create-skill/scripts/validate-routing.mjs +137 -0
- package/skills/create-skill/scripts/validate-structure.mjs +223 -0
- package/skills/design-craft/SKILL.md +134 -0
- package/skills/design-craft/evals/benchmark.json +41 -0
- package/skills/design-craft/evals/evals.json +81 -0
- package/skills/design-craft/references/anti-slop-patterns.md +49 -0
- package/skills/design-craft/references/art-direction.md +89 -0
- package/skills/design-craft/references/design-engineering.md +122 -0
- package/skills/design-craft/references/motion-craft.md +124 -0
- package/skills/design-craft/references/process.md +47 -0
- package/skills/design-craft/references/review-checklist.md +121 -0
- package/skills/guardrails/SKILL.md +118 -0
- package/skills/guardrails/evals/benchmark.json +40 -0
- package/skills/guardrails/evals/evals.json +49 -0
- package/skills/guardrails/references/guardrails-patterns.md +43 -0
- package/skills/okf-docs/SKILL.md +79 -0
- package/skills/okf-docs/evals/benchmark.json +21 -0
- package/skills/okf-docs/evals/evals.json +37 -0
- package/skills/okf-docs/references/okf-spec.md +56 -0
- package/skills/okf-docs/scripts/validate-frontmatter.mjs +130 -0
- package/skills/openspec-harden/SKILL.md +138 -0
- package/skills/openspec-harden/evals/benchmark.json +40 -0
- package/skills/openspec-harden/evals/evals.json +38 -0
- package/skills/openspec-learn/SKILL.md +216 -0
- package/skills/openspec-learn/evals/benchmark.json +44 -0
- package/skills/openspec-learn/evals/evals.json +48 -0
- package/skills/openspec-learn/evals/retrieval-bench.json +27 -0
- package/skills/openspec-learn/references/conflict-handling.md +20 -0
- package/skills/openspec-learn/references/evaluation-methodology.md +126 -0
- package/skills/openspec-learn/references/examples.md +37 -0
- package/skills/openspec-learn/references/improvement-patterns.md +155 -0
- package/skills/openspec-learn/references/report-analysis.md +104 -0
- package/skills/openspec-learn/references/skill-quality.md +103 -0
- package/skills/openspec-learn/references/tool-type-detection.md +30 -0
- package/skills/openspec-report/SKILL.md +104 -0
- package/skills/openspec-report/assets/templates/assessment.md.template +84 -0
- package/skills/openspec-report/assets/templates/report.md.template +92 -0
- package/skills/openspec-report/evals/benchmark.json +44 -0
- package/skills/openspec-report/evals/evals.json +46 -0
- package/skills/qmd-research/SKILL.md +89 -0
- package/skills/qmd-research/evals/benchmark.json +40 -0
- package/skills/qmd-research/evals/evals.json +38 -0
- package/skills/qmd-research/references/index-management.md +69 -0
- package/skills/qmd-research/references/query-craft.md +82 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join, relative, dirname, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const colors = {
|
|
8
|
+
reset: '\x1b[0m',
|
|
9
|
+
bold: '\x1b[1m',
|
|
10
|
+
dim: '\x1b[2m',
|
|
11
|
+
green: '\x1b[32m',
|
|
12
|
+
yellow: '\x1b[33m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
cyan: '\x1b[36m',
|
|
15
|
+
blue: '\x1b[34m',
|
|
16
|
+
magenta: '\x1b[35m'
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const USER_AGENT = 'agentic-dep-checker/1.0 (https://github.com/meyverick/agentic)';
|
|
20
|
+
const includeReferences = process.argv.includes('--all') || process.argv.includes('--references');
|
|
21
|
+
const isJson = process.argv.includes('--json');
|
|
22
|
+
|
|
23
|
+
const SCRIPT_FILE = fileURLToPath(import.meta.url);
|
|
24
|
+
const SCRIPT_DIR = dirname(SCRIPT_FILE);
|
|
25
|
+
const targetArg = process.argv.slice(2).find((arg) => !arg.startsWith('-'));
|
|
26
|
+
const SCAN_ROOT = targetArg ? resolve(process.cwd(), targetArg) : join(SCRIPT_DIR, '..');
|
|
27
|
+
|
|
28
|
+
const IGNORED_DIRS = new Set([
|
|
29
|
+
'node_modules',
|
|
30
|
+
'target',
|
|
31
|
+
'.git',
|
|
32
|
+
'.svelte-kit',
|
|
33
|
+
'build',
|
|
34
|
+
'dist',
|
|
35
|
+
'vendor',
|
|
36
|
+
...(includeReferences ? [] : ['references'])
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
async function asyncPool(limit, items, iteratorFn) {
|
|
40
|
+
const ret = [];
|
|
41
|
+
const executing = new Set();
|
|
42
|
+
for (const item of items) {
|
|
43
|
+
const p = Promise.resolve().then(() => iteratorFn(item));
|
|
44
|
+
ret.push(p);
|
|
45
|
+
executing.add(p);
|
|
46
|
+
const clean = () => executing.delete(p);
|
|
47
|
+
p.then(clean).catch(clean);
|
|
48
|
+
if (executing.size >= limit) {
|
|
49
|
+
await Promise.race(executing);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return Promise.all(ret);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function scanFiles(dir) {
|
|
56
|
+
const results = { cargo: [], npm: [], lockFiles: [], toolchains: [] };
|
|
57
|
+
|
|
58
|
+
async function walk(currentDir) {
|
|
59
|
+
const entries = await readdir(currentDir, { withFileTypes: true }).catch(() => []);
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const fullPath = join(currentDir, entry.name);
|
|
62
|
+
|
|
63
|
+
if (fullPath === SCRIPT_FILE || IGNORED_DIRS.has(entry.name)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
await walk(fullPath);
|
|
69
|
+
} else if (entry.isFile()) {
|
|
70
|
+
if (entry.name === 'Cargo.toml') {
|
|
71
|
+
results.cargo.push(fullPath);
|
|
72
|
+
} else if (entry.name === 'package.json') {
|
|
73
|
+
results.npm.push(fullPath);
|
|
74
|
+
} else if (entry.name === 'Cargo.lock') {
|
|
75
|
+
results.lockFiles.push(fullPath);
|
|
76
|
+
} else if (entry.name === 'rust-toolchain.toml' || entry.name === 'rust-toolchain') {
|
|
77
|
+
results.toolchains.push(fullPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await walk(dir);
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function detectToolchains(toolchainFiles, cargoFiles, npmFiles) {
|
|
88
|
+
let rustVersion = null;
|
|
89
|
+
|
|
90
|
+
for (const tf of toolchainFiles) {
|
|
91
|
+
try {
|
|
92
|
+
const content = await readFile(tf, 'utf8');
|
|
93
|
+
const channelMatch = content.match(/channel\s*=\s*"([^"]+)"/) || content.match(/^([0-9a-zA-Z.-]+)$/m);
|
|
94
|
+
if (channelMatch && !channelMatch[1].startsWith('nightly') && !channelMatch[1].startsWith('beta')) {
|
|
95
|
+
rustVersion = channelMatch[1].replace(/^stable-?/, '');
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!rustVersion) {
|
|
102
|
+
for (const cf of cargoFiles) {
|
|
103
|
+
try {
|
|
104
|
+
const content = await readFile(cf, 'utf8');
|
|
105
|
+
const msrv = content.match(/rust-version\s*=\s*"([^"]+)"/);
|
|
106
|
+
if (msrv) {
|
|
107
|
+
rustVersion = msrv[1];
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!rustVersion) {
|
|
115
|
+
try {
|
|
116
|
+
const out = execSync('rustc --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
117
|
+
const m = out.match(/rustc\s+([0-9]+\.[0-9]+\.[0-9]+)/);
|
|
118
|
+
if (m) rustVersion = m[1];
|
|
119
|
+
} catch {}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let cargoVersion = null;
|
|
123
|
+
try {
|
|
124
|
+
const out = execSync('cargo --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
125
|
+
const m = out.match(/cargo\s+([0-9]+\.[0-9]+\.[0-9]+)/);
|
|
126
|
+
if (m) cargoVersion = m[1];
|
|
127
|
+
} catch {}
|
|
128
|
+
|
|
129
|
+
let bunVersion = null;
|
|
130
|
+
for (const nf of npmFiles) {
|
|
131
|
+
try {
|
|
132
|
+
const pkg = JSON.parse(await readFile(nf, 'utf8'));
|
|
133
|
+
if (pkg.packageManager?.startsWith('bun@')) {
|
|
134
|
+
bunVersion = pkg.packageManager.replace(/^bun@/, '');
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
if (pkg.engines?.bun) {
|
|
138
|
+
bunVersion = pkg.engines.bun;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
} catch {}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!bunVersion && typeof Bun !== 'undefined') {
|
|
145
|
+
bunVersion = Bun.version;
|
|
146
|
+
} else if (!bunVersion) {
|
|
147
|
+
try {
|
|
148
|
+
const out = execSync('bun --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
149
|
+
bunVersion = out.trim();
|
|
150
|
+
} catch {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const items = [];
|
|
154
|
+
if (rustVersion) {
|
|
155
|
+
items.push({ name: 'rust', version: rustVersion });
|
|
156
|
+
}
|
|
157
|
+
if (cargoVersion || rustVersion) {
|
|
158
|
+
items.push({ name: 'cargo', version: cargoVersion || rustVersion });
|
|
159
|
+
}
|
|
160
|
+
if (bunVersion) {
|
|
161
|
+
items.push({ name: 'bun', version: bunVersion });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return items;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function fetchLatestToolchain() {
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch('https://static.rust-lang.org/dist/channel-rust-stable.toml', {
|
|
170
|
+
headers: { 'User-Agent': USER_AGENT }
|
|
171
|
+
});
|
|
172
|
+
if (!res.ok) return null;
|
|
173
|
+
const text = await res.text();
|
|
174
|
+
const match = text.match(/\[pkg\.rust\][\s\S]*?version\s*=\s*"([0-9]+\.[0-9]+\.[0-9]+)/);
|
|
175
|
+
return match ? match[1] : null;
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function parseCargoLocks(lockFiles) {
|
|
182
|
+
const lockedVersions = new Map();
|
|
183
|
+
for (const lockPath of lockFiles) {
|
|
184
|
+
try {
|
|
185
|
+
const content = await readFile(lockPath, 'utf8');
|
|
186
|
+
const blocks = content.split('[[package]]');
|
|
187
|
+
for (const block of blocks) {
|
|
188
|
+
const nameMatch = block.match(/name\s*=\s*"([^"]+)"/);
|
|
189
|
+
const verMatch = block.match(/version\s*=\s*"([^"]+)"/);
|
|
190
|
+
if (nameMatch && verMatch) {
|
|
191
|
+
const name = nameMatch[1];
|
|
192
|
+
const ver = verMatch[1];
|
|
193
|
+
if (!lockedVersions.has(name)) {
|
|
194
|
+
lockedVersions.set(name, []);
|
|
195
|
+
}
|
|
196
|
+
lockedVersions.get(name).push(ver);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
return lockedVersions;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function resolveLockedVersion(name, declaredVer, lockedMap) {
|
|
205
|
+
const versions = lockedMap.get(name);
|
|
206
|
+
if (!versions || versions.length === 0) return declaredVer;
|
|
207
|
+
if (versions.length === 1) return versions[0];
|
|
208
|
+
|
|
209
|
+
const clean = declaredVer.replace(/^[\^~>=<]+/, '').trim();
|
|
210
|
+
const dParts = clean.split('.').map((p) => parseInt(p, 10) || 0);
|
|
211
|
+
|
|
212
|
+
const matched = versions.find((v) => {
|
|
213
|
+
const vParts = v.split('.').map((p) => parseInt(p, 10) || 0);
|
|
214
|
+
if (dParts[0] === 0) {
|
|
215
|
+
return vParts[0] === 0 && vParts[1] === dParts[1];
|
|
216
|
+
}
|
|
217
|
+
return vParts[0] === dParts[0];
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return matched || versions[0];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getCrateIndexPath(crateName) {
|
|
224
|
+
const name = crateName.toLowerCase();
|
|
225
|
+
if (name.length === 1) return `1/${name}`;
|
|
226
|
+
if (name.length === 2) return `2/${name}`;
|
|
227
|
+
if (name.length === 3) return `3/${name[0]}/${name}`;
|
|
228
|
+
return `${name.slice(0, 2)}/${name.slice(2, 4)}/${name}`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function fetchLatestCrate(crateName) {
|
|
232
|
+
let version = null;
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const indexPath = getCrateIndexPath(crateName);
|
|
236
|
+
const indexRes = await fetch(`https://index.crates.io/${indexPath}`, {
|
|
237
|
+
headers: { 'User-Agent': USER_AGENT }
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
if (indexRes.ok) {
|
|
241
|
+
const text = await indexRes.text();
|
|
242
|
+
const lines = text.trim().split('\n').filter(Boolean);
|
|
243
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
244
|
+
try {
|
|
245
|
+
const entry = JSON.parse(lines[i]);
|
|
246
|
+
if (!entry.yanked) {
|
|
247
|
+
version = entry.vers;
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
} catch {}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { version };
|
|
255
|
+
} catch {
|
|
256
|
+
return { version: null };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function fetchLatestNpm(pkgName) {
|
|
261
|
+
try {
|
|
262
|
+
const encoded = pkgName.startsWith('@')
|
|
263
|
+
? `@${encodeURIComponent(pkgName.slice(1))}`
|
|
264
|
+
: encodeURIComponent(pkgName);
|
|
265
|
+
const res = await fetch(`https://registry.npmjs.org/${encoded}`);
|
|
266
|
+
if (!res.ok) return { version: null };
|
|
267
|
+
const data = await res.json();
|
|
268
|
+
const version = data['dist-tags']?.latest ?? Object.keys(data.versions || {}).pop() ?? null;
|
|
269
|
+
return { version };
|
|
270
|
+
} catch {
|
|
271
|
+
return { version: null };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function parseCargoDependencies(content) {
|
|
276
|
+
const deps = [];
|
|
277
|
+
const lines = content.split('\n');
|
|
278
|
+
let currentSection = null;
|
|
279
|
+
|
|
280
|
+
for (const line of lines) {
|
|
281
|
+
const trimmed = line.trim();
|
|
282
|
+
if (trimmed.startsWith('#') || !trimmed) continue;
|
|
283
|
+
|
|
284
|
+
const sectionMatch = trimmed.match(/^\[(.*)\]$/);
|
|
285
|
+
if (sectionMatch) {
|
|
286
|
+
currentSection = sectionMatch[1];
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (
|
|
291
|
+
currentSection === 'dependencies' ||
|
|
292
|
+
currentSection === 'dev-dependencies' ||
|
|
293
|
+
currentSection === 'build-dependencies' ||
|
|
294
|
+
currentSection === 'workspace.dependencies' ||
|
|
295
|
+
currentSection?.endsWith('.dependencies')
|
|
296
|
+
) {
|
|
297
|
+
const depMatch = trimmed.match(/^([a-zA-Z0-9_-]+)\s*=\s*(.*)$/);
|
|
298
|
+
if (depMatch) {
|
|
299
|
+
const name = depMatch[1];
|
|
300
|
+
const val = depMatch[2];
|
|
301
|
+
let version = null;
|
|
302
|
+
let git = null;
|
|
303
|
+
|
|
304
|
+
if (val.startsWith('"')) {
|
|
305
|
+
version = val.split('"')[1];
|
|
306
|
+
} else if (val.startsWith('{')) {
|
|
307
|
+
const vMatch = val.match(/version\s*=\s*"([^"]+)"/);
|
|
308
|
+
if (vMatch) version = vMatch[1];
|
|
309
|
+
const gMatch = val.match(/git\s*=\s*"([^"]+)"/);
|
|
310
|
+
if (gMatch) git = gMatch[1];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if ((version && !version.startsWith('workspace')) || git) {
|
|
314
|
+
deps.push({ name, version: version ?? 'git', git, section: currentSection });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return deps;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function getSemverDiff(declared, latest) {
|
|
324
|
+
if (!latest) return { status: 'UNKNOWN' };
|
|
325
|
+
|
|
326
|
+
const cleanDeclared = declared.replace(/^[\^~>=<]+/, '').trim();
|
|
327
|
+
if (cleanDeclared === latest || latest.startsWith(cleanDeclared)) {
|
|
328
|
+
return { status: 'UP_TO_DATE' };
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const dParts = cleanDeclared.split('.').map((p) => parseInt(p, 10) || 0);
|
|
332
|
+
const lParts = latest.split('.').map((p) => parseInt(p, 10) || 0);
|
|
333
|
+
|
|
334
|
+
if (dParts[0] === 0 && lParts[0] === 0) {
|
|
335
|
+
if (dParts[1] !== lParts[1]) {
|
|
336
|
+
return { status: 'MAJOR_BREAKING', reason: `0.${dParts[1]}.x -> 0.${lParts[1]}.x (Breaking API Shift)` };
|
|
337
|
+
}
|
|
338
|
+
return { status: 'MINOR_PATCH', reason: `0.${dParts[1]}.${dParts[2] || 0} -> ${latest}` };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (dParts[0] !== lParts[0]) {
|
|
342
|
+
return { status: 'MAJOR_BREAKING', reason: `${dParts[0]}.x -> ${lParts[0]}.x (Major Version Bump)` };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return { status: 'MINOR_PATCH', reason: `${cleanDeclared} -> ${latest}` };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function main() {
|
|
349
|
+
if (!isJson) {
|
|
350
|
+
console.log(`${colors.bold}${colors.cyan}================================================================${colors.reset}`);
|
|
351
|
+
console.log(`${colors.bold}${colors.cyan} Agentic - Dependency Freshness Verifier ${colors.reset}`);
|
|
352
|
+
console.log(`${colors.bold}${colors.cyan}================================================================${colors.reset}\n`);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const rootDir = SCAN_ROOT;
|
|
356
|
+
const { cargo, npm, lockFiles, toolchains } = await scanFiles(rootDir);
|
|
357
|
+
const lockedMap = await parseCargoLocks(lockFiles);
|
|
358
|
+
const toolchainItems = await detectToolchains(toolchains, cargo, npm);
|
|
359
|
+
|
|
360
|
+
let totalChecked = 0;
|
|
361
|
+
let totalUpToDate = 0;
|
|
362
|
+
let totalMinorPatch = 0;
|
|
363
|
+
let totalMajorBreaking = 0;
|
|
364
|
+
|
|
365
|
+
const jsonReport = {
|
|
366
|
+
summary: {
|
|
367
|
+
totalChecked: 0,
|
|
368
|
+
upToDate: 0,
|
|
369
|
+
minorPatch: 0,
|
|
370
|
+
majorBreaking: 0
|
|
371
|
+
},
|
|
372
|
+
toolchains: [],
|
|
373
|
+
rustModules: [],
|
|
374
|
+
npmModules: []
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// 1. Toolchains (Rust, Cargo, Bun)
|
|
378
|
+
if (toolchainItems.length > 0) {
|
|
379
|
+
if (!isJson) {
|
|
380
|
+
console.log(`${colors.bold}${colors.blue}⚙ Toolchains: Rust, Cargo & Bun${colors.reset}`);
|
|
381
|
+
}
|
|
382
|
+
const latestRust = await fetchLatestToolchain();
|
|
383
|
+
const { version: latestBun } = await fetchLatestNpm('bun');
|
|
384
|
+
|
|
385
|
+
for (const item of toolchainItems) {
|
|
386
|
+
totalChecked++;
|
|
387
|
+
const isBun = item.name === 'bun';
|
|
388
|
+
const latestVer = isBun ? latestBun : latestRust;
|
|
389
|
+
const depLabel = item.name.padEnd(24, ' ');
|
|
390
|
+
const currentLabel = `current: ${item.version}`.padEnd(16, ' ');
|
|
391
|
+
const latestLabel = `latest: ${latestVer ?? 'unknown'}`.padEnd(16, ' ');
|
|
392
|
+
const diff = getSemverDiff(item.version, latestVer);
|
|
393
|
+
|
|
394
|
+
if (diff.status === 'UP_TO_DATE') {
|
|
395
|
+
totalUpToDate++;
|
|
396
|
+
if (!isJson) console.log(` ${colors.green}✓${colors.reset} ${depLabel} ${currentLabel} ${latestLabel} ${colors.green}[UP TO DATE]${colors.reset}`);
|
|
397
|
+
} else if (diff.status === 'MINOR_PATCH') {
|
|
398
|
+
totalMinorPatch++;
|
|
399
|
+
if (!isJson) console.log(` ${colors.yellow}↑${colors.reset} ${colors.bold}${depLabel}${colors.reset} ${currentLabel} ${colors.yellow}${latestLabel}${colors.reset} ${colors.yellow}[UPDATE AVAILABLE]${colors.reset}`);
|
|
400
|
+
} else if (diff.status === 'MAJOR_BREAKING') {
|
|
401
|
+
totalMajorBreaking++;
|
|
402
|
+
if (!isJson) console.log(` ${colors.magenta}◆${colors.reset} ${depLabel} ${currentLabel} ${colors.magenta}${latestLabel}${colors.reset} ${colors.dim}[MAJOR/TOOLCHAIN BUMP: ${diff.reason}]${colors.reset}`);
|
|
403
|
+
} else {
|
|
404
|
+
if (!isJson) console.log(` ${colors.dim}•${colors.reset} ${depLabel} ${currentLabel} ${latestLabel} ${colors.yellow}[CHECK FAILED]${colors.reset}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (isJson) {
|
|
408
|
+
jsonReport.toolchains.push({
|
|
409
|
+
name: item.name,
|
|
410
|
+
current: item.version,
|
|
411
|
+
latest: latestVer ?? null,
|
|
412
|
+
status: diff.status,
|
|
413
|
+
reason: diff.reason ?? null
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (!isJson) console.log();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 2. Rust Modules
|
|
421
|
+
for (const cargoPath of cargo) {
|
|
422
|
+
const relPath = relative(rootDir, cargoPath);
|
|
423
|
+
const content = await readFile(cargoPath, 'utf8');
|
|
424
|
+
const deps = parseCargoDependencies(content);
|
|
425
|
+
|
|
426
|
+
if (deps.length === 0) continue;
|
|
427
|
+
|
|
428
|
+
if (!isJson) {
|
|
429
|
+
console.log(`${colors.bold}${colors.blue}📦 Rust Module: ${relPath}${colors.reset}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const currentModule = { file: relPath, dependencies: [] };
|
|
433
|
+
|
|
434
|
+
const results = await asyncPool(10, deps, async (dep) => {
|
|
435
|
+
if (dep.git) {
|
|
436
|
+
return { ...dep, latest: 'git' };
|
|
437
|
+
}
|
|
438
|
+
const { version: latest } = await fetchLatestCrate(dep.name);
|
|
439
|
+
return { ...dep, latest };
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
for (const r of results) {
|
|
443
|
+
totalChecked++;
|
|
444
|
+
const depLabel = r.name.padEnd(24, ' ');
|
|
445
|
+
const currentLabel = `current: ${r.version}`.padEnd(16, ' ');
|
|
446
|
+
const latestLabel = `latest: ${r.latest ?? 'unknown'}`.padEnd(16, ' ');
|
|
447
|
+
const diff = getSemverDiff(r.version, r.latest);
|
|
448
|
+
|
|
449
|
+
if (diff.status === 'UP_TO_DATE') {
|
|
450
|
+
totalUpToDate++;
|
|
451
|
+
if (!isJson) console.log(` ${colors.green}✓${colors.reset} ${depLabel} ${currentLabel} ${latestLabel} ${colors.green}[UP TO DATE]${colors.reset}`);
|
|
452
|
+
} else if (diff.status === 'MINOR_PATCH') {
|
|
453
|
+
totalMinorPatch++;
|
|
454
|
+
if (!isJson) console.log(` ${colors.yellow}↑${colors.reset} ${colors.bold}${depLabel}${colors.reset} ${currentLabel} ${colors.yellow}${latestLabel}${colors.reset} ${colors.yellow}[UPDATE AVAILABLE]${colors.reset}`);
|
|
455
|
+
} else if (diff.status === 'MAJOR_BREAKING') {
|
|
456
|
+
totalMajorBreaking++;
|
|
457
|
+
if (!isJson) console.log(` ${colors.magenta}◆${colors.reset} ${depLabel} ${currentLabel} ${colors.magenta}${latestLabel}${colors.reset} ${colors.dim}[MAJOR/PEER LOCKED: ${diff.reason}]${colors.reset}`);
|
|
458
|
+
} else {
|
|
459
|
+
if (!isJson) console.log(` ${colors.dim}•${colors.reset} ${depLabel} ${currentLabel} ${latestLabel} ${colors.yellow}[CHECK FAILED]${colors.reset}`);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const activeVersion = resolveLockedVersion(r.name, r.version, lockedMap);
|
|
463
|
+
|
|
464
|
+
if (isJson) {
|
|
465
|
+
currentModule.dependencies.push({
|
|
466
|
+
name: r.name,
|
|
467
|
+
declaredVersion: r.version,
|
|
468
|
+
resolvedVersion: activeVersion,
|
|
469
|
+
latest: r.latest ?? null,
|
|
470
|
+
status: diff.status,
|
|
471
|
+
reason: diff.reason ?? null,
|
|
472
|
+
section: r.section
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (isJson) jsonReport.rustModules.push(currentModule);
|
|
478
|
+
if (!isJson) console.log();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// 3. NPM Modules
|
|
482
|
+
for (const npmPath of npm) {
|
|
483
|
+
const relPath = relative(rootDir, npmPath);
|
|
484
|
+
const raw = await readFile(npmPath, 'utf8');
|
|
485
|
+
let pkgJson = {};
|
|
486
|
+
try {
|
|
487
|
+
pkgJson = JSON.parse(raw);
|
|
488
|
+
} catch {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const allDeps = [];
|
|
493
|
+
if (pkgJson.dependencies) {
|
|
494
|
+
for (const [name, version] of Object.entries(pkgJson.dependencies)) {
|
|
495
|
+
if (!version.startsWith('workspace:')) {
|
|
496
|
+
allDeps.push({ name, version, type: 'prod' });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (pkgJson.devDependencies) {
|
|
501
|
+
for (const [name, version] of Object.entries(pkgJson.devDependencies)) {
|
|
502
|
+
if (!version.startsWith('workspace:')) {
|
|
503
|
+
allDeps.push({ name, version, type: 'dev' });
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (allDeps.length === 0) continue;
|
|
509
|
+
|
|
510
|
+
if (!isJson) {
|
|
511
|
+
console.log(`${colors.bold}${colors.magenta}🌐 NPM/Web Module: ${relPath}${colors.reset}`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const currentModule = { file: relPath, dependencies: [] };
|
|
515
|
+
|
|
516
|
+
const results = await asyncPool(10, allDeps, async (dep) => {
|
|
517
|
+
const { version: latest } = await fetchLatestNpm(dep.name);
|
|
518
|
+
return { ...dep, latest };
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
for (const r of results) {
|
|
522
|
+
totalChecked++;
|
|
523
|
+
const depLabel = r.name.padEnd(28, ' ');
|
|
524
|
+
const currentLabel = `current: ${r.version}`.padEnd(18, ' ');
|
|
525
|
+
const latestLabel = `latest: ${r.latest ?? 'unknown'}`.padEnd(16, ' ');
|
|
526
|
+
const diff = getSemverDiff(r.version, r.latest);
|
|
527
|
+
|
|
528
|
+
if (diff.status === 'UP_TO_DATE') {
|
|
529
|
+
totalUpToDate++;
|
|
530
|
+
if (!isJson) console.log(` ${colors.green}✓${colors.reset} ${depLabel} ${currentLabel} ${latestLabel} ${colors.green}[UP TO DATE]${colors.reset}`);
|
|
531
|
+
} else if (diff.status === 'MINOR_PATCH') {
|
|
532
|
+
totalMinorPatch++;
|
|
533
|
+
if (!isJson) console.log(` ${colors.yellow}↑${colors.reset} ${colors.bold}${depLabel}${colors.reset} ${currentLabel} ${colors.yellow}${latestLabel}${colors.reset} ${colors.yellow}[UPDATE AVAILABLE]${colors.reset}`);
|
|
534
|
+
} else if (diff.status === 'MAJOR_BREAKING') {
|
|
535
|
+
totalMajorBreaking++;
|
|
536
|
+
if (!isJson) console.log(` ${colors.magenta}◆${colors.reset} ${depLabel} ${currentLabel} ${colors.magenta}${latestLabel}${colors.reset} ${colors.dim}[MAJOR/PEER LOCKED: ${diff.reason}]${colors.reset}`);
|
|
537
|
+
} else {
|
|
538
|
+
if (!isJson) console.log(` ${colors.dim}•${colors.reset} ${depLabel} ${currentLabel} ${latestLabel} ${colors.yellow}[CHECK FAILED]${colors.reset}`);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
if (isJson) {
|
|
542
|
+
currentModule.dependencies.push({
|
|
543
|
+
name: r.name,
|
|
544
|
+
declaredVersion: r.version,
|
|
545
|
+
latest: r.latest ?? null,
|
|
546
|
+
status: diff.status,
|
|
547
|
+
reason: diff.reason ?? null,
|
|
548
|
+
type: r.type
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (isJson) jsonReport.npmModules.push(currentModule);
|
|
554
|
+
if (!isJson) console.log();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
jsonReport.summary = {
|
|
558
|
+
totalChecked,
|
|
559
|
+
upToDate: totalUpToDate,
|
|
560
|
+
minorPatch: totalMinorPatch,
|
|
561
|
+
majorBreaking: totalMajorBreaking
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
if (isJson) {
|
|
565
|
+
console.log(JSON.stringify(jsonReport));
|
|
566
|
+
} else {
|
|
567
|
+
console.log(`${colors.bold}${colors.cyan}----------------------------------------------------------------${colors.reset}`);
|
|
568
|
+
console.log(`${colors.bold}Summary:${colors.reset} Checked ${totalChecked} dependencies:`);
|
|
569
|
+
console.log(` ${colors.green}✓ ${totalUpToDate} Up to date${colors.reset}`);
|
|
570
|
+
if (totalMinorPatch > 0) {
|
|
571
|
+
console.log(` ${colors.yellow}↑ ${totalMinorPatch} Compatible updates available${colors.reset}`);
|
|
572
|
+
}
|
|
573
|
+
if (totalMajorBreaking > 0) {
|
|
574
|
+
console.log(` ${colors.magenta}◆ ${totalMajorBreaking} Upstream/Peer-locked major releases (breaking changes requiring framework-wide migration)${colors.reset}`);
|
|
575
|
+
}
|
|
576
|
+
console.log(`${colors.bold}${colors.cyan}================================================================${colors.reset}\n`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
main().catch((err) => {
|
|
581
|
+
if (isJson) {
|
|
582
|
+
console.error(JSON.stringify({ error: err.message }));
|
|
583
|
+
} else {
|
|
584
|
+
console.error('Error running dependency check:', err);
|
|
585
|
+
}
|
|
586
|
+
process.exit(1);
|
|
587
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
3
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
4
|
+
import { basename, join } from 'node:path';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
6
|
+
|
|
7
|
+
const input = process.argv[2];
|
|
8
|
+
|
|
9
|
+
if (input === '-h' || input === '--help') {
|
|
10
|
+
console.log('Usage: git-dl.mjs <owner/repo|url> [target-dir]');
|
|
11
|
+
console.log('Default target-dir: references/<repo>');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!input) {
|
|
16
|
+
console.error('Error: Provide a GitHub URL or owner/repo');
|
|
17
|
+
console.error('Usage: git-dl.mjs <owner/repo|url> [target-dir]');
|
|
18
|
+
console.error('Default target-dir: references/<repo>');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Clean repo string: remove protocol, domain, .git suffix, and surrounding slashes
|
|
23
|
+
const repo = input
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/^git@github\.com:/, '')
|
|
26
|
+
.replace(/^https?:\/\/github\.com\//, '')
|
|
27
|
+
.replace(/^github\.com\//, '')
|
|
28
|
+
.replace(/\.git$/, '')
|
|
29
|
+
.replace(/^\/+|\/+$/g, '');
|
|
30
|
+
|
|
31
|
+
if (!repo || !repo.includes('/')) {
|
|
32
|
+
console.error(`Error: Invalid GitHub repository format: '${input}'. Expected 'owner/repo' or GitHub URL.`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const defaultDir = join('references', basename(repo));
|
|
37
|
+
const dir = process.argv[3] || defaultDir;
|
|
38
|
+
|
|
39
|
+
// Resolve the latest tag via git ls-remote (version sorted, handles prefixes and monorepos)
|
|
40
|
+
let lsOutput;
|
|
41
|
+
try {
|
|
42
|
+
lsOutput = execFileSync(
|
|
43
|
+
'git',
|
|
44
|
+
['ls-remote', '--tags', '--refs', '--sort=v:refname', `https://github.com/${repo}.git`],
|
|
45
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
46
|
+
);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const errMsg = err.stderr ? err.stderr.trim() : err.message;
|
|
49
|
+
console.error(`Error: Failed to query tags for '${repo}': ${errMsg}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const lines = lsOutput.trim().split('\n').filter(Boolean);
|
|
54
|
+
const lastLine = lines[lines.length - 1];
|
|
55
|
+
let rawTag = null;
|
|
56
|
+
if (lastLine) {
|
|
57
|
+
const parts = lastLine.split(/\s+/);
|
|
58
|
+
if (parts.length >= 2) {
|
|
59
|
+
rawTag = parts[1].replace(/^refs\/tags\//, '');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!rawTag) {
|
|
64
|
+
console.error(`Error: No tags found for '${repo}'.`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Reset target directory
|
|
69
|
+
await rm(dir, { recursive: true, force: true });
|
|
70
|
+
await mkdir(dir, { recursive: true });
|
|
71
|
+
|
|
72
|
+
// Download tarball via the GitHub codeload endpoint
|
|
73
|
+
const tarballUrl = `https://codeload.github.com/${repo}/tar.gz/refs/tags/${rawTag}`;
|
|
74
|
+
const res = await fetch(tarballUrl, {
|
|
75
|
+
headers: {
|
|
76
|
+
'User-Agent': 'git-dl/1.0'
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!res.ok) {
|
|
81
|
+
console.error(`Error: Failed to download tarball from ${tarballUrl} (${res.status} ${res.statusText})`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
const tar = spawn('tar', ['-xz', '-C', dir, '--strip-components=1'], {
|
|
87
|
+
stdio: ['pipe', 'inherit', 'inherit']
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
tar.on('error', (err) => reject(new Error(`Failed to run tar: ${err.message}`)));
|
|
91
|
+
tar.on('close', (code) => {
|
|
92
|
+
if (code === 0) resolve();
|
|
93
|
+
else reject(new Error(`tar extraction exited with code ${code}`));
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
Readable.fromWeb(res.body).pipe(tar.stdin);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const displayDir = dir.startsWith('/') || dir.startsWith('.') ? dir : `./${dir}`;
|
|
100
|
+
console.log(`Extracted ${repo} (${rawTag}) into ${displayDir}`);
|