all-for-claudecode 2.2.0 → 2.3.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-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/MIGRATION.md +24 -0
- package/README.md +85 -17
- package/agents/afc-architect.md +7 -0
- package/agents/afc-security.md +6 -0
- package/commands/analyze.md +53 -75
- package/commands/architect.md +1 -1
- package/commands/auto.md +42 -22
- package/commands/doctor.md +16 -2
- package/commands/implement.md +2 -2
- package/commands/init.md +7 -5
- package/commands/launch.md +1 -1
- package/commands/plan.md +4 -4
- package/commands/resume.md +8 -3
- package/commands/review.md +1 -1
- package/commands/spec.md +3 -2
- package/commands/tasks.md +2 -2
- package/commands/validate.md +125 -0
- package/docs/phase-gate-protocol.md +17 -5
- package/hooks/hooks.json +2 -4
- package/package.json +1 -1
- package/scripts/afc-consistency-check.sh +32 -7
- package/scripts/afc-dag-validate.sh +3 -2
- package/scripts/afc-notify.sh +5 -6
- package/scripts/afc-parallel-validate.sh +3 -5
- package/scripts/afc-permission-request.sh +16 -2
- package/scripts/afc-pipeline-manage.sh +16 -0
- package/scripts/afc-state.sh +22 -26
- package/scripts/afc-stop-todo-check.sh +83 -0
- package/scripts/afc-task-completed-gate.sh +7 -1
- package/scripts/pre-compact-checkpoint.sh +19 -4
- package/scripts/session-start-context.sh +11 -3
package/commands/init.md
CHANGED
|
@@ -136,23 +136,24 @@ Check for presence of `<!-- AFC:START -->` or `<!-- SELFISH:START -->` marker.
|
|
|
136
136
|
|
|
137
137
|
#### Step 2. Conflict Pattern Scan
|
|
138
138
|
|
|
139
|
-
Search
|
|
139
|
+
Search CLAUDE.md for the patterns below. **IMPORTANT: EXCLUDE content inside any marker blocks (`<!-- *:START -->` ~ `<!-- *:END -->`). Only scan unguarded content outside marker blocks.** Other tools (OMC, etc.) manage their own blocks — their internal agent names are not conflicts.
|
|
140
140
|
|
|
141
141
|
**A. Marker Block Detection**
|
|
142
142
|
- Regex: `<!-- ([A-Z0-9_-]+):START -->` ~ `<!-- \1:END -->`
|
|
143
143
|
- Record all found block names and line ranges
|
|
144
|
+
- **Strip these ranges from the scan target** — only scan lines NOT inside any marker block
|
|
144
145
|
|
|
145
146
|
**B. Agent Routing Conflict Detection**
|
|
146
|
-
|
|
147
|
+
In the **unguarded** (non-marker-block) content only, find directives containing these keywords:
|
|
147
148
|
- `executor`, `deep-executor` — conflicts with afc:implement
|
|
148
149
|
- `code-reviewer`, `quality-reviewer`, `style-reviewer`, `api-reviewer`, `security-reviewer`, `performance-reviewer` — conflicts with afc:review
|
|
149
150
|
- `debugger` (in agent routing context) — conflicts with afc:debug
|
|
150
151
|
- `planner` (in agent routing context) — conflicts with afc:plan
|
|
151
|
-
- `analyst`, `verifier` — conflicts with afc:
|
|
152
|
+
- `analyst`, `verifier` — conflicts with afc:validate
|
|
152
153
|
- `test-engineer` — conflicts with afc:test
|
|
153
154
|
|
|
154
155
|
**C. Skill Routing Conflict Detection**
|
|
155
|
-
|
|
156
|
+
In the **unguarded** content only, find these patterns:
|
|
156
157
|
- Another tool's skill trigger table (e.g., tables like `| situation | skill |`)
|
|
157
158
|
- `delegate to`, `route to`, `always use` + agent name combinations
|
|
158
159
|
- Directives related to `auto-trigger`, `intent detection`, `intent-based routing`
|
|
@@ -219,7 +220,8 @@ IMPORTANT: For requests matching the afc skill routing table below, always invok
|
|
|
219
220
|
| Debug | `afc:debug` | bug, error, broken, fix |
|
|
220
221
|
| Test | `afc:test` | test, coverage |
|
|
221
222
|
| Design | `afc:plan` | design, plan, how to implement |
|
|
222
|
-
|
|
|
223
|
+
| Validate | `afc:validate` | consistency, validate, validate artifacts |
|
|
224
|
+
| Analyze | `afc:analyze` | analyze, explore, investigate code, root cause |
|
|
223
225
|
| Spec | `afc:spec` | spec, specification |
|
|
224
226
|
| Tasks | `afc:tasks` | break down tasks, decompose |
|
|
225
227
|
| Research | `afc:research` | research, investigate |
|
package/commands/launch.md
CHANGED
|
@@ -21,7 +21,7 @@ model: sonnet
|
|
|
21
21
|
## Arguments
|
|
22
22
|
|
|
23
23
|
- `$ARGUMENTS` — (optional) One of:
|
|
24
|
-
- Version tag: `"v2.2.
|
|
24
|
+
- Version tag: e.g. `"v2.2.1"` — uses this as the release version
|
|
25
25
|
- `"auto"` — auto-detects version from package.json/Cargo.toml/pyproject.toml
|
|
26
26
|
- Not specified: prompts for version
|
|
27
27
|
|
package/commands/plan.md
CHANGED
|
@@ -45,9 +45,9 @@ If config file is missing:
|
|
|
45
45
|
4. Read **.claude/afc/memory/principles.md** (if present)
|
|
46
46
|
5. Read **CLAUDE.md** project context
|
|
47
47
|
6. **Memory loading** (skip gracefully if directories are empty or absent):
|
|
48
|
-
- **Quality history**: if `.claude/afc/memory/quality-history/*.json` exists, load recent
|
|
49
|
-
- **Decisions**: if `.claude/afc/memory/decisions/` exists, load
|
|
50
|
-
- **Reviews**: if `.claude/afc/memory/reviews/` exists, scan for recurring finding patterns (same file/category appearing in 2+ reviews). Flag as known risk areas.
|
|
48
|
+
- **Quality history**: if `.claude/afc/memory/quality-history/*.json` exists, load the **most recent 10 files** (sorted by filename descending) and display trend: "Last {N} pipelines: avg critic_fixes {X}, avg ci_failures {Y}". Use trends to inform risk assessment.
|
|
49
|
+
- **Decisions**: if `.claude/afc/memory/decisions/` exists, load the **most recent 30 files** (sorted by filename descending) and check for conflicts with the current feature's design direction.
|
|
50
|
+
- **Reviews**: if `.claude/afc/memory/reviews/` exists, load the **most recent 15 files** (sorted by filename descending) and scan for recurring finding patterns (same file/category appearing in 2+ reviews). Flag as known risk areas.
|
|
51
51
|
|
|
52
52
|
### 2. Clarification Check
|
|
53
53
|
|
|
@@ -205,7 +205,7 @@ Run the critic loop until convergence. Safety cap: 5 passes.
|
|
|
205
205
|
| **FEASIBILITY** | Is it compatible with the existing codebase? Are dependencies available? |
|
|
206
206
|
| **ARCHITECTURE** | Does it comply with {config.architecture} rules? |
|
|
207
207
|
| **CROSS_CONSISTENCY** | Spec↔Plan cross-artifact validation (see checklist below) |
|
|
208
|
-
| **RISK** | Are there any unidentified risks? Additionally, if `.claude/afc/memory/retrospectives/` directory contains files from previous pipeline runs, load
|
|
208
|
+
| **RISK** | Are there any unidentified risks? Additionally, if `.claude/afc/memory/retrospectives/` directory contains files from previous pipeline runs, load the **most recent 10 files** (sorted by filename descending) and check whether the current plan addresses the patterns recorded there. Tag matched patterns with `[RETRO-CHECKED]`. |
|
|
209
209
|
| **PRINCIPLES** | Does it not violate the MUST principles in principles.md? |
|
|
210
210
|
|
|
211
211
|
**CROSS_CONSISTENCY checklist** (mandatory, check all 5):
|
package/commands/resume.md
CHANGED
|
@@ -22,8 +22,10 @@ allowed-tools:
|
|
|
22
22
|
### 1. Load Checkpoint
|
|
23
23
|
|
|
24
24
|
Read `.claude/afc/memory/checkpoint.md`:
|
|
25
|
-
- If not found:
|
|
26
|
-
- If found:
|
|
25
|
+
- If not found: check **auto-memory fallback** — read `~/.claude/projects/{ENCODED_PATH}/auto-memory/checkpoint.md` (where `ENCODED_PATH` = project path with `/` replaced by `-`):
|
|
26
|
+
- If fallback found: use it as the checkpoint source (auto-memory is written by `pre-compact-checkpoint.sh` during context compaction)
|
|
27
|
+
- If fallback also not found: output "No saved checkpoint found. Use `/afc:checkpoint` to create one, or checkpoints are created automatically on context compaction." then **stop**
|
|
28
|
+
- If found: parse the full contents (extract branch, commit hash, pipeline feature, task progress, modified files)
|
|
27
29
|
|
|
28
30
|
### 2. Validate Environment
|
|
29
31
|
|
|
@@ -32,7 +34,10 @@ Compare the checkpoint state against the current environment:
|
|
|
32
34
|
1. **Branch check**: Does the checkpoint branch match the current branch?
|
|
33
35
|
- If different: warn + suggest switching
|
|
34
36
|
2. **File state**: Have any files changed since the checkpoint?
|
|
35
|
-
-
|
|
37
|
+
- First verify HEAD exists: `git rev-parse --verify HEAD 2>/dev/null`
|
|
38
|
+
- If HEAD does not exist (empty repo / no commits): report "No commits yet — cannot check changes since checkpoint." and skip this check
|
|
39
|
+
- If checkpoint hash is present and non-empty: `git log {checkpoint hash}..HEAD --oneline`
|
|
40
|
+
- If checkpoint hash is empty or missing: report "Checkpoint has no git reference — cannot diff." and skip this check
|
|
36
41
|
3. **Feature directory**: Does .claude/afc/specs/{feature}/ still exist?
|
|
37
42
|
|
|
38
43
|
### 3. Report State
|
package/commands/review.md
CHANGED
|
@@ -183,7 +183,7 @@ For each changed file, examine from the following perspectives:
|
|
|
183
183
|
|
|
184
184
|
### 5. Retrospective Check
|
|
185
185
|
|
|
186
|
-
If `.claude/afc/memory/retrospectives/` directory exists, load
|
|
186
|
+
If `.claude/afc/memory/retrospectives/` directory exists, load the **most recent 10 files** (sorted by filename descending) and check:
|
|
187
187
|
- Were there recurring Critical finding categories in past reviews? Prioritize those perspectives.
|
|
188
188
|
- Were there false positives that wasted effort? Reduce sensitivity for those patterns.
|
|
189
189
|
|
package/commands/spec.md
CHANGED
|
@@ -155,7 +155,7 @@ After writing the spec, check for `[NEEDS CLARIFICATION]` items:
|
|
|
155
155
|
|
|
156
156
|
### 4. Retrospective Check
|
|
157
157
|
|
|
158
|
-
If `.claude/afc/memory/retrospectives/` directory exists, load
|
|
158
|
+
If `.claude/afc/memory/retrospectives/` directory exists, load the **most recent 10 files** (sorted by filename descending) and check:
|
|
159
159
|
- Were there previous `[AUTO-RESOLVED]` items that turned out wrong? Flag similar patterns.
|
|
160
160
|
- Were there scope-related issues in past specs? Warn about similar ambiguities.
|
|
161
161
|
|
|
@@ -181,7 +181,8 @@ Run the critic loop until convergence. Safety cap: 5 passes.
|
|
|
181
181
|
### 5.5. Auto-Checkpoint (standalone only)
|
|
182
182
|
|
|
183
183
|
When not running inside `/afc:auto`, save progress for `/afc:resume`:
|
|
184
|
-
-
|
|
184
|
+
- Create `.claude/afc/memory/` directory if it does not exist (`mkdir -p .claude/afc/memory/`)
|
|
185
|
+
- Write/update `.claude/afc/memory/checkpoint.md` with: branch, last commit (or "none" if empty repo), feature name, current phase (spec complete), next step (`/afc:plan`)
|
|
185
186
|
- Skip if running inside auto pipeline (auto manages its own checkpoints via phase transitions)
|
|
186
187
|
|
|
187
188
|
### 6. Final Output
|
package/commands/tasks.md
CHANGED
|
@@ -87,7 +87,7 @@ Decompose tasks per Phase defined in plan.md.
|
|
|
87
87
|
|
|
88
88
|
### 3. Retrospective Check
|
|
89
89
|
|
|
90
|
-
If `.claude/afc/memory/retrospectives/` directory exists, load
|
|
90
|
+
If `.claude/afc/memory/retrospectives/` directory exists, load the **most recent 10 files** (sorted by filename descending) and check:
|
|
91
91
|
- Were there previous parallel conflict issues ([P] file overlaps)? Flag similar file patterns.
|
|
92
92
|
- Were there tasks that were over-decomposed or under-decomposed? Adjust granularity.
|
|
93
93
|
|
|
@@ -138,7 +138,7 @@ Tasks generated
|
|
|
138
138
|
├─ Phases: {phase count}
|
|
139
139
|
├─ Coverage: FR {coverage}%, NFR {coverage}%
|
|
140
140
|
├─ Critic: converged ({N} passes, {M} fixes, {E} escalations)
|
|
141
|
-
└─ Next step: /afc:
|
|
141
|
+
└─ Next step: /afc:validate (optional) or /afc:implement
|
|
142
142
|
```
|
|
143
143
|
|
|
144
144
|
## Notes
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: afc:validate
|
|
3
|
+
description: "Artifact consistency validation (read-only)"
|
|
4
|
+
argument-hint: "[validation scope: spec-plan, tasks-only]"
|
|
5
|
+
user-invocable: false
|
|
6
|
+
context: fork
|
|
7
|
+
allowed-tools:
|
|
8
|
+
- Read
|
|
9
|
+
- Grep
|
|
10
|
+
- Glob
|
|
11
|
+
model: haiku
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# /afc:validate — Artifact Consistency Validation
|
|
15
|
+
|
|
16
|
+
> Validates consistency and quality across spec.md, plan.md, and tasks.md.
|
|
17
|
+
> **Read-only** — does not modify any files.
|
|
18
|
+
|
|
19
|
+
## Arguments
|
|
20
|
+
|
|
21
|
+
- `$ARGUMENTS` — (optional) limit validation scope (e.g., "spec-plan", "tasks-only")
|
|
22
|
+
|
|
23
|
+
## Config Load
|
|
24
|
+
|
|
25
|
+
**Always** read `.claude/afc.config.md` first. This file contains free-form markdown sections:
|
|
26
|
+
- `## Architecture` — architecture pattern, layers, import rules (primary reference for this command)
|
|
27
|
+
- `## Code Style` — language, naming conventions, lint rules
|
|
28
|
+
- `## Project Context` — framework, state management, testing, etc.
|
|
29
|
+
|
|
30
|
+
If config file is missing: read `CLAUDE.md` for architecture info. Assume "Layered Architecture" if neither source has it.
|
|
31
|
+
|
|
32
|
+
## Execution Steps
|
|
33
|
+
|
|
34
|
+
### 1. Load Artifacts
|
|
35
|
+
|
|
36
|
+
From `.claude/afc/specs/{feature}/`:
|
|
37
|
+
- **spec.md** (required)
|
|
38
|
+
- **plan.md** (required)
|
|
39
|
+
- **tasks.md** (if present)
|
|
40
|
+
- **research.md** (if present)
|
|
41
|
+
|
|
42
|
+
Warn about missing files but proceed with what is available.
|
|
43
|
+
|
|
44
|
+
### 2. Run Validation
|
|
45
|
+
|
|
46
|
+
Validate across 6 categories:
|
|
47
|
+
|
|
48
|
+
#### A. Duplication Detection (DUPLICATION)
|
|
49
|
+
- Similar requirements within spec.md
|
|
50
|
+
- Overlapping tasks within tasks.md
|
|
51
|
+
|
|
52
|
+
#### B. Ambiguity Detection (AMBIGUITY)
|
|
53
|
+
- Unmeasurable adjectives ("appropriate", "fast", "good")
|
|
54
|
+
- Residual TODO/TBD/FIXME markers
|
|
55
|
+
- Incomplete sentences
|
|
56
|
+
|
|
57
|
+
#### C. Coverage Gaps (COVERAGE)
|
|
58
|
+
- spec → plan: Are all FR-*/NFR-* reflected in the plan?
|
|
59
|
+
- plan → tasks: Are all items in the plan's File Change Map present in tasks?
|
|
60
|
+
- spec → tasks: Are all requirements mapped to tasks?
|
|
61
|
+
|
|
62
|
+
#### D. Inconsistencies (INCONSISTENCY)
|
|
63
|
+
- Terminology drift (different names for the same concept)
|
|
64
|
+
- Conflicting requirements
|
|
65
|
+
- Mismatches between technical decisions in plan and execution in tasks
|
|
66
|
+
|
|
67
|
+
#### E. Principles Compliance (PRINCIPLES)
|
|
68
|
+
- Validate against MUST principles in .claude/afc/memory/principles.md if present
|
|
69
|
+
- Potential violations of {config.architecture} rules
|
|
70
|
+
|
|
71
|
+
#### F. Unidentified Risks (RISK)
|
|
72
|
+
- Are there risks not identified in plan.md?
|
|
73
|
+
- External dependency risks
|
|
74
|
+
- Potential performance bottlenecks
|
|
75
|
+
|
|
76
|
+
### 3. Severity Classification
|
|
77
|
+
|
|
78
|
+
| Severity | Criteria |
|
|
79
|
+
|----------|----------|
|
|
80
|
+
| **CRITICAL** | Principles violation, core feature blocker, security issue |
|
|
81
|
+
| **HIGH** | Duplication/conflict, untestable, coverage gap |
|
|
82
|
+
| **MEDIUM** | Terminology drift, ambiguous requirements |
|
|
83
|
+
| **LOW** | Style improvements, minor duplication |
|
|
84
|
+
|
|
85
|
+
### 4. Output Results (console)
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
## Consistency Analysis Results: {feature name}
|
|
89
|
+
|
|
90
|
+
### Findings
|
|
91
|
+
| ID | Category | Severity | Location | Summary | Recommended Action |
|
|
92
|
+
|----|----------|----------|----------|---------|-------------------|
|
|
93
|
+
| A-001 | COVERAGE | HIGH | spec FR-003 | No mapping in tasks | Add task |
|
|
94
|
+
| A-002 | AMBIGUITY | MEDIUM | spec NFR-001 | "quickly" is unmeasurable | Add numeric threshold |
|
|
95
|
+
|
|
96
|
+
### Coverage Summary
|
|
97
|
+
| Mapping | Coverage |
|
|
98
|
+
|---------|----------|
|
|
99
|
+
| spec → plan | {N}% |
|
|
100
|
+
| plan → tasks | {N}% |
|
|
101
|
+
| spec → tasks | {N}% |
|
|
102
|
+
|
|
103
|
+
### Metrics
|
|
104
|
+
- Total requirements: {N}
|
|
105
|
+
- Total tasks: {N}
|
|
106
|
+
- Issues: CRITICAL {N} / HIGH {N} / MEDIUM {N} / LOW {N}
|
|
107
|
+
|
|
108
|
+
### Next Steps
|
|
109
|
+
{Concrete action proposals for CRITICAL/HIGH issues}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 5. Final Output
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
Analysis complete
|
|
116
|
+
├─ Found: CRITICAL {N} / HIGH {N} / MEDIUM {N} / LOW {N}
|
|
117
|
+
├─ Coverage: spec→plan {N}%, plan→tasks {N}%, spec→tasks {N}%
|
|
118
|
+
└─ Recommended: {next action}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Notes
|
|
122
|
+
|
|
123
|
+
- **Read-only**: Do not modify any files. Report only.
|
|
124
|
+
- **Avoid false positives**: Do not over-flag ambiguity. Consider context.
|
|
125
|
+
- **Optional**: Not required in the pipeline. Can proceed plan → implement directly.
|
|
@@ -33,12 +33,24 @@ Quantitatively inspect changed files within the Phase against `{config.code_styl
|
|
|
33
33
|
|
|
34
34
|
After passing the Phase gate, automatically save session state:
|
|
35
35
|
|
|
36
|
+
1. Create `.claude/afc/memory/` directory if it does not exist
|
|
37
|
+
2. Write/update `.claude/afc/memory/checkpoint.md` **and** `~/.claude/projects/{ENCODED_PATH}/auto-memory/checkpoint.md` (dual-write for compaction resilience — `ENCODED_PATH` = project path with `/` replaced by `-`):
|
|
38
|
+
|
|
36
39
|
```markdown
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
# Phase Gate Checkpoint
|
|
41
|
+
> Auto-generated: {YYYY-MM-DD HH:mm:ss}
|
|
42
|
+
> Trigger: phase gate
|
|
43
|
+
|
|
44
|
+
## Git Status
|
|
45
|
+
- Branch: {current branch}
|
|
46
|
+
- Commit: {short hash} — {commit message}
|
|
47
|
+
|
|
48
|
+
## Pipeline Status
|
|
49
|
+
- Active: Yes ({feature name})
|
|
50
|
+
- Current Phase: {N}/{total}
|
|
51
|
+
- Completed tasks: {list of completed IDs}
|
|
52
|
+
- Changed files: {file list}
|
|
53
|
+
- Last CI: ✓
|
|
42
54
|
```
|
|
43
55
|
|
|
44
56
|
- Even if the session is interrupted, resume from this point with `/afc:resume`
|
package/hooks/hooks.json
CHANGED
|
@@ -84,10 +84,8 @@
|
|
|
84
84
|
{
|
|
85
85
|
"hooks": [
|
|
86
86
|
{
|
|
87
|
-
"type": "
|
|
88
|
-
"
|
|
89
|
-
"model": "haiku",
|
|
90
|
-
"timeout": 60,
|
|
87
|
+
"type": "command",
|
|
88
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/afc-stop-todo-check.sh\"",
|
|
91
89
|
"statusMessage": "Checking code completeness..."
|
|
92
90
|
}
|
|
93
91
|
]
|
package/package.json
CHANGED
|
@@ -203,8 +203,10 @@ check_version_sync() {
|
|
|
203
203
|
else
|
|
204
204
|
pkg_ver=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg" | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//;s/"//')
|
|
205
205
|
plugin_ver=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$plugin" | head -1 | sed 's/.*"version"[[:space:]]*:[[:space:]]*"//;s/"//')
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
# Extract metadata.version (appears after "metadata" key) and plugins[].version (appears after "plugins" key)
|
|
207
|
+
# Use awk to track context instead of relying on field ordering
|
|
208
|
+
market_meta=$(awk '/"metadata"/{found=1} found && /"version"/{gsub(/.*"version"[[:space:]]*:[[:space:]]*"/,""); gsub(/".*/,""); print; exit}' "$market" 2>/dev/null || true)
|
|
209
|
+
market_plugin=$(awk '/"plugins"/{found=1} found && /"version"/{gsub(/.*"version"[[:space:]]*:[[:space:]]*"/,""); gsub(/".*/,""); print; exit}' "$market" 2>/dev/null || true)
|
|
208
210
|
fi
|
|
209
211
|
|
|
210
212
|
if [ "$pkg_ver" = "$plugin_ver" ] && [ "$plugin_ver" = "$market_meta" ] && [ "$market_meta" = "$market_plugin" ]; then
|
|
@@ -221,8 +223,9 @@ check_phase_ssot() {
|
|
|
221
223
|
# shellcheck source=afc-state.sh
|
|
222
224
|
. "$SCRIPT_DIR/afc-state.sh"
|
|
223
225
|
|
|
224
|
-
local
|
|
225
|
-
|
|
226
|
+
local issues=0
|
|
227
|
+
|
|
228
|
+
# Sub-check A: No hardcoded phase lists in other scripts
|
|
226
229
|
for script in "$PROJECT_DIR"/scripts/afc-*.sh; do
|
|
227
230
|
local scriptname
|
|
228
231
|
scriptname=$(basename "$script")
|
|
@@ -233,12 +236,34 @@ check_phase_ssot() {
|
|
|
233
236
|
# Check for hardcoded phase case patterns (spec|plan|...|clean style)
|
|
234
237
|
if grep -qE 'spec\|plan\|.*\|clean' "$script" 2>/dev/null; then
|
|
235
238
|
fail "$scriptname contains hardcoded phase list — use SSOT helpers from afc-state.sh"
|
|
236
|
-
|
|
239
|
+
issues=$((issues + 1))
|
|
237
240
|
fi
|
|
238
241
|
done
|
|
239
242
|
|
|
240
|
-
|
|
241
|
-
|
|
243
|
+
# Sub-check B: Every command name should map to a valid phase or be a known non-phase command
|
|
244
|
+
# Non-phase commands that are not pipeline phases
|
|
245
|
+
# NOTE: Update this list when adding non-phase commands to commands/
|
|
246
|
+
local non_phase_cmds="auto|init|doctor|principles|checkpoint|resume|launch|ideate|research|architect|security|debug|analyze|validate|test"
|
|
247
|
+
local commands_dir="$PROJECT_DIR/commands"
|
|
248
|
+
if [ -d "$commands_dir" ]; then
|
|
249
|
+
for cmd_file in "$commands_dir"/*.md; do
|
|
250
|
+
[ -f "$cmd_file" ] || continue
|
|
251
|
+
local cmd_name
|
|
252
|
+
cmd_name=$(basename "$cmd_file" .md)
|
|
253
|
+
# Skip known non-phase commands
|
|
254
|
+
if printf '%s\n' "$non_phase_cmds" | tr '|' '\n' | grep -qxF "$cmd_name"; then
|
|
255
|
+
continue
|
|
256
|
+
fi
|
|
257
|
+
# Remaining commands should correspond to a valid phase
|
|
258
|
+
if ! afc_is_valid_phase "$cmd_name"; then
|
|
259
|
+
warn "Command '$cmd_name' is not a recognized phase in AFC_VALID_PHASES and not in non-phase list"
|
|
260
|
+
issues=$((issues + 1))
|
|
261
|
+
fi
|
|
262
|
+
done
|
|
263
|
+
fi
|
|
264
|
+
|
|
265
|
+
if [ "$issues" -eq 0 ]; then
|
|
266
|
+
ok "Phase SSOT: no hardcoded lists, all commands map to valid phases"
|
|
242
267
|
fi
|
|
243
268
|
}
|
|
244
269
|
|
|
@@ -36,8 +36,9 @@ fi
|
|
|
36
36
|
|
|
37
37
|
# Parse tasks and dependencies
|
|
38
38
|
TMPDIR_WORK="$(mktemp -d)"
|
|
39
|
-
# shellcheck disable=
|
|
40
|
-
|
|
39
|
+
# shellcheck disable=SC2329
|
|
40
|
+
cleanup() { rm -rf "$TMPDIR_WORK"; }
|
|
41
|
+
trap cleanup EXIT
|
|
41
42
|
|
|
42
43
|
NODES_FILE="$TMPDIR_WORK/nodes.txt"
|
|
43
44
|
EDGES_FILE="$TMPDIR_WORK/edges.txt"
|
package/scripts/afc-notify.sh
CHANGED
|
@@ -40,16 +40,15 @@ case "$NOTIFICATION_TYPE" in
|
|
|
40
40
|
esac
|
|
41
41
|
|
|
42
42
|
# Detect platform and send notification (non-blocking via async: true in hooks.json)
|
|
43
|
-
# Sanitize message (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# shellcheck disable=SC1003
|
|
47
|
-
SAFE_TITLE=$(printf '%s' "$TITLE" | sed 's/[\"\\$`]/\\&/g')
|
|
43
|
+
# Sanitize message (truncate, single line)
|
|
44
|
+
SAFE_MESSAGE=$(printf '%s' "$MESSAGE" | head -1 | cut -c1-200)
|
|
45
|
+
SAFE_TITLE=$(printf '%s' "$TITLE" | head -1 | cut -c1-50)
|
|
48
46
|
|
|
49
47
|
OS=$(uname -s)
|
|
50
48
|
case "$OS" in
|
|
51
49
|
Darwin)
|
|
52
|
-
|
|
50
|
+
# Use positional arguments to avoid shell/AppleScript injection
|
|
51
|
+
osascript -e 'on run argv' -e 'display notification (item 2 of argv) with title (item 1 of argv)' -e 'end run' -- "$SAFE_TITLE" "$SAFE_MESSAGE" &>/dev/null || true
|
|
53
52
|
;;
|
|
54
53
|
Linux)
|
|
55
54
|
if command -v notify-send &>/dev/null; then
|
|
@@ -15,9 +15,6 @@ cleanup() {
|
|
|
15
15
|
}
|
|
16
16
|
trap cleanup EXIT
|
|
17
17
|
|
|
18
|
-
# shellcheck disable=SC2034
|
|
19
|
-
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
20
|
-
|
|
21
18
|
TASKS_FILE="${1:-}"
|
|
22
19
|
if [ -z "$TASKS_FILE" ]; then
|
|
23
20
|
printf 'Usage: %s <tasks_file_path>\n' "$0" >&2
|
|
@@ -44,8 +41,9 @@ conflict_found=0
|
|
|
44
41
|
conflict_messages=""
|
|
45
42
|
|
|
46
43
|
TMPDIR_WORK="$(mktemp -d)"
|
|
47
|
-
# shellcheck disable=
|
|
48
|
-
|
|
44
|
+
# shellcheck disable=SC2329
|
|
45
|
+
cleanup() { rm -rf "$TMPDIR_WORK"; }
|
|
46
|
+
trap cleanup EXIT
|
|
49
47
|
|
|
50
48
|
phase_index="$TMPDIR_WORK/phase_index.tsv"
|
|
51
49
|
|
|
@@ -59,7 +59,14 @@ if [ -f "$CONFIG_FILE" ]; then
|
|
|
59
59
|
# Extract ci, gate, test values from YAML code block
|
|
60
60
|
# Handles both quoted and unquoted: ci: "npm run lint" or ci: npm run lint
|
|
61
61
|
for key in ci gate test; do
|
|
62
|
+
# Try double-quoted first, then single-quoted, then unquoted
|
|
62
63
|
val=$(grep -E "^\s*${key}:\s*\"[^\"]*\"" "$CONFIG_FILE" 2>/dev/null | head -1 | sed 's/.*'"${key}"': *"\([^"]*\)".*/\1/' || true)
|
|
64
|
+
if [ -z "$val" ] || [ "$val" = '""' ]; then
|
|
65
|
+
val=$(grep -E "^\s*${key}:\s*'[^']*'" "$CONFIG_FILE" 2>/dev/null | head -1 | sed "s/.*${key}: *'\\([^']*\\)'.*/\\1/" || true)
|
|
66
|
+
fi
|
|
67
|
+
if [ -z "$val" ] || [ "$val" = '""' ]; then
|
|
68
|
+
val=$(grep -E "^\s*${key}:\s*[^\"']" "$CONFIG_FILE" 2>/dev/null | head -1 | sed "s/.*${key}:[[:space:]]*//" | sed 's/[[:space:]]*$//' || true)
|
|
69
|
+
fi
|
|
63
70
|
if [ -n "$val" ] && [ "$val" != '""' ]; then
|
|
64
71
|
DYNAMIC_WHITELIST="${DYNAMIC_WHITELIST:+${DYNAMIC_WHITELIST}|}${val}"
|
|
65
72
|
# Generate PM-agnostic variants (npm → pnpm, yarn, bun)
|
|
@@ -101,6 +108,7 @@ fi
|
|
|
101
108
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-}"
|
|
102
109
|
if [ "$ALLOWED" = "false" ] && [ -n "$PLUGIN_ROOT" ]; then
|
|
103
110
|
case "$COMMAND" in
|
|
111
|
+
*..*) ;; # Block path traversal
|
|
104
112
|
"\"${PLUGIN_ROOT}/scripts/"*|"${PLUGIN_ROOT}/scripts/"*)
|
|
105
113
|
ALLOWED=true
|
|
106
114
|
;;
|
|
@@ -117,11 +125,17 @@ if [ "$ALLOWED" = "false" ]; then
|
|
|
117
125
|
ALLOWED=true
|
|
118
126
|
;;
|
|
119
127
|
"chmod +x "*)
|
|
120
|
-
# Only allow paths within project directory (block path traversal)
|
|
128
|
+
# Only allow paths within project directory (block path traversal + symlinks)
|
|
121
129
|
TARGET="${COMMAND#chmod +x }"
|
|
122
130
|
case "$TARGET" in
|
|
123
131
|
*..*) ;; # Block path traversal
|
|
124
|
-
"$PROJECT_DIR"/*|./scripts/*|scripts/*)
|
|
132
|
+
"$PROJECT_DIR"/*|./scripts/*|scripts/*)
|
|
133
|
+
# Resolve symlinks to verify target is actually within project
|
|
134
|
+
RESOLVED=$(realpath -m "$TARGET" 2>/dev/null || echo "$TARGET")
|
|
135
|
+
case "$RESOLVED" in
|
|
136
|
+
"$PROJECT_DIR"/*) ALLOWED=true ;;
|
|
137
|
+
esac
|
|
138
|
+
;;
|
|
125
139
|
esac
|
|
126
140
|
;;
|
|
127
141
|
esac
|
|
@@ -73,9 +73,14 @@ case "$COMMAND" in
|
|
|
73
73
|
|
|
74
74
|
phase)
|
|
75
75
|
PHASE="${2:?Phase name required}"
|
|
76
|
+
if ! afc_state_is_active; then
|
|
77
|
+
echo "[afc:pipeline] No active pipeline — run '$0 start <feature>' first" >&2
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
76
80
|
if afc_is_valid_phase "$PHASE"; then
|
|
77
81
|
afc_state_write "phase" "$PHASE"
|
|
78
82
|
afc_state_invalidate_ci
|
|
83
|
+
afc_state_checkpoint "$PHASE"
|
|
79
84
|
echo "Phase: $PHASE"
|
|
80
85
|
else
|
|
81
86
|
printf "[afc:pipeline] Invalid phase: %s\n → Valid phases: %s\n" "$PHASE" "$AFC_VALID_PHASES" >&2
|
|
@@ -84,6 +89,10 @@ case "$COMMAND" in
|
|
|
84
89
|
;;
|
|
85
90
|
|
|
86
91
|
ci-pass)
|
|
92
|
+
if ! afc_state_is_active; then
|
|
93
|
+
echo "[afc:pipeline] No active pipeline — run '$0 start <feature>' first" >&2
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
87
96
|
afc_state_ci_pass
|
|
88
97
|
echo "CI passed at $(date '+%H:%M:%S')"
|
|
89
98
|
;;
|
|
@@ -126,6 +135,13 @@ case "$COMMAND" in
|
|
|
126
135
|
CHANGE_COUNT=$(printf '%s\n' "$CHANGES" | wc -l | tr -d ' ')
|
|
127
136
|
echo "Changes: $CHANGE_COUNT files"
|
|
128
137
|
fi
|
|
138
|
+
# Show checkpoint count if available
|
|
139
|
+
if command -v jq >/dev/null 2>&1; then
|
|
140
|
+
CP_COUNT=$(jq '.phaseCheckpoints | length' "$_AFC_STATE_FILE" 2>/dev/null || echo 0)
|
|
141
|
+
if [ "$CP_COUNT" -gt 0 ]; then
|
|
142
|
+
echo "Checkpoints: $CP_COUNT phases recorded"
|
|
143
|
+
fi
|
|
144
|
+
fi
|
|
129
145
|
else
|
|
130
146
|
echo "No active pipeline"
|
|
131
147
|
fi
|
package/scripts/afc-state.sh
CHANGED
|
@@ -30,10 +30,16 @@ afc_is_ci_exempt() {
|
|
|
30
30
|
|
|
31
31
|
# --- Public API ---
|
|
32
32
|
|
|
33
|
-
# Check if pipeline is active (state file exists and
|
|
33
|
+
# Check if pipeline is active (state file exists, non-empty, and valid JSON with feature)
|
|
34
34
|
# Returns: 0 if active, 1 if not
|
|
35
35
|
afc_state_is_active() {
|
|
36
|
-
[ -f "$_AFC_STATE_FILE" ] && [ -s "$_AFC_STATE_FILE" ]
|
|
36
|
+
[ -f "$_AFC_STATE_FILE" ] && [ -s "$_AFC_STATE_FILE" ] || return 1
|
|
37
|
+
# Validate JSON structure — reject corrupt/truncated files
|
|
38
|
+
if command -v jq >/dev/null 2>&1; then
|
|
39
|
+
jq -e '.feature // empty' "$_AFC_STATE_FILE" >/dev/null 2>&1 || return 1
|
|
40
|
+
else
|
|
41
|
+
grep -q '"feature"' "$_AFC_STATE_FILE" 2>/dev/null || return 1
|
|
42
|
+
fi
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
# Read a field from state file
|
|
@@ -89,10 +95,11 @@ afc_state_write() {
|
|
|
89
95
|
fi
|
|
90
96
|
else
|
|
91
97
|
# sed fallback: replace or append field
|
|
92
|
-
# Escape sed-special chars in value: \ first, then &
|
|
98
|
+
# Escape sed-special chars in value: \ first, then & and /
|
|
93
99
|
local safe_val="$value"
|
|
94
100
|
safe_val="${safe_val//\\/\\\\}"
|
|
95
101
|
safe_val="${safe_val//&/\\&}"
|
|
102
|
+
safe_val="${safe_val//\//\\/}"
|
|
96
103
|
if grep -q "\"${field}\"" "$_AFC_STATE_FILE" 2>/dev/null; then
|
|
97
104
|
local tmp
|
|
98
105
|
tmp=$(mktemp)
|
|
@@ -133,8 +140,18 @@ afc_state_remove() {
|
|
|
133
140
|
else
|
|
134
141
|
rm -f "$tmp"
|
|
135
142
|
fi
|
|
143
|
+
else
|
|
144
|
+
# sed fallback: remove the field line from JSON
|
|
145
|
+
if grep -q "\"${field}\"" "$_AFC_STATE_FILE" 2>/dev/null; then
|
|
146
|
+
local tmp
|
|
147
|
+
tmp=$(mktemp)
|
|
148
|
+
# Remove line containing the field, then fix trailing commas
|
|
149
|
+
grep -v "\"${field}\"" "$_AFC_STATE_FILE" > "$tmp" 2>/dev/null || true
|
|
150
|
+
# Fix ",}" or ",]" left by removal
|
|
151
|
+
sed 's/,[[:space:]]*}/}/g; s/,[[:space:]]*\]/]/g' "$tmp" > "$_AFC_STATE_FILE"
|
|
152
|
+
rm -f "$tmp"
|
|
153
|
+
fi
|
|
136
154
|
fi
|
|
137
|
-
# sed fallback: skip removal (non-critical)
|
|
138
155
|
}
|
|
139
156
|
|
|
140
157
|
# Initialize state for a new pipeline
|
|
@@ -217,9 +234,7 @@ afc_state_checkpoint() {
|
|
|
217
234
|
return 1
|
|
218
235
|
fi
|
|
219
236
|
local git_sha=""
|
|
220
|
-
|
|
221
|
-
git_sha=$(git rev-parse --short HEAD 2>/dev/null || echo "")
|
|
222
|
-
fi
|
|
237
|
+
git_sha=$(cd "${CLAUDE_PROJECT_DIR:-$(pwd)}" 2>/dev/null && git rev-parse --short HEAD 2>/dev/null || echo "")
|
|
223
238
|
local now
|
|
224
239
|
now=$(date +%s)
|
|
225
240
|
if command -v jq >/dev/null 2>&1; then
|
|
@@ -235,22 +250,3 @@ afc_state_checkpoint() {
|
|
|
235
250
|
fi
|
|
236
251
|
# No sed fallback — phaseCheckpoints is array-typed, too complex for sed
|
|
237
252
|
}
|
|
238
|
-
|
|
239
|
-
# Legacy fallback: check if old flag files exist
|
|
240
|
-
# Returns: 0 if legacy state found, 1 if not
|
|
241
|
-
# Side effect: sets FEATURE and PHASE variables
|
|
242
|
-
_afc_state_legacy_check() {
|
|
243
|
-
local dir="${_AFC_STATE_DIR}"
|
|
244
|
-
if [ -f "$dir/.afc-active" ]; then
|
|
245
|
-
# shellcheck disable=SC2034
|
|
246
|
-
FEATURE=$(head -1 "$dir/.afc-active" 2>/dev/null | tr -d '\n\r')
|
|
247
|
-
# shellcheck disable=SC2034
|
|
248
|
-
PHASE=""
|
|
249
|
-
if [ -f "$dir/.afc-phase" ]; then
|
|
250
|
-
# shellcheck disable=SC2034
|
|
251
|
-
PHASE=$(head -1 "$dir/.afc-phase" 2>/dev/null | tr -d '\n\r')
|
|
252
|
-
fi
|
|
253
|
-
return 0
|
|
254
|
-
fi
|
|
255
|
-
return 1
|
|
256
|
-
}
|