sdg-agents 1.10.0 → 1.10.4
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.10.
|
|
3
|
+
"version": "1.10.4",
|
|
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",
|
|
@@ -61,13 +61,17 @@ If a lint script exists (`lint`, `lint:fix`, `lint:all`, or a config file is det
|
|
|
61
61
|
- If non-auto-fixable violations remain, surface them explicitly.
|
|
62
62
|
- Block commit if errors remain.
|
|
63
63
|
|
|
64
|
-
## Step 8 — COMMIT
|
|
64
|
+
## Step 8 — COMMIT & RELEASE
|
|
65
65
|
|
|
66
66
|
Propose the commit message and **WAIT** for explicit Developer approval before committing.
|
|
67
67
|
|
|
68
|
+
- **Option A (Manual)**: If you just want to save progress without a version bump, commit with `feat:` or `fix:`. Remember you **MUST** have content in `[Unreleased]`.
|
|
69
|
+
- **Option B (End Cycle)**: If the cycle is complete, run `npm run bump <fix|feat|major>` to consolidate the narrative and bump the version.
|
|
70
|
+
|
|
68
71
|
## Step 9 — PUSH
|
|
69
72
|
|
|
70
73
|
**ASK** for explicit permission before pushing to remote.
|
|
74
|
+
The `pre-push` hook will **BLOCK** the push if any `[Unreleased]` narrative remains (preventing unversioned leaks to main).
|
|
71
75
|
|
|
72
76
|
---
|
|
73
77
|
|
|
@@ -14,71 +14,82 @@ const PROJECT_ROOT = process.cwd();
|
|
|
14
14
|
const CHANGELOG_PATH = path.join(PROJECT_ROOT, 'CHANGELOG.md');
|
|
15
15
|
|
|
16
16
|
async function run() {
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
console.error(' ❌ Error: No commit message file provided.');
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
17
|
+
const isPrePush = process.argv.includes('--pre-push');
|
|
18
|
+
const commitMsgFile = isPrePush ? null : process.argv[2];
|
|
22
19
|
|
|
23
|
-
if (!
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
if (!isPrePush) {
|
|
21
|
+
if (!commitMsgFile) {
|
|
22
|
+
console.error(' ❌ Error: No commit message file provided.');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
if (!fs.existsSync(commitMsgFile)) {
|
|
26
|
+
console.error(` ❌ Error: Commit message file not found at ${commitMsgFile}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
const commitMsg = fs.readFileSync(commitMsgFile, 'utf8').trim();
|
|
31
|
+
const firstLine = commitMsg.split('\n')[0].trim();
|
|
30
32
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// ONLY target feat: and fix: (SDG Cycle Triggers) as per maintainer instruction
|
|
34
|
+
const isSdgTrigger = /^feat:/.test(firstLine) || /^fix:/.test(firstLine);
|
|
35
|
+
if (!isSdgTrigger) {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
let changelog = '';
|
|
38
41
|
try {
|
|
39
|
-
// Read STAGED version of CHANGELOG.md
|
|
42
|
+
// Read STAGED version of CHANGELOG.md (if possible)
|
|
40
43
|
changelog = execSync('git show :CHANGELOG.md', {
|
|
41
44
|
stdio: ['pipe', 'pipe', 'ignore'],
|
|
42
45
|
}).toString();
|
|
43
46
|
} catch {
|
|
44
|
-
// Fallback to disk if not in index (shouldn't happen in a commit hook for a tracked file)
|
|
45
47
|
if (fs.existsSync(CHANGELOG_PATH)) {
|
|
46
48
|
changelog = fs.readFileSync(CHANGELOG_PATH, 'utf8');
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
if (!changelog) {
|
|
51
|
-
console.warn(' ⚠️ Warning: CHANGELOG.md not found or empty. Skipping narrative check.');
|
|
52
53
|
process.exit(0);
|
|
53
54
|
}
|
|
54
55
|
|
|
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
56
|
const unreleasedMatch = changelog.match(
|
|
58
57
|
/##\s*\[Unreleased\].*?\n([\s\S]*?)(?=\n##\s|(?:\n){0,1}$)/i
|
|
59
58
|
);
|
|
60
59
|
|
|
61
60
|
if (!unreleasedMatch) {
|
|
61
|
+
if (isPrePush) process.exit(0);
|
|
62
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
63
|
process.exit(1);
|
|
65
64
|
}
|
|
66
65
|
|
|
67
66
|
const narrative = unreleasedMatch[1]
|
|
68
67
|
.replace(/###\s*(Added|Fixed|Changed|Removed|Security|Deprecated)/gi, '')
|
|
69
|
-
.replace(/<!--[\s\S]*?-->/g, '')
|
|
68
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
70
69
|
.trim();
|
|
71
70
|
|
|
72
|
-
|
|
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
|
-
}
|
|
71
|
+
const isNarrativeEmpty = !narrative || narrative.length < 3;
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
73
|
+
if (isPrePush) {
|
|
74
|
+
if (!isNarrativeEmpty) {
|
|
75
|
+
console.error('\n ❌ DELIVERY BLOCKED: Unreleased narrative detected.');
|
|
76
|
+
console.error(' You have documented changes in CHANGELOG.md that have not been versioned.');
|
|
77
|
+
console.error(
|
|
78
|
+
' Action: Run "npm run bump <fix|feat>" to finalize the cycle before pushing.\n'
|
|
79
|
+
);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
process.exit(0);
|
|
83
|
+
} else {
|
|
84
|
+
// commit-msg mode
|
|
85
|
+
if (isNarrativeEmpty) {
|
|
86
|
+
console.error('\n ❌ NARRATIVE VIOLATION: The [Unreleased] section is empty.');
|
|
87
|
+
console.error(' SDG cycles (feat/fix) MUST have a technical narrative before committing.\n');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
console.log(' ✅ Narrative Guard: CHANGELOG.md validated.');
|
|
91
|
+
process.exit(0);
|
|
92
|
+
}
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
run().catch((err) => {
|
|
@@ -88,6 +88,25 @@ function runIfDirect(importMetaUrl, fn) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
function detectIndentation(content) {
|
|
92
|
+
const lines = content.split('\n');
|
|
93
|
+
for (const line of lines) {
|
|
94
|
+
const match = line.match(/^(\s+)/);
|
|
95
|
+
if (match) return match[1];
|
|
96
|
+
}
|
|
97
|
+
return ' ';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function writeJsonAtomic(filePath, data, originalContent = null) {
|
|
101
|
+
const indent = originalContent ? detectIndentation(originalContent) : ' ';
|
|
102
|
+
const newContent = JSON.stringify(data, null, indent) + '\n';
|
|
103
|
+
|
|
104
|
+
if (originalContent === newContent) return false;
|
|
105
|
+
|
|
106
|
+
fs.writeFileSync(filePath, newContent);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
91
110
|
const FsUtils = {
|
|
92
111
|
getDirectories,
|
|
93
112
|
copyRecursiveSync,
|
|
@@ -96,6 +115,8 @@ const FsUtils = {
|
|
|
96
115
|
evaluateVersionCondition,
|
|
97
116
|
getDirname,
|
|
98
117
|
runIfDirect,
|
|
118
|
+
detectIndentation,
|
|
119
|
+
writeJsonAtomic,
|
|
99
120
|
};
|
|
100
121
|
|
|
101
122
|
export { FsUtils };
|
|
@@ -14,7 +14,7 @@ import { FsUtils } from './fs-utils.mjs';
|
|
|
14
14
|
|
|
15
15
|
const { displayName } = DisplayUtils;
|
|
16
16
|
const { computeHashes } = ManifestUtils;
|
|
17
|
-
const { getDirname } = FsUtils;
|
|
17
|
+
const { getDirname, writeJsonAtomic } = FsUtils;
|
|
18
18
|
|
|
19
19
|
const __dirname = getDirname(import.meta.url);
|
|
20
20
|
const SOURCE_INSTRUCTIONS = path.join(__dirname, '..', '..', 'assets', 'instructions');
|
|
@@ -463,6 +463,9 @@ function writeAgentConfig(targetDir, content, requestedAgents = []) {
|
|
|
463
463
|
const fullDir = path.join(targetDir, target.dir);
|
|
464
464
|
if (!fs.existsSync(fullDir)) fs.mkdirSync(fullDir, { recursive: true });
|
|
465
465
|
|
|
466
|
+
const targetFile = path.join(fullDir, target.file);
|
|
467
|
+
const originalContent = fs.existsSync(targetFile) ? fs.readFileSync(targetFile, 'utf8') : null;
|
|
468
|
+
|
|
466
469
|
let finalContent = content;
|
|
467
470
|
if (agent === 'cursor') {
|
|
468
471
|
finalContent = `---\ndescription: Project Governance and Architectural Rules\nglob: *\n---\n\n${content}`;
|
|
@@ -470,7 +473,9 @@ function writeAgentConfig(targetDir, content, requestedAgents = []) {
|
|
|
470
473
|
finalContent = buildClaudeContent();
|
|
471
474
|
}
|
|
472
475
|
|
|
473
|
-
|
|
476
|
+
if (originalContent !== finalContent) {
|
|
477
|
+
fs.writeFileSync(targetFile, finalContent);
|
|
478
|
+
}
|
|
474
479
|
}
|
|
475
480
|
}
|
|
476
481
|
|
|
@@ -525,7 +530,13 @@ function writeManifest(targetDir, selections, pkgVersion) {
|
|
|
525
530
|
|
|
526
531
|
const aiDir = path.join(targetDir, '.ai');
|
|
527
532
|
if (!fs.existsSync(aiDir)) fs.mkdirSync(aiDir, { recursive: true });
|
|
528
|
-
|
|
533
|
+
|
|
534
|
+
const manifestPath = path.join(aiDir, '.sdg-manifest.json');
|
|
535
|
+
const originalContent = fs.existsSync(manifestPath)
|
|
536
|
+
? fs.readFileSync(manifestPath, 'utf8')
|
|
537
|
+
: null;
|
|
538
|
+
|
|
539
|
+
writeJsonAtomic(manifestPath, manifest, originalContent);
|
|
529
540
|
}
|
|
530
541
|
|
|
531
542
|
/**
|
|
@@ -563,7 +574,7 @@ function writeAutomationScripts(targetDir, selections) {
|
|
|
563
574
|
if (!pkg.scripts) pkg.scripts = {};
|
|
564
575
|
if (!pkg.scripts.bump) {
|
|
565
576
|
pkg.scripts.bump = 'node scripts/bump.mjs';
|
|
566
|
-
|
|
577
|
+
writeJsonAtomic(pkgPath, pkg, fs.readFileSync(pkgPath, 'utf8'));
|
|
567
578
|
}
|
|
568
579
|
|
|
569
580
|
// 4. Configure Husky if .husky exists
|
|
@@ -580,9 +591,10 @@ function writeAutomationScripts(targetDir, selections) {
|
|
|
580
591
|
|
|
581
592
|
if (fs.existsSync(prePushPath)) {
|
|
582
593
|
const content = fs.readFileSync(prePushPath, 'utf8');
|
|
583
|
-
if (!content.includes('npm
|
|
594
|
+
if (!content.includes('npm test')) {
|
|
584
595
|
const separator = content.endsWith('\n') ? '' : '\n';
|
|
585
|
-
|
|
596
|
+
const newPrePushContent = `${content}${separator}\n${nvmShim}\n${bumpCmd}\n`;
|
|
597
|
+
fs.writeFileSync(prePushPath, newPrePushContent);
|
|
586
598
|
}
|
|
587
599
|
} else {
|
|
588
600
|
const prePushContent = dedent`
|