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.
- package/.claude/CLAUDE.md +1065 -48
- package/.claude/agents/dev-implementer.md +195 -0
- package/.claude/commands/{flow-archive.md → flow/archive.md} +46 -11
- package/.claude/commands/flow/context.md +150 -0
- package/.claude/commands/flow/delta.md +245 -0
- package/.claude/commands/{flow-dev.md → flow/dev.md} +112 -11
- package/.claude/commands/flow/init.md +45 -0
- package/.claude/commands/flow/quality.md +159 -0
- package/.claude/commands/flow/spec.md +186 -0
- package/.claude/commands/flow/workspace.md +146 -0
- package/.claude/commands/{cancel-ralph.md → util/cancel-ralph.md} +1 -0
- package/.claude/config/quality-gates.yml +305 -0
- package/.claude/docs/guides/TEAM_MODE_GUIDE.md +313 -0
- package/.claude/docs/templates/DELTA_SPEC_TEMPLATE.md +91 -0
- package/.claude/docs/templates/DESIGN_DECISIONS_TEMPLATE.md +151 -0
- package/.claude/docs/templates/JOURNAL_TEMPLATE.md +75 -0
- package/.claude/docs/templates/_shared/CLAUDE.md +36 -0
- package/.claude/docs/templates/_shared/CONSTITUTION_CHECK.md +125 -0
- package/.claude/docs/templates/_shared/VALIDATION_CHECKLIST.md +187 -0
- package/.claude/docs/templates/_shared/YAML_FRONTMATTER.md +164 -0
- package/.claude/docs/templates/context/dev.jsonl.template +6 -0
- package/.claude/docs/templates/context/epic.jsonl.template +5 -0
- package/.claude/docs/templates/context/prd.jsonl.template +4 -0
- package/.claude/docs/templates/context/research.jsonl.template +4 -0
- package/.claude/docs/templates/context/review.jsonl.template +5 -0
- package/.claude/docs/templates/context/tech.jsonl.template +5 -0
- package/.claude/hooks/CLAUDE.md +342 -0
- package/.claude/hooks/inject-agent-context.ts +480 -0
- package/.claude/hooks/inject-skill-context.ts +359 -0
- package/.claude/hooks/ralph-loop.ts +931 -0
- package/.claude/hooks/task-completed-hook.ts +593 -0
- package/.claude/hooks/teammate-idle-hook.ts +690 -0
- package/.claude/hooks/types/team-types.d.ts +238 -0
- package/.claude/rules/devflow-conventions.md +82 -9
- package/.claude/scripts/archive-requirement.sh +44 -1
- package/.claude/scripts/common.sh +670 -3
- package/.claude/scripts/delta-parser.ts +527 -0
- package/.claude/scripts/detect-file-conflicts.sh +151 -0
- package/.claude/scripts/flow-context-add.sh +134 -0
- package/.claude/scripts/flow-context-init.sh +133 -0
- package/.claude/scripts/flow-context-validate.sh +144 -0
- package/.claude/scripts/flow-delta-apply.sh +297 -0
- package/.claude/scripts/flow-delta-archive.sh +71 -0
- package/.claude/scripts/flow-delta-create.sh +202 -0
- package/.claude/scripts/flow-delta-list.sh +142 -0
- package/.claude/scripts/flow-delta-status.sh +235 -0
- package/.claude/scripts/flow-quality-full.sh +184 -0
- package/.claude/scripts/flow-quality-quick.sh +64 -0
- package/.claude/scripts/flow-workspace-init.sh +117 -0
- package/.claude/scripts/flow-workspace-record.sh +164 -0
- package/.claude/scripts/flow-workspace-start.sh +88 -0
- package/.claude/scripts/get-workflow-status.sh +415 -0
- package/.claude/scripts/parse-task-dependencies.js +334 -0
- package/.claude/scripts/record-quality-error.sh +165 -0
- package/.claude/scripts/run-quality-gates.sh +242 -0
- package/.claude/scripts/team-dev-init.sh +319 -0
- package/.claude/scripts/team-state-recovery.sh +229 -0
- package/.claude/scripts/workflow-status.ts +433 -0
- package/.claude/settings.json +19 -0
- package/.claude/skills/cc-devflow-orchestrator/SKILL.md +85 -200
- package/.claude/skills/domain/using-git-worktrees/SKILL.md +252 -0
- package/.claude/skills/domain/using-git-worktrees/assets/SHELL_ALIASES.md +133 -0
- package/.claude/skills/domain/using-git-worktrees/context.jsonl +4 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-cleanup.sh +218 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-create.sh +232 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-list.sh +130 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-status.sh +140 -0
- package/.claude/skills/domain/using-git-worktrees/scripts/worktree-switch.sh +70 -0
- package/.claude/skills/skill-rules.json +72 -1
- package/.claude/skills/utility/journey-checker/SKILL.md +199 -0
- package/.claude/skills/utility/journey-checker/pressure-scenarios.md +164 -0
- package/.claude/skills/utility/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/utility/skill-creator/SKILL.md +356 -0
- package/.claude/skills/utility/skill-creator/references/output-patterns.md +82 -0
- package/.claude/skills/utility/skill-creator/references/workflows.md +28 -0
- package/.claude/skills/utility/skill-creator/scripts/init_skill.py +303 -0
- package/.claude/skills/utility/skill-creator/scripts/package_skill.py +110 -0
- package/.claude/skills/utility/skill-creator/scripts/quick_validate.py +95 -0
- package/.claude/skills/workflow/flow-dev/CLAUDE.md +78 -0
- package/.claude/skills/workflow/flow-dev/SKILL.md +96 -0
- package/.claude/skills/workflow/flow-dev/assets/IMPLEMENTATION_PLAN_TEMPLATE.md +71 -0
- package/.claude/skills/workflow/flow-dev/context.jsonl +8 -0
- package/.claude/skills/workflow/flow-dev/dev-implementer.jsonl +8 -0
- package/.claude/skills/workflow/flow-dev/scripts/entry-gate.sh +116 -0
- package/.claude/skills/workflow/flow-dev/scripts/exit-gate.sh +101 -0
- package/.claude/skills/workflow/flow-dev/scripts/task-orchestrator.sh +106 -0
- package/.claude/skills/workflow/flow-fix/SKILL.md +105 -0
- package/.claude/skills/workflow/flow-fix/context.jsonl +6 -0
- package/.claude/skills/workflow/flow-fix/references/bug-analyzer.md +381 -0
- package/.claude/skills/workflow/flow-init/SKILL.md +211 -0
- package/.claude/skills/workflow/flow-init/assets/BRAINSTORM_TEMPLATE.md +148 -0
- package/.claude/skills/workflow/flow-init/assets/INIT_FLOW_TEMPLATE.md +198 -0
- package/.claude/skills/workflow/flow-init/assets/RESEARCH_TEMPLATE.md +276 -0
- package/.claude/skills/workflow/flow-init/context.jsonl +5 -0
- package/.claude/skills/workflow/flow-init/references/flow-researcher.md +132 -0
- package/.claude/skills/workflow/flow-init/scripts/check-prerequisites.sh +232 -0
- package/.claude/skills/workflow/flow-init/scripts/consolidate-research.sh +182 -0
- package/.claude/skills/workflow/flow-init/scripts/create-requirement.sh +515 -0
- package/.claude/skills/workflow/flow-init/scripts/generate-research-tasks.sh +157 -0
- package/.claude/skills/workflow/flow-init/scripts/populate-research-tasks.sh +284 -0
- package/.claude/skills/workflow/flow-init/scripts/validate-research.sh +332 -0
- package/.claude/skills/workflow/flow-quality/SKILL.md +94 -0
- package/.claude/skills/workflow/flow-quality/context.jsonl +6 -0
- package/.claude/skills/workflow/flow-quality/references/code-quality-reviewer.md +205 -0
- package/.claude/skills/workflow/flow-quality/references/qa-tester.md +313 -0
- package/.claude/skills/workflow/flow-quality/references/security-reviewer.md +314 -0
- package/.claude/skills/workflow/flow-quality/references/spec-reviewer.md +221 -0
- package/.claude/skills/workflow/flow-release/SKILL.md +126 -0
- package/.claude/skills/workflow/flow-release/context.jsonl +7 -0
- package/.claude/skills/workflow/flow-release/references/release-manager.md +295 -0
- package/.claude/skills/workflow/flow-spec/CLAUDE.md +103 -0
- package/.claude/skills/workflow/flow-spec/SKILL.md +545 -0
- package/.claude/skills/workflow/flow-spec/context.jsonl +7 -0
- package/.claude/skills/workflow/flow-spec/scripts/entry-gate.sh +194 -0
- package/.claude/skills/workflow/flow-spec/scripts/exit-gate.sh +244 -0
- package/.claude/skills/workflow/flow-spec/scripts/parallel-orchestrator.sh +205 -0
- package/.claude/skills/workflow/flow-spec/scripts/team-communication.sh +353 -0
- package/.claude/skills/workflow/flow-spec/scripts/team-init.sh +195 -0
- package/.claude/skills/workflow/flow-spec/scripts/test-team-mode.sh +496 -0
- package/.claude/skills/workflow/flow-spec/team-config.json +165 -0
- package/.claude/skills/workflow.yaml +417 -0
- package/CHANGELOG.md +254 -0
- package/README.md +193 -33
- package/README.zh-CN.md +206 -46
- package/lib/compiler/CLAUDE.md +77 -46
- package/lib/compiler/__tests__/multi-module-emitters.test.js +508 -0
- package/lib/compiler/context-expander.js +179 -0
- package/lib/compiler/emitters/antigravity-emitter.js +195 -5
- package/lib/compiler/emitters/base-emitter.js +217 -2
- package/lib/compiler/emitters/codex-emitter.js +200 -4
- package/lib/compiler/emitters/cursor-emitter.js +307 -3
- package/lib/compiler/emitters/qwen-emitter.js +196 -4
- package/lib/compiler/index.js +197 -2
- package/lib/compiler/platforms.js +270 -21
- package/package.json +1 -1
- package/.claude/commands/flow-epic.md +0 -183
- package/.claude/commands/flow-init.md +0 -370
- package/.claude/commands/flow-prd.md +0 -144
- package/.claude/commands/flow-qa.md +0 -93
- package/.claude/commands/flow-review.md +0 -257
- package/.claude/commands/flow-tech.md +0 -142
- package/.claude/commands/flow-ui.md +0 -189
- package/.claude/skills/file-header-guardian/SKILL.md +0 -56
- package/.claude/skills/skill-developer/ADVANCED.md +0 -197
- package/.claude/skills/skill-developer/HOOK_MECHANISMS.md +0 -306
- package/.claude/skills/skill-developer/PATTERNS_LIBRARY.md +0 -152
- package/.claude/skills/skill-developer/SKILL.md +0 -426
- package/.claude/skills/skill-developer/SKILL_RULES_REFERENCE.md +0 -315
- package/.claude/skills/skill-developer/TRIGGER_TYPES.md +0 -305
- package/.claude/skills/skill-developer/TROUBLESHOOTING.md +0 -514
- package/.claude/skills/writing-skills/SKILL.md +0 -655
- package/.claude/skills/writing-skills/anthropic-best-practices.md +0 -1150
- package/.claude/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
- package/.claude/skills/writing-skills/graphviz-conventions.dot +0 -172
- package/.claude/skills/writing-skills/persuasion-principles.md +0 -187
- package/.claude/skills/writing-skills/render-graphs.js +0 -168
- package/.claude/skills/writing-skills/testing-skills-with-subagents.md +0 -384
- package/.claude/tsc-cache/795ba6e3-b98a-423b-bab2-51aa62812569/affected-repos.txt +0 -1
- package/.claude/tsc-cache/ae335694-be5a-4ba4-a1a0-b676c09a7906/affected-repos.txt +0 -1
- /package/.claude/commands/{core-architecture.md → core/architecture.md} +0 -0
- /package/.claude/commands/{core-guidelines.md → core/guidelines.md} +0 -0
- /package/.claude/commands/{core-roadmap.md → core/roadmap.md} +0 -0
- /package/.claude/commands/{core-style.md → core/style.md} +0 -0
- /package/.claude/commands/{flow-checklist.md → flow/checklist.md} +0 -0
- /package/.claude/commands/{flow-clarify.md → flow/clarify.md} +0 -0
- /package/.claude/commands/{flow-constitution.md → flow/constitution.md} +0 -0
- /package/.claude/commands/{flow-fix.md → flow/fix.md} +0 -0
- /package/.claude/commands/{flow-ideate.md → flow/ideate.md} +0 -0
- /package/.claude/commands/{flow-new.md → flow/new.md} +0 -0
- /package/.claude/commands/{flow-release.md → flow/release.md} +0 -0
- /package/.claude/commands/{flow-restart.md → flow/restart.md} +0 -0
- /package/.claude/commands/{flow-status.md → flow/status.md} +0 -0
- /package/.claude/commands/{flow-update.md → flow/update.md} +0 -0
- /package/.claude/commands/{flow-upgrade.md → flow/upgrade.md} +0 -0
- /package/.claude/commands/{flow-verify.md → flow/verify.md} +0 -0
- /package/.claude/commands/{code-review-high.md → util/code-review.md} +0 -0
- /package/.claude/commands/{git-commit.md → util/git-commit.md} +0 -0
- /package/.claude/commands/{problem-analyzer.md → util/problem-analyzer.md} +0 -0
- /package/.claude/skills/{flow-attention-refresh → domain/attention-refresh}/SKILL.md +0 -0
- /package/.claude/skills/{flow-brainstorming → domain/brainstorming}/SKILL.md +0 -0
- /package/.claude/skills/{flow-debugging → domain/debugging}/SKILL.md +0 -0
- /package/.claude/skills/{flow-finishing-branch → domain/finishing-branch}/SKILL.md +0 -0
- /package/.claude/skills/{flow-receiving-review → domain/receiving-review}/SKILL.md +0 -0
- /package/.claude/skills/{flow-tdd → domain/tdd}/SKILL.md +0 -0
- /package/.claude/skills/{verification-before-completion → domain/verification}/SKILL.md +0 -0
- /package/.claude/skills/{constitution-guardian → guardrail/constitution-guardian}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-tdd-enforcer → guardrail/tdd-enforcer}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-constitution-quick-ref → utility/constitution-quick-ref}/SKILL.md +0 -0
- /package/.claude/skills/{devflow-file-standards → utility/file-standards}/SKILL.md +0 -0
- /package/.claude/skills/{fractal-docs-generator → utility/fractal-docs}/SKILL.md +0 -0
- /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();
|