@tekyzinc/gsd-t 5.10.10 → 5.10.11
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/README.md +1 -1
- package/bin/gsd-t-claude-md-changelog.cjs +360 -0
- package/bin/gsd-t.js +2 -0
- package/commands/gsd-t-init.md +13 -0
- package/commands/gsd-t-setup.md +10 -0
- package/package.json +1 -1
- package/scripts/gsd-t-changelog-check.js +67 -0
- package/scripts/gsd-t-changelog-post-edit.js +73 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GSD-T: Contract-Driven Development for Claude Code
|
|
2
2
|
|
|
3
|
-
**v5.10.
|
|
3
|
+
**v5.10.11** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
|
|
4
4
|
|
|
5
5
|
**Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
|
|
6
6
|
**Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gsd-t-claude-md-changelog.cjs
|
|
4
|
+
*
|
|
5
|
+
* M110: CLAUDE.md changelog tracking.
|
|
6
|
+
* Maintains an append-only changelog for CLAUDE.md mutations.
|
|
7
|
+
*
|
|
8
|
+
* Commands:
|
|
9
|
+
* scaffold <projectDir> Create changelog file if missing
|
|
10
|
+
* append <projectDir> <type> [desc] Append an entry (rewritten|updated|external)
|
|
11
|
+
* check-external <projectDir> Detect external updates (mtime > last entry)
|
|
12
|
+
* get-last-entry-time <projectDir> Return ISO timestamp of last entry (for hooks)
|
|
13
|
+
*
|
|
14
|
+
* Entry format:
|
|
15
|
+
* **[Rewritten|Updated|External update detected] {date} {time} | {model} | GSD-T v{gsdtVersion} | project v{projectVersion}**
|
|
16
|
+
* - bullet 1 (for Updated only)
|
|
17
|
+
* - bullet 2
|
|
18
|
+
*
|
|
19
|
+
* Exit codes:
|
|
20
|
+
* 0 — success / up-to-date
|
|
21
|
+
* 1 — external update detected (check-external only)
|
|
22
|
+
* 2 — corrupt changelog (HALT)
|
|
23
|
+
* 64 — bad input
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const CHANGELOG_FILENAME = 'CLAUDE.md updates.md';
|
|
30
|
+
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
// Helpers
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function getChangelogPath(projectDir) {
|
|
36
|
+
return path.join(projectDir, CHANGELOG_FILENAME);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getClaudeMdPath(projectDir) {
|
|
40
|
+
const rootPath = path.join(projectDir, 'CLAUDE.md');
|
|
41
|
+
const dotClaudePath = path.join(projectDir, '.claude', 'CLAUDE.md');
|
|
42
|
+
if (fs.existsSync(rootPath)) return rootPath;
|
|
43
|
+
if (fs.existsSync(dotClaudePath)) return dotClaudePath;
|
|
44
|
+
return rootPath;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function halt(message, exitCode) {
|
|
48
|
+
console.error(JSON.stringify({ error: message }));
|
|
49
|
+
process.exit(exitCode);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getGsdtVersion() {
|
|
53
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
54
|
+
if (!fs.existsSync(pkgPath)) {
|
|
55
|
+
halt('GSD-T package.json not found: ' + pkgPath, 64);
|
|
56
|
+
}
|
|
57
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
58
|
+
if (!pkg.version) {
|
|
59
|
+
halt('GSD-T package.json missing version field', 64);
|
|
60
|
+
}
|
|
61
|
+
return pkg.version;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getProjectVersion(projectDir) {
|
|
65
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
66
|
+
if (fs.existsSync(pkgPath)) {
|
|
67
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
68
|
+
if (pkg.version) return pkg.version;
|
|
69
|
+
}
|
|
70
|
+
const pyprojectPath = path.join(projectDir, 'pyproject.toml');
|
|
71
|
+
if (fs.existsSync(pyprojectPath)) {
|
|
72
|
+
const content = fs.readFileSync(pyprojectPath, 'utf8');
|
|
73
|
+
const match = content.match(/version\s*=\s*["']([^"']+)["']/);
|
|
74
|
+
if (match) return match[1];
|
|
75
|
+
}
|
|
76
|
+
return 'unversioned';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getClaudeModel() {
|
|
80
|
+
const model = process.env.CLAUDE_MODEL || process.env.MODEL;
|
|
81
|
+
if (!model) return 'Claude';
|
|
82
|
+
if (model.includes('opus-5') || model.includes('opus5')) return 'Claude-Opus-5';
|
|
83
|
+
if (model.includes('opus-4.5') || model.includes('opus4.5')) return 'Claude-Opus-4.5';
|
|
84
|
+
if (model.includes('opus')) return 'Claude-Opus';
|
|
85
|
+
if (model.includes('sonnet-5') || model.includes('sonnet5')) return 'Claude-Sonnet-5';
|
|
86
|
+
if (model.includes('sonnet')) return 'Claude-Sonnet';
|
|
87
|
+
if (model.includes('haiku')) return 'Claude-Haiku';
|
|
88
|
+
return model;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatDate(date) {
|
|
92
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
93
|
+
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
94
|
+
const month = months[date.getMonth()];
|
|
95
|
+
const day = date.getDate();
|
|
96
|
+
const year = date.getFullYear();
|
|
97
|
+
let hours = date.getHours();
|
|
98
|
+
const ampm = hours >= 12 ? 'pm' : 'am';
|
|
99
|
+
hours = hours % 12 || 12;
|
|
100
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
101
|
+
return `${month} ${day}, ${year} ${hours}:${minutes}${ampm}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse the timestamp of the last entry in the changelog.
|
|
106
|
+
*
|
|
107
|
+
* @returns {Date | null} Date of last entry, or null if no file/entries
|
|
108
|
+
* @throws Calls halt() and exits if changelog is corrupt
|
|
109
|
+
*/
|
|
110
|
+
function parseLastEntryTime(changelogPath) {
|
|
111
|
+
if (!fs.existsSync(changelogPath)) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const content = fs.readFileSync(changelogPath, 'utf8');
|
|
116
|
+
|
|
117
|
+
const hasHeader = content.includes('# CLAUDE.md Change Log');
|
|
118
|
+
const hasEntries = content.includes('**Rewritten') ||
|
|
119
|
+
content.includes('**Updated') ||
|
|
120
|
+
content.includes('**External update detected');
|
|
121
|
+
|
|
122
|
+
if (hasHeader && !hasEntries) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Match entries with optional ~ before time (e.g., "~3:00pm" or "3:00pm")
|
|
127
|
+
const entryPattern = /\*\*(?:Rewritten|Updated|External update detected)\s+(\w+\s+\d+,\s+\d{4}\s+~?\d{1,2}:\d{2}(?:am|pm))/gi;
|
|
128
|
+
|
|
129
|
+
let lastMatch = null;
|
|
130
|
+
let match;
|
|
131
|
+
while ((match = entryPattern.exec(content)) !== null) {
|
|
132
|
+
lastMatch = match[1];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!lastMatch) {
|
|
136
|
+
halt('Changelog file is corrupt — has content but no parseable entries: ' + changelogPath, 2);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Remove optional ~ and parse the date
|
|
140
|
+
const parsed = new Date(lastMatch.replace(/~/, '').replace(/(\d{1,2}:\d{2})(am|pm)/i, (_, time, ampm) => {
|
|
141
|
+
const [h, m] = time.split(':');
|
|
142
|
+
let hour = parseInt(h, 10);
|
|
143
|
+
if (ampm.toLowerCase() === 'pm' && hour !== 12) hour += 12;
|
|
144
|
+
if (ampm.toLowerCase() === 'am' && hour === 12) hour = 0;
|
|
145
|
+
return `${hour.toString().padStart(2, '0')}:${m}`;
|
|
146
|
+
}));
|
|
147
|
+
|
|
148
|
+
if (isNaN(parsed.getTime())) {
|
|
149
|
+
halt('Changelog file has unparseable date: ' + changelogPath, 2);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return parsed;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
156
|
+
// Commands
|
|
157
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
function scaffold(projectDir) {
|
|
160
|
+
const changelogPath = getChangelogPath(projectDir);
|
|
161
|
+
|
|
162
|
+
if (fs.existsSync(changelogPath)) {
|
|
163
|
+
console.log(JSON.stringify({ status: 'exists', path: changelogPath }));
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const content = `# CLAUDE.md Change Log
|
|
168
|
+
|
|
169
|
+
Append-only record of CLAUDE.md rewrites and updates.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
fs.writeFileSync(changelogPath, content, 'utf8');
|
|
176
|
+
console.log(JSON.stringify({ status: 'created', path: changelogPath }));
|
|
177
|
+
process.exit(0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function append(projectDir, type, description, bullets) {
|
|
181
|
+
const changelogPath = getChangelogPath(projectDir);
|
|
182
|
+
|
|
183
|
+
if (!fs.existsSync(changelogPath)) {
|
|
184
|
+
const content = `# CLAUDE.md Change Log
|
|
185
|
+
|
|
186
|
+
Append-only record of CLAUDE.md rewrites and updates.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
`;
|
|
191
|
+
fs.writeFileSync(changelogPath, content, 'utf8');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const now = new Date();
|
|
195
|
+
const dateStr = formatDate(now);
|
|
196
|
+
const model = getClaudeModel();
|
|
197
|
+
const gsdtVersion = getGsdtVersion();
|
|
198
|
+
const projectVersion = getProjectVersion(projectDir);
|
|
199
|
+
|
|
200
|
+
let typeLabel;
|
|
201
|
+
switch (type.toLowerCase()) {
|
|
202
|
+
case 'rewritten':
|
|
203
|
+
case 'rewrite':
|
|
204
|
+
typeLabel = 'Rewritten';
|
|
205
|
+
break;
|
|
206
|
+
case 'updated':
|
|
207
|
+
case 'update':
|
|
208
|
+
typeLabel = 'Updated';
|
|
209
|
+
break;
|
|
210
|
+
case 'external':
|
|
211
|
+
typeLabel = 'External update detected';
|
|
212
|
+
break;
|
|
213
|
+
default:
|
|
214
|
+
halt('Unknown type: ' + type + '. Use: rewritten, updated, external', 64);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let entry = `\n**${typeLabel} ${dateStr} | ${model} | GSD-T v${gsdtVersion} | project v${projectVersion}**\n`;
|
|
218
|
+
|
|
219
|
+
if (description && typeLabel === 'Rewritten') {
|
|
220
|
+
entry += `\n${description}\n`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (bullets && bullets.length > 0 && typeLabel === 'Updated') {
|
|
224
|
+
entry += '\n';
|
|
225
|
+
for (const bullet of bullets) {
|
|
226
|
+
entry += `- ${bullet}\n`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
fs.appendFileSync(changelogPath, entry, 'utf8');
|
|
231
|
+
console.log(JSON.stringify({
|
|
232
|
+
status: 'appended',
|
|
233
|
+
type: typeLabel,
|
|
234
|
+
path: changelogPath,
|
|
235
|
+
timestamp: now.toISOString()
|
|
236
|
+
}));
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* check-external: Detects if CLAUDE.md was modified outside GSD-T.
|
|
242
|
+
*
|
|
243
|
+
* Exit codes:
|
|
244
|
+
* 0 — up to date, no action needed
|
|
245
|
+
* 1 — external update detected, caller should append entry
|
|
246
|
+
* 2 — changelog is corrupt (HALT)
|
|
247
|
+
*/
|
|
248
|
+
function checkExternal(projectDir) {
|
|
249
|
+
const changelogPath = getChangelogPath(projectDir);
|
|
250
|
+
const claudeMdPath = getClaudeMdPath(projectDir);
|
|
251
|
+
|
|
252
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
253
|
+
console.log(JSON.stringify({ status: 'no-claude-md', path: claudeMdPath }));
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const claudeMdStat = fs.statSync(claudeMdPath);
|
|
258
|
+
const claudeMdMtime = claudeMdStat.mtime;
|
|
259
|
+
|
|
260
|
+
const lastEntryTime = parseLastEntryTime(changelogPath);
|
|
261
|
+
|
|
262
|
+
if (lastEntryTime === null) {
|
|
263
|
+
console.log(JSON.stringify({
|
|
264
|
+
status: 'external-detected',
|
|
265
|
+
reason: 'no-prior-entries',
|
|
266
|
+
claudeMdMtime: claudeMdMtime.toISOString()
|
|
267
|
+
}));
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const bufferMs = 60 * 1000;
|
|
272
|
+
|
|
273
|
+
if (claudeMdMtime.getTime() > lastEntryTime.getTime() + bufferMs) {
|
|
274
|
+
console.log(JSON.stringify({
|
|
275
|
+
status: 'external-detected',
|
|
276
|
+
reason: 'mtime-newer',
|
|
277
|
+
claudeMdMtime: claudeMdMtime.toISOString(),
|
|
278
|
+
lastEntryTime: lastEntryTime.toISOString()
|
|
279
|
+
}));
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
console.log(JSON.stringify({
|
|
284
|
+
status: 'up-to-date',
|
|
285
|
+
claudeMdMtime: claudeMdMtime.toISOString(),
|
|
286
|
+
lastEntryTime: lastEntryTime.toISOString()
|
|
287
|
+
}));
|
|
288
|
+
process.exit(0);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function getLastEntryTime(projectDir) {
|
|
292
|
+
const changelogPath = getChangelogPath(projectDir);
|
|
293
|
+
const result = parseLastEntryTime(changelogPath);
|
|
294
|
+
|
|
295
|
+
if (result === null) {
|
|
296
|
+
console.log(JSON.stringify({ status: 'none' }));
|
|
297
|
+
process.exit(0);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(JSON.stringify({
|
|
301
|
+
status: 'found',
|
|
302
|
+
timestamp: result.toISOString()
|
|
303
|
+
}));
|
|
304
|
+
process.exit(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
308
|
+
// CLI
|
|
309
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
function main() {
|
|
312
|
+
const args = process.argv.slice(2);
|
|
313
|
+
const command = args[0];
|
|
314
|
+
|
|
315
|
+
if (!command) {
|
|
316
|
+
console.error('Usage: gsd-t-claude-md-changelog <command> [args]');
|
|
317
|
+
console.error('Commands: scaffold, append, check-external, get-last-entry-time');
|
|
318
|
+
process.exit(64);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
switch (command) {
|
|
322
|
+
case 'scaffold': {
|
|
323
|
+
const projectDir = args[1] || process.cwd();
|
|
324
|
+
scaffold(projectDir);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
case 'append': {
|
|
328
|
+
const projectDir = args[1] || process.cwd();
|
|
329
|
+
const type = args[2];
|
|
330
|
+
const description = args[3];
|
|
331
|
+
let bullets = [];
|
|
332
|
+
if (args[4]) {
|
|
333
|
+
const parsed = JSON.parse(args[4]);
|
|
334
|
+
if (!Array.isArray(parsed)) {
|
|
335
|
+
halt('Bullets must be a JSON array', 64);
|
|
336
|
+
}
|
|
337
|
+
bullets = parsed;
|
|
338
|
+
}
|
|
339
|
+
if (!type) {
|
|
340
|
+
halt('Usage: gsd-t-claude-md-changelog append <projectDir> <type> [description] [bulletsJson]', 64);
|
|
341
|
+
}
|
|
342
|
+
append(projectDir, type, description, bullets);
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case 'check-external': {
|
|
346
|
+
const projectDir = args[1] || process.cwd();
|
|
347
|
+
checkExternal(projectDir);
|
|
348
|
+
break;
|
|
349
|
+
}
|
|
350
|
+
case 'get-last-entry-time': {
|
|
351
|
+
const projectDir = args[1] || process.cwd();
|
|
352
|
+
getLastEntryTime(projectDir);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
default:
|
|
356
|
+
halt('Unknown command: ' + command, 64);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
main();
|
package/bin/gsd-t.js
CHANGED
|
@@ -3166,6 +3166,8 @@ const PROJECT_BIN_TOOLS = [
|
|
|
3166
3166
|
// M90 D2 — Loop ledger (non-convergence detection + halt directive; §3).
|
|
3167
3167
|
// Propagated so project-local runCli helpers (gsd-t-debug.workflow.js) can invoke it.
|
|
3168
3168
|
"gsd-t-loop-ledger.cjs",
|
|
3169
|
+
// M110 — CLAUDE.md changelog tracking. Scaffolds and appends to the changelog file.
|
|
3170
|
+
"gsd-t-claude-md-changelog.cjs",
|
|
3169
3171
|
// Backlog #40 — deterministic archive+sweep of a completed milestone's domain dirs
|
|
3170
3172
|
// (complete-milestone Step 7). Propagated so complete-milestone can invoke it project-local.
|
|
3171
3173
|
"gsd-t-archive-domains.cjs",
|
package/commands/gsd-t-init.md
CHANGED
|
@@ -367,6 +367,19 @@ The `bin/gsd-t.js init` flow calls `runLoggingScaffoldStep(projectDir)` (from `b
|
|
|
367
367
|
|
|
368
368
|
See `.gsd-t/contracts/logging-scaffold-seam-contract.md` for the full seam envelope shape consumed by d2 (trace), d4 (audit), and d5 (migrate-logging).
|
|
369
369
|
|
|
370
|
+
## Step 11.6: CLAUDE.md Changelog Scaffold (M110)
|
|
371
|
+
|
|
372
|
+
Scaffold the CLAUDE.md changelog file if it doesn't exist:
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
node bin/gsd-t-claude-md-changelog.cjs scaffold .
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
This creates `CLAUDE.md updates.md` — an append-only log of CLAUDE.md changes. The file tracks:
|
|
379
|
+
- Rewrites (via `/gsd-t-setup`)
|
|
380
|
+
- Updates (user-directed edits)
|
|
381
|
+
- External changes (modifications outside GSD-T sessions)
|
|
382
|
+
|
|
370
383
|
## Step 12: Test Verification
|
|
371
384
|
|
|
372
385
|
After initialization:
|
package/commands/gsd-t-setup.md
CHANGED
|
@@ -299,6 +299,16 @@ Wait for user confirmation before writing.
|
|
|
299
299
|
2. Verify it's valid markdown (no broken tables, unclosed code blocks)
|
|
300
300
|
3. If `.gsd-t/progress.md` exists, log the setup in the Decision Log
|
|
301
301
|
|
|
302
|
+
## Step 7.5: Update CLAUDE.md Changelog (M110)
|
|
303
|
+
|
|
304
|
+
After writing the project CLAUDE.md, update the changelog:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
node bin/gsd-t-claude-md-changelog.cjs append . rewritten "Generated via /gsd-t-setup"
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
If the changelog file doesn't exist, the command scaffolds it first.
|
|
311
|
+
|
|
302
312
|
## Document Ripple
|
|
303
313
|
|
|
304
314
|
### Always update:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.11",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gsd-t-changelog-check.js
|
|
4
|
+
*
|
|
5
|
+
* SessionStart hook: checks if CLAUDE.md was modified outside GSD-T since the
|
|
6
|
+
* last tracked changelog entry. If so, appends an "External update detected" entry.
|
|
7
|
+
*
|
|
8
|
+
* Runs once at session start, not on every prompt.
|
|
9
|
+
* Exit codes:
|
|
10
|
+
* 0 — success (up to date, or external update logged)
|
|
11
|
+
* 1 — not a GSD-T project (no .gsd-t/), or no CLI found — silent skip, not an error
|
|
12
|
+
* 2 — changelog CLI failed unexpectedly
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
|
|
19
|
+
function findCli() {
|
|
20
|
+
const globalRoot = execSync('npm root -g', { encoding: 'utf8' }).trim();
|
|
21
|
+
const globalCli = path.join(globalRoot, '@tekyzinc', 'gsd-t', 'bin', 'gsd-t-claude-md-changelog.cjs');
|
|
22
|
+
if (fs.existsSync(globalCli)) return globalCli;
|
|
23
|
+
|
|
24
|
+
const localCli = path.join(process.cwd(), 'bin', 'gsd-t-claude-md-changelog.cjs');
|
|
25
|
+
if (fs.existsSync(localCli)) return localCli;
|
|
26
|
+
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function main() {
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
|
|
33
|
+
// Only run in GSD-T projects — silent exit if not
|
|
34
|
+
if (!fs.existsSync(path.join(cwd, '.gsd-t'))) {
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const cli = findCli();
|
|
39
|
+
if (!cli) {
|
|
40
|
+
// CLI not installed — silent exit (GSD-T may not be fully set up yet)
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let checkResult;
|
|
45
|
+
try {
|
|
46
|
+
checkResult = execSync(`node "${cli}" check-external "${cwd}"`, { encoding: 'utf8' });
|
|
47
|
+
// Exit code 0 = up to date
|
|
48
|
+
process.exit(0);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
if (e.status === 1) {
|
|
51
|
+
// External update detected — append entry
|
|
52
|
+
execSync(`node "${cli}" append "${cwd}" external`, { encoding: 'utf8' });
|
|
53
|
+
process.stdout.write('CLAUDE.md changelog: external update detected and logged.\n');
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
if (e.status === 2) {
|
|
57
|
+
// Corrupt changelog — report but don't block session
|
|
58
|
+
process.stderr.write('CLAUDE.md changelog is corrupt. Run: node bin/gsd-t-claude-md-changelog.cjs get-last-entry-time .\n');
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
// Unexpected error — report it
|
|
62
|
+
process.stderr.write('CLAUDE.md changelog check failed: ' + (e.message || 'unknown error') + '\n');
|
|
63
|
+
process.exit(2);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
main();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* gsd-t-changelog-post-edit.js
|
|
4
|
+
*
|
|
5
|
+
* PostToolUse hook for Write|Edit: after a successful write to CLAUDE.md,
|
|
6
|
+
* reminds Claude to update the changelog.
|
|
7
|
+
*
|
|
8
|
+
* Receives JSON on stdin with tool_input containing file_path.
|
|
9
|
+
* Outputs reminder text to stdout if the target was CLAUDE.md.
|
|
10
|
+
*
|
|
11
|
+
* Does NOT auto-append — Claude should consciously decide what changed and
|
|
12
|
+
* call the append command with appropriate bullets.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
let input = '';
|
|
19
|
+
process.stdin.setEncoding('utf8');
|
|
20
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
21
|
+
process.stdin.on('end', () => {
|
|
22
|
+
let data;
|
|
23
|
+
try {
|
|
24
|
+
data = JSON.parse(input);
|
|
25
|
+
} catch {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get the file path from tool_input
|
|
30
|
+
const toolInput = data.tool_input;
|
|
31
|
+
if (!toolInput) {
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let filePath;
|
|
36
|
+
if (typeof toolInput === 'string') {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(toolInput);
|
|
39
|
+
filePath = parsed.file_path;
|
|
40
|
+
} catch {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
} else if (typeof toolInput === 'object') {
|
|
44
|
+
filePath = toolInput.file_path;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!filePath) {
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check if it's CLAUDE.md (root or .claude/)
|
|
52
|
+
const basename = path.basename(filePath);
|
|
53
|
+
if (basename !== 'CLAUDE.md') {
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if it's NOT the global ~/.claude/CLAUDE.md (only track project CLAUDE.md)
|
|
58
|
+
const homeClaudeMd = path.join(process.env.HOME || '', '.claude', 'CLAUDE.md');
|
|
59
|
+
if (path.resolve(filePath) === path.resolve(homeClaudeMd)) {
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// It's a project CLAUDE.md — remind to update changelog
|
|
64
|
+
const projectDir = data.cwd || process.cwd();
|
|
65
|
+
const changelogPath = path.join(projectDir, 'CLAUDE.md updates.md');
|
|
66
|
+
|
|
67
|
+
process.stdout.write(
|
|
68
|
+
`[M110] You just modified CLAUDE.md. Update the changelog:\n` +
|
|
69
|
+
` node bin/gsd-t-claude-md-changelog.cjs append "${projectDir}" updated "" '["<describe what changed>"]'\n`
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
process.exit(0);
|
|
73
|
+
});
|