@3fn/core 14.0.0 → 14.1.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 (96) hide show
  1. package/.kiro/steering/Civitas-System-Overview.md +3 -12
  2. package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
  3. package/.kiro/steering/Task-Completion-Protocol.md +7 -7
  4. package/.kiro/steering/start-up-tasks.md +4 -4
  5. package/dist/ComponentTokens.android.kt +12 -12
  6. package/dist/ComponentTokens.ios.swift +12 -12
  7. package/dist/ComponentTokens.web.css +3 -3
  8. package/dist/DesignTokens.android.kt +1 -1
  9. package/dist/DesignTokens.dtcg.json +2 -2
  10. package/dist/DesignTokens.ios.swift +1 -1
  11. package/dist/DesignTokens.web.css +1 -1
  12. package/dist/android/DesignTokens.android.kt +1 -1
  13. package/dist/browser/designerpunk.esm.js +14 -9
  14. package/dist/browser/designerpunk.esm.min.js +7 -7
  15. package/dist/browser/designerpunk.umd.js +14 -9
  16. package/dist/browser/designerpunk.umd.min.js +12 -12
  17. package/dist/browser/tokens.css +3 -3
  18. package/dist/build/tokens/defineComponentTokens.d.ts +10 -0
  19. package/dist/build/tokens/defineComponentTokens.js +26 -0
  20. package/dist/components/core/Avatar-Base/avatar.tokens.d.ts +21 -26
  21. package/dist/components/core/Avatar-Base/avatar.tokens.js +31 -34
  22. package/dist/components/core/Avatar-Base/index.d.ts +1 -1
  23. package/dist/components/core/Avatar-Base/index.js +2 -2
  24. package/dist/components/core/Button-Icon/buttonIcon.tokens.d.ts +28 -14
  25. package/dist/components/core/Button-Icon/buttonIcon.tokens.js +35 -20
  26. package/dist/generators/TokenFileGenerator.js +7 -2
  27. package/dist/ios/DesignTokens.ios.swift +1 -1
  28. package/dist/tokens/component/progress.d.ts +65 -5
  29. package/dist/tokens/component/progress.js +79 -18
  30. package/dist/types/generated/TokenTypes.d.ts +1 -1
  31. package/dist/types/generated/TokenTypes.js +1 -1
  32. package/dist/web/DesignTokens.web.css +1 -1
  33. package/governance/BUILD-SYSTEM-SETUP.md +1 -2
  34. package/governance/Component-Development-Guide.md +1 -1
  35. package/governance/Component-Development-Standards.md +19 -18
  36. package/governance/Component-Family-Avatar.md +6 -6
  37. package/governance/Component-Family-Badge.md +19 -19
  38. package/governance/Component-Family-Button.md +26 -26
  39. package/governance/Component-Family-Chip.md +14 -14
  40. package/governance/Component-Family-Container.md +12 -12
  41. package/governance/Component-Family-Form-Inputs.md +61 -61
  42. package/governance/Component-Family-Icon.md +8 -8
  43. package/governance/Component-Family-Navigation.md +1 -1
  44. package/governance/Component-Inheritance-Structures.md +29 -28
  45. package/governance/Component-Readiness-Status.md +6 -7
  46. package/governance/Component-Templates.md +26 -26
  47. package/governance/Contract-System-Reference.md +1 -1
  48. package/governance/Process-Development-Workflow.md +4 -4
  49. package/governance/Process-File-Organization.md +1 -4
  50. package/governance/Process-Hook-Operations.md +6 -7
  51. package/governance/Process-Spec-Planning.md +8 -14
  52. package/governance/Rosetta-System-Architecture.md +7 -5
  53. package/governance/Token-Quick-Reference.md +35 -22
  54. package/governance/Web-Authoring-Standards.md +1 -1
  55. package/governance/classification-map.md +109 -3
  56. package/governance/completion-documentation-guide.md +14 -31
  57. package/governance/platform-implementation-guidelines.md +1 -2
  58. package/governance/release-management-system.md +28 -63
  59. package/package.json +2 -6
  60. package/src/build/tokens/__tests__/defineComponentTokens.test.ts +113 -0
  61. package/src/build/tokens/defineComponentTokens.ts +43 -1
  62. package/src/components/core/Avatar-Base/avatar.tokens.ts +31 -34
  63. package/src/components/core/Avatar-Base/index.ts +1 -1
  64. package/src/components/core/Button-Icon/buttonIcon.tokens.ts +43 -27
  65. package/src/generators/TokenFileGenerator.ts +7 -2
  66. package/src/tokens/__tests__/ProgressTokenCompliance.test.ts +5 -3
  67. package/src/tokens/__tests__/ProgressTokenFormulas.test.ts +11 -11
  68. package/src/tokens/__tests__/ProgressTokenTranslation.test.ts +22 -20
  69. package/src/tokens/component/progress.ts +83 -21
  70. package/src/types/generated/TokenTypes.ts +1 -1
  71. package/token-index/components.yaml +8 -8
  72. package/src/tools/release/__tests__/ChangeClassifier.test.ts +0 -133
  73. package/src/tools/release/__tests__/ChangeExtractor.test.ts +0 -222
  74. package/src/tools/release/__tests__/GitHubPublisher.test.ts +0 -240
  75. package/src/tools/release/__tests__/NotesRenderer.test.ts +0 -142
  76. package/src/tools/release/__tests__/NpmPublisher.test.ts +0 -289
  77. package/src/tools/release/__tests__/PipelineIntegration.test.ts +0 -188
  78. package/src/tools/release/__tests__/ReleasePipeline.test.ts +0 -192
  79. package/src/tools/release/__tests__/SemanticVersionValidator.test.ts +0 -49
  80. package/src/tools/release/__tests__/SummaryScanner.test.ts +0 -141
  81. package/src/tools/release/__tests__/TagResolver.test.ts +0 -91
  82. package/src/tools/release/__tests__/VersionCalculator.test.ts +0 -270
  83. package/src/tools/release/__tests__/helpers/NpmMockHelper.ts +0 -80
  84. package/src/tools/release/cli/ReleasePipeline.ts +0 -165
  85. package/src/tools/release/cli/release-tool.ts +0 -107
  86. package/src/tools/release/pipeline/ChangeClassifier.ts +0 -61
  87. package/src/tools/release/pipeline/ChangeExtractor.ts +0 -87
  88. package/src/tools/release/pipeline/NotesRenderer.ts +0 -66
  89. package/src/tools/release/pipeline/SummaryScanner.ts +0 -70
  90. package/src/tools/release/pipeline/TagResolver.ts +0 -40
  91. package/src/tools/release/pipeline/VersionCalculator.ts +0 -375
  92. package/src/tools/release/publishers/GitHubPublisher.ts +0 -228
  93. package/src/tools/release/publishers/NpmPublisher.ts +0 -196
  94. package/src/tools/release/release-config.json +0 -5
  95. package/src/tools/release/types/index.ts +0 -282
  96. package/src/tools/release/validators/SemanticVersionValidator.ts +0 -67
@@ -1,165 +0,0 @@
1
- /**
2
- * ReleasePipeline — orchestrates the release tool pipeline.
3
- *
4
- * analyze() → scan + classify + recommend version
5
- * generateNotes() → analyze + render markdown + write files
6
- * release() → generateNotes + tag + GitHub publish
7
- */
8
-
9
- import * as fs from 'fs';
10
- import * as path from 'path';
11
- import { TagResolver } from '../pipeline/TagResolver';
12
- import { SummaryScanner } from '../pipeline/SummaryScanner';
13
- import { ChangeExtractor } from '../pipeline/ChangeExtractor';
14
- import { ChangeClassifier } from '../pipeline/ChangeClassifier';
15
- import { NotesRenderer } from '../pipeline/NotesRenderer';
16
- import { GitHubPublisher, GitHubConfig } from '../publishers/GitHubPublisher';
17
- import { PipelineRecommendation, RenderedNotes, ReleaseConfig, ExtractedChange, ClassifiedChange, GitHubRelease } from '../types';
18
-
19
- export class ReleasePipeline {
20
- private config: ReleaseConfig;
21
- private cwd: string;
22
-
23
- constructor(cwd?: string) {
24
- this.cwd = cwd || process.cwd();
25
- this.config = this.loadConfig();
26
- }
27
-
28
- async analyze(): Promise<PipelineRecommendation> {
29
- const tagResolver = new TagResolver();
30
- const tag = await tagResolver.getLatestTag();
31
-
32
- const scanner = new SummaryScanner(this.cwd);
33
- const summaries = await scanner.findSummariesSinceTag(tag);
34
-
35
- if (summaries.length === 0) {
36
- return {
37
- currentVersion: tag?.tag.replace(/^v/, '') || '0.0.0',
38
- recommendedVersion: tag?.tag.replace(/^v/, '') || '0.0.0',
39
- bumpType: 'none',
40
- rationale: 'No new summary docs found since last tag',
41
- confidence: 1.0,
42
- changes: [],
43
- };
44
- }
45
-
46
- const extractor = new ChangeExtractor();
47
- const extracted: ExtractedChange[] = summaries
48
- .map((s) => extractor.extract(s))
49
- .filter((e): e is ExtractedChange => e !== undefined);
50
-
51
- const classifier = new ChangeClassifier();
52
- const classified: ClassifiedChange[] = classifier.classify(extracted);
53
-
54
- const currentVersion = tag?.tag.replace(/^v/, '') || '0.0.0';
55
- const bumpType = this.determineBump(classified);
56
- const recommendedVersion = this.bumpVersion(currentVersion, bumpType);
57
-
58
- return {
59
- currentVersion,
60
- recommendedVersion,
61
- bumpType,
62
- rationale: this.buildRationale(classified),
63
- confidence: classified.some((c) => c.change.deliverables?.length) ? 0.9 : 0.7,
64
- changes: classified,
65
- };
66
- }
67
-
68
- async generateNotes(): Promise<RenderedNotes> {
69
- const recommendation = await this.analyze();
70
- const renderer = new NotesRenderer();
71
- const notes = renderer.render(recommendation);
72
-
73
- const outputDir = path.resolve(this.cwd, this.config.outputDir);
74
- fs.mkdirSync(outputDir, { recursive: true });
75
-
76
- const filename = `release-${recommendation.recommendedVersion}.md`;
77
- fs.writeFileSync(path.join(outputDir, filename), notes.public, 'utf-8');
78
- fs.writeFileSync(path.join(outputDir, `${filename}.internal.md`), notes.internal, 'utf-8');
79
- fs.writeFileSync(path.join(outputDir, `${filename}.json`), JSON.stringify(notes.json, null, 2), 'utf-8');
80
-
81
- return notes;
82
- }
83
-
84
- async release(dryRun = false): Promise<{ notes: RenderedNotes; published: boolean; releaseUrl?: string; error?: string }> {
85
- const notes = await this.generateNotes();
86
- const version = notes.json.version;
87
-
88
- if (dryRun) {
89
- return { notes, published: false };
90
- }
91
-
92
- const tagResolver = new TagResolver();
93
- await tagResolver.createTag(`v${version}`, `Release ${version}`);
94
-
95
- if (this.config.repoUrl) {
96
- const [owner, repo] = this.parseRepoUrl(this.config.repoUrl);
97
- const token = process.env.GITHUB_TOKEN || '';
98
- if (!token) {
99
- return { notes, published: false, error: 'GITHUB_TOKEN not set — tag created but GitHub release skipped' };
100
- }
101
- const publisher = new GitHubPublisher({ token, owner, repo });
102
- const release: GitHubRelease = {
103
- tagName: `v${version}`,
104
- name: `Release ${version}`,
105
- body: notes.public,
106
- draft: false,
107
- prerelease: false,
108
- artifacts: [],
109
- };
110
- const result = await publisher.createRelease(release);
111
- const error = result.errors.length > 0 ? result.errors[0].message : undefined;
112
- return { notes, published: result.success, releaseUrl: result.githubReleaseUrl, error };
113
- }
114
-
115
- return { notes, published: false, error: 'No repoUrl configured — tag created but GitHub release skipped' };
116
- }
117
-
118
- checkAccumulation(): string | null {
119
- const outputDir = path.resolve(this.cwd, this.config.outputDir);
120
- if (!fs.existsSync(outputDir)) return null;
121
- const files = fs.readdirSync(outputDir).filter((f) => !f.startsWith('.'));
122
- return files.length > 5 ? `⚠️ Output directory has ${files.length} files — consider cleaning old releases` : null;
123
- }
124
-
125
- private loadConfig(): ReleaseConfig {
126
- const configPath = path.resolve(this.cwd, 'src/tools/release/release-config.json');
127
- try {
128
- return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
129
- } catch {
130
- return { npmPublishEnabled: false, repoUrl: '', outputDir: 'docs/releases' };
131
- }
132
- }
133
-
134
- private parseRepoUrl(url: string): [string, string] {
135
- const match = url.match(/github\.com[/:]([^/]+)\/([^/.]+)/);
136
- return match ? [match[1], match[2]] : ['', ''];
137
- }
138
-
139
- private determineBump(classified: ClassifiedChange[]): 'major' | 'minor' | 'patch' | 'none' {
140
- if (classified.length === 0) return 'none';
141
- if (classified.some((c) => c.priority === 'breaking')) return 'major';
142
- if (classified.some((c) => c.priority === 'prominent')) return 'minor';
143
- return 'patch';
144
- }
145
-
146
- private bumpVersion(version: string, bump: 'major' | 'minor' | 'patch' | 'none'): string {
147
- const [major, minor, patch] = version.split('.').map(Number);
148
- switch (bump) {
149
- case 'major': return `${major + 1}.0.0`;
150
- case 'minor': return `${major}.${minor + 1}.0`;
151
- case 'patch': return `${major}.${minor}.${patch + 1}`;
152
- default: return version;
153
- }
154
- }
155
-
156
- private buildRationale(classified: ClassifiedChange[]): string {
157
- const counts = { breaking: 0, prominent: 0, context: 0 };
158
- for (const c of classified) counts[c.priority]++;
159
- const parts: string[] = [];
160
- if (counts.breaking) parts.push(`${counts.breaking} breaking`);
161
- if (counts.prominent) parts.push(`${counts.prominent} prominent`);
162
- if (counts.context) parts.push(`${counts.context} context`);
163
- return `${classified.length} changes: ${parts.join(', ')}`;
164
- }
165
- }
@@ -1,107 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Release Tool CLI
5
- *
6
- * Commands:
7
- * analyze — scan changes and display version recommendation
8
- * notes — generate release notes without releasing
9
- * release — generate notes, tag, and publish (with --dry-run option)
10
- */
11
-
12
- import { ReleasePipeline } from './ReleasePipeline';
13
-
14
- const COMMANDS = ['analyze', 'notes', 'release'] as const;
15
- type Command = (typeof COMMANDS)[number];
16
-
17
- async function main(): Promise<void> {
18
- const args = process.argv.slice(2);
19
- const command = args[0] as Command;
20
- const dryRun = args.includes('--dry-run');
21
-
22
- if (!command || !COMMANDS.includes(command)) {
23
- console.log('Usage: release-tool <command> [options]\n');
24
- console.log('Commands:');
25
- console.log(' analyze Scan changes and display version recommendation');
26
- console.log(' notes Generate release notes without releasing');
27
- console.log(' release Generate notes, create tag, and publish GitHub release');
28
- console.log('\nOptions:');
29
- console.log(' --dry-run Preview release without creating tag or publishing');
30
- process.exit(1);
31
- }
32
-
33
- const pipeline = new ReleasePipeline();
34
-
35
- // Check accumulation warning
36
- const warning = pipeline.checkAccumulation();
37
- if (warning) console.log(warning + '\n');
38
-
39
- try {
40
- switch (command) {
41
- case 'analyze':
42
- return await runAnalyze(pipeline);
43
- case 'notes':
44
- return await runNotes(pipeline);
45
- case 'release':
46
- return await runRelease(pipeline, dryRun);
47
- }
48
- } catch (err) {
49
- console.error(`\n❌ ${err instanceof Error ? err.message : String(err)}`);
50
- process.exit(1);
51
- }
52
- }
53
-
54
- async function runAnalyze(pipeline: ReleasePipeline): Promise<void> {
55
- console.log('🔍 Analyzing changes since last tag...\n');
56
- const rec = await pipeline.analyze();
57
-
58
- console.log(`Current version: ${rec.currentVersion}`);
59
- console.log(`Recommended version: ${rec.recommendedVersion}`);
60
- console.log(`Bump type: ${rec.bumpType}`);
61
- console.log(`Confidence: ${Math.round(rec.confidence * 100)}%`);
62
- console.log(`Rationale: ${rec.rationale}`);
63
-
64
- if (rec.changes.length > 0) {
65
- console.log(`\nChanges (${rec.changes.length}):`);
66
- for (const c of rec.changes) {
67
- const icon = c.priority === 'breaking' ? '🔴' : c.priority === 'prominent' ? '🟡' : '🔵';
68
- console.log(` ${icon} ${c.change.taskTitle} (${c.deliverableType})`);
69
- }
70
- }
71
- }
72
-
73
- async function runNotes(pipeline: ReleasePipeline): Promise<void> {
74
- console.log('📝 Generating release notes...\n');
75
- const notes = await pipeline.generateNotes();
76
- console.log(`Version: ${notes.json.version}`);
77
- console.log(`Changes: ${notes.json.changes.length}`);
78
- console.log(`\nFiles written to docs/releases/`);
79
- console.log('\n--- Public Notes Preview ---\n');
80
- console.log(notes.public);
81
- }
82
-
83
- async function runRelease(pipeline: ReleasePipeline, dryRun: boolean): Promise<void> {
84
- if (dryRun) console.log('🏃 Dry run — no tag or publish will occur\n');
85
- console.log('🚀 Running release pipeline...\n');
86
-
87
- const result = await pipeline.release(dryRun);
88
- const version = result.notes.json.version;
89
-
90
- if (dryRun) {
91
- console.log(`Would release: v${version}`);
92
- console.log(`Changes: ${result.notes.json.changes.length}`);
93
- console.log('\n--- Public Notes Preview ---\n');
94
- console.log(result.notes.public);
95
- return;
96
- }
97
-
98
- if (result.published) {
99
- console.log(`✅ Released v${version}`);
100
- if (result.releaseUrl) console.log(` ${result.releaseUrl}`);
101
- } else {
102
- console.log(`⚠️ v${version} tagged but not published`);
103
- if (result.error) console.log(` ${result.error}`);
104
- }
105
- }
106
-
107
- main();
@@ -1,61 +0,0 @@
1
- /**
2
- * ChangeClassifier for Release Tool
3
- *
4
- * Maps ExtractedChange objects to ClassifiedChange with priority tier.
5
- * Uses structured Deliverables field when present, falls back to keyword heuristics.
6
- */
7
-
8
- import { ExtractedChange, ClassifiedChange, Priority } from '../types';
9
-
10
- const PRIORITY_MAP: Record<string, Priority> = { '🔴': 'breaking', '🟡': 'prominent', '🔵': 'context' };
11
-
12
- const KEYWORD_RULES: [RegExp, Priority, string][] = [
13
- [/\bbreaking\b/i, 'breaking', 'Breaking change'],
14
- [/\btoken\b/i, 'breaking', 'Token'],
15
- [/\bcomponent\b/i, 'breaking', 'Component'],
16
- [/\btool\b/i, 'prominent', 'Tool'],
17
- [/\bagent\b/i, 'prominent', 'Agent'],
18
- [/\bmcp\b/i, 'prominent', 'MCP'],
19
- [/\bbuild\b/i, 'prominent', 'Build'],
20
- [/\bgovernance\b/i, 'context', 'Governance'],
21
- [/\bprocess\b/i, 'context', 'Process'],
22
- [/\binfrastructure\b/i, 'context', 'Infrastructure'],
23
- [/\bdocumentation\b/i, 'context', 'Documentation'],
24
- ];
25
-
26
- const PRIORITY_ORDER: Record<Priority, number> = { breaking: 0, prominent: 1, context: 2 };
27
-
28
- export class ChangeClassifier {
29
- classify(changes: ExtractedChange[]): ClassifiedChange[] {
30
- return changes
31
- .map((change) => this.classifyOne(change))
32
- .sort((a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]);
33
- }
34
-
35
- private classifyOne(change: ExtractedChange): ClassifiedChange {
36
- if (change.deliverables && change.deliverables.length > 0) {
37
- const highest = change.deliverables.reduce((best, d) =>
38
- PRIORITY_ORDER[PRIORITY_MAP[d.priority]] < PRIORITY_ORDER[PRIORITY_MAP[best.priority]] ? d : best,
39
- );
40
- return {
41
- change,
42
- priority: PRIORITY_MAP[highest.priority],
43
- deliverableType: highest.type,
44
- };
45
- }
46
-
47
- return this.classifyByKeywords(change);
48
- }
49
-
50
- private classifyByKeywords(change: ExtractedChange): ClassifiedChange {
51
- const text = [change.taskTitle, change.whatWasDone, ...change.keyChanges].join(' ');
52
-
53
- for (const [pattern, priority, type] of KEYWORD_RULES) {
54
- if (pattern.test(text)) {
55
- return { change, priority, deliverableType: type };
56
- }
57
- }
58
-
59
- return { change, priority: 'context', deliverableType: 'Other' };
60
- }
61
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * ChangeExtractor for Release Tool
3
- *
4
- * Parses summary doc markdown into structured ExtractedChange objects.
5
- * Handles both the optional ## Deliverables field and plain section extraction.
6
- */
7
-
8
- import { SummaryDoc, ExtractedChange, DeliverableEntry } from '../types';
9
-
10
- export class ChangeExtractor {
11
- extract(doc: SummaryDoc): ExtractedChange {
12
- const sections = this.parseSections(doc.raw);
13
-
14
- return {
15
- specName: doc.specName,
16
- taskTitle: this.extractTitle(doc.raw),
17
- taskType: this.extractMetadata(doc.raw, 'Type') || 'Unknown',
18
- whatWasDone: sections['what was done'] || '',
19
- whyItMatters: sections['why it matters'] || '',
20
- keyChanges: this.extractList(sections['key changes'] || ''),
21
- impact: this.extractList(sections['impact'] || ''),
22
- deliverables: this.extractDeliverables(sections['deliverables'] || sections['deliverables *(optional)*'] || ''),
23
- };
24
- }
25
-
26
- private parseSections(raw: string): Record<string, string> {
27
- const sections: Record<string, string> = {};
28
- const lines = raw.split('\n');
29
- let currentHeading = '';
30
- let currentContent: string[] = [];
31
-
32
- for (const line of lines) {
33
- const headingMatch = line.match(/^##\s+(.+)$/);
34
- if (headingMatch) {
35
- if (currentHeading) {
36
- sections[currentHeading] = currentContent.join('\n').trim();
37
- }
38
- currentHeading = headingMatch[1].toLowerCase().trim();
39
- currentContent = [];
40
- } else if (currentHeading) {
41
- currentContent.push(line);
42
- }
43
- }
44
-
45
- if (currentHeading) {
46
- sections[currentHeading] = currentContent.join('\n').trim();
47
- }
48
-
49
- return sections;
50
- }
51
-
52
- private extractTitle(raw: string): string {
53
- const match = raw.match(/^#\s+(?:Task\s+\d+\s+Summary:\s*)?(.+)$/m);
54
- return match ? match[1].trim() : '';
55
- }
56
-
57
- private extractMetadata(raw: string, key: string): string | null {
58
- const match = raw.match(new RegExp(`\\*\\*${key}\\*\\*:\\s*(.+)`, 'i'));
59
- return match ? match[1].trim() : null;
60
- }
61
-
62
- private extractList(content: string): string[] {
63
- return content
64
- .split('\n')
65
- .filter((l) => l.trim().startsWith('-') || l.trim().startsWith('*'))
66
- .map((l) => l.replace(/^[-*]\s*/, '').replace(/^[\u{2000}-\u{FFFF}\u{1F000}-\u{1FFFF}]\uFE0F?\s*/u, '').trim())
67
- .filter((l) => l.length > 0);
68
- }
69
-
70
- private extractDeliverables(content: string): DeliverableEntry[] | undefined {
71
- if (!content) return undefined;
72
-
73
- const entries: DeliverableEntry[] = [];
74
- for (const line of content.split('\n')) {
75
- const match = line.match(/^-\s*(🔴|🟡|🔵)\s+([^:]+):\s*(.+)$/);
76
- if (match) {
77
- entries.push({
78
- priority: match[1] as '🔴' | '🟡' | '🔵',
79
- type: match[2].trim(),
80
- description: match[3].trim(),
81
- });
82
- }
83
- }
84
-
85
- return entries.length > 0 ? entries : undefined;
86
- }
87
- }
@@ -1,66 +0,0 @@
1
- /**
2
- * NotesRenderer for Release Tool
3
- *
4
- * Generates markdown release notes from a PipelineRecommendation.
5
- * Public notes include 🔴 (breaking) and 🟡 (prominent) only.
6
- * Internal notes include all tiers.
7
- */
8
-
9
- import { PipelineRecommendation, RenderedNotes, ClassifiedChange, ReleaseData } from '../types';
10
-
11
- const TIER_HEADINGS: Record<string, string> = {
12
- breaking: '🔴 Breaking / Consumer-Facing',
13
- prominent: '🟡 Ecosystem Changes',
14
- context: '🔵 Internal / Context',
15
- };
16
-
17
- export class NotesRenderer {
18
- render(recommendation: PipelineRecommendation): RenderedNotes {
19
- const grouped = this.groupByPriority(recommendation.changes);
20
- const date = new Date().toISOString().split('T')[0];
21
-
22
- const json: ReleaseData = {
23
- version: recommendation.recommendedVersion,
24
- previousVersion: recommendation.currentVersion,
25
- date,
26
- changes: recommendation.changes,
27
- recommendation,
28
- };
29
-
30
- const header = `# Release ${recommendation.recommendedVersion}\n\n**Date**: ${date} \n**Previous**: ${recommendation.currentVersion} \n**Bump**: ${recommendation.bumpType}\n`;
31
-
32
- const publicSections = this.renderSections(grouped, ['breaking', 'prominent']);
33
- const internalSections = this.renderSections(grouped, ['breaking', 'prominent', 'context']);
34
-
35
- return {
36
- public: publicSections ? `${header}\n${publicSections}` : `${header}\nNo consumer-facing changes in this release.\n`,
37
- internal: `${header}\n${internalSections || 'No changes in this release.\n'}`,
38
- json,
39
- };
40
- }
41
-
42
- private groupByPriority(changes: ClassifiedChange[]): Record<string, ClassifiedChange[]> {
43
- const grouped: Record<string, ClassifiedChange[]> = {};
44
- for (const c of changes) {
45
- (grouped[c.priority] ??= []).push(c);
46
- }
47
- return grouped;
48
- }
49
-
50
- private renderSections(grouped: Record<string, ClassifiedChange[]>, tiers: string[]): string {
51
- return tiers
52
- .filter((tier) => grouped[tier]?.length)
53
- .map((tier) => `## ${TIER_HEADINGS[tier]}\n\n${this.renderChanges(grouped[tier])}`)
54
- .join('\n');
55
- }
56
-
57
- private renderChanges(changes: ClassifiedChange[]): string {
58
- return changes
59
- .map((c) => {
60
- const lines = [`- **${c.change.taskTitle}** *(${c.deliverableType})*`];
61
- if (c.change.whatWasDone) lines.push(` ${c.change.whatWasDone}`);
62
- return lines.join('\n');
63
- })
64
- .join('\n') + '\n';
65
- }
66
- }
@@ -1,70 +0,0 @@
1
- /**
2
- * SummaryScanner for Release Tool
3
- *
4
- * Discovers task summary documents added since the last release tag.
5
- * Uses git log when a tag exists, falls back to glob when no tags.
6
- */
7
-
8
- import { execSync } from 'child_process';
9
- import * as fs from 'fs';
10
- import * as path from 'path';
11
- import { TagInfo, SummaryDoc } from '../types';
12
-
13
- const SUMMARY_GLOB = 'docs/specs/*/task-*-summary.md';
14
-
15
- export class SummaryScanner {
16
- private cwd: string;
17
-
18
- constructor(cwd?: string) {
19
- this.cwd = cwd || process.cwd();
20
- }
21
-
22
- async findSummariesSinceTag(tag: TagInfo | null): Promise<SummaryDoc[]> {
23
- const paths = tag ? this.findViaGit(tag) : this.findViaGlob();
24
- return paths.map((p) => this.parseSummaryDoc(p)).filter((d): d is SummaryDoc => d !== null);
25
- }
26
-
27
- private findViaGit(tag: TagInfo): string[] {
28
- try {
29
- const output = execSync(
30
- `git log --diff-filter=A --name-only --pretty=format: ${tag.tag}..HEAD -- "${SUMMARY_GLOB}"`,
31
- { cwd: this.cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
32
- );
33
- return output.split('\n').map((l) => l.trim()).filter((l) => l.length > 0);
34
- } catch {
35
- return this.findViaGlob();
36
- }
37
- }
38
-
39
- private findViaGlob(): string[] {
40
- try {
41
- const output = execSync(`find docs/specs -name "task-*-summary.md" -type f 2>/dev/null | sort`,
42
- { cwd: this.cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
43
- );
44
- return output.split('\n').map((l) => l.trim()).filter((l) => l.length > 0);
45
- } catch {
46
- return [];
47
- }
48
- }
49
-
50
- private parseSummaryDoc(filePath: string): SummaryDoc | null {
51
- try {
52
- const fullPath = path.resolve(this.cwd, filePath);
53
- const raw = fs.readFileSync(fullPath, 'utf-8');
54
-
55
- const specMatch = filePath.match(/docs\/specs\/([^/]+)\//);
56
- const taskMatch = filePath.match(/task-(\d+)-summary\.md$/);
57
-
58
- if (!specMatch || !taskMatch) return null;
59
-
60
- return {
61
- path: filePath,
62
- specName: specMatch[1],
63
- taskNumber: parseInt(taskMatch[1]),
64
- raw,
65
- };
66
- } catch {
67
- return null;
68
- }
69
- }
70
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * TagResolver for Release Tool
3
- *
4
- * Resolves release boundaries using git tags.
5
- * Git tags are the only persistent state in this system.
6
- */
7
-
8
- import { execSync } from 'child_process';
9
- import { TagInfo } from '../types';
10
-
11
- export class TagResolver {
12
- private cwd: string;
13
-
14
- constructor(cwd?: string) {
15
- this.cwd = cwd || process.cwd();
16
- }
17
-
18
- async getLatestTag(): Promise<TagInfo | null> {
19
- try {
20
- const tag = this.exec('git describe --tags --abbrev=0').trim();
21
- if (!tag) return null;
22
-
23
- const commit = this.exec(`git rev-list -n 1 ${tag}`).trim();
24
- const date = this.exec(`git log -1 --format=%aI ${tag}`).trim();
25
-
26
- return { tag, commit, date };
27
- } catch {
28
- return null;
29
- }
30
- }
31
-
32
- async createTag(version: string, message: string): Promise<void> {
33
- const tag = version.startsWith('v') ? version : `v${version}`;
34
- this.exec(`git tag -a ${tag} -m "${message.replace(/"/g, '\\"')}"`);
35
- }
36
-
37
- private exec(command: string): string {
38
- return execSync(command, { cwd: this.cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
39
- }
40
- }