all-for-claudecode 2.0.0 → 2.2.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 (67) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/MIGRATION.md +10 -7
  4. package/README.md +68 -119
  5. package/agents/afc-architect.md +16 -0
  6. package/agents/afc-impl-worker.md +40 -0
  7. package/agents/afc-security.md +11 -0
  8. package/bin/cli.mjs +1 -1
  9. package/commands/analyze.md +6 -7
  10. package/commands/architect.md +5 -7
  11. package/commands/auto.md +355 -102
  12. package/commands/checkpoint.md +3 -4
  13. package/commands/clarify.md +8 -1
  14. package/commands/debug.md +40 -3
  15. package/commands/doctor.md +12 -13
  16. package/commands/ideate.md +191 -0
  17. package/commands/implement.md +211 -66
  18. package/commands/init.md +76 -61
  19. package/commands/launch.md +181 -0
  20. package/commands/plan.md +86 -22
  21. package/commands/principles.md +6 -2
  22. package/commands/resume.md +1 -2
  23. package/commands/review.md +68 -18
  24. package/commands/security.md +10 -13
  25. package/commands/spec.md +60 -3
  26. package/commands/tasks.md +19 -4
  27. package/commands/test.md +24 -6
  28. package/docs/phase-gate-protocol.md +6 -6
  29. package/hooks/hooks.json +29 -3
  30. package/package.json +19 -11
  31. package/schemas/hooks.schema.json +75 -0
  32. package/schemas/marketplace.schema.json +52 -0
  33. package/schemas/plugin.schema.json +53 -0
  34. package/scripts/afc-bash-guard.sh +6 -6
  35. package/scripts/afc-blast-radius.sh +418 -0
  36. package/scripts/afc-config-change.sh +6 -4
  37. package/scripts/afc-consistency-check.sh +261 -0
  38. package/scripts/afc-dag-validate.mjs +94 -0
  39. package/scripts/afc-dag-validate.sh +142 -0
  40. package/scripts/afc-failure-hint.sh +6 -4
  41. package/scripts/afc-parallel-validate.mjs +81 -0
  42. package/scripts/afc-parallel-validate.sh +33 -45
  43. package/scripts/afc-permission-request.sh +56 -11
  44. package/scripts/afc-pipeline-manage.sh +46 -46
  45. package/scripts/afc-preflight-check.sh +6 -3
  46. package/scripts/afc-schema-validate.sh +225 -0
  47. package/scripts/afc-session-end.sh +5 -5
  48. package/scripts/afc-state.sh +256 -0
  49. package/scripts/afc-stop-gate.sh +32 -24
  50. package/scripts/afc-subagent-context.sh +15 -6
  51. package/scripts/afc-subagent-stop.sh +4 -2
  52. package/scripts/afc-task-completed-gate.sh +19 -25
  53. package/scripts/afc-teammate-idle.sh +9 -14
  54. package/scripts/afc-test-pre-gen.sh +141 -0
  55. package/scripts/afc-timeline-log.sh +9 -6
  56. package/scripts/afc-user-prompt-submit.sh +8 -10
  57. package/scripts/afc-worktree-create.sh +56 -0
  58. package/scripts/afc-worktree-remove.sh +47 -0
  59. package/scripts/install-shellspec.sh +38 -0
  60. package/scripts/pre-compact-checkpoint.sh +6 -4
  61. package/scripts/session-start-context.sh +9 -8
  62. package/scripts/track-afc-changes.sh +6 -9
  63. package/templates/afc.config.template.md +12 -76
  64. package/templates/afc.config.express-api.md +0 -99
  65. package/templates/afc.config.monorepo.md +0 -98
  66. package/templates/afc.config.nextjs-fsd.md +0 -107
  67. package/templates/afc.config.react-spa.md +0 -96
@@ -0,0 +1,261 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # afc-consistency-check.sh — Cross-reference validation for project consistency
5
+ # Checks: config placeholders, agent names, hook scripts, test coverage
6
+ # Run as part of: npm run lint
7
+
8
+ # shellcheck disable=SC2329
9
+ cleanup() {
10
+ :
11
+ }
12
+ trap cleanup EXIT
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
15
+ PROJECT_DIR="${1:-$(cd "$SCRIPT_DIR/.." && pwd)}"
16
+ ERRORS=0
17
+ WARNINGS=0
18
+
19
+ # --- Helpers ---
20
+
21
+ fail() {
22
+ printf "[afc:consistency] FAIL: %s\n" "$1" >&2
23
+ ERRORS=$((ERRORS + 1))
24
+ }
25
+
26
+ warn() {
27
+ printf "[afc:consistency] WARN: %s\n" "$1" >&2
28
+ WARNINGS=$((WARNINGS + 1))
29
+ }
30
+
31
+ ok() {
32
+ printf "[afc:consistency] ✓ %s\n" "$1"
33
+ }
34
+
35
+ # --- Check 1: Config Placeholder Validation ---
36
+ # Verify all {config.*} references in commands/ and docs/ map to known config keys
37
+
38
+ check_config_placeholders() {
39
+ local template="$PROJECT_DIR/templates/afc.config.template.md"
40
+ if [ ! -f "$template" ]; then
41
+ warn "Config template not found: $template"
42
+ return
43
+ fi
44
+
45
+ # Extract valid config keys from template
46
+ # YAML keys: ci, gate, test
47
+ local yaml_keys
48
+ yaml_keys=$(grep -oE '^\s*[a-z_]+:' "$template" 2>/dev/null | sed 's/[[:space:]]*//;s/://' | sort -u || true)
49
+ # Section headers → lowercase with underscores: Architecture → architecture, Code Style → code_style, Project Context → project_context
50
+ local section_keys
51
+ section_keys=$(grep -oE '^## [A-Za-z ]+' "$template" 2>/dev/null \
52
+ | sed 's/^## //' \
53
+ | tr '[:upper:]' '[:lower:]' \
54
+ | sed 's/ /_/g' \
55
+ | sort -u || true)
56
+
57
+ local valid_keys
58
+ valid_keys=$(printf '%s\n%s\n' "$yaml_keys" "$section_keys" | sort -u)
59
+
60
+ # Extract all {config.*} references from commands and docs
61
+ local refs
62
+ refs=$(grep -rohE '\{config\.[a-z_]+\}' "$PROJECT_DIR/commands/" "$PROJECT_DIR/docs/" 2>/dev/null \
63
+ | sed 's/{config\.//;s/}//' \
64
+ | sort -u || true)
65
+
66
+ local count=0
67
+ local invalid=0
68
+ for ref in $refs; do
69
+ count=$((count + 1))
70
+ if ! printf '%s\n' "$valid_keys" | grep -qxF "$ref"; then
71
+ fail "{config.$ref} referenced but not defined in config template"
72
+ invalid=$((invalid + 1))
73
+ fi
74
+ done
75
+
76
+ if [ "$invalid" -eq 0 ]; then
77
+ ok "Config placeholders: $count references, all valid"
78
+ fi
79
+ }
80
+
81
+ # --- Check 2: Agent Name Consistency ---
82
+ # Verify subagent_type references in commands match agent definitions
83
+
84
+ check_agent_names() {
85
+ local agents_dir="$PROJECT_DIR/agents"
86
+ if [ ! -d "$agents_dir" ]; then
87
+ warn "Agents directory not found"
88
+ return
89
+ fi
90
+
91
+ # Extract agent names from agent files (name: field in frontmatter)
92
+ local defined_agents
93
+ defined_agents=$(grep -h '^name:' "$agents_dir"/*.md 2>/dev/null \
94
+ | sed 's/^name:[[:space:]]*//' \
95
+ | tr -d '"' \
96
+ | sort -u || true)
97
+
98
+ # Extract subagent_type references from commands (afc:agent-name pattern)
99
+ local referenced_agents
100
+ referenced_agents=$(grep -rohE 'subagent_type:[[:space:]]*"afc:[^"]*"' "$PROJECT_DIR/commands/" 2>/dev/null \
101
+ | sed 's/.*"afc://;s/"//' \
102
+ | sort -u || true)
103
+
104
+ local count=0
105
+ local invalid=0
106
+ for ref in $referenced_agents; do
107
+ count=$((count + 1))
108
+ if ! printf '%s\n' "$defined_agents" | grep -qxF "$ref"; then
109
+ fail "subagent_type 'afc:$ref' referenced but no agents/$ref.md found"
110
+ invalid=$((invalid + 1))
111
+ fi
112
+ done
113
+
114
+ # Check for unprefixed subagent_type that should have afc: prefix
115
+ local unprefixed
116
+ unprefixed=$(grep -rohE 'subagent_type:[[:space:]]*"afc-[^"]*"' "$PROJECT_DIR/commands/" 2>/dev/null \
117
+ | sed 's/.*subagent_type:[[:space:]]*"//;s/".*//' \
118
+ | sort -u || true)
119
+ for ref in $unprefixed; do
120
+ if printf '%s\n' "$defined_agents" | grep -qxF "$ref"; then
121
+ fail "subagent_type '$ref' should use 'afc:$ref' prefix (found in agents/)"
122
+ invalid=$((invalid + 1))
123
+ fi
124
+ done
125
+
126
+ if [ "$invalid" -eq 0 ]; then
127
+ ok "Agent names: $count references, all consistent"
128
+ fi
129
+ }
130
+
131
+ # --- Check 3: Hook Script Existence ---
132
+ # Verify all scripts referenced in hooks.json actually exist
133
+
134
+ check_hook_scripts() {
135
+ local hooks_file="$PROJECT_DIR/hooks/hooks.json"
136
+ if [ ! -f "$hooks_file" ]; then
137
+ warn "hooks.json not found"
138
+ return
139
+ fi
140
+
141
+ local scripts
142
+ scripts=$(grep -oE 'scripts/[^"]+\.sh' "$hooks_file" 2>/dev/null | sort -u || true)
143
+
144
+ local count=0
145
+ local missing=0
146
+ for script in $scripts; do
147
+ count=$((count + 1))
148
+ if [ ! -f "$PROJECT_DIR/$script" ]; then
149
+ fail "hooks.json references '$script' but file not found"
150
+ missing=$((missing + 1))
151
+ fi
152
+ done
153
+
154
+ if [ "$missing" -eq 0 ]; then
155
+ ok "Hook scripts: $count references, all exist"
156
+ fi
157
+ }
158
+
159
+ # --- Check 4: Test Coverage ---
160
+ # Verify each afc-*.sh script (except afc-state.sh library) has a spec file
161
+
162
+ check_test_coverage() {
163
+ local count=0
164
+ local missing=0
165
+ for script in "$PROJECT_DIR"/scripts/afc-*.sh; do
166
+ local scriptname
167
+ scriptname=$(basename "$script" .sh)
168
+ # Skip shared library and self (validation script)
169
+ if [ "$scriptname" = "afc-state" ] || [ "$scriptname" = "afc-consistency-check" ]; then
170
+ continue
171
+ fi
172
+ count=$((count + 1))
173
+ if [ ! -f "$PROJECT_DIR/spec/${scriptname}_spec.sh" ]; then
174
+ fail "scripts/$scriptname.sh has no spec/${scriptname}_spec.sh"
175
+ missing=$((missing + 1))
176
+ fi
177
+ done
178
+
179
+ if [ "$missing" -eq 0 ]; then
180
+ ok "Test coverage: $count scripts, all have specs"
181
+ fi
182
+ }
183
+
184
+ # --- Check 5: Version Sync ---
185
+ # Verify version numbers match across package.json, plugin.json, marketplace.json
186
+
187
+ check_version_sync() {
188
+ local pkg="$PROJECT_DIR/package.json"
189
+ local plugin="$PROJECT_DIR/.claude-plugin/plugin.json"
190
+ local market="$PROJECT_DIR/.claude-plugin/marketplace.json"
191
+
192
+ if [ ! -f "$pkg" ] || [ ! -f "$plugin" ] || [ ! -f "$market" ]; then
193
+ warn "One or more version files missing"
194
+ return
195
+ fi
196
+
197
+ local pkg_ver plugin_ver market_meta market_plugin
198
+ if command -v jq >/dev/null 2>&1; then
199
+ pkg_ver=$(jq -r '.version' "$pkg")
200
+ plugin_ver=$(jq -r '.version' "$plugin")
201
+ market_meta=$(jq -r '.metadata.version' "$market")
202
+ market_plugin=$(jq -r '.plugins[0].version' "$market")
203
+ else
204
+ pkg_ver=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg" | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//;s/"//')
205
+ plugin_ver=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$plugin" | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//;s/"//')
206
+ market_meta=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$market" | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//;s/"//')
207
+ market_plugin=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$market" | sed -n '2p' | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//;s/"//')
208
+ fi
209
+
210
+ if [ "$pkg_ver" = "$plugin_ver" ] && [ "$plugin_ver" = "$market_meta" ] && [ "$market_meta" = "$market_plugin" ]; then
211
+ ok "Version sync: $pkg_ver (all 4 match)"
212
+ else
213
+ fail "Version mismatch: package.json=$pkg_ver plugin.json=$plugin_ver marketplace.meta=$market_meta marketplace.plugin=$market_plugin"
214
+ fi
215
+ }
216
+
217
+ # --- Check 6: SSOT Phase Constants ---
218
+ # Verify SSOT phase list in afc-state.sh is not duplicated in other scripts
219
+
220
+ check_phase_ssot() {
221
+ # shellcheck source=afc-state.sh
222
+ . "$SCRIPT_DIR/afc-state.sh"
223
+
224
+ local dupes=0
225
+ # Look for hardcoded phase lists (pipe-separated patterns with 3+ known phases)
226
+ for script in "$PROJECT_DIR"/scripts/afc-*.sh; do
227
+ local scriptname
228
+ scriptname=$(basename "$script")
229
+ # Skip the SSOT source itself and this validation script
230
+ if [ "$scriptname" = "afc-state.sh" ] || [ "$scriptname" = "afc-consistency-check.sh" ]; then
231
+ continue
232
+ fi
233
+ # Check for hardcoded phase case patterns (spec|plan|...|clean style)
234
+ if grep -qE 'spec\|plan\|.*\|clean' "$script" 2>/dev/null; then
235
+ fail "$scriptname contains hardcoded phase list — use SSOT helpers from afc-state.sh"
236
+ dupes=$((dupes + 1))
237
+ fi
238
+ done
239
+
240
+ if [ "$dupes" -eq 0 ]; then
241
+ ok "Phase SSOT: no hardcoded phase lists found in scripts"
242
+ fi
243
+ }
244
+
245
+ # --- Run All Checks ---
246
+
247
+ printf "[afc:consistency] Running cross-reference validation...\n"
248
+
249
+ check_config_placeholders
250
+ check_agent_names
251
+ check_hook_scripts
252
+ check_test_coverage
253
+ check_version_sync
254
+ check_phase_ssot
255
+
256
+ printf "\n[afc:consistency] Done: %d errors, %d warnings\n" "$ERRORS" "$WARNINGS"
257
+
258
+ if [ "$ERRORS" -gt 0 ]; then
259
+ exit 1
260
+ fi
261
+ exit 0
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env node
2
+
3
+ // DAG Validator: Check task dependency graph for circular references
4
+ // Parses tasks.md and validates that depends: declarations form a valid DAG.
5
+ //
6
+ // Usage: afc-dag-validate.mjs <tasks_file_path>
7
+ // Exit 0: valid DAG (no cycles)
8
+ // Exit 1: cycle detected — prints cycle path
9
+
10
+ import { readFileSync } from 'fs';
11
+
12
+ const tasksFile = process.argv[2];
13
+ if (!tasksFile) {
14
+ process.stderr.write(`Usage: ${process.argv[1]} <tasks_file_path>\n`);
15
+ process.exit(1);
16
+ }
17
+
18
+ let content;
19
+ try {
20
+ content = readFileSync(tasksFile, 'utf8');
21
+ } catch {
22
+ process.stderr.write(`Error: file not found: ${tasksFile}\n`);
23
+ process.exit(1);
24
+ }
25
+
26
+ // Parse tasks and dependencies
27
+ const taskPattern = /^\s*-\s*\[[ xX]\]\s+(T\d+)/;
28
+ const depsPattern = /depends:\s*\[([^\]]*)\]/;
29
+
30
+ const nodes = new Set();
31
+ const edges = new Map(); // from -> [to, ...]
32
+
33
+ for (const line of content.split(/\r?\n/)) {
34
+ const taskMatch = line.match(taskPattern);
35
+ if (!taskMatch) continue;
36
+
37
+ const taskId = taskMatch[1];
38
+ nodes.add(taskId);
39
+ if (!edges.has(taskId)) edges.set(taskId, []);
40
+
41
+ const depsMatch = line.match(depsPattern);
42
+ if (depsMatch) {
43
+ const deps = depsMatch[1].match(/T\d+/g) || [];
44
+ for (const dep of deps) {
45
+ // Edge: dep → taskId (taskId depends on dep)
46
+ if (!edges.has(dep)) edges.set(dep, []);
47
+ edges.get(dep).push(taskId);
48
+ }
49
+ }
50
+ }
51
+
52
+ if (nodes.size === 0) {
53
+ process.stdout.write('Valid: no tasks found, nothing to validate\n');
54
+ process.exit(0);
55
+ }
56
+
57
+ // DFS cycle detection with full cycle path
58
+ const WHITE = 0, GRAY = 1, BLACK = 2;
59
+ const color = new Map();
60
+ const parent = new Map();
61
+
62
+ for (const node of nodes) color.set(node, WHITE);
63
+
64
+ function dfs(node) {
65
+ color.set(node, GRAY);
66
+ for (const neighbor of (edges.get(node) || [])) {
67
+ if (!color.has(neighbor)) continue;
68
+ if (color.get(neighbor) === GRAY) {
69
+ // Cycle found — reconstruct path
70
+ const cycle = [neighbor, node];
71
+ let cur = node;
72
+ while (cur !== neighbor && parent.has(cur)) {
73
+ cur = parent.get(cur);
74
+ cycle.push(cur);
75
+ }
76
+ cycle.reverse();
77
+ process.stdout.write(`CYCLE: ${cycle.join(' → ')}\n`);
78
+ process.exit(1);
79
+ }
80
+ if (color.get(neighbor) === WHITE) {
81
+ parent.set(neighbor, node);
82
+ dfs(neighbor);
83
+ }
84
+ }
85
+ color.set(node, BLACK);
86
+ }
87
+
88
+ for (const node of nodes) {
89
+ if (color.get(node) === WHITE) {
90
+ dfs(node);
91
+ }
92
+ }
93
+
94
+ process.stdout.write(`Valid: ${nodes.size} tasks, no circular dependencies\n`);
@@ -0,0 +1,142 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # DAG Validator: Check task dependency graph for circular references
5
+ # Calls Node.js ESM version if available, falls back to bash implementation.
6
+ #
7
+ # Usage: afc-dag-validate.sh <tasks_file_path>
8
+ # Exit 0: valid DAG (no cycles)
9
+ # Exit 1: cycle detected — prints cycle path
10
+
11
+ # shellcheck disable=SC2329
12
+ cleanup() {
13
+ :
14
+ }
15
+ trap cleanup EXIT
16
+
17
+ TASKS_FILE="${1:-}"
18
+ if [ -z "$TASKS_FILE" ]; then
19
+ printf 'Usage: %s <tasks_file_path>\n' "$0" >&2
20
+ exit 1
21
+ fi
22
+
23
+ if [ ! -f "$TASKS_FILE" ]; then
24
+ printf 'Error: file not found: %s\n' "$TASKS_FILE" >&2
25
+ exit 1
26
+ fi
27
+
28
+ # --- Node.js fast path ---
29
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
30
+ if command -v node >/dev/null 2>&1; then
31
+ node "$SCRIPT_DIR/afc-dag-validate.mjs" "$TASKS_FILE"
32
+ exit $?
33
+ fi
34
+
35
+ # --- Bash fallback ---
36
+
37
+ # Parse tasks and dependencies
38
+ TMPDIR_WORK="$(mktemp -d)"
39
+ # shellcheck disable=SC2064
40
+ trap "rm -rf '$TMPDIR_WORK'; :" EXIT
41
+
42
+ NODES_FILE="$TMPDIR_WORK/nodes.txt"
43
+ EDGES_FILE="$TMPDIR_WORK/edges.txt"
44
+ : > "$NODES_FILE"
45
+ : > "$EDGES_FILE"
46
+
47
+ while IFS= read -r line || [ -n "$line" ]; do
48
+ if ! printf '%s\n' "$line" | grep -qE '^\s*-\s*\[[ xX]\]\s+T[0-9]+'; then
49
+ continue
50
+ fi
51
+
52
+ task_id="$(printf '%s\n' "$line" | grep -oE 'T[0-9]+' | head -1)"
53
+ [ -z "$task_id" ] && continue
54
+
55
+ printf '%s\n' "$task_id" >> "$NODES_FILE"
56
+
57
+ deps_raw="$(printf '%s\n' "$line" | grep -oE 'depends:\s*\[([^]]*)\]' | sed 's/depends:[[:space:]]*\[//;s/\]//' || true)"
58
+ if [ -n "$deps_raw" ]; then
59
+ printf '%s\n' "$deps_raw" | tr ',' '\n' | while IFS= read -r dep; do
60
+ dep_id="$(printf '%s\n' "$dep" | grep -oE 'T[0-9]+' || true)"
61
+ if [ -n "$dep_id" ]; then
62
+ printf '%s\t%s\n' "$dep_id" "$task_id" >> "$EDGES_FILE"
63
+ fi
64
+ done
65
+ fi
66
+ done < "$TASKS_FILE"
67
+
68
+ TOTAL_TASKS=$(wc -l < "$NODES_FILE" | tr -d ' ')
69
+
70
+ if [ "$TOTAL_TASKS" -eq 0 ]; then
71
+ printf 'Valid: no tasks found, nothing to validate\n'
72
+ exit 0
73
+ fi
74
+
75
+ # DFS cycle detection using color marking
76
+ COLOR_DIR="$TMPDIR_WORK/colors"
77
+ mkdir -p "$COLOR_DIR"
78
+
79
+ while IFS= read -r node; do
80
+ printf '0' > "$COLOR_DIR/$node"
81
+ done < "$NODES_FILE"
82
+
83
+ CYCLE_FOUND=0
84
+ CYCLE_PATH=""
85
+
86
+ dfs_check() {
87
+ local start="$1"
88
+ local stack_file="$TMPDIR_WORK/stack.txt"
89
+ printf '%s\n' "$start" > "$stack_file"
90
+
91
+ while [ -s "$stack_file" ]; do
92
+ current="$(tail -1 "$stack_file")"
93
+
94
+ color_file="$COLOR_DIR/$current"
95
+ [ ! -f "$color_file" ] && { sed -i '' '$d' "$stack_file" 2>/dev/null || sed -i '$d' "$stack_file"; continue; }
96
+
97
+ color="$(cat "$color_file")"
98
+
99
+ if [ "$color" = "0" ]; then
100
+ printf '1' > "$color_file"
101
+
102
+ neighbors="$(grep -E "^${current}\t" "$EDGES_FILE" | cut -f2 || true)"
103
+ if [ -n "$neighbors" ]; then
104
+ while IFS= read -r neighbor; do
105
+ [ -z "$neighbor" ] && continue
106
+ nb_color_file="$COLOR_DIR/$neighbor"
107
+ [ ! -f "$nb_color_file" ] && continue
108
+
109
+ nb_color="$(cat "$nb_color_file")"
110
+ if [ "$nb_color" = "1" ]; then
111
+ CYCLE_FOUND=1
112
+ CYCLE_PATH="CYCLE: $neighbor → $current → $neighbor"
113
+ return
114
+ elif [ "$nb_color" = "0" ]; then
115
+ printf '%s\n' "$neighbor" >> "$stack_file"
116
+ fi
117
+ done <<EOF_NEIGHBORS
118
+ $neighbors
119
+ EOF_NEIGHBORS
120
+ fi
121
+ elif [ "$color" = "1" ]; then
122
+ printf '2' > "$color_file"
123
+ sed -i '' '$d' "$stack_file" 2>/dev/null || sed -i '$d' "$stack_file"
124
+ else
125
+ sed -i '' '$d' "$stack_file" 2>/dev/null || sed -i '$d' "$stack_file"
126
+ fi
127
+ done
128
+ }
129
+
130
+ while IFS= read -r node; do
131
+ color="$(cat "$COLOR_DIR/$node" 2>/dev/null || printf '0')"
132
+ if [ "$color" = "0" ]; then
133
+ dfs_check "$node"
134
+ if [ "$CYCLE_FOUND" -eq 1 ]; then
135
+ printf '%s\n' "$CYCLE_PATH"
136
+ exit 1
137
+ fi
138
+ fi
139
+ done < "$NODES_FILE"
140
+
141
+ printf 'Valid: %d tasks, no circular dependencies\n' "$TOTAL_TASKS"
142
+ exit 0
@@ -2,6 +2,9 @@
2
2
  set -euo pipefail
3
3
  # PostToolUseFailure Hook: Output hints matching error patterns on tool failure
4
4
 
5
+ # shellcheck source=afc-state.sh
6
+ . "$(dirname "$0")/afc-state.sh"
7
+
5
8
  # shellcheck disable=SC2329
6
9
  cleanup() {
7
10
  # Placeholder for temporary resource cleanup if needed
@@ -10,7 +13,6 @@ cleanup() {
10
13
  trap cleanup EXIT
11
14
 
12
15
  PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
13
- PIPELINE_FLAG="$PROJECT_DIR/.claude/.afc-active"
14
16
  FAILURES_LOG="$PROJECT_DIR/.claude/.afc-failures.log"
15
17
 
16
18
  # Parse input from stdin
@@ -29,7 +31,7 @@ TOOL_NAME="${TOOL_NAME:-unknown}"
29
31
  ERROR="${ERROR:-}"
30
32
 
31
33
  # If pipeline is active, log failure (normalize error message to single line)
32
- if [ -f "$PIPELINE_FLAG" ] && [ -n "$ERROR" ]; then
34
+ if afc_state_is_active && [ -n "$ERROR" ]; then
33
35
  ERROR_ONELINE=$(printf '%s\n' "$ERROR" | head -1 | cut -c1-200)
34
36
  printf '%s\n' "$(date +%s) $TOOL_NAME: $ERROR_ONELINE" >> "$FAILURES_LOG"
35
37
  fi
@@ -64,14 +66,14 @@ esac
64
66
  if [ -n "$HINT" ]; then
65
67
  # Generate safe JSON with jq if available, otherwise strip special chars and use printf
66
68
  if command -v jq &> /dev/null; then
67
- jq -n --arg ctx "[AFC HINT] $HINT (tool: $TOOL_NAME)" \
69
+ jq -n --arg ctx "[afc:hint] $HINT (tool: $TOOL_NAME)" \
68
70
  '{"hookSpecificOutput":{"hookEventName":"PostToolUseFailure","additionalContext":$ctx}}' 2>/dev/null || true
69
71
  else
70
72
  # shellcheck disable=SC1003
71
73
  SAFE_HINT=$(printf '%s' "$HINT" | tr -d '"' | tr -d '\\')
72
74
  # shellcheck disable=SC1003
73
75
  SAFE_TOOL=$(printf '%s' "$TOOL_NAME" | tr -d '"' | tr -d '\\')
74
- printf '{"hookSpecificOutput":{"hookEventName":"PostToolUseFailure","additionalContext":"[AFC HINT] %s (tool: %s)"}}\n' "$SAFE_HINT" "$SAFE_TOOL"
76
+ printf '{"hookSpecificOutput":{"hookEventName":"PostToolUseFailure","additionalContext":"[afc:hint] %s (tool: %s)"}}\n' "$SAFE_HINT" "$SAFE_TOOL"
75
77
  fi
76
78
  fi
77
79
 
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Parallel Task Validator: Check for file path conflicts among [P] tasks
4
+ // within the same phase.
5
+ //
6
+ // Usage: afc-parallel-validate.mjs <tasks_file_path>
7
+ // Exit 0: valid (no overlaps, or no [P] tasks)
8
+ // Exit 1: overlaps detected — prints conflict details
9
+
10
+ import { readFileSync } from 'fs';
11
+
12
+ const tasksFile = process.argv[2];
13
+ if (!tasksFile) {
14
+ process.stderr.write(`Usage: ${process.argv[1]} <tasks_file_path>\n`);
15
+ process.exit(1);
16
+ }
17
+
18
+ let content;
19
+ try {
20
+ content = readFileSync(tasksFile, 'utf8');
21
+ } catch {
22
+ process.stderr.write(`Error: file not found: ${tasksFile}\n`);
23
+ process.exit(1);
24
+ }
25
+
26
+ const phasePattern = /^## Phase (\d+)/;
27
+ const taskPPattern = /^\s*-\s*\[[ xX]\]\s+(T\d+)\s+\[P\]/;
28
+ const backtickPattern = /`([^`]+)`/g;
29
+
30
+ let currentPhase = '';
31
+ let totalPTasks = 0;
32
+ const conflicts = [];
33
+ const phasesWithP = new Set();
34
+
35
+ // Single-pass parsing
36
+ let phaseFileMap = new Map(); // file_path -> task_id
37
+
38
+ for (const line of content.split(/\r?\n/)) {
39
+ const phaseMatch = line.match(phasePattern);
40
+ if (phaseMatch) {
41
+ currentPhase = phaseMatch[1];
42
+ phaseFileMap = new Map();
43
+ continue;
44
+ }
45
+
46
+ if (!currentPhase) continue;
47
+
48
+ const taskMatch = line.match(taskPPattern);
49
+ if (!taskMatch) continue;
50
+
51
+ const taskId = taskMatch[1];
52
+ totalPTasks++;
53
+ phasesWithP.add(currentPhase);
54
+
55
+ // Extract backtick-wrapped file paths (containing / or .)
56
+ let match;
57
+ backtickPattern.lastIndex = 0;
58
+ while ((match = backtickPattern.exec(line)) !== null) {
59
+ const path = match[1];
60
+ if (!/[/.]/.test(path)) continue;
61
+
62
+ const existing = phaseFileMap.get(path);
63
+ if (existing) {
64
+ conflicts.push(`CONFLICT: Phase ${currentPhase} — ${existing} and ${taskId} both target ${path}`);
65
+ } else {
66
+ phaseFileMap.set(path, taskId);
67
+ }
68
+ }
69
+ }
70
+
71
+ if (totalPTasks === 0) {
72
+ process.stdout.write('Valid: no [P] tasks found, nothing to validate\n');
73
+ process.exit(0);
74
+ }
75
+
76
+ if (conflicts.length > 0) {
77
+ process.stdout.write(conflicts.join('\n') + '\n');
78
+ process.exit(1);
79
+ }
80
+
81
+ process.stdout.write(`Valid: ${totalPTasks} [P] tasks across ${phasesWithP.size} phases, no file overlaps\n`);