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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* The cross-family reviewer lane (program R4): `pan-tools review cross-family <phase>`.
|
|
4
|
+
*
|
|
5
|
+
* A reviewer from the same model family as the author tends to share its blind spots.
|
|
6
|
+
* This lane runs one more review of the phase on another host's CLI, read-only (the
|
|
7
|
+
* argv is hosts.cjs `review`), and keeps its pan-verdict block beside the others, so
|
|
8
|
+
* review-deep can merge it as a fourth source (`--cross-file`).
|
|
9
|
+
*
|
|
10
|
+
* Opt-in per project: `workflow.cross_family_review` is `off` by default, or names a
|
|
11
|
+
* host. `--host` overrides it for one run; `--dry-run` prints the lane, the argv and the
|
|
12
|
+
* prompt and spawns nothing; `--reps N` runs it N times and reports each verdict, which
|
|
13
|
+
* is what the lane's evidence run needs.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { spawnSync } = require('child_process');
|
|
19
|
+
const { output, execGit, safeReadFile, toPosix } = require('./core.cjs');
|
|
20
|
+
const { planningPath } = require('./utils.cjs');
|
|
21
|
+
const { HOSTS } = require('./hosts.cjs');
|
|
22
|
+
const { findVerdictBlocks, validateVerdict } = require('./verdict.cjs');
|
|
23
|
+
|
|
24
|
+
const MAX_DIFF_CHARS = 120000;
|
|
25
|
+
// Windows caps a command line near 32k characters; a longer prompt goes through a file.
|
|
26
|
+
const MAX_ARG_PROMPT = 24000;
|
|
27
|
+
|
|
28
|
+
function laneHost(cwd, override) {
|
|
29
|
+
if (override) return override;
|
|
30
|
+
const wf = (require('./test-gate.cjs').rawConfig(cwd).workflow) || {};
|
|
31
|
+
const v = wf.cross_family_review;
|
|
32
|
+
return typeof v === 'string' && v && v !== 'off' ? v : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function buildPrompt(cwd, phase) {
|
|
36
|
+
const { reviewScope } = require('./verify-scope.cjs');
|
|
37
|
+
const scope = reviewScope(cwd, phase);
|
|
38
|
+
if (scope.error) return { error: scope.error, reason: scope.reason };
|
|
39
|
+
let diff = '';
|
|
40
|
+
if (scope.base) {
|
|
41
|
+
const r = execGit(cwd, ['diff', scope.base, '--', ...scope.files.map((f) => f.path)]);
|
|
42
|
+
if (r.exitCode === 0) diff = r.stdout;
|
|
43
|
+
} else if (scope.files.length) {
|
|
44
|
+
const r = execGit(cwd, ['diff', 'HEAD', '--', ...scope.files.map((f) => f.path)]);
|
|
45
|
+
if (r.exitCode === 0) diff = r.stdout;
|
|
46
|
+
}
|
|
47
|
+
const truncated = diff.length > MAX_DIFF_CHARS;
|
|
48
|
+
if (truncated) diff = diff.slice(0, MAX_DIFF_CHARS);
|
|
49
|
+
const contract = safeReadFile(path.join(__dirname, '..', '..', 'references', 'verdict-contract.md')) || '';
|
|
50
|
+
const intent = scope.intent.plans.map((p) => `### ${p.plan}\n${p.objective || ''}${p.review_focus ? `\n\nReview focus:\n${p.review_focus}` : ''}`).join('\n\n');
|
|
51
|
+
const reqs = scope.intent.requirements.map((r) => `- ${r.id}: ${r.text || '(missing)'}`).join('\n');
|
|
52
|
+
const prompt = [
|
|
53
|
+
`You are an independent code reviewer from a different model family than the author. Review phase ${scope.phase} of this project: find correctness, security and maintainability defects in the change below, judged against the plans' intent. Do not edit anything.`,
|
|
54
|
+
`Reply with one fenced \`pan-verdict\` block, as the contract below defines: \`"contract": "1.0"\`, \`"agent": "cross-family-reviewer"\`, \`"phase": "${scope.phase}"\`, a \`verdict\` and \`outcome\`, and one finding per defect (\`where\` as \`file:line\`). Name what you could not judge in \`declined\`.`,
|
|
55
|
+
'## Verdict contract', contract.trim(),
|
|
56
|
+
'## Intent', intent || '(no plans found)', reqs ? `## Requirements\n${reqs}` : '',
|
|
57
|
+
`## Files in scope\n${scope.files.map((f) => `- ${f.path}`).join('\n') || '(none)'}`,
|
|
58
|
+
`## Diff${scope.base ? ` (from ${scope.base.slice(0, 12)})` : ''}${truncated ? ' (truncated)' : ''}`, '```diff', diff || '(no diff)', '```',
|
|
59
|
+
].filter(Boolean).join('\n\n');
|
|
60
|
+
return { phase: scope.phase, prompt, truncated };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* How to launch a host binary on this platform. Off Windows: by name, no shell. On Windows
|
|
65
|
+
* a binary that resolves to an .exe (Copilot CLI's installer, say) is launched directly by
|
|
66
|
+
* its path, so Node quotes each argument and a multi-line prompt arrives whole. Anything
|
|
67
|
+
* else (an npm .cmd shim, a stand-in) needs cmd.exe, which joins arguments unquoted and
|
|
68
|
+
* ends the command at a newline: there the prompt goes through a file and each argument
|
|
69
|
+
* with whitespace or a quote is quoted (2026-10-23: `-p <prompt>` reached cmd.exe as words).
|
|
70
|
+
*/
|
|
71
|
+
function launchPlan(bin, platform = process.platform, env = process.env) {
|
|
72
|
+
if (platform !== 'win32') return { file: bin, shell: false, promptFile: false };
|
|
73
|
+
const exts = String(env.PATHEXT || '.COM;.EXE;.BAT;.CMD').split(';').filter(Boolean).map((e) => e.toLowerCase());
|
|
74
|
+
for (const dir of String(env.PATH || env.Path || '').split(path.delimiter).map((d) => d.trim().replace(/^"(.*)"$/, '$1')).filter(Boolean)) {
|
|
75
|
+
for (const ext of exts) {
|
|
76
|
+
const p = path.join(dir, bin + ext);
|
|
77
|
+
if (!fs.existsSync(p)) continue;
|
|
78
|
+
if (ext === '.exe' || ext === '.com') return { file: p, shell: false, promptFile: false };
|
|
79
|
+
return { file: bin, shell: true, promptFile: true };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { file: bin, shell: true, promptFile: true };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One argument for cmd.exe's command line: quoted when it holds whitespace, a quote or one of
|
|
87
|
+
* cmd's operators (& | < > ^), which are literal inside quotes. Pure.
|
|
88
|
+
*/
|
|
89
|
+
function quoteForCmd(arg) {
|
|
90
|
+
const s = String(arg);
|
|
91
|
+
return /[\s"&|<>^]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseVerdict(text) {
|
|
95
|
+
const blocks = findVerdictBlocks(text || '');
|
|
96
|
+
const last = blocks.filter((b) => b.closed).pop();
|
|
97
|
+
if (!last) return { verdict: null, error: 'no_verdict_block' };
|
|
98
|
+
try {
|
|
99
|
+
const r = validateVerdict(JSON.parse(last.body));
|
|
100
|
+
return r.ok ? { verdict: r.verdict } : { verdict: null, error: r.error };
|
|
101
|
+
} catch { return { verdict: null, error: 'invalid_json' }; }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Plan or run the lane. Pure apart from reading the project and, without dryRun,
|
|
106
|
+
* spawning the host and writing `.planning/reviews/<NN>/cross-family-<host>.md`.
|
|
107
|
+
*/
|
|
108
|
+
function crossFamilyReview(cwd, phase, opts = {}) {
|
|
109
|
+
if (!phase) return { error: 'phase_required', reason: 'Usage: review cross-family <phase> [--host <name>] [--reps N] [--dry-run]' };
|
|
110
|
+
const host = laneHost(cwd, opts.host);
|
|
111
|
+
if (!host) return { enabled: false, reason: 'lane_off', hint: 'set workflow.cross_family_review to a host, or pass --host' };
|
|
112
|
+
const h = HOSTS[host];
|
|
113
|
+
if (!h || !h.review) return { error: 'unknown_host', reason: `no review lane for host "${host}"`, hosts: Object.keys(HOSTS) };
|
|
114
|
+
const built = buildPrompt(cwd, phase);
|
|
115
|
+
if (built.error) return built;
|
|
116
|
+
const nn = String(built.phase).padStart(2, '0');
|
|
117
|
+
const dir = planningPath(cwd, 'reviews', nn);
|
|
118
|
+
let prompt = built.prompt;
|
|
119
|
+
let promptFile = null;
|
|
120
|
+
const launch = launchPlan(h.bin);
|
|
121
|
+
if (h.review.promptVia === 'arg' && (prompt.length > MAX_ARG_PROMPT || launch.promptFile)) {
|
|
122
|
+
promptFile = path.join(dir, `cross-family-${host}-prompt.md`);
|
|
123
|
+
prompt = `Read ${toPosix(path.relative(cwd, promptFile))} and follow it exactly. Do not edit anything.`;
|
|
124
|
+
}
|
|
125
|
+
const argv = h.review.args(h.review.promptVia === 'arg' ? prompt : '');
|
|
126
|
+
const base = {
|
|
127
|
+
enabled: true, lane: `cross-family:${host}`, host, family: h.family, bin: h.bin,
|
|
128
|
+
argv, prompt_via: h.review.promptVia, truncated_diff: built.truncated,
|
|
129
|
+
};
|
|
130
|
+
if (opts.dryRun) return { ...base, prompt: built.prompt, prompt_file: promptFile && toPosix(path.relative(cwd, promptFile)), dry_run: true };
|
|
131
|
+
|
|
132
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
133
|
+
if (promptFile) fs.writeFileSync(promptFile, built.prompt);
|
|
134
|
+
const reps = Math.max(1, Math.min(10, Number(opts.reps) || 1));
|
|
135
|
+
const runs = [];
|
|
136
|
+
const outputs = [];
|
|
137
|
+
const refused = require('./autodev.cjs').standinMissing(h.bin);
|
|
138
|
+
if (refused) return { ...base, error: 'standin_missing', reason: refused };
|
|
139
|
+
for (let i = 0; i < reps; i++) {
|
|
140
|
+
const r = spawnSync(launch.file, launch.shell ? argv.map(quoteForCmd) : argv, {
|
|
141
|
+
cwd, encoding: 'utf8', input: h.review.promptVia === 'stdin' ? built.prompt : undefined,
|
|
142
|
+
shell: launch.shell, timeout: Number(opts.timeoutMs) || 15 * 60 * 1000, maxBuffer: 32 * 1024 * 1024,
|
|
143
|
+
});
|
|
144
|
+
if (r.error) return { ...base, error: 'spawn_failed', reason: r.error.message };
|
|
145
|
+
const v = parseVerdict(r.stdout);
|
|
146
|
+
outputs.push(r.stdout || '');
|
|
147
|
+
runs.push({ rep: i + 1, exit: r.status, verdict: v.verdict ? v.verdict.verdict : null, outcome: v.verdict ? v.verdict.outcome : null, findings: v.verdict ? v.verdict.findings.length : 0, error: v.error });
|
|
148
|
+
}
|
|
149
|
+
const file = path.join(dir, `cross-family-${host}.md`);
|
|
150
|
+
// The last parseable run is the one review-deep merges; every rep is kept for the record.
|
|
151
|
+
const keep = outputs.map((o, i) => [o, runs[i]]).filter(([, r]) => r.outcome).pop();
|
|
152
|
+
fs.writeFileSync(file, `# Cross-family review: phase ${nn} on ${host} (${h.family})\n\n${(keep ? keep[0] : outputs[outputs.length - 1]).trim()}\n`);
|
|
153
|
+
const counts = {};
|
|
154
|
+
for (const r of runs) if (r.verdict) counts[r.verdict] = (counts[r.verdict] || 0) + 1;
|
|
155
|
+
const majority = Object.entries(counts).sort((a, b) => b[1] - a[1])[0];
|
|
156
|
+
return { ...base, reps, runs, verdict: majority ? majority[0] : null, file: toPosix(path.relative(cwd, file)) };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function cmdReviewCrossFamily(cwd, phase, opts, raw) {
|
|
160
|
+
const r = crossFamilyReview(cwd, phase, opts);
|
|
161
|
+
output(r, raw, r.file || r.reason || r.error);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = { crossFamilyReview, cmdReviewCrossFamily, buildPrompt, parseVerdict, launchPlan, quoteForCmd };
|
|
@@ -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 };
|
|
@@ -343,11 +343,13 @@ function scanDocFlags(cwd, opts = {}) {
|
|
|
343
343
|
const docDirs = opts.docDirs || ['docs'];
|
|
344
344
|
const collected = [];
|
|
345
345
|
for (const d of docDirs) {
|
|
346
|
-
const abs = path.
|
|
346
|
+
const abs = path.resolve(cwd, d);
|
|
347
347
|
if (!fs.existsSync(abs)) continue;
|
|
348
348
|
for (const f of walkMarkdownFiles(abs)) {
|
|
349
349
|
if (f.readError) continue;
|
|
350
|
-
|
|
350
|
+
// Relative to the project root whether --doc-dir was given relative or
|
|
351
|
+
// absolute, so the frozen-doc allowlist sees project segments only.
|
|
352
|
+
collected.push({ rel: path.relative(cwd, path.join(abs, f.relativePath)).replace(/\\/g, '/'), content: f.content });
|
|
351
353
|
}
|
|
352
354
|
}
|
|
353
355
|
for (const rel of (opts.files || [])) {
|
|
@@ -379,7 +381,7 @@ function scanDocFlags(cwd, opts = {}) {
|
|
|
379
381
|
|
|
380
382
|
function cmdDocLintFlags(cwd, opts = {}, raw) {
|
|
381
383
|
for (const d of opts.docDirs || []) {
|
|
382
|
-
const abs = path.
|
|
384
|
+
const abs = path.resolve(cwd, d);
|
|
383
385
|
if (fs.existsSync(abs)) requireDirectory(abs);
|
|
384
386
|
}
|
|
385
387
|
const r = scanDocFlags(cwd, opts);
|
|
@@ -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 };
|