@savvy-web/silk-effects 0.6.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -17
- package/_virtual/_rolldown/runtime.js +18 -0
- package/changesets/api/categories.js +247 -0
- package/changesets/api/changelog.js +134 -0
- package/changesets/api/dependency-table.js +163 -0
- package/changesets/api/linter.js +168 -0
- package/changesets/api/transformer.js +140 -0
- package/changesets/categories/index.js +299 -0
- package/changesets/categories/types.js +66 -0
- package/changesets/changelog/formatting.js +119 -0
- package/changesets/changelog/getDependencyReleaseLine.js +114 -0
- package/changesets/changelog/getReleaseLine.js +122 -0
- package/changesets/changelog/index.js +99 -0
- package/changesets/constants.js +43 -0
- package/changesets/errors.js +305 -0
- package/changesets/index.js +146 -0
- package/changesets/markdownlint/index.js +29 -0
- package/changesets/markdownlint/rules/content-structure.js +98 -0
- package/changesets/markdownlint/rules/dependency-table-format.js +170 -0
- package/changesets/markdownlint/rules/heading-hierarchy.js +61 -0
- package/changesets/markdownlint/rules/required-sections.js +54 -0
- package/changesets/markdownlint/rules/uncategorized-content.js +54 -0
- package/changesets/markdownlint/rules/utils.js +30 -0
- package/changesets/remark/plugins/aggregate-dependency-tables.js +47 -0
- package/changesets/remark/plugins/contributor-footnotes.js +123 -0
- package/changesets/remark/plugins/deduplicate-items.js +30 -0
- package/changesets/remark/plugins/issue-link-refs.js +58 -0
- package/changesets/remark/plugins/merge-sections.js +43 -0
- package/changesets/remark/plugins/normalize-format.js +47 -0
- package/changesets/remark/plugins/reorder-sections.js +34 -0
- package/changesets/remark/presets.js +119 -0
- package/changesets/remark/rules/content-structure.js +22 -0
- package/changesets/remark/rules/dependency-table-format.js +40 -0
- package/changesets/remark/rules/heading-hierarchy.js +19 -0
- package/changesets/remark/rules/required-sections.js +17 -0
- package/changesets/remark/rules/uncategorized-content.js +31 -0
- package/changesets/schemas/changeset.js +146 -0
- package/changesets/schemas/dependency-table.js +189 -0
- package/changesets/schemas/git.js +69 -0
- package/changesets/schemas/github.js +175 -0
- package/changesets/schemas/options.js +182 -0
- package/changesets/schemas/package-scope.js +128 -0
- package/changesets/schemas/primitives.js +72 -0
- package/changesets/schemas/version-files.js +151 -0
- package/changesets/services/branch-analyzer.js +278 -0
- package/changesets/services/changelog.js +50 -0
- package/changesets/services/config-inspector.js +390 -0
- package/changesets/services/github.js +178 -0
- package/changesets/services/markdown.js +106 -0
- package/changesets/services/workspace-snapshot.js +182 -0
- package/changesets/utils/commit-parser.js +80 -0
- package/changesets/utils/dep-diff.js +77 -0
- package/changesets/utils/dependency-table.js +347 -0
- package/changesets/utils/issue-refs.js +101 -0
- package/changesets/utils/jsonpath.js +175 -0
- package/changesets/utils/logger.js +50 -0
- package/changesets/utils/markdown-link.js +57 -0
- package/changesets/utils/publishability.js +39 -0
- package/changesets/utils/remark-pipeline.js +79 -0
- package/changesets/utils/section-parser.js +94 -0
- package/changesets/utils/strip-frontmatter.js +46 -0
- package/changesets/utils/version-blocks.js +108 -0
- package/changesets/utils/version-files.js +336 -0
- package/changesets/utils/worktree-snapshot.js +142 -0
- package/changesets/vendor/github-info.js +55 -0
- package/commitlint/config/factory.js +69 -0
- package/commitlint/config/plugins.js +227 -0
- package/commitlint/config/rules.js +155 -0
- package/commitlint/config/schema.js +46 -0
- package/commitlint/detection/dco.js +53 -0
- package/commitlint/detection/scopes.js +45 -0
- package/commitlint/formatter/format.js +85 -0
- package/commitlint/formatter/messages.js +79 -0
- package/commitlint/hook/diagnostics/branch.js +36 -0
- package/commitlint/hook/diagnostics/cache.js +37 -0
- package/commitlint/hook/diagnostics/commitlint-config.js +36 -0
- package/commitlint/hook/diagnostics/open-issues.js +56 -0
- package/commitlint/hook/diagnostics/package-manager.js +51 -0
- package/commitlint/hook/diagnostics/signing.js +107 -0
- package/commitlint/hook/envelope.js +46 -0
- package/commitlint/hook/output.js +45 -0
- package/commitlint/hook/parse-bash-command.js +105 -0
- package/commitlint/hook/rules/closes-trailer.js +31 -0
- package/commitlint/hook/rules/forbidden-content.js +32 -0
- package/commitlint/hook/rules/plan-leakage.js +36 -0
- package/commitlint/hook/rules/signing-flag-conflict.js +25 -0
- package/commitlint/hook/rules/soft-wrap.js +37 -0
- package/commitlint/hook/rules/types.js +14 -0
- package/commitlint/hook/rules/verbosity.js +31 -0
- package/commitlint/hook/silence-logger.js +39 -0
- package/commitlint/index.js +146 -0
- package/commitlint/prompt/config.js +91 -0
- package/commitlint/prompt/emojis.js +74 -0
- package/commitlint/prompt/prompter.js +135 -0
- package/commitlint/static.js +73 -0
- package/errors/BiomeSyncError.js +21 -0
- package/errors/ChangesetConfigError.js +20 -0
- package/errors/ConfigNotFoundError.js +21 -0
- package/errors/SectionParseError.js +16 -0
- package/errors/SectionValidationError.js +16 -0
- package/errors/SectionWriteError.js +16 -0
- package/errors/TagFormatError.js +20 -0
- package/errors/ToolNotFoundError.js +11 -0
- package/errors/ToolResolutionError.js +11 -0
- package/errors/ToolVersionMismatchError.js +11 -0
- package/errors/VersioningDetectionError.js +20 -0
- package/errors/WorkspaceAnalysisError.js +21 -0
- package/index.d.ts +9743 -8380
- package/index.js +36 -6657
- package/lint/Handler.js +39 -0
- package/lint/cli/sections.js +65 -0
- package/lint/cli/templates/markdownlint.gen.js +183 -0
- package/lint/config/Preset.js +152 -0
- package/lint/config/createConfig.js +89 -0
- package/lint/handlers/Biome.js +179 -0
- package/lint/handlers/Markdown.js +139 -0
- package/lint/handlers/PackageJson.js +130 -0
- package/lint/handlers/PnpmWorkspace.js +141 -0
- package/lint/handlers/ShellScripts.js +58 -0
- package/lint/handlers/TypeScript.js +134 -0
- package/lint/handlers/Yaml.js +167 -0
- package/lint/index.js +52 -0
- package/lint/utils/Command.js +285 -0
- package/lint/utils/Filter.js +100 -0
- package/lint/utils/Workspace.js +86 -0
- package/package.json +52 -63
- package/schemas/CommentStyle.js +16 -0
- package/schemas/ResolvedTool.js +63 -0
- package/schemas/SavvySections.js +113 -0
- package/schemas/SectionBlock.js +70 -0
- package/schemas/SectionDefinition.js +121 -0
- package/schemas/SectionResults.js +12 -0
- package/schemas/TagStrategySchemas.js +18 -0
- package/schemas/ToolDefinition.js +39 -0
- package/schemas/ToolResults.js +14 -0
- package/schemas/VersioningSchemas.js +95 -0
- package/schemas/WorkspaceAnalysisSchemas.js +190 -0
- package/services/BiomeSchemaSync.js +133 -0
- package/services/ChangesetConfig.js +78 -0
- package/services/ChangesetConfigReader.js +106 -0
- package/services/ConfigDiscovery.js +71 -0
- package/services/ManagedSection.js +288 -0
- package/services/SilkPublishability.js +193 -0
- package/services/SilkWorkspaceAnalyzer.js +213 -0
- package/services/TagStrategy.js +54 -0
- package/services/ToolDiscovery.js +229 -0
- package/services/VersioningStrategy.js +67 -0
- package/tsdoc-metadata.json +11 -11
- package/turbo/digest.js +127 -0
- package/turbo/errors.js +48 -0
- package/turbo/index.js +32 -0
- package/turbo/schemas/DryRun.js +57 -0
- package/turbo/schemas/results.js +61 -0
- package/turbo/services/TurboInspector.js +100 -0
- package/utils/ToolCommand.js +40 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { detectDCO } from "../detection/dco.js";
|
|
2
|
+
import { COMMIT_TYPES } from "./rules.js";
|
|
3
|
+
import { createPromptConfig } from "../prompt/config.js";
|
|
4
|
+
import { createScopeEnumRule, silkPlugin } from "./plugins.js";
|
|
5
|
+
|
|
6
|
+
//#region src/commitlint/config/factory.ts
|
|
7
|
+
/**
|
|
8
|
+
* Configuration factory implementation.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Create a commitlint configuration with auto-detection.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This function is the internal implementation for {@link CommitlintConfig.silk}.
|
|
17
|
+
* It receives already-validated options and performs the actual configuration
|
|
18
|
+
* assembly with auto-detection of repository settings.
|
|
19
|
+
*
|
|
20
|
+
* @param options - Resolved configuration options (after Zod parsing)
|
|
21
|
+
* @returns Commitlint UserConfig object
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
function createConfig(options) {
|
|
26
|
+
const cwd = options.cwd ?? process.cwd();
|
|
27
|
+
const dco = process.env.COMMITLINT_SKIP_DCO === "1" || process.env.COMMITLINT_SKIP_DCO === "true" ? false : options.dco ?? detectDCO(cwd);
|
|
28
|
+
const scopes = options.scopes ?? [];
|
|
29
|
+
const allScopes = [...new Set([...scopes, ...options.additionalScopes ?? []])].sort();
|
|
30
|
+
const rules = {
|
|
31
|
+
"body-max-line-length": [
|
|
32
|
+
2,
|
|
33
|
+
"always",
|
|
34
|
+
options.bodyMaxLineLength
|
|
35
|
+
],
|
|
36
|
+
"type-enum": [
|
|
37
|
+
2,
|
|
38
|
+
"always",
|
|
39
|
+
[...COMMIT_TYPES]
|
|
40
|
+
],
|
|
41
|
+
"subject-case": [0]
|
|
42
|
+
};
|
|
43
|
+
if (allScopes.length > 0) {
|
|
44
|
+
rules["scope-enum"] = [0];
|
|
45
|
+
rules["silk/tdd-scope"] = [0];
|
|
46
|
+
rules["silk/scope-enum"] = [2, "always"];
|
|
47
|
+
} else rules["silk/tdd-scope"] = [2, "always"];
|
|
48
|
+
const plugins = [allScopes.length > 0 ? { rules: {
|
|
49
|
+
...silkPlugin.rules,
|
|
50
|
+
"silk/scope-enum": createScopeEnumRule(allScopes)
|
|
51
|
+
} } : silkPlugin];
|
|
52
|
+
if (dco) rules["silk/signed-off-by"] = [2, "always"];
|
|
53
|
+
if (options.noMarkdown) {
|
|
54
|
+
rules["silk/body-no-markdown"] = [2, "always"];
|
|
55
|
+
rules["silk/subject-no-markdown"] = [2, "always"];
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
extends: ["@commitlint/config-conventional"],
|
|
59
|
+
plugins,
|
|
60
|
+
rules,
|
|
61
|
+
prompt: createPromptConfig({
|
|
62
|
+
emojis: options.emojis,
|
|
63
|
+
...allScopes.length > 0 && { scopes: allScopes }
|
|
64
|
+
})
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
//#endregion
|
|
69
|
+
export { createConfig };
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { TDD_SCOPE_PATTERN } from "./rules.js";
|
|
2
|
+
|
|
3
|
+
//#region src/commitlint/config/plugins.ts
|
|
4
|
+
/**
|
|
5
|
+
* Custom commitlint plugin rules.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* These rules help enforce plain-text commit messages by rejecting
|
|
9
|
+
* common markdown formatting patterns that AI agents tend to add.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Patterns that indicate markdown formatting in commit messages.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
const MARKDOWN_PATTERNS = {
|
|
19
|
+
/** Markdown headers (# Header, ## Header, etc.) */
|
|
20
|
+
headers: /^#{1,6}\s/m,
|
|
21
|
+
/** Markdown bullet lists (- item, * item) */
|
|
22
|
+
bullets: /^[\t ]*[-*]\s/m,
|
|
23
|
+
/** Markdown numbered lists (1. item, 2. item) */
|
|
24
|
+
numberedLists: /^[\t ]*\d+\.\s/m,
|
|
25
|
+
/** Markdown code fences (triple backticks) */
|
|
26
|
+
codeFences: /```/,
|
|
27
|
+
/** Markdown inline code (`code`) - only flag if excessive */
|
|
28
|
+
inlineCode: /`[^`]+`/g,
|
|
29
|
+
/** Markdown bold (**text** or __text__) */
|
|
30
|
+
bold: /(\*\*|__)[^*_]+(\*\*|__)/,
|
|
31
|
+
/** Markdown italic (*text* or _text_) - be careful not to match normal underscores */
|
|
32
|
+
italic: /(?<!\w)\*[^*]+\*(?!\w)/,
|
|
33
|
+
/** Markdown links [text](url) */
|
|
34
|
+
links: /\[.+?\]\(.+?\)/,
|
|
35
|
+
/** Markdown horizontal rules (---, ***, ___) */
|
|
36
|
+
horizontalRules: /^[-*_]{3,}$/m
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Check if text contains markdown formatting.
|
|
40
|
+
*
|
|
41
|
+
* @param text - Text to check
|
|
42
|
+
* @returns Object with detected patterns
|
|
43
|
+
*/
|
|
44
|
+
function detectMarkdown(text) {
|
|
45
|
+
const detected = [];
|
|
46
|
+
if (MARKDOWN_PATTERNS.headers.test(text)) detected.push("headers (#)");
|
|
47
|
+
if (MARKDOWN_PATTERNS.numberedLists.test(text)) detected.push("numbered lists (1.)");
|
|
48
|
+
if (MARKDOWN_PATTERNS.codeFences.test(text)) detected.push("code fences (```)");
|
|
49
|
+
if (MARKDOWN_PATTERNS.bold.test(text)) detected.push("bold (**text**)");
|
|
50
|
+
if (MARKDOWN_PATTERNS.links.test(text)) detected.push("links ([text](url))");
|
|
51
|
+
if (MARKDOWN_PATTERNS.horizontalRules.test(text)) detected.push("horizontal rules (---)");
|
|
52
|
+
const inlineCodeMatches = text.match(MARKDOWN_PATTERNS.inlineCode);
|
|
53
|
+
if (inlineCodeMatches && inlineCodeMatches.length > 2) detected.push("excessive inline code (`code`)");
|
|
54
|
+
return {
|
|
55
|
+
hasMarkdown: detected.length > 0,
|
|
56
|
+
patterns: detected
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Rule: body-no-markdown
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* Rejects commit message bodies that contain markdown formatting.
|
|
64
|
+
* This helps ensure commit messages are plain text and readable
|
|
65
|
+
* in terminals, git log, and other tools that don't render markdown.
|
|
66
|
+
*
|
|
67
|
+
* Simple unordered lists (`-` or `*`) are allowed for readability.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```
|
|
71
|
+
* // Invalid - contains markdown headers
|
|
72
|
+
* feat: add feature
|
|
73
|
+
*
|
|
74
|
+
* ## Summary
|
|
75
|
+
* This adds a new feature.
|
|
76
|
+
*
|
|
77
|
+
* // Valid - plain text with lists
|
|
78
|
+
* feat: add feature
|
|
79
|
+
*
|
|
80
|
+
* Added new feature and fixed related bug:
|
|
81
|
+
* - Implemented user authentication
|
|
82
|
+
* - Fixed session timeout issue
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
const bodyNoMarkdown = (parsed) => {
|
|
86
|
+
const body = parsed.body;
|
|
87
|
+
if (!body) return [true, ""];
|
|
88
|
+
const { hasMarkdown, patterns } = detectMarkdown(body);
|
|
89
|
+
if (hasMarkdown) return [false, `body contains markdown formatting: ${patterns.join(", ")}`];
|
|
90
|
+
return [true, ""];
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Rule: subject-no-markdown
|
|
94
|
+
*
|
|
95
|
+
* @remarks
|
|
96
|
+
* Rejects commit message subjects (first line) that contain markdown.
|
|
97
|
+
* Subjects should be plain text without any formatting.
|
|
98
|
+
*/
|
|
99
|
+
const subjectNoMarkdown = (parsed) => {
|
|
100
|
+
const subject = parsed.subject;
|
|
101
|
+
if (!subject) return [true, ""];
|
|
102
|
+
const { hasMarkdown, patterns } = detectMarkdown(subject);
|
|
103
|
+
if (hasMarkdown) return [false, `subject contains markdown formatting: ${patterns.join(", ")}`];
|
|
104
|
+
return [true, ""];
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Rule: body-prose-only
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* A stricter rule that requires commit bodies to be prose paragraphs,
|
|
111
|
+
* rejecting any list-like structures even without markdown markers.
|
|
112
|
+
* Checks for lines that look like list items.
|
|
113
|
+
*/
|
|
114
|
+
const bodyProseOnly = (parsed) => {
|
|
115
|
+
const body = parsed.body;
|
|
116
|
+
if (!body) return [true, ""];
|
|
117
|
+
if (/^[\t ]*(?:[-*•]|\d+[.):])\s/m.test(body)) return [false, "body should be prose paragraphs, not lists"];
|
|
118
|
+
return [true, ""];
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Rule: signed-off-by
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* Case-insensitive check for DCO signoff trailer. Accepts both
|
|
125
|
+
* "Signed-off-by:" and "signed-off-by:" (and any other casing).
|
|
126
|
+
*
|
|
127
|
+
* This replaces the built-in commitlint signed-off-by rule which
|
|
128
|
+
* is case-sensitive.
|
|
129
|
+
*/
|
|
130
|
+
const signedOffBy = (parsed) => {
|
|
131
|
+
const raw = parsed.raw;
|
|
132
|
+
if (!raw) return [false, "message must be signed off"];
|
|
133
|
+
if (/^signed-off-by:\s*.+$/im.test(raw)) return [true, ""];
|
|
134
|
+
return [false, "message must be signed off"];
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Rule: tdd-scope
|
|
138
|
+
*
|
|
139
|
+
* @remarks
|
|
140
|
+
* Enforces scope format for TDD commits. Non-TDD commits pass unconditionally.
|
|
141
|
+
* TDD commits require a scope in the format `<goalId>:<state>` where:
|
|
142
|
+
* - goalId is a numeric ID (one or more digits)
|
|
143
|
+
* - state is one of: spike, red, green, refactor
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```
|
|
147
|
+
* // Valid - numeric goalId with valid state
|
|
148
|
+
* tdd(7:green): implement feature
|
|
149
|
+
* tdd(12:spike): research approach
|
|
150
|
+
*
|
|
151
|
+
* // Invalid - no scope
|
|
152
|
+
* tdd: implement feature
|
|
153
|
+
*
|
|
154
|
+
* // Invalid - missing state
|
|
155
|
+
* tdd(7): implement feature
|
|
156
|
+
*
|
|
157
|
+
* // Invalid - non-numeric goalId
|
|
158
|
+
* tdd(feature:green): implement feature
|
|
159
|
+
*
|
|
160
|
+
* // Invalid - invalid state
|
|
161
|
+
* tdd(7:done): implement feature
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
function checkTddScope(scope) {
|
|
165
|
+
if (!scope) return [false, "tdd commits require a scope in the format <goalId>:<state>"];
|
|
166
|
+
if (!TDD_SCOPE_PATTERN.test(scope)) return [false, `tdd scope must match <digits>:(spike|red|green|refactor), got: ${scope}`];
|
|
167
|
+
return [true, ""];
|
|
168
|
+
}
|
|
169
|
+
const tddScope = (parsed) => {
|
|
170
|
+
if (parsed.type !== "tdd") return [true, ""];
|
|
171
|
+
return checkTddScope(parsed.scope);
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Custom commitlint plugin with markdown prevention rules.
|
|
175
|
+
*
|
|
176
|
+
* @remarks
|
|
177
|
+
* This plugin provides rules to enforce plain-text commit messages.
|
|
178
|
+
* Rules are prefixed with `silk/` to namespace them.
|
|
179
|
+
*
|
|
180
|
+
* Available rules:
|
|
181
|
+
* - `silk/body-no-markdown`: Reject markdown in commit body
|
|
182
|
+
* - `silk/subject-no-markdown`: Reject markdown in commit subject
|
|
183
|
+
* - `silk/body-prose-only`: Require prose paragraphs (no lists)
|
|
184
|
+
* - `silk/signed-off-by`: Require DCO signoff
|
|
185
|
+
* - `silk/tdd-scope`: Enforce TDD scope format
|
|
186
|
+
*
|
|
187
|
+
* @internal
|
|
188
|
+
*/
|
|
189
|
+
const silkPlugin = { rules: {
|
|
190
|
+
"silk/body-no-markdown": bodyNoMarkdown,
|
|
191
|
+
"silk/subject-no-markdown": subjectNoMarkdown,
|
|
192
|
+
"silk/body-prose-only": bodyProseOnly,
|
|
193
|
+
"silk/signed-off-by": signedOffBy,
|
|
194
|
+
"silk/tdd-scope": tddScope
|
|
195
|
+
} };
|
|
196
|
+
/**
|
|
197
|
+
* Factory function to create a scope enum rule.
|
|
198
|
+
*
|
|
199
|
+
* @remarks
|
|
200
|
+
* Creates a rule that validates commit scopes, with special handling for TDD commits.
|
|
201
|
+
* - For TDD commits: enforces scope format `<goalId>:<state>` where state is one of spike, red, green, refactor
|
|
202
|
+
* - For non-TDD commits: enforces scope is one of the provided project scopes
|
|
203
|
+
*
|
|
204
|
+
* This replaces the built-in commitlint `scope-enum` rule, avoiding duplicate error
|
|
205
|
+
* messages when a TDD commit has an invalid scope.
|
|
206
|
+
*
|
|
207
|
+
* @param scopes - Array of valid project scope strings (e.g. ["api", "cli"])
|
|
208
|
+
* @returns A Rule function that validates scopes
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```ts
|
|
212
|
+
* const rule = createScopeEnumRule(["api", "cli"]);
|
|
213
|
+
* const [valid, msg] = rule(parsedCommit);
|
|
214
|
+
* ```
|
|
215
|
+
*
|
|
216
|
+
* @internal
|
|
217
|
+
*/
|
|
218
|
+
function createScopeEnumRule(scopes) {
|
|
219
|
+
return (parsed) => {
|
|
220
|
+
if (parsed.type === "tdd") return checkTddScope(parsed.scope);
|
|
221
|
+
if (!parsed.scope || !scopes.includes(parsed.scope)) return [false, `scope must be one of: ${scopes.join(", ")}`];
|
|
222
|
+
return [true, ""];
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
//#endregion
|
|
227
|
+
export { createScopeEnumRule, silkPlugin };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
//#region src/commitlint/config/rules.ts
|
|
2
|
+
/**
|
|
3
|
+
* Allowed commit types for conventional commits.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Extends the standard conventional commit types with:
|
|
7
|
+
* - `ai`: For AI/LLM agent document updates (CLAUDE.md, context files)
|
|
8
|
+
* - `release`: For release commits (version bumps, changelogs)
|
|
9
|
+
*
|
|
10
|
+
* Types are sorted alphabetically for consistent ordering.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
*/
|
|
14
|
+
const COMMIT_TYPES = [
|
|
15
|
+
"ai",
|
|
16
|
+
"build",
|
|
17
|
+
"chore",
|
|
18
|
+
"ci",
|
|
19
|
+
"docs",
|
|
20
|
+
"feat",
|
|
21
|
+
"fix",
|
|
22
|
+
"perf",
|
|
23
|
+
"refactor",
|
|
24
|
+
"release",
|
|
25
|
+
"revert",
|
|
26
|
+
"style",
|
|
27
|
+
"tdd",
|
|
28
|
+
"test"
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Pattern for valid TDD commit scope.
|
|
32
|
+
*
|
|
33
|
+
* @remarks
|
|
34
|
+
* TDD commits require a scope in the format: `<goalId>:<state>`
|
|
35
|
+
* where goalId is a numeric ID and state is one of: spike, red, green, refactor.
|
|
36
|
+
*
|
|
37
|
+
* Example: `tdd(42:red)` or `tdd(1:green)`
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
const TDD_SCOPE_PATTERN = /^\d+:(spike|red|green|refactor)$/;
|
|
42
|
+
/**
|
|
43
|
+
* Valid TDD states for commit scope.
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* These states correspond to the phases of test-driven development:
|
|
47
|
+
* - spike: Research/exploration phase
|
|
48
|
+
* - red: Write failing test
|
|
49
|
+
* - green: Make test pass
|
|
50
|
+
* - refactor: Clean up code
|
|
51
|
+
*
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
const TDD_STATES = [
|
|
55
|
+
"spike",
|
|
56
|
+
"red",
|
|
57
|
+
"green",
|
|
58
|
+
"refactor"
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Commit type definitions with metadata for prompts and changelogs.
|
|
62
|
+
*
|
|
63
|
+
* @remarks
|
|
64
|
+
* Each definition provides human-readable text for interactive commit
|
|
65
|
+
* tools and changelog generation. The order here determines display
|
|
66
|
+
* order in prompts (most common types first).
|
|
67
|
+
*
|
|
68
|
+
* @public
|
|
69
|
+
*/
|
|
70
|
+
const COMMIT_TYPE_DEFINITIONS = [
|
|
71
|
+
{
|
|
72
|
+
type: "ai",
|
|
73
|
+
description: "AI/LLM agent document updates (CLAUDE.md, context files)",
|
|
74
|
+
title: "AI Context"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: "feat",
|
|
78
|
+
description: "A new feature",
|
|
79
|
+
title: "Features"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
type: "fix",
|
|
83
|
+
description: "A bug fix",
|
|
84
|
+
title: "Bug Fixes"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: "docs",
|
|
88
|
+
description: "Documentation only changes",
|
|
89
|
+
title: "Documentation"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: "style",
|
|
93
|
+
description: "Code style changes (formatting, semicolons, etc)",
|
|
94
|
+
title: "Styles"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: "refactor",
|
|
98
|
+
description: "Code change that neither fixes a bug nor adds a feature",
|
|
99
|
+
title: "Code Refactoring"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: "perf",
|
|
103
|
+
description: "A code change that improves performance",
|
|
104
|
+
title: "Performance Improvements"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: "test",
|
|
108
|
+
description: "Adding missing tests or correcting existing tests",
|
|
109
|
+
title: "Tests"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: "tdd",
|
|
113
|
+
description: "TDD agent commit (goalId:state scope required)",
|
|
114
|
+
title: "TDD"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: "build",
|
|
118
|
+
description: "Changes to build system or external dependencies",
|
|
119
|
+
title: "Builds"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: "ci",
|
|
123
|
+
description: "Changes to CI configuration files and scripts",
|
|
124
|
+
title: "Continuous Integration"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
type: "chore",
|
|
128
|
+
description: "Other changes that don't modify src or test files",
|
|
129
|
+
title: "Chores"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: "revert",
|
|
133
|
+
description: "Reverts a previous commit",
|
|
134
|
+
title: "Reverts"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
type: "release",
|
|
138
|
+
description: "Release commits (version bumps, changelogs)",
|
|
139
|
+
title: "Releases"
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
/**
|
|
143
|
+
* DCO (Developer Certificate of Origin) signoff text.
|
|
144
|
+
*
|
|
145
|
+
* @remarks
|
|
146
|
+
* This is the standard DCO signoff prefix. When DCO is required,
|
|
147
|
+
* commit messages must include a trailer in the format:
|
|
148
|
+
* `Signed-off-by: Name <email>`
|
|
149
|
+
*
|
|
150
|
+
* @public
|
|
151
|
+
*/
|
|
152
|
+
const DCO_SIGNOFF_TEXT = "Signed-off-by:";
|
|
153
|
+
|
|
154
|
+
//#endregion
|
|
155
|
+
export { COMMIT_TYPES, COMMIT_TYPE_DEFINITIONS, DCO_SIGNOFF_TEXT, TDD_SCOPE_PATTERN, TDD_STATES };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/commitlint/config/schema.ts
|
|
4
|
+
/**
|
|
5
|
+
* Effect Schemas for commitlint configuration options.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Effect Schema for {@link ReleaseFormat}.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
const ReleaseFormatSchema = Schema.Literal("semver", "packages", "scoped");
|
|
15
|
+
/**
|
|
16
|
+
* Effect Schema for validating {@link ConfigOptions}.
|
|
17
|
+
*
|
|
18
|
+
* @remarks
|
|
19
|
+
* This schema validates and applies defaults to user-provided configuration.
|
|
20
|
+
* Used internally by {@link CommitlintConfig.silk} via {@link decodeConfigOptions}.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
const ConfigOptionsSchema = Schema.Struct({
|
|
25
|
+
dco: Schema.optional(Schema.Boolean),
|
|
26
|
+
scopes: Schema.optional(Schema.Array(Schema.String)),
|
|
27
|
+
additionalScopes: Schema.optional(Schema.Array(Schema.String)),
|
|
28
|
+
releaseFormat: Schema.optional(ReleaseFormatSchema),
|
|
29
|
+
emojis: Schema.optionalWith(Schema.Boolean, { default: () => false }),
|
|
30
|
+
bodyMaxLineLength: Schema.optionalWith(Schema.Number.pipe(Schema.positive()), { default: () => 300 }),
|
|
31
|
+
noMarkdown: Schema.optionalWith(Schema.Boolean, { default: () => true }),
|
|
32
|
+
cwd: Schema.optional(Schema.String)
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Decode unknown input into {@link ResolvedConfigOptions}, applying defaults.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Synchronous decoder that throws a `ParseError` on invalid input, mirroring
|
|
39
|
+
* the previous Zod `.parse()` contract used by {@link CommitlintConfig.silk}.
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
const decodeConfigOptions = Schema.decodeUnknownSync(ConfigOptionsSchema);
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { decodeConfigOptions };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/commitlint/detection/dco.ts
|
|
5
|
+
/**
|
|
6
|
+
* DCO (Developer Certificate of Origin) detection module.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
/** Filename for the DCO file that indicates signoff is required. */
|
|
11
|
+
const DCO_FILENAME = "DCO";
|
|
12
|
+
/** Markers that indicate a project root directory. */
|
|
13
|
+
const ROOT_MARKERS = [
|
|
14
|
+
"pnpm-workspace.yaml",
|
|
15
|
+
".git",
|
|
16
|
+
"package.json"
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* Walk up the directory tree to find the project root.
|
|
20
|
+
*
|
|
21
|
+
* @param cwd - Starting directory
|
|
22
|
+
* @returns Project root path or null if not found
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
function findProjectRoot(cwd) {
|
|
27
|
+
let dir = resolve(cwd);
|
|
28
|
+
while (true) {
|
|
29
|
+
for (const marker of ROOT_MARKERS) if (existsSync(join(dir, marker))) return dir;
|
|
30
|
+
const parent = dirname(dir);
|
|
31
|
+
if (parent === dir) return null;
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Detect if DCO signoff should be required.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* Checks for the presence of a DCO file at the repository root.
|
|
40
|
+
* Walks up the directory tree to find the project root by looking
|
|
41
|
+
* for workspace markers (pnpm-workspace.yaml, .git, package.json).
|
|
42
|
+
*
|
|
43
|
+
* @param cwd - Working directory (defaults to process.cwd())
|
|
44
|
+
* @returns `true` if DCO file exists at repo root, `false` otherwise
|
|
45
|
+
*
|
|
46
|
+
* @public
|
|
47
|
+
*/
|
|
48
|
+
function detectDCO(cwd = process.cwd()) {
|
|
49
|
+
return existsSync(join(findProjectRoot(cwd) ?? cwd, DCO_FILENAME));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { detectDCO };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { WorkspaceDiscovery } from "workspaces-effect";
|
|
3
|
+
|
|
4
|
+
//#region src/commitlint/detection/scopes.ts
|
|
5
|
+
/**
|
|
6
|
+
* Workspace scope detection module.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Extract scope-friendly name from a package name.
|
|
12
|
+
*
|
|
13
|
+
* @param name - Package name (e.g., `@scope/package-name` or `package-name`)
|
|
14
|
+
* @returns Package name without scope prefix
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
function extractScopeName(name) {
|
|
19
|
+
if (name.startsWith("@")) return name.split("/")[1];
|
|
20
|
+
return name;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect package scopes from workspace configuration.
|
|
24
|
+
*
|
|
25
|
+
* @remarks
|
|
26
|
+
* Uses workspaces-effect to find all packages in the workspace and extracts
|
|
27
|
+
* their names as potential commit scopes. For scoped packages like
|
|
28
|
+
* `@scope/package-name`, only the package name portion is used as the scope.
|
|
29
|
+
*
|
|
30
|
+
* @returns Effect yielding sorted array of scope names, requires WorkspaceDiscovery
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
const detectScopes = Effect.gen(function* () {
|
|
35
|
+
const packages = yield* (yield* WorkspaceDiscovery).listPackages();
|
|
36
|
+
const scopes = [];
|
|
37
|
+
for (const pkg of packages) {
|
|
38
|
+
const scopeName = extractScopeName(pkg.name);
|
|
39
|
+
if (scopeName) scopes.push(scopeName);
|
|
40
|
+
}
|
|
41
|
+
return scopes.sort();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { detectScopes };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { getExplanation, getSuggestion } from "./messages.js";
|
|
2
|
+
|
|
3
|
+
//#region src/commitlint/formatter/format.ts
|
|
4
|
+
/**
|
|
5
|
+
* Custom formatter implementation for commitlint.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
/** Unicode symbol for error indicator. */
|
|
10
|
+
const ERROR_ICON = "✗";
|
|
11
|
+
/** Unicode symbol for warning indicator. */
|
|
12
|
+
const WARNING_ICON = "⚠";
|
|
13
|
+
/**
|
|
14
|
+
* Format a single rule result with explanation and suggestion.
|
|
15
|
+
*
|
|
16
|
+
* @param result - Rule result to format
|
|
17
|
+
* @param level - "error" or "warning"
|
|
18
|
+
* @returns Formatted string
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
function formatRuleResult(result, level) {
|
|
23
|
+
const lines = [` ${level === "error" ? ERROR_ICON : WARNING_ICON} ${result.name}: ${result.message}`];
|
|
24
|
+
const explanation = getExplanation(result.name);
|
|
25
|
+
if (explanation) lines.push(` ${explanation}`);
|
|
26
|
+
const suggestion = getSuggestion(result.name);
|
|
27
|
+
if (suggestion) lines.push(` Suggestion: ${suggestion}`);
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build summary line for error/warning counts.
|
|
32
|
+
*
|
|
33
|
+
* @param errorCount - Number of errors
|
|
34
|
+
* @param warningCount - Number of warnings
|
|
35
|
+
* @returns Summary string
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
function buildSummary(errorCount, warningCount) {
|
|
40
|
+
const parts = [];
|
|
41
|
+
if (errorCount > 0) parts.push(`${errorCount} error${errorCount === 1 ? "" : "s"}`);
|
|
42
|
+
if (warningCount > 0) parts.push(`${warningCount} warning${warningCount === 1 ? "" : "s"}`);
|
|
43
|
+
return `Found ${parts.join(", ")}.`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Format lint results for display.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* This is the main formatter function exported for commitlint.
|
|
50
|
+
* It formats errors and warnings with helpful explanations and suggestions.
|
|
51
|
+
*
|
|
52
|
+
* @param formatterResult - Results from commitlint
|
|
53
|
+
* @returns Formatted output string
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
function format(formatterResult) {
|
|
58
|
+
const { results, options } = formatterResult;
|
|
59
|
+
const output = [];
|
|
60
|
+
let totalErrors = 0;
|
|
61
|
+
let totalWarnings = 0;
|
|
62
|
+
for (const result of results) {
|
|
63
|
+
if (!(!result.valid || result.errors.length > 0 || result.warnings.length > 0)) continue;
|
|
64
|
+
const firstLine = result.input.split("\n")[0];
|
|
65
|
+
output.push(`\nInput: "${firstLine}..."`);
|
|
66
|
+
output.push("");
|
|
67
|
+
for (const error of result.errors) {
|
|
68
|
+
output.push(formatRuleResult(error, "error"));
|
|
69
|
+
totalErrors++;
|
|
70
|
+
}
|
|
71
|
+
for (const warning of result.warnings) {
|
|
72
|
+
output.push(formatRuleResult(warning, "warning"));
|
|
73
|
+
totalWarnings++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (totalErrors > 0 || totalWarnings > 0) {
|
|
77
|
+
output.push("");
|
|
78
|
+
output.push(buildSummary(totalErrors, totalWarnings));
|
|
79
|
+
if (options?.helpUrl) output.push(`\nFor more information, see: ${options.helpUrl}`);
|
|
80
|
+
}
|
|
81
|
+
return output.join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
//#endregion
|
|
85
|
+
export { format };
|