← back to Cli Printing Press
feat(pipeline): add PhaseAgentReadiness and plugin dependency
bdc9a90f563c1a89d37977bafd4a91a016dfe2e9 · 2026-03-27 19:32:35 -0700 · Trevin Chow
- Add PhaseAgentReadiness constant between PhaseReview and PhaseComparative
- Switch PlanPath derivation from index-based to name-based format
- Bump state version to 2 with migration that:
- Backfills agent-readiness phase with Status+PlanStatus completed
- Updates all PlanPaths from %02d-name to name-only format
- Fixes latent bug where backfilled phases had no PlanStatus
- Add seed template for agent-readiness phase
- Update planner.go comment to note AgentReadiness uses static seed
- Create .claude/settings.json with compound-engineering plugin dependency
- Add tests for phase ordering, migration v1→v2, name-based PlanPath
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files touched
A .claude/settings.jsonM internal/pipeline/planner.goM internal/pipeline/seeds.goM internal/pipeline/state.goM internal/pipeline/state_test.go
Diff
commit bdc9a90f563c1a89d37977bafd4a91a016dfe2e9
Author: Trevin Chow <trevin@trevinchow.com>
Date: Fri Mar 27 19:32:35 2026 -0700
feat(pipeline): add PhaseAgentReadiness and plugin dependency
- Add PhaseAgentReadiness constant between PhaseReview and PhaseComparative
- Switch PlanPath derivation from index-based to name-based format
- Bump state version to 2 with migration that:
- Backfills agent-readiness phase with Status+PlanStatus completed
- Updates all PlanPaths from %02d-name to name-only format
- Fixes latent bug where backfilled phases had no PlanStatus
- Add seed template for agent-readiness phase
- Update planner.go comment to note AgentReadiness uses static seed
- Create .claude/settings.json with compound-engineering plugin dependency
- Add tests for phase ordering, migration v1→v2, name-based PlanPath
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
.claude/settings.json | 5 +++
internal/pipeline/planner.go | 2 +-
internal/pipeline/seeds.go | 38 +++++++++++++++++++
internal/pipeline/state.go | 40 ++++++++++++--------
internal/pipeline/state_test.go | 84 +++++++++++++++++++++++++++++++++++++++++
5 files changed, 153 insertions(+), 16 deletions(-)
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 00000000..eca411a1
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,5 @@
+{
+ "enabledPlugins": {
+ "compound-engineering@every-marketplace": true
+ }
+}
diff --git a/internal/pipeline/planner.go b/internal/pipeline/planner.go
index 54aa4cd5..167f07f8 100644
--- a/internal/pipeline/planner.go
+++ b/internal/pipeline/planner.go
@@ -47,7 +47,7 @@ func GenerateNextPlan(state *PipelineState, nextPhase string) (string, error) {
case PhaseShip:
return generateShipPlan(ctx)
default:
- // Preflight, Research use static seeds
+ // Preflight, Research, AgentReadiness use static seeds
return RenderSeed(nextPhase, ctx.SeedData)
}
}
diff --git a/internal/pipeline/seeds.go b/internal/pipeline/seeds.go
index 42b51dc5..12e0c3d2 100644
--- a/internal/pipeline/seeds.go
+++ b/internal/pipeline/seeds.go
@@ -300,6 +300,44 @@ Evaluate the generated CLI with static scoring and dogfooding evidence that dete
- Review scoring rules defined by the pipeline review plan for this phase
- Dogfood model uses three tiers: Tier 1 no credentials, Tier 2 read-only, Tier 3 sandbox write
- Generated CLI binary and help surfaces in {{.OutputDir}}
+`,
+ PhaseAgentReadiness: `---
+title: "{{.APIName}} CLI Pipeline - Agent Readiness Review"
+type: feat
+status: seed
+pipeline_phase: agent-readiness
+pipeline_api: {{.APIName}}
+date: {{now}}
+---
+
+# Phase Goal
+
+Run the compound-engineering:cli-agent-readiness-reviewer agent on the generated {{.APIName}} CLI
+and implement its fixes in a severity-gated loop (max 2 passes) until no Blockers or Frictions remain.
+
+## Context
+
+- Pipeline directory: {{.PipelineDir}}
+- Output directory: {{.OutputDir}}
+- Spec URL: {{.SpecURL}}
+- Spec source: {{.SpecSource}}
+
+## What This Phase Must Produce
+
+- Agent readiness reviewer scorecard (7 principles x severity)
+- Fix implementation log (which fixes were applied, which were skipped/reverted)
+- Phase verdict: Pass (zero Blockers and Frictions), Warn (Frictions remain), or Degrade (Blockers remain)
+
+## Prior Phase Outputs
+
+- Runtime verification results from Phase 4.8 (pass rate, data pipeline status)
+- Working CLI binary in {{.OutputDir}}
+
+## Codebase Pointers
+
+- Reviewer agent: compound-engineering:cli-agent-readiness-reviewer (external plugin)
+- Plugin dependency declared in .claude/settings.json
+- Phase 4.8 analog: SKILL.md Phase 4.8 (Runtime Verification)
`,
PhaseShip: `---
title: "{{.APIName}} CLI Pipeline - Phase 5: Ship"
diff --git a/internal/pipeline/state.go b/internal/pipeline/state.go
index aec39709..7c5c5b0a 100644
--- a/internal/pipeline/state.go
+++ b/internal/pipeline/state.go
@@ -10,14 +10,15 @@ import (
// Phase names in execution order.
const (
- PhasePreflight = "preflight"
- PhaseResearch = "research"
- PhaseScaffold = "scaffold"
- PhaseEnrich = "enrich"
- PhaseRegenerate = "regenerate"
- PhaseReview = "review"
- PhaseComparative = "comparative"
- PhaseShip = "ship"
+ PhasePreflight = "preflight"
+ PhaseResearch = "research"
+ PhaseScaffold = "scaffold"
+ PhaseEnrich = "enrich"
+ PhaseRegenerate = "regenerate"
+ PhaseReview = "review"
+ PhaseAgentReadiness = "agent-readiness"
+ PhaseComparative = "comparative"
+ PhaseShip = "ship"
)
// PhaseOrder defines execution order.
@@ -28,6 +29,7 @@ var PhaseOrder = []string{
PhaseEnrich,
PhaseRegenerate,
PhaseReview,
+ PhaseAgentReadiness,
PhaseComparative,
PhaseShip,
}
@@ -59,7 +61,7 @@ type PipelineState struct {
DogfoodTier int `json:"dogfood_tier,omitempty"` // max tier to run (1-3, default 1)
}
-const currentStateVersion = 1
+const currentStateVersion = 2
// PhaseState tracks a single phase.
type PhaseState struct {
@@ -81,10 +83,10 @@ func StatePath(apiName string) string {
// NewState creates a fresh pipeline state.
func NewState(apiName, outputDir string) *PipelineState {
phases := make(map[string]PhaseState, len(PhaseOrder))
- for i, name := range PhaseOrder {
+ for _, name := range PhaseOrder {
phases[name] = PhaseState{
Status: StatusPending,
- PlanPath: filepath.Join(PipelineDir(apiName), fmt.Sprintf("%02d-%s-plan.md", i, name)),
+ PlanPath: filepath.Join(PipelineDir(apiName), fmt.Sprintf("%s-plan.md", name)),
}
}
state := &PipelineState{
@@ -122,14 +124,22 @@ func LoadState(apiName string) (*PipelineState, error) {
if err := json.Unmarshal(data, &s); err != nil {
return nil, fmt.Errorf("parsing state: %w", err)
}
- // Migrate: add any missing phases from PhaseOrder (e.g., research, comparative).
+ // Migrate: add missing phases and update PlanPath from index-based to name-based format.
if s.Version < currentStateVersion {
- for i, name := range PhaseOrder {
+ for _, name := range PhaseOrder {
if _, ok := s.Phases[name]; !ok {
+ // Backfill missing phases as completed (both Status and PlanStatus)
+ // so NextPhase() doesn't treat them as pending.
s.Phases[name] = PhaseState{
- Status: StatusCompleted,
- PlanPath: filepath.Join(PipelineDir(apiName), fmt.Sprintf("%02d-%s-plan.md", i, name)),
+ Status: StatusCompleted,
+ PlanStatus: PlanStatusCompleted,
+ PlanPath: filepath.Join(PipelineDir(apiName), fmt.Sprintf("%s-plan.md", name)),
}
+ } else {
+ // Migrate existing phases from index-based to name-based PlanPath.
+ p := s.Phases[name]
+ p.PlanPath = filepath.Join(PipelineDir(apiName), fmt.Sprintf("%s-plan.md", name))
+ s.Phases[name] = p
}
}
s.Version = currentStateVersion
diff --git a/internal/pipeline/state_test.go b/internal/pipeline/state_test.go
index c132b17e..b4ee6abf 100644
--- a/internal/pipeline/state_test.go
+++ b/internal/pipeline/state_test.go
@@ -118,6 +118,90 @@ func TestDefaultOutputDir(t *testing.T) {
}
}
+func TestPhaseAgentReadinessInPhaseOrder(t *testing.T) {
+ // PhaseAgentReadiness must be between PhaseReview and PhaseComparative.
+ var reviewIdx, agentIdx, compIdx int
+ for i, name := range PhaseOrder {
+ switch name {
+ case PhaseReview:
+ reviewIdx = i
+ case PhaseAgentReadiness:
+ agentIdx = i
+ case PhaseComparative:
+ compIdx = i
+ }
+ }
+ assert.Greater(t, agentIdx, reviewIdx, "agent-readiness must come after review")
+ assert.Less(t, agentIdx, compIdx, "agent-readiness must come before comparative")
+}
+
+func TestNextPhaseReturnsAgentReadiness(t *testing.T) {
+ s := NewState("ar-test", "/tmp/test")
+ // Complete through PhaseReview.
+ for _, name := range PhaseOrder {
+ if name == PhaseAgentReadiness {
+ break
+ }
+ s.Complete(name)
+ }
+ assert.Equal(t, PhaseAgentReadiness, s.NextPhase())
+}
+
+func TestNewStatePlanPathNameBased(t *testing.T) {
+ s := NewState("path-test", "/tmp/test")
+ for _, name := range PhaseOrder {
+ expected := "docs/plans/path-test-pipeline/" + name + "-plan.md"
+ assert.Equal(t, expected, s.Phases[name].PlanPath, "PlanPath for %s", name)
+ }
+}
+
+func TestLoadStateMigratesV1ToV2(t *testing.T) {
+ apiName := "migrate-v1-test"
+ dir := PipelineDir(apiName)
+ require.NoError(t, os.MkdirAll(dir, 0o755))
+ defer os.RemoveAll(dir)
+
+ // Simulate a v1 state file without agent-readiness phase and with index-based paths.
+ v1State := PipelineState{
+ Version: 1,
+ APIName: apiName,
+ OutputDir: "/tmp/migrate-cli",
+ Phases: map[string]PhaseState{
+ PhasePreflight: {Status: StatusCompleted, PlanStatus: PlanStatusCompleted, PlanPath: dir + "/00-preflight-plan.md"},
+ PhaseResearch: {Status: StatusCompleted, PlanStatus: PlanStatusCompleted, PlanPath: dir + "/01-research-plan.md"},
+ PhaseScaffold: {Status: StatusCompleted, PlanStatus: PlanStatusCompleted, PlanPath: dir + "/02-scaffold-plan.md"},
+ PhaseEnrich: {Status: StatusCompleted, PlanStatus: PlanStatusCompleted, PlanPath: dir + "/03-enrich-plan.md"},
+ PhaseRegenerate: {Status: StatusCompleted, PlanStatus: PlanStatusCompleted, PlanPath: dir + "/04-regenerate-plan.md"},
+ PhaseReview: {Status: StatusCompleted, PlanStatus: PlanStatusCompleted, PlanPath: dir + "/05-review-plan.md"},
+ PhaseComparative: {Status: StatusPending, PlanPath: dir + "/06-comparative-plan.md"},
+ PhaseShip: {Status: StatusPending, PlanPath: dir + "/07-ship-plan.md"},
+ },
+ }
+ data, err := json.MarshalIndent(v1State, "", " ")
+ require.NoError(t, err)
+ require.NoError(t, os.WriteFile(StatePath(apiName), data, 0o644))
+
+ loaded, err := LoadState(apiName)
+ require.NoError(t, err)
+
+ // Version bumped.
+ assert.Equal(t, 2, loaded.Version)
+
+ // New phase was backfilled as completed.
+ ar := loaded.Phases[PhaseAgentReadiness]
+ assert.Equal(t, StatusCompleted, ar.Status)
+ assert.Equal(t, PlanStatusCompleted, ar.PlanStatus)
+
+ // All phases have name-based PlanPaths.
+ for _, name := range PhaseOrder {
+ expected := dir + "/" + name + "-plan.md"
+ assert.Equal(t, expected, loaded.Phases[name].PlanPath, "migrated PlanPath for %s", name)
+ }
+
+ // Existing phase statuses preserved (comparative was pending).
+ assert.Equal(t, StatusPending, loaded.Phases[PhaseComparative].Status)
+}
+
func TestPhaseStateJSONIncludesPlanStatus(t *testing.T) {
state := PhaseState{
Status: StatusPlanned,
← e49ec256 docs: add implementation plan for Phase 4.9 agent readiness
·
back to Cli Printing Press
·
feat(skill): add Phase 4.9 agent readiness review loop to SK 5a6c8095 →