forgedock 1.0.22 → 1.1.1

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.
Files changed (46) hide show
  1. package/README.md +217 -218
  2. package/bin/cinema.mjs +320 -0
  3. package/bin/engine/phases.mjs +148 -0
  4. package/bin/engine/projector.mjs +32 -0
  5. package/bin/engine/reconcile.mjs +11 -0
  6. package/bin/engine/runlog.mjs +79 -0
  7. package/bin/engine/state.mjs +27 -0
  8. package/bin/engine-cli.mjs +50 -0
  9. package/bin/engine.mjs +138 -0
  10. package/bin/forgedock.mjs +464 -1491
  11. package/bin/journey.mjs +783 -0
  12. package/bin/runner.mjs +80 -13
  13. package/bin/settings-hook.mjs +112 -0
  14. package/bin/tui.mjs +6 -3
  15. package/commands/autopilot.md +4 -4
  16. package/commands/deploy-info.md +5 -3
  17. package/commands/design-bench.md +1 -0
  18. package/commands/design-render-critique-loop.md +1 -0
  19. package/commands/design.md +1 -0
  20. package/commands/forge-stats.md +1 -0
  21. package/commands/issue.md +1 -1
  22. package/commands/orchestrate.md +27 -9
  23. package/commands/quality-gate.md +80 -34
  24. package/commands/review-pr-agents.md +7 -7
  25. package/commands/review-pr.md +21 -4
  26. package/commands/work-on/build/implement.md +16 -4
  27. package/commands/work-on/build/validate.md +38 -1
  28. package/commands/work-on/build.md +27 -9
  29. package/commands/work-on/close.md +2 -2
  30. package/commands/work-on/review.md +24 -0
  31. package/commands/work-on-monolithic.md +1 -0
  32. package/commands/work-on.md +45 -3
  33. package/docs/BRAND.md +6 -2
  34. package/docs/CONFIG.md +39 -34
  35. package/docs/site/.vitepress/config.mjs +85 -0
  36. package/docs/site/getting-started.md +15 -8
  37. package/docs/site/index.md +1 -1
  38. package/docs/superpowers/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
  39. package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
  40. package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
  41. package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -0
  42. package/package.json +10 -4
  43. package/scripts/verify-env-vars.sh +18 -2
  44. package/scripts/verify-host-headers.sh +4 -4
  45. package/scripts/verify-route-registration.sh +2 -2
  46. package/scripts/worktree-lifecycle.sh +202 -0
@@ -0,0 +1,157 @@
1
+ # Cinematic Onboarding TUI — Design
2
+
3
+ **Date:** 2026-07-03
4
+ **Status:** Approved (design); pending implementation plan
5
+ **Supersedes/extends:** `2026-06-09-intelligent-onboarding-design.md` (Pillars A & B were built as modules but never wired into the CLI; this design closes that seam and defines the visual/experiential layer on top)
6
+
7
+ ## Problem
8
+
9
+ The onboarding audit (2026-07-03) found two classes of failure:
10
+
11
+ 1. **The intelligent onboarding is dead code.** `init-detect.mjs` (confidence-scored detection), `annotatedReviewScreen` (tui.mjs:835-1170), `init-enrich-api.mjs` (BYO-key AI enrichment), `registry.mjs`, and `bin/hooks/session-start.mjs` all exist with tests — and none are reachable. `forgedock.mjs`'s `init()` uses its own inline template; `install()` never registers the SessionStart hook; `enable`/`disable`/`status` don't exist.
12
+ 2. **The funnel lies.** README advertises `npx forgedock demo` (dead — `Unknown command`), `integrate` (dead), and a CLAUDE.md injection that never happens. `getting-started.md` sends users to `/forgedock-init`, which hard-errors without a forge.yaml. `CONFIG.md` documents `--manual`/`--verbose` flags that parse nowhere. `plugin.json` says 1.0.1 while `package.json` says 1.0.8.
13
+
14
+ Beyond correctness, the shipped experience is plain warnings-and-homework. The goal is an install that feels revolutionary.
15
+
16
+ ## Decisions (locked with user)
17
+
18
+ | Decision | Choice |
19
+ | --- | --- |
20
+ | TUI stack | Zero-dependency, hand-rolled — extend existing `tui.mjs`, no frameworks |
21
+ | Scope | Full onboarding arc: preflight → install → detect → review → celebrate, plus SessionStart hook wiring and `enable`/`disable`/`status`. `forgedock demo` is **out of scope** (own milestone) |
22
+ | Motion | Full cinematic, **always** — no first-run-only fast path; every command plays its full choreography. Automatic degradation (non-TTY/CI/`NO_COLOR`) and explicit escape hatches (`--fast`, `FORGE_NO_MOTION=1`). Note: an already-configured bare invocation shows the status screen (per approved storyboard) — that screen has its own full choreography; it doesn't replay the install acts |
23
+ | Visual identity | **Molten Forge**: fire-orange → amber gradients, "forging" language |
24
+ | Logo treatment | **Chrome & Ember**: block-art F mark keeps the official metallic chrome/champagne gradient; ember lives in the wordmark and UI. Hero mark on install; compact 4-row lockup on all other commands |
25
+ | Flow shape | **One continuous journey**: bare `npx forgedock` plays the whole arc; `install`/`init` are aliases that jump into the relevant acts |
26
+ | Architecture | **Cinema layer + journey orchestrator** (new `bin/cinema.mjs` + `bin/journey.mjs`; `forgedock.mjs` becomes a thin router) |
27
+
28
+ ## The Experience — five acts, one keypress
29
+
30
+ The only interaction in the happy path is a single Enter at Act IV. Total added latency from theater: motion overlays real work wherever work exists; pure-theater beats (shimmer, quench flash) total ≈1.2s.
31
+
32
+ ### Act I — Ignition (~2.5s)
33
+
34
+ Chrome block-art mark materializes with a one-pass heat-shimmer sweep (~800ms); ember gradient wordmark `F O R G E D O C K` + version; rule line "lighting the forge". Preflight rows appear one-by-one, each live (`○` → braille spinner → `✔`):
35
+
36
+ - Node ≥18 (version shown)
37
+ - git present + repo detected
38
+ - Claude Code (`~/.claude` exists)
39
+ - GitHub CLI installed + authenticated (account shown)
40
+
41
+ A failed check renders an ember-bordered **fix card** with the exact copy-paste remedy (e.g. `winget install GitHub.cli`, `gh auth login`) and the journey **continues** with whatever still works. Preflight failures are advisory, not fatal — missing `gh` degrades detection confidence, it does not stop the forge.
42
+
43
+ ### Act II — Forging (real work)
44
+
45
+ - Progress bar with molten leading edge (`█▓▒░`) and per-command name ticker while symlinking the 24 commands into `~/.claude/commands` (existing logic, restyled).
46
+ - **Seam-closing (new):** merge a SessionStart hook entry into `~/.claude/settings.json` (idempotent read-modify-write; preserves existing hooks; removed on `uninstall`; malformed JSON → skip with fix card, never clobber). Write the registry entry marking the directory forge-managed.
47
+ - Settle lines: `✔ 24 slash commands linked`, `✔ SessionStart hook registered`, `✔ directory forge-managed`.
48
+
49
+ ### Act III — Reading your repository
50
+
51
+ Each detection field resolves as a live row: value + confidence badge + source, e.g.
52
+ `✔ owner/repo RapierCraftStudios/ForgeDock [high] git remote`.
53
+ Driven by the existing `detectConfig()` (init-detect.mjs). Fields: owner/repo, default branch, staging branch, project name, description, worktree base, project board (via `gh` when available). If `ANTHROPIC_API_KEY` is set, `init-enrich-api.mjs` fills review/verification sections (`✦ enriching with AI…`); if not, one quiet skip line. Spinners here cover *actual* latency (git/gh/API calls) — nothing is artificially slowed.
54
+
55
+ ### Act IV — The Review (the single interaction)
56
+
57
+ The existing `annotatedReviewScreen` (tui.mjs), wired in at last and restyled to ember: numbered field table with `[high]/[med]/[low]` badges, confidence legend, low-confidence warning. `Press Enter to forge, or a number to edit a field`. Low-confidence fields are written with `# TODO(forgedock:<field>)` comments instead of blocking. Existing forge.yaml → overwrite banner + backup to `forge.yaml.bak` before write (existing behavior preserved).
58
+
59
+ ### Act V — Forged (celebration)
60
+
61
+ Compact mark flashes white-hot once (quench, ~400ms), then settles:
62
+
63
+ - `Forged.` + **real elapsed time** (`install → config in 34s`) — proof, not hype
64
+ - Receipt lines: forge.yaml written (with TODO count), commands + hook active
65
+ - Boxed "what's next" card: `1. open claude in this repo` / `2. run /work-on next`
66
+ - Footer: docs link + star nudge
67
+
68
+ Next Claude Code session, the now-installed SessionStart hook greets the user with the command table — the experience continues past the terminal.
69
+
70
+ ### Re-runs and aliases
71
+
72
+ - Bare `npx forgedock` in an already-configured repo → short status screen (compact lockup, current config summary, hook state) + offer to review config. Not the full movie.
73
+ - `npx forgedock install` → Acts I–II (+ III–V only if unconfigured, matching today's auto-init).
74
+ - `npx forgedock init` → Acts III–V.
75
+ - `enable` / `disable` / `status` → registry operations, compact lockup, instant.
76
+ - `update` / `uninstall` → existing logic, compact restyle; `uninstall` also removes the hook entry and registry entries.
77
+
78
+ ## Architecture
79
+
80
+ ### `bin/cinema.mjs` (new — animation engine)
81
+
82
+ Kept separate from tui.mjs (already 1,171 lines). Exports:
83
+
84
+ - Truecolor per-cell gradient rendering with 256-color fallback (`COLORTERM`/`TERM` detection); gradient text lines and gradient block art
85
+ - `renderMark(size)` — the chrome F mark, hero (8-row) and compact (4-row); block-art geometry approved v1 from mockups, may be pixel-tuned during implementation
86
+ - `shimmer(lines, opts)` — one-pass highlight sweep
87
+ - `reveal(rows, opts)` — staged live rows (`○` → spinner → `✔`/`✖`/fix card)
88
+ - `progressBar` molten variant with ticker
89
+ - `motionEnabled()` — the single motion gate: TTY ∧ ¬`NO_COLOR` ∧ ¬`FORGE_NO_MOTION` ∧ ¬`--fast` ∧ ¬CI
90
+
91
+ **No alternate screen buffer.** Animations redraw in place (cursor-up) only while active, then settle to static lines — scrollback keeps a complete plain receipt of the run.
92
+
93
+ ### `bin/journey.mjs` (new — choreographer)
94
+
95
+ Five act functions — `preflight()`, `forge()`, `read()`, `review()`, `celebrate()` — over a shared context object; each act independently callable (for aliases) and unit-testable. Orchestrates the existing modules: `init-detect.mjs`, `init-enrich-api.mjs`, `annotatedReviewScreen`, `registry.mjs`. The forge.yaml writer moves here, driven from the detection `ConfigDraft` (single source of truth; deletes the duplicate inline template in forgedock.mjs) and emits `# TODO(forgedock:<field>)` for low-confidence values.
96
+
97
+ ### `bin/forgedock.mjs` (shrinks to router)
98
+
99
+ Command dispatch → journey acts as above. Fixes: `HOME` check gains `USERPROFILE`/`os.homedir()` fallback (currently hard-fails on bare Windows shells); flag parsing added (`--fast`, `--manual`, `--verbose`). `--manual` = plain-prompt path (no detection auto-accept), making CONFIG.md's documented flags real.
100
+
101
+ ### Hook wiring (the seam)
102
+
103
+ `forge()` merges into `~/.claude/settings.json`:
104
+
105
+ ```json
106
+ { "hooks": { "SessionStart": [ { "hooks": [ { "type": "command",
107
+ "command": "node \"$FORGE_HOME/bin/hooks/session-start.mjs\"" } ] } ] } }
108
+ ```
109
+
110
+ The command path is resolved to the absolute installed package location at install time (the same resolution `install()` already performs for symlink sources) — written as an absolute path, not a literal env reference, so the hook works regardless of shell profile.
111
+
112
+ Idempotent (detects its own entry, never duplicates), preserves unrelated hooks, guards malformed JSON (skip + fix card, never overwrite), and is removed by `uninstall`. This makes the existing `session-start.mjs` (managed-active greeting / missing-config nudge / one-time unmanaged nudge) actually fire.
113
+
114
+ ## Degradation ladder
115
+
116
+ 1. Full cinematic — interactive TTY, truecolor
117
+ 2. 256-color — same choreography, quantized gradients
118
+ 3. `NO_COLOR` — monochrome, motion intact
119
+ 4. `--fast` / `FORGE_NO_MOTION=1` — instant renders, no animation frames
120
+ 5. Non-TTY/CI — deterministic plain sequential log, same information, zero ANSI animation; review screen auto-accepts nothing — in non-TTY, detection results are written with TODO flags and the run says so (preserves current "never block CI" behavior while still protecting existing configs via the existing non-interactive abort on overwrite)
121
+ 6. Narrow terminals (<60 cols) — compact lockup replaces hero mark; tables wrap to stacked rows
122
+
123
+ ## Error handling
124
+
125
+ - **Never a dead stop, never a lie.** Preflight fix cards + continue; degraded steps stated inline at the moment they happen and echoed in the Act V receipt (e.g. `2 fields flagged # TODO`). No silently skipped sections.
126
+ - **Ctrl-C per act:** restore cursor/style, print partial-state summary (`commands installed, config not written — run npx forgedock init to finish`), exit cleanly. No half-drawn screens, no orphaned temp files.
127
+ - **Filesystem safety unchanged or better:** forge.yaml backup before overwrite; settings.json read-modify-write with parse guard; registry writes stay atomic (existing).
128
+
129
+ ## Testing
130
+
131
+ Extends the existing `node --test` suite in `bin/tests/`:
132
+
133
+ - **Acts:** unit tests with injected exec/IO stubs — preflight verdict matrix, hook-merge idempotency (twice → one entry), unrelated-hook preservation, TODO-flag emission, ConfigDraft→YAML writer output.
134
+ - **Degradation:** snapshot tests of the non-TTY plain log (`FORGE_NO_MOTION=1`, piped stdout) — deterministic byte-for-byte.
135
+ - **Cinema:** frame-function tests (gradient math, color-mode fallback selection), not timing tests — motion verified by eye; everything beneath it deterministic.
136
+ - **Windows first-class:** path handling, `USERPROFILE` fallback, symlink-vs-copy behavior — primary dev platform is Windows.
137
+
138
+ ## Docs truth-pass (ships in this milestone)
139
+
140
+ - **README:** remove `demo` (deferred) and `integrate`; replace CLAUDE.md-injection claim with the (now true) hook story; collapse the two-step install to one `npx forgedock`; requirements list gains Node ≥18.
141
+ - **getting-started.md:** fix the `/forgedock-init`-without-forge.yaml dead end; document the journey.
142
+ - **CONFIG.md:** `--manual`/`--verbose` now real; describe actual flags only.
143
+ - **Versioning:** reconcile `plugin.json` (1.0.1) with `package.json` (1.0.8); ship as **1.1.0**.
144
+ - **demo.tape:** re-record after implementation so the README gif shows the real flow.
145
+
146
+ ## Out of scope
147
+
148
+ - `npx forgedock demo` (sandbox pipeline showcase) — its own future milestone
149
+ - Self-healing `validate` (spec 2026-06-09 Pillar C, AI-fix loop) — future
150
+ - Skill-backend enrichment inside Claude Code — future; API backend + deterministic baseline only for now
151
+
152
+ ## Success criteria
153
+
154
+ - Fresh `npx forgedock` on a clean machine: zero commands to remember, one Enter, ends with a valid forge.yaml, installed commands, live SessionStart hook, and next-step card — in under ~60s on a normal repo.
155
+ - Re-run is a status screen, not a movie sit-through beyond its own short choreography.
156
+ - CI/non-TTY runs produce a stable plain log and never hang on a prompt.
157
+ - No user-facing doc advertises a command or behavior that doesn't exist.
@@ -0,0 +1,194 @@
1
+ # Durable Execution Engine — Design Spec
2
+
3
+ **Date:** 2026-07-03
4
+ **Status:** Implemented (headless engine core) on branch feat/durable-execution-engine — see docs/superpowers/plans/2026-07-03-durable-execution-engine.md
5
+ **Tracking:** #1256 (keystone), epic #1320 (five foundations of autonomy)
6
+ **Scope of this spec:** the durable engine **core** only — phase state machine, run-log, resume, and orchestrator integration for **headless / orchestrated** runs. Verification, learning, economics, and provenance (#1315–#1319) are separate specs that build on this substrate.
7
+
8
+ ---
9
+
10
+ ## 1. Motivation
11
+
12
+ The pipeline today is a **prose-driven state machine**. `work-on.md` describes a linear walk (Phase 0→7) whose only continuation mechanism is imperative text ("do NOT stop"), and its subcommands reference a numbered state router that `work-on.md` never implements (#1308). Phase transitions therefore depend on an LLM *choosing* not to emit `end_turn` at a Skill-return boundary. This produces the recurring failure classes:
13
+
14
+ - **review→close stalls** (~half of runs merge the PR but leave the issue open).
15
+ - **quality-gate→3H stalls.**
16
+ - **"Prompt is too long" deaths** mid-run (#1305), patched with a self-counted "≥20 Skill calls" heuristic.
17
+ - **Lossy state** — GitHub prose comments are the de-facto database; resume re-parses them.
18
+ - **Blind observability** — progress is inferred from JSONL output files, which are 0-byte on Windows (#1307).
19
+
20
+ The insight that makes a fix cheap: **`bin/runner.mjs` already owns the Anthropic tool-use loop** (issue #1151). ForgeDock is not locked inside Claude Code's opaque agent loop — it drives its own loop. So the work is not "adopt a runtime from scratch," it is "make the loop we already own durable."
21
+
22
+ ## 2. Goals / Non-goals
23
+
24
+ **Goals**
25
+ - Phase transitions are decided by **deterministic code**, never by an LLM's choice to continue.
26
+ - A run survives crash / kill / compaction / rate-limit and **resumes from the last committed phase** in a fresh context.
27
+ - State is **machine-readable and shared**, so any machine or the orchestrator can resume and observe a run.
28
+ - Eliminate the stall and "prompt too long" classes structurally, not with prose.
29
+
30
+ **Non-goals (explicitly out; each is its own spec / increment)**
31
+ - The interactive Claude Code `/work-on` path (a later increment adopts the same core via hooks).
32
+ - Per-turn / mid-phase durability (this spec is **per-phase** granularity).
33
+ - The verification gate, learning memory, economic governance, provenance (#1315–#1319).
34
+ - Full per-command parity for the standalone runtime (#1151 follow-up).
35
+
36
+ ## 3. Locked decisions
37
+
38
+ These were settled during design and are not open:
39
+
40
+ 1. **Durability granularity: per-phase.** The engine checkpoints each pipeline *phase*. A mid-phase crash re-runs that one phase (idempotently). Per-turn durability is rejected as over-complex (partial file edits / half-made commits).
41
+ 2. **State authority: hybrid (local hot + GitHub mirror), GitHub wins on divergence.** A local append-only run-log is the crash-safe hot path; a structured `FORGE:STATE` block on the issue is the durable shared mirror. On conflict the GitHub mirror is authoritative and invalidates the local cache.
42
+ 3. **First surface: headless / orchestrated.** The engine drives runs through `runner.mjs` — where autonomy matters most and there is no human to un-stick a stall. Interactive Claude Code adopts the same core later.
43
+ 4. **`FORGE:STATE` is a compact index, not self-describing.** It carries the resume pointer (phase, `committed[]`, branch, pr, version, lease) — **not** the rich per-phase outputs. Rich outputs live in the existing `FORGE:INVESTIGATOR` / `ARCHITECT` / etc. structured comments (which the engine writes anyway). A fresh-clone resume reads the index for *where*, then reads those comments for *content*. This avoids duplicating data and preserves human-readable artifacts.
44
+
45
+ ## 4. Architecture
46
+
47
+ Five components, each with one responsibility and a clean seam so the interactive path can later slot in as a second execution adapter.
48
+
49
+ | Component | File | Responsibility |
50
+ |---|---|---|
51
+ | **Phase state machine** | `bin/engine/phases.mjs` | Declarative table encoding the canonical phase sequence (today's prose in `work-on.md`). Each entry: `id`, `entryCondition(state)`, `command` (spec to run), `terminal?`, `reconcile(state)`. |
52
+ | **Engine loop** | `bin/engine.mjs` | The durable driver. Loads state, picks the next phase deterministically, invokes the phase, commits, handles failure. |
53
+ | **LLM-execution adapter** | `bin/runner.mjs` *(existing)* | Runs one phase = one bounded `runCommand()` with a **fresh context window**. Loop unchanged; new caller. |
54
+ | **Run-log store** | `bin/engine/runlog.mjs` | Append-only `.forge/runs/{issue}.jsonl`. Crash-safe local hot path. |
55
+ | **GitHub adapter (projector)** | `bin/engine/projector.mjs` | Reads/writes the `FORGE:STATE` block; manages the lease. Durable shared mirror + data source for `forgedock watch` (#1312). |
56
+
57
+ The engine core (`phases`, `engine`, `runlog`, `projector`) is runtime-agnostic; `runner.mjs` is the LLM adapter; the projector is the GitHub adapter.
58
+
59
+ ### Engine loop (shape)
60
+
61
+ ```
62
+ state = load(issue) // local log → else FORGE:STATE → else fresh
63
+ while (next = pickPhase(state)) { // ENGINE decides; never the LLM
64
+ if (next.terminal) break
65
+ reconcile(state, next) // make re-entry safe (idempotency)
66
+ result = runner.runCommand(next.command, ctx(state)) // fresh context per phase
67
+ switch (result.status) {
68
+ case 'committed': commit(state, next, result.outputs); break // append log + mirror
69
+ case 'failed': if (attempts(next) < maxAttempts) retry; else block(state, next); break
70
+ case 'blocked': terminate(state, 'needs-human', result.detail); return
71
+ }
72
+ } // no event on crash → next run drops + re-runs
73
+ ```
74
+
75
+ ## 5. State model
76
+
77
+ ### 5.1 Run-log (`.forge/runs/{issue}.jsonl`)
78
+
79
+ Append-only, one JSON event per line. `seq` is a monotonic per-issue version counter.
80
+
81
+ ```jsonc
82
+ {"seq":1,"event":"RUN_START","issue":42,"run":"r_a1","lane":"staging","baseSha":"9ea38…"}
83
+ {"seq":2,"event":"PHASE_START","phase":"investigate"}
84
+ {"seq":3,"event":"PHASE_COMMIT","phase":"investigate","outputs":{"verdict":"CONFIRMED","decompose":false}}
85
+ {"seq":4,"event":"PHASE_START","phase":"build"}
86
+ {"seq":5,"event":"PHASE_FAILED","phase":"build","attempt":1,"reason":"tsc error"}
87
+ {"seq":6,"event":"PHASE_START","phase":"build"}
88
+ {"seq":7,"event":"PHASE_COMMIT","phase":"build","outputs":{"branch":"fix/pay-42","headSha":"…","skipped":false}}
89
+ {"seq":8,"event":"RUN_TERMINAL","reason":"merged"}
90
+ ```
91
+
92
+ Events: `RUN_START`, `PHASE_START`, `PHASE_COMMIT`, `PHASE_FAILED`, `RUN_TERMINAL`.
93
+
94
+ **The commit rule (the entire durability story):** a phase is *committed* iff a `PHASE_COMMIT` follows its latest `PHASE_START`. A `PHASE_START` with no following `PHASE_COMMIT` = a crashed / in-flight phase → **dropped and re-run**.
95
+
96
+ **Corruption tolerance:** a crash mid-write leaves a truncated final line; the reader ignores any final line that does not parse as complete JSON.
97
+
98
+ ### 5.2 GitHub mirror (`FORGE:STATE`)
99
+
100
+ One machine-readable HTML-comment block the projector **overwrites in place** (not appends) after each `PHASE_COMMIT`:
101
+
102
+ ```html
103
+ <!-- FORGE:STATE
104
+ {"v":7,"run":"r_a1","phase":"review","committed":["investigate","context","architect","build"],
105
+ "branch":"fix/pay-42","pr":null,"lane":"staging","terminal":false,
106
+ "lease":{"by":"agent_7","until":"2026-07-03T04:10Z"}}
107
+ -->
108
+ ```
109
+
110
+ `v` mirrors the run-log `seq` at last commit — the shared version used for divergence detection. It carries the **compact index only** (see locked decision 4).
111
+
112
+ ### 5.3 Resume + divergence (`GitHub wins`)
113
+
114
+ ```
115
+ onStart(issue):
116
+ local = readRunLog(issue) // absent on fresh clone / other machine
117
+ remote = projector.read(issue) // FORGE:STATE, may be absent
118
+ state = reconcile(local, remote)
119
+
120
+ reconcile(local, remote):
121
+ if !remote: return local ?? fresh
122
+ if !local: return hydrateFromRemote(remote) // fresh clone / other machine
123
+ if remote.v > local.lastSeq: return hydrateFromRemote(remote) // advanced elsewhere → GitHub wins, discard local
124
+ if remote.v < local.lastSeq: projector.write(fromLocal(local)); return local // we crashed pre-mirror → re-mirror, keep local
125
+ return local // in sync
126
+ ```
127
+
128
+ `hydrateFromRemote` reconstructs working state from the compact index plus, where a phase's rich outputs are needed downstream, the existing `FORGE:*` structured comments on the issue.
129
+
130
+ ## 6. Deterministic transitions & idempotency
131
+
132
+ **`pickPhase(state)`** = the first phase in the `phases.mjs` table whose `entryCondition(state)` holds and whose `id` is not in `state.committed`. If none qualify or a terminal condition holds → terminal. No LLM involvement — this is the fix for the stall class.
133
+
134
+ **`phase.reconcile(state)`** runs *before* the LLM on every entry, because a dropped phase re-runs and must be safe to re-enter:
135
+
136
+ - `build.reconcile`: branch already exists and is ahead of base → resume rather than re-create.
137
+ - `review.reconcile`: a PR already exists for the branch → adopt it, do not open a second.
138
+ - `close.reconcile`: PR already merged → proceed straight to close steps.
139
+
140
+ These leverage the existing `FORGE:*` markers and `gh` queries; the engine makes idempotency a required step rather than an ad-hoc convention.
141
+
142
+ ## 7. Failure policy
143
+
144
+ Each phase returns a typed result; the engine acts on the **type** (replacing prose "try again / do not stop"):
145
+
146
+ | Result | Meaning | Engine action |
147
+ |---|---|---|
148
+ | `committed` | phase produced outputs | write `PHASE_COMMIT`, advance |
149
+ | `failed` | transient (rate-limit, network, `gh` timeout, context-budget hit mid-phase) | `PHASE_FAILED`; retry same phase with backoff up to `maxAttempts` (default 3); on exhaustion → treat as `blocked` |
150
+ | `blocked` | deterministic human-needed (gate failed after loop, contract wrong, ancestry violation, push rejected) | `RUN_TERMINAL{needs-human}`, set label via projector, **stop visibly** |
151
+ | *(no event)* | crash / kill / compaction | next run drops the uncommitted `PHASE_START` and re-runs the phase |
152
+
153
+ **Visible skips (closes #1306):** a phase that no-ops (e.g. missing `verification.commands`) must emit `outputs:{skipped:true, which:"typecheck"}` into its `PHASE_COMMIT` and `FORGE:STATE`. A skip is surfaced (PR body / trajectory / review verdict), never a silent pass.
154
+
155
+ **"Prompt too long" (dissolves #1305):** fresh context per phase removes the dominant cause. If a *single* phase still exhausts context mid-run, it is a `failed` attempt whose `reconcile` resumes from committed artifacts on retry. Note honestly: an oversized single-phase input (e.g. a 10k-line review diff) remains the review phase's own concern — the engine bounds it (`runner` tool-result caps + iteration budget) but does not shrink it; that is the verification/review spec's responsibility.
156
+
157
+ ## 8. Concurrency & orchestrator integration
158
+
159
+ **Lease.** `FORGE:STATE.lease = {by: agentId, until: ts}` is written on `RUN_START`, renewed on each `PHASE_COMMIT`, released on `RUN_TERMINAL`. On start, if a live lease held by another agent exists, the caller defers. This is best-effort mutual exclusion (checked on start; not an atomic CAS) — GitHub issue-edit has no compare-and-swap, so a concurrent start can still race between the lease read and the next write. It replaces the orchestrator's fuzzy "already in progress" label heuristic with a much tighter, race-*reducing* primitive.
160
+
161
+ **`/orchestrate` becomes a thin driver:**
162
+ - Decomposes into the wave/DAG (unchanged), then calls `engine.run(issue)` per issue.
163
+ - Watches `FORGE:STATE` per issue for progress — which also feeds `forgedock watch` (#1312) and removes any dependence on JSONL output files (killing the Windows-blind audit problem #1307 for orchestrated runs).
164
+ - **Exact stall detection:** a lease whose `until` expired on a non-terminal state = a dead agent → re-spawn `engine.run(issue)`, which resumes from the last `PHASE_COMMIT`. This replaces "resume up to 3× with nagging."
165
+ - File-overlap serialization remains the orchestrator's responsibility (unchanged), aided by the engine's `baseSha`/branch tracking.
166
+
167
+ ## 9. Testing strategy
168
+
169
+ Correctness of a durable engine is won in tests. `runner.mjs` already lazy-imports the SDK and separates pure helpers, so the engine is tested with a **fake `runCommand`** that returns scripted phase outputs — no live LLM.
170
+
171
+ - **State-machine units** (`bin/tests/phases.test.mjs`, `engine.test.mjs`): table-driven `pickPhase` over crafted states; `reconcile` across all divergence cases (local-only, remote-only, remote-ahead, local-ahead, in-sync); the commit-detection rule.
172
+ - **Crash-injection harness** (the durability proof): run the engine against the fake runner, kill after each phase boundary *and* mid-phase, assert resume lands on the correct phase with no double-PR / double-commit.
173
+ - **Run-log** (`runlog.test.mjs`): append/read/replay; truncated-final-line tolerance.
174
+ - **Projector** (`projector.test.mjs`): `FORGE:STATE` serialize/parse round-trip; overwrite-in-place; lease acquire / renew / expire against a mocked `gh`.
175
+ - **Per-phase `reconcile`** idempotency tests (build resumes, review adopts existing PR, close handles already-merged).
176
+
177
+ ## 10. Open implementation-time decision (defer to the plan)
178
+
179
+ Whether the engine runs concurrent issues as **separate processes** (matches today's background-agent model; strong isolation) or an **in-process worker pool** (one place to observe; shared rate-limit backpressure). Recommendation: **in-process pool + a git worktree per issue** for filesystem isolation (worktrees are already the pattern). Decide during planning.
180
+
181
+ ## 11. Relationship to existing code & the epic
182
+
183
+ - **Reuses:** `runner.mjs` (`runCommand`, tool handlers, result caps, lazy SDK) as the unmodified LLM adapter; the `work-on` phase sequence as the seed for `phases.mjs`; the existing `FORGE:*` structured comments as rich-output storage; git worktrees for isolation.
184
+ - **Retires / fixes:** the phantom numbered-state router (#1308), the "≥20 Skill calls" heuristic and "prompt too long" deaths (#1305), silent verification skips (#1306), and — for orchestrated runs — the JSONL-based audit blindness (#1307).
185
+ - **Enables (as substrate, not in scope):** the run-log's structured `outputs` and `FORGE:STATE` are deliberately the foundation for verification (#1315), learning memory (#1316), economics (#1317), and provenance (#1318).
186
+
187
+ ## 12. Definition of done
188
+
189
+ - A headless `engine.run(issue)` drives an issue through the full phase sequence with the engine deciding every transition.
190
+ - Killing the process at any phase boundary or mid-phase resumes correctly on the next run, with no duplicated PRs/commits (crash-injection suite green).
191
+ - A fresh clone / second machine resumes a run from `FORGE:STATE` alone.
192
+ - `/orchestrate` drives issues via `engine.run`, coordinates via the lease, and detects/recovers stalls via lease expiry.
193
+ - Skipped verification is visible on the PR; a `blocked` result stops the run with `needs-human`, never silently.
194
+ - Unit + crash-injection + projector/lease test suites pass with a mocked runner and mocked `gh`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forgedock",
3
- "version": "1.0.22",
3
+ "version": "1.1.1",
4
4
  "description": "Autonomous dev pipeline for Claude Code. GitHub as institutional memory — every issue, PR, and annotation survives context resets and makes the next agent smarter.",
5
5
  "bin": {
6
6
  "forgedock": "bin/forgedock.mjs"
@@ -27,7 +27,7 @@
27
27
  "type": "git",
28
28
  "url": "git+https://github.com/RapierCraftStudios/ForgeDock.git"
29
29
  },
30
- "homepage": "https://forgedock.dev",
30
+ "homepage": "https://github.com/RapierCraftStudios/ForgeDock#readme",
31
31
  "bugs": {
32
32
  "url": "https://github.com/RapierCraftStudios/ForgeDock/issues"
33
33
  },
@@ -43,13 +43,19 @@
43
43
  "README.md"
44
44
  ],
45
45
  "scripts": {
46
- "test": "node --test \"bin/tests/**/*.test.mjs\""
46
+ "test": "node --test \"bin/tests/**/*.test.mjs\"",
47
+ "docs:dev": "vitepress dev docs/site",
48
+ "docs:build": "vitepress build docs/site",
49
+ "docs:preview": "vitepress preview docs/site"
47
50
  },
48
51
  "engines": {
49
52
  "node": ">=18"
50
53
  },
51
54
  "optionalDependencies": {
52
- "@anthropic-ai/sdk": ">=0.30.0"
55
+ "@anthropic-ai/sdk": ">=0.30.0 <1"
56
+ },
57
+ "devDependencies": {
58
+ "vitepress": "^1.6.3"
53
59
  },
54
60
  "peerDependencies": {
55
61
  "@anthropic-ai/claude-code": ">=2.0.0"
@@ -179,8 +179,24 @@ for var in $VAR_NAMES; do
179
179
  fi
180
180
 
181
181
  if [ -z "$FOUND_IN" ]; then
182
- echo "BLOCKING: $var — not found in .env.example, docker-compose.yml, or env_validation.py"
183
- BLOCKING=$((BLOCKING + 1))
182
+ # Determine whether any of the optional surfaces exist in this repo.
183
+ # If NONE of the known surfaces exist (not even .env.example), the repo
184
+ # uses a layout we can't validate — emit SKIP instead of a false BLOCKING.
185
+ # This is the verify-sops-chain.sh pattern: unknown layout → graceful skip,
186
+ # not a spurious failure. <!-- Added: forge#1349 -->
187
+ HAS_ANY_SURFACE=0
188
+ [ -f "$REPO_ROOT/.env.example" ] && HAS_ANY_SURFACE=1
189
+ [ -f "$REPO_ROOT/docker-compose.yml" ] && HAS_ANY_SURFACE=1
190
+ [ -f "$REPO_ROOT/docker-compose.prod.yml" ] && HAS_ANY_SURFACE=1
191
+ [ -f "$REPO_ROOT/services/api/app/core/env_validation.py" ] && HAS_ANY_SURFACE=1
192
+ [ -f "$REPO_ROOT/scripts/decrypt-secrets.sh" ] && HAS_ANY_SURFACE=1
193
+
194
+ if [ "$HAS_ANY_SURFACE" -eq 0 ]; then
195
+ echo "SKIP: $var — no known env-wiring surfaces found in repo (no .env.example, docker-compose, env_validation.py, or SOPS mapping). Configure verification.env_check_surfaces in forge.yaml or add .env.example to enable this check."
196
+ else
197
+ echo "BLOCKING: $var — not found in any detected env-wiring surface (.env.example, docker-compose.yml, env_validation.py, or decrypt-secrets.sh)"
198
+ BLOCKING=$((BLOCKING + 1))
199
+ fi
184
200
  else
185
201
  echo "OK: $var — found in:$FOUND_IN"
186
202
  fi
@@ -67,9 +67,9 @@ while read -r f; do
67
67
  elif echo "$CONTENT" | grep -qE "\-\-header\s+[\"']Host:"; then
68
68
  echo "OK: $f:$LINENO — Host header explicitly set (--header)"
69
69
  else
70
- echo "BLOCKING: $f:$LINENO — curl/wget to internal service without explicit Host header. TrustedHostMiddleware will reject raw IP as Host value."
70
+ echo "BLOCKING: $f:$LINENO — curl/wget to internal service without explicit Host header. Host-validation middleware will reject a raw IP as the Host value."
71
71
  echo " Line: $CONTENT"
72
- echo " Fix: Add -H 'Host: localhost' (or appropriate allowed host from services/api/app/main.py allowed_hosts)"
72
+ echo " Fix: Add -H 'Host: <allowed-hostname>' — check your service's allowed_hosts configuration for the correct value"
73
73
  BLOCKING=$((BLOCKING + 1))
74
74
  fi
75
75
  done < <(echo "$CURL_LINES")
@@ -96,11 +96,11 @@ if [ -n "$FRONTEND_FILES" ]; then
96
96
  fi
97
97
 
98
98
  if [ -n "$DIRECT_CALLS" ]; then
99
- echo "BLOCKING: $f — direct /api/v1/ call bypasses Next.js proxy (auth will fail)"
99
+ echo "BLOCKING: $f — direct /api/v1/ call bypasses frontend proxy (auth middleware will be skipped)"
100
100
  while IFS= read -r match; do
101
101
  echo " $match"
102
102
  done < <(echo "$DIRECT_CALLS")
103
- echo " Fix: Use /api/* proxy routes instead of /api/v1/* direct FastAPI calls"
103
+ echo " Fix: Use the frontend proxy route (e.g. /api/*) instead of calling the backend API directly"
104
104
  BLOCKING=$((BLOCKING + 1))
105
105
  else
106
106
  echo "OK: $f — no direct backend calls"
@@ -5,7 +5,7 @@
5
5
  # changed_files_file: path to a file listing changed files (one per line)
6
6
  # repo_root: path to the repository root
7
7
  #
8
- # Layout path overrides (env vars — override defaults for non-AlterLab layouts):
8
+ # Layout path overrides (env vars — override defaults for project-specific layouts):
9
9
  # FORGE_PAGES_ROOT Root of Next.js App Router pages dir (default: web/src/app)
10
10
  # FORGE_API_ROUTERS_DIR Dir containing FastAPI router .py files (default: services/api/app/routers)
11
11
  # FORGE_API_MAIN Path to API app entrypoint, relative to repo root (default: services/api/app/main.py)
@@ -35,7 +35,7 @@ else
35
35
  FILES=$(cat "$CHANGED_FILES")
36
36
  fi
37
37
 
38
- # Layout path configuration — read from env vars, fall back to AlterLab defaults
38
+ # Layout path configuration — read from env vars, fall back to project defaults
39
39
  FORGE_PAGES_ROOT="${FORGE_PAGES_ROOT:-web/src/app}"
40
40
  FORGE_API_ROUTERS_DIR="${FORGE_API_ROUTERS_DIR:-services/api/app/routers}"
41
41
  FORGE_API_MAIN="${FORGE_API_MAIN:-services/api/app/main.py}"