@ankurhalder/codebase-snapshot 3.0.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/dist/cli.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { O as OutputFormat } from './types-CJqkePM4.cjs';
1
+ import { O as OutputFormat } from './types-BdpT_4b8.cjs';
2
2
 
3
3
  type Command = "generate" | "validate" | "report" | "init" | "explain" | "diff" | "describe" | "skill";
4
4
  /** A user-input problem: printed without a stack trace, exits 2. */
@@ -11,6 +11,8 @@ interface ParsedInvocation {
11
11
  format: OutputFormat;
12
12
  dryRun: boolean;
13
13
  watch: boolean;
14
+ /** Ignore any config file and run on the built-in defaults. */
15
+ noConfig: boolean;
14
16
  preset?: string;
15
17
  /** The size exactly as typed, so the generated config can keep `120k`. */
16
18
  maxPartBytesRaw?: string;
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { O as OutputFormat } from './types-CJqkePM4.js';
1
+ import { O as OutputFormat } from './types-BdpT_4b8.js';
2
2
 
3
3
  type Command = "generate" | "validate" | "report" | "init" | "explain" | "diff" | "describe" | "skill";
4
4
  /** A user-input problem: printed without a stack trace, exits 2. */
@@ -11,6 +11,8 @@ interface ParsedInvocation {
11
11
  format: OutputFormat;
12
12
  dryRun: boolean;
13
13
  watch: boolean;
14
+ /** Ignore any config file and run on the built-in defaults. */
15
+ noConfig: boolean;
14
16
  preset?: string;
15
17
  /** The size exactly as typed, so the generated config can keep `120k`. */
16
18
  maxPartBytesRaw?: string;
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-5B4UVQLR.js';
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";
@@ -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 @2) when you need the exact same version on every machine.",
99
- "This package exposes exactly one executable on purpose: npm cannot infer which one to run for a scoped package that ships several.",
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: "prints v2.x.x on stdout and exits 0",
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, 1]
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: "generate/validate/report/explain",
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: "generate/validate/report",
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: "generate/validate/report",
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: "generate",
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: "generate",
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: "generate/validate/report",
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: "generate",
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: "generate",
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: "all commands",
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: "all commands",
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: "generate",
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: "generate",
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: "init",
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: "init",
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: "init",
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: "init",
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: "init",
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: "init",
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: "skill",
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: "skill",
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: "The executable was not found: an offline or partially populated npm cache, or an npm proxy serving an incomplete manifest.",
423
- fix: `Clear the npx cache (npm cache clean --force), then run npx ${PACKAGE_NAME}@latest <command>.`
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\` listing **every** setting at its
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 if (existing.includes(AGENTS_MARKER_START) && existing.includes(AGENTS_MARKER_END)) {
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
- next = `${existing.slice(0, start)}${block.trimEnd()}${existing.slice(end)}`;
602
- } else {
603
- next = `${existing.trimEnd()}
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
- if (command !== "init") {
876
- const initOnly = [];
877
- if (values.preset !== void 0) initOnly.push("--preset");
878
- if (values.include !== void 0) initOnly.push("--include");
879
- if (values.exclude !== void 0) initOnly.push("--exclude");
880
- if (values.yes === true) initOnly.push("--yes");
881
- if (values.minimal === true) initOnly.push("--minimal");
882
- if (values.force === true) initOnly.push("--force");
883
- if (initOnly.length > 0) {
884
- throw new UsageError(
885
- `${initOnly.join(", ")} ${initOnly.length === 1 ? "is" : "are"} only valid with \`init\`. To scope \`${command}\`, set include/excludePatterns/preset in snapshot.config.ts (or run \`codebase-snapshot init\` to generate one).`
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 !== "skill" && (values.install !== void 0 || values.target !== void 0)) {
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
- info(" Next: npm run snapshot");
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 result.included ? 0 : 1;
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(` - Would write: ${manifest.counts.partsCount} part(s) under ${resolved.outDir}/parts/`);
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(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
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(` - Reports: ${resolved.outDir}/reports/ (${reports.join(", ")})`);
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 manifest = generateSnapshot(resolved, { dryRun: invocation.dryRun });
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 = () => {