pan-wizard 3.8.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.
- package/README.md +80 -9
- package/agents/pan-conductor.md +15 -3
- package/agents/pan-counterfactual.md +1 -2
- package/agents/pan-debugger.md +1 -2
- package/agents/pan-distiller.md +1 -2
- package/agents/pan-document_code.md +1 -0
- package/agents/pan-executor.md +1 -0
- package/agents/pan-experiment-runner.md +1 -2
- package/agents/pan-hardener.md +1 -2
- package/agents/pan-integration-checker.md +1 -2
- package/agents/pan-knowledge.md +1 -2
- package/agents/pan-meta-reviewer.md +1 -2
- package/agents/pan-optimizer.md +1 -0
- package/agents/pan-phase-researcher.md +1 -0
- package/agents/pan-plan-checker.md +1 -2
- package/agents/pan-planner.md +1 -0
- package/agents/pan-previewer.md +1 -2
- package/agents/pan-project-researcher.md +6 -0
- package/agents/pan-release.md +58 -0
- package/agents/pan-research-synthesizer.md +7 -0
- package/agents/pan-reviewer.md +2 -3
- package/agents/pan-roadmapper.md +1 -0
- package/agents/pan-verifier.md +1 -2
- package/assets/pan-avatar.png +0 -0
- package/assets/pan-developer.png +0 -0
- package/assets/pan-docs-header.png +0 -0
- package/assets/pan-hero.png +0 -0
- package/assets/pan-logo-2000-transparent.svg +11 -30
- package/assets/pan-logo-2000.svg +12 -43
- package/assets/pan-logo-lockup.svg +11 -0
- package/assets/pan-mark.svg +7 -0
- package/assets/pan-orchestration.png +0 -0
- package/assets/pan-readme-hero.png +0 -0
- package/assets/terminal.svg +39 -119
- package/bin/install-lib.cjs +661 -46
- package/bin/install.js +722 -116
- package/commands/pan/army.md +169 -0
- package/commands/pan/dashboard.md +25 -0
- package/commands/pan/experiment.md +2 -0
- package/commands/pan/focus-auto.md +32 -4
- package/commands/pan/hud.md +91 -0
- package/commands/pan/profile.md +2 -0
- package/hooks/dist/pan-cost-logger.js +22 -7
- package/package.json +5 -4
- package/pan-wizard-core/bin/lib/campaign.cjs +198 -0
- package/pan-wizard-core/bin/lib/commands-learnings.cjs +544 -0
- package/pan-wizard-core/bin/lib/commands.cjs +12 -523
- package/pan-wizard-core/bin/lib/constants.cjs +8 -0
- package/pan-wizard-core/bin/lib/core.cjs +80 -0
- package/pan-wizard-core/bin/lib/cost.cjs +62 -8
- package/pan-wizard-core/bin/lib/focus.cjs +13 -1
- package/pan-wizard-core/bin/lib/git.cjs +6 -1
- package/pan-wizard-core/bin/lib/hud.cjs +887 -0
- package/pan-wizard-core/bin/lib/lock.cjs +108 -0
- package/pan-wizard-core/bin/lib/milestone.cjs +3 -2
- package/pan-wizard-core/bin/lib/phase-remove.cjs +392 -0
- package/pan-wizard-core/bin/lib/phase.cjs +4 -369
- package/pan-wizard-core/bin/lib/runner.cjs +5 -0
- package/pan-wizard-core/bin/lib/squads.cjs +152 -0
- package/pan-wizard-core/bin/lib/state.cjs +10 -1
- package/pan-wizard-core/bin/lib/verify-deploy.cjs +181 -0
- package/pan-wizard-core/bin/lib/verify-drift.cjs +255 -0
- package/pan-wizard-core/bin/lib/verify-preflight.cjs +261 -0
- package/pan-wizard-core/bin/lib/verify-retro.cjs +177 -0
- package/pan-wizard-core/bin/lib/verify.cjs +10 -797
- package/pan-wizard-core/bin/lib/worktree.cjs +123 -0
- package/pan-wizard-core/bin/pan-tools.cjs +78 -0
- package/pan-wizard-core/learnings/universal/autonomous-loop.md +56 -0
- package/pan-wizard-core/workflows/plan-phase.md +11 -0
- package/scripts/build-plugin.js +105 -0
- package/scripts/install-git-hooks.js +64 -0
- package/scripts/release-check.js +13 -2
|
@@ -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>
|
|
@@ -105,6 +105,8 @@ Spawn the external AI runtime against the experiment folder. **Synchronous** —
|
|
|
105
105
|
|
|
106
106
|
**Runtime support:** claude / codex / gemini / opencode (via `RUNTIME_RUNNERS` adapter map in `runner.cjs`). GitHub Copilot CLI is **unsupported** for the `run` subcommand — no documented headless prompt mode. Copilot users can still scaffold and harvest manually.
|
|
107
107
|
|
|
108
|
+
**Billing note (Claude runtime):** headless `claude -p` runs bill against the **Claude Agent SDK credit pool** — a monthly allotment separate from your interactive subscription limits (Anthropic split the two effective June 15, 2026). Experiment runs do not consume interactive-session quota, but heavy experimentation can exhaust the SDK pool independently. Captured metrics are tagged `billing_pool: "agent_sdk"` so you can reconcile experiment spend separately.
|
|
109
|
+
|
|
108
110
|
### `/pan:experiment status <slug>`
|
|
109
111
|
|
|
110
112
|
Read the current `run-state.json` snapshot. Returns the full state object (`status`, `stop_reason`, `exit_code`, `elapsed_ms`, `events`).
|
|
@@ -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]
|
|
91
|
-
[--total-budget N] [--continue] [--stop] [--status]
|
|
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
|
-
| `--
|
|
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/commands/pan/profile.md
CHANGED
|
@@ -71,4 +71,6 @@ Final tier → provider-native model name
|
|
|
71
71
|
- All rules are additive to the `quality` / `balanced` / `budget` profile you pick here — profile sets the floor, capability hints adjust upward or downward within that floor's band.
|
|
72
72
|
|
|
73
73
|
**Inspecting routing:** use `pan-tools resolve-model <agent> --metadata '{"context_estimate":900000,"needs_thinking":true}'` to see what tier a given hint set resolves to.
|
|
74
|
+
|
|
75
|
+
**Effort dimension (2026-06):** `resolve-model` also returns an `effort` level (`low`/`medium`/`high`/`xhigh`) per agent — the within-model reasoning-depth dial on current models. Profiles modulate it: `budget` steps each agent's base effort down one level (floor `low`); `quality`/`balanced` keep the base. Per-agent override: `.planning/config.json` → `"effort_overrides": { "pan-verifier": "xhigh" }`.
|
|
74
76
|
</tier_decision_tree>
|
|
@@ -37,23 +37,33 @@ function buildCostRecord(data, cwd) {
|
|
|
37
37
|
// doesn't include it in the SubagentStop payload), fall back to reading the
|
|
38
38
|
// transcript_path JSONL and summing usage across the subagent's messages.
|
|
39
39
|
// Same approach as pan-trace-logger.js for consistency.
|
|
40
|
+
//
|
|
41
|
+
// 2026-06: the SubagentStop payload carries no model id either, which left
|
|
42
|
+
// every hook record with model:null and /pan:cost unable to price it. The
|
|
43
|
+
// transcript's assistant messages carry message.model right next to the
|
|
44
|
+
// usage we already read — capture it whenever data.model is absent.
|
|
40
45
|
let inputTokens = extractNumber(data.usage, 'input_tokens');
|
|
41
46
|
let outputTokens = extractNumber(data.usage, 'output_tokens');
|
|
42
47
|
let cacheRead = extractNumber(data.usage, 'cache_read_input_tokens');
|
|
43
48
|
let cacheWrite = extractNumber(data.usage, 'cache_creation_input_tokens');
|
|
44
|
-
|
|
49
|
+
let model = typeof data.model === 'string' && data.model ? data.model : null;
|
|
50
|
+
const needUsage = (inputTokens + outputTokens + cacheRead + cacheWrite) === 0;
|
|
51
|
+
if ((needUsage || !model) && data.transcript_path) {
|
|
45
52
|
const fromTranscript = readUsageFromTranscript(data.transcript_path, data.session_id);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
if (needUsage) {
|
|
54
|
+
inputTokens = fromTranscript.input_tokens;
|
|
55
|
+
outputTokens = fromTranscript.output_tokens;
|
|
56
|
+
cacheRead = fromTranscript.cache_read_input_tokens;
|
|
57
|
+
cacheWrite = fromTranscript.cache_creation_input_tokens;
|
|
58
|
+
}
|
|
59
|
+
if (!model) model = fromTranscript.model;
|
|
50
60
|
}
|
|
51
61
|
|
|
52
62
|
const record = {
|
|
53
63
|
ts: new Date().toISOString(),
|
|
54
64
|
agent: data.agent_type || data.subagent_type || null,
|
|
55
65
|
command: null,
|
|
56
|
-
model
|
|
66
|
+
model,
|
|
57
67
|
tier: null,
|
|
58
68
|
input_tokens: inputTokens,
|
|
59
69
|
output_tokens: outputTokens,
|
|
@@ -85,6 +95,7 @@ function readUsageFromTranscript(transcriptPath, sessionId) {
|
|
|
85
95
|
output_tokens: 0,
|
|
86
96
|
cache_read_input_tokens: 0,
|
|
87
97
|
cache_creation_input_tokens: 0,
|
|
98
|
+
model: null,
|
|
88
99
|
};
|
|
89
100
|
if (!transcriptPath || typeof transcriptPath !== 'string') return totals;
|
|
90
101
|
let raw;
|
|
@@ -94,6 +105,10 @@ function readUsageFromTranscript(transcriptPath, sessionId) {
|
|
|
94
105
|
let entry;
|
|
95
106
|
try { entry = JSON.parse(line); } catch { continue; }
|
|
96
107
|
if (sessionId && entry.session_id && entry.session_id !== sessionId) continue;
|
|
108
|
+
// Assistant messages carry the model id alongside their usage — keep the
|
|
109
|
+
// last one seen (mid-session model switches resolve to the final model).
|
|
110
|
+
const entryModel = entry.message?.model || entry.model || null;
|
|
111
|
+
if (typeof entryModel === 'string' && entryModel) totals.model = entryModel;
|
|
97
112
|
const usage = entry.usage
|
|
98
113
|
|| entry.message?.usage
|
|
99
114
|
|| entry.response?.usage
|
|
@@ -149,4 +164,4 @@ if (require.main === module) {
|
|
|
149
164
|
});
|
|
150
165
|
}
|
|
151
166
|
|
|
152
|
-
module.exports = { buildCostRecord, appendRecord, METRICS_DIR, TOKENS_FILE };
|
|
167
|
+
module.exports = { buildCostRecord, appendRecord, readUsageFromTranscript, METRICS_DIR, TOKENS_FILE };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pan-wizard",
|
|
3
|
-
"version": "3.
|
|
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"
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@playwright/test": "^1.58.2",
|
|
53
|
-
"@vscode/test-electron": "^2.5.2"
|
|
54
|
-
"esbuild": "^0.28.0"
|
|
53
|
+
"@vscode/test-electron": "^2.5.2"
|
|
55
54
|
},
|
|
56
55
|
"scripts": {
|
|
57
56
|
"build:hooks": "node scripts/build-hooks.js",
|
|
57
|
+
"prepare": "node scripts/install-git-hooks.js",
|
|
58
58
|
"release:check": "node scripts/release-check.js",
|
|
59
59
|
"prepublishOnly": "node scripts/release-check.js",
|
|
60
60
|
"test": "node --test tests/*.test.cjs",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"test:all": "node --test tests/*.test.cjs tests/scenarios/*.test.cjs",
|
|
63
63
|
"test:e2e": "node --test tests/scenarios/*.test.cjs",
|
|
64
64
|
"test:vscode": "npx playwright test --config tests/e2e/playwright.config.mjs",
|
|
65
|
-
"test:watch": "node --test --watch tests/*.test.cjs"
|
|
65
|
+
"test:watch": "node --test --watch tests/*.test.cjs",
|
|
66
|
+
"build:plugin": "node scripts/build-plugin.js"
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -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
|
+
};
|