pan-wizard 3.8.0 → 3.10.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.
Files changed (49) hide show
  1. package/README.md +4 -1
  2. package/agents/pan-conductor.md +1 -2
  3. package/agents/pan-counterfactual.md +1 -2
  4. package/agents/pan-debugger.md +1 -2
  5. package/agents/pan-distiller.md +1 -2
  6. package/agents/pan-document_code.md +1 -0
  7. package/agents/pan-executor.md +1 -0
  8. package/agents/pan-experiment-runner.md +1 -2
  9. package/agents/pan-hardener.md +1 -2
  10. package/agents/pan-integration-checker.md +1 -2
  11. package/agents/pan-knowledge.md +1 -2
  12. package/agents/pan-meta-reviewer.md +1 -2
  13. package/agents/pan-optimizer.md +1 -0
  14. package/agents/pan-phase-researcher.md +1 -0
  15. package/agents/pan-plan-checker.md +1 -2
  16. package/agents/pan-planner.md +1 -0
  17. package/agents/pan-previewer.md +1 -2
  18. package/agents/pan-project-researcher.md +6 -0
  19. package/agents/pan-research-synthesizer.md +7 -0
  20. package/agents/pan-reviewer.md +2 -3
  21. package/agents/pan-roadmapper.md +1 -0
  22. package/agents/pan-verifier.md +1 -2
  23. package/bin/install-lib.cjs +661 -46
  24. package/bin/install.js +722 -116
  25. package/commands/pan/experiment.md +2 -0
  26. package/commands/pan/profile.md +2 -0
  27. package/hooks/dist/pan-cost-logger.js +22 -7
  28. package/package.json +5 -4
  29. package/pan-wizard-core/bin/lib/commands-learnings.cjs +544 -0
  30. package/pan-wizard-core/bin/lib/commands.cjs +12 -523
  31. package/pan-wizard-core/bin/lib/core.cjs +69 -0
  32. package/pan-wizard-core/bin/lib/cost.cjs +62 -8
  33. package/pan-wizard-core/bin/lib/git.cjs +6 -1
  34. package/pan-wizard-core/bin/lib/lock.cjs +108 -0
  35. package/pan-wizard-core/bin/lib/milestone.cjs +3 -2
  36. package/pan-wizard-core/bin/lib/phase-remove.cjs +392 -0
  37. package/pan-wizard-core/bin/lib/phase.cjs +4 -369
  38. package/pan-wizard-core/bin/lib/runner.cjs +5 -0
  39. package/pan-wizard-core/bin/lib/state.cjs +10 -1
  40. package/pan-wizard-core/bin/lib/verify-deploy.cjs +181 -0
  41. package/pan-wizard-core/bin/lib/verify-drift.cjs +255 -0
  42. package/pan-wizard-core/bin/lib/verify-preflight.cjs +261 -0
  43. package/pan-wizard-core/bin/lib/verify-retro.cjs +177 -0
  44. package/pan-wizard-core/bin/lib/verify.cjs +10 -797
  45. package/pan-wizard-core/bin/pan-tools.cjs +10 -0
  46. package/pan-wizard-core/workflows/plan-phase.md +11 -0
  47. package/scripts/build-plugin.js +105 -0
  48. package/scripts/install-git-hooks.js +64 -0
  49. package/scripts/release-check.js +13 -2
@@ -360,9 +360,11 @@ function convertClaudeToOpencodeFrontmatter(content) {
360
360
  }
361
361
 
362
362
  if (allowedTools.length > 0) {
363
- newLines.push('tools:');
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)}: true`);
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 thinking directive from the generic PAN frontmatter shape
622
- * into the runtime-specific syntax (or prose fallback).
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
- * PAN agents declare: `thinking: enabled` and `thinking_budget: 8000` in
625
- * frontmatter. Runtimes that support native extended thinking consume those
626
- * fields directly. Runtimes without native support get a prose preamble
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 - {enabled: boolean, budget: number}
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 || !directive.enabled) return result;
638
- const budget = Number(directive.budget) > 0 ? Number(directive.budget) : 2000;
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 these natively.
643
- result.frontmatter = { thinking: 'enabled', thinking_budget: budget };
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 thinking support.
651
- result.preamble = `Think through the problem step-by-step before taking any action. Reason about edge cases, hidden dependencies, and likely failure modes. Use up to ~${budget} tokens of internal reasoning. Only after that, call tools or write output.`;
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 Opus 4.7 `thinking` / `thinking_budget` frontmatter fields from an
658
- * agent markdown file for runtimes that don't support native extended
659
- * thinking. When thinking was enabled, inject a prose preamble at the top
660
- * of the body instructing the model to think step-by-step.
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 — those fields stay in frontmatter so the
663
- * host runtime consumes them.
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
- if (!thinkingValue && !budgetValue) return content;
754
+ const effortValue = extractFrontmatterField(frontmatter, 'effort');
755
+ if (!thinkingValue && !budgetValue && !effortValue) return content;
679
756
 
680
- // Remove the two fields (match on their own lines only).
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
- // If thinking was enabled, prepend a prose preamble.
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
- if (enabled) {
692
- const directive = { enabled: true, budget: Number(budgetValue) || 2000 };
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
- * @param {string} modelName - e.g. "claude-opus-4-7", "claude-sonnet-4-6", "gpt-5"
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('opus-4-7') || n.includes('opus-4.7')) {
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-6') || n.includes('opus-4.6') || 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') || n.includes('sonnet-4')) {
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 (the manifest was just written
782
- * from these files, so re-hashing would only catch corruption-after-write
783
- * which is a different concern). We DO catch the much more common case of
784
- * "file silently failed to land in the first place."
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 install is
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, no command works.
798
- const dispatcherPath = path_v.join(configDir, 'pan-wizard-core', 'bin', 'pan-tools.cjs');
799
- if (!fs_v.existsSync(dispatcherPath)) {
800
- missing.push('pan-wizard-core/bin/pan-tools.cjs (dispatcher install is unusable without it)');
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
- if (!fs_v.existsSync(abs)) {
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
  };