@codluv/versionguard 0.4.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.
package/dist/calver.d.ts CHANGED
@@ -154,6 +154,7 @@ export declare function parse(version: string, calverFormat: CalVerFormat): CalV
154
154
  * @param version - Version string to validate.
155
155
  * @param calverFormat - Format expected for the version string.
156
156
  * @param preventFutureDates - Whether future dates should be reported as errors.
157
+ * @param schemeRules - Optional scheme rules for modifier validation and segment count warnings.
157
158
  * @returns A validation result containing any discovered errors and the parsed version on success.
158
159
  *
159
160
  * @example
@@ -1 +1 @@
1
- {"version":3,"file":"calver.d.ts","sourceRoot":"","sources":["../src/calver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EAEZ,WAAW,EAEX,gBAAgB,EACjB,MAAM,SAAS,CAAC;AA8BjB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,IAAI,YAAY,CAoBhF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAE3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;IAE1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAEnB;;;;OAIG;IACH,GAAG,CAAC,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;IAExB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,YAAY,GAAG,kBAAkB,CAoB1E;AAyCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAKpE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAqDhF;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,EAC1B,kBAAkB,GAAE,OAAc,EAClC,WAAW,CAAC,EAAE,WAAW,GACxB,gBAAgB,CA6GlB;AAgBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsB9C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,YAAY,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAY5F;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,CAiBhF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,CAqB7E;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,EAAE,CAE5F"}
1
+ {"version":3,"file":"calver.d.ts","sourceRoot":"","sources":["../src/calver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EAEZ,WAAW,EAEX,gBAAgB,EACjB,MAAM,SAAS,CAAC;AA8BjB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,IAAI,YAAY,CAoBhF;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IAE3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;IAE1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IAEnB;;;;OAIG;IACH,GAAG,CAAC,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,CAAC;IAExB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,YAAY,GAAG,kBAAkB,CAoB1E;AAyCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,YAAY,GAAG,MAAM,CAKpE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAqDhF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,QAAQ,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,EAC1B,kBAAkB,GAAE,OAAc,EAClC,WAAW,CAAC,EAAE,WAAW,GACxB,gBAAgB,CA6GlB;AAgBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAsB9C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,YAAY,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAY5F;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,CAiBhF;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,CAqB7E;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,MAAM,EAAE,CAE5F"}
@@ -80,4 +80,56 @@ export declare function getLatestVersion(changelogPath: string): string | null;
80
80
  * ```
81
81
  */
82
82
  export declare function addVersionEntry(changelogPath: string, version: string, date?: string): void;
83
+ /**
84
+ * Detects whether a changelog has been mangled by Changesets.
85
+ *
86
+ * @remarks
87
+ * Changesets prepends version content above the Keep a Changelog preamble,
88
+ * producing `## 0.4.0` (no brackets, no date) before the "All notable changes"
89
+ * paragraph. This function detects that pattern.
90
+ *
91
+ * @param changelogPath - Path to the changelog file.
92
+ * @returns `true` when the changelog appears to be mangled by Changesets.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { isChangesetMangled } from 'versionguard';
97
+ *
98
+ * if (isChangesetMangled('CHANGELOG.md')) {
99
+ * fixChangesetMangling('CHANGELOG.md');
100
+ * }
101
+ * ```
102
+ *
103
+ * @public
104
+ * @since 0.4.0
105
+ */
106
+ export declare function isChangesetMangled(changelogPath: string): boolean;
107
+ /**
108
+ * Fixes a Changesets-mangled changelog into proper Keep a Changelog format.
109
+ *
110
+ * @remarks
111
+ * This function:
112
+ * 1. Extracts the version number and content prepended by Changesets
113
+ * 2. Converts Changesets section names (Minor Changes, Patch Changes) to
114
+ * Keep a Changelog names (Added, Fixed)
115
+ * 3. Strips commit hashes from entry lines
116
+ * 4. Adds the date and brackets to the version header
117
+ * 5. Inserts the entry after `## [Unreleased]` in the correct position
118
+ * 6. Restores the preamble to its proper location
119
+ *
120
+ * @param changelogPath - Path to the changelog file to fix.
121
+ * @param date - Release date in `YYYY-MM-DD` format.
122
+ * @returns `true` when the file was modified, `false` when no fix was needed.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * import { fixChangesetMangling } from 'versionguard';
127
+ *
128
+ * const fixed = fixChangesetMangling('CHANGELOG.md');
129
+ * ```
130
+ *
131
+ * @public
132
+ * @since 0.4.0
133
+ */
134
+ export declare function fixChangesetMangling(changelogPath: string, date?: string): boolean;
83
135
  //# sourceMappingURL=changelog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,OAAc,EACtB,YAAY,GAAE,OAAc,GAC3B,yBAAyB,CAgD3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAA8C,GACnD,IAAI,CAmBN"}
1
+ {"version":3,"file":"changelog.d.ts","sourceRoot":"","sources":["../src/changelog.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IACf;;OAEG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;OAEG;IACH,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAID;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,OAAc,EACtB,YAAY,GAAE,OAAc,GAC3B,yBAAyB,CAgD3B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQrE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAA8C,GACnD,IAAI,CAmBN;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAMjE;AASD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE,MAAM,EACrB,IAAI,GAAE,MAA8C,GACnD,OAAO,CAoET"}
@@ -381,10 +381,109 @@ function addVersionEntry(changelogPath, version, date = (/* @__PURE__ */ new Dat
381
381
  const updated = `${content.slice(0, insertIndex)}${block}${content.slice(insertIndex)}`;
382
382
  fs.writeFileSync(changelogPath, updated, "utf-8");
383
383
  }
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
+ }
384
481
  function escapeRegExp$1(value) {
385
482
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
386
483
  }
387
484
  const HOOK_NAMES$1 = ["pre-commit", "pre-push", "post-tag"];
485
+ const VG_BLOCK_START = "# >>> versionguard >>>";
486
+ const VG_BLOCK_END = "# <<< versionguard <<<";
388
487
  function installHooks(config, cwd = process.cwd()) {
389
488
  const gitDir = findGitDir(cwd);
390
489
  if (!gitDir) {
@@ -395,7 +494,36 @@ function installHooks(config, cwd = process.cwd()) {
395
494
  for (const hookName of HOOK_NAMES$1) {
396
495
  if (config.hooks[hookName]) {
397
496
  const hookPath = path.join(hooksDir, hookName);
398
- 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
+ }
399
527
  }
400
528
  }
401
529
  }
@@ -407,7 +535,21 @@ function uninstallHooks(cwd = process.cwd()) {
407
535
  const hooksDir = path.join(gitDir, "hooks");
408
536
  for (const hookName of HOOK_NAMES$1) {
409
537
  const hookPath = path.join(hooksDir, hookName);
410
- 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)) {
411
553
  fs.unlinkSync(hookPath);
412
554
  }
413
555
  }
@@ -436,9 +578,8 @@ function areHooksInstalled(cwd = process.cwd()) {
436
578
  return fs.existsSync(hookPath) && fs.readFileSync(hookPath, "utf-8").includes("versionguard");
437
579
  });
438
580
  }
439
- function generateHookScript(hookName) {
440
- return `#!/bin/sh
441
- # versionguard
581
+ function generateHookBlock(hookName) {
582
+ return `${VG_BLOCK_START}
442
583
  # VersionGuard ${hookName} hook
443
584
  # --no-install prevents accidentally downloading an unscoped package
444
585
  # if @codluv/versionguard is not installed locally
@@ -448,8 +589,36 @@ if [ $status -ne 0 ]; then
448
589
  echo "VersionGuard validation failed."
449
590
  exit $status
450
591
  fi
592
+ ${VG_BLOCK_END}`;
593
+ }
594
+ function generateHookScript(hookName) {
595
+ return `#!/bin/sh
596
+
597
+ ${generateHookBlock(hookName)}
451
598
  `;
452
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
+ }
453
622
  class GitTagSource {
454
623
  /** Human-readable provider name. */
455
624
  name = "git-tag";
@@ -1364,6 +1533,163 @@ function getCalVerConfig(config) {
1364
1533
  }
1365
1534
  return config.versioning.calver;
1366
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
+ }
1367
1693
  const CONFIG_FILE_NAMES = [
1368
1694
  ".versionguard.yml",
1369
1695
  ".versionguard.yaml",
@@ -1870,6 +2196,17 @@ function fixAll(config, targetVersion, cwd = process.cwd()) {
1870
2196
  const syncResults = fixSyncIssues(config, cwd);
1871
2197
  results.push(...syncResults);
1872
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
+ }
1873
2210
  const changelogResult = fixChangelog(version, config, cwd);
1874
2211
  if (changelogResult.fixed) {
1875
2212
  results.push(changelogResult);
@@ -2028,6 +2365,92 @@ function runGuardChecks(config, cwd) {
2028
2365
  warnings
2029
2366
  };
2030
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
+ }
2031
2454
  function runGit(cwd, args, encoding) {
2032
2455
  return childProcess.execFileSync("git", args, {
2033
2456
  cwd,
@@ -2405,53 +2828,58 @@ function isWorktreeClean(cwd) {
2405
2828
  }
2406
2829
  }
2407
2830
  export {
2408
- fixChangelog as A,
2409
- fixPackageVersion as B,
2410
- getAllTags as C,
2411
- getCalVerConfig as D,
2412
- getLatestTag as E,
2413
- getTagFeedback 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,
2414
2837
  GitTagSource as G,
2415
- getVersionSource as H,
2416
- initConfig as I,
2838
+ fixPackageVersion as H,
2839
+ getAllTags as I,
2417
2840
  JsonVersionSource as J,
2418
- resolveVersionSource as K,
2419
- semver as L,
2420
- suggestTagMessage as M,
2421
- sync as N,
2422
- syncVersion as O,
2423
- validateChangelog as P,
2424
- validateTagForPush as Q,
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,
2425
2848
  RegexVersionSource as R,
2426
- validateVersion as S,
2849
+ suggestTagMessage as S,
2427
2850
  TomlVersionSource as T,
2851
+ sync as U,
2428
2852
  VersionFileSource as V,
2853
+ syncVersion as W,
2854
+ validateChangelog as X,
2429
2855
  YamlVersionSource as Y,
2856
+ validateTagForPush as Z,
2857
+ validateVersion as _,
2430
2858
  installHooks as a,
2431
2859
  getPackageVersion as b,
2432
- getVersionFeedback as c,
2433
- getSyncFeedback as d,
2434
- getChangelogFeedback as e,
2435
- doctor as f,
2860
+ createCkmEngine as c,
2861
+ getVersionFeedback as d,
2862
+ getSyncFeedback as e,
2863
+ getChangelogFeedback as f,
2436
2864
  getConfig as g,
2437
2865
  handlePostTag as h,
2438
2866
  isValidCalVerFormat as i,
2439
- fixAll as j,
2440
- fixSyncIssues as k,
2441
- setPackageVersion as l,
2442
- createTag as m,
2443
- areHooksInstalled as n,
2444
- calver as o,
2445
- canBump as p,
2446
- checkEnforceHooksPolicy as q,
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,
2447
2875
  runGuardChecks as r,
2448
2876
  suggestNextVersion as s,
2449
- checkHardcodedVersions as t,
2877
+ findProjectRoot as t,
2450
2878
  uninstallHooks as u,
2451
2879
  validate as v,
2452
- checkHookIntegrity as w,
2453
- checkHooksPathOverride as x,
2454
- checkHuskyBypass as y,
2455
- detectManifests as z
2880
+ formatNotProjectError as w,
2881
+ calver as x,
2882
+ canBump as y,
2883
+ checkEnforceHooksPolicy as z
2456
2884
  };
2457
- //# sourceMappingURL=index-B3R60bYJ.js.map
2885
+ //# sourceMappingURL=index-CwOyEn5L.js.map