@engineereddev/fractal-planner 0.1.1

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 (46) hide show
  1. package/.claude-plugin/marketplace.json +22 -0
  2. package/.claude-plugin/plugin.json +19 -0
  3. package/LICENSE +21 -0
  4. package/README.md +257 -0
  5. package/agents/fp-analyst.md +96 -0
  6. package/agents/fp-context-builder.md +87 -0
  7. package/agents/fp-critic.md +140 -0
  8. package/agents/fp-decomposer.md +261 -0
  9. package/agents/fp-interviewer.md +263 -0
  10. package/agents/fp-linear-sync.md +128 -0
  11. package/agents/fp-researcher.md +82 -0
  12. package/agents/fp-task-tracker.md +134 -0
  13. package/dist/cli/classify-intent.js +118 -0
  14. package/dist/cli/compute-signals.js +495 -0
  15. package/dist/cli/generate-plan.js +14209 -0
  16. package/dist/cli/load-config.js +13661 -0
  17. package/dist/cli/validate-tasks.js +467 -0
  18. package/dist/index.js +24598 -0
  19. package/dist/src/cli/classify-intent.d.ts +3 -0
  20. package/dist/src/cli/compute-signals.d.ts +14 -0
  21. package/dist/src/cli/generate-plan.d.ts +3 -0
  22. package/dist/src/cli/load-config.d.ts +3 -0
  23. package/dist/src/cli/validate-tasks.d.ts +3 -0
  24. package/dist/src/config.d.ts +182 -0
  25. package/dist/src/index.d.ts +12 -0
  26. package/dist/src/phases/clearance.d.ts +12 -0
  27. package/dist/src/phases/decomposition.d.ts +41 -0
  28. package/dist/src/phases/interview.d.ts +17 -0
  29. package/dist/src/phases/planning.d.ts +21 -0
  30. package/dist/src/phases/research.d.ts +9 -0
  31. package/dist/src/types/index.d.ts +116 -0
  32. package/dist/src/utils/draft.d.ts +21 -0
  33. package/dist/src/utils/question-strategies.d.ts +24 -0
  34. package/dist/src/utils/task-parser.d.ts +3 -0
  35. package/hooks/hooks.json +27 -0
  36. package/hooks/nudge-teammate.sh +216 -0
  37. package/hooks/run-comment-checker.sh +91 -0
  38. package/package.json +65 -0
  39. package/skills/commit/SKILL.md +157 -0
  40. package/skills/fp/SKILL.md +857 -0
  41. package/skills/fp/scripts/resolve-env.sh +66 -0
  42. package/skills/handoff/SKILL.md +195 -0
  43. package/skills/implement/SKILL.md +783 -0
  44. package/skills/implement/reference.md +935 -0
  45. package/skills/retry/SKILL.md +333 -0
  46. package/skills/status/SKILL.md +182 -0
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # TeammateIdle hook: detects stalled teammates and re-injects continuation prompts.
5
+ # Handles two teammate types:
6
+ # - builder-* on fp-impl-* teams (task-based stall detection)
7
+ # - interviewer on fp-interview-* teams (consecutive idle stall detection)
8
+ #
9
+ # Exit 0 = no action (hook passes through).
10
+ # Exit 2 + stderr = re-inject continuation prompt into the teammate.
11
+
12
+ # --- Read stdin (TeammateIdle hook input) ---
13
+ INPUT=$(cat)
14
+
15
+ # --- Extract fields via python3 ---
16
+ TEAMMATE_NAME=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('teammate_name',''))" 2>/dev/null || echo "")
17
+ TEAM_NAME=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('team_name',''))" 2>/dev/null || echo "")
18
+ CWD=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('cwd',''))" 2>/dev/null || echo "")
19
+
20
+ # --- Determine teammate type ---
21
+ IS_BUILDER=false
22
+ IS_INTERVIEWER=false
23
+
24
+ if [[ "$TEAM_NAME" == fp-impl-* ]] && [[ "$TEAMMATE_NAME" == builder-* ]]; then
25
+ IS_BUILDER=true
26
+ elif [[ "$TEAM_NAME" == fp-interview-* ]] && [[ "$TEAMMATE_NAME" == "interviewer" ]]; then
27
+ IS_INTERVIEWER=true
28
+ else
29
+ exit 0
30
+ fi
31
+
32
+ # --- Read merged config (user + project, matching TypeScript loadConfig merge order) ---
33
+ NUDGE_CFG=$(python3 << PYEOF
34
+ import json, os
35
+ def rj(p):
36
+ try:
37
+ with open(p) as f: return json.load(f)
38
+ except: return {}
39
+ xdg = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
40
+ cwd = "${CWD:-.}"
41
+ m = {**rj(os.path.join(xdg, 'fractal-planner', 'config.json')),
42
+ **rj(os.path.join(cwd, '.fractal-planner', 'config.json'))}
43
+ n = m.get('nudge', {})
44
+ print(f"{str(n.get('enabled', True)).lower()}|{n.get('maxRetries', 3)}")
45
+ PYEOF
46
+ ) || NUDGE_CFG="true|3"
47
+ IFS='|' read -r NUDGE_ENABLED MAX_RETRIES <<< "$NUDGE_CFG"
48
+
49
+ if [[ "$NUDGE_ENABLED" == "false" ]]; then
50
+ exit 0
51
+ fi
52
+
53
+ # --- State directory and file ---
54
+ STATE_DIR="$HOME/.claude/teams/${TEAM_NAME}"
55
+ STATE_FILE="${STATE_DIR}/nudge-${TEAMMATE_NAME}.json"
56
+
57
+ # =============================================================================
58
+ # BUILDER PATH: task-based stall detection (unchanged logic)
59
+ # =============================================================================
60
+ if [[ "$IS_BUILDER" == true ]]; then
61
+ TASKS_DIR="$HOME/.claude/tasks/${TEAM_NAME}"
62
+ if [[ ! -d "$TASKS_DIR" ]]; then
63
+ exit 0
64
+ fi
65
+
66
+ MATCHED_TASK=""
67
+ MATCHED_NATIVE_ID=""
68
+ MATCHED_PLAN_TASK_ID=""
69
+
70
+ for TASK_FILE in "$TASKS_DIR"/*.json; do
71
+ [[ -f "$TASK_FILE" ]] || continue
72
+
73
+ TASK_INFO=$(python3 -c "
74
+ import json,sys
75
+ try:
76
+ t=json.load(open('$TASK_FILE'))
77
+ status=t.get('status','')
78
+ owner=t.get('owner','')
79
+ internal=t.get('metadata',{}).get('_internal',False)
80
+ task_id=t.get('id','')
81
+ subject=t.get('subject','')
82
+ fp_status=t.get('metadata',{}).get('fpStatus','')
83
+ print(f'{status}|{owner}|{internal}|{task_id}|{subject}|{fp_status}')
84
+ except: print('|||||')
85
+ " 2>/dev/null || echo "|||||")
86
+
87
+ IFS='|' read -r T_STATUS T_OWNER T_INTERNAL T_ID T_SUBJECT T_FP_STATUS <<< "$TASK_INFO"
88
+
89
+ if [[ "$T_STATUS" == "in_progress" ]] && [[ "$T_OWNER" == "$TEAMMATE_NAME" ]] && [[ "$T_INTERNAL" != "True" ]]; then
90
+ MATCHED_NATIVE_ID="$T_ID"
91
+ MATCHED_PLAN_TASK_ID=$(echo "$T_SUBJECT" | python3 -c "
92
+ import sys,re
93
+ s=sys.stdin.read().strip()
94
+ m=re.match(r'\[([^\]]+)\]',s)
95
+ print(m.group(1) if m else '')
96
+ " 2>/dev/null || echo "")
97
+ MATCHED_FP_STATUS="$T_FP_STATUS"
98
+ MATCHED_TASK="$TASK_FILE"
99
+ break
100
+ fi
101
+ done
102
+
103
+ if [[ -z "$MATCHED_TASK" ]]; then
104
+ rm -f "$STATE_FILE"
105
+ exit 0
106
+ fi
107
+
108
+ # Builder is waiting for lead's verification response — not stalled
109
+ if [[ "$MATCHED_FP_STATUS" == "AWAITING_VERIFICATION" ]]; then
110
+ exit 0
111
+ fi
112
+
113
+ # Read or initialize state
114
+ CURRENT_RETRIES=0
115
+ CURRENT_TASK_ID=""
116
+
117
+ if [[ -f "$STATE_FILE" ]]; then
118
+ STATE_INFO=$(python3 -c "
119
+ import json,sys
120
+ try:
121
+ s=json.load(open('$STATE_FILE'))
122
+ print(f'{s.get(\"retries\",0)}|{s.get(\"taskId\",\"\")}')
123
+ except: print('0|')
124
+ " 2>/dev/null || echo "0|")
125
+
126
+ IFS='|' read -r CURRENT_RETRIES CURRENT_TASK_ID <<< "$STATE_INFO"
127
+ fi
128
+
129
+ if [[ "$CURRENT_TASK_ID" != "$MATCHED_NATIVE_ID" ]]; then
130
+ CURRENT_RETRIES=0
131
+ fi
132
+
133
+ if [[ "$CURRENT_RETRIES" -ge "$MAX_RETRIES" ]]; then
134
+ rm -f "$STATE_FILE"
135
+ exit 0
136
+ fi
137
+
138
+ CURRENT_RETRIES=$((CURRENT_RETRIES + 1))
139
+ mkdir -p "$STATE_DIR"
140
+
141
+ python3 -c "
142
+ import json
143
+ state = {
144
+ 'retries': $CURRENT_RETRIES,
145
+ 'lastRetryAt': __import__('datetime').datetime.now(__import__('datetime').timezone.utc).isoformat(),
146
+ 'taskId': '$MATCHED_NATIVE_ID'
147
+ }
148
+ with open('$STATE_FILE', 'w') as f:
149
+ json.dump(state, f, indent=2)
150
+ " 2>/dev/null
151
+
152
+ cat >&2 <<PROMPT
153
+ You are ${TEAMMATE_NAME} and you have an in-progress task [${MATCHED_PLAN_TASK_ID}] that you have not completed.
154
+ You appear to have stalled (idle detection, attempt ${CURRENT_RETRIES}/${MAX_RETRIES}).
155
+
156
+ Resume your self-claiming work loop:
157
+ 1. You already own task ${MATCHED_PLAN_TASK_ID} (native ID: ${MATCHED_NATIVE_ID}). Call TaskGet(${MATCHED_NATIVE_ID}) to re-read the task spec.
158
+ 2. Continue implementing the task according to the spec.
159
+ 3. When done, send "IMPLEMENTATION COMPLETE: ${MATCHED_PLAN_TASK_ID}" with FILES_MODIFIED to "team-lead".
160
+
161
+ If you previously sent IMPLEMENTATION COMPLETE and are waiting for verification, send the message again -- the lead may have missed it.
162
+ PROMPT
163
+
164
+ exit 2
165
+ fi
166
+
167
+ # =============================================================================
168
+ # INTERVIEWER PATH: consecutive idle stall detection
169
+ # =============================================================================
170
+ if [[ "$IS_INTERVIEWER" == true ]]; then
171
+ CURRENT_RETRIES=0
172
+
173
+ if [[ -f "$STATE_FILE" ]]; then
174
+ CURRENT_RETRIES=$(python3 -c "
175
+ import json,sys
176
+ try:
177
+ s=json.load(open('$STATE_FILE'))
178
+ print(s.get('retries',0))
179
+ except: print('0')
180
+ " 2>/dev/null || echo "0")
181
+ fi
182
+
183
+ if [[ "$CURRENT_RETRIES" -ge "$MAX_RETRIES" ]]; then
184
+ rm -f "$STATE_FILE"
185
+ exit 0
186
+ fi
187
+
188
+ CURRENT_RETRIES=$((CURRENT_RETRIES + 1))
189
+ mkdir -p "$STATE_DIR"
190
+
191
+ python3 -c "
192
+ import json
193
+ state = {
194
+ 'retries': $CURRENT_RETRIES,
195
+ 'lastRetryAt': __import__('datetime').datetime.now(__import__('datetime').timezone.utc).isoformat()
196
+ }
197
+ with open('$STATE_FILE', 'w') as f:
198
+ json.dump(state, f, indent=2)
199
+ " 2>/dev/null
200
+
201
+ cat >&2 <<PROMPT
202
+ You are the interviewer and you appear to have stalled (idle detection, attempt ${CURRENT_RETRIES}/${MAX_RETRIES}).
203
+
204
+ You MUST complete your mandatory draft update loop. Do one of the following:
205
+
206
+ 1. If you have pending questions for the user, format and send them as a QUESTIONS message to the lead.
207
+ 2. If you are evaluating clearance, finish the evaluation and send either:
208
+ - "DRAFT UPDATED (Round N)" with clearance status, then follow up with QUESTIONS if gaps remain
209
+ - "CLEARANCE ACHIEVED" if all 6 clearance criteria are met
210
+ 3. If you already sent a message and are waiting for a response, send it again -- the lead may have missed it.
211
+
212
+ Do NOT go idle without sending a protocol message (QUESTIONS, DRAFT UPDATED, or CLEARANCE ACHIEVED).
213
+ PROMPT
214
+
215
+ exit 2
216
+ fi
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # PostToolUse hook wrapper for comment-checker.
5
+ # Reads tool_input from stdin, runs the comment-checker binary,
6
+ # and outputs a JSON systemMessage if unnecessary comments are detected.
7
+ #
8
+ # Exit 0 always — hooks must never block Claude Code.
9
+
10
+ # --- Read merged config (user + project, matching TypeScript loadConfig merge order) ---
11
+ eval "$(python3 << 'PYEOF'
12
+ import json, os, shlex
13
+ def rj(p):
14
+ try:
15
+ with open(p) as f: return json.load(f)
16
+ except: return {}
17
+ xdg = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
18
+ m = {**rj(os.path.join(xdg, 'fractal-planner', 'config.json')), **rj('.fractal-planner/config.json')}
19
+ cc = m.get('commentChecker', {})
20
+ print(f"CFG_ENABLED={shlex.quote(str(cc.get('enabled', True)).lower())}")
21
+ print(f"CFG_BINARY_PATH={shlex.quote(cc.get('binaryPath', ''))}")
22
+ print(f"CFG_CUSTOM_PROMPT={shlex.quote(cc.get('customPrompt', ''))}")
23
+ PYEOF
24
+ )" || { CFG_ENABLED="true"; CFG_BINARY_PATH=""; CFG_CUSTOM_PROMPT=""; }
25
+
26
+ # --- Check disabled ---
27
+ if [[ "$CFG_ENABLED" == "false" ]]; then
28
+ exit 0
29
+ fi
30
+
31
+ # --- Locate binary (config > node_modules > PATH > cache dirs) ---
32
+ BINARY=""
33
+
34
+ if [[ -n "$CFG_BINARY_PATH" ]] && [[ -x "$CFG_BINARY_PATH" ]]; then
35
+ BINARY="$CFG_BINARY_PATH"
36
+ else
37
+ # Resolve plugin root (directory containing hooks/)
38
+ PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
39
+ CANDIDATE="$PLUGIN_ROOT/node_modules/.bin/comment-checker"
40
+ if [[ -x "$CANDIDATE" ]]; then
41
+ BINARY="$CANDIDATE"
42
+ elif command -v comment-checker &>/dev/null; then
43
+ BINARY="$(command -v comment-checker)"
44
+ else
45
+ # Check XDG cache directories
46
+ XDG_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}"
47
+ for dir in "$XDG_CACHE/fractal-planner/bin" "$XDG_CACHE/oh-my-opencode/bin"; do
48
+ if [[ -x "$dir/comment-checker" ]]; then
49
+ BINARY="$dir/comment-checker"
50
+ break
51
+ fi
52
+ done
53
+ fi
54
+ fi
55
+
56
+ # Not found — silent skip
57
+ if [[ -z "$BINARY" ]]; then
58
+ exit 0
59
+ fi
60
+
61
+ # --- Determine custom prompt ---
62
+ CUSTOM_PROMPT="$CFG_CUSTOM_PROMPT"
63
+
64
+ # --- Read stdin (PostToolUse hook input) ---
65
+ INPUT=$(cat)
66
+
67
+ # --- Build command args ---
68
+ CMD_ARGS=("$BINARY" "check")
69
+ if [[ -n "$CUSTOM_PROMPT" ]]; then
70
+ CMD_ARGS+=("--prompt" "$CUSTOM_PROMPT")
71
+ fi
72
+
73
+ # --- Run binary and handle output ---
74
+ STDERR_FILE=$(mktemp)
75
+ trap 'rm -f "$STDERR_FILE"' EXIT
76
+
77
+ EXIT_CODE=0
78
+ echo "$INPUT" | "${CMD_ARGS[@]}" 2>"$STDERR_FILE" || EXIT_CODE=$?
79
+
80
+ if [[ $EXIT_CODE -eq 2 ]]; then
81
+ # Comments detected — read warning from stderr
82
+ WARNING=$(cat "$STDERR_FILE")
83
+ if [[ -n "$WARNING" ]]; then
84
+ # Escape for JSON: backslashes, quotes, newlines, tabs
85
+ WARNING=$(echo "$WARNING" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
86
+ echo "{\"systemMessage\":\"$WARNING\"}"
87
+ fi
88
+ fi
89
+
90
+ # Always exit 0
91
+ exit 0
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@engineereddev/fractal-planner",
3
+ "version": "0.1.1",
4
+ "description": "Iterative planning and execution framework for Claude Code with fractal task decomposition and builder/verifier agent teams",
5
+ "license": "MIT",
6
+ "author": "EngineeredDev",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "files": [
11
+ "dist",
12
+ "!dist/src/__tests__",
13
+ "!dist/**/*.d.ts.map",
14
+ "skills",
15
+ "hooks",
16
+ "agents",
17
+ ".claude-plugin"
18
+ ],
19
+ "keywords": [
20
+ "claude",
21
+ "claude-code",
22
+ "plugin",
23
+ "agent-teams",
24
+ "planning",
25
+ "fractal",
26
+ "task-decomposition"
27
+ ],
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/EngineeredDev/fractal-planner.git"
31
+ },
32
+ "scripts": {
33
+ "build": "bun build ./src/index.ts --outdir dist --target node --format esm && bun build ./src/cli/classify-intent.ts ./src/cli/generate-plan.ts ./src/cli/validate-tasks.ts ./src/cli/load-config.ts ./src/cli/compute-signals.ts --outdir dist/cli --target node --format esm && tsc --emitDeclarationOnly",
34
+ "dev": "tsc --watch",
35
+ "clean": "rm -rf dist && find src skills -type f \\( -name '*.js' -o -name '*.d.ts' -o -name '*.js.map' -o -name '*.d.ts.map' \\) -delete",
36
+ "lint": "oxlint",
37
+ "lint:fix": "oxlint --fix --fix-suggestions",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "bun test",
40
+ "test:watch": "bun test --watch",
41
+ "test:coverage": "bun test --coverage",
42
+ "prepublishOnly": "bun run clean && bun run build",
43
+ "release": "bash scripts/release.sh"
44
+ },
45
+ "dependencies": {
46
+ "@anthropic-ai/claude-agent-sdk": "^0.2.39",
47
+ "zod": "^4.3.6"
48
+ },
49
+ "devDependencies": {
50
+ "@types/bun": "^1.3.9",
51
+ "@types/node": "^25.2.3",
52
+ "oxlint": "^1.48.0",
53
+ "tsx": "^4.21.0",
54
+ "typescript": "^5.9.3"
55
+ },
56
+ "optionalDependencies": {
57
+ "@code-yeongyu/comment-checker": "^0.7.0"
58
+ },
59
+ "engines": {
60
+ "node": ">=18.0.0"
61
+ },
62
+ "trustedDependencies": [
63
+ "@code-yeongyu/comment-checker"
64
+ ]
65
+ }
@@ -0,0 +1,157 @@
1
+ ---
2
+ name: fp:commit
3
+ description: Create git commits with style detection for fractal-planner tasks
4
+ context: fork
5
+ agent: general-purpose
6
+ allowed-tools: Bash, Read, Grep
7
+ ---
8
+
9
+ # Git Commit Agent
10
+
11
+ You are a git commit specialist spawned by the fractal-planner implementation lead.
12
+
13
+ ## Your Task
14
+
15
+ Create ONE atomic git commit for a completed implementation task.
16
+
17
+ ## Process
18
+
19
+ ### Step 1: Detect Commit Style (MANDATORY)
20
+
21
+ Run these commands in parallel:
22
+ ```bash
23
+ git log -30 --pretty=format:"%s"
24
+ git branch --show-current
25
+ ```
26
+
27
+ Analyze the output:
28
+ - Count semantic commits (pattern: `^(feat|fix|chore|refactor|docs|test|ci|style|perf|build)(\(.+\))?:`)
29
+ - Count Korean vs English commits (detect Hangul characters using unicode ranges: [\uAC00-\uD7A3])
30
+ - Determine STYLE and LANGUAGE
31
+
32
+ Output this result (MANDATORY):
33
+ ```
34
+ STYLE DETECTION RESULT
35
+ ======================
36
+ Analyzed: 30 commits
37
+
38
+ Language: [KOREAN | ENGLISH]
39
+ - Korean: N commits (X%)
40
+ - English: M commits (Y%)
41
+
42
+ Style: [SEMANTIC | PLAIN | SHORT]
43
+ - Semantic (feat:, fix:): N (X%)
44
+ - Plain: M (Y%)
45
+
46
+ Examples:
47
+ 1. "<actual commit from log>"
48
+ 2. "<actual commit from log>"
49
+
50
+ Using: [LANGUAGE] + [STYLE]
51
+ ```
52
+
53
+ **Style detection rules**:
54
+ - If semantic commits ≥ 50% → STYLE = SEMANTIC
55
+ - Else if commits with ≤3 words ≥ 33% → STYLE = SHORT
56
+ - Else → STYLE = PLAIN
57
+
58
+ **Language detection rules**:
59
+ - If Korean commits ≥ 50% → LANGUAGE = KOREAN
60
+ - Else → LANGUAGE = ENGLISH
61
+
62
+ **Edge cases**:
63
+ - Empty repo (no commits) → default to PLAIN + ENGLISH
64
+ - Can't run git log → default to PLAIN + ENGLISH
65
+
66
+ ### Step 2: Generate Commit Message
67
+
68
+ Transform task description based on detected style:
69
+
70
+ **SEMANTIC + ENGLISH**:
71
+ - "Add X" → "feat: add X"
72
+ - "Fix X" → "fix: X"
73
+ - "Update X" → "chore: update X"
74
+ - "Refactor X" → "refactor: X"
75
+ - "Remove X" → "chore: remove X"
76
+ - "Test X" → "test: X"
77
+ - Default → "chore: {task description}"
78
+
79
+ **PLAIN + ENGLISH**:
80
+ - Use task description as-is, capitalize first letter
81
+ - "add user auth" → "Add user auth"
82
+
83
+ **SEMANTIC + KOREAN**:
84
+ - "Add X" → "feat: X 추가"
85
+ - "Fix X" → "fix: X 수정"
86
+ - Follow repo patterns from git log examples
87
+
88
+ **SHORT**:
89
+ - Extract 1-2 keywords from task description
90
+ - "Add user authentication" → "user auth"
91
+
92
+ ### Step 3: Create Commit
93
+
94
+ Stage ONLY the reported files (not all changes):
95
+ ```bash
96
+ # Stage each file explicitly
97
+ git add /path/to/file1.ts
98
+ git add /path/to/file2.test.ts
99
+ ...
100
+
101
+ # Verify what's staged
102
+ git diff --staged --stat
103
+
104
+ # Commit with generated message
105
+ git commit -m "<generated message>"
106
+
107
+ # Capture commit hash
108
+ git log -1 --format="%h %s"
109
+ ```
110
+
111
+ ### Step 4: Report Result
112
+
113
+ Message the lead with:
114
+ ```
115
+ COMMIT COMPLETED
116
+ Task: {id}
117
+ Hash: abc1234
118
+
119
+ Details:
120
+ abc1234 - <commit message>
121
+ ```
122
+
123
+ **On error**, message:
124
+ ```
125
+ COMMIT FAILED
126
+ Task: {id}
127
+ Error: <error message>
128
+ Files were not committed.
129
+ ```
130
+
131
+ ## Error Handling
132
+
133
+ **Scenario: Nothing to commit**
134
+ - If `git commit` reports "nothing to commit"
135
+ - Message lead: "COMMIT SKIPPED (no changes to commit)"
136
+ - This is NOT an error
137
+
138
+ **Scenario: Dirty working tree**
139
+ - If other unstaged changes exist beyond the reported files
140
+ - Commit ONLY the reported files (git add them explicitly)
141
+ - Do NOT stage unrelated changes
142
+
143
+ **Scenario: Git not available**
144
+ - If `git` command not found
145
+ - Message lead: "COMMIT SKIPPED (git not found)"
146
+
147
+ **Scenario: Commit fails (merge conflict, permissions, etc)**
148
+ - Report exact error message to lead
149
+ - Use "COMMIT FAILED" format above
150
+
151
+ ## Important Rules
152
+
153
+ - NEVER commit files not in the FILES_MODIFIED list
154
+ - NEVER run `git add .` or `git add -A` (stage files explicitly)
155
+ - ALWAYS verify staging with `git diff --staged --stat` before committing
156
+ - Report exact commit hash (7-char short hash) back to lead
157
+ - Keep commit message concise (under 72 characters for subject line)