sdg-agents 1.2.3 → 1.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdg-agents",
3
- "version": "1.2.3",
3
+ "version": "1.4.0",
4
4
  "description": "Structured working protocol and engineering rules for AI agents. Works with Claude Code, Antigravity, Codex, and others.",
5
5
  "type": "module",
6
6
  "main": "./src/engine/bin/index.mjs",
@@ -14,7 +14,7 @@
14
14
  "dev": "node src/engine/bin/index.mjs",
15
15
  "build": "node src/engine/bin/build-bundle.mjs",
16
16
  "review": "node src/engine/bin/review-bundle.mjs",
17
- "bump": "node src/engine/bin/auto-bump.mjs",
17
+ "bump": "node scripts/bump.mjs",
18
18
  "add-idiom": "node src/engine/bin/add-idiom.mjs",
19
19
  "clear": "node src/engine/bin/clear-bundle.mjs",
20
20
  "test": "node --test src/engine/lib/*.test.mjs",
@@ -0,0 +1,90 @@
1
+ /**
2
+ * SDG-Agents: Bump & Changelog Automation
3
+ * Automates semantic versioning and promotes Unreleased changes in CHANGELOG.md.
4
+ */
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { execSync } from 'node:child_process';
9
+
10
+ const ROOT_DIR = process.cwd();
11
+ const PACKAGE_JSON_PATH = path.join(ROOT_DIR, 'package.json');
12
+ const CHANGELOG_PATH = path.join(ROOT_DIR, 'CHANGELOG.md');
13
+
14
+ function run() {
15
+ const args = process.argv.slice(2);
16
+ const bumpType = args[0]; // feat, fix, major
17
+
18
+ if (!['feat', 'fix', 'major'].includes(bumpType)) {
19
+ console.error('❌ Error: Please specify bump type (feat, fix, or major).');
20
+ console.log('Usage: npm run bump <feat|fix|major>');
21
+ process.exit(1);
22
+ }
23
+
24
+ const typeMap = {
25
+ feat: 'minor',
26
+ fix: 'patch',
27
+ major: 'major',
28
+ };
29
+
30
+ const npmType = typeMap[bumpType];
31
+
32
+ try {
33
+ // 1. Get current version
34
+ const pkg = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8'));
35
+ const oldVersion = pkg.version;
36
+
37
+ // 2. Bump version in package.json (no git tag/commit yet)
38
+ console.log(`🚀 Bumping version (${npmType})...`);
39
+ execSync(`npm version ${npmType} --no-git-tag-version`, { stdio: 'inherit' });
40
+
41
+ // 3. Get new version
42
+ const newPkg = JSON.parse(fs.readFileSync(PACKAGE_JSON_PATH, 'utf8'));
43
+ const newVersion = newPkg.version;
44
+
45
+ // 4. Update CHANGELOG.md
46
+ updateChangelog(newVersion);
47
+
48
+ console.log(`✅ Success: ${oldVersion} → ${newVersion}`);
49
+ console.log('🔗 CHANGELOG.md updated and promoted to current date.');
50
+ } catch (error) {
51
+ console.error('❌ Error during bump strategy:', error.message);
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ function updateChangelog(newVersion) {
57
+ if (!fs.existsSync(CHANGELOG_PATH)) {
58
+ console.warn('⚠️ CHANGELOG.md not found. Skipping changelog update.');
59
+ return;
60
+ }
61
+
62
+ const content = fs.readFileSync(CHANGELOG_PATH, 'utf8');
63
+ const today = new Date().toISOString().split('T')[0];
64
+
65
+ // Pattern to find the [Unreleased] section
66
+ // It handles both formats: ## [Unreleased] and ## [Unreleased] - YYYY-MM-DD
67
+ const unreleasedRegex = /##\s*\[Unreleased\](\s*-\s*\d{4}-\d{2}-\d{2})?/i;
68
+
69
+ if (!unreleasedRegex.test(content)) {
70
+ console.warn('⚠️ Could not find "## [Unreleased]" header in CHANGELOG.md.');
71
+ console.log('Skipping content promotion.');
72
+ return;
73
+ }
74
+
75
+ const newHeader = `## [${newVersion}] - ${today}`;
76
+
77
+ // 1. Promote Unreleased to New Version
78
+ let updatedContent = content.replace(unreleasedRegex, newHeader);
79
+
80
+ // 2. Inject new [Unreleased] block at the top
81
+ const insertIndex = updatedContent.indexOf(newHeader);
82
+ const nextBlock = `## [Unreleased]\n\n### Added\n\n### Fixed\n\n`;
83
+
84
+ updatedContent =
85
+ updatedContent.slice(0, insertIndex) + nextBlock + updatedContent.slice(insertIndex);
86
+
87
+ fs.writeFileSync(CHANGELOG_PATH, updatedContent);
88
+ }
89
+
90
+ run();
@@ -140,7 +140,8 @@ On every request, classify intent before acting:
140
140
  ### END Checklist (mandatory — execute in order, mark each before proceeding)
141
141
 
142
142
  - [ ] **SUMMARIZE** — one sentence per completed PLAN task written in response
143
- - [ ] **CHANGELOG** — Append entry. **Identify next version** (check \`package.json\`) to determine the header (\`## [NEXT_VERSION] - YYYY-MM-DD\` for releases/auto-bump, or \`## [Unreleased]\`). Categories: \`### Added\` (feat), \`### Fixed\` (fix).
143
+ - [ ] **BUMP** — run \`npm run bump <feat|fix>\` to promote CHANGELOG and package.json version. Skip if not applicable.
144
+ - [ ] **CHANGELOG** — Verify [Unreleased] content was promoted. Append any manual notes if needed.
144
145
  - [ ] **BACKLOG: tasks.md** — all completed tasks moved to `## Done` with `[DONE]` status
145
146
  - [ ] **BACKLOG: context.md** — \`## Now\` updated with next objective or cleared
146
147
  - [ ] **KNOWLEDGE** — Log any patterns, findings, or rework discovered during this cycle. Update \`.ai-backlog/learned.md\` (for successful feats) or \`.ai-backlog/troubleshoot.md\` (for fixed incidents). Curate stale or irrelevant items.
@@ -21,6 +21,7 @@ const {
21
21
  writeBacklogFiles,
22
22
  writeGitignore,
23
23
  writeManifest,
24
+ writeAutomationScripts,
24
25
  } = InstructionAssembler;
25
26
  const { success } = ResultUtils;
26
27
  const { runIfDirect } = FsUtils;
@@ -207,6 +208,7 @@ function executeQuickPipeline(targetDir, selections, { noDevGuides = false } = {
207
208
 
208
209
  printStep(5, 5, 'Injecting spec templates...');
209
210
  injectPrompts(targetDir, selections.track);
211
+ writeAutomationScripts(targetDir, selections);
210
212
  writeManifest(targetDir, selections, packageJson.version);
211
213
  }
212
214
 
@@ -239,6 +241,7 @@ function executeAgentsPipeline(targetDir, selections, { noDevGuides = false } =
239
241
  writeGitignore(targetDir);
240
242
 
241
243
  printStep(5, 5, 'Finalizing manifest...');
244
+ writeAutomationScripts(targetDir, selections);
242
245
  writeManifest(targetDir, selections, packageJson.version);
243
246
  }
244
247
 
@@ -17,6 +17,7 @@ function parseCliArgs(argv) {
17
17
  mode: getArgValue(argv, '--mode'),
18
18
  track: getArgValue(argv, '--track'),
19
19
  scope: getArgValue(argv, '--scope'),
20
+ bump: !argv.includes('--no-bump'),
20
21
  };
21
22
  }
22
23
 
@@ -528,6 +528,76 @@ function writeManifest(targetDir, selections, pkgVersion) {
528
528
  fs.writeFileSync(path.join(aiDir, '.sdg-manifest.json'), JSON.stringify(manifest, null, 2));
529
529
  }
530
530
 
531
+ /**
532
+ * Injects automation scripts and configurations (Bump, Husky) if enabled.
533
+ * Idempotent: skips if scripts/bump.mjs exists or if selections.bump is false.
534
+ */
535
+ function writeAutomationScripts(targetDir, selections) {
536
+ if (selections.bump === false) return;
537
+
538
+ const scriptsDir = path.join(targetDir, 'scripts');
539
+ const bumpScriptPath = path.join(scriptsDir, 'bump.mjs');
540
+
541
+ // 1. Check for existing bump script in package.json to avoid collision
542
+ const pkgPath = path.join(targetDir, 'package.json');
543
+ if (!fs.existsSync(pkgPath)) return;
544
+
545
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
546
+ const hasExistingBump =
547
+ pkg.scripts && (pkg.scripts.bump || pkg.scripts.release || pkg.scripts.version);
548
+
549
+ if (hasExistingBump && !fs.existsSync(bumpScriptPath)) {
550
+ // If they have a script but not our file, we respect their script
551
+ return;
552
+ }
553
+
554
+ // 2. Write bump.mjs template
555
+ if (!fs.existsSync(bumpScriptPath)) {
556
+ if (!fs.existsSync(scriptsDir)) fs.mkdirSync(scriptsDir, { recursive: true });
557
+ const templatePath = path.join(SOURCE_INSTRUCTIONS, 'templates', 'bump.mjs');
558
+ const templateContent = fs.readFileSync(templatePath, 'utf8');
559
+ fs.writeFileSync(bumpScriptPath, templateContent);
560
+ }
561
+
562
+ // 3. Update package.json scripts
563
+ if (!pkg.scripts) pkg.scripts = {};
564
+ if (!pkg.scripts.bump) {
565
+ pkg.scripts.bump = 'node scripts/bump.mjs';
566
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
567
+ }
568
+
569
+ // 4. Configure Husky if .husky exists
570
+ const huskyDir = path.join(targetDir, '.husky');
571
+ if (fs.existsSync(huskyDir)) {
572
+ const prePushPath = path.join(huskyDir, 'pre-push');
573
+ const nvmShim = dedent`
574
+ # NVM Shim (Essential for projects Staff in Linux/NVM)
575
+ export NVM_DIR="$HOME/.nvm"
576
+ [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"
577
+ `;
578
+
579
+ const bumpCmd = 'npm run bump fix';
580
+
581
+ if (fs.existsSync(prePushPath)) {
582
+ const content = fs.readFileSync(prePushPath, 'utf8');
583
+ if (!content.includes('npm run bump')) {
584
+ const separator = content.endsWith('\n') ? '' : '\n';
585
+ fs.appendFileSync(prePushPath, `${separator}\n${nvmShim}\n${bumpCmd}\n`);
586
+ }
587
+ } else {
588
+ const prePushContent = dedent`
589
+ #!/usr/bin/env sh
590
+ . "$(dirname -- "$0")/_/husky.sh"
591
+
592
+ ${nvmShim}
593
+
594
+ ${bumpCmd}
595
+ `;
596
+ fs.writeFileSync(prePushPath, prePushContent, { mode: 0o755 });
597
+ }
598
+ }
599
+ }
600
+
531
601
  const InstructionAssembler = {
532
602
  buildMasterInstructions,
533
603
  buildClaudeContent,
@@ -536,6 +606,7 @@ const InstructionAssembler = {
536
606
  writeBacklogFiles,
537
607
  writeGitignore,
538
608
  writeManifest,
609
+ writeAutomationScripts,
539
610
  };
540
611
 
541
612
  export { InstructionAssembler };
@@ -33,7 +33,7 @@ async function gatherUserSelections(targetDir = process.cwd()) {
33
33
  let scope = 'fullstack';
34
34
  let step = 0;
35
35
 
36
- const finalStep = () => (selections.mode === 'prompts' ? 2 : 8);
36
+ const finalStep = () => (selections.mode === 'prompts' ? 2 : 9);
37
37
 
38
38
  while (step < finalStep()) {
39
39
  const context = {
@@ -74,6 +74,7 @@ async function gatherUserSelections(targetDir = process.cwd()) {
74
74
  if (stepValue.ide) currentSelections.ide = stepValue.ide;
75
75
  if (stepValue.undoLastIdiom) currentSelections.idioms.pop();
76
76
  if (stepValue.resetIdioms) currentSelections.idioms = [];
77
+ if (stepValue.bump !== undefined) currentSelections.bump = stepValue.bump;
77
78
  return nextScope;
78
79
  }
79
80
  }
@@ -98,8 +99,10 @@ async function executeWizardStep(step, context) {
98
99
  return promptDesignPreset(context);
99
100
  case 7:
100
101
  return promptIdeSelection();
102
+ case 8:
103
+ return promptBumpAutomation(context);
101
104
  default: {
102
- const defaultResult = success({ nextStep: 8 });
105
+ const defaultResult = success({ nextStep: 9 });
103
106
  return defaultResult;
104
107
  }
105
108
  }
@@ -393,6 +396,24 @@ async function promptIdeSelection() {
393
396
  return ideResult;
394
397
  }
395
398
 
399
+ async function promptBumpAutomation(context) {
400
+ const { selections } = context;
401
+
402
+ const hasJsTs = selections.idioms.some((id) => id === 'javascript' || id === 'typescript');
403
+
404
+ if (!hasJsTs) {
405
+ return success({ nextStep: 9, bump: false });
406
+ }
407
+
408
+ const result = await safeConfirm({
409
+ message: 'Enable automated versioning (Bump & Changelog)?',
410
+ default: true,
411
+ });
412
+
413
+ const bumpResult = success({ nextStep: 9, bump: result });
414
+ return bumpResult;
415
+ }
416
+
396
417
  function validateSelections(selections) {
397
418
  if (selections.mode === 'quick') {
398
419
  selections.flavor = selections.flavor || 'lite';