@bongos/core 1.17.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/.bongos-core.json +5727 -0
- package/.claude/skills/blocker-review/SKILL.md +91 -0
- package/.claude/skills/blocker-solve/SKILL.md +96 -0
- package/.claude/skills/builder-backup/SKILL.md +54 -0
- package/.claude/skills/builder-box/SKILL.md +45 -0
- package/.claude/skills/builder-claim/SKILL.md +76 -0
- package/.claude/skills/builder-connect/SKILL.md +59 -0
- package/.claude/skills/builder-cost/SKILL.md +47 -0
- package/.claude/skills/builder-end/SKILL.md +81 -0
- package/.claude/skills/builder-exit/SKILL.md +88 -0
- package/.claude/skills/builder-key/SKILL.md +40 -0
- package/.claude/skills/builder-reauth/SKILL.md +50 -0
- package/.claude/skills/builder-redteam/SKILL.md +109 -0
- package/.claude/skills/builder-release/SKILL.md +41 -0
- package/.claude/skills/builder-setup/SKILL.md +62 -0
- package/.claude/skills/builder-ship/SKILL.md +79 -0
- package/.claude/skills/builder-stage/SKILL.md +51 -0
- package/.claude/skills/builder-start/SKILL.md +46 -0
- package/.claude/skills/close-box/SKILL.md +43 -0
- package/.claude/skills/design-sync/SKILL.md +69 -0
- package/.claude/skills/dev/SKILL.md +49 -0
- package/.claude/skills/feedback/SKILL.md +32 -0
- package/.claude/skills/figma-design-sync/SKILL.md +74 -0
- package/.claude/skills/goal-review/SKILL.md +89 -0
- package/.claude/skills/idea-triage/SKILL.md +144 -0
- package/.claude/skills/ideate/SKILL.md +82 -0
- package/.claude/skills/merge-mode/SKILL.md +169 -0
- package/.claude/skills/new-project/SKILL.md +108 -0
- package/.claude/skills/otb-character-review/SKILL.md +71 -0
- package/.claude/skills/otb-design-review/SKILL.md +47 -0
- package/.claude/skills/otb-feedback-capture/SKILL.md +45 -0
- package/.claude/skills/otb-figma-sync/SKILL.md +54 -0
- package/.claude/skills/otb-tile-generate/SKILL.md +45 -0
- package/.claude/skills/paint/SKILL.md +70 -0
- package/.claude/skills/planning-session/SKILL.md +299 -0
- package/.claude/skills/priority-session/SKILL.md +134 -0
- package/.claude/skills/read-session-export/SKILL.md +58 -0
- package/.claude/skills/recall/SKILL.md +52 -0
- package/.claude/skills/session-handoff/SKILL.md +46 -0
- package/.claude/skills/status/SKILL.md +33 -0
- package/.devcontainer/Dockerfile +69 -0
- package/.devcontainer/README.md +73 -0
- package/.devcontainer/devcontainer-lock.json +19 -0
- package/.devcontainer/devcontainer.json +93 -0
- package/.devcontainer/healthcheck.sh +84 -0
- package/.devcontainer/init-firewall.sh +198 -0
- package/.devcontainer/post-create.sh +153 -0
- package/.dockerignore +36 -0
- package/.env.local.example +32 -0
- package/.gitattributes +83 -0
- package/.gitignore +91 -0
- package/CLAUDE.md +242 -0
- package/CODE_OF_CONDUCT.md +46 -0
- package/CONTRIBUTING.md +51 -0
- package/Dockerfile +46 -0
- package/GOVERNANCE.md +44 -0
- package/LICENSE +661 -0
- package/README.md +88 -0
- package/bin/bongos.js +222 -0
- package/clients/bongos-client/README.md +48 -0
- package/clients/bongos-client/bongos-client.global.js +617 -0
- package/clients/bongos-client/examples/hello-world.mjs +62 -0
- package/clients/bongos-client/index.cjs +619 -0
- package/clients/bongos-client/index.d.ts +793 -0
- package/clients/bongos-client/index.mjs +614 -0
- package/clients/bongos-client/package.json +32 -0
- package/config/branding.neutral.json +60 -0
- package/config/design-tokens.neutral.json +51 -0
- package/config/design-tokens.schema.json +96 -0
- package/config/modules.neutral.json +9 -0
- package/docker-compose.yml +52 -0
- package/docker-entrypoint.sh +30 -0
- package/docs/adr/0001-stack-choice-single-vps.md +45 -0
- package/docs/adr/0002-digitalocean-over-hetzner.md +27 -0
- package/docs/adr/0003-cloudflare-origin-cert-over-letsencrypt.md +35 -0
- package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +45 -0
- package/docs/adr/0005-invisible-other-players-in-v1.md +37 -0
- package/docs/adr/0006-singleton-worldroom-plus-queueroom.md +30 -0
- package/docs/adr/0007-persistent-identity-via-localstorage-uuid.md +31 -0
- package/docs/adr/0008-family-based-tile-generation.md +86 -0
- package/docs/adr/0008-google-chat-oauth-user-auth.md +37 -0
- package/docs/adr/0009-autotile-architecture.md +181 -0
- package/docs/adr/0010-pms-architecture.md +70 -0
- package/docs/adr/0011-terrain-id-rendering-model.md +174 -0
- package/docs/adr/0012-ground-object-cell-split.md +208 -0
- package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +158 -0
- package/docs/adr/0014-families-canonical-over-vocabulary.md +103 -0
- package/docs/adr/0015-task-dependencies-and-auto-promotion.md +125 -0
- package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +132 -0
- package/docs/adr/0017-code-simplification-and-repo-cleanliness.md +74 -0
- package/docs/adr/0018-three-rank-model-goes-live.md +97 -0
- package/docs/adr/0019-red-team-bounty-payout-table.md +52 -0
- package/docs/adr/0020-security-model-public-stub.md +70 -0
- package/docs/adr/0021-per-builder-skill-model-preferences.md +99 -0
- package/docs/adr/0022-secrets-policy.md +124 -0
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +118 -0
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +277 -0
- package/docs/adr/0024-multi-agent-system-architecture.md +125 -0
- package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +59 -0
- package/docs/adr/0025-structured-criterion-task-link.md +44 -0
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +117 -0
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +170 -0
- package/docs/adr/0028-honest-llm-spend-accounting.md +109 -0
- package/docs/adr/0029-status-page-outage-resilience.md +59 -0
- package/docs/adr/0030-builders-hall-live-updates.md +59 -0
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +222 -0
- package/docs/adr/0032-discord-mirror-of-gds.md +50 -0
- package/docs/adr/0033-discord-bot-service-principal.md +82 -0
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +53 -0
- package/docs/adr/0035-builder-onboarding-three-paths.md +128 -0
- package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +40 -0
- package/docs/adr/0037-discord-channel-management.md +51 -0
- package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +111 -0
- package/docs/adr/0039-setup-first-onboarding-ux.md +49 -0
- package/docs/adr/0040-remote-control-default-browser-onramp.md +47 -0
- package/docs/adr/0041-temporary-grader-bypass-killswitch.md +40 -0
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +82 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +74 -0
- package/docs/adr/0044-mediterranean-palette-replacement.md +73 -0
- package/docs/adr/0044-per-box-live-game-preview.md +57 -0
- package/docs/adr/0045-devbox-desktop-app.md +133 -0
- package/docs/adr/0046-sandbox-first-review-gate.md +72 -0
- package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +36 -0
- package/docs/adr/0048-works-in-progress-ia-split.md +32 -0
- package/docs/adr/0049-split-parallel-safety-contract.md +46 -0
- package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +43 -0
- package/docs/adr/0051-full-session-transcript-corpus.md +57 -0
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +70 -0
- package/docs/adr/0053-scoped-dev-box-session.md +55 -0
- package/docs/adr/0054-session-token-reward-cost-plus.md +63 -0
- package/docs/adr/0055-server-mediated-branch-publish.md +47 -0
- package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +62 -0
- package/docs/adr/0057-container-cost-ledger.md +28 -0
- package/docs/adr/0057-discord-archon-approval-channels.md +64 -0
- package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +64 -0
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +37 -0
- package/docs/adr/0060-gds-retrieval-layer.md +97 -0
- package/docs/adr/0061-context-layer-decomposition.md +61 -0
- package/docs/adr/0062-medusa-instance-model-decoupling.md +107 -0
- package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +44 -0
- package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +49 -0
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +69 -0
- package/docs/adr/0066-generated-file-map-sections.md +51 -0
- package/docs/adr/0067-session-start-fast-forward-pull.md +43 -0
- package/docs/adr/0068-shared-art-key-for-newcomers.md +38 -0
- package/docs/adr/0069-hall-live-channel-sse-decouple.md +35 -0
- package/docs/adr/0070-deterministic-session-card-delivery.md +44 -0
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +59 -0
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +35 -0
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +39 -0
- package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +64 -0
- package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +37 -0
- package/docs/adr/0074-memory-map-stays-light.md +32 -0
- package/docs/adr/0075-per-system-cost-attribution.md +37 -0
- package/docs/adr/0076-in-session-deterministic-compaction.md +58 -0
- package/docs/adr/0077-content-addressed-llm-cache.md +32 -0
- package/docs/adr/0078-deterministic-first-cron.md +48 -0
- package/docs/adr/0079-value-gated-autonomous-engagement.md +39 -0
- package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +46 -0
- package/docs/adr/0081-tool-agnostic-design-layer.md +62 -0
- package/docs/adr/0082-productized-key-storage-provisioning.md +32 -0
- package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +55 -0
- package/docs/adr/0083-modular-architecture-strangler-build-plan.md +143 -0
- package/docs/adr/0084-auto-derived-per-task-rank-floor.md +86 -0
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +39 -0
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +123 -0
- package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +49 -0
- package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +65 -0
- package/docs/adr/0088-consistent-character-generative-animation.md +169 -0
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +179 -0
- package/docs/adr/0090-metic-task-authoring.md +73 -0
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +106 -0
- package/docs/adr/0092-hosting-module-and-on-prem-target.md +117 -0
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +72 -0
- package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +49 -0
- package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +65 -0
- package/docs/adr/0095-cross-agent-context-management.md +58 -0
- package/docs/adr/0096-require-reward-before-workable.md +209 -0
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +46 -0
- package/docs/adr/0097-retroactive-reward-backfill.md +124 -0
- package/docs/adr/0098-public-mirror-publish-manifest.md +92 -0
- package/docs/adr/0099-delayed-redacted-mirror-export.md +55 -0
- package/docs/adr/0100-two-repo-instance-model.md +60 -0
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +90 -0
- package/docs/adr/0102-finish-bounding-the-kernel.md +36 -0
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +70 -0
- package/docs/adr/0103-gdsv4-bongos-consolidation.md +28 -0
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +42 -0
- package/docs/adr/0105-instance-seed-migrations-out-of-core.md +47 -0
- package/docs/adr/0106-goal-owner-manager-authority.md +84 -0
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +83 -0
- package/docs/adr/0108-instance-composition-configurable-root.md +87 -0
- package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +44 -0
- package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +49 -0
- package/docs/adr/0111-instance-hosting-provisioning-module.md +135 -0
- package/docs/adr/0112-private-goals-invitations-join-requests.md +72 -0
- package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +41 -0
- package/docs/adr/0114-instance-docs-discovery-contract.md +32 -0
- package/docs/adr/0115-scheduled-routines-portability.md +31 -0
- package/docs/adr/0116-api-error-envelope-and-code-taxonomy.md +62 -0
- package/docs/adr/0117-api-versioning-and-deprecation-policy.md +50 -0
- package/docs/adr/0118-api-contract-source-of-truth-code-first.md +47 -0
- package/docs/adr/0119-api-pagination-contract.md +34 -0
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +108 -0
- package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +97 -0
- package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +43 -0
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +36 -0
- package/docs/adr/0124-typed-api-response-bodies.md +37 -0
- package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +36 -0
- package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +30 -0
- package/docs/adr/0127-cross-box-fleet-observability.md +64 -0
- package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +46 -0
- package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +34 -0
- package/docs/adr/0130-provision-remote-exec-over-ssh.md +61 -0
- package/docs/adr/0131-rank-scoped-skill-visibility.md +47 -0
- package/docs/adr/0132-cohost-fleet-control-plane-migration.md +54 -0
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +42 -0
- package/docs/adr/0134-private-first-npm-distribution.md +38 -0
- package/docs/adr/README.md +208 -0
- package/docs/api/openapi.json +13720 -0
- package/docs/api-reference.md +437 -0
- package/docs/architecture.md +244 -0
- package/docs/branding-contract.md +66 -0
- package/docs/branding-fill-prompt.md +25 -0
- package/docs/canonical-permissions.md +241 -0
- package/docs/design/container-exterior-tiles.md +49 -0
- package/docs/design/container-exterior.md +81 -0
- package/docs/design/container-interior.md +77 -0
- package/docs/design/modular-architecture/00-research-report.md +107 -0
- package/docs/design/modular-architecture/01-architecture.md +125 -0
- package/docs/design/modular-architecture/02-module-map.md +116 -0
- package/docs/design/modular-architecture/03-builder-flows.md +104 -0
- package/docs/design/modular-architecture/04-module-lifecycle.md +62 -0
- package/docs/design/modular-architecture/README.md +27 -0
- package/docs/design/vanilla-hall-ui-redesign-scope.md +284 -0
- package/docs/design/world-map-scroll.md +41 -0
- package/docs/design-contract.md +320 -0
- package/docs/file-map.md +418 -0
- package/docs/handoff-template.md +92 -0
- package/docs/modules-contract.md +293 -0
- package/docs/onboarding/browser-terminal-guide.md +73 -0
- package/docs/onboarding/diagrams/01-task-lifecycle.mmd +28 -0
- package/docs/onboarding/diagrams/02-rank-ladder.mmd +54 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +37 -0
- package/docs/onboarding/diagrams/04-architecture.mmd +51 -0
- package/docs/onboarding/diagrams/05-versioning.mmd +29 -0
- package/docs/onboarding/diagrams/README.md +94 -0
- package/docs/onboarding/diagrams/assertions.json +170 -0
- package/docs/onboarding/diagrams/puppeteer-config.json +1 -0
- package/docs/onboarding/diagrams/render-config.json +9 -0
- package/docs/onboarding/drachmae-and-karma.md +178 -0
- package/docs/onboarding/primer.md +202 -0
- package/docs/project-context.template.md +67 -0
- package/docs/recipes/README.md +35 -0
- package/docs/recipes/audit-replay.md +100 -0
- package/docs/recipes/bongos-cli-release.md +66 -0
- package/docs/recipes/builder-box-lifecycle.md +152 -0
- package/docs/recipes/claude-design-loop.md +143 -0
- package/docs/recipes/claude-gemini-bongos-doors.md +155 -0
- package/docs/recipes/cloudbongos-staging-gate.md +139 -0
- package/docs/recipes/cloudbongos-standup.md +111 -0
- package/docs/recipes/devbox-app-release.md +93 -0
- package/docs/recipes/devbox-app-windows-test.md +128 -0
- package/docs/recipes/failed-ship-recovery-proof.md +102 -0
- package/docs/recipes/figma-design-loop.md +129 -0
- package/docs/recipes/gds-db-backup.md +255 -0
- package/docs/recipes/instance-deploy.md +37 -0
- package/docs/recipes/managed-settings-remote-control.md +416 -0
- package/docs/recipes/memory-backup.md +71 -0
- package/docs/recipes/memory-sync-conflicts.md +98 -0
- package/docs/recipes/multi-builder-merge.md +78 -0
- package/docs/recipes/ops-gotchas.md +212 -0
- package/docs/recipes/overnight-builder.md +53 -0
- package/docs/recipes/packaging-the-core.md +49 -0
- package/docs/recipes/parallel-load-results-2026-05-30.md +63 -0
- package/docs/recipes/parallel-load-test.md +32 -0
- package/docs/recipes/pixel-art-pipeline.md +411 -0
- package/docs/recipes/private-npm-distribution.md +79 -0
- package/docs/recipes/search-index-ingestion.md +67 -0
- package/docs/recipes/self-host.md +60 -0
- package/docs/recipes/ship-and-regrade.md +72 -0
- package/docs/recipes/staging-data.md +50 -0
- package/docs/recipes/staging-environment.md +215 -0
- package/docs/recipes/upgrading-the-core.md +58 -0
- package/docs/research/cursor-competitive-analysis.md +255 -0
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +227 -0
- package/migrations/003_pms.sql +284 -0
- package/migrations/004_pms_cost_idempotency.sql +13 -0
- package/migrations/005_pms_smoke_filter.sql +44 -0
- package/migrations/006_pms_v2.sql +378 -0
- package/migrations/007_pms_v2_hierarchy_trigger.sql +59 -0
- package/migrations/008_pms_v2_migration_allocator.sql +27 -0
- package/migrations/009_pms_v2_blockers_table.sql +68 -0
- package/migrations/010_uptime.sql +29 -0
- package/migrations/011_backfill_v2_v3_done_when.sql +20 -0
- package/migrations/012_pms_v2_task_lifecycle_states.sql +82 -0
- package/migrations/013_claim_locking.sql +22 -0
- package/migrations/014_pms_task_discipline.sql +86 -0
- package/migrations/015_pms_estimation_calibration.sql +140 -0
- package/migrations/016_pms_task_grades.sql +60 -0
- package/migrations/017_version_progress_exclude_abandoned.sql +49 -0
- package/migrations/019_rename_pms_v3_to_gds_v3.sql +65 -0
- package/migrations/020_task_dependencies.sql +99 -0
- package/migrations/021_v3_renumber_and_deps.sql +224 -0
- package/migrations/022_v3_reconcile_dep_gated_status.sql +41 -0
- package/migrations/023_pms_rank.sql +30 -0
- package/migrations/025_builder_exit_status.sql +15 -0
- package/migrations/027_builders_preferred_disciplines.sql +46 -0
- package/migrations/028_tasks_parallel_safe.sql +71 -0
- package/migrations/029_rank_three_live.sql +32 -0
- package/migrations/030_vulnerability_reports.sql +61 -0
- package/migrations/031_reward_simplification.sql +57 -0
- package/migrations/032_repo_health_snapshots.sql +56 -0
- package/migrations/033_audit_log.sql +64 -0
- package/migrations/034_lifecycle_breakdown_leaf_only.sql +62 -0
- package/migrations/035_auth_token_hygiene.sql +66 -0
- package/migrations/037_builder_skill_model_prefs.sql +44 -0
- package/migrations/038_task_override_requests.sql +51 -0
- package/migrations/039_newcomer_friendly_column.sql +124 -0
- package/migrations/040_builder_karma.sql +58 -0
- package/migrations/041_kind_multipliers_and_idea_bonus.sql +42 -0
- package/migrations/042_criterion_proposal_workflow.sql +78 -0
- package/migrations/043_builder_onboarding_observability.sql +66 -0
- package/migrations/044_tasks_requires_rank.sql +97 -0
- package/migrations/045_builder_onboarding_state.sql +147 -0
- package/migrations/046_cost_log_builder_attribution.sql +42 -0
- package/migrations/054_builder_memory.sql +108 -0
- package/migrations/055_task_criteria.sql +90 -0
- package/migrations/058_peer_votes.sql +41 -0
- package/migrations/059_onboarding_pick_disciplines.sql +59 -0
- package/migrations/060_access_requests.sql +46 -0
- package/migrations/061_backfill_override_ship_attribution.sql +29 -0
- package/migrations/062_session_records.sql +34 -0
- package/migrations/063_session_records_tool_errors_idx.sql +11 -0
- package/migrations/064_grade_attempts.sql +18 -0
- package/migrations/065_bfg_principal.sql +58 -0
- package/migrations/066_live_notify_triggers.sql +69 -0
- package/migrations/067_builder_boxes.sql +117 -0
- package/migrations/067_discord_bot_foundation.sql +69 -0
- package/migrations/068_thetes_requires_rank.sql +27 -0
- package/migrations/069_builder_ssh_keys.sql +66 -0
- package/migrations/070_box_intents.sql +101 -0
- package/migrations/071_box_first_provision_approval.sql +46 -0
- package/migrations/072_onboarding_ship_three_remove_primer.sql +80 -0
- package/migrations/073_box_terminal_access.sql +46 -0
- package/migrations/075_builder_sound_prefs.sql +28 -0
- package/migrations/078_box_claude_active.sql +22 -0
- package/migrations/079_box_first_connected_at.sql +20 -0
- package/migrations/081_tasks_needs_migration.sql +34 -0
- package/migrations/084_tasks_security_sensitive.sql +24 -0
- package/migrations/085_security_engagement_docs.sql +34 -0
- package/migrations/086_rename_disciplines.sql +125 -0
- package/migrations/088_builders_anthropic_link.sql +28 -0
- package/migrations/089_builders_monthly_budget.sql +19 -0
- package/migrations/090_box_scoped_sessions.sql +38 -0
- package/migrations/091_session_token_reward.sql +44 -0
- package/migrations/093_tasks_sandbox_review.sql +22 -0
- package/migrations/102_remove_box_first_provision_approval.sql +58 -0
- package/migrations/103_search_chunks.sql +90 -0
- package/migrations/104_builder_box_blocked.sql +34 -0
- package/migrations/105_box_host_keys.sql +35 -0
- package/migrations/111_medusa_customer0_framing.sql +35 -0
- package/migrations/112_drop_track_check.sql +25 -0
- package/migrations/113_merge_lease.sql +16 -0
- package/migrations/114_cloudbongos_rename_live_strings.sql +32 -0
- package/migrations/118_currency_label_decoupling.sql +23 -0
- package/migrations/119_builder_behavior_prefs.sql +31 -0
- package/migrations/121_box_code_version.sql +37 -0
- package/migrations/122_builder_gemini_key.sql +30 -0
- package/migrations/123_cost_log_tiers.sql +33 -0
- package/migrations/124_cost_baseline.sql +45 -0
- package/migrations/125_llm_cache.sql +54 -0
- package/migrations/126_autonomy_runs.sql +33 -0
- package/migrations/143_cost_calibration.sql +63 -0
- package/migrations/145_backfill_task_requires_rank.sql +84 -0
- package/migrations/160_goal_tier.sql +88 -0
- package/migrations/163_polymorphic_dependencies.sql +187 -0
- package/migrations/164_event_sound_prefs.sql +20 -0
- package/migrations/165_task_module_key.sql +15 -0
- package/migrations/171_claims_published_head_sha.sql +24 -0
- package/migrations/174_close_orphaned_claims_on_terminal_tasks.sql +30 -0
- package/migrations/175_task_grades_diff_hash.sql +9 -0
- package/migrations/176_promotion_respects_open_blockers.sql +58 -0
- package/migrations/176_ui_discipline.sql +40 -0
- package/migrations/177_require_reward_before_workable.sql +76 -0
- package/migrations/178_reward_gate_auto_assign_revert_autopromote.sql +61 -0
- package/migrations/179_search_chunk_usefulness.sql +33 -0
- package/migrations/180_one_active_claim_per_session.sql +54 -0
- package/migrations/181_cost_log_skill_name.sql +33 -0
- package/migrations/184_claim_release_requested.sql +25 -0
- package/migrations/185_module_upstreaming_goal_wiring.sql +59 -0
- package/migrations/187_goal_sort_order.sql +30 -0
- package/migrations/187_tasks_kind_check_add_verify.sql +21 -0
- package/migrations/README.md +88 -0
- package/migrations/core_188_goal_visibility_and_membership_requests.sql +71 -0
- package/migrations/core_189_core_upgrades_ledger.sql +37 -0
- package/migrations/core_189_credit_log_task_id_index.sql +19 -0
- package/migrations/core_190_rebuild_claimable_task_views.sql +71 -0
- package/migrations/instance/018_pms_v3_planning_seed.sql +131 -0
- package/migrations/instance/080_gds_v4_planning_seed.sql +75 -0
- package/migrations/instance/110_medusa_planning_seed.sql +70 -0
- package/migrations/instance/120_bongos_v1_planning_seed.sql +69 -0
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +26 -0
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +29 -0
- package/migrations/instance/172_v2_world_planning_seed.sql +163 -0
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +33 -0
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +132 -0
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +116 -0
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +69 -0
- package/modules/autonomy/CLAUDE.md +49 -0
- package/modules/autonomy/db.js +50 -0
- package/modules/autonomy/module.json +13 -0
- package/modules/autonomy/pollers/routine-timers.js +146 -0
- package/modules/autonomy/routes/autonomy.js +37 -0
- package/modules/builder-settings/CLAUDE.md +58 -0
- package/modules/builder-settings/builder-needs.js +115 -0
- package/modules/builder-settings/db.js +168 -0
- package/modules/builder-settings/display-name.js +84 -0
- package/modules/builder-settings/event-sound-prefs.js +169 -0
- package/modules/builder-settings/interaction-prefs.js +201 -0
- package/modules/builder-settings/model-alloc-prefs.js +147 -0
- package/modules/builder-settings/module.json +10 -0
- package/modules/builder-settings/render-prefs.js +103 -0
- package/modules/builder-settings/routes/settings.js +417 -0
- package/modules/builder-settings/settings.js +52 -0
- package/modules/builder-settings/skill-prefs.js +196 -0
- package/modules/builder-settings/sound-prefs.js +178 -0
- package/modules/builder-settings/wandering-prefs.js +282 -0
- package/modules/dev-box/CLAUDE.md +38 -0
- package/modules/dev-box/box-access.js +275 -0
- package/modules/dev-box/box-credential.js +94 -0
- package/modules/dev-box/box-onboard.js +486 -0
- package/modules/dev-box/boxes.js +842 -0
- package/modules/dev-box/db.js +39 -0
- package/modules/dev-box/module.json +14 -0
- package/modules/dev-box/routes/box.js +977 -0
- package/modules/discord/CLAUDE.md +50 -0
- package/modules/discord/auth.js +54 -0
- package/modules/discord/bug-attachments.js +210 -0
- package/modules/discord/db.js +96 -0
- package/modules/discord/discord-approvals.js +145 -0
- package/modules/discord/discord-audit.js +29 -0
- package/modules/discord/discord-bot.js +545 -0
- package/modules/discord/discord-bugs.js +257 -0
- package/modules/discord/discord-channels.js +412 -0
- package/modules/discord/discord-inbound.js +147 -0
- package/modules/discord/discord-message-buffer.js +54 -0
- package/modules/discord/discord-reaction.js +186 -0
- package/modules/discord/discord-role-sync.js +318 -0
- package/modules/discord/module.json +14 -0
- package/modules/discord/pollers/discord-bot.js +19 -0
- package/modules/discord/routes/bug-attachments.js +44 -0
- package/modules/discord/routes/discord.js +102 -0
- package/modules/discord/ship-broadcast.js +78 -0
- package/modules/discord/webhook.js +74 -0
- package/modules/economy/CLAUDE.md +48 -0
- package/modules/economy/achievements.js +594 -0
- package/modules/economy/cost-classify.js +331 -0
- package/modules/economy/cost-summary-shape.js +90 -0
- package/modules/economy/cost.js +438 -0
- package/modules/economy/credits.js +430 -0
- package/modules/economy/migrations/economy_001_fix_gds_shipper_description.sql +91 -0
- package/modules/economy/module.json +10 -0
- package/modules/economy/reward.js +61 -0
- package/modules/economy/routes/cost.js +97 -0
- package/modules/economy/routes/leaderboard.js +26 -0
- package/modules/economy/routes/reward.js +42 -0
- package/modules/economy/streak.js +285 -0
- package/modules/grading/CLAUDE.md +42 -0
- package/modules/grading/db.js +39 -0
- package/modules/grading/grader-rubric.json +151 -0
- package/modules/grading/grader-workers/efficiency.js +213 -0
- package/modules/grading/grader-workers/extract-json.js +80 -0
- package/modules/grading/grader-workers/fence.js +100 -0
- package/modules/grading/grader-workers/hacker.js +239 -0
- package/modules/grading/grader-workers/index.js +183 -0
- package/modules/grading/grader-workers/narc.js +207 -0
- package/modules/grading/grader-workers/quality.js +198 -0
- package/modules/grading/grader-workers/rubric.js +20 -0
- package/modules/grading/grader-workers/worker-prompt.js +89 -0
- package/modules/grading/grader-workers/worker-verdict.js +50 -0
- package/modules/grading/grader.js +1790 -0
- package/modules/grading/module.json +12 -0
- package/modules/grading/routes/grade.js +51 -0
- package/modules/hall-ui/CLAUDE.md +61 -0
- package/modules/hall-ui/module.json +14 -0
- package/modules/hall-ui/public/atlas.html +180 -0
- package/modules/hall-ui/public/atlas.js +336 -0
- package/modules/hall-ui/public/box-panel.js +423 -0
- package/modules/hall-ui/public/brand-apply.js +117 -0
- package/modules/hall-ui/public/builders.js +3028 -0
- package/modules/hall-ui/public/chime.wav +0 -0
- package/modules/hall-ui/public/copy-on-click.js +174 -0
- package/modules/hall-ui/public/cursors/arrow.png +0 -0
- package/modules/hall-ui/public/cursors/hand-pointer.png +0 -0
- package/modules/hall-ui/public/cursors/magnifier-out.png +0 -0
- package/modules/hall-ui/public/cursors/magnifier.png +0 -0
- package/modules/hall-ui/public/diagram-viewer.css +125 -0
- package/modules/hall-ui/public/diagram-viewer.js +330 -0
- package/modules/hall-ui/public/diagrams.html +113 -0
- package/modules/hall-ui/public/dom-utils.js +212 -0
- package/modules/hall-ui/public/drachmae.html +68 -0
- package/modules/hall-ui/public/drachmae.js +136 -0
- package/modules/hall-ui/public/gate.html +143 -0
- package/modules/hall-ui/public/gate.js +228 -0
- package/modules/hall-ui/public/goals-page.js +1297 -0
- package/modules/hall-ui/public/goals.html +376 -0
- package/modules/hall-ui/public/goals.js +119 -0
- package/modules/hall-ui/public/hall-widgets.js +189 -0
- package/modules/hall-ui/public/harbor.html +167 -0
- package/modules/hall-ui/public/harbor.js +335 -0
- package/modules/hall-ui/public/index.html +116 -0
- package/modules/hall-ui/public/instances-panel.js +172 -0
- package/modules/hall-ui/public/md-reader.js +234 -0
- package/modules/hall-ui/public/modules.html +119 -0
- package/modules/hall-ui/public/modules.js +154 -0
- package/modules/hall-ui/public/new-project-wizard.js +186 -0
- package/modules/hall-ui/public/not-ready.html +48 -0
- package/modules/hall-ui/public/pair.html +119 -0
- package/modules/hall-ui/public/pair.js +132 -0
- package/modules/hall-ui/public/primer.css +404 -0
- package/modules/hall-ui/public/primer.html +85 -0
- package/modules/hall-ui/public/primer.js +641 -0
- package/modules/hall-ui/public/profile.html +85 -0
- package/modules/hall-ui/public/profile.js +236 -0
- package/modules/hall-ui/public/ranks.html +69 -0
- package/modules/hall-ui/public/ranks.js +206 -0
- package/modules/hall-ui/public/roadmap.html +209 -0
- package/modules/hall-ui/public/roadmap.js +375 -0
- package/modules/hall-ui/public/sessions.html +92 -0
- package/modules/hall-ui/public/sessions.js +222 -0
- package/modules/hall-ui/public/settings.html +640 -0
- package/modules/hall-ui/public/settings.js +1219 -0
- package/modules/hall-ui/public/shell.js +495 -0
- package/modules/hall-ui/public/style.css +1748 -0
- package/modules/hall-ui/public/task-detail.js +253 -0
- package/modules/hall-ui/public/task.html +83 -0
- package/modules/hall-ui/public/task.js +326 -0
- package/modules/hall-ui/public/watch.html +333 -0
- package/modules/hall-ui/public/watch.js +859 -0
- package/modules/hall-ui/public/work.html +469 -0
- package/modules/hall-ui/public/work.js +1292 -0
- package/modules/ideas/CLAUDE.md +37 -0
- package/modules/ideas/blockers.js +100 -0
- package/modules/ideas/inbox.js +459 -0
- package/modules/ideas/module.json +10 -0
- package/modules/ideas/routes/blockers.js +120 -0
- package/modules/ideas/routes/inbox.js +230 -0
- package/modules/lifecycle/CLAUDE.md +63 -0
- package/modules/lifecycle/conflict-resolve.js +436 -0
- package/modules/lifecycle/db.js +4970 -0
- package/modules/lifecycle/done-when.js +355 -0
- package/modules/lifecycle/github-push.js +969 -0
- package/modules/lifecycle/goal-conflicts.js +133 -0
- package/modules/lifecycle/hierarchy.js +104 -0
- package/modules/lifecycle/lifecycle.js +58 -0
- package/modules/lifecycle/merge-lock.js +261 -0
- package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +28 -0
- package/modules/lifecycle/migrations/lifecycle_002_backfill_orphan_task_goals.sql +36 -0
- package/modules/lifecycle/module.json +13 -0
- package/modules/lifecycle/pollers/publish-reconciler.js +20 -0
- package/modules/lifecycle/publish-reconciler.js +418 -0
- package/modules/lifecycle/routes/analytics.js +63 -0
- package/modules/lifecycle/routes/claims.js +506 -0
- package/modules/lifecycle/routes/dependencies.js +82 -0
- package/modules/lifecycle/routes/done-when.js +136 -0
- package/modules/lifecycle/routes/gate-approvals.js +197 -0
- package/modules/lifecycle/routes/goals.js +1419 -0
- package/modules/lifecycle/routes/lifecycle.js +43 -0
- package/modules/lifecycle/routes/tasks.js +1703 -0
- package/modules/lifecycle/routes/versions.js +128 -0
- package/modules/lifecycle/ship-card.js +505 -0
- package/modules/lifecycle/ship-preflight.js +225 -0
- package/modules/lifecycle/task-classifier.js +512 -0
- package/modules/lifecycle/version-close.js +106 -0
- package/modules/memory/CLAUDE.md +40 -0
- package/modules/memory/learnings.js +275 -0
- package/modules/memory/memory.js +572 -0
- package/modules/memory/module.json +12 -0
- package/modules/memory/routes/learnings.js +90 -0
- package/modules/memory/routes/memory.js +916 -0
- package/modules/memory/routes/search.js +188 -0
- package/modules/memory/search-chunker.js +252 -0
- package/modules/memory/search.js +320 -0
- package/modules/onboarding/CLAUDE.md +55 -0
- package/modules/onboarding/approval-broadcast.js +59 -0
- package/modules/onboarding/db.js +54 -0
- package/modules/onboarding/module.json +10 -0
- package/modules/onboarding/newcomer-restock.js +212 -0
- package/modules/onboarding/onboarding-state.js +532 -0
- package/modules/onboarding/onboarding.js +508 -0
- package/modules/onboarding/port.js +63 -0
- package/modules/onboarding/routes/access-requests.js +208 -0
- package/modules/onboarding/routes/onboarding.js +54 -0
- package/modules/security/CLAUDE.md +44 -0
- package/modules/security/module.json +10 -0
- package/modules/security/routes/reports.js +251 -0
- package/modules/security/secret-scrub.js +201 -0
- package/modules/security/security.js +294 -0
- package/modules/sessions/CLAUDE.md +39 -0
- package/modules/sessions/bfg.js +357 -0
- package/modules/sessions/db.js +326 -0
- package/modules/sessions/module.json +10 -0
- package/modules/sessions/pulse.js +149 -0
- package/modules/sessions/routes/sessions.js +331 -0
- package/modules/status-ui/CLAUDE.md +47 -0
- package/modules/status-ui/module.json +14 -0
- package/modules/status-ui/public/README.md +29 -0
- package/modules/status-ui/public/cursors/arrow.png +0 -0
- package/modules/status-ui/public/cursors/hand-pointer.png +0 -0
- package/modules/status-ui/public/index.html +363 -0
- package/modules/status-ui/public/snapshots/.gitkeep +0 -0
- package/modules/status-ui/public/status.js +1375 -0
- package/modules/status-ui/public/style.css +1342 -0
- package/package-lock.json +975 -0
- package/package.json +28 -0
- package/public-docs/index.html +35 -0
- package/public-docs/redoc.standalone.js +1838 -0
- package/public-docs/redoc.standalone.js.LICENSE.txt +102 -0
- package/public-landing/buddha.png +0 -0
- package/public-landing/create.html +868 -0
- package/public-landing/index.html +168 -0
- package/scripts/bongos-mcp.js +28 -0
- package/scripts/cron/style_guide_audit.sh +134 -0
- package/scripts/discord/README.md +52 -0
- package/scripts/discord/lib.js +80 -0
- package/scripts/discord/post.js +51 -0
- package/scripts/gds/CLAUDE.md +144 -0
- package/scripts/gds/abandon.js +58 -0
- package/scripts/gds/adopt-detect.js +123 -0
- package/scripts/gds/adopt-import.js +111 -0
- package/scripts/gds/adopt-preflight.js +121 -0
- package/scripts/gds/api.js +78 -0
- package/scripts/gds/architect-audit.js +288 -0
- package/scripts/gds/art-stage.js +152 -0
- package/scripts/gds/audit-authorship.sh +243 -0
- package/scripts/gds/audit-deps.js +121 -0
- package/scripts/gds/audit-exports.js +128 -0
- package/scripts/gds/audit-replay.sh +161 -0
- package/scripts/gds/audit-ships.js +251 -0
- package/scripts/gds/autonomy-gate.js +135 -0
- package/scripts/gds/backfill-cost.js +165 -0
- package/scripts/gds/backfill-sessions.js +351 -0
- package/scripts/gds/backfill-task-rewards.js +408 -0
- package/scripts/gds/backup.js +94 -0
- package/scripts/gds/bfg-principal-token.js +65 -0
- package/scripts/gds/box-auth-check.js +160 -0
- package/scripts/gds/box-connect-lib.js +453 -0
- package/scripts/gds/box.js +1258 -0
- package/scripts/gds/build-bongos.js +94 -0
- package/scripts/gds/capture.js +123 -0
- package/scripts/gds/cf-tunnel.js +419 -0
- package/scripts/gds/ci-grade.js +82 -0
- package/scripts/gds/claim-recall.js +112 -0
- package/scripts/gds/claim.js +333 -0
- package/scripts/gds/classify-discipline.js +153 -0
- package/scripts/gds/claude-materialize.js +176 -0
- package/scripts/gds/cleanup-orphans.js +74 -0
- package/scripts/gds/cli-lib.js +518 -0
- package/scripts/gds/code.js +101 -0
- package/scripts/gds/codemod-rename-src-gds.js +145 -0
- package/scripts/gds/conductor-dispatch.js +426 -0
- package/scripts/gds/context-pack.js +228 -0
- package/scripts/gds/coreB-carve-proof.sh +716 -0
- package/scripts/gds/cost.js +47 -0
- package/scripts/gds/db-backup-nightly.sh +160 -0
- package/scripts/gds/db-backup-offsite.sh +152 -0
- package/scripts/gds/demote.js +40 -0
- package/scripts/gds/design-sync.js +62 -0
- package/scripts/gds/diagnose-achievements.js +118 -0
- package/scripts/gds/diagram-facts.js +319 -0
- package/scripts/gds/discipline-modes.json +25 -0
- package/scripts/gds/discord-channels.js +66 -0
- package/scripts/gds/do-api.js +176 -0
- package/scripts/gds/docs-entropy.js +403 -0
- package/scripts/gds/doctor.js +389 -0
- package/scripts/gds/feedback-latest.js +94 -0
- package/scripts/gds/fetch-art-key.js +106 -0
- package/scripts/gds/fetch-sound-prefs.js +88 -0
- package/scripts/gds/figma-design-sync.js +61 -0
- package/scripts/gds/fitness.js +1446 -0
- package/scripts/gds/gate-review.js +322 -0
- package/scripts/gds/gating.js +325 -0
- package/scripts/gds/gds-db-backup.service +24 -0
- package/scripts/gds/gds-db-backup.timer +20 -0
- package/scripts/gds/gds-db-offsite.service +29 -0
- package/scripts/gds/gds-db-offsite.timer +18 -0
- package/scripts/gds/gen-api-client.js +508 -0
- package/scripts/gds/gen-api-docs.js +1037 -0
- package/scripts/gds/gen-atlas.js +366 -0
- package/scripts/gds/gen-diagrams.js +608 -0
- package/scripts/gds/gen-file-map.js +196 -0
- package/scripts/gds/gen-repo-map.js +519 -0
- package/scripts/gds/gen-session-index.js +210 -0
- package/scripts/gds/git-merge-regen.js +147 -0
- package/scripts/gds/grade-smoke.js +205 -0
- package/scripts/gds/hooks/pre-push.js +169 -0
- package/scripts/gds/http-api-client.js +88 -0
- package/scripts/gds/init.js +903 -0
- package/scripts/gds/install-git-hooks.js +114 -0
- package/scripts/gds/key-card.js +213 -0
- package/scripts/gds/key-status.js +126 -0
- package/scripts/gds/land-watch.js +168 -0
- package/scripts/gds/learning-capture.js +145 -0
- package/scripts/gds/lib.sh +44 -0
- package/scripts/gds/linkify-refs.js +473 -0
- package/scripts/gds/local-preview-lib.js +159 -0
- package/scripts/gds/local-preview.js +95 -0
- package/scripts/gds/login.js +225 -0
- package/scripts/gds/main-audit.js +279 -0
- package/scripts/gds/me.js +88 -0
- package/scripts/gds/memory-map.js +174 -0
- package/scripts/gds/merge-lock.js +71 -0
- package/scripts/gds/migration-namespace.js +200 -0
- package/scripts/gds/mirror-lineage-check.js +319 -0
- package/scripts/gds/mirror-redact.js +305 -0
- package/scripts/gds/module.js +482 -0
- package/scripts/gds/newcomer-floor.config.json +13 -0
- package/scripts/gds/newcomer-floor.js +415 -0
- package/scripts/gds/newcomer-floor.templates.json +86 -0
- package/scripts/gds/newcomer-floor.test.js +235 -0
- package/scripts/gds/next-number.js +163 -0
- package/scripts/gds/oauth-secret.js +197 -0
- package/scripts/gds/onboard.js +217 -0
- package/scripts/gds/onboarding-analyze.js +226 -0
- package/scripts/gds/onboarding-config.js +178 -0
- package/scripts/gds/onboarding-ship.js +92 -0
- package/scripts/gds/package-core.js +684 -0
- package/scripts/gds/paste-token.js +131 -0
- package/scripts/gds/portability-smoke-art.sh +85 -0
- package/scripts/gds/portability-smoke-memory.sh +147 -0
- package/scripts/gds/provision.js +1324 -0
- package/scripts/gds/publish-manifest.js +307 -0
- package/scripts/gds/pulse.js +309 -0
- package/scripts/gds/push-main.js +45 -0
- package/scripts/gds/read-session-export.js +359 -0
- package/scripts/gds/reauth.js +149 -0
- package/scripts/gds/recall.js +135 -0
- package/scripts/gds/release.js +71 -0
- package/scripts/gds/render-ideas.js +173 -0
- package/scripts/gds/repo-metrics.js +363 -0
- package/scripts/gds/resolve-skill-model.js +57 -0
- package/scripts/gds/run-routine.js +197 -0
- package/scripts/gds/run-unit-tests.js +178 -0
- package/scripts/gds/sandbox-stage.js +653 -0
- package/scripts/gds/search-ingest-db.js +209 -0
- package/scripts/gds/search-ingest-md.js +225 -0
- package/scripts/gds/security-baseline-check.sh +180 -0
- package/scripts/gds/seed-adopt-tasks.js +75 -0
- package/scripts/gds/seed-api-contract-goal-tasks.js +141 -0
- package/scripts/gds/seed-bongos-app-tasks.js +294 -0
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +81 -0
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +100 -0
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +307 -0
- package/scripts/gds/seed-bongos-module-system-tasks.js +346 -0
- package/scripts/gds/seed-bongos-tasks.js +214 -0
- package/scripts/gds/seed-gds-v4-tasks.js +624 -0
- package/scripts/gds/seed-governance-tasks.js +340 -0
- package/scripts/gds/seed-medusa-tasks.js +440 -0
- package/scripts/gds/seed-module-upstreaming.js +219 -0
- package/scripts/gds/seed-productization-tasks.js +235 -0
- package/scripts/gds/seed-provisioning-tasks.js +83 -0
- package/scripts/gds/seed-staging.sh +104 -0
- package/scripts/gds/seed-v2-world-tasks.js +283 -0
- package/scripts/gds/seed-v3-tasks.js +1981 -0
- package/scripts/gds/session-cost.js +393 -0
- package/scripts/gds/session-digest.js +179 -0
- package/scripts/gds/session-eval.js +576 -0
- package/scripts/gds/session.js +404 -0
- package/scripts/gds/setup.js +504 -0
- package/scripts/gds/shell.js +100 -0
- package/scripts/gds/ship.js +3618 -0
- package/scripts/gds/smoke-batch-claim.sh +213 -0
- package/scripts/gds/smoke-batch-release.sh +296 -0
- package/scripts/gds/smoke-box.sh +150 -0
- package/scripts/gds/smoke-claim-consistency.sh +212 -0
- package/scripts/gds/smoke-dependencies.sh +84 -0
- package/scripts/gds/smoke-gds.js +125 -0
- package/scripts/gds/smoke-write.sh +168 -0
- package/scripts/gds/start.js +782 -0
- package/scripts/gds/status.js +365 -0
- package/scripts/gds/triage.js +366 -0
- package/scripts/gds/upgrade.js +421 -0
- package/scripts/gds/validate-design.js +254 -0
- package/scripts/gds/version-close.js +549 -0
- package/scripts/migrate.sh +183 -0
- package/scripts/migrate_cost_ledger.sh +178 -0
- package/scripts/public-mirror-export.js +291 -0
- package/scripts/render-diagrams.sh +88 -0
- package/scripts/security/dep-audit.js +276 -0
- package/scripts/setup-builder.js +274 -0
- package/scripts/setup-builder.sh +28 -0
- package/scripts/snapshots/backfill-history.mjs +292 -0
- package/scripts/snapshots/maybe-render-on-deploy.sh +66 -0
- package/scripts/snapshots/render-pre-art-eras.mjs +329 -0
- package/scripts/snapshots/render-world.mjs +322 -0
- package/scripts/status-mirror-sync.js +192 -0
- package/scripts/status-probe.mjs +185 -0
- package/server.js +212 -0
- package/src/asset-version.js +71 -0
- package/src/bongos/CLAUDE.md +64 -0
- package/src/bongos/api-errors.js +281 -0
- package/src/bongos/api-path-404.js +67 -0
- package/src/bongos/api-prefix.js +38 -0
- package/src/bongos/app-pair.js +172 -0
- package/src/bongos/auth.js +979 -0
- package/src/bongos/cascade-dispatch.js +123 -0
- package/src/bongos/db-kernel.js +252 -0
- package/src/bongos/db.js +911 -0
- package/src/bongos/goal-scope-check.js +120 -0
- package/src/bongos/hierarchy-config.js +189 -0
- package/src/bongos/live-channel.js +145 -0
- package/src/bongos/llm-cache.js +260 -0
- package/src/bongos/llm-pricing.js +220 -0
- package/src/bongos/metrics.js +286 -0
- package/src/bongos/middleware/audit.js +274 -0
- package/src/bongos/middleware/error-envelope.js +93 -0
- package/src/bongos/middleware/rate-limit.js +195 -0
- package/src/bongos/module-scope-map.js +271 -0
- package/src/bongos/optimizer.js +166 -0
- package/src/bongos/path-match.js +47 -0
- package/src/bongos/permission-path-check.js +248 -0
- package/src/bongos/pool.js +55 -0
- package/src/bongos/repo-info.js +91 -0
- package/src/bongos/route-rank-check.js +420 -0
- package/src/bongos/routes/CLAUDE.md +43 -0
- package/src/bongos/routes/_helpers.js +369 -0
- package/src/bongos/routes/audit-log.js +59 -0
- package/src/bongos/routes/auth.js +755 -0
- package/src/bongos/routes/backup.js +104 -0
- package/src/bongos/routes/builders.js +526 -0
- package/src/bongos/routes/healthz.js +16 -0
- package/src/bongos/routes/instance.js +129 -0
- package/src/bongos/routes/live.js +125 -0
- package/src/bongos/routes/llm-cache.js +119 -0
- package/src/bongos/routes/me.js +380 -0
- package/src/bongos/routes/modules.js +131 -0
- package/src/bongos/routes/optimizer.js +54 -0
- package/src/bongos/routes/public.js +476 -0
- package/src/bongos/routes/security.js +207 -0
- package/src/bongos/routes.js +166 -0
- package/src/bongos/secret-box.js +177 -0
- package/src/bongos/sensitive-surfaces.js +30 -0
- package/src/bongos/serve-internal.js +889 -0
- package/src/bongos/uptime-poller.js +90 -0
- package/src/bongos-downloads.js +70 -0
- package/src/branding.js +182 -0
- package/src/build-info.js +77 -0
- package/src/devbox-downloads.js +67 -0
- package/src/instance-config.js +200 -0
- package/src/module-api.js +366 -0
- package/src/module-loader/loader.js +268 -0
- package/src/module-loader/manifest-schema.js +140 -0
- package/src/module-loader/provenance.js +28 -0
- package/src/module-loader/semver.js +100 -0
- package/src/module-seams.js +291 -0
- package/src/modules.js +254 -0
- package/src/platform-server.js +135 -0
- package/src/ui/adapters/claude-design/index.js +333 -0
- package/src/ui/adapters/figma/index.js +328 -0
- package/tests/abandon_task.mjs +138 -0
- package/tests/achievements_reconcile.mjs +167 -0
- package/tests/adopt_detect.mjs +141 -0
- package/tests/adopt_import.mjs +132 -0
- package/tests/adopt_preflight.mjs +105 -0
- package/tests/anti_grid.mjs +89 -0
- package/tests/api_client.mjs +231 -0
- package/tests/api_docs.mjs +283 -0
- package/tests/api_error_envelope_buildcheck.mjs +56 -0
- package/tests/api_error_envelope_middleware.mjs +99 -0
- package/tests/api_error_registry.mjs +108 -0
- package/tests/api_pagination_contract.mjs +51 -0
- package/tests/api_path_404.mjs +96 -0
- package/tests/app_pair.mjs +168 -0
- package/tests/architect.mjs +140 -0
- package/tests/art_key.mjs +121 -0
- package/tests/art_stage.mjs +121 -0
- package/tests/asset_version.mjs +94 -0
- package/tests/atlas.mjs +69 -0
- package/tests/audit_deps.mjs +74 -0
- package/tests/audit_middleware.mjs +385 -0
- package/tests/auth_admission_gate.mjs +164 -0
- package/tests/auth_anthropic_identity.mjs +95 -0
- package/tests/auth_duplicate_cookie.mjs +56 -0
- package/tests/auth_error_brand.mjs +77 -0
- package/tests/auth_flow_slice1.mjs +223 -0
- package/tests/auth_inactive_builder.mjs +94 -0
- package/tests/auth_page_gate.mjs +92 -0
- package/tests/auth_session_ttl.mjs +155 -0
- package/tests/autonomous_fit_protected_paths.mjs +115 -0
- package/tests/autonomy_gate.mjs +85 -0
- package/tests/autotile_variants.mjs +256 -0
- package/tests/autotile_xverify.mjs +133 -0
- package/tests/backfill_sessions.mjs +101 -0
- package/tests/backfill_task_rewards.mjs +152 -0
- package/tests/backup_cli.mjs +104 -0
- package/tests/bfg.mjs +327 -0
- package/tests/bfg_write.mjs +423 -0
- package/tests/bongos_box_connect.mjs +89 -0
- package/tests/bongos_dispatch.mjs +74 -0
- package/tests/bongos_downloads.mjs +118 -0
- package/tests/bongos_login.mjs +34 -0
- package/tests/box_access.mjs +186 -0
- package/tests/box_auth_check.mjs +114 -0
- package/tests/box_code_staleness.mjs +116 -0
- package/tests/box_connect_e2e.mjs +196 -0
- package/tests/box_cost.mjs +70 -0
- package/tests/box_dns_repoint.mjs +151 -0
- package/tests/box_host_keys.mjs +102 -0
- package/tests/box_onboard.mjs +317 -0
- package/tests/box_scope_session.mjs +115 -0
- package/tests/box_ship_permissions.mjs +71 -0
- package/tests/box_terminal_ssrf.mjs +90 -0
- package/tests/boxes.mjs +1255 -0
- package/tests/branding.mjs +88 -0
- package/tests/bug_attachments.mjs +189 -0
- package/tests/build_info.mjs +88 -0
- package/tests/builder_needs.mjs +99 -0
- package/tests/cap_newcomer_tasks.mjs +114 -0
- package/tests/cascade-dispatch.mjs +95 -0
- package/tests/cf_tunnel.mjs +459 -0
- package/tests/claim_recall.mjs +150 -0
- package/tests/claims_bind_session.mjs +47 -0
- package/tests/claude_materialize.mjs +148 -0
- package/tests/cli_box_scope.mjs +52 -0
- package/tests/cli_lib_formatters.mjs +87 -0
- package/tests/cli_token_reissue.mjs +147 -0
- package/tests/client_bundle_route.mjs +47 -0
- package/tests/cloud_bongos_pack.mjs +85 -0
- package/tests/conductor.mjs +496 -0
- package/tests/confirm_attribution.mjs +156 -0
- package/tests/conflict_resolve.mjs +462 -0
- package/tests/context_pack.mjs +240 -0
- package/tests/cost.mjs +94 -0
- package/tests/cost_audit.mjs +122 -0
- package/tests/cost_budget_status.mjs +78 -0
- package/tests/cost_reconcile.mjs +102 -0
- package/tests/cost_summary_shape.mjs +94 -0
- package/tests/credit_grant.mjs +376 -0
- package/tests/criterion_progress.mjs +220 -0
- package/tests/criterion_proposal.mjs +109 -0
- package/tests/criterion_ref_parse.mjs +53 -0
- package/tests/currency_label.mjs +119 -0
- package/tests/day_cycle.mjs +197 -0
- package/tests/dependencies_migration.mjs +77 -0
- package/tests/dependencies_polymorphic.mjs +212 -0
- package/tests/deploy_instance.mjs +78 -0
- package/tests/devbox_downloads.mjs +122 -0
- package/tests/diff_derived_signals.mjs +115 -0
- package/tests/discord_approvals.mjs +174 -0
- package/tests/discord_autojoin.mjs +158 -0
- package/tests/discord_bot.mjs +94 -0
- package/tests/discord_bugs.mjs +297 -0
- package/tests/discord_channels.mjs +337 -0
- package/tests/discord_foundation.mjs +76 -0
- package/tests/discord_inbound.mjs +190 -0
- package/tests/discord_message_buffer.mjs +87 -0
- package/tests/discord_oauth.mjs +55 -0
- package/tests/discord_reaction.mjs +170 -0
- package/tests/discord_role_sync.mjs +151 -0
- package/tests/display_name.mjs +123 -0
- package/tests/dns_preflight.mjs +74 -0
- package/tests/event_sound_prefs.mjs +194 -0
- package/tests/eviction_policy.mjs +279 -0
- package/tests/figma_adapter.mjs +199 -0
- package/tests/file_map.mjs +91 -0
- package/tests/first_admin_bootstrap.mjs +40 -0
- package/tests/fitness.mjs +435 -0
- package/tests/fitness_two_root.mjs +104 -0
- package/tests/fixtures/session-506-digest.json +34 -0
- package/tests/gate_approvals.mjs +451 -0
- package/tests/gate_review.mjs +152 -0
- package/tests/git_merge_regen.mjs +151 -0
- package/tests/github_push_ancestry.mjs +119 -0
- package/tests/github_push_land_proof.mjs +127 -0
- package/tests/github_push_merge_driver.mjs +175 -0
- package/tests/goal_abuse_matrix.mjs +187 -0
- package/tests/goal_achievement.mjs +146 -0
- package/tests/goal_conflicts.mjs +305 -0
- package/tests/goal_dependencies_read.mjs +73 -0
- package/tests/goal_member_roles.mjs +420 -0
- package/tests/goal_membership_requests.mjs +332 -0
- package/tests/goal_routes.mjs +203 -0
- package/tests/goal_scope_check.mjs +167 -0
- package/tests/goal_tier.mjs +312 -0
- package/tests/goal_wall_proof.mjs +112 -0
- package/tests/grade_attempts.mjs +73 -0
- package/tests/grade_attribution.mjs +117 -0
- package/tests/grade_diff_hash.mjs +191 -0
- package/tests/grade_server_authoritative.mjs +305 -0
- package/tests/grade_unavailable_display.mjs +66 -0
- package/tests/grader.mjs +1960 -0
- package/tests/grader_bypass.mjs +57 -0
- package/tests/grader_panel.mjs +252 -0
- package/tests/grader_prompt_fencing.mjs +192 -0
- package/tests/helpers.mjs +52 -0
- package/tests/hierarchy-config.mjs +81 -0
- package/tests/init.mjs +463 -0
- package/tests/instance_config.mjs +76 -0
- package/tests/instance_manifest.mjs +110 -0
- package/tests/interaction_prefs.mjs +72 -0
- package/tests/key-card.mjs +110 -0
- package/tests/key-status.mjs +45 -0
- package/tests/land_watch.mjs +115 -0
- package/tests/lifecycle-card.mjs +145 -0
- package/tests/lifecycle_breakdown_view.mjs +236 -0
- package/tests/linkify_refs.mjs +264 -0
- package/tests/live_channel.mjs +128 -0
- package/tests/live_sse.mjs +122 -0
- package/tests/llm-cache-route.mjs +110 -0
- package/tests/llm-cache.mjs +140 -0
- package/tests/llm-pricing.mjs +152 -0
- package/tests/local_preview.mjs +136 -0
- package/tests/main_audit.mjs +137 -0
- package/tests/memory_isolation.mjs +431 -0
- package/tests/memory_map.mjs +107 -0
- package/tests/memory_pull.mjs +429 -0
- package/tests/merge_lease.mjs +100 -0
- package/tests/merge_lock.mjs +312 -0
- package/tests/metrics.mjs +147 -0
- package/tests/migration_allocation.mjs +146 -0
- package/tests/migration_namespace.mjs +185 -0
- package/tests/migrations_fresh_replay_guard.mjs +38 -0
- package/tests/mirror_lineage_fulltree.mjs +45 -0
- package/tests/mirror_lineage_proof.mjs +72 -0
- package/tests/mirror_redact.mjs +193 -0
- package/tests/model_alloc_prefs.mjs +63 -0
- package/tests/module-scope-map.mjs +88 -0
- package/tests/module_api.mjs +110 -0
- package/tests/module_cli.mjs +292 -0
- package/tests/module_contributions.mjs +148 -0
- package/tests/module_loader.mjs +286 -0
- package/tests/module_manifest.mjs +194 -0
- package/tests/module_owned_tests.mjs +105 -0
- package/tests/module_provenance.mjs +34 -0
- package/tests/module_route_rank.mjs +147 -0
- package/tests/module_seams.mjs +68 -0
- package/tests/modules.mjs +156 -0
- package/tests/modules_route.mjs +38 -0
- package/tests/newcomer_floor.mjs +17 -0
- package/tests/newcomer_restock.mjs +80 -0
- package/tests/next_number.mjs +154 -0
- package/tests/oauth_secret.mjs +60 -0
- package/tests/onboard.mjs +70 -0
- package/tests/onboarding_gate.mjs +267 -0
- package/tests/onboarding_observability.mjs +344 -0
- package/tests/onboarding_route_signals.mjs +175 -0
- package/tests/onboarding_state.mjs +346 -0
- package/tests/one_active_claim_per_session.mjs +111 -0
- package/tests/orphaned_claim_ship.mjs +135 -0
- package/tests/override_attribution.mjs +142 -0
- package/tests/override_request_ownership.mjs +235 -0
- package/tests/package_core.mjs +435 -0
- package/tests/parallel-load.mjs +402 -0
- package/tests/path_match.mjs +62 -0
- package/tests/permission_path.mjs +151 -0
- package/tests/platform_boot.mjs +167 -0
- package/tests/pre_compact.mjs +60 -0
- package/tests/preview_server.mjs +127 -0
- package/tests/profile_route.mjs +197 -0
- package/tests/provision.mjs +805 -0
- package/tests/provisioning_github_app_routes.mjs +98 -0
- package/tests/public_mirror_export.mjs +124 -0
- package/tests/public_task_resolver.mjs +112 -0
- package/tests/publish_branch_route.mjs +422 -0
- package/tests/publish_manifest.mjs +184 -0
- package/tests/publish_reconciler.mjs +728 -0
- package/tests/rank_gate.mjs +463 -0
- package/tests/rate_limit_public.mjs +217 -0
- package/tests/read_session_export.mjs +89 -0
- package/tests/recall_cli.mjs +82 -0
- package/tests/recall_hints.mjs +119 -0
- package/tests/recent_shipped_window.mjs +80 -0
- package/tests/regrade_eligibility.mjs +202 -0
- package/tests/render_prefs.mjs +122 -0
- package/tests/repo_map.mjs +92 -0
- package/tests/reward_gate.mjs +284 -0
- package/tests/route_rank_check.mjs +243 -0
- package/tests/route_validation.mjs +490 -0
- package/tests/routine_timers.mjs +72 -0
- package/tests/rubric_fixtures.mjs +140 -0
- package/tests/run_routine.mjs +69 -0
- package/tests/sandbox_stage.mjs +582 -0
- package/tests/scrubber_corpus.mjs +257 -0
- package/tests/search_chunker.mjs +209 -0
- package/tests/search_ingest.mjs +96 -0
- package/tests/search_ingest_db.mjs +88 -0
- package/tests/search_isolation.mjs +204 -0
- package/tests/search_route.mjs +95 -0
- package/tests/secret_box.mjs +189 -0
- package/tests/security_docs.mjs +186 -0
- package/tests/security_route.mjs +108 -0
- package/tests/served_paths_shipped.mjs +118 -0
- package/tests/session-cards.mjs +142 -0
- package/tests/session_506_regression.mjs +69 -0
- package/tests/session_caps.mjs +238 -0
- package/tests/session_cost.mjs +133 -0
- package/tests/session_digest.mjs +73 -0
- package/tests/session_eval.mjs +386 -0
- package/tests/session_index.mjs +105 -0
- package/tests/session_records.mjs +369 -0
- package/tests/session_search.mjs +78 -0
- package/tests/session_start_freshness.mjs +123 -0
- package/tests/setup_smoke.mjs +154 -0
- package/tests/ship_autolinkify.mjs +60 -0
- package/tests/ship_automerge_injection.mjs +174 -0
- package/tests/ship_broadcast_routing.mjs +125 -0
- package/tests/ship_broadcast_server.mjs +175 -0
- package/tests/ship_card.mjs +280 -0
- package/tests/ship_ci_deploy.mjs +374 -0
- package/tests/ship_preflight.mjs +368 -0
- package/tests/ship_premerge.mjs +75 -0
- package/tests/ship_resume.mjs +129 -0
- package/tests/ship_upload_skip.mjs +89 -0
- package/tests/ship_zero_commit.mjs +60 -0
- package/tests/single_host_hall.mjs +98 -0
- package/tests/skill_prefs.mjs +150 -0
- package/tests/smoke_preflight.mjs +66 -0
- package/tests/snapshot_picker.mjs +233 -0
- package/tests/sound_prefs.mjs +198 -0
- package/tests/sprite_facing.mjs +35 -0
- package/tests/status-probe.mjs +89 -0
- package/tests/status_backfill.mjs +94 -0
- package/tests/stop_refcheck.mjs +127 -0
- package/tests/streak.mjs +175 -0
- package/tests/subdomain_auth.mjs +58 -0
- package/tests/task_classifier_vocab.mjs +41 -0
- package/tests/task_create_idempotency.mjs +96 -0
- package/tests/task_goal_link.mjs +93 -0
- package/tests/task_ownership_gate.mjs +125 -0
- package/tests/terrain_picker.mjs +298 -0
- package/tests/touches_overlap.mjs +75 -0
- package/tests/triage_development.mjs +140 -0
- package/tests/turn_in_place.mjs +280 -0
- package/tests/upgrade.mjs +357 -0
- package/tests/verify_task_guard.mjs +64 -0
- package/tests/version_close.mjs +76 -0
- package/tests/versions_route.mjs +93 -0
- package/tests/walkability.mjs +188 -0
- package/tests/wandering_prefs.mjs +176 -0
- package/tests/worktree_disambiguation.mjs +105 -0
- package/tests/worktree_guard.mjs +90 -0
|
@@ -0,0 +1,1790 @@
|
|
|
1
|
+
// GDS-V3 task #410 (MAS Phase 2.0) — Quality grader v4 (worker panel).
|
|
2
|
+
//
|
|
3
|
+
// Phase 2.0 of the Multi-Agent System (ADR 0024). v3 was a 3-shot generalist
|
|
4
|
+
// (one prompt, 3 parallel runs, unanimous PASS). v4 trades sampling-variance
|
|
5
|
+
// for lens-specialization: 3 specialist workers replace the 3 shots. Each
|
|
6
|
+
// worker is a distinct prompt focused on one axis; gate is unanimous PASS
|
|
7
|
+
// across the GATING workers (any gating fail blocks; any gating concerns falls
|
|
8
|
+
// through). The Narc (functional_fidelity) is ADVISORY since #481 — its
|
|
9
|
+
// concerns never block and its fail downgrades to manual-confirm; see
|
|
10
|
+
// aggregatePanel.
|
|
11
|
+
//
|
|
12
|
+
// Phase 2.0 ships 2 of the 3 planned workers (narc + quality); 2.1 adds
|
|
13
|
+
// hacker (security), 2.2 adds efficiency_monger. The framework is the
|
|
14
|
+
// load-bearing change here — adding workers later is a one-line plug-in.
|
|
15
|
+
//
|
|
16
|
+
// ## Phase 1 (v3) carry-overs that remain true in v4
|
|
17
|
+
//
|
|
18
|
+
// 1. CROSS-FAMILY. Default grader model is Sonnet 4.6; the assumed builder
|
|
19
|
+
// is Opus. selectGraderModel() flips the model based on the builder's
|
|
20
|
+
// model when known. Each worker inherits the same selection.
|
|
21
|
+
// 2. DEFAULT-FAIL FRAMING. Each worker's prompt is independently default-
|
|
22
|
+
// fail, and each must enumerate concrete issues before any pass verdict.
|
|
23
|
+
//
|
|
24
|
+
// ## What's new in v4
|
|
25
|
+
//
|
|
26
|
+
// 3. WORKER PANEL. gradeSubagent runs DEFAULT_PANEL (narc + quality) in
|
|
27
|
+
// parallel; aggregatePanel applies the gate. Each worker has its own
|
|
28
|
+
// prompt, parser, scoring, and per-worker pass/concerns/fail verdict.
|
|
29
|
+
// 4. STRUCTURED VERDICTS. pass | concerns | fail (not just pass/fail).
|
|
30
|
+
// 'concerns' falls through to the manual-confirm path the same way
|
|
31
|
+
// low-confidence did in v3.
|
|
32
|
+
// 5. SERVER-COMPAT FORWARDING. The grade POST body still uses the v3
|
|
33
|
+
// shape (ff, cq, economy, confidence, issues) so the server's
|
|
34
|
+
// validateAndScore re-validation runs cleanly. The panel's verdict
|
|
35
|
+
// drives the forwarded confidence so the server's gate aligns:
|
|
36
|
+
// pass → forward median confidence (server passes)
|
|
37
|
+
// concerns → forward 0.3 (server fails on low-conf → fallthrough)
|
|
38
|
+
// fail → forward 0 + the failing worker's worst score
|
|
39
|
+
//
|
|
40
|
+
// ## Surface (v4)
|
|
41
|
+
//
|
|
42
|
+
// gradeSubagent({task, valueSummary, diff, changedFiles, diffStats,
|
|
43
|
+
// workers?, builderModel, runOpts})
|
|
44
|
+
// → end-to-end panel grade-result. workers? defaults to DEFAULT_PANEL
|
|
45
|
+
// (narc + quality). Wraps subprocess failures so resolveClaim/applyGrade
|
|
46
|
+
// can always write task_grades.
|
|
47
|
+
//
|
|
48
|
+
// aggregatePanel(workerResults, meta)
|
|
49
|
+
// → final grade-result from per-worker WorkerResults. Applies the
|
|
50
|
+
// any-fail / any-concerns / all-pass gate. Outputs are server-
|
|
51
|
+
// compatible with the v3 grade POST shape.
|
|
52
|
+
//
|
|
53
|
+
// selectGraderModel({builderModel})
|
|
54
|
+
// → unchanged from v3.
|
|
55
|
+
//
|
|
56
|
+
// ## v3 surface (kept exported for diagnostics + back-compat)
|
|
57
|
+
//
|
|
58
|
+
// buildPrompt({...}) — the v3 generalist default-fail prompt. NOT used by
|
|
59
|
+
// the v4 panel; kept for explicit single-prompt diagnostic callers.
|
|
60
|
+
//
|
|
61
|
+
// validateAndScore(rawSubagent, meta) — v3 per-shot validator. Workers
|
|
62
|
+
// have their own (more specific) validators; this remains exported for
|
|
63
|
+
// the legacy single-shot diagnostics path and for tests.
|
|
64
|
+
//
|
|
65
|
+
// aggregateShots(shotResults, meta) — v3 multi-shot aggregator. Same
|
|
66
|
+
// reasoning: kept for diagnostic callers.
|
|
67
|
+
//
|
|
68
|
+
// runSubagent({prompt, opts}) / runOneShot(...) — subprocess plumbing,
|
|
69
|
+
// unchanged from v3. Workers spawn via runSubagent through the workers
|
|
70
|
+
// module (which receives it via dependency injection to avoid a circular
|
|
71
|
+
// require).
|
|
72
|
+
//
|
|
73
|
+
// ## Legacy v1 (still exported)
|
|
74
|
+
//
|
|
75
|
+
// gradeDeterministic(...) — v1 deterministic path, kept for
|
|
76
|
+
// scripts/gds/audit-ships.js (retroactive audit). NOT invoked at ship time.
|
|
77
|
+
|
|
78
|
+
const path = require('node:path');
|
|
79
|
+
const fs = require('node:fs');
|
|
80
|
+
const { spawn } = require('node:child_process');
|
|
81
|
+
|
|
82
|
+
const RUBRIC = JSON.parse(
|
|
83
|
+
fs.readFileSync(path.join(__dirname, 'grader-rubric.json'), 'utf8')
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// V4 = the active panel-based gate. V3 / V1 are kept under their fields for
|
|
87
|
+
// the two paths that still read them: V3 = the trivial-diff multi-shot grader
|
|
88
|
+
// (ship.js #513), V1 = the audit-ships.js retroactive deterministic path. The
|
|
89
|
+
// v2 binding + its rubric block were dead (no reader) and removed in task 466.
|
|
90
|
+
const V4 = RUBRIC.v4;
|
|
91
|
+
const V3 = RUBRIC.v3;
|
|
92
|
+
const V1 = RUBRIC.v1;
|
|
93
|
+
const THRESHOLD = V4.threshold;
|
|
94
|
+
const VERSION = V4.version;
|
|
95
|
+
const LOW_CONFIDENCE = V4.low_confidence_threshold;
|
|
96
|
+
// DEFAULT_SHOTS is kept for the v3 multi-shot path (still exported for
|
|
97
|
+
// diagnostics); v4 doesn't use shots — it uses workers.
|
|
98
|
+
const DEFAULT_SHOTS = V3.shots || 3;
|
|
99
|
+
const ASSUMED_BUILDER_MODEL = V4.assumed_builder_model || 'claude-opus-4-7';
|
|
100
|
+
const DEFAULT_GRADER_MODEL = V4.default_model || 'claude-sonnet-4-6';
|
|
101
|
+
const FALLTHROUGH_CONFIDENCE = V4.fallthrough_confidence ?? 0.3;
|
|
102
|
+
const workers = require('./grader-workers');
|
|
103
|
+
// Carved into modules/grading/ (BV1.R73 / ADR 0091 §1+§4): the two deterministic
|
|
104
|
+
// trust classifiers are KERNEL (KERNEL_FILES, R70) — reached through the doorway,
|
|
105
|
+
// never a deep core require. The worker panel + extract-json are intra-module.
|
|
106
|
+
const api = require('../../src/module-api');
|
|
107
|
+
const routeRankCheck = api.routeRankCheck;
|
|
108
|
+
const permissionPathCheck = api.permissionPathCheck;
|
|
109
|
+
|
|
110
|
+
// Deterministic pre-pass: the sub-Metic permission-path gate (SEC #863). Runs
|
|
111
|
+
// ONLY when the change set touches a permission-sensitive path AND the builder
|
|
112
|
+
// rank resolves below Metic — otherwise null (zero cost / no finding). Like the
|
|
113
|
+
// route-rank pre-pass, the result is a pseudo-worker appended to the panel so
|
|
114
|
+
// aggregatePanel's any-fail gate hard-fails a sub-Metic ship of protected code
|
|
115
|
+
// with no gate-logic change. builderRank comes from the caller's live /me read
|
|
116
|
+
// (ship.js) or req.builder.rank (server); unknown rank fails OPEN here (the
|
|
117
|
+
// post-push main audit, which reads the real author rank from the DB, owns the
|
|
118
|
+
// unknown/dishonest case).
|
|
119
|
+
function permissionPathPrePass(changedFiles, builderRank) {
|
|
120
|
+
const result = permissionPathCheck.checkPermissionPaths({ changedFiles, builderRank });
|
|
121
|
+
if (!result.hardFail) return null;
|
|
122
|
+
return { findings: result.findings, workerResult: permissionPathCheck.toWorkerResult(result) };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Deterministic pre-pass: the route-rank invariant (#475 / C7). Runs ONLY when
|
|
126
|
+
// a src/bongos/routes/ file is in the change set (zero cost otherwise). Returns
|
|
127
|
+
// the findings (fed to the Hacker worker as input) plus a pseudo-worker result
|
|
128
|
+
// appended to the panel so aggregatePanel's any-fail gate handles it directly.
|
|
129
|
+
// Shared verbatim by gradeSubagent (ship-time) and conductor-dispatch.js
|
|
130
|
+
// (mid-flight) so the route-parsing logic lives in exactly one place.
|
|
131
|
+
function routeRankPrePass(changedFiles) {
|
|
132
|
+
const touched = (changedFiles || []).some((f) => routeRankCheck.ROUTE_FILE_RE.test(f));
|
|
133
|
+
if (!touched) return null;
|
|
134
|
+
const result = routeRankCheck.checkRouteRanks();
|
|
135
|
+
return { findings: result.findings, workerResult: routeRankCheck.toWorkerResult(result) };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function r1(n) {
|
|
139
|
+
return Math.round(Number(n) * 10) / 10;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function clamp(n, lo, hi) {
|
|
143
|
+
return Math.max(lo, Math.min(hi, n));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function median(arr) {
|
|
147
|
+
const v = arr.filter((n) => Number.isFinite(n)).slice().sort((a, b) => a - b);
|
|
148
|
+
if (v.length === 0) return null;
|
|
149
|
+
const mid = Math.floor(v.length / 2);
|
|
150
|
+
return v.length % 2 ? r1(v[mid]) : r1((v[mid - 1] + v[mid]) / 2);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------- v4.1: trivial-diff fast-path (#513) ----------
|
|
154
|
+
//
|
|
155
|
+
// The #506 post-mortem: a +1/-1 welcome-copy tweak ran the full 4-worker
|
|
156
|
+
// adversarial Opus/Sonnet panel — disproportionate cost for a one-word change.
|
|
157
|
+
// isTrivialDiff classifies a change as "copy/docs only, small, and touching no
|
|
158
|
+
// logic path". ship.js routes those to a SINGLE generalist grader shot instead
|
|
159
|
+
// of the panel ("lighter, not skipped" — the 1-shot still scores ff+cq+economy
|
|
160
|
+
// +confidence so the server gate applies, and the deterministic scanner + smoke
|
|
161
|
+
// + route-rank checks run regardless). Everything else keeps the full panel.
|
|
162
|
+
//
|
|
163
|
+
// Deliberately conservative — defaults to "not trivial" (full panel) whenever
|
|
164
|
+
// in doubt (missing diffStats, zero files, any logic-path file, oversized diff).
|
|
165
|
+
const TRIVIAL_MAX_FILES = 3;
|
|
166
|
+
const TRIVIAL_MAX_LINES = 30;
|
|
167
|
+
// Copy/docs/front-end surfaces eligible for the lighter single-shot grade.
|
|
168
|
+
// (Both web surfaces moved under modules/<key>/public/: the status dashboard to
|
|
169
|
+
// modules/status-ui/public/ in BV1.R84 and the hall to modules/hall-ui/public/ in
|
|
170
|
+
// BV1.R85.)
|
|
171
|
+
const TRIVIAL_PATH_RE = /(?:^|\/)[^/]+\.md$|^docs\/|^public\/|^modules\/hall-ui\/public\/|^modules\/status-ui\/public\//;
|
|
172
|
+
// Logic / infra / test surfaces that force the full panel even on a tiny diff —
|
|
173
|
+
// these are where a subtle one-liner can do real damage, so they never qualify.
|
|
174
|
+
const LOGIC_PATH_RE = /^src\/|^scripts\/|^migrations\/|^server\.js$|^tests\/|^\.claude\//;
|
|
175
|
+
// Executable code AND config are NEVER trivial, even under public*/ (#513
|
|
176
|
+
// Hacker). A copy tweak and an XSS / client-authz regression are
|
|
177
|
+
// indistinguishable to a path+size heuristic, and the builders-hall + game
|
|
178
|
+
// front-ends carry auth flows and game logic — so any code extension forces the
|
|
179
|
+
// full adversarial panel. Config/data files (.json/.yaml/.env/…) are excluded
|
|
180
|
+
// too: a client-side config change is security-relevant and shouldn't get the
|
|
181
|
+
// lighter shot. Only genuinely static content (markdown, CSS/HTML, images,
|
|
182
|
+
// plain text) takes the single shot.
|
|
183
|
+
// HTML and SVG are executable in a browser (inline <script>, on* handlers,
|
|
184
|
+
// <svg onload>), so they are NOT "static" for grading purposes — a small
|
|
185
|
+
// edit could introduce client-side XSS that only the full panel's Hacker
|
|
186
|
+
// would catch. Excluded alongside code + config (#513 Hacker).
|
|
187
|
+
const NON_TRIVIAL_EXT_RE = /\.(?:js|mjs|cjs|jsx|ts|tsx|py|sh|bash|rb|go|sql|json|ya?ml|toml|ini|env|html?|svg)$|(?:^|\/)\.env(?:\.|$)/;
|
|
188
|
+
|
|
189
|
+
function isTrivialDiff({ changedFiles, diffStats } = {}) {
|
|
190
|
+
if (!diffStats || typeof diffStats !== 'object') return false;
|
|
191
|
+
const files = Array.isArray(changedFiles) ? changedFiles.filter(Boolean) : [];
|
|
192
|
+
if (files.length === 0) return false;
|
|
193
|
+
const fc = Number(diffStats.files_changed);
|
|
194
|
+
const lines = (Number(diffStats.lines_added) || 0) + (Number(diffStats.lines_removed) || 0);
|
|
195
|
+
// Cap on BOTH the shortstat count and the actual file-list length — a rename
|
|
196
|
+
// can disagree between the two, so take the stricter of the two (#513 nit).
|
|
197
|
+
if (!Number.isFinite(fc) || fc <= 0 || fc > TRIVIAL_MAX_FILES) return false;
|
|
198
|
+
if (files.length > TRIVIAL_MAX_FILES) return false;
|
|
199
|
+
if (lines <= 0 || lines > TRIVIAL_MAX_LINES) return false;
|
|
200
|
+
// Every changed file must be a copy/docs path, none in a logic path, and none
|
|
201
|
+
// an executable-code or config file (those always get the full panel).
|
|
202
|
+
return files.every((f) =>
|
|
203
|
+
TRIVIAL_PATH_RE.test(f) && !LOGIC_PATH_RE.test(f) && !NON_TRIVIAL_EXT_RE.test(f));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ---------- v3: cross-family model selection (ADR 0024) ----------
|
|
207
|
+
|
|
208
|
+
// Returns the grader model given the builder's model — a DIFFERENT family from
|
|
209
|
+
// the builder is the cross-family adversarial guarantee. With no builderModel
|
|
210
|
+
// the caller is assuming the default Opus builder (CLAUDE.md §2), so it resolves
|
|
211
|
+
// through the opus branch to Sonnet — that is the legitimate default path the
|
|
212
|
+
// live callers (selectGraderModel({})) take.
|
|
213
|
+
//
|
|
214
|
+
// #117: an EXPLICITLY-supplied model we don't recognize is a misconfiguration,
|
|
215
|
+
// not a default. We used to silently fall back to Sonnet, which would mask a
|
|
216
|
+
// future model family (one whose id lacks 'opus'/'sonnet'/'haiku') and quietly
|
|
217
|
+
// grade it on the wrong cross-family assumption. Throw loudly instead so the
|
|
218
|
+
// caller fixes the input rather than getting a silently-wrong grader model.
|
|
219
|
+
function selectGraderModel({ builderModel } = {}) {
|
|
220
|
+
const bm = (builderModel || ASSUMED_BUILDER_MODEL).toLowerCase();
|
|
221
|
+
if (bm.includes('opus')) return 'claude-sonnet-4-6';
|
|
222
|
+
if (bm.includes('sonnet')) return 'claude-opus-4-7';
|
|
223
|
+
if (bm.includes('haiku')) return 'claude-opus-4-7';
|
|
224
|
+
throw new Error(
|
|
225
|
+
`selectGraderModel: unrecognized builder model ${JSON.stringify(builderModel)} — ` +
|
|
226
|
+
`expected an id containing 'opus', 'sonnet', or 'haiku'. Refusing to silently ` +
|
|
227
|
+
`fall back to a default grader model (#117).`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---------- v3: prompt builder (default-fail framing) ----------
|
|
232
|
+
|
|
233
|
+
// The instance's project name, injected into the grader's identity line so a
|
|
234
|
+
// non-OTB Cloud Bongos instance is graded against ITS OWN project, never a
|
|
235
|
+
// hardcoded "Off the Boats" (ADR 0062 §3/§5; the branding contract's
|
|
236
|
+
// llmProjectName field). Reached through the doorway like every other host-
|
|
237
|
+
// context read in this module — never a deep core require. Resolved once at
|
|
238
|
+
// module load (the brand is a per-process singleton); falls back to a neutral
|
|
239
|
+
// phrase if branding fails to resolve so the gate never crashes on a config error.
|
|
240
|
+
const LLM_PROJECT = (() => {
|
|
241
|
+
try { return api.branding().llmProjectName || 'this project'; }
|
|
242
|
+
catch { return 'this project'; }
|
|
243
|
+
})();
|
|
244
|
+
|
|
245
|
+
const PROMPT_HEADER = `You are an adversarial code-review subagent for the ${LLM_PROJECT} project. You are reviewing one shipped change to decide whether it deserves to ship.
|
|
246
|
+
|
|
247
|
+
Your job is to FIND WHAT IS WRONG with this change. **The default verdict is FAIL.** To return a PASS, you must affirmatively conclude — after a careful read of the diff and (using your Read tool) the relevant post-merge files — that no material problems remain.
|
|
248
|
+
|
|
249
|
+
Begin by hunting for these specific failure modes. When you find one, CITE the diff line or name the failing acceptance criterion:
|
|
250
|
+
|
|
251
|
+
- TODOs, FIXMEs, "for now", "temporary" or "stub" comments left in shipped code.
|
|
252
|
+
- Hardcoded shortcuts: assuming one builder, one user, one project root, one tenant, one environment, or one identity. Look for literal "lars", "lars589", absolute /Users paths, hardcoded ports, hardcoded IDs.
|
|
253
|
+
- Scope gaps: acceptance criteria in the description that the diff DOES NOT satisfy. Read the description's "Acceptance" section line by line.
|
|
254
|
+
- Demo / dummy / mock code that lies: returns a fixed value, fakes a successful result, mocks at the wrong layer, or pretends to call something it doesn't.
|
|
255
|
+
- Missing tests for new logic: any non-trivial new function, route handler, branch, or aggregation that ships without a covering test.
|
|
256
|
+
- Broken invariants: methodology rules in CLAUDE.md, cross-verified Python/JS autotile lookups, ground-vs-object cell split per ADR 0012, the "every change is backed by a claimed GDS task" rule, the trust-boundary rule per ADR 0016 ("server-enforced permissions"), the no-hardcoded-Archon-identity rule per V3.R12 #236.
|
|
257
|
+
- Dead code: helpers, exports, branches, or files added but never called from anywhere in the post-merge tree. Use Read on the receiving site if unsure.
|
|
258
|
+
- Reads/writes outside the declared touches[]: if the diff edits files the task didn't claim, that's a scope creep — call it out.
|
|
259
|
+
|
|
260
|
+
You return three scores (0-10), notes, an issues array, and a confidence value:
|
|
261
|
+
|
|
262
|
+
1. functional_fidelity (0-10): did the diff implement what the description scoped? 10 = every acceptance criterion satisfied by code or explicitly out-of-scope per the description. 6 = mostly there with one NAMED gap. Below 6 = scope gaps, broken acceptance criteria, or demo code that lies.
|
|
263
|
+
2. code_quality (0-10): is the code well-written for THIS codebase? 10 = production-quality, would-merge-without-comment. 6 = ships but with a clear improvement a reviewer would suggest. Below 6 = broken style, dead code, broken invariants, missing tests where warranted.
|
|
264
|
+
3. economy (0-10, ADVISORY): is the change as small and simple as the task warrants? 10 = minimal and focused. 6 = a bit more than necessary. Below 6 = bloated, speculative abstraction, duplicated logic.
|
|
265
|
+
|
|
266
|
+
**The hard gate** (applied downstream, you do not enforce it yourself; just score honestly): functional_fidelity >= 6 AND code_quality >= 6 AND confidence >= 0.4. Below 6 on either axis is a FAIL. Economy is advisory only.
|
|
267
|
+
|
|
268
|
+
**For every issue you list:** include severity (blocker | major | minor | nit), the file path, and a one-line summary that quotes the offending diff line or names the failing acceptance criterion. Vague summaries like "code quality could be better" are not acceptable — be specific or omit it.
|
|
269
|
+
|
|
270
|
+
**Sanity checks:**
|
|
271
|
+
- A PASS with no issues[] entries is suspicious. If you found nothing material wrong, briefly say WHY in notes_md: which acceptance criteria you verified, which files you Read, which could-have-gone-wrong didn't.
|
|
272
|
+
- A FAIL with no issues[] entries is invalid. At least one concrete issue must explain the verdict.
|
|
273
|
+
- A score below 6 with no issue at severity major or blocker is inconsistent — name the major problem.
|
|
274
|
+
|
|
275
|
+
**Confidence:** 0-1, your self-reported confidence in your two scores. If you cannot judge (description too vague to grade against, diff too large to fit comfortably in context, key context missing), set confidence LOW (< 0.4). Low confidence falls through to manual confirm — be honest about uncertainty rather than guessing.
|
|
276
|
+
|
|
277
|
+
Return ONLY JSON matching this schema — no prose before or after, no markdown fences:
|
|
278
|
+
{
|
|
279
|
+
"functional_fidelity": <number 0-10>,
|
|
280
|
+
"code_quality": <number 0-10>,
|
|
281
|
+
"economy": <number 0-10>,
|
|
282
|
+
"notes_md": <string>,
|
|
283
|
+
"confidence": <number 0-1>,
|
|
284
|
+
"issues": [{"severity": "blocker"|"major"|"minor"|"nit", "file": <path>, "summary": <string>}, ...]
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
You have a Read tool. Spend reads checking that removed code is unreferenced elsewhere, that new helpers match surrounding style, that the diff's value_summary matches what the diff actually does, and that acceptance criteria you can't verify from the diff alone are actually satisfied in the post-merge file. Do not run tests, do not edit files, do not call the network.`;
|
|
288
|
+
|
|
289
|
+
function buildPrompt({ task, valueSummary, diff, changedFiles, diffStats }) {
|
|
290
|
+
const t = task || {};
|
|
291
|
+
const desc = (t.description || '(no description)').trim();
|
|
292
|
+
const title = (t.title || '(no title)').trim();
|
|
293
|
+
const vs = (valueSummary || '(no value summary)').trim();
|
|
294
|
+
const files = Array.isArray(changedFiles) ? changedFiles : [];
|
|
295
|
+
const diffText = (diff || '(empty diff)').trim();
|
|
296
|
+
|
|
297
|
+
const lines = [];
|
|
298
|
+
lines.push(PROMPT_HEADER);
|
|
299
|
+
lines.push('');
|
|
300
|
+
lines.push('---');
|
|
301
|
+
lines.push('## Task');
|
|
302
|
+
lines.push('');
|
|
303
|
+
lines.push(`**ID:** ${t.id ?? '(unknown)'}`);
|
|
304
|
+
lines.push(`**Title:** ${title}`);
|
|
305
|
+
lines.push(`**Kind:** ${t.kind ?? 'unclassified'} · **Discipline:** ${t.discipline ?? 'unclassified'}`);
|
|
306
|
+
lines.push(`**Estimated effort:** ${t.est_minutes != null ? `${t.est_minutes} min` : '(not estimated)'} — use this to judge whether the diff size is proportionate to the work scoped.`);
|
|
307
|
+
if (t.version_id) lines.push(`**Version:** ${t.version_id}`);
|
|
308
|
+
lines.push('');
|
|
309
|
+
lines.push('**Description:**');
|
|
310
|
+
lines.push('');
|
|
311
|
+
lines.push(desc);
|
|
312
|
+
lines.push('');
|
|
313
|
+
lines.push('---');
|
|
314
|
+
lines.push('## Builder\'s value summary');
|
|
315
|
+
lines.push('');
|
|
316
|
+
lines.push(vs);
|
|
317
|
+
lines.push('');
|
|
318
|
+
lines.push('---');
|
|
319
|
+
lines.push(`## Changed files (${files.length})`);
|
|
320
|
+
lines.push('');
|
|
321
|
+
for (const f of files) lines.push(`- ${f}`);
|
|
322
|
+
if (files.length === 0) lines.push('(no files changed — this is suspicious)');
|
|
323
|
+
lines.push('');
|
|
324
|
+
lines.push('You can Read any of these files to see their post-merge state.');
|
|
325
|
+
lines.push('');
|
|
326
|
+
lines.push('---');
|
|
327
|
+
lines.push('## Diff size (for the economy axis)');
|
|
328
|
+
lines.push('');
|
|
329
|
+
if (diffStats && typeof diffStats === 'object') {
|
|
330
|
+
const fc = diffStats.files_changed ?? '?';
|
|
331
|
+
const la = diffStats.lines_added ?? '?';
|
|
332
|
+
const lr = diffStats.lines_removed ?? '?';
|
|
333
|
+
lines.push(`- files changed: ${fc}`);
|
|
334
|
+
lines.push(`- lines added: ${la}`);
|
|
335
|
+
lines.push(`- lines removed: ${lr}`);
|
|
336
|
+
lines.push('');
|
|
337
|
+
lines.push('Weigh this against the task description, kind, and estimated effort above when scoring economy. A diff much larger than the scoped work warrants — or one padded with speculative abstraction, duplication, or dead code — should score low on economy.');
|
|
338
|
+
} else {
|
|
339
|
+
lines.push('(diff stats unavailable — score economy from the diff itself.)');
|
|
340
|
+
}
|
|
341
|
+
lines.push('');
|
|
342
|
+
lines.push('---');
|
|
343
|
+
lines.push('## Unified diff');
|
|
344
|
+
lines.push('');
|
|
345
|
+
lines.push('```diff');
|
|
346
|
+
lines.push(diffText);
|
|
347
|
+
lines.push('```');
|
|
348
|
+
lines.push('');
|
|
349
|
+
lines.push('Now grade. Default-fail unless your read of the diff and post-merge files turns up no material problems. Output ONLY the JSON object — nothing before or after it.');
|
|
350
|
+
return lines.join('\n');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ---------- v3: per-shot validator (same as v2 logic) ----------
|
|
354
|
+
|
|
355
|
+
// Tries to extract a JSON object from a subagent response. The subagent is
|
|
356
|
+
// instructed to return ONLY JSON, but in practice models sometimes wrap it
|
|
357
|
+
// in markdown fences or add a line of prose. Be tolerant.
|
|
358
|
+
// #79 (#380, completed #493): robust JSON extraction now lives in the shared
|
|
359
|
+
// leaf module so grader.js AND every worker module use ONE implementation.
|
|
360
|
+
// Re-exported here for back-compat (validateAndScore + tests use
|
|
361
|
+
// grader.extractJson).
|
|
362
|
+
const { extractJson } = require('./grader-workers/extract-json');
|
|
363
|
+
|
|
364
|
+
function isNum(n) {
|
|
365
|
+
return Number.isFinite(n);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Validates ONE shot's subagent JSON and produces a per-shot grade-result.
|
|
369
|
+
// Never throws — malformed inputs produce a passed=false result with notes_md.
|
|
370
|
+
// The downstream aggregateShots collapses N of these into a final verdict.
|
|
371
|
+
function validateAndScore(rawSubagent, meta = {}) {
|
|
372
|
+
const parsed =
|
|
373
|
+
typeof rawSubagent === 'string' ? extractJson(rawSubagent) : rawSubagent;
|
|
374
|
+
|
|
375
|
+
const signals = {
|
|
376
|
+
grader_kind: 'subagent',
|
|
377
|
+
grader_version: VERSION,
|
|
378
|
+
model: meta.model || DEFAULT_GRADER_MODEL,
|
|
379
|
+
threshold: THRESHOLD,
|
|
380
|
+
...meta,
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
384
|
+
return {
|
|
385
|
+
score: 0,
|
|
386
|
+
passed: false,
|
|
387
|
+
threshold: THRESHOLD,
|
|
388
|
+
grader_kind: 'subagent',
|
|
389
|
+
grader_version: VERSION,
|
|
390
|
+
rubric_scores: {},
|
|
391
|
+
signals: { ...signals, parse_error: true },
|
|
392
|
+
notes_md: `Subagent grader ${VERSION}: could not parse subagent output as JSON.`,
|
|
393
|
+
issues: [],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const ff = Number(parsed.functional_fidelity);
|
|
398
|
+
const cq = Number(parsed.code_quality);
|
|
399
|
+
const economyRaw = Number(parsed.economy);
|
|
400
|
+
const economy =
|
|
401
|
+
isNum(economyRaw) && economyRaw >= 0 && economyRaw <= 10 ? economyRaw : null;
|
|
402
|
+
// MAS Phase 2.5 (#423): security_score (from Hacker, Phase 2.1) and
|
|
403
|
+
// efficiency_score (from Efficiency Monger, Phase 2.2). Both ADVISORY —
|
|
404
|
+
// recorded in rubric_scores but do not independently gate the ship. Same
|
|
405
|
+
// back-compat shape as economy: out-of-range / non-numeric / missing →
|
|
406
|
+
// recorded as null, no penalty. Older callers that don't forward these
|
|
407
|
+
// fields grade exactly as before.
|
|
408
|
+
const securityRaw = Number(parsed.security_score);
|
|
409
|
+
const securityScore =
|
|
410
|
+
isNum(securityRaw) && securityRaw >= 0 && securityRaw <= 10 ? securityRaw : null;
|
|
411
|
+
const efficiencyRaw = Number(parsed.efficiency_score);
|
|
412
|
+
const efficiencyScore =
|
|
413
|
+
isNum(efficiencyRaw) && efficiencyRaw >= 0 && efficiencyRaw <= 10 ? efficiencyRaw : null;
|
|
414
|
+
// Treat null/undefined confidence as ABSENT, not as 0. Number(null) === 0,
|
|
415
|
+
// and isNum(0) === true — a naive `isNum(Number(parsed.confidence))` check
|
|
416
|
+
// would accept null as a valid low-confidence signal and FAIL the ship for
|
|
417
|
+
// a missing field. The MAS Phase 1 self-ship (#395) hit this exactly when
|
|
418
|
+
// aggregateShots forwarded a null top-level confidence to the server's
|
|
419
|
+
// validateAndScore. Explicit null-check first; coerce only when present.
|
|
420
|
+
const confidence = parsed.confidence == null
|
|
421
|
+
? null
|
|
422
|
+
: isNum(Number(parsed.confidence)) ? Number(parsed.confidence) : null;
|
|
423
|
+
const issues = Array.isArray(parsed.issues)
|
|
424
|
+
? parsed.issues.filter((it) => it && typeof it === 'object').slice(0, 50)
|
|
425
|
+
: [];
|
|
426
|
+
const notesFromSubagent = typeof parsed.notes_md === 'string' ? parsed.notes_md : '';
|
|
427
|
+
|
|
428
|
+
signals.confidence = confidence;
|
|
429
|
+
signals.issue_count = issues.length;
|
|
430
|
+
|
|
431
|
+
if (!isNum(ff) || !isNum(cq) || ff < 0 || ff > 10 || cq < 0 || cq > 10) {
|
|
432
|
+
return {
|
|
433
|
+
score: 0,
|
|
434
|
+
passed: false,
|
|
435
|
+
threshold: THRESHOLD,
|
|
436
|
+
grader_kind: 'subagent',
|
|
437
|
+
grader_version: VERSION,
|
|
438
|
+
rubric_scores: {},
|
|
439
|
+
signals: { ...signals, schema_error: true },
|
|
440
|
+
notes_md: `Subagent grader ${VERSION}: subagent returned scores outside [0,10] or non-numeric. Got functional_fidelity=${parsed.functional_fidelity}, code_quality=${parsed.code_quality}.`,
|
|
441
|
+
issues,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const ffR = r1(clamp(ff, 0, 10));
|
|
446
|
+
const cqR = r1(clamp(cq, 0, 10));
|
|
447
|
+
const economyR = economy != null ? r1(clamp(economy, 0, 10)) : null;
|
|
448
|
+
const securityR = securityScore != null ? r1(clamp(securityScore, 0, 10)) : null;
|
|
449
|
+
const efficiencyR = efficiencyScore != null ? r1(clamp(efficiencyScore, 0, 10)) : null;
|
|
450
|
+
const economyPenalty =
|
|
451
|
+
economyR != null ? Math.max(0, (THRESHOLD - economyR) / 2) : 0;
|
|
452
|
+
const cqEffective = economyR != null ? r1(clamp(cqR - economyPenalty, 0, 10)) : cqR;
|
|
453
|
+
const blended = r1((ffR + cqR) / 2);
|
|
454
|
+
|
|
455
|
+
// confidence must be present (not null) for a PASS. A subagent that omits
|
|
456
|
+
// confidence entirely silently bypassed the low-confidence gate under the
|
|
457
|
+
// old `confidence != null && confidence < LOW_CONFIDENCE` check — null made
|
|
458
|
+
// lowConfidence=false and let high ff/cq scores through unchecked (#489 / #56).
|
|
459
|
+
// Distinguishing "absent" from "low" gives a clear verdict message and leaves
|
|
460
|
+
// the aggregateShots #395 fix intact: shots that omit confidence now FAIL here,
|
|
461
|
+
// so aggregateShots can never reach unanimousPASS with a null confidenceMedian.
|
|
462
|
+
const confidenceAbsent = confidence === null;
|
|
463
|
+
const lowConfidence = !confidenceAbsent && confidence < LOW_CONFIDENCE;
|
|
464
|
+
const bothPass = ffR >= THRESHOLD && cqR >= THRESHOLD;
|
|
465
|
+
const passed = bothPass && !lowConfidence && !confidenceAbsent;
|
|
466
|
+
|
|
467
|
+
const verdict = passed
|
|
468
|
+
? 'PASS'
|
|
469
|
+
: confidenceAbsent
|
|
470
|
+
? 'FAIL — confidence absent'
|
|
471
|
+
: lowConfidence
|
|
472
|
+
? 'FAIL — low confidence'
|
|
473
|
+
: bothPass
|
|
474
|
+
? 'FAIL'
|
|
475
|
+
: ffR < THRESHOLD && cqR < THRESHOLD
|
|
476
|
+
? `FAIL — both axes below ${THRESHOLD}`
|
|
477
|
+
: ffR < THRESHOLD
|
|
478
|
+
? `FAIL — functional_fidelity below ${THRESHOLD}`
|
|
479
|
+
: `FAIL — code_quality below ${THRESHOLD}`;
|
|
480
|
+
|
|
481
|
+
const notesLines = [];
|
|
482
|
+
notesLines.push(`Shot ${signals.shot_index != null ? signals.shot_index : '?'} (${signals.model}) — ${verdict}`);
|
|
483
|
+
notesLines.push(` ff=${ffR}/10 cq=${cqR}/10${economyR != null ? ` econ=${economyR}/10 (advisory)` : ''} conf=${confidence ?? '?'}`);
|
|
484
|
+
if (notesFromSubagent) {
|
|
485
|
+
notesLines.push('');
|
|
486
|
+
notesLines.push(notesFromSubagent.trim());
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
score: blended,
|
|
491
|
+
passed,
|
|
492
|
+
threshold: THRESHOLD,
|
|
493
|
+
grader_kind: 'subagent',
|
|
494
|
+
grader_version: VERSION,
|
|
495
|
+
rubric_scores: {
|
|
496
|
+
functional_fidelity: ffR,
|
|
497
|
+
code_quality: cqR,
|
|
498
|
+
economy: economyR,
|
|
499
|
+
code_quality_effective: cqEffective,
|
|
500
|
+
security_score: securityR,
|
|
501
|
+
efficiency_score: efficiencyR,
|
|
502
|
+
},
|
|
503
|
+
signals,
|
|
504
|
+
notes_md: notesLines.join('\n'),
|
|
505
|
+
issues,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// ---------- server-authoritative grade guards (SR-9 / SR-18 / F1 / G5) ----------
|
|
510
|
+
//
|
|
511
|
+
// THE FINDING (SR-9 / SR-18): POST /api/gds/tasks/:id/grade trusts the client.
|
|
512
|
+
// validateAndScore only shape-validates the numbers — `passed` is computed from
|
|
513
|
+
// the CLIENT's submitted scores. The real adversarial panel + the deterministic
|
|
514
|
+
// route-rank / permission-path pre-passes run CLIENT-SIDE in scripts/gds/ship.js;
|
|
515
|
+
// the server never re-runs them. So a builder can POST
|
|
516
|
+
// {functional_fidelity:10, code_quality:10, confidence:1, issues:[]} to self-pass.
|
|
517
|
+
// AND the credit/achievement side-effects (net-negative "Subtractor" bonus,
|
|
518
|
+
// shipper badges) are driven by client-supplied signals.diff_stats +
|
|
519
|
+
// committed_files the server never re-derives (SR-18).
|
|
520
|
+
//
|
|
521
|
+
// WHAT THE SERVER CAN AND CAN'T DO (the boundary — see report):
|
|
522
|
+
// * The web tier is checked out at `main`. The builder's feature-branch
|
|
523
|
+
// commits are NOT on the server at grade time (they only arrive when the
|
|
524
|
+
// task merges to main). So the server CANNOT independently re-derive the
|
|
525
|
+
// committed diff at the claimed head_sha. The diff, diff_stats, and
|
|
526
|
+
// committed_files in the request body remain CLIENT-SUPPLIED.
|
|
527
|
+
// * BUT the server DOES know the caller's LIVE, authoritative rank
|
|
528
|
+
// (req.builder.rank, read fresh from the DB per request — ADR 0016). So the
|
|
529
|
+
// deterministic permission-path pre-pass can run with a TRUSTED rank against
|
|
530
|
+
// the (client-supplied) committed_files — strictly raising the bar.
|
|
531
|
+
// * The route-rank pre-pass reads the route files in the server's OWN
|
|
532
|
+
// checkout (fs) — fully server-authoritative, independent of any client
|
|
533
|
+
// input. (NB: it audits `main`, not the feature branch — the post-push main
|
|
534
|
+
// audit owns feature-branch route changes. Re-running here still hard-fails
|
|
535
|
+
// a forged pass while `main` already carries an ungated write route.)
|
|
536
|
+
//
|
|
537
|
+
// THE GUARD (additive, fail-toward-reject-only): serverSideGuards re-runs the
|
|
538
|
+
// two deterministic pre-passes server-side and applies plausibility/bounding
|
|
539
|
+
// checks. It can ONLY downgrade a client PASS to a non-pass (hard FAIL or
|
|
540
|
+
// manual-confirm); it NEVER upgrades a fail, never relaxes the existing gate,
|
|
541
|
+
// and is a no-op on a client that already reported not-passed. A legitimate
|
|
542
|
+
// honest grade is unaffected.
|
|
543
|
+
//
|
|
544
|
+
// The Archon / GRADER_BYPASS paths do not flow through /grade's passed-gate the
|
|
545
|
+
// way an ordinary ship does (they confirm via /tasks/:id/confirm, which is a
|
|
546
|
+
// separate route), so this guard does not touch them — see the route comment.
|
|
547
|
+
|
|
548
|
+
// Plausibility threshold: a client PASS that reports NO issues on a NON-TRIVIAL
|
|
549
|
+
// diff is not a credible adversarial-panel verdict — the panel is default-fail
|
|
550
|
+
// and every worker must enumerate concrete issues before a pass; a clean pass on
|
|
551
|
+
// a large diff with an empty issues[] is the exact shape of a forged
|
|
552
|
+
// {ff:10,cq:10,issues:[]} self-pass (SR-9). We don't reject it outright (a small
|
|
553
|
+
// honest change can legitimately have no issues) — we DOWNGRADE it to
|
|
554
|
+
// manual-confirm so a human lands it via /confirm. Thresholds mirror the
|
|
555
|
+
// trivial-diff fast-path's spirit (a change past these sizes is "real work"):
|
|
556
|
+
// - more than this many changed files, OR
|
|
557
|
+
// - more than this many added+removed lines
|
|
558
|
+
// makes an empty-issues PASS implausible-as-automatic and forces manual-confirm.
|
|
559
|
+
const IMPLAUSIBLE_PASS_MAX_FILES = TRIVIAL_MAX_FILES; // 3 — same "small change" bar
|
|
560
|
+
const IMPLAUSIBLE_PASS_MAX_LINES = TRIVIAL_MAX_LINES; // 30
|
|
561
|
+
|
|
562
|
+
// Re-derive the size of the change from the submitted committed_files +
|
|
563
|
+
// diff_stats. Both are client-supplied (see boundary note) — we take the
|
|
564
|
+
// STRICTER (larger) of the file-list length and the shortstat files_changed so a
|
|
565
|
+
// forger can't shrink one to dodge the plausibility bar while the other betrays
|
|
566
|
+
// a large diff. Returns { files, lines } with NaN coerced to 0.
|
|
567
|
+
function changeMagnitude({ committedFiles, diffStats } = {}) {
|
|
568
|
+
const listLen = Array.isArray(committedFiles)
|
|
569
|
+
? committedFiles.filter((f) => typeof f === 'string' && f.trim()).length
|
|
570
|
+
: 0;
|
|
571
|
+
const statFiles = diffStats && Number.isFinite(Number(diffStats.files_changed))
|
|
572
|
+
? Number(diffStats.files_changed)
|
|
573
|
+
: 0;
|
|
574
|
+
const files = Math.max(listLen, statFiles);
|
|
575
|
+
const lines =
|
|
576
|
+
(Number(diffStats?.lines_added) || 0) + (Number(diffStats?.lines_removed) || 0);
|
|
577
|
+
return { files, lines };
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// serverSideGuards — the server-authoritative hardening layer (SR-9 / SR-18).
|
|
581
|
+
//
|
|
582
|
+
// Inputs:
|
|
583
|
+
// gradeResult — the output of validateAndScore on the SUBMITTED body. Its
|
|
584
|
+
// `passed` is computed from the client's numbers. We may
|
|
585
|
+
// override it (downgrade only).
|
|
586
|
+
// committedFiles — client-supplied list of committed paths (or null).
|
|
587
|
+
// builderRank — the caller's LIVE rank (req.builder.rank — SERVER-KNOWN,
|
|
588
|
+
// authoritative). Drives the permission-path pre-pass.
|
|
589
|
+
// diffStats — client-supplied { files_changed, lines_added, lines_removed }
|
|
590
|
+
// (gradeResult.signals.diff_stats if not passed explicitly).
|
|
591
|
+
// routesDir — optional override for tests; defaults to the live routes dir.
|
|
592
|
+
//
|
|
593
|
+
// Returns a NEW grade-result (never mutates the input). When a guard trips, the
|
|
594
|
+
// returned result has passed=false and a `server_guard` block in signals naming
|
|
595
|
+
// every triggered guard + the residual notes. When no guard trips, the input is
|
|
596
|
+
// returned with a passed-through `server_guard: { applied: [...], ok: true }`
|
|
597
|
+
// audit marker so the trail shows the guards RAN (and found nothing) rather than
|
|
598
|
+
// being silently absent.
|
|
599
|
+
//
|
|
600
|
+
// CONTRACT: downgrade-only. If the client already reported not-passed, we leave
|
|
601
|
+
// it not-passed (no resurrection). We only ever flip true→false.
|
|
602
|
+
function serverSideGuards(gradeResult, { committedFiles, builderRank, diffStats, routesDir } = {}) {
|
|
603
|
+
const base = gradeResult || {};
|
|
604
|
+
const ds = diffStats || base.signals?.diff_stats || null;
|
|
605
|
+
const guardsTripped = []; // [{ guard, severity, summary }]
|
|
606
|
+
const extraIssues = []; // surfaced into the result's issues[]
|
|
607
|
+
let downgradeTo = null; // 'fail' | 'concerns' | null
|
|
608
|
+
|
|
609
|
+
// The guards only have teeth on a CLIENT PASS — a non-pass is already
|
|
610
|
+
// blocked/fallen-through by the existing gate, and downgrade-only means we
|
|
611
|
+
// never touch it. We still RUN the deterministic checks (their findings are a
|
|
612
|
+
// useful audit signal even on a fail) but only let them flip the verdict when
|
|
613
|
+
// base.passed === true.
|
|
614
|
+
const clientPassed = base.passed === true;
|
|
615
|
+
|
|
616
|
+
// --- Guard 1: deterministic pre-passes, server-side (SR-9 / F1) ---
|
|
617
|
+
// route-rank: reads the server's own route files (fs) — server-authoritative.
|
|
618
|
+
// permission-path: runs with the TRUSTED live rank against the (client) files.
|
|
619
|
+
// Either hard-fail flips a client PASS to a hard FAIL regardless of submitted
|
|
620
|
+
// scores — this is the core "re-run the deterministic checks server-side"
|
|
621
|
+
// hardening. A non-hardFail (e.g. an unclassified READ route → 'concerns') is
|
|
622
|
+
// recorded but does NOT block here: the client-side panel's Hacker already
|
|
623
|
+
// adjudicates read-route intent, and a server-side downgrade-to-concerns on a
|
|
624
|
+
// read route would be a behavior change with no SR-9 security value.
|
|
625
|
+
let routeRank = null;
|
|
626
|
+
try {
|
|
627
|
+
routeRank = routeRankCheck.checkRouteRanks(routesDir ? { routesDir } : {});
|
|
628
|
+
} catch (_) { routeRank = null; /* fs error → no server-side route finding; client panel + main audit still gate */ }
|
|
629
|
+
const permPath = permissionPathCheck.checkPermissionPaths({ changedFiles: committedFiles || [], builderRank });
|
|
630
|
+
|
|
631
|
+
if (routeRank && routeRank.hardFail) {
|
|
632
|
+
guardsTripped.push({ guard: 'route_rank', severity: 'blocker',
|
|
633
|
+
summary: 'server-side route-rank pre-pass hard-failed (an ungated write route exists) — a client PASS cannot override this (SR-9)' });
|
|
634
|
+
for (const f of routeRank.findings.filter((x) => x.severity === 'blocker')) {
|
|
635
|
+
extraIssues.push({ severity: 'blocker', file: f.file, summary: `[server route-rank] ${f.summary}`, worker: 'server-route-rank' });
|
|
636
|
+
}
|
|
637
|
+
if (clientPassed) downgradeTo = 'fail';
|
|
638
|
+
}
|
|
639
|
+
if (permPath && permPath.hardFail) {
|
|
640
|
+
guardsTripped.push({ guard: 'permission_path', severity: 'blocker',
|
|
641
|
+
summary: `server-side permission-path pre-pass hard-failed: rank '${builderRank}' is below Metic and the submitted committed_files touch protected machinery — a client PASS cannot override this (SR-9 / SEC #863)` });
|
|
642
|
+
for (const f of permPath.findings) {
|
|
643
|
+
extraIssues.push({ severity: 'blocker', file: f.file, summary: `[server permission-path] ${f.summary}`, worker: 'server-permission-path' });
|
|
644
|
+
}
|
|
645
|
+
if (clientPassed) downgradeTo = 'fail';
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// --- Guard 2: implausible client verdict (SR-9) ---
|
|
649
|
+
// A client PASS with an empty issues[] on a non-trivial diff is not a credible
|
|
650
|
+
// default-fail panel verdict. Downgrade to manual-confirm (a human lands it via
|
|
651
|
+
// /confirm) rather than auto-confirm + auto-credit. Only escalates the verdict
|
|
652
|
+
// (never de-escalates a Guard-1 hard FAIL).
|
|
653
|
+
const issueCount = Array.isArray(base.issues) ? base.issues.length : 0;
|
|
654
|
+
const { files: changeFiles, lines: changeLines } = changeMagnitude({ committedFiles, diffStats: ds });
|
|
655
|
+
const nonTrivial = changeFiles > IMPLAUSIBLE_PASS_MAX_FILES || changeLines > IMPLAUSIBLE_PASS_MAX_LINES;
|
|
656
|
+
if (clientPassed && issueCount === 0 && nonTrivial) {
|
|
657
|
+
guardsTripped.push({ guard: 'implausible_pass', severity: 'major',
|
|
658
|
+
summary: `client PASS with empty issues[] on a non-trivial diff (${changeFiles} file(s), ${changeLines} line(s) > ${IMPLAUSIBLE_PASS_MAX_FILES}/${IMPLAUSIBLE_PASS_MAX_LINES}) is not a credible default-fail panel verdict → manual-confirm (SR-9)` });
|
|
659
|
+
extraIssues.push({ severity: 'major', file: '(grade)', summary:
|
|
660
|
+
`[server plausibility] a passing grade with no enumerated issues on a ${changeFiles}-file / ${changeLines}-line change must be human-confirmed via /confirm`, worker: 'server-plausibility' });
|
|
661
|
+
if (downgradeTo !== 'fail') downgradeTo = 'concerns';
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// --- Guard 3: bound the credit/achievement signals (SR-18) ---
|
|
665
|
+
// The net-negative "Subtractor" bonus + shipper badges key off client-supplied
|
|
666
|
+
// signals.diff_stats + committed_files the server can't re-derive. We can't
|
|
667
|
+
// make them server-authoritative without the feature-branch diff (residual,
|
|
668
|
+
// see report), but we CAN strip an OBVIOUSLY-FORGED net-negative claim:
|
|
669
|
+
// diff_stats that claims a net-negative (removed > added) while the submitted
|
|
670
|
+
// committed_files list is large is internally inconsistent with a real
|
|
671
|
+
// simplification, and is the cheap way to farm the Subtractor bonus. When the
|
|
672
|
+
// signals are internally implausible we NEUTRALIZE the net-negative reward by
|
|
673
|
+
// zeroing lines_removed in the forwarded diff_stats (so awardNetNegativeBonus
|
|
674
|
+
// sees removed<=added → bonus 0) WITHOUT altering the displayed/audited stats.
|
|
675
|
+
// Conservative: only fires on the inconsistent shape; an honest net-negative
|
|
676
|
+
// simplification (few files, removed>added) is untouched.
|
|
677
|
+
let sanitizedSignals = base.signals;
|
|
678
|
+
const claimsNetNegative =
|
|
679
|
+
ds && (Number(ds.lines_removed) || 0) > (Number(ds.lines_added) || 0);
|
|
680
|
+
const NET_NEGATIVE_MAX_FILES = 25; // a "simplification" touching >25 files is implausibly broad
|
|
681
|
+
if (claimsNetNegative && changeFiles > NET_NEGATIVE_MAX_FILES) {
|
|
682
|
+
guardsTripped.push({ guard: 'forged_net_negative', severity: 'minor',
|
|
683
|
+
summary: `net-negative diff_stats (removed ${ds.lines_removed} > added ${ds.lines_added}) across ${changeFiles} files is implausibly broad for a simplification — net-negative bonus neutralized (SR-18)` });
|
|
684
|
+
// Clone signals + zero the credit-driving field WITHOUT mutating the input
|
|
685
|
+
// or the displayed diff_stats audit value (we add a sibling marker instead).
|
|
686
|
+
sanitizedSignals = {
|
|
687
|
+
...(base.signals || {}),
|
|
688
|
+
diff_stats: { ...ds, lines_removed: Number(ds.lines_added) || 0 },
|
|
689
|
+
diff_stats_reported: ds, // preserve what the client claimed for the audit trail
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// No guard tripped (or only audit-level findings on a non-pass) → return the
|
|
694
|
+
// input with a passed-through audit marker so the trail shows guards ran.
|
|
695
|
+
if (downgradeTo == null && guardsTripped.length === 0 && sanitizedSignals === base.signals) {
|
|
696
|
+
return {
|
|
697
|
+
...base,
|
|
698
|
+
signals: {
|
|
699
|
+
...(base.signals || {}),
|
|
700
|
+
server_guard: { applied: ['route_rank', 'permission_path', 'implausible_pass', 'forged_net_negative'], ok: true, tripped: [] },
|
|
701
|
+
},
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Build the downgraded result. Downgrade-only: if base wasn't passing we keep
|
|
706
|
+
// it not-passing; we never resurrect. Map the panel verdict onto the v3
|
|
707
|
+
// server gate the same way aggregatePanel does (fail → confidence 0; concerns
|
|
708
|
+
// → FALLTHROUGH_CONFIDENCE below the low-confidence cap; pass left as-is).
|
|
709
|
+
const newPassed = clientPassed && downgradeTo == null;
|
|
710
|
+
const forwardedConfidence =
|
|
711
|
+
downgradeTo === 'fail'
|
|
712
|
+
? 0
|
|
713
|
+
: downgradeTo === 'concerns'
|
|
714
|
+
? FALLTHROUGH_CONFIDENCE
|
|
715
|
+
: (base.signals?.confidence ?? null);
|
|
716
|
+
|
|
717
|
+
const guardNote = guardsTripped.length
|
|
718
|
+
? `\n\nServer-authoritative guards (SR-9 / SR-18) ${downgradeTo ? `DOWNGRADED this grade to ${downgradeTo.toUpperCase()}` : 'flagged'}:\n`
|
|
719
|
+
+ guardsTripped.map((g) => ` [${g.severity}] ${g.guard}: ${g.summary}`).join('\n')
|
|
720
|
+
: '';
|
|
721
|
+
|
|
722
|
+
return {
|
|
723
|
+
...base,
|
|
724
|
+
passed: newPassed,
|
|
725
|
+
issues: [...(base.issues || []), ...extraIssues],
|
|
726
|
+
signals: {
|
|
727
|
+
...sanitizedSignals,
|
|
728
|
+
// confidence drives the server-side gate alignment (validateAndScore on the
|
|
729
|
+
// body already ran; this is the authoritative post-hoc adjustment).
|
|
730
|
+
confidence: forwardedConfidence,
|
|
731
|
+
server_guard: {
|
|
732
|
+
applied: ['route_rank', 'permission_path', 'implausible_pass', 'forged_net_negative'],
|
|
733
|
+
ok: guardsTripped.length === 0,
|
|
734
|
+
downgraded_to: downgradeTo,
|
|
735
|
+
client_passed: clientPassed,
|
|
736
|
+
tripped: guardsTripped,
|
|
737
|
+
},
|
|
738
|
+
},
|
|
739
|
+
notes_md: `${base.notes_md || ''}${guardNote}`,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// ---------- v3: multi-shot aggregation ----------
|
|
744
|
+
|
|
745
|
+
// Collapses N per-shot grade-results into the final v3 grade-result.
|
|
746
|
+
// Pass requires unanimous PASS on all shots; anything else (any FAIL, any
|
|
747
|
+
// error, any disagreement) → not passed. Issues are deduped across shots
|
|
748
|
+
// by (file, summary). Displayed rubric_scores use the median across shots
|
|
749
|
+
// so the trend dashboard stays one-number-per-task.
|
|
750
|
+
function aggregateShots(shotResults, meta = {}) {
|
|
751
|
+
const N = shotResults.length;
|
|
752
|
+
const verdicts = shotResults.map((s) => ({
|
|
753
|
+
passed: s.passed,
|
|
754
|
+
ff: s.rubric_scores?.functional_fidelity ?? null,
|
|
755
|
+
cq: s.rubric_scores?.code_quality ?? null,
|
|
756
|
+
economy: s.rubric_scores?.economy ?? null,
|
|
757
|
+
cq_effective: s.rubric_scores?.code_quality_effective ?? null,
|
|
758
|
+
confidence: s.signals?.confidence ?? null,
|
|
759
|
+
model: s.signals?.model ?? null,
|
|
760
|
+
run_ms: s.signals?.run_ms ?? null,
|
|
761
|
+
shot_index: s.signals?.shot_index ?? null,
|
|
762
|
+
notes_md: s.notes_md,
|
|
763
|
+
issues: s.issues || [],
|
|
764
|
+
error:
|
|
765
|
+
s.signals?.parse_error
|
|
766
|
+
? 'parse_error'
|
|
767
|
+
: s.signals?.schema_error
|
|
768
|
+
? 'schema_error'
|
|
769
|
+
: s.signals?.subprocess_error || null,
|
|
770
|
+
}));
|
|
771
|
+
|
|
772
|
+
const ff = median(verdicts.map((v) => v.ff));
|
|
773
|
+
const cq = median(verdicts.map((v) => v.cq));
|
|
774
|
+
const economy = median(verdicts.map((v) => v.economy));
|
|
775
|
+
const cqEffective = median(verdicts.map((v) => v.cq_effective));
|
|
776
|
+
const blended = ff != null && cq != null ? r1((ff + cq) / 2) : 0;
|
|
777
|
+
// Top-level aggregated confidence = median of per-shot confidences. Without
|
|
778
|
+
// this, ship.js forwards a null top-level confidence to /tasks/:id/grade,
|
|
779
|
+
// and the server's validateAndScore (re-running on the POST body) hits the
|
|
780
|
+
// Number(null)=0 trap → FAIL even though every shot independently passed.
|
|
781
|
+
// Caught on the MAS Phase 1 self-ship (#395).
|
|
782
|
+
//
|
|
783
|
+
// Computed UNROUNDED (no r1()) — confidence sits near a 0.4 gate cap so
|
|
784
|
+
// rounding to one decimal could nudge a borderline value across the cap
|
|
785
|
+
// in either direction. Per-shot confidences are already 0-1 floats.
|
|
786
|
+
const confVals = verdicts
|
|
787
|
+
.map((v) => v.confidence)
|
|
788
|
+
.filter((n) => Number.isFinite(n))
|
|
789
|
+
.sort((a, b) => a - b);
|
|
790
|
+
const confidenceMedian = confVals.length === 0
|
|
791
|
+
? null
|
|
792
|
+
: confVals.length % 2
|
|
793
|
+
? confVals[(confVals.length - 1) / 2]
|
|
794
|
+
: (confVals[confVals.length / 2 - 1] + confVals[confVals.length / 2]) / 2;
|
|
795
|
+
|
|
796
|
+
// Dedupe issues across shots by (file, summary). Keep first-seen severity.
|
|
797
|
+
const seen = new Set();
|
|
798
|
+
const issues = [];
|
|
799
|
+
for (const v of verdicts) {
|
|
800
|
+
for (const it of v.issues) {
|
|
801
|
+
if (!it || typeof it !== 'object') continue;
|
|
802
|
+
const key = `${it.file ?? ''}::${(it.summary ?? '').trim()}`;
|
|
803
|
+
if (seen.has(key)) continue;
|
|
804
|
+
seen.add(key);
|
|
805
|
+
issues.push(it);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const passCount = verdicts.filter((v) => v.passed === true).length;
|
|
810
|
+
const failCount = verdicts.filter((v) => v.passed === false && !v.error).length;
|
|
811
|
+
const errorCount = verdicts.filter((v) => v.error).length;
|
|
812
|
+
const allPassed = N > 0 && passCount === N;
|
|
813
|
+
|
|
814
|
+
let verdict;
|
|
815
|
+
let passed;
|
|
816
|
+
let aggregationKind;
|
|
817
|
+
if (errorCount > 0) {
|
|
818
|
+
passed = false;
|
|
819
|
+
verdict = `FAIL — ${errorCount}/${N} shot(s) errored`;
|
|
820
|
+
aggregationKind = 'errored';
|
|
821
|
+
} else if (allPassed) {
|
|
822
|
+
passed = true;
|
|
823
|
+
verdict = `PASS — ${N}/${N} shots unanimous`;
|
|
824
|
+
aggregationKind = 'unanimous_pass';
|
|
825
|
+
} else if (passCount === 0) {
|
|
826
|
+
passed = false;
|
|
827
|
+
verdict = `FAIL — ${failCount}/${N} shots`;
|
|
828
|
+
aggregationKind = 'unanimous_fail';
|
|
829
|
+
} else {
|
|
830
|
+
passed = false;
|
|
831
|
+
verdict = `FAIL — ${passCount} pass, ${failCount} fail (no unanimous pass)`;
|
|
832
|
+
aggregationKind = 'disagreement';
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const lines = [];
|
|
836
|
+
lines.push(`Subagent grader ${VERSION} (multi-shot, ${N}× shots) — ${verdict}`);
|
|
837
|
+
lines.push(` functional_fidelity (median): ${ff ?? '?'}/10`);
|
|
838
|
+
lines.push(` code_quality (median): ${cq ?? '?'}/10`);
|
|
839
|
+
if (economy != null) {
|
|
840
|
+
lines.push(` economy (advisory, median): ${economy}/10`);
|
|
841
|
+
}
|
|
842
|
+
lines.push(` blended (median): ${blended}/10 (threshold ${THRESHOLD}, both axes must clear on EVERY shot)`);
|
|
843
|
+
lines.push('');
|
|
844
|
+
lines.push('Per-shot:');
|
|
845
|
+
for (let i = 0; i < verdicts.length; i++) {
|
|
846
|
+
const v = verdicts[i];
|
|
847
|
+
const status = v.error ? 'ERROR' : v.passed === true ? 'PASS' : 'FAIL';
|
|
848
|
+
const ffStr = v.ff != null ? `${v.ff}` : '?';
|
|
849
|
+
const cqStr = v.cq != null ? `${v.cq}` : '?';
|
|
850
|
+
const confStr = v.confidence != null ? ` conf=${v.confidence}` : '';
|
|
851
|
+
const errStr = v.error ? ` (${v.error})` : '';
|
|
852
|
+
lines.push(` shot[${i}] ${status} ff=${ffStr} cq=${cqStr}${confStr} ${v.model ?? '?'}${errStr}`);
|
|
853
|
+
}
|
|
854
|
+
if (issues.length > 0) {
|
|
855
|
+
lines.push('');
|
|
856
|
+
lines.push(`Issues (${issues.length} unique across shots):`);
|
|
857
|
+
for (const it of issues.slice(0, 20)) {
|
|
858
|
+
const sev = String(it.severity || '?').padEnd(7);
|
|
859
|
+
const file = String(it.file || '?');
|
|
860
|
+
const summary = String(it.summary || '').replace(/\s+/g, ' ').trim();
|
|
861
|
+
lines.push(` [${sev}] ${file} — ${summary}`);
|
|
862
|
+
}
|
|
863
|
+
if (issues.length > 20) {
|
|
864
|
+
lines.push(` ... ${issues.length - 20} more (full set in signals.shots[].issues)`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// #486 (ADR 0028 path 1): sum each shot's own subprocess cost (envelope
|
|
869
|
+
// total_cost_usd) so the trivial-diff path reports panel_cost_usd the same way
|
|
870
|
+
// the full panel does. The single-shot trivial path is the common case here.
|
|
871
|
+
const panelCostUsd = shotResults.reduce((s, r) => s + (Number(r.cost_usd) || 0), 0);
|
|
872
|
+
|
|
873
|
+
return {
|
|
874
|
+
score: blended,
|
|
875
|
+
passed,
|
|
876
|
+
threshold: THRESHOLD,
|
|
877
|
+
grader_kind: 'subagent-multishot',
|
|
878
|
+
grader_version: VERSION,
|
|
879
|
+
panel_cost_usd: panelCostUsd, // #486: panel's own API spend (USD)
|
|
880
|
+
rubric_scores: {
|
|
881
|
+
functional_fidelity: ff,
|
|
882
|
+
code_quality: cq,
|
|
883
|
+
economy,
|
|
884
|
+
code_quality_effective: cqEffective,
|
|
885
|
+
},
|
|
886
|
+
signals: {
|
|
887
|
+
grader_kind: 'subagent-multishot',
|
|
888
|
+
grader_version: VERSION,
|
|
889
|
+
threshold: THRESHOLD,
|
|
890
|
+
// Top-level confidence = median across shots. ship.js forwards this to
|
|
891
|
+
// POST /tasks/:id/grade so the server-side re-validation has a real
|
|
892
|
+
// number rather than null (see Number(null)=0 fix in validateAndScore).
|
|
893
|
+
confidence: confidenceMedian,
|
|
894
|
+
shots: verdicts,
|
|
895
|
+
shot_count: N,
|
|
896
|
+
pass_count: passCount,
|
|
897
|
+
fail_count: failCount,
|
|
898
|
+
error_count: errorCount,
|
|
899
|
+
shot_aggregation: aggregationKind,
|
|
900
|
+
...meta,
|
|
901
|
+
},
|
|
902
|
+
notes_md: lines.join('\n'),
|
|
903
|
+
issues,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// ---------- v3: subagent subprocess invoker ----------
|
|
908
|
+
|
|
909
|
+
// Unchanged from v2 — spawns `claude -p --print --model <model> --output-
|
|
910
|
+
// format json` with stdin = prompt. The v3 caller varies `model` per shot
|
|
911
|
+
// via opts.model (set by gradeSubagent below).
|
|
912
|
+
function runSubagent({ prompt, opts = {} }) {
|
|
913
|
+
return new Promise((resolve, reject) => {
|
|
914
|
+
const timeoutMs = opts.timeoutMs ?? 240_000;
|
|
915
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
916
|
+
const model = opts.model ?? DEFAULT_GRADER_MODEL;
|
|
917
|
+
// Env escape hatch (OTB_CLAUDE_BIN / CLAUDE_BIN) for installs where the CLI
|
|
918
|
+
// isn't named `claude` on PATH — covers any OS, not just Windows.
|
|
919
|
+
const bin = opts.claudeBin ?? process.env.OTB_CLAUDE_BIN ?? process.env.CLAUDE_BIN ?? 'claude';
|
|
920
|
+
|
|
921
|
+
const args = [
|
|
922
|
+
'-p',
|
|
923
|
+
'--print',
|
|
924
|
+
'--model', model,
|
|
925
|
+
'--output-format', 'json',
|
|
926
|
+
'--tools', 'Read',
|
|
927
|
+
'--disable-slash-commands',
|
|
928
|
+
'--permission-mode', 'bypassPermissions',
|
|
929
|
+
];
|
|
930
|
+
|
|
931
|
+
const startedAt = Date.now();
|
|
932
|
+
const child = spawn(bin, args, {
|
|
933
|
+
cwd,
|
|
934
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
935
|
+
// On Windows the `claude` CLI is a `claude.cmd` shim that Node's spawn
|
|
936
|
+
// can't launch without a shell (ENOENT -> SUBAGENT_SPAWN_FAILED, observed
|
|
937
|
+
// on #662). Route through cmd.exe on win32 only; Mac/Linux keep
|
|
938
|
+
// shell:false (the default, and the proven-working path). args carry no
|
|
939
|
+
// user input (the prompt goes in via stdin), so shell injection is a
|
|
940
|
+
// non-issue. windowsHide suppresses the transient console window.
|
|
941
|
+
shell: process.platform === 'win32',
|
|
942
|
+
windowsHide: true,
|
|
943
|
+
// MAS Phase 3 (#427): mark this child as a subagent so the Conductor
|
|
944
|
+
// UserPromptSubmit hook (.claude/hooks/conductor.js) bails instead of
|
|
945
|
+
// injecting advisory context into the grader worker's prompt. Any
|
|
946
|
+
// subagent we spawn inherits this flag and is likewise suppressed.
|
|
947
|
+
env: { ...process.env, OTB_SUBAGENT: '1' },
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
let stdout = '';
|
|
951
|
+
let stderr = '';
|
|
952
|
+
let timedOut = false;
|
|
953
|
+
|
|
954
|
+
const killTimer = setTimeout(() => {
|
|
955
|
+
timedOut = true;
|
|
956
|
+
try { child.kill('SIGTERM'); } catch (_) {}
|
|
957
|
+
setTimeout(() => { try { child.kill('SIGKILL'); } catch (_) {} }, 5_000);
|
|
958
|
+
}, timeoutMs);
|
|
959
|
+
|
|
960
|
+
child.stdout.on('data', (b) => { stdout += b.toString('utf8'); });
|
|
961
|
+
child.stderr.on('data', (b) => { stderr += b.toString('utf8'); });
|
|
962
|
+
|
|
963
|
+
child.on('error', (err) => {
|
|
964
|
+
clearTimeout(killTimer);
|
|
965
|
+
reject(Object.assign(new Error(`subagent spawn failed: ${err.message}`), {
|
|
966
|
+
code: 'SUBAGENT_SPAWN_FAILED',
|
|
967
|
+
cause: err,
|
|
968
|
+
}));
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
child.on('close', (exitCode) => {
|
|
972
|
+
clearTimeout(killTimer);
|
|
973
|
+
const runMs = Date.now() - startedAt;
|
|
974
|
+
if (timedOut) {
|
|
975
|
+
return reject(Object.assign(new Error(`subagent timed out after ${timeoutMs}ms`), {
|
|
976
|
+
code: 'SUBAGENT_TIMEOUT',
|
|
977
|
+
run_ms: runMs,
|
|
978
|
+
stdout,
|
|
979
|
+
stderr,
|
|
980
|
+
}));
|
|
981
|
+
}
|
|
982
|
+
if (exitCode !== 0) {
|
|
983
|
+
return reject(Object.assign(new Error(`subagent exited with code ${exitCode}`), {
|
|
984
|
+
code: 'SUBAGENT_NONZERO_EXIT',
|
|
985
|
+
exit_code: exitCode,
|
|
986
|
+
run_ms: runMs,
|
|
987
|
+
stdout,
|
|
988
|
+
stderr,
|
|
989
|
+
}));
|
|
990
|
+
}
|
|
991
|
+
resolve({ stdout, stderr, run_ms: runMs, exit_code: exitCode, cost_usd: extractCostUsd(stdout) });
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
child.stdin.write(prompt, 'utf8');
|
|
995
|
+
child.stdin.end();
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// System 5 (task 1359): an OPT-IN cached path around runSubagent. When the caller
|
|
1000
|
+
// supplies a `cache` spec (from llm-cache.makeCacheSpec), a hit replays the stored
|
|
1001
|
+
// result at cost_usd:0 (so $0 flows naturally into extractCostUsd → session-cost);
|
|
1002
|
+
// a miss runs the untouched runSubagent and stores a clean (exit 0) result. With
|
|
1003
|
+
// no cache spec it's exactly runSubagent — the default path is never altered. The
|
|
1004
|
+
// llm-cache reaches us through the doorway (BV1.R73: it's shared infra core also
|
|
1005
|
+
// uses, so a module can't own it); if it's somehow absent the path degrades to an
|
|
1006
|
+
// ordinary uncached run, exactly as the old lazy-require catch did.
|
|
1007
|
+
async function runSubagentCached({ prompt, opts = {}, cache = null }) {
|
|
1008
|
+
if (!cache || !cache.key) return runSubagent({ prompt, opts });
|
|
1009
|
+
const llmCache = api.llmCache;
|
|
1010
|
+
if (!llmCache) return runSubagent({ prompt, opts });
|
|
1011
|
+
return llmCache.withCache(cache, () => runSubagent({ prompt, opts }));
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// #486 (ADR 0028 path 1): the `claude -p --output-format json` envelope reports
|
|
1015
|
+
// the subprocess's own dollar cost in `total_cost_usd` (verified present:
|
|
1016
|
+
// {type, ..., total_cost_usd, usage, modelUsage}). We were parsing only
|
|
1017
|
+
// `env.result` (extractAssistantResult) and silently discarding that cost — the
|
|
1018
|
+
// single biggest reason cost_log's `api` category sat at $0.60 across the whole
|
|
1019
|
+
// project (#482 audit F1). Pull it out so the panel's spend can be logged
|
|
1020
|
+
// (category=api, source=grader). Returns null on non-JSON / partial output, so
|
|
1021
|
+
// a cost signal never fabricates a number it doesn't have.
|
|
1022
|
+
function extractCostUsd(rawStdout) {
|
|
1023
|
+
try {
|
|
1024
|
+
const env = JSON.parse(rawStdout);
|
|
1025
|
+
if (env && typeof env.total_cost_usd === 'number' && env.total_cost_usd >= 0) {
|
|
1026
|
+
return env.total_cost_usd;
|
|
1027
|
+
}
|
|
1028
|
+
} catch (_) { /* non-JSON or partial stdout — no cost signal */ }
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function extractAssistantResult(rawStdout) {
|
|
1033
|
+
try {
|
|
1034
|
+
const env = JSON.parse(rawStdout);
|
|
1035
|
+
if (env && typeof env.result === 'string') return env.result;
|
|
1036
|
+
if (env && typeof env === 'object' && 'functional_fidelity' in env) return env;
|
|
1037
|
+
return rawStdout;
|
|
1038
|
+
} catch (_) {
|
|
1039
|
+
return rawStdout;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Run one shot end-to-end: build the prompt (caller did this), spawn the
|
|
1044
|
+
// subagent at the requested model, parse + validate. Returns a per-shot
|
|
1045
|
+
// grade-result; subprocess failures become passed=false per-shot results so
|
|
1046
|
+
// aggregateShots can still produce a final verdict.
|
|
1047
|
+
async function runOneShot({ prompt, model, shotIndex, runOpts }) {
|
|
1048
|
+
try {
|
|
1049
|
+
const { stdout, run_ms, exit_code, cost_usd } = await runSubagent({
|
|
1050
|
+
prompt,
|
|
1051
|
+
opts: { ...runOpts, model },
|
|
1052
|
+
});
|
|
1053
|
+
const assistantResult = extractAssistantResult(stdout);
|
|
1054
|
+
const scored = validateAndScore(assistantResult, {
|
|
1055
|
+
run_ms,
|
|
1056
|
+
exit_code,
|
|
1057
|
+
raw_chars: stdout.length,
|
|
1058
|
+
model,
|
|
1059
|
+
shot_index: shotIndex,
|
|
1060
|
+
});
|
|
1061
|
+
// #486 (ADR 0028 path 1): carry this shot's own subprocess cost so the
|
|
1062
|
+
// trivial-diff path (gradeSubagentV3MultiShot → aggregateShots) can sum it
|
|
1063
|
+
// into panel_cost_usd just like the full panel does.
|
|
1064
|
+
scored.cost_usd = cost_usd ?? null;
|
|
1065
|
+
return scored;
|
|
1066
|
+
} catch (err) {
|
|
1067
|
+
return {
|
|
1068
|
+
score: 0,
|
|
1069
|
+
passed: false,
|
|
1070
|
+
threshold: THRESHOLD,
|
|
1071
|
+
grader_kind: 'subagent',
|
|
1072
|
+
grader_version: VERSION,
|
|
1073
|
+
rubric_scores: {},
|
|
1074
|
+
signals: {
|
|
1075
|
+
grader_kind: 'subagent',
|
|
1076
|
+
grader_version: VERSION,
|
|
1077
|
+
model,
|
|
1078
|
+
threshold: THRESHOLD,
|
|
1079
|
+
shot_index: shotIndex,
|
|
1080
|
+
run_ms: err.run_ms ?? null,
|
|
1081
|
+
exit_code: err.exit_code ?? null,
|
|
1082
|
+
subprocess_error: err.code || 'UNKNOWN',
|
|
1083
|
+
stderr_tail: (err.stderr || '').slice(-500),
|
|
1084
|
+
},
|
|
1085
|
+
notes_md: `Shot ${shotIndex}: subprocess failed (${err.code || 'UNKNOWN'}: ${err.message}).`,
|
|
1086
|
+
issues: [],
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// ---------- v4: panel aggregation (MAS Phase 2.0) ----------
|
|
1092
|
+
|
|
1093
|
+
// Collapses N per-worker WorkerResults into the final v4 grade-result.
|
|
1094
|
+
//
|
|
1095
|
+
// Gate logic:
|
|
1096
|
+
// - Any worker `fail` → overall FAIL (a real finding blocks).
|
|
1097
|
+
// - Else any worker errored → overall FALLTHROUGH (manual-confirm) —
|
|
1098
|
+
// a crash is missing signal, not a
|
|
1099
|
+
// finding, so it must not hard-block
|
|
1100
|
+
// on infra flakiness (#126).
|
|
1101
|
+
// - Else any worker `concerns` → overall FALLTHROUGH (manual-confirm).
|
|
1102
|
+
// - All workers `pass` → overall PASS.
|
|
1103
|
+
//
|
|
1104
|
+
// Server-compat forwarding:
|
|
1105
|
+
// The grade POST shape (ff, cq, economy, confidence, issues) is preserved
|
|
1106
|
+
// so the server's existing validateAndScore re-validation aligns with the
|
|
1107
|
+
// panel's verdict. We achieve this by setting the forwarded confidence:
|
|
1108
|
+
// pass → median worker confidence (server's gate also passes)
|
|
1109
|
+
// concerns → FALLTHROUGH_CONFIDENCE (below LOW_CONFIDENCE → server fails)
|
|
1110
|
+
// fail → 0 + a failing axis preserved at < threshold (server fails)
|
|
1111
|
+
//
|
|
1112
|
+
// rubric_scores:
|
|
1113
|
+
// Each axis is the median across workers that reported it (Narc reports
|
|
1114
|
+
// functional_fidelity; Quality reports code_quality + economy). Preserves
|
|
1115
|
+
// the trend dashboard's one-number-per-task contract.
|
|
1116
|
+
//
|
|
1117
|
+
// signals.workers[] carries the full per-worker audit trail.
|
|
1118
|
+
function aggregatePanel(workerResults, meta = {}) {
|
|
1119
|
+
const N = workerResults.length;
|
|
1120
|
+
|
|
1121
|
+
// Score axes — median across workers that reported each. Reuses the
|
|
1122
|
+
// module-level median() helper (rounded to one decimal).
|
|
1123
|
+
const ff = median(workerResults.map((w) => w.scores?.functional_fidelity));
|
|
1124
|
+
const cq = median(workerResults.map((w) => w.scores?.code_quality));
|
|
1125
|
+
const economy = median(workerResults.map((w) => w.scores?.economy));
|
|
1126
|
+
// Phase 2.1 (#416): Hacker reports security_score. Added to rubric_scores
|
|
1127
|
+
// for the audit trail and trend dashboard; not directly server-gated
|
|
1128
|
+
// (Hacker's verdict still flows through the existing confidence-forwarding).
|
|
1129
|
+
const security = median(workerResults.map((w) => w.scores?.security_score));
|
|
1130
|
+
// Phase 2.2 (#421): Efficiency Monger reports efficiency_score. Same
|
|
1131
|
+
// additive treatment as security_score.
|
|
1132
|
+
const efficiencyScore = median(workerResults.map((w) => w.scores?.efficiency_score));
|
|
1133
|
+
|
|
1134
|
+
const economyPenalty =
|
|
1135
|
+
economy != null ? Math.max(0, (THRESHOLD - economy) / 2) : 0;
|
|
1136
|
+
const cqEffective = cq != null && economy != null ? r1(clamp(cq - economyPenalty, 0, 10)) : cq;
|
|
1137
|
+
const blended = ff != null && cq != null ? r1((ff + cq) / 2) : 0;
|
|
1138
|
+
|
|
1139
|
+
// Verdict counts.
|
|
1140
|
+
const passCount = workerResults.filter((w) => w.verdict === 'pass').length;
|
|
1141
|
+
const concernsCount = workerResults.filter((w) => w.verdict === 'concerns').length;
|
|
1142
|
+
// A crashed worker sets verdict='fail' as a DEFAULT in its computeVerdict()
|
|
1143
|
+
// subprocess_error / parse_error / schema_error early returns (see each
|
|
1144
|
+
// worker module) — that is MISSING SIGNAL, not a substantive finding, and
|
|
1145
|
+
// those early returns bail before any issues are extracted (error set ⟹ no
|
|
1146
|
+
// findings). Treat such a worker as "missing signal" so it counts ONLY toward
|
|
1147
|
+
// errorCount; otherwise the errorCount fall-through below is unreachable dead
|
|
1148
|
+
// code (#473 grader catch).
|
|
1149
|
+
//
|
|
1150
|
+
// DEFENSE-IN-DEPTH (#473 Hacker [major]): the above invariant is the load-
|
|
1151
|
+
// bearing claim. Rather than trust it blindly, a worker is "missing signal"
|
|
1152
|
+
// only if it errored AND surfaced NO blocking/major issue. So even if a future
|
|
1153
|
+
// worker ever set both `error` and a real finding, that finding still blocks
|
|
1154
|
+
// (counts as a substantive fail) instead of being reclassified as a flake.
|
|
1155
|
+
const hasSubstantiveIssue = (w) =>
|
|
1156
|
+
(w.issues || []).some((i) => i.severity === 'blocker' || i.severity === 'major');
|
|
1157
|
+
const isMissingSignal = (w) => !!w.error && !hasSubstantiveIssue(w);
|
|
1158
|
+
const failCount = workerResults.filter((w) => w.verdict === 'fail' && !isMissingSignal(w)).length;
|
|
1159
|
+
// NOTE (#473/#589): signals.error_count counts only isMissingSignal workers — i.e. a worker that
|
|
1160
|
+
// errored AND surfaced no blocking issue. A worker that errored but still raised a blocker/major
|
|
1161
|
+
// is a real finding, so it counts in fail_count (above), NOT here. This is a deliberate semantic
|
|
1162
|
+
// change from the old "all w.error" meaning; keep error_count = missing-signal-only.
|
|
1163
|
+
const errorCount = workerResults.filter((w) => isMissingSignal(w)).length;
|
|
1164
|
+
|
|
1165
|
+
// V3.R85 (#481): the Narc (functional_fidelity) is ADVISORY in the gate —
|
|
1166
|
+
// Lars's call 2026-05-30 after #303, where over-literal acceptance-criteria
|
|
1167
|
+
// objections from this one lens repeatedly blocked ships that Quality,
|
|
1168
|
+
// Hacker, and Efficiency all cleared. The Narc keeps its lens (scores +
|
|
1169
|
+
// issues are still recorded and displayed), but its weight in the GATE drops:
|
|
1170
|
+
// - a Narc 'concerns' → advisory only; never blocks.
|
|
1171
|
+
// - a Narc-only 'fail' → downgrades to manual-confirm (CONCERNS), not a
|
|
1172
|
+
// hard panel FAIL — so a genuine functional gap still surfaces and pauses
|
|
1173
|
+
// for a human, but isn't an automatic rejection.
|
|
1174
|
+
// - Quality / Hacker / Efficiency keep their hard veto unchanged.
|
|
1175
|
+
// The raw counts above are untouched, so the audit trail + dashboards still
|
|
1176
|
+
// see every worker's real verdict.
|
|
1177
|
+
//
|
|
1178
|
+
// DEFENSE-IN-DEPTH (#481 Hacker [major]): a worker is advisory ONLY if it
|
|
1179
|
+
// carries no blocker-severity issue. So even if a future change routed a
|
|
1180
|
+
// gating finding through an advisory kind (e.g. the deterministic route-rank
|
|
1181
|
+
// pre-pass — today kind='route-rank', NOT advisory — or any relabel), a
|
|
1182
|
+
// blocker-severity finding can never be silently downgraded to manual-confirm.
|
|
1183
|
+
// This mirrors the isMissingSignal defense-in-depth (#473): a real finding
|
|
1184
|
+
// always blocks. Over-literal Narc objections are major/minor, never blocker,
|
|
1185
|
+
// so the loosening still bites where intended.
|
|
1186
|
+
const ADVISORY_WORKERS = new Set(['narc']);
|
|
1187
|
+
const hasBlocker = (w) => (w.issues || []).some((i) => i.severity === 'blocker');
|
|
1188
|
+
const isAdvisory = (w) => ADVISORY_WORKERS.has(w.kind) && !hasBlocker(w);
|
|
1189
|
+
// If the panel were somehow advisory-only, fall back to gating on everyone so
|
|
1190
|
+
// a lone Narc still counts (defensive — the default panel always has 3+
|
|
1191
|
+
// gating workers).
|
|
1192
|
+
const nonAdvisory = workerResults.filter((w) => !isAdvisory(w));
|
|
1193
|
+
const gating = nonAdvisory.length ? nonAdvisory : workerResults;
|
|
1194
|
+
const gatingFail = gating.filter((w) => w.verdict === 'fail' && !isMissingSignal(w)).length;
|
|
1195
|
+
const gatingError = gating.filter((w) => isMissingSignal(w)).length;
|
|
1196
|
+
const gatingConcerns = gating.filter((w) => w.verdict === 'concerns').length;
|
|
1197
|
+
const gatingAllPass = gating.length > 0 && gating.every((w) => w.verdict === 'pass');
|
|
1198
|
+
// #574 (idea 130): the Hacker (security lens) is the ONE worker whose absence
|
|
1199
|
+
// must not soften the gate. A diff crafted to RELIABLY crash the Hacker
|
|
1200
|
+
// (adversarial content → parse_error/SUBAGENT_TIMEOUT) would otherwise convert
|
|
1201
|
+
// the outcome from auto-block to lenient manual-confirm. Detect a gating Hacker
|
|
1202
|
+
// that produced no signal (missing-signal = errored AND no substantive finding;
|
|
1203
|
+
// a Hacker that errored but still raised a blocker is handled by gatingFail).
|
|
1204
|
+
const securityLensErrored = gating.some((w) => w.kind === 'hacker' && isMissingSignal(w));
|
|
1205
|
+
// #943: TOTAL grader outage — EVERY gating worker is missing-signal (its
|
|
1206
|
+
// subprocess failed to spawn / timed out / produced unparseable output). With
|
|
1207
|
+
// no quality signal at all, the panel has not actually GRADED anything; this
|
|
1208
|
+
// is an infra/outage condition, NOT a 0/10 quality rejection. Distinguished
|
|
1209
|
+
// from the partial-error case (some gating workers errored, others reported)
|
|
1210
|
+
// so callers (ship.js) can present "grader unavailable" instead of a FAIL.
|
|
1211
|
+
const gatingErrorAll = gating.length > 0 && gating.every((w) => isMissingSignal(w));
|
|
1212
|
+
// Advisory-worker outcomes — never a hard block. Uses isAdvisory so a Narc
|
|
1213
|
+
// that DID raise a blocker is treated as gating (in `nonAdvisory`), not here.
|
|
1214
|
+
const advisoryFail = workerResults.some((w) => isAdvisory(w) && w.verdict === 'fail' && !isMissingSignal(w));
|
|
1215
|
+
const advisoryConcerns = workerResults.some((w) => isAdvisory(w) && w.verdict === 'concerns');
|
|
1216
|
+
|
|
1217
|
+
// Confidence — median for pass, fallthrough for concerns, 0 for fail.
|
|
1218
|
+
// (Drives the server-side gate; see file header.) Computed UNROUNDED —
|
|
1219
|
+
// confidence sits near the 0.4 gate cap so the module-level median()
|
|
1220
|
+
// helper (which rounds via r1()) would risk nudging a borderline value
|
|
1221
|
+
// across the boundary. Inlined for that one-decimal-precision reason.
|
|
1222
|
+
const confVals = workerResults
|
|
1223
|
+
.map((w) => w.confidence)
|
|
1224
|
+
.filter((n) => Number.isFinite(n))
|
|
1225
|
+
.slice()
|
|
1226
|
+
.sort((a, b) => a - b);
|
|
1227
|
+
const medianConf = confVals.length === 0
|
|
1228
|
+
? null
|
|
1229
|
+
: confVals.length % 2
|
|
1230
|
+
? confVals[(confVals.length - 1) / 2]
|
|
1231
|
+
: (confVals[confVals.length / 2 - 1] + confVals[confVals.length / 2]) / 2;
|
|
1232
|
+
|
|
1233
|
+
let verdict;
|
|
1234
|
+
let panelOutcome;
|
|
1235
|
+
let forwardedConfidence;
|
|
1236
|
+
// Gate on the GATING (non-advisory) workers. The Narc, when advisory, can at
|
|
1237
|
+
// most push the panel to CONCERNS (manual-confirm) — never PASS-block-free
|
|
1238
|
+
// nor hard-FAIL.
|
|
1239
|
+
const advisoryNote = advisoryFail
|
|
1240
|
+
? ' — narc (advisory) flagged a functional-fidelity failure: confirm only after a human checks the scope is met'
|
|
1241
|
+
: '';
|
|
1242
|
+
if (gatingFail > 0) {
|
|
1243
|
+
// A substantive failure from a GATING worker hard-blocks (errored gating
|
|
1244
|
+
// workers, if any, are noted but the real failure is what governs).
|
|
1245
|
+
verdict = (gatingError > 0
|
|
1246
|
+
? `FAIL — ${gatingFail}/${N} gating worker(s) failed, ${gatingError}/${N} errored`
|
|
1247
|
+
: `FAIL — ${gatingFail}/${N} gating worker(s) failed`) + advisoryNote;
|
|
1248
|
+
panelOutcome = 'fail';
|
|
1249
|
+
forwardedConfidence = 0;
|
|
1250
|
+
} else if (gatingErrorAll) {
|
|
1251
|
+
// #943: EVERY gating worker failed to run — a total grader outage (the
|
|
1252
|
+
// grader subprocess couldn't spawn / timed out across the board, e.g. the
|
|
1253
|
+
// `claude` CLI 401s in a desktop session, or an API outage). There is NO
|
|
1254
|
+
// quality signal, so this is NOT a quality rejection — it is "the grader
|
|
1255
|
+
// could not run". A distinct 'unavailable' outcome (vs the partial-error
|
|
1256
|
+
// 'concerns' below) lets ship.js present it as "grader unavailable" rather
|
|
1257
|
+
// than the misleading "FAIL · 0/10" that reached the builder in session
|
|
1258
|
+
// 1659. passed stays false → routes to manual-confirm (never auto-pass).
|
|
1259
|
+
const erroredKinds = gating.filter((w) => w.error).map((w) => w.kind);
|
|
1260
|
+
verdict =
|
|
1261
|
+
`GRADER UNAVAILABLE — all ${gatingError}/${gating.length} gating worker(s) failed to run (${erroredKinds.join(', ')}); no quality signal was produced. This is an infra/outage condition, NOT a quality rejection — manual-confirm available.` +
|
|
1262
|
+
advisoryNote;
|
|
1263
|
+
panelOutcome = 'unavailable';
|
|
1264
|
+
forwardedConfidence = FALLTHROUGH_CONFIDENCE;
|
|
1265
|
+
} else if (securityLensErrored) {
|
|
1266
|
+
// #574 (idea 130): the security lens (Hacker) went dark while the rest of the
|
|
1267
|
+
// panel ran — the adversarial signature. Unlike other lenses, a missing
|
|
1268
|
+
// Hacker HARD-BLOCKS rather than falling through to manual-confirm: a diff
|
|
1269
|
+
// engineered to reliably crash the Hacker (parse_error/SUBAGENT_TIMEOUT) would
|
|
1270
|
+
// otherwise downgrade an auto-block into "await Archon", quietly removing the
|
|
1271
|
+
// one lens that catches injected XSS / client-authz regressions. Errored
|
|
1272
|
+
// workers are already auto-retried once (#465), so a Hacker still dark here has
|
|
1273
|
+
// failed twice — treat that as a FAIL, not flakiness. (The all-errored infra
|
|
1274
|
+
// outage is the 'unavailable' case above (#943); a real Hacker blocker is the
|
|
1275
|
+
// gatingFail case above — both take precedence over this branch.) Re-run the
|
|
1276
|
+
// grader once the diff/grader is healthy to clear it.
|
|
1277
|
+
const otherErrored = gating.filter((w) => w.error && w.kind !== 'hacker').map((w) => w.kind);
|
|
1278
|
+
verdict =
|
|
1279
|
+
`FAIL — ⚠ SECURITY LENS MISSING: the Hacker (security) worker errored and produced no signal; security is non-optional, so a reliably-missing Hacker hard-blocks (re-run the grader once healthy)` +
|
|
1280
|
+
(otherErrored.length ? ` — also errored: ${otherErrored.join(', ')}` : '') +
|
|
1281
|
+
advisoryNote;
|
|
1282
|
+
panelOutcome = 'fail';
|
|
1283
|
+
forwardedConfidence = 0;
|
|
1284
|
+
} else if (gatingError > 0) {
|
|
1285
|
+
// #126: a GATING worker that crashed (parse_error / SUBAGENT_TIMEOUT /
|
|
1286
|
+
// subprocess error) produced NO signal — missing information, not a
|
|
1287
|
+
// finding. With no substantive failures (and at least one gating worker
|
|
1288
|
+
// that DID report), fall through to manual-confirm rather than hard-FAIL on
|
|
1289
|
+
// infrastructure flakiness. Name the errored workers. The security (Hacker)
|
|
1290
|
+
// lens is NOT among them here — a missing Hacker is hard-blocked by the
|
|
1291
|
+
// securityLensErrored branch above (#574). An errored ADVISORY worker (Narc)
|
|
1292
|
+
// is not counted here: its lens is advisory, so its absence doesn't gate.
|
|
1293
|
+
// (The all-errored total-outage case is handled above, #943.)
|
|
1294
|
+
const erroredKinds = gating.filter((w) => w.error).map((w) => w.kind);
|
|
1295
|
+
verdict =
|
|
1296
|
+
`CONCERNS — ${gatingError}/${N} gating worker(s) errored (${erroredKinds.join(', ')}), none failed (manual-confirm available)` +
|
|
1297
|
+
advisoryNote;
|
|
1298
|
+
panelOutcome = 'concerns';
|
|
1299
|
+
forwardedConfidence = FALLTHROUGH_CONFIDENCE;
|
|
1300
|
+
} else if (gatingConcerns > 0 || advisoryFail) {
|
|
1301
|
+
// Gating-worker concerns, OR an advisory Narc 'fail' (downgraded here from a
|
|
1302
|
+
// hard block) → manual-confirm.
|
|
1303
|
+
const reasons = [];
|
|
1304
|
+
if (gatingConcerns > 0) reasons.push(`${gatingConcerns}/${N} gating worker(s) flagged concerns`);
|
|
1305
|
+
if (advisoryFail) reasons.push('narc (advisory) flagged a functional-fidelity failure');
|
|
1306
|
+
verdict = `CONCERNS — ${reasons.join('; ')} (manual-confirm available)`;
|
|
1307
|
+
panelOutcome = 'concerns';
|
|
1308
|
+
forwardedConfidence = FALLTHROUGH_CONFIDENCE;
|
|
1309
|
+
} else if (gatingAllPass) {
|
|
1310
|
+
// Every gating worker passed. A Narc 'concerns' here is advisory only.
|
|
1311
|
+
verdict = `PASS — ${gating.length}/${gating.length} gating worker(s) cleared${advisoryConcerns ? ' (narc advisory: concerns)' : ''}`;
|
|
1312
|
+
panelOutcome = 'pass';
|
|
1313
|
+
forwardedConfidence = medianConf;
|
|
1314
|
+
} else {
|
|
1315
|
+
// Should be unreachable (no fail/concerns and not all gating pass implies an
|
|
1316
|
+
// unknown verdict) but be defensive.
|
|
1317
|
+
verdict = `FAIL — unknown verdict state (pass=${passCount} concerns=${concernsCount} fail=${failCount} of ${N})`;
|
|
1318
|
+
panelOutcome = 'fail';
|
|
1319
|
+
forwardedConfidence = 0;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
const passed = panelOutcome === 'pass';
|
|
1323
|
+
|
|
1324
|
+
// Dedupe issues by (worker kind, file, summary) — intentional. If Narc and
|
|
1325
|
+
// Quality both flag the same (file, summary), the two appear separately
|
|
1326
|
+
// because they are independent confirmations from different lenses; merging
|
|
1327
|
+
// would erase that signal.
|
|
1328
|
+
const seen = new Set();
|
|
1329
|
+
const issues = [];
|
|
1330
|
+
for (const w of workerResults) {
|
|
1331
|
+
for (const it of (w.issues || [])) {
|
|
1332
|
+
if (!it || typeof it !== 'object') continue;
|
|
1333
|
+
const key = `${w.kind}::${it.file ?? ''}::${(it.summary ?? '').trim()}`;
|
|
1334
|
+
if (seen.has(key)) continue;
|
|
1335
|
+
seen.add(key);
|
|
1336
|
+
issues.push({ ...it, worker: w.kind });
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Build display notes.
|
|
1341
|
+
const lines = [];
|
|
1342
|
+
lines.push(`Subagent grader ${VERSION} (panel of ${N}: ${workerResults.map((w) => w.kind).join(', ')}) — ${verdict}`);
|
|
1343
|
+
if (ff != null) lines.push(` functional_fidelity (Narc): ${ff}/10`);
|
|
1344
|
+
if (cq != null) lines.push(` code_quality (Quality): ${cq}/10`);
|
|
1345
|
+
if (economy != null) lines.push(` economy (Quality, adv.): ${economy}/10`);
|
|
1346
|
+
if (security != null) lines.push(` security_score (Hacker): ${security}/10`);
|
|
1347
|
+
if (efficiencyScore != null) lines.push(` efficiency_score (EffMng): ${efficiencyScore}/10`);
|
|
1348
|
+
lines.push(` blended: ${blended}/10 (threshold ${THRESHOLD}, gating workers must PASS; narc advisory — #481)`);
|
|
1349
|
+
lines.push(` forwarded confidence: ${forwardedConfidence ?? '?'} (panel: ${panelOutcome})`);
|
|
1350
|
+
lines.push('');
|
|
1351
|
+
lines.push('Per-worker:');
|
|
1352
|
+
for (const w of workerResults) {
|
|
1353
|
+
const status = w.error ? 'ERROR' : w.verdict.toUpperCase();
|
|
1354
|
+
const scores = Object.entries(w.scores || {})
|
|
1355
|
+
.filter(([_, v]) => Number.isFinite(v))
|
|
1356
|
+
.map(([k, v]) => `${k.split('_').map((p) => p[0]).join('')}=${v}`)
|
|
1357
|
+
.join(' ');
|
|
1358
|
+
const confStr = w.confidence != null ? ` conf=${w.confidence}` : '';
|
|
1359
|
+
const errStr = w.error ? ` (${w.error})` : '';
|
|
1360
|
+
const retryStr = w.retried ? ' (retried — #465)' : '';
|
|
1361
|
+
lines.push(` ${w.kind.padEnd(10)} ${status.padEnd(8)} ${scores}${confStr} ${w.model ?? '?'}${errStr}${retryStr}`);
|
|
1362
|
+
}
|
|
1363
|
+
if (issues.length > 0) {
|
|
1364
|
+
lines.push('');
|
|
1365
|
+
lines.push(`Issues (${issues.length} unique across workers):`);
|
|
1366
|
+
for (const it of issues.slice(0, 30)) {
|
|
1367
|
+
const sev = String(it.severity || '?').padEnd(7);
|
|
1368
|
+
const file = String(it.file || '?');
|
|
1369
|
+
const summary = String(it.summary || '').replace(/\s+/g, ' ').trim();
|
|
1370
|
+
lines.push(` [${sev}] ${file} (${it.worker}) — ${summary}`);
|
|
1371
|
+
}
|
|
1372
|
+
if (issues.length > 30) {
|
|
1373
|
+
lines.push(` ... ${issues.length - 30} more (full set in signals.workers[].issues)`);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// #486 (ADR 0028 path 1): sum each worker's own subprocess cost (from its
|
|
1378
|
+
// `claude -p` envelope total_cost_usd). Workers that errored / never produced
|
|
1379
|
+
// a JSON envelope contribute null → 0. The deterministic route-rank pseudo-
|
|
1380
|
+
// worker has no subprocess and no cost_usd, so it's naturally excluded.
|
|
1381
|
+
const panelCostUsd = workerResults.reduce((s, w) => s + (Number(w.cost_usd) || 0), 0);
|
|
1382
|
+
|
|
1383
|
+
return {
|
|
1384
|
+
// server-compat top-level
|
|
1385
|
+
score: blended,
|
|
1386
|
+
passed,
|
|
1387
|
+
threshold: THRESHOLD,
|
|
1388
|
+
grader_kind: 'subagent-panel',
|
|
1389
|
+
grader_version: VERSION,
|
|
1390
|
+
// #486: the panel's own API spend (USD), for cost_log (source=grader).
|
|
1391
|
+
panel_cost_usd: panelCostUsd,
|
|
1392
|
+
// rubric_scores keep the v3 keys so the trend dashboard + audit-ships
|
|
1393
|
+
// legacy v1 entry-points continue to read them. Phase 2.1 added
|
|
1394
|
+
// security_score; Phase 2.2 adds efficiency_score. Both additive —
|
|
1395
|
+
// consumers that don't know about them just ignore the fields.
|
|
1396
|
+
rubric_scores: {
|
|
1397
|
+
functional_fidelity: ff,
|
|
1398
|
+
code_quality: cq,
|
|
1399
|
+
economy,
|
|
1400
|
+
code_quality_effective: cqEffective,
|
|
1401
|
+
security_score: security,
|
|
1402
|
+
efficiency_score: efficiencyScore,
|
|
1403
|
+
},
|
|
1404
|
+
signals: {
|
|
1405
|
+
grader_kind: 'subagent-panel',
|
|
1406
|
+
grader_version: VERSION,
|
|
1407
|
+
threshold: THRESHOLD,
|
|
1408
|
+
// forwardedConfidence drives the server-side gate alignment
|
|
1409
|
+
confidence: forwardedConfidence,
|
|
1410
|
+
panel_outcome: panelOutcome,
|
|
1411
|
+
workers: workerResults,
|
|
1412
|
+
worker_count: N,
|
|
1413
|
+
pass_count: passCount,
|
|
1414
|
+
concerns_count: concernsCount,
|
|
1415
|
+
fail_count: failCount,
|
|
1416
|
+
error_count: errorCount,
|
|
1417
|
+
panel_cost_usd: panelCostUsd, // #486: mirror into signals for the audit trail
|
|
1418
|
+
...meta,
|
|
1419
|
+
},
|
|
1420
|
+
notes_md: lines.join('\n'),
|
|
1421
|
+
issues,
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// #943: synthesize the distinct "grader unavailable / bypassed" result WITHOUT
|
|
1426
|
+
// spawning any worker. Same shape as aggregatePanel's return so every consumer
|
|
1427
|
+
// (buildGradePayload, the server's validateAndScore re-run, ship.js's
|
|
1428
|
+
// printGradeOutcome) works unchanged. ff/cq are null (nothing was graded) and
|
|
1429
|
+
// the forwarded confidence is FALLTHROUGH_CONFIDENCE, so the server gate treats
|
|
1430
|
+
// it as manual-confirm — never an auto-pass, never an auto-credit. panel_outcome
|
|
1431
|
+
// carries the 'unavailable' tag so ship.js presents "grader unavailable" rather
|
|
1432
|
+
// than a misleading "FAIL · 0/10". `reason` is recorded for the audit trail.
|
|
1433
|
+
function graderUnavailableResult({ reason, note, meta = {} } = {}) {
|
|
1434
|
+
const notes = [
|
|
1435
|
+
`Subagent grader ${VERSION} — GRADER UNAVAILABLE (${reason}).`,
|
|
1436
|
+
note || 'The grader did not run — this is an infra/outage/bypass condition, NOT a quality rejection. Manual-confirm available.',
|
|
1437
|
+
].join('\n');
|
|
1438
|
+
return {
|
|
1439
|
+
score: 0,
|
|
1440
|
+
passed: false,
|
|
1441
|
+
threshold: THRESHOLD,
|
|
1442
|
+
grader_kind: 'subagent-panel',
|
|
1443
|
+
grader_version: VERSION,
|
|
1444
|
+
panel_cost_usd: 0,
|
|
1445
|
+
rubric_scores: {
|
|
1446
|
+
functional_fidelity: null,
|
|
1447
|
+
code_quality: null,
|
|
1448
|
+
economy: null,
|
|
1449
|
+
code_quality_effective: null,
|
|
1450
|
+
security_score: null,
|
|
1451
|
+
efficiency_score: null,
|
|
1452
|
+
},
|
|
1453
|
+
signals: {
|
|
1454
|
+
grader_kind: 'subagent-panel',
|
|
1455
|
+
grader_version: VERSION,
|
|
1456
|
+
threshold: THRESHOLD,
|
|
1457
|
+
confidence: FALLTHROUGH_CONFIDENCE,
|
|
1458
|
+
panel_outcome: 'unavailable',
|
|
1459
|
+
grader_unavailable_reason: reason,
|
|
1460
|
+
workers: [],
|
|
1461
|
+
worker_count: 0,
|
|
1462
|
+
pass_count: 0,
|
|
1463
|
+
concerns_count: 0,
|
|
1464
|
+
fail_count: 0,
|
|
1465
|
+
error_count: 0,
|
|
1466
|
+
panel_cost_usd: 0,
|
|
1467
|
+
...meta,
|
|
1468
|
+
},
|
|
1469
|
+
notes_md: notes,
|
|
1470
|
+
issues: [],
|
|
1471
|
+
};
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// End-to-end v4 grader. Runs the worker panel through the cross-family
|
|
1475
|
+
// grader model and returns the panel-aggregated grade-result.
|
|
1476
|
+
//
|
|
1477
|
+
// workers: defaults to DEFAULT_PANEL (narc + quality). An array of worker
|
|
1478
|
+
// kinds (strings) like ['narc', 'quality']. Phase 2.1/2.2 will extend.
|
|
1479
|
+
// builderModel: the model the builder used. Defaults to ASSUMED_BUILDER_MODEL
|
|
1480
|
+
// (Opus, per CLAUDE.md §2). Drives selectGraderModel — workers pick the
|
|
1481
|
+
// cross-family alternative.
|
|
1482
|
+
async function gradeSubagent({ task, valueSummary, diff, changedFiles, diffStats, workers: workerKinds, builderModel, builderRank, runOpts = {} }) {
|
|
1483
|
+
// #943: bypass / disabled short-circuit — BEFORE resolving the panel or
|
|
1484
|
+
// spawning any worker. When the grader is globally bypassed (the
|
|
1485
|
+
// GRADER_BYPASS_ENABLED killswitch, ADR 0041) or a caller explicitly disables
|
|
1486
|
+
// it (runOpts.bypass), do NOT spawn the panel. Spawning into an outage/bypass
|
|
1487
|
+
// window is exactly what manufactured the "FAIL · 0/10" false-verdict in
|
|
1488
|
+
// session 1659 — the bypass was honored only DOWNSTREAM of the spawn. Return
|
|
1489
|
+
// the distinct 'unavailable' outcome so the result reads as "grader bypassed",
|
|
1490
|
+
// never a quality 0/10.
|
|
1491
|
+
if (runOpts.bypass === true || process.env.GRADER_BYPASS_ENABLED === '1') {
|
|
1492
|
+
const reason = runOpts.bypass === true ? 'bypass_requested' : 'grader_bypass_enabled';
|
|
1493
|
+
return graderUnavailableResult({
|
|
1494
|
+
reason,
|
|
1495
|
+
note: 'Grader bypassed before worker spawn (GRADER_BYPASS_ENABLED / runOpts.bypass) — no panel ran. Land via manual-confirm (/confirm).',
|
|
1496
|
+
meta: { builder_model_assumed: builderModel || ASSUMED_BUILDER_MODEL, bypassed: true },
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
const panel = workers.resolvePanel(workerKinds ?? workers.DEFAULT_PANEL);
|
|
1500
|
+
const graderModel = selectGraderModel({ builderModel });
|
|
1501
|
+
|
|
1502
|
+
// Deterministic route-rank pre-pass — its findings feed the Hacker as input;
|
|
1503
|
+
// its pseudo-worker result is appended to the panel below.
|
|
1504
|
+
const routeRank = routeRankPrePass(changedFiles);
|
|
1505
|
+
// Deterministic permission-path pre-pass (SEC #863) — a sub-Metic diff that
|
|
1506
|
+
// touches the rank/permission/ship-grade/deploy machinery hard-fails.
|
|
1507
|
+
const permPath = permissionPathPrePass(changedFiles, builderRank);
|
|
1508
|
+
const deterministicFindings = [
|
|
1509
|
+
...(routeRank ? routeRank.findings : []),
|
|
1510
|
+
...(permPath ? permPath.findings : []),
|
|
1511
|
+
];
|
|
1512
|
+
|
|
1513
|
+
const startedAt = Date.now();
|
|
1514
|
+
const workerPromises = panel.map((worker) => workers.runWorker(worker, {
|
|
1515
|
+
task,
|
|
1516
|
+
valueSummary,
|
|
1517
|
+
diff,
|
|
1518
|
+
changedFiles,
|
|
1519
|
+
diffStats,
|
|
1520
|
+
model: graderModel,
|
|
1521
|
+
runOpts,
|
|
1522
|
+
runSubagentFn: runSubagent,
|
|
1523
|
+
extractAssistantResultFn: extractAssistantResult,
|
|
1524
|
+
deterministicFindings: deterministicFindings.length ? deterministicFindings : null,
|
|
1525
|
+
}));
|
|
1526
|
+
const workerResults = await Promise.all(workerPromises);
|
|
1527
|
+
// Append the deterministic checks as pseudo-workers so the any-fail gate
|
|
1528
|
+
// treats a write-route 'unknown' OR a sub-Metic permission-path edit as a
|
|
1529
|
+
// hard block with no gate-logic change.
|
|
1530
|
+
if (routeRank) workerResults.push(routeRank.workerResult);
|
|
1531
|
+
if (permPath) workerResults.push(permPath.workerResult);
|
|
1532
|
+
const totalMs = Date.now() - startedAt;
|
|
1533
|
+
|
|
1534
|
+
return aggregatePanel(workerResults, {
|
|
1535
|
+
builder_model_assumed: builderModel || ASSUMED_BUILDER_MODEL,
|
|
1536
|
+
grader_model: graderModel,
|
|
1537
|
+
total_ms: totalMs,
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
// ---------- v3 multi-shot path (kept exported for explicit diagnostics) ----------
|
|
1542
|
+
//
|
|
1543
|
+
// Same shape as the original v3 gradeSubagent; renamed so the public name
|
|
1544
|
+
// (gradeSubagent) points at the v4 panel. Useful for re-running a single
|
|
1545
|
+
// generalist prompt N times for diagnostic comparison against the panel.
|
|
1546
|
+
async function gradeSubagentV3MultiShot({ task, valueSummary, diff, changedFiles, diffStats, shots = DEFAULT_SHOTS, builderModel, runOpts = {} }) {
|
|
1547
|
+
const prompt = buildPrompt({ task, valueSummary, diff, changedFiles, diffStats });
|
|
1548
|
+
const graderModel = selectGraderModel({ builderModel });
|
|
1549
|
+
const N = Math.max(1, Math.trunc(shots));
|
|
1550
|
+
|
|
1551
|
+
const startedAt = Date.now();
|
|
1552
|
+
const shotPromises = [];
|
|
1553
|
+
for (let i = 0; i < N; i++) {
|
|
1554
|
+
shotPromises.push(runOneShot({ prompt, model: graderModel, shotIndex: i, runOpts }));
|
|
1555
|
+
}
|
|
1556
|
+
const shotResults = await Promise.all(shotPromises);
|
|
1557
|
+
const totalMs = Date.now() - startedAt;
|
|
1558
|
+
|
|
1559
|
+
return aggregateShots(shotResults, {
|
|
1560
|
+
builder_model_assumed: builderModel || ASSUMED_BUILDER_MODEL,
|
|
1561
|
+
grader_model: graderModel,
|
|
1562
|
+
total_ms: totalMs,
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// =========================================================================
|
|
1567
|
+
// Legacy v1 (deterministic) — kept for scripts/gds/audit-ships.js only.
|
|
1568
|
+
// =========================================================================
|
|
1569
|
+
|
|
1570
|
+
function scoreEstActualBand(actualMinutes, estMinutes) {
|
|
1571
|
+
if (!Number.isFinite(actualMinutes) || actualMinutes <= 0) return 5;
|
|
1572
|
+
if (!Number.isFinite(estMinutes) || estMinutes <= 0) return 6;
|
|
1573
|
+
const ratio = actualMinutes / estMinutes;
|
|
1574
|
+
if (ratio >= 0.5 && ratio <= 2.0) return 10;
|
|
1575
|
+
if (ratio < 0.5) {
|
|
1576
|
+
if (ratio <= 0.1) return 0;
|
|
1577
|
+
return r1(10 * ((ratio - 0.1) / 0.4));
|
|
1578
|
+
}
|
|
1579
|
+
if (ratio > 2.0) {
|
|
1580
|
+
if (ratio >= 10) return 0;
|
|
1581
|
+
return r1(10 * (1 - (ratio - 2.0) / 8.0));
|
|
1582
|
+
}
|
|
1583
|
+
return 5;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
function scoreValueSummary(valueSummary) {
|
|
1587
|
+
const s = (valueSummary || '').trim();
|
|
1588
|
+
if (s.length === 0) return 0;
|
|
1589
|
+
if (s.length < 40) return 5;
|
|
1590
|
+
if (s.length < 120) return 8;
|
|
1591
|
+
return /\b(add|fix|update|remove|refactor|wire|land|migrate|enable|disable|introduce|deprecate|ship|deploy|prevent|capture|gate|allocate|carry|expose|surface)\b/i.test(s)
|
|
1592
|
+
? 10
|
|
1593
|
+
: 8;
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
function scoreDiffHygiene(diffStats) {
|
|
1597
|
+
if (!diffStats) return 5;
|
|
1598
|
+
const files = Number(diffStats.files_changed) || 0;
|
|
1599
|
+
const added = Number(diffStats.lines_added) || 0;
|
|
1600
|
+
const removed = Number(diffStats.lines_removed) || 0;
|
|
1601
|
+
if (files === 0 || added + removed === 0) return 0;
|
|
1602
|
+
if (files > 50) return 5;
|
|
1603
|
+
return 10;
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
function scoreScannerSmoke(verification) {
|
|
1607
|
+
if (!verification) return 5;
|
|
1608
|
+
return verification.scanner_clean && verification.smoke_clean ? 10 : 5;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
function scoreScopingSignal(scopingConfidence) {
|
|
1612
|
+
if (scopingConfidence == null) return 6;
|
|
1613
|
+
const c = Number(scopingConfidence);
|
|
1614
|
+
if (c <= 0) return 2;
|
|
1615
|
+
if (c === 1) return 4;
|
|
1616
|
+
if (c === 2) return 6;
|
|
1617
|
+
if (c === 3) return 7;
|
|
1618
|
+
if (c === 4) return 8;
|
|
1619
|
+
return 10;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function scoreLocNetNegative(diffStats) {
|
|
1623
|
+
if (!diffStats) return 5;
|
|
1624
|
+
const added = Number(diffStats.lines_added) || 0;
|
|
1625
|
+
const removed = Number(diffStats.lines_removed) || 0;
|
|
1626
|
+
if (removed > added) return 10;
|
|
1627
|
+
if (removed === added) return 5;
|
|
1628
|
+
return 0;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
function scoreLearningRow(learningRow) {
|
|
1632
|
+
if (!learningRow) return 0;
|
|
1633
|
+
const body = (learningRow.body_md || '').trim();
|
|
1634
|
+
if (body.length > 100) return 10;
|
|
1635
|
+
if (body.length > 0) return 5;
|
|
1636
|
+
return 0;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function scoreArtRubricAvg(value) {
|
|
1640
|
+
if (!Number.isFinite(value)) return null;
|
|
1641
|
+
return clamp(r1(value), 0, 10);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function blendWeightsForKind(kind) {
|
|
1645
|
+
const blended = {};
|
|
1646
|
+
for (const [name, def] of Object.entries(V1.weights.common)) {
|
|
1647
|
+
blended[name] = def.weight;
|
|
1648
|
+
}
|
|
1649
|
+
const kindBonus = V1.weights.kind_specific[kind];
|
|
1650
|
+
if (kindBonus) {
|
|
1651
|
+
for (const [name, def] of Object.entries(kindBonus)) {
|
|
1652
|
+
if (name === 'loc_net_negative' && 'diff_hygiene' in blended) delete blended.diff_hygiene;
|
|
1653
|
+
if (name === 'learning_row' && 'diff_hygiene' in blended) delete blended.diff_hygiene;
|
|
1654
|
+
blended[name] = def.weight;
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
const total = Object.values(blended).reduce((a, b) => a + b, 0);
|
|
1658
|
+
for (const k of Object.keys(blended)) blended[k] = blended[k] / total;
|
|
1659
|
+
return blended;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// Legacy v1 entry. Renamed from `grade()` in V3.R18 (#209). Now ONLY
|
|
1663
|
+
// called from scripts/gds/audit-ships.js (retroactive #204 audit). The
|
|
1664
|
+
// runtime ship path uses gradeSubagent (v3 multi-shot).
|
|
1665
|
+
function gradeDeterministic({ task, verification, actualMinutes, valueSummary, learningRow }) {
|
|
1666
|
+
const kind = task?.kind || 'unclassified';
|
|
1667
|
+
const weights = blendWeightsForKind(kind);
|
|
1668
|
+
const rubricScores = {};
|
|
1669
|
+
const signals = {
|
|
1670
|
+
kind,
|
|
1671
|
+
est_minutes: task?.est_minutes ?? null,
|
|
1672
|
+
actual_minutes: actualMinutes ?? null,
|
|
1673
|
+
scoping_confidence: task?.scoping_confidence ?? null,
|
|
1674
|
+
scanner_clean: verification?.scanner_clean ?? null,
|
|
1675
|
+
smoke_clean: verification?.smoke_clean ?? null,
|
|
1676
|
+
diff_stats: verification?.diff_stats ?? null,
|
|
1677
|
+
art_rubric_avg: verification?.art_rubric_avg ?? null,
|
|
1678
|
+
value_summary_chars: (valueSummary || '').trim().length,
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
if ('est_actual_band' in weights) {
|
|
1682
|
+
rubricScores.est_actual_band = scoreEstActualBand(actualMinutes, task?.est_minutes);
|
|
1683
|
+
}
|
|
1684
|
+
if ('value_summary' in weights) {
|
|
1685
|
+
rubricScores.value_summary = scoreValueSummary(valueSummary);
|
|
1686
|
+
}
|
|
1687
|
+
if ('diff_hygiene' in weights) {
|
|
1688
|
+
rubricScores.diff_hygiene = scoreDiffHygiene(verification?.diff_stats);
|
|
1689
|
+
}
|
|
1690
|
+
if ('scanner_smoke' in weights) {
|
|
1691
|
+
rubricScores.scanner_smoke = scoreScannerSmoke(verification);
|
|
1692
|
+
}
|
|
1693
|
+
if ('scoping_signal' in weights) {
|
|
1694
|
+
rubricScores.scoping_signal = scoreScopingSignal(task?.scoping_confidence);
|
|
1695
|
+
}
|
|
1696
|
+
if ('loc_net_negative' in weights) {
|
|
1697
|
+
rubricScores.loc_net_negative = scoreLocNetNegative(verification?.diff_stats);
|
|
1698
|
+
}
|
|
1699
|
+
if ('learning_row' in weights) {
|
|
1700
|
+
rubricScores.learning_row = scoreLearningRow(learningRow);
|
|
1701
|
+
}
|
|
1702
|
+
if ('art_rubric_avg' in weights) {
|
|
1703
|
+
const s = scoreArtRubricAvg(verification?.art_rubric_avg);
|
|
1704
|
+
if (s == null) {
|
|
1705
|
+
const w = weights.art_rubric_avg;
|
|
1706
|
+
delete weights.art_rubric_avg;
|
|
1707
|
+
const remaining = Object.values(weights).reduce((a, b) => a + b, 0);
|
|
1708
|
+
if (remaining > 0) {
|
|
1709
|
+
for (const k of Object.keys(weights)) weights[k] = weights[k] / remaining;
|
|
1710
|
+
}
|
|
1711
|
+
} else {
|
|
1712
|
+
rubricScores.art_rubric_avg = s;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
let score = 0;
|
|
1717
|
+
for (const [name, w] of Object.entries(weights)) {
|
|
1718
|
+
if (rubricScores[name] != null) score += rubricScores[name] * w;
|
|
1719
|
+
}
|
|
1720
|
+
score = r1(clamp(score, 0, 10));
|
|
1721
|
+
|
|
1722
|
+
const threshold = V1.threshold;
|
|
1723
|
+
const passed = score >= threshold;
|
|
1724
|
+
|
|
1725
|
+
const lines = [];
|
|
1726
|
+
lines.push(`Auto-grader ${V1.version} (deprecated) — task_kind=${kind}.`);
|
|
1727
|
+
lines.push(`Score ${score}/10 vs threshold ${threshold} → ${passed ? 'PASS' : 'FAIL'}.`);
|
|
1728
|
+
lines.push('');
|
|
1729
|
+
lines.push('Per-criterion:');
|
|
1730
|
+
for (const [name, w] of Object.entries(weights)) {
|
|
1731
|
+
const s = rubricScores[name];
|
|
1732
|
+
if (s == null) continue;
|
|
1733
|
+
const contrib = r1(s * w);
|
|
1734
|
+
lines.push(` - ${name.padEnd(20)} ${String(s).padStart(4)}/10 × ${w.toFixed(2)} = ${contrib}`);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
return {
|
|
1738
|
+
score,
|
|
1739
|
+
passed,
|
|
1740
|
+
threshold,
|
|
1741
|
+
grader_kind: 'auto',
|
|
1742
|
+
grader_version: V1.version,
|
|
1743
|
+
rubric_scores: rubricScores,
|
|
1744
|
+
signals,
|
|
1745
|
+
notes_md: lines.join('\n'),
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
module.exports = {
|
|
1750
|
+
// v4 surface (active panel-based gate)
|
|
1751
|
+
aggregatePanel,
|
|
1752
|
+
gradeSubagent, // panel by default
|
|
1753
|
+
graderUnavailableResult, // #943: distinct grader-unavailable / bypassed result (no spawn)
|
|
1754
|
+
isTrivialDiff, // #513: copy/docs fast-path classifier
|
|
1755
|
+
WORKERS: workers, // registry re-export
|
|
1756
|
+
routeRankPrePass, // deterministic route-rank pre-pass (#475)
|
|
1757
|
+
permissionPathPrePass, // deterministic sub-Metic permission-path gate (SEC #863)
|
|
1758
|
+
serverSideGuards, // SR-9 / SR-18: server-authoritative grade hardening (downgrade-only)
|
|
1759
|
+
changeMagnitude, // SR-9 helper: file/line magnitude from committed_files + diff_stats
|
|
1760
|
+
FALLTHROUGH_CONFIDENCE,
|
|
1761
|
+
// v3 surface (kept exported for diagnostics + tests)
|
|
1762
|
+
buildPrompt,
|
|
1763
|
+
selectGraderModel,
|
|
1764
|
+
validateAndScore,
|
|
1765
|
+
aggregateShots,
|
|
1766
|
+
runSubagent,
|
|
1767
|
+
runSubagentCached, // System 5 (task 1359): opt-in content-addressed cached path
|
|
1768
|
+
runOneShot,
|
|
1769
|
+
gradeSubagentV3MultiShot, // explicit v3 multi-shot diagnostic path
|
|
1770
|
+
extractJson,
|
|
1771
|
+
extractAssistantResult,
|
|
1772
|
+
extractCostUsd, // #486: parse total_cost_usd from the envelope
|
|
1773
|
+
THRESHOLD,
|
|
1774
|
+
VERSION,
|
|
1775
|
+
DEFAULT_GRADER_MODEL,
|
|
1776
|
+
DEFAULT_SHOTS,
|
|
1777
|
+
ASSUMED_BUILDER_MODEL,
|
|
1778
|
+
// legacy v1 (audit-ships.js only)
|
|
1779
|
+
gradeDeterministic,
|
|
1780
|
+
scoreEstActualBand,
|
|
1781
|
+
scoreValueSummary,
|
|
1782
|
+
scoreDiffHygiene,
|
|
1783
|
+
scoreScannerSmoke,
|
|
1784
|
+
scoreScopingSignal,
|
|
1785
|
+
scoreLocNetNegative,
|
|
1786
|
+
scoreLearningRow,
|
|
1787
|
+
scoreArtRubricAvg,
|
|
1788
|
+
blendWeightsForKind,
|
|
1789
|
+
RUBRIC,
|
|
1790
|
+
};
|