Skill: shalomobongo/codex-conductor (version 1.0.0)

--- name: codex-orchestrator description: Methodical end-to-end software delivery orchestrator for Codex CLI with dual project modes (greenfield for new builds, brownfield for existing systems) and dual execution modes (autonomous and gated). Use when users want full lifecycle delivery with strict stage gates, progress tracking, per-step manual/automated testing, continuous docs updates, change-impact management, and a reusable AGENTS.md workflow for any coding agent. --- # Codex Orchestrator Coordinate Codex as a disciplined delivery system, not a one-shot generator. ## Core Modes Select both: - `project_mode` - `greenfield`: build from scratch - `brownfield`: onboard and modernize an existing system - `execution_mode` - `autonomous`: proceed automatically when gates pass - `gated`: pause at every gate for user approval ## Governing Principle: Spec-Driven Development **No code without a spec.** This is non-negotiable. Before any implementation, a written spec must exist with: - What is being built - Why it's needed - Acceptance criteria (testable) - Constraints and out-of-scope The coding agent MUST NOT: - Guess at requirements - Make assumptions about behavior - Add unrequested features - Invent abstractions not in spec If spec is unclear → STOP and ask. Never guess. See `references/spec-driven-development.md` for full spec templates and enforcement rules. ## Non-Negotiable Sequence 1. Intake + planning questionnaire 2. **Spec creation + approval** (specs written BEFORE any code) 3. Docs scaffold + AGENTS.md contract 4. Mode-specific pre-architecture work 5. Architecture + ADR baseline (references specs) 6. Build by vertical slices (each task references spec) 7. Verification against spec acceptance criteria 8. Security/quality gates 9. Release readiness + handover Never skip gates silently. Never implement without a spec. ## Required Resources Read these references before running: - `references/spec-driven-development.md` (MANDATORY FIRST - governs all work) - `references/planning-questionnaire.md` - `references/modes.md` - `references/gate-checklists.md` - `references/testing-matrix.md` - `references/manual-test-templates.md` - `references/codex-runbook.md` - `references/gate-prompts.md` - `scripts/agent_exec.py` - `references/research-playbook.md` (if `research_mode=true`) ## Scaffolding Initialize project artifacts: ```bash python scripts/init_project_docs.py --root <project-path> --mode <greenfield|brownfield> ``` This creates/updates: - `AGENTS.md` (project workflow contract) - `docs/*.md` planning/architecture/test/progress/change docs - brownfield docs (when mode is brownfield) - `.orchestrator/status.json` (machine-readable state) - `.orchestrator/context.json` (project/execution/research mode context) ## Planning Rules Before anything else, ask the user which coding agent to use (`codex` | `claude` | `opencode` | `pi`) and fallback agent. Then ask all required questions from `references/planning-questionnaire.md`. Minimum required answers: - mission - top user journeys - v1 scope - hosting target - stack preference (or explicit request for recommendation) - `project_mode` - `execution_mode` - definition of done - acceptance tests If `research_mode=true`, produce `docs/research-notes.md` and architecture recommendation before G2. ## Mode-Specific Requirements ### Greenfield Must complete before G2: - requirements + DoD clarity - architecture baseline - ADR-0001 with alternatives - CI/test baseline plan ### Brownfield Must complete before G2 (and authored by coding agent, not orchestrator): - as-is architecture and system inventory - dependency map and risk register - characterization-test baseline - migration strategy + rollback approach - compatibility boundaries documented ## Gate Engine Use gates `G0` through `G7` defined in `references/gate-checklists.md`. Update gate state via script: ```bash python scripts/gate_status.py set --root <project-path> --gate G3 --state PASS --note "slice-1 verified" ``` Validate status schema: ```bash python scripts/gate_status.py validate --root <project-path> ``` Allowed states: `PENDING | IN_PROGRESS | PASS | FAIL | BLOCKED`. By default, gate preconditions are enforced (sequence + mode-aware docs checks). ## Validation Rules Use `references/testing-matrix.md`. Mandatory checks per progression: - lint/type/build - unit/integration/e2e (as applicable) - API contract sanity (if API exists) - security baseline - docs sync verification Also execute manual test scripts from `references/manual-test-templates.md`. ## Documentation Rules For each meaningful step: - update `docs/tasks.md` - update `docs/progress.md` - append `docs/change-log.md` - update `docs/traceability.md` - record test evidence in `docs/test-results.md` For user-requested changes, run: ```bash python scripts/change_impact.py --root <project-path> --request "<change request>" ``` Then complete all TODOs it emits in impacted docs. ## Codex Execution Pattern Use PTY/background for long runs. Follow command patterns in `references/codex-runbook.md`. Critical rule: each run executes ONE task, not a whole project in one prompt. For G4, maintain `docs/g4-task-plan.md` checklist and process tasks one by one. Generate gate-specific prompts with: ```bash python scripts/generate_gate_prompt.py --gate <G1..G7> --agent <codex|claude|opencode|pi> --project-mode <greenfield|brownfield> --execution-mode <autonomous|gated> --research-mode <true|false> --task "<single task summary>" --spec-ref "<spec ref when applicable>" ``` `update_docs_step.py` is now a fallback utility for recovery/manual bookkeeping only. Primary expectation: the coding agent updates docs directly during each task. Required loop: 1. verify spec exists for the task (no spec = no implementation) 2. launch selected coding agent with spec-driven prompt template 3. coding agent updates docs immediately after task completion (including handoff checklist) 4. coding agent wakes OpenClaw with task summary + where verification steps are documented 5. OpenClaw agent runs verification itself: - CLI checks in terminal tools - Browser/manual checks in browser tools (for web flows) 6. verify output matches spec acceptance criteria 7. if validations fail, OpenClaw sends exact failures back to coding agent and re-runs fix cycle 8. write final gate status only after validations pass (or mark FAIL/BLOCKED) Enforcement: - `run_gate.py` requires `--spec-ref` for G3/G4 tasks (implementation gates). - `run_gate.py` requires coding agent + fallback agent context. - Each task requires validation evidence (`--validate-cmd` and/or `--ui-review-note`). - Tasks flagged with `--requires-browser-check` must include `--ui-review-note`. - `status=PASS` requires at least one `--validate-cmd`. - `status=PASS` is blocked when `--agent-dry-run` is used. - For G4, PASS is blocked until `docs/g4-task-plan.md` has no unchecked tasks. - Validation output is recorded in `docs/validation-log.md`. - Coding agent must update docs after every task, including `docs/agent-handoff.md`. - In brownfield mode, G1/G2 fail if onboarding docs are not updated by the coding agent. - Coding agent prompts MUST include spec preamble from `references/spec-driven-development.md`. - Any implementation without spec reference = automatic FAIL. - In autonomous mode, failed validations trigger automatic fix retries (default: 2) with failure details passed back to coding agent. - Optional strict mode: `--auto-block-on-retry-exhaust` auto-classifies gate as BLOCKED when retries are exhausted. ## Progress Visibility Generate a quick status board: ```bash python scripts/progress_dashboard.py --root <project-path> ``` This summarizes current gate, completion %, blockers, and recent activity. Run a single-task gate step with one command: ```bash python scripts/run_gate.py --root <project-path> --gate G2 --agent codex --fallback-agent claude --project-mode brownfield --execution-mode gated --research-mode true --task "architecture baseline refined for API routing" --status IN_PROGRESS --validate-cmd "npm run -s typecheck" --ui-review-note "N/A for architecture-only task" ``` Mark PASS only after all gate-level checklist items are complete: ```bash python scripts/run_gate.py --root <project-path> --gate G2 --agent codex --task "architecture gate complete" --status PASS --validate-cmd "npm run -s typecheck" ``` For web/UI tasks, require browser verification by OpenClaw agent: ```bash python scripts/run_gate.py ... --requires-browser-check --ui-review-note "Verified login + CRUD manually in browser via OpenClaw browser tools" ``` Package distributable skill artifact: ```bash python scripts/package_skill.py --skill-dir . --out dist ``` ## End-State Deliverables At completion provide: - `docs/progress.md` at 100% - final gate summary from `.orchestrator/status.json` - test result summary + unresolved risks - deployment + rollback notes - next-iteration backlog If blockers remain, mark as `PARTIAL_COMPLETE` with explicit blockers and owners.

path: references/codex-runbook.md
content: # Coding-Agent Runbook (PTY + Background)

This orchestrator MUST delegate implementation tasks to a coding agent.
Do not hand-code feature work directly when the skill is active.

Supported agents:
- `codex`
- `claude`
- `opencode`
- `pi`

## First Rule

At skill start, ask:
1) Which coding agent should run tasks?
2) Which fallback agent should be used if primary fails?

## Launch Patterns

### Codex
```bash
codex exec --full-auto "<gate task prompt>"
```

### Claude
```bash
claude "<gate task prompt>"
```

### OpenCode
```bash
opencode run "<gate task prompt>"
```

### Pi
```bash
pi -p "<gate task prompt>"
```

OpenClaw execution recommendation:
- `pty:true` for interactive CLIs
- `background:true` for long-running work
- `workdir:<project-root>`

## Required Orchestration Loop

1. Generate gate prompt (`generate_gate_prompt.py`).
2. Execute selected coding agent with that prompt (`agent_exec.py` or equivalent).
3. Require coding agent to update docs immediately after task completion:
   - docs/tasks.md
   - docs/progress.md
   - docs/change-log.md
   - docs/traceability.md
   - docs/test-results.md
   - docs/agent-handoff.md
4. OpenClaw agent runs verification itself:
   - CLI checks in terminal
   - Browser/manual checks for web journeys
5. If validation fails:
   - summarize issue clearly with command/flow + output
   - re-spawn coding agent with fix prompt (same task/spec)
   - require docs updates again
   - re-test
6. Only then update gate status.

## Manual Review Responsibility

Even in autonomous mode, the OpenClaw agent performs manual verification itself:
- Web/UI flows: run in browser tools, test critical journeys.
- CLI flows: run required commands in terminal and inspect outputs.

If checks fail, send concrete failure details to coding agent, request fix, and retest.

## Completion Wake Pattern

For long runs, require coding agent wake messages to include task + verification handoff.

"When fully done, run:
openclaw gateway wake --text 'Done: <gate> | task: <summary> | handoff: see docs/agent-handoff.md for CLI+Browser checks' --mode now"


path: references/gate-checklists.md
content: # Gate Checklists

## G0 Intake Complete
- Planning questionnaire started
- Mission, scope, journeys captured
- project_mode and execution_mode selected

## G1 Planning Approved
- Requirements testable and clear
- **Specs created for all v1 features** (in `docs/specs/` or `docs/requirements.md`)
- **Each spec has acceptance criteria (testable, not vague)**
- Definition of Done captured
- Acceptance tests drafted
- Risks and assumptions listed

## G2 Architecture Approved
Common:
- architecture doc updated
- ADR-0001 completed with alternatives
- **ADRs reference relevant specs**
- test strategy and security baseline included
- **`docs/specs/` directory exists with feature specs**

Greenfield preconditions:
- bootstrap architecture is complete
- **At least one feature spec approved**

Brownfield preconditions:
- as-is architecture complete
- system inventory + dependency map complete
- characterization baseline exists
- migration plan + compatibility matrix complete
- **Existing behavior documented before changes specced**

## G3 Slice-1 Build Verified
- **Task references spec section** (e.g., `Spec: specs/auth.md#login`)
- first vertical slice implemented
- **Implementation matches spec acceptance criteria**
- unit tests pass for slice
- integration test for key path passes
- manual smoke path passes
- docs updated

## G4 Full Build Verified
- **All tasks in g4-task-plan.md have spec references**
- lint/type/build pass
- unit + integration suite pass
- e2e critical paths pass
- contract checks pass (if API boundaries exist)
- migration checks pass (brownfield)
- **All spec acceptance criteria verified**

## G5 Security & Quality Verified
- secret scanning baseline
- dependency vulnerability baseline
- auth/authorization checks
- input validation checks
- error handling/logging checks
- performance smoke checks

## G6 Release Candidate Verified
- release checklist complete
- rollback instructions tested/validated
- monitoring/alerts configured
- open risks acknowledged

## G7 Production/Handover Complete
- post-deploy smoke passes
- handover notes complete
- incident/runbook notes complete
- backlog of follow-ups created

## State Transitions
Allowed: PENDING -> IN_PROGRESS -> PASS/FAIL/BLOCKED
- FAIL requires evidence + remediation plan
- BLOCKED requires owner + unblock condition


path: references/gate-prompts.md
content: # Gate Prompt Templates (Codex)

Copy/adapt these templates per gate. Keep prompts explicit and evidence-oriented.

## Common Prompt Header

```text
You are implementing Gate <GATE_ID> for this project.

Constraints:
- Follow AGENTS.md workflow rules exactly.
- Update documentation after every meaningful change.
- Run required validations and report evidence.
- Do not claim completion without test outputs.
- Do not assume requirements; if unclear, stop and ask.
- If spec reference is missing for implementation work, return BLOCKED and do not code.

Output contract (mandatory):
- STATUS: DONE | BLOCKED
- TASK: <single task>
- SPEC_REF: <reference or BLOCKED reason>
- FILES_CHANGED: <list>
- VALIDATION_RUN: <commands + outcomes>
- OPENCLAW_VERIFY: <cli checks + browser checks or N/A>
- RISKS: <list or NONE>

Required docs updates:
- docs/tasks.md
- docs/progress.md
- docs/change-log.md
- docs/traceability.md
- docs/test-results.md

When fully done, run:
openclaw gateway wake --text "Done: <GATE_ID> completed with evidence" --mode now
```

## G1 Planning Prompt

```text
Objective: Complete planning artifacts.

Tasks:
1) Finalize requirements with testable acceptance criteria.
2) Capture Definition of Done.
3) List assumptions and risks.
4) If research_mode=true, produce docs/research-notes.md with options and recommendation.

Validations:
- Ensure requirements are testable and unambiguous.
- Ensure acceptance criteria map to at least one test each.

Done condition:
- docs/requirements.md complete
- docs/plan.md updated
- docs/progress.md updated for G1
```

## G2 Architecture Prompt

```text
Objective: Complete architecture baseline and ADR.

Tasks:
1) Update docs/architecture.md (components, data flow, deployment, security baseline).
2) Update docs/adr/ADR-0001-initial-architecture.md with alternatives and trade-offs.
3) For brownfield, ensure as-is architecture + migration artifacts are current.

Validations:
- Architecture supports must-have journeys.
- ADR includes at least 2 alternatives.

Done condition:
- G2 artifacts complete and cross-linked in docs/traceability.md
```

## G3 Slice-1 Prompt

```text
Objective: Deliver and verify first vertical slice.

Tasks:
1) Implement first slice for the top priority user journey.
2) Add unit and integration tests for this slice.
3) Execute manual smoke test for the slice.

Validations:
- unit tests pass
- integration tests pass
- manual smoke scenario recorded in docs/test-results.md

Done condition:
- slice-1 works end-to-end with evidence
```

## G4 Full Build Prompt

```text
Objective: Complete full build and baseline verification.

Tasks:
1) Implement remaining in-scope v1 features.
2) Run full validation suite.
3) Resolve failures or document blockers.

Validations:
- lint/type/build pass
- unit/integration/e2e pass
- contract checks pass if API boundaries exist

Done condition:
- all in-scope features implemented and verified
```

## G5 Security & Quality Prompt

```text
Objective: Execute security and quality gate.

Tasks:
1) Run dependency/secret baseline checks.
2) Verify auth/input validation/error handling.
3) Run performance smoke checks.

Validations:
- no unresolved critical/high issues
- mitigation plan logged for medium/low issues

Done condition:
- security and quality evidence logged
```

## G6 Release Candidate Prompt

```text
Objective: Prepare and verify release candidate.

Tasks:
1) Complete release checklist.
2) Validate rollback instructions.
3) Confirm monitoring/alerts baseline.

Validations:
- release-checklist complete
- rollback approach validated
- docs versioned and coherent

Done condition:
- RC ready for approval/deployment
```

## G7 Handover Prompt

```text
Objective: Complete handover and close orchestration.

Tasks:
1) Execute post-deploy smoke tests.
2) Finalize handover notes + runbook pointers.
3) Create next-iteration backlog.

Validations:
- critical journeys pass in deployed environment
- unresolved risks have owners

Done condition:
- docs/progress.md reaches 100% and project is handover-ready
```


path: references/manual-test-templates.md
content: # Manual Test Templates

Use these templates for human-verifiable checks. Record all runs in `docs/test-results.md`.

## Mandatory Orchestrator Behavior

- The orchestrator itself performs manual verification after coding agent changes.
- For web/UI systems: run real browser checks.
- For CLI systems: run actual commands and inspect outputs.
- If verification fails: orchestrator re-spawns coding agent with a fix prompt, then re-tests.

## Web App Manual Tests

### WT-001: Auth Login Journey (if auth exists)
- Preconditions: test user account exists
- Steps:
  1. Open login page in a real browser
  2. Submit valid credentials
  3. Confirm landing on authenticated area
- Expected: login succeeds, no console/server errors

### WT-002: Core CRUD Journey
- Steps:
  1. Create an entity
  2. View it in listing/detail
  3. Edit it
  4. Delete it
- Expected: data lifecycle works end-to-end

### WT-003: Failure Path
- Steps:
  1. Trigger invalid input
  2. Trigger API/server failure scenario
- Expected: graceful errors, no crash, clear recovery path

### WT-004: Payment Journey (if payments exist)
- Steps:
  1. Execute success path
  2. Execute failure/cancel path
- Expected: both handled correctly with consistent state

## CLI Manual Tests

### CT-001: Happy Path Command
- Steps: run primary command with valid inputs
- Expected: success exit code and expected output

### CT-002: Invalid Input Handling
- Steps: run command with malformed/missing args
- Expected: clear error, non-zero exit, no crash

### CT-003: Config Handling
- Steps: run with expected config + missing config
- Expected: explicit behavior and guidance

### CT-004: Output Contract
- Steps: verify stdout/stderr format against docs
- Expected: output consistent and parseable if required

## Brownfield Migration Tests

### BT-001: Legacy/Modern Parity Check
- Steps: run same scenario against old and new path
- Expected: equivalent behavior for supported scope

### BT-002: Rollback Rehearsal
- Steps: deploy migration slice then execute rollback procedure
- Expected: service restored cleanly to prior known-good state

### BT-003: Contract Compatibility
- Steps: verify consumer/provider boundary contracts
- Expected: no breaking contract changes


path: references/modes.md
content: # Modes

## 1) Project Mode

### greenfield
Use for new systems from scratch.

Expected pre-architecture outputs:
- requirements baseline
- architecture baseline
- ADR-0001
- initial CI/test strategy

### brownfield
Use for onboarding and evolving existing systems.

Expected pre-architecture outputs:
- as-is architecture
- system inventory
- dependency map
- legacy risk register
- characterization test baseline
- migration strategy with rollback points
- compatibility matrix

## 2) Execution Mode

### autonomous
- proceed automatically when gate checks pass
- auto-repair up to configured retries (default 2)
- pause only on persistent failures/blockers

### gated
- pause at every gate
- present pass/fail evidence
- require explicit user go-ahead to proceed

## Recommended Defaults

- Unknown/new domain → `gated`
- High-risk brownfield migration → `gated`
- Well-understood internal greenfield project → `autonomous`


path: references/planning-questionnaire.md
content: # Planning Questionnaire (Mandatory)

Ask these in order. Do not start implementation until critical answers are provided.

## 0) Coding Agent Selection (Ask First)
1. Which coding agent should run implementation tasks? (`codex` | `claude` | `opencode` | `pi`)
2. What is the fallback coding agent if the primary fails repeatedly?

## A) Outcome and Scope
3. What are we building (one-sentence mission)?
4. Who are the target users?
5. What is in scope for v1?
6. What is explicitly out of scope?
7. What is the deadline (if any)?

## B) User Journeys and Success
8. What are the top 3 user journeys?
9. What must work on day one (must-have features)?
10. What metrics define success (adoption, conversion, latency, reliability)?
11. What does “Definition of Done” mean for this project?

## C) Product and Compliance Constraints
12. Any legal/compliance constraints (privacy, data residency, PCI, HIPAA, etc.)?
13. Any accessibility level target (e.g., WCAG baseline)?
14. Any browser/device/platform constraints?
15. Any third-party integrations required?

## D) Technical Constraints
16. Preferred stack (frontend/backend/database/infra)?
17. Existing repo or greenfield?
18. Required hosting target (Cloudflare, Vercel, AWS, on-prem, etc.)?
19. Required CI/CD platform?
20. Auth requirements (roles, SSO, OAuth providers)?
21. Payments/subscriptions needed?
22. Data model complexity and expected scale?

## E) Quality and Operations
23. Required test levels (unit/integration/e2e/perf/security)?
24. Availability target/SLO?
25. Logging/monitoring/alerting requirements?
26. Rollback expectations?
27. Backup and disaster recovery expectations?

## F) Orchestration Preferences
28. Mode: `autonomous` or `gated`?
29. Should `research_mode` run during planning? (`true/false`)
30. In gated mode, who approves each gate?
31. In autonomous mode, should orchestrator auto-repair failures up to 2 retries? (`true/false`)
32. Preferred progress update frequency?

## G) Acceptance and Sign-off
33. What are the exact acceptance tests for launch?
34. What evidence is required at each gate?
35. Final approver for release?

## Minimum Inputs Required to Start Build
- Primary coding agent choice
- Mission
- Top user journeys
- v1 scope
- Hosting target
- Stack preference (or explicit “recommend one”)
- Mode (`autonomous` or `gated`)
- Definition of Done
- Acceptance tests


path: references/research-playbook.md
content: # Research Playbook

Use during planning when `research_mode=true`.

## Goals
- Reduce architecture risk before implementation
- Provide transparent option comparison
- Tie decisions to requirements and constraints

## Research Procedure
1. Restate research questions from planning gaps.
2. Define decision criteria (cost, complexity, speed, security, scale, lock-in).
3. Generate 2-4 viable options per major decision:
   - app architecture
   - data layer
   - deployment model
   - auth model
   - testing strategy
4. For each option, record:
   - fit for requirements
   - trade-offs
   - operational burden
   - risk profile
5. Recommend one option with confidence score (low/medium/high).
6. Convert recommendation into ADR draft.

## Output Template (`docs/research-notes.md`)
- Questions
- Decision Criteria
- Options Compared
- Recommendation
- Risks and Mitigations
- Follow-up Questions

## Quality Rules
- Prefer primary docs and well-established references.
- Avoid single-source decisions for critical architecture choices.
- Mark unknowns explicitly.
- Do not present uncertain conclusions as facts.


path: references/spec-driven-development.md
content: # Spec-Driven Development (Non-Negotiable)

This is the governing principle of the orchestrator: **no code without a spec**.

## Core Rule

The coding agent MUST NOT write implementation code until a written, approved spec exists for what it is about to build. This prevents:

- Guessing at requirements
- Making assumptions about behavior
- Building features the user didn't ask for
- Architectural drift from undocumented decisions

## What Counts as a Spec

A spec is a written document (in `docs/` or inline in a task file) that includes:

1. **What** is being built (feature/component/fix)
2. **Why** it's needed (user story, problem statement)
3. **Acceptance criteria** (testable conditions for "done")
4. **Constraints** (tech stack, performance, security, compatibility)
5. **Out of scope** (what this does NOT do)

Minimum viable spec for a single task:
```markdown
## Task: [Name]
**Goal:** [One sentence]
**Acceptance Criteria:**
- [ ] Criterion 1
- [ ] Criterion 2
**Constraints:** [Any limits]
**Out of Scope:** [What we're not doing]
```

## Spec Lifecycle

### 1. Spec Creation (Before G2)
- Orchestrator (or user) writes the spec
- Spec is stored in `docs/specs/` or embedded in `docs/requirements.md`
- For brownfield: existing behavior must be documented first

### 2. Spec Approval (Before Implementation)
- User reviews and approves (in gated mode)
- Or orchestrator validates completeness (in autonomous mode)
- Spec is marked APPROVED in `docs/specs/` or status.json

### 3. Spec → Task Mapping (G3/G4)
- Each task in `docs/g4-task-plan.md` MUST reference a spec section
- Format: `Spec: requirements.md#feature-name` or `Spec: specs/auth.md`
- Tasks without spec references are BLOCKED

### 4. Implementation (Coding Agent)
- Agent receives: spec + task description + context
- Agent MUST NOT invent features not in spec
- Agent MUST flag spec gaps and request clarification (not guess)

### 5. Verification Against Spec
- Orchestrator checks implementation against acceptance criteria
- Deviation from spec = FAIL (not creative license)

## Enforcement Points

### Gate G1 (Planning Approved)
- `docs/requirements.md` must exist with testable requirements
- Acceptance criteria must be explicit, not vague

### Gate G2 (Architecture Approved)
- `docs/specs/` directory must exist with at least one spec file
- Or `docs/requirements.md` must have spec-level detail for v1 features
- ADR references must point to spec decisions

### Gate G3/G4 (Build)
- Each task prompt MUST include:
  - Spec reference
  - Acceptance criteria from spec
  - Explicit boundaries
- `run_gate.py` blocks tasks without `--spec-ref` argument

### Coding Agent Prompt Template
All coding agent prompts MUST include this preamble:

```
## SPEC-DRIVEN RULES
1. You are implementing ONLY what is specified below.
2. Do NOT add features, abstractions, or "improvements" not in spec.
3. If the spec is unclear or incomplete, STOP and ask for clarification.
4. Do NOT guess at requirements. Ever.
5. Your output will be verified against the acceptance criteria below.

## SPEC
[Insert spec section here]

## ACCEPTANCE CRITERIA
[Insert criteria here]

## TASK
[Insert specific task]
```

## Red Flags (Auto-Fail)

The following trigger automatic gate failure:

- Task executed without spec reference
- Coding agent added unrequested features
- Acceptance criteria missing or vague ("should work well")
- Implementation diverged from spec without change request
- Assumptions documented as facts

## Change Requests

If requirements change mid-build:

1. Run `change_impact.py` to assess impact
2. Update spec documents
3. Re-approve affected specs
4. Update traceability matrix
5. Only then resume implementation

No "I'll just add this quickly" — all changes go through spec update.

## Spec Templates

### Feature Spec (`docs/specs/feature-name.md`)
```markdown
# Feature: [Name]

## Overview
[1-2 sentences]

## User Story
As a [user type], I want [goal] so that [benefit].

## Acceptance Criteria
- AC-1: Given [context], when [action], then [result]
- AC-2: Given [context], when [action], then [result]

## Allowed Scope Files
- src/path/to/feature/**
- tests/path/to/feature/**

## Technical Constraints
- [Stack/performance/security constraints]

## Dependencies
- [Other features, APIs, services]

## Out of Scope
- [What this feature explicitly does NOT do]

## Open Questions
- [Anything needing clarification before implementation]
```

### API Endpoint Spec
```markdown
# Endpoint: [Method] [Path]

## Purpose
[What this endpoint does]

## Request
- Method: [GET/POST/etc]
- Path: [/api/v1/resource]
- Auth: [Required/None/Scope]
- Body: [Schema or example]

## Response
- Success: [Status + schema]
- Errors: [Status codes + meanings]

## Validation Rules
- [Field validations]

## Side Effects
- [Database changes, events emitted, etc]
```

## Summary

**Spec → Approve → Implement → Verify**

No shortcuts. No guessing. No "I assumed you wanted..."

The spec is the contract. Deviate = Fail.


path: references/testing-matrix.md
content: # Testing Matrix (Gate-Based)

Apply this matrix on every project. Expand when domain-specific risks appear.

## Gate G1 (Planning)
- Validate requirements clarity
- Validate acceptance criteria are testable
- Validate risks and assumptions listed

## Gate G2 (Architecture)
- Validate architecture supports all must-have journeys
- Validate threat model baseline exists
- Validate ADR exists with alternatives and trade-offs

## Gate G3 (Slice-1 Build)
- Unit tests for first slice pass
- Integration test for key flow passes
- Manual smoke test of one critical journey passes
- Docs updated for slice

## Gate G4 (Full Build)
- Lint/type/build clean
- Unit/integration suite pass
- E2E critical paths pass
- API contract checks pass (if relevant)
- Data migration checks pass (if relevant)

## Gate G5 (Security & Quality)
- Secret scanning baseline
- Dependency vulnerability scan baseline
- AuthN/AuthZ checks
- Input validation checks
- Error handling/logging checks
- Performance smoke checks

## Gate G6 (Release Candidate)
- Release checklist complete
- Rollback steps tested or validated
- Monitoring/alerts configured
- Versioned docs complete

## Gate G7 (Production/Handover)
- Post-deploy smoke tests pass
- Incident runbook available
- Handover notes complete
- Open risks tracked with owners

## Manual Testing Requirements

For Web Projects:
- Login flow (if auth exists)
- Core create/read/update/delete journey
- Payment happy path + failure path (if payments exist)
- Error page and recovery behavior

For CLI Projects:
- Core command success path
- Invalid input handling
- Config loading behavior
- Output format consistency

## Evidence Format

For every gate, record in `docs/test-results.md`:
- test name
- command or steps
- expected result
- actual result
- pass/fail
- evidence link/snippet
- timestamp


path: scripts/agent_exec.py
content: #!/usr/bin/env python3
import argparse
import shlex
import subprocess
from pathlib import Path


def run(cmd, cwd=None):
    p = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
    if p.stdout:
        print(p.stdout.strip())
    if p.returncode != 0:
        if p.stderr:
            print(p.stderr.strip())
        raise SystemExit(p.returncode)


def main():
    parser = argparse.ArgumentParser(description="Execute a gate prompt with selected coding agent")
    parser.add_argument("--root", default=".", help="Project root")
    parser.add_argument("--agent", required=True, choices=["codex", "claude", "opencode", "pi"])
    parser.add_argument("--prompt-file", required=True, help="Prompt file path")
    parser.add_argument("--spec-ref", default="", help="Spec reference for dispatch safety checks")
    parser.add_argument(
        "--enforce-spec-ref",
        action="store_true",
        help="Fail dispatch if --spec-ref is missing (use for implementation gates)",
    )
    parser.add_argument("--full-auto", action="store_true", help="Use full-auto mode where supported")
    parser.add_argument("--dry-run", action="store_true", help="Print command without executing")
    args = parser.parse_args()

    root = Path(args.root).resolve()
    prompt = Path(args.prompt_file).resolve()
    if not prompt.exists():
        raise SystemExit(f"Prompt file not found: {prompt}")

    if args.enforce_spec_ref and not args.spec_ref.strip():
        raise SystemExit("Dispatch blocked: --spec-ref is required for this run.")

    text = prompt.read_text(encoding="utf-8")

    # Additional dispatch-time guard: do not launch if prompt itself indicates missing spec.
    if args.enforce_spec_ref and "Spec Reference: (not provided for this gate)" in text:
        raise SystemExit("Dispatch blocked: prompt indicates missing spec reference.")

    if args.agent == "codex":
        cmd = ["codex", "exec"]
        if args.full_auto:
            cmd.append("--full-auto")
        cmd.append(text)
    elif args.agent == "claude":
        # Claude CLI generally accepts task text directly
        cmd = ["claude", text]
    elif args.agent == "opencode":
        cmd = ["opencode", "run", text]
    else:  # pi
        cmd = ["pi", "-p", text]

    print("Agent command:")
    print(" ".join(shlex.quote(c) for c in cmd))

    if args.dry_run:
        return

    run(cmd, cwd=str(root))


if __name__ == "__main__":
    main()


path: scripts/change_impact.py
content: #!/usr/bin/env python3
import argparse
from pathlib import Path
from datetime import datetime, timezone


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def append_line(path: Path, line: str):
    path.parent.mkdir(parents=True, exist_ok=True)
    if not path.exists():
        path.write_text("", encoding="utf-8")
    with path.open("a", encoding="utf-8") as f:
        f.write(line)


def ensure_section(path: Path, header: str):
    if not path.exists():
        path.write_text(f"{header}\n\n", encoding="utf-8")
    else:
        txt = path.read_text(encoding="utf-8")
        if header not in txt:
            path.write_text(txt + f"\n{header}\n\n", encoding="utf-8")


def main():
    parser = argparse.ArgumentParser(description="Record change request impact")
    parser.add_argument("--root", default=".", help="Project root")
    parser.add_argument("--request", required=True, help="Change request text")
    args = parser.parse_args()

    root = Path(args.root).resolve()
    ts = now_iso()

    change_log = root / "docs" / "change-log.md"
    append_line(change_log, f"| {ts} | {args.request} | User-requested change | requirements/architecture/tests/tasks | orchestrator |\n")

    tasks = root / "docs" / "tasks.md"
    append_line(tasks, f"| CR-{ts} | Assess and implement change: {args.request} | TODO | G1/G2+ | orchestrator | {ts} |\n")

    trace = root / "docs" / "traceability.md"
    append_line(trace, f"| CR-{ts} | docs/requirements.md | implementation TBD | tests TBD | TODO |\n")

    impact = root / "docs" / "change-impact.md"
    ensure_section(impact, "# Change Impact")
    append_line(
        impact,
        (
            f"\n## {ts}\n"
            f"Request: {args.request}\n"
            f"Impacted docs (review/update):\n"
            f"- docs/requirements.md\n"
            f"- docs/architecture.md\n"
            f"- docs/test-plan.md\n"
            f"- docs/test-results.md\n"
            f"- docs/tasks.md\n"
            f"- docs/progress.md\n"
            f"Validation actions:\n"
            f"- Re-run impacted unit/integration/e2e tests\n"
            f"- Re-run manual scenarios tied to changed behavior\n"
        ),
    )

    print("Change impact recorded. Review docs/change-impact.md for TODOs.")


if __name__ == "__main__":
    main()


path: scripts/gate_status.py
content: #!/usr/bin/env python3
import argparse
import json
from pathlib import Path
from datetime import datetime, timezone

GATES = ["G0", "G1", "G2", "G3", "G4", "G5", "G6", "G7"]
STATES = ["PENDING", "IN_PROGRESS", "PASS", "FAIL", "BLOCKED"]


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def default_status():
    return {
        "meta": {
            "createdAt": now_iso(),
            "updatedAt": now_iso(),
            "status": "IN_PROGRESS",
        },
        "gates": {g: {"state": "PENDING", "updatedAt": None, "note": ""} for g in GATES},
        "history": [],
    }


def load_json(path: Path, default):
    if not path.exists():
        return default
    return json.loads(path.read_text(encoding="utf-8"))


def save(path: Path, data):
    path.parent.mkdir(parents=True, exist_ok=True)
    data["meta"]["updatedAt"] = now_iso()
    path.write_text(json.dumps(data, indent=2), encoding="utf-8")


def status_path(root: Path) -> Path:
    return root / ".orchestrator" / "status.json"


def context_path(root: Path) -> Path:
    return root / ".orchestrator" / "context.json"


def load(root: Path):
    return load_json(status_path(root), default_status())


def load_context(root: Path):
    return load_json(
        context_path(root),
        {
            "projectMode": "greenfield",
            "executionMode": "gated",
            "researchMode": False,
        },
    )


def validate_status_schema(data):
    if not isinstance(data, dict):
        return False, "status.json must be an object"
    if "meta" not in data or "gates" not in data or "history" not in data:
        return False, "status.json missing required top-level keys: meta/gates/history"

    if not isinstance(data["gates"], dict):
        return False, "gates must be an object"

    for g in GATES:
        if g not in data["gates"]:
            return False, f"missing gate entry: {g}"
        entry = data["gates"][g]
        if not isinstance(entry, dict):
            return False, f"gate entry must be object: {g}"
        if entry.get("state") not in STATES:
            return False, f"invalid state for {g}: {entry.get('state')}"

    if not isinstance(data["history"], list):
        return False, "history must be an array"

    return True, "OK"


def doc_has_substance(path: Path) -> bool:
    if not path.exists():
        return False
    text = path.read_text(encoding="utf-8", errors="ignore")
    for raw in text.splitlines():
        line = raw.strip()
        if not line:
            continue
        if line.startswith("#"):
            continue
        # Ignore markdown table separator lines like |---|---|
        if set(line.replace("|", "").replace("-", "").replace(":", "").strip()) == set():
            continue
        if line.startswith("-") and len(line) <= 3:
            continue
        if "TBD" in line.upper():
            continue
        return True
    return False


def sequential_prereq(gate: str):
    i = GATES.index(gate)
    if i == 0:
        return []
    return GATES[:i]


def mode_preconditions(root: Path, gate: str, project_mode: str):
    checks = []
    if gate == "G2" and project_mode == "greenfield":
        checks.extend(
            [
                root / "docs" / "requirements.md",
                root / "docs" / "architecture.md",
                root / "docs" / "adr" / "ADR-0001-initial-architecture.md",
            ]
        )
    if gate == "G2" and project_mode == "brownfield":
        checks.extend(
            [
                root / "docs" / "as-is-architecture.md",
                root / "docs" / "system-inventory.md",
                root / "docs" / "dependency-map.md",
                root / "docs" / "legacy-risk-register.md",
                root / "docs" / "compatibility-matrix.md",
                root / "docs" / "migration-plan.md",
                root / "docs" / "characterization-tests.md",
            ]
        )
    if gate in ("G4", "G6") and project_mode == "brownfield":
        checks.extend(
            [
                root / "docs" / "compatibility-matrix.md",
                root / "docs" / "migration-plan.md",
            ]
        )
    return checks


def check_preconditions(root: Path, data, gate: str, target_state: str):
    if target_state not in ("IN_PROGRESS", "PASS"):
        return True, "No preconditions required for this transition"

    # Sequential progression
    required_prev = sequential_prereq(gate)
    for g in required_prev:
        if data["gates"][g]["state"] != "PASS":
            return False, f"Precondition failed: previous gate {g} must be PASS"

    # Mode-specific docs checks
    ctx = load_context(root)
    project_mode = ctx.get("projectMode", "greenfield")
    for p in mode_preconditions(root, gate, project_mode):
        if not p.exists():
            return False, f"Precondition failed: missing required document {p.relative_to(root)}"
        if gate in ("G2",) and not doc_has_substance(p):
            return False, f"Precondition failed: document lacks substantive content {p.relative_to(root)}"

    return True, "OK"


def set_meta_status(data):
    if all(data["gates"][g]["state"] == "PASS" for g in GATES):
        data["meta"]["status"] = "COMPLETE"
    elif any(data["gates"][g]["state"] in ("FAIL", "BLOCKED") for g in GATES):
        data["meta"]["status"] = "ATTENTION"
    else:
        data["meta"]["status"] = "IN_PROGRESS"


def cmd_set(args):
    root = Path(args.root).resolve()
    path = status_path(root)
    data = load(root)

    ok, msg = validate_status_schema(data)
    if not ok:
        raise SystemExit(f"Invalid status schema: {msg}")

    if args.gate not in GATES:
        raise SystemExit(f"Invalid gate: {args.gate}")
    if args.state not in STATES:
        raise SystemExit(f"Invalid state: {args.state}")

    if not args.no_enforce:
        ok, msg = check_preconditions(root, data, args.gate, args.state)
        if not ok:
            raise SystemExit(msg)

    data["gates"][args.gate] = {"state": args.state, "updatedAt": now_iso(), "note": args.note or ""}
    data["history"].append(
        {
            "timestamp": now_iso(),
            "gate": args.gate,
            "state": args.state,
            "note": args.note or "",
        }
    )

    set_meta_status(data)
    save(path, data)
    print(f"Updated {args.gate} -> {args.state}")


def cmd_show(args):
    root = Path(args.root).resolve()
    data = load(root)
    print(json.dumps(data, indent=2))


def cmd_validate(args):
    root = Path(args.root).resolve()
    p = status_path(root)
    if not p.exists():
        raise SystemExit(f"Missing {p}")
    data = load(root)
    ok, msg = validate_status_schema(data)
    if not ok:
        raise SystemExit(f"Invalid: {msg}")
    print("status.json schema: OK")


def main():
    parser = argparse.ArgumentParser(description="Gate status manager")
    sub = parser.add_subparsers(dest="cmd", required=True)

    p_set = sub.add_parser("set", help="Set a gate state")
    p_set.add_argument("--root", default=".", help="Project root")
    p_set.add_argument("--gate", required=True, help="Gate id (G0..G7)")
    p_set.add_argument("--state", required=True, help="State")
    p_set.add_argument("--note", default="", help="Optional note")
    p_set.add_argument("--no-enforce", action="store_true", help="Disable precondition enforcement")
    p_set.set_defaults(func=cmd_set)

    p_show = sub.add_parser("show", help="Show current status")
    p_show.add_argument("--root", default=".", help="Project root")
    p_show.set_defaults(func=cmd_show)

    p_validate = sub.add_parser("validate", help="Validate status schema")
    p_validate.add_argument("--root", default=".", help="Project root")
    p_validate.set_defaults(func=cmd_validate)

    args = parser.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()


path: scripts/generate_gate_prompt.py
content: #!/usr/bin/env python3
import argparse
from pathlib import Path

TEMPLATES = {
    "G1": """Objective: Complete planning artifacts.
Tasks:
1) Finalize requirements with testable acceptance criteria.
2) Capture Definition of Done.
3) List assumptions and risks.
4) If research_mode=true, produce docs/research-notes.md with options and recommendation.
Validations:
- requirements are testable and unambiguous
- acceptance criteria map to tests
Done condition:
- docs/requirements.md, docs/plan.md, docs/progress.md updated""",
    "G2": """Objective: Complete architecture baseline and ADR.
Tasks:
1) Update docs/architecture.md.
2) Update docs/adr/ADR-0001-initial-architecture.md.
3) For brownfield, ensure onboarding artifacts are complete.
Validations:
- architecture supports must-have journeys
- ADR has alternatives + trade-offs
Done condition:
- G2 docs complete and traceability updated""",
    "G3": """Objective: Deliver and verify first vertical slice.
Tasks:
1) Implement first slice for top priority journey.
2) Add unit + integration tests.
3) Run manual smoke test.
Validations:
- unit/integration pass
- manual smoke recorded
Done condition:
- slice works end-to-end with evidence""",
    "G4": """Objective: Complete full build and baseline verification.
Tasks:
1) Implement remaining v1 scope.
2) Run full validation suite.
3) Resolve failures or document blockers.
Validations:
- lint/type/build pass
- unit/integration/e2e pass
Done condition:
- in-scope v1 complete with evidence""",
    "G5": """Objective: Execute security and quality gate.
Tasks:
1) Run secret/dependency checks.
2) Verify auth/input/error handling.
3) Run performance smoke checks.
Validations:
- no unresolved critical/high findings
Done condition:
- security evidence captured""",
    "G6": """Objective: Prepare and verify release candidate.
Tasks:
1) Complete release checklist.
2) Validate rollback instructions.
3) Confirm monitoring/alerts baseline.
Validations:
- release checklist complete
- rollback validated
Done condition:
- RC ready for approval/deploy""",
    "G7": """Objective: Complete handover.
Tasks:
1) Run post-deploy smoke tests.
2) Finalize handover notes.
3) Create next-iteration backlog.
Validations:
- critical journeys pass in deployed env
Done condition:
- progress=100% and handover complete""",
}

HEADER = """You are implementing Gate {gate} for this project.

## SPEC-DRIVEN RULES (NON-NEGOTIABLE)
1. You are implementing ONLY what is specified in the spec document.
2. Do NOT add features, abstractions, or "improvements" not in the spec.
3. If the spec is unclear or incomplete, STOP and ask for clarification.
4. Do NOT guess at requirements. Ever.
5. Your output will be verified against the acceptance criteria from the spec.
6. No spec = No implementation. Period.

## TASK-LEVEL DOC UPDATE RULES (NON-NEGOTIABLE)
After THIS task, you MUST directly update all relevant docs yourself:
- docs/tasks.md
- docs/progress.md
- docs/change-log.md
- docs/traceability.md
- docs/test-results.md
- docs/agent-handoff.md

In docs/agent-handoff.md include:
- Task summary (what you changed)
- Spec reference used
- CLI checks for OpenClaw agent to run
- Browser/manual checks for OpenClaw agent to run (or N/A)
- Known risks / caveats

## OUTPUT CONTRACT (STRICT)
Return a final completion block that includes:
- STATUS: DONE | BLOCKED
- TASK: <exact task>
- SPEC_REF: <exact spec ref>
- FILES_CHANGED: <list>
- VALIDATION_RUN: <commands and outcomes>
- OPENCLAW_VERIFY: <cli checks + browser checks or N/A>
- RISKS: <explicit list or NONE>

If any required input is missing (especially spec), output STATUS: BLOCKED and do not implement.

## CONSTRAINTS
- Follow AGENTS.md workflow rules exactly.
- This run is for ONE task only; do not attempt whole-project implementation in one pass.
- Do not claim completion without evidence.
- Implementation must match spec acceptance criteria exactly.
"""


def main():
    parser = argparse.ArgumentParser(description="Generate gate-specific coding-agent prompt")
    parser.add_argument("--gate", required=True, choices=["G1", "G2", "G3", "G4", "G5", "G6", "G7"])
    parser.add_argument("--agent", required=True, choices=["codex", "claude", "opencode", "pi"])
    parser.add_argument("--project-mode", choices=["greenfield", "brownfield"], default="greenfield")
    parser.add_argument("--execution-mode", choices=["autonomous", "gated"], default="gated")
    parser.add_argument("--research-mode", choices=["true", "false"], default="false")
    parser.add_argument("--task", required=True, help="Single task summary")
    parser.add_argument("--spec-ref", default="", help="Spec reference for this task")
    parser.add_argument("--output", help="Write prompt to file")
    args = parser.parse_args()

    body = TEMPLATES[args.gate]
    if args.gate == "G4":
        body += "\nTask slicing requirement:\n- Read docs/g4-task-plan.md and execute only one unchecked task for this run.\n- Mark only that task as done, leave others untouched.\n"
    mode_note = f"Coding agent: {args.agent}\nProject mode: {args.project_mode}\nExecution mode: {args.execution_mode}\nResearch mode: {args.research_mode}\n"

    if args.project_mode == "brownfield" and args.gate in ("G2", "G4"):
        mode_note += "Brownfield emphasis: preserve compatibility, run parity and rollback checks.\n"
    if args.project_mode == "brownfield" and args.gate in ("G1", "G2"):
        mode_note += (
            "Brownfield onboarding requirement: you (coding agent) must directly update onboarding docs: "
            "as-is-architecture, system-inventory, dependency-map, legacy-risk-register, "
            "compatibility-matrix, migration-plan, characterization-tests.\n"
        )

    spec_section = ""
    if args.spec_ref:
        spec_section = f"\n## TASK INPUTS\n- Task: {args.task}\n- Spec Reference: {args.spec_ref}\n"
    else:
        spec_section = f"\n## TASK INPUTS\n- Task: {args.task}\n- Spec Reference: (not provided for this gate)\n"

    handoff_instruction = (
        "\n## WAKE HANDOFF FORMAT (MANDATORY)\n"
        "When done, run exactly one wake command that includes: task done + check instructions.\n"
        "Format:\n"
        "openclaw gateway wake --text \"Done: <gate> | task: <short> | handoff: see docs/agent-handoff.md for CLI+Browser checks\" --mode now\n"
    )

    prompt = (
        HEADER.format(gate=args.gate)
        + "\n"
        + mode_note
        + spec_section
        + "\n"
        + body
        + "\n"
        + handoff_instruction
    )

    if args.output:
        Path(args.output).write_text(prompt, encoding="utf-8")
        print(f"Prompt written to {args.output}")
    else:
        print(prompt)


if __name__ == "__main__":
    main()


path: scripts/init_project_docs.py
content: #!/usr/bin/env python3
from pathlib import Path
from datetime import datetime, timezone
import argparse
import json

BASE_TEMPLATES = {
    "docs/plan.md": """# Plan

- Status: DRAFT
- Project Mode: TBD
- Execution Mode: TBD
- Research Mode: TBD
- Owner: TBD

## Summary
TBD

## Milestones
- [ ] G0 Intake
- [ ] G1 Planning
- [ ] G2 Architecture
- [ ] G3 Slice-1
- [ ] G4 Full Build
- [ ] G5 Security/Quality
- [ ] G6 Release Candidate
- [ ] G7 Handover
""",
    "docs/requirements.md": """# Requirements

## Problem Statement
TBD

## Users
TBD

## In Scope (v1)
- TBD

## Out of Scope
- TBD

## Functional Requirements
- FR-001: TBD

## Non-Functional Requirements
- NFR-001: TBD

## Definition of Done
TBD
""",
    "docs/architecture.md": """# Architecture

## Context
TBD

## High-Level Design
TBD

## Data Flow
TBD

## Deployment Topology
TBD

## Security Baseline
TBD
""",
    "docs/adr/ADR-0001-initial-architecture.md": """# ADR-0001: Initial Architecture

- Status: Proposed
- Date: TBD

## Context
TBD

## Decision
TBD

## Alternatives Considered
1. TBD
2. TBD

## Consequences
TBD
""",
    "docs/tasks.md": """# Tasks

| ID | Task | Status | Gate | Owner | Updated |
|---|---|---|---|---|---|
""",
    "docs/progress.md": """# Progress

- Overall: 0%
- Current Gate: G0
- Status: IN_PROGRESS

## Gate Status
- G0 Intake: PENDING
- G1 Planning: PENDING
- G2 Architecture: PENDING
- G3 Slice-1: PENDING
- G4 Full Build: PENDING
- G5 Security/Quality: PENDING
- G6 Release Candidate: PENDING
- G7 Handover: PENDING

## Activity Log
- [TBD] Orchestration initialized
""",
    "docs/test-plan.md": """# Test Plan

## Test Levels
- Unit
- Integration
- E2E
- Security Baseline

## Critical Journeys
1. TBD
""",
    "docs/test-results.md": """# Test Results

| Gate | Test | Steps/Command | Expected | Actual | Result | Evidence | Timestamp |
|---|---|---|---|---|---|---|---|
""",
    "docs/change-log.md": """# Change Log

| Date | Change | Why | Impact | Owner |
|---|---|---|---|---|
""",
    "docs/release-checklist.md": """# Release Checklist

- [ ] All required tests pass
- [ ] Security baseline complete
- [ ] Rollback steps documented
- [ ] Monitoring configured
- [ ] Docs updated
- [ ] Final approval obtained
""",
    "docs/g4-task-plan.md": """# G4 Task Plan (Task-by-Task)

Use this checklist to break G4 into discrete tasks.
Only one task should be executed per `run_gate.py` invocation.
Each task MUST reference a spec section.

- [ ] T1: TBD (Spec: specs/TBD.md#section)
- [ ] T2: TBD (Spec: specs/TBD.md#section)
- [ ] T3: TBD (Spec: specs/TBD.md#section)
""",
    "docs/specs/README.md": """# Specs Directory

This directory contains feature specifications.
**No implementation without a spec.**

## Spec Template

Each spec must include:
1. **What** is being built
2. **Why** it's needed (user story)
3. **Acceptance criteria** (testable)
4. **Constraints** (tech/perf/security)
5. **Out of scope**

## Naming Convention

- `feature-name.md` for feature specs
- `api-endpoint.md` for API specs
- Use lowercase with hyphens

## Status

Mark specs with status:
- DRAFT: Being written
- REVIEW: Ready for approval
- APPROVED: Ready for implementation
- IMPLEMENTED: Done and verified
""",
    "docs/traceability.md": """# Traceability Matrix

| Requirement | Design Ref | Implementation Ref | Test Ref | Status |
|---|---|---|---|---|
""",
    "docs/agent-handoff.md": """# Agent Handoff (Coding Agent -> OpenClaw Verifier)

Update this file after EVERY task.

## Latest Task
- Gate: TBD
- Task: TBD
- Spec Ref: TBD
- Summary of changes: TBD

## Acceptance Criteria Mapping
- AC-1: PASS/FAIL - evidence
- AC-2: PASS/FAIL - evidence

## OpenClaw Verification Checklist
### CLI Checks (OpenClaw agent runs in terminal)
- [ ] <command 1>
- [ ] <command 2>

### Browser/Manual Checks (OpenClaw agent runs in browser tools)
- [ ] <flow 1>
- [ ] <flow 2>
- [ ] N/A (if no web surface)

## Known Risks / Caveats
- TBD

## If Verification Fails
- Describe the failure clearly and hand back to coding agent with exact repro + logs.
""",
    "AGENTS.md": """# AGENTS.md (Project Master Workflow)

## Branching
- Prefer trunk-based flow with short-lived branches.

## Coding Standards
- Keep changes small, testable, and documented.

## Test Expectations
- Unit + integration required for feature logic.
- E2E required for critical user journeys.

## Gate Criteria
- Do not advance gates without recorded evidence.

## Documentation Obligations
- Update docs/progress.md, docs/tasks.md, docs/change-log.md after each meaningful step.

## Change Control
- Any scope change requires change-log entry and affected docs updates.

## Release Readiness
- Must satisfy docs/release-checklist.md.
""",
}

BROWNFIELD_TEMPLATES = {
    "docs/as-is-architecture.md": """# As-Is Architecture (Brownfield)

## Current System Overview
TBD

## Components and Responsibilities
TBD

## Known Pain Points
TBD
""",
    "docs/system-inventory.md": """# System Inventory (Brownfield)

| Area | Current State | Notes |
|---|---|---|
| Repositories | TBD | |
| Services | TBD | |
| Data Stores | TBD | |
| External Integrations | TBD | |
""",
    "docs/dependency-map.md": """# Dependency Map (Brownfield)

## Service/Module Dependencies
TBD

## Critical Dependencies
TBD
""",
    "docs/legacy-risk-register.md": """# Legacy Risk Register (Brownfield)

| Risk | Severity | Likelihood | Mitigation | Owner |
|---|---|---|---|---|
""",
    "docs/compatibility-matrix.md": """# Compatibility Matrix (Brownfield)

| Interface | Legacy Behavior | New Behavior | Compatible? | Notes |
|---|---|---|---|---|
""",
    "docs/migration-plan.md": """# Migration Plan (Brownfield)

## Strategy
- Incremental modernization (strangler-style slices)

## Cutover Plan
TBD

## Rollback Plan
TBD
""",
    "docs/characterization-tests.md": """# Characterization Tests (Brownfield)

## Objective
Capture current legacy behavior before change.

## Baseline Scenarios
- CTB-001: TBD
""",
}


def write_if_missing(path: Path, content: str):
    path.parent.mkdir(parents=True, exist_ok=True)
    if not path.exists():
        path.write_text(content, encoding="utf-8")


def append_progress(root: Path, message: str):
    p = root / "docs" / "progress.md"
    now = datetime.now(timezone.utc).isoformat()
    if not p.exists():
        p.parent.mkdir(parents=True, exist_ok=True)
        p.write_text("# Progress\n\n## Activity Log\n", encoding="utf-8")
    text = p.read_text(encoding="utf-8")
    text += f"\n- [{now}] {message}\n"
    p.write_text(text, encoding="utf-8")


def init_status_json(root: Path):
    now = datetime.now(timezone.utc).isoformat()
    status = {
        "meta": {
            "createdAt": now,
            "updatedAt": now,
            "status": "IN_PROGRESS",
        },
        "gates": {
            g: {"state": "PENDING", "updatedAt": None, "note": ""}
            for g in ["G0", "G1", "G2", "G3", "G4", "G5", "G6", "G7"]
        },
        "history": [],
    }
    p = root / ".orchestrator" / "status.json"
    p.parent.mkdir(parents=True, exist_ok=True)
    if not p.exists():
        p.write_text(json.dumps(status, indent=2), encoding="utf-8")


def init_context_json(root: Path, mode: str):
    ctx = {
        "projectMode": mode,
        "executionMode": "gated",
        "researchMode": False,
        "primaryAgent": "",
        "fallbackAgent": "",
    }
    p = root / ".orchestrator" / "context.json"
    p.parent.mkdir(parents=True, exist_ok=True)
    if not p.exists():
        p.write_text(json.dumps(ctx, indent=2), encoding="utf-8")


def main():
    parser = argparse.ArgumentParser(description="Initialize codex-orchestrator project docs")
    parser.add_argument("--root", default=".", help="Project root")
    parser.add_argument("--mode", choices=["greenfield", "brownfield"], default="greenfield")
    args = parser.parse_args()

    root = Path(args.root).resolve()

    for rel, content in BASE_TEMPLATES.items():
        write_if_missing(root / rel, content)

    if args.mode == "brownfield":
        for rel, content in BROWNFIELD_TEMPLATES.items():
            write_if_missing(root / rel, content)

    init_status_json(root)
    init_context_json(root, args.mode)
    append_progress(root, f"Documentation scaffold initialized (mode={args.mode})")

    print(f"Initialized docs scaffold at {root} (mode={args.mode})")


if __name__ == "__main__":
    main()


path: scripts/package_skill.py
content: #!/usr/bin/env python3
import argparse
import zipfile
from pathlib import Path


def validate(skill_dir: Path):
    required = [
        skill_dir / "SKILL.md",
        skill_dir / "scripts" / "init_project_docs.py",
        skill_dir / "references" / "planning-questionnaire.md",
        skill_dir / "references" / "testing-matrix.md",
        skill_dir / "scripts" / "run_gate.py",
        skill_dir / "scripts" / "gate_status.py",
        skill_dir / "scripts" / "agent_exec.py",
    ]
    missing = [str(p) for p in required if not p.exists()]
    if missing:
        raise SystemExit("Missing required files:\n" + "\n".join(missing))


def package(skill_dir: Path, out_dir: Path):
    out_dir.mkdir(parents=True, exist_ok=True)
    skill_name = skill_dir.name
    out_file = out_dir / f"{skill_name}.skill"

    with zipfile.ZipFile(out_file, "w", zipfile.ZIP_DEFLATED) as zf:
        for path in skill_dir.rglob("*"):
            if path.is_file():
                arcname = f"{skill_name}/{path.relative_to(skill_dir)}"
                zf.write(path, arcname)

    return out_file


def main():
    parser = argparse.ArgumentParser(description="Package codex-orchestrator skill without external deps")
    parser.add_argument("--skill-dir", default=".", help="Skill directory")
    parser.add_argument("--out", default="dist", help="Output directory")
    args = parser.parse_args()

    skill_dir = Path(args.skill_dir).resolve()
    out_dir = Path(args.out).resolve()

    validate(skill_dir)
    out_file = package(skill_dir, out_dir)
    print(f"Packaged: {out_file}")


if __name__ == "__main__":
    main()


path: scripts/progress_dashboard.py
content: #!/usr/bin/env python3
import argparse
import json
from pathlib import Path


def read_status(root: Path):
    p = root / ".orchestrator" / "status.json"
    if not p.exists():
        return None
    return json.loads(p.read_text(encoding="utf-8"))


def main():
    parser = argparse.ArgumentParser(description="Render orchestrator progress dashboard")
    parser.add_argument("--root", default=".", help="Project root")
    args = parser.parse_args()

    root = Path(args.root).resolve()
    data = read_status(root)

    if not data:
        print("No .orchestrator/status.json found")
        return

    gates = data.get("gates", {})
    total = len(gates)
    passed = sum(1 for g in gates.values() if g.get("state") == "PASS")
    pct = int((passed / total) * 100) if total else 0

    print("=== Codex Orchestrator Dashboard ===")
    print(f"Root: {root}")
    print(f"Overall Status: {data.get('meta', {}).get('status', 'UNKNOWN')}")
    print(f"Completion: {pct}% ({passed}/{total} gates passed)")
    print()
    print("Gate States:")
    for gate in sorted(gates.keys()):
        state = gates[gate].get("state")
        note = gates[gate].get("note")
        print(f"- {gate}: {state} {('- ' + note) if note else ''}")

    hist = data.get("history", [])
    if hist:
        print()
        print("Recent Activity:")
        for item in hist[-5:]:
            print(f"- {item.get('timestamp')} | {item.get('gate')} -> {item.get('state')} | {item.get('note', '')}")


if __name__ == "__main__":
    main()


path: scripts/run_gate.py
content: #!/usr/bin/env python3
import argparse
import hashlib
import json
import subprocess
from datetime import datetime, timezone
from pathlib import Path


ROOT = Path(__file__).resolve().parent
DOC_UPDATE_FILES = [
    "docs/tasks.md",
    "docs/change-log.md",
    "docs/traceability.md",
    "docs/test-results.md",
    "docs/progress.md",
    "docs/agent-handoff.md",
]

BROWNFIELD_ONBOARD_FILES = [
    "docs/as-is-architecture.md",
    "docs/system-inventory.md",
    "docs/dependency-map.md",
    "docs/legacy-risk-register.md",
    "docs/compatibility-matrix.md",
    "docs/migration-plan.md",
    "docs/characterization-tests.md",
]

ASSUMPTION_MARKERS = [
    "i assumed",
    "we assumed",
    "assumed that",
    "probably",
    "likely",
    "guessed",
    "defaulted to",
    "for convenience",
]

def run(cmd, cwd=None):
    p = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
    if p.stdout:
        print(p.stdout.strip())
    if p.returncode != 0:
        if p.stderr:
            print(p.stderr.strip())
        raise SystemExit(p.returncode)


def run_capture(cmd, cwd=None):
    p = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
    out = (p.stdout or "") + ("\n" + p.stderr if p.stderr else "")
    return p.returncode, out.strip()


def file_hash(path: Path) -> str:
    if not path.exists():
        return "MISSING"
    h = hashlib.sha256()
    h.update(path.read_bytes())
    return h.hexdigest()


def snapshot_files(root: Path, files: list[str]):
    snap = {}
    for rel in files:
        snap[rel] = file_hash(root / rel)
    return snap


def changed_files(root: Path, files: list[str], before: dict):
    changed = []
    for rel in files:
        if before.get(rel) != file_hash(root / rel):
            changed.append(rel)
    return changed


def load_context(root: Path):
    p = root / ".orchestrator" / "context.json"
    if not p.exists():
        raise SystemExit(f"Missing context file: {p}. Run init_project_docs.py first.")
    return p, json.loads(p.read_text(encoding="utf-8"))


def save_context(path: Path, ctx):
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(ctx, indent=2), encoding="utf-8")


def append_validation_log(root: Path, gate: str, lines: list[str]):
    log_path = root / "docs" / "validation-log.md"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    with log_path.open("a", encoding="utf-8") as f:
        f.write(f"\n## {gate} Validation\n")
        for line in lines:
            f.write(f"- {line}\n")


def set_gate_state(root: Path, gate: str, state: str, note: str):
    run(
        [
            "python3",
            str(ROOT / "gate_status.py"),
            "set",
            "--root",
            str(root),
            "--gate",
            gate,
            "--state",
            state,
            "--note",
            note,
        ]
    )


def check_g4_task_plan(root: Path):
    path = root / "docs" / "g4-task-plan.md"
    if not path.exists():
        raise SystemExit("G4 requires docs/g4-task-plan.md with task breakdown.")
    text = path.read_text(encoding="utf-8", errors="ignore")
    if "[ ]" not in text and "[x]" not in text and "[X]" not in text:
        raise SystemExit("G4 task plan must use checklist format ([ ] and [x]).")


def check_g4_ready_for_pass(root: Path):
    path = root / "docs" / "g4-task-plan.md"
    text = path.read_text(encoding="utf-8", errors="ignore")
    if "[ ]" in text:
        raise SystemExit("Cannot mark G4 PASS while unchecked tasks remain in docs/g4-task-plan.md")


def build_fix_prompt(
    proj_root: Path,
    gate: str,
    task: str,
    spec_ref: str,
    failing_cmd: str,
    failure_output: str,
    retry_num: int,
    max_retries: int,
):
    prompt_path = proj_root / "docs" / f"prompt-{gate}-fix-{retry_num}.txt"
    safe_out = (failure_output or "(no output)")[:1200]
    spec_line = spec_ref if spec_ref else "requirements.md#relevant-section"
    prompt = f"""You are fixing Gate {gate} after validation failure.

## SPEC-DRIVEN RULES (NON-NEGOTIABLE)
1. Implement ONLY what is specified.
2. Do NOT add unrequested features.
3. Do NOT guess at requirements.
4. If ambiguity remains, document open questions and stop.

## TASK CONTEXT
- Task: {task}
- Spec reference: {spec_line}
- Retry attempt: {retry_num}/{max_retries}

## FAILURE TO FIX
- Command: {failing_cmd}
- Output:
{safe_out}

## REQUIRED ACTIONS
1. Fix the concrete failure above.
2. Re-run the relevant local checks you can run.
3. Update these docs yourself:
   - docs/tasks.md
   - docs/progress.md
   - docs/change-log.md
   - docs/traceability.md
   - docs/test-results.md
   - docs/agent-handoff.md
4. In docs/agent-handoff.md include:
   - What you changed
   - Why it failed
   - Exact CLI checks for OpenClaw agent to run
   - Exact browser checks for OpenClaw agent to run (or N/A)

When fully done, run:
openclaw gateway wake --text "Done: {gate} fix attempt {retry_num} complete | verify: docs/agent-handoff.md" --mode now
"""
    prompt_path.write_text(prompt, encoding="utf-8")
    return prompt_path


def execute_agent(agent_cmd_base: list[str], prompt_file: str):
    cmd = list(agent_cmd_base) + ["--prompt-file", str(prompt_file)]
    run(cmd)


def run_validation_commands(validate_cmds: list[str], proj_root: Path):
    lines = []
    for cmd in validate_cmds:
        code, out = run_capture(["bash", "-lc", cmd], cwd=str(proj_root))
        snippet = (out[:500] + "...") if len(out) > 500 else out
        if code != 0:
            lines.append(f"FAIL `{cmd}` exit={code}")
            if snippet:
                lines.append(f"Output: {snippet}")
            return False, lines, cmd, snippet
        lines.append(f"PASS `{cmd}`")
        if snippet:
            lines.append(f"Output: {snippet}")
    return True, lines, "", ""


def check_assumption_markers(proj_root: Path):
    handoff = proj_root / "docs" / "agent-handoff.md"
    if not handoff.exists():
        return True, []
    text = handoff.read_text(encoding="utf-8", errors="ignore").lower()
    hits = [m for m in ASSUMPTION_MARKERS if m in text]
    if hits:
        return False, hits
    return True, []


def parse_spec_acceptance_criteria(spec_file: Path):
    if not spec_file.exists():
        return []
    lines = spec_file.read_text(encoding="utf-8", errors="ignore").splitlines()
    ids = []
    for line in lines:
        s = line.strip()
        if s.startswith("AC-") and ":" in s:
            ids.append(s.split(":", 1)[0].strip())
    return sorted(set(ids))


def collect_g4_task_spec_refs(root: Path):
    path = root / "docs" / "g4-task-plan.md"
    if not path.exists():
        return []
    refs = []
    for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
        s = line.strip()
        if not s.startswith("-"):
            continue
        marker = "(Spec: "
        if marker in s and ")" in s.split(marker, 1)[1]:
            ref = s.split(marker, 1)[1].split(")", 1)[0].strip()
            if ref:
                refs.append(ref)
    return refs


def validate_spec_coverage_for_g4(root: Path, spec_ref: str):
    refs = collect_g4_task_spec_refs(root)
    if not refs:
        return False, "G4 spec coverage check failed: docs/g4-task-plan.md has no '(Spec: ...)' references."
    if spec_ref and spec_ref not in refs:
        return False, f"G4 spec coverage check failed: {spec_ref} not present in docs/g4-task-plan.md task refs."
    return True, ""


def validate_ac_mapping(proj_root: Path, spec_ref: str):
    if not spec_ref:
        return True, []
    spec_rel = spec_ref.split("#", 1)[0]
    spec_file = proj_root / "docs" / spec_rel
    ac_ids = parse_spec_acceptance_criteria(spec_file)
    if not ac_ids:
        return True, []
    handoff = proj_root / "docs" / "agent-handoff.md"
    if not handoff.exists():
        return False, ["AC mapping check failed: docs/agent-handoff.md missing."]
    text = handoff.read_text(encoding="utf-8", errors="ignore")
    missing = [ac for ac in ac_ids if ac not in text]
    if missing:
        return False, ["AC mapping missing in agent handoff: " + ", ".join(missing)]
    return True, []


def get_git_changed_files(proj_root: Path):
    code, out = run_capture(["git", "status", "--porcelain"], cwd=str(proj_root))
    if code != 0:
        return []
    changed = []
    for line in out.splitlines():
        if not line.strip():
            continue
        path = line[3:].strip() if len(line) > 3 else ""
        if path:
            changed.append(path)
    return changed


def parse_allowed_scope_from_spec(spec_file: Path):
    if not spec_file.exists():
        return []
    lines = spec_file.read_text(encoding="utf-8", errors="ignore").splitlines()
    allowed = []
    capture = False
    for line in lines:
        s = line.strip()
        lower = s.lower()
        if lower.startswith("##") and "allowed scope files" in lower:
            capture = True
            continue
        if capture and s.startswith("##") and "allowed scope files" not in lower:
            break
        if capture and s.startswith("-"):
            candidate = s.lstrip("-").strip().strip("`")
            if candidate:
                allowed.append(candidate)
    return allowed


def validate_drift_against_spec(proj_root: Path, spec_ref: str):
    if not spec_ref:
        return True, []
    spec_rel = spec_ref.split("#", 1)[0]
    spec_file = proj_root / "docs" / spec_rel
    allowed = parse_allowed_scope_from_spec(spec_file)
    if not allowed:
        return True, []
    changed = get_git_changed_files(proj_root)
    if not changed:
        return True, []

    violations = []
    for path in changed:
        if path.startswith("docs/") or path.startswith(".orchestrator/"):
            continue
        if not any(path == a or path.startswith(a.rstrip("/") + "/") for a in allowed):
            violations.append(path)

    if violations:
        return False, ["Spec drift detected (changed outside allowed scope): " + ", ".join(sorted(set(violations)))]
    return True, []


def write_validation_artifact(
    proj_root: Path,
    gate: str,
    task: str,
    spec_ref: str,
    validate_cmds: list[str],
    validation_lines: list[str],
    ui_review_note: str,
    status: str,
):
    ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
    out_dir = proj_root / "docs" / "validation-artifacts"
    out_dir.mkdir(parents=True, exist_ok=True)
    path = out_dir / f"{gate}-{ts}.json"
    payload = {
        "timestamp": ts,
        "gate": gate,
        "task": task,
        "specRef": spec_ref,
        "status": status,
        "validateCmds": validate_cmds,
        "uiReviewNote": ui_review_note,
        "results": validation_lines,
    }
    path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    return path


def main():
    parser = argparse.ArgumentParser(description="Single-task gate runner (agent-executed, evidence-driven)")
    parser.add_argument("--root", default=".", help="Project root")
    parser.add_argument("--gate", required=True, choices=["G1", "G2", "G3", "G4", "G5", "G6", "G7"])
    parser.add_argument(
        "--agent",
        required=True,
        choices=["codex", "claude", "opencode", "pi"],
        help="Coding agent to execute implementation work",
    )
    parser.add_argument(
        "--fallback-agent",
        choices=["codex", "claude", "opencode", "pi"],
        help="Fallback coding agent (required first time if context has no fallback)",
    )
    parser.add_argument("--project-mode", choices=["greenfield", "brownfield"], default="greenfield")
    parser.add_argument("--execution-mode", choices=["autonomous", "gated"], default="gated")
    parser.add_argument("--research-mode", choices=["true", "false"], default="false")
    parser.add_argument("--task", required=True, help="Single task summary (one task per run)")
    parser.add_argument("--evidence", default="", help="Evidence summary")
    parser.add_argument(
        "--status",
        default="IN_PROGRESS",
        choices=["IN_PROGRESS", "PASS", "FAIL", "BLOCKED"],
        help="Gate status to set for this run",
    )
    parser.add_argument("--prompt-out", help="Optional prompt output path")
    parser.add_argument("--full-auto", action="store_true", help="Pass full-auto to codex in agent_exec")
    parser.add_argument("--agent-dry-run", action="store_true", help="Print coding-agent command without executing it")
    parser.add_argument(
        "--validate-cmd",
        action="append",
        default=[],
        help="Validation command to run after agent execution (repeatable). Required when status=PASS.",
    )
    parser.add_argument(
        "--ui-review-note",
        default="",
        help="Manual browser/UI verification notes produced by OpenClaw agent after checks.",
    )
    parser.add_argument(
        "--requires-browser-check",
        action="store_true",
        help="Require explicit browser/manual review note for this task.",
    )
    parser.add_argument(
        "--spec-ref",
        default="",
        help="Spec reference for this task (required for G3/G4). Format: specs/feature.md#section or requirements.md#feature",
    )
    parser.add_argument(
        "--auto-fix-retries",
        type=int,
        default=2,
        help="Autonomous mode: retries with fix prompts after failed validations (default: 2).",
    )
    parser.add_argument(
        "--auto-block-on-retry-exhaust",
        action="store_true",
        help="When retries are exhausted, auto-set gate state to BLOCKED with failure reason before exit.",
    )
    args = parser.parse_args()

    proj_root = Path(args.root).resolve()
    prompt_out = args.prompt_out or str(proj_root / "docs" / f"prompt-{args.gate}.txt")

    # Context + agent persistence
    ctx_path, ctx = load_context(proj_root)
    ctx.setdefault("primaryAgent", "")
    ctx.setdefault("fallbackAgent", "")

    if not ctx["primaryAgent"]:
        ctx["primaryAgent"] = args.agent
    elif ctx["primaryAgent"] != args.agent:
        print(f"[warn] overriding primaryAgent from {ctx['primaryAgent']} to {args.agent}")
        ctx["primaryAgent"] = args.agent

    if not ctx["fallbackAgent"]:
        if not args.fallback_agent:
            raise SystemExit("Missing fallback agent. Provide --fallback-agent for first run.")
        ctx["fallbackAgent"] = args.fallback_agent
    elif args.fallback_agent and args.fallback_agent != ctx["fallbackAgent"]:
        print(f"[warn] overriding fallbackAgent from {ctx['fallbackAgent']} to {args.fallback_agent}")
        ctx["fallbackAgent"] = args.fallback_agent

    ctx["projectMode"] = args.project_mode
    ctx["executionMode"] = args.execution_mode
    ctx["researchMode"] = True if args.research_mode == "true" else False
    save_context(ctx_path, ctx)

    # Evidence and manual-check policy
    if args.status == "PASS" and not args.validate_cmd:
        raise SystemExit("status=PASS requires at least one --validate-cmd to prove working behavior.")
    if args.status == "PASS" and args.agent_dry_run:
        raise SystemExit("status=PASS is not allowed with --agent-dry-run. Execute the coding agent for real.")

    # Every task should include validation activity (CLI and/or browser)
    if args.status in ("IN_PROGRESS", "PASS") and not args.validate_cmd and not args.ui_review_note:
        raise SystemExit(
            "Each task must include post-task validation evidence. Provide --validate-cmd and/or --ui-review-note."
        )

    if args.requires_browser_check and not args.ui_review_note:
        raise SystemExit("This task requires browser checks. Provide --ui-review-note after manual browser validation.")

    # Spec-driven enforcement: G3/G4 require spec reference
    if args.gate in ("G3", "G4") and not args.spec_ref:
        raise SystemExit(
            f"{args.gate} requires --spec-ref. No implementation without a spec.\n"
            "Format: --spec-ref specs/feature.md#section or --spec-ref requirements.md#feature"
        )

    # Verify spec file exists
    if args.spec_ref:
        spec_path = args.spec_ref.split("#")[0]
        full_spec_path = proj_root / "docs" / spec_path
        if not full_spec_path.exists():
            raise SystemExit(f"Spec file not found: {full_spec_path}. Create the spec before implementation.")

    if args.gate == "G4":
        check_g4_task_plan(proj_root)
        if args.status == "PASS":
            check_g4_ready_for_pass(proj_root)

    # 1) Generate initial gate prompt
    run(
        [
            "python3",
            str(ROOT / "generate_gate_prompt.py"),
            "--gate",
            args.gate,
            "--agent",
            args.agent,
            "--project-mode",
            args.project_mode,
            "--execution-mode",
            args.execution_mode,
            "--research-mode",
            args.research_mode,
            "--task",
            args.task,
            "--spec-ref",
            args.spec_ref,
            "--output",
            prompt_out,
        ]
    )

    # Build agent command base (prompt-file added per attempt)
    agent_cmd_base = [
        "python3",
        str(ROOT / "agent_exec.py"),
        "--root",
        str(proj_root),
        "--agent",
        args.agent,
        "--spec-ref",
        args.spec_ref,
    ]
    if args.gate in ("G3", "G4"):
        agent_cmd_base.append("--enforce-spec-ref")
    if args.full_auto:
        agent_cmd_base.append("--full-auto")
    if args.agent_dry_run:
        agent_cmd_base.append("--dry-run")

    max_fix_retries = args.auto_fix_retries if args.execution_mode == "autonomous" else 0
    attempt = 0
    current_prompt = prompt_out
    all_validation_lines = []

    while True:
        before_docs = snapshot_files(proj_root, DOC_UPDATE_FILES)
        before_brownfield = snapshot_files(proj_root, BROWNFIELD_ONBOARD_FILES)

        # 2) Execute coding agent
        execute_agent(agent_cmd_base, current_prompt)

        # 3) Verify docs were updated by coding agent (every task, every run)
        changed = changed_files(proj_root, DOC_UPDATE_FILES, before_docs)
        if not args.agent_dry_run and args.status in ("IN_PROGRESS", "PASS") and not changed:
            raise SystemExit(
                "Coding agent did not update required docs (tasks/change-log/traceability/test-results/progress/agent-handoff). "
                "Docs updates must be done by the coding agent after each task."
            )

        # Brownfield onboarding (G1/G2) must also be authored by coding agent
        changed_brownfield = []
        if args.project_mode == "brownfield" and args.gate in ("G1", "G2") and not args.agent_dry_run:
            changed_brownfield = changed_files(proj_root, BROWNFIELD_ONBOARD_FILES, before_brownfield)
            if not changed_brownfield:
                raise SystemExit(
                    "Brownfield onboarding docs were not updated by the coding agent during this run. "
                    "Agent must update onboarding artifacts directly."
                )

        # 4) CLI/manual validations by orchestrator
        ok, validation_lines, failing_cmd, failure_snippet = run_validation_commands(args.validate_cmd, proj_root)

        assumptions_ok, assumption_hits = check_assumption_markers(proj_root)
        if not assumptions_ok:
            ok = False
            failing_cmd = "assumption-detector:docs/agent-handoff.md"
            failure_snippet = "Assumption language detected: " + ", ".join(assumption_hits)
            validation_lines.append("FAIL assumption detector: " + ", ".join(assumption_hits))

        if args.gate == "G4":
            coverage_ok, coverage_msg = validate_spec_coverage_for_g4(proj_root, args.spec_ref)
            if not coverage_ok:
                ok = False
                failing_cmd = "g4-spec-coverage:docs/g4-task-plan.md"
                failure_snippet = coverage_msg
                validation_lines.append("FAIL " + coverage_msg)

        if args.gate in ("G3", "G4") and args.spec_ref:
            ac_ok, ac_msgs = validate_ac_mapping(proj_root, args.spec_ref)
            if not ac_ok:
                ok = False
                failing_cmd = "ac-mapping:docs/agent-handoff.md"
                failure_snippet = " | ".join(ac_msgs)
                validation_lines.extend(["FAIL " + msg for msg in ac_msgs])

            drift_ok, drift_msgs = validate_drift_against_spec(proj_root, args.spec_ref)
            if not drift_ok:
                ok = False
                failing_cmd = "spec-drift:git-status"
                failure_snippet = " | ".join(drift_msgs)
                validation_lines.extend(["FAIL " + msg for msg in drift_msgs])

        if args.ui_review_note:
            validation_lines.append(f"UI review: {args.ui_review_note}")
        if changed:
            validation_lines.append("Docs updated by agent: " + ", ".join(changed))
        if changed_brownfield:
            validation_lines.append("Brownfield onboarding docs updated by agent: " + ", ".join(changed_brownfield))

        all_validation_lines.extend(validation_lines)

        if ok:
            break

        # Validation failed: autonomous fix-retry loop
        if args.agent_dry_run:
            append_validation_log(proj_root, args.gate, all_validation_lines)
            raise SystemExit(f"Validation failed in dry-run mode: `{failing_cmd}`")

        if attempt >= max_fix_retries:
            append_validation_log(proj_root, args.gate, all_validation_lines)
            failure_note = (
                f"Retry exhausted after {attempt} attempts. Last failed command: {failing_cmd}. "
                f"Failure: {failure_snippet[:240]}"
            )
            if args.auto_block_on_retry_exhaust:
                set_gate_state(proj_root, args.gate, "BLOCKED", failure_note)
                append_validation_log(proj_root, args.gate, ["Auto-classified as BLOCKED due to retry exhaustion."])
            raise SystemExit(
                f"Validation failed after {attempt} fix retries. Last failed command: `{failing_cmd}`"
            )

        attempt += 1
        all_validation_lines.append(
            f"Auto-fix retry {attempt}/{max_fix_retries}: re-invoking coding agent with failure details."
        )

        fix_prompt_path = build_fix_prompt(
            proj_root=proj_root,
            gate=args.gate,
            task=args.task,
            spec_ref=args.spec_ref,
            failing_cmd=failing_cmd,
            failure_output=failure_snippet,
            retry_num=attempt,
            max_retries=max_fix_retries,
        )
        current_prompt = str(fix_prompt_path)

    # 5) Persist validation evidence log + machine artifact
    if all_validation_lines:
        append_validation_log(proj_root, args.gate, all_validation_lines)

    artifact_path = write_validation_artifact(
        proj_root=proj_root,
        gate=args.gate,
        task=args.task,
        spec_ref=args.spec_ref,
        validate_cmds=args.validate_cmd,
        validation_lines=all_validation_lines,
        ui_review_note=args.ui_review_note,
        status=args.status,
    )
    append_validation_log(proj_root, args.gate, [f"Validation artifact: {artifact_path.relative_to(proj_root)}"])

    # 6) Gate status + dashboard
    set_gate_state(proj_root, args.gate, args.status, args.task)

    run(["python3", str(ROOT / "progress_dashboard.py"), "--root", str(proj_root)])


if __name__ == "__main__":
    main()


path: scripts/update_docs_step.py
content: #!/usr/bin/env python3
import argparse
from pathlib import Path
from datetime import datetime, timezone


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def append(path: Path, text: str):
    path.parent.mkdir(parents=True, exist_ok=True)
    if not path.exists():
        path.write_text("", encoding="utf-8")
    with path.open("a", encoding="utf-8") as f:
        f.write(text)


def main():
    parser = argparse.ArgumentParser(description="Append standard per-step doc updates")
    parser.add_argument("--root", default=".", help="Project root")
    parser.add_argument("--gate", required=True, help="Gate id, e.g. G3")
    parser.add_argument("--task", required=True, help="Task summary")
    parser.add_argument("--status", default="DONE", help="Task status")
    parser.add_argument("--evidence", default="", help="Evidence summary")
    parser.add_argument("--owner", default="orchestrator", help="Owner")
    args = parser.parse_args()

    root = Path(args.root).resolve()
    ts = now_iso()

    append(root / "docs" / "tasks.md", f"| {args.gate}-{ts} | {args.task} | {args.status} | {args.gate} | {args.owner} | {ts} |\n")
    append(root / "docs" / "change-log.md", f"| {ts} | {args.task} | Gate {args.gate} execution | code/docs/tests | {args.owner} |\n")
    append(root / "docs" / "traceability.md", f"| {args.gate}-{ts} | docs/architecture.md/docs/requirements.md | implementation update | test run | {args.status} |\n")
    append(root / "docs" / "test-results.md", f"| {args.gate} | {args.task} | see commands/logs | expected met | {args.evidence or 'see logs'} | {'PASS' if args.status.upper() in ('DONE','PASS') else args.status.upper()} | {args.evidence or 'n/a'} | {ts} |\n")
    append(root / "docs" / "progress.md", f"\n- [{ts}] {args.gate}: {args.task} ({args.status})\n")

    print("Documentation step updates appended.")


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)