pan-wizard 3.10.0 → 3.12.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.
@@ -0,0 +1,169 @@
1
+ ---
2
+ name: army
3
+ group: Army
4
+ description: Bot-army campaign — Mission Control (Opus conductor) delegates a whole-project goal to squads (architecture / build / quality / release), each squad working branch-per-agent worktrees under a hard safety harness, gated by CI + a human merge, looping plan→delegate→execute→review→integrate→learn until the goal ships or a stop condition fires.
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Grep
11
+ - Glob
12
+ - Agent
13
+ - Task
14
+ ---
15
+
16
+ # /pan:army — Bot-Army Campaign (mission control → squads → ship)
17
+
18
+ Run a whole-project delivery as a coordinated bot army (ADR-0032 squads · ADR-0033 campaign). **Mission Control** — the Opus `pan-conductor`, elevated to campaign scope — plans the mission, delegates to **squads** over the Agent toolset, and never writes code itself. Each squad owns its lifecycle role; the Build squad parallelizes by giving every builder its own `army/<task>` branch in an isolated git worktree. Nothing reaches a protected branch without green CI and a human's approval. $ARGUMENTS
19
+
20
+ The army is the campaign-scale sibling of `/pan:exec-phase --hierarchical` (one phase) and `/pan:focus-auto` (a category/backlog loop). It composes both: the conductor harness bounds it, the focus-auto loop drives it, the squads structure it.
21
+
22
+ ---
23
+
24
+ ## Tiers (from `pan-tools squad list`)
25
+
26
+ | Tier | Who | Model | Access |
27
+ |------|-----|-------|--------|
28
+ | 0 · Mission Control | `pan-conductor` | Opus 4.8 | delegation-only (Agent toolset) — never codes |
29
+ | 1 · Architecture | roadmapper · planner · plan-checker · researchers | Sonnet (reasoning) | read-only |
30
+ | 1 · Build | `pan-executor` | Sonnet (reasoning) | read / write / bash — one branch+worktree per agent |
31
+ | 1 · Quality | reviewer · hardener · meta · verifier · integration · debugger | Sonnet/Haiku (mid) | read-only, adversarial |
32
+ | 1 · Release | `pan-release` | Sonnet (mid) | always-ask — human gate |
33
+ | 2 · Workers | document_code · distiller | Haiku (fast) | narrow, high-volume jobs |
34
+
35
+ Resolve the roster at runtime — never hardcode it: `pan-tools squad list` and `pan-tools squad show <name>`.
36
+
37
+ ---
38
+
39
+ ## Concurrency model (LOAD-BEARING)
40
+
41
+ Read-only stages fan out; the mutating stage stays isolated, never shared:
42
+
43
+ | Stage | Concurrency |
44
+ |-------|-------------|
45
+ | Research (Architecture, Quality) | **Parallel** — read-only, mutate nothing |
46
+ | Build | **Parallel across agents, but each in its OWN worktree** — `pan-tools worktree create <task>`; two agents never share a tree or a file |
47
+ | Integrate / Release | **Serial, human-gated** — one merge at a time, `always-ask` |
48
+
49
+ If the project declares `concurrency.serial_build: true` in `.planning/config.json`, builds additionally run one-at-a-time even across worktrees (for build trees that corrupt under concurrency). Off by default.
50
+
51
+ ---
52
+
53
+ ## Safety harness (inherited from pan-conductor — mandatory)
54
+
55
+ Every cap the conductor enforces applies to the campaign, scaled up:
56
+
57
+ | Cap | Mechanism |
58
+ |-----|-----------|
59
+ | Nesting depth 2 | Mission Control spawns squad agents; squad agents MUST NOT spawn further |
60
+ | Spawn / budget ceiling | per-cycle spawn cap + `--total-budget`; stop when the next spawn exceeds remaining budget |
61
+ | Abort kill-switch | `.planning/orchestration/abort` present → stop immediately, preserve state |
62
+ | Protected main | no direct push; merge is an `always-ask` human gate |
63
+ | Worktree isolation | branch-per-agent — parallel builders cannot collide |
64
+ | Rollback never rewrite | recovery = `git revert` / previous tag; never force-push |
65
+
66
+ ---
67
+
68
+ ## Arguments
69
+
70
+ ```
71
+ /pan:army "<goal>" [--source scan|backlog] [--max-cycles N] [--total-budget N]
72
+ [--squads a,b,c] [--no-build-worktrees] [--push] [--clean-seal]
73
+ [--schedule <cadence>] [--daily-budget N]
74
+ [--dry-run] [--continue] [--stop] [--status]
75
+ ```
76
+
77
+ | Flag | Default | Effect |
78
+ |------|---------|--------|
79
+ | `--source` | `backlog` | Work selection (delegates to focus-auto): `backlog` = ranked roadmap/requirements items; `scan` = category code-scan. |
80
+ | `--max-cycles` | 5 | Mission items landed before stopping. |
81
+ | `--total-budget` | 300 | Cumulative point ceiling. |
82
+ | `--squads` | all | Restrict to a subset, e.g. `--squads architecture,build,quality`. |
83
+ | `--no-build-worktrees` | off | Build in the main tree instead of branch-per-agent worktrees (small/serial projects). |
84
+ | `--push` | off | Push approved merges to origin (still human-gated). |
85
+ | `--clean-seal` | off | One clean build + full verification after the last item (commands from config). |
86
+ | `--schedule` | off | Arm a self-resuming campaign at this cadence (`hourly`/`daily`/`weekly`/`Nh`/`Nd`) instead of running once — writes the schedule descriptor (ADR-0034). Pair with `--daily-budget`. |
87
+ | `--daily-budget` | 300 | Per-day point ceiling for a scheduled campaign; the day's run stops when reached, resumes next day. |
88
+ | `--dry-run` | off | Plan + squad delegation preview only; STOP. |
89
+ | `--continue` / `--stop` / `--status` | — | Resume / halt / report from `.planning/orchestration/` + focus-auto state. |
90
+
91
+ ---
92
+
93
+ ## Pipeline — the six-phase loop
94
+
95
+ ```
96
+ /pan:army
97
+ Phase 0 MUSTER — squad list + roster validate · cache prime · baseline · loop-state · abort-file clear
98
+ Phase 1 PLAN — Mission Control (Opus, extended thinking) decomposes the goal into dependency-ordered missions
99
+ Phase 2 DELEGATE — pick the next item (focus-auto --source) · route to the owning squad over the Agent toolset
100
+ Phase 3 EXECUTE — Build squad: one army/<task> worktree per agent (parallel); Architecture/Quality research in parallel (read-only)
101
+ Phase 4 REVIEW — Quality squad on the built tree: reviewer + hardener + meta → verdict ladder; a block is a hard gate
102
+ Phase 5 INTEGRATE— Release squad: prepare squash-merge → CI/verification → ALWAYS-ASK human approval → tag → deploy hand-off
103
+ Phase 6 LEARN — summaries return to Mission Control; retro/learn writes patterns to memory (this is "Dreaming")
104
+ → loop to Phase 2 until a stop condition; --clean-seal once at the end
105
+ ```
106
+
107
+ ### Phase 0 — Muster (once)
108
+ 1. **Onboarding gate (existing projects).** Run `pan-tools init new-project` to detect state. If `is_brownfield` (existing code) and `needs_codebase_map` (no `.planning/codebase/`), the army cannot plan blind — STOP and route through onboarding first: `/pan:map-codebase` (Architecture squad's `pan-document_code` maps the existing system into `.planning/codebase/`), then `/pan:new-project` to build `roadmap.md` + `requirements.md` *against the existing system*. Re-run `/pan:army` once a backlog exists. If a codebase map + roadmap already exist, continue.
109
+ 2. `pan-tools squad list` and validate the roster is healthy.
110
+ 3. Prime the cache; capture baseline (`git status` clean of project source; tests green or STOP). On a brownfield repo, the baseline is the current `main` — every `army/<task>` branch forks from it, so the existing code is never edited in place.
111
+ 4. Ensure `.planning/orchestration/` exists; clear any stale `abort` file; init loop-state.
112
+ 5. `--dry-run` → print the plan + per-squad delegation and STOP.
113
+
114
+ ### Phase 1 — Plan (Mission Control)
115
+ Spawn the conductor in campaign mode (it plans, it does not code). It decomposes the goal into ordered, dependency-aware missions and assigns each to a squad.
116
+
117
+ ### Phase 2–3 — Delegate + Execute
118
+ - Select the next item via `focus-auto --source {source}`.
119
+ - Architecture squad (read-only, parallel) produces the contract for a design-heavy item.
120
+ - Build squad: for each independent task, `pan-tools worktree create "<task>"` → spawn one `pan-executor` per worktree (parallel, isolated). Honor the spawn/budget caps before every spawn.
121
+
122
+ ### Phase 4 — Review (Quality)
123
+ Spawn the Quality squad on the built tree (parallel, read-only). Merge findings into one verdict (`/pan:review-deep` if available). `block` / `review_required` → fix serially, then re-review. Never integrate red.
124
+
125
+ ### Phase 5 — Integrate (Release, human-gated)
126
+ Spawn `pan-release`. It prepares the squash-merge, runs the configured `verification`, and surfaces an **always-ask** approval request. A human approves the merge to the protected branch; release then tags and records the rollback target. `--push` pushes the approved result.
127
+
128
+ ### Phase 6 — Learn (Dreaming)
129
+ Squad summaries return to Mission Control. Run `/pan:retro --write-memory` (and `/pan:learn` if traces exist) so recurring patterns persist into agent memory for the next mission. Strike the landed item; update loop-state. For a scheduled campaign, also `pan-tools campaign record-run --items <n> --points <p>` so the next-due time and the day's spend advance.
130
+
131
+ ---
132
+
133
+ ## Scheduled, self-resuming campaigns (ADR-0034)
134
+
135
+ PAN is not a daemon — it cannot wake itself while the session is closed. `--schedule` arms a campaign and lets an external trigger drive it; the human merge gate is never relaxed.
136
+
137
+ - **Arm:** `/pan:army "<goal>" --schedule daily --daily-budget 200` writes `.planning/orchestration/schedule.json` (cadence, daily budget, next-due) instead of running once.
138
+ - **The trigger (you wire one):** a host scheduler (Claude Code routines / cron / scheduled-tasks) or a `/loop` runs `pan-tools campaign due` and, when it reports due, invokes `/pan:army --continue`. On next session open, a due campaign is surfaced as a nudge.
139
+ - **Resume (`--continue`):** read the schedule + `.planning/orchestration/` + focus-auto state. If `campaign due` is true and the day's `--daily-budget` isn't spent, run the next mission(s), then `campaign record-run` (advances next-due, accrues the day's spend). If not due or budget-spent, report next-due and STOP.
140
+ - **Bounded spend:** the per-day budget caps each day's run; the per-run `--total-budget` and the conductor caps still bound each cycle. A scheduled campaign runs the backlog down to staged, reviewed, green PRs over days — and still waits for a human at every merge.
141
+
142
+ Manage it: `pan-tools campaign status` (active/paused, spent today, next-due), `campaign schedule --pause` / `--resume` / `--disable`.
143
+
144
+ ---
145
+
146
+ ## Completion contract
147
+ The campaign is complete when ANY holds: `--max-cycles` reached · `--total-budget` exhausted · backlog empty · abort file present · context < 25% · a mission cannot pass Quality and can't be cleanly reverted (HARD STOP — preserve state, report). Always run `--clean-seal` (unless omitted) after the last item.
148
+
149
+ ## NEVER DO
150
+ - Let Mission Control write code, or let a squad agent spawn further agents (depth cap).
151
+ - Run two builders in the same worktree, or merge to a protected branch without the human gate.
152
+ - Force-push or rewrite history; recovery is revert / previous tag only.
153
+ - Hardcode the squad roster — read it from `pan-tools squad list`.
154
+ - Integrate a mission that hasn't passed Quality green.
155
+
156
+ ## ALWAYS DO
157
+ - Plan on Opus, delegate over the Agent toolset, keep each squad's return a tight summary.
158
+ - One worktree per Build agent; parallel research/verify; serial human-gated integrate.
159
+ - Check the abort file + spawn/budget caps before every spawn.
160
+ - Finish with the clean-build seal; write learnings back to memory.
161
+
162
+ ## Examples
163
+ ```
164
+ /pan:army "ship the v1 reporting module" --source backlog --max-cycles 5
165
+ /pan:army "harden auth across the app" --squads architecture,build,quality --clean-seal
166
+ /pan:army "<goal>" --dry-run # show the plan + squad delegation, no code
167
+ /pan:army --status # campaign progress
168
+ /pan:army --stop # graceful halt, state preserved
169
+ ```
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: pan:dashboard
3
+ group: Observability
4
+ description: Alias for /pan:hud — generate the single-page HTML dashboard of the bot army and project
5
+ argument-hint: "[--out <file>] [--open] [--stdout]"
6
+ allowed-tools:
7
+ - Read
8
+ - Bash
9
+ ---
10
+
11
+ <objective>
12
+ `/pan:dashboard` is a discoverability alias for **`/pan:hud`** (ADR-0035). It generates the same single-page, self-contained HTML dashboard of the project and its bot army.
13
+
14
+ Run it exactly like `/pan:hud`:
15
+
16
+ ```
17
+ pan-tools hud [--out <file>] [--open] [--stdout]
18
+ ```
19
+
20
+ See **`/pan:hud`** for the full panel list, flags, JSON result shape, and runtime compatibility. The two commands are interchangeable.
21
+ </objective>
22
+
23
+ <execution_context>
24
+ @~/.claude/pan-wizard-core/bin/lib/hud.cjs
25
+ </execution_context>
@@ -87,14 +87,16 @@ Wait for the user's reply before proceeding. Do not guess or pick a default cate
87
87
  ## Arguments
88
88
 
89
89
  ```
90
- /pan:focus-auto [--category CAT] [--mode MODE] [--budget N] [--max-cycles N]
91
- [--total-budget N] [--continue] [--stop] [--status] [--dry-run]
92
- [--deep-review]
90
+ /pan:focus-auto [--source scan|backlog] [--category CAT] [--mode MODE] [--budget N]
91
+ [--max-cycles N] [--total-budget N] [--continue] [--stop] [--status]
92
+ [--dry-run] [--deep-review]
93
+ [--parallel-research] [--parallel-verify] [--clean-seal]
93
94
  ```
94
95
 
95
96
  | Flag | Default | Description |
96
97
  |------|---------|-------------|
97
- | `--category` | null (all) | cleanup, tests, stability, features, docs, optimize, prompts, security, distill |
98
+ | `--source` | `scan` | Work selection. `scan` = category-scoped code scan (below). `backlog` = rank actionable items from `roadmap.md` / `requirements.md` (ADR-0031). |
99
+ | `--category` | null (all) | cleanup, tests, stability, features, docs, optimize, prompts, security, distill. Applies to `--source scan`. |
98
100
  | `--mode` | category-dependent | bugfix, balanced, features, full |
99
101
  | `--budget` | category-dependent | Points per cycle (5-100) |
100
102
  | `--max-cycles` | 10 | Maximum iterations (1-50) |
@@ -104,6 +106,9 @@ Wait for the user's reply before proceeding. Do not guess or pick a default cate
104
106
  | `--status` | — | Show current campaign progress |
105
107
  | `--dry-run` | — | Show plan without executing |
106
108
  | `--deep-review` | off | After every exec cycle, run inline OWASP security check on changed files. Verdict `block` or `review_required` stops the campaign (6th safety harness). Works with all categories. |
109
+ | `--parallel-research` | off | Fan out the per-item *research* stage via the Workflow tool (read-only agents). No-op fallback to sequential where the host has no Workflow tool. (ADR-0031) |
110
+ | `--parallel-verify` | off | Fan out the per-item *verify* stage via the Workflow tool (read-only). The implement/exec stage always stays a single agent. (ADR-0031) |
111
+ | `--clean-seal` | off | After the loop's last item, run one clean build + full verification (commands from `config.json → build`/`verification`) to catch cross-item orphans. (ADR-0031) |
107
112
 
108
113
  ## Category Defaults
109
114
 
@@ -118,6 +123,29 @@ Wait for the user's reply before proceeding. Do not guess or pick a default cate
118
123
  | prompts | P0-P6 | balanced | 100 |
119
124
  | security | P0-P2 | bugfix | 40 |
120
125
 
126
+ ## Backlog source (`--source backlog`, ADR-0031)
127
+
128
+ When `--source backlog` is set, work is selected from the **curated planning surface** instead of a code scan — for campaigns that work a human-prioritized roadmap rather than whatever grep finds.
129
+
130
+ 1. **Read the backlog once** at Phase 0: actionable items are unchecked rows in `roadmap.md` (phase/plan checkboxes) and unmet `requirements.md` REQ rows. Skip anything struck, completed, or marked blocked.
131
+ 2. **Score from the CURRENT document — never a hardcoded ID list.** For each item, derive `RS = (UserValue + TimeCriticality + RiskReduction) / Effort` (1–5 each; Effort from the row's size tag). Sort by wave/priority ascending → RS descending → effort ascending. This re-derives the order from whatever the roadmap says today, so it never goes stale.
132
+ 3. **Pop the top survivor each cycle**; re-rank only if a landing changed a dependency. The same budget/cycle/context stops and safety harness apply unchanged.
133
+ 4. The backlog ranker reads only PAN's planning files — it embeds **no** project-specific item IDs, test counts, or build commands. A project with no actionable backlog items is a clean stop (`scan returns zero items` equivalent).
134
+
135
+ ## Concurrency model (when `--parallel-research` / `--parallel-verify`, ADR-0031)
136
+
137
+ The proven shape is **parallel read-only research → exactly ONE serial implement/exec → parallel read-only verify**:
138
+
139
+ | Stage | Concurrency | Why |
140
+ |-------|-------------|-----|
141
+ | Research | Parallel (Workflow fan-out, read-only) when `--parallel-research` | Reads source/specs; mutates nothing. |
142
+ | Implement / exec | **Single agent, always** | Mutates the tree; never fanned out. |
143
+ | Verify | Parallel (Workflow fan-out, read-only) when `--parallel-verify` | Runs against the already-built tree; mutates nothing. |
144
+
145
+ **Serial-build constraint:** if `.planning/config.json → concurrency.serial_build` is `true`, the runner additionally guarantees at most one build process at any instant across the whole loop (for projects whose build trees corrupt under concurrency). This is **off by default** — most projects build in parallel safely. PAN does not assume it.
146
+
147
+ **Commit-quality gates** (advisory, always on): a *staging-miss guard* (no exec-touched file left unstaged) and an *orphan audit* (HEAD must not reference a symbol defined only in an uncommitted file). With `--clean-seal`, a single clean build + full verification runs after the last item to catch cross-item orphans the per-cycle incremental commits hid.
148
+
121
149
  ## Pipeline
122
150
 
123
151
  ### Phase 0: Initialization
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: pan:hud
3
+ group: Observability
4
+ description: Generate a single-page, self-contained HTML dashboard of the bot army and the project's current state
5
+ argument-hint: "[--out <file>] [--open] [--stdout]"
6
+ allowed-tools:
7
+ - Read
8
+ - Bash
9
+ ---
10
+
11
+ <objective>
12
+ Render one **self-contained HTML page** — no server, no network, no external CSS or JS — that shows the whole picture of a PAN project and its bot army in their current state.
13
+
14
+ It is a *view*, not a new source of truth: every panel aggregates state PAN already tracks (`state.md`, the roadmap/phases on disk, the squad registry, the campaign schedule, army worktrees, the cost ledger, `requirements.md`, verification artifacts, and git history). The command writes only the rendered file, so it can never corrupt planning data.
15
+
16
+ Default output is `.planning/hud.html`. Open it in any browser. Re-run any time for a fresh snapshot.
17
+ </objective>
18
+
19
+ <execution_context>
20
+ @~/.claude/pan-wizard-core/bin/lib/hud.cjs
21
+ </execution_context>
22
+
23
+ <usage>
24
+
25
+ ```
26
+ pan-tools hud [--out <file>] [--open] [--stdout]
27
+ ```
28
+
29
+ **Flags:**
30
+ - `--out <file>` — write to a custom path instead of `.planning/hud.html` (relative paths resolve against the project root).
31
+ - `--open` — best-effort: launch the file in the default browser after writing (cross-platform; silently no-ops if no opener is available).
32
+ - `--stdout` — print the HTML to stdout instead of writing a file (for piping into another tool or a web response).
33
+
34
+ **JSON result shape** (default, when not `--stdout`):
35
+ ```json
36
+ {
37
+ "path": ".planning/hud.html",
38
+ "bytes": 18234,
39
+ "army_active": true,
40
+ "sections": ["mission", "command-stack", "campaign", "safety-harness", "worktrees", "roadmap", "telemetry", "requirements-quality", "activity"],
41
+ "opened": false
42
+ }
43
+ ```
44
+
45
+ </usage>
46
+
47
+ <panels>
48
+
49
+ The dashboard is composed of up to ten panels. Panels render only when they have data — a plain (non-army) project still gets a complete, useful page.
50
+
51
+ | Panel | What it shows | Source |
52
+ |-------|---------------|--------|
53
+ | **Mission banner** | Project, core value, status, version/milestone + metric cards (progress, phase, requirements, spend) | `package.json`, `project.md`, `state.md`, phase scan, cost ledger |
54
+ | **Command stack** *(army)* | Mission Control over the four squads with per-squad agent drill-down (active / idle, calls, tokens) | squad registry + cost ledger |
55
+ | **Campaign** *(army)* | Cadence, next-due, daily-budget bar, last run, run history | `schedule.json` |
56
+ | **Safety harness** *(army)* | Merge gate, abort switch (pause), active worktrees, daily budget, concurrency | config + pause file + worktrees + schedule |
57
+ | **Worktrees** *(army)* | Active `army/*` branches and their paths | `git worktree list` |
58
+ | **Roadmap** | Every phase with status + completion | phases on disk |
59
+ | **Telemetry** | Total spend, tokens, cache-hit rate, by-squad breakdown | cost ledger |
60
+ | **Requirements & quality** | Requirements done/open + last verification artifacts | `requirements.md`, phase `*-verification.md` / `*-uat.md` |
61
+ | **Recent activity** | Last commits (the army's committed output) | `git log` |
62
+
63
+ *(army)* panels appear only when a campaign is scheduled or army worktrees exist — graceful degradation per ADR-0035.
64
+
65
+ </panels>
66
+
67
+ <workflow>
68
+
69
+ **Glance check:** run `/pan:hud --open` to see the project and army at a glance in your browser.
70
+
71
+ **Share status:** the file is fully self-contained — send `.planning/hud.html` to anyone; it opens with no dependencies.
72
+
73
+ **During a campaign:** re-run after each cycle to watch squads, budget, worktrees, and committed output evolve. Pair with `/pan:army` and `pan-tools campaign status`.
74
+
75
+ **Pipe it:** `pan-tools hud --stdout > dashboard.html` or feed the JSON result into another tool.
76
+
77
+ </workflow>
78
+
79
+ <runtime_compatibility>
80
+
81
+ | Runtime | Support |
82
+ |---------|---------|
83
+ | Claude Code | Full |
84
+ | OpenCode | Full |
85
+ | Gemini | Full |
86
+ | Codex | Full |
87
+ | Copilot CLI | Full |
88
+
89
+ The aggregator and renderer are pure, zero-dependency, runtime-agnostic CommonJS — the dashboard is identical across all five runtimes. `--open` depends on the host OS having a default browser opener.
90
+
91
+ </runtime_compatibility>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pan-wizard",
3
- "version": "3.10.0",
3
+ "version": "3.12.0",
4
4
  "description": "A lightweight workflow automation and context engineering system for Claude Code, OpenCode, Gemini CLI, Codex, and Copilot CLI.",
5
5
  "bin": {
6
6
  "pan-wizard": "bin/install.js"
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Campaign — scheduled, self-resuming bot-army campaigns (ADR-0034).
3
+ *
4
+ * PAN is not a daemon: this module owns the schedule DESCRIPTOR and the
5
+ * decision of whether a run is DUE. An external trigger (host scheduler,
6
+ * cron, /loop, or a human) polls `campaign due` and fires `/pan:army
7
+ * --continue`. The always-ask human merge gate is never affected by
8
+ * scheduling. Pure + synchronous; `now` is injected for testability.
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { output, error } = require('./core.cjs');
16
+ const { PLANNING_DIR } = require('./constants.cjs');
17
+
18
+ const ORCH_DIR = 'orchestration';
19
+ const SCHEDULE_FILE = 'schedule.json';
20
+ const HISTORY_CAP = 50;
21
+ const DAY_MS = 86400000;
22
+
23
+ function schedulePath(cwd) {
24
+ return path.join(cwd, PLANNING_DIR, ORCH_DIR, SCHEDULE_FILE);
25
+ }
26
+
27
+ /**
28
+ * Parse a cadence string into milliseconds.
29
+ * @param {string} c - 'hourly' | 'daily' | 'weekly' | 'Nh' | 'Nd'
30
+ * @returns {number|null} ms, or null if unparseable
31
+ */
32
+ function parseCadence(c) {
33
+ if (!c || typeof c !== 'string') return null;
34
+ const s = c.trim().toLowerCase();
35
+ if (s === 'hourly') return 3600000;
36
+ if (s === 'daily') return DAY_MS;
37
+ if (s === 'weekly') return 7 * DAY_MS;
38
+ const m = s.match(/^(\d+)\s*([hd])$/);
39
+ if (!m) return null;
40
+ const n = parseInt(m[1], 10);
41
+ if (n <= 0) return null;
42
+ return m[2] === 'h' ? n * 3600000 : n * DAY_MS;
43
+ }
44
+
45
+ /** @returns {object|null} the schedule descriptor, or null if none/unreadable */
46
+ function readSchedule(cwd) {
47
+ try {
48
+ return JSON.parse(fs.readFileSync(schedulePath(cwd), 'utf8'));
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ function writeScheduleFile(cwd, schedule) {
55
+ const p = schedulePath(cwd);
56
+ fs.mkdirSync(path.dirname(p), { recursive: true });
57
+ fs.writeFileSync(p, JSON.stringify(schedule, null, 2) + '\n', 'utf8');
58
+ }
59
+
60
+ /**
61
+ * Arm or update a campaign schedule.
62
+ * @param {string} cwd
63
+ * @param {object} opts - { goal, source, cadence, daily_budget, enabled, paused }
64
+ * @param {Date} now
65
+ * @returns {object|{error}} the written descriptor
66
+ */
67
+ function writeSchedule(cwd, opts, now) {
68
+ const cadence = opts.cadence || 'daily';
69
+ if (parseCadence(cadence) === null) {
70
+ return { error: `Invalid cadence "${cadence}". Use hourly | daily | weekly | Nh | Nd.` };
71
+ }
72
+ const at = now || new Date();
73
+ const existing = readSchedule(cwd) || {};
74
+ const schedule = {
75
+ goal: opts.goal ?? existing.goal ?? null,
76
+ source: opts.source ?? existing.source ?? 'backlog',
77
+ cadence,
78
+ daily_budget: opts.daily_budget != null ? Number(opts.daily_budget) : (existing.daily_budget ?? 300),
79
+ enabled: opts.enabled != null ? Boolean(opts.enabled) : (existing.enabled ?? true),
80
+ paused: opts.paused != null ? Boolean(opts.paused) : (existing.paused ?? false),
81
+ next_due: existing.next_due ?? at.toISOString(),
82
+ last_run: existing.last_run ?? null,
83
+ history: Array.isArray(existing.history) ? existing.history : [],
84
+ };
85
+ writeScheduleFile(cwd, schedule);
86
+ return schedule;
87
+ }
88
+
89
+ function sameUtcDay(a, b) {
90
+ return a.getUTCFullYear() === b.getUTCFullYear()
91
+ && a.getUTCMonth() === b.getUTCMonth()
92
+ && a.getUTCDate() === b.getUTCDate();
93
+ }
94
+
95
+ function spentToday(schedule, now) {
96
+ return (schedule.history || [])
97
+ .filter(h => { const t = new Date(h.ts); return !isNaN(t) && sameUtcDay(t, now); })
98
+ .reduce((sum, h) => sum + (Number(h.points_used) || 0), 0);
99
+ }
100
+
101
+ /**
102
+ * Is a scheduled run due right now?
103
+ * @returns {{due: boolean, reason: string, next_due: string|null, spent_today: number}}
104
+ */
105
+ function isRunDue(schedule, now) {
106
+ const at = now || new Date();
107
+ if (!schedule) return { due: false, reason: 'no_schedule', next_due: null, spent_today: 0 };
108
+ const spent = spentToday(schedule, at);
109
+ if (!schedule.enabled) return { due: false, reason: 'disabled', next_due: schedule.next_due, spent_today: spent };
110
+ if (schedule.paused) return { due: false, reason: 'paused', next_due: schedule.next_due, spent_today: spent };
111
+ if (schedule.daily_budget != null && spent >= schedule.daily_budget) {
112
+ return { due: false, reason: 'budget_exhausted_today', next_due: schedule.next_due, spent_today: spent };
113
+ }
114
+ const due = new Date(schedule.next_due);
115
+ if (isNaN(due) || at.getTime() >= due.getTime()) {
116
+ return { due: true, reason: 'due', next_due: schedule.next_due, spent_today: spent };
117
+ }
118
+ return { due: false, reason: 'not_yet', next_due: schedule.next_due, spent_today: spent };
119
+ }
120
+
121
+ /**
122
+ * Record a completed campaign run and advance next_due.
123
+ * @returns {object|{error}} updated descriptor
124
+ */
125
+ function recordRun(cwd, run, now) {
126
+ const schedule = readSchedule(cwd);
127
+ if (!schedule) return { error: 'No campaign schedule to record against' };
128
+ const at = now || new Date();
129
+ const ts = run?.ts || at.toISOString();
130
+ schedule.history = (schedule.history || []).concat([{
131
+ ts,
132
+ items_landed: Number(run?.items_landed) || 0,
133
+ points_used: Number(run?.points_used) || 0,
134
+ }]).slice(-HISTORY_CAP);
135
+ schedule.last_run = ts;
136
+ const step = parseCadence(schedule.cadence) || DAY_MS;
137
+ schedule.next_due = new Date(at.getTime() + step).toISOString();
138
+ writeScheduleFile(cwd, schedule);
139
+ return schedule;
140
+ }
141
+
142
+ /**
143
+ * Is the retro/learn ("dream") step due? Default: once per calendar day that
144
+ * had activity — curate memory between missions, not after every cycle.
145
+ */
146
+ function isDreamDue(schedule, now) {
147
+ if (!schedule || !schedule.enabled || schedule.paused) return false;
148
+ const at = now || new Date();
149
+ if (!schedule.last_run) return false;
150
+ const last = new Date(schedule.last_run);
151
+ if (isNaN(last)) return false;
152
+ return !sameUtcDay(last, at) || (schedule.history || []).length > 0;
153
+ }
154
+
155
+ // ─── CLI ─────────────────────────────────────────────────────────────────────
156
+
157
+ function cmdCampaignSchedule(cwd, opts, raw) {
158
+ const r = writeSchedule(cwd, opts, opts.now);
159
+ if (r.error) return error(r.error);
160
+ const human = `Campaign scheduled: ${r.cadence} · budget ${r.daily_budget}/day · next ${r.next_due}${r.goal ? ` · goal: ${r.goal}` : ''}`;
161
+ output(r, raw, human);
162
+ }
163
+
164
+ function cmdCampaignStatus(cwd, raw) {
165
+ const schedule = readSchedule(cwd);
166
+ if (!schedule) return output({ scheduled: false }, raw, 'No campaign scheduled');
167
+ const at = new Date();
168
+ const d = isRunDue(schedule, at);
169
+ const result = {
170
+ scheduled: true, enabled: schedule.enabled, paused: schedule.paused,
171
+ cadence: schedule.cadence, daily_budget: schedule.daily_budget,
172
+ next_due: schedule.next_due, last_run: schedule.last_run,
173
+ spent_today: d.spent_today, runs: (schedule.history || []).length,
174
+ due: d.due, reason: d.reason,
175
+ };
176
+ const human = `Campaign ${schedule.enabled ? (schedule.paused ? 'paused' : 'active') : 'disabled'} · ${schedule.cadence} · spent ${d.spent_today}/${schedule.daily_budget} today · next ${schedule.next_due} · ${d.due ? 'DUE NOW' : d.reason}`;
177
+ output(result, raw, human);
178
+ }
179
+
180
+ function cmdCampaignDue(cwd, raw) {
181
+ const schedule = readSchedule(cwd);
182
+ const d = isRunDue(schedule, new Date());
183
+ // exit-coded so a host scheduler can gate: 0 = due, 1 = not due
184
+ output({ due: d.due, reason: d.reason, next_due: d.next_due }, raw, d.due ? 'due' : `not due (${d.reason})`);
185
+ }
186
+
187
+ module.exports = {
188
+ parseCadence,
189
+ readSchedule,
190
+ writeSchedule,
191
+ isRunDue,
192
+ recordRun,
193
+ isDreamDue,
194
+ cmdCampaignSchedule,
195
+ cmdCampaignStatus,
196
+ cmdCampaignDue,
197
+ SCHEDULE_FILE,
198
+ };
@@ -126,6 +126,13 @@ const AUTO_RUN_FILE = 'auto-run.json';
126
126
  /** Focus auto-runner categories */
127
127
  const FOCUS_CATEGORIES = ['cleanup', 'tests', 'stability', 'features', 'docs', 'optimize', 'prompts', 'security', 'distill'];
128
128
 
129
+ /**
130
+ * Focus auto-runner work sources (ADR-0031):
131
+ * - scan: category-scoped code scan (default; today's behavior)
132
+ * - backlog: rank actionable items from roadmap.md / requirements.md
133
+ */
134
+ const FOCUS_SOURCES = ['scan', 'backlog'];
135
+
129
136
  /** Category → priority index range (indices into PRIORITY_LEVELS) */
130
137
  const CATEGORY_PRIORITY_RANGE = {
131
138
  cleanup: { min: 3, max: 5 }, // P3-P5
@@ -681,6 +688,7 @@ module.exports = {
681
688
  FOCUS_DIR,
682
689
  AUTO_RUN_FILE,
683
690
  FOCUS_CATEGORIES,
691
+ FOCUS_SOURCES,
684
692
  DOC_SYNC_FILES,
685
693
  COMMAND_RENAME_MAP,
686
694
  CATEGORY_PRIORITY_RANGE,
@@ -73,6 +73,8 @@ const MODEL_PROFILES = {
73
73
  'pan-distiller': { quality: 'reasoning', balanced: 'fast', budget: 'fast' },
74
74
  // v3.7.0 self-improvement loop — observation-only watchdog
75
75
  'pan-experiment-runner': { quality: 'reasoning', balanced: 'fast', budget: 'fast' },
76
+ // ADR-0033 bot-army — Release squad
77
+ 'pan-release': { quality: 'reasoning', balanced: 'mid', budget: 'fast' },
76
78
  };
77
79
 
78
80
  // ─── Effort Profiles (2026-06, adaptive-thinking era) ───────────────────────
@@ -104,6 +106,7 @@ const AGENT_BASE_EFFORT = {
104
106
  'pan-previewer': 'high',
105
107
  'pan-experiment-runner': 'high',
106
108
  'pan-optimizer': 'high',
109
+ 'pan-release': 'high',
107
110
  // Research/synthesis/review — moderate depth
108
111
  'pan-phase-researcher': 'medium',
109
112
  'pan-project-researcher': 'medium',
@@ -279,6 +282,11 @@ function loadConfig(cwd) {
279
282
  model_overrides: parsed.model_overrides || {},
280
283
  effort_overrides: parsed.effort_overrides || {},
281
284
  routing: parsed.routing || { strategy: 'static', provider: 'auto' },
285
+ // ADR-0031: project build/verification commands. null = not configured
286
+ // (focus-auto --clean-seal then asks or skips rather than guessing).
287
+ build: parsed.build || null,
288
+ verification: parsed.verification || null,
289
+ concurrency: parsed.concurrency || { serial_build: false },
282
290
  };
283
291
  } catch { // Config missing or malformed — use defaults
284
292
  return {
@@ -290,6 +298,9 @@ function loadConfig(cwd) {
290
298
  model_overrides: {},
291
299
  effort_overrides: {},
292
300
  routing: { strategy: 'static', provider: 'auto' },
301
+ build: null,
302
+ verification: null,
303
+ concurrency: { serial_build: false },
293
304
  };
294
305
  }
295
306
  }
@@ -14,7 +14,7 @@ const {
14
14
  FOCUS_MODES, FOCUS_TIERS, FOCUS_DIR,
15
15
  BUDGET_LIMIT_BUGFIX, BUDGET_LIMIT_FULL, STABILITY_RATIO, FEATURE_RATIO,
16
16
  DIMINISHING_RETURNS_THRESHOLD,
17
- AUTO_RUN_FILE, FOCUS_CATEGORIES, CATEGORY_PRIORITY_RANGE, CATEGORY_DEFAULTS,
17
+ AUTO_RUN_FILE, FOCUS_CATEGORIES, FOCUS_SOURCES, CATEGORY_PRIORITY_RANGE, CATEGORY_DEFAULTS,
18
18
  DEFAULT_MAX_CYCLES, DEFAULT_TOTAL_BUDGET,
19
19
  BUDGET_MIN, BUDGET_MAX, MAX_CYCLES_MIN, MAX_CYCLES_MAX, TOTAL_BUDGET_MIN, TOTAL_BUDGET_MAX,
20
20
  AUTORUN_STATUSES, DOC_SYNC_FILES, COMMAND_RENAME_MAP,
@@ -829,6 +829,14 @@ function focusAutoInit(cwd, raw, getVal, hasFlag) {
829
829
  return error(`Category must be one of: ${FOCUS_CATEGORIES.join(', ')}`);
830
830
  }
831
831
 
832
+ // ADR-0031: work source — 'scan' (category code-scan, default) or 'backlog'
833
+ // (rank actionable roadmap.md / requirements.md items). Category applies to
834
+ // scan mode; backlog mode ranks the whole actionable backlog.
835
+ const source = getVal('--source', 'scan');
836
+ if (!FOCUS_SOURCES.includes(source)) {
837
+ return error(`Source must be one of: ${FOCUS_SOURCES.join(', ')}`);
838
+ }
839
+
832
840
  const existing = readAutoRun(cwd);
833
841
  if (existing && (existing.status === AUTORUN_STATUSES.IN_PROGRESS || existing.status === AUTORUN_STATUSES.INITIALIZED)) {
834
842
  return error('Auto-run already in progress. Use --stop to end it, or --continue to resume.');
@@ -848,8 +856,12 @@ function focusAutoInit(cwd, raw, getVal, hasFlag) {
848
856
  const runData = {
849
857
  run_id: generateRunId(cwd),
850
858
  status: AUTORUN_STATUSES.INITIALIZED,
859
+ source: source,
851
860
  category: category,
852
861
  mode: mode,
862
+ parallel_research: hasFlag('--parallel-research'),
863
+ parallel_verify: hasFlag('--parallel-verify'),
864
+ clean_seal: hasFlag('--clean-seal'),
853
865
  budget_per_cycle: budget,
854
866
  max_cycles: maxCycles,
855
867
  total_budget: totalBudget,