pan-wizard 3.35.0 → 3.37.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/CHANGELOG.md +3645 -0
- package/README.md +46 -52
- package/agents/pan-conductor.md +2 -3
- package/agents/pan-counterfactual.md +1 -2
- package/agents/pan-debugger.md +14 -3
- package/agents/pan-design-checker.md +1 -2
- package/agents/pan-designer.md +2 -3
- package/agents/pan-document_code.md +1 -2
- package/agents/pan-executor.md +25 -0
- package/agents/pan-hardener.md +4 -3
- package/agents/pan-integration-checker.md +1 -2
- package/agents/pan-knowledge.md +1 -2
- package/agents/pan-meta-reviewer.md +3 -3
- package/agents/pan-optimizer.md +4 -4
- package/agents/pan-phase-researcher.md +80 -475
- package/agents/pan-plan-checker.md +40 -3
- package/agents/pan-planner.md +21 -7
- package/agents/pan-previewer.md +3 -2
- package/agents/pan-project-researcher.md +72 -493
- package/agents/pan-release.md +1 -1
- package/agents/pan-reviewer.md +14 -3
- package/agents/pan-roadmapper.md +12 -7
- package/agents/pan-verifier.md +9 -4
- package/bin/install-lib.cjs +134 -14
- package/bin/install.js +59 -12
- package/commands/pan/army.md +1 -1
- package/commands/pan/autodev.md +76 -0
- package/commands/pan/cost.md +21 -1
- package/commands/pan/exec-phase.md +3 -4
- package/commands/pan/focus-auto.md +14 -7
- package/commands/pan/focus-design.md +4 -5
- package/commands/pan/focus-docs.md +24 -0
- package/commands/pan/learn.md +13 -6
- package/commands/pan/milestone-audit.md +8 -1
- package/commands/pan/milestone-done.md +10 -5
- package/commands/pan/milestone-new.md +2 -2
- package/commands/pan/new-project.md +15 -12
- package/commands/pan/plan-phase.md +15 -9
- package/commands/pan/preview.md +1 -1
- package/commands/pan/quick.md +1 -1
- package/commands/pan/review-deep.md +11 -5
- package/commands/pan/settings.md +6 -0
- package/commands/pan/verify-phase.md +6 -2
- package/docs/AGENTS.md +917 -0
- package/docs/ARCHITECTURE.md +1054 -0
- package/docs/CLI-REFERENCE.md +4582 -0
- package/docs/COMPARISON.md +116 -0
- package/docs/DEVELOPMENT.md +490 -0
- package/docs/ECOSYSTEM-REVIEW-2026-06.md +107 -0
- package/docs/ECOSYSTEM-REVIEW-2026-08.md +349 -0
- package/docs/EXAMPLES.md +584 -0
- package/docs/FAQ.md +254 -0
- package/docs/FIELD-HARVEST-2026-07.md +88 -0
- package/docs/FIELD-REPORT-army-2026-06.md +121 -0
- package/docs/HOOKS.md +638 -0
- package/docs/IMPROVEMENT-TODO.md +246 -0
- package/docs/INTERNALS.md +485 -0
- package/docs/MIGRATION-v2-to-v3.md +258 -0
- package/docs/SECURITY-HARDENING.md +230 -0
- package/docs/SKILLS-ENHANCEMENT-PLAN-V2.md +122 -0
- package/docs/SKILLS-FULL-TEXT.md +9685 -0
- package/docs/SKILLS-REFERENCE.md +1037 -0
- package/docs/TROUBLESHOOTING.md +1084 -0
- package/docs/USER-GUIDE.md +1437 -0
- package/docs/archive/SKILLS-ENHANCEMENT-PLAN.md +230 -0
- package/docs/audits/AUDIT-2026-08-e2e-ROADMAP.md +117 -0
- package/docs/audits/AUDIT-2026-08-e2e-VERIFICATION.md +693 -0
- package/docs/audits/AUDIT-2026-08-e2e.md +1546 -0
- package/docs/audits/OUTSTANDING-2026-08.md +275 -0
- package/docs/audits/STABILITY-TEST-2026-08.md +640 -0
- package/docs/branding/README-brand-draft.md +119 -0
- package/docs/branding/handoff/IMPLEMENTATION.md +205 -0
- package/docs/branding/handoff/README-brand-draft.md +119 -0
- package/docs/branding/handoff/image-prompts.md +46 -0
- package/docs/branding/image-prompts.md +46 -0
- package/docs/branding/nano-banana-prompts.txt +60 -0
- package/docs/context-monitor.md +13 -0
- package/docs/decisions/ADR-0001-strategic-feature-prioritization.md +83 -0
- package/docs/decisions/ADR-0002-copilot-cli-runtime.md +71 -0
- package/docs/decisions/ADR-0003-smart-execution.md +84 -0
- package/docs/decisions/ADR-0004-copilot-cli-interaction.md +89 -0
- package/docs/decisions/ADR-0005-command-naming.md +80 -0
- package/docs/decisions/ADR-0006-focus-commands.md +101 -0
- package/docs/decisions/ADR-0007-ship-v1.md +73 -0
- package/docs/decisions/ADR-0008-map-codebase-mermaid-togaf.md +80 -0
- package/docs/decisions/ADR-0009-production-deployment-checklist.md +68 -0
- package/docs/decisions/ADR-0010-standards-integration.md +79 -0
- package/docs/decisions/ADR-0011-help-commands-rewrite.md +64 -0
- package/docs/decisions/ADR-0012-contributors-attribution-index.md +42 -0
- package/docs/decisions/ADR-0013-strategic-feature-additions.md +53 -0
- package/docs/decisions/ADR-0014-internal-cleanup-code-quality.md +53 -0
- package/docs/decisions/ADR-0015-focus-auto-runner.md +296 -0
- package/docs/decisions/ADR-0016-enhanced-doc-sync.md +54 -0
- package/docs/decisions/ADR-0017-enhanced-focus-design.md +69 -0
- package/docs/decisions/ADR-0018-user-reality-testing.md +84 -0
- package/docs/decisions/ADR-0019-end-to-end-user-acceptance-testing.md +113 -0
- package/docs/decisions/ADR-0020-ai-drift-prevention.md +55 -0
- package/docs/decisions/ADR-0021-codebase-mapper-v2.md +76 -0
- package/docs/decisions/ADR-0022-lifecycle-completeness.md +63 -0
- package/docs/decisions/ADR-0023-opus-4-7-adoption.md +89 -0
- package/docs/decisions/ADR-0024-spec-b-v2-completion.md +137 -0
- package/docs/decisions/ADR-0025-behavioral-guardrails-layer.md +129 -0
- package/docs/decisions/ADR-0026-self-improvement-loop.md +171 -0
- package/docs/decisions/ADR-0027-doc-code-link-graph.md +164 -0
- package/docs/decisions/ADR-0028-unified-agents-skills-target.md +46 -0
- package/docs/decisions/ADR-0029-experiment-evaluator.md +76 -0
- package/docs/decisions/ADR-0030-planning-concurrency.md +37 -0
- package/docs/decisions/ADR-0031-focus-auto-backlog-source.md +47 -0
- package/docs/decisions/ADR-0032-squad-model.md +64 -0
- package/docs/decisions/ADR-0033-army-campaign.md +40 -0
- package/docs/decisions/ADR-0034-scheduled-campaigns.md +57 -0
- package/docs/decisions/ADR-0035-army-hud-dashboard.md +40 -0
- package/docs/decisions/ADR-0036-agentic-memory-architecture.md +106 -0
- package/docs/decisions/ADR-0037-pin-security-agents-to-opus.md +51 -0
- package/docs/decisions/ADR-0038-skill-aligned-decomposition.md +41 -0
- package/docs/decisions/ADR-0039-worktree-as-project-variant.md +37 -0
- package/docs/decisions/ADR-0040-memory-injection-defense.md +55 -0
- package/docs/decisions/ADR-0041-mcp-dual-era-bridge.md +47 -0
- package/docs/decisions/ADR-0042-design-parity-both-flows.md +45 -0
- package/docs/decisions/ADR-0043-addressable-planning-roots.md +56 -0
- package/docs/decisions/ADR-0044-bound-the-cached-context.md +104 -0
- package/docs/decisions/ADR-0045-agent-plugins-bundle.md +76 -0
- package/docs/decisions/ADR-0046-subagent-frontmatter-decisions.md +39 -0
- package/docs/decisions/ADR-0047-pan-harness.md +76 -0
- package/docs/decisions/ADR-0048-one-core-per-project.md +43 -0
- package/docs/decisions/ADR-0049-machine-verdicts-and-findings-ledger.md +116 -0
- package/docs/specs/ai_drift_prevention_featureai.md +286 -0
- package/docs/specs/codebase_mapper_v2_featureai.md +930 -0
- package/docs/specs/command_naming_optimization_featureai.md +930 -0
- package/docs/specs/comp_analysis_markdown_v1.md +149 -0
- package/docs/specs/continuous_autonomous_execution_featureai.md +304 -0
- package/docs/specs/contributors_attribution_index_featureai.md +476 -0
- package/docs/specs/copilot_cli_interaction_featureai.md +868 -0
- package/docs/specs/copilot_cli_runtime_featureai.md +647 -0
- package/docs/specs/custom-skills-assessment.md +461 -0
- package/docs/specs/deployment_audit_findings_featureai.md +243 -0
- package/docs/specs/doc_code_link_graph_featureai.md +253 -0
- package/docs/specs/documentation_alignment_cleanup_featureai.md +69 -0
- package/docs/specs/e2e_visual_testing_featureai.md +615 -0
- package/docs/specs/end_to_end_user_acceptance_testing_featureai.md +810 -0
- package/docs/specs/evidence-loop-plan.md +76 -0
- package/docs/specs/evidence_loop_featureai.md +497 -0
- package/docs/specs/focus-auto-optimize-category.md +449 -0
- package/docs/specs/focus-auto-optimize-superplan.md +188 -0
- package/docs/specs/focus_auto_runner_featureai.md +1102 -0
- package/docs/specs/focus_design_v2_featureai.md +197 -0
- package/docs/specs/focus_sync_v2_featureai.md +202 -0
- package/docs/specs/git_integration_overhaul_featureai.md +326 -0
- package/docs/specs/glm_zai_provider_featureai.md +200 -0
- package/docs/specs/googlecli_adoption_featureai.md +661 -0
- package/docs/specs/help_commands_rewrite_featureai.md +860 -0
- package/docs/specs/industry_analysis_strategic_additions_featureai.md +231 -0
- package/docs/specs/industry_comparison_and_feature_gaps_featureai.md +674 -0
- package/docs/specs/internal_cleanup_code_quality_featureai.md +486 -0
- package/docs/specs/investigations/README.md +31 -0
- package/docs/specs/investigations/digests/2026-09-28.md +47 -0
- package/docs/specs/investigations/digests/2026-10-08.md +62 -0
- package/docs/specs/investigations/digests/2026-10-10.md +61 -0
- package/docs/specs/investigations/features/debugger-red-loop-first.md +55 -0
- package/docs/specs/investigations/features/harness-cross-host-behaviour.md +93 -0
- package/docs/specs/investigations/features/optimize-apply-regression-gate.md +66 -0
- package/docs/specs/investigations/features/reviewer-smell-baseline.md +57 -0
- package/docs/specs/investigations/features/trace-failure-capture.md +69 -0
- package/docs/specs/lifecycle_completeness_featureai.md +754 -0
- package/docs/specs/local-first-ci.md +42 -0
- package/docs/specs/map_codebase_mermaid_togaf_featureai.md +805 -0
- package/docs/specs/market-delta-2026-09-superplan.md +310 -0
- package/docs/specs/market-ideas-2026-09.md +131 -0
- package/docs/specs/market-ideas-2026-10.md +428 -0
- package/docs/specs/market-ideas-ledger.md +177 -0
- package/docs/specs/memory-optimization-2026-10.md +285 -0
- package/docs/specs/multi-track-planning-roots.md +143 -0
- package/docs/specs/multi_model_routing_featureai.md +687 -0
- package/docs/specs/multi_model_routing_superplan.md +197 -0
- package/docs/specs/opus_47_existing_enhancements_featureai.md +247 -0
- package/docs/specs/opus_47_extended_features_featureai.md +363 -0
- package/docs/specs/opus_47_extended_features_v2_featureai.md +397 -0
- package/docs/specs/pan_focus_commands_featureai.md +2336 -0
- package/docs/specs/pan_zcode_mcp_bridge_featureai.md +120 -0
- package/docs/specs/product-excellence-superplan.md +315 -0
- package/docs/specs/product_excellence_program_2026_q4_featureai.md +365 -0
- package/docs/specs/production_deployment_readiness_featureai.md +433 -0
- package/docs/specs/production_readiness_ship_v1_featureai.md +698 -0
- package/docs/specs/real_test_enforcement_featureai.md +213 -0
- package/docs/specs/reality-check-2026-09-superplan.md +362 -0
- package/docs/specs/right-sized-ceremony.md +74 -0
- package/docs/specs/roadmap_and_competition_2026_q4_featureai.md +390 -0
- package/docs/specs/self_improvement_loop_featureai.md +875 -0
- package/docs/specs/skill-aligned-decomposition.md +155 -0
- package/docs/specs/standards_integration_featureai.md +833 -0
- package/docs/specs/testing-system-redesign-2026-09.md +267 -0
- package/docs/specs/user_reality_testing_system_featureai.md +788 -0
- package/docs/specs/watch-triggers.json +22 -0
- package/docs/specs/workflow-integration-redesign.md +742 -0
- package/docs/specs/workflow_state_consistency_featureai.md +183 -0
- package/hooks/dist/pan-context-monitor.js +9 -2
- package/hooks/dist/pan-cost-logger.js +5 -0
- package/hooks/dist/pan-guard.js +28 -5
- package/hooks/dist/pan-stop-guard.js +9 -9
- package/hooks/dist/pan-trace-logger.js +5 -0
- package/package.json +10 -2
- package/pan-wizard-core/bin/lib/agents-md.cjs +57 -8
- package/pan-wizard-core/bin/lib/autodev-report.cjs +180 -0
- package/pan-wizard-core/bin/lib/autodev.cjs +642 -0
- package/pan-wizard-core/bin/lib/ci.cjs +143 -0
- package/pan-wizard-core/bin/lib/commands.cjs +1 -1
- package/pan-wizard-core/bin/lib/config.cjs +41 -5
- package/pan-wizard-core/bin/lib/constants.cjs +27 -1
- package/pan-wizard-core/bin/lib/core.cjs +31 -14
- package/pan-wizard-core/bin/lib/cost.cjs +160 -4
- package/pan-wizard-core/bin/lib/cross-family.cjs +164 -0
- package/pan-wizard-core/bin/lib/debug-cases.cjs +104 -0
- package/pan-wizard-core/bin/lib/doc-lint.cjs +5 -3
- package/pan-wizard-core/bin/lib/failure-class.cjs +81 -0
- package/pan-wizard-core/bin/lib/findings.cjs +148 -9
- package/pan-wizard-core/bin/lib/focus.cjs +1 -1
- package/pan-wizard-core/bin/lib/git.cjs +1 -1
- package/pan-wizard-core/bin/lib/hosts.cjs +88 -0
- package/pan-wizard-core/bin/lib/init.cjs +7 -2
- package/pan-wizard-core/bin/lib/knowledge.cjs +2 -0
- package/pan-wizard-core/bin/lib/merge.cjs +50 -0
- package/pan-wizard-core/bin/lib/mutate.cjs +268 -0
- package/pan-wizard-core/bin/lib/next-action.cjs +112 -0
- package/pan-wizard-core/bin/lib/optimize.cjs +2 -2
- package/pan-wizard-core/bin/lib/phase-gate.cjs +15 -9
- package/pan-wizard-core/bin/lib/phase-remove.cjs +91 -11
- package/pan-wizard-core/bin/lib/phase.cjs +85 -13
- package/pan-wizard-core/bin/lib/plan-lint.cjs +310 -0
- package/pan-wizard-core/bin/lib/preview.cjs +26 -4
- package/pan-wizard-core/bin/lib/requirements-lint.cjs +130 -0
- package/pan-wizard-core/bin/lib/research-lint.cjs +268 -0
- package/pan-wizard-core/bin/lib/review-deep.cjs +14 -22
- package/pan-wizard-core/bin/lib/review-vote.cjs +105 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +1 -0
- package/pan-wizard-core/bin/lib/runner.cjs +2 -16
- package/pan-wizard-core/bin/lib/sizing.cjs +356 -0
- package/pan-wizard-core/bin/lib/squads.cjs +1 -1
- package/pan-wizard-core/bin/lib/state-write.cjs +247 -0
- package/pan-wizard-core/bin/lib/test-gate.cjs +433 -0
- package/pan-wizard-core/bin/lib/uat.cjs +149 -0
- package/pan-wizard-core/bin/lib/usage.cjs +104 -0
- package/pan-wizard-core/bin/lib/verdict.cjs +26 -3
- package/pan-wizard-core/bin/lib/verify-plan-set.cjs +30 -1
- package/pan-wizard-core/bin/lib/verify-preflight.cjs +2 -2
- package/pan-wizard-core/bin/lib/verify-red.cjs +174 -0
- package/pan-wizard-core/bin/lib/verify-retro.cjs +3 -2
- package/pan-wizard-core/bin/lib/verify-scope.cjs +143 -2
- package/pan-wizard-core/bin/lib/verify.cjs +86 -31
- package/pan-wizard-core/bin/lib/worktree.cjs +70 -0
- package/pan-wizard-core/bin/pan-tools.cjs +257 -28
- package/pan-wizard-core/learnings/universal/autonomous-loop.md +2 -2
- package/pan-wizard-core/mcp/orchestrator.cjs +4 -1
- package/pan-wizard-core/references/continuation-format.md +3 -3
- package/pan-wizard-core/references/design-methodology.md +1 -1
- package/pan-wizard-core/references/guardrails.md +1 -1
- package/pan-wizard-core/references/handoff-decisions.md +1 -1
- package/pan-wizard-core/references/model-profiles.md +2 -3
- package/pan-wizard-core/references/questioning.md +24 -0
- package/pan-wizard-core/references/ui-brand.md +4 -6
- package/pan-wizard-core/references/verdict-contract.md +2 -1
- package/pan-wizard-core/templates/config.json +10 -2
- package/pan-wizard-core/templates/debug.md +1 -0
- package/pan-wizard-core/templates/design.md +5 -4
- package/pan-wizard-core/templates/discovery.md +2 -2
- package/pan-wizard-core/templates/phase-prompt.md +7 -0
- package/pan-wizard-core/templates/requirements.md +9 -0
- package/pan-wizard-core/templates/research.md +10 -0
- package/pan-wizard-core/templates/summary-complex.md +5 -1
- package/pan-wizard-core/templates/summary-minimal.md +4 -0
- package/pan-wizard-core/templates/summary-standard.md +5 -1
- package/pan-wizard-core/templates/summary.md +8 -1
- package/pan-wizard-core/workflows/add-phase.md +4 -6
- package/pan-wizard-core/workflows/assumptions.md +19 -1
- package/pan-wizard-core/workflows/diagnose-issues.md +2 -0
- package/pan-wizard-core/workflows/discuss-phase.md +4 -2
- package/pan-wizard-core/workflows/exec-lean.md +69 -0
- package/pan-wizard-core/workflows/exec-phase.md +79 -15
- package/pan-wizard-core/workflows/execute-plan.md +3 -1
- package/{commands/pan → pan-wizard-core/workflows}/focus-doc-audit.md +13 -29
- package/{commands/pan → pan-wizard-core/workflows}/focus-drift-walking.md +3 -18
- package/{commands/pan → pan-wizard-core/workflows}/focus-exec.md +8 -23
- package/{commands/pan → pan-wizard-core/workflows}/focus-plan.md +6 -20
- package/{commands/pan → pan-wizard-core/workflows}/focus-scan.md +3 -16
- package/{commands/pan → pan-wizard-core/workflows}/focus-sync.md +7 -21
- package/pan-wizard-core/workflows/health.md +6 -0
- package/pan-wizard-core/workflows/help.md +114 -77
- package/pan-wizard-core/workflows/insert-phase.md +4 -6
- package/pan-wizard-core/workflows/learn.md +3 -3
- package/pan-wizard-core/workflows/milestone-audit.md +2 -2
- package/pan-wizard-core/workflows/milestone-new.md +38 -95
- package/pan-wizard-core/workflows/new-project.md +96 -258
- package/pan-wizard-core/workflows/optimize.md +107 -5
- package/pan-wizard-core/workflows/phase-tests.md +10 -0
- package/pan-wizard-core/workflows/plan-phase.md +106 -45
- package/pan-wizard-core/workflows/profile.md +50 -0
- package/pan-wizard-core/workflows/progress.md +10 -0
- package/pan-wizard-core/workflows/quick.md +34 -43
- package/pan-wizard-core/workflows/remove-phase.md +11 -1
- package/pan-wizard-core/workflows/research-phase.md +118 -40
- package/pan-wizard-core/workflows/resume-project.md +18 -2
- package/pan-wizard-core/workflows/settings.md +33 -19
- package/pan-wizard-core/workflows/todo-check.md +6 -2
- package/pan-wizard-core/workflows/transition.md +22 -4
- package/pan-wizard-core/workflows/uat-walkthrough.md +90 -0
- package/pan-wizard-core/workflows/update.md +1 -1
- package/pan-wizard-core/workflows/verify-phase.md +39 -46
- package/scripts/build-agent-plugin.js +35 -15
- package/scripts/build-help.cjs +106 -0
- package/scripts/build-plugin.js +10 -1
- package/scripts/deprecate-old-versions.js +70 -3
- package/scripts/generate-skills-docs.py +1 -1
- package/scripts/git-hooks/pre-push +27 -0
- package/scripts/install-git-hooks.js +7 -5
- package/scripts/install-pre-push.cjs +38 -0
- package/scripts/land.cjs +151 -0
- package/scripts/local-ci.cjs +501 -0
- package/scripts/local-schedule.cjs +277 -0
- package/scripts/mutation-probe.cjs +7 -80
- package/scripts/prompt-lint.cjs +32 -0
- package/scripts/prompt-rules.cjs +156 -0
- package/scripts/run-tests.cjs +7 -1
- package/scripts/test-quality-lint.cjs +3 -3
- package/scripts/test-surface.cjs +175 -6
- package/agents/pan-research-synthesizer.md +0 -246
- package/commands/pan/audit-deployment.md +0 -386
- package/commands/pan/dashboard.md +0 -22
- package/commands/pan/discord.md +0 -19
- package/commands/pan/mcp-bridge.md +0 -143
- package/commands/pan/milestone-cleanup.md +0 -19
- package/commands/pan/milestone-gaps.md +0 -35
- package/commands/pan/optimize.md +0 -115
- package/commands/pan/profile.md +0 -76
- package/commands/pan/research-phase.md +0 -189
- package/pan-wizard-core/bin/lib/bridge.cjs +0 -269
- package/pan-wizard-core/templates/research-project/architecture.md +0 -204
- package/pan-wizard-core/templates/research-project/features.md +0 -147
- package/pan-wizard-core/templates/research-project/pitfalls.md +0 -200
- package/pan-wizard-core/templates/research-project/stack.md +0 -120
- package/pan-wizard-core/templates/research-project/summary.md +0 -170
|
@@ -27,6 +27,12 @@
|
|
|
27
27
|
* - IDEMPOTENCY: recording the same artifact twice is a no-op that returns the
|
|
28
28
|
* first verdict (record_sig). That is why exec-phase and verify-phase may both
|
|
29
29
|
* record one verification.md.
|
|
30
|
+
* - LEARNED SUPPRESSIONS (R3): a dismissed finding is noise for its file as it was
|
|
31
|
+
* when dismissed. The same judge raising it again (same signature, sameFinding)
|
|
32
|
+
* is filtered, in the dismissal's phase (candidate) or, once dismissed in two
|
|
33
|
+
* phases, in any phase (promoted). When the file changes the suppression retires
|
|
34
|
+
* and the finding comes back open. Security and logic findings, and findings
|
|
35
|
+
* with no file, are never suppressed: a re-report reopens them.
|
|
30
36
|
*/
|
|
31
37
|
const fs = require('fs');
|
|
32
38
|
const path = require('path');
|
|
@@ -40,6 +46,11 @@ const LEDGER_V = 1;
|
|
|
40
46
|
const REASON_MAX = 300;
|
|
41
47
|
const FINDING_ID_RE = /^f_[0-9a-f]{10}$/;
|
|
42
48
|
const STATUSES = Object.freeze(['open', ...V.DISPOSITIONS]);
|
|
49
|
+
// Logic findings are never suppressed (R3): what the code does wrong, or not at all.
|
|
50
|
+
const LOGIC_CLASSES = Object.freeze(['defect', 'contradicts', 'missing', 'partial']);
|
|
51
|
+
// Security findings are never suppressed either, whatever class a judge gave them.
|
|
52
|
+
const SECURITY_RE = /secur|inject|xss|csrf|ssrf|secret|credential|password|authenticat|authoriz|\bauth\b|token|crypt|traversal|privilege|sanitiz|escap|cve-|owasp|\brce\b|deserializ/i;
|
|
53
|
+
const SECURITY_AGENTS = Object.freeze(['pan-hardener']);
|
|
43
54
|
|
|
44
55
|
function ledgerPath(cwd) {
|
|
45
56
|
return planningPath(cwd, FINDINGS_FILE);
|
|
@@ -80,7 +91,9 @@ function foldFindings(rows) {
|
|
|
80
91
|
id: r.id, phase: r.phase, milestone: r.milestone || null, agent: r.agent, class: r.class,
|
|
81
92
|
severity: r.severity, where: r.where || null, summary: r.summary, first_seen: r.ts,
|
|
82
93
|
first_verdict_id: r.verdict_id || null, reports: 0, last_reported: null, _reportedSeq: -1,
|
|
83
|
-
disposition: null, reason: null, disposed_at: null, auto: false, _disposedSeq: -1,
|
|
94
|
+
disposition: null, reason: null, disposed_at: null, auto: false, file_sha: null, _disposedSeq: -1, _returnedSeq: -1,
|
|
95
|
+
...(r.pre_existing === true ? { pre_existing: true } : {}),
|
|
96
|
+
proof: null,
|
|
84
97
|
});
|
|
85
98
|
} else if (r.kind === 'verdict') {
|
|
86
99
|
verdicts.push({ ...r, _seq: seq });
|
|
@@ -91,6 +104,15 @@ function foldFindings(rows) {
|
|
|
91
104
|
f.last_reported = r.ts;
|
|
92
105
|
f._reportedSeq = seq;
|
|
93
106
|
}
|
|
107
|
+
// A dismissed finding raised again once its suppression no longer holds (R3).
|
|
108
|
+
for (const id of Array.isArray(r.returned_ids) ? r.returned_ids : []) {
|
|
109
|
+
const f = findings.get(id);
|
|
110
|
+
if (f) f._returnedSeq = seq;
|
|
111
|
+
}
|
|
112
|
+
} else if (r.kind === 'proof' && typeof r.id === 'string') {
|
|
113
|
+
// R5: the latest catching-test run for a finding.
|
|
114
|
+
const f = findings.get(r.id);
|
|
115
|
+
if (f) f.proof = { status: r.status, test: r.test };
|
|
94
116
|
} else if (r.kind === 'disposition' && typeof r.id === 'string') {
|
|
95
117
|
const f = findings.get(r.id);
|
|
96
118
|
if (!f || !V.DISPOSITIONS.includes(r.disposition)) return;
|
|
@@ -98,12 +120,14 @@ function foldFindings(rows) {
|
|
|
98
120
|
f.reason = r.reason || null;
|
|
99
121
|
f.disposed_at = r.ts;
|
|
100
122
|
f.auto = r.auto === true;
|
|
123
|
+
f.file_sha = r.file_sha || null;
|
|
101
124
|
f._disposedSeq = seq;
|
|
102
125
|
}
|
|
103
126
|
});
|
|
104
127
|
for (const f of findings.values()) {
|
|
105
128
|
if (!f.disposition) f.status = 'open';
|
|
106
129
|
else if (f.disposition === 'fixed' && f._reportedSeq > f._disposedSeq) f.status = 'open';
|
|
130
|
+
else if (f._returnedSeq > f._disposedSeq) f.status = 'open';
|
|
107
131
|
else f.status = f.disposition;
|
|
108
132
|
}
|
|
109
133
|
return { verdicts, findings };
|
|
@@ -111,8 +135,9 @@ function foldFindings(rows) {
|
|
|
111
135
|
|
|
112
136
|
/** A finding as the CLI shows it (internal sequence fields removed). */
|
|
113
137
|
function publicFinding(f) {
|
|
114
|
-
const { _reportedSeq, _disposedSeq, ...rest } = f;
|
|
115
|
-
|
|
138
|
+
const { _reportedSeq, _disposedSeq, _returnedSeq, ...rest } = f;
|
|
139
|
+
// R5: confirmed only with a catching test that passed its proof; a candidate otherwise.
|
|
140
|
+
return { ...rest, confidence: f.proof && f.proof.status === 'confirmed' ? 'confirmed' : 'candidate' };
|
|
116
141
|
}
|
|
117
142
|
|
|
118
143
|
function countBy(list, key) {
|
|
@@ -168,6 +193,52 @@ function truncateReason(s) {
|
|
|
168
193
|
return String(s).replace(/\s+/g, ' ').trim().slice(0, REASON_MAX);
|
|
169
194
|
}
|
|
170
195
|
|
|
196
|
+
// ─── Learned suppressions (R3) ──────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
/** Security and logic findings are always reported, dismissed or not. */
|
|
199
|
+
function neverSuppressed(f, agent) {
|
|
200
|
+
return LOGIC_CLASSES.includes(f.class) || SECURITY_AGENTS.includes(agent) || SECURITY_RE.test(String(f.summary || ''));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** The content hash of the project file a `where` names, or null (no file, outside the project, unreadable). */
|
|
204
|
+
function fileSha(cwd, where) {
|
|
205
|
+
const file = fileOf(where);
|
|
206
|
+
if (!file || file.startsWith('code-fact:')) return null;
|
|
207
|
+
const abs = path.resolve(cwd, file);
|
|
208
|
+
const rel = path.relative(path.resolve(cwd), abs);
|
|
209
|
+
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null;
|
|
210
|
+
try { return crypto.createHash('sha256').update(fs.readFileSync(abs)).digest('hex'); } catch { return null; }
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Every learned suppression: a dismissed finding scoped to a file, of a kind that may
|
|
215
|
+
* be suppressed. `active` while the file is as it was when dismissed; `promoted` once
|
|
216
|
+
* the same judge dismissed the same signature in two phases (active ones only).
|
|
217
|
+
*/
|
|
218
|
+
function learnedSuppressions(cwd, fold) {
|
|
219
|
+
const shas = new Map();
|
|
220
|
+
const current = (where) => {
|
|
221
|
+
const k = fileOf(where);
|
|
222
|
+
if (!shas.has(k)) shas.set(k, fileSha(cwd, where));
|
|
223
|
+
return shas.get(k);
|
|
224
|
+
};
|
|
225
|
+
const all = [...fold.findings.values()]
|
|
226
|
+
.filter((f) => f.status === 'dismissed' && f.file_sha && !neverSuppressed(f, f.agent))
|
|
227
|
+
.map((f) => ({ f, active: current(f.where) === f.file_sha }));
|
|
228
|
+
return all.map((s) => {
|
|
229
|
+
const phases = new Set(all.filter((o) => o.active && o.f.agent === s.f.agent && sameFinding(o.f, s.f)).map((o) => V.comparablePhase(o.f.phase)));
|
|
230
|
+
return { ...s, scope: phases.size >= 2 ? 'promoted' : 'candidate' };
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The suppression that filters this judge's finding in this phase, or null. */
|
|
235
|
+
function suppressionFor(supps, agent, phase, f) {
|
|
236
|
+
if (neverSuppressed(f, agent)) return null;
|
|
237
|
+
const hits = supps.filter((s) => s.active && s.f.agent === agent && sameFinding(s.f, f)
|
|
238
|
+
&& (s.scope === 'promoted' || samePhase(s.f.phase, phase)));
|
|
239
|
+
return hits.find((s) => samePhase(s.f.phase, phase)) || hits[0] || null;
|
|
240
|
+
}
|
|
241
|
+
|
|
171
242
|
/**
|
|
172
243
|
* How stable a judge's findings are across its reports on one phase (V6): between
|
|
173
244
|
* consecutive verdicts, how many ids appeared and how many disappeared. A judge whose
|
|
@@ -218,8 +289,14 @@ function recordVerdict(cwd, input) {
|
|
|
218
289
|
const text = typeof input.text === 'string' ? input.text : '';
|
|
219
290
|
if (!text.trim()) return { error: 'empty_input', reason: 'nothing to record' };
|
|
220
291
|
|
|
221
|
-
|
|
292
|
+
let parsed = V.parseVerdictText(text, { agent: input.agent, phase });
|
|
222
293
|
if (!parsed.ok) return { error: parsed.error, reason: parsed.reason };
|
|
294
|
+
// A verification is read against the engine's test run (V2): a failed run is a code
|
|
295
|
+
// fact that lowers the verifier's pass, as a failed test_gate already does (V9).
|
|
296
|
+
if (parsed.verdict.agent === 'pan-verifier') {
|
|
297
|
+
const facts = require('./test-gate.cjs').testRunFacts(cwd, phase);
|
|
298
|
+
if (facts.length) parsed = V.parseVerdictText(text, { agent: input.agent, phase, facts });
|
|
299
|
+
}
|
|
223
300
|
const verdict = parsed.verdict;
|
|
224
301
|
const agent = verdict.agent;
|
|
225
302
|
const recordSig = V.recordSig(text);
|
|
@@ -237,7 +314,7 @@ function recordVerdict(cwd, input) {
|
|
|
237
314
|
|
|
238
315
|
const dup = fold.verdicts.find((r) => r.record_sig === recordSig && r.agent === agent && samePhase(r.phase, phase));
|
|
239
316
|
if (dup) {
|
|
240
|
-
return { ...base, recorded: false, duplicate: true, verdict_id: dup.verdict_id, attempt: dup.attempt, phase, findings: (dup.finding_ids || []).length, new: 0, rematched: 0, resolved: 0, unaddressed: [], reopened: 0 };
|
|
317
|
+
return { ...base, recorded: false, duplicate: true, verdict_id: dup.verdict_id, attempt: dup.attempt, phase, findings: (dup.finding_ids || []).length, new: 0, rematched: 0, resolved: 0, unaddressed: [], reopened: 0, suppressed: dup.suppressed || [], returned: (dup.returned_ids || []).length };
|
|
241
318
|
}
|
|
242
319
|
|
|
243
320
|
const prior = fold.verdicts.filter((r) => r.agent === agent && samePhase(r.phase, phase));
|
|
@@ -251,6 +328,10 @@ function recordVerdict(cwd, input) {
|
|
|
251
328
|
const newRows = [];
|
|
252
329
|
const findingRows = [];
|
|
253
330
|
const mine = [...fold.findings.values()].filter((e) => e.agent === agent && samePhase(e.phase, phase));
|
|
331
|
+
const supps = learnedSuppressions(cwd, fold);
|
|
332
|
+
const suppressed = [];
|
|
333
|
+
const returned = [];
|
|
334
|
+
const preExisting = [];
|
|
254
335
|
let rematched = 0;
|
|
255
336
|
for (const f of verdict.findings) {
|
|
256
337
|
let id = V.findingId(phase, agent, f);
|
|
@@ -261,9 +342,21 @@ function recordVerdict(cwd, input) {
|
|
|
261
342
|
if (earlier) { id = earlier.id; rematched++; }
|
|
262
343
|
}
|
|
263
344
|
if (ids.includes(id)) continue; // the same finding twice in one report is one finding
|
|
345
|
+
// A learned suppression (R3) filters it; a dismissed finding no suppression
|
|
346
|
+
// covers any more comes back open.
|
|
347
|
+
const existing = fold.findings.get(id);
|
|
348
|
+
if (!existing || existing.status === 'dismissed') {
|
|
349
|
+
const s = suppressionFor(supps, agent, phase, f);
|
|
350
|
+
if (s) {
|
|
351
|
+
suppressed.push({ id: s.f.id, scope: s.scope, summary: f.summary });
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (existing) returned.push(id);
|
|
355
|
+
}
|
|
264
356
|
ids.push(id);
|
|
265
357
|
if (!fold.findings.has(id)) {
|
|
266
|
-
findingRows.push({ v: LEDGER_V, kind: 'finding', ts, id, verdict_id: verdictId, phase, milestone, agent, class: f.class, severity: f.severity, where: f.where, summary: f.summary });
|
|
358
|
+
findingRows.push({ v: LEDGER_V, kind: 'finding', ts, id, verdict_id: verdictId, phase, milestone, agent, class: f.class, severity: f.severity, where: f.where, summary: f.summary, ...(f.pre_existing ? { pre_existing: true } : {}) });
|
|
359
|
+
if (f.pre_existing) preExisting.push(id);
|
|
267
360
|
}
|
|
268
361
|
}
|
|
269
362
|
|
|
@@ -307,13 +400,19 @@ function recordVerdict(cwd, input) {
|
|
|
307
400
|
newRows.push({
|
|
308
401
|
v: LEDGER_V, kind: 'verdict', ts, verdict_id: verdictId, record_sig: recordSig, phase, milestone, agent,
|
|
309
402
|
verdict: verdict.verdict, outcome: verdict.outcome, ...lowered, attempt, source, finding_ids: ids,
|
|
403
|
+
...(returned.length ? { returned_ids: returned } : {}),
|
|
404
|
+
...(suppressed.length ? { suppressed } : {}),
|
|
310
405
|
by_severity: bySeverity, by_class: byClass, warnings: parsed.warnings,
|
|
311
406
|
...(notChecked.length ? { not_checked: notChecked } : {}),
|
|
407
|
+
...(verdict.declined && verdict.declined.length ? { declined: verdict.declined } : {}),
|
|
312
408
|
// A plan check is bound to the plans it checked (V7): exec-phase compares this
|
|
313
409
|
// hash with the plans it is about to run.
|
|
314
410
|
...(agent === 'pan-plan-checker' ? { plans_sha: require('./verify-plan-set.cjs').plansShaFor(cwd, phase) } : {}),
|
|
315
411
|
});
|
|
316
|
-
|
|
412
|
+
// A pre-existing finding (M52) is in code the phase did not change: debt the milestone
|
|
413
|
+
// audit reads, never a blocker for this phase.
|
|
414
|
+
const deferRows = preExisting.map((id) => ({ v: LEDGER_V, kind: 'disposition', ts, id, disposition: 'deferred', reason: `pre-existing: in code phase ${phase} did not change; recorded as debt, not a blocker`, auto: true, verdict_id: verdictId }));
|
|
415
|
+
appendRows(cwd, [...findingRows, ...newRows, ...resolveRows, ...deferRows]);
|
|
317
416
|
|
|
318
417
|
const category = { pass: 'verdict_passed', fail: 'verdict_failed', needs_human: 'verdict_needs_human' }[verdict.outcome];
|
|
319
418
|
const serious = (bySeverity.critical || 0) + (bySeverity.high || 0) > 0;
|
|
@@ -338,7 +437,7 @@ function recordVerdict(cwd, input) {
|
|
|
338
437
|
});
|
|
339
438
|
}
|
|
340
439
|
|
|
341
|
-
return { ...base, recorded: true, duplicate: false, verdict_id: verdictId, attempt, phase, findings: ids.length, new: findingRows.length, rematched, resolved: resolveRows.length, unaddressed, reopened, ledger: planningRel(FINDINGS_FILE) };
|
|
440
|
+
return { ...base, recorded: true, duplicate: false, verdict_id: verdictId, attempt, phase, findings: ids.length, new: findingRows.length, rematched, resolved: resolveRows.length, unaddressed, reopened, suppressed, returned: returned.length, pre_existing: preExisting.length, ...(verdict.declined && verdict.declined.length ? { declined: verdict.declined } : {}), ledger: planningRel(FINDINGS_FILE) };
|
|
342
441
|
}
|
|
343
442
|
|
|
344
443
|
/**
|
|
@@ -373,7 +472,10 @@ function disposeFindings(cwd, input) {
|
|
|
373
472
|
const f = fold.findings.get(id);
|
|
374
473
|
if (!f) { skipped.push({ id, why: 'unknown id' }); continue; }
|
|
375
474
|
if (f.status === as) { skipped.push({ id, why: `already ${as}` }); continue; }
|
|
376
|
-
|
|
475
|
+
// A dismissal remembers its file as it was (R3): it suppresses the finding only
|
|
476
|
+
// until that file changes.
|
|
477
|
+
const fsha = as === 'dismissed' ? fileSha(cwd, f.where) : null;
|
|
478
|
+
out.push({ v: LEDGER_V, kind: 'disposition', ts, id, disposition: as, reason: reason || null, auto: false, ...(fsha ? { file_sha: fsha } : {}) });
|
|
377
479
|
disposed.push(id);
|
|
378
480
|
}
|
|
379
481
|
appendRows(cwd, out);
|
|
@@ -408,6 +510,19 @@ function listFindings(cwd, filters = {}) {
|
|
|
408
510
|
};
|
|
409
511
|
}
|
|
410
512
|
|
|
513
|
+
/** Learned suppressions (R3), each with its scope and whether its file is unchanged. */
|
|
514
|
+
function listSuppressions(cwd, filters = {}) {
|
|
515
|
+
const fold = foldFindings(readLedger(cwd).rows);
|
|
516
|
+
let list = learnedSuppressions(cwd, fold);
|
|
517
|
+
if (filters.agent) list = list.filter((s) => s.f.agent === filters.agent);
|
|
518
|
+
if (filters.phase) list = list.filter((s) => s.scope === 'promoted' || samePhase(s.f.phase, filters.phase));
|
|
519
|
+
const suppressions = list.map((s) => ({
|
|
520
|
+
id: s.f.id, agent: s.f.agent, phase: s.f.phase, class: s.f.class, file: fileOf(s.f.where), summary: s.f.summary,
|
|
521
|
+
reason: s.f.reason, scope: s.scope, active: s.active,
|
|
522
|
+
}));
|
|
523
|
+
return { contract: V.VERDICT_CONTRACT, suppressions, active: suppressions.filter((s) => s.active).length };
|
|
524
|
+
}
|
|
525
|
+
|
|
411
526
|
/**
|
|
412
527
|
* Tech debt for a milestone audit: deferred findings (with the reason recorded at
|
|
413
528
|
* the time) and findings nobody disposed, grouped by phase.
|
|
@@ -470,6 +585,26 @@ function cmdFindingsList(cwd, opts, raw) {
|
|
|
470
585
|
output(r, raw, String(r.counts.total));
|
|
471
586
|
}
|
|
472
587
|
|
|
588
|
+
function cmdFindingsSuppressions(cwd, opts, raw) {
|
|
589
|
+
const r = listSuppressions(cwd, opts);
|
|
590
|
+
output(r, raw, String(r.active));
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* `findings prove <id> --test <file>` (program R5): run the catching test at the phase's
|
|
595
|
+
* base and at HEAD, and record the proof. Exit 1 unless it confirms the finding.
|
|
596
|
+
*/
|
|
597
|
+
function cmdFindingsProve(cwd, opts, raw) {
|
|
598
|
+
if (!requirePlanningTree(cwd)) { output({ error: 'not_a_pan_project', reason: 'no .planning/ directory here' }, raw); return; }
|
|
599
|
+
const id = opts.id;
|
|
600
|
+
if (!id || !FINDING_ID_RE.test(id) || !opts.test) error('Usage: findings prove <f_id> --test <test-file> [--timeout <ms>]');
|
|
601
|
+
const f = foldFindings(readLedger(cwd).rows).findings.get(id);
|
|
602
|
+
if (!f) { output({ error: 'finding_not_found', id }, raw, 'error', 1); return; }
|
|
603
|
+
const p = require('./verify-red.cjs').proveFinding(cwd, f.phase, opts.test, { timeoutMs: opts.timeout ? Number(opts.timeout) : undefined });
|
|
604
|
+
appendRows(cwd, [{ v: LEDGER_V, kind: 'proof', ts: new Date().toISOString(), id, test: p.test, status: p.status }]);
|
|
605
|
+
output({ id, ...p, confidence: p.status === 'confirmed' ? 'confirmed' : 'candidate' }, raw, p.status, p.status === 'confirmed' ? 0 : 1);
|
|
606
|
+
}
|
|
607
|
+
|
|
473
608
|
function cmdFindingsDebt(cwd, opts, raw) {
|
|
474
609
|
const r = findingsDebt(cwd, opts);
|
|
475
610
|
output(r, raw, `${r.deferred_count} deferred, ${r.open_count} open`);
|
|
@@ -482,12 +617,15 @@ function findingIdsIn(args) {
|
|
|
482
617
|
|
|
483
618
|
module.exports = {
|
|
484
619
|
FINDINGS_FILE,
|
|
620
|
+
sameFinding,
|
|
621
|
+
cmdFindingsProve,
|
|
485
622
|
STATUSES,
|
|
486
623
|
readLedger,
|
|
487
624
|
foldFindings,
|
|
488
625
|
recordVerdict,
|
|
489
626
|
disposeFindings,
|
|
490
627
|
listFindings,
|
|
628
|
+
listSuppressions,
|
|
491
629
|
findingsDebt,
|
|
492
630
|
findingsChurn,
|
|
493
631
|
sameFinding,
|
|
@@ -496,4 +634,5 @@ module.exports = {
|
|
|
496
634
|
cmdFindingsDispose,
|
|
497
635
|
cmdFindingsList,
|
|
498
636
|
cmdFindingsDebt,
|
|
637
|
+
cmdFindingsSuppressions,
|
|
499
638
|
};
|
|
@@ -608,7 +608,7 @@ function listBatchFiles(cwd) {
|
|
|
608
608
|
*
|
|
609
609
|
* Newest, not oldest. v2.10.0 (E-12) sorted ascending so "older unfinished batches
|
|
610
610
|
* execute before newer ones", but nothing ever marks a batch finished, so it returned
|
|
611
|
-
* the first batch a project planned, every time. `/pan:focus-exec` executes the newest
|
|
611
|
+
* the first batch a project planned, every time. `/pan:focus-auto --step exec` executes the newest
|
|
612
612
|
* batch, so its `classify-stages` step was classifying a different batch from the one it
|
|
613
613
|
* ran. A batch is a snapshot of the backlog: a newer plan already carries whatever an
|
|
614
614
|
* older one left undone.
|
|
@@ -175,7 +175,7 @@ function cmdGitPush(cwd, opts, raw) {
|
|
|
175
175
|
function cmdGitStatus(cwd, opts, raw) {
|
|
176
176
|
if (!isGitRepo(cwd)) { _notGitRepo(raw); return; }
|
|
177
177
|
const branch = getCurrentBranch(cwd);
|
|
178
|
-
const r = execGit(cwd, ['status', '--porcelain']);
|
|
178
|
+
const r = execGit(cwd, ['status', '--porcelain'], { keepLeading: true });
|
|
179
179
|
if (r.exitCode !== 0) {
|
|
180
180
|
output({ error: 'status_failed', detail: r.stderr }, raw, 'status failed');
|
|
181
181
|
return;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* One host table (program A0): how PAN runs each host CLI.
|
|
4
|
+
*
|
|
5
|
+
* runner the autonomous runner (experiments; the autodev launch builds on it):
|
|
6
|
+
* `buildArgs(prompt, opts)` for a run that acts on its own, and `shell`
|
|
7
|
+
* ('win32' launches the npm .cmd shim through a shell on Windows, where
|
|
8
|
+
* spawnSync cannot resolve it). null: no adapter is written yet.
|
|
9
|
+
* family the model family the host runs by default; the cross-family review lane
|
|
10
|
+
* (R4) exists to get a second opinion from a different one. Copilot and
|
|
11
|
+
* OpenCode route to several vendors, so theirs names the router.
|
|
12
|
+
* review the cross-family review lane (R4): a read-only run, `promptVia` and
|
|
13
|
+
* `args(prompt)`. No flag that writes or auto-approves tools: the reviewer
|
|
14
|
+
* reads the change it is given and answers with a verdict block.
|
|
15
|
+
* headless the harness's model steps: `promptVia` ('stdin' | 'arg'), `args(prompt)`
|
|
16
|
+
* for hosts whose JSON is not parsed yet, and `parse: true` for the one
|
|
17
|
+
* whose output and usage PAN reads (Claude Code; its argv is built by the
|
|
18
|
+
* harness with the step's options).
|
|
19
|
+
*
|
|
20
|
+
* runner.cjs and harness/src/model.cjs used to keep their own tables, and a host change
|
|
21
|
+
* had to be made twice. tests/hosts.test.cjs pins the argv both produce.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const HOSTS = Object.freeze({
|
|
25
|
+
claude: Object.freeze({
|
|
26
|
+
bin: 'claude',
|
|
27
|
+
family: 'anthropic',
|
|
28
|
+
review: Object.freeze({ promptVia: 'stdin', args: () => ['-p', '--allowedTools', 'Read,Grep,Glob', '--output-format', 'text'] }),
|
|
29
|
+
runner: Object.freeze({
|
|
30
|
+
bin: 'claude',
|
|
31
|
+
buildArgs: (prompt, opts) => {
|
|
32
|
+
const args = ['-p', '--dangerously-skip-permissions'];
|
|
33
|
+
if (opts && opts.captureMetrics) args.push('--output-format', 'json');
|
|
34
|
+
args.push(prompt);
|
|
35
|
+
return args;
|
|
36
|
+
},
|
|
37
|
+
shell: 'win32',
|
|
38
|
+
}),
|
|
39
|
+
headless: Object.freeze({ bin: 'claude', promptVia: 'stdin', parse: true }),
|
|
40
|
+
}),
|
|
41
|
+
codex: Object.freeze({
|
|
42
|
+
bin: 'codex',
|
|
43
|
+
family: 'openai',
|
|
44
|
+
review: Object.freeze({ promptVia: 'stdin', args: () => ['exec', '--sandbox', 'read-only', '--skip-git-repo-check', '-'] }),
|
|
45
|
+
runner: Object.freeze({ bin: 'codex', buildArgs: (prompt) => ['exec', prompt], shell: 'win32' }),
|
|
46
|
+
// `codex exec -` reads the prompt from stdin; --json streams JSONL events.
|
|
47
|
+
headless: Object.freeze({ bin: 'codex', promptVia: 'stdin', args: () => ['exec', '--json', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox', '-'] }),
|
|
48
|
+
}),
|
|
49
|
+
copilot: Object.freeze({
|
|
50
|
+
bin: 'copilot',
|
|
51
|
+
family: 'github',
|
|
52
|
+
// No --allow-all-tools: a non-interactive run is refused any tool it would ask about.
|
|
53
|
+
review: Object.freeze({ promptVia: 'arg', args: (p) => ['-p', p] }),
|
|
54
|
+
// No autonomous adapter yet: `copilot -p` exists, but nothing is written around it.
|
|
55
|
+
runner: null,
|
|
56
|
+
// `-p` takes the prompt as an argument; --output-format json is JSONL.
|
|
57
|
+
headless: Object.freeze({ bin: 'copilot', promptVia: 'arg', args: (p) => ['-p', p, '--output-format', 'json', '--allow-all-tools', '--allow-all-paths'] }),
|
|
58
|
+
}),
|
|
59
|
+
gemini: Object.freeze({
|
|
60
|
+
bin: 'gemini',
|
|
61
|
+
family: 'google',
|
|
62
|
+
// Headless `-p` appends standard input to its prompt; default approval refuses writes.
|
|
63
|
+
review: Object.freeze({ promptVia: 'stdin', args: () => ['-p', 'Review the change on standard input. Reply with the pan-verdict block only.'] }),
|
|
64
|
+
runner: Object.freeze({ bin: 'gemini', buildArgs: (prompt) => ['-p', '--yolo', prompt], shell: 'win32' }),
|
|
65
|
+
// `-p` runs headless; -o json; --approval-mode yolo auto-approves tools.
|
|
66
|
+
headless: Object.freeze({ bin: 'gemini', promptVia: 'arg', args: (p) => ['-p', p, '-o', 'json', '--approval-mode', 'yolo'] }),
|
|
67
|
+
}),
|
|
68
|
+
opencode: Object.freeze({
|
|
69
|
+
bin: 'opencode',
|
|
70
|
+
family: 'opencode',
|
|
71
|
+
review: Object.freeze({ promptVia: 'arg', args: (p) => ['run', p] }),
|
|
72
|
+
runner: Object.freeze({ bin: 'opencode', buildArgs: (prompt) => [prompt], shell: 'win32' }),
|
|
73
|
+
// `run <message..>` with --format json (raw JSON events).
|
|
74
|
+
headless: Object.freeze({ bin: 'opencode', promptVia: 'arg', args: (p) => ['run', '--format', 'json', p] }),
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/** The runner adapters by host, as runner.cjs exposes them (RUNTIME_RUNNERS). */
|
|
79
|
+
function runnerTable() {
|
|
80
|
+
return Object.freeze(Object.fromEntries(Object.entries(HOSTS).map(([k, h]) => [k, h.runner])));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The headless model-step entries by host, as the harness exposes them (MODEL_RUNTIMES). */
|
|
84
|
+
function headlessTable() {
|
|
85
|
+
return Object.fromEntries(Object.entries(HOSTS).map(([k, h]) => [k, h.headless]));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { HOSTS, runnerTable, headlessTable };
|
|
@@ -71,6 +71,11 @@ function attachPhaseArtifactPaths(result, cwd, phaseDirectory) {
|
|
|
71
71
|
if (uatFile) {
|
|
72
72
|
result.uat_path = toPosix(path.join(phaseDirectory, uatFile));
|
|
73
73
|
}
|
|
74
|
+
// L6b: the user's corrections to /pan:assumptions, which discuss and plan read.
|
|
75
|
+
const assumptionsFile = files.find((f) => f.endsWith('-assumptions.md') || f === 'assumptions.md');
|
|
76
|
+
if (assumptionsFile) {
|
|
77
|
+
result.assumptions_path = toPosix(path.join(phaseDirectory, assumptionsFile));
|
|
78
|
+
}
|
|
74
79
|
} catch {
|
|
75
80
|
// Phase directory may not exist yet or may be unreadable
|
|
76
81
|
}
|
|
@@ -150,6 +155,8 @@ function cmdInitExecutePhase(cwd, phase, raw, opts) {
|
|
|
150
155
|
// Config flags
|
|
151
156
|
commit_docs: config.commit_docs,
|
|
152
157
|
parallelization: config.parallelization,
|
|
158
|
+
// A3: each pan-exec-waves executor in its own git worktree (Claude Code's Workflow tool).
|
|
159
|
+
worktree_isolation: (require('./test-gate.cjs').rawConfig(cwd).workflow || {}).worktree_isolation === true,
|
|
153
160
|
branching_strategy: config.branching_strategy,
|
|
154
161
|
phase_branch_template: config.phase_branch_template,
|
|
155
162
|
milestone_branch_template: config.milestone_branch_template,
|
|
@@ -337,7 +344,6 @@ function cmdInitNewProject(cwd, raw) {
|
|
|
337
344
|
const result = {
|
|
338
345
|
// Models
|
|
339
346
|
researcher_model: resolveModelInternal(cwd, 'pan-project-researcher'),
|
|
340
|
-
synthesizer_model: resolveModelInternal(cwd, 'pan-research-synthesizer'),
|
|
341
347
|
roadmapper_model: resolveModelInternal(cwd, 'pan-roadmapper'),
|
|
342
348
|
|
|
343
349
|
// Config
|
|
@@ -386,7 +392,6 @@ function cmdInitNewMilestone(cwd, raw) {
|
|
|
386
392
|
const result = {
|
|
387
393
|
// Models
|
|
388
394
|
researcher_model: resolveModelInternal(cwd, 'pan-project-researcher'),
|
|
389
|
-
synthesizer_model: resolveModelInternal(cwd, 'pan-research-synthesizer'),
|
|
390
395
|
roadmapper_model: resolveModelInternal(cwd, 'pan-roadmapper'),
|
|
391
396
|
|
|
392
397
|
// Config
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* The human merge gate on the CLI (program L6a): what `/pan:army`'s release step uses.
|
|
4
|
+
*
|
|
5
|
+
* `merge request <branch> [--phase N] [--ci-green]` stages a merge through the same
|
|
6
|
+
* gate the MCP bridge uses (mcp/merge-gate.cjs). With `--phase`, verify_pass is the
|
|
7
|
+
* phase gate's verdict, not the caller's word. The approval token is never printed: it
|
|
8
|
+
* is for a person, not for the agent that staged the merge.
|
|
9
|
+
*
|
|
10
|
+
* `merge confirm <branch>` performs the squash-merge only in an interactive terminal,
|
|
11
|
+
* after the person types the branch name. An agent runs commands without a TTY, so it
|
|
12
|
+
* can stage a merge but never approve one. There is deliberately no environment
|
|
13
|
+
* variable or flag that skips the terminal check: anything an agent can set, an agent
|
|
14
|
+
* can set to approve itself.
|
|
15
|
+
*/
|
|
16
|
+
const readline = require('readline');
|
|
17
|
+
const { output, error, execGit } = require('./core.cjs');
|
|
18
|
+
const gate = require('../../mcp/merge-gate.cjs');
|
|
19
|
+
|
|
20
|
+
function cmdMergeRequest(cwd, branch, opts, raw) {
|
|
21
|
+
if (!branch) error('Usage: merge request <branch> [--phase N] [--ci-green]');
|
|
22
|
+
let rec;
|
|
23
|
+
try {
|
|
24
|
+
rec = gate.requestMerge(cwd, { branch, phase: opts.phase, ci_green: !!opts.ciGreen, now: new Date().toISOString() });
|
|
25
|
+
} catch (e) { output({ error: 'invalid_branch', reason: e.message }, raw, 'error', 1); return; }
|
|
26
|
+
const { approval_token: _token, ...shown } = rec;
|
|
27
|
+
output({ ...shown, next: `A person runs, in their own terminal: pan-tools merge confirm ${rec.branch}` }, raw, rec.status);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cmdMergeConfirm(cwd, branch, raw) {
|
|
31
|
+
if (!branch) error('Usage: merge confirm <branch>');
|
|
32
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
33
|
+
output({ error: 'human_terminal_required', reason: 'merge confirm runs only in an interactive terminal: an agent can stage a merge, a person approves it', branch }, raw, 'error', 1);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let record;
|
|
37
|
+
try { record = JSON.parse(require('fs').readFileSync(require('path').join(gate.approvalsDir(cwd), `${gate.slugify(gate.validateBranch(branch))}.json`), 'utf8')); } catch { record = null; }
|
|
38
|
+
if (!record) { output({ error: 'no_merge_request', branch }, raw, 'error', 1); return; }
|
|
39
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
40
|
+
rl.question(`Squash-merge ${record.branch} into the current branch? CI green: ${record.ci_green}, verified: ${record.verify_pass}. Type the branch name to approve: `, (answer) => {
|
|
41
|
+
rl.close();
|
|
42
|
+
if (answer.trim() !== record.branch) { output({ merged: false, reasons: ['not_confirmed'], branch: record.branch }, raw, 'not_confirmed', 1); return; }
|
|
43
|
+
const env = { ...process.env, PAN_MERGE_APPROVAL: record.approval_token };
|
|
44
|
+
const git = (argv) => { const r = execGit(cwd, argv); return { ok: r.exitCode === 0, stdout: r.stdout, stderr: r.stderr }; };
|
|
45
|
+
const r = gate.confirmMerge(cwd, { branch: record.branch }, env, git);
|
|
46
|
+
output(r, raw, r.merged ? 'merged' : 'refused', r.merged ? 0 : 1);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { cmdMergeRequest, cmdMergeConfirm };
|