pan-wizard 3.15.1 → 3.18.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 +24 -1
- package/commands/pan/army.md +4 -0
- package/commands/pan/focus-doc-audit.md +1 -1
- package/commands/pan/report.md +4 -3
- package/package.json +3 -2
- package/pan-wizard-core/bin/lib/config.cjs +2 -0
- package/pan-wizard-core/bin/lib/focus.cjs +37 -0
- package/pan-wizard-core/bin/lib/phase-report.cjs +84 -22
- package/pan-wizard-core/bin/pan-tools.cjs +1 -0
- package/pan-wizard-core/templates/config.json +2 -1
- package/pan-wizard-core/workflows/exec-phase.md +17 -1
- package/pan-zcode/KNOWN-BETA-RISKS.md +36 -0
- package/pan-zcode/README.md +58 -0
- package/pan-zcode/bin/install-zcode.js +149 -0
- package/pan-zcode/lib/convert-agent.cjs +90 -0
- package/pan-zcode/mcp/merge-gate.cjs +126 -0
- package/pan-zcode/mcp/native-tools.cjs +63 -0
- package/pan-zcode/mcp/orchestrator.cjs +64 -0
- package/pan-zcode/mcp/server.cjs +229 -0
- package/pan-zcode/mcp/tool-registry.cjs +94 -0
package/README.md
CHANGED
|
@@ -201,6 +201,29 @@ npm run test:all # All tests (unit + scenario)
|
|
|
201
201
|
|
|
202
202
|
</details>
|
|
203
203
|
|
|
204
|
+
<details>
|
|
205
|
+
<summary><strong>Experimental: ZCode support (preview) — ZCode is beta</strong></summary>
|
|
206
|
+
|
|
207
|
+
**PAN-Z** is an experimental, separate subsystem that brings the PAN workflow to
|
|
208
|
+
[ZCode](https://zcode.z.ai), z.ai's GLM coding-agent harness. ZCode has no
|
|
209
|
+
slash-commands or hooks to host PAN directly, so PAN-Z instead exposes PAN's engine to
|
|
210
|
+
ZCode over **MCP**: PAN's agents become ZCode subagents, and the deterministic engine —
|
|
211
|
+
including a model-proof human merge gate — is reached as MCP tools.
|
|
212
|
+
|
|
213
|
+
> **ZCode is beta**, and its on-disk formats change frequently. PAN-Z is a **preview**:
|
|
214
|
+
> two facts (whether a subagent can call MCP tools, and whether local MCP calls are
|
|
215
|
+
> metered) can only be confirmed on a live ZCode install. See
|
|
216
|
+
> [`pan-zcode/README.md`](pan-zcode/README.md) and
|
|
217
|
+
> [`pan-zcode/KNOWN-BETA-RISKS.md`](pan-zcode/KNOWN-BETA-RISKS.md).
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# From an installed pan-wizard package, build the ZCode bundle into a target dir
|
|
221
|
+
node "$(npm root -g)/pan-wizard/pan-zcode/bin/install-zcode.js" --target ./zcode-bundle
|
|
222
|
+
# then finish setup inside ZCode per the generated INSTALL-ZCODE.md
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
</details>
|
|
226
|
+
|
|
204
227
|
### Recommended: Skip Permissions Mode
|
|
205
228
|
|
|
206
229
|
PAN is designed for frictionless automation. Run Claude Code with:
|
|
@@ -553,7 +576,7 @@ You're never locked in. The system adapts.
|
|
|
553
576
|
|
|
554
577
|
PAN is not a replacement for your IDE or AI agent — it's the orchestration layer that handles everything *around* them: breaking work into context-safe chunks, researching before planning, verifying after execution, and maintaining state across sessions.
|
|
555
578
|
|
|
556
|
-
> For the full comparison across
|
|
579
|
+
> For the full comparison across the major AI coding tools and many dimensions, see [COMPARISON.md](docs/COMPARISON.md).
|
|
557
580
|
|
|
558
581
|
---
|
|
559
582
|
|
package/commands/pan/army.md
CHANGED
|
@@ -125,9 +125,13 @@ Spawn the Quality squad on the built tree (parallel, read-only). Merge findings
|
|
|
125
125
|
### Phase 5 — Integrate (Release, human-gated)
|
|
126
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
127
|
|
|
128
|
+
**Phase report (opt-in build deliverable):** when `workflow.phase_reports.enabled` is `true`, generate the mission's self-contained per-phase HTML report **in the built tree, before staging the squash-merge** — `pan-tools report phase <N>` — so the report rides along in the merge as a phase deliverable. **Never run `report index` inside a squad worktree:** the timeline index is a single shared file that aggregates *all* phases, so a worktree would see only its own phase and concurrent squads would race on it. The index is a single-writer, post-merge concern (Phase 6). Never opens a browser.
|
|
129
|
+
|
|
128
130
|
### Phase 6 — Learn (Dreaming)
|
|
129
131
|
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
132
|
|
|
133
|
+
**Rebuild the timeline index (single writer).** When `workflow.phase_reports.enabled` and `workflow.phase_reports.index` are `true`, Mission Control — and *only* Mission Control, on the integration branch after the merge has landed — rebuilds the project index once against the now-merged set of phases: `pan-tools report index`, then commit it (the commit honors `commit_docs`). Doing this post-merge from the single conductor is what keeps `report-index.html` consistent while builds run in parallel worktrees.
|
|
134
|
+
|
|
131
135
|
---
|
|
132
136
|
|
|
133
137
|
## Scheduled, self-resuming campaigns (ADR-0034)
|
|
@@ -156,7 +156,7 @@ For each target document, extract every verifiable claim.
|
|
|
156
156
|
| Category | Pattern | Example |
|
|
157
157
|
|----------|---------|---------|
|
|
158
158
|
| **Version claim** | `v1.2.3`, `version 1.2`, `Version: X` | "Currently at v3.1.0" |
|
|
159
|
-
| **Count claim** | Number + noun | "42
|
|
159
|
+
| **Count claim** | Number + noun | "42 database tables", "15 API endpoints" |
|
|
160
160
|
| **File path claim** | Path-like strings | "`src/core/handler.ts`" |
|
|
161
161
|
| **Command claim** | Code block with shell prefix or instruction | "`npm run build`" |
|
|
162
162
|
| **Feature claim** | "supports X", "includes Y", "provides Z" | "Supports hot reloading" |
|
package/commands/pan/report.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: pan:report
|
|
3
3
|
group: Observability
|
|
4
4
|
description: Generate a self-contained HTML report for one phase, or a project-level timeline index linking every phase report
|
|
5
|
-
argument-hint: "phase <N> | index | all [--out <file>] [--open] [--stdout]"
|
|
5
|
+
argument-hint: "phase <N> | index [--bundle] | all [--out <file>] [--open] [--stdout]"
|
|
6
6
|
allowed-tools:
|
|
7
7
|
- Read
|
|
8
8
|
- Bash
|
|
@@ -24,7 +24,7 @@ Reports are honest by construction: the `verify reconcile` verdict is shown besi
|
|
|
24
24
|
|
|
25
25
|
```
|
|
26
26
|
pan-tools report phase <N> [--out <file>] [--open] [--stdout]
|
|
27
|
-
pan-tools report index [--out <file>] [--open] [--stdout]
|
|
27
|
+
pan-tools report index [--out <file>] [--open] [--stdout] [--bundle]
|
|
28
28
|
pan-tools report all [--open]
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -37,6 +37,7 @@ pan-tools report all [--open]
|
|
|
37
37
|
- `--out <file>` — write to a custom path instead of the default (relative paths resolve against the project root).
|
|
38
38
|
- `--open` — best-effort: launch the written file in the default browser (cross-platform; silently no-ops if no opener is available, and never opens when nothing was written).
|
|
39
39
|
- `--stdout` — print the HTML to stdout instead of writing a file (for `phase`/`index`).
|
|
40
|
+
- `--bundle` (on `index`) — emit one self-contained `.planning/report-bundle.html`: the timeline plus every phase report inlined under in-page anchors, with no links to sibling files. This is the form to email or attach — a single file that stays whole when moved off-disk.
|
|
40
41
|
|
|
41
42
|
**Behaviour worth knowing:**
|
|
42
43
|
- **Deterministic writes.** Re-running with unchanged phase data rewrites nothing (the only volatile value, the generated-at timestamp, is ignored when comparing) — so reports produce no git churn.
|
|
@@ -61,7 +62,7 @@ pan-tools report all [--open]
|
|
|
61
62
|
|
|
62
63
|
**Review a phase:** run `pan-tools report phase <N> --open` to see one phase's objective, what changed, and its verification verdict at a glance.
|
|
63
64
|
|
|
64
|
-
**Share the project:** `pan-tools report index` builds the timeline entry point — send `.planning/report-index.html` and the linked phase files, or open the index and click through.
|
|
65
|
+
**Share the project:** `pan-tools report index` builds the timeline entry point — send `.planning/report-index.html` and the linked phase files, or open the index and click through. To hand someone a single file instead, `pan-tools report index --bundle` inlines every phase into one `.planning/report-bundle.html` that survives being emailed or moved.
|
|
65
66
|
|
|
66
67
|
**Refresh everything:** `pan-tools report all` after a milestone regenerates every report; unchanged ones are skipped, so only what actually moved is rewritten.
|
|
67
68
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pan-wizard",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.18.0",
|
|
4
4
|
"description": "Command a bot army for your codebase: an Opus Mission Control delegates whole-project goals to specialist squads and ships behind a human merge gate. Five AI CLIs, zero context rot.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pan-wizard": "bin/install.js"
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"agents",
|
|
13
13
|
"hooks/dist",
|
|
14
14
|
"scripts",
|
|
15
|
-
"assets"
|
|
15
|
+
"assets",
|
|
16
|
+
"pan-zcode"
|
|
16
17
|
],
|
|
17
18
|
"keywords": [
|
|
18
19
|
"claude",
|
|
@@ -53,6 +53,8 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
|
|
|
53
53
|
verifier: true,
|
|
54
54
|
nyquist_validation: false,
|
|
55
55
|
phase_record_compact: false,
|
|
56
|
+
// M2: opt-in per-phase HTML reports at the verify→complete gate.
|
|
57
|
+
phase_reports: { enabled: false, open: false, theme: 'auto', index: true },
|
|
56
58
|
},
|
|
57
59
|
parallelization: true,
|
|
58
60
|
brave_search: hasBraveSearch,
|
|
@@ -809,11 +809,48 @@ function focusAutoUpdate(cwd, raw, getVal) {
|
|
|
809
809
|
}, raw);
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
+
/**
|
|
813
|
+
* Whether opt-in HTML phase reports are enabled for this project.
|
|
814
|
+
* Reads the raw config.json (loadConfig doesn't surface the workflow block);
|
|
815
|
+
* absent / malformed config → disabled.
|
|
816
|
+
*/
|
|
817
|
+
function phaseReportsEnabled(cwd) {
|
|
818
|
+
try {
|
|
819
|
+
const raw = JSON.parse(fs.readFileSync(path.join(planningPath(cwd), 'config.json'), 'utf-8'));
|
|
820
|
+
return !!(raw.workflow && raw.workflow.phase_reports && raw.workflow.phase_reports.enabled);
|
|
821
|
+
} catch {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Regenerate every phase report + index as part of a focus-auto checkpoint,
|
|
828
|
+
* when the feature is enabled. Best-effort and side-effect-light: never opens a
|
|
829
|
+
* browser, and silently no-ops for phase-less / focus-auto-only projects. The
|
|
830
|
+
* fresh HTML lands in .planning/ so the checkpoint's `git add` sweeps it in.
|
|
831
|
+
*/
|
|
832
|
+
function maybeRenderPhaseReports(cwd) {
|
|
833
|
+
if (!phaseReportsEnabled(cwd)) return;
|
|
834
|
+
try {
|
|
835
|
+
const { renderAllToDisk } = require('./phase-report.cjs');
|
|
836
|
+
renderAllToDisk(cwd, {});
|
|
837
|
+
} catch {
|
|
838
|
+
// Report generation must never block the checkpoint commit.
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
812
842
|
function focusAutoCheckpointCommit(cwd, cycle, run) {
|
|
813
843
|
if (!isGitRepo(cwd)) return null;
|
|
814
844
|
const config = loadConfig(cwd);
|
|
815
845
|
const autoCommit = config.focus && config.focus.auto_commit !== undefined ? config.focus.auto_commit : true;
|
|
816
846
|
if (!autoCommit) return null;
|
|
847
|
+
// commit_docs is the global switch for committing planning artifacts; focus-auto
|
|
848
|
+
// is a planning-doc committer, so honor it. commit_docs=false → hands the .planning
|
|
849
|
+
// commit (and any report regeneration) back to the user.
|
|
850
|
+
if (config.commit_docs === false) return null;
|
|
851
|
+
// Enabled projects: refresh the HTML reports before staging so the committed
|
|
852
|
+
// .planning/ snapshot reflects this cycle.
|
|
853
|
+
maybeRenderPhaseReports(cwd);
|
|
817
854
|
const status = execGit(cwd, ['status', '--porcelain', PLANNING_DIR + '/']);
|
|
818
855
|
if (status.exitCode !== 0 || !status.stdout) return null;
|
|
819
856
|
execGit(cwd, ['add', PLANNING_DIR + '/']);
|
|
@@ -44,6 +44,7 @@ const cost = require('./cost.cjs');
|
|
|
44
44
|
|
|
45
45
|
const REPORT_SUFFIX = '-report.html';
|
|
46
46
|
const INDEX_FILE = 'report-index.html';
|
|
47
|
+
const BUNDLE_FILE = 'report-bundle.html';
|
|
47
48
|
|
|
48
49
|
// ─── small helpers ─────────────────────────────────────────────────────────────
|
|
49
50
|
|
|
@@ -548,7 +549,8 @@ function indexHero(d) {
|
|
|
548
549
|
</section>`;
|
|
549
550
|
}
|
|
550
551
|
|
|
551
|
-
function indexTimeline(d) {
|
|
552
|
+
function indexTimeline(d, hrefFn) {
|
|
553
|
+
const href = hrefFn || ((p) => p.href);
|
|
552
554
|
const rows = d.phases.map((p, i) => {
|
|
553
555
|
const color = STATUS_DOT[p.status] || '#C9C0AE';
|
|
554
556
|
const isLast = i === d.phases.length - 1;
|
|
@@ -560,7 +562,7 @@ function indexTimeline(d) {
|
|
|
560
562
|
return `
|
|
561
563
|
<div class="trow">
|
|
562
564
|
<div class="rail"><span class="rd" style="background:${color}"></span>${isLast ? '' : `<span class="rl" style="background:${railColor}"></span>`}</div>
|
|
563
|
-
<a class="tcard" href="${esc(p
|
|
565
|
+
<a class="tcard" href="${esc(href(p))}">
|
|
564
566
|
<div>
|
|
565
567
|
<div class="tt">${esc(p.number)}${p.name ? ' · ' + esc(p.name) : ''}</div>
|
|
566
568
|
<div class="tsub"><span class="mini">${mini}</span><span>${p.plansTotal ? p.plansDone + ' / ' + p.plansTotal + ' plans' : 'no plans'}</span>${p.has_report ? '' : '<span>· report pending</span>'}</div>
|
|
@@ -577,6 +579,33 @@ function renderIndexHtml(d) {
|
|
|
577
579
|
return docShell(`PanWizard · ${d.project} — timeline`, body, d.generated_at);
|
|
578
580
|
}
|
|
579
581
|
|
|
582
|
+
/**
|
|
583
|
+
* Bundle: the timeline index plus EVERY phase report inlined into one
|
|
584
|
+
* self-contained document, cross-linked by in-page #phase-NN anchors. Because
|
|
585
|
+
* navigation is anchors (not sibling files), the links never dangle when the
|
|
586
|
+
* single file is emailed or copied on its own. (indexData, phaseDatas) are pure.
|
|
587
|
+
*/
|
|
588
|
+
function renderBundleHtml(indexData, phaseDatas) {
|
|
589
|
+
const anchorHref = (p) => '#phase-' + encodeURIComponent(String(p.number));
|
|
590
|
+
const sections = (phaseDatas || []).filter(Boolean).map(d => `
|
|
591
|
+
<section id="phase-${esc(d.phase.number)}" style="margin-top:26px;border-top:1px solid var(--border);padding-top:8px">
|
|
592
|
+
<div class="crumbs"><a href="#top">↑ back to timeline</a></div>
|
|
593
|
+
${reportHero(d)}
|
|
594
|
+
${reportMetrics(d)}
|
|
595
|
+
<div class="grid"><div class="gcol">${reportObjective(d)}</div><div class="gcol">${reportVerification(d)}</div></div>
|
|
596
|
+
${reportChanges(d)}
|
|
597
|
+
${reportGaps(d)}
|
|
598
|
+
</section>`).join('\n');
|
|
599
|
+
const body = [
|
|
600
|
+
'<span id="top"></span>',
|
|
601
|
+
indexTopBar(indexData),
|
|
602
|
+
indexHero(indexData),
|
|
603
|
+
indexTimeline(indexData, anchorHref),
|
|
604
|
+
sections,
|
|
605
|
+
].filter(Boolean).join('\n');
|
|
606
|
+
return docShell(`PanWizard · ${indexData.project} — full report`, body, indexData.generated_at);
|
|
607
|
+
}
|
|
608
|
+
|
|
580
609
|
// ─── side effects (the only impure layer) ────────────────────────────────────────
|
|
581
610
|
|
|
582
611
|
/**
|
|
@@ -667,6 +696,19 @@ function cmdReport(cwd, opts = {}, raw) {
|
|
|
667
696
|
if (action === 'index') {
|
|
668
697
|
const data = collectIndexData(cwd, { now });
|
|
669
698
|
if (!data) return error('No phases found — nothing to index (phase-less / focus-auto project). Use `pan-tools hud` instead.');
|
|
699
|
+
if (opts.bundle) {
|
|
700
|
+
const phaseDatas = data.phases.map(p => collectPhaseData(cwd, p.number, { now })).filter(Boolean);
|
|
701
|
+
const html = renderBundleHtml(data, phaseDatas);
|
|
702
|
+
if (opts.stdout) { process.stdout.write(html); return; }
|
|
703
|
+
const outPath = opts.out ? path.resolve(cwd, opts.out) : path.join(planningPath(cwd), BUNDLE_FILE);
|
|
704
|
+
const res = writeIfChanged(outPath, html);
|
|
705
|
+
const opened = opts.open && res.written ? openInBrowser(outPath) : false;
|
|
706
|
+
return output(
|
|
707
|
+
{ action: 'bundle', phases: phaseDatas.length, path: toPosix(outPath), bytes: Buffer.byteLength(html), written: res.written, opened },
|
|
708
|
+
raw,
|
|
709
|
+
`bundle ${res.written ? 'written' : 'unchanged'}: ${toPosix(outPath)}`,
|
|
710
|
+
);
|
|
711
|
+
}
|
|
670
712
|
const html = renderIndexHtml(data);
|
|
671
713
|
if (opts.stdout) { process.stdout.write(html); return; }
|
|
672
714
|
const outPath = opts.out ? path.resolve(cwd, opts.out) : path.join(planningPath(cwd), INDEX_FILE);
|
|
@@ -680,42 +722,62 @@ function cmdReport(cwd, opts = {}, raw) {
|
|
|
680
722
|
}
|
|
681
723
|
|
|
682
724
|
if (action === 'all') {
|
|
683
|
-
const
|
|
684
|
-
if (!
|
|
685
|
-
const reports = [];
|
|
686
|
-
for (const dirName of dirs) {
|
|
687
|
-
const { number } = parsePhaseDir(dirName);
|
|
688
|
-
const data = collectPhaseData(cwd, number, { now });
|
|
689
|
-
if (!data) continue;
|
|
690
|
-
const outPath = path.join(data.phase.dir, `${data.phase.number}${REPORT_SUFFIX}`);
|
|
691
|
-
const res = writeIfChanged(outPath, renderPhaseHtml(data));
|
|
692
|
-
reports.push({ phase: data.phase.number, path: toPosix(outPath), written: res.written });
|
|
693
|
-
}
|
|
694
|
-
const idx = collectIndexData(cwd, { now });
|
|
695
|
-
let index = { path: null, written: false };
|
|
696
|
-
if (idx) {
|
|
697
|
-
const outPath = path.join(planningPath(cwd), INDEX_FILE);
|
|
698
|
-
const res = writeIfChanged(outPath, renderIndexHtml(idx));
|
|
699
|
-
index = { path: toPosix(outPath), written: res.written };
|
|
700
|
-
}
|
|
725
|
+
const res = renderAllToDisk(cwd, { now });
|
|
726
|
+
if (!res) return error('No phases found — nothing to report.');
|
|
701
727
|
return output(
|
|
702
|
-
{ action, reports, index },
|
|
728
|
+
{ action, reports: res.reports, index: res.index },
|
|
703
729
|
raw,
|
|
704
|
-
`generated ${reports.length} phase report(s)${index.path ? ' + index' : ''}`,
|
|
730
|
+
`generated ${res.reports.length} phase report(s)${res.index.path ? ' + index' : ''}`,
|
|
705
731
|
);
|
|
706
732
|
}
|
|
707
733
|
|
|
708
734
|
return error('Unknown report action. Available: phase <N>, index, all');
|
|
709
735
|
}
|
|
710
736
|
|
|
737
|
+
/**
|
|
738
|
+
* Regenerate every phase report + the timeline index straight to disk.
|
|
739
|
+
* Programmatic sibling of `cmdReport('all')` with NO stdout side effects and
|
|
740
|
+
* no browser opening — used by focus-auto / army in-tree checkpoints, which
|
|
741
|
+
* emit their own JSON and must not have report output interleaved into it.
|
|
742
|
+
* @param {string} cwd
|
|
743
|
+
* @param {{now?:Date}} opts
|
|
744
|
+
* @returns {{reports:Array<{phase:string,path:string,written:boolean}>, index:{path:string|null,written:boolean}}|null}
|
|
745
|
+
* null when the project has no phases (phase-less / focus-auto-only).
|
|
746
|
+
*/
|
|
747
|
+
function renderAllToDisk(cwd, opts = {}) {
|
|
748
|
+
const now = opts.now;
|
|
749
|
+
const dirs = listPhaseDirs(cwd);
|
|
750
|
+
if (!dirs.length) return null;
|
|
751
|
+
const reports = [];
|
|
752
|
+
for (const dirName of dirs) {
|
|
753
|
+
const { number } = parsePhaseDir(dirName);
|
|
754
|
+
const data = collectPhaseData(cwd, number, { now });
|
|
755
|
+
if (!data) continue;
|
|
756
|
+
const outPath = path.join(data.phase.dir, `${data.phase.number}${REPORT_SUFFIX}`);
|
|
757
|
+
const res = writeIfChanged(outPath, renderPhaseHtml(data));
|
|
758
|
+
reports.push({ phase: data.phase.number, path: toPosix(outPath), written: res.written });
|
|
759
|
+
}
|
|
760
|
+
const idx = collectIndexData(cwd, { now });
|
|
761
|
+
let index = { path: null, written: false };
|
|
762
|
+
if (idx) {
|
|
763
|
+
const outPath = path.join(planningPath(cwd), INDEX_FILE);
|
|
764
|
+
const res = writeIfChanged(outPath, renderIndexHtml(idx));
|
|
765
|
+
index = { path: toPosix(outPath), written: res.written };
|
|
766
|
+
}
|
|
767
|
+
return { reports, index };
|
|
768
|
+
}
|
|
769
|
+
|
|
711
770
|
module.exports = {
|
|
712
771
|
REPORT_SUFFIX,
|
|
713
772
|
INDEX_FILE,
|
|
773
|
+
BUNDLE_FILE,
|
|
714
774
|
collectPhaseData,
|
|
715
775
|
collectIndexData,
|
|
716
776
|
renderPhaseHtml,
|
|
717
777
|
renderIndexHtml,
|
|
778
|
+
renderBundleHtml,
|
|
718
779
|
cmdReport,
|
|
780
|
+
renderAllToDisk,
|
|
719
781
|
// exported for focused unit tests
|
|
720
782
|
resolvePhase,
|
|
721
783
|
extractPhaseGoal,
|
|
@@ -890,6 +890,7 @@ async function main() {
|
|
|
890
890
|
out: getArgValue(args, '--out'),
|
|
891
891
|
open: args.includes('--open'),
|
|
892
892
|
stdout: args.includes('--stdout'),
|
|
893
|
+
bundle: args.includes('--bundle'),
|
|
893
894
|
}, raw);
|
|
894
895
|
} else if (subcommand === 'all') {
|
|
895
896
|
phaseReport.cmdReport(cwd, { action: 'all', open: args.includes('--open') }, raw);
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
"verifier": true,
|
|
8
8
|
"auto_advance": false,
|
|
9
9
|
"nyquist_validation": false,
|
|
10
|
-
"phase_record_compact": false
|
|
10
|
+
"phase_record_compact": false,
|
|
11
|
+
"phase_reports": { "enabled": false, "open": false, "theme": "auto", "index": true }
|
|
11
12
|
},
|
|
12
13
|
"planning": {
|
|
13
14
|
"commit_docs": true,
|
|
@@ -602,8 +602,24 @@ The CLI handles:
|
|
|
602
602
|
|
|
603
603
|
Extract from result: `next_phase`, `next_phase_name`, `is_last_phase`.
|
|
604
604
|
|
|
605
|
+
**Phase reports (opt-in build deliverable):** when `workflow.phase_reports.enabled` is `true`, generate the self-contained per-phase HTML report — and, when `workflow.phase_reports.index` is `true`, the project timeline index — at this verify→complete gate so they ship with the phase and ride the commit below. Disabled by default; phase-less projects are skipped automatically by `report`. Never opens a browser here (that's reserved for a manual `pan-tools report --open`).
|
|
606
|
+
|
|
607
|
+
```bash
|
|
608
|
+
REPORT_FILES=""
|
|
609
|
+
if [ "$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-get workflow.phase_reports.enabled 2>/dev/null || echo false)" = "true" ]; then
|
|
610
|
+
if node ~/.claude/pan-wizard-core/bin/pan-tools.cjs report phase "${PHASE_NUMBER}" >/dev/null 2>&1; then
|
|
611
|
+
REPORT_FILES="{phase_dir}/*-report.html"
|
|
612
|
+
if [ "$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-get workflow.phase_reports.index 2>/dev/null || echo true)" = "true" ]; then
|
|
613
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs report index >/dev/null 2>&1 && REPORT_FILES="$REPORT_FILES .planning/report-index.html"
|
|
614
|
+
fi
|
|
615
|
+
fi
|
|
616
|
+
fi
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
The report files are appended to the completion commit's `--files` list (`$REPORT_FILES`, empty when disabled), so `cmdCommit` still honors `commit_docs` + gitignore + safety checks with no new commit logic:
|
|
620
|
+
|
|
605
621
|
```bash
|
|
606
|
-
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs commit "docs(phase-{X}): complete phase execution" --files .planning/roadmap.md .planning/state.md .planning/requirements.md {phase_dir}/*-verification.md
|
|
622
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs commit "docs(phase-{X}): complete phase execution" --files .planning/roadmap.md .planning/state.md .planning/requirements.md {phase_dir}/*-verification.md $REPORT_FILES
|
|
607
623
|
```
|
|
608
624
|
|
|
609
625
|
**Circular optimization — finalize trace session:**
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# PAN-Z — Known Beta Risks & M0 Open Questions
|
|
2
|
+
|
|
3
|
+
ZCode is a fast-moving, closed Beta: three subsystems PAN-Z touches (subagent
|
|
4
|
+
frontmatter, plugin/skill layout, MCP config path) changed within a single ~12-day
|
|
5
|
+
window on ZCode's changelog. This ledger tracks what the design deliberately does **not**
|
|
6
|
+
hard-depend on, and the go/no-go facts that can only be settled on a real ZCode install
|
|
7
|
+
(the **M0 verify spike**). Re-check these before trusting any on-disk format.
|
|
8
|
+
|
|
9
|
+
## M0 — go/no-go (settle empirically on a real ZCode)
|
|
10
|
+
|
|
11
|
+
| # | Question | If NO → fallback (already in the design) |
|
|
12
|
+
|---|----------|------------------------------------------|
|
|
13
|
+
| 1 | Can a **subagent call MCP tools**? | Primary Agent makes every `pan-mcp` call and feeds results into subagent prompts. Determinism unaffected. |
|
|
14
|
+
| 2 | Are **local stdio MCP calls metered** against the MCP-Calls quota? | Read verbs are MCP *resources*, not tools; batch/cache; never make a hosted z.ai MCP load-bearing. |
|
|
15
|
+
| 3 | Exact **subagent frontmatter schema** / plugin layout / MCP config path? | Drive ZCode's own Import / Settings; treat `pan-zcode/lib/convert-agent.cjs` output as a labelled fallback, not a contract. |
|
|
16
|
+
| 4 | Any **file-based / headless** way to register an MCP server, or GUI-only? | `install-zcode.js` emits a bundle + `INSTALL-ZCODE.md`; the human finishes in ZCode's UI. |
|
|
17
|
+
| 5 | Any **lifecycle hook** that can hard-gate a spawn? | Enforce caps at the MCP-tool-call boundary (the orchestrator), not pre-spawn. |
|
|
18
|
+
|
|
19
|
+
## Standing constraints (confirmed by the review)
|
|
20
|
+
|
|
21
|
+
- **The merge gate is not self-sufficient.** Under ZCode Full Access, a raw Bash
|
|
22
|
+
`git push` bypasses `pan_confirm_merge`. Non-bypassability rests on **server-side
|
|
23
|
+
branch protection**; the MCP gate is the second, in-process lock. `INSTALL-ZCODE.md`
|
|
24
|
+
mandates branch protection and "never Full Access during install."
|
|
25
|
+
- **No subagent nesting.** The army flattens to one delegation layer (PAN already caps
|
|
26
|
+
nesting at 2, so this is tolerable). `Task` is dropped from ported subagents.
|
|
27
|
+
- **User-global subagents only.** No repo-scoped rosters or per-project model profiles.
|
|
28
|
+
- **Genuinely lost:** scheduled self-resuming multi-day campaigns (no headless/daemon),
|
|
29
|
+
background execution, committable permissions, and custom slash-commands.
|
|
30
|
+
|
|
31
|
+
## Format-drift policy
|
|
32
|
+
|
|
33
|
+
Do **not** rely on a passive "write the file and hope" strategy. All three churning
|
|
34
|
+
formats are GUI-owned implementation details: prefer ZCode's authoring surfaces, keep the
|
|
35
|
+
converter output clearly labelled best-effort, and re-verify against this ledger on every
|
|
36
|
+
ZCode version bump.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# PAN-Z — a ZCode-native PAN subsystem
|
|
2
|
+
|
|
3
|
+
PAN-Z gives [ZCode](https://zcode.z.ai) (z.ai's GLM-5.2 coding harness) the PAN Wizard
|
|
4
|
+
workflow — the multi-phase lifecycle, the bot-army with a human merge gate, deterministic
|
|
5
|
+
state tracking — **without** cloning PAN's slash-commands or hooks (ZCode has neither).
|
|
6
|
+
|
|
7
|
+
Instead of porting the command surface, PAN-Z **reuses PAN's engine in place** and reaches
|
|
8
|
+
ZCode through the one interface it speaks: **MCP**.
|
|
9
|
+
|
|
10
|
+
> Design + verified feasibility: [`docs/specs/pan_zcode_mcp_bridge_featureai.md`](../docs/specs/pan_zcode_mcp_bridge_featureai.md).
|
|
11
|
+
> The architecture was confirmed by a 38-agent adversarial review; four optimistic
|
|
12
|
+
> assumptions were refuted and their fixes folded into the design.
|
|
13
|
+
|
|
14
|
+
## How it fits
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
ZCode harness (GLM-5.2) primary Agent drives everything; ported subagents fan out
|
|
18
|
+
│ MCP · local stdio
|
|
19
|
+
pan-zcode/mcp (this subsystem) a thin, zero-dep bridge — verbs → MCP tools/resources
|
|
20
|
+
│ spawn: node pan-tools.cjs <verb> --raw --cwd <root>
|
|
21
|
+
pan-wizard-core (reused as-is) the deterministic engine; .planning/ stays the state store
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Status — M1–M5 built (M0 is the human verify spike)
|
|
25
|
+
|
|
26
|
+
- **M1 — bridge core.** `mcp/tool-registry.cjs` (pure verb→tool/resource map, with a hard
|
|
27
|
+
guardrail against exposing a force/reset/rebase/push verb) + `mcp/server.cjs` (a
|
|
28
|
+
**zero-dependency** JSON-RPC 2.0 stdio MCP server; reads → resources, actions → tools with
|
|
29
|
+
accurate hints; shell-less `execFile` spawn; `@file:` overflow protocol; protocol-version
|
|
30
|
+
negotiation; strict per-arg validation).
|
|
31
|
+
- **M2 — determinism grafts.** `mcp/merge-gate.cjs` (two-step, model-proof merge: a human-origin
|
|
32
|
+
env token that ignores agent-supplied approval; never force/reset/push) + `mcp/orchestrator.cjs`
|
|
33
|
+
(the deterministic `next-action` state machine with safety caps + regression circuit-breaker),
|
|
34
|
+
exposed as native MCP tools via `mcp/native-tools.cjs`.
|
|
35
|
+
- **M3 — content port.** `lib/convert-agent.cjs` — Claude agents → ZCode subagents (reusing the
|
|
36
|
+
installer's frontmatter helpers): drops `Task` (no nesting), maps PAN tiers → `inherit`,
|
|
37
|
+
preserves the body; plus a command → skill wrapper.
|
|
38
|
+
- **M4 — bundle + install.** `bin/install-zcode.js` — assembles `agents/` + `pan-mcp.json` +
|
|
39
|
+
manifest + `INSTALL-ZCODE.md` into a `--target` dir; refuses to write inside the source repo;
|
|
40
|
+
drives ZCode's own Import to finish.
|
|
41
|
+
- **M5 — hardening + docs.** Full test matrix + [`KNOWN-BETA-RISKS.md`](KNOWN-BETA-RISKS.md)
|
|
42
|
+
(the beta-churn ledger + the M0 go/no-go questions).
|
|
43
|
+
|
|
44
|
+
Tests: `tests/pan-zcode-mcp.test.cjs`, `tests/pan-zcode-orchestration.test.cjs`,
|
|
45
|
+
`tests/pan-zcode-install.test.cjs`.
|
|
46
|
+
|
|
47
|
+
## Still pending — M0 (needs a real ZCode install)
|
|
48
|
+
|
|
49
|
+
Two go/no-go facts can only be settled empirically: **can a subagent call MCP tools?** and **are
|
|
50
|
+
local stdio MCP calls metered?** Both have folded-in fallbacks (see `KNOWN-BETA-RISKS.md`), so the
|
|
51
|
+
design holds either way — but confirm them before relying on the richer paths.
|
|
52
|
+
|
|
53
|
+
## Zero dependencies
|
|
54
|
+
|
|
55
|
+
Like the rest of PAN, this subsystem ships **no runtime dependencies**. The MCP protocol is
|
|
56
|
+
implemented directly rather than via an SDK. If protocol drift ever makes that costly, the
|
|
57
|
+
escape hatch is to vendor an MCP SDK **inside this package only**, leaving `pan-wizard-core`
|
|
58
|
+
untouched.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PAN-Z M4 — assemble the ZCode bundle and emit a pull-based install.
|
|
6
|
+
*
|
|
7
|
+
* Produces, into an explicit --target directory:
|
|
8
|
+
* agents/*.md PAN agents converted to ZCode subagents (best-effort)
|
|
9
|
+
* pan-mcp.json the MCP server registration (command/args/env)
|
|
10
|
+
* pan-zcode-manifest.json what was generated
|
|
11
|
+
* INSTALL-ZCODE.md how to finish setup inside ZCode
|
|
12
|
+
*
|
|
13
|
+
* It NEVER writes to a real ~/.zcode on its own and NEVER writes inside the PAN
|
|
14
|
+
* source repo — ZCode's on-disk formats churn weekly, so the human finishes setup by
|
|
15
|
+
* driving ZCode's own "Import from Claude Code" / Settings surfaces using this bundle.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { convertAgentsDir } = require('../lib/convert-agent.cjs');
|
|
21
|
+
|
|
22
|
+
/** The MCP registration ZCode reads (Claude-compatible: command/args/env). */
|
|
23
|
+
function mcpServerConfig(panToolsPath, serverPath, projectRoot) {
|
|
24
|
+
return {
|
|
25
|
+
mcpServers: {
|
|
26
|
+
'pan-mcp': {
|
|
27
|
+
command: 'node',
|
|
28
|
+
args: [serverPath],
|
|
29
|
+
env: { PAN_TOOLS_PATH: panToolsPath, PAN_PROJECT_ROOT: projectRoot || '.' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const INSTRUCTIONS = `# Install PAN-Z into ZCode
|
|
36
|
+
|
|
37
|
+
PAN-Z bridges PAN's engine to ZCode over MCP. Finish setup inside ZCode:
|
|
38
|
+
|
|
39
|
+
1. **Register the MCP server.** Add the contents of \`pan-mcp.json\` to ZCode's MCP
|
|
40
|
+
configuration (Settings → MCP), or point ZCode's MCP config at this file. This
|
|
41
|
+
exposes the \`pan_*\` tools (state, plan, verify, report, next-action, merge gate).
|
|
42
|
+
|
|
43
|
+
2. **Import the subagents.** The \`agents/\` folder holds PAN agents in ZCode subagent
|
|
44
|
+
form. Prefer ZCode's **Import from Claude Code** / Settings → Subagents to register
|
|
45
|
+
them, since ZCode owns the on-disk format (it is a fast-moving Beta). These files are
|
|
46
|
+
a fallback, not a contract.
|
|
47
|
+
|
|
48
|
+
3. **Protect your branches.** The merge gate (\`pan_confirm_merge\`) is one lock; the
|
|
49
|
+
real, non-bypassable one is **server-side branch protection** on your remote. Enable
|
|
50
|
+
required reviews so a raw push under Full Access cannot merge. Approve a staged merge
|
|
51
|
+
by setting \`PAN_MERGE_APPROVAL=<request-id>\` in the MCP server's environment.
|
|
52
|
+
|
|
53
|
+
4. **Never run the install/import step in ZCode Full Access mode.**
|
|
54
|
+
|
|
55
|
+
Not yet resolved (the M0 verify spike, on your real ZCode): whether a subagent can call
|
|
56
|
+
MCP tools, and whether local stdio MCP calls are metered. Until confirmed, the primary
|
|
57
|
+
Agent makes every pan-mcp call.
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
/** Real path of the nearest EXISTING ancestor of p, with the not-yet-created tail re-attached. */
|
|
61
|
+
function realpathNearest(p) {
|
|
62
|
+
const tail = [];
|
|
63
|
+
let cur = path.resolve(p);
|
|
64
|
+
for (;;) {
|
|
65
|
+
try {
|
|
66
|
+
const real = fs.realpathSync(cur);
|
|
67
|
+
return tail.length ? path.join(real, ...tail) : real;
|
|
68
|
+
} catch {
|
|
69
|
+
const parent = path.dirname(cur);
|
|
70
|
+
if (parent === cur) return path.resolve(p); // hit the root; nothing existed
|
|
71
|
+
tail.unshift(path.basename(cur));
|
|
72
|
+
cur = parent;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function assertNotInSourceRepo(destDir, repoRoot) {
|
|
78
|
+
// Resolve symlinks/junctions (a --target under a junction into the repo would else
|
|
79
|
+
// slip past) and case-fold on case-insensitive filesystems (NTFS/APFS) so a
|
|
80
|
+
// case-variant path can't bypass the guard.
|
|
81
|
+
const ci = process.platform === 'win32' || process.platform === 'darwin';
|
|
82
|
+
const norm = (p) => { const r = realpathNearest(p); return ci ? r.toLowerCase() : r; };
|
|
83
|
+
const d = norm(destDir);
|
|
84
|
+
const r = norm(repoRoot);
|
|
85
|
+
if (d === r || d.startsWith(r + path.sep)) {
|
|
86
|
+
throw new Error('Refusing to write the PAN-Z bundle inside the PAN source repo. Choose a --target outside it.');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Build the bundle.
|
|
92
|
+
* @param {{repoRoot:string, destDir:string, projectRoot?:string}} o
|
|
93
|
+
* @returns {{agents:number, destDir:string, mcp:Object, files:string[]}}
|
|
94
|
+
*/
|
|
95
|
+
function buildBundle(o) {
|
|
96
|
+
const repoRoot = path.resolve(o.repoRoot);
|
|
97
|
+
const destDir = path.resolve(o.destDir);
|
|
98
|
+
assertNotInSourceRepo(destDir, repoRoot);
|
|
99
|
+
|
|
100
|
+
const agentsSrc = path.join(repoRoot, 'agents');
|
|
101
|
+
const serverPath = path.join(repoRoot, 'pan-zcode', 'mcp', 'server.cjs');
|
|
102
|
+
const panToolsPath = path.join(repoRoot, 'pan-wizard-core', 'bin', 'pan-tools.cjs');
|
|
103
|
+
|
|
104
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
105
|
+
const agents = fs.existsSync(agentsSrc) ? convertAgentsDir(agentsSrc, path.join(destDir, 'agents')) : [];
|
|
106
|
+
|
|
107
|
+
const mcp = mcpServerConfig(panToolsPath, serverPath, o.projectRoot);
|
|
108
|
+
fs.writeFileSync(path.join(destDir, 'pan-mcp.json'), JSON.stringify(mcp, null, 2), 'utf8');
|
|
109
|
+
|
|
110
|
+
const manifest = {
|
|
111
|
+
subsystem: 'pan-zcode',
|
|
112
|
+
agents: agents.map((a) => a.name),
|
|
113
|
+
mcp_config: 'pan-mcp.json',
|
|
114
|
+
server: serverPath,
|
|
115
|
+
pan_tools: panToolsPath,
|
|
116
|
+
};
|
|
117
|
+
fs.writeFileSync(path.join(destDir, 'pan-zcode-manifest.json'), JSON.stringify(manifest, null, 2), 'utf8');
|
|
118
|
+
fs.writeFileSync(path.join(destDir, 'INSTALL-ZCODE.md'), INSTRUCTIONS, 'utf8');
|
|
119
|
+
|
|
120
|
+
return { agents: agents.length, destDir, mcp, files: ['agents/', 'pan-mcp.json', 'pan-zcode-manifest.json', 'INSTALL-ZCODE.md'] };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getArg(args, name) {
|
|
124
|
+
const i = args.indexOf(name);
|
|
125
|
+
return i !== -1 && i + 1 < args.length ? args[i + 1] : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function main() {
|
|
129
|
+
const args = process.argv.slice(2);
|
|
130
|
+
const target = getArg(args, '--target');
|
|
131
|
+
if (!target) {
|
|
132
|
+
console.error('Usage: install-zcode.js --target <dir> [--project-root <dir>]');
|
|
133
|
+
console.error('Writes the PAN-Z ZCode bundle to <dir> (outside the PAN source repo).');
|
|
134
|
+
process.exit(2);
|
|
135
|
+
}
|
|
136
|
+
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
137
|
+
try {
|
|
138
|
+
const res = buildBundle({ repoRoot, destDir: target, projectRoot: getArg(args, '--project-root') });
|
|
139
|
+
console.log(`pan-zcode bundle written to ${res.destDir} (${res.agents} subagents + MCP config).`);
|
|
140
|
+
console.log('Next: open INSTALL-ZCODE.md and finish setup inside ZCode.');
|
|
141
|
+
} catch (e) {
|
|
142
|
+
console.error(`install-zcode: ${e.message}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (require.main === module) main();
|
|
148
|
+
|
|
149
|
+
module.exports = { buildBundle, mcpServerConfig, assertNotInSourceRepo };
|