@codluv/versionguard 0.3.0 → 0.5.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.
Files changed (54) hide show
  1. package/dist/calver.d.ts +66 -22
  2. package/dist/calver.d.ts.map +1 -1
  3. package/dist/changelog.d.ts +52 -0
  4. package/dist/changelog.d.ts.map +1 -1
  5. package/dist/chunks/{index-BrZJDWya.js → index-CwOyEn5L.js} +904 -176
  6. package/dist/chunks/index-CwOyEn5L.js.map +1 -0
  7. package/dist/ckm/engine.d.ts +92 -0
  8. package/dist/ckm/engine.d.ts.map +1 -0
  9. package/dist/ckm/index.d.ts +32 -0
  10. package/dist/ckm/index.d.ts.map +1 -0
  11. package/dist/ckm/types.d.ts +168 -0
  12. package/dist/ckm/types.d.ts.map +1 -0
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +1333 -27
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/feedback/index.d.ts +1 -1
  18. package/dist/feedback/index.d.ts.map +1 -1
  19. package/dist/fix/index.d.ts +6 -1
  20. package/dist/fix/index.d.ts.map +1 -1
  21. package/dist/guard.d.ts +45 -1
  22. package/dist/guard.d.ts.map +1 -1
  23. package/dist/hooks.d.ts +14 -7
  24. package/dist/hooks.d.ts.map +1 -1
  25. package/dist/index.d.ts +4 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +38 -32
  28. package/dist/init-wizard.d.ts +68 -0
  29. package/dist/init-wizard.d.ts.map +1 -0
  30. package/dist/project-root.d.ts +74 -0
  31. package/dist/project-root.d.ts.map +1 -0
  32. package/dist/project.d.ts +23 -2
  33. package/dist/project.d.ts.map +1 -1
  34. package/dist/sources/git-tag.d.ts +29 -0
  35. package/dist/sources/git-tag.d.ts.map +1 -1
  36. package/dist/sources/json.d.ts +31 -0
  37. package/dist/sources/json.d.ts.map +1 -1
  38. package/dist/sources/regex.d.ts +30 -0
  39. package/dist/sources/regex.d.ts.map +1 -1
  40. package/dist/sources/resolve.d.ts +27 -8
  41. package/dist/sources/resolve.d.ts.map +1 -1
  42. package/dist/sources/toml.d.ts +36 -1
  43. package/dist/sources/toml.d.ts.map +1 -1
  44. package/dist/sources/utils.d.ts +73 -0
  45. package/dist/sources/utils.d.ts.map +1 -0
  46. package/dist/sources/version-file.d.ts +28 -1
  47. package/dist/sources/version-file.d.ts.map +1 -1
  48. package/dist/sources/yaml.d.ts +29 -0
  49. package/dist/sources/yaml.d.ts.map +1 -1
  50. package/dist/tag/index.d.ts.map +1 -1
  51. package/dist/types.d.ts +89 -4
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +3 -2
  54. package/dist/chunks/index-BrZJDWya.js.map +0 -1
@@ -6,38 +6,83 @@ import { parse as parse$2 } from "smol-toml";
6
6
  import * as yaml from "js-yaml";
7
7
  import { globSync } from "glob";
8
8
  import { fileURLToPath } from "node:url";
9
+ const VALID_TOKENS = /* @__PURE__ */ new Set([
10
+ "YYYY",
11
+ "YY",
12
+ "0Y",
13
+ "MM",
14
+ "M",
15
+ "0M",
16
+ "WW",
17
+ "0W",
18
+ "DD",
19
+ "D",
20
+ "0D",
21
+ "MICRO",
22
+ "PATCH"
23
+ ]);
24
+ const YEAR_TOKENS = /* @__PURE__ */ new Set(["YYYY", "YY", "0Y"]);
25
+ const MONTH_TOKENS = /* @__PURE__ */ new Set(["MM", "M", "0M"]);
26
+ const WEEK_TOKENS = /* @__PURE__ */ new Set(["WW", "0W"]);
27
+ const DAY_TOKENS = /* @__PURE__ */ new Set(["DD", "D", "0D"]);
28
+ const COUNTER_TOKENS = /* @__PURE__ */ new Set(["MICRO", "PATCH"]);
29
+ function isValidCalVerFormat(formatStr) {
30
+ const tokens = formatStr.split(".");
31
+ if (tokens.length < 2) return false;
32
+ if (!tokens.every((t) => VALID_TOKENS.has(t))) return false;
33
+ if (!YEAR_TOKENS.has(tokens[0])) return false;
34
+ const hasWeek = tokens.some((t) => WEEK_TOKENS.has(t));
35
+ const hasMonthOrDay = tokens.some((t) => MONTH_TOKENS.has(t) || DAY_TOKENS.has(t));
36
+ if (hasWeek && hasMonthOrDay) return false;
37
+ const counterIndex = tokens.findIndex((t) => COUNTER_TOKENS.has(t));
38
+ if (counterIndex !== -1 && counterIndex !== tokens.length - 1) return false;
39
+ return true;
40
+ }
9
41
  function parseFormat(calverFormat) {
10
- const parts = calverFormat.split(".");
42
+ const tokens = calverFormat.split(".");
11
43
  const result = {
12
- year: parts[0],
13
- month: parts[1]
44
+ year: tokens[0]
14
45
  };
15
- if (parts[2] === "PATCH") {
16
- result.patch = "PATCH";
17
- } else if (parts[2]) {
18
- result.day = parts[2];
19
- }
20
- if (parts[3] === "PATCH") {
21
- result.patch = "PATCH";
46
+ for (let i = 1; i < tokens.length; i++) {
47
+ const token = tokens[i];
48
+ if (MONTH_TOKENS.has(token)) {
49
+ result.month = token;
50
+ } else if (WEEK_TOKENS.has(token)) {
51
+ result.week = token;
52
+ } else if (DAY_TOKENS.has(token)) {
53
+ result.day = token;
54
+ } else if (COUNTER_TOKENS.has(token)) {
55
+ result.counter = token;
56
+ }
22
57
  }
23
58
  return result;
24
59
  }
60
+ const MODIFIER_PATTERN = "(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?";
25
61
  function tokenPattern(token) {
26
62
  switch (token) {
27
63
  case "YYYY":
28
- return "(\\d{4})";
64
+ return "([1-9]\\d{3})";
29
65
  case "YY":
30
- return "(\\d{2})";
31
- case "0M":
32
- case "0D":
33
- return "(\\d{2})";
66
+ return "(\\d{1,3})";
67
+ case "0Y":
68
+ return "(\\d{2,3})";
34
69
  case "MM":
35
- case "DD":
36
70
  case "M":
71
+ return "([1-9]|1[0-2])";
72
+ case "0M":
73
+ return "(0[1-9]|1[0-2])";
74
+ case "WW":
75
+ return "([1-9]|[1-4]\\d|5[0-3])";
76
+ case "0W":
77
+ return "(0[1-9]|[1-4]\\d|5[0-3])";
78
+ case "DD":
37
79
  case "D":
38
- return "(\\d{1,2})";
80
+ return "([1-9]|[12]\\d|3[01])";
81
+ case "0D":
82
+ return "(0[1-9]|[12]\\d|3[01])";
83
+ case "MICRO":
39
84
  case "PATCH":
40
- return "(\\d+)";
85
+ return "(0|[1-9]\\d*)";
41
86
  default:
42
87
  throw new Error(`Unsupported CalVer token: ${token}`);
43
88
  }
@@ -45,7 +90,7 @@ function tokenPattern(token) {
45
90
  function getRegexForFormat(calverFormat) {
46
91
  const tokens = calverFormat.split(".");
47
92
  const pattern = tokens.map(tokenPattern).join("\\.");
48
- return new RegExp(`^${pattern}$`);
93
+ return new RegExp(`^${pattern}${MODIFIER_PATTERN}$`);
49
94
  }
50
95
  function parse$1(version, calverFormat) {
51
96
  const match = version.match(getRegexForFormat(calverFormat));
@@ -53,28 +98,44 @@ function parse$1(version, calverFormat) {
53
98
  return null;
54
99
  }
55
100
  const definition = parseFormat(calverFormat);
56
- const year = definition.year === "YYYY" ? Number.parseInt(match[1], 10) : 2e3 + Number.parseInt(match[1], 10);
57
- const month = Number.parseInt(match[2], 10);
58
- let cursor = 3;
101
+ const yearToken = definition.year;
102
+ let year = Number.parseInt(match[1], 10);
103
+ if (yearToken === "YY" || yearToken === "0Y") {
104
+ year = 2e3 + year;
105
+ }
106
+ let cursor = 2;
107
+ let month;
59
108
  let day;
60
109
  let patch;
110
+ if (definition.month) {
111
+ month = Number.parseInt(match[cursor], 10);
112
+ cursor += 1;
113
+ }
114
+ if (definition.week) {
115
+ month = Number.parseInt(match[cursor], 10);
116
+ cursor += 1;
117
+ }
61
118
  if (definition.day) {
62
119
  day = Number.parseInt(match[cursor], 10);
63
120
  cursor += 1;
64
121
  }
65
- if (definition.patch) {
122
+ if (definition.counter) {
66
123
  patch = Number.parseInt(match[cursor], 10);
124
+ cursor += 1;
67
125
  }
126
+ const modifierGroup = match[cursor];
127
+ const modifier = modifierGroup || void 0;
68
128
  return {
69
129
  year,
70
- month,
130
+ month: month ?? 1,
71
131
  day,
72
132
  patch,
133
+ modifier,
73
134
  format: calverFormat,
74
135
  raw: version
75
136
  };
76
137
  }
77
- function validate$2(version, calverFormat, preventFutureDates = true) {
138
+ function validate$2(version, calverFormat, preventFutureDates = true, schemeRules) {
78
139
  const errors = [];
79
140
  const parsed = parse$1(version, calverFormat);
80
141
  if (!parsed) {
@@ -88,19 +149,26 @@ function validate$2(version, calverFormat, preventFutureDates = true) {
88
149
  ]
89
150
  };
90
151
  }
91
- if (parsed.month < 1 || parsed.month > 12) {
152
+ const definition = parseFormat(calverFormat);
153
+ if (definition.month && (parsed.month < 1 || parsed.month > 12)) {
92
154
  errors.push({
93
155
  message: `Invalid month: ${parsed.month}. Must be between 1 and 12.`,
94
156
  severity: "error"
95
157
  });
96
158
  }
159
+ if (definition.week && (parsed.month < 1 || parsed.month > 53)) {
160
+ errors.push({
161
+ message: `Invalid week: ${parsed.month}. Must be between 1 and 53.`,
162
+ severity: "error"
163
+ });
164
+ }
97
165
  if (parsed.day !== void 0) {
98
166
  if (parsed.day < 1 || parsed.day > 31) {
99
167
  errors.push({
100
168
  message: `Invalid day: ${parsed.day}. Must be between 1 and 31.`,
101
169
  severity: "error"
102
170
  });
103
- } else {
171
+ } else if (definition.month) {
104
172
  const daysInMonth = new Date(parsed.year, parsed.month, 0).getDate();
105
173
  if (parsed.day > daysInMonth) {
106
174
  errors.push({
@@ -120,56 +188,83 @@ function validate$2(version, calverFormat, preventFutureDates = true) {
120
188
  message: `Future year not allowed: ${parsed.year}. Current year is ${currentYear}.`,
121
189
  severity: "error"
122
190
  });
123
- } else if (parsed.year === currentYear && parsed.month > currentMonth) {
191
+ } else if (definition.month && parsed.year === currentYear && parsed.month > currentMonth) {
124
192
  errors.push({
125
193
  message: `Future month not allowed: ${parsed.year}.${parsed.month}. Current month is ${currentMonth}.`,
126
194
  severity: "error"
127
195
  });
128
- } else if (parsed.year === currentYear && parsed.month === currentMonth && parsed.day !== void 0 && parsed.day > currentDay) {
196
+ } else if (definition.month && parsed.year === currentYear && parsed.month === currentMonth && parsed.day !== void 0 && parsed.day > currentDay) {
129
197
  errors.push({
130
198
  message: `Future day not allowed: ${parsed.year}.${parsed.month}.${parsed.day}. Current day is ${currentDay}.`,
131
199
  severity: "error"
132
200
  });
133
201
  }
134
202
  }
203
+ if (parsed.modifier && schemeRules?.allowedModifiers) {
204
+ const baseModifier = parsed.modifier.replace(/[\d.]+$/, "") || parsed.modifier;
205
+ if (!schemeRules.allowedModifiers.includes(baseModifier)) {
206
+ errors.push({
207
+ message: `Modifier "${parsed.modifier}" is not allowed. Allowed: ${schemeRules.allowedModifiers.join(", ")}`,
208
+ severity: "error"
209
+ });
210
+ }
211
+ }
212
+ if (schemeRules?.maxNumericSegments) {
213
+ const segmentCount = calverFormat.split(".").length;
214
+ if (segmentCount > schemeRules.maxNumericSegments) {
215
+ errors.push({
216
+ message: `Format has ${segmentCount} segments, convention recommends ${schemeRules.maxNumericSegments} or fewer`,
217
+ severity: "warning"
218
+ });
219
+ }
220
+ }
135
221
  return {
136
- valid: errors.length === 0,
222
+ valid: errors.filter((e) => e.severity === "error").length === 0,
137
223
  errors,
138
224
  version: { type: "calver", version: parsed }
139
225
  };
140
226
  }
141
227
  function formatToken(token, value) {
142
- if (token === "0M" || token === "0D") {
143
- return String(value).padStart(2, "0");
144
- }
145
- if (token === "YY") {
146
- return String(value % 100).padStart(2, "0");
228
+ switch (token) {
229
+ case "0M":
230
+ case "0D":
231
+ case "0W":
232
+ case "0Y":
233
+ return String(token === "0Y" ? value % 100 : value).padStart(2, "0");
234
+ case "YY":
235
+ return String(value % 100).padStart(2, "0");
236
+ default:
237
+ return String(value);
147
238
  }
148
- return String(value);
149
239
  }
150
240
  function format$1(version) {
151
- const tokens = version.format.split(".");
152
- const values = [version.year, version.month];
153
- if (tokens.includes("DD") || tokens.includes("D") || tokens.includes("0D")) {
154
- values.push(version.day ?? 1);
241
+ const definition = parseFormat(version.format);
242
+ const parts = [formatToken(definition.year, version.year)];
243
+ if (definition.month) {
244
+ parts.push(formatToken(definition.month, version.month));
155
245
  }
156
- if (tokens.includes("PATCH")) {
157
- values.push(version.patch ?? 0);
246
+ if (definition.week) {
247
+ parts.push(formatToken(definition.week, version.month));
158
248
  }
159
- return tokens.map((token, index) => formatToken(token, values[index])).join(".");
249
+ if (definition.day) {
250
+ parts.push(formatToken(definition.day, version.day ?? 1));
251
+ }
252
+ if (definition.counter) {
253
+ parts.push(formatToken(definition.counter, version.patch ?? 0));
254
+ }
255
+ const base = parts.join(".");
256
+ return version.modifier ? `${base}-${version.modifier}` : base;
160
257
  }
161
258
  function getCurrentVersion(calverFormat, now = /* @__PURE__ */ new Date()) {
162
259
  const definition = parseFormat(calverFormat);
163
- const currentDay = now.getDate();
164
260
  const base = {
165
261
  year: now.getFullYear(),
166
262
  month: now.getMonth() + 1,
167
- day: definition.day ? currentDay : void 0,
168
- patch: definition.patch ? 0 : void 0
263
+ day: definition.day ? now.getDate() : void 0,
264
+ patch: definition.counter ? 0 : void 0,
265
+ format: calverFormat
169
266
  };
170
- const day = base.day ?? currentDay;
171
- const patch = base.patch ?? 0;
172
- return formatToken(definition.year, base.year).concat(`.${formatToken(definition.month, base.month)}`).concat(definition.day ? `.${formatToken(definition.day, day)}` : "").concat(definition.patch ? `.${patch}` : "");
267
+ return format$1(base);
173
268
  }
174
269
  function compare$1(a, b, calverFormat) {
175
270
  const left = parse$1(a, calverFormat);
@@ -195,12 +290,11 @@ function increment$1(version, calverFormat) {
195
290
  const next = {
196
291
  ...parsed
197
292
  };
198
- if (definition.patch) {
199
- const patch = parsed.patch ?? 0;
200
- next.patch = patch + 1;
293
+ if (definition.counter) {
294
+ next.patch = (parsed.patch ?? 0) + 1;
201
295
  } else {
202
296
  next.patch = 0;
203
- next.format = `${calverFormat}.PATCH`;
297
+ next.format = `${calverFormat}.MICRO`;
204
298
  }
205
299
  return format$1(next);
206
300
  }
@@ -215,6 +309,7 @@ const calver = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
215
309
  getNextVersions,
216
310
  getRegexForFormat,
217
311
  increment: increment$1,
312
+ isValidCalVerFormat,
218
313
  parse: parse$1,
219
314
  parseFormat,
220
315
  validate: validate$2
@@ -246,7 +341,7 @@ function validateChangelog(changelogPath, version, strict = true, requireEntry =
246
341
  errors.push("Changelog should include compare links at the bottom");
247
342
  }
248
343
  const versionHeaderMatch = content.match(
249
- new RegExp(`## \\[${escapeRegExp$2(version)}\\] - ([^\r
344
+ new RegExp(`## \\[${escapeRegExp$1(version)}\\] - ([^\r
250
345
  ]+)`)
251
346
  );
252
347
  if (requireEntry && hasEntryForVersion) {
@@ -286,10 +381,109 @@ function addVersionEntry(changelogPath, version, date = (/* @__PURE__ */ new Dat
286
381
  const updated = `${content.slice(0, insertIndex)}${block}${content.slice(insertIndex)}`;
287
382
  fs.writeFileSync(changelogPath, updated, "utf-8");
288
383
  }
289
- function escapeRegExp$2(value) {
384
+ function isChangesetMangled(changelogPath) {
385
+ if (!fs.existsSync(changelogPath)) return false;
386
+ const content = fs.readFileSync(changelogPath, "utf-8");
387
+ return /^## \d+\.\d+/m.test(content) && content.includes("## [Unreleased]");
388
+ }
389
+ const SECTION_MAP = {
390
+ "Major Changes": "Changed",
391
+ "Minor Changes": "Added",
392
+ "Patch Changes": "Fixed"
393
+ };
394
+ function fixChangesetMangling(changelogPath, date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) {
395
+ if (!fs.existsSync(changelogPath)) return false;
396
+ const content = fs.readFileSync(changelogPath, "utf-8");
397
+ const versionMatch = content.match(/^## (\d+\.\d+\.\d+[^\n]*)\n/m);
398
+ if (!versionMatch || versionMatch.index === void 0) return false;
399
+ const fullHeader = versionMatch[0];
400
+ if (fullHeader.includes("[")) return false;
401
+ const version = versionMatch[1].trim();
402
+ if (content.includes(`## [${version}]`)) return false;
403
+ const startIndex = versionMatch.index;
404
+ const preambleMatch = content.indexOf("All notable changes", startIndex);
405
+ const unreleasedMatch = content.indexOf("## [Unreleased]", startIndex);
406
+ let endIndex;
407
+ if (preambleMatch !== -1 && preambleMatch < unreleasedMatch) {
408
+ endIndex = preambleMatch;
409
+ } else if (unreleasedMatch !== -1) {
410
+ endIndex = unreleasedMatch;
411
+ } else {
412
+ return false;
413
+ }
414
+ const changesetsBlock = content.slice(startIndex + fullHeader.length, endIndex).trim();
415
+ const transformedSections = transformChangesetsContent(changesetsBlock);
416
+ const newEntry = `## [${version}] - ${date}
417
+
418
+ ${transformedSections}
419
+
420
+ `;
421
+ const beforeChangesets = content.slice(0, startIndex);
422
+ const afterChangesets = content.slice(endIndex);
423
+ const unreleasedInAfter = afterChangesets.indexOf("## [Unreleased]");
424
+ if (unreleasedInAfter === -1) {
425
+ const rebuilt2 = `${beforeChangesets}${newEntry}${afterChangesets}`;
426
+ fs.writeFileSync(changelogPath, rebuilt2, "utf-8");
427
+ return true;
428
+ }
429
+ const unreleasedLineEnd = afterChangesets.indexOf("\n", unreleasedInAfter);
430
+ const afterUnreleased = unreleasedLineEnd !== -1 ? afterChangesets.slice(0, unreleasedLineEnd + 1) : afterChangesets;
431
+ const rest = unreleasedLineEnd !== -1 ? afterChangesets.slice(unreleasedLineEnd + 1) : "";
432
+ const rebuilt = `${beforeChangesets}${afterUnreleased}
433
+ ${newEntry}${rest}`;
434
+ const withLinks = updateCompareLinks(rebuilt, version);
435
+ fs.writeFileSync(changelogPath, withLinks, "utf-8");
436
+ return true;
437
+ }
438
+ function transformChangesetsContent(block) {
439
+ const lines = block.split("\n");
440
+ const result = [];
441
+ for (const line of lines) {
442
+ const sectionMatch = line.match(/^### (.+)/);
443
+ if (sectionMatch) {
444
+ const mapped = SECTION_MAP[sectionMatch[1]] ?? sectionMatch[1];
445
+ result.push(`### ${mapped}`);
446
+ continue;
447
+ }
448
+ const entryMatch = line.match(
449
+ /^(\s*-\s+)[a-f0-9]{7,}: (?:feat|fix|chore|docs|refactor|perf|test|ci|build|style)(?:\([^)]*\))?: (.+)/
450
+ );
451
+ if (entryMatch) {
452
+ result.push(`${entryMatch[1]}${entryMatch[2]}`);
453
+ continue;
454
+ }
455
+ const simpleHashMatch = line.match(/^(\s*-\s+)[a-f0-9]{7,}: (.+)/);
456
+ if (simpleHashMatch) {
457
+ result.push(`${simpleHashMatch[1]}${simpleHashMatch[2]}`);
458
+ continue;
459
+ }
460
+ result.push(line);
461
+ }
462
+ return result.join("\n");
463
+ }
464
+ function updateCompareLinks(content, version) {
465
+ const unreleasedLinkRegex = /\[Unreleased\]: (https:\/\/[^\s]+\/compare\/v)([\d.]+)(\.\.\.HEAD)/;
466
+ const match = content.match(unreleasedLinkRegex);
467
+ if (match) {
468
+ const baseUrl = match[1].replace(/v$/, "");
469
+ const previousVersion = match[2];
470
+ const newUnreleasedLink = `[Unreleased]: ${baseUrl}v${version}...HEAD`;
471
+ const newVersionLink = `[${version}]: ${baseUrl}v${previousVersion}...v${version}`;
472
+ let updated = content.replace(unreleasedLinkRegex, newUnreleasedLink);
473
+ if (!updated.includes(`[${version}]:`)) {
474
+ updated = updated.replace(newUnreleasedLink, `${newUnreleasedLink}
475
+ ${newVersionLink}`);
476
+ }
477
+ return updated;
478
+ }
479
+ return content;
480
+ }
481
+ function escapeRegExp$1(value) {
290
482
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
291
483
  }
292
484
  const HOOK_NAMES$1 = ["pre-commit", "pre-push", "post-tag"];
485
+ const VG_BLOCK_START = "# >>> versionguard >>>";
486
+ const VG_BLOCK_END = "# <<< versionguard <<<";
293
487
  function installHooks(config, cwd = process.cwd()) {
294
488
  const gitDir = findGitDir(cwd);
295
489
  if (!gitDir) {
@@ -300,7 +494,36 @@ function installHooks(config, cwd = process.cwd()) {
300
494
  for (const hookName of HOOK_NAMES$1) {
301
495
  if (config.hooks[hookName]) {
302
496
  const hookPath = path.join(hooksDir, hookName);
303
- fs.writeFileSync(hookPath, generateHookScript(hookName), { encoding: "utf-8", mode: 493 });
497
+ const vgBlock = generateHookBlock(hookName);
498
+ if (fs.existsSync(hookPath)) {
499
+ const existing = fs.readFileSync(hookPath, "utf-8");
500
+ if (existing.includes(VG_BLOCK_START)) {
501
+ const updated = replaceVgBlock(existing, vgBlock);
502
+ fs.writeFileSync(hookPath, updated, { encoding: "utf-8", mode: 493 });
503
+ } else if (isLegacyVgHook(existing)) {
504
+ fs.writeFileSync(hookPath, `#!/bin/sh
505
+
506
+ ${vgBlock}
507
+ `, {
508
+ encoding: "utf-8",
509
+ mode: 493
510
+ });
511
+ } else {
512
+ const appended = `${existing.trimEnd()}
513
+
514
+ ${vgBlock}
515
+ `;
516
+ fs.writeFileSync(hookPath, appended, { encoding: "utf-8", mode: 493 });
517
+ }
518
+ } else {
519
+ fs.writeFileSync(hookPath, `#!/bin/sh
520
+
521
+ ${vgBlock}
522
+ `, {
523
+ encoding: "utf-8",
524
+ mode: 493
525
+ });
526
+ }
304
527
  }
305
528
  }
306
529
  }
@@ -312,7 +535,21 @@ function uninstallHooks(cwd = process.cwd()) {
312
535
  const hooksDir = path.join(gitDir, "hooks");
313
536
  for (const hookName of HOOK_NAMES$1) {
314
537
  const hookPath = path.join(hooksDir, hookName);
315
- if (fs.existsSync(hookPath) && fs.readFileSync(hookPath, "utf-8").includes("versionguard")) {
538
+ if (!fs.existsSync(hookPath)) continue;
539
+ const content = fs.readFileSync(hookPath, "utf-8");
540
+ if (!content.includes("versionguard")) continue;
541
+ if (content.includes(VG_BLOCK_START)) {
542
+ const cleaned = removeVgBlock(content);
543
+ const trimmed = cleaned.trim();
544
+ if (!trimmed || trimmed === "#!/bin/sh") {
545
+ fs.unlinkSync(hookPath);
546
+ } else if (isLegacyVgHook(trimmed)) {
547
+ fs.unlinkSync(hookPath);
548
+ } else {
549
+ fs.writeFileSync(hookPath, `${trimmed}
550
+ `, { encoding: "utf-8", mode: 493 });
551
+ }
552
+ } else if (isLegacyVgHook(content)) {
316
553
  fs.unlinkSync(hookPath);
317
554
  }
318
555
  }
@@ -341,9 +578,8 @@ function areHooksInstalled(cwd = process.cwd()) {
341
578
  return fs.existsSync(hookPath) && fs.readFileSync(hookPath, "utf-8").includes("versionguard");
342
579
  });
343
580
  }
344
- function generateHookScript(hookName) {
345
- return `#!/bin/sh
346
- # versionguard
581
+ function generateHookBlock(hookName) {
582
+ return `${VG_BLOCK_START}
347
583
  # VersionGuard ${hookName} hook
348
584
  # --no-install prevents accidentally downloading an unscoped package
349
585
  # if @codluv/versionguard is not installed locally
@@ -353,11 +589,47 @@ if [ $status -ne 0 ]; then
353
589
  echo "VersionGuard validation failed."
354
590
  exit $status
355
591
  fi
592
+ ${VG_BLOCK_END}`;
593
+ }
594
+ function generateHookScript(hookName) {
595
+ return `#!/bin/sh
596
+
597
+ ${generateHookBlock(hookName)}
356
598
  `;
357
599
  }
600
+ function isLegacyVgHook(content) {
601
+ if (!content.includes("versionguard validate")) return false;
602
+ if (content.includes(VG_BLOCK_START)) return false;
603
+ if (content.includes("husky")) return false;
604
+ if (content.includes("lefthook")) return false;
605
+ if (content.includes("pre-commit run")) return false;
606
+ return true;
607
+ }
608
+ function replaceVgBlock(content, newBlock) {
609
+ const startIdx = content.indexOf(VG_BLOCK_START);
610
+ const endIdx = content.indexOf(VG_BLOCK_END);
611
+ if (startIdx === -1 || endIdx === -1) return content;
612
+ return content.slice(0, startIdx) + newBlock + content.slice(endIdx + VG_BLOCK_END.length);
613
+ }
614
+ function removeVgBlock(content) {
615
+ const startIdx = content.indexOf(VG_BLOCK_START);
616
+ const endIdx = content.indexOf(VG_BLOCK_END);
617
+ if (startIdx === -1 || endIdx === -1) return content;
618
+ const before = content.slice(0, startIdx).replace(/\n\n$/, "\n");
619
+ const after = content.slice(endIdx + VG_BLOCK_END.length).replace(/^\n\n/, "\n");
620
+ return before + after;
621
+ }
358
622
  class GitTagSource {
623
+ /** Human-readable provider name. */
359
624
  name = "git-tag";
625
+ /** Empty string since git-tag has no manifest file. */
360
626
  manifestFile = "";
627
+ /**
628
+ * Returns `true` when `cwd` is inside a Git repository.
629
+ *
630
+ * @param cwd - Project directory to check.
631
+ * @returns Whether a Git repository is found.
632
+ */
361
633
  exists(cwd) {
362
634
  try {
363
635
  execFileSync("git", ["rev-parse", "--git-dir"], {
@@ -369,87 +641,167 @@ class GitTagSource {
369
641
  return false;
370
642
  }
371
643
  }
644
+ /**
645
+ * Reads the version string from the latest Git tag.
646
+ *
647
+ * @param cwd - Project directory containing the Git repository.
648
+ * @returns The version string extracted from the latest version tag.
649
+ */
372
650
  getVersion(cwd) {
373
651
  try {
374
- const tag = execFileSync("git", ["describe", "--tags", "--abbrev=0"], {
375
- cwd,
376
- encoding: "utf-8",
377
- stdio: ["pipe", "pipe", "ignore"]
378
- }).trim();
652
+ const tag = this.describeVersionTag(cwd);
379
653
  return tag.replace(/^v/, "");
380
654
  } catch {
381
- throw new Error("No git tags found. Create a tag first (e.g., git tag v0.1.0)");
655
+ throw new Error("No version tags found. Create a tag first (e.g., git tag v0.1.0)");
382
656
  }
383
657
  }
658
+ /**
659
+ * Creates a new annotated Git tag for the given version.
660
+ *
661
+ * @param version - Version string to tag.
662
+ * @param cwd - Project directory containing the Git repository.
663
+ */
384
664
  setVersion(version, cwd) {
385
- const tagName = `v${version}`;
665
+ const prefix = this.detectPrefix(cwd);
666
+ const tagName = `${prefix}${version}`;
386
667
  execFileSync("git", ["tag", "-a", tagName, "-m", `Release ${version}`], {
387
668
  cwd,
388
669
  stdio: ["pipe", "pipe", "ignore"]
389
670
  });
390
671
  }
672
+ /** Try version-like tag patterns, fall back to any tag. */
673
+ describeVersionTag(cwd) {
674
+ try {
675
+ return execFileSync("git", ["describe", "--tags", "--abbrev=0", "--match", "v[0-9]*"], {
676
+ cwd,
677
+ encoding: "utf-8",
678
+ stdio: ["pipe", "pipe", "ignore"]
679
+ }).trim();
680
+ } catch {
681
+ }
682
+ try {
683
+ return execFileSync("git", ["describe", "--tags", "--abbrev=0", "--match", "[0-9]*"], {
684
+ cwd,
685
+ encoding: "utf-8",
686
+ stdio: ["pipe", "pipe", "ignore"]
687
+ }).trim();
688
+ } catch {
689
+ throw new Error("No version tags found");
690
+ }
691
+ }
692
+ /** Detect whether existing tags use a `v` prefix or not. */
693
+ detectPrefix(cwd) {
694
+ try {
695
+ const tag = this.describeVersionTag(cwd);
696
+ return tag.startsWith("v") ? "v" : "";
697
+ } catch {
698
+ return "v";
699
+ }
700
+ }
701
+ }
702
+ function getNestedValue(obj, dotPath) {
703
+ let current = obj;
704
+ for (const key of dotPath.split(".")) {
705
+ if (current === null || typeof current !== "object") {
706
+ return void 0;
707
+ }
708
+ current = current[key];
709
+ }
710
+ return current;
711
+ }
712
+ function setNestedValue(obj, dotPath, value) {
713
+ const keys = dotPath.split(".");
714
+ let current = obj;
715
+ for (let i = 0; i < keys.length - 1; i++) {
716
+ const next = current[keys[i]];
717
+ if (typeof next !== "object" || next === null) {
718
+ throw new Error(`Missing intermediate key '${keys.slice(0, i + 1).join(".")}' in manifest`);
719
+ }
720
+ current = next;
721
+ }
722
+ current[keys[keys.length - 1]] = value;
723
+ }
724
+ function escapeRegExp(value) {
725
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
391
726
  }
392
727
  class JsonVersionSource {
728
+ /** Human-readable provider name. */
393
729
  name;
730
+ /** Filename of the JSON manifest (e.g. `'package.json'`). */
394
731
  manifestFile;
732
+ /** Dotted key path to the version field within the JSON document. */
395
733
  versionPath;
734
+ /**
735
+ * Creates a new JSON version source.
736
+ *
737
+ * @param manifestFile - JSON manifest filename.
738
+ * @param versionPath - Dotted key path to the version field.
739
+ */
396
740
  constructor(manifestFile = "package.json", versionPath = "version") {
397
741
  this.name = manifestFile;
398
742
  this.manifestFile = manifestFile;
399
743
  this.versionPath = versionPath;
400
744
  }
745
+ /**
746
+ * Returns `true` when the manifest file exists in `cwd`.
747
+ *
748
+ * @param cwd - Project directory to check.
749
+ * @returns Whether the manifest file exists.
750
+ */
401
751
  exists(cwd) {
402
752
  return fs.existsSync(path.join(cwd, this.manifestFile));
403
753
  }
754
+ /**
755
+ * Reads the version string from the JSON manifest.
756
+ *
757
+ * @param cwd - Project directory containing the manifest.
758
+ * @returns The version string extracted from the manifest.
759
+ */
404
760
  getVersion(cwd) {
405
761
  const filePath = path.join(cwd, this.manifestFile);
406
762
  if (!fs.existsSync(filePath)) {
407
763
  throw new Error(`${this.manifestFile} not found in ${cwd}`);
408
764
  }
409
765
  const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
410
- const version = getNestedValue$1(content, this.versionPath);
766
+ const version = getNestedValue(content, this.versionPath);
411
767
  if (typeof version !== "string" || version.length === 0) {
412
768
  throw new Error(`No version field in ${this.manifestFile}`);
413
769
  }
414
770
  return version;
415
771
  }
772
+ /**
773
+ * Writes a version string to the JSON manifest, preserving indentation.
774
+ *
775
+ * @param version - Version string to write.
776
+ * @param cwd - Project directory containing the manifest.
777
+ */
416
778
  setVersion(version, cwd) {
417
779
  const filePath = path.join(cwd, this.manifestFile);
418
780
  if (!fs.existsSync(filePath)) {
419
781
  throw new Error(`${this.manifestFile} not found in ${cwd}`);
420
782
  }
421
- const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
783
+ const raw = fs.readFileSync(filePath, "utf-8");
784
+ const indentMatch = raw.match(/^(\s+)"/m);
785
+ const indent = indentMatch?.[1]?.length ?? 2;
786
+ const content = JSON.parse(raw);
422
787
  setNestedValue(content, this.versionPath, version);
423
- fs.writeFileSync(filePath, `${JSON.stringify(content, null, 2)}
788
+ fs.writeFileSync(filePath, `${JSON.stringify(content, null, indent)}
424
789
  `, "utf-8");
425
790
  }
426
791
  }
427
- function getNestedValue$1(obj, dotPath) {
428
- let current = obj;
429
- for (const key of dotPath.split(".")) {
430
- if (current === null || typeof current !== "object") {
431
- return void 0;
432
- }
433
- current = current[key];
434
- }
435
- return current;
436
- }
437
- function setNestedValue(obj, dotPath, value) {
438
- const keys = dotPath.split(".");
439
- let current = obj;
440
- for (let i = 0; i < keys.length - 1; i++) {
441
- const key = keys[i];
442
- if (typeof current[key] !== "object" || current[key] === null) {
443
- current[key] = {};
444
- }
445
- current = current[key];
446
- }
447
- current[keys[keys.length - 1]] = value;
448
- }
449
792
  class RegexVersionSource {
793
+ /** Human-readable provider name. */
450
794
  name;
795
+ /** Filename of the source manifest (e.g. `'setup.py'`). */
451
796
  manifestFile;
797
+ /** Compiled regex used to locate the version string. */
452
798
  versionRegex;
799
+ /**
800
+ * Creates a new regex version source.
801
+ *
802
+ * @param manifestFile - Source manifest filename.
803
+ * @param versionRegex - Regex string with at least one capture group for the version.
804
+ */
453
805
  constructor(manifestFile, versionRegex) {
454
806
  this.name = manifestFile;
455
807
  this.manifestFile = manifestFile;
@@ -462,9 +814,21 @@ class RegexVersionSource {
462
814
  throw new Error(`Version regex for ${manifestFile} must contain at least one capture group`);
463
815
  }
464
816
  }
817
+ /**
818
+ * Returns `true` when the manifest file exists in `cwd`.
819
+ *
820
+ * @param cwd - Project directory to check.
821
+ * @returns Whether the manifest file exists.
822
+ */
465
823
  exists(cwd) {
466
824
  return fs.existsSync(path.join(cwd, this.manifestFile));
467
825
  }
826
+ /**
827
+ * Reads the version string from the source manifest using regex extraction.
828
+ *
829
+ * @param cwd - Project directory containing the manifest.
830
+ * @returns The version string captured by group 1 of the regex.
831
+ */
468
832
  getVersion(cwd) {
469
833
  const filePath = path.join(cwd, this.manifestFile);
470
834
  if (!fs.existsSync(filePath)) {
@@ -477,6 +841,12 @@ class RegexVersionSource {
477
841
  }
478
842
  return match[1];
479
843
  }
844
+ /**
845
+ * Writes a version string to the source manifest using position-based replacement.
846
+ *
847
+ * @param version - Version string to write.
848
+ * @param cwd - Project directory containing the manifest.
849
+ */
480
850
  setVersion(version, cwd) {
481
851
  const filePath = path.join(cwd, this.manifestFile);
482
852
  if (!fs.existsSync(filePath)) {
@@ -494,17 +864,38 @@ class RegexVersionSource {
494
864
  }
495
865
  }
496
866
  class TomlVersionSource {
867
+ /** Human-readable provider name. */
497
868
  name;
869
+ /** Filename of the TOML manifest (e.g. `'Cargo.toml'`). */
498
870
  manifestFile;
871
+ /** Dotted key path to the version field within the TOML document. */
499
872
  versionPath;
873
+ /**
874
+ * Creates a new TOML version source.
875
+ *
876
+ * @param manifestFile - TOML manifest filename.
877
+ * @param versionPath - Dotted key path to the version field.
878
+ */
500
879
  constructor(manifestFile = "Cargo.toml", versionPath = "package.version") {
501
880
  this.name = manifestFile;
502
881
  this.manifestFile = manifestFile;
503
882
  this.versionPath = versionPath;
504
883
  }
884
+ /**
885
+ * Returns `true` when the manifest file exists in `cwd`.
886
+ *
887
+ * @param cwd - Project directory to check.
888
+ * @returns Whether the manifest file exists.
889
+ */
505
890
  exists(cwd) {
506
891
  return fs.existsSync(path.join(cwd, this.manifestFile));
507
892
  }
893
+ /**
894
+ * Reads the version string from the TOML manifest.
895
+ *
896
+ * @param cwd - Project directory containing the manifest.
897
+ * @returns The version string extracted from the manifest.
898
+ */
508
899
  getVersion(cwd) {
509
900
  const filePath = path.join(cwd, this.manifestFile);
510
901
  if (!fs.existsSync(filePath)) {
@@ -518,6 +909,12 @@ class TomlVersionSource {
518
909
  }
519
910
  return version;
520
911
  }
912
+ /**
913
+ * Writes a version string to the TOML manifest, preserving formatting.
914
+ *
915
+ * @param version - Version string to write.
916
+ * @param cwd - Project directory containing the manifest.
917
+ */
521
918
  setVersion(version, cwd) {
522
919
  const filePath = path.join(cwd, this.manifestFile);
523
920
  if (!fs.existsSync(filePath)) {
@@ -531,6 +928,11 @@ class TomlVersionSource {
531
928
  }
532
929
  fs.writeFileSync(filePath, updated, "utf-8");
533
930
  }
931
+ /**
932
+ * Splits the dotted version path into a TOML section name and key name.
933
+ *
934
+ * @returns An object with `section` and `key` components.
935
+ */
534
936
  getSectionKey() {
535
937
  const parts = this.versionPath.split(".");
536
938
  if (parts.length === 1) {
@@ -542,21 +944,32 @@ class TomlVersionSource {
542
944
  };
543
945
  }
544
946
  }
545
- function getNestedValue(obj, dotPath) {
546
- let current = obj;
547
- for (const key of dotPath.split(".")) {
548
- if (current === null || typeof current !== "object") {
549
- return void 0;
550
- }
551
- current = current[key];
947
+ function replaceTomlVersion(content, target, newVersion) {
948
+ const result = replaceInSection(content, target, newVersion);
949
+ if (result !== content) return result;
950
+ if (target.section) {
951
+ const dottedRegex = new RegExp(
952
+ `^(\\s*${escapeRegExp(target.section)}\\.${escapeRegExp(target.key)}\\s*=\\s*)(["'])([^"']*)(\\2)`,
953
+ "m"
954
+ );
955
+ const dottedResult = content.replace(dottedRegex, `$1$2${newVersion}$4`);
956
+ if (dottedResult !== content) return dottedResult;
552
957
  }
553
- return current;
958
+ if (target.section) {
959
+ const inlineRegex = new RegExp(
960
+ `^(\\s*${escapeRegExp(target.section)}\\s*=\\s*\\{[^}]*${escapeRegExp(target.key)}\\s*=\\s*)(["'])([^"']*)(\\2)`,
961
+ "m"
962
+ );
963
+ const inlineResult = content.replace(inlineRegex, `$1$2${newVersion}$4`);
964
+ if (inlineResult !== content) return inlineResult;
965
+ }
966
+ return content;
554
967
  }
555
- function replaceTomlVersion(content, target, newVersion) {
968
+ function replaceInSection(content, target, newVersion) {
556
969
  const lines = content.split("\n");
557
970
  const sectionHeader = target.section ? `[${target.section}]` : null;
558
971
  let inSection = sectionHeader === null;
559
- const versionRegex = new RegExp(`^(\\s*${escapeRegExp$1(target.key)}\\s*=\\s*)(["'])([^"']*)(\\2)`);
972
+ const versionRegex = new RegExp(`^(\\s*${escapeRegExp(target.key)}\\s*=\\s*)(["'])([^"']*)(\\2)`);
560
973
  for (let i = 0; i < lines.length; i++) {
561
974
  const trimmed = lines[i].trim();
562
975
  if (sectionHeader !== null) {
@@ -579,30 +992,56 @@ function replaceTomlVersion(content, target, newVersion) {
579
992
  }
580
993
  return content;
581
994
  }
582
- function escapeRegExp$1(value) {
583
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
584
- }
585
995
  class VersionFileSource {
996
+ /** Human-readable provider name. */
586
997
  name;
998
+ /** Filename of the version file (e.g. `'VERSION'`). */
587
999
  manifestFile;
1000
+ /**
1001
+ * Creates a new plain text version file source.
1002
+ *
1003
+ * @param manifestFile - Version filename.
1004
+ */
588
1005
  constructor(manifestFile = "VERSION") {
589
1006
  this.name = manifestFile;
590
1007
  this.manifestFile = manifestFile;
591
1008
  }
1009
+ /**
1010
+ * Returns `true` when the version file exists in `cwd`.
1011
+ *
1012
+ * @param cwd - Project directory to check.
1013
+ * @returns Whether the version file exists.
1014
+ */
592
1015
  exists(cwd) {
593
1016
  return fs.existsSync(path.join(cwd, this.manifestFile));
594
1017
  }
1018
+ /**
1019
+ * Reads the version string from the plain text version file.
1020
+ *
1021
+ * @param cwd - Project directory containing the version file.
1022
+ * @returns The version string from the first line of the file.
1023
+ */
595
1024
  getVersion(cwd) {
596
1025
  const filePath = path.join(cwd, this.manifestFile);
597
1026
  if (!fs.existsSync(filePath)) {
598
1027
  throw new Error(`${this.manifestFile} not found in ${cwd}`);
599
1028
  }
600
- const version = fs.readFileSync(filePath, "utf-8").trim();
1029
+ const raw = fs.readFileSync(filePath, "utf-8");
1030
+ if (raw.includes("\0")) {
1031
+ throw new Error(`${this.manifestFile} appears to be a binary file`);
1032
+ }
1033
+ const version = raw.split("\n")[0].trim();
601
1034
  if (version.length === 0) {
602
1035
  throw new Error(`${this.manifestFile} is empty`);
603
1036
  }
604
1037
  return version;
605
1038
  }
1039
+ /**
1040
+ * Writes a version string to the plain text version file.
1041
+ *
1042
+ * @param version - Version string to write.
1043
+ * @param cwd - Project directory containing the version file.
1044
+ */
606
1045
  setVersion(version, cwd) {
607
1046
  const filePath = path.join(cwd, this.manifestFile);
608
1047
  if (!fs.existsSync(filePath)) {
@@ -613,17 +1052,38 @@ class VersionFileSource {
613
1052
  }
614
1053
  }
615
1054
  class YamlVersionSource {
1055
+ /** Human-readable provider name. */
616
1056
  name;
1057
+ /** Filename of the YAML manifest (e.g. `'pubspec.yaml'`). */
617
1058
  manifestFile;
1059
+ /** Dotted key path to the version field within the YAML document. */
618
1060
  versionKey;
1061
+ /**
1062
+ * Creates a new YAML version source.
1063
+ *
1064
+ * @param manifestFile - YAML manifest filename.
1065
+ * @param versionKey - Dotted key path to the version field.
1066
+ */
619
1067
  constructor(manifestFile = "pubspec.yaml", versionKey = "version") {
620
1068
  this.name = manifestFile;
621
1069
  this.manifestFile = manifestFile;
622
1070
  this.versionKey = versionKey;
623
1071
  }
1072
+ /**
1073
+ * Returns `true` when the manifest file exists in `cwd`.
1074
+ *
1075
+ * @param cwd - Project directory to check.
1076
+ * @returns Whether the manifest file exists.
1077
+ */
624
1078
  exists(cwd) {
625
1079
  return fs.existsSync(path.join(cwd, this.manifestFile));
626
1080
  }
1081
+ /**
1082
+ * Reads the version string from the YAML manifest.
1083
+ *
1084
+ * @param cwd - Project directory containing the manifest.
1085
+ * @returns The version string extracted from the manifest.
1086
+ */
627
1087
  getVersion(cwd) {
628
1088
  const filePath = path.join(cwd, this.manifestFile);
629
1089
  if (!fs.existsSync(filePath)) {
@@ -634,7 +1094,7 @@ class YamlVersionSource {
634
1094
  if (!parsed || typeof parsed !== "object") {
635
1095
  throw new Error(`Failed to parse ${this.manifestFile}`);
636
1096
  }
637
- const version = parsed[this.versionKey];
1097
+ const version = getNestedValue(parsed, this.versionKey);
638
1098
  if (typeof version !== "string" || version.length === 0) {
639
1099
  if (typeof version === "number") {
640
1100
  return String(version);
@@ -643,13 +1103,21 @@ class YamlVersionSource {
643
1103
  }
644
1104
  return version;
645
1105
  }
1106
+ /**
1107
+ * Writes a version string to the YAML manifest, preserving formatting.
1108
+ *
1109
+ * @param version - Version string to write.
1110
+ * @param cwd - Project directory containing the manifest.
1111
+ */
646
1112
  setVersion(version, cwd) {
647
1113
  const filePath = path.join(cwd, this.manifestFile);
648
1114
  if (!fs.existsSync(filePath)) {
649
1115
  throw new Error(`${this.manifestFile} not found in ${cwd}`);
650
1116
  }
1117
+ const keyParts = this.versionKey.split(".");
1118
+ const leafKey = keyParts[keyParts.length - 1];
651
1119
  const content = fs.readFileSync(filePath, "utf-8");
652
- const regex = new RegExp(`^(${escapeRegExp(this.versionKey)}:\\s*)(["']?)(.+?)\\2\\s*$`, "m");
1120
+ const regex = new RegExp(`^(\\s*${escapeRegExp(leafKey)}:\\s*)(["']?)(.+?)\\2\\s*$`, "m");
653
1121
  const updated = content.replace(regex, `$1$2${version}$2`);
654
1122
  if (updated === content) {
655
1123
  throw new Error(`Could not find version field to update in ${this.manifestFile}`);
@@ -657,9 +1125,6 @@ class YamlVersionSource {
657
1125
  fs.writeFileSync(filePath, updated, "utf-8");
658
1126
  }
659
1127
  }
660
- function escapeRegExp(value) {
661
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
662
- }
663
1128
  const VALID_SOURCES = /* @__PURE__ */ new Set([
664
1129
  "auto",
665
1130
  "package.json",
@@ -765,7 +1230,10 @@ function resolveVersionSource(config, cwd = process.cwd()) {
765
1230
  return provider;
766
1231
  }
767
1232
  }
768
- return new JsonVersionSource("package.json", "version");
1233
+ const supported = DETECTION_TABLE.map((e) => e.file).join(", ");
1234
+ throw new Error(
1235
+ `No supported manifest file found in ${cwd}. Looked for: ${supported}. Set manifest.source explicitly in .versionguard.yml or create a supported manifest file.`
1236
+ );
769
1237
  }
770
1238
  function detectManifests(cwd = process.cwd()) {
771
1239
  const detected = [];
@@ -1059,6 +1527,169 @@ function checkHardcodedVersions(expectedVersion, config, ignorePatterns, cwd = p
1059
1527
  }
1060
1528
  return mismatches;
1061
1529
  }
1530
+ function getCalVerConfig(config) {
1531
+ if (!config.versioning.calver) {
1532
+ throw new Error('CalVer configuration is required when versioning.type is "calver"');
1533
+ }
1534
+ return config.versioning.calver;
1535
+ }
1536
+ function deriveTopicSlug(conceptName) {
1537
+ return conceptName.replace(/Config$/, "").replace(/Result$/, "").replace(/Options$/, "").toLowerCase();
1538
+ }
1539
+ function isTopicConcept(name) {
1540
+ return name.endsWith("Config") && name !== "VersionGuardConfig";
1541
+ }
1542
+ function operationMatchesTopic(op, topicSlug, conceptNames) {
1543
+ const haystack = `${op.name} ${op.what}`.toLowerCase();
1544
+ if (haystack.includes(topicSlug)) return true;
1545
+ return conceptNames.some((n) => haystack.includes(n.toLowerCase()));
1546
+ }
1547
+ function createCkmEngine(manifest) {
1548
+ const topics = deriveTopics(manifest);
1549
+ return {
1550
+ topics,
1551
+ getTopicIndex: (toolName = "tool") => formatTopicIndex(topics, toolName),
1552
+ getTopicContent: (name) => formatTopicContent(topics, name),
1553
+ getTopicJson: (name) => buildTopicJson(topics, manifest, name),
1554
+ getManifest: () => manifest
1555
+ };
1556
+ }
1557
+ function deriveTopics(manifest) {
1558
+ const topics = [];
1559
+ for (const concept of manifest.concepts) {
1560
+ if (!isTopicConcept(concept.name)) continue;
1561
+ const slug = deriveTopicSlug(concept.name);
1562
+ const conceptNames = [concept.name];
1563
+ const relatedConcepts = manifest.concepts.filter(
1564
+ (c) => c.name !== concept.name && (c.name.toLowerCase().includes(slug) || slug.includes(deriveTopicSlug(c.name)))
1565
+ );
1566
+ conceptNames.push(...relatedConcepts.map((c) => c.name));
1567
+ const operations = manifest.operations.filter(
1568
+ (op) => operationMatchesTopic(op, slug, conceptNames)
1569
+ );
1570
+ const configSchema = manifest.configSchema.filter(
1571
+ (c) => conceptNames.some((n) => c.key?.startsWith(n))
1572
+ );
1573
+ const constraints = manifest.constraints.filter(
1574
+ (c) => conceptNames.some((n) => c.enforcedBy?.includes(n)) || operations.some((o) => c.enforcedBy?.includes(o.name))
1575
+ );
1576
+ topics.push({
1577
+ name: slug,
1578
+ summary: concept.what,
1579
+ concepts: [concept, ...relatedConcepts],
1580
+ operations,
1581
+ configSchema,
1582
+ constraints
1583
+ });
1584
+ }
1585
+ return topics;
1586
+ }
1587
+ function formatTopicIndex(topics, toolName) {
1588
+ const lines = [
1589
+ `${toolName} CKM — Codebase Knowledge Manifest`,
1590
+ "",
1591
+ `Usage: ${toolName} ckm [topic] [--json] [--llm]`,
1592
+ "",
1593
+ "Topics:"
1594
+ ];
1595
+ const maxName = Math.max(...topics.map((t) => t.name.length));
1596
+ for (const topic of topics) {
1597
+ lines.push(` ${topic.name.padEnd(maxName + 2)}${topic.summary}`);
1598
+ }
1599
+ lines.push("");
1600
+ lines.push("Flags:");
1601
+ lines.push(" --json Machine-readable CKM output (concepts, operations, config schema)");
1602
+ lines.push(" --llm Full API context for LLM agents (forge-ts llms.txt)");
1603
+ return lines.join("\n");
1604
+ }
1605
+ function formatTopicContent(topics, topicName) {
1606
+ const topic = topics.find((t) => t.name === topicName);
1607
+ if (!topic) return null;
1608
+ const lines = [`# ${topic.summary}`, ""];
1609
+ if (topic.concepts.length > 0) {
1610
+ lines.push("## Concepts", "");
1611
+ for (const c of topic.concepts) {
1612
+ lines.push(` ${c.name} — ${c.what}`);
1613
+ if (c.properties) {
1614
+ for (const p of c.properties) {
1615
+ const def = findDefault(topic.configSchema, c.name, p.name);
1616
+ lines.push(` ${p.name}: ${p.type}${def ? ` = ${def}` : ""}`);
1617
+ if (p.description) {
1618
+ lines.push(` ${p.description}`);
1619
+ }
1620
+ }
1621
+ }
1622
+ lines.push("");
1623
+ }
1624
+ }
1625
+ if (topic.operations.length > 0) {
1626
+ lines.push("## Operations", "");
1627
+ for (const o of topic.operations) {
1628
+ lines.push(` ${o.name}() — ${o.what}`);
1629
+ if (o.inputs) {
1630
+ for (const i of o.inputs) {
1631
+ lines.push(` @param ${i.name}: ${i.description}`);
1632
+ }
1633
+ }
1634
+ lines.push("");
1635
+ }
1636
+ }
1637
+ if (topic.configSchema.length > 0) {
1638
+ lines.push("## Config Fields", "");
1639
+ for (const c of topic.configSchema) {
1640
+ lines.push(` ${c.key}: ${c.type}${c.default ? ` = ${c.default}` : ""}`);
1641
+ if (c.description) {
1642
+ lines.push(` ${c.description}`);
1643
+ }
1644
+ }
1645
+ lines.push("");
1646
+ }
1647
+ if (topic.constraints.length > 0) {
1648
+ lines.push("## Constraints", "");
1649
+ for (const c of topic.constraints) {
1650
+ lines.push(` [${c.id}] ${c.rule}`);
1651
+ lines.push(` Enforced by: ${c.enforcedBy}`);
1652
+ }
1653
+ lines.push("");
1654
+ }
1655
+ return lines.join("\n");
1656
+ }
1657
+ function findDefault(schema, conceptName, propName) {
1658
+ return schema.find((c) => c.key === `${conceptName}.${propName}`)?.default;
1659
+ }
1660
+ function buildTopicJson(topics, manifest, topicName) {
1661
+ if (!topicName) {
1662
+ return {
1663
+ topics: topics.map((t) => ({
1664
+ name: t.name,
1665
+ summary: t.summary,
1666
+ concepts: t.concepts.length,
1667
+ operations: t.operations.length,
1668
+ configFields: t.configSchema.length,
1669
+ constraints: t.constraints.length
1670
+ })),
1671
+ ckm: {
1672
+ concepts: manifest.concepts.length,
1673
+ operations: manifest.operations.length,
1674
+ constraints: manifest.constraints.length,
1675
+ workflows: manifest.workflows.length,
1676
+ configSchema: manifest.configSchema.length
1677
+ }
1678
+ };
1679
+ }
1680
+ const topic = topics.find((t) => t.name === topicName);
1681
+ if (!topic) {
1682
+ return { error: `Unknown topic: ${topicName}`, topics: topics.map((t) => t.name) };
1683
+ }
1684
+ return {
1685
+ topic: topic.name,
1686
+ summary: topic.summary,
1687
+ concepts: topic.concepts,
1688
+ operations: topic.operations,
1689
+ configSchema: topic.configSchema,
1690
+ constraints: topic.constraints
1691
+ };
1692
+ }
1062
1693
  const CONFIG_FILE_NAMES = [
1063
1694
  ".versionguard.yml",
1064
1695
  ".versionguard.yaml",
@@ -1068,9 +1699,14 @@ const CONFIG_FILE_NAMES = [
1068
1699
  const DEFAULT_CONFIG = {
1069
1700
  versioning: {
1070
1701
  type: "semver",
1702
+ schemeRules: {
1703
+ maxNumericSegments: 3,
1704
+ allowedModifiers: ["dev", "alpha", "beta", "rc"]
1705
+ },
1071
1706
  calver: {
1072
1707
  format: "YYYY.MM.PATCH",
1073
- preventFutureDates: true
1708
+ preventFutureDates: true,
1709
+ strictMutualExclusion: true
1074
1710
  }
1075
1711
  },
1076
1712
  manifest: {
@@ -1196,13 +1832,7 @@ function getVersionFeedback(version, config, previousVersion) {
1196
1832
  if (config.versioning.type === "semver") {
1197
1833
  return getSemVerFeedback(version, previousVersion);
1198
1834
  }
1199
- return getCalVerFeedback(version, getCalVerConfig$2(config), previousVersion);
1200
- }
1201
- function getCalVerConfig$2(config) {
1202
- if (!config.versioning.calver) {
1203
- throw new Error('CalVer configuration is required when versioning.type is "calver"');
1204
- }
1205
- return config.versioning.calver;
1835
+ return getCalVerFeedback(version, getCalVerConfig(config), previousVersion);
1206
1836
  }
1207
1837
  function getSemVerFeedback(version, previousVersion) {
1208
1838
  const errors = [];
@@ -1218,7 +1848,7 @@ function getSemVerFeedback(version, previousVersion) {
1218
1848
  });
1219
1849
  suggestions.push({
1220
1850
  message: `Remove the 'v' prefix`,
1221
- fix: `npm version ${cleanVersion}`,
1851
+ fix: `npx versionguard fix --version ${cleanVersion}`,
1222
1852
  autoFixable: true
1223
1853
  });
1224
1854
  } else if (version.split(".").length === 2) {
@@ -1228,7 +1858,7 @@ function getSemVerFeedback(version, previousVersion) {
1228
1858
  });
1229
1859
  suggestions.push({
1230
1860
  message: `Add patch number (e.g., ${version}.0)`,
1231
- fix: `npm version ${version}.0`,
1861
+ fix: `npx versionguard fix --version ${version}.0`,
1232
1862
  autoFixable: true
1233
1863
  });
1234
1864
  } else if (/^\d+\.\d+\.\d+\.\d+$/.test(version)) {
@@ -1278,7 +1908,7 @@ function getSemVerFeedback(version, previousVersion) {
1278
1908
  });
1279
1909
  suggestions.push({
1280
1910
  message: `Version must be greater than ${previousVersion}`,
1281
- fix: `npm version ${increment(previousVersion, "patch")}`,
1911
+ fix: `npx versionguard fix --version ${increment(previousVersion, "patch")}`,
1282
1912
  autoFixable: true
1283
1913
  });
1284
1914
  } else if (comparison === 0) {
@@ -1288,7 +1918,7 @@ function getSemVerFeedback(version, previousVersion) {
1288
1918
  });
1289
1919
  suggestions.push({
1290
1920
  message: `Bump the version`,
1291
- fix: `npm version ${increment(previousVersion, "patch")}`,
1921
+ fix: `npx versionguard fix --version ${increment(previousVersion, "patch")}`,
1292
1922
  autoFixable: true
1293
1923
  });
1294
1924
  } else {
@@ -1358,21 +1988,21 @@ function getCalVerFeedback(version, calverConfig, previousVersion) {
1358
1988
  if (preventFutureDates && parsed.year > now.getFullYear()) {
1359
1989
  suggestions.push({
1360
1990
  message: `Use current year (${now.getFullYear()}) or a past year`,
1361
- fix: `npm version ${formatCalVerVersion({ ...parsed, year: now.getFullYear() })}`,
1991
+ fix: `npx versionguard fix --version ${formatCalVerVersion({ ...parsed, year: now.getFullYear() })}`,
1362
1992
  autoFixable: true
1363
1993
  });
1364
1994
  }
1365
1995
  if (preventFutureDates && parsed.year === now.getFullYear() && parsed.month > now.getMonth() + 1) {
1366
1996
  suggestions.push({
1367
1997
  message: `Current month is ${now.getMonth() + 1}`,
1368
- fix: `npm version ${formatCalVerVersion({ ...parsed, month: now.getMonth() + 1 })}`,
1998
+ fix: `npx versionguard fix --version ${formatCalVerVersion({ ...parsed, month: now.getMonth() + 1 })}`,
1369
1999
  autoFixable: true
1370
2000
  });
1371
2001
  }
1372
2002
  if (preventFutureDates && parsed.year === now.getFullYear() && parsed.month === now.getMonth() + 1 && parsed.day !== void 0 && parsed.day > now.getDate()) {
1373
2003
  suggestions.push({
1374
2004
  message: `Current day is ${now.getDate()}`,
1375
- fix: `npm version ${formatCalVerVersion({ ...parsed, day: now.getDate() })}`,
2005
+ fix: `npx versionguard fix --version ${formatCalVerVersion({ ...parsed, day: now.getDate() })}`,
1376
2006
  autoFixable: true
1377
2007
  });
1378
2008
  }
@@ -1386,7 +2016,7 @@ function getCalVerFeedback(version, calverConfig, previousVersion) {
1386
2016
  });
1387
2017
  suggestions.push({
1388
2018
  message: `CalVer must increase over time`,
1389
- fix: `npm version ${increment$1(previousVersion, format2)}`,
2019
+ fix: `npx versionguard fix --version ${increment$1(previousVersion, format2)}`,
1390
2020
  autoFixable: true
1391
2021
  });
1392
2022
  }
@@ -1566,6 +2196,17 @@ function fixAll(config, targetVersion, cwd = process.cwd()) {
1566
2196
  const syncResults = fixSyncIssues(config, cwd);
1567
2197
  results.push(...syncResults);
1568
2198
  if (config.changelog.enabled) {
2199
+ const changelogPath = path.join(cwd, config.changelog.file);
2200
+ if (isChangesetMangled(changelogPath)) {
2201
+ const fixed = fixChangesetMangling(changelogPath);
2202
+ if (fixed) {
2203
+ results.push({
2204
+ fixed: true,
2205
+ message: `Restructured ${config.changelog.file} from Changesets format to Keep a Changelog`,
2206
+ file: changelogPath
2207
+ });
2208
+ }
2209
+ }
1569
2210
  const changelogResult = fixChangelog(version, config, cwd);
1570
2211
  if (changelogResult.fixed) {
1571
2212
  results.push(changelogResult);
@@ -1595,7 +2236,7 @@ function suggestNextVersion(currentVersion, config, changeType) {
1595
2236
  });
1596
2237
  }
1597
2238
  } else {
1598
- const format2 = getCalVerConfig$1(config).format;
2239
+ const format2 = getCalVerConfig(config).format;
1599
2240
  const currentCal = getCurrentVersion(format2);
1600
2241
  suggestions.push({
1601
2242
  version: currentCal,
@@ -1608,12 +2249,6 @@ function suggestNextVersion(currentVersion, config, changeType) {
1608
2249
  }
1609
2250
  return suggestions;
1610
2251
  }
1611
- function getCalVerConfig$1(config) {
1612
- if (!config.versioning.calver) {
1613
- throw new Error('CalVer configuration is required when versioning.type is "calver"');
1614
- }
1615
- return config.versioning.calver;
1616
- }
1617
2252
  const HOOK_NAMES = ["pre-commit", "pre-push", "post-tag"];
1618
2253
  function checkHooksPathOverride(cwd) {
1619
2254
  try {
@@ -1730,6 +2365,92 @@ function runGuardChecks(config, cwd) {
1730
2365
  warnings
1731
2366
  };
1732
2367
  }
2368
+ const PROJECT_MARKERS = [
2369
+ ".versionguard.yml",
2370
+ ".versionguard.yaml",
2371
+ "versionguard.yml",
2372
+ "versionguard.yaml",
2373
+ ".git",
2374
+ "package.json",
2375
+ "Cargo.toml",
2376
+ "pyproject.toml",
2377
+ "pubspec.yaml",
2378
+ "composer.json",
2379
+ "pom.xml",
2380
+ "go.mod",
2381
+ "mix.exs",
2382
+ "Gemfile",
2383
+ ".csproj"
2384
+ ];
2385
+ function findProjectRoot(startDir) {
2386
+ let current = path.resolve(startDir);
2387
+ while (true) {
2388
+ for (const marker of PROJECT_MARKERS) {
2389
+ if (marker.startsWith(".") && marker !== ".git" && !marker.startsWith(".version")) {
2390
+ try {
2391
+ const files = fs.readdirSync(current);
2392
+ if (files.some((f) => f.endsWith(marker))) {
2393
+ return buildResult(current, marker);
2394
+ }
2395
+ } catch {
2396
+ }
2397
+ } else if (fs.existsSync(path.join(current, marker))) {
2398
+ return buildResult(current, marker);
2399
+ }
2400
+ }
2401
+ const parent = path.dirname(current);
2402
+ if (parent === current) {
2403
+ return {
2404
+ found: false,
2405
+ root: path.resolve(startDir),
2406
+ hasConfig: false,
2407
+ hasGit: false,
2408
+ hasManifest: false
2409
+ };
2410
+ }
2411
+ current = parent;
2412
+ }
2413
+ }
2414
+ function buildResult(root, marker) {
2415
+ const configNames = [
2416
+ ".versionguard.yml",
2417
+ ".versionguard.yaml",
2418
+ "versionguard.yml",
2419
+ "versionguard.yaml"
2420
+ ];
2421
+ return {
2422
+ found: true,
2423
+ root,
2424
+ marker,
2425
+ hasConfig: configNames.some((c) => fs.existsSync(path.join(root, c))),
2426
+ hasGit: fs.existsSync(path.join(root, ".git")),
2427
+ hasManifest: [
2428
+ "package.json",
2429
+ "Cargo.toml",
2430
+ "pyproject.toml",
2431
+ "pubspec.yaml",
2432
+ "composer.json",
2433
+ "pom.xml",
2434
+ "VERSION"
2435
+ ].some((m) => fs.existsSync(path.join(root, m)))
2436
+ };
2437
+ }
2438
+ function formatNotProjectError(cwd, command) {
2439
+ const dir = path.basename(cwd) || cwd;
2440
+ const lines = [
2441
+ `Not a VersionGuard project: ${dir}`,
2442
+ "",
2443
+ "No .versionguard.yml, .git directory, or manifest file found.",
2444
+ "",
2445
+ "To get started:",
2446
+ " versionguard init Set up a new project interactively",
2447
+ " versionguard init --yes Set up with defaults",
2448
+ "",
2449
+ "Or run from a project root directory:",
2450
+ ` cd /path/to/project && versionguard ${command}`
2451
+ ];
2452
+ return lines.join("\n");
2453
+ }
1733
2454
  function runGit(cwd, args, encoding) {
1734
2455
  return childProcess.execFileSync("git", args, {
1735
2456
  cwd,
@@ -1897,7 +2618,8 @@ function getTagPreflightError(config, cwd, expectedVersion, allowAutoFix = false
1897
2618
  const versionResult = config.versioning.type === "semver" ? validate$1(version) : validate$2(
1898
2619
  version,
1899
2620
  config.versioning.calver?.format ?? "YYYY.MM.PATCH",
1900
- config.versioning.calver?.preventFutureDates ?? true
2621
+ config.versioning.calver?.preventFutureDates ?? true,
2622
+ config.versioning.schemeRules
1901
2623
  );
1902
2624
  if (!versionResult.valid) {
1903
2625
  return versionResult.errors[0]?.message ?? `Invalid version: ${version}`;
@@ -1981,7 +2703,12 @@ function validateVersion(version, config) {
1981
2703
  return validate$1(version);
1982
2704
  }
1983
2705
  const calverConfig = getCalVerConfig(config);
1984
- return validate$2(version, calverConfig.format, calverConfig.preventFutureDates);
2706
+ return validate$2(
2707
+ version,
2708
+ calverConfig.format,
2709
+ calverConfig.preventFutureDates,
2710
+ config.versioning.schemeRules
2711
+ );
1985
2712
  }
1986
2713
  function validate(config, cwd = process.cwd()) {
1987
2714
  const errors = [];
@@ -2093,12 +2820,6 @@ function canBump(currentVersion, newVersion, config) {
2093
2820
  }
2094
2821
  return { canBump: true };
2095
2822
  }
2096
- function getCalVerConfig(config) {
2097
- if (!config.versioning.calver) {
2098
- throw new Error('CalVer configuration is required when versioning.type is "calver"');
2099
- }
2100
- return config.versioning.calver;
2101
- }
2102
2823
  function isWorktreeClean(cwd) {
2103
2824
  try {
2104
2825
  return execSync("git status --porcelain", { cwd, encoding: "utf-8" }).trim().length === 0;
@@ -2107,51 +2828,58 @@ function isWorktreeClean(cwd) {
2107
2828
  }
2108
2829
  }
2109
2830
  export {
2110
- fixChangelog as A,
2111
- fixPackageVersion as B,
2112
- getAllTags as C,
2113
- getLatestTag as D,
2114
- getTagFeedback as E,
2115
- getVersionSource as F,
2831
+ checkHardcodedVersions as A,
2832
+ checkHookIntegrity as B,
2833
+ checkHooksPathOverride as C,
2834
+ checkHuskyBypass as D,
2835
+ detectManifests as E,
2836
+ fixChangelog as F,
2116
2837
  GitTagSource as G,
2117
- resolveVersionSource as H,
2118
- semver as I,
2838
+ fixPackageVersion as H,
2839
+ getAllTags as I,
2119
2840
  JsonVersionSource as J,
2120
- suggestTagMessage as K,
2121
- sync as L,
2122
- syncVersion as M,
2123
- validateChangelog as N,
2124
- validateTagForPush as O,
2125
- validateVersion as P,
2841
+ getCalVerConfig as K,
2842
+ getLatestTag as L,
2843
+ getTagFeedback as M,
2844
+ getVersionSource as N,
2845
+ initConfig as O,
2846
+ resolveVersionSource as P,
2847
+ semver as Q,
2126
2848
  RegexVersionSource as R,
2849
+ suggestTagMessage as S,
2127
2850
  TomlVersionSource as T,
2851
+ sync as U,
2128
2852
  VersionFileSource as V,
2853
+ syncVersion as W,
2854
+ validateChangelog as X,
2129
2855
  YamlVersionSource as Y,
2856
+ validateTagForPush as Z,
2857
+ validateVersion as _,
2130
2858
  installHooks as a,
2131
2859
  getPackageVersion as b,
2132
- getVersionFeedback as c,
2133
- getSyncFeedback as d,
2134
- getChangelogFeedback as e,
2135
- doctor as f,
2860
+ createCkmEngine as c,
2861
+ getVersionFeedback as d,
2862
+ getSyncFeedback as e,
2863
+ getChangelogFeedback as f,
2136
2864
  getConfig as g,
2137
2865
  handlePostTag as h,
2138
- initConfig as i,
2139
- fixAll as j,
2140
- fixSyncIssues as k,
2141
- setPackageVersion as l,
2142
- createTag as m,
2143
- areHooksInstalled as n,
2144
- calver as o,
2145
- canBump as p,
2146
- checkEnforceHooksPolicy as q,
2866
+ isValidCalVerFormat as i,
2867
+ doctor as j,
2868
+ fixAll as k,
2869
+ isChangesetMangled as l,
2870
+ fixChangesetMangling as m,
2871
+ fixSyncIssues as n,
2872
+ setPackageVersion as o,
2873
+ createTag as p,
2874
+ areHooksInstalled as q,
2147
2875
  runGuardChecks as r,
2148
2876
  suggestNextVersion as s,
2149
- checkHardcodedVersions as t,
2877
+ findProjectRoot as t,
2150
2878
  uninstallHooks as u,
2151
2879
  validate as v,
2152
- checkHookIntegrity as w,
2153
- checkHooksPathOverride as x,
2154
- checkHuskyBypass as y,
2155
- detectManifests as z
2880
+ formatNotProjectError as w,
2881
+ calver as x,
2882
+ canBump as y,
2883
+ checkEnforceHooksPolicy as z
2156
2884
  };
2157
- //# sourceMappingURL=index-BrZJDWya.js.map
2885
+ //# sourceMappingURL=index-CwOyEn5L.js.map