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,143 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* CI as an oracle for the autodev loop (program A2).
|
|
4
|
+
*
|
|
5
|
+
* The loop never takes a session's word for its tests. Locally the oracle is the phase
|
|
6
|
+
* gate and the test-run engine (V1, V2); with `autodev run --ci` it is also the pull
|
|
7
|
+
* request's checks, read through GitHub's `gh` CLI (optional: without it the CI oracle
|
|
8
|
+
* is `unavailable`). A check that already fails on the base branch is not this change's
|
|
9
|
+
* doing and is ignored. The failing log tail is what a fix round gets as feedback, and a
|
|
10
|
+
* breaker stops after N rounds.
|
|
11
|
+
*
|
|
12
|
+
* `autodev run --ci local` needs no pull request: the oracle is the project's own gate,
|
|
13
|
+
* run here — `npm run ci:local` when package.json has that script, else `npm test`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const DEFAULT_CI_ROUNDS = 3;
|
|
19
|
+
|
|
20
|
+
function defaultExec(cwd) {
|
|
21
|
+
const refused = require('./autodev.cjs').standinMissing('gh');
|
|
22
|
+
if (refused) return () => ({ status: null, error: new Error(refused), stdout: '', stderr: refused });
|
|
23
|
+
return (args) => spawnSync('gh', args, { cwd, encoding: 'utf8', shell: process.platform === 'win32', timeout: 120000 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseJson(r) {
|
|
27
|
+
if (!r || r.error || r.status !== 0) return null;
|
|
28
|
+
try { return JSON.parse(r.stdout); } catch { return null; }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const BUCKET = { pass: 'pass', fail: 'fail', pending: 'pending', skipping: 'skip', cancel: 'fail' };
|
|
32
|
+
function bucketOf(c) {
|
|
33
|
+
if (c.bucket && BUCKET[c.bucket]) return BUCKET[c.bucket];
|
|
34
|
+
const s = String(c.state || c.conclusion || '').toUpperCase();
|
|
35
|
+
if (['SUCCESS', 'NEUTRAL'].includes(s)) return 'pass';
|
|
36
|
+
if (['SKIPPED'].includes(s)) return 'skip';
|
|
37
|
+
if (['FAILURE', 'ERROR', 'CANCELLED', 'TIMED_OUT', 'ACTION_REQUIRED', 'STARTUP_FAILURE'].includes(s)) return 'fail';
|
|
38
|
+
return 'pending';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Names (check and workflow) whose latest run on the base branch failed. */
|
|
42
|
+
function baseFailing(exec, base) {
|
|
43
|
+
if (!base) return new Set();
|
|
44
|
+
const runs = parseJson(exec(['run', 'list', '--branch', base, '--limit', '30', '--json', 'name,workflowName,conclusion']));
|
|
45
|
+
const latest = new Map();
|
|
46
|
+
for (const r of Array.isArray(runs) ? runs : []) {
|
|
47
|
+
const key = r.workflowName || r.name;
|
|
48
|
+
if (!latest.has(key)) latest.set(key, r);
|
|
49
|
+
}
|
|
50
|
+
const out = new Set();
|
|
51
|
+
for (const [key, r] of latest) if (String(r.conclusion).toLowerCase() === 'failure') { out.add(key); if (r.name) out.add(r.name); }
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function logTail(exec, checks) {
|
|
56
|
+
for (const c of checks) {
|
|
57
|
+
const m = String(c.link || '').match(/\/actions\/runs\/(\d+)/);
|
|
58
|
+
if (!m) continue;
|
|
59
|
+
const r = exec(['run', 'view', m[1], '--log-failed']);
|
|
60
|
+
if (r && r.status === 0 && r.stdout) return r.stdout.trim().split(/\r?\n/).slice(-40).join('\n');
|
|
61
|
+
}
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The CI oracle for the current branch's pull request.
|
|
67
|
+
* @param {string} cwd
|
|
68
|
+
* @param {{exec?: Function, ref?: string, base?: string, head?: string}} opts
|
|
69
|
+
* @returns {{status: 'passed'|'failed'|'pending'|'unavailable'|'stale', failed: string[], ignored: string[], tail: string, reason?: string}}
|
|
70
|
+
*/
|
|
71
|
+
function ciOracle(cwd, opts = {}) {
|
|
72
|
+
const exec = opts.exec || defaultExec(cwd);
|
|
73
|
+
const res = { status: 'unavailable', failed: [], ignored: [], tail: '' };
|
|
74
|
+
const checksRaw = exec(['pr', 'checks', ...(opts.ref ? [opts.ref] : []), '--json', 'name,state,bucket,link,workflow']);
|
|
75
|
+
if (!checksRaw || checksRaw.error || checksRaw.status == null) return { ...res, reason: 'gh is not available' };
|
|
76
|
+
const checks = parseJson(checksRaw);
|
|
77
|
+
if (!Array.isArray(checks)) return { ...res, reason: (checksRaw.stderr || 'no pull request checks').trim().slice(0, 200) };
|
|
78
|
+
if (opts.head) {
|
|
79
|
+
const pr = parseJson(exec(['pr', 'view', ...(opts.ref ? [opts.ref] : []), '--json', 'headRefOid']));
|
|
80
|
+
if (pr && pr.headRefOid && pr.headRefOid !== opts.head) return { ...res, status: 'stale', reason: `the checks are for ${pr.headRefOid.slice(0, 9)}, HEAD is ${opts.head.slice(0, 9)}: push the branch` };
|
|
81
|
+
}
|
|
82
|
+
const onBase = baseFailing(exec, opts.base);
|
|
83
|
+
const failing = checks.filter((c) => bucketOf(c) === 'fail');
|
|
84
|
+
const ignored = failing.filter((c) => onBase.has(c.name) || onBase.has(c.workflow));
|
|
85
|
+
const counted = failing.filter((c) => !ignored.includes(c));
|
|
86
|
+
res.ignored = ignored.map((c) => c.name);
|
|
87
|
+
res.failed = counted.map((c) => c.name);
|
|
88
|
+
if (counted.length) return { ...res, status: 'failed', tail: logTail(exec, counted) };
|
|
89
|
+
if (checks.some((c) => bucketOf(c) === 'pending')) return { ...res, status: 'pending' };
|
|
90
|
+
return { ...res, status: 'passed' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Wait for pending checks, up to `timeoutMs`. */
|
|
94
|
+
function waitForCi(cwd, opts = {}) {
|
|
95
|
+
const deadline = Date.now() + (opts.timeoutMs || 30 * 60 * 1000);
|
|
96
|
+
for (;;) {
|
|
97
|
+
const r = ciOracle(cwd, opts);
|
|
98
|
+
if (r.status !== 'pending' || Date.now() >= deadline) return r;
|
|
99
|
+
spawnSync(process.execPath, ['-e', `setTimeout(() => {}, ${Math.min(opts.pollMs || 30000, Math.max(0, deadline - Date.now()))})`]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The project's own gate: its `ci:local` script, else its `test` script, else none. */
|
|
104
|
+
function localGate(cwd) {
|
|
105
|
+
let scripts = {};
|
|
106
|
+
try { scripts = JSON.parse(require('fs').readFileSync(require('path').join(cwd, 'package.json'), 'utf8')).scripts || {}; } catch { /* no package.json */ }
|
|
107
|
+
if (scripts['ci:local']) return { name: 'ci:local', args: ['run', 'ci:local'] };
|
|
108
|
+
if (scripts.test) return { name: 'test', args: ['test'] };
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The local CI oracle: run the project's gate in `cwd` and judge it by its exit code.
|
|
114
|
+
* With ci:local, the failing gate ids come from .ci-local/report.json.
|
|
115
|
+
* @param {string} cwd
|
|
116
|
+
* @param {{exec?: Function, timeoutMs?: number}} opts
|
|
117
|
+
* @returns {{status: 'passed'|'failed'|'unavailable', failed: string[], ignored: string[], tail: string, reason?: string}}
|
|
118
|
+
*/
|
|
119
|
+
function localOracle(cwd, opts = {}) {
|
|
120
|
+
const res = { status: 'unavailable', failed: [], ignored: [], tail: '' };
|
|
121
|
+
const gate = localGate(cwd);
|
|
122
|
+
if (!gate) return { ...res, reason: 'package.json has neither a ci:local nor a test script' };
|
|
123
|
+
const exec = opts.exec || ((args) => spawnSync('npm', args, { cwd, encoding: 'utf8', shell: process.platform === 'win32', timeout: opts.timeoutMs || 60 * 60 * 1000, maxBuffer: 256 * 1024 * 1024 }));
|
|
124
|
+
const r = exec(gate.args);
|
|
125
|
+
if (!r || r.error || r.status == null) return { ...res, reason: `npm ${gate.args.join(' ')} did not run${r && r.error ? `: ${r.error.message}` : ''}` };
|
|
126
|
+
const tail = `${r.stdout || ''}${r.stderr || ''}`.trim().split(/\r?\n/).slice(-40).join('\n');
|
|
127
|
+
if (r.status === 0) return { ...res, status: 'passed', tail };
|
|
128
|
+
let failed = [gate.name];
|
|
129
|
+
if (gate.name === 'ci:local') {
|
|
130
|
+
try {
|
|
131
|
+
const report = JSON.parse(require('fs').readFileSync(require('path').join(cwd, '.ci-local', 'report.json'), 'utf8'));
|
|
132
|
+
const ids = (report.gates || []).filter((g) => g.status === 'fail').map((g) => g.id);
|
|
133
|
+
if (ids.length) failed = ids;
|
|
134
|
+
} catch { /* the exit code is the verdict; the gate names are a courtesy */ }
|
|
135
|
+
}
|
|
136
|
+
return { ...res, status: 'failed', failed, tail };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function breakerTripped(rounds, max = DEFAULT_CI_ROUNDS) {
|
|
140
|
+
return rounds >= max;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { ciOracle, waitForCi, localOracle, localGate, breakerTripped, baseFailing, bucketOf, DEFAULT_CI_ROUNDS };
|
|
@@ -941,7 +941,7 @@ function cmdScaffold(cwd, type, options, raw) {
|
|
|
941
941
|
}
|
|
942
942
|
case 'uat': {
|
|
943
943
|
filePath = path.join(phaseDir, `${padded}${UAT_SUFFIX}`);
|
|
944
|
-
content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} -- User Acceptance Testing\n\n##
|
|
944
|
+
content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} -- User Acceptance Testing\n\n## Tests\n\n_One \`### N. <name>\` per user-observable truth, with \`expected:\` and \`result: [pending]\` (the walkthrough writes them; \`uat record\` fills the results)_\n\n## Gaps\n\n## Summary\n\n_Pending UAT_\n`;
|
|
945
945
|
break;
|
|
946
946
|
}
|
|
947
947
|
case 'verification': {
|
|
@@ -48,7 +48,9 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
|
|
|
48
48
|
phase_branch_template: 'pan/phase-{phase}-{slug}',
|
|
49
49
|
milestone_branch_template: 'pan/{milestone}-{slug}',
|
|
50
50
|
workflow: {
|
|
51
|
-
|
|
51
|
+
// Research is opt-in (2026-10-25): `--research` on new-project or plan-phase, or
|
|
52
|
+
// this key set true. A config that already says true keeps it.
|
|
53
|
+
research: false,
|
|
52
54
|
plan_check: true,
|
|
53
55
|
verifier: true,
|
|
54
56
|
nyquist_validation: false,
|
|
@@ -59,6 +61,23 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
|
|
|
59
61
|
// V5: the mutation report on a phase's diff. `report` lists surviving mutants and
|
|
60
62
|
// never refuses; `off` skips it; `gate` is reserved for V5G.
|
|
61
63
|
mutation_gate: 'report',
|
|
64
|
+
// V5G: the score `gate` mode needs (killed / sampled, 0..1).
|
|
65
|
+
mutation_floor: 0.6,
|
|
66
|
+
// R2: `single` runs one reviewer pass; `lenses` runs one per lens (review-vote.cjs
|
|
67
|
+
// LENSES) and keeps only the findings a quorum of passes reported. Opt-in until
|
|
68
|
+
// its paired corpus run decides.
|
|
69
|
+
review_mode: 'single',
|
|
70
|
+
// R4: a second review on another host's CLI (another model family), read-only.
|
|
71
|
+
// `off`, or a host: claude | codex | copilot | gemini | opencode.
|
|
72
|
+
cross_family_review: 'off',
|
|
73
|
+
// A3: pan-exec-waves runs each executor in its own git worktree (Claude Code).
|
|
74
|
+
worktree_isolation: false,
|
|
75
|
+
// Right-sized ceremony (sizing.cjs): `auto` sizes each phase and quick task and gives a
|
|
76
|
+
// small, well-specified one the lean path; `full` always runs the whole ceremony. The
|
|
77
|
+
// `quality` profile also means full.
|
|
78
|
+
ceremony: 'auto',
|
|
79
|
+
// The most requirements a phase can map and still be sized lean.
|
|
80
|
+
lean_max_requirements: 5,
|
|
62
81
|
// M2: opt-in per-phase HTML reports at the verify→complete gate.
|
|
63
82
|
phase_reports: { enabled: false, open: false, theme: 'auto', index: true },
|
|
64
83
|
},
|
|
@@ -80,6 +99,10 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
|
|
|
80
99
|
// Fraction of the spawn budget held back for re-verification (0..0.5).
|
|
81
100
|
// Surfaced as an indicator always; a hard early stop only under enforce.
|
|
82
101
|
verify_reserve: 0.15,
|
|
102
|
+
// A6: autodev's dollar caps, per run and per day (UTC), across every run's ledger.
|
|
103
|
+
// null: none set. An unattended run needs a run cap, here or as --max-usd.
|
|
104
|
+
max_usd_run: null,
|
|
105
|
+
max_usd_day: null,
|
|
83
106
|
},
|
|
84
107
|
commit: {
|
|
85
108
|
safety_checks: true,
|
|
@@ -156,8 +179,13 @@ function knownConfigKeys() {
|
|
|
156
179
|
* @param {string} keyPath
|
|
157
180
|
* @returns {boolean}
|
|
158
181
|
*/
|
|
182
|
+
// A lowercase `x-` key is the user's own metadata, never a typo (market-ideas M51,
|
|
183
|
+
// after OpenSpec v1.14.2): PAN ignores it at the top level and inside a section.
|
|
184
|
+
const USER_KEY = /^x-/;
|
|
185
|
+
|
|
159
186
|
function isKnownConfigPath(keyPath) {
|
|
160
187
|
const [head, sub] = String(keyPath).split('.');
|
|
188
|
+
if (USER_KEY.test(head) || (sub !== undefined && USER_KEY.test(sub))) return true;
|
|
161
189
|
const { top, sections } = knownConfigKeys();
|
|
162
190
|
if (!top.has(head)) return false;
|
|
163
191
|
return sub === undefined || !sections.has(head) || sections.get(head).has(sub);
|
|
@@ -174,10 +202,11 @@ function unknownConfigKeys(config) {
|
|
|
174
202
|
const { top, sections } = knownConfigKeys();
|
|
175
203
|
const unknown = [];
|
|
176
204
|
for (const [k, v] of Object.entries(config)) {
|
|
205
|
+
if (USER_KEY.test(k)) continue;
|
|
177
206
|
if (!top.has(k)) { unknown.push(k); continue; }
|
|
178
207
|
const known = sections.get(k);
|
|
179
208
|
if (!known || !v || typeof v !== 'object' || Array.isArray(v)) continue;
|
|
180
|
-
for (const sub of Object.keys(v)) if (!known.has(sub)) unknown.push(`${k}.${sub}`);
|
|
209
|
+
for (const sub of Object.keys(v)) if (!known.has(sub) && !USER_KEY.test(sub)) unknown.push(`${k}.${sub}`);
|
|
181
210
|
}
|
|
182
211
|
return unknown;
|
|
183
212
|
}
|
|
@@ -165,6 +165,29 @@ const CATEGORY_DEFAULTS = {
|
|
|
165
165
|
/** Doc files to scan for staleness (focus sync) */
|
|
166
166
|
const DOC_SYNC_FILES = ['README.md', 'docs/DEVELOPMENT.md', 'docs/CLI-REFERENCE.md', 'docs/USER-GUIDE.md', 'docs/ARCHITECTURE.md'];
|
|
167
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Commands folded into another (program L7). The installer writes an alias command for
|
|
170
|
+
* each old name on every runtime, so `/pan:<old>` keeps working: it runs `to` with
|
|
171
|
+
* `args` in front of whatever the user passed.
|
|
172
|
+
*/
|
|
173
|
+
const COMMAND_ALIASES = {
|
|
174
|
+
dashboard: { to: 'hud', args: '' },
|
|
175
|
+
'milestone-cleanup': { to: 'milestone-done', args: '--cleanup' },
|
|
176
|
+
'milestone-gaps': { to: 'milestone-audit', args: '--gaps' },
|
|
177
|
+
optimize: { to: 'learn', args: '--optimize' },
|
|
178
|
+
'research-phase': { to: 'plan-phase', args: '--research-only' },
|
|
179
|
+
profile: { to: 'settings', args: '--profile' },
|
|
180
|
+
'focus-doc-audit': { to: 'focus-docs', args: '' },
|
|
181
|
+
'focus-sync': { to: 'focus-docs', args: '--sync' },
|
|
182
|
+
'focus-scan': { to: 'focus-auto', args: '--step scan' },
|
|
183
|
+
'focus-plan': { to: 'focus-auto', args: '--step plan' },
|
|
184
|
+
'focus-exec': { to: 'focus-auto', args: '--step exec' },
|
|
185
|
+
'focus-drift-walking': { to: 'focus-auto', args: '--step drift' },
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/** Commands removed with no replacement (program L7). */
|
|
189
|
+
const RETIRED_COMMANDS = ['discord', 'mcp-bridge'];
|
|
190
|
+
|
|
168
191
|
/** Old→new command name mapping for detecting stale docs */
|
|
169
192
|
const COMMAND_RENAME_MAP = {
|
|
170
193
|
'execute-phase': 'exec-phase',
|
|
@@ -178,7 +201,8 @@ const COMMAND_RENAME_MAP = {
|
|
|
178
201
|
'new-milestone': 'milestone-new',
|
|
179
202
|
'complete-milestone': 'milestone-done',
|
|
180
203
|
'audit-milestone': 'milestone-audit',
|
|
181
|
-
'plan-milestone-gaps': 'milestone-gaps',
|
|
204
|
+
'plan-milestone-gaps': 'milestone-audit --gaps',
|
|
205
|
+
...Object.fromEntries(Object.entries(COMMAND_ALIASES).map(([old, a]) => [old, a.args ? `${a.to} ${a.args}` : a.to])),
|
|
182
206
|
};
|
|
183
207
|
|
|
184
208
|
/** Default max cycles for auto-runner */
|
|
@@ -818,6 +842,8 @@ module.exports = {
|
|
|
818
842
|
FOCUS_SOURCES,
|
|
819
843
|
DOC_SYNC_FILES,
|
|
820
844
|
COMMAND_RENAME_MAP,
|
|
845
|
+
COMMAND_ALIASES,
|
|
846
|
+
RETIRED_COMMANDS,
|
|
821
847
|
CATEGORY_PRIORITY_RANGE,
|
|
822
848
|
CATEGORY_DEFAULTS,
|
|
823
849
|
DEFAULT_MAX_CYCLES,
|
|
@@ -132,13 +132,14 @@ const MODEL_PROFILES = {
|
|
|
132
132
|
'pan-designer': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
|
|
133
133
|
'pan-roadmapper': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
|
|
134
134
|
'pan-executor': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
'pan-
|
|
135
|
+
// Right-sized ceremony (2026-10-24): balanced runs the researchers and the plan-checker
|
|
136
|
+
// on the mid tier, about half the launch model's price; quality keeps them on reasoning.
|
|
137
|
+
'pan-phase-researcher': { quality: 'reasoning', balanced: 'mid', budget: 'fast' },
|
|
138
|
+
'pan-project-researcher': { quality: 'reasoning', balanced: 'mid', budget: 'fast' },
|
|
138
139
|
'pan-debugger': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
|
|
139
140
|
'pan-document_code': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
|
|
140
141
|
'pan-verifier': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
|
|
141
|
-
'pan-plan-checker': { quality: 'reasoning', balanced: '
|
|
142
|
+
'pan-plan-checker': { quality: 'reasoning', balanced: 'mid', budget: 'fast' },
|
|
142
143
|
'pan-design-checker': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
|
|
143
144
|
'pan-integration-checker': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
|
|
144
145
|
'pan-reviewer': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
|
|
@@ -193,7 +194,6 @@ const AGENT_BASE_EFFORT = {
|
|
|
193
194
|
// Research/synthesis/review — moderate depth
|
|
194
195
|
'pan-phase-researcher': 'medium',
|
|
195
196
|
'pan-project-researcher': 'medium',
|
|
196
|
-
'pan-research-synthesizer': 'medium',
|
|
197
197
|
'pan-knowledge': 'medium',
|
|
198
198
|
'pan-distiller': 'medium',
|
|
199
199
|
'pan-meta-reviewer': 'medium',
|
|
@@ -324,7 +324,7 @@ function reportsFailure(result) {
|
|
|
324
324
|
*/
|
|
325
325
|
function output(result, raw, rawValue, exitCode) {
|
|
326
326
|
if (raw && rawValue !== undefined) {
|
|
327
|
-
|
|
327
|
+
writeStdoutSync(String(rawValue));
|
|
328
328
|
} else {
|
|
329
329
|
const json = JSON.stringify(result, null, 2);
|
|
330
330
|
// Large payloads exceed Claude Code's Bash tool buffer (~50KB).
|
|
@@ -337,19 +337,31 @@ function output(result, raw, rawValue, exitCode) {
|
|
|
337
337
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pan-'));
|
|
338
338
|
const tmpPath = path.join(tmpDir, 'out.json');
|
|
339
339
|
fs.writeFileSync(tmpPath, json, { encoding: 'utf-8', flag: 'wx' });
|
|
340
|
-
|
|
340
|
+
writeStdoutSync('@file:' + tmpPath);
|
|
341
341
|
} catch {
|
|
342
342
|
// Tmpfile write failed (disk full, permissions) — truncate and write to stdout
|
|
343
343
|
const truncated = json.slice(0, MAX_JSON_SIZE);
|
|
344
|
-
|
|
344
|
+
writeStdoutSync(truncated);
|
|
345
345
|
}
|
|
346
346
|
} else {
|
|
347
|
-
|
|
347
|
+
writeStdoutSync(json);
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
process.exit(exitCode === undefined ? (reportsFailure(result) ? 1 : 0) : exitCode);
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
/**
|
|
354
|
+
* Write all of `text` to stdout before returning. output() exits right after it writes,
|
|
355
|
+
* and on macOS (and Linux) stdout to a pipe is asynchronous: process.exit() dropped
|
|
356
|
+
* whatever had not drained past the pipe buffer, so a reader got JSON cut at about 8 KB.
|
|
357
|
+
* Making the stdout handle blocking first turns the write synchronous on a pipe too.
|
|
358
|
+
*/
|
|
359
|
+
function writeStdoutSync(text) {
|
|
360
|
+
const handle = process.stdout._handle;
|
|
361
|
+
if (handle && typeof handle.setBlocking === 'function') handle.setBlocking(true);
|
|
362
|
+
process.stdout.write(String(text));
|
|
363
|
+
}
|
|
364
|
+
|
|
353
365
|
/**
|
|
354
366
|
* Write error message to stderr and exit with code 1.
|
|
355
367
|
* @param {string} message - Error message
|
|
@@ -434,7 +446,7 @@ function loadConfig(cwd) {
|
|
|
434
446
|
branching_strategy: 'none',
|
|
435
447
|
phase_branch_template: 'pan/phase-{phase}-{slug}',
|
|
436
448
|
milestone_branch_template: 'pan/{milestone}-{slug}',
|
|
437
|
-
research: true
|
|
449
|
+
research: false, // opt-in: a config that says true keeps it
|
|
438
450
|
plan_checker: true,
|
|
439
451
|
verifier: true,
|
|
440
452
|
nyquist_validation: false,
|
|
@@ -149,7 +149,8 @@ const DEFAULT_RATES = {
|
|
|
149
149
|
|
|
150
150
|
// Google Gemini — ai.google.dev/gemini-api/docs/pricing, paid tier, text input,
|
|
151
151
|
// the ≤200K-token prompt tier where the page splits by length, read 2026-09-23.
|
|
152
|
-
// cache_read is the page's "context caching" price: 0.1× input on every row
|
|
152
|
+
// cache_read is the page's "context caching" price: 0.1× input on every row but
|
|
153
|
+
// Gemini 4 Argon (0.05×), not
|
|
153
154
|
// the 0.25× this block carried until 2026-09-22 (cache reads 2.5× high, R26). The
|
|
154
155
|
// tokens that CREATE a cache bill at the standard input price (Google Cloud's
|
|
155
156
|
// context-cache overview; the Gemini API pages list only cached-token and storage
|
|
@@ -157,7 +158,12 @@ const DEFAULT_RATES = {
|
|
|
157
158
|
// HOUR, which a per-token table cannot carry: PAN's Gemini totals exclude it.
|
|
158
159
|
// The 3.6/3.7/3.8 Flash prices run "through December 31, 2026" and double from
|
|
159
160
|
// January 1, 2027 (the page states both) — re-read them after that date.
|
|
160
|
-
|
|
161
|
+
// Gemini 4 Argon (paid tier only; one price at every prompt length; implicit caching
|
|
162
|
+
// only): these rates run "through November 30, 2026" and double from December 1,
|
|
163
|
+
// 2026 (input $4.00, output $20.00, caching $0.20; read 2026-10-19) — change the row
|
|
164
|
+
// on that date.
|
|
165
|
+
'gemini-4-argon': { input: 2.00, output: 10.0, cache_read: 0.10, cache_write: 2.00 },
|
|
166
|
+
'gemini-3.8-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
|
|
161
167
|
'gemini-3.7-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
|
|
162
168
|
'gemini-3.6-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
|
|
163
169
|
'gemini-3.5-flash': { input: 1.50, output: 9.00, cache_read: 0.15, cache_write: 1.50 },
|
|
@@ -899,8 +905,12 @@ function cmdCostAppend(cwd, rec, raw) {
|
|
|
899
905
|
|
|
900
906
|
function cmdCostClear(cwd, raw) {
|
|
901
907
|
try {
|
|
902
|
-
|
|
903
|
-
|
|
908
|
+
// L6a: the ledger is the only record of what was spent; keep a copy before deleting it.
|
|
909
|
+
const file = tokensFile(cwd);
|
|
910
|
+
const backup = `${file}.bak-${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}`;
|
|
911
|
+
fs.copyFileSync(file, backup);
|
|
912
|
+
fs.unlinkSync(file);
|
|
913
|
+
output({ cleared: true, file, backup }, raw);
|
|
904
914
|
} catch (e) {
|
|
905
915
|
output({ cleared: false, error: e.message || 'ledger_clear_failed' }, raw);
|
|
906
916
|
}
|
|
@@ -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 };
|