pan-wizard 3.32.0 → 3.33.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/agents/pan-conductor.md +2 -2
- package/agents/pan-document_code.md +2 -2
- package/agents/pan-executor.md +3 -3
- package/agents/pan-experiment-runner.md +7 -10
- package/agents/pan-hardener.md +15 -15
- package/agents/pan-knowledge.md +1 -1
- package/agents/pan-meta-reviewer.md +2 -2
- package/agents/pan-optimizer.md +2 -2
- package/agents/pan-phase-researcher.md +2 -2
- package/agents/pan-plan-checker.md +6 -2
- package/agents/pan-planner.md +23 -10
- package/agents/pan-project-researcher.md +10 -67
- package/agents/pan-roadmapper.md +5 -48
- package/agents/pan-verifier.md +26 -11
- package/bin/install-lib.cjs +170 -15
- package/bin/install.js +39 -14
- package/commands/pan/army.md +2 -2
- package/commands/pan/experiment.md +2 -2
- package/commands/pan/focus-auto.md +12 -8
- package/commands/pan/focus-design.md +2 -0
- package/commands/pan/focus-exec.md +1 -1
- package/commands/pan/focus-scan.md +2 -7
- package/commands/pan/map-codebase.md +4 -4
- package/commands/pan/mcp-bridge.md +1 -1
- package/commands/pan/milestone-done.md +1 -2
- package/commands/pan/phase-tests.md +2 -2
- package/commands/pan/profile.md +2 -2
- package/commands/pan/research-phase.md +2 -4
- package/commands/pan/review-deep.md +3 -3
- package/commands/pan/todo-add.md +2 -2
- package/commands/pan/todo-check.md +2 -2
- package/hooks/dist/pan-state-reinject.js +140 -15
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +4 -1
- package/pan-wizard-core/bin/lib/commands.cjs +93 -1
- package/pan-wizard-core/bin/lib/config.cjs +110 -0
- package/pan-wizard-core/bin/lib/constants.cjs +11 -9
- package/pan-wizard-core/bin/lib/core.cjs +27 -7
- package/pan-wizard-core/bin/lib/cost.cjs +13 -3
- package/pan-wizard-core/bin/lib/findings.cjs +6 -2
- package/pan-wizard-core/bin/lib/init.cjs +5 -1
- package/pan-wizard-core/bin/lib/phase.cjs +52 -2
- package/pan-wizard-core/bin/lib/review-deep.cjs +2 -2
- package/pan-wizard-core/bin/lib/state.cjs +3 -2
- package/pan-wizard-core/bin/lib/verdict.cjs +54 -4
- package/pan-wizard-core/bin/lib/verify-drift.cjs +14 -5
- package/pan-wizard-core/bin/lib/verify-scope.cjs +69 -2
- package/pan-wizard-core/bin/lib/verify.cjs +87 -7
- package/pan-wizard-core/bin/pan-tools.cjs +10 -2
- package/pan-wizard-core/mcp/server.cjs +18 -9
- package/pan-wizard-core/references/checkpoints.md +4 -2
- package/pan-wizard-core/references/verdict-contract.md +2 -1
- package/pan-wizard-core/templates/discovery.md +1 -1
- package/pan-wizard-core/templates/phase-prompt.md +2 -1
- package/pan-wizard-core/templates/verification-report.md +5 -0
- package/pan-wizard-core/workflows/exec-phase.md +8 -3
- package/pan-wizard-core/workflows/health.md +1 -0
- package/pan-wizard-core/workflows/new-project.md +1 -1
- package/pan-wizard-core/workflows/quick.md +6 -6
- package/pan-wizard-core/workflows/verify-phase.md +7 -2
- package/scripts/build-agent-plugin.js +19 -5
- package/scripts/test-surface.cjs +4 -0
|
@@ -16,13 +16,14 @@ const {
|
|
|
16
16
|
} = require('./constants.cjs');
|
|
17
17
|
const { planningPath, phasesPath, filterPlanFiles, filterSummaryFiles, fileAccessible, detectPlanningModel } = require('./utils.cjs');
|
|
18
18
|
const { detectForeignPlanningTree } = require('./foreign-planning.cjs');
|
|
19
|
+
const { unknownConfigKeys, nearestConfigKey } = require('./config.cjs');
|
|
19
20
|
// Drift detection lives in verify-drift.cjs; re-exported below so consumers of
|
|
20
21
|
// verify.cjs are unaffected by the decomposition.
|
|
21
22
|
const { runDriftCheck, parseConventionRules, checkFileConventions, calculateDriftScore, getChangedFiles, cmdDriftCheck } = require('./verify-drift.cjs');
|
|
22
23
|
const { collectVerificationStats, countRoadmapPhases, groupGapPatterns, cmdRetro } = require('./verify-retro.cjs');
|
|
23
24
|
const { detectInstalledRuntimes, validateRuntimeInstall, cmdValidateDeployment } = require('./verify-deploy.cjs');
|
|
24
25
|
const { cmdPreflight, cmdDepsValidate } = require('./verify-preflight.cjs');
|
|
25
|
-
const { scopePhase, cmdVerifyScope } = require('./verify-scope.cjs');
|
|
26
|
+
const { scopePhase, cmdVerifyScope, verificationFreshness, cmdVerifyStale } = require('./verify-scope.cjs');
|
|
26
27
|
const { planningRootRel } = require('./planning-root.cjs');
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -116,6 +117,56 @@ function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
|
116
117
|
}, raw, passed ? 'passed' : 'failed');
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// Longer than a signature, a type or a short config snippet: an implementation body.
|
|
121
|
+
const MAX_ACTION_CODE_LINES = 20;
|
|
122
|
+
|
|
123
|
+
/** The line count of the longest fenced code block in a piece of markdown (0 if none). */
|
|
124
|
+
function longestFencedBlock(text) {
|
|
125
|
+
let longest = 0;
|
|
126
|
+
for (const m of String(text).matchAll(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n[ \t]*\2[ \t]*(?=\n|$)/g)) {
|
|
127
|
+
longest = Math.max(longest, m[3].split('\n').length);
|
|
128
|
+
}
|
|
129
|
+
return longest;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// The elements each checkpoint type needs (references/checkpoints.md).
|
|
133
|
+
const CHECKPOINT_REQUIRED = {
|
|
134
|
+
'checkpoint:human-verify': ['what-built', 'how-to-verify'],
|
|
135
|
+
'checkpoint:decision': ['decision', 'options'],
|
|
136
|
+
'checkpoint:human-action': ['action'],
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check one checkpoint task against its type's shape. A decision checkpoint may
|
|
141
|
+
* carry `auto_select="<option id>"`: the option auto mode takes. Without one, auto
|
|
142
|
+
* mode stops for a human instead of taking whichever option is listed first
|
|
143
|
+
* (market-ideas M22), and an `auto_select` naming no option is an error.
|
|
144
|
+
* @returns {{name: string, type: string, auto_select?: string|null}} the task entry
|
|
145
|
+
*/
|
|
146
|
+
function checkCheckpointTask(type, attrs, body, errors, warnings) {
|
|
147
|
+
const keyText = (body.match(/<(?:decision|what-built|action)>([\s\S]*?)<\/(?:decision|what-built|action)>/) || [])[1];
|
|
148
|
+
const label = keyText ? keyText.trim().replace(/\s+/g, ' ').slice(0, 80) : `unnamed ${type}`;
|
|
149
|
+
const required = CHECKPOINT_REQUIRED[type];
|
|
150
|
+
if (!required) {
|
|
151
|
+
errors.push(`Task '${label}' has unknown checkpoint type '${type}'`);
|
|
152
|
+
return { name: label, type };
|
|
153
|
+
}
|
|
154
|
+
for (const el of required) {
|
|
155
|
+
if (!body.includes(`<${el}>`)) errors.push(`Checkpoint '${label}' missing <${el}>`);
|
|
156
|
+
}
|
|
157
|
+
if (type !== 'checkpoint:decision') return { name: label, type };
|
|
158
|
+
|
|
159
|
+
const optionIds = [...body.matchAll(/<option\b[^>]*\bid=["']([^"']+)["']/g)].map(m => m[1]);
|
|
160
|
+
if (optionIds.length === 0) errors.push(`Decision checkpoint '${label}' has no <option id="...">`);
|
|
161
|
+
const autoMatch = attrs.match(/\bauto_select=["']([^"']*)["']/);
|
|
162
|
+
if (!autoMatch) {
|
|
163
|
+
warnings.push(`Decision checkpoint '${label}' has no auto_select: auto mode stops here for a human`);
|
|
164
|
+
} else if (!optionIds.includes(autoMatch[1])) {
|
|
165
|
+
errors.push(`Decision checkpoint '${label}' auto_select="${autoMatch[1]}" names no option (options: ${optionIds.join(', ') || 'none'})`);
|
|
166
|
+
}
|
|
167
|
+
return { name: label, type, auto_select: autoMatch ? autoMatch[1] : null };
|
|
168
|
+
}
|
|
169
|
+
|
|
119
170
|
/**
|
|
120
171
|
* Validate a plan.md structure: required frontmatter, task elements, and consistency.
|
|
121
172
|
* @param {string} cwd - Working directory path
|
|
@@ -139,14 +190,24 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
139
190
|
if (fm[field] === undefined) errors.push(`Missing required frontmatter field: ${field}`);
|
|
140
191
|
}
|
|
141
192
|
|
|
142
|
-
// Parse XML <task> elements
|
|
143
|
-
// <
|
|
144
|
-
//
|
|
145
|
-
|
|
193
|
+
// Parse XML <task> elements. An auto task needs <name> and <action> and should
|
|
194
|
+
// have <verify>, <done> and <files>. A checkpoint task has its own shape
|
|
195
|
+
// (references/checkpoints.md) and is checked against that instead: until
|
|
196
|
+
// 2026-10-03 every checkpoint failed the auto-task checks, and the `<task[^>]*>`
|
|
197
|
+
// pattern also matched the `<tasks>` wrapper, so a decision's first option name
|
|
198
|
+
// was read as the task's name. `\b` keeps the wrapper out.
|
|
199
|
+
const taskPattern = /<task\b([^>]*)>([\s\S]*?)<\/task>/g;
|
|
146
200
|
const tasks = [];
|
|
147
201
|
let taskMatch;
|
|
148
202
|
while ((taskMatch = taskPattern.exec(content)) !== null) {
|
|
149
|
-
const
|
|
203
|
+
const attrs = taskMatch[1];
|
|
204
|
+
const taskContent = taskMatch[2];
|
|
205
|
+
const typeMatch = attrs.match(/\btype=["']?([\w:-]+)/);
|
|
206
|
+
const taskType = typeMatch ? typeMatch[1] : 'auto';
|
|
207
|
+
if (taskType.startsWith('checkpoint:')) {
|
|
208
|
+
tasks.push(checkCheckpointTask(taskType, attrs, taskContent, errors, warnings));
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
150
211
|
const nameMatch = taskContent.match(/<name>([\s\S]*?)<\/name>/);
|
|
151
212
|
const taskName = nameMatch ? nameMatch[1].trim() : 'unnamed';
|
|
152
213
|
const hasFiles = /<files>/.test(taskContent);
|
|
@@ -156,11 +217,19 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
156
217
|
|
|
157
218
|
if (!nameMatch) errors.push('Task missing <name> element');
|
|
158
219
|
if (!hasAction) errors.push(`Task '${taskName}' missing <action>`);
|
|
220
|
+
// Decisions, not code (market-ideas M32): an <action> names signatures, values,
|
|
221
|
+
// assertions and the verify command. A long fenced block there is an
|
|
222
|
+
// implementation body the planner wrote for the executor.
|
|
223
|
+
const actionText = (taskContent.match(/<action>([\s\S]*?)<\/action>/) || [])[1] || '';
|
|
224
|
+
const longest = longestFencedBlock(actionText);
|
|
225
|
+
if (longest > MAX_ACTION_CODE_LINES) {
|
|
226
|
+
warnings.push(`Task '${taskName}' embeds a ${longest}-line code block in <action>: a plan records decisions (signatures, values, assertions, the verify command), not implementation`);
|
|
227
|
+
}
|
|
159
228
|
if (!hasVerify) warnings.push(`Task '${taskName}' missing <verify>`);
|
|
160
229
|
if (!hasDone) warnings.push(`Task '${taskName}' missing <done>`);
|
|
161
230
|
if (!hasFiles) warnings.push(`Task '${taskName}' missing <files>`);
|
|
162
231
|
|
|
163
|
-
tasks.push({ name: taskName, hasFiles, hasAction, hasVerify, hasDone });
|
|
232
|
+
tasks.push({ name: taskName, type: taskType, hasFiles, hasAction, hasVerify, hasDone });
|
|
164
233
|
}
|
|
165
234
|
|
|
166
235
|
if (tasks.length === 0) warnings.push('No <task> elements found');
|
|
@@ -856,6 +925,15 @@ function checkConfigFile(cwd, addIssue, repairs) {
|
|
|
856
925
|
if (parsed.model_profile && !validProfiles.includes(parsed.model_profile)) {
|
|
857
926
|
addIssue('warning', 'W004', `${CONFIG_FILE}: invalid model_profile "${parsed.model_profile}"`, `Valid values: ${validProfiles.join(', ')}`);
|
|
858
927
|
}
|
|
928
|
+
// I006 (market-ideas M26): keys PAN does not read. Info, not a warning: the key
|
|
929
|
+
// may be the user's own note or meant for a newer PAN, but a typo must not pass
|
|
930
|
+
// as a setting that took effect.
|
|
931
|
+
const unknown = unknownConfigKeys(parsed);
|
|
932
|
+
if (unknown.length) {
|
|
933
|
+
const hints = unknown.map((k) => { const n = nearestConfigKey(k); return n ? `${k} → ${n}?` : k; });
|
|
934
|
+
addIssue('info', 'I006', `${CONFIG_FILE}: PAN does not read ${unknown.length === 1 ? 'this key' : 'these keys'}: ${unknown.join(', ')}`,
|
|
935
|
+
`Check the spelling (${hints.join('; ')}), or remove the key. pan-tools config-set names an unknown key when you set it.`);
|
|
936
|
+
}
|
|
859
937
|
} catch (err) {
|
|
860
938
|
// JSON parse failed -- config is corrupt and should be reset
|
|
861
939
|
addIssue('error', 'E005', `${CONFIG_FILE}: JSON parse error - ${err.message}`, 'Run /pan:health --repair to reset to defaults', true);
|
|
@@ -1558,6 +1636,8 @@ module.exports = {
|
|
|
1558
1636
|
cmdVerifyStubs,
|
|
1559
1637
|
scopePhase,
|
|
1560
1638
|
cmdVerifyScope,
|
|
1639
|
+
verificationFreshness,
|
|
1640
|
+
cmdVerifyStale,
|
|
1561
1641
|
cmdValidateConsistency,
|
|
1562
1642
|
cmdValidateHealth,
|
|
1563
1643
|
cmdPreflight,
|
|
@@ -611,12 +611,19 @@ async function main() {
|
|
|
611
611
|
verify.cmdVerifyStubs(cwd, { gate: args.includes('--gate') }, raw);
|
|
612
612
|
} else if (subcommand === 'scope') {
|
|
613
613
|
verify.cmdVerifyScope(cwd, args[2], raw);
|
|
614
|
+
} else if (subcommand === 'stale') {
|
|
615
|
+
verify.cmdVerifyStale(cwd, args[2], raw);
|
|
614
616
|
} else {
|
|
615
|
-
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope');
|
|
617
|
+
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope, stale');
|
|
616
618
|
}
|
|
617
619
|
break;
|
|
618
620
|
}
|
|
619
621
|
|
|
622
|
+
case 'version': {
|
|
623
|
+
commands.cmdVersion(cwd, { check: args.includes('--check') }, raw);
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
|
|
620
627
|
case 'generate-slug': {
|
|
621
628
|
commands.cmdGenerateSlug(args[1], raw);
|
|
622
629
|
break;
|
|
@@ -844,7 +851,8 @@ async function main() {
|
|
|
844
851
|
}
|
|
845
852
|
|
|
846
853
|
case 'phase-plan-index': {
|
|
847
|
-
|
|
854
|
+
const failedIdx = args.indexOf('--failed');
|
|
855
|
+
phase.cmdPhasePlanIndex(cwd, args[1], raw, { failed: failedIdx !== -1 ? args[failedIdx + 1] : null });
|
|
848
856
|
break;
|
|
849
857
|
}
|
|
850
858
|
|
|
@@ -50,6 +50,15 @@ const META_SERVER_INFO_KEY = 'io.modelcontextprotocol/serverInfo';
|
|
|
50
50
|
// claim to speak a version we don't. Newest first (the `server/discover` order).
|
|
51
51
|
const SUPPORTED_VERSIONS_LIST = [MODERN_PROTOCOL_VERSION, '2025-06-18', '2025-03-26', '2024-11-05'];
|
|
52
52
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set(SUPPORTED_VERSIONS_LIST);
|
|
53
|
+
// The `CacheableResult` fields a modern result MUST carry where its type extends
|
|
54
|
+
// that interface: DiscoverResult, ListToolsResult, ListResourcesResult and
|
|
55
|
+
// ReadResourceResult (schema/2026-07-28/schema.ts). Claude Code 2.1.288 drops a
|
|
56
|
+
// list without them, so the server showed as connected with no tools and no
|
|
57
|
+
// resources (live check H2, 2026-10-03). The registry is fixed for the life of the
|
|
58
|
+
// process, so its lists are public and long-lived; a resource is the project's
|
|
59
|
+
// state at the moment it is read, so a read is private and stale at once.
|
|
60
|
+
const REGISTRY_CACHE = Object.freeze({ ttlMs: 3600000, cacheScope: 'public' });
|
|
61
|
+
const READ_CACHE = Object.freeze({ ttlMs: 0, cacheScope: 'private' });
|
|
53
62
|
/**
|
|
54
63
|
* The version the server reports in `initialize` / `server/discover`. Read from the
|
|
55
64
|
* package.json two levels up: the repository root in the source tree, the runtime
|
|
@@ -320,12 +329,13 @@ function createServer(opts = {}) {
|
|
|
320
329
|
{ supported: SUPPORTED_VERSIONS_LIST, requested: requestedVersion });
|
|
321
330
|
}
|
|
322
331
|
|
|
323
|
-
// Modern results MUST carry a `resultType
|
|
324
|
-
//
|
|
332
|
+
// Modern results MUST carry a `resultType`, a cacheable one its caching fields,
|
|
333
|
+
// and each SHOULD name the server in `_meta`; legacy results MUST NOT change
|
|
334
|
+
// shape (clients treat an absent resultType as "complete"). Stamp them only on
|
|
325
335
|
// the modern path.
|
|
326
|
-
const reply = (result) => rpcResult(id,
|
|
336
|
+
const reply = (result, cache) => rpcResult(id,
|
|
327
337
|
isModern && result && typeof result === 'object' && result.resultType === undefined
|
|
328
|
-
? { resultType: 'complete', ...result }
|
|
338
|
+
? { resultType: 'complete', ...cache, ...result, _meta: { ...result._meta, [META_SERVER_INFO_KEY]: SERVER_INFO } }
|
|
329
339
|
: result);
|
|
330
340
|
|
|
331
341
|
switch (method) {
|
|
@@ -339,8 +349,7 @@ function createServer(opts = {}) {
|
|
|
339
349
|
supportedVersions: SUPPORTED_VERSIONS_LIST,
|
|
340
350
|
capabilities: { tools: {}, resources: {} },
|
|
341
351
|
instructions: 'PAN Wizard engine bridge: planning, verification, and orchestration tools backed by the pan-tools CLI. All tools are read-only except the gated pan_confirm_merge.',
|
|
342
|
-
|
|
343
|
-
cacheScope: 'public',
|
|
352
|
+
...REGISTRY_CACHE,
|
|
344
353
|
_meta: { [META_SERVER_INFO_KEY]: SERVER_INFO },
|
|
345
354
|
});
|
|
346
355
|
case 'initialize': {
|
|
@@ -354,9 +363,9 @@ function createServer(opts = {}) {
|
|
|
354
363
|
case 'ping':
|
|
355
364
|
return reply({});
|
|
356
365
|
case 'tools/list':
|
|
357
|
-
return reply({ tools: reg.TOOLS.map(toMcpTool) });
|
|
366
|
+
return reply({ tools: reg.TOOLS.map(toMcpTool) }, REGISTRY_CACHE);
|
|
358
367
|
case 'resources/list':
|
|
359
|
-
return reply({ resources: reg.RESOURCES.map(toMcpResource) });
|
|
368
|
+
return reply({ resources: reg.RESOURCES.map(toMcpResource) }, REGISTRY_CACHE);
|
|
360
369
|
case 'tools/call': {
|
|
361
370
|
const out = callTool(params && params.name, params && params.arguments);
|
|
362
371
|
return out.error ? rpcError(id, out.error.code, out.error.message) : reply(out.result);
|
|
@@ -364,7 +373,7 @@ function createServer(opts = {}) {
|
|
|
364
373
|
case 'resources/read': {
|
|
365
374
|
const out = readResource(params && params.uri);
|
|
366
375
|
if (out.unknown) return rpcError(id, -32602, `Unknown resource: ${params && params.uri}`);
|
|
367
|
-
return out.error ? rpcError(id, out.error.code, out.error.message) : reply(out.result);
|
|
376
|
+
return out.error ? rpcError(id, out.error.code, out.error.message) : reply(out.result, READ_CACHE);
|
|
368
377
|
}
|
|
369
378
|
default:
|
|
370
379
|
return rpcError(id, -32601, `Method not found: ${method}`);
|
|
@@ -8,7 +8,7 @@ Plans execute autonomously. Checkpoints formalize interaction points where human
|
|
|
8
8
|
2. **Claude sets up the verification environment** - Start dev servers, seed databases, configure env vars
|
|
9
9
|
3. **User only does what requires human judgment** - Visual checks, UX evaluation, "does this feel right?"
|
|
10
10
|
4. **Secrets come from user, automation comes from Claude** - Ask for API keys, then Claude uses them via CLI
|
|
11
|
-
5. **Auto-mode bypasses verification
|
|
11
|
+
5. **Auto-mode bypasses verification checkpoints, and decisions only where the planner named a default** — When `workflow.auto_advance` is true in config: human-verify auto-approves; a decision with `auto_select="<option id>"` takes that option, and a decision without it stops for a human (auto mode never takes an option because it is listed first); human-action still stops (auth gates cannot be automated)
|
|
12
12
|
</overview>
|
|
13
13
|
|
|
14
14
|
<checkpoint_types>
|
|
@@ -105,7 +105,7 @@ Plans execute autonomously. Checkpoints formalize interaction points where human
|
|
|
105
105
|
|
|
106
106
|
**Structure:**
|
|
107
107
|
```xml
|
|
108
|
-
<task type="checkpoint:decision" gate="blocking">
|
|
108
|
+
<task type="checkpoint:decision" gate="blocking" auto_select="option-a">
|
|
109
109
|
<decision>[What's being decided]</decision>
|
|
110
110
|
<context>[Why this decision matters]</context>
|
|
111
111
|
<options>
|
|
@@ -124,6 +124,8 @@ Plans execute autonomously. Checkpoints formalize interaction points where human
|
|
|
124
124
|
</task>
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
**`auto_select` (optional)** names the option auto mode takes. Set it only when the choice is safe to make without the user: reversible, no new paid service, no credential, no data-model lock-in. It must name an `id` from this checkpoint's options; `verify plan-structure` fails one that names no option. Leave it out when a human must choose, and auto mode stops at this checkpoint. The examples below omit it: picking a vendor or a database is the user's call.
|
|
128
|
+
|
|
127
129
|
**Example: Auth Provider Selection**
|
|
128
130
|
```xml
|
|
129
131
|
<task type="checkpoint:decision" gate="blocking">
|
|
@@ -26,6 +26,7 @@ A judging agent ends its report with one fenced `pan-verdict` block. The block i
|
|
|
26
26
|
| `verdict` | yes | your own verdict word (table below) |
|
|
27
27
|
| `outcome` | yes | `pass`, `fail` or `needs_human` |
|
|
28
28
|
| `findings` | yes (may be empty) | a list of `{class, severity, where, summary}` |
|
|
29
|
+
| `not_checked` | when a check could not run | a list of `{check, reason}`: a check you were asked to make and could not run, and why (no test script, no browser, a file you could not read). Never fold it into `pass`: a verdict with entries here says what it did not see |
|
|
29
30
|
|
|
30
31
|
| Agent | `verdict` → `outcome` |
|
|
31
32
|
|---|---|
|
|
@@ -33,7 +34,7 @@ A judging agent ends its report with one fenced `pan-verdict` block. The block i
|
|
|
33
34
|
| pan-reviewer | `PASS` → `pass` · `PASS_WITH_WARNINGS` → `pass` · `NEEDS_FIXES` → `fail` |
|
|
34
35
|
| pan-design-checker | `PASS` → `pass` · `GAPS` → `fail` |
|
|
35
36
|
|
|
36
|
-
The verifier does not emit this block. Its verification.md frontmatter (`status`, `gaps`, `human_verification`, `unrequested`) is its machine contract, and the recorder reads that directly.
|
|
37
|
+
The verifier does not emit this block. Its verification.md frontmatter (`status`, `gaps`, `human_verification`, `unrequested`, `test_gate`, `not_checked`) is its machine contract, and the recorder reads that directly. `test_gate` is `passed`, `failed` or `skipped`. A skipped gate is recorded as a `tests` entry in `not_checked` even when the list leaves it out, and a verification that does not record `test_gate` at all is recorded with a `test_gate_unrecorded` warning.
|
|
37
38
|
|
|
38
39
|
## `class`: what kind of finding
|
|
39
40
|
|
|
@@ -30,7 +30,7 @@ Discover [topic] to inform [phase name] implementation.
|
|
|
30
30
|
|
|
31
31
|
Purpose: [What decision/implementation this enables]
|
|
32
32
|
Scope: [Boundaries]
|
|
33
|
-
Output:
|
|
33
|
+
Output: discovery.md with recommendation
|
|
34
34
|
</discovery_objective>
|
|
35
35
|
|
|
36
36
|
<discovery_scope>
|
|
@@ -79,7 +79,8 @@ Output: [What artifacts will be created]
|
|
|
79
79
|
<!-- For checkpoint task examples and patterns, see @~/.claude/pan-wizard-core/references/checkpoints.md -->
|
|
80
80
|
<!-- Key rule: Claude starts dev server BEFORE human-verify checkpoints. User only visits URLs. -->
|
|
81
81
|
|
|
82
|
-
<task type="checkpoint:decision" gate="blocking">
|
|
82
|
+
<task type="checkpoint:decision" gate="blocking" auto_select="option-a">
|
|
83
|
+
<!-- auto_select: the option auto mode takes. Omit it when a human must choose. -->
|
|
83
84
|
<decision>[What needs deciding]</decision>
|
|
84
85
|
<context>[Why this decision matters]</context>
|
|
85
86
|
<options>
|
|
@@ -10,8 +10,13 @@ Template for `.planning/phases/XX-name/{phase_num}-verification.md` — phase go
|
|
|
10
10
|
---
|
|
11
11
|
phase: XX-name
|
|
12
12
|
verified: YYYY-MM-DDTHH:MM:SSZ
|
|
13
|
+
verified_commit: <sha> # git rev-parse HEAD before writing this file; `pan-tools verify stale` compares against it
|
|
13
14
|
status: passed | gaps_found | human_needed
|
|
14
15
|
score: N/M must-haves verified
|
|
16
|
+
test_gate: passed | failed | skipped
|
|
17
|
+
not_checked: # Only for checks that could not run, with why — a skipped test gate is one
|
|
18
|
+
- check: "tests"
|
|
19
|
+
reason: "no test script in package.json"
|
|
15
20
|
unrequested: # Only when the scope check (`pan-tools verify scope <phase>`) found work no plan asked for
|
|
16
21
|
- path: "src/admin/panel.tsx"
|
|
17
22
|
what: "An admin panel no plan or requirement asked for"
|
|
@@ -256,7 +256,11 @@ Execute each wave in sequence. Within a wave: parallel if `PARALLELIZATION=true`
|
|
|
256
256
|
|
|
257
257
|
**Known Claude Code bug (classifyHandoffIfNeeded):** If an agent reports "failed" with error containing `classifyHandoffIfNeeded is not defined`, this is a Claude Code runtime bug — not a PAN or agent issue. The error fires in the completion handler AFTER all tool calls finish. In this case: run the same spot-checks as step 4 (summary.md exists, git commits present, no Self-Check: FAILED). If spot-checks PASS → treat as **successful**. If spot-checks FAIL → treat as real failure below.
|
|
258
258
|
|
|
259
|
-
For real failures: report which plan failed → ask "Continue?" or "Stop?"
|
|
259
|
+
For real failures: report which plan failed → ask "Continue?" or "Stop?". If stop, partial completion report. If continue, never run a plan that depends on a failed one; ask the engine which plans are blocked:
|
|
260
|
+
```bash
|
|
261
|
+
BLOCKED=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs phase-plan-index "${PHASE_NUMBER}" --failed "${FAILED_PLAN_IDS}")
|
|
262
|
+
```
|
|
263
|
+
`${FAILED_PLAN_IDS}` is the comma-separated ids of every plan that failed so far. `blocked` lists each unfinished plan whose `depends_on` reaches a failed plan, directly or through another blocked plan, with the failed plans behind it. Do not spawn those plans in this or any later wave. Name them in the wave report and the final summary ("Skipped 03-03 and 03-04: they depend on 03-01, which failed"), and run every other plan as planned. The same applies after "Continue with remaining waves?" in step 4.
|
|
260
264
|
|
|
261
265
|
6. **Execute checkpoint plans between waves** — see `<checkpoint_handling>`.
|
|
262
266
|
|
|
@@ -275,7 +279,7 @@ AUTO_CFG=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-get workflow.
|
|
|
275
279
|
|
|
276
280
|
When executor returns a checkpoint AND `AUTO_CFG` is `"true"`:
|
|
277
281
|
- **human-verify** → Auto-spawn continuation agent with `{user_response}` = `"approved"`. Log `⚡ Auto-approved checkpoint`.
|
|
278
|
-
- **decision** →
|
|
282
|
+
- **decision** → The executor already took the plan's `auto_select` option when the task had one, so a decision that comes back has none: present it to the user (standard flow below). If a returned decision's task does carry `auto_select` in the plan file, auto-spawn the continuation agent with `{user_response}` = that option id and log `⚡ Auto-selected: [option] (auto_select)`. Never answer with an option because it is listed first.
|
|
279
283
|
- **human-action** → Present to user (existing behavior below). Auth gates cannot be automated.
|
|
280
284
|
|
|
281
285
|
**Standard flow (not auto-mode, or human-action type):**
|
|
@@ -567,6 +571,7 @@ Phase goal: {goal from roadmap.md}
|
|
|
567
571
|
Phase requirement IDs: {phase_req_ids}
|
|
568
572
|
Check must_haves against actual codebase.
|
|
569
573
|
Cross-reference requirement IDs from PLAN frontmatter against requirements.md — every ID MUST be accounted for.
|
|
574
|
+
Run the project's test suite as the test gate (the run_test_suite step of @~/.claude/pan-wizard-core/workflows/verify-phase.md) and record test_gate in the frontmatter.
|
|
570
575
|
Create verification.md.",
|
|
571
576
|
subagent_type="pan-verifier",
|
|
572
577
|
model="{verifier_model}"
|
|
@@ -585,7 +590,7 @@ VERIF_STATUS=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings record
|
|
|
585
590
|
|
|
586
591
|
| Status | Action |
|
|
587
592
|
|--------|--------|
|
|
588
|
-
| `passed` | → update_roadmap |
|
|
593
|
+
| `passed` | → update_roadmap. If the verification frontmatter has `test_gate: skipped` or a `not_checked` list, say so when you report the phase: "passed — not checked: tests (no test script)". A pass never hides what it did not check |
|
|
589
594
|
| `human_needed` | Present items for human testing, get approval or feedback |
|
|
590
595
|
| `gaps_found` | Present gap summary, offer `/pan:plan-phase {phase} --gaps` |
|
|
591
596
|
|
|
@@ -165,6 +165,7 @@ Report final status.
|
|
|
165
165
|
| I003 | info | Focus-model or campaign tree — the phase-model checks do not apply | No |
|
|
166
166
|
| I004 | info | planning-with-files shares the tree — PAN leaves its files alone | No |
|
|
167
167
|
| I005 | info | The planning tree is gitignored while commit_docs is true — planning commits commit nothing | No |
|
|
168
|
+
| I006 | info | config.json holds keys PAN does not read (usually a typo); the fix names the nearest known key | No |
|
|
168
169
|
| STATE_REQ_DRIFT | warning | state.md complete but REQUIREMENTS.md has unchecked boxes | Yes |
|
|
169
170
|
| STATE_ROADMAP_DRIFT | warning | state.md complete but roadmap.md has unchecked plan boxes | Yes |
|
|
170
171
|
| VERIFICATION_GATE_MISSING | warning | Phase has completed plans but no verification record | No |
|
|
@@ -985,7 +985,7 @@ Use AskUserQuestion:
|
|
|
985
985
|
- .planning/roadmap.md (Current roadmap to revise)
|
|
986
986
|
</files_to_read>
|
|
987
987
|
|
|
988
|
-
Update the roadmap based on feedback
|
|
988
|
+
Update the roadmap based on feedback: rewrite each changed file with one Write.
|
|
989
989
|
Return ROADMAP REVISED with changes made.
|
|
990
990
|
</revision>
|
|
991
991
|
", subagent_type="pan-roadmapper", model="{roadmapper_model}", description="Revise roadmap")
|
|
@@ -46,7 +46,7 @@ If `$FULL_MODE`:
|
|
|
46
46
|
INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs init quick "$DESCRIPTION")
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `next_num`, `slug`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
|
|
49
|
+
Parse JSON for: `planner_model`, `executor_model`, `checker_model`, `verifier_model`, `commit_docs`, `next_num`, `slug`, `description_cell`, `date`, `timestamp`, `quick_dir`, `task_dir`, `roadmap_exists`, `planning_exists`.
|
|
50
50
|
|
|
51
51
|
**If `roadmap_exists` is false:** Error — Quick mode requires an active project with roadmap.md. Run `/pan:new-project` first.
|
|
52
52
|
|
|
@@ -365,23 +365,23 @@ Insert after `### Blockers/Concerns` section:
|
|
|
365
365
|
|
|
366
366
|
**7c. Append new row to table:**
|
|
367
367
|
|
|
368
|
-
Use `date` from init
|
|
368
|
+
Use `date` and `description_cell` from init. Write `description_cell` exactly as init returned it, never `$DESCRIPTION`: init escapes `|` and folds line breaks, and a raw `|` in a description would split the row into an extra column and break the table for every later read.
|
|
369
369
|
|
|
370
370
|
**If `$FULL_MODE` (or table has Status column):**
|
|
371
371
|
```markdown
|
|
372
|
-
| ${next_num} | ${
|
|
372
|
+
| ${next_num} | ${description_cell} | ${date} | ${commit_hash} | ${VERIFICATION_STATUS} | [${next_num}-${slug}](./quick/${next_num}-${slug}/) |
|
|
373
373
|
```
|
|
374
374
|
|
|
375
375
|
**If NOT `$FULL_MODE` (and table has no Status column):**
|
|
376
376
|
```markdown
|
|
377
|
-
| ${next_num} | ${
|
|
377
|
+
| ${next_num} | ${description_cell} | ${date} | ${commit_hash} | [${next_num}-${slug}](./quick/${next_num}-${slug}/) |
|
|
378
378
|
```
|
|
379
379
|
|
|
380
380
|
**7d. Update "Last activity" line:**
|
|
381
381
|
|
|
382
|
-
Use `date` from init:
|
|
382
|
+
Use `date` and `description_cell` from init (one line, whatever the description held):
|
|
383
383
|
```
|
|
384
|
-
Last activity: ${date} - Completed quick task ${next_num}: ${
|
|
384
|
+
Last activity: ${date} - Completed quick task ${next_num}: ${description_cell}
|
|
385
385
|
```
|
|
386
386
|
|
|
387
387
|
Use Edit tool to make these changes atomically
|
|
@@ -136,6 +136,10 @@ TEST_TOTAL: ${TEST_COUNT}
|
|
|
136
136
|
TEST_PASSED: ${TEST_PASS}
|
|
137
137
|
TEST_FAILED: ${TEST_FAIL}
|
|
138
138
|
```
|
|
139
|
+
Write `test_gate: ${TEST_GATE_STATUS}` into the verification.md frontmatter. When it is
|
|
140
|
+
`skipped`, also add a `not_checked` entry (`check: "tests"`, `reason: "no test script in
|
|
141
|
+
package.json"`). The recorder reads both: a pass whose tests never ran is recorded as a
|
|
142
|
+
pass with tests not checked, never as a plain pass.
|
|
139
143
|
|
|
140
144
|
**If test gate FAILED:** Continue verification (gather full picture) but set overall status to `gaps_found` regardless of goal-backward results. Include test failures as gaps in the report.
|
|
141
145
|
</step>
|
|
@@ -284,7 +288,7 @@ Format each as: Test Name → What to do → Expected result → Why can't verif
|
|
|
284
288
|
</step>
|
|
285
289
|
|
|
286
290
|
<step name="determine_status">
|
|
287
|
-
**passed:** All truths VERIFIED, all artifacts pass levels 1-3, all key links WIRED, no blocker anti-patterns, AND test gate passed or skipped.
|
|
291
|
+
**passed:** All truths VERIFIED, all artifacts pass levels 1-3, all key links WIRED, no blocker anti-patterns, AND test gate passed or skipped. A skipped gate is listed in `not_checked` with its reason, and the report to the user says the tests did not run.
|
|
288
292
|
|
|
289
293
|
**gaps_found:** Any truth FAILED, artifact MISSING/STUB, key link NOT_WIRED, blocker found, OR test gate FAILED (test regressions detected).
|
|
290
294
|
|
|
@@ -306,9 +310,10 @@ If gaps_found:
|
|
|
306
310
|
<step name="create_report">
|
|
307
311
|
```bash
|
|
308
312
|
REPORT_PATH="$PHASE_DIR/${PHASE_NUM}-verification.md"
|
|
313
|
+
VERIFIED_COMMIT=$(git rev-parse HEAD 2>/dev/null)
|
|
309
314
|
```
|
|
310
315
|
|
|
311
|
-
Fill template sections: frontmatter (phase/timestamp
|
|
316
|
+
Fill template sections: frontmatter (phase/timestamp/`verified_commit: ${VERIFIED_COMMIT}`/status/score/`test_gate`), **test gate results** (status/total/passed/failed), goal achievement, artifact table, wiring table, requirements coverage, anti-patterns, human verification, gaps summary, fix plans (if gaps_found), metadata.
|
|
312
317
|
|
|
313
318
|
See ~/.claude/pan-wizard-core/templates/verification-report.md for complete template.
|
|
314
319
|
</step>
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* plugin.json closed-schema manifest ($schema + name + metadata)
|
|
7
7
|
* skills/pan-<name>/SKILL.md every PAN command as an Agent Skill, from the
|
|
8
|
-
* ONE unified-skills compiler (ADR-0028)
|
|
8
|
+
* ONE unified-skills compiler (ADR-0028); a skill
|
|
9
|
+
* over Codex's 8,000-byte plugin cut is a pointer
|
|
10
|
+
* page plus references/instructions.md (S8)
|
|
9
11
|
* mcp.json the bundled bridge, launched as
|
|
10
12
|
* `node ${PLUGIN_ROOT}/pan-wizard-core/mcp/server.cjs`
|
|
11
13
|
* pan-wizard-core/ dispatcher + modules + workflows + templates +
|
|
@@ -74,9 +76,14 @@ function assertSafeToReplace(dir) {
|
|
|
74
76
|
throw new Error(`build-agent-plugin: refusing to replace ${dir} — it is non-empty and does not look like a previous bundle build (no Agent Plugins plugin.json)`);
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
/**
|
|
79
|
+
/**
|
|
80
|
+
* commands/pan/**.md → skills/pan-<name>/SKILL.md, mirroring the installer's recursion.
|
|
81
|
+
* A skill longer than Codex loads from a plugin (8,000 bytes) is split: a pointer
|
|
82
|
+
* SKILL.md plus references/instructions.md (lib.splitOversizedSkill, market-ideas S8).
|
|
83
|
+
*/
|
|
78
84
|
function emitSkills(srcDir, skillsDir, prefix) {
|
|
79
85
|
let count = 0;
|
|
86
|
+
let split = 0;
|
|
80
87
|
(function recurse(currentSrc, currentPrefix) {
|
|
81
88
|
for (const entry of fs.readdirSync(currentSrc, { withFileTypes: true })) {
|
|
82
89
|
const srcPath = path.join(currentSrc, entry.name);
|
|
@@ -88,11 +95,18 @@ function emitSkills(srcDir, skillsDir, prefix) {
|
|
|
88
95
|
let content = fs.readFileSync(srcPath, 'utf8');
|
|
89
96
|
content = lib.rewriteUnifiedSkillCommandContent(content, REWRITE);
|
|
90
97
|
content = lib.convertClaudeCommandToUnifiedSkill(content, skillName, { adapterNote: lib.agentPluginSkillAdapterNote() });
|
|
91
|
-
|
|
98
|
+
const { skill, reference } = lib.splitOversizedSkill(content, skillName);
|
|
99
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skill);
|
|
100
|
+
if (reference) {
|
|
101
|
+
const refPath = path.join(skillDir, ...lib.AGENT_PLUGIN_SKILL_REFERENCE.split('/'));
|
|
102
|
+
fs.mkdirSync(path.dirname(refPath), { recursive: true });
|
|
103
|
+
fs.writeFileSync(refPath, reference);
|
|
104
|
+
split++;
|
|
105
|
+
}
|
|
92
106
|
count++;
|
|
93
107
|
}
|
|
94
108
|
})(srcDir, prefix);
|
|
95
|
-
return count;
|
|
109
|
+
return { count, split };
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
/** pan-wizard-core → bundle, markdown rewritten, everything else verbatim. */
|
|
@@ -214,7 +228,7 @@ function main() {
|
|
|
214
228
|
const copilotAgents = emitCopilotNamespace(path.join(ROOT, 'agents'), path.join(OUT, lib.COPILOT_PLUGIN_NAMESPACE));
|
|
215
229
|
|
|
216
230
|
console.log('PAN Agent Plugins bundle built at', path.relative(ROOT, OUT) || OUT);
|
|
217
|
-
console.log(' skills:', skills);
|
|
231
|
+
console.log(' skills:', skills.count, `(${skills.split} as a short SKILL.md + ${lib.AGENT_PLUGIN_SKILL_REFERENCE})`);
|
|
218
232
|
console.log(' agent reference copies:', agents);
|
|
219
233
|
console.log(' hook scripts:', hookScripts.length);
|
|
220
234
|
console.log(` ${lib.COPILOT_PLUGIN_NAMESPACE}/agents:`, copilotAgents);
|
package/scripts/test-surface.cjs
CHANGED
|
@@ -69,6 +69,10 @@ const EVENT_HOOKS = Object.freeze({
|
|
|
69
69
|
subagentStop: ['pan-cost-logger.js', 'pan-trace-logger.js'],
|
|
70
70
|
stop: ['pan-stop-guard.js'],
|
|
71
71
|
compact: ['pan-state-reinject.js'],
|
|
72
|
+
// Gemini and Copilot (market-ideas M33): the marker before a compaction, and the
|
|
73
|
+
// block on the next tool result — the same script in two modes.
|
|
74
|
+
compactMark: ['pan-state-reinject.js'],
|
|
75
|
+
compactInject: ['pan-state-reinject.js'],
|
|
72
76
|
});
|
|
73
77
|
|
|
74
78
|
// ─── Parsers (pure) ─────────────────────────────────────────────────────────
|