@tekyzinc/gsd-t 2.20.5 → 2.20.7

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.
@@ -1,3 +1,5 @@
1
+ # Reload CLAUDE.md Directives
2
+
1
3
  Reload and apply CLAUDE.md directives for this session.
2
4
 
3
5
  1. Read `~/.claude/CLAUDE.md` (global directives). Note any changes from what you've been following.
@@ -44,8 +44,8 @@ For each task:
44
44
  8. **Run ALL tests** — unit, integration, and full Playwright suite. Fix any failures before proceeding (up to 2 attempts)
45
45
  9. Run the Pre-Commit Gate checklist from CLAUDE.md — update ALL affected docs BEFORE committing
46
46
  10. Commit with a descriptive message: `[{domain}] Task {N}: {description}`
47
- 10. Update `.gsd-t/progress.md` — mark task complete
48
- 11. If you've reached a CHECKPOINT in integration-points.md, pause and verify the contract before continuing
47
+ 11. Update `.gsd-t/progress.md` — mark task complete
48
+ 12. If you've reached a CHECKPOINT in integration-points.md, pause and verify the contract before continuing
49
49
 
50
50
  ### Team Mode (when agent teams are enabled)
51
51
  Spawn teammates for independent domains:
@@ -143,4 +143,17 @@ When all tasks in all domains are complete:
143
143
 
144
144
  **Level 1–2**: Report completion summary and recommend proceeding to integrate phase. Wait for confirmation.
145
145
 
146
+ ## Document Ripple
147
+
148
+ Execute modifies source code, so the Pre-Commit Gate (referenced in Step 9) covers document updates. For clarity, the key documents affected by execution:
149
+
150
+ ### Always update:
151
+ 1. **`.gsd-t/progress.md`** — Mark tasks complete, update domain status, log execution summary
152
+
153
+ ### Check if affected (per task):
154
+ 2. **`.gsd-t/contracts/`** — Did a task change an API endpoint, schema, or component interface? Update the contract
155
+ 3. **`docs/requirements.md`** — Did a task implement or change a requirement? Mark it complete or revise
156
+ 4. **`docs/architecture.md`** — Did a task add/change a component or data flow? Update it
157
+ 5. **`.gsd-t/techdebt.md`** — Did a task resolve a debt item? Mark it done. Did it reveal new debt? Add it
158
+
146
159
  $ARGUMENTS
@@ -401,4 +401,20 @@ All detailed findings are in `.gsd-t/scan/` for review.
401
401
 
402
402
  Ask: "Want to promote any tech debt items to milestones? Or address the critical items first?"
403
403
 
404
+ ## Document Ripple
405
+
406
+ Scan produces analysis files and updates living documents (Step 5 already covers most updates). Verify:
407
+
408
+ ### Always update:
409
+ 1. **`.gsd-t/progress.md`** — Log scan completion with summary stats in Decision Log
410
+ 2. **`docs/architecture.md`** — Merge scan findings (Step 5)
411
+ 3. **`docs/workflows.md`** — Merge business rules findings (Step 5)
412
+ 4. **`docs/infrastructure.md`** — Merge operational findings (Step 5)
413
+ 5. **`docs/requirements.md`** — Merge discovered requirements (Step 5)
414
+ 6. **`README.md`** — Update tech stack and setup if needed (Step 5)
415
+
416
+ ### Check if affected:
417
+ 7. **`.gsd-t/techdebt.md`** — Created/updated with all findings (Step 3)
418
+ 8. **`CLAUDE.md`** — If new conventions or patterns were discovered, suggest additions
419
+
404
420
  $ARGUMENTS
@@ -317,4 +317,15 @@ Generated 5 test tasks → added to current domain
317
317
 
318
318
  **Level 1–2**: Present the full report and wait for user input before proceeding.
319
319
 
320
+ ## Document Ripple
321
+
322
+ ### Always update:
323
+ 1. **`.gsd-t/progress.md`** — Log test sync results in Decision Log (standalone mode)
324
+ 2. **`.gsd-t/test-coverage.md`** — Created/updated with coverage report (Step 5)
325
+
326
+ ### Check if affected:
327
+ 3. **`docs/requirements.md`** — If test tasks map to requirements, update the Test Coverage table
328
+ 4. **`.gsd-t/domains/{current}/tasks.md`** — If test tasks were generated, append them (Step 6)
329
+ 5. **`.gsd-t/techdebt.md`** — If persistent test gaps were found, add as debt items
330
+
320
331
  $ARGUMENTS
@@ -157,4 +157,15 @@ Update `.gsd-t/progress.md`:
157
157
  - CONDITIONAL PASS → User decides if warnings are acceptable
158
158
  - FAIL → Return to execute phase for remediation tasks
159
159
 
160
+ ## Document Ripple
161
+
162
+ ### Always update:
163
+ 1. **`.gsd-t/progress.md`** — Set status to VERIFIED/VERIFY-FAILED, log verification summary
164
+ 2. **`.gsd-t/verify-report.md`** — Created with full verification results (Step 4)
165
+
166
+ ### Check if affected:
167
+ 3. **`.gsd-t/domains/{domain}/tasks.md`** — If remediation tasks were created (Step 5)
168
+ 4. **`.gsd-t/techdebt.md`** — If verification found new quality or security issues, add as debt
169
+ 5. **`docs/requirements.md`** — If verification revealed unmet requirements, update status
170
+
160
171
  $ARGUMENTS
package/package.json CHANGED
@@ -1,35 +1,39 @@
1
- {
2
- "name": "@tekyzinc/gsd-t",
3
- "version": "2.20.5",
4
- "description": "GSD-T: Contract-Driven Development for Claude Code — 41 slash commands with backlog management, impact analysis, test sync, and milestone archival",
5
- "author": "Tekyz, Inc.",
6
- "license": "MIT",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/Tekyz-Inc/get-stuff-done-teams.git"
10
- },
11
- "homepage": "https://github.com/Tekyz-Inc/get-stuff-done-teams#readme",
12
- "keywords": [
13
- "claude-code",
14
- "gsd",
15
- "ai-development",
16
- "agent-teams",
17
- "contract-driven-development",
18
- "slash-commands"
19
- ],
20
- "bin": {
21
- "gsd-t": "bin/gsd-t.js"
22
- },
23
- "files": [
24
- "bin/",
25
- "commands/",
26
- "scripts/",
27
- "templates/",
28
- "examples/",
29
- "docs/",
30
- "CHANGELOG.md"
31
- ],
32
- "engines": {
33
- "node": ">=16.0.0"
34
- }
35
- }
1
+ {
2
+ "name": "@tekyzinc/gsd-t",
3
+ "version": "2.20.7",
4
+ "description": "GSD-T: Contract-Driven Development for Claude Code — 41 slash commands with backlog management, impact analysis, test sync, and milestone archival",
5
+ "author": "Tekyz, Inc.",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Tekyz-Inc/get-stuff-done-teams.git"
10
+ },
11
+ "homepage": "https://github.com/Tekyz-Inc/get-stuff-done-teams#readme",
12
+ "keywords": [
13
+ "claude-code",
14
+ "gsd",
15
+ "ai-development",
16
+ "agent-teams",
17
+ "contract-driven-development",
18
+ "slash-commands"
19
+ ],
20
+ "main": "bin/gsd-t.js",
21
+ "bin": {
22
+ "gsd-t": "bin/gsd-t.js"
23
+ },
24
+ "scripts": {
25
+ "test": "node --test"
26
+ },
27
+ "files": [
28
+ "bin/",
29
+ "commands/",
30
+ "scripts/",
31
+ "templates/",
32
+ "examples/",
33
+ "docs/",
34
+ "CHANGELOG.md"
35
+ ],
36
+ "engines": {
37
+ "node": ">=16.0.0"
38
+ }
39
+ }
@@ -1,156 +1,198 @@
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
- }
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
+ const MAX_STDIN = 1024 * 1024; // 1MB — prevent OOM from unbounded input
18
+ const SAFE_SID = /^[a-zA-Z0-9_-]+$/; // Allowlist for session_id — blocks path traversal
19
+ const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days — auto-cleanup threshold
20
+
21
+ let input = "";
22
+ let aborted = false;
23
+ process.stdin.setEncoding("utf8");
24
+ process.stdin.on("data", (d) => {
25
+ input += d;
26
+ if (input.length > MAX_STDIN) {
27
+ aborted = true;
28
+ process.stdin.destroy();
29
+ }
30
+ });
31
+ process.stdin.on("end", () => {
32
+ if (aborted) return; // Silently discard oversized input
33
+ try {
34
+ const hook = JSON.parse(input);
35
+ const dir = hook.cwd || process.cwd();
36
+
37
+ // Validate cwd is absolute path
38
+ if (!path.isAbsolute(dir)) return;
39
+
40
+ const gsdtDir = path.join(dir, ".gsd-t");
41
+ if (!fs.existsSync(gsdtDir)) return;
42
+
43
+ const sid = hook.session_id || "unknown";
44
+
45
+ // Validate session_id — block path traversal (e.g., "../../etc/evil")
46
+ if (!SAFE_SID.test(sid)) return;
47
+
48
+ const file = path.join(gsdtDir, `heartbeat-${sid}.jsonl`);
49
+
50
+ // Verify resolved path is still within .gsd-t/ directory
51
+ const resolvedFile = path.resolve(file);
52
+ const resolvedDir = path.resolve(gsdtDir);
53
+ if (!resolvedFile.startsWith(resolvedDir + path.sep)) return;
54
+
55
+ const event = buildEvent(hook);
56
+ if (event) {
57
+ cleanupOldHeartbeats(gsdtDir);
58
+ fs.appendFileSync(file, JSON.stringify(event) + "\n");
59
+ }
60
+ } catch (e) {
61
+ // Silent failure — never interfere with Claude Code
62
+ }
63
+ });
64
+
65
+ function cleanupOldHeartbeats(gsdtDir) {
66
+ try {
67
+ const files = fs.readdirSync(gsdtDir);
68
+ const now = Date.now();
69
+ for (const f of files) {
70
+ if (!f.startsWith("heartbeat-") || !f.endsWith(".jsonl")) continue;
71
+ const fp = path.join(gsdtDir, f);
72
+ const stat = fs.statSync(fp);
73
+ if (now - stat.mtimeMs > MAX_AGE_MS) {
74
+ fs.unlinkSync(fp);
75
+ }
76
+ }
77
+ } catch {
78
+ // Silent failure — never interfere with Claude Code
79
+ }
80
+ }
81
+
82
+ function buildEvent(hook) {
83
+ const base = {
84
+ ts: new Date().toISOString(),
85
+ sid: hook.session_id,
86
+ };
87
+
88
+ switch (hook.hook_event_name) {
89
+ case "SessionStart":
90
+ return {
91
+ ...base,
92
+ evt: "session_start",
93
+ data: { source: hook.source, model: hook.model },
94
+ };
95
+
96
+ case "PostToolUse":
97
+ return {
98
+ ...base,
99
+ evt: "tool",
100
+ tool: hook.tool_name,
101
+ data: summarize(hook.tool_name, hook.tool_input),
102
+ };
103
+
104
+ case "SubagentStart":
105
+ return {
106
+ ...base,
107
+ evt: "agent_spawn",
108
+ data: { agent_id: hook.agent_id, agent_type: hook.agent_type },
109
+ };
110
+
111
+ case "SubagentStop":
112
+ return {
113
+ ...base,
114
+ evt: "agent_stop",
115
+ data: { agent_id: hook.agent_id, agent_type: hook.agent_type },
116
+ };
117
+
118
+ case "TaskCompleted":
119
+ return {
120
+ ...base,
121
+ evt: "task_done",
122
+ data: { task: hook.task_subject, agent: hook.teammate_name },
123
+ };
124
+
125
+ case "TeammateIdle":
126
+ return {
127
+ ...base,
128
+ evt: "agent_idle",
129
+ data: { agent: hook.teammate_name, team: hook.team_name },
130
+ };
131
+
132
+ case "Notification":
133
+ return {
134
+ ...base,
135
+ evt: "notification",
136
+ data: { message: hook.message, title: hook.title },
137
+ };
138
+
139
+ case "Stop":
140
+ return { ...base, evt: "session_stop" };
141
+
142
+ case "SessionEnd":
143
+ return {
144
+ ...base,
145
+ evt: "session_end",
146
+ data: { reason: hook.reason },
147
+ };
148
+
149
+ default:
150
+ return null;
151
+ }
152
+ }
153
+
154
+ function summarize(tool, input) {
155
+ if (!tool || !input) return {};
156
+ switch (tool) {
157
+ case "Read":
158
+ return { file: shortPath(input.file_path) };
159
+ case "Edit":
160
+ return { file: shortPath(input.file_path) };
161
+ case "Write":
162
+ return { file: shortPath(input.file_path) };
163
+ case "Bash":
164
+ return {
165
+ cmd: (input.command || "").slice(0, 150),
166
+ desc: input.description,
167
+ };
168
+ case "Grep":
169
+ return { pattern: input.pattern, path: shortPath(input.path) };
170
+ case "Glob":
171
+ return { pattern: input.pattern };
172
+ case "Task":
173
+ return { desc: input.description, type: input.subagent_type };
174
+ case "WebSearch":
175
+ return { query: input.query };
176
+ case "WebFetch":
177
+ return { url: input.url };
178
+ case "NotebookEdit":
179
+ return { file: shortPath(input.notebook_path) };
180
+ default:
181
+ return {};
182
+ }
183
+ }
184
+
185
+ function shortPath(p) {
186
+ if (!p) return null;
187
+ // Convert absolute paths to relative for readability
188
+ const cwd = process.cwd();
189
+ if (p.startsWith(cwd)) {
190
+ return p.slice(cwd.length + 1).replace(/\\/g, "/");
191
+ }
192
+ // For home-dir paths, abbreviate
193
+ const home = require("os").homedir();
194
+ if (p.startsWith(home)) {
195
+ return "~" + p.slice(home.length).replace(/\\/g, "/");
196
+ }
197
+ return p.replace(/\\/g, "/");
198
+ }