pan-wizard 3.36.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 +36 -48
- 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 +1 -2
- package/agents/pan-document_code.md +1 -2
- package/agents/pan-hardener.md +2 -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 +79 -488
- package/agents/pan-plan-checker.md +12 -2
- package/agents/pan-planner.md +10 -6
- package/agents/pan-previewer.md +1 -2
- package/agents/pan-project-researcher.md +70 -499
- package/agents/pan-release.md +1 -1
- package/agents/pan-reviewer.md +9 -3
- package/agents/pan-roadmapper.md +12 -7
- package/agents/pan-verifier.md +7 -2
- 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/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/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 +31 -2
- package/pan-wizard-core/bin/lib/constants.cjs +27 -1
- package/pan-wizard-core/bin/lib/core.cjs +22 -10
- package/pan-wizard-core/bin/lib/cost.cjs +14 -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/failure-class.cjs +81 -0
- package/pan-wizard-core/bin/lib/findings.cjs +34 -4
- package/pan-wizard-core/bin/lib/focus.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 +16 -5
- 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.cjs +48 -4
- package/pan-wizard-core/bin/lib/plan-lint.cjs +150 -1
- package/pan-wizard-core/bin/lib/preview.cjs +23 -4
- package/pan-wizard-core/bin/lib/requirements-lint.cjs +130 -0
- package/pan-wizard-core/bin/lib/research-lint.cjs +150 -3
- 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/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/uat.cjs +149 -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 +37 -1
- package/pan-wizard-core/bin/lib/verify-retro.cjs +3 -2
- package/pan-wizard-core/bin/lib/verify.cjs +59 -4
- package/pan-wizard-core/bin/lib/worktree.cjs +70 -0
- package/pan-wizard-core/bin/pan-tools.cjs +168 -27
- package/pan-wizard-core/learnings/universal/autonomous-loop.md +2 -2
- package/pan-wizard-core/references/continuation-format.md +3 -3
- package/pan-wizard-core/references/guardrails.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 +5 -1
- package/pan-wizard-core/templates/debug.md +1 -0
- package/pan-wizard-core/templates/discovery.md +2 -2
- package/pan-wizard-core/templates/requirements.md +9 -0
- 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 +36 -8
- 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 +31 -53
- 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 +86 -43
- 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/research-phase.md +118 -40
- package/pan-wizard-core/workflows/resume-project.md +18 -2
- package/pan-wizard-core/workflows/settings.md +21 -24
- 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 +2 -2
- package/scripts/build-agent-plugin.js +35 -15
- 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/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 +9 -2
- 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 -210
- package/pan-wizard-core/templates/research-project/features.md +0 -153
- package/pan-wizard-core/templates/research-project/pitfalls.md +0 -206
- package/pan-wizard-core/templates/research-project/stack.md +0 -126
- package/pan-wizard-core/templates/research-project/summary.md +0 -170
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Debug sessions as evidence and as a case base (program P5).
|
|
4
|
+
*
|
|
5
|
+
* `debug repro <slug>` runs the reproduction a session recorded (its `## Reproduction`
|
|
6
|
+
* `command:`), so a gap or a debug session closes only when that command turns green,
|
|
7
|
+
* not when an agent says it is fixed. The reproduction may name the requirement it
|
|
8
|
+
* stands for (`requirement: REQ-n`): one reproduction per requirement a gap breaks.
|
|
9
|
+
*
|
|
10
|
+
* `debug similar "<symptom>"` ranks the resolved sessions in `.planning/debug/resolved/`
|
|
11
|
+
* by token overlap with the new symptom (their symptoms, errors and root cause), so a
|
|
12
|
+
* recurring bug starts from the root cause and fix that worked before.
|
|
13
|
+
*/
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { output, error, toLf } = require('./core.cjs');
|
|
17
|
+
const TG = require('./test-gate.cjs');
|
|
18
|
+
|
|
19
|
+
const DEBUG_DIR = path.join('.planning', 'debug');
|
|
20
|
+
const RESOLVED_DIR = path.join(DEBUG_DIR, 'resolved');
|
|
21
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9._-]*$/i;
|
|
22
|
+
const DEFAULT_REPRO_TIMEOUT_MS = 2 * 60 * 1000;
|
|
23
|
+
const STOP = new Set(['the', 'and', 'for', 'with', 'that', 'this', 'when', 'after', 'before', 'from', 'into', 'are', 'was', 'not', 'but', 'its', 'every', 'right']);
|
|
24
|
+
|
|
25
|
+
/** The `key: value` lines of one `## Heading` section. */
|
|
26
|
+
function sectionFields(text, heading) {
|
|
27
|
+
const m = new RegExp(`^## ${heading}\\s*$([\\s\\S]*?)(?=^## |(?![\\s\\S]))`, 'm').exec(text);
|
|
28
|
+
const out = {};
|
|
29
|
+
if (!m) return out;
|
|
30
|
+
for (const line of m[1].split('\n')) {
|
|
31
|
+
const kv = /^([a-z_]+):\s*(.*)$/.exec(line.trim());
|
|
32
|
+
if (kv) out[kv[1]] = kv[2].trim();
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Parse a debug session file. */
|
|
38
|
+
function parseDebugSession(raw) {
|
|
39
|
+
const text = toLf(String(raw || ''));
|
|
40
|
+
const fm = /^---\n([\s\S]*?)\n---/.exec(text);
|
|
41
|
+
const status = fm ? ((/^status:\s*(.+)$/m.exec(fm[1]) || [])[1] || '').trim() : '';
|
|
42
|
+
return {
|
|
43
|
+
status,
|
|
44
|
+
symptoms: sectionFields(text, 'Symptoms'),
|
|
45
|
+
reproduction: sectionFields(text, 'Reproduction'),
|
|
46
|
+
resolution: sectionFields(text, 'Resolution'),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Run a session's reproduction. green | red | none (no command recorded). */
|
|
51
|
+
function reproCheck(cwd, slug, opts = {}) {
|
|
52
|
+
if (!slug || !SLUG_RE.test(slug) || slug.includes('..')) return { error: 'invalid_slug', reason: 'a debug session slug is letters, digits, dots, dashes and underscores' };
|
|
53
|
+
const candidates = [path.join(cwd, DEBUG_DIR, `${slug}.md`), path.join(cwd, RESOLVED_DIR, `${slug}.md`)];
|
|
54
|
+
const file = candidates.find((f) => fs.existsSync(f));
|
|
55
|
+
if (!file) return { error: 'session_not_found', reason: `no .planning/debug/${slug}.md` };
|
|
56
|
+
const s = parseDebugSession(fs.readFileSync(file, 'utf8'));
|
|
57
|
+
const command = s.reproduction.command || '';
|
|
58
|
+
const requirement = s.reproduction.requirement || null;
|
|
59
|
+
if (!command || /^none\b/i.test(command) || /^\[/.test(command)) {
|
|
60
|
+
return { slug, result: 'none', closable: false, requirement, detail: 'the session records no reproduction command; its closing needs a human check' };
|
|
61
|
+
}
|
|
62
|
+
const r = TG.runCommand(cwd, command, opts.timeoutMs || DEFAULT_REPRO_TIMEOUT_MS);
|
|
63
|
+
const result = r.exit === 0 ? 'green' : 'red';
|
|
64
|
+
return { slug, result, closable: result === 'green', requirement, command, exit: r.exit, ...(r.timed_out ? { timed_out: true } : {}) };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function tokens(s) {
|
|
68
|
+
return new Set(String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').split(' ').filter((w) => w.length > 2 && !STOP.has(w)));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Rank resolved sessions by overlap with a symptom. */
|
|
72
|
+
function similarCases(cwd, symptom, opts = {}) {
|
|
73
|
+
const want = tokens(symptom);
|
|
74
|
+
const dir = path.join(cwd, RESOLVED_DIR);
|
|
75
|
+
let files = [];
|
|
76
|
+
try { files = fs.readdirSync(dir).filter((f) => f.endsWith('.md')); } catch { /* none yet */ }
|
|
77
|
+
const cases = [];
|
|
78
|
+
for (const f of files) {
|
|
79
|
+
const s = parseDebugSession(fs.readFileSync(path.join(dir, f), 'utf8'));
|
|
80
|
+
const have = tokens([s.symptoms.expected, s.symptoms.actual, s.symptoms.errors, s.resolution.root_cause].join(' '));
|
|
81
|
+
let both = 0;
|
|
82
|
+
for (const t of want) if (have.has(t)) both++;
|
|
83
|
+
const score = want.size && have.size ? both / (want.size + have.size - both) : 0;
|
|
84
|
+
if (score <= 0) continue;
|
|
85
|
+
cases.push({ slug: f.replace(/\.md$/, ''), score: Number(score.toFixed(3)), root_cause: s.resolution.root_cause || null, fix: s.resolution.fix || null, reproduction: s.reproduction.command || null });
|
|
86
|
+
}
|
|
87
|
+
cases.sort((a, b) => b.score - a.score || a.slug.localeCompare(b.slug));
|
|
88
|
+
return { symptom, cases: cases.slice(0, opts.limit || 5) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function cmdDebugRepro(cwd, slug, raw, opts = {}) {
|
|
92
|
+
if (!slug) error('Usage: debug repro <slug> [--timeout <ms>]');
|
|
93
|
+
const r = reproCheck(cwd, slug, { timeoutMs: opts.timeout ? Number(opts.timeout) : undefined });
|
|
94
|
+
if (r.error) { output(r, raw, 'error', 1); return; }
|
|
95
|
+
output(r, raw, r.result, r.result === 'green' ? 0 : 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function cmdDebugSimilar(cwd, symptom, raw, opts = {}) {
|
|
99
|
+
if (!symptom) error('Usage: debug similar "<symptom>" [--limit N]');
|
|
100
|
+
const r = similarCases(cwd, symptom, { limit: opts.limit ? Number(opts.limit) : undefined });
|
|
101
|
+
output(r, raw, r.cases.map((c) => c.slug).join('\n'));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
module.exports = { parseDebugSession, reproCheck, similarCases, cmdDebugRepro, cmdDebugSimilar };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* The failure-class router (program A4).
|
|
4
|
+
*
|
|
5
|
+
* When an autodev unit fails, what to do next depends on why. classifyFailure reads the
|
|
6
|
+
* exit, the output tail and whether it timed out; routeFailure gives the class's policy
|
|
7
|
+
* for this attempt. Breadth before depth (Olausson et al.): a cheap retry with the error
|
|
8
|
+
* in front of the model comes before a tier escalation, and anything a model cannot fix
|
|
9
|
+
* (a missing CLI or login, a merge conflict, the guard's deny, an undeclared file) goes
|
|
10
|
+
* to a human.
|
|
11
|
+
*
|
|
12
|
+
* retry_with_tail rerun the unit with the error tail in the prompt
|
|
13
|
+
* fresh_restart rerun the unit as it was
|
|
14
|
+
* escalate_tier rerun it at the next model tier (core.cjs escalateTier)
|
|
15
|
+
* quarantine record it as a flake and go on
|
|
16
|
+
* ask stop: human_needed
|
|
17
|
+
* stop_budget stop: budget_usd
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const { escalateTier } = require('./core.cjs');
|
|
21
|
+
|
|
22
|
+
const FAILURE_CLASSES = Object.freeze(['compile', 'assertion', 'timeout', 'flake', 'missing_env', 'merge_conflict', 'integrity_violation', 'scope_violation', 'budget', 'unknown']);
|
|
23
|
+
|
|
24
|
+
// First match wins: the specific, unfixable classes are checked before the generic ones.
|
|
25
|
+
const PATTERNS = [
|
|
26
|
+
['budget', /max-budget-usd|credit balance|usage limit|rate limit|quota exceeded|budget exceeded/i],
|
|
27
|
+
['missing_env', /is not recognized as an internal or external command|command not found|ENOENT|invalid api key|not logged in|please run \/login|authenticat(e|ion) (failed|required)|missing (an )?api key/i],
|
|
28
|
+
['integrity_violation', /PAN guard:|integrity (check|violation)|hash mismatch|tamper/i],
|
|
29
|
+
['merge_conflict', /CONFLICT \(|merge conflict|Automatic merge failed/i],
|
|
30
|
+
['scope_violation', /scope violation|undeclared (file|test)|not declared by any plan|outside the plan/i],
|
|
31
|
+
['timeout', /ETIMEDOUT|timed out|timeout of \d+/i],
|
|
32
|
+
['compile', /error TS\d+|SyntaxError|ReferenceError|Cannot find (module|name)|compilation failed|failed to compile|cannot compile/i],
|
|
33
|
+
['assertion', /AssertionError|assert\w*\.\w+|expected .{1,80} (to|but)|\btests? failed\b|\d+ failing\b|^not ok /im],
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {{exit?: number|null, stderr?: string, stdout?: string, timedOut?: boolean, flaky?: boolean}} f
|
|
38
|
+
* @returns {string} one of FAILURE_CLASSES
|
|
39
|
+
*/
|
|
40
|
+
function classifyFailure(f = {}) {
|
|
41
|
+
if (f.flaky) return 'flake';
|
|
42
|
+
if (f.timedOut) return 'timeout';
|
|
43
|
+
const text = `${f.stderr || ''}\n${f.stdout || ''}`;
|
|
44
|
+
for (const [cls, re] of PATTERNS) if (re.test(text)) return cls;
|
|
45
|
+
if (f.exit === 127) return 'missing_env';
|
|
46
|
+
return 'unknown';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const POLICIES = {
|
|
50
|
+
compile: ['retry_with_tail', 'escalate_tier'],
|
|
51
|
+
assertion: ['retry_with_tail', 'escalate_tier'],
|
|
52
|
+
timeout: ['fresh_restart'],
|
|
53
|
+
flake: ['quarantine'],
|
|
54
|
+
missing_env: [],
|
|
55
|
+
merge_conflict: [],
|
|
56
|
+
integrity_violation: [],
|
|
57
|
+
scope_violation: [],
|
|
58
|
+
budget: ['stop_budget'],
|
|
59
|
+
unknown: ['fresh_restart'],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The policy for the `attempt`th failure of a unit (1 = its first failure).
|
|
64
|
+
* @returns {{action: string, tier?: string}}
|
|
65
|
+
*/
|
|
66
|
+
function routeFailure(cls, attempt = 1) {
|
|
67
|
+
const ladder = POLICIES[cls] || POLICIES.unknown;
|
|
68
|
+
if (cls === 'flake') return { action: 'quarantine' };
|
|
69
|
+
if (cls === 'budget') return { action: 'stop_budget' };
|
|
70
|
+
const action = ladder[attempt - 1] || 'ask';
|
|
71
|
+
if (action === 'escalate_tier') return { action, tier: escalateTier('mid', attempt).tier };
|
|
72
|
+
return { action };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The last lines of a failure's output, for the retry prompt (one line, shell-safe). */
|
|
76
|
+
function errorTail(f = {}, max = 300) {
|
|
77
|
+
const text = `${f.stderr || ''}\n${f.stdout || ''}`.trim().split(/\r?\n/).filter(Boolean).slice(-4).join(' / ');
|
|
78
|
+
return text.replace(/[\r\n&|<>%^"]/g, ' ').replace(/\s+/g, ' ').slice(-max).trim();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { FAILURE_CLASSES, classifyFailure, routeFailure, errorTail, POLICIES };
|
|
@@ -92,6 +92,8 @@ function foldFindings(rows) {
|
|
|
92
92
|
severity: r.severity, where: r.where || null, summary: r.summary, first_seen: r.ts,
|
|
93
93
|
first_verdict_id: r.verdict_id || null, reports: 0, last_reported: null, _reportedSeq: -1,
|
|
94
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,
|
|
95
97
|
});
|
|
96
98
|
} else if (r.kind === 'verdict') {
|
|
97
99
|
verdicts.push({ ...r, _seq: seq });
|
|
@@ -107,6 +109,10 @@ function foldFindings(rows) {
|
|
|
107
109
|
const f = findings.get(id);
|
|
108
110
|
if (f) f._returnedSeq = seq;
|
|
109
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 };
|
|
110
116
|
} else if (r.kind === 'disposition' && typeof r.id === 'string') {
|
|
111
117
|
const f = findings.get(r.id);
|
|
112
118
|
if (!f || !V.DISPOSITIONS.includes(r.disposition)) return;
|
|
@@ -130,7 +136,8 @@ function foldFindings(rows) {
|
|
|
130
136
|
/** A finding as the CLI shows it (internal sequence fields removed). */
|
|
131
137
|
function publicFinding(f) {
|
|
132
138
|
const { _reportedSeq, _disposedSeq, _returnedSeq, ...rest } = f;
|
|
133
|
-
|
|
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' };
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
function countBy(list, key) {
|
|
@@ -324,6 +331,7 @@ function recordVerdict(cwd, input) {
|
|
|
324
331
|
const supps = learnedSuppressions(cwd, fold);
|
|
325
332
|
const suppressed = [];
|
|
326
333
|
const returned = [];
|
|
334
|
+
const preExisting = [];
|
|
327
335
|
let rematched = 0;
|
|
328
336
|
for (const f of verdict.findings) {
|
|
329
337
|
let id = V.findingId(phase, agent, f);
|
|
@@ -347,7 +355,8 @@ function recordVerdict(cwd, input) {
|
|
|
347
355
|
}
|
|
348
356
|
ids.push(id);
|
|
349
357
|
if (!fold.findings.has(id)) {
|
|
350
|
-
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);
|
|
351
360
|
}
|
|
352
361
|
}
|
|
353
362
|
|
|
@@ -395,11 +404,15 @@ function recordVerdict(cwd, input) {
|
|
|
395
404
|
...(suppressed.length ? { suppressed } : {}),
|
|
396
405
|
by_severity: bySeverity, by_class: byClass, warnings: parsed.warnings,
|
|
397
406
|
...(notChecked.length ? { not_checked: notChecked } : {}),
|
|
407
|
+
...(verdict.declined && verdict.declined.length ? { declined: verdict.declined } : {}),
|
|
398
408
|
// A plan check is bound to the plans it checked (V7): exec-phase compares this
|
|
399
409
|
// hash with the plans it is about to run.
|
|
400
410
|
...(agent === 'pan-plan-checker' ? { plans_sha: require('./verify-plan-set.cjs').plansShaFor(cwd, phase) } : {}),
|
|
401
411
|
});
|
|
402
|
-
|
|
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]);
|
|
403
416
|
|
|
404
417
|
const category = { pass: 'verdict_passed', fail: 'verdict_failed', needs_human: 'verdict_needs_human' }[verdict.outcome];
|
|
405
418
|
const serious = (bySeverity.critical || 0) + (bySeverity.high || 0) > 0;
|
|
@@ -424,7 +437,7 @@ function recordVerdict(cwd, input) {
|
|
|
424
437
|
});
|
|
425
438
|
}
|
|
426
439
|
|
|
427
|
-
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, 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) };
|
|
428
441
|
}
|
|
429
442
|
|
|
430
443
|
/**
|
|
@@ -577,6 +590,21 @@ function cmdFindingsSuppressions(cwd, opts, raw) {
|
|
|
577
590
|
output(r, raw, String(r.active));
|
|
578
591
|
}
|
|
579
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
|
+
|
|
580
608
|
function cmdFindingsDebt(cwd, opts, raw) {
|
|
581
609
|
const r = findingsDebt(cwd, opts);
|
|
582
610
|
output(r, raw, `${r.deferred_count} deferred, ${r.open_count} open`);
|
|
@@ -589,6 +617,8 @@ function findingIdsIn(args) {
|
|
|
589
617
|
|
|
590
618
|
module.exports = {
|
|
591
619
|
FINDINGS_FILE,
|
|
620
|
+
sameFinding,
|
|
621
|
+
cmdFindingsProve,
|
|
592
622
|
STATUSES,
|
|
593
623
|
readLedger,
|
|
594
624
|
foldFindings,
|
|
@@ -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.
|
|
@@ -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 };
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
* `{NN}-mutation.json`
|
|
18
18
|
*
|
|
19
19
|
* `workflow.mutation_gate`: `off` · `report` (the default: a report, never a refusal) ·
|
|
20
|
-
* `gate` (
|
|
20
|
+
* `gate` (V5G, opt-in: a score below `workflow.mutation_floor`, 0.6, fails the gate, and
|
|
21
|
+
* verify reconcile then treats a passed verification as a contradiction, so the phase gate
|
|
22
|
+
* refuses until the tests are stronger). No coverage is collected:
|
|
21
23
|
* the full suite runs per mutant, so the sample is kept small.
|
|
22
24
|
*
|
|
23
25
|
* The operator table started as scripts/mutation-probe.cjs's, which PAN runs on itself;
|
|
@@ -29,6 +31,7 @@ const { execGit, findPhaseInternal, output, error, toPosix } = require('./core.c
|
|
|
29
31
|
|
|
30
32
|
const MODES = Object.freeze(['off', 'report', 'gate']);
|
|
31
33
|
const DEFAULT_MAX = 8;
|
|
34
|
+
const DEFAULT_FLOOR = 0.6;
|
|
32
35
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
33
36
|
|
|
34
37
|
const C_FAMILY_COMPARE = [
|
|
@@ -171,6 +174,12 @@ function mutationMode(cwd) {
|
|
|
171
174
|
* @param {string} phase
|
|
172
175
|
* @param {{max?: number, seed?: number, timeoutMs?: number}} [opts]
|
|
173
176
|
*/
|
|
177
|
+
/** `workflow.mutation_floor`: the score gate mode needs, 0..1; 0.6 unless set. */
|
|
178
|
+
function mutationFloor(cwd) {
|
|
179
|
+
const v = Number((require('./test-gate.cjs').rawConfig(cwd).workflow || {}).mutation_floor);
|
|
180
|
+
return Number.isFinite(v) && v >= 0 && v <= 1 ? v : DEFAULT_FLOOR;
|
|
181
|
+
}
|
|
182
|
+
|
|
174
183
|
function mutatePhase(cwd, phase, opts = {}) {
|
|
175
184
|
const TG = require('./test-gate.cjs');
|
|
176
185
|
const { phaseBase, withWorktree } = require('./verify-red.cjs');
|
|
@@ -231,16 +240,18 @@ function mutatePhase(cwd, phase, opts = {}) {
|
|
|
231
240
|
const results = wt.value.results;
|
|
232
241
|
const killed = results.filter((r) => r.killed).length;
|
|
233
242
|
const survivors = results.filter((r) => !r.killed).map(({ file, line, op, before, after }) => ({ file: toPosix(file), line, op, before: before.trim(), after: after.trim() }));
|
|
243
|
+
const score = results.length ? Number((killed / results.length).toFixed(2)) : null;
|
|
244
|
+
const gate = mode === 'gate' && score !== null ? { floor: mutationFloor(cwd), failed: score < mutationFloor(cwd) } : undefined;
|
|
234
245
|
return {
|
|
235
246
|
...base, checked: results.length > 0, base: b.base, head, command: suite.command,
|
|
236
247
|
candidates: candidates.length, sampled: results.length, killed, survived: survivors.length,
|
|
237
|
-
score
|
|
248
|
+
score, survivors, ...(gate ? { gate } : {}),
|
|
238
249
|
};
|
|
239
250
|
}
|
|
240
251
|
|
|
241
252
|
/**
|
|
242
253
|
* `pan-tools verify mutate --phase N [--max n] [--seed n] [--timeout ms]`: writes
|
|
243
|
-
* `{NN}-mutation.json
|
|
254
|
+
* `{NN}-mutation.json`. Exits 0, except in gate mode when the score is below the floor (V5G).
|
|
244
255
|
*/
|
|
245
256
|
function cmdVerifyMutate(cwd, opts, raw) {
|
|
246
257
|
if (!opts.phase) error('Usage: verify mutate --phase <N> [--max <n>] [--seed <n>] [--timeout <ms>]');
|
|
@@ -251,7 +262,7 @@ function cmdVerifyMutate(cwd, opts, raw) {
|
|
|
251
262
|
const file = path.join(cwd, info.directory, `${prefix}-mutation.json`);
|
|
252
263
|
fs.writeFileSync(file, JSON.stringify(r, null, 2) + '\n');
|
|
253
264
|
r.record = toPosix(path.relative(cwd, file));
|
|
254
|
-
output(r, raw, r.aborted ? `aborted:${r.aborted}` : (r.checked ? `${r.killed}/${r.sampled} killed` : 'not_checked'));
|
|
265
|
+
output(r, raw, r.aborted ? `aborted:${r.aborted}` : (r.checked ? `${r.killed}/${r.sampled} killed${r.gate && r.gate.failed ? ' (gate failed)' : ''}` : 'not_checked'), r.gate && r.gate.failed ? 1 : 0);
|
|
255
266
|
}
|
|
256
267
|
|
|
257
|
-
module.exports = { OPERATORS, MODES, languageOf, mutableLine, mutationsFor, applyMutation, sample, changedLines, mutationMode, mutatePhase, cmdVerifyMutate };
|
|
268
|
+
module.exports = { mutationFloor, DEFAULT_FLOOR, OPERATORS, MODES, languageOf, mutableLine, mutationsFor, applyMutation, sample, changedLines, mutationMode, mutatePhase, cmdVerifyMutate };
|