@ankurhalder/codebase-snapshot 2.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -309
- package/dist/bin.js +1946 -577
- package/dist/{chunk-5I564BGX.js → chunk-FM4OOQPF.js} +1558 -390
- package/dist/cli.cjs +1914 -544
- package/dist/cli.d.cts +3 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +272 -71
- package/dist/index.cjs +1557 -387
- package/dist/index.d.cts +22 -28
- package/dist/index.d.ts +22 -28
- package/dist/index.js +1 -1
- package/dist/{types-CJqkePM4.d.cts → types-BdpT_4b8.d.cts} +37 -4
- package/dist/{types-CJqkePM4.d.ts → types-BdpT_4b8.d.ts} +37 -4
- package/docs/agent.md +78 -38
- package/docs/cli.md +315 -0
- package/docs/configuration.md +254 -226
- package/docs/install.md +115 -84
- package/docs/output.md +213 -0
- package/docs/troubleshooting.md +277 -0
- package/package.json +5 -3
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-FM4OOQPF.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";
|
|
@@ -91,11 +201,12 @@ function buildAgentContract() {
|
|
|
91
201
|
requires: { node: ">=18.0.0", dependencies: ["typescript"] },
|
|
92
202
|
invocation: {
|
|
93
203
|
oneShot: `npx ${PACKAGE_NAME}@latest`,
|
|
94
|
-
installed: "npx
|
|
95
|
-
bins: ["
|
|
204
|
+
installed: "npx codebase-snapshot",
|
|
205
|
+
bins: ["codebase-snapshot", "snap"],
|
|
96
206
|
notes: [
|
|
97
|
-
`
|
|
98
|
-
"
|
|
207
|
+
`The bare package name also works: npx ${PACKAGE_NAME} <command>.`,
|
|
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.",
|
|
99
210
|
"Human progress goes to stderr. With --format json the payload on stdout is pure JSON."
|
|
100
211
|
]
|
|
101
212
|
},
|
|
@@ -104,8 +215,8 @@ function buildAgentContract() {
|
|
|
104
215
|
step: 1,
|
|
105
216
|
id: "verify",
|
|
106
217
|
run: `npx ${PACKAGE_NAME}@latest --version`,
|
|
107
|
-
expect:
|
|
108
|
-
notes: "If it prints nothing,
|
|
218
|
+
expect: `prints v${GENERATOR_VERSION} on stdout and exits 0`,
|
|
219
|
+
notes: "If it prints nothing, clear the npm cache and retry, or use `npx -p <pkg> codebase-snapshot`."
|
|
109
220
|
},
|
|
110
221
|
{
|
|
111
222
|
step: 2,
|
|
@@ -174,9 +285,9 @@ function buildAgentContract() {
|
|
|
174
285
|
{
|
|
175
286
|
name: "explain",
|
|
176
287
|
aliases: [],
|
|
177
|
-
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.",
|
|
178
289
|
positionals: ["<path>"],
|
|
179
|
-
exits: [0
|
|
290
|
+
exits: [0]
|
|
180
291
|
},
|
|
181
292
|
{
|
|
182
293
|
name: "diff",
|
|
@@ -210,140 +321,147 @@ function buildAgentContract() {
|
|
|
210
321
|
flag: "--out-dir",
|
|
211
322
|
type: "string",
|
|
212
323
|
default: "snapshot",
|
|
213
|
-
appliesTo: "
|
|
324
|
+
appliesTo: optionAppliesTo("out-dir"),
|
|
214
325
|
summary: "Where the snapshot is written. Must not be the repo root or a directory the tool did not create."
|
|
215
326
|
},
|
|
216
327
|
{
|
|
217
328
|
flag: "--max-part-bytes",
|
|
218
329
|
type: "string",
|
|
219
330
|
default: 307200,
|
|
220
|
-
appliesTo: "
|
|
331
|
+
appliesTo: optionAppliesTo("max-part-bytes"),
|
|
221
332
|
summary: 'Byte budget per markdown part. Accepts "120k" / "1.5mb". Minimum 4096.'
|
|
222
333
|
},
|
|
223
334
|
{
|
|
224
335
|
flag: "--large-file-bytes",
|
|
225
336
|
type: "string",
|
|
226
337
|
default: 204800,
|
|
227
|
-
appliesTo: "
|
|
338
|
+
appliesTo: optionAppliesTo("large-file-bytes"),
|
|
228
339
|
summary: "Above this size a file gets a head/tail preview instead of the full body."
|
|
229
340
|
},
|
|
230
341
|
{
|
|
231
342
|
flag: "--max-monolith-bytes",
|
|
232
343
|
type: "string",
|
|
233
344
|
default: "unset",
|
|
234
|
-
appliesTo: "
|
|
345
|
+
appliesTo: optionAppliesTo("max-monolith-bytes"),
|
|
235
346
|
summary: "Skip the monolith artifacts when they would exceed this size."
|
|
236
347
|
},
|
|
237
348
|
{
|
|
238
349
|
flag: "--include-monolith",
|
|
239
350
|
type: "boolean",
|
|
240
351
|
default: false,
|
|
241
|
-
appliesTo: "
|
|
352
|
+
appliesTo: optionAppliesTo("include-monolith"),
|
|
242
353
|
summary: "Also write full-codebase.json and full-codebase.md."
|
|
243
354
|
},
|
|
244
355
|
{
|
|
245
356
|
flag: "--include-sensitive-files",
|
|
246
357
|
type: "boolean",
|
|
247
358
|
default: false,
|
|
248
|
-
appliesTo: "
|
|
359
|
+
appliesTo: optionAppliesTo("include-sensitive-files"),
|
|
249
360
|
summary: "Also emit .env / *.pem / .npmrc style files. Off by default; they are recorded as skipped."
|
|
250
361
|
},
|
|
251
362
|
{
|
|
252
363
|
flag: "--skip-artifacts",
|
|
253
364
|
type: "string",
|
|
254
365
|
default: "unset",
|
|
255
|
-
appliesTo: "
|
|
366
|
+
appliesTo: optionAppliesTo("skip-artifacts"),
|
|
256
367
|
summary: "Comma-separated artifacts to omit (readme, manifest, parts, navigation, projectTree, audit, monolith)."
|
|
257
368
|
},
|
|
258
369
|
{
|
|
259
370
|
flag: "--only-artifacts",
|
|
260
371
|
type: "string",
|
|
261
372
|
default: "unset",
|
|
262
|
-
appliesTo: "
|
|
373
|
+
appliesTo: optionAppliesTo("only-artifacts"),
|
|
263
374
|
summary: "Write only these artifacts. Cannot be combined with --skip-artifacts."
|
|
264
375
|
},
|
|
265
376
|
{
|
|
266
377
|
flag: "--format",
|
|
267
378
|
type: "string",
|
|
268
379
|
default: "text",
|
|
269
|
-
appliesTo: "
|
|
380
|
+
appliesTo: optionAppliesTo("format"),
|
|
270
381
|
summary: 'Output format: "text" or "json". JSON goes to stdout, progress to stderr.'
|
|
271
382
|
},
|
|
272
383
|
{
|
|
273
384
|
flag: "--cwd",
|
|
274
385
|
type: "string",
|
|
275
386
|
default: "process.cwd()",
|
|
276
|
-
appliesTo: "
|
|
387
|
+
appliesTo: optionAppliesTo("cwd"),
|
|
277
388
|
summary: "Repository to operate on. Must be an existing directory."
|
|
278
389
|
},
|
|
279
390
|
{
|
|
280
391
|
flag: "--dry-run",
|
|
281
392
|
type: "boolean",
|
|
282
393
|
default: false,
|
|
283
|
-
appliesTo: "
|
|
394
|
+
appliesTo: optionAppliesTo("dry-run"),
|
|
284
395
|
summary: "Compute everything and print the result without writing."
|
|
285
396
|
},
|
|
286
397
|
{
|
|
287
398
|
flag: "--watch",
|
|
288
399
|
type: "boolean",
|
|
289
400
|
default: false,
|
|
290
|
-
appliesTo: "
|
|
401
|
+
appliesTo: optionAppliesTo("watch"),
|
|
291
402
|
summary: "Regenerate on every change until interrupted."
|
|
292
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
|
+
},
|
|
293
411
|
{
|
|
294
412
|
flag: "--preset",
|
|
295
413
|
type: "string",
|
|
296
414
|
default: "detected",
|
|
297
|
-
appliesTo: "
|
|
415
|
+
appliesTo: optionAppliesTo("preset"),
|
|
298
416
|
summary: "Force a project preset instead of detecting one. Unknown names are a usage error."
|
|
299
417
|
},
|
|
300
418
|
{
|
|
301
419
|
flag: "--include",
|
|
302
420
|
type: "string",
|
|
303
421
|
default: "unset",
|
|
304
|
-
appliesTo: "
|
|
422
|
+
appliesTo: optionAppliesTo("include"),
|
|
305
423
|
summary: "Comma-separated allow-list of globs to record in the generated config."
|
|
306
424
|
},
|
|
307
425
|
{
|
|
308
426
|
flag: "--exclude",
|
|
309
427
|
type: "string",
|
|
310
428
|
default: "unset",
|
|
311
|
-
appliesTo: "
|
|
429
|
+
appliesTo: optionAppliesTo("exclude"),
|
|
312
430
|
summary: "Comma-separated globs to record in the generated config."
|
|
313
431
|
},
|
|
314
432
|
{
|
|
315
433
|
flag: "--yes",
|
|
316
434
|
type: "boolean",
|
|
317
435
|
default: false,
|
|
318
|
-
appliesTo: "
|
|
436
|
+
appliesTo: optionAppliesTo("yes"),
|
|
319
437
|
summary: "Accept every default without prompting. Required in a non-TTY."
|
|
320
438
|
},
|
|
321
439
|
{
|
|
322
440
|
flag: "--minimal",
|
|
323
441
|
type: "boolean",
|
|
324
442
|
default: false,
|
|
325
|
-
appliesTo: "
|
|
443
|
+
appliesTo: optionAppliesTo("minimal"),
|
|
326
444
|
summary: "Write only the settings that differ from the defaults."
|
|
327
445
|
},
|
|
328
446
|
{
|
|
329
447
|
flag: "--force",
|
|
330
448
|
type: "boolean",
|
|
331
449
|
default: false,
|
|
332
|
-
appliesTo: "
|
|
450
|
+
appliesTo: optionAppliesTo("force"),
|
|
333
451
|
summary: "Overwrite an existing config file."
|
|
334
452
|
},
|
|
335
453
|
{
|
|
336
454
|
flag: "--install",
|
|
337
455
|
type: "boolean",
|
|
338
456
|
default: false,
|
|
339
|
-
appliesTo: "
|
|
457
|
+
appliesTo: optionAppliesTo("install"),
|
|
340
458
|
summary: "Write the skill into the repository instead of printing it."
|
|
341
459
|
},
|
|
342
460
|
{
|
|
343
461
|
flag: "--target",
|
|
344
462
|
type: "string",
|
|
345
463
|
default: "all",
|
|
346
|
-
appliesTo: "
|
|
464
|
+
appliesTo: optionAppliesTo("target"),
|
|
347
465
|
summary: 'Where to install: "claude", "agents", "cursor" or "all".'
|
|
348
466
|
}
|
|
349
467
|
],
|
|
@@ -418,8 +536,8 @@ function buildAgentContract() {
|
|
|
418
536
|
troubleshooting: [
|
|
419
537
|
{
|
|
420
538
|
symptom: "Command prints nothing and exits 0",
|
|
421
|
-
cause: "A
|
|
422
|
-
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."
|
|
423
541
|
},
|
|
424
542
|
{
|
|
425
543
|
symptom: "Exit 1 with 'Refusing to replace outDir'",
|
|
@@ -471,10 +589,10 @@ another model, and reading files one by one would not fit in context.
|
|
|
471
589
|
This tool is a Node CLI. It needs **Node.js 18 or newer**. You do not need to install it
|
|
472
590
|
globally, and you do not need to clone anything.
|
|
473
591
|
|
|
474
|
-
**
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
the
|
|
592
|
+
**Pin the tag** \u2014 \`npx ${PACKAGE_NAME2}@latest <command>\` \u2014 when you need the same version on
|
|
593
|
+
every machine. The bare package name works too: this package ships exactly one executable,
|
|
594
|
+
which is what lets npm pick it. If a command exits 0 printing nothing, the executable was
|
|
595
|
+
not resolved \u2014 clear the npm cache and retry; do not assume the repository is broken.
|
|
478
596
|
|
|
479
597
|
## Step by step
|
|
480
598
|
|
|
@@ -493,7 +611,8 @@ the cause, not a broken repository.
|
|
|
493
611
|
\`--format json\` if you want to parse it.
|
|
494
612
|
|
|
495
613
|
4. **Configure (recommended).** Run \`npx ${PACKAGE_NAME2}@latest init --yes\`. It detects
|
|
496
|
-
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
|
|
497
616
|
current value, commented. Pass \`--preset <name>\` only if detection is wrong; the
|
|
498
617
|
preset names are in the contract. This step is optional: without a config the tool
|
|
499
618
|
runs on the same defaults.
|
|
@@ -594,14 +713,24 @@ function mergeAgentsFile(cwd, skill) {
|
|
|
594
713
|
next = `# AGENTS.md
|
|
595
714
|
|
|
596
715
|
${block}`;
|
|
597
|
-
} else
|
|
716
|
+
} else {
|
|
717
|
+
const starts = existing.split(AGENTS_MARKER_START).length - 1;
|
|
718
|
+
const ends = existing.split(AGENTS_MARKER_END).length - 1;
|
|
598
719
|
const start = existing.indexOf(AGENTS_MARKER_START);
|
|
599
720
|
const end = existing.indexOf(AGENTS_MARKER_END) + AGENTS_MARKER_END.length;
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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()}
|
|
603
731
|
|
|
604
732
|
${block}`;
|
|
733
|
+
}
|
|
605
734
|
}
|
|
606
735
|
const action = existing === null ? "created" : existing === next ? "unchanged" : "updated";
|
|
607
736
|
if (action !== "unchanged") {
|
|
@@ -664,6 +793,7 @@ var CLI_OPTIONS = {
|
|
|
664
793
|
mode: { type: "string" },
|
|
665
794
|
"dry-run": { type: "boolean" },
|
|
666
795
|
watch: { type: "boolean" },
|
|
796
|
+
"no-config": { type: "boolean" },
|
|
667
797
|
preset: { type: "string" },
|
|
668
798
|
include: { type: "string" },
|
|
669
799
|
exclude: { type: "string" },
|
|
@@ -683,7 +813,6 @@ Production-grade LLM codebase snapshot generator, auditor, and AST partitioner
|
|
|
683
813
|
|
|
684
814
|
Usage:
|
|
685
815
|
codebase-snapshot [command] [options]
|
|
686
|
-
snap [command] [options]
|
|
687
816
|
|
|
688
817
|
Commands:
|
|
689
818
|
generate (default) Generate a complete partitioned codebase snapshot
|
|
@@ -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.");
|
|
@@ -1166,7 +1320,7 @@ ${USAGE}
|
|
|
1166
1320
|
if (invocation.command === "explain") {
|
|
1167
1321
|
const result = explainPath(resolved, invocation.args[0]);
|
|
1168
1322
|
emit(invocation.format, result, () => info(renderExplainText(result)));
|
|
1169
|
-
return
|
|
1323
|
+
return 0;
|
|
1170
1324
|
}
|
|
1171
1325
|
if (invocation.command === "diff") {
|
|
1172
1326
|
try {
|
|
@@ -1190,7 +1344,9 @@ ${USAGE}
|
|
|
1190
1344
|
if (invocation.dryRun) {
|
|
1191
1345
|
info("\n\u{1F9EA} Dry run \u2014 nothing was written:");
|
|
1192
1346
|
info(` - Would track: ${manifest.counts.totalFiles} files`);
|
|
1193
|
-
info(
|
|
1347
|
+
info(
|
|
1348
|
+
` - Would write: ${manifest.counts.partsCount} part(s) under ${displayOutPath(resolved.cwd, resolved.outDir, "parts")}`
|
|
1349
|
+
);
|
|
1194
1350
|
info(` - Skipped: ${manifest.counts.skippedCount} file(s)`);
|
|
1195
1351
|
info(` - Warnings: ${manifest.warnings.length}`);
|
|
1196
1352
|
info(` - Integrity: ${manifest.validation.status}`);
|
|
@@ -1205,7 +1361,9 @@ ${USAGE}
|
|
|
1205
1361
|
);
|
|
1206
1362
|
const { artifacts } = resolved;
|
|
1207
1363
|
if (artifacts.parts) {
|
|
1208
|
-
info(
|
|
1364
|
+
info(
|
|
1365
|
+
` - Parts written: ${manifest.counts.partsCount} under ${displayOutPath(resolved.cwd, resolved.outDir, "parts")}/`
|
|
1366
|
+
);
|
|
1209
1367
|
}
|
|
1210
1368
|
const reports = [
|
|
1211
1369
|
artifacts.projectTree && "project-tree",
|
|
@@ -1213,10 +1371,28 @@ ${USAGE}
|
|
|
1213
1371
|
artifacts.audit && "audit"
|
|
1214
1372
|
].filter(Boolean);
|
|
1215
1373
|
if (reports.length > 0) {
|
|
1216
|
-
info(
|
|
1374
|
+
info(
|
|
1375
|
+
` - Reports: ${displayOutPath(resolved.cwd, resolved.outDir, "reports")}/ (${reports.join(", ")})`
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1378
|
+
if (artifacts.manifest) {
|
|
1379
|
+
info(` - Manifest: ${displayOutPath(resolved.cwd, resolved.outDir, "manifest.json")}`);
|
|
1380
|
+
}
|
|
1381
|
+
if (artifacts.monolith) {
|
|
1382
|
+
const monolithFiles = [
|
|
1383
|
+
manifest.artifacts?.fullJson ? "full-codebase.json" : null,
|
|
1384
|
+
manifest.artifacts?.fullMarkdown ? "full-codebase.md" : null
|
|
1385
|
+
].filter((name) => name !== null);
|
|
1386
|
+
if (monolithFiles.length === 2) {
|
|
1387
|
+
info(
|
|
1388
|
+
` - Monolith: ${displayOutPath(resolved.cwd, resolved.outDir, "full-codebase.{json,md}")}`
|
|
1389
|
+
);
|
|
1390
|
+
} else if (monolithFiles.length === 1) {
|
|
1391
|
+
info(
|
|
1392
|
+
` - Monolith: ${displayOutPath(resolved.cwd, resolved.outDir, monolithFiles[0])} (the other was skipped by --max-monolith-bytes)`
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1217
1395
|
}
|
|
1218
|
-
if (artifacts.manifest) info(` - Manifest: ${resolved.outDir}/manifest.json`);
|
|
1219
|
-
if (artifacts.monolith) info(` - Monolith: ${resolved.outDir}/full-codebase.{json,md}`);
|
|
1220
1396
|
if (reports.length === 0 && !artifacts.parts && !artifacts.manifest && !artifacts.monolith) {
|
|
1221
1397
|
info(` - Artifacts: all disabled (only the ownership marker was written)`);
|
|
1222
1398
|
}
|
|
@@ -1251,18 +1427,43 @@ ${USAGE}
|
|
|
1251
1427
|
async function runWatchLoop(invocation, resolved) {
|
|
1252
1428
|
const outDir = path2.resolve(resolved.cwd, resolved.outDir);
|
|
1253
1429
|
const tmpDir = path2.resolve(resolved.cwd, resolved.tmpDir);
|
|
1430
|
+
const VCS_SEGMENTS = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr"]);
|
|
1254
1431
|
const outName = path2.basename(outDir).toLowerCase();
|
|
1255
1432
|
const tmpName = path2.basename(tmpDir).toLowerCase();
|
|
1256
1433
|
const isGenerated = (candidate) => candidate.toLowerCase().split(/[\\/]/).some(
|
|
1257
|
-
(segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`)
|
|
1434
|
+
(segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`) || VCS_SEGMENTS.has(segment)
|
|
1258
1435
|
);
|
|
1259
1436
|
let running = false;
|
|
1260
|
-
const regenerate = () => {
|
|
1437
|
+
const regenerate = async () => {
|
|
1261
1438
|
if (running) return;
|
|
1262
1439
|
running = true;
|
|
1263
1440
|
const started = performance.now();
|
|
1264
1441
|
try {
|
|
1265
|
-
const
|
|
1442
|
+
const loaded = await loadConfigFileDetailed(invocation.cwd);
|
|
1443
|
+
if (loaded.errors.length > 0) {
|
|
1444
|
+
for (const error of loaded.errors) warn(`\u26A0 Config: ${error}`);
|
|
1445
|
+
warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
|
|
1449
|
+
const next = resolveConfig(
|
|
1450
|
+
loaded.config,
|
|
1451
|
+
{
|
|
1452
|
+
mode: invocation.command,
|
|
1453
|
+
format: invocation.format,
|
|
1454
|
+
dryRun: invocation.dryRun,
|
|
1455
|
+
cwd: invocation.cwd,
|
|
1456
|
+
outDir: invocation.outDir,
|
|
1457
|
+
includeMonolith: invocation.includeMonolith,
|
|
1458
|
+
includeSensitiveFiles: invocation.includeSensitiveFiles,
|
|
1459
|
+
maxPartBytes: invocation.maxPartBytes,
|
|
1460
|
+
largeFileBytes: invocation.largeFileBytes,
|
|
1461
|
+
maxMonolithBytes: invocation.maxMonolithBytes,
|
|
1462
|
+
skipArtifacts: invocation.skipArtifacts
|
|
1463
|
+
},
|
|
1464
|
+
invocation.cwd
|
|
1465
|
+
);
|
|
1466
|
+
const manifest = generateSnapshot(next, { dryRun: invocation.dryRun });
|
|
1266
1467
|
const elapsed = ((performance.now() - started) / 1e3).toFixed(2);
|
|
1267
1468
|
info(
|
|
1268
1469
|
`\u267B\uFE0F ${(/* @__PURE__ */ new Date()).toISOString()} ${manifest.counts.totalFiles} file(s), ${manifest.counts.partsCount} part(s), ${manifest.validation.status} (${elapsed}s)`
|
|
@@ -1274,7 +1475,7 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
1274
1475
|
}
|
|
1275
1476
|
};
|
|
1276
1477
|
info("\u{1F440} Watching for changes. Press Ctrl+C to stop.");
|
|
1277
|
-
regenerate();
|
|
1478
|
+
void regenerate();
|
|
1278
1479
|
let timer = null;
|
|
1279
1480
|
const watcher = fs.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
|
|
1280
1481
|
if (filename) {
|
|
@@ -1282,7 +1483,7 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
1282
1483
|
if (isGenerated(changed)) return;
|
|
1283
1484
|
}
|
|
1284
1485
|
if (timer) clearTimeout(timer);
|
|
1285
|
-
timer = setTimeout(regenerate, 250);
|
|
1486
|
+
timer = setTimeout(() => void regenerate(), 250);
|
|
1286
1487
|
});
|
|
1287
1488
|
await new Promise((resolve2) => {
|
|
1288
1489
|
const stop = () => {
|