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
|
@@ -13,6 +13,18 @@
|
|
|
13
13
|
* its type is `stale`, not an error: what a version or a price was two months ago
|
|
14
14
|
* says little about today, while a practice lasts for years. The plan-checker weighs
|
|
15
15
|
* stale claims against what the plans rest on.
|
|
16
|
+
*
|
|
17
|
+
* Research answers questions (2026-10-25: research that surveyed a domain never
|
|
18
|
+
* finished and grew the scope). The orchestrator writes at most five open questions
|
|
19
|
+
* that block building; one researcher answers them in a table
|
|
20
|
+
*
|
|
21
|
+
* | # | Question | Answer | Source | Confidence | Decision it settles |
|
|
22
|
+
*
|
|
23
|
+
* `research check <questions> <research> [--market]` holds it to that: at most five
|
|
24
|
+
* questions, every question a row with an answer and a source, or an answer marked
|
|
25
|
+
* `unknown` that names the assumption the plan uses; in market mode, at most five
|
|
26
|
+
* comparable products. Without a `## Claims` table, `research lint` reads the
|
|
27
|
+
* answered rows of that table as claims, with the date in the source cell.
|
|
16
28
|
*/
|
|
17
29
|
const fs = require('fs');
|
|
18
30
|
const path = require('path');
|
|
@@ -75,10 +87,22 @@ function lintResearch(cwd, target, opts = {}) {
|
|
|
75
87
|
const text = toLf(fs.readFileSync(at.abs, 'utf-8'));
|
|
76
88
|
const base = { file, ok: true, claims: 0, errors: [], warnings: [], stale: [], windows: FRESHNESS_DAYS };
|
|
77
89
|
const body = sectionBody(text, 'Claims');
|
|
90
|
+
let header;
|
|
91
|
+
let rows;
|
|
78
92
|
if (body == null) {
|
|
79
|
-
|
|
93
|
+
// A question-driven research file: its answered rows are the claims.
|
|
94
|
+
const answers = answerTable(text);
|
|
95
|
+
if (!answers) {
|
|
96
|
+
return { ...base, ok: false, errors: [{ code: 'claims_missing', detail: 'no `## Claims` table (claim · type · source · checked) and no answers table (# · question · answer · source)' }] };
|
|
97
|
+
}
|
|
98
|
+
header = ['claim', 'type', 'source', 'checked'];
|
|
99
|
+
rows = answers.rows.filter((r) => !isUnknown(r.answer)).map((r) => {
|
|
100
|
+
const date = (r.source.match(/\d{4}-\d{2}-\d{2}/) || [''])[0];
|
|
101
|
+
return [r.answer, 'general', r.source, date];
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
({ header, rows } = claimRows(body));
|
|
80
105
|
}
|
|
81
|
-
const { header, rows } = claimRows(body);
|
|
82
106
|
const col = (name, fallback) => {
|
|
83
107
|
const i = header ? header.findIndex((h) => h.startsWith(name)) : -1;
|
|
84
108
|
return i === -1 ? fallback : i;
|
|
@@ -111,6 +135,129 @@ function lintResearch(cwd, target, opts = {}) {
|
|
|
111
135
|
return { ...base, ok: errors.length === 0, claims: rows.length, errors, warnings, stale };
|
|
112
136
|
}
|
|
113
137
|
|
|
138
|
+
const MAX_QUESTIONS = 5;
|
|
139
|
+
const MAX_PRODUCTS = 5;
|
|
140
|
+
|
|
141
|
+
/** Every markdown table in `text` as arrays of cells (rule rows dropped); a non-table line ends a table. */
|
|
142
|
+
function tables(text) {
|
|
143
|
+
const out = [];
|
|
144
|
+
let cur = null;
|
|
145
|
+
for (const line of String(text).split('\n')) {
|
|
146
|
+
const t = line.trim();
|
|
147
|
+
if (!t.startsWith('|')) { cur = null; continue; }
|
|
148
|
+
const cells = t.replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
|
|
149
|
+
if (cells.every((c) => /^:?-+:?$/.test(c) || c === '')) continue;
|
|
150
|
+
if (!cur) { cur = []; out.push(cur); }
|
|
151
|
+
cur.push(cells);
|
|
152
|
+
}
|
|
153
|
+
return out;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The answers table (`# · Question · Answer · Source …`), rows as objects, or null. */
|
|
157
|
+
function answerTable(text) {
|
|
158
|
+
for (const t of tables(text)) {
|
|
159
|
+
const h = t[0].map((c) => c.toLowerCase());
|
|
160
|
+
const qi = h.findIndex((c) => c.startsWith('question'));
|
|
161
|
+
const ai = h.findIndex((c) => c.startsWith('answer'));
|
|
162
|
+
const si = h.findIndex((c) => c.startsWith('source'));
|
|
163
|
+
if (qi === -1 || ai === -1 || si === -1) continue;
|
|
164
|
+
const ni = h.findIndex((c) => c === '#' || c === 'no' || c === 'n');
|
|
165
|
+
const ci = h.findIndex((c) => c.startsWith('confidence'));
|
|
166
|
+
const di = h.findIndex((c) => c.startsWith('decision'));
|
|
167
|
+
return {
|
|
168
|
+
rows: t.slice(1).map((r, i) => ({
|
|
169
|
+
n: ni === -1 ? i + 1 : parseInt(String(r[ni] || '').replace(/\D/g, ''), 10),
|
|
170
|
+
question: r[qi] || '', answer: r[ai] || '', source: r[si] || '',
|
|
171
|
+
confidence: ci === -1 ? '' : (r[ci] || ''), decision: di === -1 ? '' : (r[di] || ''),
|
|
172
|
+
})),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Rows of the comparable-products table (first header cell `Product`), or null. */
|
|
179
|
+
function productTable(text) {
|
|
180
|
+
const t = tables(text).find((tb) => /^products?$/i.test(tb[0][0] || ''));
|
|
181
|
+
return t ? t.slice(1) : null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const isUnknown = (answer) => /^\W*unknown\b/i.test(answer);
|
|
185
|
+
const hasAssumption = (cell) => /assum\w*\s*[:\u2014\u2013-]?\s*\S/i.test(cell);
|
|
186
|
+
|
|
187
|
+
/** Numbered questions in a questions file: lines `1. …` or `1) …`, or `| 1 | … |` table rows. */
|
|
188
|
+
function readQuestions(text) {
|
|
189
|
+
const qs = [];
|
|
190
|
+
for (const line of String(text).split('\n')) {
|
|
191
|
+
const m = line.match(/^\s*(\d+)[.)]\s+(\S.*)$/) || line.match(/^\s*\|\s*(\d+)\s*\|\s*([^|]*\S[^|]*)\|/);
|
|
192
|
+
if (m) qs.push({ n: parseInt(m[1], 10), text: m[2].trim() });
|
|
193
|
+
}
|
|
194
|
+
return qs;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @param {string} cwd
|
|
199
|
+
* @param {string} questionsPath the questions file the orchestrator wrote
|
|
200
|
+
* @param {string} researchPath the researcher's answers
|
|
201
|
+
* @param {{market?: boolean}} [opts] market mode also requires the products table
|
|
202
|
+
*/
|
|
203
|
+
function checkResearch(cwd, questionsPath, researchPath, opts = {}) {
|
|
204
|
+
const qAbs = path.resolve(cwd, String(questionsPath));
|
|
205
|
+
const rAbs = path.resolve(cwd, String(researchPath));
|
|
206
|
+
if (!fs.existsSync(qAbs)) return { error: 'file_not_found', reason: `no questions file at ${questionsPath}` };
|
|
207
|
+
if (!fs.existsSync(rAbs)) return { error: 'file_not_found', reason: `no research file at ${researchPath}` };
|
|
208
|
+
const questions = readQuestions(toLf(fs.readFileSync(qAbs, 'utf-8')));
|
|
209
|
+
const text = toLf(fs.readFileSync(rAbs, 'utf-8'));
|
|
210
|
+
const errors = [];
|
|
211
|
+
const warnings = [];
|
|
212
|
+
if (questions.length === 0) errors.push({ code: 'no_questions', detail: 'the questions file lists no numbered question: with nothing blocking the build, no research runs' });
|
|
213
|
+
if (questions.length > MAX_QUESTIONS) errors.push({ code: 'too_many_questions', detail: `${questions.length} questions; at most ${MAX_QUESTIONS}, the ones that block building` });
|
|
214
|
+
const table = answerTable(text);
|
|
215
|
+
let answered = 0;
|
|
216
|
+
let unknown = 0;
|
|
217
|
+
if (!table) {
|
|
218
|
+
errors.push({ code: 'answers_missing', detail: 'no answers table (| # | Question | Answer | Source | Confidence | Decision it settles |)' });
|
|
219
|
+
} else {
|
|
220
|
+
for (const q of questions) {
|
|
221
|
+
const row = table.rows.find((r) => r.n === q.n);
|
|
222
|
+
if (!row || !row.answer || (PLACEHOLDER_RE.test(row.answer) && !isUnknown(row.answer))) {
|
|
223
|
+
errors.push({ code: 'question_unanswered', question: q.n, detail: `question ${q.n} has no answer row` });
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (isUnknown(row.answer)) {
|
|
227
|
+
if (hasAssumption(`${row.answer} ${row.decision}`)) unknown++;
|
|
228
|
+
else errors.push({ code: 'unknown_without_assumption', question: q.n, detail: `question ${q.n} is unknown but names no assumption the plan should use` });
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
if (!row.source || PLACEHOLDER_RE.test(row.source)) {
|
|
232
|
+
errors.push({ code: 'answer_no_source', question: q.n, detail: `question ${q.n} has an answer with no source (URL or doc, and date)` });
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (!/^(high|medium|low)\b/i.test(row.confidence)) warnings.push({ code: 'answer_no_confidence', question: q.n, detail: `question ${q.n} gives no HIGH, MEDIUM or LOW confidence` });
|
|
236
|
+
answered++;
|
|
237
|
+
}
|
|
238
|
+
const known = new Set(questions.map((q) => q.n));
|
|
239
|
+
for (const r of table.rows) {
|
|
240
|
+
if (!known.has(r.n)) warnings.push({ code: 'extra_row', row: r.n, detail: `row ${r.n} answers no listed question: new scope goes under Ideas, not into the answers` });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const products = productTable(text);
|
|
244
|
+
if (products && products.length > MAX_PRODUCTS) errors.push({ code: 'too_many_products', detail: `${products.length} comparable products; at most ${MAX_PRODUCTS}` });
|
|
245
|
+
if (opts.market && !products) errors.push({ code: 'products_missing', detail: 'market mode: no table whose first column is Product' });
|
|
246
|
+
const rel = (abs) => { const r = path.relative(path.resolve(cwd), abs); return r && !r.startsWith('..') ? toPosix(r) : toPosix(abs); };
|
|
247
|
+
return {
|
|
248
|
+
questions_file: rel(qAbs), research_file: rel(rAbs), ok: errors.length === 0,
|
|
249
|
+
questions: questions.length, answered, unknown, products: products ? products.length : 0,
|
|
250
|
+
limits: { questions: MAX_QUESTIONS, products: MAX_PRODUCTS }, errors, warnings,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function cmdResearchCheck(cwd, questionsPath, researchPath, opts, raw) {
|
|
255
|
+
if (!questionsPath || !researchPath) error('Usage: research check <questions file> <research file> [--market]');
|
|
256
|
+
const r = checkResearch(cwd, questionsPath, researchPath, opts);
|
|
257
|
+
if (r.error) { output(r, raw); return; }
|
|
258
|
+
output(r, raw, r.ok ? 'valid' : 'invalid', r.ok ? 0 : 1);
|
|
259
|
+
}
|
|
260
|
+
|
|
114
261
|
function cmdResearchLint(cwd, target, opts, raw) {
|
|
115
262
|
if (!target) error('Usage: research lint <phase|path> [--now YYYY-MM-DD]');
|
|
116
263
|
const r = lintResearch(cwd, target, opts);
|
|
@@ -118,4 +265,4 @@ function cmdResearchLint(cwd, target, opts, raw) {
|
|
|
118
265
|
output(r, raw, r.ok ? 'valid' : 'invalid', r.ok ? 0 : 1);
|
|
119
266
|
}
|
|
120
267
|
|
|
121
|
-
module.exports = { FRESHNESS_DAYS, lintResearch, cmdResearchLint };
|
|
268
|
+
module.exports = { FRESHNESS_DAYS, MAX_QUESTIONS, MAX_PRODUCTS, lintResearch, cmdResearchLint, checkResearch, cmdResearchCheck };
|
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
* - writeDeepReview(cwd, phaseNum, payload) — serialize the merged output
|
|
15
15
|
* to .planning/reviews/<N>/deep-review.md
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* It used to publish each merge to a `review-handoff` bus channel that nothing read;
|
|
18
|
+
* deep-review.md is the record (program L7).
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
const fs = require('fs');
|
|
21
22
|
const path = require('path');
|
|
22
23
|
const { output, error, safeReadFile, toPosix, escapeTableCell } = require('./core.cjs');
|
|
23
24
|
const { planningPath } = require('./utils.cjs');
|
|
24
|
-
const { publish } = require('./bus.cjs');
|
|
25
25
|
const { findVerdictBlocks, validateVerdict } = require('./verdict.cjs');
|
|
26
26
|
|
|
27
27
|
const REVIEWS_DIR = 'reviews';
|
|
@@ -126,14 +126,16 @@ function findingsFromVerdictBlock(content, source) {
|
|
|
126
126
|
* @param {Array|string} reviewer - reviewer findings array OR markdown content
|
|
127
127
|
* @param {Array|string} hardener - hardener findings array OR markdown content
|
|
128
128
|
* @param {Array|string} [meta] - optional meta-reviewer findings
|
|
129
|
+
* @param {Array|string} [cross] - optional cross-family lane findings (R4)
|
|
129
130
|
* @returns {Object} Merged payload
|
|
130
131
|
*/
|
|
131
|
-
function mergeReviews(reviewer, hardener, meta) {
|
|
132
|
+
function mergeReviews(reviewer, hardener, meta, cross) {
|
|
132
133
|
const r = Array.isArray(reviewer) ? reviewer : parseReviewFindings(reviewer || '', 'reviewer');
|
|
133
134
|
const h = Array.isArray(hardener) ? hardener : parseReviewFindings(hardener || '', 'hardener');
|
|
134
135
|
const m = Array.isArray(meta) ? meta : parseReviewFindings(meta || '', 'meta-reviewer');
|
|
136
|
+
const x = Array.isArray(cross) ? cross : parseReviewFindings(cross || '', 'cross-family');
|
|
135
137
|
|
|
136
|
-
const findings = [...r, ...h, ...m].sort((a, b) => {
|
|
138
|
+
const findings = [...r, ...h, ...m, ...x].sort((a, b) => {
|
|
137
139
|
const wa = SEVERITY_WEIGHT[a.severity] ?? 0;
|
|
138
140
|
const wb = SEVERITY_WEIGHT[b.severity] ?? 0;
|
|
139
141
|
if (wa !== wb) return wb - wa;
|
|
@@ -174,6 +176,7 @@ function mergeReviews(reviewer, hardener, meta) {
|
|
|
174
176
|
reviewer: r.length,
|
|
175
177
|
hardener: h.length,
|
|
176
178
|
meta_reviewer: m.length,
|
|
179
|
+
cross_family: x.length,
|
|
177
180
|
},
|
|
178
181
|
by_severity: SEVERITIES.reduce((acc, s) => { acc[s] = findings.filter(f => f.severity === s).length; return acc; }, {}),
|
|
179
182
|
};
|
|
@@ -263,19 +266,6 @@ function writeDeepReview(cwd, phaseNum, payload, opts) {
|
|
|
263
266
|
return { error: `Failed to write ${file}: ${e.message}` };
|
|
264
267
|
}
|
|
265
268
|
|
|
266
|
-
// Audit trail on the review-handoff bus channel (best-effort).
|
|
267
|
-
if (opts?.audit_channel !== false) {
|
|
268
|
-
try {
|
|
269
|
-
publish(cwd, 'review-handoff', {
|
|
270
|
-
phase: phaseNum,
|
|
271
|
-
verdict: payload.verdict,
|
|
272
|
-
finding_count: payload.coverage.total,
|
|
273
|
-
conflict_count: payload.conflicts.length,
|
|
274
|
-
file: toPosix(path.relative(cwd, file)),
|
|
275
|
-
}, { source: 'pan-meta-reviewer' });
|
|
276
|
-
} catch { /* non-blocking */ }
|
|
277
|
-
}
|
|
278
|
-
|
|
279
269
|
return { written: true, file: toPosix(path.relative(cwd, file)) };
|
|
280
270
|
}
|
|
281
271
|
|
|
@@ -290,15 +280,16 @@ function resolveAgainstCwd(cwd, p) {
|
|
|
290
280
|
}
|
|
291
281
|
|
|
292
282
|
function cmdReviewDeepMerge(cwd, phaseNum, opts, raw) {
|
|
293
|
-
if (!phaseNum) error('Usage: review-deep merge <phase> --reviewer-file X --hardener-file Y [--meta-file Z]');
|
|
283
|
+
if (!phaseNum) error('Usage: review-deep merge <phase> --reviewer-file X --hardener-file Y [--meta-file Z] [--cross-file W]');
|
|
294
284
|
const reviewerContent = opts.reviewerFile ? safeReadFile(resolveAgainstCwd(cwd, opts.reviewerFile)) : '';
|
|
295
285
|
const hardenerContent = opts.hardenerFile ? safeReadFile(resolveAgainstCwd(cwd, opts.hardenerFile)) : '';
|
|
296
286
|
const metaContent = opts.metaFile ? safeReadFile(resolveAgainstCwd(cwd, opts.metaFile)) : '';
|
|
297
|
-
|
|
287
|
+
const crossContent = opts.crossFile ? safeReadFile(resolveAgainstCwd(cwd, opts.crossFile)) : '';
|
|
288
|
+
if (!reviewerContent && !hardenerContent && !metaContent && !crossContent) {
|
|
298
289
|
output({ error: 'No input files provided or readable' }, raw);
|
|
299
290
|
return;
|
|
300
291
|
}
|
|
301
|
-
const payload = mergeReviews(reviewerContent, hardenerContent, metaContent);
|
|
292
|
+
const payload = mergeReviews(reviewerContent, hardenerContent, metaContent, crossContent);
|
|
302
293
|
const result = writeDeepReview(cwd, phaseNum, payload);
|
|
303
294
|
if (result.error) { output(result, raw); return; }
|
|
304
295
|
output({ ...result, verdict: payload.verdict, coverage: payload.coverage, conflicts: payload.conflicts.length }, raw);
|
|
@@ -306,11 +297,12 @@ function cmdReviewDeepMerge(cwd, phaseNum, opts, raw) {
|
|
|
306
297
|
|
|
307
298
|
function cmdReviewDeepAnalyze(cwd, phaseNum, opts, raw) {
|
|
308
299
|
// Returns the merged payload WITHOUT writing a file. Useful for piping.
|
|
309
|
-
if (!phaseNum) error('Usage: review-deep analyze <phase> --reviewer-file X --hardener-file Y [--meta-file Z]');
|
|
300
|
+
if (!phaseNum) error('Usage: review-deep analyze <phase> --reviewer-file X --hardener-file Y [--meta-file Z] [--cross-file W]');
|
|
310
301
|
const reviewerContent = opts.reviewerFile ? safeReadFile(resolveAgainstCwd(cwd, opts.reviewerFile)) : '';
|
|
311
302
|
const hardenerContent = opts.hardenerFile ? safeReadFile(resolveAgainstCwd(cwd, opts.hardenerFile)) : '';
|
|
312
303
|
const metaContent = opts.metaFile ? safeReadFile(resolveAgainstCwd(cwd, opts.metaFile)) : '';
|
|
313
|
-
|
|
304
|
+
const crossContent = opts.crossFile ? safeReadFile(resolveAgainstCwd(cwd, opts.crossFile)) : '';
|
|
305
|
+
output(mergeReviews(reviewerContent, hardenerContent, metaContent, crossContent), raw);
|
|
314
306
|
}
|
|
315
307
|
|
|
316
308
|
module.exports = {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Review lenses and k-pass voting (program R2).
|
|
4
|
+
*
|
|
5
|
+
* With `workflow.review_mode: lenses`, exec-phase runs the reviewer once per lens
|
|
6
|
+
* (LENSES) instead of once, and `review vote` merges the passes in code. A finding
|
|
7
|
+
* survives only when a quorum of passes reported it (the validator: a finding one
|
|
8
|
+
* pass alone raised is a likely false alarm), at the highest severity any pass gave
|
|
9
|
+
* it. The verdict is recomputed from what survived, never taken from one pass. The
|
|
10
|
+
* merged report carries a normal `pan-verdict` block, so `findings record` reads it
|
|
11
|
+
* like any single review.
|
|
12
|
+
*
|
|
13
|
+
* Opt-in until its evidence gate decides: a paired corpus run must show lenses
|
|
14
|
+
* catching more without raising false alarms (docs/specs/product-excellence-superplan.md).
|
|
15
|
+
*/
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { output, error } = require('./core.cjs');
|
|
19
|
+
const V = require('./verdict.cjs');
|
|
20
|
+
const { sameFinding } = require('./findings.cjs');
|
|
21
|
+
|
|
22
|
+
const LENSES = Object.freeze(['correctness', 'security', 'maintainability']);
|
|
23
|
+
const SEVERITY_RANK = Object.fromEntries(V.SEVERITIES.map((s, i) => [s, V.SEVERITIES.length - i]));
|
|
24
|
+
const FENCE = '```';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Merge reviewer passes.
|
|
28
|
+
* @param {string[]} texts - each pass's full report (with its pan-verdict block)
|
|
29
|
+
* @param {{phase?: string, quorum?: number}} [opts]
|
|
30
|
+
* @returns {{ok:true, passes, quorum, verdict, dropped, text}|{ok:false, error, reason}}
|
|
31
|
+
*/
|
|
32
|
+
function voteVerdicts(texts, opts = {}) {
|
|
33
|
+
const parsed = [];
|
|
34
|
+
for (let i = 0; i < texts.length; i++) {
|
|
35
|
+
const p = V.parseVerdictText(String(texts[i] || ''), { agent: 'pan-reviewer', phase: opts.phase });
|
|
36
|
+
if (!p.ok) return { ok: false, error: 'unreadable_pass', reason: `pass ${i + 1} has no readable pan-verdict block (${p.error})` };
|
|
37
|
+
parsed.push(p.verdict);
|
|
38
|
+
}
|
|
39
|
+
if (parsed.length === 0) return { ok: false, error: 'no_passes', reason: 'review vote needs at least one pass' };
|
|
40
|
+
const quorum = Number.isInteger(opts.quorum) && opts.quorum > 0 ? opts.quorum : Math.ceil(parsed.length / 2);
|
|
41
|
+
|
|
42
|
+
// Cluster findings across passes; a pass counts once per cluster.
|
|
43
|
+
const clusters = [];
|
|
44
|
+
parsed.forEach((v, pass) => {
|
|
45
|
+
for (const f of v.findings) {
|
|
46
|
+
const c = clusters.find((x) => sameFinding(x.finding, f));
|
|
47
|
+
if (c) {
|
|
48
|
+
c.passes.add(pass);
|
|
49
|
+
if ((SEVERITY_RANK[f.severity] || 0) > (SEVERITY_RANK[c.finding.severity] || 0)) c.finding = { ...c.finding, severity: f.severity };
|
|
50
|
+
} else {
|
|
51
|
+
clusters.push({ finding: { ...f }, passes: new Set([pass]) });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const kept = clusters.filter((c) => c.passes.size >= quorum).map((c) => ({ ...c.finding, votes: c.passes.size }));
|
|
56
|
+
const dropped = clusters.filter((c) => c.passes.size < quorum).map((c) => ({ ...c.finding, votes: c.passes.size }));
|
|
57
|
+
|
|
58
|
+
const counted = kept.filter((f) => !f.pre_existing);
|
|
59
|
+
const errors = counted.some((f) => f.severity === 'critical' || f.severity === 'high');
|
|
60
|
+
const warnings = counted.some((f) => f.severity === 'medium' || f.severity === 'low');
|
|
61
|
+
const verdictWord = errors ? 'NEEDS_FIXES' : (warnings ? 'PASS_WITH_WARNINGS' : 'PASS');
|
|
62
|
+
|
|
63
|
+
const declined = [];
|
|
64
|
+
for (const v of parsed) for (const d of v.declined || []) if (!declined.some((x) => x.behaviour === d.behaviour)) declined.push(d);
|
|
65
|
+
const notChecked = [];
|
|
66
|
+
for (const v of parsed) for (const n of v.not_checked || []) if (!notChecked.some((x) => x.check === n.check)) notChecked.push(n);
|
|
67
|
+
|
|
68
|
+
const verdict = {
|
|
69
|
+
contract: V.VERDICT_CONTRACT, agent: 'pan-reviewer', phase: opts.phase || parsed[0].phase || null,
|
|
70
|
+
verdict: verdictWord, outcome: errors ? 'fail' : 'pass',
|
|
71
|
+
findings: kept.map(({ votes, ...f }) => ({ ...f, votes })),
|
|
72
|
+
...(declined.length ? { declined } : {}),
|
|
73
|
+
...(notChecked.length ? { not_checked: notChecked } : {}),
|
|
74
|
+
};
|
|
75
|
+
const lines = [
|
|
76
|
+
`## Code Review — Phase ${verdict.phase || '?'} (${parsed.length} passes, quorum ${quorum})`,
|
|
77
|
+
'',
|
|
78
|
+
`### Verdict`,
|
|
79
|
+
verdictWord,
|
|
80
|
+
'',
|
|
81
|
+
`Kept ${kept.length} finding(s) at least ${quorum} pass(es) reported; dropped ${dropped.length} only fewer passes raised.`,
|
|
82
|
+
...kept.map((f) => `- ${f.severity.toUpperCase()} ${f.where || '(no location)'}: ${f.summary} (${f.votes}/${parsed.length})`),
|
|
83
|
+
...(dropped.length ? ['', 'Dropped (below quorum):', ...dropped.map((f) => `- ${f.severity} ${f.where || '(no location)'}: ${f.summary} (${f.votes}/${parsed.length})`)] : []),
|
|
84
|
+
'',
|
|
85
|
+
`${FENCE}pan-verdict`,
|
|
86
|
+
JSON.stringify(verdict),
|
|
87
|
+
FENCE,
|
|
88
|
+
'',
|
|
89
|
+
];
|
|
90
|
+
return { ok: true, passes: parsed.length, quorum, verdict, dropped, text: lines.join('\n') };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function cmdReviewVote(cwd, opts, raw) {
|
|
94
|
+
const files = opts.files || [];
|
|
95
|
+
if (!files.length) error('Usage: review vote [--phase N] [--quorum K] [--out <file>] <pass-file>...');
|
|
96
|
+
const texts = files.map((f) => {
|
|
97
|
+
try { return fs.readFileSync(path.isAbsolute(f) ? f : path.join(cwd, f), 'utf8'); } catch { return ''; }
|
|
98
|
+
});
|
|
99
|
+
const r = voteVerdicts(texts, { phase: opts.phase, quorum: opts.quorum ? Number(opts.quorum) : undefined });
|
|
100
|
+
if (!r.ok) { output(r, raw, 'error', 1); return; }
|
|
101
|
+
if (opts.out) fs.writeFileSync(path.isAbsolute(opts.out) ? opts.out : path.join(cwd, opts.out), r.text);
|
|
102
|
+
output({ ok: true, passes: r.passes, quorum: r.quorum, kept: r.verdict.findings.length, dropped: r.dropped.length, verdict: r.verdict.verdict, out: opts.out || null }, raw, r.verdict.verdict);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { LENSES, voteVerdicts, cmdReviewVote };
|
|
@@ -55,22 +55,8 @@ const { getExperimentManifest, PAN_EXPERIMENTS_ROOT_DEFAULT } = require('./exper
|
|
|
55
55
|
// claude into `--output-format json` so the trailing usage envelope can be
|
|
56
56
|
// parsed for cost/token metrics. Other runtimes are unchanged — token
|
|
57
57
|
// metering for codex/gemini/opencode is deferred (no equivalent flag).
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
bin: 'claude',
|
|
61
|
-
buildArgs: (prompt, opts) => {
|
|
62
|
-
const args = ['-p', '--dangerously-skip-permissions'];
|
|
63
|
-
if (opts && opts.captureMetrics) args.push('--output-format', 'json');
|
|
64
|
-
args.push(prompt);
|
|
65
|
-
return args;
|
|
66
|
-
},
|
|
67
|
-
shell: 'win32',
|
|
68
|
-
},
|
|
69
|
-
codex: { bin: 'codex', buildArgs: (prompt) => ['exec', prompt], shell: 'win32' },
|
|
70
|
-
gemini: { bin: 'gemini', buildArgs: (prompt) => ['-p', '--yolo', prompt], shell: 'win32' },
|
|
71
|
-
opencode: { bin: 'opencode', buildArgs: (prompt) => [prompt], shell: 'win32' },
|
|
72
|
-
copilot: null,
|
|
73
|
-
});
|
|
58
|
+
// The adapters live in hosts.cjs, the one host table (program A0).
|
|
59
|
+
const RUNTIME_RUNNERS = require('./hosts.cjs').runnerTable();
|
|
74
60
|
|
|
75
61
|
// ── Stop reasons (enum-ish) ─────────────────────────────────────────────────
|
|
76
62
|
|