@savvy-web/changesets 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,19 +2,18 @@
2
2
 
3
3
  [![npm version][npm-badge]][npm-url]
4
4
  [![License: MIT][license-badge]][license-url]
5
+ [![Node.js >= 24][node-badge]][node-url]
5
6
 
6
7
  Custom changelog formatter and markdown processing pipeline for the Silk Suite. Replaces the default `@changesets/cli/changelog` formatter with a three-layer architecture that validates changeset files, formats structured changelog entries, and post-processes the generated CHANGELOG.md.
7
8
 
8
9
  ## Features
9
10
 
10
- - **Section-aware changesets** -- Use h2 headings in changeset files to categorize changes (Features, Bug Fixes, Breaking Changes, etc.)
11
+ - **Section-aware changesets** -- Categorize changes with h2 headings (Features, Bug Fixes, Breaking Changes, etc.)
11
12
  - **Three-layer pipeline** -- Pre-validation (remark-lint), changelog formatting (Changesets API), and post-processing (remark-transform)
12
- - **13 section categories** -- Consistent categorization with priority-based ordering across all layers
13
- - **CLI tooling** -- `savvy-changesets` binary with init, lint, transform, check, and version subcommands for CI and local use
13
+ - **CLI tooling** -- `savvy-changesets` binary with init, lint, check, transform, and version subcommands
14
14
  - **GitHub integration** -- Automatic PR links, commit references, and contributor attribution
15
- - **Version file syncing** -- Bump version fields in additional JSON files (beyond `package.json`) using glob patterns and JSONPath expressions
16
- - **Remark plugins** -- Lint rules and transform plugins via `@savvy-web/changesets/remark`
17
- - **markdownlint rules** -- Custom rules compatible with [markdownlint-cli2](https://www.npmjs.com/package/markdownlint-cli2) and the VS Code extension via `@savvy-web/changesets/markdownlint`
15
+ - **Version file syncing** -- Bump version fields in additional JSON files using glob patterns and JSONPath expressions
16
+ - **Editor support** -- markdownlint rules for real-time validation in VS Code and CI
18
17
 
19
18
  ## Installation
20
19
 
@@ -30,7 +29,7 @@ Bootstrap your repository:
30
29
  savvy-changesets init
31
30
  ```
32
31
 
33
- This creates `.changeset/config.json` with auto-detected GitHub repo settings. Or configure manually:
32
+ This creates `.changeset/config.json` with auto-detected GitHub repo settings and configures markdownlint rules. Or configure manually:
34
33
 
35
34
  ```json
36
35
  {
@@ -41,7 +40,7 @@ This creates `.changeset/config.json` with auto-detected GitHub repo settings. O
41
40
  }
42
41
  ```
43
42
 
44
- Write [section-aware changeset files](docs/section-aware-pipeline.md):
43
+ Write section-aware changeset files:
45
44
 
46
45
  ```markdown
47
46
  ---
@@ -58,58 +57,13 @@ Added a new authentication system with OAuth2 support.
58
57
  - Updated integration test fixtures
59
58
  ```
60
59
 
61
- ## Version File Syncing
62
-
63
- If your project has JSON files beyond `package.json` that contain version fields (e.g., `plugin.json`, `marketplace.json`), add `versionFiles` to your changelog options to keep them in sync during `changeset version`:
64
-
65
- ```json
66
- {
67
- "changelog": ["@savvy-web/changesets/changelog", {
68
- "repo": "owner/repo",
69
- "versionFiles": [
70
- { "glob": "plugin.json" },
71
- { "glob": ".claude-plugin/marketplace.json", "paths": ["$.metadata.version", "$.plugins[*].version"] }
72
- ]
73
- }]
74
- }
75
- ```
76
-
77
- When `paths` is omitted it defaults to `["$.version"]`. In monorepos, each matched file inherits the version from its nearest workspace package. See [Configuration -- versionFiles](./docs/configuration.md#versionfiles-optional) for full details including supported JSONPath syntax.
78
-
79
- ## markdownlint Integration
80
-
81
- Register the custom rules in your base config (e.g., `lib/configs/.markdownlint-cli2.jsonc`):
82
-
83
- ```jsonc
84
- {
85
- "customRules": [
86
- "@savvy-web/changesets/markdownlint"
87
- ],
88
- "config": {
89
- "changeset-heading-hierarchy": false,
90
- "changeset-required-sections": false,
91
- "changeset-content-structure": false
92
- }
93
- }
94
- ```
95
-
96
- Then enable the rules only for changeset files by creating `.changeset/.markdownlint.json`:
97
-
98
- ```json
99
- {
100
- "extends": "../lib/configs/.markdownlint-cli2.jsonc",
101
- "default": false,
102
- "changeset-heading-hierarchy": true,
103
- "changeset-required-sections": true,
104
- "changeset-content-structure": true,
105
- "MD041": false
106
- }
107
- ```
108
-
109
60
  ## Documentation
110
61
 
111
- - [Section-Aware Pipeline](./docs/section-aware-pipeline.md) -- End-to-end walkthrough of how section-aware changesets flow through the three-layer pipeline
112
- - [Full documentation](./docs/) -- CLI usage, API reference, configuration, and architecture
62
+ - [Section-Aware Pipeline](./docs/section-aware-pipeline.md) -- End-to-end walkthrough of how section-aware changesets flow through the pipeline
63
+ - [Configuration](./docs/configuration.md) -- Options, version files, markdownlint integration, CI scripts
64
+ - [CLI Reference](./docs/cli.md) -- All commands and options
65
+ - [API Reference](./docs/api.md) -- Classes, types, Effect services, remark plugins
66
+ - [Architecture](./docs/architecture.md) -- Three-layer pipeline design and export map
113
67
 
114
68
  ## License
115
69
 
@@ -119,3 +73,5 @@ MIT
119
73
  [npm-url]: https://www.npmjs.com/package/@savvy-web/changesets
120
74
  [license-badge]: https://img.shields.io/badge/License-MIT-yellow.svg
121
75
  [license-url]: https://opensource.org/licenses/MIT
76
+ [node-badge]: https://img.shields.io/badge/node-%3E%3D24-brightgreen
77
+ [node-url]: https://nodejs.org/
package/cjs/index.cjs CHANGED
@@ -859,6 +859,19 @@ var __webpack_exports__ = {};
859
859
  if (!(0, categories.Lr)(text)) file.message(`Unknown section "${text}". Valid sections: ${(0, categories.Rr)().join(", ")}`, node);
860
860
  });
861
861
  });
862
+ const IGNORED_TYPES = new Set([
863
+ "html"
864
+ ]);
865
+ function isContentNode(node) {
866
+ if ("heading" === node.type) return false;
867
+ return !IGNORED_TYPES.has(node.type);
868
+ }
869
+ const UncategorizedContentRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-uncategorized-content", (tree, file)=>{
870
+ for (const node of tree.children){
871
+ if ("heading" === node.type && 2 === node.depth) break;
872
+ if (isContentNode(node)) file.message("Content must be placed under a category heading (## heading)", node);
873
+ }
874
+ });
862
875
  function stripFrontmatter(content) {
863
876
  return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
864
877
  }
@@ -869,7 +882,7 @@ var __webpack_exports__ = {};
869
882
  }
870
883
  static validateContent(content, filePath = "<input>") {
871
884
  const body = stripFrontmatter(content);
872
- const processor = (0, external_unified_.unified)().use(external_remark_parse_default()).use(external_remark_stringify_default()).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule);
885
+ const processor = (0, external_unified_.unified)().use(external_remark_parse_default()).use(external_remark_stringify_default()).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule).use(UncategorizedContentRule);
873
886
  const file = processor.processSync(body);
874
887
  return file.messages.map((msg)=>({
875
888
  file: filePath,
package/cjs/index.d.cts CHANGED
@@ -256,9 +256,9 @@ export declare interface Changeset extends Schema.Schema.Type<typeof ChangesetSc
256
256
  /**
257
257
  * Static class for linting changeset files.
258
258
  *
259
- * Runs the three remark-lint rules (heading-hierarchy, required-sections,
260
- * content-structure) against changeset markdown and returns structured
261
- * diagnostic messages.
259
+ * Runs the four remark-lint rules (heading-hierarchy, required-sections,
260
+ * content-structure, uncategorized-content) against changeset markdown
261
+ * and returns structured diagnostic messages.
262
262
  *
263
263
  * @example
264
264
  * ```typescript
@@ -26,6 +26,7 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  ContentStructureRule: ()=>ContentStructureRule,
28
28
  HeadingHierarchyRule: ()=>HeadingHierarchyRule,
29
+ UncategorizedContentRule: ()=>UncategorizedContentRule,
29
30
  default: ()=>markdownlint,
30
31
  RequiredSectionsRule: ()=>RequiredSectionsRule
31
32
  });
@@ -276,20 +277,46 @@ const RequiredSectionsRule = {
276
277
  }
277
278
  }
278
279
  };
280
+ const UncategorizedContentRule = {
281
+ names: [
282
+ "changeset-uncategorized-content",
283
+ "CSH004"
284
+ ],
285
+ description: "All content must be placed under a category heading (## heading)",
286
+ tags: [
287
+ "changeset"
288
+ ],
289
+ parser: "micromark",
290
+ function: function(params, onError) {
291
+ const tokens = params.parsers.micromark.tokens;
292
+ for (const token of tokens){
293
+ if ("atxHeading" === token.type && 2 === getHeadingLevel(token)) break;
294
+ if ("lineEnding" !== token.type && "lineEndingBlank" !== token.type && "htmlFlow" !== token.type) {
295
+ if ("atxHeading" !== token.type) onError({
296
+ lineNumber: token.startLine,
297
+ detail: "Content must be placed under a category heading (## heading)"
298
+ });
299
+ }
300
+ }
301
+ }
302
+ };
279
303
  const SilkChangesetsRules = [
280
304
  HeadingHierarchyRule,
281
305
  RequiredSectionsRule,
282
- ContentStructureRule
306
+ ContentStructureRule,
307
+ UncategorizedContentRule
283
308
  ];
284
309
  const markdownlint = SilkChangesetsRules;
285
310
  exports.ContentStructureRule = __webpack_exports__.ContentStructureRule;
286
311
  exports.HeadingHierarchyRule = __webpack_exports__.HeadingHierarchyRule;
287
312
  exports.RequiredSectionsRule = __webpack_exports__.RequiredSectionsRule;
313
+ exports.UncategorizedContentRule = __webpack_exports__.UncategorizedContentRule;
288
314
  exports["default"] = __webpack_exports__["default"];
289
315
  for(var __rspack_i in __webpack_exports__)if (-1 === [
290
316
  "ContentStructureRule",
291
317
  "HeadingHierarchyRule",
292
318
  "RequiredSectionsRule",
319
+ "UncategorizedContentRule",
293
320
  "default"
294
321
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
295
322
  Object.defineProperty(exports, '__esModule', {
@@ -8,6 +8,7 @@
8
8
  * - `changeset-heading-hierarchy` (CSH001): Enforce h2 start, no h1, no depth skips
9
9
  * - `changeset-required-sections` (CSH002): Validate section headings match known categories
10
10
  * - `changeset-content-structure` (CSH003): Content quality validation
11
+ * - `changeset-uncategorized-content` (CSH004): Reject content before first h2 heading
11
12
  *
12
13
  * @packageDocumentation
13
14
  */
@@ -58,4 +59,12 @@ export declare const RequiredSectionsRule: Rule;
58
59
  declare const SilkChangesetsRules: Rule[];
59
60
  export default SilkChangesetsRules;
60
61
 
62
+ /**
63
+ * markdownlint rule: changeset-uncategorized-content (CSH004)
64
+ *
65
+ * Detects content that appears before the first h2 heading in a changeset file.
66
+ * All content must be placed under a categorized section (## heading).
67
+ */
68
+ export declare const UncategorizedContentRule: Rule;
69
+
61
70
  export { }
package/cjs/remark.cjs CHANGED
@@ -24,6 +24,7 @@ var __webpack_require__ = {};
24
24
  var __webpack_exports__ = {};
25
25
  __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
+ UncategorizedContentRule: ()=>UncategorizedContentRule,
27
28
  SilkChangesetTransformPreset: ()=>SilkChangesetTransformPreset,
28
29
  NormalizeFormatPlugin: ()=>NormalizeFormatPlugin,
29
30
  IssueLinkRefsPlugin: ()=>IssueLinkRefsPlugin,
@@ -502,10 +503,24 @@ const RequiredSectionsRule = (0, external_unified_lint_rule_namespaceObject.lint
502
503
  if (!isValidHeading(text)) file.message(`Unknown section "${text}". Valid sections: ${allHeadings().join(", ")}`, node);
503
504
  });
504
505
  });
506
+ const IGNORED_TYPES = new Set([
507
+ "html"
508
+ ]);
509
+ function isContentNode(node) {
510
+ if ("heading" === node.type) return false;
511
+ return !IGNORED_TYPES.has(node.type);
512
+ }
513
+ const UncategorizedContentRule = (0, external_unified_lint_rule_namespaceObject.lintRule)("remark-lint:changeset-uncategorized-content", (tree, file)=>{
514
+ for (const node of tree.children){
515
+ if ("heading" === node.type && 2 === node.depth) break;
516
+ if (isContentNode(node)) file.message("Content must be placed under a category heading (## heading)", node);
517
+ }
518
+ });
505
519
  const SilkChangesetPreset = [
506
520
  HeadingHierarchyRule,
507
521
  RequiredSectionsRule,
508
- ContentStructureRule
522
+ ContentStructureRule,
523
+ UncategorizedContentRule
509
524
  ];
510
525
  const SilkChangesetTransformPreset = [
511
526
  MergeSectionsPlugin,
@@ -526,6 +541,7 @@ exports.ReorderSectionsPlugin = __webpack_exports__.ReorderSectionsPlugin;
526
541
  exports.RequiredSectionsRule = __webpack_exports__.RequiredSectionsRule;
527
542
  exports.SilkChangesetPreset = __webpack_exports__.SilkChangesetPreset;
528
543
  exports.SilkChangesetTransformPreset = __webpack_exports__.SilkChangesetTransformPreset;
544
+ exports.UncategorizedContentRule = __webpack_exports__.UncategorizedContentRule;
529
545
  for(var __rspack_i in __webpack_exports__)if (-1 === [
530
546
  "ContentStructureRule",
531
547
  "ContributorFootnotesPlugin",
@@ -537,7 +553,8 @@ for(var __rspack_i in __webpack_exports__)if (-1 === [
537
553
  "ReorderSectionsPlugin",
538
554
  "RequiredSectionsRule",
539
555
  "SilkChangesetPreset",
540
- "SilkChangesetTransformPreset"
556
+ "SilkChangesetTransformPreset",
557
+ "UncategorizedContentRule"
541
558
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
542
559
  Object.defineProperty(exports, '__esModule', {
543
560
  value: true
package/cjs/remark.d.cts CHANGED
@@ -71,7 +71,7 @@ export declare const RequiredSectionsRule: Plugin_2<Root, unknown>;
71
71
  *
72
72
  * @public
73
73
  */
74
- export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
74
+ export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
75
75
 
76
76
  /**
77
77
  * Ordered array of all transform plugins in the correct execution order.
@@ -89,4 +89,6 @@ export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plu
89
89
  */
90
90
  export declare const SilkChangesetTransformPreset: readonly [Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>];
91
91
 
92
+ export declare const UncategorizedContentRule: Plugin_2<Root, unknown>;
93
+
92
94
  export { }
package/esm/273.js CHANGED
@@ -1,7 +1,7 @@
1
- import { existsSync, globSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import { join, relative, resolve } from "node:path";
3
3
  import { unified, remark_gfm, remark_stringify, remark_parse } from "./795.js";
4
- import { MergeSectionsPlugin, DeduplicateItemsPlugin, IssueLinkRefsPlugin, ContentStructureRule, HeadingHierarchyRule, NormalizeFormatPlugin, ReorderSectionsPlugin, ContributorFootnotesPlugin, RequiredSectionsRule } from "./234.js";
4
+ import { UncategorizedContentRule, MergeSectionsPlugin, DeduplicateItemsPlugin, IssueLinkRefsPlugin, ContentStructureRule, HeadingHierarchyRule, NormalizeFormatPlugin, ReorderSectionsPlugin, ContributorFootnotesPlugin, RequiredSectionsRule } from "./622.js";
5
5
  function stripFrontmatter(content) {
6
6
  return content.replace(/^---\n[\s\S]*?\n---\n?/, "");
7
7
  }
@@ -12,7 +12,7 @@ class ChangesetLinter {
12
12
  }
13
13
  static validateContent(content, filePath = "<input>") {
14
14
  const body = stripFrontmatter(content);
15
- const processor = unified().use(remark_parse).use(remark_stringify).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule);
15
+ const processor = unified().use(remark_parse).use(remark_stringify).use(HeadingHierarchyRule).use(RequiredSectionsRule).use(ContentStructureRule).use(UncategorizedContentRule);
16
16
  const file = processor.processSync(body);
17
17
  return file.messages.map((msg)=>({
18
18
  file: filePath,
@@ -38,4 +38,4 @@ class ChangelogTransformer {
38
38
  writeFileSync(filePath, result, "utf-8");
39
39
  }
40
40
  }
41
- export { ChangelogTransformer, ChangesetLinter, existsSync, globSync, join, mkdirSync, readFileSync, relative, resolve, writeFileSync };
41
+ export { ChangelogTransformer, ChangesetLinter, existsSync, join, mkdirSync, readFileSync, relative, resolve, writeFileSync };
@@ -31,6 +31,19 @@ const RequiredSectionsRule = lintRule("remark-lint:changeset-required-sections",
31
31
  if (!isValidHeading(text)) file.message(`Unknown section "${text}". Valid sections: ${allHeadings().join(", ")}`, node);
32
32
  });
33
33
  });
34
+ const IGNORED_TYPES = new Set([
35
+ "html"
36
+ ]);
37
+ function isContentNode(node) {
38
+ if ("heading" === node.type) return false;
39
+ return !IGNORED_TYPES.has(node.type);
40
+ }
41
+ const UncategorizedContentRule = lintRule("remark-lint:changeset-uncategorized-content", (tree, file)=>{
42
+ for (const node of tree.children){
43
+ if ("heading" === node.type && 2 === node.depth) break;
44
+ if (isContentNode(node)) file.message("Content must be placed under a category heading (## heading)", node);
45
+ }
46
+ });
34
47
  function getVersionBlocks(tree) {
35
48
  const blocks = [];
36
49
  for(let i = 0; i < tree.children.length; i++){
@@ -334,4 +347,4 @@ const ReorderSectionsPlugin = ()=>(tree)=>{
334
347
  tree.children.splice(block.startIndex, blockLength, ...newChildren);
335
348
  }
336
349
  };
337
- export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule };
350
+ export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule, UncategorizedContentRule };
@@ -4,7 +4,8 @@ import { NodeContext, NodeRuntime } from "@effect/platform-node";
4
4
  import { execSync } from "node:child_process";
5
5
  import { findProjectRoot, getWorkspaceInfos } from "workspace-tools";
6
6
  import { parse } from "jsonc-parser";
7
- import { globSync, readFileSync, ChangelogTransformer, ChangesetLinter, resolve, existsSync, relative, mkdirSync, writeFileSync, join } from "../273.js";
7
+ import { globSync } from "tinyglobby";
8
+ import { readFileSync, ChangelogTransformer, ChangesetLinter, resolve, existsSync, relative, mkdirSync, writeFileSync, join } from "../273.js";
8
9
  import { VersionFilesSchema, Schema, VersionFileError, Data, Effect } from "../795.js";
9
10
  const dirArg = Args.directory({
10
11
  name: "dir"
@@ -47,7 +48,8 @@ const MARKDOWNLINT_CONFIG_PATHS = [
47
48
  const RULE_NAMES = [
48
49
  "changeset-heading-hierarchy",
49
50
  "changeset-required-sections",
50
- "changeset-content-structure"
51
+ "changeset-content-structure",
52
+ "changeset-uncategorized-content"
51
53
  ];
52
54
  const DEFAULT_CONFIG = {
53
55
  $schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
@@ -559,7 +561,9 @@ class VersionFiles {
559
561
  for (const config of configs){
560
562
  const matches = globSync(config.glob, {
561
563
  cwd: resolvedCwd,
562
- exclude: (f)=>f.includes("node_modules")
564
+ ignore: [
565
+ "**/node_modules/**"
566
+ ]
563
567
  });
564
568
  for (const match of matches)results.push([
565
569
  join(resolvedCwd, match),
@@ -755,7 +759,7 @@ const rootCommand = Command.make("savvy-changesets").pipe(Command.withSubcommand
755
759
  ]));
756
760
  const cli = Command.run(rootCommand, {
757
761
  name: "savvy-changesets",
758
- version: "0.2.0"
762
+ version: "0.3.0"
759
763
  });
760
764
  function runCli() {
761
765
  const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer));
package/esm/index.d.ts CHANGED
@@ -256,9 +256,9 @@ export declare interface Changeset extends Schema.Schema.Type<typeof ChangesetSc
256
256
  /**
257
257
  * Static class for linting changeset files.
258
258
  *
259
- * Runs the three remark-lint rules (heading-hierarchy, required-sections,
260
- * content-structure) against changeset markdown and returns structured
261
- * diagnostic messages.
259
+ * Runs the four remark-lint rules (heading-hierarchy, required-sections,
260
+ * content-structure, uncategorized-content) against changeset markdown
261
+ * and returns structured diagnostic messages.
262
262
  *
263
263
  * @example
264
264
  * ```typescript
@@ -8,6 +8,7 @@
8
8
  * - `changeset-heading-hierarchy` (CSH001): Enforce h2 start, no h1, no depth skips
9
9
  * - `changeset-required-sections` (CSH002): Validate section headings match known categories
10
10
  * - `changeset-content-structure` (CSH003): Content quality validation
11
+ * - `changeset-uncategorized-content` (CSH004): Reject content before first h2 heading
11
12
  *
12
13
  * @packageDocumentation
13
14
  */
@@ -58,4 +59,12 @@ export declare const RequiredSectionsRule: Rule;
58
59
  declare const SilkChangesetsRules: Rule[];
59
60
  export default SilkChangesetsRules;
60
61
 
62
+ /**
63
+ * markdownlint rule: changeset-uncategorized-content (CSH004)
64
+ *
65
+ * Detects content that appears before the first h2 heading in a changeset file.
66
+ * All content must be placed under a categorized section (## heading).
67
+ */
68
+ export declare const UncategorizedContentRule: Rule;
69
+
61
70
  export { }
@@ -118,11 +118,35 @@ const RequiredSectionsRule = {
118
118
  }
119
119
  }
120
120
  };
121
+ const UncategorizedContentRule = {
122
+ names: [
123
+ "changeset-uncategorized-content",
124
+ "CSH004"
125
+ ],
126
+ description: "All content must be placed under a category heading (## heading)",
127
+ tags: [
128
+ "changeset"
129
+ ],
130
+ parser: "micromark",
131
+ function: function(params, onError) {
132
+ const tokens = params.parsers.micromark.tokens;
133
+ for (const token of tokens){
134
+ if ("atxHeading" === token.type && 2 === getHeadingLevel(token)) break;
135
+ if ("lineEnding" !== token.type && "lineEndingBlank" !== token.type && "htmlFlow" !== token.type) {
136
+ if ("atxHeading" !== token.type) onError({
137
+ lineNumber: token.startLine,
138
+ detail: "Content must be placed under a category heading (## heading)"
139
+ });
140
+ }
141
+ }
142
+ }
143
+ };
121
144
  const SilkChangesetsRules = [
122
145
  HeadingHierarchyRule,
123
146
  RequiredSectionsRule,
124
- ContentStructureRule
147
+ ContentStructureRule,
148
+ UncategorizedContentRule
125
149
  ];
126
150
  const markdownlint = SilkChangesetsRules;
127
151
  export default markdownlint;
128
- export { ContentStructureRule, HeadingHierarchyRule, RequiredSectionsRule };
152
+ export { ContentStructureRule, HeadingHierarchyRule, RequiredSectionsRule, UncategorizedContentRule };
package/esm/remark.d.ts CHANGED
@@ -71,7 +71,7 @@ export declare const RequiredSectionsRule: Plugin_2<Root, unknown>;
71
71
  *
72
72
  * @public
73
73
  */
74
- export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
74
+ export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>, Plugin_2<Root, unknown>];
75
75
 
76
76
  /**
77
77
  * Ordered array of all transform plugins in the correct execution order.
@@ -89,4 +89,6 @@ export declare const SilkChangesetPreset: readonly [Plugin_2<Root, unknown>, Plu
89
89
  */
90
90
  export declare const SilkChangesetTransformPreset: readonly [Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>, Plugin<[], Root>];
91
91
 
92
+ export declare const UncategorizedContentRule: Plugin_2<Root, unknown>;
93
+
92
94
  export { }
package/esm/remark.js CHANGED
@@ -1,8 +1,9 @@
1
- import { NormalizeFormatPlugin, DeduplicateItemsPlugin, MergeSectionsPlugin, ContentStructureRule, IssueLinkRefsPlugin, ReorderSectionsPlugin, HeadingHierarchyRule, ContributorFootnotesPlugin, RequiredSectionsRule } from "./234.js";
1
+ import { UncategorizedContentRule, NormalizeFormatPlugin, DeduplicateItemsPlugin, MergeSectionsPlugin, ContentStructureRule, IssueLinkRefsPlugin, ReorderSectionsPlugin, HeadingHierarchyRule, ContributorFootnotesPlugin, RequiredSectionsRule } from "./622.js";
2
2
  const SilkChangesetPreset = [
3
3
  HeadingHierarchyRule,
4
4
  RequiredSectionsRule,
5
- ContentStructureRule
5
+ ContentStructureRule,
6
+ UncategorizedContentRule
6
7
  ];
7
8
  const SilkChangesetTransformPreset = [
8
9
  MergeSectionsPlugin,
@@ -12,5 +13,5 @@ const SilkChangesetTransformPreset = [
12
13
  IssueLinkRefsPlugin,
13
14
  NormalizeFormatPlugin
14
15
  ];
15
- export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule } from "./234.js";
16
+ export { ContentStructureRule, ContributorFootnotesPlugin, DeduplicateItemsPlugin, HeadingHierarchyRule, IssueLinkRefsPlugin, MergeSectionsPlugin, NormalizeFormatPlugin, ReorderSectionsPlugin, RequiredSectionsRule, UncategorizedContentRule } from "./622.js";
16
17
  export { SilkChangesetPreset, SilkChangesetTransformPreset };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvy-web/changesets",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Custom changelog formatter and markdown processing pipeline for the Silk Suite. Provides structured changeset sections, remark-based validation and transformation, and an Effect CLI.",
6
6
  "keywords": [
@@ -64,6 +64,7 @@
64
64
  "remark-gfm": "^4.0.1",
65
65
  "remark-parse": "^11.0.0",
66
66
  "remark-stringify": "^11.0.0",
67
+ "tinyglobby": "^0.2.15",
67
68
  "unified": "^11.0.5",
68
69
  "unified-lint-rule": "^3.0.1",
69
70
  "unist-util-visit": "^5.1.0",