pan-wizard 3.8.0 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -9
- package/agents/pan-conductor.md +15 -3
- package/agents/pan-counterfactual.md +1 -2
- package/agents/pan-debugger.md +1 -2
- package/agents/pan-distiller.md +1 -2
- package/agents/pan-document_code.md +1 -0
- package/agents/pan-executor.md +1 -0
- package/agents/pan-experiment-runner.md +1 -2
- package/agents/pan-hardener.md +1 -2
- package/agents/pan-integration-checker.md +1 -2
- package/agents/pan-knowledge.md +1 -2
- package/agents/pan-meta-reviewer.md +1 -2
- package/agents/pan-optimizer.md +1 -0
- package/agents/pan-phase-researcher.md +1 -0
- package/agents/pan-plan-checker.md +1 -2
- package/agents/pan-planner.md +1 -0
- package/agents/pan-previewer.md +1 -2
- package/agents/pan-project-researcher.md +6 -0
- package/agents/pan-release.md +58 -0
- package/agents/pan-research-synthesizer.md +7 -0
- package/agents/pan-reviewer.md +2 -3
- package/agents/pan-roadmapper.md +1 -0
- package/agents/pan-verifier.md +1 -2
- package/assets/pan-avatar.png +0 -0
- package/assets/pan-developer.png +0 -0
- package/assets/pan-docs-header.png +0 -0
- package/assets/pan-hero.png +0 -0
- package/assets/pan-logo-2000-transparent.svg +11 -30
- package/assets/pan-logo-2000.svg +12 -43
- package/assets/pan-logo-lockup.svg +11 -0
- package/assets/pan-mark.svg +7 -0
- package/assets/pan-orchestration.png +0 -0
- package/assets/pan-readme-hero.png +0 -0
- package/assets/terminal.svg +39 -119
- package/bin/install-lib.cjs +661 -46
- package/bin/install.js +722 -116
- package/commands/pan/army.md +169 -0
- package/commands/pan/dashboard.md +25 -0
- package/commands/pan/experiment.md +2 -0
- package/commands/pan/focus-auto.md +32 -4
- package/commands/pan/hud.md +91 -0
- package/commands/pan/profile.md +2 -0
- package/hooks/dist/pan-cost-logger.js +22 -7
- package/package.json +5 -4
- package/pan-wizard-core/bin/lib/campaign.cjs +198 -0
- package/pan-wizard-core/bin/lib/commands-learnings.cjs +544 -0
- package/pan-wizard-core/bin/lib/commands.cjs +12 -523
- package/pan-wizard-core/bin/lib/constants.cjs +8 -0
- package/pan-wizard-core/bin/lib/core.cjs +80 -0
- package/pan-wizard-core/bin/lib/cost.cjs +62 -8
- package/pan-wizard-core/bin/lib/focus.cjs +13 -1
- package/pan-wizard-core/bin/lib/git.cjs +6 -1
- package/pan-wizard-core/bin/lib/hud.cjs +887 -0
- package/pan-wizard-core/bin/lib/lock.cjs +108 -0
- package/pan-wizard-core/bin/lib/milestone.cjs +3 -2
- package/pan-wizard-core/bin/lib/phase-remove.cjs +392 -0
- package/pan-wizard-core/bin/lib/phase.cjs +4 -369
- package/pan-wizard-core/bin/lib/runner.cjs +5 -0
- package/pan-wizard-core/bin/lib/squads.cjs +152 -0
- package/pan-wizard-core/bin/lib/state.cjs +10 -1
- package/pan-wizard-core/bin/lib/verify-deploy.cjs +181 -0
- package/pan-wizard-core/bin/lib/verify-drift.cjs +255 -0
- package/pan-wizard-core/bin/lib/verify-preflight.cjs +261 -0
- package/pan-wizard-core/bin/lib/verify-retro.cjs +177 -0
- package/pan-wizard-core/bin/lib/verify.cjs +10 -797
- package/pan-wizard-core/bin/lib/worktree.cjs +123 -0
- package/pan-wizard-core/bin/pan-tools.cjs +78 -0
- package/pan-wizard-core/learnings/universal/autonomous-loop.md +56 -0
- package/pan-wizard-core/workflows/plan-phase.md +11 -0
- package/scripts/build-plugin.js +105 -0
- package/scripts/install-git-hooks.js +64 -0
- package/scripts/release-check.js +13 -2
package/bin/install-lib.cjs
CHANGED
|
@@ -360,9 +360,11 @@ function convertClaudeToOpencodeFrontmatter(content) {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
if (allowedTools.length > 0) {
|
|
363
|
-
|
|
363
|
+
// OpenCode 2026 agent frontmatter: `permission` (allow/ask/deny) replaced
|
|
364
|
+
// the deprecated `tools: {name: true}` map.
|
|
365
|
+
newLines.push('permission:');
|
|
364
366
|
for (const tool of allowedTools) {
|
|
365
|
-
newLines.push(` ${convertToolName(tool)}:
|
|
367
|
+
newLines.push(` ${convertToolName(tool)}: allow`);
|
|
366
368
|
}
|
|
367
369
|
}
|
|
368
370
|
|
|
@@ -431,6 +433,51 @@ function convertClaudeCommandToCodexSkill(content, skillName) {
|
|
|
431
433
|
return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
432
434
|
}
|
|
433
435
|
|
|
436
|
+
/**
|
|
437
|
+
* Generate the runtime-neutral skill adapter header (ADR-0028 Phase 1).
|
|
438
|
+
*
|
|
439
|
+
* Unlike the Codex/Copilot adapters, this header makes no assumptions about
|
|
440
|
+
* the consuming runtime — the same SKILL.md in the shared `.agents/skills/`
|
|
441
|
+
* tree is read by every runtime, so invocation, delegation, and interaction
|
|
442
|
+
* guidance are phrased in terms of "your runtime's native mechanism".
|
|
443
|
+
*/
|
|
444
|
+
function getUnifiedSkillAdapterHeader(skillName) {
|
|
445
|
+
return `<pan_skill_adapter>
|
|
446
|
+
PAN unified skill (Agent Skills standard, shared .agents/skills/ tree):
|
|
447
|
+
- This skill is invoked through your runtime's skill mechanism — slash command (\`/${skillName}\`), mention (\`$${skillName}\`), or skill picker.
|
|
448
|
+
- Treat all user text after the invocation as \`{{PAN_ARGS}}\`. If none is present, treat \`{{PAN_ARGS}}\` as empty.
|
|
449
|
+
- References like \`/pan-<name>\` in this document denote other PAN skills — invoke them with your runtime's own skill syntax.
|
|
450
|
+
|
|
451
|
+
Sub-agent orchestration:
|
|
452
|
+
- Any \`Task(...)\` pattern in referenced workflow docs is legacy syntax.
|
|
453
|
+
- Delegate with your runtime's native mechanism (Claude Code \`Task\` tool, Codex \`spawn_agent\`, Copilot CLI \`/agent\`, OpenCode agents, Gemini sub-agents).
|
|
454
|
+
- Treat legacy \`subagent_type\` names as the role of the sub-agent to invoke.
|
|
455
|
+
|
|
456
|
+
User interaction (runtimes without a native question tool):
|
|
457
|
+
- Ask one question at a time; show numbered options; mark the recommended option with **(recommended)**.
|
|
458
|
+
- Accept numbers ("1"), labels, or free-text descriptions as valid answers.
|
|
459
|
+
- Native interaction tools (e.g. AskUserQuestion blocks), where supported by your runtime, take precedence over this fallback.
|
|
460
|
+
</pan_skill_adapter>`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** Claude command → runtime-neutral SKILL.md (ADR-0028 Phase 1) */
|
|
464
|
+
function convertClaudeCommandToUnifiedSkill(content, skillName) {
|
|
465
|
+
// Normalize command mentions to the readable /pan-<name> form; the adapter
|
|
466
|
+
// header tells each runtime to map that onto its own invocation syntax.
|
|
467
|
+
let converted = convertSlashCommandsToCopilotSkillMentions(content);
|
|
468
|
+
converted = converted.replace(/\$ARGUMENTS\b/g, '{{PAN_ARGS}}');
|
|
469
|
+
const { frontmatter, body } = extractFrontmatterAndBody(converted);
|
|
470
|
+
let description = `Run PAN workflow ${skillName}.`;
|
|
471
|
+
if (frontmatter) {
|
|
472
|
+
const maybeDescription = extractFrontmatterField(frontmatter, 'description');
|
|
473
|
+
if (maybeDescription) description = maybeDescription;
|
|
474
|
+
}
|
|
475
|
+
description = toSingleLine(description);
|
|
476
|
+
const shortDescription = description.length > 180 ? `${description.slice(0, 177)}...` : description;
|
|
477
|
+
const adapter = getUnifiedSkillAdapterHeader(skillName);
|
|
478
|
+
return `---\nname: ${yamlQuote(skillName)}\ndescription: ${yamlQuote(description)}\nmetadata:\n short-description: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
479
|
+
}
|
|
480
|
+
|
|
434
481
|
/** Generate Copilot CLI skill adapter header */
|
|
435
482
|
function getCopilotSkillAdapterHeader(skillName) {
|
|
436
483
|
const invocation = `/pan-${skillName.replace(/^pan-/, '')}`;
|
|
@@ -618,49 +665,78 @@ function buildClaudeSkillShim(opts) {
|
|
|
618
665
|
}
|
|
619
666
|
|
|
620
667
|
/**
|
|
621
|
-
* Translate a
|
|
622
|
-
* into
|
|
668
|
+
* Translate a reasoning-depth directive from the generic PAN frontmatter
|
|
669
|
+
* shape into runtime-specific syntax (or prose fallback).
|
|
670
|
+
*
|
|
671
|
+
* Current shape (2026-06, adaptive-thinking era): PAN agents declare
|
|
672
|
+
* `effort: low|medium|high|xhigh|max` in frontmatter. Claude Code consumes
|
|
673
|
+
* `effort` natively — adaptive thinking replaced fixed thinking budgets,
|
|
674
|
+
* and `thinking_budget`-style controls were removed from the API on
|
|
675
|
+
* Opus 4.7+ models. Runtimes without a native effort field get a prose
|
|
676
|
+
* preamble that coaches the model to think before tool calls.
|
|
623
677
|
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
* that coaches the model to think step-by-step before tool calls.
|
|
678
|
+
* Legacy shape `{enabled: boolean, budget: number}` (from the retired
|
|
679
|
+
* `thinking:` / `thinking_budget:` fields) is still accepted; budgets map
|
|
680
|
+
* to effort levels (≤4000 → medium, ≤6000 → high, >6000 → xhigh).
|
|
628
681
|
*
|
|
629
682
|
* @param {string} runtime - 'claude'|'codex'|'gemini'|'opencode'|'copilot'
|
|
630
|
-
* @param {Object} directive - {
|
|
683
|
+
* @param {Object} directive - {effort: string} or legacy {enabled, budget}
|
|
631
684
|
* @returns {{frontmatter: Object, preamble: string}} Translated directive.
|
|
632
685
|
* `frontmatter` = fields to add to the agent's YAML header.
|
|
633
686
|
* `preamble` = prose to inject at top of agent prompt.
|
|
634
687
|
*/
|
|
688
|
+
const EFFORT_LEVELS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
689
|
+
|
|
690
|
+
function effortFromLegacyBudget(budget) {
|
|
691
|
+
const b = Number(budget) > 0 ? Number(budget) : 2000;
|
|
692
|
+
if (b <= 4000) return 'medium';
|
|
693
|
+
if (b <= 6000) return 'high';
|
|
694
|
+
return 'xhigh';
|
|
695
|
+
}
|
|
696
|
+
|
|
635
697
|
function translateThinkingDirective(runtime, directive) {
|
|
636
698
|
const result = { frontmatter: {}, preamble: '' };
|
|
637
|
-
if (!directive
|
|
638
|
-
|
|
699
|
+
if (!directive) return result;
|
|
700
|
+
|
|
701
|
+
let effort = null;
|
|
702
|
+
const rawEffort = typeof directive.effort === 'string' ? directive.effort.toLowerCase().trim() : '';
|
|
703
|
+
if (EFFORT_LEVELS.includes(rawEffort)) {
|
|
704
|
+
effort = rawEffort;
|
|
705
|
+
} else if (directive.enabled) {
|
|
706
|
+
effort = effortFromLegacyBudget(directive.budget);
|
|
707
|
+
}
|
|
708
|
+
if (!effort) return result;
|
|
639
709
|
|
|
640
710
|
switch (runtime) {
|
|
641
711
|
case 'claude':
|
|
642
|
-
// Claude Code consumes
|
|
643
|
-
result.frontmatter = {
|
|
712
|
+
// Claude Code consumes `effort` natively (adaptive thinking is the default).
|
|
713
|
+
result.frontmatter = { effort };
|
|
644
714
|
return result;
|
|
645
715
|
case 'codex':
|
|
646
716
|
case 'opencode':
|
|
647
717
|
case 'gemini':
|
|
648
718
|
case 'copilot':
|
|
649
|
-
default:
|
|
650
|
-
// Prose fallback — host runtime has no native
|
|
651
|
-
|
|
719
|
+
default: {
|
|
720
|
+
// Prose fallback — host runtime has no native effort field.
|
|
721
|
+
const depth = effort === 'low'
|
|
722
|
+
? 'Keep reasoning brief and focused on the immediate task.'
|
|
723
|
+
: effort === 'medium'
|
|
724
|
+
? 'Reason about edge cases, hidden dependencies, and likely failure modes.'
|
|
725
|
+
: 'Be thorough: reason about edge cases, hidden dependencies, and likely failure modes, preferring deeper analysis over speed.';
|
|
726
|
+
result.preamble = `Think through the problem step-by-step before taking any action. ${depth} Only after that, call tools or write output.`;
|
|
652
727
|
return result;
|
|
728
|
+
}
|
|
653
729
|
}
|
|
654
730
|
}
|
|
655
731
|
|
|
656
732
|
/**
|
|
657
|
-
* Strip
|
|
658
|
-
* agent markdown file for runtimes that
|
|
659
|
-
*
|
|
660
|
-
*
|
|
733
|
+
* Strip reasoning-depth frontmatter (`effort`, plus the legacy `thinking` /
|
|
734
|
+
* `thinking_budget` pair) from an agent markdown file for runtimes that
|
|
735
|
+
* don't support those fields natively. When a directive was present,
|
|
736
|
+
* inject a prose preamble at the top of the body instead.
|
|
661
737
|
*
|
|
662
|
-
* Claude runtime is a no-op —
|
|
663
|
-
*
|
|
738
|
+
* Claude runtime is a no-op — `effort` stays in frontmatter so Claude Code
|
|
739
|
+
* consumes it natively.
|
|
664
740
|
*
|
|
665
741
|
* @param {string} content - Full agent .md content
|
|
666
742
|
* @param {string} runtime - 'claude'|'codex'|'gemini'|'opencode'|'copilot'
|
|
@@ -675,21 +751,25 @@ function stripThinkingFrontmatter(content, runtime) {
|
|
|
675
751
|
|
|
676
752
|
const thinkingValue = extractFrontmatterField(frontmatter, 'thinking');
|
|
677
753
|
const budgetValue = extractFrontmatterField(frontmatter, 'thinking_budget');
|
|
678
|
-
|
|
754
|
+
const effortValue = extractFrontmatterField(frontmatter, 'effort');
|
|
755
|
+
if (!thinkingValue && !budgetValue && !effortValue) return content;
|
|
679
756
|
|
|
680
|
-
// Remove the
|
|
757
|
+
// Remove the fields (match on their own lines only).
|
|
681
758
|
let fmBody = frontmatter
|
|
682
759
|
.replace(/^thinking:\s*[^\n]*\n?/gm, '')
|
|
683
|
-
.replace(/^thinking_budget:\s*[^\n]*\n?/gm, '')
|
|
760
|
+
.replace(/^thinking_budget:\s*[^\n]*\n?/gm, '')
|
|
761
|
+
.replace(/^effort:\s*[^\n]*\n?/gm, '');
|
|
684
762
|
|
|
685
763
|
const rebuilt = `---\n${fmBody.replace(/^---\n|\n---$/g, '')}\n---`;
|
|
686
764
|
let out = rebuilt.replace(/\n\n+/g, '\n\n') + '\n\n';
|
|
687
765
|
|
|
688
|
-
//
|
|
766
|
+
// Build the directive: `effort` wins; legacy thinking/budget falls back.
|
|
689
767
|
const enabled = String(thinkingValue || '').toLowerCase().trim() === 'enabled'
|
|
690
768
|
|| String(thinkingValue || '').toLowerCase().trim() === 'true';
|
|
691
|
-
|
|
692
|
-
|
|
769
|
+
const directive = effortValue
|
|
770
|
+
? { effort: String(effortValue) }
|
|
771
|
+
: (enabled ? { enabled: true, budget: Number(budgetValue) || 2000 } : null);
|
|
772
|
+
if (directive) {
|
|
693
773
|
const { preamble } = translateThinkingDirective(runtime, directive);
|
|
694
774
|
if (preamble) {
|
|
695
775
|
out += `<!-- pan:thinking -->\n${preamble}\n<!-- /pan:thinking -->\n\n`;
|
|
@@ -700,6 +780,214 @@ function stripThinkingFrontmatter(content, runtime) {
|
|
|
700
780
|
return out;
|
|
701
781
|
}
|
|
702
782
|
|
|
783
|
+
// ─── Copilot CLI hooks config (2026-06) ─────────────────────────────────────
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Build a Copilot CLI hooks config object for `.github/hooks/pan.json`.
|
|
787
|
+
*
|
|
788
|
+
* Copilot CLI reads hook configuration from `.github/hooks/*.json` (repo) with
|
|
789
|
+
* a `version: 1` envelope and per-event arrays of `{type, command, ...}`
|
|
790
|
+
* entries — NOT from `config.json`. A command hook supplies one of `bash`,
|
|
791
|
+
* `powershell`, or `command` (cross-platform fallback). PAN's hooks are
|
|
792
|
+
* Node.js scripts invoked via `node …`, so the cross-platform `command` key
|
|
793
|
+
* is the correct fit on every OS. Verified against
|
|
794
|
+
* docs.github.com/en/copilot/reference/hooks-configuration (2026-06).
|
|
795
|
+
*
|
|
796
|
+
* Pure function so the generated config is unit-testable.
|
|
797
|
+
*
|
|
798
|
+
* @param {Object} commands
|
|
799
|
+
* @param {string} commands.updateCheckCommand - node invocation for pan-check-update.js
|
|
800
|
+
* @param {string} commands.contextMonitorCommand - node invocation for pan-context-monitor.js
|
|
801
|
+
* @returns {Object} A `.github/hooks/pan.json` config object
|
|
802
|
+
*/
|
|
803
|
+
function buildCopilotHooksConfig(commands) {
|
|
804
|
+
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand } = commands || {};
|
|
805
|
+
const config = { version: 1, hooks: {} };
|
|
806
|
+
if (updateCheckCommand) {
|
|
807
|
+
config.hooks.sessionStart = [{ type: 'command', command: updateCheckCommand }];
|
|
808
|
+
}
|
|
809
|
+
if (contextMonitorCommand) {
|
|
810
|
+
config.hooks.postToolUse = [{ type: 'command', command: contextMonitorCommand }];
|
|
811
|
+
}
|
|
812
|
+
// subagentStop is Copilot's SubagentStop equivalent (verified docs.github.com
|
|
813
|
+
// 2026-06) — carries the cost + trace loggers, same as Claude/Gemini.
|
|
814
|
+
const subagentStop = [];
|
|
815
|
+
if (costLoggerCommand) subagentStop.push({ type: 'command', command: costLoggerCommand });
|
|
816
|
+
if (traceLoggerCommand) subagentStop.push({ type: 'command', command: traceLoggerCommand });
|
|
817
|
+
if (subagentStop.length > 0) {
|
|
818
|
+
config.hooks.subagentStop = subagentStop;
|
|
819
|
+
}
|
|
820
|
+
return config;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// ─── Codex hooks config (2026-06) ───────────────────────────────────────────
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Cross-runtime hook event map (canonical PAN event → per-runtime name).
|
|
827
|
+
* Claude/Gemini register in settings.json; Codex in `.codex/hooks.json`
|
|
828
|
+
* (Claude-compatible PascalCase events — verified developers.openai.com
|
|
829
|
+
* 2026-06, project-scoped hooks load once the project is trusted); Copilot
|
|
830
|
+
* in `.github/hooks/pan.json` (camelCase — verified docs.github.com 2026-06).
|
|
831
|
+
* OpenCode has no hook support.
|
|
832
|
+
*/
|
|
833
|
+
const HOOK_EVENT_MAP = Object.freeze({
|
|
834
|
+
claude: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop' },
|
|
835
|
+
gemini: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop' },
|
|
836
|
+
codex: { surface: 'hooks.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop' },
|
|
837
|
+
copilot: { surface: 'hooks/pan.json', sessionStart: 'sessionStart', postToolUse: 'postToolUse', subagentStop: 'subagentStop' },
|
|
838
|
+
opencode: null,
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Merge PAN hook registrations into a `.codex/hooks.json` config.
|
|
843
|
+
*
|
|
844
|
+
* Codex hooks use the Claude-style shape — `{hooks: {EventName: [{matcher?,
|
|
845
|
+
* hooks: [{type: 'command', command}]}]}}` with PascalCase event names —
|
|
846
|
+
* and `.codex/hooks.json` is a single shared file, so PAN entries are merged
|
|
847
|
+
* non-destructively: existing non-PAN entries are preserved, and PAN entries
|
|
848
|
+
* are deduplicated by their pan-* command substring (idempotent reinstall).
|
|
849
|
+
*
|
|
850
|
+
* @param {object|null} existing - Parsed existing hooks.json content, or null
|
|
851
|
+
* @param {Object} commands - node invocations keyed like buildCopilotHooksConfig
|
|
852
|
+
* @returns {object} Merged config object to serialize back to hooks.json
|
|
853
|
+
*/
|
|
854
|
+
function mergeCodexHooksConfig(existing, commands) {
|
|
855
|
+
const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand } = commands || {};
|
|
856
|
+
const config = (existing && typeof existing === 'object') ? existing : {};
|
|
857
|
+
if (!config.hooks || typeof config.hooks !== 'object') config.hooks = {};
|
|
858
|
+
|
|
859
|
+
const wanted = [
|
|
860
|
+
['SessionStart', updateCheckCommand, 'pan-check-update'],
|
|
861
|
+
['PostToolUse', contextMonitorCommand, 'pan-context-monitor'],
|
|
862
|
+
['SubagentStop', costLoggerCommand, 'pan-cost-logger'],
|
|
863
|
+
['SubagentStop', traceLoggerCommand, 'pan-trace-logger'],
|
|
864
|
+
];
|
|
865
|
+
|
|
866
|
+
for (const [event, command, marker] of wanted) {
|
|
867
|
+
if (!command) continue;
|
|
868
|
+
if (!Array.isArray(config.hooks[event])) config.hooks[event] = [];
|
|
869
|
+
const present = config.hooks[event].some(group =>
|
|
870
|
+
Array.isArray(group.hooks) && group.hooks.some(h => h.command && h.command.includes(marker)));
|
|
871
|
+
if (!present) {
|
|
872
|
+
config.hooks[event].push({ hooks: [{ type: 'command', command }] });
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return config;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Remove PAN hook registrations from a `.codex/hooks.json` config.
|
|
880
|
+
* @param {object|null} existing - Parsed existing hooks.json content
|
|
881
|
+
* @returns {object|null} Config without PAN entries, or null when nothing
|
|
882
|
+
* meaningful remains (caller should delete the file).
|
|
883
|
+
*/
|
|
884
|
+
function removeCodexPanHooks(existing) {
|
|
885
|
+
if (!existing || typeof existing !== 'object' || !existing.hooks) return existing || null;
|
|
886
|
+
for (const event of Object.keys(existing.hooks)) {
|
|
887
|
+
if (!Array.isArray(existing.hooks[event])) continue;
|
|
888
|
+
existing.hooks[event] = existing.hooks[event].filter(group =>
|
|
889
|
+
!(Array.isArray(group.hooks) && group.hooks.some(h => h.command && /pan-(check-update|context-monitor|cost-logger|trace-logger)/.test(h.command))));
|
|
890
|
+
if (existing.hooks[event].length === 0) delete existing.hooks[event];
|
|
891
|
+
}
|
|
892
|
+
if (Object.keys(existing.hooks).length === 0) delete existing.hooks;
|
|
893
|
+
return Object.keys(existing).length === 0 ? null : existing;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// ─── Codex agents (TOML) + trust notice (2026-06) ───────────────────────────
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Convert a Claude agent markdown file into a Codex custom-agent TOML file.
|
|
900
|
+
*
|
|
901
|
+
* Codex custom agents are standalone TOML files in `.codex/agents/` (project)
|
|
902
|
+
* or `~/.codex/agents/` (personal). Required fields: name, description,
|
|
903
|
+
* developer_instructions. PAN's `effort` frontmatter maps to Codex's
|
|
904
|
+
* `model_reasoning_effort`. Model/tier is left to inherit from the parent
|
|
905
|
+
* session (PAN tiers don't map to OpenAI model ids).
|
|
906
|
+
* Verified against developers.openai.com/codex/subagents (2026-06).
|
|
907
|
+
*
|
|
908
|
+
* @param {string} content - Full Claude agent .md content
|
|
909
|
+
* @returns {string|null} TOML string, or null when content has no frontmatter
|
|
910
|
+
*/
|
|
911
|
+
function convertClaudeAgentToCodexToml(content) {
|
|
912
|
+
if (typeof content !== 'string' || !content.startsWith('---')) return null;
|
|
913
|
+
const endIndex = content.indexOf('---', 3);
|
|
914
|
+
if (endIndex === -1) return null;
|
|
915
|
+
|
|
916
|
+
const frontmatter = content.substring(3, endIndex);
|
|
917
|
+
const body = content.substring(endIndex + 3).replace(/^\n+/, '');
|
|
918
|
+
|
|
919
|
+
const field = (key) => {
|
|
920
|
+
const m = frontmatter.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'));
|
|
921
|
+
return m ? m[1].trim() : '';
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
const name = field('name');
|
|
925
|
+
const description = field('description');
|
|
926
|
+
const effort = field('effort').toLowerCase();
|
|
927
|
+
if (!name) return null;
|
|
928
|
+
|
|
929
|
+
// TOML multi-line basic string: escape backslashes, then break any """ runs.
|
|
930
|
+
const instructions = body
|
|
931
|
+
.replace(/\\/g, '\\\\')
|
|
932
|
+
.replace(/"""/g, '"\\""');
|
|
933
|
+
|
|
934
|
+
const lines = [
|
|
935
|
+
`name = ${JSON.stringify(name)}`,
|
|
936
|
+
`description = ${JSON.stringify(description)}`,
|
|
937
|
+
];
|
|
938
|
+
if (['minimal', 'low', 'medium', 'high', 'xhigh'].includes(effort)) {
|
|
939
|
+
lines.push(`model_reasoning_effort = ${JSON.stringify(effort)}`);
|
|
940
|
+
}
|
|
941
|
+
lines.push('developer_instructions = """');
|
|
942
|
+
lines.push(instructions.replace(/\n+$/, ''));
|
|
943
|
+
lines.push('"""');
|
|
944
|
+
return lines.join('\n') + '\n';
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
/**
|
|
948
|
+
* Informational notice shown after a local (project-scoped) Codex install.
|
|
949
|
+
*
|
|
950
|
+
* Codex gates project-level `.codex/` configuration (including custom agents)
|
|
951
|
+
* behind project trust: untrusted projects silently skip it. Pure function so
|
|
952
|
+
* the installer message is unit-testable.
|
|
953
|
+
*
|
|
954
|
+
* @returns {string} Multi-line plain-text notice (no ANSI codes).
|
|
955
|
+
*/
|
|
956
|
+
function codexTrustNotice() {
|
|
957
|
+
return [
|
|
958
|
+
'Codex trust note: project-scoped .codex/ configuration (including the',
|
|
959
|
+
'installed pan-* agents) only loads once the project is trusted in Codex.',
|
|
960
|
+
'If commands or agents seem missing, approve the project when Codex prompts,',
|
|
961
|
+
'or set trust_level for this path in ~/.codex/config.toml.',
|
|
962
|
+
].join('\n');
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// ─── Gemini CLI → Antigravity transition notice (2026-06) ──────────────────
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Informational notice shown after a Gemini CLI install.
|
|
969
|
+
*
|
|
970
|
+
* Google announced (2026-05-19) that from 2026-06-18 the Gemini CLI serves
|
|
971
|
+
* Gemini Code Assist (Standard/Enterprise) customers only; individual
|
|
972
|
+
* free / AI Pro / Ultra accounts are directed to Antigravity CLI instead.
|
|
973
|
+
* PAN's --gemini target installs for Gemini CLI; Antigravity CLI is not yet
|
|
974
|
+
* a PAN install target (tracked in docs/ECOSYSTEM-REVIEW-2026-06.md).
|
|
975
|
+
*
|
|
976
|
+
* Pure function so the installer message is unit-testable.
|
|
977
|
+
*
|
|
978
|
+
* @returns {string} Multi-line plain-text notice (no ANSI codes).
|
|
979
|
+
*/
|
|
980
|
+
function geminiTransitionNotice() {
|
|
981
|
+
return [
|
|
982
|
+
'Gemini CLI transition notice: from June 18, 2026, Google\'s Gemini CLI serves',
|
|
983
|
+
'Gemini Code Assist (Standard/Enterprise) customers; individual free / AI Pro /',
|
|
984
|
+
'Ultra accounts are directed to Antigravity CLI instead. This install targets',
|
|
985
|
+
'Gemini CLI. Antigravity CLI is not yet a PAN install target, but it reads the',
|
|
986
|
+
'shared .agents/skills/ tree natively — PAN skills installed there (today via',
|
|
987
|
+
'the --codex target) are usable from Antigravity in the same project.',
|
|
988
|
+
].join('\n');
|
|
989
|
+
}
|
|
990
|
+
|
|
703
991
|
// ─── Opus 4.7 Capability Detection ──────────────────────────────────────────
|
|
704
992
|
|
|
705
993
|
/**
|
|
@@ -707,7 +995,11 @@ function stripThinkingFrontmatter(content, runtime) {
|
|
|
707
995
|
* Used by installer to warn users when their default model lacks features
|
|
708
996
|
* PAN 2.10+ relies on (1M context, extended thinking, prompt caching).
|
|
709
997
|
*
|
|
710
|
-
*
|
|
998
|
+
* Capability data refreshed 2026-06: Fable 5 and Opus 4.8/4.7/4.6 plus
|
|
999
|
+
* Sonnet 4.6 all carry a 1M context window; only the legacy Opus/Sonnet
|
|
1000
|
+
* 4.0–4.5 generations are 200K.
|
|
1001
|
+
*
|
|
1002
|
+
* @param {string} modelName - e.g. "claude-fable-5", "claude-opus-4-8", "gpt-5"
|
|
711
1003
|
* @returns {{has_1m_ctx: boolean, has_thinking: boolean, has_cache: boolean, tier: string}}
|
|
712
1004
|
*/
|
|
713
1005
|
function detectModelCapabilities(modelName) {
|
|
@@ -716,13 +1008,21 @@ function detectModelCapabilities(modelName) {
|
|
|
716
1008
|
const n = modelName.toLowerCase();
|
|
717
1009
|
|
|
718
1010
|
// Anthropic Claude family
|
|
719
|
-
if (n.includes('
|
|
1011
|
+
if (n.includes('fable')) {
|
|
1012
|
+
return { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'reasoning' };
|
|
1013
|
+
}
|
|
1014
|
+
if (n.includes('opus-4-8') || n.includes('opus-4.8')
|
|
1015
|
+
|| n.includes('opus-4-7') || n.includes('opus-4.7')
|
|
1016
|
+
|| n.includes('opus-4-6') || n.includes('opus-4.6')) {
|
|
720
1017
|
return { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'reasoning' };
|
|
721
1018
|
}
|
|
722
|
-
if (n.includes('opus-4
|
|
1019
|
+
if (n.includes('opus-4')) { // legacy Opus 4.0 / 4.1 / 4.5 — 200K context
|
|
723
1020
|
return { has_1m_ctx: false, has_thinking: true, has_cache: true, tier: 'reasoning' };
|
|
724
1021
|
}
|
|
725
|
-
if (n.includes('sonnet-4-6') || n.includes('sonnet-4.6')
|
|
1022
|
+
if (n.includes('sonnet-4-6') || n.includes('sonnet-4.6')) {
|
|
1023
|
+
return { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'mid' };
|
|
1024
|
+
}
|
|
1025
|
+
if (n.includes('sonnet-4')) { // legacy Sonnet 4.0 / 4.5 — 200K context
|
|
726
1026
|
return { has_1m_ctx: false, has_thinking: true, has_cache: true, tier: 'mid' };
|
|
727
1027
|
}
|
|
728
1028
|
if (n.includes('haiku-4-5') || n.includes('haiku-4.5') || n.includes('haiku-4')) {
|
|
@@ -778,33 +1078,48 @@ const path_v = require('path');
|
|
|
778
1078
|
* Verify installed files against the manifest.
|
|
779
1079
|
*
|
|
780
1080
|
* For each entry in manifest.files, check the file is present on disk at
|
|
781
|
-
* the expected location. We do NOT re-hash
|
|
782
|
-
* from these files, so re-hashing
|
|
783
|
-
*
|
|
784
|
-
*
|
|
1081
|
+
* the expected location AND non-empty. We do NOT re-hash: the manifest was
|
|
1082
|
+
* just written from these files, so re-hashing is tautological — a 0-byte
|
|
1083
|
+
* copy gets a 0-byte hash recorded and would "verify" cleanly. The size
|
|
1084
|
+
* check is what actually catches the canonical silent copy failure
|
|
1085
|
+
* (truncated/empty file landed instead of content).
|
|
785
1086
|
*
|
|
786
|
-
* Also verifies critical anchor files that, if missing, mean the
|
|
787
|
-
* unusable: pan-tools.cjs, the dispatcher.
|
|
1087
|
+
* Also verifies critical anchor files that, if missing or empty, mean the
|
|
1088
|
+
* install is unusable: pan-tools.cjs, the dispatcher.
|
|
788
1089
|
*
|
|
789
1090
|
* @param {string} configDir - install root (e.g., ~/.claude or ./.codex)
|
|
790
1091
|
* @param {object} manifest - the manifest object returned by writeManifest()
|
|
791
|
-
* @returns {object} { ok: bool, missing: string[], warnings: string[] }
|
|
1092
|
+
* @returns {object} { ok: bool, missing: string[], empty: string[], warnings: string[] }
|
|
792
1093
|
*/
|
|
793
1094
|
function verifyInstall(configDir, manifest) {
|
|
794
1095
|
const missing = [];
|
|
1096
|
+
const empty = [];
|
|
795
1097
|
const warnings = [];
|
|
796
1098
|
|
|
797
|
-
// Critical anchor: pan-tools.cjs MUST exist; without it,
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1099
|
+
// Critical anchor: pan-tools.cjs MUST exist and carry content; without it,
|
|
1100
|
+
// no command works.
|
|
1101
|
+
const dispatcherRel = 'pan-wizard-core/bin/pan-tools.cjs';
|
|
1102
|
+
const dispatcherPath = path_v.join(configDir, dispatcherRel);
|
|
1103
|
+
try {
|
|
1104
|
+
const st = fs_v.statSync(dispatcherPath);
|
|
1105
|
+
if (st.size === 0) {
|
|
1106
|
+
empty.push(`${dispatcherRel} (dispatcher is empty — copy failed; install is unusable)`);
|
|
1107
|
+
}
|
|
1108
|
+
} catch {
|
|
1109
|
+
missing.push(`${dispatcherRel} (dispatcher — install is unusable without it)`);
|
|
801
1110
|
}
|
|
802
1111
|
|
|
803
|
-
// Manifest-level: every tracked file must exist.
|
|
1112
|
+
// Manifest-level: every tracked file must exist and be non-empty. No
|
|
1113
|
+
// shipped PAN file is legitimately 0 bytes.
|
|
804
1114
|
if (manifest && manifest.files) {
|
|
805
1115
|
for (const rel of Object.keys(manifest.files)) {
|
|
806
1116
|
const abs = path_v.join(configDir, rel);
|
|
807
|
-
|
|
1117
|
+
try {
|
|
1118
|
+
const st = fs_v.statSync(abs);
|
|
1119
|
+
if (st.size === 0) {
|
|
1120
|
+
empty.push(rel);
|
|
1121
|
+
}
|
|
1122
|
+
} catch {
|
|
808
1123
|
missing.push(rel);
|
|
809
1124
|
}
|
|
810
1125
|
}
|
|
@@ -812,7 +1127,284 @@ function verifyInstall(configDir, manifest) {
|
|
|
812
1127
|
warnings.push('manifest is missing or has no files entry — verification is degraded');
|
|
813
1128
|
}
|
|
814
1129
|
|
|
815
|
-
return { ok: missing.length === 0, missing, warnings };
|
|
1130
|
+
return { ok: missing.length === 0 && empty.length === 0, missing, empty, warnings };
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// ─── Claude Code plugin packaging (2026-06) ─────────────────────────────────
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Build the .claude-plugin/plugin.json manifest for the PAN plugin build.
|
|
1137
|
+
* Format verified against code.claude.com/docs/en/plugins-reference (2026-06):
|
|
1138
|
+
* manifest is optional metadata; components auto-discover from commands/,
|
|
1139
|
+
* agents/, hooks/hooks.json in the plugin root.
|
|
1140
|
+
*
|
|
1141
|
+
* @param {object} pkg - parsed package.json
|
|
1142
|
+
* @returns {object} plugin.json object
|
|
1143
|
+
*/
|
|
1144
|
+
function buildPluginManifest(pkg) {
|
|
1145
|
+
return {
|
|
1146
|
+
name: 'pan-wizard',
|
|
1147
|
+
displayName: 'PAN Wizard',
|
|
1148
|
+
version: pkg.version,
|
|
1149
|
+
description: pkg.description || 'Structured, phase-based planning and execution for AI coding agents.',
|
|
1150
|
+
author: { name: 'PAN Wizard contributors', url: 'https://github.com/oharms/PanWizard' },
|
|
1151
|
+
repository: 'https://github.com/oharms/PanWizard',
|
|
1152
|
+
license: pkg.license || 'MIT',
|
|
1153
|
+
keywords: ['planning', 'workflow', 'agents', 'phases'],
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Build the plugin hooks/hooks.json — PAN's four hooks registered with
|
|
1159
|
+
* ${CLAUDE_PLUGIN_ROOT}-anchored commands (the documented plugin-relative
|
|
1160
|
+
* path convention for hook configs).
|
|
1161
|
+
* @returns {object} hooks.json object
|
|
1162
|
+
*/
|
|
1163
|
+
function buildPluginHooksConfig() {
|
|
1164
|
+
const hook = (script) => ({
|
|
1165
|
+
hooks: [{ type: 'command', command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${script}"` }],
|
|
1166
|
+
});
|
|
1167
|
+
return {
|
|
1168
|
+
hooks: {
|
|
1169
|
+
SessionStart: [hook('pan-check-update.js')],
|
|
1170
|
+
PostToolUse: [hook('pan-context-monitor.js')],
|
|
1171
|
+
SubagentStop: [hook('pan-cost-logger.js'), hook('pan-trace-logger.js')],
|
|
1172
|
+
},
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// ─── Native Claude Code workflows (2026-06) ─────────────────────────────────
|
|
1177
|
+
//
|
|
1178
|
+
// Claude Code discovers deterministic orchestration scripts in
|
|
1179
|
+
// `.claude/workflows/*.js` (export const meta + agent()/parallel()/pipeline()
|
|
1180
|
+
// hooks). PAN ships native scripts only for protocols that are genuinely
|
|
1181
|
+
// deterministic fan-outs — the markdown protocols remain the source of truth
|
|
1182
|
+
// for judgment-heavy flows. Claude-only; other runtimes have no equivalent.
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Build the PAN native workflow scripts.
|
|
1186
|
+
* Pure function — returns [{name, content}] for the installer to write.
|
|
1187
|
+
* @returns {Array<{name: string, content: string}>}
|
|
1188
|
+
*/
|
|
1189
|
+
function buildNativeWorkflowScripts() {
|
|
1190
|
+
const reviewPipeline = `export const meta = {
|
|
1191
|
+
name: 'pan-review-pipeline',
|
|
1192
|
+
description: 'PAN deep review: reviewer + hardener fan-out, meta-reviewer merge',
|
|
1193
|
+
whenToUse: 'Deterministic version of the /pan-review-deep fan-out. Pass the phase number or a description of the change set as args.',
|
|
1194
|
+
phases: [
|
|
1195
|
+
{ title: 'Find', detail: 'reviewer + security hardener in parallel' },
|
|
1196
|
+
{ title: 'Merge', detail: 'meta-reviewer dedupes, disputes, and issues the verdict' },
|
|
1197
|
+
],
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const target = (typeof args === 'string' && args.trim())
|
|
1201
|
+
? args.trim()
|
|
1202
|
+
: 'the uncommitted/current phase changes in this repository'
|
|
1203
|
+
|
|
1204
|
+
const FINDINGS = {
|
|
1205
|
+
type: 'object',
|
|
1206
|
+
properties: {
|
|
1207
|
+
findings: {
|
|
1208
|
+
type: 'array',
|
|
1209
|
+
items: {
|
|
1210
|
+
type: 'object',
|
|
1211
|
+
properties: {
|
|
1212
|
+
title: { type: 'string' },
|
|
1213
|
+
file: { type: 'string' },
|
|
1214
|
+
severity: { type: 'string', enum: ['critical', 'major', 'minor', 'info'] },
|
|
1215
|
+
detail: { type: 'string' },
|
|
1216
|
+
},
|
|
1217
|
+
required: ['title', 'severity', 'detail'],
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
},
|
|
1221
|
+
required: ['findings'],
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
phase('Find')
|
|
1225
|
+
const results = await parallel([
|
|
1226
|
+
() => agent(
|
|
1227
|
+
'Review ' + target + '. Report EVERY finding you see, tagged with the right severity tier — coverage, not filtering; the meta-reviewer downstream is the filter.',
|
|
1228
|
+
{ agentType: 'pan-reviewer', label: 'review', phase: 'Find', schema: FINDINGS }),
|
|
1229
|
+
() => agent(
|
|
1230
|
+
'Security-audit ' + target + ' (OWASP Top 10 + STRIDE). Report every concrete finding with severity.',
|
|
1231
|
+
{ agentType: 'pan-hardener', label: 'harden', phase: 'Find', schema: FINDINGS }),
|
|
1232
|
+
])
|
|
1233
|
+
const found = results.filter(Boolean).flatMap(r => r.findings)
|
|
1234
|
+
log(found.length + ' raw findings collected')
|
|
1235
|
+
|
|
1236
|
+
phase('Merge')
|
|
1237
|
+
const VERDICT = {
|
|
1238
|
+
type: 'object',
|
|
1239
|
+
properties: {
|
|
1240
|
+
verdict: { type: 'string', enum: ['ok', 'ok_with_minor', 'fix_before_merge', 'review_required', 'block'] },
|
|
1241
|
+
confirmed: { type: 'array', items: { type: 'object' } },
|
|
1242
|
+
disputed: { type: 'array', items: { type: 'object' } },
|
|
1243
|
+
summary: { type: 'string' },
|
|
1244
|
+
},
|
|
1245
|
+
required: ['verdict', 'summary'],
|
|
1246
|
+
}
|
|
1247
|
+
const merged = await agent(
|
|
1248
|
+
'Merge these raw review findings: dedupe overlaps, dispute overstated ones, then issue a single verdict on the PAN ladder (ok / ok_with_minor / fix_before_merge / review_required / block).\\n\\nFindings:\\n' + JSON.stringify(found, null, 2),
|
|
1249
|
+
{ agentType: 'pan-meta-reviewer', label: 'merge', phase: 'Merge', schema: VERDICT })
|
|
1250
|
+
|
|
1251
|
+
return merged
|
|
1252
|
+
`;
|
|
1253
|
+
|
|
1254
|
+
const mapCodebase = `export const meta = {
|
|
1255
|
+
name: 'pan-map-codebase',
|
|
1256
|
+
description: 'PAN codebase mapping: shard fan-out per top-level area, then synthesis',
|
|
1257
|
+
whenToUse: 'Deterministic version of the /pan-map-codebase shard pattern for repositories too large for a single pass.',
|
|
1258
|
+
phases: [
|
|
1259
|
+
{ title: 'Scan', detail: 'discover top-level areas worth documenting' },
|
|
1260
|
+
{ title: 'Map', detail: 'one documenter per area, in parallel' },
|
|
1261
|
+
{ title: 'Synthesize', detail: 'merge area maps into one codebase overview' },
|
|
1262
|
+
],
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
phase('Scan')
|
|
1266
|
+
const AREAS = {
|
|
1267
|
+
type: 'object',
|
|
1268
|
+
properties: { areas: { type: 'array', items: { type: 'string' } } },
|
|
1269
|
+
required: ['areas'],
|
|
1270
|
+
}
|
|
1271
|
+
const scan = await agent(
|
|
1272
|
+
'List the top-level areas of this repository worth documenting separately (source dirs, test dirs, docs, infra). Skip vendored/generated content (node_modules, dist, build artifacts). Return at most 8 area paths.',
|
|
1273
|
+
{ label: 'scan', phase: 'Scan', schema: AREAS })
|
|
1274
|
+
const areas = (scan && scan.areas ? scan.areas : []).slice(0, 8)
|
|
1275
|
+
if (areas.length === 0) return { error: 'no areas discovered' }
|
|
1276
|
+
log('mapping ' + areas.length + ' areas')
|
|
1277
|
+
|
|
1278
|
+
phase('Map')
|
|
1279
|
+
const AREA_MAP = {
|
|
1280
|
+
type: 'object',
|
|
1281
|
+
properties: {
|
|
1282
|
+
area: { type: 'string' },
|
|
1283
|
+
purpose: { type: 'string' },
|
|
1284
|
+
key_files: { type: 'array', items: { type: 'string' } },
|
|
1285
|
+
conventions: { type: 'array', items: { type: 'string' } },
|
|
1286
|
+
risks: { type: 'array', items: { type: 'string' } },
|
|
1287
|
+
},
|
|
1288
|
+
required: ['area', 'purpose'],
|
|
1289
|
+
}
|
|
1290
|
+
const maps = await parallel(areas.map(a => () =>
|
|
1291
|
+
agent('Document the "' + a + '" area of this repository: purpose, key files, conventions in force, and risks/gotchas. Be specific and cite file paths.',
|
|
1292
|
+
{ agentType: 'pan-document_code', label: 'map:' + a, phase: 'Map', schema: AREA_MAP })))
|
|
1293
|
+
|
|
1294
|
+
phase('Synthesize')
|
|
1295
|
+
const synthesis = await agent(
|
|
1296
|
+
'Merge these per-area maps into one coherent codebase overview (architecture summary, cross-area conventions, integration points, top risks). Write the result to .planning/codebase/ using the PAN codebase templates if a .planning directory exists; otherwise return it as your final answer.\\n\\nArea maps:\\n' + JSON.stringify(maps.filter(Boolean), null, 2),
|
|
1297
|
+
{ label: 'synthesize', phase: 'Synthesize' })
|
|
1298
|
+
|
|
1299
|
+
return { areas_mapped: maps.filter(Boolean).length, synthesis }
|
|
1300
|
+
`;
|
|
1301
|
+
|
|
1302
|
+
return [
|
|
1303
|
+
{ name: 'pan-review-pipeline.js', content: reviewPipeline },
|
|
1304
|
+
{ name: 'pan-map-codebase.js', content: mapCodebase },
|
|
1305
|
+
];
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// ─── AGENTS.md universal rules layer (ADR-0028 Phase 3) ─────────────────────
|
|
1309
|
+
//
|
|
1310
|
+
// AGENTS.md is the cross-runtime project-instructions standard; every PAN
|
|
1311
|
+
// target runtime (and Antigravity CLI) reads it natively. PAN contributes one
|
|
1312
|
+
// marker-fenced section so agents in any runtime understand the PAN context
|
|
1313
|
+
// when reading the repo. User content outside the markers is never touched.
|
|
1314
|
+
|
|
1315
|
+
const PAN_AGENTS_BEGIN = '<!-- BEGIN PAN WIZARD -->';
|
|
1316
|
+
const PAN_AGENTS_END = '<!-- END PAN WIZARD -->';
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Build the PAN section for AGENTS.md (marker-fenced, runtime-neutral).
|
|
1320
|
+
* @returns {string} The fenced section, no leading/trailing blank lines.
|
|
1321
|
+
*/
|
|
1322
|
+
function buildAgentsMdSection() {
|
|
1323
|
+
return [
|
|
1324
|
+
PAN_AGENTS_BEGIN,
|
|
1325
|
+
'## PAN Wizard',
|
|
1326
|
+
'',
|
|
1327
|
+
'This project uses PAN Wizard for structured, phase-based planning and execution.',
|
|
1328
|
+
'',
|
|
1329
|
+
'- `.planning/` is PAN\'s state directory (state.md, roadmap.md, phase directories). Treat it as the source of truth for planning state and modify it through PAN commands, not by hand.',
|
|
1330
|
+
'- PAN commands install as `pan-*` skills/commands (for example `/pan-help`, `/pan-new-project`, `/pan-exec-phase`). Start with `/pan-help`.',
|
|
1331
|
+
'- The `pan-tools` dispatcher backs every command; it lives under `pan-wizard-core/` inside the runtime\'s config directory (or `.agents/` for unified installs).',
|
|
1332
|
+
PAN_AGENTS_END,
|
|
1333
|
+
].join('\n');
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
/**
|
|
1337
|
+
* Insert or replace the PAN section in AGENTS.md content.
|
|
1338
|
+
* - No existing content (null/empty) → just the section.
|
|
1339
|
+
* - Markers present → replace exactly the fenced block, preserving everything
|
|
1340
|
+
* around it.
|
|
1341
|
+
* - Markers absent → append with a separating blank line.
|
|
1342
|
+
* @param {string|null} existing - Current AGENTS.md content, or null if absent
|
|
1343
|
+
* @param {string} section - Output of buildAgentsMdSection()
|
|
1344
|
+
* @returns {string} New file content (always newline-terminated)
|
|
1345
|
+
*/
|
|
1346
|
+
function upsertAgentsMdSection(existing, section) {
|
|
1347
|
+
if (!existing || !existing.trim()) {
|
|
1348
|
+
return section + '\n';
|
|
1349
|
+
}
|
|
1350
|
+
const beginIdx = existing.indexOf(PAN_AGENTS_BEGIN);
|
|
1351
|
+
const endIdx = existing.indexOf(PAN_AGENTS_END);
|
|
1352
|
+
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {
|
|
1353
|
+
const before = existing.slice(0, beginIdx);
|
|
1354
|
+
const after = existing.slice(endIdx + PAN_AGENTS_END.length);
|
|
1355
|
+
return before + section + after;
|
|
1356
|
+
}
|
|
1357
|
+
return existing.trimEnd() + '\n\n' + section + '\n';
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Remove the PAN section from AGENTS.md content.
|
|
1362
|
+
* @param {string} existing - Current AGENTS.md content
|
|
1363
|
+
* @returns {string|null} Content without the PAN block, or null when nothing
|
|
1364
|
+
* meaningful remains (caller should delete the file).
|
|
1365
|
+
*/
|
|
1366
|
+
function removeAgentsMdSection(existing) {
|
|
1367
|
+
if (!existing) return null;
|
|
1368
|
+
const beginIdx = existing.indexOf(PAN_AGENTS_BEGIN);
|
|
1369
|
+
const endIdx = existing.indexOf(PAN_AGENTS_END);
|
|
1370
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx < beginIdx) {
|
|
1371
|
+
return existing; // no PAN block — leave untouched
|
|
1372
|
+
}
|
|
1373
|
+
const before = existing.slice(0, beginIdx);
|
|
1374
|
+
const after = existing.slice(endIdx + PAN_AGENTS_END.length);
|
|
1375
|
+
const remaining = (before.trimEnd() + '\n\n' + after.trimStart()).trim();
|
|
1376
|
+
return remaining ? remaining + '\n' : null;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* Ensure CLAUDE.md bridges to AGENTS.md via a marker-fenced @AGENTS.md import
|
|
1381
|
+
* (Claude Code's documented pattern for adopting the universal rules file).
|
|
1382
|
+
* Idempotent; preserves all user content.
|
|
1383
|
+
* @param {string|null} existing - Current CLAUDE.md content, or null if absent
|
|
1384
|
+
* @returns {string} New file content
|
|
1385
|
+
*/
|
|
1386
|
+
function ensureClaudeMdImport(existing) {
|
|
1387
|
+
const block = `${PAN_AGENTS_BEGIN}\n@AGENTS.md\n${PAN_AGENTS_END}`;
|
|
1388
|
+
if (!existing || !existing.trim()) {
|
|
1389
|
+
return block + '\n';
|
|
1390
|
+
}
|
|
1391
|
+
if (existing.includes(PAN_AGENTS_BEGIN)) {
|
|
1392
|
+
return existing; // bridge (or another PAN block) already present
|
|
1393
|
+
}
|
|
1394
|
+
if (/^@AGENTS\.md\s*$/m.test(existing)) {
|
|
1395
|
+
return existing; // user already imports AGENTS.md themselves
|
|
1396
|
+
}
|
|
1397
|
+
return existing.trimEnd() + '\n\n' + block + '\n';
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
/**
|
|
1401
|
+
* Remove the PAN bridge block from CLAUDE.md content.
|
|
1402
|
+
* @param {string} existing - Current CLAUDE.md content
|
|
1403
|
+
* @returns {string|null} Content without the bridge, or null when nothing
|
|
1404
|
+
* meaningful remains (caller should delete the file).
|
|
1405
|
+
*/
|
|
1406
|
+
function removeClaudeMdImport(existing) {
|
|
1407
|
+
return removeAgentsMdSection(existing);
|
|
816
1408
|
}
|
|
817
1409
|
|
|
818
1410
|
// ─── Exports ────────────────────────────────────────────────────────────────
|
|
@@ -851,6 +1443,8 @@ module.exports = {
|
|
|
851
1443
|
// Skill/agent builders
|
|
852
1444
|
getCodexSkillAdapterHeader,
|
|
853
1445
|
convertClaudeCommandToCodexSkill,
|
|
1446
|
+
getUnifiedSkillAdapterHeader,
|
|
1447
|
+
convertClaudeCommandToUnifiedSkill,
|
|
854
1448
|
getCopilotSkillAdapterHeader,
|
|
855
1449
|
convertClaudeCommandToCopilotSkill,
|
|
856
1450
|
convertClaudeToCopilotAgent,
|
|
@@ -863,6 +1457,27 @@ module.exports = {
|
|
|
863
1457
|
buildClaudeSkillShim,
|
|
864
1458
|
translateThinkingDirective,
|
|
865
1459
|
stripThinkingFrontmatter,
|
|
1460
|
+
// Gemini CLI → Antigravity transition (2026-06)
|
|
1461
|
+
geminiTransitionNotice,
|
|
1462
|
+
// Codex agents (TOML) + trust notice (2026-06)
|
|
1463
|
+
convertClaudeAgentToCodexToml,
|
|
1464
|
+
codexTrustNotice,
|
|
1465
|
+
// Copilot CLI hooks config (2026-06)
|
|
1466
|
+
buildCopilotHooksConfig,
|
|
1467
|
+
HOOK_EVENT_MAP,
|
|
1468
|
+
mergeCodexHooksConfig,
|
|
1469
|
+
removeCodexPanHooks,
|
|
1470
|
+
buildNativeWorkflowScripts,
|
|
1471
|
+
buildPluginManifest,
|
|
1472
|
+
buildPluginHooksConfig,
|
|
866
1473
|
// Install verification (v3.7.10)
|
|
867
1474
|
verifyInstall,
|
|
1475
|
+
// AGENTS.md universal rules layer (ADR-0028 Phase 3)
|
|
1476
|
+
buildAgentsMdSection,
|
|
1477
|
+
upsertAgentsMdSection,
|
|
1478
|
+
removeAgentsMdSection,
|
|
1479
|
+
ensureClaudeMdImport,
|
|
1480
|
+
removeClaudeMdImport,
|
|
1481
|
+
PAN_AGENTS_BEGIN,
|
|
1482
|
+
PAN_AGENTS_END,
|
|
868
1483
|
};
|