@ankurhalder/codebase-snapshot 3.0.0 → 4.0.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/README.md +161 -310
- package/dist/bin.js +2247 -709
- package/dist/{chunk-5B4UVQLR.js → chunk-RGIMMEAW.js} +1763 -425
- package/dist/cli.cjs +2212 -673
- package/dist/cli.d.cts +3 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +268 -68
- package/dist/index.cjs +1762 -422
- package/dist/index.d.cts +28 -29
- package/dist/index.d.ts +28 -29
- package/dist/index.js +1 -1
- package/dist/{types-CJqkePM4.d.cts → types-LSfwsKE-.d.cts} +38 -4
- package/dist/{types-CJqkePM4.d.ts → types-LSfwsKE-.d.ts} +38 -4
- package/docs/agent.md +77 -37
- package/docs/cli.md +317 -0
- package/docs/configuration.md +254 -226
- package/docs/install.md +114 -86
- package/docs/output.md +213 -0
- package/docs/troubleshooting.md +277 -0
- package/package.json +7 -4
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,118 @@
|
|
|
1
|
-
import { parseByteSize, GENERATOR_VERSION, ARTIFACT_NAMES, getPreset, unknownPresetMessage, MIN_MAX_PART_BYTES, loadConfigFileDetailed, resolveConfig, validateSnapshot, explainPath, compareManifests, loadManifest, renderDiffText, generateSnapshot, GENERATOR_NAME, runInit, PromptCancelled, SCHEMA_VERSION, PRESETS, renderExplainText } from './chunk-
|
|
1
|
+
import { parseByteSize, GENERATOR_VERSION, ARTIFACT_NAMES, getPreset, unknownPresetMessage, MIN_MAX_PART_BYTES, loadConfigFileDetailed, resolveConfig, validateSnapshot, explainPath, compareManifests, loadManifest, renderDiffText, generateSnapshot, GENERATOR_NAME, runInit, PromptCancelled, SCHEMA_VERSION, PRESETS, renderExplainText } from './chunk-RGIMMEAW.js';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path2 from 'path';
|
|
4
4
|
import { parseArgs } from 'util';
|
|
5
5
|
|
|
6
|
+
// src/agent/option-scope.ts
|
|
7
|
+
var COMMAND_OPTION_SCOPE = {
|
|
8
|
+
generate: [
|
|
9
|
+
"cwd",
|
|
10
|
+
"out-dir",
|
|
11
|
+
"max-part-bytes",
|
|
12
|
+
"large-file-bytes",
|
|
13
|
+
"max-monolith-bytes",
|
|
14
|
+
"include-monolith",
|
|
15
|
+
"include-sensitive-files",
|
|
16
|
+
"skip-artifacts",
|
|
17
|
+
"only-artifacts",
|
|
18
|
+
"no-config",
|
|
19
|
+
"format",
|
|
20
|
+
"mode",
|
|
21
|
+
"dry-run",
|
|
22
|
+
"watch",
|
|
23
|
+
"help",
|
|
24
|
+
"version"
|
|
25
|
+
],
|
|
26
|
+
validate: [
|
|
27
|
+
"cwd",
|
|
28
|
+
"out-dir",
|
|
29
|
+
"max-part-bytes",
|
|
30
|
+
"large-file-bytes",
|
|
31
|
+
"max-monolith-bytes",
|
|
32
|
+
"include-monolith",
|
|
33
|
+
"include-sensitive-files",
|
|
34
|
+
"skip-artifacts",
|
|
35
|
+
"only-artifacts",
|
|
36
|
+
"no-config",
|
|
37
|
+
"format",
|
|
38
|
+
"mode",
|
|
39
|
+
"help",
|
|
40
|
+
"version"
|
|
41
|
+
],
|
|
42
|
+
report: [
|
|
43
|
+
"cwd",
|
|
44
|
+
"out-dir",
|
|
45
|
+
"max-part-bytes",
|
|
46
|
+
"large-file-bytes",
|
|
47
|
+
"max-monolith-bytes",
|
|
48
|
+
"include-monolith",
|
|
49
|
+
"include-sensitive-files",
|
|
50
|
+
"skip-artifacts",
|
|
51
|
+
"only-artifacts",
|
|
52
|
+
"no-config",
|
|
53
|
+
"format",
|
|
54
|
+
"mode",
|
|
55
|
+
"help",
|
|
56
|
+
"version"
|
|
57
|
+
],
|
|
58
|
+
explain: [
|
|
59
|
+
"cwd",
|
|
60
|
+
"out-dir",
|
|
61
|
+
"max-part-bytes",
|
|
62
|
+
"large-file-bytes",
|
|
63
|
+
"max-monolith-bytes",
|
|
64
|
+
"include-monolith",
|
|
65
|
+
"include-sensitive-files",
|
|
66
|
+
"skip-artifacts",
|
|
67
|
+
"only-artifacts",
|
|
68
|
+
"no-config",
|
|
69
|
+
"format",
|
|
70
|
+
"mode",
|
|
71
|
+
"help",
|
|
72
|
+
"version"
|
|
73
|
+
],
|
|
74
|
+
diff: ["format", "mode", "help", "version"],
|
|
75
|
+
describe: ["format", "mode", "help", "version"],
|
|
76
|
+
init: [
|
|
77
|
+
"cwd",
|
|
78
|
+
"out-dir",
|
|
79
|
+
"max-part-bytes",
|
|
80
|
+
"include-sensitive-files",
|
|
81
|
+
"skip-artifacts",
|
|
82
|
+
"only-artifacts",
|
|
83
|
+
"preset",
|
|
84
|
+
"include",
|
|
85
|
+
"exclude",
|
|
86
|
+
"yes",
|
|
87
|
+
"minimal",
|
|
88
|
+
"force",
|
|
89
|
+
"dry-run",
|
|
90
|
+
"format",
|
|
91
|
+
"mode",
|
|
92
|
+
"help",
|
|
93
|
+
"version"
|
|
94
|
+
],
|
|
95
|
+
skill: ["cwd", "format", "mode", "install", "target", "help", "version"]
|
|
96
|
+
};
|
|
97
|
+
var COMMAND_ORDER = [
|
|
98
|
+
"generate",
|
|
99
|
+
"validate",
|
|
100
|
+
"report",
|
|
101
|
+
"explain",
|
|
102
|
+
"init",
|
|
103
|
+
"diff",
|
|
104
|
+
"describe",
|
|
105
|
+
"skill"
|
|
106
|
+
];
|
|
107
|
+
function commandsForOption(option) {
|
|
108
|
+
return COMMAND_ORDER.filter((command) => COMMAND_OPTION_SCOPE[command].includes(option));
|
|
109
|
+
}
|
|
110
|
+
function optionAppliesTo(option) {
|
|
111
|
+
const commands = commandsForOption(option);
|
|
112
|
+
if (commands.length === COMMAND_ORDER.length) return "all commands";
|
|
113
|
+
return commands.join("/");
|
|
114
|
+
}
|
|
115
|
+
|
|
6
116
|
// src/agent/contract.ts
|
|
7
117
|
var CONTRACT_VERSION = "1.0.0";
|
|
8
118
|
var PACKAGE_NAME = "@ankurhalder/codebase-snapshot";
|
|
@@ -92,11 +202,11 @@ function buildAgentContract() {
|
|
|
92
202
|
invocation: {
|
|
93
203
|
oneShot: `npx ${PACKAGE_NAME}@latest`,
|
|
94
204
|
installed: "npx codebase-snapshot",
|
|
95
|
-
bins: ["codebase-snapshot"],
|
|
205
|
+
bins: ["codebase-snapshot", "snap"],
|
|
96
206
|
notes: [
|
|
97
207
|
`The bare package name also works: npx ${PACKAGE_NAME} <command>.`,
|
|
98
|
-
"Pin @latest (or @
|
|
99
|
-
"
|
|
208
|
+
"Pin @latest (or @4) when you need the exact same version on every machine.",
|
|
209
|
+
"Both bin names point at the same file. npm resolves a package by its bin entries, so they must stay aliases of one executable.",
|
|
100
210
|
"Human progress goes to stderr. With --format json the payload on stdout is pure JSON."
|
|
101
211
|
]
|
|
102
212
|
},
|
|
@@ -105,7 +215,7 @@ function buildAgentContract() {
|
|
|
105
215
|
step: 1,
|
|
106
216
|
id: "verify",
|
|
107
217
|
run: `npx ${PACKAGE_NAME}@latest --version`,
|
|
108
|
-
expect:
|
|
218
|
+
expect: `prints v${GENERATOR_VERSION} on stdout and exits 0`,
|
|
109
219
|
notes: "If it prints nothing, clear the npm cache and retry, or use `npx -p <pkg> codebase-snapshot`."
|
|
110
220
|
},
|
|
111
221
|
{
|
|
@@ -175,9 +285,9 @@ function buildAgentContract() {
|
|
|
175
285
|
{
|
|
176
286
|
name: "explain",
|
|
177
287
|
aliases: [],
|
|
178
|
-
summary: "Explain how one path would be treated: layer, kind, part, skip reason.",
|
|
288
|
+
summary: "Explain how one path would be treated: layer, kind, part, skip reason. Always exits 0 when it can report; read `included` and `reason` instead of the exit code.",
|
|
179
289
|
positionals: ["<path>"],
|
|
180
|
-
exits: [0
|
|
290
|
+
exits: [0]
|
|
181
291
|
},
|
|
182
292
|
{
|
|
183
293
|
name: "diff",
|
|
@@ -211,140 +321,147 @@ function buildAgentContract() {
|
|
|
211
321
|
flag: "--out-dir",
|
|
212
322
|
type: "string",
|
|
213
323
|
default: "snapshot",
|
|
214
|
-
appliesTo: "
|
|
324
|
+
appliesTo: optionAppliesTo("out-dir"),
|
|
215
325
|
summary: "Where the snapshot is written. Must not be the repo root or a directory the tool did not create."
|
|
216
326
|
},
|
|
217
327
|
{
|
|
218
328
|
flag: "--max-part-bytes",
|
|
219
329
|
type: "string",
|
|
220
330
|
default: 307200,
|
|
221
|
-
appliesTo: "
|
|
331
|
+
appliesTo: optionAppliesTo("max-part-bytes"),
|
|
222
332
|
summary: 'Byte budget per markdown part. Accepts "120k" / "1.5mb". Minimum 4096.'
|
|
223
333
|
},
|
|
224
334
|
{
|
|
225
335
|
flag: "--large-file-bytes",
|
|
226
336
|
type: "string",
|
|
227
337
|
default: 204800,
|
|
228
|
-
appliesTo: "
|
|
338
|
+
appliesTo: optionAppliesTo("large-file-bytes"),
|
|
229
339
|
summary: "Above this size a file gets a head/tail preview instead of the full body."
|
|
230
340
|
},
|
|
231
341
|
{
|
|
232
342
|
flag: "--max-monolith-bytes",
|
|
233
343
|
type: "string",
|
|
234
344
|
default: "unset",
|
|
235
|
-
appliesTo: "
|
|
345
|
+
appliesTo: optionAppliesTo("max-monolith-bytes"),
|
|
236
346
|
summary: "Skip the monolith artifacts when they would exceed this size."
|
|
237
347
|
},
|
|
238
348
|
{
|
|
239
349
|
flag: "--include-monolith",
|
|
240
350
|
type: "boolean",
|
|
241
351
|
default: false,
|
|
242
|
-
appliesTo: "
|
|
352
|
+
appliesTo: optionAppliesTo("include-monolith"),
|
|
243
353
|
summary: "Also write full-codebase.json and full-codebase.md."
|
|
244
354
|
},
|
|
245
355
|
{
|
|
246
356
|
flag: "--include-sensitive-files",
|
|
247
357
|
type: "boolean",
|
|
248
358
|
default: false,
|
|
249
|
-
appliesTo: "
|
|
359
|
+
appliesTo: optionAppliesTo("include-sensitive-files"),
|
|
250
360
|
summary: "Also emit .env / *.pem / .npmrc style files. Off by default; they are recorded as skipped."
|
|
251
361
|
},
|
|
252
362
|
{
|
|
253
363
|
flag: "--skip-artifacts",
|
|
254
364
|
type: "string",
|
|
255
365
|
default: "unset",
|
|
256
|
-
appliesTo: "
|
|
366
|
+
appliesTo: optionAppliesTo("skip-artifacts"),
|
|
257
367
|
summary: "Comma-separated artifacts to omit (readme, manifest, parts, navigation, projectTree, audit, monolith)."
|
|
258
368
|
},
|
|
259
369
|
{
|
|
260
370
|
flag: "--only-artifacts",
|
|
261
371
|
type: "string",
|
|
262
372
|
default: "unset",
|
|
263
|
-
appliesTo: "
|
|
373
|
+
appliesTo: optionAppliesTo("only-artifacts"),
|
|
264
374
|
summary: "Write only these artifacts. Cannot be combined with --skip-artifacts."
|
|
265
375
|
},
|
|
266
376
|
{
|
|
267
377
|
flag: "--format",
|
|
268
378
|
type: "string",
|
|
269
379
|
default: "text",
|
|
270
|
-
appliesTo: "
|
|
380
|
+
appliesTo: optionAppliesTo("format"),
|
|
271
381
|
summary: 'Output format: "text" or "json". JSON goes to stdout, progress to stderr.'
|
|
272
382
|
},
|
|
273
383
|
{
|
|
274
384
|
flag: "--cwd",
|
|
275
385
|
type: "string",
|
|
276
386
|
default: "process.cwd()",
|
|
277
|
-
appliesTo: "
|
|
387
|
+
appliesTo: optionAppliesTo("cwd"),
|
|
278
388
|
summary: "Repository to operate on. Must be an existing directory."
|
|
279
389
|
},
|
|
280
390
|
{
|
|
281
391
|
flag: "--dry-run",
|
|
282
392
|
type: "boolean",
|
|
283
393
|
default: false,
|
|
284
|
-
appliesTo: "
|
|
394
|
+
appliesTo: optionAppliesTo("dry-run"),
|
|
285
395
|
summary: "Compute everything and print the result without writing."
|
|
286
396
|
},
|
|
287
397
|
{
|
|
288
398
|
flag: "--watch",
|
|
289
399
|
type: "boolean",
|
|
290
400
|
default: false,
|
|
291
|
-
appliesTo: "
|
|
401
|
+
appliesTo: optionAppliesTo("watch"),
|
|
292
402
|
summary: "Regenerate on every change until interrupted."
|
|
293
403
|
},
|
|
404
|
+
{
|
|
405
|
+
flag: "--no-config",
|
|
406
|
+
type: "boolean",
|
|
407
|
+
default: false,
|
|
408
|
+
appliesTo: optionAppliesTo("no-config"),
|
|
409
|
+
summary: "Ignore the config file and run on the built-in defaults."
|
|
410
|
+
},
|
|
294
411
|
{
|
|
295
412
|
flag: "--preset",
|
|
296
413
|
type: "string",
|
|
297
414
|
default: "detected",
|
|
298
|
-
appliesTo: "
|
|
415
|
+
appliesTo: optionAppliesTo("preset"),
|
|
299
416
|
summary: "Force a project preset instead of detecting one. Unknown names are a usage error."
|
|
300
417
|
},
|
|
301
418
|
{
|
|
302
419
|
flag: "--include",
|
|
303
420
|
type: "string",
|
|
304
421
|
default: "unset",
|
|
305
|
-
appliesTo: "
|
|
422
|
+
appliesTo: optionAppliesTo("include"),
|
|
306
423
|
summary: "Comma-separated allow-list of globs to record in the generated config."
|
|
307
424
|
},
|
|
308
425
|
{
|
|
309
426
|
flag: "--exclude",
|
|
310
427
|
type: "string",
|
|
311
428
|
default: "unset",
|
|
312
|
-
appliesTo: "
|
|
429
|
+
appliesTo: optionAppliesTo("exclude"),
|
|
313
430
|
summary: "Comma-separated globs to record in the generated config."
|
|
314
431
|
},
|
|
315
432
|
{
|
|
316
433
|
flag: "--yes",
|
|
317
434
|
type: "boolean",
|
|
318
435
|
default: false,
|
|
319
|
-
appliesTo: "
|
|
436
|
+
appliesTo: optionAppliesTo("yes"),
|
|
320
437
|
summary: "Accept every default without prompting. Required in a non-TTY."
|
|
321
438
|
},
|
|
322
439
|
{
|
|
323
440
|
flag: "--minimal",
|
|
324
441
|
type: "boolean",
|
|
325
442
|
default: false,
|
|
326
|
-
appliesTo: "
|
|
443
|
+
appliesTo: optionAppliesTo("minimal"),
|
|
327
444
|
summary: "Write only the settings that differ from the defaults."
|
|
328
445
|
},
|
|
329
446
|
{
|
|
330
447
|
flag: "--force",
|
|
331
448
|
type: "boolean",
|
|
332
449
|
default: false,
|
|
333
|
-
appliesTo: "
|
|
450
|
+
appliesTo: optionAppliesTo("force"),
|
|
334
451
|
summary: "Overwrite an existing config file."
|
|
335
452
|
},
|
|
336
453
|
{
|
|
337
454
|
flag: "--install",
|
|
338
455
|
type: "boolean",
|
|
339
456
|
default: false,
|
|
340
|
-
appliesTo: "
|
|
457
|
+
appliesTo: optionAppliesTo("install"),
|
|
341
458
|
summary: "Write the skill into the repository instead of printing it."
|
|
342
459
|
},
|
|
343
460
|
{
|
|
344
461
|
flag: "--target",
|
|
345
462
|
type: "string",
|
|
346
463
|
default: "all",
|
|
347
|
-
appliesTo: "
|
|
464
|
+
appliesTo: optionAppliesTo("target"),
|
|
348
465
|
summary: 'Where to install: "claude", "agents", "cursor" or "all".'
|
|
349
466
|
}
|
|
350
467
|
],
|
|
@@ -419,8 +536,8 @@ function buildAgentContract() {
|
|
|
419
536
|
troubleshooting: [
|
|
420
537
|
{
|
|
421
538
|
symptom: "Command prints nothing and exits 0",
|
|
422
|
-
cause: "
|
|
423
|
-
fix: `
|
|
539
|
+
cause: "A stale global install of this package (often a leftover `npm link`) is shadowing npx: npm runs the global binary instead of installing the published one.",
|
|
540
|
+
fix: "Check `npm ls -g --depth=0` for @ankurhalder/codebase-snapshot and remove it with `npm rm -g @ankurhalder/codebase-snapshot`. If it is not installed globally, clear the npx cache with `npm cache clean --force` and retry."
|
|
424
541
|
},
|
|
425
542
|
{
|
|
426
543
|
symptom: "Exit 1 with 'Refusing to replace outDir'",
|
|
@@ -494,7 +611,8 @@ not resolved \u2014 clear the npm cache and retry; do not assume the repository
|
|
|
494
611
|
\`--format json\` if you want to parse it.
|
|
495
612
|
|
|
496
613
|
4. **Configure (recommended).** Run \`npx ${PACKAGE_NAME2}@latest init --yes\`. It detects
|
|
497
|
-
the project shape, then writes \`snapshot.config.ts\`
|
|
614
|
+
the project shape, then writes \`snapshot.config.ts\` (or \`.mjs\` when the project is
|
|
615
|
+
not an ES module) listing **every** setting at its
|
|
498
616
|
current value, commented. Pass \`--preset <name>\` only if detection is wrong; the
|
|
499
617
|
preset names are in the contract. This step is optional: without a config the tool
|
|
500
618
|
runs on the same defaults.
|
|
@@ -595,14 +713,24 @@ function mergeAgentsFile(cwd, skill) {
|
|
|
595
713
|
next = `# AGENTS.md
|
|
596
714
|
|
|
597
715
|
${block}`;
|
|
598
|
-
} else
|
|
716
|
+
} else {
|
|
717
|
+
const starts = existing.split(AGENTS_MARKER_START).length - 1;
|
|
718
|
+
const ends = existing.split(AGENTS_MARKER_END).length - 1;
|
|
599
719
|
const start = existing.indexOf(AGENTS_MARKER_START);
|
|
600
720
|
const end = existing.indexOf(AGENTS_MARKER_END) + AGENTS_MARKER_END.length;
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
721
|
+
if (starts === 1 && ends === 1 && start < end) {
|
|
722
|
+
next = `${existing.slice(0, start)}${block.trimEnd()}${existing.slice(end)}`;
|
|
723
|
+
} else if (starts > 0 && ends === 0) {
|
|
724
|
+
next = `${existing.slice(0, start)}${block.trimEnd()}${existing.slice(
|
|
725
|
+
start + AGENTS_MARKER_START.length
|
|
726
|
+
)}`;
|
|
727
|
+
} else if (existing.includes(block.trimEnd())) {
|
|
728
|
+
next = existing;
|
|
729
|
+
} else {
|
|
730
|
+
next = `${existing.trimEnd()}
|
|
604
731
|
|
|
605
732
|
${block}`;
|
|
733
|
+
}
|
|
606
734
|
}
|
|
607
735
|
const action = existing === null ? "created" : existing === next ? "unchanged" : "updated";
|
|
608
736
|
if (action !== "unchanged") {
|
|
@@ -665,6 +793,7 @@ var CLI_OPTIONS = {
|
|
|
665
793
|
mode: { type: "string" },
|
|
666
794
|
"dry-run": { type: "boolean" },
|
|
667
795
|
watch: { type: "boolean" },
|
|
796
|
+
"no-config": { type: "boolean" },
|
|
668
797
|
preset: { type: "string" },
|
|
669
798
|
include: { type: "string" },
|
|
670
799
|
exclude: { type: "string" },
|
|
@@ -711,6 +840,7 @@ Options:
|
|
|
711
840
|
--format <text|json> CLI output format (default: text)
|
|
712
841
|
--dry-run Compute the snapshot without writing anything
|
|
713
842
|
--watch Re-generate whenever the repository changes
|
|
843
|
+
--no-config Ignore snapshot.config.* and use the built-in defaults
|
|
714
844
|
|
|
715
845
|
init only:
|
|
716
846
|
--preset <name> Force a project preset instead of detecting one
|
|
@@ -872,28 +1002,32 @@ function parseCliArgs(argv) {
|
|
|
872
1002
|
} else if (rawSkip !== void 0) {
|
|
873
1003
|
skipArtifacts = parseArtifactList(String(rawSkip), "--skip-artifacts");
|
|
874
1004
|
}
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
if (
|
|
879
|
-
|
|
880
|
-
if (
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1005
|
+
const scope = COMMAND_OPTION_SCOPE[command];
|
|
1006
|
+
const ignored = [];
|
|
1007
|
+
for (const name of Object.keys(CLI_OPTIONS)) {
|
|
1008
|
+
if (scope.includes(name)) continue;
|
|
1009
|
+
const value = values[name];
|
|
1010
|
+
if (value === void 0 || value === false) continue;
|
|
1011
|
+
ignored.push(`--${name}`);
|
|
1012
|
+
}
|
|
1013
|
+
if (ignored.length > 0) {
|
|
1014
|
+
const owners = Array.from(
|
|
1015
|
+
new Set(
|
|
1016
|
+
ignored.flatMap(
|
|
1017
|
+
(flag) => commandsForOption(flag.replace(/^--/, "")).map((owner) => `\`${owner}\``)
|
|
1018
|
+
)
|
|
1019
|
+
)
|
|
1020
|
+
);
|
|
1021
|
+
const hint = ignored.some((flag) => ["--preset", "--include", "--exclude"].includes(flag)) ? " To scope a run, set include/excludePatterns/preset in snapshot.config.ts (or run `codebase-snapshot init` to generate one)." : "";
|
|
1022
|
+
throw new UsageError(
|
|
1023
|
+
`${ignored.join(", ")} ${ignored.length === 1 ? "does" : "do"} not apply to \`${command}\` and ${ignored.length === 1 ? "was" : "were"} not applied. ${ignored.length === 1 ? "It is" : "They are"} accepted by: ${owners.join(", ")}.${hint}`
|
|
1024
|
+
);
|
|
888
1025
|
}
|
|
889
1026
|
if (command === "init" && values.preset !== void 0) {
|
|
890
1027
|
const requested = String(values.preset);
|
|
891
1028
|
if (!getPreset(requested)) throw new UsageError(unknownPresetMessage(requested));
|
|
892
1029
|
}
|
|
893
|
-
if (command
|
|
894
|
-
throw new UsageError("--install and --target are only valid with `skill`.");
|
|
895
|
-
}
|
|
896
|
-
if (values.target !== void 0 && !isSkillTarget(String(values.target))) {
|
|
1030
|
+
if (command === "skill" && values.target !== void 0 && !isSkillTarget(String(values.target))) {
|
|
897
1031
|
throw new UsageError(
|
|
898
1032
|
`--target must be one of: ${SKILL_TARGETS.join(", ")} (received "${String(values.target)}").`
|
|
899
1033
|
);
|
|
@@ -904,6 +1038,7 @@ function parseCliArgs(argv) {
|
|
|
904
1038
|
format: rawFormat,
|
|
905
1039
|
dryRun: values["dry-run"] === true,
|
|
906
1040
|
watch: values.watch === true,
|
|
1041
|
+
noConfig: values["no-config"] === true,
|
|
907
1042
|
preset: values.preset === void 0 ? void 0 : String(values.preset),
|
|
908
1043
|
maxPartBytesRaw: values["max-part-bytes"] === void 0 ? void 0 : String(values["max-part-bytes"]),
|
|
909
1044
|
include: values.include === void 0 ? void 0 : String(values.include),
|
|
@@ -957,6 +1092,12 @@ function splitList(value) {
|
|
|
957
1092
|
if (value === void 0) return void 0;
|
|
958
1093
|
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
959
1094
|
}
|
|
1095
|
+
function displayOutPath(cwd, outDir, ...segments) {
|
|
1096
|
+
const absolute = path2.resolve(cwd, outDir, ...segments);
|
|
1097
|
+
const relative2 = path2.relative(cwd, absolute);
|
|
1098
|
+
if (relative2 && !relative2.startsWith("..") && !path2.isAbsolute(relative2)) return relative2;
|
|
1099
|
+
return absolute;
|
|
1100
|
+
}
|
|
960
1101
|
function describeManifestShapeProblem(manifest) {
|
|
961
1102
|
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
962
1103
|
return "not a JSON object";
|
|
@@ -976,6 +1117,7 @@ async function runInitCommand(invocation) {
|
|
|
976
1117
|
const result = await runInit({
|
|
977
1118
|
cwd: invocation.cwd,
|
|
978
1119
|
preset: invocation.preset,
|
|
1120
|
+
outDir: invocation.outDir,
|
|
979
1121
|
maxPartBytes: invocation.maxPartBytesRaw ?? invocation.maxPartBytes,
|
|
980
1122
|
include: splitList(invocation.include),
|
|
981
1123
|
exclude: splitList(invocation.exclude),
|
|
@@ -1002,7 +1144,11 @@ async function runInitCommand(invocation) {
|
|
|
1002
1144
|
}
|
|
1003
1145
|
for (const note of result.notes) info(` \u26A0 ${note}`);
|
|
1004
1146
|
info("");
|
|
1005
|
-
|
|
1147
|
+
if (result.scripts.length > 0) {
|
|
1148
|
+
info(" Next: npm run snapshot");
|
|
1149
|
+
} else {
|
|
1150
|
+
info(` Next: ${GENERATOR_NAME} generate`);
|
|
1151
|
+
}
|
|
1006
1152
|
info(
|
|
1007
1153
|
` Tip: \`${GENERATOR_NAME} skill --install\` makes this repo discoverable by AI agents.`
|
|
1008
1154
|
);
|
|
@@ -1018,6 +1164,13 @@ async function runInitCommand(invocation) {
|
|
|
1018
1164
|
}
|
|
1019
1165
|
function runSkillCommand(invocation) {
|
|
1020
1166
|
if (!invocation.install) {
|
|
1167
|
+
if (invocation.format === "json") {
|
|
1168
|
+
process.stdout.write(
|
|
1169
|
+
`${JSON.stringify({ name: SKILL_NAME, version: GENERATOR_VERSION, skill: renderSkill() }, null, 2)}
|
|
1170
|
+
`
|
|
1171
|
+
);
|
|
1172
|
+
return 0;
|
|
1173
|
+
}
|
|
1021
1174
|
process.stdout.write(renderSkill());
|
|
1022
1175
|
return 0;
|
|
1023
1176
|
}
|
|
@@ -1094,7 +1247,8 @@ ${USAGE}
|
|
|
1094
1247
|
if (invocation.command === "skill") {
|
|
1095
1248
|
return runSkillCommand(invocation);
|
|
1096
1249
|
}
|
|
1097
|
-
const loaded = await loadConfigFileDetailed(invocation.cwd);
|
|
1250
|
+
const loaded = invocation.noConfig ? { config: null, warnings: [], errors: [], file: null } : await loadConfigFileDetailed(invocation.cwd);
|
|
1251
|
+
if (invocation.noConfig) info("\u2699 --no-config: using built-in defaults, config file ignored");
|
|
1098
1252
|
for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
|
|
1099
1253
|
if (loaded.errors.length > 0) {
|
|
1100
1254
|
warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
|
|
@@ -1141,8 +1295,7 @@ ${USAGE}
|
|
|
1141
1295
|
warn(`\u274C ${manifestPath} is not a snapshot manifest: ${shapeError}`);
|
|
1142
1296
|
return 1;
|
|
1143
1297
|
}
|
|
1144
|
-
|
|
1145
|
-
emit(invocation.format, { manifest, validation }, () => {
|
|
1298
|
+
emit(invocation.format, { manifest, validation: null }, () => {
|
|
1146
1299
|
info(`
|
|
1147
1300
|
\u{1F4CA} Codebase Snapshot Report (${resolved.cwd})`);
|
|
1148
1301
|
info(` - Generated at: ${manifest.generatedAt}`);
|
|
@@ -1154,19 +1307,19 @@ ${USAGE}
|
|
|
1154
1307
|
` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
|
|
1155
1308
|
);
|
|
1156
1309
|
info(` - Total parts: ${manifest.counts.partsCount}`);
|
|
1157
|
-
info(
|
|
1310
|
+
info(" - Integrity: not checked (run `codebase-snapshot validate`)");
|
|
1158
1311
|
info(` - Warnings: ${manifest.warnings?.length ?? 0}`);
|
|
1159
1312
|
info("\n Layers:");
|
|
1160
1313
|
for (const [layer, count] of Object.entries(manifest.layers)) {
|
|
1161
1314
|
info(` - ${layer.padEnd(16)}: ${count} files`);
|
|
1162
1315
|
}
|
|
1163
1316
|
});
|
|
1164
|
-
return
|
|
1317
|
+
return 0;
|
|
1165
1318
|
}
|
|
1166
1319
|
if (invocation.command === "explain") {
|
|
1167
1320
|
const result = explainPath(resolved, invocation.args[0]);
|
|
1168
1321
|
emit(invocation.format, result, () => info(renderExplainText(result)));
|
|
1169
|
-
return
|
|
1322
|
+
return 0;
|
|
1170
1323
|
}
|
|
1171
1324
|
if (invocation.command === "diff") {
|
|
1172
1325
|
try {
|
|
@@ -1190,7 +1343,9 @@ ${USAGE}
|
|
|
1190
1343
|
if (invocation.dryRun) {
|
|
1191
1344
|
info("\n\u{1F9EA} Dry run \u2014 nothing was written:");
|
|
1192
1345
|
info(` - Would track: ${manifest.counts.totalFiles} files`);
|
|
1193
|
-
info(
|
|
1346
|
+
info(
|
|
1347
|
+
` - Would write: ${manifest.counts.partsCount} part(s) under ${displayOutPath(resolved.cwd, resolved.outDir, "parts")}`
|
|
1348
|
+
);
|
|
1194
1349
|
info(` - Skipped: ${manifest.counts.skippedCount} file(s)`);
|
|
1195
1350
|
info(` - Warnings: ${manifest.warnings.length}`);
|
|
1196
1351
|
info(` - Integrity: ${manifest.validation.status}`);
|
|
@@ -1205,7 +1360,9 @@ ${USAGE}
|
|
|
1205
1360
|
);
|
|
1206
1361
|
const { artifacts } = resolved;
|
|
1207
1362
|
if (artifacts.parts) {
|
|
1208
|
-
info(
|
|
1363
|
+
info(
|
|
1364
|
+
` - Parts written: ${manifest.counts.partsCount} under ${displayOutPath(resolved.cwd, resolved.outDir, "parts")}/`
|
|
1365
|
+
);
|
|
1209
1366
|
}
|
|
1210
1367
|
const reports = [
|
|
1211
1368
|
artifacts.projectTree && "project-tree",
|
|
@@ -1213,10 +1370,28 @@ ${USAGE}
|
|
|
1213
1370
|
artifacts.audit && "audit"
|
|
1214
1371
|
].filter(Boolean);
|
|
1215
1372
|
if (reports.length > 0) {
|
|
1216
|
-
info(
|
|
1373
|
+
info(
|
|
1374
|
+
` - Reports: ${displayOutPath(resolved.cwd, resolved.outDir, "reports")}/ (${reports.join(", ")})`
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
if (artifacts.manifest) {
|
|
1378
|
+
info(` - Manifest: ${displayOutPath(resolved.cwd, resolved.outDir, "manifest.json")}`);
|
|
1379
|
+
}
|
|
1380
|
+
if (artifacts.monolith) {
|
|
1381
|
+
const monolithFiles = [
|
|
1382
|
+
manifest.artifacts?.fullJson ? "full-codebase.json" : null,
|
|
1383
|
+
manifest.artifacts?.fullMarkdown ? "full-codebase.md" : null
|
|
1384
|
+
].filter((name) => name !== null);
|
|
1385
|
+
if (monolithFiles.length === 2) {
|
|
1386
|
+
info(
|
|
1387
|
+
` - Monolith: ${displayOutPath(resolved.cwd, resolved.outDir, "full-codebase.{json,md}")}`
|
|
1388
|
+
);
|
|
1389
|
+
} else if (monolithFiles.length === 1) {
|
|
1390
|
+
info(
|
|
1391
|
+
` - Monolith: ${displayOutPath(resolved.cwd, resolved.outDir, monolithFiles[0])} (the other was skipped by --max-monolith-bytes)`
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1217
1394
|
}
|
|
1218
|
-
if (artifacts.manifest) info(` - Manifest: ${resolved.outDir}/manifest.json`);
|
|
1219
|
-
if (artifacts.monolith) info(` - Monolith: ${resolved.outDir}/full-codebase.{json,md}`);
|
|
1220
1395
|
if (reports.length === 0 && !artifacts.parts && !artifacts.manifest && !artifacts.monolith) {
|
|
1221
1396
|
info(` - Artifacts: all disabled (only the ownership marker was written)`);
|
|
1222
1397
|
}
|
|
@@ -1251,18 +1426,43 @@ ${USAGE}
|
|
|
1251
1426
|
async function runWatchLoop(invocation, resolved) {
|
|
1252
1427
|
const outDir = path2.resolve(resolved.cwd, resolved.outDir);
|
|
1253
1428
|
const tmpDir = path2.resolve(resolved.cwd, resolved.tmpDir);
|
|
1429
|
+
const VCS_SEGMENTS = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr"]);
|
|
1254
1430
|
const outName = path2.basename(outDir).toLowerCase();
|
|
1255
1431
|
const tmpName = path2.basename(tmpDir).toLowerCase();
|
|
1256
1432
|
const isGenerated = (candidate) => candidate.toLowerCase().split(/[\\/]/).some(
|
|
1257
|
-
(segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`)
|
|
1433
|
+
(segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`) || VCS_SEGMENTS.has(segment)
|
|
1258
1434
|
);
|
|
1259
1435
|
let running = false;
|
|
1260
|
-
const regenerate = () => {
|
|
1436
|
+
const regenerate = async () => {
|
|
1261
1437
|
if (running) return;
|
|
1262
1438
|
running = true;
|
|
1263
1439
|
const started = performance.now();
|
|
1264
1440
|
try {
|
|
1265
|
-
const
|
|
1441
|
+
const loaded = await loadConfigFileDetailed(invocation.cwd);
|
|
1442
|
+
if (loaded.errors.length > 0) {
|
|
1443
|
+
for (const error of loaded.errors) warn(`\u26A0 Config: ${error}`);
|
|
1444
|
+
warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
|
|
1445
|
+
return;
|
|
1446
|
+
}
|
|
1447
|
+
for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
|
|
1448
|
+
const next = resolveConfig(
|
|
1449
|
+
loaded.config,
|
|
1450
|
+
{
|
|
1451
|
+
mode: invocation.command,
|
|
1452
|
+
format: invocation.format,
|
|
1453
|
+
dryRun: invocation.dryRun,
|
|
1454
|
+
cwd: invocation.cwd,
|
|
1455
|
+
outDir: invocation.outDir,
|
|
1456
|
+
includeMonolith: invocation.includeMonolith,
|
|
1457
|
+
includeSensitiveFiles: invocation.includeSensitiveFiles,
|
|
1458
|
+
maxPartBytes: invocation.maxPartBytes,
|
|
1459
|
+
largeFileBytes: invocation.largeFileBytes,
|
|
1460
|
+
maxMonolithBytes: invocation.maxMonolithBytes,
|
|
1461
|
+
skipArtifacts: invocation.skipArtifacts
|
|
1462
|
+
},
|
|
1463
|
+
invocation.cwd
|
|
1464
|
+
);
|
|
1465
|
+
const manifest = generateSnapshot(next, { dryRun: invocation.dryRun });
|
|
1266
1466
|
const elapsed = ((performance.now() - started) / 1e3).toFixed(2);
|
|
1267
1467
|
info(
|
|
1268
1468
|
`\u267B\uFE0F ${(/* @__PURE__ */ new Date()).toISOString()} ${manifest.counts.totalFiles} file(s), ${manifest.counts.partsCount} part(s), ${manifest.validation.status} (${elapsed}s)`
|
|
@@ -1274,7 +1474,7 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
1274
1474
|
}
|
|
1275
1475
|
};
|
|
1276
1476
|
info("\u{1F440} Watching for changes. Press Ctrl+C to stop.");
|
|
1277
|
-
regenerate();
|
|
1477
|
+
void regenerate();
|
|
1278
1478
|
let timer = null;
|
|
1279
1479
|
const watcher = fs.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
|
|
1280
1480
|
if (filename) {
|
|
@@ -1282,7 +1482,7 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
1282
1482
|
if (isGenerated(changed)) return;
|
|
1283
1483
|
}
|
|
1284
1484
|
if (timer) clearTimeout(timer);
|
|
1285
|
-
timer = setTimeout(regenerate, 250);
|
|
1485
|
+
timer = setTimeout(() => void regenerate(), 250);
|
|
1286
1486
|
});
|
|
1287
1487
|
await new Promise((resolve2) => {
|
|
1288
1488
|
const stop = () => {
|