forge-orkes 0.77.0 → 0.80.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-forge.js +27 -0
- package/experimental/m10/README.md +4 -4
- package/experimental/m10/install.sh +10 -9
- package/experimental/m10/source/hooks/protect-primary-checkout.sh +149 -0
- package/package.json +1 -1
- package/template/.claude/hooks/forge-context-migrate.sh +297 -0
- package/template/.claude/hooks/forge-size-gate.sh +175 -0
- package/template/.claude/hooks/forge-state-rollup.sh +135 -2
- package/template/.claude/hooks/protect-primary-checkout.sh +149 -0
- package/template/.claude/hooks/skill-gate-tiers.txt +6 -0
- package/template/.claude/hooks/tests/forge-context-migrate.test.sh +166 -0
- package/template/.claude/hooks/tests/forge-size-gate.test.sh +132 -0
- package/template/.claude/hooks/tests/protect-primary-checkout.test.sh +147 -0
- package/template/.claude/settings.json +14 -1
- package/template/.claude/skills/discussing/SKILL.md +30 -31
- package/template/.claude/skills/executing/SKILL.md +3 -3
- package/template/.claude/skills/forge/SKILL.md +112 -170
- package/template/.claude/skills/forge/references/manual-fallback-procedures.md +54 -0
- package/template/.claude/skills/initializing/SKILL.md +20 -1
- package/template/.claude/skills/planning/SKILL.md +11 -9
- package/template/.claude/skills/researching/SKILL.md +2 -0
- package/template/.claude/skills/reviewing/SKILL.md +17 -0
- package/template/.claude/skills/upgrading/SKILL.md +22 -2
- package/template/.claude/skills/verifying/SKILL.md +2 -2
- package/template/.forge/FORGE.md +81 -90
- package/template/.forge/bin/new-worktree.sh +61 -0
- package/template/.forge/bin/tests/new-worktree.test.sh +70 -0
- package/template/.forge/checks/forge-jarvis-awareness.sh +46 -2
- package/template/.forge/checks/forge-jarvis-relay.sh +64 -16
- package/template/.forge/checks/tests/forge-jarvis-awareness.test.sh +58 -0
- package/template/.forge/checks/tests/forge-jarvis-relay.test.sh +50 -0
- package/template/.forge/git-hooks/pre-commit +57 -0
- package/template/.forge/git-hooks/tests/pre-commit.test.sh +97 -0
- package/template/.forge/migrations/0.78.0-weld-primary-checkout.md +66 -0
- package/template/.forge/migrations/0.80.0-context-md-sharding.md +158 -0
- package/template/.forge/migrations/0.80.0-size-gate-wiring.md +145 -0
- package/template/.forge/templates/constitution.md +1 -1
- package/template/.forge/templates/context-milestone.md +30 -0
- package/template/.forge/templates/slice-runner/config.yml +7 -1
- package/template/.forge/templates/state/desire-path.yml +3 -0
- package/experimental/m10/source/hooks/forge-branch-guard.sh +0 -61
package/template/.forge/FORGE.md
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# Forge
|
|
2
2
|
|
|
3
|
-
Lean meta-prompting framework. Context engineering + constitutional governance on agent-native primitives. This file is the always-loaded **routing/index layer**: tables, invariants,
|
|
3
|
+
Lean meta-prompting framework. Context engineering + constitutional governance on agent-native primitives. This file is the always-loaded **routing/index layer**: tables, invariants, pointers. Procedures live in the skills that execute them; rationale lives in ADRs — both load on demand only.
|
|
4
4
|
|
|
5
5
|
## Critical: No Native Plan Mode
|
|
6
6
|
|
|
7
|
-
**NEVER use `EnterPlanMode` when Forge active.** Forge writes plans to `.forge/phases/` — native plan mode conflicts.
|
|
8
|
-
|
|
9
|
-
All phases: **invoke via `Skill` tool**, never native behavior. `planning` → `Skill(planning)`, not `EnterPlanMode`.
|
|
7
|
+
**NEVER use `EnterPlanMode` when Forge active.** Forge writes plans to `.forge/phases/` — native plan mode conflicts. All phases: invoke via `Skill` tool, e.g. `planning` → `Skill(planning)`, not `EnterPlanMode`.
|
|
10
8
|
|
|
11
9
|
## Core Principles
|
|
12
10
|
|
|
13
11
|
1. **Lean default, powerful when needed.** Quick → skip ceremony. Complex → full governance.
|
|
14
|
-
2. **Adapter-native.** Use each platform's built-ins through adapters
|
|
12
|
+
2. **Adapter-native.** Use each platform's built-ins through adapters; avoid custom runtime unless the substrate needs it.
|
|
15
13
|
3. **Context is sacred.** Size-gate artifacts, lazy-load skills, fresh agents for isolated work.
|
|
16
14
|
4. **Decisions are contracts.** Lock before building. Honor or flag — never silently override.
|
|
17
15
|
5. **Verify goals, not tasks.** "Does it work?" > "Did we finish checklist?"
|
|
@@ -21,7 +19,7 @@ All phases: **invoke via `Skill` tool**, never native behavior. `planning` → `
|
|
|
21
19
|
|
|
22
20
|
## Core Protocol And Adapters
|
|
23
21
|
|
|
24
|
-
**Core protocol** lives in `.forge/`: project/constitution state, milestones, requirements, roadmap, context, streams, work packages, plans, verification, audits,
|
|
22
|
+
**Core protocol** lives in `.forge/`: project/constitution state, milestones, requirements, roadmap, context, streams, work packages, plans, verification, audits, state ownership rules — durable behavior, not one platform's tool names. **Platform adapters** translate the protocol into a runtime (Claude Code skills/hooks, Codex/AGENTS behavior, manual handoffs) and stay out of core requirements.
|
|
25
23
|
|
|
26
24
|
## Project Streams
|
|
27
25
|
|
|
@@ -33,18 +31,16 @@ Three runtime coordination layers: Project, Stream, Work Package.
|
|
|
33
31
|
| **Stream** | One active line of work: goal, branch/worktree, state, blockers, next action | `.forge/streams/{stream}.yml` + `brief.md` |
|
|
34
32
|
| **Work Package** | Delegated bounded task inside a stream | `.forge/streams/{stream}/packages/{id}.yml` |
|
|
35
33
|
|
|
36
|
-
Ordinary Quick/Standard/Full tiers run inside a stream
|
|
34
|
+
Ordinary Quick/Standard/Full tiers run inside a stream; Project Chief / Chief of Staff coordinates multiple active streams or pivots without dropping context. Workers report to the stream or project chief, never laterally. M10 worktrees are an optional isolation backend, not the primary UX.
|
|
37
35
|
|
|
38
|
-
**Merge often and early
|
|
36
|
+
**Merge often and early** — integrate ready work as soon as it is ready, not at milestone end (long-lived branches drift, merges grow riskier). Realized producer-side (checkpoint publishing, below) + project-side (the Chief's Merge Cadence Check). Forge never force-pushes, auto-resolves a conflict, or auto-rebases.
|
|
39
37
|
|
|
40
38
|
### Stream Integration Checkpoints
|
|
41
39
|
|
|
42
|
-
Event-driven continuous integration
|
|
40
|
+
Event-driven continuous integration ([ADR-027](../docs/decisions/ADR-027-integration-checkpoints-declaration-split.md)). Producer: `verifying` (Integration Checkpoint Publish). Consumer: `forge` boot (Step 1 preflight).
|
|
43
41
|
|
|
44
|
-
- **
|
|
45
|
-
- **
|
|
46
|
-
- **Intake (declaration split, B3'):** a new milestone/stream's **first** worktree commit is a declaration — milestone id (+ reservation), one-line intent, stream registration with `ownership` claims — ff-pushed to main immediately; content stays worktree-only until the milestone's own checkpoint. One-breath rule: main knows THAT work exists; the worktree knows WHAT it is.
|
|
47
|
-
- **Consumer:** boot reads the flag (absent or already-seen → skip; no fetch, no scan). Clean fast-forwards run automatically in worktrees **and** the primary main checkout; any divergence surfaces a prompt — never an auto-rebase. Cross-machine, the flag doesn't exist — the opt-in `forge.worktree_rebase_check` / periodic fetch covers it, eventually.
|
|
42
|
+
- **Producer:** plan-marked checkpoints (`integration_checkpoint: true`, operator-confirmed) fast-forward-push on a worktree's verified PASS, strand-guarded (push only if local `main` is an ancestor of HEAD; stranded → "integrate through local main first", forge#13), then set the machine-local integration flag. A stream's first worktree commit is a lighter declaration-only push (id + intent) so main knows work exists before content lands.
|
|
43
|
+
- **Consumer:** boot reads the flag (absent/seen ⇒ skip, no fetch); clean fast-forwards run automatically, any divergence prompts — never auto-rebase. Cross-machine: opt-in `forge.worktree_rebase_check`.
|
|
48
44
|
|
|
49
45
|
### Worktree Convention (base)
|
|
50
46
|
|
|
@@ -59,6 +55,8 @@ Where worktrees live and what they're named — single source of truth, base con
|
|
|
59
55
|
|
|
60
56
|
**Recorded path is authoritative once set** — `lifecycle.worktree_path` (milestone) / `runtime.worktree` (stream) is read verbatim, never re-derived; changing `worktree_root` affects only new worktrees; move a live worktree only with `git worktree move` + update the record. No recorded path → resolve from the convention. `forge` boot and `chief-of-staff` adoption **validate advisorily**: recorded path outside the resolved root → warn (never block, never move); an explicit `worktree_root` is honored.
|
|
61
57
|
|
|
58
|
+
**Primary checkout is welded to the default branch — all tiers** ([ADR-031](../docs/decisions/ADR-031-weld-primary-checkout-to-default-branch.md)). Its HEAD is shared across concurrent sessions; a `checkout -b` there silently redirects others' commits. Enforced (`git-dir == git-common-dir` scoped, never touches worktrees): `.claude/hooks/protect-primary-checkout.sh` (PreToolUse — denies branch create/switch/detach + hard-reset) + `.forge/git-hooks/pre-commit` (off-default-commit backstop; wired per-clone by `initializing`/`upgrading` via repo-relative `core.hooksPath`, detect+warn+never-clobber). Branch work: `.forge/bin/new-worktree.sh <slug>`. Escape: `ALLOW_MAIN_BRANCHING=1`.
|
|
59
|
+
|
|
62
60
|
**`orchestration.worktree_hydrate`** (optional, `project.yml`; forge#18) lists superproject-relative paths to required-but-gitignored artifacts (e.g. `.mcp.json`) a fresh worktree's tracked-only checkout lacks. Copy is guarded `source-exists && dest-absent` — never overwrites, never `git add`s, so the secret stays untracked in both trees. Applied by `orchestrating` at creation and re-asserted idempotently in `executing`'s Workspace Prerequisites, covering worktrees of any origin. Omit ⇒ no propagation. The per-worktree runtime counterpart of the install-time carve-out propagation below (State Ownership § Stable shared).
|
|
63
61
|
|
|
64
62
|
## Workflow Tiers
|
|
@@ -66,9 +64,9 @@ Where worktrees live and what they're named — single source of truth, base con
|
|
|
66
64
|
Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
67
65
|
|
|
68
66
|
### Prototyping (minutes–hours, disposable)
|
|
69
|
-
**Triggers:** "mock up"
|
|
67
|
+
**Triggers:** disposable in-app visual exploration ("mock up", "sketch", "prototype", "explore how X could look") behind an alpha/feature flag, no requirements locked yet
|
|
70
68
|
**Flow:** `prototyping` — establish flag → iterate ⟲ (render → react → adjust) → resolve (graduate→milestone | park | discard)
|
|
71
|
-
**No verify/review gate by design** —
|
|
69
|
+
**No verify/review gate by design** — a thing to look at and react to, not shippable code. Real UI work with known requirements is Standard + `designing` (which *does* gate on quality). See [ADR-022](../docs/decisions/ADR-022-prototyping-tier.md).
|
|
72
70
|
|
|
73
71
|
### Quick (minutes)
|
|
74
72
|
**Triggers:** bug fix, typo, config, dep bump, <50 lines
|
|
@@ -89,29 +87,27 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
|
89
87
|
|
|
90
88
|
## Skill Routing
|
|
91
89
|
|
|
92
|
-
| Need to... | Skill |
|
|
93
|
-
|
|
94
|
-
| Start any task | `forge` |
|
|
95
|
-
|
|
|
96
|
-
| Investigate
|
|
97
|
-
| Talk through
|
|
98
|
-
| Architectural decisions | `architecting` |
|
|
99
|
-
| Break work into tasks
|
|
100
|
-
| Build with deviation rules + atomic commits | `executing` |
|
|
101
|
-
| Prove
|
|
102
|
-
|
|
|
103
|
-
| Small scoped fix | `quick-tasking` |
|
|
104
|
-
|
|
|
105
|
-
| UI with design system | `designing` |
|
|
106
|
-
| Security review | `securing` |
|
|
107
|
-
| E2E/integration tests + suite audit (+ M9 author-mode gate) | `testing` |
|
|
108
|
-
| Systematic debugging | `debugging` |
|
|
109
|
-
| Upgrade Forge files
|
|
110
|
-
| Cross-session memory | `beads-integration` |
|
|
111
|
-
| Project/read Forge work in Notion | `notion-integration` |
|
|
112
|
-
| Multi-agent backend (experimental) | `orchestrating` |
|
|
113
|
-
|
|
114
|
-
> Experimental skills require opt-in install — see `packages/create-forge/experimental/m10/README.md`.
|
|
90
|
+
| Need to... | Skill |
|
|
91
|
+
|------------|-------|
|
|
92
|
+
| Start any task | `forge` |
|
|
93
|
+
| First-run project setup | `initializing` |
|
|
94
|
+
| Investigate before building | `researching` |
|
|
95
|
+
| Talk through trade-offs | `discussing` |
|
|
96
|
+
| Architectural decisions | `architecting` |
|
|
97
|
+
| Break work into gated tasks | `planning` |
|
|
98
|
+
| Build with deviation rules + atomic commits | `executing` |
|
|
99
|
+
| Prove goals delivered (+ M9 e2e gate; see Human Verification Gate) | `verifying` |
|
|
100
|
+
| Health/security/refactor gate + close-the-loop | `reviewing` |
|
|
101
|
+
| Small scoped fix | `quick-tasking` |
|
|
102
|
+
| Disposable UI mockup behind an alpha flag | `prototyping` |
|
|
103
|
+
| UI with design system | `designing` |
|
|
104
|
+
| Security review | `securing` |
|
|
105
|
+
| E2E/integration tests + suite audit (+ M9 author-mode gate) | `testing` |
|
|
106
|
+
| Systematic debugging | `debugging` |
|
|
107
|
+
| Upgrade Forge files (registry default; `/upgrading dev` for local sync) | `upgrading` |
|
|
108
|
+
| Cross-session memory | `beads-integration` |
|
|
109
|
+
| Project/read Forge work in Notion | `notion-integration` |
|
|
110
|
+
| Multi-agent backend (experimental — see `packages/create-forge/experimental/m10/README.md`) | `orchestrating` |
|
|
115
111
|
|
|
116
112
|
## Context Engineering
|
|
117
113
|
|
|
@@ -123,7 +119,7 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
|
123
119
|
| `requirements/m{N}.yml` | 50 KB/file | Prevents scope creep |
|
|
124
120
|
| `plan.md` | 30 KB | Keeps executor context <50% |
|
|
125
121
|
| `constitution.md` | 10 KB | Gates must be scannable |
|
|
126
|
-
| `context.md` | 12 KB |
|
|
122
|
+
| `context.md` | 12 KB | Derived index, rendered fresh every boot — one row per milestone, pointing at `context/{id}.md` (full decisions live there, single-owner-mutable) |
|
|
127
123
|
| `refactor-backlog.yml` | 150 KB | Terminal items archive to `refactor-backlog-archive.yml` (advisory) |
|
|
128
124
|
| `streams/active.yml` | 20 KB | Traffic map must stay cheap to load |
|
|
129
125
|
| `streams/{stream}.yml` | 15 KB | Stream state stays resumable without becoming history |
|
|
@@ -134,89 +130,84 @@ Auto-detects complexity. Override: "Use Quick/Standard/Full tier."
|
|
|
134
130
|
|
|
135
131
|
### Accumulating Artifacts
|
|
136
132
|
|
|
137
|
-
Every new accumulating artifact declares
|
|
133
|
+
Every new accumulating artifact declares owner, size gate, archive/compaction rule, `derived` vs source-of-truth status, migration behavior. `roadmap.yml`, `requirements/*.yml`, `releases.yml`, `reservations.yml` are source-of-truth — load scoped slices, not whole history. `phases/`, `audits/`, `research/`, `archive/`, `context-archive.md`, `reservations-archive.yml` are historical — load only by milestone/stream/package/version (R104: `reservations-archive.yml` holds every row `compact` dropped; the working file keeps each kind's floor). `prototypes/{slug}.md` is single-owner while `exploring|parked`; terminal → `prototypes/archive/`. Runtime/generated files (`.mcp-server/*.db*`, logs, spike DBs, `.git/forge/*`) are excluded unless debugging. Board (ADR-025): `.forge/notion/map.yml` is derived; config is `board:` in `project.yml` (absent ⇒ inert). Template copies sync mechanically.
|
|
138
134
|
|
|
139
135
|
### Fresh Agent Pattern
|
|
140
|
-
|
|
136
|
+
20+ files or a complex subsystem → spawn a fresh executor with isolated context. Prevents context rot.
|
|
141
137
|
|
|
142
138
|
### Context Handoff
|
|
143
|
-
Each phase writes outputs to `.forge/` before completing
|
|
139
|
+
Each phase writes outputs to `.forge/` before completing; at boundaries recommend `/clear`, next phase loads from disk. Advisory — skip under 40% context.
|
|
144
140
|
|
|
145
141
|
### Lazy Loading
|
|
146
|
-
Skills load only when invoked.
|
|
142
|
+
Skills load only when invoked. `.forge/context.md` is a derived index (id/name/status/path per milestone) — consult it for orientation, then read the specific `context/{id}.md` when a milestone's actual decisions are needed. `context-archive.md` predates this model and is superseded (its content lives in `context/{id}.md` now); consult it only for pre-0.80.0 history not yet folded in.
|
|
147
143
|
|
|
148
144
|
### Rules Layer (`.claude/rules/`)
|
|
149
|
-
Three policy weights, lightest-first (ADR-018):
|
|
145
|
+
Three policy weights, lightest-first (ADR-018): `constitution.md` (always-loaded hard gates, 10 KB) → `.claude/rules/` (ambient guidance — always-on, or path-scoped via `paths:` glob at zero cost until a matching file is touched) → skills (heavy, opt-in). Core ships one always-on rule, `agent-discipline.md`; stack-specific rules are opt-in (`experimental/conventions/`). Format + authoring: `.claude/rules/README.md`.
|
|
150
146
|
|
|
151
147
|
## Model Routing
|
|
152
148
|
|
|
153
|
-
Configured in `project.yml` under `models:` — `default`, `parent_session` (advisory), `by_complexity.{trivial|standard|complex}` (per-task
|
|
149
|
+
Configured in `project.yml` under `models:` — `default`, `parent_session` (advisory), `by_complexity.{trivial|standard|complex}` (per-task), `skills.{name}` (per-skill).
|
|
154
150
|
|
|
155
|
-
**Precedence:** `by_complexity.{task.complexity}` → `skills.X` → `default` → parent model. **Complexity overrides skill** (LOCKED, context.md § M24)
|
|
151
|
+
**Precedence:** `by_complexity.{task.complexity}` → `skills.X` → `default` → parent model. **Complexity overrides skill** (LOCKED, `context.md` § M24) — a task with no complexity resolves via the unchanged chain, zero behavior change untagged.
|
|
156
152
|
|
|
157
|
-
**All advisory** — forge displays the expected model at each transition
|
|
153
|
+
**All advisory** — forge displays the expected model at each transition, suggests `/model` on mismatch; the review gate logs the model used. Forge cannot auto-switch models.
|
|
158
154
|
|
|
159
|
-
**Subagent inheritance.** A skill spawning subagents MUST pass the resolved `model` param explicitly — subagents do NOT inherit skill-level routing
|
|
155
|
+
**Subagent inheritance.** A skill spawning subagents MUST pass the resolved `model` param explicitly — subagents do NOT inherit skill-level routing. Resolve per task where it declares `complexity`, else the skill chain; surface *"Spawning {role} with model: {model} (from {source})"*. An inline task inherits the session model.
|
|
160
156
|
|
|
161
|
-
**Spawn gate — predicate A (LOCKED, context.md § M24; refined m-33).** Executing's mandatory spawn on a complexity-tagged task fires
|
|
157
|
+
**Spawn gate — predicate A (LOCKED, `context.md` § M24; refined m-33).** Executing's mandatory spawn on a complexity-tagged task fires only when the resolved model differs from the live session model — resolved == live (always true with no `models:` block) makes the spawn a no-op, so the tag becomes a context signal only.
|
|
162
158
|
|
|
163
159
|
## Agents
|
|
164
160
|
|
|
165
161
|
| Agent | Role | Tools | When |
|
|
166
162
|
|-------|------|-------|------|
|
|
167
|
-
| `researcher` | Investigation | Read-only
|
|
168
|
-
| `planner` | Planning + constitutional gates | Read + Write (
|
|
163
|
+
| `researcher` | Investigation | Read-only | Research |
|
|
164
|
+
| `planner` | Planning + constitutional gates | Read + Write (plans) | Planning |
|
|
169
165
|
| `executor` | Building + deviation rules | All dev tools | Execution |
|
|
170
166
|
| `verifier` | Goal-backward verification | Read + Bash (tests) | Verification |
|
|
171
|
-
| `reviewer` | Security
|
|
172
|
-
| `tester` | E2E/integration authoring + suite analysis |
|
|
173
|
-
| `security-reviewer` |
|
|
174
|
-
| `performance-reviewer` | Real bottlenecks only (
|
|
175
|
-
| `doc-reviewer` | Docs-vs-source accuracy (≥8; Blocker/Major/Minor) | Read, Grep, Glob, Bash |
|
|
167
|
+
| `reviewer` | Security/architecture/refactoring (breadth, gates the health report) | Read, Bash, Grep, Glob | Reviewing |
|
|
168
|
+
| `tester` | E2E/integration authoring + suite analysis | Author (Write/Edit/Bash/git) or read-only | Testing skill |
|
|
169
|
+
| `security-reviewer` | Security depth (≥8, exploit-scenario required) | Read, Grep, Glob, Bash | Deep-dive |
|
|
170
|
+
| `performance-reviewer` | Real bottlenecks only (frequency × cost; ≥8 + ≥Medium) | Read, Grep, Glob, Bash | Deep-dive |
|
|
171
|
+
| `doc-reviewer` | Docs-vs-source accuracy (≥8; Blocker/Major/Minor) | Read, Grep, Glob, Bash | Deep-dive |
|
|
176
172
|
|
|
177
|
-
The three
|
|
173
|
+
The three specialists complement `reviewer` — single-axis, read-only, pulled in deliberately. `reviewer`'s 3-mode pass remains the breadth gate on the milestone health report.
|
|
178
174
|
|
|
179
175
|
## Project Init (First Run)
|
|
180
176
|
|
|
181
|
-
No `.forge/project.yml` → auto-detect project type:
|
|
182
|
-
|
|
183
|
-
**Brownfield** (has package.json/src/.git): detect frameworks → absorb → verify docs vs code → scan stack → detect design system → analyze patterns → infer constitution → user confirms.
|
|
184
|
-
|
|
185
|
-
**Greenfield**: project basics → design system → constitution → state init.
|
|
186
|
-
|
|
187
|
-
Quick tier skips init.
|
|
177
|
+
No `.forge/project.yml` → auto-detect project type. **Brownfield** (has package.json/src/.git): frameworks → absorb → verify docs vs code → scan stack → design system → patterns → infer constitution → user confirms. **Greenfield**: project basics → design system → constitution → state init. Quick tier skips init.
|
|
188
178
|
|
|
189
179
|
## State Management
|
|
190
180
|
|
|
191
181
|
State lives in `.forge/`. One line per artifact; protocol detail lives in the named ADRs and skills:
|
|
192
182
|
|
|
193
|
-
- `project.yml` — vision, stack, design system, verification, constraints (<5 KB). Optional `jarvis.dev_lane_cmd` (m-48)
|
|
183
|
+
- `project.yml` — vision, stack, design system, verification, constraints (<5 KB). Optional `jarvis.dev_lane_cmd` (m-48) seams Jarvis's dev-server spin-up for UAT; absent ⇒ manual start (`docs/jarvis.md`).
|
|
194
184
|
- `constitution.md` — active architectural gates
|
|
195
185
|
- `design-system.md` — component mapping table
|
|
196
|
-
- `requirements/m{N}.yml` — per-milestone structured requirements
|
|
186
|
+
- `requirements/m{N}.yml` — per-milestone structured requirements, `[NEEDS CLARIFICATION]` markers. **FR/DEF/NFR ids globally unique** — reserve first. M9 e2e gate fields lazy-migrate; absent ⇒ `false`.
|
|
197
187
|
- `roadmap.yml` — phases, milestones, dependencies
|
|
198
|
-
- `state/index.yml` — DERIVED milestone registry,
|
|
199
|
-
- **Derived `release_state`** ([ADR-024](../docs/decisions/ADR-024-merged-validated-fold.md)) — `{unmerged, merged, validated}
|
|
188
|
+
- `state/index.yml` — DERIVED milestone registry, git-ignored render-on-read cache (0.53.0), regenerated at boot by `forge-state-rollup.sh` (issue #22; read-only for skills); never committed; absent on a fresh clone (sentinel `project.yml`). Rows carry derived `release_state`.
|
|
189
|
+
- **Derived `release_state`** ([ADR-024](../docs/decisions/ADR-024-merged-validated-fold.md)) — `{unmerged, merged, validated}`, computed at rollup (`forge-release-fold.sh`, git-only, offline), never stored. Binding via `forge.validation_event`: `in_session_signoff` (default) or `promotion_approval` (CI-signed tag); inert until opted in.
|
|
200
190
|
- `state/milestone-{id}.yml` — per-milestone cursor (**single source of truth**): position, decisions, blockers. One owner at a time.
|
|
201
|
-
- `state/desire-paths/` — append-only
|
|
202
|
-
- `context.md` —
|
|
203
|
-
- `streams/active.yml` — DERIVED
|
|
204
|
-
- `streams/{stream}.yml` (+ `brief.md`, `packages/{id}.yml`) — per-stream **source of truth**:
|
|
191
|
+
- `state/desire-paths/` — append-only usage observations, `scope: project|framework` (absent ⇒ project); counts derived by globbing. `forge` boot is the single review owner — surfaces groups at 3+, persists declines, routes `framework`-scope upstream ([ADR-012](../docs/decisions/ADR-012-upstream-desire-path-feedback-channel.md)); `verifying` captures, never surfaces. Issue-sourced entries carry `resolves_issue: N`.
|
|
192
|
+
- `context/{id}.md` (+ derived `context.md` index) — locked decisions + deferred ideas per milestone; history archives to `context-archive.md`
|
|
193
|
+
- `streams/active.yml` — DERIVED traffic map, git-ignored render-on-read cache, same hook — see Stream Rollup ([ADR-015](../docs/decisions/ADR-015-derived-stream-registry.md))
|
|
194
|
+
- `streams/{stream}.yml` (+ `brief.md`, `packages/{id}.yml`) — per-stream **source of truth**: lifecycle, ownership, surfaces, blockers, merge readiness, optional `stream.milestone` link (rollup reads phase from the milestone)
|
|
205
195
|
- `research/milestone-{id}.md` — dated immutable findings snapshot
|
|
206
196
|
- `phases/milestone-{id}/{phase}-{name}/plan-{NN}.md` — task plans with must_haves frontmatter (`{id}`=milestone, `{phase}`=phase# verbatim, `{name}`=kebab, `{NN}`=seq)
|
|
207
|
-
- `refactor-backlog.yml` —
|
|
197
|
+
- `refactor-backlog.yml` — quick-tasking catalog; vocab `pending|in_progress|done|dismissed|deferred`; terminal items auto-archive
|
|
208
198
|
- `releases.yml` — append-only version-reservation registry (Version Reservation Protocol)
|
|
209
|
-
- `reservations.yml` — append-only id-reservation audit trail + cross-machine floor
|
|
199
|
+
- `reservations.yml` — append-only id-reservation audit trail + cross-machine floor; `forge-reserve compact` (R104) moves dropped rows to `reservations-archive.yml`, keeping each kind's max + a 14-day tail
|
|
200
|
+
- `state/issue-loop/declined/{N}.yml` — close-the-loop decline markers (no re-nag) from `reviewing`; inert once the issue closes
|
|
210
201
|
- `archive/milestone-{id}/` — archived milestone artifacts (archive-delete)
|
|
211
|
-
- `prototypes/{slug}.md` —
|
|
202
|
+
- `prototypes/{slug}.md` — disposable-mockup state (`exploring|parked|graduated|discarded`); terminal → `prototypes/archive/`. Single-owner. See `Skill(prototyping)`.
|
|
212
203
|
|
|
213
|
-
**Milestones** group phases into concurrent streams — own state file, no conflicts
|
|
204
|
+
**Milestones** group phases into concurrent streams — own state file, no cross-session conflicts; deferred (frozen) or archive-deleted. Status vocab: `not_started | active | deferred | complete`. **`current.status` is authoritative** — 100% tasks ≠ done (verifying + reviewing + the recorded human sign-off still gate).
|
|
214
205
|
|
|
215
206
|
**Format:** YAML for machine state, Markdown for human content.
|
|
216
207
|
|
|
217
208
|
### Stream Rollup
|
|
218
209
|
|
|
219
|
-
`active.yml` is a
|
|
210
|
+
`active.yml` is a deterministic **join**: per-stream files contribute coordination state; active milestones contribute `phase` (stream-file-less active milestone → derived `implicit` row); `merge_queue` derives from `merge.readiness: ready`. A **step-0 auto-close sweep** closes milestone-backed streams whose milestone is `complete`, not `closed`, with no live worktree. Rendered by the same hook at boot and at `chief-of-staff` Show/Sync/CRUD; Rollup 1.0 runs first, sweep confined to main. Numbered procedure: the ADR-015 addendum — each field has one source, so drift cannot occur.
|
|
220
211
|
|
|
221
212
|
### State Commit Protocol
|
|
222
213
|
|
|
@@ -227,7 +218,7 @@ git add .forge/ # scoped — respects .gitignore; never `git add .`
|
|
|
227
218
|
git commit -m "chore(forge): sync state after {phase} — m{N} {phase-name}"
|
|
228
219
|
```
|
|
229
220
|
|
|
230
|
-
**In a worktree session, pin with the recorded path** — `git -C {worktree_path} add/commit` (
|
|
221
|
+
**In a worktree session, pin with the recorded path** — `git -C {worktree_path} add/commit` (cwd can silently reset to the launch checkout across turns; a wrong-cwd op can even exit 0). Guard: compare `git rev-parse --show-toplevel` to the recorded path before the first write — full discipline in `executing`/`verifying`. State-sync commits are separate from per-task code commits.
|
|
231
222
|
|
|
232
223
|
### State Ownership (multi-worktree safety)
|
|
233
224
|
|
|
@@ -235,12 +226,12 @@ git commit -m "chore(forge): sync state after {phase} — m{N} {phase-name}"
|
|
|
235
226
|
|
|
236
227
|
| Class | Files | Write rule |
|
|
237
228
|
|---|---|---|
|
|
238
|
-
| **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md` | Exactly one writer at a time — the checkout currently driving that milestone. Other worktrees never touch it (not even read-then-write; pull from main instead). |
|
|
229
|
+
| **Single-owner mutable** | `state/milestone-{id}.yml`, `phases/milestone-{id}/`, `research/milestone-{id}.md`, `context/{id}.md` | Exactly one writer at a time — the checkout currently driving that milestone. Other worktrees never touch it (not even read-then-write; pull from main instead). |
|
|
239
230
|
| **Single-owner mutable (per stream)** | `streams/{stream}.yml`, `streams/{stream}/brief.md`, `streams/{stream}/packages/*.yml` | The stream's current driver writes them; distinct streams own distinct files — no cross-stream contention. |
|
|
240
|
-
| **Derived (git-ignored render-on-read cache)** | `state/index.yml`, `streams/active.yml`, the rendered `release_state` column | Never hand-edited, **never committed** — regenerated by rollup from sources at every boot. Any session may render its own copy; absence on a fresh clone is normal (render-then-read; the sentinel is `project.yml`). |
|
|
231
|
+
| **Derived (git-ignored render-on-read cache)** | `state/index.yml`, `streams/active.yml`, `context.md`, the rendered `release_state` column | Never hand-edited, **never committed** — regenerated by rollup from sources at every boot. Any session may render its own copy; absence on a fresh clone is normal (render-then-read; the sentinel is `project.yml`). `context.md` specifically joins `context/{id}.md` files with `state/index.yml` (ADR-033). |
|
|
241
232
|
| **Append-only shared** | `releases.yml`, `reservations.yml`, `state/desire-paths/*`, `deferred-issues/*` | Many writers OK; structure prevents collision (append-only entries / distinct filenames). |
|
|
242
233
|
| **Machine-local runtime** | `.git/forge/id-reservations`, `.git/forge/integration-pending` | Git common dir — shared across a machine's worktrees, never git-tracked, excluded from framework context. |
|
|
243
|
-
| **Shared mutable** | `
|
|
234
|
+
| **Shared mutable** | `refactor-backlog.yml`, `requirements/m{N}.yml` | Write only your milestone/stream block; within a block, append-only — strike through instead of rewriting. |
|
|
244
235
|
| **Stable shared** | `project.yml`, `constitution.md`, `design-system.md`, `roadmap.yml`, `FORGE.md`, `.claude/skills/`, `.claude/agents/` | Rarely changes except via `/upgrading` (registry default — the npm bin is the sync engine; local dev sync only via explicit `/upgrading dev`). Canonical on main; worktrees lag until they rebase. An upgrade touches only its own checkout — `upgrading` Step 8 offers live-worktree rebases (never auto); opt-in `forge.worktree_rebase_check` is the boot-time net. Gitignored carve-outs (`.mcp.json`, experimental hooks) propagate via the experimental installer, not git. |
|
|
245
236
|
|
|
246
237
|
Ownership rules (quick reference):
|
|
@@ -252,11 +243,11 @@ Ownership rules (quick reference):
|
|
|
252
243
|
|
|
253
244
|
### Version Reservation Protocol
|
|
254
245
|
|
|
255
|
-
The
|
|
246
|
+
The version + CHANGELOG slot are shared — `.forge/releases.yml` (append-only). **Reserve at planning**: derive `max() + bump`, claim in the ledger first (`forge-reserve version --value <X.Y.Z>` — conflict names the sibling milestone holding it), append ONE `{milestone, version, bump, reserved_at, summary}` entry, **commit + push before editing the version file**. Never edit prior entries; never invent a number at delivery — no reservation found → reserve then, same steps. Push race → rebase onto the file, re-read `max()`. Bump criteria + race narrative: `planning`'s Reserve-version step + ADR-029.
|
|
256
247
|
|
|
257
248
|
### ID Reservation Protocol
|
|
258
249
|
|
|
259
|
-
Sequential ids
|
|
250
|
+
Sequential ids (`ADR-NNN`, `DEF-NNN`, `FR-`/`NFR-NNN`, milestone `M-NN`, refactor `R0NN`, roadmap phase/wave) are shared cross-worktree resources — scan-and-increment collides silently until merge. **Reserve before create**: `forge-reserve <kind> --milestone <id> [--summary <text>]` (`.claude/hooks/forge-reserve.sh`, kinds `adr|def|fr|nfr|milestone|refactor|phase|wave|version`). Next id = **`max(ledger ∪ in-tree ∪ main:reservations.yml) + 1`** per kind; residual collision → older milestone keeps the id, newer renumbers. Reserve points: `architecting` (adr), `planning` (fr/nfr/def, phase/wave, version), `forge` Rollup/`discussing`/`prototyping` (milestone), `reviewing` (refactor). Deferred issues are counter-free — nothing to collide on. Full mechanics (ledger + lock, id-format table, cross-machine floor, lazy migration): the helper's header comment + [ADR-021](../docs/decisions/ADR-021-id-reservation-ledger-git-common-dir.md) (supersedes ADR-016) + ADR-023 + ADR-029.
|
|
260
251
|
|
|
261
252
|
## Deviation Rules
|
|
262
253
|
|
|
@@ -281,16 +272,16 @@ verification:
|
|
|
281
272
|
- cmd: "npm run lint"
|
|
282
273
|
- cmd: "npm test"
|
|
283
274
|
- cmd: "npx tsc --noEmit"
|
|
284
|
-
advisory: true
|
|
285
|
-
auto_fix: true
|
|
286
|
-
max_retries: 2
|
|
275
|
+
advisory: true # pre-existing failures — warn only
|
|
276
|
+
auto_fix: true # fix and retry on failure
|
|
277
|
+
max_retries: 2 # max attempts per command
|
|
287
278
|
```
|
|
288
279
|
|
|
289
|
-
Auto-detected from `package.json` at init. Advisory mode warns, never blocks. Auto-fix loop: read output → fix → amend → re-run (≤ `max_retries`; retries count toward the 3-strike
|
|
280
|
+
Auto-detected from `package.json` at init. Advisory mode warns, never blocks. Auto-fix loop: read output → fix → amend → re-run (≤ `max_retries`; retries count toward the 3-strike limit). Empty commands = opt-out. `verification.e2e_soft_cap` (default 10) — advisory per-milestone cap on `e2e:true` stories, surfaced by `reviewing`, never blocks.
|
|
290
281
|
|
|
291
282
|
### Truth checks (executable must_haves)
|
|
292
283
|
|
|
293
|
-
Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exit 0 = pass; authored goal-backward at planning, before code exists
|
|
284
|
+
Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exit 0 = pass; authored goal-backward at planning, before code exists). `executing` runs declared checks once at plan completion (early fix-loop signal, never a verdict); `verifying` re-executes, applying the **executed-evidence rule**: VERIFIED only with a recorded command + exit code + output excerpt — no evidence → UNCERTAIN or human-route. Legacy bare-string truths stay valid.
|
|
294
285
|
|
|
295
286
|
### Human Verification Gate (close precondition)
|
|
296
287
|
|
|
@@ -305,8 +296,8 @@ Plans MAY declare a per-truth `check:` shell command in `must_haves.truths` (exi
|
|
|
305
296
|
|
|
306
297
|
## Beads Integration (Optional)
|
|
307
298
|
|
|
308
|
-
With Beads
|
|
299
|
+
With Beads: `bd prime` (session context), `bd ready` (unblocked tasks), `bd complete` (update deps), `bd compact` (summarize old). Without it, `.forge/state/` + Session Memory suffice.
|
|
309
300
|
|
|
310
301
|
## Atomic Commits
|
|
311
302
|
|
|
312
|
-
One commit per task. Format: `{type}({scope}): {description}` — types `feat fix test refactor chore docs`. Never `git add
|
|
303
|
+
One commit per task. Format: `{type}({scope}): {description}` — types `feat fix test refactor chore docs`. Never `git add .`/`-A` — stage individually. Phase handoffs additionally emit one scoped `chore(forge): sync state …` commit (State Commit Protocol) so `.forge/` state never drifts out of git.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One-command worktree for any commit-producing task (issue #21, ADR-031).
|
|
3
|
+
#
|
|
4
|
+
# bash .forge/bin/new-worktree.sh <slug> [base-ref]
|
|
5
|
+
#
|
|
6
|
+
# Creates {worktree_root}/{slug} on branch forge/{slug} off origin/main (or [base-ref]).
|
|
7
|
+
# The primary checkout's HEAD never moves — that is the whole point of the weld: branch
|
|
8
|
+
# work lives in worktrees, each with its own independent HEAD, so concurrent sessions
|
|
9
|
+
# never clobber each other's commits.
|
|
10
|
+
#
|
|
11
|
+
# worktree_root follows the FORGE.md Worktree Convention: `orchestration.worktree_root`
|
|
12
|
+
# from .forge/project.yml, default `../<repo-basename>-worktrees/`. Relative paths resolve
|
|
13
|
+
# against the repo root; absolute / ~ honored verbatim.
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
SLUG=${1:?usage: new-worktree.sh <slug> [base-ref]}
|
|
18
|
+
BASE=${2:-origin/main}
|
|
19
|
+
|
|
20
|
+
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
21
|
+
REPO_BASE=$(basename "$REPO_ROOT")
|
|
22
|
+
|
|
23
|
+
# Resolve worktree_root from project.yml (light grep — no yaml dependency). The value
|
|
24
|
+
# lives under an `orchestration:` block as `worktree_root: <path>`.
|
|
25
|
+
WT_ROOT_CFG=$(grep -E '^[[:space:]]*worktree_root:' "$REPO_ROOT/.forge/project.yml" 2>/dev/null \
|
|
26
|
+
| head -1 | sed -E 's/^[[:space:]]*worktree_root:[[:space:]]*//; s/^["'\'']//; s/["'\'']$//; s/[[:space:]]*(#.*)?$//' || true)
|
|
27
|
+
|
|
28
|
+
if [ -z "$WT_ROOT_CFG" ]; then
|
|
29
|
+
WORKTREE_ROOT="$(dirname "$REPO_ROOT")/${REPO_BASE}-worktrees"
|
|
30
|
+
else
|
|
31
|
+
case "$WT_ROOT_CFG" in
|
|
32
|
+
/*) WORKTREE_ROOT="$WT_ROOT_CFG" ;; # absolute — verbatim
|
|
33
|
+
"~"*) WORKTREE_ROOT="${WT_ROOT_CFG/#\~/$HOME}" ;; # ~ — expand
|
|
34
|
+
*) WORKTREE_ROOT="$REPO_ROOT/$WT_ROOT_CFG" ;; # relative — against repo root
|
|
35
|
+
esac
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# Slashes in the slug become dashes in the directory name; the branch keeps the slash.
|
|
39
|
+
DIR_NAME=$(printf '%s' "$SLUG" | tr '/' '-')
|
|
40
|
+
WT_PATH="$WORKTREE_ROOT/$DIR_NAME"
|
|
41
|
+
BRANCH="forge/$SLUG"
|
|
42
|
+
|
|
43
|
+
if [ -e "$WT_PATH" ]; then
|
|
44
|
+
echo "✋ $WT_PATH already exists. Pick another slug or remove it first (git worktree remove)." >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
mkdir -p "$WORKTREE_ROOT"
|
|
49
|
+
# Best-effort refresh of origin/main so the worktree starts from current main; offline-safe.
|
|
50
|
+
git fetch origin main --quiet 2>/dev/null || true
|
|
51
|
+
git worktree add -b "$BRANCH" "$WT_PATH" "$BASE"
|
|
52
|
+
|
|
53
|
+
echo ""
|
|
54
|
+
echo "✅ Worktree ready"
|
|
55
|
+
echo " path: $WT_PATH"
|
|
56
|
+
echo " branch: $BRANCH (from $BASE)"
|
|
57
|
+
echo ""
|
|
58
|
+
echo " cd \"$WT_PATH\""
|
|
59
|
+
echo ""
|
|
60
|
+
echo "Reminders: run your install step after entering (worktrees don't share deps);"
|
|
61
|
+
echo "the primary checkout stayed on its default branch — that is intentional."
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Scratch-repo tests for .forge/bin/new-worktree.sh (issue #21, ADR-031).
|
|
3
|
+
#
|
|
4
|
+
# Builds a throwaway repo with a committed HEAD, runs the helper (base ref = HEAD so no
|
|
5
|
+
# network is needed), and asserts the worktree dir + branch it creates. Self-cleans. Run:
|
|
6
|
+
# ./.forge/bin/tests/new-worktree.test.sh
|
|
7
|
+
# Prints `ALL PASS` and exits 0 iff every case passes; non-zero on any failure.
|
|
8
|
+
|
|
9
|
+
set -uo pipefail
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
|
+
HELPER="$SCRIPT_DIR/../new-worktree.sh"
|
|
13
|
+
[ -x "$HELPER" ] || { printf 'FATAL: helper not executable: %s\n' "$HELPER" >&2; exit 1; }
|
|
14
|
+
|
|
15
|
+
ROOT="$(mktemp -d)"
|
|
16
|
+
trap 'rm -rf "$ROOT"' EXIT INT TERM
|
|
17
|
+
|
|
18
|
+
PASSED=0
|
|
19
|
+
FAILED=0
|
|
20
|
+
pass() { PASSED=$((PASSED + 1)); }
|
|
21
|
+
fail() { FAILED=$((FAILED + 1)); printf ' FAIL: %s\n' "$1" >&2; }
|
|
22
|
+
|
|
23
|
+
# A repo named so its default worktree_root is predictable: <base>-worktrees next to it.
|
|
24
|
+
new_repo() {
|
|
25
|
+
local d="$ROOT/$1"
|
|
26
|
+
mkdir -p "$d"
|
|
27
|
+
git -C "$d" init -q
|
|
28
|
+
git -C "$d" symbolic-ref HEAD refs/heads/main
|
|
29
|
+
git -C "$d" config user.email t@example.com
|
|
30
|
+
git -C "$d" config user.name tester
|
|
31
|
+
mkdir -p "$d/.forge"
|
|
32
|
+
: > "$d/seed"
|
|
33
|
+
git -C "$d" add -A && git -C "$d" commit -qm init
|
|
34
|
+
printf '%s\n' "$d"
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ── Case 1: default worktree_root, simple slug ───────────────────────────────────
|
|
38
|
+
R="$(new_repo proj)"
|
|
39
|
+
( cd "$R" && "$HELPER" myslug HEAD ) >/dev/null 2>&1 || true
|
|
40
|
+
WT="$ROOT/proj-worktrees/myslug"
|
|
41
|
+
if [ -d "$WT" ]; then pass; else fail "case1: worktree dir not created at $WT"; fi
|
|
42
|
+
br=$(git -C "$WT" branch --show-current 2>/dev/null || true)
|
|
43
|
+
if [ "$br" = "forge/myslug" ]; then pass; else fail "case1: branch is '$br', want forge/myslug"; fi
|
|
44
|
+
|
|
45
|
+
# ── Case 2: re-running the same slug errors (dir exists) ─────────────────────────
|
|
46
|
+
if ( cd "$R" && "$HELPER" myslug HEAD ) >/dev/null 2>&1; then
|
|
47
|
+
fail "case2: duplicate slug should have errored (exit non-zero)"
|
|
48
|
+
else
|
|
49
|
+
pass
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# ── Case 3: slug with a slash → dashed dir, slashed branch ───────────────────────
|
|
53
|
+
R2="$(new_repo proj2)"
|
|
54
|
+
( cd "$R2" && "$HELPER" feat/thing HEAD ) >/dev/null 2>&1 || true
|
|
55
|
+
WT2="$ROOT/proj2-worktrees/feat-thing"
|
|
56
|
+
if [ -d "$WT2" ]; then pass; else fail "case3: dashed dir not created at $WT2"; fi
|
|
57
|
+
br2=$(git -C "$WT2" branch --show-current 2>/dev/null || true)
|
|
58
|
+
if [ "$br2" = "forge/feat/thing" ]; then pass; else fail "case3: branch is '$br2', want forge/feat/thing"; fi
|
|
59
|
+
|
|
60
|
+
# ── Case 4: configured relative worktree_root honored ────────────────────────────
|
|
61
|
+
R3="$(new_repo proj3)"
|
|
62
|
+
printf 'orchestration:\n worktree_root: .wt\n' > "$R3/.forge/project.yml"
|
|
63
|
+
git -C "$R3" add -A && git -C "$R3" commit -qm cfg
|
|
64
|
+
( cd "$R3" && "$HELPER" wtslug HEAD ) >/dev/null 2>&1 || true
|
|
65
|
+
WT3="$R3/.wt/wtslug"
|
|
66
|
+
if [ -d "$WT3" ]; then pass; else fail "case4: configured worktree_root .wt not honored (want $WT3)"; fi
|
|
67
|
+
|
|
68
|
+
printf '\n%d passed, %d failed\n' "$PASSED" "$FAILED"
|
|
69
|
+
if [ "$FAILED" -eq 0 ]; then printf 'ALL PASS\n'; exit 0; fi
|
|
70
|
+
exit 1
|
|
@@ -69,6 +69,43 @@ fold_state() { # fold_state <milestone-id>
|
|
|
69
69
|
[ -n "$_rs" ] && printf '%s' "$_rs" || printf 'unknown'
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
# ── milestone name lookup (issue #34 — bare ids are useless with 40+ milestones in flight).
|
|
73
|
+
# Reads milestone.name (nested under the top-level `milestone:` key) from the worktree's own
|
|
74
|
+
# state file — a naive `grep -m1 '^name:'` returns empty since the key is indented under
|
|
75
|
+
# `milestone:`, not top-level. Same nested-key awk pattern as yget() in
|
|
76
|
+
# forge-board-render.sh (parent/child scan), specialized to milestone/name. Degrades to empty
|
|
77
|
+
# on any miss (absent file, absent key) — callers degrade to the bare id, never crash. ────────
|
|
78
|
+
milestone_name() { # milestone_name <milestone-id> <worktree-path>
|
|
79
|
+
_mid="${1:-}"; _wt="${2:-}"
|
|
80
|
+
[ -n "$_mid" ] && [ -n "$_wt" ] || { printf ''; return 0; }
|
|
81
|
+
_id="${_mid#m-}"
|
|
82
|
+
_f="$_wt/.forge/state/milestone-$_id.yml"
|
|
83
|
+
[ -f "$_f" ] || { printf ''; return 0; }
|
|
84
|
+
awk '
|
|
85
|
+
/^milestone:/ { inp=1; next }
|
|
86
|
+
inp && /^[A-Za-z]/ { inp=0 }
|
|
87
|
+
inp && /^[[:space:]]+name:/ {
|
|
88
|
+
sub(/^[[:space:]]*[^:]+:[[:space:]]*/, "")
|
|
89
|
+
sub(/[[:space:]]*#.*/, "")
|
|
90
|
+
gsub(/^["'"'"']|["'"'"']$/, "")
|
|
91
|
+
print; exit
|
|
92
|
+
}
|
|
93
|
+
' "$_f"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ── "m-<id> — <name>", truncated ~60 chars so one slice-list row stays one line; degrades to
|
|
97
|
+
# the bare id (never a blank "m- — " prefix) when the name can't be resolved. ────────────────
|
|
98
|
+
named_milestone() { # named_milestone <milestone-id> <worktree-path>
|
|
99
|
+
_mid="${1:-}"; _wt="${2:-}"
|
|
100
|
+
[ -n "$_mid" ] || { printf ''; return 0; }
|
|
101
|
+
_nm="$(milestone_name "$_mid" "$_wt")"
|
|
102
|
+
if [ -n "$_nm" ]; then
|
|
103
|
+
printf '%s' "$_mid — $_nm" | cut -c1-60
|
|
104
|
+
else
|
|
105
|
+
printf '%s' "$_mid"
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
72
109
|
# ── milestone id derivation for a matched branch (issue #25, bare-branch loosening). A
|
|
73
110
|
# forge/m-* branch keeps its original textual derivation unchanged (strip forge/, strip the
|
|
74
111
|
# 8-hex session suffix). A bare branch (m-<id>-slug | m<digits>-slug) derives the id from the
|
|
@@ -143,7 +180,8 @@ _slices="$(list_slices)"
|
|
|
143
180
|
if [ -n "$_slices" ]; then
|
|
144
181
|
printf '%s\n' "$_slices" | while IFS="$TAB" read -r _m _br _pt; do
|
|
145
182
|
if [ "$DRY_RUN" -eq 1 ]; then _rs='(dry-run)'; else _rs="$(fold_state "$_m")"; fi
|
|
146
|
-
|
|
183
|
+
_disp="$(named_milestone "$_m" "$_pt")"
|
|
184
|
+
printf ' %-40s %-26s release_state=%-9s %s\n' "$_disp" "$_br" "$_rs" "$_pt"
|
|
147
185
|
done
|
|
148
186
|
else
|
|
149
187
|
printf ' (none on this machine)\n'
|
|
@@ -165,7 +203,13 @@ elif [ -f "$BOARD" ]; then
|
|
|
165
203
|
_id="$(printf '%s' "$_ln" | sed -n 's/.*"forge_id":[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
166
204
|
_st="$(printf '%s' "$_ln" | sed -n 's/.*"status":[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
167
205
|
_rs="$(printf '%s' "$_ln" | sed -n 's/.*"release_state":[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
168
|
-
printf '
|
|
206
|
+
_nm="$(printf '%s' "$_ln" | sed -n 's/.*"name":[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
207
|
+
if [ -n "$_nm" ]; then
|
|
208
|
+
_disp="$(printf '%s' "${_id:-?} — $_nm" | cut -c1-60)"
|
|
209
|
+
else
|
|
210
|
+
_disp="${_id:-?}"
|
|
211
|
+
fi
|
|
212
|
+
printf ' %s status=%s release_state=%s\n' "$_disp" "${_st:-?}" "${_rs:-?}"
|
|
169
213
|
done
|
|
170
214
|
else
|
|
171
215
|
printf ' (all board units complete — nothing in flight)\n'
|