sdg-agents 1.4.0 → 1.10.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.4.0",
3
+ "version": "1.10.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",
@@ -12,20 +12,19 @@ Write one sentence per completed PLAN task. If no PLAN existed (e.g. `[S]` tasks
12
12
 
13
13
  ## Step 2 — CHANGELOG
14
14
 
15
- - Append an entry under `## [Unreleased]` in `CHANGELOG.md`:
16
-
17
- 1. **Identify Version**: Check `package.json` (or equivalent) for the current version.
18
- 2. **Calculate Next**: Determine the next version (patch/minor) based on the cycle type (`feat` minor/patch, `fix` patch).
19
- 3. **Update Header**:
20
- - If the project uses auto-bump or a release is intended: Create/Update the header to `## [NEXT_VERSION] - YYYY-MM-DD`.
21
- - Otherwise: Append under `## [Unreleased]`.
22
- 4. **Append Entry**:
23
- - `feat:` cycle → `### Added`
24
- - `fix:` cycle → `### Fixed`
25
- - `docs:` cycle → skip this step
26
- - `land:` cycle → skip this step
27
-
28
- If `## [Unreleased]` (or the appropriate version header) does not exist, create it above the most recent entry.
15
+ Prepare your technical narrative in the `CHANGELOG.md`.
16
+
17
+ 1. **Identify Strategy**:
18
+ - **Automated**: If `auto-bump.mjs` exists, you **MUST** first manually populate the `## [Unreleased]` section with your technical narrative. The automated pipeline will promote this content to the new version header during the commit. **DO NOT commit with an empty [Unreleased] section.**
19
+ - **Manual**: If no automation is present, you must promote the header manually (e.g., `## [1.2.0] - 2026-04-11`) or run the designated `bump` script.
20
+
21
+ 2. **Append Entry**:
22
+ - `feat:` cycle → Add under `### Added`
23
+ - `fix:` cycle → Add under `### Fixed`
24
+ - `docs:` cycle → Optional (for major architectural docs)
25
+ - `land:` cycle → Skip
26
+
27
+ 3. **Verify Header**: Ensure `## [Unreleased]` exists at the top. If missing, create it above the most recent version entry.
29
28
 
30
29
  ## Step 3 — BACKLOG: tasks.md
31
30
 
@@ -1,82 +1,45 @@
1
- # Writing Soul — Professional Authority Standards
1
+ # The Writing Soul
2
2
 
3
- > [!IMPORTANT]
4
- > **GOVERNANCE MANDATE**
5
- > This specification applies to all **non-code writing tasks**, including READMEs, Guides, UI Content, CHANGELOGs, and Commit Messages.
6
- > The goal is to eliminate "AI Slop" and restore **Developer intentionality**, density, and soul to every project artifact.
3
+ Good technical writing starts with the realization that there is a person on both sides of the screen. We believe that software documentation is a shared conversation, and maintaining a sense of mind behind the words is essential for effective communication. Sterile or voiceless text often feels like a missed opportunity to connect and share real engineering wisdom. These standards apply to all non-code writing tasks, including READMEs, guides, UI content, and commit messages.
7
4
 
8
- ## 1. Personality and Soul
5
+ ## Cultivating Personality
9
6
 
10
- Good technical writing has a **mind** behind it. Sterile, voiceless writing is as obvious as low-density slop.
7
+ Effective writing reflects the natural rhythms of how we think and solve problems. You can bring a sense of purpose to your text by offering a clear perspective on the facts you present. Mixing brief observations with longer, more detailed explanations helps keep the reader engaged.
11
8
 
12
- - **Have opinions**: Don't just report facts react to them.
13
- - **Vary your rhythm**: Mix short, punchy sentences with longer, flowing ones.
14
- - **Acknowledge complexity**: Real professionals have mixed feelings and uncertainty.
15
- - **Let some mess in**: Perfectly algorithmic structure feels fake. Tangents and asides add pulse.
9
+ Authenticity also comes from acknowledging that engineering is complex. Professionals often navigate uncertainty or hold nuanced views on a topic, and reflecting those feelings makes your writing more reliable. Allowing space for a well-placed aside or a brief tangent can create the pulse that makes a technical guide feel alive and uniquely human.
16
10
 
17
- ---
18
-
19
- ## 2. Content Patterns to Eliminate
20
-
21
- ### I. Significance Inflation
11
+ ## Seeking Authenticity
22
12
 
23
- **Banned phrases**: "testament/reminder to", "pivotal moment", "is a testament/reminder", "evolving landscape", "underscores its significance".
24
- **Fix**: Remove the "puffery". Just state the facts and their direct consequences.
13
+ Maintaining a professional and grounded tone involves recognizing patterns that sometimes cloud the clarity of our message. We can improve our writing by focusing on direct observations rather than relying on stylistic crutches.
25
14
 
26
- ### II. Superficial -ing Endings
15
+ ### Natural Expression
27
16
 
28
- **Patterns**: "...highlighting X", "...ensuring Y", "...reflecting Z".
29
- **Fix**: Break into clear, active clauses. Avoid tacking on "fake depth" via present participle phrases.
17
+ True authority often speaks for itself. We can avoid significance inflation by stating facts and their direct consequences, allowing the quality of the work to be the primary focus. Using phrases like "testament to" or "pivotal moment" often adds unnecessary weight where a simple description of the impact would be more effective.
30
18
 
31
- ### III. Promotional & Sycophantic Tone
19
+ ### Active Clarity
32
20
 
33
- **Banned adjectives**: "vibrant", "rich", "groundbreaking", "breathtaking", "seamless".
34
- **Banned artifacts**: "Great question!", "I hope this helps!", "Certainly!".
35
- **Fix**: Maintain a professional, peer-to-level tone. No sales pitch, no servility.
21
+ Direct communication is usually the most helpful for the reader. We can sharpen our message by breaking complex ideas into clear, active clauses. While present participle phrases ending in "-ing" are common, they sometimes obscure the relationship between actions. Choosing active verbs helps ensure the reader understands the exact flow of information or logic.
36
22
 
37
- ### IV. Generic Positive Conclusions
38
-
39
- **Patterns**: "The future looks bright", "Exciting times lie ahead".
40
- **Fix**: End with a concrete next step or a specific fact about future plans.
41
-
42
- ---
23
+ ### Professional Peerage
43
24
 
44
- ## 3. Language & Style Hardening
25
+ We value a tone that respects the expertise of the reader. A calm and peer-to-level approach builds trust more effectively than using promotional adjectives like "vibrant" or "groundbreaking." This same principle applies to our interactions, where a professional and direct response carries more weight than conversational fillers or overly decorative enthusiasm.
45
26
 
46
- ### I. Copula Avoidance
27
+ ## Language and Style Practices
47
28
 
48
- **Stop**: "serves as", "stands as", "represents [a]".
49
- **Use**: "is", "are", "has". Simple is more direct.
29
+ The way we structure our language influences how easily a reader can follow our logic. We prefer simple and direct verbs to keep the focus on the content. Words like "is," "are," and "has" are often more effective than complex alternatives that can slow down the reading experience.
50
30
 
51
- ### II. Em Dash & Boldface Overuse
31
+ ### Visual Serenity
52
32
 
53
- **Rule**: Em dashes (—) are banned. No exceptions. AI uses them constantly to fake rhetorical punch.
54
- **Fix**: Rewrite with a comma, a period, or a new sentence. If it needs a pause, earn it with structure.
55
- Bold is for true technical emphasis only: a term, a command, a key constraint. Not decoration.
56
-
57
- ### III. Title Case & Emojis
58
-
59
- **Rule**: No Title Case for everything. Emojis are reserved for **pattern signaling only** — marking BAD/GOOD examples, callouts, or explicit visual cues in documentation.
60
- **Banned**: Decorative emojis in section headers, link anchors, or inline prose (e.g. `## 🚀 Quick Start`, `✨ Try the app`).
61
- **Allowed**: Signal emojis that carry semantic meaning in structured examples:
62
-
63
- - `BAD`: `❌ if (data == null)` / `GOOD`: `✅ if (!data)`
64
- - `[!WARNING]`, `[!TIP]`, `[!NOTE]` callouts (GitHub-native, not emoji)
65
- **Fix**: Use Sentence case for headings. Strip all decorative emoji. If a header needs visual weight, use formatting — not icons.
66
-
67
- ---
33
+ Structure and rhythm should guide the reader through the text naturally. We find that avoiding em dashes encourages a more thoughtful sentence structure, as it requires earning each pause through careful composition. Similarly, we use bold formatting exclusively for technical emphasis, such as specific terms, commands, or key constraints.
68
34
 
69
- ## 4. The Authority Loop
35
+ Our approach to formatting also prioritizes clarity. Headings follow sentence case to maintain a serene and professional appearance. We use emojis only when they carry semantic meaning for pattern signaling, such as marking successful or unsuccessful examples in a technical guide. This keeps the visual environment focused on the information being shared.
70
36
 
71
- Before finalizing any content, perform a self-audit:
37
+ ## The Refinement Process
72
38
 
73
- 1. **Identify Patterns**: Scan for the AI-isms listed above.
74
- 2. **Rewrite**: Replace slop with direct, active voice.
75
- 3. **Add Soul**: Inject a specific observation or a varied rhythm.
76
- 4. **Final Check**: Ask: _"What makes this so obviously AI-generated?"_ Fix the remaining tells.
39
+ Before sharing your work, a brief moment of reflection can help ensure the content reflects your intention. You might find it helpful to look for familiar patterns that feel more like general summaries than specific observations. Replacing these with active, direct language often makes the writing feel more genuine. Adding a unique insight or varying the length of your sentences can provide the final touch that makes the text feel truly yours.
77
40
 
78
41
  ---
79
42
 
80
- ## Reference & Credits
43
+ ## Reference and Credits
81
44
 
82
- This specification is based on the **Wikipedia:Signs of AI writing** project, maintained by **WikiProject AI Cleanup**. It serves as the foundational standard for eliminating statistical slop and restoring **Developer intentionality** to generated text.
45
+ This standard is inspired by the collective efforts of the project to eliminate statistical slop and restore intentionality to generated text. It serves as a foundation for building clear, purposeful, and human-centered documentation across the entire ecosystem.
@@ -63,7 +63,6 @@ function updateChangelog(newVersion) {
63
63
  const today = new Date().toISOString().split('T')[0];
64
64
 
65
65
  // Pattern to find the [Unreleased] section
66
- // It handles both formats: ## [Unreleased] and ## [Unreleased] - YYYY-MM-DD
67
66
  const unreleasedRegex = /##\s*\[Unreleased\](\s*-\s*\d{4}-\d{2}-\d{2})?/i;
68
67
 
69
68
  if (!unreleasedRegex.test(content)) {
@@ -12,6 +12,7 @@ const { runIfDirect } = FsUtils;
12
12
  // bin/ → engine/ → src/ → root
13
13
  const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../');
14
14
  const PACKAGE_PATHS = [path.join(ROOT_DIR, 'package.json')];
15
+ const CHANGELOG_PATH = path.join(ROOT_DIR, 'CHANGELOG.md');
15
16
 
16
17
  // --- Orchestrator ---
17
18
 
@@ -27,7 +28,13 @@ async function run() {
27
28
  const rootPkg = readPackageJson(resolveRootPackagePath());
28
29
  const nextVersion = bumpVersion(rootPkg.version, bumpType);
29
30
 
31
+ // 1. Promote Changelog
32
+ updateChangelog(nextVersion);
33
+
34
+ // 2. Sync Package.json files
30
35
  syncAllPackages(nextVersion);
36
+
37
+ // 3. Commit the bump
31
38
  stageAndCommit(nextVersion);
32
39
 
33
40
  console.log(` auto-bump: ${rootPkg.version} → ${nextVersion} (${bumpType})`);
@@ -57,9 +64,37 @@ function bumpVersion(current, bumpType) {
57
64
  return `${major}.${minor + 1}.0`;
58
65
  case 'patch':
59
66
  return `${major}.${minor}.${patch + 1}`;
67
+ default:
68
+ return current;
60
69
  }
61
70
  }
62
71
 
72
+ function updateChangelog(newVersion) {
73
+ if (!fs.existsSync(CHANGELOG_PATH)) return;
74
+
75
+ const content = fs.readFileSync(CHANGELOG_PATH, 'utf8');
76
+ const today = new Date().toISOString().split('T')[0];
77
+
78
+ // Pattern to find the [Unreleased] section
79
+ const unreleasedRegex = /##\s*\[Unreleased\](\s*-\s*\d{4}-\d{2}-\d{2})?/i;
80
+
81
+ if (!unreleasedRegex.test(content)) return;
82
+
83
+ const newHeader = `## [${newVersion}] - ${today}`;
84
+
85
+ // 1. Promote Unreleased to New Version
86
+ let updatedContent = content.replace(unreleasedRegex, newHeader);
87
+
88
+ // 2. Inject new [Unreleased] block at the top if it was promoted
89
+ const insertIndex = updatedContent.indexOf(newHeader);
90
+ const nextBlock = `## [Unreleased]\n\n### Added\n\n### Fixed\n\n`;
91
+
92
+ updatedContent =
93
+ updatedContent.slice(0, insertIndex) + nextBlock + updatedContent.slice(insertIndex);
94
+
95
+ fs.writeFileSync(CHANGELOG_PATH, updatedContent);
96
+ }
97
+
63
98
  // --- Sync & Commit ---
64
99
 
65
100
  function syncAllPackages(nextVersion) {
@@ -71,7 +106,8 @@ function syncAllPackages(nextVersion) {
71
106
 
72
107
  function stageAndCommit(nextVersion) {
73
108
  const paths = resolvePackagePaths();
74
- const files = paths.filter((p) => fs.existsSync(p)).join(' ');
109
+ const files = [...paths, CHANGELOG_PATH].filter((p) => fs.existsSync(p)).join(' ');
110
+
75
111
  execSync(`git add ${files}`, { stdio: 'inherit' });
76
112
  execSync(`git commit -m "chore: bump version to ${nextVersion}"`, { stdio: 'inherit' });
77
113
  }
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Narrative Guard — Blocks feat: and fix: commits if CHANGELOG.md [Unreleased] is empty.
5
+ * Ensures the auto-bump pipeline always has content to promote.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ import { execSync } from 'node:child_process';
12
+
13
+ const PROJECT_ROOT = process.cwd();
14
+ const CHANGELOG_PATH = path.join(PROJECT_ROOT, 'CHANGELOG.md');
15
+
16
+ async function run() {
17
+ const commitMsgFile = process.argv[2];
18
+ if (!commitMsgFile) {
19
+ console.error(' ❌ Error: No commit message file provided.');
20
+ process.exit(1);
21
+ }
22
+
23
+ if (!fs.existsSync(commitMsgFile)) {
24
+ console.error(` ❌ Error: Commit message file not found at ${commitMsgFile}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ const commitMsg = fs.readFileSync(commitMsgFile, 'utf8').trim();
29
+ const firstLine = commitMsg.split('\n')[0].trim();
30
+
31
+ // ONLY target feat: and fix: (SDG Cycle Triggers) as per maintainer instruction
32
+ const isSdgTrigger = /^feat:/.test(firstLine) || /^fix:/.test(firstLine);
33
+ if (!isSdgTrigger) {
34
+ process.exit(0);
35
+ }
36
+
37
+ let changelog = '';
38
+ try {
39
+ // Read STAGED version of CHANGELOG.md to prevent issues with lint-staged or stashing
40
+ changelog = execSync('git show :CHANGELOG.md', {
41
+ stdio: ['pipe', 'pipe', 'ignore'],
42
+ }).toString();
43
+ } catch {
44
+ // Fallback to disk if not in index (shouldn't happen in a commit hook for a tracked file)
45
+ if (fs.existsSync(CHANGELOG_PATH)) {
46
+ changelog = fs.readFileSync(CHANGELOG_PATH, 'utf8');
47
+ }
48
+ }
49
+
50
+ if (!changelog) {
51
+ console.warn(' ⚠️ Warning: CHANGELOG.md not found or empty. Skipping narrative check.');
52
+ process.exit(0);
53
+ }
54
+
55
+ // Extract content between [Unreleased] and the next version header (## [...) or end of file
56
+ // We use (?=\n##\s) to match the next level-2 header specifically, avoiding matches on ### sub-headers.
57
+ const unreleasedMatch = changelog.match(
58
+ /##\s*\[Unreleased\].*?\n([\s\S]*?)(?=\n##\s|(?:\n){0,1}$)/i
59
+ );
60
+
61
+ if (!unreleasedMatch) {
62
+ console.error('\n ❌ NARRATIVE VIOLATION: "## [Unreleased]" section missing in CHANGELOG.md.');
63
+ console.error(' SDG cycles (feat/fix) MUST have a technical narrative before committing.\n');
64
+ process.exit(1);
65
+ }
66
+
67
+ const narrative = unreleasedMatch[1]
68
+ .replace(/###\s*(Added|Fixed|Changed|Removed|Security|Deprecated)/gi, '')
69
+ .replace(/<!--[\s\S]*?-->/g, '') // Remove markdown comments
70
+ .trim();
71
+
72
+ // If the narrative is just whitespace or empty after removing headers/comments
73
+ if (!narrative || narrative.length < 3) {
74
+ console.error('\n ❌ NARRATIVE VIOLATION: The [Unreleased] section is empty.');
75
+ console.error(' You are committing a "feat:" or "fix:" which triggers a version bump.');
76
+ console.error(' Please document your changes in CHANGELOG.md under [Unreleased] first.\n');
77
+ process.exit(1);
78
+ }
79
+
80
+ console.log(' ✅ Narrative Guard: CHANGELOG.md validated.');
81
+ process.exit(0);
82
+ }
83
+
84
+ run().catch((err) => {
85
+ console.error(' ❌ Narrative Guard Exception:', err.message);
86
+ process.exit(1);
87
+ });
88
+ // test
@@ -13,13 +13,12 @@ import crypto from 'node:crypto';
13
13
  import { FsUtils } from '../lib/fs-utils.mjs';
14
14
  import { ResultUtils } from '../lib/result-utils.mjs';
15
15
 
16
- const { getDirname, runIfDirect } = FsUtils;
16
+ const { runIfDirect } = FsUtils;
17
17
  const { success, fail } = ResultUtils;
18
18
 
19
- const __dirname = getDirname(import.meta.url);
20
- const MONOREPO_ROOT = path.join(__dirname, '..', '..', '..', '..', '..');
21
- const ASSETS_DIR = path.join(MONOREPO_ROOT, 'packages', 'cli', 'src', 'assets', 'instructions');
22
- const AI_DIR = path.join(MONOREPO_ROOT, 'packages', 'cli', '.ai', 'instructions');
19
+ const PROJECT_ROOT = process.cwd();
20
+ const ASSETS_DIR = path.join(PROJECT_ROOT, 'src', 'assets', 'instructions');
21
+ const AI_DIR = path.join(PROJECT_ROOT, '.ai', 'instructions');
23
22
 
24
23
  // why: flavor/ in .ai/ is populated by renaming flavors/{id}/ — no direct mirror exists in src/assets/flavor/
25
24
  const MIRRORED_DIRS = ['core', 'idioms', 'templates', 'competencies'];
@@ -39,6 +39,11 @@ async function run() {
39
39
  // Resolve target directory early for the entire cycle
40
40
  args.targetDir = path.resolve(args.targetDir || process.cwd());
41
41
 
42
+ // Maintainer Protocol: ensuring core instructions are synced to .ai/ for the agent
43
+ if (!args.subcommand && !args.help && !args.version) {
44
+ await ensureMaintainerSync(args.targetDir);
45
+ }
46
+
42
47
  if (args.subcommand) {
43
48
  await executeSubcommand(args);
44
49
  } else {
@@ -210,6 +215,35 @@ async function runSettingsMenu(targetDir) {
210
215
 
211
216
  // --- System ---
212
217
 
218
+ async function ensureMaintainerSync(targetDir) {
219
+ const { isMaintainerMode } = PromptUtils;
220
+ if (!isMaintainerMode()) return;
221
+
222
+ const { SyncChecker } = await import('./check-sync.mjs');
223
+ const syncResult = SyncChecker.run();
224
+
225
+ if (syncResult.isFailure) {
226
+ console.log('\n 🛠️ MAINTAINER MODE: Drift detected in core instructions.');
227
+ console.log(' 🔄 Automatic sync in progress...\n');
228
+
229
+ const { ManifestUtils } = await import('../lib/manifest-utils.mjs');
230
+ const { SDG } = await import('./build-bundle.mjs');
231
+ const manifest = ManifestUtils.loadManifest(targetDir);
232
+
233
+ if (manifest) {
234
+ try {
235
+ await SDG.run(targetDir, { selections: manifest.selections });
236
+ console.log('\n ✅ Core instructions synchronized. Agent rules are up-to-date.\n');
237
+ console.log('─'.repeat(50) + '\n');
238
+ } catch (error) {
239
+ console.log(`\n ⚠️ Automatic sync failed: ${error.message}\n`);
240
+ }
241
+ } else {
242
+ console.log(' ⚠️ Cannot auto-sync: No manifest found. Run "init" once.\n');
243
+ }
244
+ }
245
+ }
246
+
213
247
  function handleExitError(error) {
214
248
  if (error.message?.includes('force closed') || error.name === 'ExitPromptError') {
215
249
  console.log('\n\n 👋 Goodbye! See you soon engineer.');
@@ -573,10 +573,10 @@ function writeAutomationScripts(targetDir, selections) {
573
573
  const nvmShim = dedent`
574
574
  # NVM Shim (Essential for projects Staff in Linux/NVM)
575
575
  export NVM_DIR="$HOME/.nvm"
576
- [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"
576
+ [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
577
577
  `;
578
578
 
579
- const bumpCmd = 'npm run bump fix';
579
+ const bumpCmd = '# Pre-push check (non-mutating)\nnpm test';
580
580
 
581
581
  if (fs.existsSync(prePushPath)) {
582
582
  const content = fs.readFileSync(prePushPath, 'utf8');
@@ -587,7 +587,6 @@ function writeAutomationScripts(targetDir, selections) {
587
587
  } else {
588
588
  const prePushContent = dedent`
589
589
  #!/usr/bin/env sh
590
- . "$(dirname -- "$0")/_/husky.sh"
591
590
 
592
591
  ${nvmShim}
593
592