@thecodesaiyan/claude-dev-workflow-hook 1.0.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/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@thecodesaiyan/claude-dev-workflow-hook",
3
+ "version": "1.0.0",
4
+ "description": "SessionStart hook that injects a structured development workflow protocol into Claude Code sessions",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "workflow",
9
+ "hook",
10
+ "tdd",
11
+ "agent-teams",
12
+ "development-workflow"
13
+ ],
14
+ "homepage": "https://github.com/ntatschner/ai-utilities/tree/main/tools/claude-dev-workflow-hook",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ntatschner/ai-utilities.git",
18
+ "directory": "tools/claude-dev-workflow-hook"
19
+ },
20
+ "license": "MIT",
21
+ "author": "ntatschner",
22
+ "bin": {
23
+ "claude-dev-workflow-hook": "bin/cli.js"
24
+ },
25
+ "files": [
26
+ "bin/",
27
+ "src/",
28
+ "AGENTS.md",
29
+ "README.md"
30
+ ],
31
+ "scripts": {
32
+ "test": "node --test tests/hook.test.js tests/installer.test.js"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ }
37
+ }
package/src/hook.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Claude Code Development Workflow Protocol — SessionStart Hook
6
+ *
7
+ * Generic, project-agnostic version. Works in any repository.
8
+ * Injects orchestration rules (sequencing, testing, team coordination)
9
+ * once per session via SessionStart hookSpecificOutput.additionalContext.
10
+ *
11
+ * Registered as SessionStart so the protocol is injected once per session
12
+ * (on startup, resume, clear, and compact) rather than on every prompt.
13
+ */
14
+
15
+ const { PROTOCOL } = require('./protocol.js');
16
+
17
+ function emitOutput() {
18
+ const output = {
19
+ hookSpecificOutput: {
20
+ hookEventName: 'SessionStart',
21
+ additionalContext: PROTOCOL,
22
+ },
23
+ };
24
+ // Use write callback + exitCode instead of process.exit(0) to ensure
25
+ // stdout fully flushes before the process terminates (fixes truncation
26
+ // on macOS Node 20 where the buffer doesn't drain before exit).
27
+ process.stdout.write(JSON.stringify(output) + '\n', () => {
28
+ process.exitCode = 0;
29
+ });
30
+ }
31
+
32
+ function main() {
33
+ // Drain stdin — hook runner pipes JSON input; we must consume it.
34
+ // The input is not used; SessionStart hooks only output additionalContext.
35
+ process.stdin.on('data', () => {}); // discard chunks
36
+ process.stdin.on('end', emitOutput);
37
+ process.stdin.on('error', emitOutput); // emit output even if stdin errors
38
+
39
+ // Handle stdin already closed (e.g. piped empty input)
40
+ process.stdin.resume();
41
+ }
42
+
43
+ main();
@@ -0,0 +1,318 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const os = require('node:os');
6
+ const { execFileSync } = require('node:child_process');
7
+ const { green, yellow, red, bold, readJsonFile, writeJsonFile } = require('./utils.js');
8
+
9
+ // --------------------------------------------------------------------------- //
10
+ // Constants
11
+ // --------------------------------------------------------------------------- //
12
+
13
+ const HOOK_SRC = path.join(__dirname, 'hook.js');
14
+ const HOOK_FILENAME = 'session-start.js';
15
+ const SETTINGS_FILENAME = 'settings.json';
16
+
17
+ const CLAUDE_MD_BINDING = `
18
+ <!-- WORKFLOW PROTOCOL: Do not remove this section -->
19
+ ## Workflow Protocol
20
+ Follow all rules in \`<dev-workflow-protocol>\` blocks from system-reminders.
21
+ These are injected by the development workflow hook and define mandatory
22
+ orchestration rules (planning, TDD, test loops, team coordination).
23
+ They MUST be followed for all implementation work.
24
+ <!-- END WORKFLOW PROTOCOL -->
25
+ `.trim();
26
+
27
+ const CLAUDE_MD_MARKER = '<!-- WORKFLOW PROTOCOL:';
28
+
29
+ // Legacy patterns for migration
30
+ const LEGACY_HOOK_FILENAMES = ['session-start.py', 'session-start.js'];
31
+ const LEGACY_EVENT = 'UserPromptSubmit';
32
+
33
+ // --------------------------------------------------------------------------- //
34
+ // Helpers
35
+ // --------------------------------------------------------------------------- //
36
+
37
+ function getClaudeHome() {
38
+ return path.join(os.homedir(), '.claude');
39
+ }
40
+
41
+ function getProjectClaude() {
42
+ return path.join(process.cwd(), '.claude');
43
+ }
44
+
45
+ function matchesAnyHookFilename(command) {
46
+ return LEGACY_HOOK_FILENAMES.some((name) =>
47
+ command.includes('/' + name) || command.includes('\\' + name) || command.endsWith(name)
48
+ );
49
+ }
50
+
51
+ function isGlobalDir(targetDir) {
52
+ return path.resolve(targetDir) === path.resolve(getClaudeHome());
53
+ }
54
+
55
+ // --------------------------------------------------------------------------- //
56
+ // Step 1: Copy hook
57
+ // --------------------------------------------------------------------------- //
58
+
59
+ function copyHook(targetDir) {
60
+ const hooksDir = path.join(targetDir, 'hooks');
61
+ fs.mkdirSync(hooksDir, { recursive: true });
62
+
63
+ const dst = path.join(hooksDir, HOOK_FILENAME);
64
+
65
+ if (!fs.existsSync(HOOK_SRC)) {
66
+ console.log(red(` ERROR: ${HOOK_SRC} not found.`));
67
+ return false;
68
+ }
69
+
70
+ const srcContent = fs.readFileSync(HOOK_SRC, 'utf-8');
71
+
72
+ if (fs.existsSync(dst)) {
73
+ // Refuse to overwrite symlinks
74
+ const stat = fs.lstatSync(dst);
75
+ if (stat.isSymbolicLink()) {
76
+ console.log(red(` ERROR: ${dst} is a symlink — refusing to overwrite`));
77
+ return false;
78
+ }
79
+
80
+ const dstContent = fs.readFileSync(dst, 'utf-8');
81
+ if (srcContent === dstContent) {
82
+ console.log(yellow(` SKIP: ${dst} already up-to-date`));
83
+ return true;
84
+ }
85
+ console.log(yellow(` UPDATE: ${dst} exists — overwriting with new version`));
86
+ }
87
+
88
+ // Copy hook.js and its dependency protocol.js
89
+ fs.copyFileSync(HOOK_SRC, dst);
90
+
91
+ const protocolSrc = path.join(__dirname, 'protocol.js');
92
+ const protocolDst = path.join(hooksDir, 'protocol.js');
93
+ fs.copyFileSync(protocolSrc, protocolDst);
94
+
95
+ console.log(green(` OK: Copied ${HOOK_FILENAME} + protocol.js -> ${hooksDir}`));
96
+ return true;
97
+ }
98
+
99
+ // --------------------------------------------------------------------------- //
100
+ // Step 2: Merge settings
101
+ // --------------------------------------------------------------------------- //
102
+
103
+ function mergeSettings(targetDir, isGlobal) {
104
+ const settingsPath = path.join(targetDir, SETTINGS_FILENAME);
105
+
106
+ const hookPrefix = isGlobal ? '~/.claude/hooks/' : '.claude/hooks/';
107
+ const command = `node ${hookPrefix}${HOOK_FILENAME}`;
108
+
109
+ const newHookEntry = {
110
+ hooks: [{ type: 'command', command }],
111
+ };
112
+
113
+ let settings;
114
+ try {
115
+ settings = readJsonFile(settingsPath) || {};
116
+ } catch {
117
+ console.log(red(` ERROR: ${settingsPath} contains invalid JSON. Fix it manually.`));
118
+ return false;
119
+ }
120
+
121
+ // Check if hook is already registered under SessionStart
122
+ const existingHooks = (settings.hooks || {}).SessionStart || [];
123
+ for (const entry of existingHooks) {
124
+ for (const hook of entry.hooks || []) {
125
+ if (matchesAnyHookFilename(hook.command || '')) {
126
+ // Check if it's already the Node.js version
127
+ if ((hook.command || '').includes(HOOK_FILENAME)) {
128
+ console.log(yellow(` SKIP: Hook already registered in ${settingsPath}`));
129
+ return true;
130
+ }
131
+ // Migrate Python -> Node.js: create new entry instead of mutating
132
+ entry.hooks = entry.hooks.map((h) =>
133
+ matchesAnyHookFilename(h.command || '')
134
+ ? { ...h, command }
135
+ : h
136
+ );
137
+ writeJsonFile(settingsPath, settings);
138
+ console.log(green(` OK: Migrated Python -> Node.js hook in ${settingsPath}`));
139
+ console.log(` Command: ${command}`);
140
+ return true;
141
+ }
142
+ }
143
+ }
144
+
145
+ // Migrate: remove old UserPromptSubmit registration if present
146
+ const oldHooks = (settings.hooks || {})[LEGACY_EVENT] || [];
147
+ if (oldHooks.length > 0) {
148
+ const filtered = oldHooks.filter(
149
+ (entry) => !(entry.hooks || []).some((h) => matchesAnyHookFilename(h.command || ''))
150
+ );
151
+ if (filtered.length < oldHooks.length) {
152
+ if (filtered.length > 0) {
153
+ settings.hooks[LEGACY_EVENT] = filtered;
154
+ } else {
155
+ delete settings.hooks[LEGACY_EVENT];
156
+ }
157
+ console.log(green(' OK: Removed old UserPromptSubmit registration'));
158
+ }
159
+ }
160
+
161
+ // Add the hook under SessionStart
162
+ if (!settings.hooks) settings.hooks = {};
163
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
164
+ settings.hooks.SessionStart = [...settings.hooks.SessionStart, newHookEntry];
165
+
166
+ writeJsonFile(settingsPath, settings);
167
+ console.log(green(` OK: Registered hook in ${settingsPath}`));
168
+ console.log(` Command: ${command}`);
169
+ return true;
170
+ }
171
+
172
+ // --------------------------------------------------------------------------- //
173
+ // Step 3: CLAUDE.md binding
174
+ // --------------------------------------------------------------------------- //
175
+
176
+ function addClaudeMdBinding(targetDir) {
177
+ const claudeMdPath = isGlobalDir(targetDir)
178
+ ? path.join(getClaudeHome(), 'CLAUDE.md')
179
+ : path.join(path.dirname(targetDir), 'CLAUDE.md');
180
+
181
+ if (fs.existsSync(claudeMdPath)) {
182
+ const content = fs.readFileSync(claudeMdPath, 'utf-8');
183
+ if (content.includes(CLAUDE_MD_MARKER)) {
184
+ console.log(yellow(` SKIP: Authority binding already in ${claudeMdPath}`));
185
+ return true;
186
+ }
187
+
188
+ // Insert after the first top-level heading
189
+ const lines = content.split('\n');
190
+ let insertIdx = 0;
191
+ for (let i = 0; i < lines.length; i++) {
192
+ if (lines[i].startsWith('# ')) {
193
+ insertIdx = i + 1;
194
+ while (insertIdx < lines.length && lines[insertIdx].trim() === '') {
195
+ insertIdx++;
196
+ }
197
+ break;
198
+ }
199
+ }
200
+
201
+ const bindingLines = CLAUDE_MD_BINDING.split('\n');
202
+ const before = lines.slice(0, insertIdx);
203
+ const after = lines.slice(insertIdx);
204
+ const newContent = [...before, '', ...bindingLines, '', ...after].join('\n');
205
+
206
+ fs.writeFileSync(claudeMdPath, newContent, 'utf-8');
207
+ console.log(green(` OK: Added authority binding to ${claudeMdPath}`));
208
+ } else {
209
+ fs.writeFileSync(claudeMdPath, `# Project\n\n${CLAUDE_MD_BINDING}\n`, 'utf-8');
210
+ console.log(green(` OK: Created ${claudeMdPath} with authority binding`));
211
+ }
212
+
213
+ return true;
214
+ }
215
+
216
+ // --------------------------------------------------------------------------- //
217
+ // Step 4: Verify
218
+ // --------------------------------------------------------------------------- //
219
+
220
+ function verifyInstallation(targetDir) {
221
+ const hookPath = path.join(targetDir, 'hooks', HOOK_FILENAME);
222
+
223
+ if (!fs.existsSync(hookPath)) {
224
+ console.log(red(` FAIL: ${hookPath} not found`));
225
+ return false;
226
+ }
227
+
228
+ try {
229
+ const stdout = execFileSync(process.execPath, [hookPath], {
230
+ input: '{}',
231
+ encoding: 'utf-8',
232
+ timeout: 10_000,
233
+ });
234
+
235
+ const output = JSON.parse(stdout.trim());
236
+ const ctx = output.hookSpecificOutput.additionalContext;
237
+ const ruleCount = (ctx.match(/## RULE/g) || []).length;
238
+ const hasXml = ctx.includes('<dev-workflow-protocol>') && ctx.includes('</dev-workflow-protocol>');
239
+ const hasCompliance = ctx.includes('COMPLIANCE REPORTING');
240
+
241
+ const checks = [
242
+ ['Valid JSON output', true],
243
+ ['hookSpecificOutput present', 'hookSpecificOutput' in output],
244
+ ['additionalContext present', ctx.length > 0],
245
+ [`Protocol length: ${ctx.length} chars`, ctx.length > 1000],
246
+ [`Rules found: ${ruleCount}`, ruleCount === 7],
247
+ ['XML wrapper tags', hasXml],
248
+ ['Compliance reporting section', hasCompliance],
249
+ ];
250
+
251
+ let allOk = true;
252
+ for (const [label, passed] of checks) {
253
+ const icon = passed ? green('PASS') : red('FAIL');
254
+ console.log(` ${icon}: ${label}`);
255
+ if (!passed) allOk = false;
256
+ }
257
+
258
+ return allOk;
259
+ } catch (err) {
260
+ if (err.killed) {
261
+ console.log(red(' FAIL: Hook timed out (10s)'));
262
+ } else {
263
+ console.log(red(` FAIL: ${err.message}`));
264
+ }
265
+ return false;
266
+ }
267
+ }
268
+
269
+ // --------------------------------------------------------------------------- //
270
+ // Install orchestrator
271
+ // --------------------------------------------------------------------------- //
272
+
273
+ function install(targetDir, scopeIsGlobal) {
274
+ const scopeLabel = scopeIsGlobal ? 'Global' : 'Project';
275
+
276
+ console.log(bold(`\nInstalling (${scopeLabel})...`));
277
+ console.log(`Target: ${targetDir}`);
278
+
279
+ console.log(bold('\n[1/4] Copying hook script'));
280
+ if (!copyHook(targetDir)) process.exit(1);
281
+
282
+ console.log(bold('\n[2/4] Registering in settings.json'));
283
+ if (!mergeSettings(targetDir, scopeIsGlobal)) process.exit(1);
284
+
285
+ console.log(bold('\n[3/4] Adding CLAUDE.md authority binding'));
286
+ addClaudeMdBinding(targetDir);
287
+
288
+ console.log(bold('\n[4/4] Verifying installation'));
289
+ if (verifyInstallation(targetDir)) {
290
+ console.log(green(bold('\nInstallation complete!')));
291
+ console.log('\nRestart Claude Code to activate the workflow protocol.');
292
+ console.log('Verify by asking: "What development workflow rules are active?"');
293
+ } else {
294
+ console.log(yellow(bold('\nInstallation finished with warnings.')));
295
+ console.log('The hook was copied and registered but verification found issues.');
296
+ console.log('Check the output above and fix any problems.');
297
+ }
298
+ }
299
+
300
+ // --------------------------------------------------------------------------- //
301
+ // Exports (for testing and CLI)
302
+ // --------------------------------------------------------------------------- //
303
+
304
+ module.exports = {
305
+ install,
306
+ copyHook,
307
+ mergeSettings,
308
+ addClaudeMdBinding,
309
+ verifyInstallation,
310
+ getClaudeHome,
311
+ getProjectClaude,
312
+ HOOK_FILENAME,
313
+ SETTINGS_FILENAME,
314
+ CLAUDE_MD_BINDING,
315
+ CLAUDE_MD_MARKER,
316
+ LEGACY_HOOK_FILENAMES,
317
+ LEGACY_EVENT,
318
+ };
@@ -0,0 +1,230 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Development Workflow Protocol — single source of truth.
5
+ *
6
+ * This string is identical to the PROTOCOL in session-start.py.
7
+ * hook.js imports it and injects it via hookSpecificOutput.additionalContext.
8
+ */
9
+
10
+ const PROTOCOL = `<dev-workflow-protocol>
11
+ MANDATORY WORKFLOW RULES — You MUST follow these rules for ALL work in this project.
12
+ Violations (skipping tests, skipping planning, ignoring TDD) are NOT acceptable.
13
+
14
+ ## COMPLIANCE REPORTING (NON-NEGOTIABLE)
15
+
16
+ You MUST announce rule adherence explicitly as you work. Before starting any task:
17
+ 1. State which rules apply (use the Quick Reference table to determine the rule set).
18
+ 2. As you execute each rule, prefix your action with \`[PROTOCOL Rule N]\`.
19
+ 3. If you skip a rule, you MUST state why (e.g., Rule 1 small task exception).
20
+
21
+ Format:
22
+ \`\`\`
23
+ [PROTOCOL Rule 0] Planning: This task modifies X files — <list files>.
24
+ [PROTOCOL Rule 2] TDD RED: Writing failing test for <feature>...
25
+ [PROTOCOL Rule 4] Orchestration: Agent Teams mode / Subagent fallback — <reason>.
26
+ [PROTOCOL Rule 4] Phase 2: Spawning 2 implementation teammates (backend, frontend)...
27
+ \`\`\`
28
+
29
+ If you do NOT output these prefixes, you are violating this protocol.
30
+
31
+ ## RULE 0 — Plan Before Acting
32
+
33
+ Before writing ANY code:
34
+ 1. Identify every file that will be created or modified.
35
+ 2. Map dependencies between changes (e.g., backend model change breaks frontend types, API contract change breaks consumers).
36
+ 3. If >2 files are affected, enter Plan Mode and get approval.
37
+ 4. If the plan changes mid-implementation, STOP and revise the plan.
38
+
39
+ ## RULE 1 — Small Task Exception
40
+
41
+ If a task changes 2 or fewer files AND does not add a new entity/endpoint:
42
+ - Skip team orchestration (Rule 4).
43
+ - Still follow Rules 0, 2, 3 (plan, TDD, test loop).
44
+ - Still follow Rule 5 if modifying frontend components.
45
+ - Still follow Rule 6 if modifying API endpoints/services.
46
+ - Still run the full test suite before marking done.
47
+
48
+ ## RULE 2 — TDD Enforcement
49
+
50
+ For new features and bug fixes:
51
+ 1. RED — Write a failing test that describes the expected behavior.
52
+ 2. GREEN — Write the minimum code to make the test pass.
53
+ 3. IMPROVE — Refactor without changing behavior; re-run tests.
54
+ 4. Target 80%+ coverage. Use the \`tdd-guide\` agent when available.
55
+
56
+ ## RULE 3 — Test After Every Change
57
+
58
+ After EVERY code change (not just at the end):
59
+ 1. Run the relevant test suite for the area you changed.
60
+ 2. If ANY test fails, fix it BEFORE moving to the next change.
61
+ 3. Never stack multiple untested changes — each change must pass before proceeding.
62
+ 4. After all changes, run the project's full test suite.
63
+
64
+ ### Auto-Detect Test Runner
65
+ Use the first match found in the project root:
66
+ - \`package.json\` with \`test\` script -> \`npm test\` (or \`yarn test\` / \`pnpm test\` / \`bun test\` based on lockfile)
67
+ - \`Makefile\` with \`test\` target -> \`make test\`
68
+ - \`Cargo.toml\` -> \`cargo test\`
69
+ - \`go.mod\` -> \`go test ./...\`
70
+ - \`*.sln\` or \`*.slnx\` -> \`dotnet test <solution-file>\`
71
+ - \`pyproject.toml\` or \`setup.py\` -> \`pytest\` (or \`python -m pytest\`)
72
+ - \`build.gradle\` or \`pom.xml\` -> \`./gradlew test\` or \`mvn test\`
73
+ - \`mix.exs\` -> \`mix test\`
74
+ - \`Gemfile\` -> \`bundle exec rspec\` or \`bundle exec rake test\`
75
+ - If multiple ecosystems exist (e.g., .NET backend + React frontend), run BOTH test suites.
76
+
77
+ ## RULE 4 — Agent Team Orchestration (Multi-File Features)
78
+
79
+ For features touching >2 files, use a 7-phase pipeline with maximum parallelism.
80
+ Do NOT do all work inline — delegate to Agent Teams or subagents.
81
+
82
+ ### Detecting Orchestration Mode
83
+
84
+ Before Phase 1, determine which mode to use:
85
+
86
+ 1. Run: \`echo $CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS\`
87
+ 2. If the value is \`"1"\` → **Agent Teams mode** (REQUIRED — you MUST use it).
88
+ 3. Otherwise → **Subagent fallback mode** (use the \`Task\` tool).
89
+ 4. Announce: \`[PROTOCOL Rule 4] Orchestration: Agent Teams mode\` or
90
+ \`[PROTOCOL Rule 4] Orchestration: Subagent fallback — Agent Teams not enabled\`
91
+
92
+ You MUST NOT use Subagent fallback when Agent Teams are available. This is a violation.
93
+
94
+ ### Phase 1: Research — SEQUENTIAL
95
+
96
+ **Agent Teams:** Spawn a research teammate to map affected files, existing patterns,
97
+ test coverage, and the dependency graph. Wait for completion before Phase 2.
98
+
99
+ **Subagent fallback:** Launch a \`Task\` call with the best role-matching \`subagent_type\`
100
+ from the Task tool description. Prefer plugin/custom agents (names with \`:\`) over
101
+ built-in types (\`Explore\`, \`Plan\`). State your selection rationale.
102
+
103
+ ### Phase 2: Implementation — PARALLEL
104
+
105
+ **Agent Teams (REQUIRED when available):**
106
+ 1. Analyze Phase 1 output. Partition work into **file-isolated subsystems** — no two
107
+ teammates may edit the same file (concurrent edits cause overwrites).
108
+ 2. Create tasks on the **shared task list** with \`blockedBy\` dependencies. Example:
109
+ \`\`\`
110
+ Task 1: Backend models (no deps) → starts immediately
111
+ Task 2: Backend services (blockedBy: [1])
112
+ Task 3: Frontend types (blockedBy: [2] — needs API contract)
113
+ Task 4: Frontend components (blockedBy: [3])
114
+ Task 5: Backend tests (blockedBy: [2])
115
+ Task 6: Frontend tests (blockedBy: [4])
116
+ \`\`\`
117
+ When Task 2 completes, Tasks 3 AND 5 unblock in parallel — maximum concurrency.
118
+ 3. Spawn implementation teammates (one per subsystem, e.g., backend, frontend, tests).
119
+ 4. Each teammate's spawn prompt MUST include:
120
+ - Phase 1 research summary (teammates do NOT inherit your conversation)
121
+ - Their assigned file set and which files NOT to touch
122
+ - Dependency info (what they wait on, what depends on them)
123
+ - Project conventions discovered in Phase 1
124
+ 5. Teammates self-claim tasks as dependencies resolve. Monitor progress.
125
+
126
+ **Subagent fallback:**
127
+ Implement sequentially in dependency order (backend models → services → controllers,
128
+ then frontend types → components → pages). Follow TDD (Rule 2) and test after each
129
+ file change (Rule 3). You MAY delegate isolated subsystems via \`Task\` tool.
130
+
131
+ ### Phases 3+4+5: Review Team — PARALLEL
132
+
133
+ **Agent Teams:** Spawn 3 review teammates simultaneously. Tell them to **communicate
134
+ directly and challenge each other's findings** — this cross-pollination is the key
135
+ advantage over independent subagents.
136
+
137
+ **Subagent fallback:** Launch exactly 3 \`Task\` calls in a SINGLE response message
138
+ (parallel execution). Select the best role-matching \`subagent_type\` for each.
139
+
140
+ **Phase 3 — Fact Check:** Verify all planned changes were implemented. Check API
141
+ contracts match between backend and frontend. Check i18n completeness.
142
+
143
+ **Phase 4 — Security & Quality:** Audit for hardcoded secrets, unvalidated inputs,
144
+ injection vectors, N+1 queries. Verify naming and file-structure conventions.
145
+
146
+ **Phase 5 — Readability:** Review naming consistency, function size, nesting depth.
147
+ Flag redundant or missing comments where logic is non-obvious.
148
+
149
+ ### Phase 6: Synthesis — SEQUENTIAL
150
+
151
+ Read all review outputs (teammate findings or subagent results). Fix bugs,
152
+ inconsistencies, or issues raised. Run the full test suite one final time.
153
+
154
+ ### Phase 7: Finalize — SEQUENTIAL
155
+ - If the project uses containers: rebuild and verify all services are healthy.
156
+ - Smoke-test the new feature (manual or automated).
157
+ - Update project memory/docs if the project tracks build status or feature summaries.
158
+
159
+ ### Phase Dependencies
160
+ \`\`\`
161
+ Phase 1 (research)
162
+ -> Phase 2 (parallel implementation via teammates OR sequential)
163
+ -> Phases 3+4+5 (parallel review team)
164
+ -> Phase 6 (synthesis + fix + final tests)
165
+ -> Phase 7 (finalize)
166
+ \`\`\`
167
+
168
+ ### Enforcement
169
+ - Agent Teams mode is REQUIRED when \`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1\`.
170
+ - In subagent fallback mode: prefer plugin/custom subagent_types over built-in types.
171
+ - Never skip phases. If a phase finds no issues, report it ran clean.
172
+ - Report each phase: \`[PROTOCOL Rule 4] Phase N: <description>...\`
173
+
174
+ ## RULE 5 — UI & Layout Standards
175
+
176
+ When modifying frontend components:
177
+ - **Responsive testing**: Verify at 375px (mobile), 768px (tablet), and 1024px (desktop) breakpoints.
178
+ - **Layout stability**: Ensure no content overflow, clipped dropdowns, or scroll containers trapping positioned elements.
179
+ - **Overflow rules**: Prefer \`overflow-x-clip\` over \`overflow-hidden\` when you need clipping without creating a scroll container. Never use \`overflow-x-auto\` on wrappers containing absolutely-positioned menus (CSS spec forces \`overflow-y: auto\` too).
180
+ - **Flexbox constraint chain**: \`h-screen\` (root) -> \`min-h-0\` (flex items) -> \`overflow-y-auto\` (scrollable area).
181
+ - **Accessibility basics**: Semantic HTML, keyboard navigation for interactive elements, visible focus indicators, sufficient color contrast.
182
+ - **Consistency**: Follow the project's existing design system, color tokens, and component patterns.
183
+ - **i18n**: If the project uses internationalization, ensure all user-facing strings go through the translation system. Update ALL locale files when adding new keys.
184
+
185
+ ## RULE 6 — Integration Testing
186
+
187
+ When adding or modifying API endpoints, services, or middleware:
188
+
189
+ ### Container-First Testing (if applicable)
190
+ 1. Start the project's container stack BEFORE running tests.
191
+ 2. Verify all services are healthy.
192
+ 3. Test new endpoints against live containers first — catches issues that in-memory/mock providers hide (constraint violations, nullable columns, auth middleware, etc.).
193
+ 4. Kill stale dev processes that may occupy the same ports.
194
+
195
+ ### Backend Integration Tests
196
+ 1. Use the project's test factory/fixture (e.g., WebApplicationFactory, TestServer, Supertest setup).
197
+ 2. Mock or stub external dependencies: databases (in-memory/SQLite), caches (no-op), job queues (no-op), third-party APIs (mock HTTP).
198
+ 3. When adding new DI services, add corresponding test doubles in the test factory.
199
+ 4. When adding constructor dependencies to existing services, fix ALL test files that mock that service.
200
+ 5. Be aware of global query filters, soft-delete scopes, or tenant isolation that may behave differently in test vs production databases.
201
+
202
+ ### Frontend Test Isolation
203
+ 1. Initialize i18n in test setup so translation keys resolve to readable strings.
204
+ 2. Mock API calls (MSW, manual mocks, or test interceptors) — never hit real endpoints from unit/integration tests.
205
+ 3. Test paginated API consumers: verify they handle the pagination wrapper (e.g., \`.items\`, \`.data\`, \`.results\`), not raw arrays.
206
+ 4. For components using data-fetching libraries (React Query, SWR, Apollo), wrap in the appropriate provider with a fresh client per test.
207
+
208
+ ### Common Pitfalls
209
+ - In-memory database providers don't support migrations, constraints, or triggers — guard migration calls with provider checks.
210
+ - Background job frameworks often need a no-op storage set BEFORE the app starts in test mode.
211
+ - WebApplicationFactory patterns may require a \`public partial class Program { }\` sentinel.
212
+ - HTTP status codes matter for client-side interceptors (e.g., 401 may trigger auto-logout — don't return 401 for validation errors).
213
+
214
+ ## Quick Reference
215
+
216
+ | Situation | Required Rules |
217
+ |-----------|---------------|
218
+ | New entity + API + frontend | Rules 0, 2, 3, 4 (agent team), 5, 6 (full pipeline) |
219
+ | Multi-file feature (>2 files) | Rules 0, 2, 3, 4 (agent team), + 5/6 if applicable |
220
+ | Bug fix (1-2 files) | Rules 0, 1, 2, 3 |
221
+ | UI-only change (1-2 files) | Rules 0, 1, 2, 3, 5 |
222
+ | UI change (>2 files) | Rules 0, 2, 3, 4, 5 |
223
+ | Backend-only change (1-2 files) | Rules 0, 1, 2, 3, 6 |
224
+ | Backend change (>2 files) | Rules 0, 2, 3, 4, 6 |
225
+ | New API endpoint | Rules 0, 1, 2, 3, 6 |
226
+ | Refactor (>2 files) | Rules 0, 3, 4 + full test suite before AND after |
227
+ | Refactor (1-2 files) | Rules 0, 1, 3 + full test suite before AND after |
228
+ </dev-workflow-protocol>`.trim();
229
+
230
+ module.exports = { PROTOCOL };