cc-devflow 2.4.6 → 4.1.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 (191) hide show
  1. package/.claude/CLAUDE.md +1065 -48
  2. package/.claude/agents/dev-implementer.md +195 -0
  3. package/.claude/commands/{flow-archive.md → flow/archive.md} +46 -11
  4. package/.claude/commands/flow/context.md +150 -0
  5. package/.claude/commands/flow/delta.md +245 -0
  6. package/.claude/commands/{flow-dev.md → flow/dev.md} +112 -11
  7. package/.claude/commands/flow/init.md +45 -0
  8. package/.claude/commands/flow/quality.md +159 -0
  9. package/.claude/commands/flow/spec.md +186 -0
  10. package/.claude/commands/flow/workspace.md +146 -0
  11. package/.claude/commands/{cancel-ralph.md → util/cancel-ralph.md} +1 -0
  12. package/.claude/config/quality-gates.yml +305 -0
  13. package/.claude/docs/guides/TEAM_MODE_GUIDE.md +313 -0
  14. package/.claude/docs/templates/DELTA_SPEC_TEMPLATE.md +91 -0
  15. package/.claude/docs/templates/DESIGN_DECISIONS_TEMPLATE.md +151 -0
  16. package/.claude/docs/templates/JOURNAL_TEMPLATE.md +75 -0
  17. package/.claude/docs/templates/_shared/CLAUDE.md +36 -0
  18. package/.claude/docs/templates/_shared/CONSTITUTION_CHECK.md +125 -0
  19. package/.claude/docs/templates/_shared/VALIDATION_CHECKLIST.md +187 -0
  20. package/.claude/docs/templates/_shared/YAML_FRONTMATTER.md +164 -0
  21. package/.claude/docs/templates/context/dev.jsonl.template +6 -0
  22. package/.claude/docs/templates/context/epic.jsonl.template +5 -0
  23. package/.claude/docs/templates/context/prd.jsonl.template +4 -0
  24. package/.claude/docs/templates/context/research.jsonl.template +4 -0
  25. package/.claude/docs/templates/context/review.jsonl.template +5 -0
  26. package/.claude/docs/templates/context/tech.jsonl.template +5 -0
  27. package/.claude/hooks/CLAUDE.md +342 -0
  28. package/.claude/hooks/inject-agent-context.ts +480 -0
  29. package/.claude/hooks/inject-skill-context.ts +359 -0
  30. package/.claude/hooks/ralph-loop.ts +931 -0
  31. package/.claude/hooks/task-completed-hook.ts +593 -0
  32. package/.claude/hooks/teammate-idle-hook.ts +690 -0
  33. package/.claude/hooks/types/team-types.d.ts +238 -0
  34. package/.claude/rules/devflow-conventions.md +82 -9
  35. package/.claude/scripts/archive-requirement.sh +44 -1
  36. package/.claude/scripts/common.sh +670 -3
  37. package/.claude/scripts/delta-parser.ts +527 -0
  38. package/.claude/scripts/detect-file-conflicts.sh +151 -0
  39. package/.claude/scripts/flow-context-add.sh +134 -0
  40. package/.claude/scripts/flow-context-init.sh +133 -0
  41. package/.claude/scripts/flow-context-validate.sh +144 -0
  42. package/.claude/scripts/flow-delta-apply.sh +297 -0
  43. package/.claude/scripts/flow-delta-archive.sh +71 -0
  44. package/.claude/scripts/flow-delta-create.sh +202 -0
  45. package/.claude/scripts/flow-delta-list.sh +142 -0
  46. package/.claude/scripts/flow-delta-status.sh +235 -0
  47. package/.claude/scripts/flow-quality-full.sh +184 -0
  48. package/.claude/scripts/flow-quality-quick.sh +64 -0
  49. package/.claude/scripts/flow-workspace-init.sh +117 -0
  50. package/.claude/scripts/flow-workspace-record.sh +164 -0
  51. package/.claude/scripts/flow-workspace-start.sh +88 -0
  52. package/.claude/scripts/get-workflow-status.sh +415 -0
  53. package/.claude/scripts/parse-task-dependencies.js +334 -0
  54. package/.claude/scripts/record-quality-error.sh +165 -0
  55. package/.claude/scripts/run-quality-gates.sh +242 -0
  56. package/.claude/scripts/team-dev-init.sh +319 -0
  57. package/.claude/scripts/team-state-recovery.sh +229 -0
  58. package/.claude/scripts/workflow-status.ts +433 -0
  59. package/.claude/settings.json +19 -0
  60. package/.claude/skills/cc-devflow-orchestrator/SKILL.md +85 -200
  61. package/.claude/skills/domain/using-git-worktrees/SKILL.md +252 -0
  62. package/.claude/skills/domain/using-git-worktrees/assets/SHELL_ALIASES.md +133 -0
  63. package/.claude/skills/domain/using-git-worktrees/context.jsonl +4 -0
  64. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-cleanup.sh +218 -0
  65. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-create.sh +232 -0
  66. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-list.sh +130 -0
  67. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-status.sh +140 -0
  68. package/.claude/skills/domain/using-git-worktrees/scripts/worktree-switch.sh +70 -0
  69. package/.claude/skills/skill-rules.json +72 -1
  70. package/.claude/skills/utility/journey-checker/SKILL.md +199 -0
  71. package/.claude/skills/utility/journey-checker/pressure-scenarios.md +164 -0
  72. package/.claude/skills/utility/skill-creator/LICENSE.txt +202 -0
  73. package/.claude/skills/utility/skill-creator/SKILL.md +356 -0
  74. package/.claude/skills/utility/skill-creator/references/output-patterns.md +82 -0
  75. package/.claude/skills/utility/skill-creator/references/workflows.md +28 -0
  76. package/.claude/skills/utility/skill-creator/scripts/init_skill.py +303 -0
  77. package/.claude/skills/utility/skill-creator/scripts/package_skill.py +110 -0
  78. package/.claude/skills/utility/skill-creator/scripts/quick_validate.py +95 -0
  79. package/.claude/skills/workflow/flow-dev/CLAUDE.md +78 -0
  80. package/.claude/skills/workflow/flow-dev/SKILL.md +96 -0
  81. package/.claude/skills/workflow/flow-dev/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +71 -0
  82. package/.claude/skills/workflow/flow-dev/context.jsonl +8 -0
  83. package/.claude/skills/workflow/flow-dev/dev-implementer.jsonl +8 -0
  84. package/.claude/skills/workflow/flow-dev/scripts/entry-gate.sh +116 -0
  85. package/.claude/skills/workflow/flow-dev/scripts/exit-gate.sh +101 -0
  86. package/.claude/skills/workflow/flow-dev/scripts/task-orchestrator.sh +106 -0
  87. package/.claude/skills/workflow/flow-fix/SKILL.md +105 -0
  88. package/.claude/skills/workflow/flow-fix/context.jsonl +6 -0
  89. package/.claude/skills/workflow/flow-fix/references/bug-analyzer.md +381 -0
  90. package/.claude/skills/workflow/flow-init/SKILL.md +211 -0
  91. package/.claude/skills/workflow/flow-init/assets/BRAINSTORM_TEMPLATE.md +148 -0
  92. package/.claude/skills/workflow/flow-init/assets/INIT_FLOW_TEMPLATE.md +198 -0
  93. package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +276 -0
  94. package/.claude/skills/workflow/flow-init/context.jsonl +5 -0
  95. package/.claude/skills/workflow/flow-init/references/flow-researcher.md +132 -0
  96. package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +232 -0
  97. package/.claude/skills/workflow/flow-init/scripts/consolidate-research.sh +182 -0
  98. package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +515 -0
  99. package/.claude/skills/workflow/flow-init/scripts/generate-research-tasks.sh +157 -0
  100. package/.claude/skills/workflow/flow-init/scripts/populate-research-tasks.sh +284 -0
  101. package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +332 -0
  102. package/.claude/skills/workflow/flow-quality/SKILL.md +94 -0
  103. package/.claude/skills/workflow/flow-quality/context.jsonl +6 -0
  104. package/.claude/skills/workflow/flow-quality/references/code-quality-reviewer.md +205 -0
  105. package/.claude/skills/workflow/flow-quality/references/qa-tester.md +313 -0
  106. package/.claude/skills/workflow/flow-quality/references/security-reviewer.md +314 -0
  107. package/.claude/skills/workflow/flow-quality/references/spec-reviewer.md +221 -0
  108. package/.claude/skills/workflow/flow-release/SKILL.md +126 -0
  109. package/.claude/skills/workflow/flow-release/context.jsonl +7 -0
  110. package/.claude/skills/workflow/flow-release/references/release-manager.md +295 -0
  111. package/.claude/skills/workflow/flow-spec/CLAUDE.md +103 -0
  112. package/.claude/skills/workflow/flow-spec/SKILL.md +545 -0
  113. package/.claude/skills/workflow/flow-spec/context.jsonl +7 -0
  114. package/.claude/skills/workflow/flow-spec/scripts/entry-gate.sh +194 -0
  115. package/.claude/skills/workflow/flow-spec/scripts/exit-gate.sh +244 -0
  116. package/.claude/skills/workflow/flow-spec/scripts/parallel-orchestrator.sh +205 -0
  117. package/.claude/skills/workflow/flow-spec/scripts/team-communication.sh +353 -0
  118. package/.claude/skills/workflow/flow-spec/scripts/team-init.sh +195 -0
  119. package/.claude/skills/workflow/flow-spec/scripts/test-team-mode.sh +496 -0
  120. package/.claude/skills/workflow/flow-spec/team-config.json +165 -0
  121. package/.claude/skills/workflow.yaml +417 -0
  122. package/CHANGELOG.md +254 -0
  123. package/README.md +193 -33
  124. package/README.zh-CN.md +206 -46
  125. package/lib/compiler/CLAUDE.md +77 -46
  126. package/lib/compiler/__tests__/multi-module-emitters.test.js +508 -0
  127. package/lib/compiler/context-expander.js +179 -0
  128. package/lib/compiler/emitters/antigravity-emitter.js +195 -5
  129. package/lib/compiler/emitters/base-emitter.js +217 -2
  130. package/lib/compiler/emitters/codex-emitter.js +200 -4
  131. package/lib/compiler/emitters/cursor-emitter.js +307 -3
  132. package/lib/compiler/emitters/qwen-emitter.js +196 -4
  133. package/lib/compiler/index.js +197 -2
  134. package/lib/compiler/platforms.js +270 -21
  135. package/package.json +1 -1
  136. package/.claude/commands/flow-epic.md +0 -183
  137. package/.claude/commands/flow-init.md +0 -370
  138. package/.claude/commands/flow-prd.md +0 -144
  139. package/.claude/commands/flow-qa.md +0 -93
  140. package/.claude/commands/flow-review.md +0 -257
  141. package/.claude/commands/flow-tech.md +0 -142
  142. package/.claude/commands/flow-ui.md +0 -189
  143. package/.claude/skills/file-header-guardian/SKILL.md +0 -56
  144. package/.claude/skills/skill-developer/ADVANCED.md +0 -197
  145. package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +0 -306
  146. package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +0 -152
  147. package/.claude/skills/skill-developer/SKILL.md +0 -426
  148. package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +0 -315
  149. package/.claude/skills/skill-developer/TRIGGER_TYPES.md +0 -305
  150. package/.claude/skills/skill-developer/TROUBLESHOOTING.md +0 -514
  151. package/.claude/skills/writing-skills/SKILL.md +0 -655
  152. package/.claude/skills/writing-skills/anthropic-best-practices.md +0 -1150
  153. package/.claude/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
  154. package/.claude/skills/writing-skills/graphviz-conventions.dot +0 -172
  155. package/.claude/skills/writing-skills/persuasion-principles.md +0 -187
  156. package/.claude/skills/writing-skills/render-graphs.js +0 -168
  157. package/.claude/skills/writing-skills/testing-skills-with-subagents.md +0 -384
  158. package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +0 -1
  159. package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +0 -1
  160. /package/.claude/commands/{core-architecture.md → core/architecture.md} +0 -0
  161. /package/.claude/commands/{core-guidelines.md → core/guidelines.md} +0 -0
  162. /package/.claude/commands/{core-roadmap.md → core/roadmap.md} +0 -0
  163. /package/.claude/commands/{core-style.md → core/style.md} +0 -0
  164. /package/.claude/commands/{flow-checklist.md → flow/checklist.md} +0 -0
  165. /package/.claude/commands/{flow-clarify.md → flow/clarify.md} +0 -0
  166. /package/.claude/commands/{flow-constitution.md → flow/constitution.md} +0 -0
  167. /package/.claude/commands/{flow-fix.md → flow/fix.md} +0 -0
  168. /package/.claude/commands/{flow-ideate.md → flow/ideate.md} +0 -0
  169. /package/.claude/commands/{flow-new.md → flow/new.md} +0 -0
  170. /package/.claude/commands/{flow-release.md → flow/release.md} +0 -0
  171. /package/.claude/commands/{flow-restart.md → flow/restart.md} +0 -0
  172. /package/.claude/commands/{flow-status.md → flow/status.md} +0 -0
  173. /package/.claude/commands/{flow-update.md → flow/update.md} +0 -0
  174. /package/.claude/commands/{flow-upgrade.md → flow/upgrade.md} +0 -0
  175. /package/.claude/commands/{flow-verify.md → flow/verify.md} +0 -0
  176. /package/.claude/commands/{code-review-high.md → util/code-review.md} +0 -0
  177. /package/.claude/commands/{git-commit.md → util/git-commit.md} +0 -0
  178. /package/.claude/commands/{problem-analyzer.md → util/problem-analyzer.md} +0 -0
  179. /package/.claude/skills/{flow-attention-refresh → domain/attention-refresh}/SKILL.md +0 -0
  180. /package/.claude/skills/{flow-brainstorming → domain/brainstorming}/SKILL.md +0 -0
  181. /package/.claude/skills/{flow-debugging → domain/debugging}/SKILL.md +0 -0
  182. /package/.claude/skills/{flow-finishing-branch → domain/finishing-branch}/SKILL.md +0 -0
  183. /package/.claude/skills/{flow-receiving-review → domain/receiving-review}/SKILL.md +0 -0
  184. /package/.claude/skills/{flow-tdd → domain/tdd}/SKILL.md +0 -0
  185. /package/.claude/skills/{verification-before-completion → domain/verification}/SKILL.md +0 -0
  186. /package/.claude/skills/{constitution-guardian → guardrail/constitution-guardian}/SKILL.md +0 -0
  187. /package/.claude/skills/{devflow-tdd-enforcer → guardrail/tdd-enforcer}/SKILL.md +0 -0
  188. /package/.claude/skills/{devflow-constitution-quick-ref → utility/constitution-quick-ref}/SKILL.md +0 -0
  189. /package/.claude/skills/{devflow-file-standards → utility/file-standards}/SKILL.md +0 -0
  190. /package/.claude/skills/{fractal-docs-generator → utility/fractal-docs}/SKILL.md +0 -0
  191. /package/.claude/skills/{npm-release → utility/npm-release}/SKILL.md +0 -0
@@ -0,0 +1,480 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * [INPUT]: 依赖 fs, path, child_process
4
+ * [OUTPUT]: PreToolUse hook 拦截 Task tool 调用,注入上下文
5
+ * [POS]: hooks 的核心上下文注入逻辑,被 settings.json 配置触发
6
+ * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
7
+ *
8
+ * ==============================================
9
+ * PreToolUse Hook: Agent Context Injection (RM-015)
10
+ * ==============================================
11
+ *
12
+ * 借鉴 Trellis 的 inject-subagent-context.py 实现
13
+ *
14
+ * 功能: 拦截 Task tool 调用,根据 subagent_type 注入对应的上下文
15
+ *
16
+ * 工作流程:
17
+ * 1. 检测 Task tool 调用
18
+ * 2. 获取 subagent_type 参数
19
+ * 3. 定位当前任务目录 (从 .claude/.current-task 或环境变量或分支名)
20
+ * 4. 读取对应的 {subagent_type}.jsonl 文件
21
+ * 5. 解析 JSONL,读取每个文件内容
22
+ * 6. 注入到 prompt 参数中
23
+ *
24
+ * JSONL 格式 (Trellis 风格):
25
+ * {"file": "path/to/file.md", "reason": "Why this file is needed"}
26
+ * {"file": "path/to/dir/", "type": "directory", "reason": "Why this dir"}
27
+ *
28
+ * Exit Codes:
29
+ * 0 - Allow (context injected or no injection needed)
30
+ */
31
+
32
+ import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
33
+ import { join, resolve, dirname } from 'path';
34
+ import { execSync } from 'child_process';
35
+
36
+ // ============================================================================
37
+ // Constants
38
+ // ============================================================================
39
+
40
+ const DIR_WORKFLOW = '.claude';
41
+ const DIR_SKILLS = 'skills/workflow';
42
+ const DIR_REQUIREMENTS = 'devflow/requirements';
43
+ const FILE_CURRENT_TASK = '.current-task';
44
+
45
+ // Agent type to skill directory mapping
46
+ const AGENT_SKILL_MAP: Record<string, string> = {
47
+ 'flow-researcher': 'flow-init',
48
+ 'prd-writer': 'flow-spec',
49
+ 'tech-architect': 'flow-spec',
50
+ 'ui-designer': 'flow-spec',
51
+ 'planner': 'flow-spec',
52
+ 'dev-implementer': 'flow-dev',
53
+ 'qa-tester': 'flow-quality',
54
+ 'security-reviewer': 'flow-quality',
55
+ 'release-manager': 'flow-release',
56
+ };
57
+
58
+ // Agent type to JSONL filename mapping
59
+ const AGENT_JSONL_MAP: Record<string, string> = {
60
+ 'flow-researcher': 'researcher.jsonl',
61
+ 'prd-writer': 'prd-writer.jsonl',
62
+ 'tech-architect': 'tech-architect.jsonl',
63
+ 'ui-designer': 'ui-designer.jsonl',
64
+ 'planner': 'planner.jsonl',
65
+ 'dev-implementer': 'dev-implementer.jsonl',
66
+ 'qa-tester': 'qa-tester.jsonl',
67
+ 'security-reviewer': 'security-reviewer.jsonl',
68
+ 'release-manager': 'release-manager.jsonl',
69
+ };
70
+
71
+ const MAX_FILE_SIZE = 50000; // 50KB per file
72
+ const MAX_TOTAL_SIZE = 200000; // 200KB total
73
+
74
+ // ============================================================================
75
+ // JSONL Entry Types (Trellis Style)
76
+ // ============================================================================
77
+
78
+ interface JsonlEntry {
79
+ file?: string;
80
+ path?: string;
81
+ type?: 'file' | 'directory';
82
+ reason: string;
83
+ required?: boolean;
84
+ optional?: boolean;
85
+ }
86
+
87
+ interface ResolvedFile {
88
+ path: string;
89
+ content: string;
90
+ reason: string;
91
+ found: boolean;
92
+ truncated: boolean;
93
+ }
94
+
95
+ // ============================================================================
96
+ // Path Resolution
97
+ // ============================================================================
98
+
99
+ function findRepoRoot(startPath: string): string | null {
100
+ let current = resolve(startPath);
101
+ while (current !== dirname(current)) {
102
+ if (existsSync(join(current, '.git'))) {
103
+ return current;
104
+ }
105
+ current = dirname(current);
106
+ }
107
+ return null;
108
+ }
109
+
110
+ function getCurrentTask(repoRoot: string): string | null {
111
+ const currentTaskFile = join(repoRoot, DIR_WORKFLOW, FILE_CURRENT_TASK);
112
+ if (!existsSync(currentTaskFile)) {
113
+ return null;
114
+ }
115
+ try {
116
+ const content = readFileSync(currentTaskFile, 'utf-8').trim();
117
+ return content || null;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ function getReqIdFromBranch(repoRoot: string): string | null {
124
+ try {
125
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
126
+ cwd: repoRoot,
127
+ encoding: 'utf-8',
128
+ }).trim();
129
+ const match = branch.match(/REQ-\d+/i);
130
+ return match ? match[0].toUpperCase() : null;
131
+ } catch {
132
+ return null;
133
+ }
134
+ }
135
+
136
+ function getReqIdFromEnv(): string | null {
137
+ return process.env.DEVFLOW_REQ_ID || process.env.REQ_ID || null;
138
+ }
139
+
140
+ // ============================================================================
141
+ // File Reading
142
+ // ============================================================================
143
+
144
+ function readFileContent(filePath: string): string | null {
145
+ if (!existsSync(filePath) || !statSync(filePath).isFile()) {
146
+ return null;
147
+ }
148
+ try {
149
+ let content = readFileSync(filePath, 'utf-8');
150
+ if (content.length > MAX_FILE_SIZE) {
151
+ content = content.substring(0, MAX_FILE_SIZE) + '\n\n... [TRUNCATED]';
152
+ }
153
+ return content;
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ function readDirectoryContents(
160
+ dirPath: string,
161
+ maxFiles: number = 20
162
+ ): Array<{ path: string; content: string }> {
163
+ if (!existsSync(dirPath) || !statSync(dirPath).isDirectory()) {
164
+ return [];
165
+ }
166
+
167
+ const results: Array<{ path: string; content: string }> = [];
168
+ try {
169
+ const files = readdirSync(dirPath)
170
+ .filter((f) => /\.(md|yaml|yml|json|ts|js)$/.test(f))
171
+ .sort()
172
+ .slice(0, maxFiles);
173
+
174
+ for (const file of files) {
175
+ const fullPath = join(dirPath, file);
176
+ const content = readFileContent(fullPath);
177
+ if (content) {
178
+ results.push({ path: fullPath, content });
179
+ }
180
+ }
181
+ } catch {
182
+ // Ignore errors
183
+ }
184
+ return results;
185
+ }
186
+
187
+ // ============================================================================
188
+ // JSONL Parsing
189
+ // ============================================================================
190
+
191
+ function parseJsonlFile(filePath: string): JsonlEntry[] {
192
+ if (!existsSync(filePath)) {
193
+ return [];
194
+ }
195
+
196
+ const entries: JsonlEntry[] = [];
197
+ try {
198
+ const content = readFileSync(filePath, 'utf-8');
199
+ for (const line of content.split('\n')) {
200
+ const trimmed = line.trim();
201
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('//')) {
202
+ continue;
203
+ }
204
+ try {
205
+ const entry = JSON.parse(trimmed) as JsonlEntry;
206
+ entries.push(entry);
207
+ } catch {
208
+ // Skip invalid lines
209
+ }
210
+ }
211
+ } catch {
212
+ // Ignore file read errors
213
+ }
214
+ return entries;
215
+ }
216
+
217
+ function resolveJsonlEntries(
218
+ entries: JsonlEntry[],
219
+ reqPath: string,
220
+ repoRoot: string,
221
+ reqId: string
222
+ ): ResolvedFile[] {
223
+ const results: ResolvedFile[] = [];
224
+
225
+ for (const entry of entries) {
226
+ const filePath = entry.file || entry.path;
227
+ if (!filePath) continue;
228
+
229
+ const entryType = entry.type || 'file';
230
+ const reason = entry.reason || 'Context file';
231
+
232
+ // Replace {REQ} placeholder with actual REQ-ID
233
+ const resolvedPath = filePath.replace(/\{REQ\}/g, reqId);
234
+
235
+ // Determine absolute path
236
+ let absolutePath: string;
237
+ if (resolvedPath.startsWith('/')) {
238
+ absolutePath = resolvedPath;
239
+ } else if (
240
+ resolvedPath.startsWith('.claude/') ||
241
+ resolvedPath.startsWith('devflow/')
242
+ ) {
243
+ absolutePath = join(repoRoot, resolvedPath);
244
+ } else {
245
+ absolutePath = join(reqPath, resolvedPath);
246
+ }
247
+
248
+ if (entryType === 'directory') {
249
+ const dirContents = readDirectoryContents(absolutePath);
250
+ for (const { path, content } of dirContents) {
251
+ results.push({
252
+ path,
253
+ content,
254
+ reason: `${reason} - ${path.split('/').pop()}`,
255
+ found: true,
256
+ truncated: content.includes('[TRUNCATED]'),
257
+ });
258
+ }
259
+ } else {
260
+ const content = readFileContent(absolutePath);
261
+ results.push({
262
+ path: resolvedPath,
263
+ content: content || '',
264
+ reason,
265
+ found: !!content,
266
+ truncated: content?.includes('[TRUNCATED]') || false,
267
+ });
268
+ }
269
+ }
270
+
271
+ return results;
272
+ }
273
+
274
+ // ============================================================================
275
+ // Context Building
276
+ // ============================================================================
277
+
278
+ function buildContextString(files: ResolvedFile[]): string {
279
+ const sections: string[] = [];
280
+ let totalSize = 0;
281
+
282
+ for (const file of files) {
283
+ if (!file.found) continue;
284
+ if (totalSize + file.content.length > MAX_TOTAL_SIZE) {
285
+ console.error(`⚠️ Context size limit reached, skipping: ${file.path}`);
286
+ break;
287
+ }
288
+
289
+ sections.push(`=== ${file.path} (${file.reason}) ===\n${file.content}`);
290
+ totalSize += file.content.length;
291
+ }
292
+
293
+ return sections.join('\n\n');
294
+ }
295
+
296
+ function buildEnhancedPrompt(
297
+ originalPrompt: string,
298
+ context: string,
299
+ agentType: string,
300
+ reqId: string
301
+ ): string {
302
+ return `# ${agentType} Agent Task
303
+
304
+ ## Injected Context (REQ: ${reqId})
305
+
306
+ The following context has been automatically loaded:
307
+
308
+ ${context}
309
+
310
+ ---
311
+
312
+ ## Your Task
313
+
314
+ ${originalPrompt}
315
+
316
+ ---
317
+
318
+ ## Important
319
+
320
+ - All relevant context is provided above
321
+ - Follow the specifications and conventions in the injected files
322
+ - Report any issues or missing information`;
323
+ }
324
+
325
+ // ============================================================================
326
+ // Main Hook Logic
327
+ // ============================================================================
328
+
329
+ interface HookInput {
330
+ session_id: string;
331
+ tool_name: string;
332
+ tool_input: {
333
+ subagent_type?: string;
334
+ prompt?: string;
335
+ description?: string;
336
+ [key: string]: unknown;
337
+ };
338
+ cwd?: string;
339
+ }
340
+
341
+ interface HookOutput {
342
+ hookSpecificOutput: {
343
+ hookEventName: string;
344
+ permissionDecision: string;
345
+ updatedInput?: Record<string, unknown>;
346
+ };
347
+ }
348
+
349
+ function main() {
350
+ try {
351
+ const input = readFileSync(0, 'utf-8');
352
+ const data: HookInput = JSON.parse(input);
353
+
354
+ const { tool_name, tool_input, cwd } = data;
355
+
356
+ // Only process Task tool calls
357
+ if (tool_name !== 'Task') {
358
+ process.exit(0);
359
+ }
360
+
361
+ const subagentType = tool_input.subagent_type;
362
+ if (!subagentType) {
363
+ process.exit(0);
364
+ }
365
+
366
+ // Check if we have a mapping for this agent type
367
+ const skillDir = AGENT_SKILL_MAP[subagentType];
368
+ if (!skillDir) {
369
+ // Unknown agent type, no context injection
370
+ process.exit(0);
371
+ }
372
+
373
+ // Find repo root
374
+ const repoRoot = findRepoRoot(cwd || process.cwd());
375
+ if (!repoRoot) {
376
+ process.exit(0);
377
+ }
378
+
379
+ // Get REQ-ID from multiple sources
380
+ const reqId =
381
+ getReqIdFromEnv() ||
382
+ (() => {
383
+ const taskDir = getCurrentTask(repoRoot);
384
+ if (taskDir) {
385
+ const match = taskDir.match(/REQ-\d+/i);
386
+ return match ? match[0].toUpperCase() : null;
387
+ }
388
+ return null;
389
+ })() ||
390
+ getReqIdFromBranch(repoRoot);
391
+
392
+ if (!reqId) {
393
+ console.error(`ℹ️ Context injection skipped: No REQ-ID found`);
394
+ process.exit(0);
395
+ }
396
+
397
+ const reqPath = join(repoRoot, DIR_REQUIREMENTS, reqId);
398
+ if (!existsSync(reqPath)) {
399
+ console.error(`ℹ️ Context injection skipped: REQ path not found: ${reqPath}`);
400
+ process.exit(0);
401
+ }
402
+
403
+ // Try to find JSONL file in multiple locations
404
+ const jsonlFilename = AGENT_JSONL_MAP[subagentType] || 'context.jsonl';
405
+ const possiblePaths = [
406
+ // 1. Requirement-specific context
407
+ join(reqPath, 'context', jsonlFilename),
408
+ // 2. Skill-specific context
409
+ join(repoRoot, DIR_WORKFLOW, DIR_SKILLS, skillDir, jsonlFilename),
410
+ // 3. Skill default context.jsonl
411
+ join(repoRoot, DIR_WORKFLOW, DIR_SKILLS, skillDir, 'context.jsonl'),
412
+ ];
413
+
414
+ let entries: JsonlEntry[] = [];
415
+ let usedPath = '';
416
+
417
+ for (const path of possiblePaths) {
418
+ if (existsSync(path)) {
419
+ entries = parseJsonlFile(path);
420
+ if (entries.length > 0) {
421
+ usedPath = path;
422
+ break;
423
+ }
424
+ }
425
+ }
426
+
427
+ if (entries.length === 0) {
428
+ console.error(`ℹ️ No context JSONL found for: ${subagentType}`);
429
+ process.exit(0);
430
+ }
431
+
432
+ // Resolve entries to actual file contents
433
+ const resolvedFiles = resolveJsonlEntries(entries, reqPath, repoRoot, reqId);
434
+ const foundFiles = resolvedFiles.filter((f) => f.found);
435
+
436
+ if (foundFiles.length === 0) {
437
+ console.error(`ℹ️ No context files found for: ${subagentType}`);
438
+ process.exit(0);
439
+ }
440
+
441
+ // Build context string
442
+ const contextString = buildContextString(foundFiles);
443
+ const enhancedPrompt = buildEnhancedPrompt(
444
+ tool_input.prompt || '',
445
+ contextString,
446
+ subagentType,
447
+ reqId
448
+ );
449
+
450
+ // Output injection info
451
+ console.error(`\n✅ Context Injection (RM-015)`);
452
+ console.error(` Agent: ${subagentType}`);
453
+ console.error(` REQ: ${reqId}`);
454
+ console.error(` JSONL: ${usedPath}`);
455
+ console.error(` Files: ${foundFiles.length}/${resolvedFiles.length}`);
456
+ console.error(` Size: ~${Math.round(contextString.length / 1000)}KB`);
457
+ console.error('');
458
+
459
+ // Return updated input
460
+ const output: HookOutput = {
461
+ hookSpecificOutput: {
462
+ hookEventName: 'PreToolUse',
463
+ permissionDecision: 'allow',
464
+ updatedInput: {
465
+ ...tool_input,
466
+ prompt: enhancedPrompt,
467
+ },
468
+ },
469
+ };
470
+
471
+ console.log(JSON.stringify(output, null, 0));
472
+ process.exit(0);
473
+ } catch (error) {
474
+ console.error('❌ Context Injection Hook Error:', error);
475
+ // Fail open - don't block on errors
476
+ process.exit(0);
477
+ }
478
+ }
479
+
480
+ main();