pan-wizard 3.30.0 → 3.32.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 +30 -26
- package/agents/pan-design-checker.md +18 -0
- package/agents/pan-optimizer.md +5 -1
- package/agents/pan-plan-checker.md +23 -0
- package/agents/pan-reviewer.md +19 -0
- package/agents/pan-verifier.md +23 -0
- package/bin/install-lib.cjs +129 -33
- package/bin/install.js +16 -9
- package/commands/pan/army.md +4 -4
- package/commands/pan/design-phase.md +8 -0
- package/commands/pan/exec-phase.md +1 -1
- package/commands/pan/git.md +2 -2
- package/commands/pan/learn.md +2 -0
- package/commands/pan/optimize.md +14 -2
- package/commands/pan/review-deep.md +5 -5
- package/commands/pan/verify-phase.md +8 -9
- package/hooks/dist/pan-check-update.js +53 -2
- package/hooks/dist/pan-context-monitor.js +42 -1
- package/hooks/dist/pan-cost-logger.js +132 -11
- package/hooks/dist/pan-state-reinject.js +158 -0
- package/hooks/dist/pan-stop-guard.js +50 -4
- package/hooks/dist/pan-trace-logger.js +324 -18
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/commands.cjs +22 -5
- package/pan-wizard-core/bin/lib/config.cjs +2 -0
- package/pan-wizard-core/bin/lib/constants.cjs +31 -1
- package/pan-wizard-core/bin/lib/context-budget.cjs +27 -2
- package/pan-wizard-core/bin/lib/core.cjs +232 -23
- package/pan-wizard-core/bin/lib/cost-rebuild.cjs +11 -0
- package/pan-wizard-core/bin/lib/cost.cjs +135 -46
- package/pan-wizard-core/bin/lib/findings.cjs +381 -0
- package/pan-wizard-core/bin/lib/focus.cjs +72 -19
- package/pan-wizard-core/bin/lib/foreign-planning.cjs +24 -6
- package/pan-wizard-core/bin/lib/frontmatter.cjs +5 -2
- package/pan-wizard-core/bin/lib/hygiene.cjs +12 -2
- package/pan-wizard-core/bin/lib/init.cjs +4 -1
- package/pan-wizard-core/bin/lib/memory-optimize.cjs +12 -6
- package/pan-wizard-core/bin/lib/memory-rebuild.cjs +9 -2
- package/pan-wizard-core/bin/lib/optimize.cjs +349 -21
- package/pan-wizard-core/bin/lib/phase-report.cjs +4 -1
- package/pan-wizard-core/bin/lib/review-deep.cjs +36 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +87 -18
- package/pan-wizard-core/bin/lib/state.cjs +93 -24
- package/pan-wizard-core/bin/lib/suggest.cjs +21 -9
- package/pan-wizard-core/bin/lib/template.cjs +6 -3
- package/pan-wizard-core/bin/lib/verdict.cjs +431 -0
- package/pan-wizard-core/bin/lib/verify-deploy.cjs +19 -2
- package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -2
- package/pan-wizard-core/bin/lib/verify-scope.cjs +156 -0
- package/pan-wizard-core/bin/lib/verify.cjs +25 -2
- package/pan-wizard-core/bin/pan-tools.cjs +69 -6
- package/pan-wizard-core/learnings/README.md +10 -7
- package/pan-wizard-core/mcp/tool-registry.cjs +16 -4
- package/pan-wizard-core/references/verdict-contract.md +101 -0
- package/pan-wizard-core/templates/verification-report.md +16 -0
- package/pan-wizard-core/workflows/exec-phase.md +73 -47
- package/pan-wizard-core/workflows/health.md +2 -0
- package/pan-wizard-core/workflows/help.md +2 -2
- package/pan-wizard-core/workflows/learn.md +4 -2
- package/pan-wizard-core/workflows/milestone-audit.md +15 -3
- package/pan-wizard-core/workflows/optimize.md +14 -1
- package/pan-wizard-core/workflows/phase-tests.md +1 -1
- package/pan-wizard-core/workflows/plan-phase.md +20 -15
- package/pan-wizard-core/workflows/progress.md +1 -1
- package/pan-wizard-core/workflows/todo-add.md +1 -1
- package/pan-wizard-core/workflows/todo-check.md +7 -6
- package/pan-wizard-core/workflows/verify-phase.md +3 -13
- package/pan-zcode/README.md +3 -3
- package/scripts/build-agent-plugin.js +4 -0
- package/scripts/build-hooks.js +2 -1
- package/scripts/build-plugin.js +10 -0
- package/scripts/git-hooks/pre-commit +2 -1
- package/scripts/npm-dist-tag.js +47 -0
- package/scripts/plugin-path.js +6 -1
- package/scripts/test-surface.cjs +1 -0
package/bin/install-lib.cjs
CHANGED
|
@@ -677,7 +677,7 @@ function buildAgentPluginMcpConfig() {
|
|
|
677
677
|
* @param {{updateCheckCommand?:string, contextMonitorCommand?:string, costLoggerCommand?:string, traceLoggerCommand?:string}} commands
|
|
678
678
|
*/
|
|
679
679
|
function buildCopilotPluginHooksConfig(commands) {
|
|
680
|
-
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand } = commands || {};
|
|
680
|
+
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand } = commands || {};
|
|
681
681
|
const hooks = {};
|
|
682
682
|
if (updateCheckCommand) hooks.SessionStart = [{ type: 'command', command: updateCheckCommand }];
|
|
683
683
|
if (contextMonitorCommand) hooks.PostToolUse = [{ type: 'command', command: contextMonitorCommand }];
|
|
@@ -685,6 +685,8 @@ function buildCopilotPluginHooksConfig(commands) {
|
|
|
685
685
|
if (costLoggerCommand) subagentStop.push({ type: 'command', command: costLoggerCommand });
|
|
686
686
|
if (traceLoggerCommand) subagentStop.push({ type: 'command', command: traceLoggerCommand });
|
|
687
687
|
if (subagentStop.length > 0) hooks.SubagentStop = subagentStop;
|
|
688
|
+
// Copilot's documented PascalCase alias for agentStop (M14).
|
|
689
|
+
if (stopGuardCommand) hooks.Stop = [{ type: 'command', command: stopGuardCommand }];
|
|
688
690
|
return { hooks };
|
|
689
691
|
}
|
|
690
692
|
|
|
@@ -692,9 +694,15 @@ function buildCopilotPluginHooksConfig(commands) {
|
|
|
692
694
|
const COPILOT_PLUGIN_NAMESPACE = 'com.github.copilot';
|
|
693
695
|
|
|
694
696
|
/** The adapter paragraph appended to every bundled skill (ADR-0045 D3). */
|
|
695
|
-
function agentPluginSkillAdapterNote() {
|
|
697
|
+
function agentPluginSkillAdapterNote(kind = 'skill') {
|
|
698
|
+
// An agent file (<root>/com.github.copilot/agents/<name>.agent.md) sits as deep as a
|
|
699
|
+
// skill (<root>/skills/<name>/SKILL.md), so the same derivation holds; until
|
|
700
|
+
// 2026-09-26 only the skills carried the note, and the bundled Copilot agents held
|
|
701
|
+
// the token with nothing to say what it meant (3.28.0 review, LOW).
|
|
702
|
+
const what = kind === 'agent' ? 'agent' : 'skill';
|
|
703
|
+
const file = kind === 'agent' ? 'this agent file' : 'this SKILL.md';
|
|
696
704
|
return `Plugin bundle (Agent Plugins format):
|
|
697
|
-
- \`${AGENT_PLUGIN_ROOT_TOKEN}\` in this
|
|
705
|
+
- \`${AGENT_PLUGIN_ROOT_TOKEN}\` in this ${what} is the directory that holds this plugin's \`plugin.json\` — two levels above ${file}. Your runtime reports this ${what}'s file location when it loads it; derive the root from that path and substitute it wherever \`${AGENT_PLUGIN_ROOT_TOKEN}\` appears before running a command.
|
|
698
706
|
- Prefer the \`pan\` MCP server's tools when your runtime has connected this plugin's \`mcp.json\`, and pass the project's absolute path as each tool's \`cwd\` argument — the server is started in the plugin's directory, which is never the project. Otherwise run \`node ${AGENT_PLUGIN_ROOT_TOKEN}/pan-wizard-core/bin/pan-tools.cjs <verb>\` from the project root.
|
|
699
707
|
- \`${AGENT_PLUGIN_RUNTIME_HOME_TOKEN}\` is your runtime's user-level configuration directory (for example \`~/.claude\`, \`~/.codex\`, \`~/.gemini\`, \`~/.config/opencode\`, \`~/.copilot\`) and \`${AGENT_PLUGIN_RUNTIME_DIR_TOKEN}\` its project-level directory (\`.claude\`, \`.codex\`, \`.gemini\`, \`.opencode\`, \`.github\`). Substitute the one that applies to the runtime you are.`;
|
|
700
708
|
}
|
|
@@ -742,13 +750,29 @@ function convertClaudeCommandToCopilotSkill(content, skillName) {
|
|
|
742
750
|
/**
|
|
743
751
|
* @param {string} content - Claude agent markdown
|
|
744
752
|
* @param {object} [opts]
|
|
745
|
-
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
753
|
+
* Every agent carries `include-custom-instructions: true` (market-ideas M15). Copilot
|
|
754
|
+
* does not give a custom agent spawned as a subagent the repository's instruction
|
|
755
|
+
* files — `copilot-instructions.md`, `AGENTS.md`, `CLAUDE.md` — unless its frontmatter
|
|
756
|
+
* opts in (Copilot CLI reference, "Repository custom instructions for subagents";
|
|
757
|
+
* default false). PAN's rules section lives in AGENTS.md, and PAN's agents run as
|
|
758
|
+
* subagents. Measured 2026-09-26 on Copilot CLI 1.0.88: with an AGENTS.md canary, a
|
|
759
|
+
* subagent with the field answered it and the same agent without the field answered
|
|
760
|
+
* that it had none.
|
|
761
|
+
*
|
|
762
|
+
* @param {Record<string,string[]>} [opts.modelLists] - Copilot CLI accepts a priority
|
|
763
|
+
* list under `models:` ("the runtime uses the first model the user's plan can
|
|
764
|
+
* access; if none resolve, dispatch falls back to the session's model") plus
|
|
765
|
+
* `model-policy`. When a PAN agent pins `model: <alias>` and this map has an entry
|
|
766
|
+
* for the alias, the Copilot agent gets that list and `model-policy: preferred`
|
|
767
|
+
* (degrade gracefully; `required` would refuse to run). Until 2026-09-26 the list
|
|
768
|
+
* was written under the singular `model:` and the policy as `prefer`; the first live
|
|
769
|
+
* probe (Copilot CLI 1.0.88) refused to load that agent — `model-policy: Expected
|
|
770
|
+
* "preferred" or "required"` — and the reference names the list field `models`.
|
|
771
|
+
* Copilot's model ids are dotted (`claude-opus-5.5`; its /models list, same day). A
|
|
772
|
+
* session on `auto` — every Copilot Free session — ignores the list, so the list's
|
|
773
|
+
* fall-through is still unmeasured; NOT wired into the installer until it is, on a
|
|
774
|
+
* paid plan (harness/scenarios/copilot-agent-frontmatter.json). Reality check RC15 /
|
|
775
|
+
* plan item R13, 2026-09-10.
|
|
752
776
|
*/
|
|
753
777
|
function convertClaudeToCopilotAgent(content, opts = {}) {
|
|
754
778
|
const converted = convertClaudeToCopilotMarkdown(content);
|
|
@@ -806,10 +830,10 @@ function convertClaudeToCopilotAgent(content, opts = {}) {
|
|
|
806
830
|
const pinned = extractFrontmatterField(frontmatter, 'model');
|
|
807
831
|
const list = pinned && Array.isArray(lists[pinned]) ? lists[pinned].filter(Boolean) : null;
|
|
808
832
|
if (list && list.length) {
|
|
809
|
-
modelYaml = `\
|
|
833
|
+
modelYaml = `\nmodels:\n${list.map(m => ` - ${yamlQuote(m)}`).join('\n')}\nmodel-policy: preferred`;
|
|
810
834
|
}
|
|
811
835
|
}
|
|
812
|
-
return `---\nname: ${yamlQuote(name)}\ndescription: ${yamlQuote(description)}${toolsYaml}${modelYaml}\n---\n${body}`;
|
|
836
|
+
return `---\nname: ${yamlQuote(name)}\ndescription: ${yamlQuote(description)}${toolsYaml}\ninclude-custom-instructions: true${modelYaml}\n---\n${body}`;
|
|
813
837
|
}
|
|
814
838
|
|
|
815
839
|
// ─── Attribution Processing ─────────────────────────────────────────────────
|
|
@@ -1021,7 +1045,7 @@ function stripThinkingFrontmatter(content, runtime) {
|
|
|
1021
1045
|
* @returns {Object} A `.github/hooks/pan.json` config object
|
|
1022
1046
|
*/
|
|
1023
1047
|
function buildCopilotHooksConfig(commands) {
|
|
1024
|
-
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand } = commands || {};
|
|
1048
|
+
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand } = commands || {};
|
|
1025
1049
|
const config = { version: 1, hooks: {} };
|
|
1026
1050
|
if (updateCheckCommand) {
|
|
1027
1051
|
config.hooks.sessionStart = [{ type: 'command', command: updateCheckCommand }];
|
|
@@ -1037,6 +1061,12 @@ function buildCopilotHooksConfig(commands) {
|
|
|
1037
1061
|
if (subagentStop.length > 0) {
|
|
1038
1062
|
config.hooks.subagentStop = subagentStop;
|
|
1039
1063
|
}
|
|
1064
|
+
// agentStop: the auto-advance stop guard (M14). Copilot blocks a stop only on
|
|
1065
|
+
// the JSON `decision: "block"` output, and ends the turn anyway after eight
|
|
1066
|
+
// consecutive blocks; the guard blocks once per stop chain.
|
|
1067
|
+
if (stopGuardCommand) {
|
|
1068
|
+
config.hooks.agentStop = [{ type: 'command', command: stopGuardCommand }];
|
|
1069
|
+
}
|
|
1040
1070
|
return config;
|
|
1041
1071
|
}
|
|
1042
1072
|
|
|
@@ -1066,20 +1096,38 @@ function buildCopilotHooksConfig(commands) {
|
|
|
1066
1096
|
* - copilot: `.github/hooks/pan.json`, camelCase (docs.github.com hooks reference).
|
|
1067
1097
|
* - opencode: no hook system.
|
|
1068
1098
|
*
|
|
1069
|
-
* `stop` is where the auto-advance stop guard registers (P-1809): Claude's Stop
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1099
|
+
* `stop` is where the auto-advance stop guard registers (P-1809): Claude's Stop,
|
|
1100
|
+
* Gemini's AfterAgent, Codex's Stop and Copilot's agentStop — each re-prompts the
|
|
1101
|
+
* agent when a hook prints `{"decision":"block","reason":…}`, and each hands the
|
|
1102
|
+
* hook `stop_hook_active` (M14, 2026-09-26: codex-rs hooks/schema
|
|
1103
|
+
* stop.command.input.schema.json; Copilot hooks-reference, where the field stays
|
|
1104
|
+
* snake_case in the camelCase agentStop payload). Copilot does not block on exit
|
|
1105
|
+
* code 2 for agentStop — only the JSON form, which is what the guard prints.
|
|
1106
|
+
*
|
|
1107
|
+
* `compact` is where the state re-injection hook registers (M10): SessionStart with
|
|
1108
|
+
* the `compact` matcher (HOOK_SLOT_MATCHERS), the one event after a compaction whose
|
|
1109
|
+
* `hookSpecificOutput.additionalContext` reaches the model on Claude Code and Codex —
|
|
1110
|
+
* both hosts' PostCompact discard it (hooks references, read 2026-09-26). Gemini's
|
|
1111
|
+
* PreCompress fires before the summary and Copilot's preCompact is notification-only,
|
|
1112
|
+
* so neither gets it.
|
|
1072
1113
|
*/
|
|
1073
1114
|
const HOOK_EVENT_MAP = Object.freeze({
|
|
1074
|
-
claude: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop' },
|
|
1075
|
-
gemini: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: null, subagentStop: null, stop: 'AfterAgent' },
|
|
1076
|
-
codex: { surface: 'hooks.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop:
|
|
1077
|
-
copilot: { surface: 'hooks/pan.json', sessionStart: 'sessionStart', postToolUse: 'postToolUse', subagentStop: 'subagentStop', stop: null },
|
|
1115
|
+
claude: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart' },
|
|
1116
|
+
gemini: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: null, subagentStop: null, stop: 'AfterAgent', compact: null },
|
|
1117
|
+
codex: { surface: 'hooks.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart' },
|
|
1118
|
+
copilot: { surface: 'hooks/pan.json', sessionStart: 'sessionStart', postToolUse: 'postToolUse', subagentStop: 'subagentStop', stop: 'agentStop', compact: null },
|
|
1078
1119
|
opencode: null,
|
|
1079
1120
|
});
|
|
1080
1121
|
|
|
1122
|
+
/**
|
|
1123
|
+
* The matcher a slot's registration carries, where the event alone is too broad.
|
|
1124
|
+
* `compact` is SessionStart narrowed to the start that follows a compaction — the
|
|
1125
|
+
* `source: "compact"` matcher value on Claude Code and Codex alike.
|
|
1126
|
+
*/
|
|
1127
|
+
const HOOK_SLOT_MATCHERS = Object.freeze({ compact: 'compact' });
|
|
1128
|
+
|
|
1081
1129
|
/** The hook scripts PAN registers in a Claude-shaped settings.json `hooks` block. */
|
|
1082
|
-
const PAN_SETTINGS_HOOKS = Object.freeze(['pan-check-update', 'pan-context-monitor', 'pan-cost-logger', 'pan-trace-logger', 'pan-stop-guard']);
|
|
1130
|
+
const PAN_SETTINGS_HOOKS = Object.freeze(['pan-check-update', 'pan-context-monitor', 'pan-cost-logger', 'pan-trace-logger', 'pan-stop-guard', 'pan-state-reinject']);
|
|
1083
1131
|
|
|
1084
1132
|
/**
|
|
1085
1133
|
* Remove, from every event array of a Claude-shaped `hooks` object except
|
|
@@ -1263,7 +1311,7 @@ function buildCodexMcpSnippet(serverPath, panToolsPath, projectRoot, serverName
|
|
|
1263
1311
|
* @returns {object} Merged config object to serialize back to hooks.json
|
|
1264
1312
|
*/
|
|
1265
1313
|
function mergeCodexHooksConfig(existing, commands) {
|
|
1266
|
-
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand } = commands || {};
|
|
1314
|
+
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand } = commands || {};
|
|
1267
1315
|
const config = (existing && typeof existing === 'object') ? existing : {};
|
|
1268
1316
|
if (!config.hooks || typeof config.hooks !== 'object') config.hooks = {};
|
|
1269
1317
|
|
|
@@ -1276,6 +1324,10 @@ function mergeCodexHooksConfig(existing, commands) {
|
|
|
1276
1324
|
// - check-update spawns a detached child and prints nothing → async.
|
|
1277
1325
|
// - context-monitor returns `additionalContext` the model must see THIS
|
|
1278
1326
|
// turn → stays synchronous.
|
|
1327
|
+
// - stop-guard decides whether the turn may end (`decision: "block"`), which
|
|
1328
|
+
// an async handler cannot do → synchronous (M14).
|
|
1329
|
+
// - state-reinject returns `additionalContext` for the continuation after a
|
|
1330
|
+
// compaction → synchronous, and matched to `source: "compact"` (M10).
|
|
1279
1331
|
// Codex-only: Claude Code and Copilot hook schemas were not checked for an
|
|
1280
1332
|
// equivalent flag (plan item 2 gate) — do not copy this column into their
|
|
1281
1333
|
// builders without reading their docs first.
|
|
@@ -1284,9 +1336,11 @@ function mergeCodexHooksConfig(existing, commands) {
|
|
|
1284
1336
|
['PostToolUse', contextMonitorCommand, 'pan-context-monitor', false],
|
|
1285
1337
|
['SubagentStop', costLoggerCommand, 'pan-cost-logger', true],
|
|
1286
1338
|
['SubagentStop', traceLoggerCommand, 'pan-trace-logger', true],
|
|
1339
|
+
['Stop', stopGuardCommand, 'pan-stop-guard', false],
|
|
1340
|
+
['SessionStart', stateReinjectCommand, 'pan-state-reinject', false, HOOK_SLOT_MATCHERS.compact],
|
|
1287
1341
|
];
|
|
1288
1342
|
|
|
1289
|
-
for (const [event, command, marker, async] of wanted) {
|
|
1343
|
+
for (const [event, command, marker, async, matcher] of wanted) {
|
|
1290
1344
|
if (!command) continue;
|
|
1291
1345
|
if (!Array.isArray(config.hooks[event])) config.hooks[event] = [];
|
|
1292
1346
|
let existingHandler = null;
|
|
@@ -1306,7 +1360,7 @@ function mergeCodexHooksConfig(existing, commands) {
|
|
|
1306
1360
|
}
|
|
1307
1361
|
const handler = { type: 'command', command };
|
|
1308
1362
|
if (async) handler.async = true;
|
|
1309
|
-
config.hooks[event].push({ hooks: [handler] });
|
|
1363
|
+
config.hooks[event].push({ ...(matcher ? { matcher } : {}), hooks: [handler] });
|
|
1310
1364
|
}
|
|
1311
1365
|
return config;
|
|
1312
1366
|
}
|
|
@@ -1322,7 +1376,7 @@ function removeCodexPanHooks(existing) {
|
|
|
1322
1376
|
for (const event of Object.keys(existing.hooks)) {
|
|
1323
1377
|
if (!Array.isArray(existing.hooks[event])) continue;
|
|
1324
1378
|
existing.hooks[event] = existing.hooks[event].filter(group =>
|
|
1325
|
-
!(Array.isArray(group.hooks) && group.hooks.some(h => h.command && /pan-(check-update|context-monitor|cost-logger|trace-logger)/.test(h.command))));
|
|
1379
|
+
!(Array.isArray(group.hooks) && group.hooks.some(h => h.command && /pan-(check-update|context-monitor|cost-logger|trace-logger|stop-guard|state-reinject)/.test(h.command))));
|
|
1326
1380
|
if (existing.hooks[event].length === 0) delete existing.hooks[event];
|
|
1327
1381
|
}
|
|
1328
1382
|
if (Object.keys(existing.hooks).length === 0) delete existing.hooks;
|
|
@@ -1395,6 +1449,8 @@ function codexTrustNotice() {
|
|
|
1395
1449
|
'installed pan-* agents) only loads once the project is trusted in Codex.',
|
|
1396
1450
|
'If commands or agents seem missing, approve the project when Codex prompts,',
|
|
1397
1451
|
'or set trust_level for this path in ~/.codex/config.toml.',
|
|
1452
|
+
'Codex also skips new or changed hooks until you trust them: after installing',
|
|
1453
|
+
'or upgrading PAN, run /hooks in Codex and trust the pan-* hooks.',
|
|
1398
1454
|
].join('\n');
|
|
1399
1455
|
}
|
|
1400
1456
|
|
|
@@ -1688,20 +1744,26 @@ function buildPluginManifest(pkg) {
|
|
|
1688
1744
|
}
|
|
1689
1745
|
|
|
1690
1746
|
/**
|
|
1691
|
-
* Build the plugin hooks/hooks.json —
|
|
1692
|
-
* ${CLAUDE_PLUGIN_ROOT}-anchored commands
|
|
1693
|
-
* path convention for hook configs).
|
|
1747
|
+
* Build the plugin hooks/hooks.json — every hook a Claude settings.json install
|
|
1748
|
+
* registers (PAN_SETTINGS_HOOKS), with ${CLAUDE_PLUGIN_ROOT}-anchored commands
|
|
1749
|
+
* (the documented plugin-relative path convention for hook configs). The stop
|
|
1750
|
+
* guard was missing until R43 (2026-09-26), so an autonomous chain run from the
|
|
1751
|
+
* plugin could drop at a phase boundary with nothing to catch it.
|
|
1694
1752
|
* @returns {object} hooks.json object
|
|
1695
1753
|
*/
|
|
1696
1754
|
function buildPluginHooksConfig() {
|
|
1697
|
-
const hook = (script) => ({
|
|
1755
|
+
const hook = (script, matcher) => ({
|
|
1756
|
+
...(matcher ? { matcher } : {}),
|
|
1698
1757
|
hooks: [{ type: 'command', command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${script}"` }],
|
|
1699
1758
|
});
|
|
1759
|
+
const events = HOOK_EVENT_MAP.claude;
|
|
1700
1760
|
return {
|
|
1701
1761
|
hooks: {
|
|
1702
|
-
SessionStart
|
|
1703
|
-
|
|
1704
|
-
|
|
1762
|
+
// compact shares SessionStart with the update check, narrowed by its matcher (M10).
|
|
1763
|
+
[events.sessionStart]: [hook('pan-check-update.js'), hook('pan-state-reinject.js', HOOK_SLOT_MATCHERS.compact)],
|
|
1764
|
+
[events.postToolUse]: [hook('pan-context-monitor.js')],
|
|
1765
|
+
[events.subagentStop]: [hook('pan-cost-logger.js'), hook('pan-trace-logger.js')],
|
|
1766
|
+
[events.stop]: [hook('pan-stop-guard.js')],
|
|
1705
1767
|
},
|
|
1706
1768
|
};
|
|
1707
1769
|
}
|
|
@@ -2305,6 +2367,7 @@ module.exports = {
|
|
|
2305
2367
|
// Copilot CLI hooks config (2026-06)
|
|
2306
2368
|
buildCopilotHooksConfig,
|
|
2307
2369
|
HOOK_EVENT_MAP,
|
|
2370
|
+
HOOK_SLOT_MATCHERS,
|
|
2308
2371
|
PAN_SETTINGS_HOOKS,
|
|
2309
2372
|
stripPanHookEntries,
|
|
2310
2373
|
mergeCodexHooksConfig,
|
|
@@ -2364,7 +2427,9 @@ function dirDigest(dir) {
|
|
|
2364
2427
|
const crypto = require('crypto');
|
|
2365
2428
|
const lines = [];
|
|
2366
2429
|
const walk = (abs, rel) => {
|
|
2367
|
-
|
|
2430
|
+
// Code-unit order, not localeCompare: the digest compares builds across machines,
|
|
2431
|
+
// and a locale-dependent sort could order the same tree two ways (3.28.0 review, LOW).
|
|
2432
|
+
const entries = fs.readdirSync(abs, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
2368
2433
|
for (const e of entries) {
|
|
2369
2434
|
const childAbs = path.join(abs, e.name);
|
|
2370
2435
|
const childRel = rel ? `${rel}/${e.name}` : e.name;
|
|
@@ -2376,3 +2441,34 @@ function dirDigest(dir) {
|
|
|
2376
2441
|
return crypto.createHash('sha256').update(lines.join('\n')).digest('hex');
|
|
2377
2442
|
}
|
|
2378
2443
|
module.exports.dirDigest = dirDigest;
|
|
2444
|
+
|
|
2445
|
+
/**
|
|
2446
|
+
* Whether `buf` is the file a manifest hash was taken from, allowing for line
|
|
2447
|
+
* endings. The manifest records sha256 over raw bytes, so a file git re-checked
|
|
2448
|
+
* out with `core.autocrlf=true` — or one an editor saved with the other line
|
|
2449
|
+
* ending — hashed as a user edit, and every upgrade backed it up to
|
|
2450
|
+
* pan-local-patches/ as a "locally modified PAN file" with nothing to merge (the
|
|
2451
|
+
* 3.30.0 upgrade sweep: 8 such files in three projects, 357 in one that commits
|
|
2452
|
+
* `.claude/` to git). A file matches when its raw bytes, its all-LF form or its
|
|
2453
|
+
* all-CRLF form hash to `expectedHash`, so existing manifests keep working
|
|
2454
|
+
* whichever line ending they were written with. A buffer holding a NUL byte is
|
|
2455
|
+
* treated as binary and compared raw only. Pure. `validateRuntimeInstall` in
|
|
2456
|
+
* pan-wizard-core/bin/lib/verify-deploy.cjs carries the same rule (the installed
|
|
2457
|
+
* core cannot require this file); tests/install-eol-hash.test.cjs pins both.
|
|
2458
|
+
* @param {Buffer} buf
|
|
2459
|
+
* @param {string|null|undefined} expectedHash - hex sha256 from the manifest
|
|
2460
|
+
* @returns {boolean}
|
|
2461
|
+
*/
|
|
2462
|
+
function bytesMatchHashIgnoringEol(buf, expectedHash) {
|
|
2463
|
+
if (!expectedHash) return false;
|
|
2464
|
+
const crypto = require('crypto');
|
|
2465
|
+
const sha = b => crypto.createHash('sha256').update(b).digest('hex');
|
|
2466
|
+
if (sha(buf) === expectedHash) return true;
|
|
2467
|
+
if (buf.includes(0)) return false;
|
|
2468
|
+
// latin1 maps every byte to one char and back, so the round trip is lossless;
|
|
2469
|
+
// no UTF-8 multi-byte sequence contains 0x0D or 0x0A.
|
|
2470
|
+
const lf = buf.toString('latin1').replace(/\r\n/g, '\n');
|
|
2471
|
+
if (sha(Buffer.from(lf, 'latin1')) === expectedHash) return true;
|
|
2472
|
+
return sha(Buffer.from(lf.replace(/\n/g, '\r\n'), 'latin1')) === expectedHash;
|
|
2473
|
+
}
|
|
2474
|
+
module.exports.bytesMatchHashIgnoringEol = bytesMatchHashIgnoringEol;
|
package/bin/install.js
CHANGED
|
@@ -1218,7 +1218,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1218
1218
|
// 4. Remove PAN hooks (scripts + Copilot CLI hooks config file)
|
|
1219
1219
|
const hooksDir = path.join(targetDir, 'hooks');
|
|
1220
1220
|
if (fs.existsSync(hooksDir)) {
|
|
1221
|
-
const panHooks = ['pan-statusline.js', 'pan-check-update.js', 'pan-check-update.sh', 'pan-context-monitor.js', 'pan-cost-logger.js', 'pan-trace-logger.js', 'pan-stop-guard.js', 'pan.json'];
|
|
1221
|
+
const panHooks = ['pan-statusline.js', 'pan-check-update.js', 'pan-check-update.sh', 'pan-context-monitor.js', 'pan-cost-logger.js', 'pan-trace-logger.js', 'pan-stop-guard.js', 'pan-state-reinject.js', 'pan.json'];
|
|
1222
1222
|
let hookCount = 0;
|
|
1223
1223
|
for (const hook of panHooks) {
|
|
1224
1224
|
const hookPath = path.join(hooksDir, hook);
|
|
@@ -2014,8 +2014,10 @@ function saveLocalPatches(configDir) {
|
|
|
2014
2014
|
|
|
2015
2015
|
const fullPath = path.join(configDir, relPath);
|
|
2016
2016
|
if (!fs.existsSync(fullPath)) continue;
|
|
2017
|
-
|
|
2018
|
-
|
|
2017
|
+
// A line-ending-only difference is not a user edit (git autocrlf, editors).
|
|
2018
|
+
let current;
|
|
2019
|
+
try { current = fs.readFileSync(fullPath); } catch { continue; }
|
|
2020
|
+
if (!lib.bytesMatchHashIgnoringEol(current, originalHash)) {
|
|
2019
2021
|
const backupPath = path.join(patchesDir, safeRel);
|
|
2020
2022
|
// Defence in depth: never write outside patchesDir, whatever the manifest says.
|
|
2021
2023
|
const resolvedPatches = path.resolve(patchesDir);
|
|
@@ -2739,6 +2741,9 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2739
2741
|
const stopGuardCommand = isGlobal
|
|
2740
2742
|
? buildHookCommand(targetDir, 'pan-stop-guard.js')
|
|
2741
2743
|
: 'node ' + dirName + '/hooks/pan-stop-guard.js';
|
|
2744
|
+
const stateReinjectCommand = isGlobal
|
|
2745
|
+
? buildHookCommand(targetDir, 'pan-state-reinject.js')
|
|
2746
|
+
: 'node ' + dirName + '/hooks/pan-state-reinject.js';
|
|
2742
2747
|
|
|
2743
2748
|
if (isCodex) {
|
|
2744
2749
|
// Codex hooks (2026-06): Claude-compatible PascalCase events in the shared
|
|
@@ -2750,13 +2755,13 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2750
2755
|
let existing = null;
|
|
2751
2756
|
try { existing = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8')); } catch { /* absent or invalid — start fresh */ }
|
|
2752
2757
|
const merged = lib.mergeCodexHooksConfig(existing, {
|
|
2753
|
-
updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand,
|
|
2758
|
+
updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand,
|
|
2754
2759
|
});
|
|
2755
2760
|
fs.writeFileSync(hooksJsonPath, JSON.stringify(merged, null, 2) + '\n');
|
|
2756
2761
|
// Print the path we actually wrote. The hardcoded '.codex/hooks.json' was
|
|
2757
2762
|
// wrong for --global and for --config-dir, telling users to inspect a file
|
|
2758
2763
|
// that does not exist while the real one sat elsewhere.
|
|
2759
|
-
console.log(` ${green}✓${reset} Configured hooks (${displayPath(hooksJsonPath)}: update check, context monitor, cost + trace loggers)`);
|
|
2764
|
+
console.log(` ${green}✓${reset} Configured hooks (${displayPath(hooksJsonPath)}: update check, context monitor, cost + trace loggers, stop guard, state re-injection)`);
|
|
2760
2765
|
} catch (e) {
|
|
2761
2766
|
pushInstallWarning('codexHooks', 'hooks.json', e);
|
|
2762
2767
|
}
|
|
@@ -2768,12 +2773,12 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2768
2773
|
// from any legacy config.json registration.
|
|
2769
2774
|
if (isCopilot) {
|
|
2770
2775
|
const hooksConfigPath = path.join(targetDir, 'hooks', 'pan.json');
|
|
2771
|
-
const hooksConfig = buildCopilotHooksConfig({ updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand });
|
|
2776
|
+
const hooksConfig = buildCopilotHooksConfig({ updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand });
|
|
2772
2777
|
try {
|
|
2773
2778
|
fs.mkdirSync(path.dirname(hooksConfigPath), { recursive: true });
|
|
2774
2779
|
fs.writeFileSync(hooksConfigPath, JSON.stringify(hooksConfig, null, 2) + '\n');
|
|
2775
2780
|
// Same as the Codex case: a global Copilot install writes to ~/.copilot/, not .github/.
|
|
2776
|
-
console.log(` ${green}✓${reset} Configured hooks (${displayPath(hooksConfigPath)}: update check, context monitor, cost + trace loggers)`);
|
|
2781
|
+
console.log(` ${green}✓${reset} Configured hooks (${displayPath(hooksConfigPath)}: update check, context monitor, cost + trace loggers, stop guard)`);
|
|
2777
2782
|
} catch (e) {
|
|
2778
2783
|
console.error(` ${yellow}✗${reset} Failed to write Copilot hooks config: ${e.message}`);
|
|
2779
2784
|
}
|
|
@@ -2866,6 +2871,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2866
2871
|
{ slot: 'subagentStop', hook: 'pan-cost-logger', command: costLoggerCommand, label: 'cost logger hook' },
|
|
2867
2872
|
{ slot: 'subagentStop', hook: 'pan-trace-logger', command: traceLoggerCommand, label: 'trace logger hook' },
|
|
2868
2873
|
{ slot: 'stop', hook: 'pan-stop-guard', command: stopGuardCommand, label: 'auto-advance stop guard hook' },
|
|
2874
|
+
{ slot: 'compact', hook: 'pan-state-reinject', command: stateReinjectCommand, label: 'state re-injection hook (after compaction)' },
|
|
2869
2875
|
];
|
|
2870
2876
|
const unsupported = [];
|
|
2871
2877
|
for (const r of registrations) {
|
|
@@ -2888,7 +2894,8 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2888
2894
|
entry.hooks && entry.hooks.some(h => h.command && h.command.includes(r.hook))
|
|
2889
2895
|
);
|
|
2890
2896
|
if (!registered) {
|
|
2891
|
-
|
|
2897
|
+
const matcher = lib.HOOK_SLOT_MATCHERS[r.slot];
|
|
2898
|
+
settings.hooks[event].push({ ...(matcher ? { matcher } : {}), hooks: [{ type: 'command', command: r.command }] });
|
|
2892
2899
|
console.log(` ${green}✓${reset} Configured ${r.label}`);
|
|
2893
2900
|
}
|
|
2894
2901
|
}
|
|
@@ -2896,7 +2903,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2896
2903
|
delete settings.hooks;
|
|
2897
2904
|
}
|
|
2898
2905
|
if (isGemini && unsupported.length > 0) {
|
|
2899
|
-
console.log(` ${dim}ℹ Gemini CLI has no context-window metric for hooks
|
|
2906
|
+
console.log(` ${dim}ℹ Gemini CLI has no context-window metric for hooks, no subagent-completion event and no post-compaction event that adds context, so ${unsupported.join(', ')} ${unsupported.length === 1 ? 'is' : 'are'} not registered there${reset}`);
|
|
2900
2907
|
}
|
|
2901
2908
|
}
|
|
2902
2909
|
|
package/commands/pan/army.md
CHANGED
|
@@ -85,14 +85,14 @@ Every cap the conductor enforces applies to the campaign, scaled up:
|
|
|
85
85
|
| `--source` | `backlog` | Work selection (delegates to focus-auto): `backlog` = ranked roadmap/requirements items; `scan` = category code-scan. |
|
|
86
86
|
| `--max-cycles` | 5 | Mission items landed before stopping. |
|
|
87
87
|
| `--total-budget` | 300 | Cumulative point budget. **Advisory by default** — tracked/surfaced but not a hard stop unless `--enforce-budget` / config `budget.enforce: true`. |
|
|
88
|
-
| `--enforce-budget` | off | Make `--total-budget` a hard stop again (also settable via config `budget.enforce: true`)
|
|
88
|
+
| `--enforce-budget` | off | Make `--total-budget` a hard stop again (also settable via config `budget.enforce: true`). With `--schedule`, also arm the campaign with `pan-tools campaign schedule --enforce-budget`, so `--daily-budget` pauses the day's run once spent. |
|
|
89
89
|
| `--verify-reserve` | 0.15 | Fraction of `--total-budget` (0–0.5) held back for the final Quality re-review so it can't be starved (config `budget.verify_reserve`). Advisory by default (surfaced as `into_verify_reserve`); under `--enforce-budget` it stops taking on new missions early (`budget_reserve_reached`) and the reserved points fund the closing `--clean-seal` re-verification before the last INTEGRATE. |
|
|
90
90
|
| `--squads` | all | Restrict to a subset, e.g. `--squads architecture,build,quality`. |
|
|
91
91
|
| `--no-build-worktrees` | off | Build in the main tree instead of branch-per-agent worktrees (small/serial projects). |
|
|
92
92
|
| `--push` | off | Push approved merges to origin (still human-gated). |
|
|
93
93
|
| `--clean-seal` | off | One clean build + full verification after the last item (commands from config). |
|
|
94
94
|
| `--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`. |
|
|
95
|
-
| `--daily-budget` | 300 | Per-day point budget for a scheduled campaign. Advisory by default (an indicator of the day's spend); it
|
|
95
|
+
| `--daily-budget` | 300 | Per-day point budget for a scheduled campaign. Advisory by default (an indicator of the day's spend); it pauses the day's run only when the schedule is armed with `--enforce-budget` (`enforce_budget: true` in `schedule.json`). |
|
|
96
96
|
| `--dry-run` | off | Plan + squad delegation preview only; STOP. |
|
|
97
97
|
| `--continue` / `--stop` / `--status` | — | Resume / halt / report from `.planning/orchestration/` + focus-auto state. |
|
|
98
98
|
|
|
@@ -163,10 +163,10 @@ PAN is not a daemon — it cannot wake itself while the session is closed. `--sc
|
|
|
163
163
|
- **Arm:** `/pan:army "<goal>" --schedule daily --daily-budget 200` writes `.planning/orchestration/schedule.json` (cadence, daily budget, next-due) instead of running once.
|
|
164
164
|
- **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.
|
|
165
165
|
- **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.
|
|
166
|
-
- **Bounded spend:** point budgets (`--total-budget`, `--daily-budget`) are **advisory indicators by default** — they're tracked and surfaced, not hard stops. `--enforce-budget` (or config `budget.enforce`) makes `--total-budget` a hard stop; a scheduled campaign's `--daily-budget` pauses the day's run only when `
|
|
166
|
+
- **Bounded spend:** point budgets (`--total-budget`, `--daily-budget`) are **advisory indicators by default** — they're tracked and surfaced, not hard stops. `--enforce-budget` (or config `budget.enforce`) makes `--total-budget` a hard stop; a scheduled campaign's `--daily-budget` pauses the day's run only when the schedule was armed with `--enforce-budget` (`campaign schedule --enforce-budget`; `--advisory-budget` turns it back off). The real bounds are `--max-cycles`, the conductor caps, the abort file, and the human merge gate at every integrate. A scheduled campaign runs the backlog down to staged, reviewed, green PRs over days.
|
|
167
167
|
- **Verify reserve:** a fraction of `--total-budget` (`--verify-reserve`, default 0.15) is held back so the closing Quality re-review isn't starved. Surfaced always via `campaign status` / `focus auto --status` (`into_verify_reserve`, `new_work_budget_remaining`); under `--enforce-budget` the run stops taking on new missions early (`budget_reserve_reached`) and spends the reserve on the final `--clean-seal` verification before the last INTEGRATE.
|
|
168
168
|
|
|
169
|
-
Manage it: `pan-tools campaign status` (active/paused, spent today, next-due), `campaign schedule --pause` / `--resume` / `--disable
|
|
169
|
+
Manage it: `pan-tools campaign status` (active/paused, spent today, next-due), `campaign schedule --pause` / `--resume` / `--disable`, and `--enforce-budget` / `--advisory-budget` for the daily budget.
|
|
170
170
|
|
|
171
171
|
---
|
|
172
172
|
|
|
@@ -58,6 +58,14 @@ ELSE (default):
|
|
|
58
58
|
- If it finds gaps (iteration 1) → `pan-designer` revises (address genuine gaps only), re-check.
|
|
59
59
|
- If it finds gaps (iteration 2) → final revision, record remaining gaps as caveats.
|
|
60
60
|
- **Max 2 revision iterations** (design → check → revise → check → final) — the same guardrail `plan-phase` uses with `pan-plan-checker`.
|
|
61
|
+
- **Record each check.** Save the checker's returned text verbatim to `{phase_dir}/{padded_phase}-design-check.md` with the Write tool. Record it and branch on the verdict it prints (`PASS` or `GAPS`):
|
|
62
|
+
```bash
|
|
63
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings record --phase "{phase}" --agent pan-design-checker --file "{phase_dir}/{padded_phase}-design-check.md" --raw
|
|
64
|
+
```
|
|
65
|
+
A re-check is the next attempt and closes the gaps it no longer reports. When the final iteration still has gaps, record them as the caveats they are before writing the design:
|
|
66
|
+
```bash
|
|
67
|
+
node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings dispose --phase "{phase}" --agent pan-design-checker --open --as deferred --reason "design caveat after 2 revision iterations"
|
|
68
|
+
```
|
|
61
69
|
5. **Write** `{phase}-design.md` to the phase directory and commit via `pan-tools commit`.
|
|
62
70
|
6. **Present** results + next step (`/pan:plan-phase` — which will consume this design.md).
|
|
63
71
|
</process>
|
|
@@ -61,7 +61,7 @@ Phase: $ARGUMENTS
|
|
|
61
61
|
- `--skip-tests` — Skip automatic test generation after execution completes.
|
|
62
62
|
- `--skip-review` — Skip automatic code review after execution completes.
|
|
63
63
|
- `--fast` — Skip both test generation and code review (implies `--skip-tests --skip-review`).
|
|
64
|
-
- `--deep-review`
|
|
64
|
+
- `--deep-review` — After the normal code review, run the deep review inline: the `/pan:review-deep` process, a security audit by pan-hardener plus a cross-check by pan-meta-reviewer, merged into `.planning/reviews/<N>/deep-review.md`. It builds on the normal review, so it is skipped with `--skip-review` or `--fast`. Recommended for phases touching auth, payment, PII, migrations, or public APIs. Costs roughly 3× a normal review.
|
|
65
65
|
- `--hierarchical` (v3.4+, Claude Code only — needs native sub-agent spawning) — Spawn `pan-conductor` as a top-level orchestrator that decomposes the phase and spawns executor/reviewer/verifier sub-agents in sequence. `pan-conductor` runs on the reasoning tier under the default profile, which inherits the model you launched with (the `budget` profile drops it to mid). Bounded by safety harness: max 2 nesting levels, 12 spawns per phase, budget ceiling, `.planning/orchestration/abort` kill-switch. On runtimes that cannot spawn nested agents, this flag is a no-op with a warning and falls back to flat exec. Use only for large phases (≥4 autonomous plans) where wall-clock reduction justifies the ~20-30% orchestration tax.
|
|
66
66
|
|
|
67
67
|
Context files are resolved inside the workflow via `pan-tools init execute-phase` and per-subagent `<files_to_read>` blocks.
|
package/commands/pan/git.md
CHANGED
|
@@ -13,7 +13,7 @@ allowed-tools:
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
<objective>
|
|
16
|
-
Phase-aware git workflow with safety guardrails built in. Every subcommand that modifies history runs safety checks. Rollback uses PAN snapshot tags
|
|
16
|
+
Phase-aware git workflow with safety guardrails built in. Every subcommand that modifies history runs safety checks. Rollback uses PAN snapshot tags (`pan-rollback-*`), which `pan-tools rollback-snapshot <phase>` creates; no workflow creates them for you.
|
|
17
17
|
|
|
18
18
|
Works with any git repository — PAN installation not required.
|
|
19
19
|
</objective>
|
|
@@ -185,7 +185,7 @@ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs git rollback --tag pan-rollback
|
|
|
185
185
|
```
|
|
186
186
|
|
|
187
187
|
**Rollback workflow:**
|
|
188
|
-
1. Lists all `pan-rollback-*` tags (created
|
|
188
|
+
1. Lists all `pan-rollback-*` tags (created with `pan-tools rollback-snapshot <phase>`)
|
|
189
189
|
2. Verifies working tree is clean (blocks on dirty tree unless `--dry-run`)
|
|
190
190
|
3. Runs `git reset --hard <tag>`
|
|
191
191
|
|
package/commands/pan/learn.md
CHANGED
|
@@ -18,12 +18,14 @@ Analyze the most recent trace session and generate an optimization report.
|
|
|
18
18
|
```
|
|
19
19
|
/pan:learn
|
|
20
20
|
/pan:learn --session <session-id>
|
|
21
|
+
/pan:learn --sessions 3
|
|
21
22
|
/pan:learn --experiment <slug>
|
|
22
23
|
/pan:learn --apply
|
|
23
24
|
```
|
|
24
25
|
|
|
25
26
|
**Flags:**
|
|
26
27
|
- `--session <id>` — analyze a specific session instead of the most recent
|
|
28
|
+
- `--sessions <n>` — pool the last n sessions into one analysis, so recommendations rest on failures that recur across runs
|
|
27
29
|
- `--experiment <slug>` *(v3.7.0+, W3)* — analyze a harvested experiment instead of the current project's traces. Reads from `<source-repo>/experiments/<slug>/.planning/optimization/` and writes the report to `<source-repo>/experiments/<slug>/learnings/report-<timestamp>.md`. Used by the self-improvement loop. Run `/pan:experiment harvest <slug>` first.
|
|
28
30
|
- `--apply` — automatically apply safe optimizations after generating the report (equivalent to running `/pan:optimize apply` immediately after)
|
|
29
31
|
|
package/commands/pan/optimize.md
CHANGED
|
@@ -19,6 +19,8 @@ Manage the circular optimization loop: apply recommendations, view stats, list r
|
|
|
19
19
|
```
|
|
20
20
|
/pan:optimize apply
|
|
21
21
|
/pan:optimize apply --report <filename>
|
|
22
|
+
/pan:optimize revert <apply_id>
|
|
23
|
+
/pan:optimize revert --last
|
|
22
24
|
/pan:optimize list
|
|
23
25
|
/pan:optimize stats
|
|
24
26
|
/pan:optimize trace init [--description "what you're building"]
|
|
@@ -42,7 +44,17 @@ Requires human review (never auto-applied):
|
|
|
42
44
|
- Workflow step additions
|
|
43
45
|
- Structural changes to commands
|
|
44
46
|
|
|
45
|
-
After applying, the report lists what was applied and what still needs review.
|
|
47
|
+
After applying, the report lists what was applied and what still needs review, plus the `apply_id` that undoes it. Every apply is recorded action by action in `.planning/optimization/applied.jsonl`: the path, whether the file was created or appended to, the exact text, and a hash of the file after the write. Applying the same report a second time writes nothing: each action names the apply that already wrote it.
|
|
48
|
+
|
|
49
|
+
### revert
|
|
50
|
+
Undo one apply exactly: delete the memory files it created and cut the text it appended (`revert --last` for the newest).
|
|
51
|
+
|
|
52
|
+
Revert never destroys work someone did since:
|
|
53
|
+
- It refuses a file whose content changed after the apply. The comparison ignores line endings, so a CRLF checkout still matches.
|
|
54
|
+
- It refuses a file that a later apply also wrote. Revert that later apply first.
|
|
55
|
+
- It says which files it refused, and why.
|
|
56
|
+
|
|
57
|
+
A CRLF file comes back CRLF. Applies logged before apply records existed cannot be reverted. After a revert, the same report can be applied again.
|
|
46
58
|
|
|
47
59
|
### list
|
|
48
60
|
List all optimization reports in `.planning/optimization/reports/`, most recent first.
|
|
@@ -52,7 +64,7 @@ Show cumulative optimization statistics:
|
|
|
52
64
|
- Total trace sessions run
|
|
53
65
|
- Total events traced
|
|
54
66
|
- Total errors/gaps/redundancies seen
|
|
55
|
-
- Total optimizations applied across all runs
|
|
67
|
+
- Total optimizations applied across all runs, the apply runs, the reverted runs, and the last `apply_id`
|
|
56
68
|
- Current active trace session (if any)
|
|
57
69
|
|
|
58
70
|
### trace init
|
|
@@ -38,7 +38,7 @@ Consolidates Spec B v1's X-4 (self-review) + X-12 (harden) into a single command
|
|
|
38
38
|
/pan:review-deep 07
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Run after `/pan:exec-phase 07` completes.
|
|
41
|
+
Run after `/pan:exec-phase 07` completes. It reads the review that exec-phase's code-review step saved in the phase directory (`.planning/phases/07-<slug>/07-review.md`). The reviewer is read-only, so it is exec-phase that writes the review there.
|
|
42
42
|
|
|
43
43
|
### Integrated with exec-phase
|
|
44
44
|
|
|
@@ -46,7 +46,7 @@ Run after `/pan:exec-phase 07` completes. Requires `pan-reviewer` to have alread
|
|
|
46
46
|
/pan:exec-phase 07 --deep-review
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
Runs the normal exec → reviewer pipeline, then
|
|
49
|
+
Runs the normal exec → reviewer pipeline, then runs this command's process inline, before the verifier. Recommended for phases touching auth, payment, PII, migrations, or public APIs.
|
|
50
50
|
|
|
51
51
|
### Integrated with focus-exec
|
|
52
52
|
|
|
@@ -60,7 +60,7 @@ Per-item deep review during focus campaigns. Useful for high-stakes batches.
|
|
|
60
60
|
|
|
61
61
|
<process>
|
|
62
62
|
|
|
63
|
-
1. **Load reviewer output
|
|
63
|
+
1. **Load reviewer output.** Resolve the phase with `pan-tools find-phase <N>`, then read `{directory}/{phase_number}-review.md`, which exec-phase's code-review step wrote. If it is missing, warn and offer to run `pan-reviewer` first. Its findings come from the report's closing `pan-verdict` block.
|
|
64
64
|
|
|
65
65
|
2. **Spawn pan-hardener** (parallel-safe with step 3 isolation below, but recommended sequential for audit clarity):
|
|
66
66
|
- Prompt includes: `<files_to_read>` with phase plan + diff + reviewer output; `<output_path>` = `.planning/reviews/<N>/hardener.md`; `<framework_scope>` block reminding of OWASP/STRIDE coverage.
|
|
@@ -73,7 +73,7 @@ Per-item deep review during focus campaigns. Useful for high-stakes batches.
|
|
|
73
73
|
4. **Merge** — call:
|
|
74
74
|
```
|
|
75
75
|
pan-tools review-deep merge <N> \
|
|
76
|
-
--reviewer-file
|
|
76
|
+
--reviewer-file {directory}/{phase_number}-review.md \
|
|
77
77
|
--hardener-file .planning/reviews/<N>/hardener.md \
|
|
78
78
|
--meta-file .planning/reviews/<N>/meta.md
|
|
79
79
|
```
|
|
@@ -99,7 +99,7 @@ Verdict is driven by the highest-severity finding across all three sources. Meta
|
|
|
99
99
|
|
|
100
100
|
<output_files>
|
|
101
101
|
|
|
102
|
-
-
|
|
102
|
+
- `{phase_dir}/{phase_number}-review.md` — pan-reviewer output (written by exec-phase's code-review step)
|
|
103
103
|
- `.planning/reviews/<N>/hardener.md` — pan-hardener output (new)
|
|
104
104
|
- `.planning/reviews/<N>/meta.md` — pan-meta-reviewer output (new)
|
|
105
105
|
- `.planning/reviews/<N>/deep-review.md` — merged consolidated report (final deliverable)
|