feat(P02): opencode integration layer (#2)

18 CI agents, 11 workflows, 11 commands, 5 references, 3 contexts. Zero learnship dependencies.
This commit was merged in pull request #2.
This commit is contained in:
2026-05-29 13:27:29 +00:00
parent eedcdd4282
commit 2f738c33b7
50 changed files with 3113 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
<branch_strategy>
Canonical branch naming and lifecycle conventions for CI. Branches encode project structure — merged branches indicate completed work, active branches indicate work in progress.
---
## Branch Types
### Phase Branches
**Format:** `phase/NN-slug`
| Part | Convention |
|------|-----------|
| `NN` | Zero-padded phase number (01, 02, ..., 12) |
| `slug` | Lowercase, hyphenated phase name |
**Examples:**
- `phase/01-git-native-architecture`
- `phase/02-opencode-integration`
- `phase/03-agent-implementations`
**Lifecycle:**
1. Created at phase start by `GitBranch.createPhaseBranch()`
2. All task commits for the phase land on this branch
3. Merged to main (squash) on phase completion
4. Merged = phase complete, active = phase in progress, absent = not started
### Milestone Branches
**Format:** `milestone/vX.X-slug`
| Part | Convention |
|------|-----------|
| `vX.X` | Semver milestone version |
| `slug` | Lowercase, hyphenated milestone name |
**Examples:**
- `milestone/v0.2-git-native`
- `milestone/v1.0-mvp`
**Lifecycle:**
1. Created at first phase of milestone by `GitBranch.createMilestoneBranch()`
2. Spans multiple phases within the same milestone
3. Merged to main on milestone completion
4. Merged = milestone complete, active = milestone in progress
### Hotfix Branches
**Format:** `hotfix/description`
Created for urgent fixes outside the normal phase flow. Merged directly to main.
## Branch Status Inference
The `GitBranch` class and `GitContext` class determine status from the branch list:
```typescript
const branches = gitContext.getBranches();
// Phase branches: type = "phase", phaseNumber, merged boolean
// Milestone branches: type = "milestone", milestone string, merged boolean
```
| Branch State | Meaning |
|-------------|---------|
| Branch exists, not merged | Phase/milestone is active (in progress) |
| Branch exists, merged | Phase/milestone is complete |
| Branch does not exist | Phase/milestone has not started |
## Merge Strategy
Default: **squash merge** into main.
```typescript
gitBranch.mergePhaseBranch("phase/01-git-native-architecture", "main", true);
```
Squash merge keeps main clean while preserving full development history in the phase branch. Phase branches can be deleted after merge if desired.
## Phase Discovery
```typescript
const gitBranch = new GitBranch(projectPath);
const phases = gitBranch.listPhases();
// Returns: PhaseBranchInfo[] with phaseNumber, slug, branchName, status
const milestones = gitBranch.listMilestones();
// Returns: MilestoneBranchInfo[] with version, slug, branchName, status
```
## Branch Creation Rules
1. Always create phase branches from main (or the current milestone branch)
2. Never create a branch for a completed phase — it should already be merged
3. Milestone branches span phases — don't create one per phase
4. Use `GitBranch.createPhaseBranch()` to ensure consistent naming
5. Use `GitBranch.createMilestoneBranch()` to ensure consistent naming
## Working with Phase Branches
```bash
# Create a phase branch
git checkout -b phase/01-git-native-architecture
# Commit work with ---ci--- blocks
git commit -m "feat(P01-01-01): implement commit parser
---ci---
phase: 1
milestone: v0.2
plan: 01-01
task: 01-01-01
status: execute
---/ci---"
# Merge on completion
git checkout main
git merge --squash phase/01-git-native-architecture
git commit -m "docs(P01): complete git-native-architecture phase"
```
</branch_strategy>
@@ -0,0 +1,149 @@
<ci_files_discipline>
How CI manages the `.ci/` directory — long-lived reference documents only. Dynamic state lives in the git log via `---ci---` YAML blocks, not in files.
---
## What Lives in `.ci/`
| File | Purpose | Update Frequency |
|------|---------|-------------------|
| `config.json` | Project-level CI configuration | Rare (initialization, setting changes) |
| `PROJECT.md` | Vision, core value, requirements, constraints, key decisions | Low (phase boundaries) |
| `ARCHITECTURE.md` | System architecture, component boundaries, data flow | Low (major refactors) |
| `ROADMAP.md` | Phase breakdown, milestone mapping, success criteria | Low (phase transitions) |
| `REQUIREMENTS.md` | v1/v2 requirements with REQ-IDs, out of scope, traceability | Low (requirement changes) |
## What Does NOT Live in `.ci/`
These were removed in v0.2.0 and now live in the git log:
| Previous Location | Now In | Access Method |
|-------------------|--------|---------------|
| `.ci/audit/decisions.json` | `---ci---` decisions block | `GitContext.getDecisions()` |
| `.ci/audit/escalations.json` | `---ci---` escalations block | `GitContext.getEscalations()` |
| `.ci/audit/lessons.json` | `---ci---` lessons block | `GitContext.getLessons()` |
| `.planning/` directory | Git log + branches | `GitContext.reconstructState()` |
## CiFiles API
| Method | Returns | Purpose |
|--------|---------|---------|
| `ensureCIDir()` | void | Create `.ci/` if it doesn't exist |
| `isInitialized()` | boolean | Check if `.ci/config.json` exists |
| `readProjectMd()` | ProjectMd \| null | Read project definition |
| `writeProjectMd(project, reason)` | void | Write project definition |
| `readRoadmapMd()` | RoadmapMd \| null | Read roadmap |
| `writeRoadmapMd(roadmap)` | void | Write roadmap |
| `readRequirementsMd()` | RequirementsMd \| null | Read requirements |
| `writeRequirementsMd(requirements)` | void | Write requirements |
| `readArchitectureMd()` | ArchitectureMd \| null | Read architecture |
| `writeArchitectureMd(architecture)` | void | Write architecture |
| `updateRequirementStatus(reqId, status)` | void | Update a single requirement status |
| `updatePhaseStatus(phaseNumber, status)` | void | Update a single phase status |
## Update Discipline
1. **Update with reason**`writeProjectMd()` takes a `reason` parameter. Every write must justify why.
2. **Phase boundaries** — Major updates happen at phase transitions, not during task execution.
3. **Requirements status** — Use `updateRequirementStatus()` for single-status changes, not full rewrites.
4. **Phase status** — Use `updatePhaseStatus()` for phase transitions, not full roadmap rewrites.
5. **Commit after write** — Every `.ci/` file change should be committed immediately with a `---ci---` block.
## Update Triggers
| When | What to Update | Method |
|------|---------------|--------|
| Project initialization | All files from scratch | `write*` methods |
| Phase transition | Phase status in ROADMAP.md | `updatePhaseStatus()` |
| Requirement met | Requirement status in REQUIREMENTS.md | `updateRequirementStatus()` |
| Architecture change | ARCHITECTURE.md | `writeArchitectureMd()` |
| Scope change | PROJECT.md | `writeProjectMd()` |
## File Schemas
### PROJECT.md
```typescript
interface ProjectMd {
name: string;
coreValue: string;
requirements: {
validated: string[];
active: string[];
outOfScope: string[];
};
constraints: string[];
context: string;
keyDecisions: Array<{
decision: string;
rationale: string;
outcome: string;
}>;
}
```
### ROADMAP.md
```typescript
interface RoadmapMd {
overview: string;
phases: Array<{
number: number;
name: string;
description: string;
status: "not_started" | "in_progress" | "complete" | "deferred";
dependsOn: number[];
requirements: string[];
successCriteria: string[];
}>;
}
```
### REQUIREMENTS.md
```typescript
interface RequirementsMd {
v1: Array<{
category: string;
items: Array<{ id: string; description: string }>;
}>;
v2: Array<{
category: string;
items: Array<{ id: string; description: string }>;
}>;
outOfScope: Array<{ feature: string; reason: string }>;
traceability: Array<{
requirement: string;
phase: number;
status: "pending" | "in_progress" | "complete" | "blocked";
}>;
}
```
### ARCHITECTURE.md
```typescript
interface ArchitectureMd {
overview: string;
components: Array<{
name: string;
description: string;
boundaries: string;
dependsOn: string[];
}>;
dataFlow: string;
buildOrder: string[];
}
```
## Anti-Patterns
- Never write dynamic state (decisions, escalations, lessons) to `.ci/` files
- Never update `.ci/` files during task execution — update at phase boundaries
- Never skip the `reason` parameter when writing PROJECT.md
- Never commit `.ci/` changes without a `---ci---` block
- Never create new files in `.ci/` without updating this reference document
- Never store counters, timestamps, or session state in `.ci/` files
</ci_files_discipline>
+108
View File
@@ -0,0 +1,108 @@
<commit_schema>
Canonical `---ci---` YAML block schema for CI commits. Every CI-generated commit contains a structured YAML block that enables full project state reconstruction from the git log alone.
---
## Block Format
```
<type>(<scope>): <subject>
---ci---
phase: <number>
milestone: <string>
plan: <string> # optional
task: <string> # optional
status: <pipeline_stage>
decisions: # optional
- id: D-001
decision: <text>
rationale: <text>
confidence: <0.0-1.0>
alternatives: [<alt1>, <alt2>]
escalations: # optional
- id: E-001
type: <escalation_type>
description: <text>
resolution: pending|timeout|human|auto
requirements: # optional
covered: [REQ-01, REQ-02]
partial: [REQ-03]
lessons: # optional
- <text>
compound: # optional
category: <string>
problem: <text>
solution: <text>
---/ci---
```
## Commit Types
| Type | Purpose | Scope |
|------|---------|-------|
| `feat` | New feature | `P##-##-##` |
| `fix` | Bug fix | `P##-##-##` |
| `test` | Test-only | `P##-##-##` |
| `refactor` | Code cleanup | `P##-##-##` |
| `docs` | Documentation | `P##`, `init`, `milestone` |
| `chore` | Config, deps, tooling | `P##` |
| `perf` | Performance | `P##-##-##` |
| `wip` | Paused state | `P##` |
| `decision` | Standalone decision record | `P##` |
| `compound` | Compound learning | `P##` |
| `escalation` | Escalation artifact | `P##` |
| `verify` | Verification result | `P##` |
| `note` | Contextual annotation | `P##` |
| `todo` | Future intent | `P##` |
## Scope Format
| Context | Format | Example |
|---------|--------|---------|
| Initialization | `init` | `docs(init): initialize project (5 phases)` |
| Milestone | `milestone` | `docs(milestone): complete v1.0-mvp` |
| Phase-level | `P##` | `docs(P03): complete auth phase` |
| Plan-level | `P##-##` | `feat(P03-01): implement JWT` |
| Task-level | `P##-##-##` | `feat(P03-01-02): add refresh rotation` |
Phase numbers are zero-padded to 2 digits. Plan and task numbers are not zero-padded.
## Builder Methods
The `CommitBuilder` class provides typed constructors:
| Method | Input | Commit Type |
|--------|-------|-------------|
| `buildInitCommit` | InitCommitInput | `docs(init)` |
| `buildTaskCommit` | TaskCommitInput | any task type |
| `buildPhaseCompletionCommit` | PhaseCompletionInput | `docs(P##)` |
| `buildDecisionCommit` | DecisionCommitInput | `decision(P##)` |
| `buildEscalationCommit` | EscalationCommitInput | `escalation(P##)` |
| `buildCompoundCommit` | CompoundCommitInput | `compound(P##)` |
| `buildVerifyCommit` | VerifyCommitInput | `verify(P##)` |
| `buildResearchCommit` | phase, milestone, subject, findings | `docs(P##)` |
## Reconstruction Guarantee
An agent with access to only commit messages (no code, no diffs, no .ci/ files) can reconstruct:
1. **Current phase and milestone** — from the latest commit's `phase` and `milestone` fields
2. **Pipeline stage** — from the latest commit's `status` field
3. **All decisions** — by collecting `decisions[]` from commits where `type: decision` or any commit with a `decisions` block
4. **All escalations** — by collecting `escalations[]` from `type: escalation` commits
5. **Requirements coverage** — by aggregating `requirements.covered` and `requirements.partial` across all commits
6. **Lessons learned** — by collecting `lessons[]` across all commits
7. **Compound learnings** — by collecting `compound` objects across all commits
8. **Phase completion status** — from the branch state (merged = complete, active = in progress)
## Anti-Patterns
- Never put CI metadata in code comments — it belongs in commit messages
- Never omit the `---ci---` block from a CI-generated commit
- Never store decisions, escalations, or lessons in files — commit them
- Never use a non-standard commit type — use the 14 types above
- Never put freeform text inside the YAML block — use the structured fields
</commit_schema>
+104
View File
@@ -0,0 +1,104 @@
<decision_engine>
How CI makes decisions and commits them as git artifacts. The DecisionEngine uses bounded rationality with confidence thresholds to auto-decide or escalate.
---
## Confidence Thresholds
| Level | Range | Action |
|-------|-------|--------|
| High | > 0.85 | Auto-decide, commit with minimal logging |
| Medium | 0.600.85 | Auto-decide, commit with assumption logging |
| Low | < 0.60 | Escalate to human |
The threshold is configurable via `config.autonomy.decision_confidence_threshold` (default: 0.60).
## Decision Flow
```
Input: decision + rationale + confidence + alternatives
├─ confidence >= threshold → Auto-decide
│ ├─ High confidence: commit with type `decision`
│ └─ Medium confidence: commit with type `decision`, log assumptions
└─ confidence < threshold → Escalate
└─ Generate escalation commit, pause for human input
```
## DecisionRecord in Commits
Every decision is recorded in a `---ci---` block:
```yaml
decisions:
- id: D-001
decision: "Use YAML blocks in commit messages for project state"
rationale: "Git log is queryable, diffable, and survives repo transfers"
confidence: 0.92
alternatives: [JSON sidecar files, .ci/audit/ directory, database]
```
## DecisionEngine API
| Method | Returns | Purpose |
|--------|---------|---------|
| `makeDecision(input)` | DecisionResult | Full decision flow with confidence check |
| `makeHighConfidenceDecision(...)` | DecisionResult | Shortcut for confidence = 0.95 |
| `makeMediumConfidenceDecision(...)` | DecisionResult | Shortcut for confidence = 0.70 |
| `shouldAutoDecide(confidence)` | boolean | Check threshold without making decision |
| `isIrreversibleAction(action)` | boolean | Check against escalation hooks |
| `commitDecision(commitMessage)` | boolean | Execute git commit |
| `setPhase(phase)` | void | Update current phase |
| `setMilestone(milestone)` | void | Update current milestone |
## DecisionResult
```typescript
interface DecisionResult {
decision: Decision;
escalated: boolean;
reason?: string; // set when escalated
commitMessage?: string; // set when git.auto_commit is true
}
```
## Decision Categories
| Category | When Used |
|----------|-----------|
| `architecture` | System structure, component boundaries |
| `technology` | Library, framework, tool choices |
| `implementation` | Algorithm, data structure, approach |
| `prioritization` | Feature ordering, scope decisions |
| `security` | Threat disposition, auth approach |
| `testing` | Test strategy, coverage targets |
| `performance` | Optimization decisions, caching strategy |
## Irreversible Actions
The `isIrreversibleAction()` method checks against `config.autonomy.escalation_hooks`. Default hooks include patterns like `delete`, `drop`, `force`, `reset --hard`, and any custom patterns.
Even with high confidence, irreversible actions are flagged for additional scrutiny.
## Decision Retrieval
Decisions are retrieved from the git log, not from files:
```typescript
const gitContext = new GitContext(projectPath);
const allDecisions = gitContext.getDecisions(); // All phases
const phaseDecisions = gitContext.getDecisions(3); // Phase 3 only
const commitDecisions = gitContext.getDecisionsFromCommits(commits, 3);
```
## Anti-Patterns
- Never write decisions to a `.ci/audit/` file — commit them
- Never skip recording a decision, even high-confidence ones
- Never make a decision without listing alternatives
- Never override the confidence threshold without explicit configuration
- Never store the decision counter in a file — it's ephemeral per session
</decision_engine>
@@ -0,0 +1,97 @@
<git_context_loading>
How CI agents load project context. The git log IS the project memory — a CI agent's first impulse to gather context is `git log` + `git branch`, not file reads.
---
## Core Principle
**Read the log first, files second.**
The git log contains every decision, escalation, lesson, and compound learning through structured `---ci---` YAML blocks. Files in `.ci/` are long-lived reference documents (PROJECT.md, ARCHITECTURE.md, ROADMAP.md, REQUIREMENTS.md, config.json) that change infrequently.
## Context Loading Sequence
1. **Branch scan**`GitContext.getBranches()` to discover phase and milestone structure
2. **State reconstruction**`GitContext.reconstructState()` to get current phase, milestone, stage
3. **Decision scan**`GitContext.getDecisions()` for all project decisions
4. **Escalation check**`GitContext.getEscalations()` for any pending escalations
5. **Requirements coverage**`GitContext.getRequirementsCoverage()` for covered/partial
6. **Lessons scan**`GitContext.getLessons()` for all learned lessons
7. **Compound learnings**`GitContext.getCompounds()` for cross-phase patterns
8. **File reads** — Only now read `.ci/` files (PROJECT.md, ARCHITECTURE.md, etc.)
## GitContext API
| Method | Returns | Purpose |
|--------|---------|---------|
| `isGitRepo()` | boolean | Check if inside a git repo |
| `getCurrentBranch()` | string | Current branch name |
| `getRecentCommits(count)` | ParsedCiCommit[] | Recent commits with parsed `---ci---` blocks |
| `getLatestCiCommit()` | ParsedCiCommit \| null | Most recent CI commit |
| `getBranches()` | BranchInfo[] | All branches with type and merge status |
| `getPhaseBranches()` | BranchInfo[] | Phase branches only |
| `getMilestoneBranches()` | BranchInfo[] | Milestone branches only |
| `reconstructState()` | ProjectState | Full project state from git log |
| `getDecisions(phase?)` | CommitDecision[] | Decisions, optionally filtered by phase |
| `getLessons(phase?)` | string[] | Learned lessons |
| `getCompounds(category?)` | CompoundInfo[] | Compound learnings |
| `getEscalations()` | EscalationInfo[] | All escalations |
| `getRequirementsCoverage()` | { covered, partial } | Requirement traceability |
| `getCommitsForPhase(phase)` | ParsedCiCommit[] | All commits for a phase |
| `getCommitsForBranch(branch)` | ParsedCiCommit[] | All commits on a branch |
## ProjectState
The `reconstructState()` method returns:
```typescript
interface ProjectState {
currentPhase: number;
currentMilestone: string;
currentStage: PipelineStage;
phasesCompleted: number[];
phaseBranches: BranchInfo[];
milestoneBranches: string[];
lastCommit: ParsedCiCommit | null;
}
```
Derived entirely from git data — no file reads required.
## ParsedCiCommit
Every commit returned by `getRecentCommits()` is parsed into:
```typescript
interface ParsedCiCommit {
hash: string;
type: CommitType;
scope: string;
subject: string;
ci: CiMetadata | null; // null if no ---ci--- block
body: string;
}
```
Commits without `---ci---` blocks have `ci: null` — these are treated as non-CI commits (e.g., manual edits by the developer).
## Context Budget Strategy
When context is limited:
1. `reconstructState()` — always (cheap, single call)
2. `getDecisions(currentPhase)` — current phase decisions only
3. `getRequirementsCoverage()` — aggregate view
4. Skip lessons/compounds unless specifically needed
5. Read `.ci/ROADMAP.md` instead of scanning all phase branches
## What NOT to Do
- Never read `.ci/` files before checking the git log
- Never parse commit messages manually — use `CommitParser.parseCommitMessage()`
- Never assume the latest commit reflects the current state — check branches
- Never reconstruct state from files when git data is available
- Never skip the branch scan — merged branches indicate completed phases
</git_context_loading>