@bhargavvc/sdd-cc 1.30.1 → 1.35.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.
Files changed (242) hide show
  1. package/README.ja-JP.md +144 -110
  2. package/README.ko-KR.md +143 -107
  3. package/README.md +183 -112
  4. package/README.pt-BR.md +90 -52
  5. package/README.zh-CN.md +141 -101
  6. package/agents/sdd-advisor-researcher.md +23 -0
  7. package/agents/sdd-ai-researcher.md +133 -0
  8. package/agents/sdd-code-fixer.md +516 -0
  9. package/agents/sdd-code-reviewer.md +355 -0
  10. package/agents/sdd-codebase-mapper.md +3 -3
  11. package/agents/sdd-debugger.md +17 -5
  12. package/agents/sdd-doc-verifier.md +201 -0
  13. package/agents/sdd-doc-writer.md +602 -0
  14. package/agents/sdd-domain-researcher.md +153 -0
  15. package/agents/sdd-eval-auditor.md +164 -0
  16. package/agents/sdd-eval-planner.md +154 -0
  17. package/agents/sdd-executor.md +87 -4
  18. package/agents/sdd-framework-selector.md +160 -0
  19. package/agents/sdd-intel-updater.md +314 -0
  20. package/agents/sdd-nyquist-auditor.md +1 -1
  21. package/agents/sdd-phase-researcher.md +71 -4
  22. package/agents/sdd-plan-checker.md +100 -6
  23. package/agents/sdd-planner.md +145 -206
  24. package/agents/sdd-project-researcher.md +25 -2
  25. package/agents/sdd-research-synthesizer.md +3 -3
  26. package/agents/sdd-roadmapper.md +6 -6
  27. package/agents/sdd-security-auditor.md +128 -0
  28. package/agents/sdd-ui-auditor.md +43 -3
  29. package/agents/sdd-ui-checker.md +5 -5
  30. package/agents/sdd-ui-researcher.md +27 -4
  31. package/agents/sdd-user-profiler.md +2 -2
  32. package/agents/sdd-verifier.md +142 -22
  33. package/bin/install.js +2145 -545
  34. package/commands/sdd/add-backlog.md +5 -5
  35. package/commands/sdd/add-tests.md +2 -2
  36. package/commands/sdd/ai-integration-phase.md +36 -0
  37. package/commands/sdd/analyze-dependencies.md +34 -0
  38. package/commands/sdd/audit-fix.md +33 -0
  39. package/commands/sdd/autonomous.md +7 -2
  40. package/commands/sdd/cleanup.md +5 -0
  41. package/commands/sdd/code-review-fix.md +52 -0
  42. package/commands/sdd/code-review.md +55 -0
  43. package/commands/sdd/complete-milestone.md +6 -6
  44. package/commands/sdd/debug.md +22 -9
  45. package/commands/sdd/discuss-phase.md +7 -2
  46. package/commands/sdd/do.md +1 -1
  47. package/commands/sdd/docs-update.md +48 -0
  48. package/commands/sdd/eval-review.md +32 -0
  49. package/commands/sdd/execute-phase.md +4 -0
  50. package/commands/sdd/explore.md +27 -0
  51. package/commands/sdd/fast.md +2 -2
  52. package/commands/sdd/from-sdd2.md +45 -0
  53. package/commands/sdd/help.md +2 -0
  54. package/commands/sdd/import.md +36 -0
  55. package/commands/sdd/intel.md +179 -0
  56. package/commands/sdd/join-discord.md +2 -1
  57. package/commands/sdd/manager.md +1 -0
  58. package/commands/sdd/map-codebase.md +3 -3
  59. package/commands/sdd/new-milestone.md +1 -1
  60. package/commands/sdd/new-project.md +5 -1
  61. package/commands/sdd/new-workspace.md +1 -1
  62. package/commands/sdd/next.md +2 -0
  63. package/commands/sdd/plan-milestone-gaps.md +2 -2
  64. package/commands/sdd/plan-phase.md +6 -1
  65. package/commands/sdd/plant-seed.md +1 -1
  66. package/commands/sdd/profile-user.md +1 -1
  67. package/commands/sdd/quick.md +5 -3
  68. package/commands/sdd/reapply-patches.md +230 -42
  69. package/commands/sdd/research-phase.md +3 -3
  70. package/commands/sdd/review-backlog.md +1 -0
  71. package/commands/sdd/review.md +6 -3
  72. package/commands/sdd/scan.md +26 -0
  73. package/commands/sdd/secure-phase.md +35 -0
  74. package/commands/sdd/ship.md +1 -1
  75. package/commands/sdd/thread.md +5 -5
  76. package/commands/sdd/undo.md +34 -0
  77. package/commands/sdd/verify-work.md +1 -1
  78. package/commands/sdd/workstreams.md +17 -11
  79. package/hooks/dist/sdd-check-update.js +33 -8
  80. package/hooks/dist/sdd-context-monitor.js +17 -8
  81. package/hooks/dist/sdd-phase-boundary.sh +27 -0
  82. package/hooks/dist/sdd-prompt-guard.js +1 -0
  83. package/hooks/dist/sdd-read-guard.js +82 -0
  84. package/hooks/dist/sdd-session-state.sh +33 -0
  85. package/hooks/dist/sdd-statusline.js +137 -15
  86. package/hooks/dist/sdd-validate-commit.sh +47 -0
  87. package/hooks/dist/sdd-workflow-guard.js +4 -4
  88. package/hooks/sdd-check-update.js +139 -0
  89. package/hooks/sdd-context-monitor.js +165 -0
  90. package/hooks/sdd-phase-boundary.sh +27 -0
  91. package/hooks/sdd-prompt-guard.js +97 -0
  92. package/hooks/sdd-read-guard.js +82 -0
  93. package/hooks/sdd-session-state.sh +33 -0
  94. package/hooks/sdd-statusline.js +241 -0
  95. package/hooks/sdd-validate-commit.sh +47 -0
  96. package/hooks/sdd-workflow-guard.js +94 -0
  97. package/package.json +3 -3
  98. package/scripts/build-hooks.js +18 -7
  99. package/scripts/prompt-injection-scan.sh +1 -0
  100. package/scripts/rebrand-gsd-to-sdd.sh +221 -220
  101. package/scripts/run-tests.cjs +5 -1
  102. package/scripts/sync-upstream.sh +1 -1
  103. package/sdd/bin/lib/commands.cjs +79 -17
  104. package/sdd/bin/lib/config.cjs +90 -48
  105. package/sdd/bin/lib/core.cjs +452 -87
  106. package/sdd/bin/lib/docs.cjs +267 -0
  107. package/sdd/bin/lib/frontmatter.cjs +381 -336
  108. package/sdd/bin/lib/init.cjs +110 -16
  109. package/sdd/bin/lib/intel.cjs +660 -0
  110. package/sdd/bin/lib/learnings.cjs +378 -0
  111. package/sdd/bin/lib/milestone.cjs +42 -11
  112. package/sdd/bin/lib/model-profiles.cjs +17 -15
  113. package/sdd/bin/lib/phase.cjs +367 -288
  114. package/sdd/bin/lib/profile-output.cjs +106 -10
  115. package/sdd/bin/lib/roadmap.cjs +146 -115
  116. package/sdd/bin/lib/schema-detect.cjs +238 -0
  117. package/sdd/bin/lib/sdd2-import.cjs +511 -0
  118. package/sdd/bin/lib/security.cjs +124 -3
  119. package/sdd/bin/lib/state.cjs +648 -264
  120. package/sdd/bin/lib/template.cjs +8 -4
  121. package/sdd/bin/lib/verify.cjs +209 -28
  122. package/sdd/bin/lib/workstream.cjs +7 -3
  123. package/sdd/bin/sdd-tools.cjs +184 -12
  124. package/sdd/contexts/dev.md +21 -0
  125. package/sdd/contexts/research.md +22 -0
  126. package/sdd/contexts/review.md +22 -0
  127. package/sdd/references/agent-contracts.md +79 -0
  128. package/sdd/references/ai-evals.md +156 -0
  129. package/sdd/references/ai-frameworks.md +186 -0
  130. package/sdd/references/artifact-types.md +113 -0
  131. package/sdd/references/common-bug-patterns.md +114 -0
  132. package/sdd/references/context-budget.md +49 -0
  133. package/sdd/references/continuation-format.md +25 -25
  134. package/sdd/references/domain-probes.md +125 -0
  135. package/sdd/references/few-shot-examples/plan-checker.md +73 -0
  136. package/sdd/references/few-shot-examples/verifier.md +109 -0
  137. package/sdd/references/gate-prompts.md +100 -0
  138. package/sdd/references/gates.md +70 -0
  139. package/sdd/references/git-integration.md +1 -1
  140. package/sdd/references/ios-scaffold.md +123 -0
  141. package/sdd/references/model-profile-resolution.md +2 -0
  142. package/sdd/references/model-profiles.md +24 -18
  143. package/sdd/references/planner-gap-closure.md +62 -0
  144. package/sdd/references/planner-reviews.md +39 -0
  145. package/sdd/references/planner-revision.md +87 -0
  146. package/sdd/references/planning-config.md +252 -0
  147. package/sdd/references/revision-loop.md +97 -0
  148. package/sdd/references/thinking-models-debug.md +44 -0
  149. package/sdd/references/thinking-models-execution.md +50 -0
  150. package/sdd/references/thinking-models-planning.md +62 -0
  151. package/sdd/references/thinking-models-research.md +50 -0
  152. package/sdd/references/thinking-models-verification.md +55 -0
  153. package/sdd/references/thinking-partner.md +96 -0
  154. package/sdd/references/ui-brand.md +4 -4
  155. package/sdd/references/universal-anti-patterns.md +63 -0
  156. package/sdd/references/verification-overrides.md +227 -0
  157. package/sdd/references/workstream-flag.md +56 -3
  158. package/sdd/templates/AI-SPEC.md +246 -0
  159. package/sdd/templates/DEBUG.md +1 -1
  160. package/sdd/templates/SECURITY.md +61 -0
  161. package/sdd/templates/UAT.md +4 -4
  162. package/sdd/templates/VALIDATION.md +4 -4
  163. package/sdd/templates/claude-md.md +32 -9
  164. package/sdd/templates/config.json +4 -0
  165. package/sdd/templates/debug-subagent-prompt.md +1 -1
  166. package/sdd/templates/dev-preferences.md +1 -1
  167. package/sdd/templates/discovery.md +2 -2
  168. package/sdd/templates/phase-prompt.md +1 -1
  169. package/sdd/templates/planner-subagent-prompt.md +3 -3
  170. package/sdd/templates/project.md +1 -1
  171. package/sdd/templates/research.md +1 -1
  172. package/sdd/templates/state.md +2 -2
  173. package/sdd/workflows/add-phase.md +8 -8
  174. package/sdd/workflows/add-tests.md +12 -9
  175. package/sdd/workflows/add-todo.md +5 -3
  176. package/sdd/workflows/ai-integration-phase.md +284 -0
  177. package/sdd/workflows/analyze-dependencies.md +96 -0
  178. package/sdd/workflows/audit-fix.md +157 -0
  179. package/sdd/workflows/audit-milestone.md +11 -11
  180. package/sdd/workflows/audit-uat.md +2 -2
  181. package/sdd/workflows/autonomous.md +195 -27
  182. package/sdd/workflows/check-todos.md +12 -10
  183. package/sdd/workflows/cleanup.md +2 -0
  184. package/sdd/workflows/code-review-fix.md +497 -0
  185. package/sdd/workflows/code-review.md +515 -0
  186. package/sdd/workflows/complete-milestone.md +56 -22
  187. package/sdd/workflows/diagnose-issues.md +10 -3
  188. package/sdd/workflows/discovery-phase.md +5 -3
  189. package/sdd/workflows/discuss-phase-assumptions.md +24 -6
  190. package/sdd/workflows/discuss-phase-power.md +291 -0
  191. package/sdd/workflows/discuss-phase.md +173 -21
  192. package/sdd/workflows/do.md +23 -21
  193. package/sdd/workflows/docs-update.md +1155 -0
  194. package/sdd/workflows/eval-review.md +155 -0
  195. package/sdd/workflows/execute-phase.md +594 -38
  196. package/sdd/workflows/execute-plan.md +67 -96
  197. package/sdd/workflows/explore.md +139 -0
  198. package/sdd/workflows/fast.md +5 -5
  199. package/sdd/workflows/forensics.md +2 -2
  200. package/sdd/workflows/health.md +4 -4
  201. package/sdd/workflows/help.md +122 -119
  202. package/sdd/workflows/import.md +276 -0
  203. package/sdd/workflows/inbox.md +387 -0
  204. package/sdd/workflows/insert-phase.md +7 -7
  205. package/sdd/workflows/list-phase-assumptions.md +4 -4
  206. package/sdd/workflows/list-workspaces.md +2 -2
  207. package/sdd/workflows/manager.md +35 -32
  208. package/sdd/workflows/map-codebase.md +7 -5
  209. package/sdd/workflows/milestone-summary.md +2 -2
  210. package/sdd/workflows/new-milestone.md +17 -9
  211. package/sdd/workflows/new-project.md +50 -25
  212. package/sdd/workflows/new-workspace.md +7 -5
  213. package/sdd/workflows/next.md +67 -11
  214. package/sdd/workflows/note.md +9 -7
  215. package/sdd/workflows/pause-work.md +75 -12
  216. package/sdd/workflows/plan-milestone-gaps.md +8 -8
  217. package/sdd/workflows/plan-phase.md +294 -42
  218. package/sdd/workflows/plant-seed.md +6 -3
  219. package/sdd/workflows/pr-branch.md +42 -14
  220. package/sdd/workflows/profile-user.md +9 -7
  221. package/sdd/workflows/progress.md +45 -45
  222. package/sdd/workflows/quick.md +195 -47
  223. package/sdd/workflows/remove-phase.md +6 -6
  224. package/sdd/workflows/remove-workspace.md +3 -1
  225. package/sdd/workflows/research-phase.md +2 -2
  226. package/sdd/workflows/resume-project.md +12 -12
  227. package/sdd/workflows/review.md +109 -9
  228. package/sdd/workflows/scan.md +102 -0
  229. package/sdd/workflows/secure-phase.md +166 -0
  230. package/sdd/workflows/session-report.md +2 -2
  231. package/sdd/workflows/settings.md +38 -12
  232. package/sdd/workflows/ship.md +21 -9
  233. package/sdd/workflows/stats.md +1 -1
  234. package/sdd/workflows/transition.md +23 -23
  235. package/sdd/workflows/ui-phase.md +15 -7
  236. package/sdd/workflows/ui-review.md +29 -4
  237. package/sdd/workflows/undo.md +314 -0
  238. package/sdd/workflows/update.md +171 -20
  239. package/sdd/workflows/validate-phase.md +6 -4
  240. package/sdd/workflows/verify-phase.md +210 -6
  241. package/sdd/workflows/verify-work.md +83 -9
  242. package/sdd/commands/sdd/workstreams.md +0 -63
@@ -11,7 +11,7 @@ const { spawn } = require('child_process');
11
11
  const homeDir = os.homedir();
12
12
  const cwd = process.cwd();
13
13
 
14
- // Detect runtime config directory (supports Claude, OpenCode, Gemini)
14
+ // Detect runtime config directory (supports Claude, OpenCode, Kilo, Gemini)
15
15
  // Respects CLAUDE_CONFIG_DIR for custom config directory setups
16
16
  function detectConfigDir(baseDir) {
17
17
  // Check env override first (supports multi-account setups)
@@ -19,7 +19,7 @@ function detectConfigDir(baseDir) {
19
19
  if (envDir && fs.existsSync(path.join(envDir, 'sdd', 'VERSION'))) {
20
20
  return envDir;
21
21
  }
22
- for (const dir of ['.config/opencode', '.opencode', '.gemini', '.claude']) {
22
+ for (const dir of ['.claude', '.gemini', '.config/kilo', '.kilo', '.config/opencode', '.opencode']) {
23
23
  if (fs.existsSync(path.join(baseDir, dir, 'sdd', 'VERSION'))) {
24
24
  return path.join(baseDir, dir);
25
25
  }
@@ -29,7 +29,10 @@ function detectConfigDir(baseDir) {
29
29
 
30
30
  const globalConfigDir = detectConfigDir(homeDir);
31
31
  const projectConfigDir = detectConfigDir(cwd);
32
- const cacheDir = path.join(globalConfigDir, 'cache');
32
+ // Use a shared, tool-agnostic cache directory to avoid multi-runtime
33
+ // resolution mismatches where check-update writes to one runtime's cache
34
+ // but statusline reads from another (#1421).
35
+ const cacheDir = path.join(homeDir, '.cache', 'sdd');
33
36
  const cacheFile = path.join(cacheDir, 'sdd-update-check.json');
34
37
 
35
38
  // VERSION file locations (check project first, then global)
@@ -47,6 +50,18 @@ const child = spawn(process.execPath, ['-e', `
47
50
  const path = require('path');
48
51
  const { execSync } = require('child_process');
49
52
 
53
+ // Compare semver: true if a > b (a is strictly newer than b)
54
+ // Strips pre-release suffixes (e.g. '3-beta.1' → '3') to avoid NaN from Number()
55
+ function isNewer(a, b) {
56
+ const pa = (a || '').split('.').map(s => Number(s.replace(/-.*/, '')) || 0);
57
+ const pb = (b || '').split('.').map(s => Number(s.replace(/-.*/, '')) || 0);
58
+ for (let i = 0; i < 3; i++) {
59
+ if (pa[i] > pb[i]) return true;
60
+ if (pa[i] < pb[i]) return false;
61
+ }
62
+ return false;
63
+ }
64
+
50
65
  const cacheFile = ${JSON.stringify(cacheFile)};
51
66
  const projectVersionFile = ${JSON.stringify(projectVersionFile)};
52
67
  const globalVersionFile = ${JSON.stringify(globalVersionFile)};
@@ -65,20 +80,30 @@ const child = spawn(process.execPath, ['-e', `
65
80
  } catch (e) {}
66
81
 
67
82
  // Check for stale hooks — compare hook version headers against installed VERSION
68
- // Hooks live inside sdd/hooks/, not configDir/hooks/
83
+ // Hooks are installed at configDir/hooks/ (e.g. ~/.claude/hooks/) (#1421)
84
+ // Only check hooks that SDD currently ships — orphaned files from removed features
85
+ // (e.g., sdd-intel-*.js) must be ignored to avoid permanent stale warnings (#1750)
86
+ const MANAGED_HOOKS = [
87
+ 'sdd-check-update.js',
88
+ 'sdd-context-monitor.js',
89
+ 'sdd-prompt-guard.js',
90
+ 'sdd-read-guard.js',
91
+ 'sdd-statusline.js',
92
+ 'sdd-workflow-guard.js',
93
+ ];
69
94
  let staleHooks = [];
70
95
  if (configDir) {
71
- const hooksDir = path.join(configDir, 'sdd', 'hooks');
96
+ const hooksDir = path.join(configDir, 'hooks');
72
97
  try {
73
98
  if (fs.existsSync(hooksDir)) {
74
- const hookFiles = fs.readdirSync(hooksDir).filter(f => f.startsWith('sdd-') && f.endsWith('.js'));
99
+ const hookFiles = fs.readdirSync(hooksDir).filter(f => MANAGED_HOOKS.includes(f));
75
100
  for (const hookFile of hookFiles) {
76
101
  try {
77
102
  const content = fs.readFileSync(path.join(hooksDir, hookFile), 'utf8');
78
103
  const versionMatch = content.match(/\\/\\/ sdd-hook-version:\\s*(.+)/);
79
104
  if (versionMatch) {
80
105
  const hookVersion = versionMatch[1].trim();
81
- if (hookVersion !== installed && !hookVersion.includes('{{')) {
106
+ if (isNewer(installed, hookVersion) && !hookVersion.includes('{{')) {
82
107
  staleHooks.push({ file: hookFile, hookVersion, installedVersion: installed });
83
108
  }
84
109
  } else {
@@ -97,7 +122,7 @@ const child = spawn(process.execPath, ['-e', `
97
122
  } catch (e) {}
98
123
 
99
124
  const result = {
100
- update_available: latest && installed !== latest,
125
+ update_available: latest && isNewer(latest, installed),
101
126
  installed,
102
127
  latest: latest || 'unknown',
103
128
  checked: Math.floor(Date.now() / 1000),
@@ -45,17 +45,26 @@ process.stdin.on('end', () => {
45
45
  process.exit(0);
46
46
  }
47
47
 
48
- // Check if context warnings are disabled via config
48
+ // Reject session IDs that contain path traversal sequences or path separators.
49
+ // session_id is used to construct file paths in /tmp — an unsanitized value
50
+ // could escape the temp directory and read or write arbitrary files.
51
+ if (/[/\\]|\.\./.test(sessionId)) {
52
+ process.exit(0);
53
+ }
54
+
55
+ // Check if context warnings are disabled via config.
56
+ // Quick sentinel check: skip config read entirely for non-SDD projects (#P2.5).
49
57
  const cwd = data.cwd || process.cwd();
50
- const configPath = path.join(cwd, '.planning', 'config.json');
51
- if (fs.existsSync(configPath)) {
58
+ const planningDir = path.join(cwd, '.planning');
59
+ if (fs.existsSync(planningDir)) {
52
60
  try {
61
+ const configPath = path.join(planningDir, 'config.json');
53
62
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
54
63
  if (config.hooks?.context_warnings === false) {
55
64
  process.exit(0);
56
65
  }
57
66
  } catch (e) {
58
- // Ignore config parse errors
67
+ // Ignore config read/parse errors (config may not exist in .planning/)
59
68
  }
60
69
  }
61
70
 
@@ -117,22 +126,22 @@ process.stdin.on('end', () => {
117
126
  fs.writeFileSync(warnPath, JSON.stringify(warnData));
118
127
 
119
128
  // Detect if SDD is active (has .planning/STATE.md in working directory)
120
- const isGsdActive = fs.existsSync(path.join(cwd, '.planning', 'STATE.md'));
129
+ const isSddActive = fs.existsSync(path.join(cwd, '.planning', 'STATE.md'));
121
130
 
122
131
  // Build advisory warning message (never use imperative commands that
123
132
  // override user preferences — see #884)
124
133
  let message;
125
134
  if (isCritical) {
126
- message = isGsdActive
135
+ message = isSddActive
127
136
  ? `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
128
137
  'Context is nearly exhausted. Do NOT start new complex work or write handoff files — ' +
129
138
  'SDD state is already tracked in STATE.md. Inform the user so they can run ' +
130
- '/sdd:pause-work at the next natural stopping point.'
139
+ '/sdd-pause-work at the next natural stopping point.'
131
140
  : `CONTEXT CRITICAL: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
132
141
  'Context is nearly exhausted. Inform the user that context is low and ask how they ' +
133
142
  'want to proceed. Do NOT autonomously save state or write handoff files unless the user asks.';
134
143
  } else {
135
- message = isGsdActive
144
+ message = isSddActive
136
145
  ? `CONTEXT WARNING: Usage at ${usedPct}%. Remaining: ${remaining}%. ` +
137
146
  'Context is getting limited. Avoid starting new complex work. If not between ' +
138
147
  'defined plan steps, inform the user so they can prepare to pause.'
@@ -0,0 +1,27 @@
1
+ #!/bin/bash
2
+ # sdd-phase-boundary.sh — PostToolUse hook: detect .planning/ file writes
3
+ # Outputs a reminder when planning files are modified outside normal workflow.
4
+ # Uses Node.js for JSON parsing (always available in SDD projects, no jq dependency).
5
+ #
6
+ # OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
7
+ # Enable with: "hooks": { "community": true } in .planning/config.json
8
+
9
+ # Check opt-in config — exit silently if not enabled
10
+ if [ -f .planning/config.json ]; then
11
+ ENABLED=$(node -e "try{const c=require('./.planning/config.json');process.stdout.write(c.hooks?.community===true?'1':'0')}catch{process.stdout.write('0')}" 2>/dev/null)
12
+ if [ "$ENABLED" != "1" ]; then exit 0; fi
13
+ else
14
+ exit 0
15
+ fi
16
+
17
+ INPUT=$(cat)
18
+
19
+ # Extract file_path from JSON using Node (handles escaping correctly)
20
+ FILE=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.file_path||'')}catch{}})" 2>/dev/null)
21
+
22
+ if [[ "$FILE" == *.planning/* ]] || [[ "$FILE" == .planning/* ]]; then
23
+ echo ".planning/ file modified: $FILE"
24
+ echo "Check: Should STATE.md be updated to reflect this change?"
25
+ fi
26
+
27
+ exit 0
@@ -22,6 +22,7 @@ const INJECTION_PATTERNS = [
22
22
  /forget\s+(all\s+)?(your\s+)?instructions/i,
23
23
  /override\s+(system|previous)\s+(prompt|instructions)/i,
24
24
  /you\s+are\s+now\s+(?:a|an|the)\s+/i,
25
+ /act\s+as\s+(?:a|an|the)\s+(?!plan|phase|wave)/i,
25
26
  /pretend\s+(?:you(?:'re| are)\s+|to\s+be\s+)/i,
26
27
  /from\s+now\s+on,?\s+you\s+(?:are|will|should|must)/i,
27
28
  /(?:print|output|reveal|show|display|repeat)\s+(?:your\s+)?(?:system\s+)?(?:prompt|instructions)/i,
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // sdd-hook-version: {{SDD_VERSION}}
3
+ // SDD Read Guard — PreToolUse hook
4
+ // Injects advisory guidance when Write/Edit targets an existing file,
5
+ // reminding the model to Read the file first.
6
+ //
7
+ // Background: Non-Claude models (e.g. MiniMax M2.5 on OpenCode) don't
8
+ // natively follow the read-before-edit pattern. When they attempt to
9
+ // Write/Edit an existing file without reading it, the runtime rejects
10
+ // with "You must read file before overwriting it." The model retries
11
+ // without reading, creating an infinite loop that burns through usage.
12
+ //
13
+ // This hook prevents that loop by injecting clear guidance BEFORE the
14
+ // tool call reaches the runtime. The model sees the advisory and can
15
+ // issue a Read call on the next turn.
16
+ //
17
+ // Triggers on: Write and Edit tool calls
18
+ // Action: Advisory (does not block) — injects read-first guidance
19
+ // Only fires when the target file already exists on disk.
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ let input = '';
25
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
26
+ process.stdin.setEncoding('utf8');
27
+ process.stdin.on('data', chunk => input += chunk);
28
+ process.stdin.on('end', () => {
29
+ clearTimeout(stdinTimeout);
30
+ try {
31
+ const data = JSON.parse(input);
32
+ const toolName = data.tool_name;
33
+
34
+ // Only intercept Write and Edit tool calls
35
+ if (toolName !== 'Write' && toolName !== 'Edit') {
36
+ process.exit(0);
37
+ }
38
+
39
+ // Claude Code natively enforces read-before-edit — skip the advisory (#1984)
40
+ if (process.env.CLAUDE_SESSION_ID) {
41
+ process.exit(0);
42
+ }
43
+
44
+ const filePath = data.tool_input?.file_path || '';
45
+ if (!filePath) {
46
+ process.exit(0);
47
+ }
48
+
49
+ // Only inject guidance when the file already exists.
50
+ // New files don't need a prior Read — the runtime allows creating them directly.
51
+ let fileExists = false;
52
+ try {
53
+ fs.accessSync(filePath, fs.constants.F_OK);
54
+ fileExists = true;
55
+ } catch {
56
+ // File does not exist — no guidance needed
57
+ }
58
+
59
+ if (!fileExists) {
60
+ process.exit(0);
61
+ }
62
+
63
+ const fileName = path.basename(filePath);
64
+
65
+ // Advisory guidance — does not block the operation
66
+ const output = {
67
+ hookSpecificOutput: {
68
+ hookEventName: 'PreToolUse',
69
+ additionalContext:
70
+ `READ-BEFORE-EDIT REMINDER: You are about to modify "${fileName}" which already exists. ` +
71
+ 'If you have not already used the Read tool to read this file in the current session, ' +
72
+ 'you MUST Read it first before editing. The runtime will reject edits to files that ' +
73
+ 'have not been read. Use the Read tool on this file path, then retry your edit.',
74
+ },
75
+ };
76
+
77
+ process.stdout.write(JSON.stringify(output));
78
+ } catch {
79
+ // Silent fail — never block tool execution
80
+ process.exit(0);
81
+ }
82
+ });
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ # sdd-session-state.sh — SessionStart hook: inject project state reminder
3
+ # Outputs STATE.md head on every session start for orientation.
4
+ #
5
+ # OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
6
+ # Enable with: "hooks": { "community": true } in .planning/config.json
7
+
8
+ # Check opt-in config — exit silently if not enabled
9
+ if [ -f .planning/config.json ]; then
10
+ ENABLED=$(node -e "try{const c=require('./.planning/config.json');process.stdout.write(c.hooks?.community===true?'1':'0')}catch{process.stdout.write('0')}" 2>/dev/null)
11
+ if [ "$ENABLED" != "1" ]; then exit 0; fi
12
+ else
13
+ exit 0
14
+ fi
15
+
16
+ echo '## Project State Reminder'
17
+ echo ''
18
+
19
+ if [ -f .planning/STATE.md ]; then
20
+ echo 'STATE.md exists - check for blockers and current phase.'
21
+ head -20 .planning/STATE.md
22
+ else
23
+ echo 'No .planning/ found - suggest /sdd-new-project if starting new work.'
24
+ fi
25
+
26
+ echo ''
27
+
28
+ if [ -f .planning/config.json ]; then
29
+ MODE=$(grep -o '"mode"[[:space:]]*:[[:space:]]*"[^"]*"' .planning/config.json 2>/dev/null || echo '"mode": "unknown"')
30
+ echo "Config: $MODE"
31
+ fi
32
+
33
+ exit 0
@@ -1,20 +1,120 @@
1
1
  #!/usr/bin/env node
2
2
  // sdd-hook-version: {{SDD_VERSION}}
3
3
  // Claude Code Statusline - SDD Edition
4
- // Shows: model | current task | directory | context usage
4
+ // Shows: model | current task (or SDD state) | directory | context usage
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
  const os = require('os');
9
9
 
10
- // Read JSON from stdin
11
- let input = '';
12
- // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
13
- // Windows/Git Bash), exit silently instead of hanging. See #775.
14
- const stdinTimeout = setTimeout(() => process.exit(0), 3000);
15
- process.stdin.setEncoding('utf8');
16
- process.stdin.on('data', chunk => input += chunk);
17
- process.stdin.on('end', () => {
10
+ // --- SDD state reader -------------------------------------------------------
11
+
12
+ /**
13
+ * Walk up from dir looking for .planning/STATE.md.
14
+ * Returns parsed state object or null.
15
+ */
16
+ function readSddState(dir) {
17
+ const home = os.homedir();
18
+ let current = dir;
19
+ for (let i = 0; i < 10; i++) {
20
+ const candidate = path.join(current, '.planning', 'STATE.md');
21
+ if (fs.existsSync(candidate)) {
22
+ try {
23
+ return parseStateMd(fs.readFileSync(candidate, 'utf8'));
24
+ } catch (e) {
25
+ return null;
26
+ }
27
+ }
28
+ const parent = path.dirname(current);
29
+ if (parent === current || current === home) break;
30
+ current = parent;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ /**
36
+ * Parse STATE.md frontmatter + Phase line from body.
37
+ * Returns { status, milestone, milestoneName, phaseNum, phaseTotal, phaseName }
38
+ */
39
+ function parseStateMd(content) {
40
+ const state = {};
41
+
42
+ // YAML frontmatter between --- markers
43
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
44
+ if (fmMatch) {
45
+ for (const line of fmMatch[1].split('\n')) {
46
+ const m = line.match(/^(\w+):\s*(.+)/);
47
+ if (!m) continue;
48
+ const [, key, val] = m;
49
+ const v = val.trim().replace(/^["']|["']$/g, '');
50
+ if (key === 'status') state.status = v === 'null' ? null : v;
51
+ if (key === 'milestone') state.milestone = v === 'null' ? null : v;
52
+ if (key === 'milestone_name') state.milestoneName = v === 'null' ? null : v;
53
+ }
54
+ }
55
+
56
+ // Phase: N of M (name) or Phase: none active (...)
57
+ const phaseMatch = content.match(/^Phase:\s*(\d+)\s+of\s+(\d+)(?:\s+\(([^)]+)\))?/m);
58
+ if (phaseMatch) {
59
+ state.phaseNum = phaseMatch[1];
60
+ state.phaseTotal = phaseMatch[2];
61
+ state.phaseName = phaseMatch[3] || null;
62
+ }
63
+
64
+ // Fallback: parse Status: from body when frontmatter is absent
65
+ if (!state.status) {
66
+ const bodyStatus = content.match(/^Status:\s*(.+)/m);
67
+ if (bodyStatus) {
68
+ const raw = bodyStatus[1].trim().toLowerCase();
69
+ if (raw.includes('ready to plan') || raw.includes('planning')) state.status = 'planning';
70
+ else if (raw.includes('execut')) state.status = 'executing';
71
+ else if (raw.includes('complet') || raw.includes('archived')) state.status = 'complete';
72
+ }
73
+ }
74
+
75
+ return state;
76
+ }
77
+
78
+ /**
79
+ * Format SDD state into display string.
80
+ * Format: "v1.9 Code Quality · executing · fix-graphiti-deployment (1/5)"
81
+ * Gracefully degrades when parts are missing.
82
+ */
83
+ function formatSddState(s) {
84
+ const parts = [];
85
+
86
+ // Milestone: version + name (skip placeholder "milestone")
87
+ if (s.milestone || s.milestoneName) {
88
+ const ver = s.milestone || '';
89
+ const name = (s.milestoneName && s.milestoneName !== 'milestone') ? s.milestoneName : '';
90
+ const ms = [ver, name].filter(Boolean).join(' ');
91
+ if (ms) parts.push(ms);
92
+ }
93
+
94
+ // Status
95
+ if (s.status) parts.push(s.status);
96
+
97
+ // Phase
98
+ if (s.phaseNum && s.phaseTotal) {
99
+ const phase = s.phaseName
100
+ ? `${s.phaseName} (${s.phaseNum}/${s.phaseTotal})`
101
+ : `ph ${s.phaseNum}/${s.phaseTotal}`;
102
+ parts.push(phase);
103
+ }
104
+
105
+ return parts.join(' · ');
106
+ }
107
+
108
+ // --- stdin ------------------------------------------------------------------
109
+
110
+ function runStatusline() {
111
+ let input = '';
112
+ // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
113
+ // Windows/Git Bash), exit silently instead of hanging. See #775.
114
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
115
+ process.stdin.setEncoding('utf8');
116
+ process.stdin.on('data', chunk => input += chunk);
117
+ process.stdin.on('end', () => {
18
118
  clearTimeout(stdinTimeout);
19
119
  try {
20
120
  const data = JSON.parse(input);
@@ -35,7 +135,10 @@ process.stdin.on('end', () => {
35
135
 
36
136
  // Write context metrics to bridge file for the context-monitor PostToolUse hook.
37
137
  // The monitor reads this file to inject agent-facing warnings when context is low.
38
- if (session) {
138
+ // Reject session IDs with path separators or traversal sequences to prevent
139
+ // a malicious session_id from writing files outside the temp directory.
140
+ const sessionSafe = session && !/[/\\]|\.\./.test(session);
141
+ if (sessionSafe) {
39
142
  try {
40
143
  const bridgePath = path.join(os.tmpdir(), `claude-ctx-${session}.json`);
41
144
  const bridgeData = JSON.stringify({
@@ -91,25 +194,38 @@ process.stdin.on('end', () => {
91
194
  }
92
195
  }
93
196
 
197
+ // SDD state (milestone · status · phase) — shown when no todo task
198
+ const sddStateStr = task ? '' : formatSddState(readSddState(dir) || {});
199
+
94
200
  // SDD update available?
201
+ // Check shared cache first (#1421), fall back to runtime-specific cache for
202
+ // backward compatibility with older sdd-check-update.js versions.
95
203
  let sddUpdate = '';
96
- const cacheFile = path.join(claudeDir, 'cache', 'sdd-update-check.json');
204
+ const sharedCacheFile = path.join(homeDir, '.cache', 'sdd', 'sdd-update-check.json');
205
+ const legacyCacheFile = path.join(claudeDir, 'cache', 'sdd-update-check.json');
206
+ const cacheFile = fs.existsSync(sharedCacheFile) ? sharedCacheFile : legacyCacheFile;
97
207
  if (fs.existsSync(cacheFile)) {
98
208
  try {
99
209
  const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
100
210
  if (cache.update_available) {
101
- sddUpdate = '\x1b[33m⬆ /sdd:update\x1b[0m │ ';
211
+ sddUpdate = '\x1b[33m⬆ /sdd-update\x1b[0m │ ';
102
212
  }
103
213
  if (cache.stale_hooks && cache.stale_hooks.length > 0) {
104
- sddUpdate += '\x1b[31m⚠ stale hooks — run /sdd:update\x1b[0m │ ';
214
+ sddUpdate += '\x1b[31m⚠ stale hooks — run /sdd-update\x1b[0m │ ';
105
215
  }
106
216
  } catch (e) {}
107
217
  }
108
218
 
109
219
  // Output
110
220
  const dirname = path.basename(dir);
111
- if (task) {
112
- process.stdout.write(`${sddUpdate}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
221
+ const middle = task
222
+ ? `\x1b[1m${task}\x1b[0m`
223
+ : sddStateStr
224
+ ? `\x1b[2m${sddStateStr}\x1b[0m`
225
+ : null;
226
+
227
+ if (middle) {
228
+ process.stdout.write(`${sddUpdate}\x1b[2m${model}\x1b[0m │ ${middle} │ \x1b[2m${dirname}\x1b[0m${ctx}`);
113
229
  } else {
114
230
  process.stdout.write(`${sddUpdate}\x1b[2m${model}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
115
231
  }
@@ -117,3 +233,9 @@ process.stdin.on('end', () => {
117
233
  // Silent fail - don't break statusline on parse errors
118
234
  }
119
235
  });
236
+ }
237
+
238
+ // Export helpers for unit tests. Harmless when run as a script.
239
+ module.exports = { readSddState, parseStateMd, formatSddState };
240
+
241
+ if (require.main === module) runStatusline();
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # sdd-validate-commit.sh — PreToolUse hook: enforce Conventional Commits format
3
+ # Blocks git commit commands with non-conforming messages (exit 2).
4
+ # Allows conforming messages and all non-commit commands (exit 0).
5
+ # Uses Node.js for JSON parsing (always available in SDD projects, no jq dependency).
6
+ #
7
+ # OPT-IN: This hook is a no-op unless config.json has hooks.community: true.
8
+ # Enable with: "hooks": { "community": true } in .planning/config.json
9
+
10
+ # Check opt-in config — exit silently if not enabled
11
+ if [ -f .planning/config.json ]; then
12
+ ENABLED=$(node -e "try{const c=require('./.planning/config.json');process.stdout.write(c.hooks?.community===true?'1':'0')}catch{process.stdout.write('0')}" 2>/dev/null)
13
+ if [ "$ENABLED" != "1" ]; then exit 0; fi
14
+ else
15
+ exit 0
16
+ fi
17
+
18
+ INPUT=$(cat)
19
+
20
+ # Extract command from JSON using Node (handles escaping correctly, no jq needed)
21
+ CMD=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.command||'')}catch{}})" 2>/dev/null)
22
+
23
+ # Only check git commit commands
24
+ if [[ "$CMD" =~ ^git[[:space:]]+commit ]]; then
25
+ # Extract message from -m flag
26
+ MSG=""
27
+ if [[ "$CMD" =~ -m[[:space:]]+\"([^\"]+)\" ]]; then
28
+ MSG="${BASH_REMATCH[1]}"
29
+ elif [[ "$CMD" =~ -m[[:space:]]+\'([^\']+)\' ]]; then
30
+ MSG="${BASH_REMATCH[1]}"
31
+ fi
32
+
33
+ if [ -n "$MSG" ]; then
34
+ SUBJECT=$(echo "$MSG" | head -1)
35
+ # Validate Conventional Commits format
36
+ if ! [[ "$SUBJECT" =~ ^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)(\(.+\))?:[[:space:]].+ ]]; then
37
+ echo '{"decision": "block", "reason": "Commit message must follow Conventional Commits: <type>(<scope>): <subject>. Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore. Subject must be <=72 chars, lowercase, imperative mood, no trailing period."}'
38
+ exit 2
39
+ fi
40
+ if [ ${#SUBJECT} -gt 72 ]; then
41
+ echo '{"decision": "block", "reason": "Commit subject must be 72 characters or less."}'
42
+ exit 2
43
+ fi
44
+ fi
45
+ fi
46
+
47
+ exit 0
@@ -2,10 +2,10 @@
2
2
  // sdd-hook-version: {{SDD_VERSION}}
3
3
  // SDD Workflow Guard — PreToolUse hook
4
4
  // Detects when Claude attempts file edits outside a SDD workflow context
5
- // (no active /sdd: command or Task subagent) and injects an advisory warning.
5
+ // (no active /sdd- skill or Task subagent) and injects an advisory warning.
6
6
  //
7
7
  // This is a SOFT guard — it advises, not blocks. The edit still proceeds.
8
- // The warning nudges Claude to use /sdd:quick or /sdd:fast instead of
8
+ // The warning nudges Claude to use /sdd-quick or /sdd-fast instead of
9
9
  // making direct edits that bypass state tracking.
10
10
  //
11
11
  // Enable via config: hooks.workflow_guard: true (default: false)
@@ -29,7 +29,7 @@ process.stdin.on('end', () => {
29
29
  process.exit(0);
30
30
  }
31
31
 
32
- // Check if we're inside a SDD workflow (Task subagent or /sdd: command)
32
+ // Check if we're inside a SDD workflow (Task subagent or /sdd- skill)
33
33
  // Subagents have a session_id that differs from the parent
34
34
  // and typically have a description field set by the orchestrator
35
35
  if (data.tool_input?.is_subagent || data.session_type === 'task') {
@@ -80,7 +80,7 @@ process.stdin.on('end', () => {
80
80
  hookEventName: "PreToolUse",
81
81
  additionalContext: `⚠️ WORKFLOW ADVISORY: You're editing ${path.basename(filePath)} directly without a SDD command. ` +
82
82
  'This edit will not be tracked in STATE.md or produce a SUMMARY.md. ' +
83
- 'Consider using /sdd:fast for trivial fixes or /sdd:quick for larger changes ' +
83
+ 'Consider using /sdd-fast for trivial fixes or /sdd-quick for larger changes ' +
84
84
  'to maintain project state tracking. ' +
85
85
  'If this is intentional (e.g., user explicitly asked for a direct edit), proceed normally.'
86
86
  }