@tekyzinc/gsd-t 2.17.0 → 2.18.2
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/CHANGELOG.md +23 -0
- package/bin/gsd-t.js +86 -2
- package/commands/gsd-t-complete-milestone.md +21 -4
- package/commands/gsd-t-init.md +8 -0
- package/package.json +2 -1
- package/scripts/gsd-t-heartbeat.js +156 -0
- package/templates/CLAUDE-global.md +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [2.18.2] - 2026-02-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Gap Analysis Gate in `gsd-t-complete-milestone` — mandatory requirements verification before archiving
|
|
9
|
+
- Self-correction loop: auto-fixes gaps, re-verifies, re-analyzes (up to 2 cycles), stops if unresolvable
|
|
10
|
+
- Explicit Playwright E2E test execution in milestone test verification step
|
|
11
|
+
|
|
12
|
+
## [2.18.1] - 2026-02-16
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- Auto-Init Guard — GSD-T workflow commands automatically run `gsd-t-init` if any init files are missing, then continue with the original command
|
|
16
|
+
- `gsd-t-init` copies `~/.claude/settings.local` → `.claude/settings.local.json` during project initialization
|
|
17
|
+
- Exempt commands that skip auto-init: `gsd-t-init`, `gsd-t-init-scan-setup`, `gsd-t-help`, `gsd-t-version-update`, `gsd-t-version-update-all`, `gsd-t-prompt`, `gsd-t-brainstorm`
|
|
18
|
+
|
|
19
|
+
## [2.18.0] - 2026-02-16
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Heartbeat system — real-time event streaming from Claude Code sessions via async hooks
|
|
23
|
+
- `scripts/gsd-t-heartbeat.js` — hook handler that writes JSONL events to `.gsd-t/heartbeat-{session_id}.jsonl`
|
|
24
|
+
- 9 Claude Code hooks: SessionStart, PostToolUse, SubagentStart, SubagentStop, TaskCompleted, TeammateIdle, Notification, Stop, SessionEnd
|
|
25
|
+
- Installer auto-configures heartbeat hooks in settings.json (all async, zero performance impact)
|
|
26
|
+
- Event types: session lifecycle, tool calls with file/command summaries, agent spawn/stop/idle, task completions
|
|
27
|
+
|
|
5
28
|
## [2.17.0] - 2026-02-16
|
|
6
29
|
|
|
7
30
|
### Added
|
package/bin/gsd-t.js
CHANGED
|
@@ -24,6 +24,7 @@ const { execSync } = require("child_process");
|
|
|
24
24
|
|
|
25
25
|
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
26
26
|
const COMMANDS_DIR = path.join(CLAUDE_DIR, "commands");
|
|
27
|
+
const SCRIPTS_DIR = path.join(CLAUDE_DIR, "scripts");
|
|
27
28
|
const GLOBAL_CLAUDE_MD = path.join(CLAUDE_DIR, "CLAUDE.md");
|
|
28
29
|
const SETTINGS_JSON = path.join(CLAUDE_DIR, "settings.json");
|
|
29
30
|
const VERSION_FILE = path.join(CLAUDE_DIR, ".gsd-t-version");
|
|
@@ -33,6 +34,7 @@ const UPDATE_CHECK_FILE = path.join(CLAUDE_DIR, ".gsd-t-update-check");
|
|
|
33
34
|
// Where our package files live (relative to this script)
|
|
34
35
|
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
35
36
|
const PKG_COMMANDS = path.join(PKG_ROOT, "commands");
|
|
37
|
+
const PKG_SCRIPTS = path.join(PKG_ROOT, "scripts");
|
|
36
38
|
const PKG_TEMPLATES = path.join(PKG_ROOT, "templates");
|
|
37
39
|
const PKG_EXAMPLES = path.join(PKG_ROOT, "examples");
|
|
38
40
|
|
|
@@ -144,6 +146,84 @@ function getInstalledCommands() {
|
|
|
144
146
|
}
|
|
145
147
|
}
|
|
146
148
|
|
|
149
|
+
// ─── Heartbeat ──────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
const HEARTBEAT_SCRIPT = "gsd-t-heartbeat.js";
|
|
152
|
+
const HEARTBEAT_HOOKS = [
|
|
153
|
+
"SessionStart", "PostToolUse", "SubagentStart", "SubagentStop",
|
|
154
|
+
"TaskCompleted", "TeammateIdle", "Notification", "Stop", "SessionEnd"
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
function installHeartbeat() {
|
|
158
|
+
ensureDir(SCRIPTS_DIR);
|
|
159
|
+
|
|
160
|
+
// Copy heartbeat script
|
|
161
|
+
const src = path.join(PKG_SCRIPTS, HEARTBEAT_SCRIPT);
|
|
162
|
+
const dest = path.join(SCRIPTS_DIR, HEARTBEAT_SCRIPT);
|
|
163
|
+
|
|
164
|
+
if (!fs.existsSync(src)) {
|
|
165
|
+
warn("Heartbeat script not found in package — skipping");
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const srcContent = fs.readFileSync(src, "utf8");
|
|
170
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : "";
|
|
171
|
+
|
|
172
|
+
if (srcContent !== destContent) {
|
|
173
|
+
copyFile(src, dest, HEARTBEAT_SCRIPT);
|
|
174
|
+
} else {
|
|
175
|
+
info("Heartbeat script unchanged");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Configure hooks in settings.json
|
|
179
|
+
const hooksAdded = configureHeartbeatHooks(dest);
|
|
180
|
+
if (hooksAdded > 0) {
|
|
181
|
+
success(`${hooksAdded} heartbeat hooks configured in settings.json`);
|
|
182
|
+
} else {
|
|
183
|
+
info("Heartbeat hooks already configured");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function configureHeartbeatHooks(scriptPath) {
|
|
188
|
+
let settings = {};
|
|
189
|
+
if (fs.existsSync(SETTINGS_JSON)) {
|
|
190
|
+
try {
|
|
191
|
+
settings = JSON.parse(fs.readFileSync(SETTINGS_JSON, "utf8"));
|
|
192
|
+
} catch {
|
|
193
|
+
warn("settings.json has invalid JSON — cannot configure hooks");
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!settings.hooks) settings.hooks = {};
|
|
199
|
+
|
|
200
|
+
const cmd = `node "${scriptPath.replace(/\\/g, "\\\\")}"`;
|
|
201
|
+
let added = 0;
|
|
202
|
+
|
|
203
|
+
for (const event of HEARTBEAT_HOOKS) {
|
|
204
|
+
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
205
|
+
|
|
206
|
+
// Check if heartbeat hook already exists for this event
|
|
207
|
+
const hasHeartbeat = settings.hooks[event].some((entry) =>
|
|
208
|
+
entry.hooks && entry.hooks.some((h) => h.command && h.command.includes(HEARTBEAT_SCRIPT))
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
if (!hasHeartbeat) {
|
|
212
|
+
settings.hooks[event].push({
|
|
213
|
+
matcher: "",
|
|
214
|
+
hooks: [{ type: "command", command: cmd, async: true }],
|
|
215
|
+
});
|
|
216
|
+
added++;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (added > 0) {
|
|
221
|
+
fs.writeFileSync(SETTINGS_JSON, JSON.stringify(settings, null, 2));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return added;
|
|
225
|
+
}
|
|
226
|
+
|
|
147
227
|
// ─── Commands ────────────────────────────────────────────────────────────────
|
|
148
228
|
|
|
149
229
|
function doInstall(opts = {}) {
|
|
@@ -226,10 +306,14 @@ function doInstall(opts = {}) {
|
|
|
226
306
|
copyFile(globalSrc, GLOBAL_CLAUDE_MD, "CLAUDE.md installed → ~/.claude/CLAUDE.md");
|
|
227
307
|
}
|
|
228
308
|
|
|
229
|
-
// 4.
|
|
309
|
+
// 4. Install heartbeat script + hooks
|
|
310
|
+
heading("Heartbeat (Real-time Events)");
|
|
311
|
+
installHeartbeat();
|
|
312
|
+
|
|
313
|
+
// 5. Save version
|
|
230
314
|
saveInstalledVersion();
|
|
231
315
|
|
|
232
|
-
//
|
|
316
|
+
// 6. Summary
|
|
233
317
|
heading("Installation Complete!");
|
|
234
318
|
log("");
|
|
235
319
|
log(` Commands: ${gsdtCommands.length} GSD-T + ${utilityCommands.length} utility commands in ~/.claude/commands/`);
|
|
@@ -17,6 +17,22 @@ If status is not VERIFIED:
|
|
|
17
17
|
|
|
18
18
|
If `--force` flag provided, proceed with warning in archive.
|
|
19
19
|
|
|
20
|
+
## Step 1.5: Gap Analysis Gate
|
|
21
|
+
|
|
22
|
+
After verification passes, run a gap analysis against `docs/requirements.md` scoped to this milestone's deliverables:
|
|
23
|
+
|
|
24
|
+
1. Identify which requirements this milestone was supposed to satisfy (from domain scopes, tasks, and milestone definition)
|
|
25
|
+
2. Run `gsd-t-gap-analysis` against those requirements, comparing spec to actual code
|
|
26
|
+
3. If **all gaps resolved** (100% Implemented) → proceed to Step 2
|
|
27
|
+
4. If **gaps found** (Partial, Incorrect, or Not Implemented):
|
|
28
|
+
a. Auto-fix: execute remediation for each gap (prioritize Critical → High → Medium)
|
|
29
|
+
b. Run affected tests (unit + integration + Playwright E2E if configured)
|
|
30
|
+
c. Re-run `gsd-t-verify` to confirm fixes don't break anything
|
|
31
|
+
d. Re-run gap analysis to confirm gaps are resolved
|
|
32
|
+
e. If gaps remain after **2 fix cycles** → STOP and report unresolved gaps to user
|
|
33
|
+
|
|
34
|
+
This is a **mandatory gate** — the milestone cannot be archived with known gaps against its requirements.
|
|
35
|
+
|
|
20
36
|
## Step 2: Gather Milestone Artifacts
|
|
21
37
|
|
|
22
38
|
Collect all files related to this milestone:
|
|
@@ -163,10 +179,11 @@ Before creating the git tag, verify all documentation is up to date:
|
|
|
163
179
|
|
|
164
180
|
Before creating the git tag, verify the milestone is truly complete:
|
|
165
181
|
|
|
166
|
-
1. **Run the full test suite**: Execute ALL tests — unit, integration, and E2E
|
|
167
|
-
2. **
|
|
168
|
-
3. **
|
|
169
|
-
4. **
|
|
182
|
+
1. **Run the full test suite**: Execute ALL tests — unit, integration, and E2E
|
|
183
|
+
2. **Run Playwright E2E** (if configured): Detect `playwright.config.*` or Playwright in dependencies. If present, run the full Playwright suite. If specs are missing or stale, invoke `gsd-t-test-sync` first.
|
|
184
|
+
3. **Verify all pass**: Every test must pass. If any fail, fix before tagging (up to 2 attempts)
|
|
185
|
+
4. **Compare to baseline**: If a test baseline was recorded at milestone start, verify coverage has improved or at minimum not regressed
|
|
186
|
+
5. **Log test results**: Include test pass/fail counts in the milestone summary (Step 4)
|
|
170
187
|
|
|
171
188
|
## Step 8: Create Git Tag
|
|
172
189
|
|
package/commands/gsd-t-init.md
CHANGED
|
@@ -18,6 +18,14 @@ Report current state and ask if user wants to reset or continue.
|
|
|
18
18
|
Offer to migrate: "Found legacy GSD structure. Want me to migrate to GSD-T?"
|
|
19
19
|
If yes, read `.gsd/` state and create equivalent `.gsd-t/` structure.
|
|
20
20
|
|
|
21
|
+
## Step 1.5: Copy Local Settings
|
|
22
|
+
|
|
23
|
+
If `~/.claude/settings.local` exists and `.claude/settings.local.json` does not exist in the project:
|
|
24
|
+
1. Create the `.claude/` directory in the project root if it doesn't exist
|
|
25
|
+
2. Copy `~/.claude/settings.local` → `.claude/settings.local.json`
|
|
26
|
+
|
|
27
|
+
Skip silently if the source file doesn't exist or the target already exists.
|
|
28
|
+
|
|
21
29
|
## Step 2: Create Directory Structure
|
|
22
30
|
|
|
23
31
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.18.2",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 41 slash commands with backlog management, impact analysis, test sync, and milestone archival",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"bin/",
|
|
25
25
|
"commands/",
|
|
26
|
+
"scripts/",
|
|
26
27
|
"templates/",
|
|
27
28
|
"examples/",
|
|
28
29
|
"docs/",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD-T Heartbeat — Claude Code Hook Event Writer
|
|
5
|
+
*
|
|
6
|
+
* Writes structured events to .gsd-t/heartbeat-{session_id}.jsonl
|
|
7
|
+
* Installed as an async hook for multiple Claude Code events.
|
|
8
|
+
*
|
|
9
|
+
* Events captured:
|
|
10
|
+
* SessionStart, PostToolUse, SubagentStart, SubagentStop,
|
|
11
|
+
* TaskCompleted, TeammateIdle, Notification, Stop, SessionEnd
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
|
|
17
|
+
let input = "";
|
|
18
|
+
process.stdin.setEncoding("utf8");
|
|
19
|
+
process.stdin.on("data", (d) => (input += d));
|
|
20
|
+
process.stdin.on("end", () => {
|
|
21
|
+
try {
|
|
22
|
+
const hook = JSON.parse(input);
|
|
23
|
+
const dir = hook.cwd || process.cwd();
|
|
24
|
+
const gsdtDir = path.join(dir, ".gsd-t");
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(gsdtDir)) return;
|
|
27
|
+
|
|
28
|
+
const sid = hook.session_id || "unknown";
|
|
29
|
+
const file = path.join(gsdtDir, `heartbeat-${sid}.jsonl`);
|
|
30
|
+
|
|
31
|
+
const event = buildEvent(hook);
|
|
32
|
+
if (event) {
|
|
33
|
+
fs.appendFileSync(file, JSON.stringify(event) + "\n");
|
|
34
|
+
}
|
|
35
|
+
} catch (e) {
|
|
36
|
+
// Silent failure — never interfere with Claude Code
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function buildEvent(hook) {
|
|
41
|
+
const base = {
|
|
42
|
+
ts: new Date().toISOString(),
|
|
43
|
+
sid: hook.session_id,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
switch (hook.hook_event_name) {
|
|
47
|
+
case "SessionStart":
|
|
48
|
+
return {
|
|
49
|
+
...base,
|
|
50
|
+
evt: "session_start",
|
|
51
|
+
data: { source: hook.source, model: hook.model },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
case "PostToolUse":
|
|
55
|
+
return {
|
|
56
|
+
...base,
|
|
57
|
+
evt: "tool",
|
|
58
|
+
tool: hook.tool_name,
|
|
59
|
+
data: summarize(hook.tool_name, hook.tool_input),
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
case "SubagentStart":
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
evt: "agent_spawn",
|
|
66
|
+
data: { agent_id: hook.agent_id, agent_type: hook.agent_type },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
case "SubagentStop":
|
|
70
|
+
return {
|
|
71
|
+
...base,
|
|
72
|
+
evt: "agent_stop",
|
|
73
|
+
data: { agent_id: hook.agent_id, agent_type: hook.agent_type },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
case "TaskCompleted":
|
|
77
|
+
return {
|
|
78
|
+
...base,
|
|
79
|
+
evt: "task_done",
|
|
80
|
+
data: { task: hook.task_subject, agent: hook.teammate_name },
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
case "TeammateIdle":
|
|
84
|
+
return {
|
|
85
|
+
...base,
|
|
86
|
+
evt: "agent_idle",
|
|
87
|
+
data: { agent: hook.teammate_name, team: hook.team_name },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
case "Notification":
|
|
91
|
+
return {
|
|
92
|
+
...base,
|
|
93
|
+
evt: "notification",
|
|
94
|
+
data: { message: hook.message, title: hook.title },
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
case "Stop":
|
|
98
|
+
return { ...base, evt: "session_stop" };
|
|
99
|
+
|
|
100
|
+
case "SessionEnd":
|
|
101
|
+
return {
|
|
102
|
+
...base,
|
|
103
|
+
evt: "session_end",
|
|
104
|
+
data: { reason: hook.reason },
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function summarize(tool, input) {
|
|
113
|
+
if (!tool || !input) return {};
|
|
114
|
+
switch (tool) {
|
|
115
|
+
case "Read":
|
|
116
|
+
return { file: shortPath(input.file_path) };
|
|
117
|
+
case "Edit":
|
|
118
|
+
return { file: shortPath(input.file_path) };
|
|
119
|
+
case "Write":
|
|
120
|
+
return { file: shortPath(input.file_path) };
|
|
121
|
+
case "Bash":
|
|
122
|
+
return {
|
|
123
|
+
cmd: (input.command || "").slice(0, 150),
|
|
124
|
+
desc: input.description,
|
|
125
|
+
};
|
|
126
|
+
case "Grep":
|
|
127
|
+
return { pattern: input.pattern, path: shortPath(input.path) };
|
|
128
|
+
case "Glob":
|
|
129
|
+
return { pattern: input.pattern };
|
|
130
|
+
case "Task":
|
|
131
|
+
return { desc: input.description, type: input.subagent_type };
|
|
132
|
+
case "WebSearch":
|
|
133
|
+
return { query: input.query };
|
|
134
|
+
case "WebFetch":
|
|
135
|
+
return { url: input.url };
|
|
136
|
+
case "NotebookEdit":
|
|
137
|
+
return { file: shortPath(input.notebook_path) };
|
|
138
|
+
default:
|
|
139
|
+
return {};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function shortPath(p) {
|
|
144
|
+
if (!p) return null;
|
|
145
|
+
// Convert absolute paths to relative for readability
|
|
146
|
+
const cwd = process.cwd();
|
|
147
|
+
if (p.startsWith(cwd)) {
|
|
148
|
+
return p.slice(cwd.length + 1).replace(/\\/g, "/");
|
|
149
|
+
}
|
|
150
|
+
// For home-dir paths, abbreviate
|
|
151
|
+
const home = require("os").homedir();
|
|
152
|
+
if (p.startsWith(home)) {
|
|
153
|
+
return "~" + p.slice(home.length).replace(/\\/g, "/");
|
|
154
|
+
}
|
|
155
|
+
return p.replace(/\\/g, "/");
|
|
156
|
+
}
|
|
@@ -176,6 +176,21 @@ On session start, a version check hook outputs one of two messages. Show the res
|
|
|
176
176
|
|
|
177
177
|
Only execute GSD-T workflow behavior when a `/gsd-t-*` command is invoked or when actively mid-phase (resumed via `/gsd-t-resume`). **Plain text messages — especially questions — should be answered conversationally.** Do not launch into workflow execution, file reading, or phase advancement from a question or comment. If the user wants work done, they will invoke a command.
|
|
178
178
|
|
|
179
|
+
## Auto-Init Guard
|
|
180
|
+
|
|
181
|
+
Before executing any GSD-T workflow command, check if **any** of these files are missing in the current project:
|
|
182
|
+
- `.gsd-t/progress.md`, `.gsd-t/backlog.md`, `.gsd-t/backlog-settings.md`
|
|
183
|
+
- `.gsd-t/contracts/`, `.gsd-t/domains/`
|
|
184
|
+
- `.claude/settings.local.json` (if `~/.claude/settings.local` exists)
|
|
185
|
+
- `CLAUDE.md`, `README.md`
|
|
186
|
+
- `docs/requirements.md`, `docs/architecture.md`, `docs/workflows.md`, `docs/infrastructure.md`
|
|
187
|
+
|
|
188
|
+
If any are missing:
|
|
189
|
+
1. Run `gsd-t-init` automatically (it skips files that already exist)
|
|
190
|
+
2. Then continue with the originally requested command
|
|
191
|
+
|
|
192
|
+
**Exempt commands** (do not trigger auto-init): `gsd-t-init`, `gsd-t-init-scan-setup`, `gsd-t-help`, `gsd-t-version-update`, `gsd-t-version-update-all`, `gsd-t-prompt`, `gsd-t-brainstorm`.
|
|
193
|
+
|
|
179
194
|
## Prime Rule
|
|
180
195
|
KEEP GOING. Only stop for:
|
|
181
196
|
1. Unrecoverable errors after 2 fix attempts
|