sinapse-ai 1.12.2 → 1.13.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 (89) hide show
  1. package/.claude/CLAUDE.md +2 -2
  2. package/.claude/rules/agent-memory-imports.md +2 -0
  3. package/.sinapse-ai/cli/commands/pro/index.js +8 -48
  4. package/.sinapse-ai/constitution.md +6 -2
  5. package/.sinapse-ai/data/entity-registry.yaml +12 -36
  6. package/.sinapse-ai/development/agents/architect/MEMORY.md +35 -0
  7. package/.sinapse-ai/development/agents/data-engineer/MEMORY.md +38 -0
  8. package/.sinapse-ai/development/agents/developer/MEMORY.md +36 -0
  9. package/.sinapse-ai/development/agents/devops/MEMORY.md +39 -0
  10. package/.sinapse-ai/development/agents/product-lead/MEMORY.md +36 -0
  11. package/.sinapse-ai/development/agents/project-lead/MEMORY.md +36 -0
  12. package/.sinapse-ai/development/agents/quality-gate/MEMORY.md +35 -0
  13. package/.sinapse-ai/development/agents/sprint-lead/MEMORY.md +36 -0
  14. package/.sinapse-ai/development/templates/chrome-brain/knowledge-base/chrome-brain.md +2 -2
  15. package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +15 -0
  16. package/.sinapse-ai/infrastructure/scripts/validate-agents.js +159 -8
  17. package/.sinapse-ai/install-manifest.yaml +12 -148
  18. package/AGENTS.md +13 -2
  19. package/CHANGELOG.md +122 -2
  20. package/README.en.md +1 -1
  21. package/README.md +6 -26
  22. package/bin/cli.js +2 -7
  23. package/bin/commands/install.js +19 -257
  24. package/bin/commands/update.js +18 -78
  25. package/bin/lib/command-generator.js +261 -0
  26. package/bin/lib/prompts.js +2 -65
  27. package/bin/modules/chrome-brain-installer.js +2 -1
  28. package/bin/postinstall.js +6 -6
  29. package/docs/getting-started.md +0 -1
  30. package/docs/pt/getting-started.md +1 -1
  31. package/docs/security/dependabot-triage.md +17 -17
  32. package/package.json +6 -3
  33. package/packages/installer/src/wizard/i18n.js +3 -204
  34. package/packages/installer/src/wizard/index.js +1 -1
  35. package/packages/installer/src/wizard/questions.js +5 -368
  36. package/packages/installer/src/wizard/wizard.js +0 -9
  37. package/scripts/generate-install-manifest.js +6 -0
  38. package/scripts/validate-changelog-tags.js +97 -0
  39. package/scripts/validate-no-personal-leaks.js +184 -28
  40. package/scripts/validate-schemas.js +55 -10
  41. package/scripts/validate-story-meta.js +25 -12
  42. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +1 -1
  43. package/squads/claude-code-mastery/agents/hooks-architect.md +12 -20
  44. package/squads/claude-code-mastery/tasks/audit-setup.md +1 -1
  45. package/squads/squad-product/agents/product-orqx.md +0 -1
  46. package/squads/squad-product/agents/ps-client-product-manager.md +0 -1
  47. package/squads/squad-product/agents/ps-delivery-manager.md +0 -1
  48. package/squads/squad-product/agents/ps-discovery-lead.md +0 -1
  49. package/squads/squad-product/agents/ps-product-analyst.md +0 -1
  50. package/squads/squad-product/agents/ps-product-ops-specialist.md +0 -1
  51. package/squads/squad-product/agents/ps-product-strategist.md +0 -1
  52. package/.sinapse-ai/core/grounding/README.md +0 -83
  53. package/.sinapse-ai/core/grounding/brand.cjs +0 -29
  54. package/.sinapse-ai/core/grounding/config-loader.cjs +0 -52
  55. package/.sinapse-ai/core/grounding/design-system.cjs +0 -29
  56. package/.sinapse-ai/core/grounding/vault.cjs +0 -36
  57. package/.sinapse-ai/development/scripts/approval-workflow.js +0 -643
  58. package/.sinapse-ai/development/scripts/backup-manager.js +0 -607
  59. package/.sinapse-ai/development/scripts/branch-manager.js +0 -390
  60. package/.sinapse-ai/development/scripts/code-quality-improver.js +0 -1329
  61. package/.sinapse-ai/development/scripts/commit-message-generator.js +0 -850
  62. package/.sinapse-ai/development/scripts/conflict-resolver.js +0 -675
  63. package/.sinapse-ai/development/scripts/dependency-analyzer.js +0 -638
  64. package/.sinapse-ai/development/scripts/diff-generator.js +0 -352
  65. package/.sinapse-ai/development/scripts/git-wrapper.js +0 -462
  66. package/.sinapse-ai/development/scripts/modification-validator.js +0 -555
  67. package/.sinapse-ai/development/scripts/performance-analyzer.js +0 -758
  68. package/.sinapse-ai/development/scripts/refactoring-suggester.js +0 -1148
  69. package/.sinapse-ai/development/scripts/rollback-handler.js +0 -531
  70. package/.sinapse-ai/development/scripts/security-checker.js +0 -359
  71. package/.sinapse-ai/development/scripts/template-engine.js +0 -240
  72. package/.sinapse-ai/development/scripts/template-validator.js +0 -279
  73. package/.sinapse-ai/development/scripts/test-generator.js +0 -844
  74. package/.sinapse-ai/development/scripts/transaction-manager.js +0 -590
  75. package/.sinapse-ai/development/scripts/yaml-validator.js +0 -397
  76. package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +0 -162
  77. package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +0 -211
  78. package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +0 -194
  79. package/bin/lib/register-grounding-hooks.js +0 -145
  80. package/docs/guides/agents/traces/execution-traces.zip +0 -0
  81. package/docs/guides/grounding-setup.md +0 -154
  82. package/docs/guides/workflows/SINAPSE-WORKFLOWS.zip +0 -0
  83. package/packages/installer/src/pro/pro-scaffolder.js +0 -449
  84. package/packages/installer/src/wizard/grounding-config.js +0 -131
  85. package/packages/installer/src/wizard/pro-setup.js +0 -1439
  86. package/packages/installer/templates/README.md +0 -16
  87. package/packages/installer/templates/brand-routing.example.json +0 -12
  88. package/packages/installer/templates/ds-routing.example.json +0 -12
  89. package/packages/installer/templates/vault-routing.example.json +0 -12
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * Wizard Questions Definitions
3
3
  *
4
- * Modular question system for SINAPSE installation wizard
5
- * Questions from Stories 1.3-1.6 will be added here
4
+ * Modular question system for SINAPSE installation wizard.
5
+ * Only the questions actually asked by the live 2-step wizard remain here
6
+ * (language + LLM). IDE selection helper is kept for the IDE selector module.
6
7
  *
7
8
  * @module wizard/questions
8
9
  */
9
10
 
10
11
  const { colors } = require('../utils/sinapse-colors');
11
- const { createInquirerValidator, validateProjectType } = require('./validators');
12
- const { t, getLanguageChoices, setLanguage: _setLanguage } = require('./i18n');
12
+ const { t, getLanguageChoices } = require('./i18n');
13
13
 
14
14
  /**
15
15
  * Get language selection question (first question)
@@ -21,33 +21,7 @@ function getLanguageQuestion() {
21
21
  name: 'language',
22
22
  message: '🌐 Language:',
23
23
  choices: getLanguageChoices(),
24
- default: 'en',
25
- };
26
- }
27
-
28
- /**
29
- * Get user profile question (Story 10.2 - Epic 10: User Profile System)
30
- * @deprecated No longer asked interactively. Kept for backward compatibility.
31
- * @returns {Object} Inquirer question object
32
- */
33
- function getUserProfileQuestion() {
34
- return {
35
- type: 'list',
36
- name: 'userProfile',
37
- message: colors.primary(t('userProfileQuestion')),
38
- choices: [
39
- {
40
- name:
41
- colors.highlight(`🚀 ${t('modoAssistido')}`) +
42
- colors.dim(` (${t('recommended')})`),
43
- value: 'bob',
44
- },
45
- {
46
- name: `⚙️ ${t('modoAvancado')}`,
47
- value: 'advanced',
48
- },
49
- ],
50
- default: 0,
24
+ default: 'pt',
51
25
  };
52
26
  }
53
27
 
@@ -80,32 +54,6 @@ function getLLMQuestion() {
80
54
  };
81
55
  }
82
56
 
83
- /**
84
- * Get project type question (Story 1.3)
85
- * @deprecated Project type is now auto-detected. Kept for backward compat.
86
- *
87
- * @returns {Object} Inquirer question object
88
- */
89
- function getProjectTypeQuestion() {
90
- return {
91
- type: 'list',
92
- name: 'projectType',
93
- message: colors.primary(t('projectTypeQuestion')),
94
- choices: [
95
- {
96
- name: colors.highlight(t('greenfield')) + colors.dim(` (${t('greenfieldDesc')})`),
97
- value: 'greenfield',
98
- },
99
- {
100
- name: t('brownfield') + colors.dim(` (${t('brownfieldDesc')})`),
101
- value: 'brownfield',
102
- },
103
- ],
104
- default: 0,
105
- validate: createInquirerValidator(validateProjectType),
106
- };
107
- }
108
-
109
57
  /**
110
58
  * Get IDE selection questions (Story 1.4)
111
59
  *
@@ -116,319 +64,8 @@ function getIDEQuestions() {
116
64
  return [getIDESelectionQuestion()];
117
65
  }
118
66
 
119
- /**
120
- * Get package manager selection question (Story 1.7)
121
- *
122
- * @param {string} detectedPM - Auto-detected package manager
123
- * @returns {Object} Inquirer question object
124
- */
125
- function getPackageManagerQuestion(detectedPM = 'npm') {
126
- return {
127
- type: 'list',
128
- name: 'packageManager',
129
- message: colors.primary('Which package manager should be used?'),
130
- choices: [
131
- {
132
- name: detectedPM === 'npm' ? colors.highlight('npm') + colors.dim(' (detected)') : 'npm',
133
- value: 'npm',
134
- },
135
- {
136
- name: detectedPM === 'yarn' ? colors.highlight('yarn') + colors.dim(' (detected)') : 'yarn',
137
- value: 'yarn',
138
- },
139
- {
140
- name: detectedPM === 'pnpm' ? colors.highlight('pnpm') + colors.dim(' (detected)') : 'pnpm',
141
- value: 'pnpm',
142
- },
143
- {
144
- name: detectedPM === 'bun' ? colors.highlight('bun') + colors.dim(' (detected)') : 'bun',
145
- value: 'bun',
146
- },
147
- ],
148
- default: ['npm', 'yarn', 'pnpm', 'bun'].indexOf(detectedPM) || 0,
149
- };
150
- }
151
-
152
- /**
153
- * Get MCP selection questions (Story 1.5 / 1.8 Integration)
154
- *
155
- * @returns {Object[]} Array of inquirer question objects
156
- */
157
- function getMCPQuestions() {
158
- return [
159
- {
160
- type: 'checkbox',
161
- name: 'selectedMCPs',
162
- message: colors.primary('Select MCPs to install (project-level):'),
163
- choices: [
164
- {
165
- name:
166
- colors.highlight('Browser (Puppeteer)') + colors.dim(' - Web automation and testing'),
167
- value: 'browser',
168
- checked: true,
169
- },
170
- {
171
- name: colors.highlight('Context7') + colors.dim(' - Library documentation search'),
172
- value: 'context7',
173
- checked: true,
174
- },
175
- {
176
- name: colors.highlight('Exa') + colors.dim(' - Advanced web search'),
177
- value: 'exa',
178
- checked: true,
179
- },
180
- {
181
- name: colors.highlight('Desktop Commander') + colors.dim(' - File system access'),
182
- value: 'desktop-commander',
183
- checked: true,
184
- },
185
- ],
186
- validate: () => {
187
- // Allow empty selection (user can skip MCP installation)
188
- return true;
189
- },
190
- },
191
- // Note: API keys are configured later via sinapse-orqx or directly in .env
192
- ];
193
- }
194
-
195
- /**
196
- * Get environment configuration questions (Story 1.6)
197
- *
198
- * DESIGN NOTE: Environment configuration uses its own prompt system
199
- * via @clack/prompts in packages/installer/src/config/configure-environment.js
200
- *
201
- * API key prompts are NOT part of wizard questions to keep the
202
- * environment module self-contained and testable independently.
203
- *
204
- * The wizard calls configureEnvironment() directly after IDE selection
205
- * in src/wizard/index.js (Task 1.6.7)
206
- *
207
- * @returns {Object[]} Empty array - prompts handled in environment module
208
- */
209
- function getEnvironmentQuestions() {
210
- // Environment config prompts handled in configure-environment.js
211
- // No wizard questions needed for this story
212
- return [];
213
- }
214
-
215
- /**
216
- * Get Squad selection questions
217
- *
218
- * Available squads for v4.0:
219
- * - squad-creator: Tools to create custom squads
220
- * - etl: ETL pipeline for knowledge base creation
221
- *
222
- * Note: This function is currently DISABLED. Squad selection is handled
223
- * directly in sinapse-init.js using the squads/ directory.
224
- *
225
- * @returns {Object[]} Array of inquirer question objects
226
- * @deprecated Use squads/ directory directly in sinapse-init.js
227
- */
228
- function getSquadQuestions() {
229
- return [
230
- {
231
- type: 'checkbox',
232
- name: 'selectedSquads',
233
- message: colors.primary('Select Squads to install (optional):'),
234
- choices: [
235
- {
236
- name:
237
- colors.highlight('squad-creator') +
238
- colors.dim(' - Tools to create custom squads'),
239
- value: 'squad-creator',
240
- checked: false,
241
- },
242
- {
243
- name: colors.highlight('etl') + colors.dim(' - ETL pipeline for knowledge base creation'),
244
- value: 'etl',
245
- checked: false,
246
- },
247
- ],
248
- validate: () => {
249
- // Allow empty selection (user can skip squad installation)
250
- return true;
251
- },
252
- },
253
- ];
254
- }
255
-
256
- /**
257
- * Get Tech Preset selection question
258
- *
259
- * Tech presets provide pre-configured architecture patterns and standards
260
- * for different technology stacks.
261
- *
262
- * @returns {Object[]} Array of inquirer question objects
263
- */
264
- function getTechPresetQuestion() {
265
- return [
266
- {
267
- type: 'list',
268
- name: 'selectedTechPreset',
269
- message: colors.primary('Select a Tech Preset for architecture patterns:'),
270
- choices: [
271
- {
272
- name:
273
- colors.highlight('nextjs-react') +
274
- colors.dim(' - Next.js 16+, React, TypeScript, Tailwind, Zustand'),
275
- value: 'nextjs-react',
276
- },
277
- {
278
- name: colors.highlight('go') + colors.dim(' - Go services and microservices'),
279
- value: 'go',
280
- },
281
- {
282
- name: colors.highlight('java') + colors.dim(' - Java 21+ with Spring Boot'),
283
- value: 'java',
284
- },
285
- {
286
- name: colors.highlight('rust') + colors.dim(' - High-reliability Rust services'),
287
- value: 'rust',
288
- },
289
- {
290
- name: colors.highlight('csharp') + colors.dim(' - .NET 9+ ASP.NET Core services'),
291
- value: 'csharp',
292
- },
293
- {
294
- name: colors.highlight('php') + colors.dim(' - PHP 8.3+ with Laravel'),
295
- value: 'php',
296
- },
297
- {
298
- name: 'None' + colors.dim(' - Let SINAPSE decide based on project'),
299
- value: 'none',
300
- },
301
- ],
302
- default: 0,
303
- },
304
- ];
305
- }
306
-
307
- /**
308
- * Build complete question sequence
309
- * Allows conditional questions based on previous answers
310
- *
311
- * @param {Object} context - Context with previous answers
312
- * @returns {Object[]} Array of questions
313
- */
314
- function buildQuestionSequence(_context = {}) {
315
- const questions = [];
316
-
317
- // Language selection (first question)
318
- questions.push(getLanguageQuestion());
319
-
320
- // Story 1.2: Project type is auto-detected (question removed from flow)
321
-
322
- // Story 1.4: IDE Selection
323
- questions.push(...getIDEQuestions());
324
-
325
- // Story 1.5/1.8: MCP Selection
326
- // DISABLED: MCPs are advanced config that can confuse beginners
327
- // TODO: Remove entirely in future version - each project has unique MCP needs
328
- // questions.push(...getMCPQuestions());
329
-
330
- // Squad Selection - DISABLED: Handled directly in sinapse-init.js
331
- // TODO: Consider removing getSquadQuestions() entirely in future version
332
- // questions.push(...getSquadQuestions());
333
-
334
- // Tech Preset Selection
335
- questions.push(...getTechPresetQuestion());
336
-
337
- // Story 1.7: Package Manager - Auto-detected (no question needed)
338
- // The wizard will auto-detect and use the appropriate package manager
339
- // See detectPackageManager() in dependency-installer.js
340
-
341
- // Story 1.6: Environment Configuration
342
- // Note: Env config prompts handled directly in configureEnvironment()
343
- // See src/wizard/index.js integration (after IDE config step)
344
-
345
- // Future: Conditional questions based on projectType
346
- // if (context.projectType === 'greenfield') { ... }
347
-
348
- return questions;
349
- }
350
-
351
- /**
352
- * Story 10.47 — Grounding questions (vault / design system / brand)
353
- *
354
- * Each helper returns a single inquirer `input` question. An empty answer
355
- * means "skip" — the corresponding grounding section stays disabled and the
356
- * shipped hook is a no-op. Suffixed instruction in the message reinforces
357
- * that skipping activates the high-quality fallback.
358
- */
359
-
360
- function getVaultGroundingQuestion() {
361
- return {
362
- type: 'input',
363
- name: 'vaultPath',
364
- message: '📚 Vault de notas markdown — caminho absoluto, ou Enter pra pular:',
365
- default: '',
366
- filter: (value) => (typeof value === 'string' ? value.trim() : ''),
367
- };
368
- }
369
-
370
- function getDesignSystemGroundingQuestion() {
371
- return {
372
- type: 'input',
373
- name: 'designSystemPath',
374
- message: '🎨 Design system / source-of-truth visual — caminho absoluto, ou Enter pra pular:',
375
- default: '',
376
- filter: (value) => (typeof value === 'string' ? value.trim() : ''),
377
- };
378
- }
379
-
380
- function getBrandGroundingQuestion() {
381
- return {
382
- type: 'input',
383
- name: 'brandbookPath',
384
- message: '🪪 Brandbook do projeto — caminho absoluto, ou Enter pra pular:',
385
- default: '',
386
- filter: (value) => (typeof value === 'string' ? value.trim() : ''),
387
- };
388
- }
389
-
390
- function getGroundingQuestions() {
391
- return [
392
- getVaultGroundingQuestion(),
393
- getDesignSystemGroundingQuestion(),
394
- getBrandGroundingQuestion(),
395
- ];
396
- }
397
-
398
- /**
399
- * Get question by ID
400
- * Useful for testing individual questions
401
- *
402
- * @param {string} questionId - Question identifier
403
- * @returns {Object|null} Question object or null if not found
404
- */
405
- function getQuestionById(questionId) {
406
- const questionMap = {
407
- projectType: getProjectTypeQuestion(),
408
- // Future questions will be added here
409
- };
410
-
411
- return questionMap[questionId] || null;
412
- }
413
-
414
67
  module.exports = {
415
68
  getLanguageQuestion,
416
- getUserProfileQuestion,
417
69
  getLLMQuestion,
418
- getProjectTypeQuestion,
419
70
  getIDEQuestions,
420
- getMCPQuestions,
421
- getSquadQuestions,
422
- // Backward compat alias (deprecated)
423
- getExpansionPackQuestions: getSquadQuestions,
424
- getTechPresetQuestion,
425
- getEnvironmentQuestions,
426
- getPackageManagerQuestion,
427
- buildQuestionSequence,
428
- getQuestionById,
429
- // Story 10.47 — grounding (opt-in BYO)
430
- getVaultGroundingQuestion,
431
- getDesignSystemGroundingQuestion,
432
- getBrandGroundingQuestion,
433
- getGroundingQuestions,
434
71
  };
@@ -152,7 +152,6 @@ async function selectInstallationMode(detected, opts = {}) {
152
152
  * @param {string} [summary.language] - Language code (pt/en) or label
153
153
  * @param {string|string[]} [summary.ide] - IDE selection
154
154
  * @param {string} [summary.targetDir] - Target directory
155
- * @param {Object} [summary.grounding] - Grounding configuration (vault/DS/brand)
156
155
  * @param {Object} [opts]
157
156
  * @param {boolean} [opts.assumeYes=false] - Skip the confirmation prompt
158
157
  * @returns {Promise<boolean>} true if confirmed (or non-interactive), false to abort
@@ -166,14 +165,6 @@ async function confirmInstallSummary(summary, opts = {}) {
166
165
  const ide = Array.isArray(summary.ide) ? summary.ide.join(', ') : summary.ide;
167
166
  console.log(` IDE: ${ide}`);
168
167
  }
169
- if (summary.grounding) {
170
- const g = summary.grounding;
171
- const parts = [];
172
- if (g.vault) parts.push('vault');
173
- if (g.designSystem) parts.push('design-system');
174
- if (g.brand) parts.push('brand');
175
- console.log(` Grounding: ${parts.length ? parts.join(', ') : 'none'}`);
176
- }
177
168
  console.log('─────────────────────────\n');
178
169
 
179
170
  if (opts.assumeYes || !isInteractive()) return true;
@@ -76,6 +76,12 @@ const EXCLUDE_PATTERNS = [
76
76
  /^docs\/template-syntax\.md$/,
77
77
  /^docs\/troubleshooting-guide\.md$/,
78
78
  /^docs\/session-update-pattern\.md$/,
79
+ // Agent memory files are L3-mutable (user/agent-edited per project, see
80
+ // CLAUDE.md L1-L4 boundary). Their hashes churn the committed manifest on
81
+ // every memory change with no drift-tracking value — the brownfield upgrader
82
+ // must never overwrite them anyway. Anchored to the canonical nested path so
83
+ // we don't accidentally exclude anything else named MEMORY.md.
84
+ /(^|\/)development\/agents\/[^/]+\/MEMORY\.md$/,
79
85
  // Gitignored generated files
80
86
  /data\/registry-update-log\.jsonl$/,
81
87
  /data\/registry-healing-log\.jsonl$/,
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Validate Changelog Tags
4
+ *
5
+ * Guards against release/version drift: every published git version tag MUST
6
+ * have a matching `## [x.y.z]` heading in CHANGELOG.md. This catches the failure
7
+ * mode where semantic-release publishes a version but the CHANGELOG entry was
8
+ * never committed back to the repo (the root cause of the 1.9.0 -> 1.12.2 drift).
9
+ *
10
+ * Usage:
11
+ * node scripts/validate-changelog-tags.js
12
+ * npm run validate:changelog
13
+ *
14
+ * Exit codes:
15
+ * 0 - Every version tag has a CHANGELOG heading (or git unavailable -> SKIP)
16
+ * 1 - One or more version tags are missing a CHANGELOG heading
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const { execFileSync } = require('child_process');
24
+
25
+ const ROOT = path.resolve(__dirname, '..');
26
+ const CHANGELOG_PATH = path.join(ROOT, 'CHANGELOG.md');
27
+
28
+ // Matches a clean semver core: x.y.z (no pre-release/build suffix).
29
+ const SEMVER_RE = /^\d+\.\d+\.\d+$/;
30
+
31
+ /**
32
+ * List git version tags, normalized (leading `v` stripped) and filtered to
33
+ * clean `x.y.z` semver. Returns null if git is unavailable (offline / no repo).
34
+ * @returns {string[]|null}
35
+ */
36
+ function listVersionTags() {
37
+ let raw;
38
+ try {
39
+ raw = execFileSync('git', ['tag', '-l'], { cwd: ROOT, encoding: 'utf8' });
40
+ } catch {
41
+ return null; // git not available or not a repo
42
+ }
43
+
44
+ return raw
45
+ .split('\n')
46
+ .map((t) => t.trim())
47
+ .filter(Boolean)
48
+ .map((t) => t.replace(/^v/, ''))
49
+ .filter((t) => SEMVER_RE.test(t));
50
+ }
51
+
52
+ /**
53
+ * Collect all `## [x.y.z]` headings from CHANGELOG.md.
54
+ * @returns {Set<string>}
55
+ */
56
+ function collectChangelogVersions() {
57
+ const content = fs.readFileSync(CHANGELOG_PATH, 'utf8');
58
+ const headings = new Set();
59
+ const re = /^##\s*\[(\d+\.\d+\.\d+)\]/gm;
60
+ let m;
61
+ while ((m = re.exec(content)) !== null) {
62
+ headings.add(m[1]);
63
+ }
64
+ return headings;
65
+ }
66
+
67
+ function main() {
68
+ const tags = listVersionTags();
69
+
70
+ if (tags === null) {
71
+ console.log('SKIP — git is unavailable; cannot cross-check tags vs CHANGELOG.');
72
+ process.exit(0);
73
+ }
74
+
75
+ if (!fs.existsSync(CHANGELOG_PATH)) {
76
+ console.error('FAIL — CHANGELOG.md not found.');
77
+ process.exit(1);
78
+ }
79
+
80
+ const documented = collectChangelogVersions();
81
+ const missing = tags.filter((t) => !documented.has(t)).sort();
82
+
83
+ if (missing.length > 0) {
84
+ console.error('FAIL — version tags without a CHANGELOG heading:');
85
+ for (const v of missing) {
86
+ console.error(` - ${v} (expected "## [${v}]" in CHANGELOG.md)`);
87
+ }
88
+ console.error('');
89
+ console.error('Add the missing entries to CHANGELOG.md before releasing.');
90
+ process.exit(1);
91
+ }
92
+
93
+ console.log(`OK — all ${tags.length} version tag(s) have a CHANGELOG heading.`);
94
+ process.exit(0);
95
+ }
96
+
97
+ main();