@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,257 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Discord #bugs → GDS tasks (kind='bug') — Phase 3 F8 (ADR 0033 §F8, ADR 0047).
|
|
4
|
+
//
|
|
5
|
+
// The bot turns a message posted in the designated #bugs channel into a GDS
|
|
6
|
+
// task with kind='bug', STRAIGHT THROUGH — no idea_inbox, no triage step. This
|
|
7
|
+
// is the deliberate difference from #ideas (discord-inbound.js): a bug is a
|
|
8
|
+
// small fix a builder wants to drop without context-switching, and it becomes
|
|
9
|
+
// claimable work directly.
|
|
10
|
+
//
|
|
11
|
+
// ANTI-ABUSE GUARD (ADR 0047 — the red-team resolution). Creating claimable
|
|
12
|
+
// work with no triage gate is an injection surface, so this path is guarded in
|
|
13
|
+
// depth, all server-side, all fail-closed:
|
|
14
|
+
// 1. LINKED-BUILDER-ONLY. An unlinked Discord author cannot create a task —
|
|
15
|
+
// their message is ignored (not filed). Unlike #ideas (which captures
|
|
16
|
+
// unattributed), making *claimable work* requires being an accountable,
|
|
17
|
+
// known builder. This is the single biggest lever.
|
|
18
|
+
// 2. RANK-AWARE LANDING. A linked Metic+ builder's bug lands 'ready'
|
|
19
|
+
// (instantly claimable — the true straight-through path their trust earns).
|
|
20
|
+
// A linked Xenos/Thetes builder's bug lands 'backlog' (needs a one-click
|
|
21
|
+
// Metic+ promote, like any other backlog task). Mirrors the trust ladder
|
|
22
|
+
// used everywhere else in the GDS.
|
|
23
|
+
// 3. PER-BUILDER RATE-LIMIT. At most N bug-tasks per builder per rolling hour
|
|
24
|
+
// (counted from audit_log). Over-limit filings are ignored AND audited so
|
|
25
|
+
// a spammer surfaces in forensics / the red-team view.
|
|
26
|
+
// 4. BODY SCREEN. Empty / too-short / text-free messages are ignored before
|
|
27
|
+
// any DB write; title + body are capped to the GDS limits.
|
|
28
|
+
//
|
|
29
|
+
// TRUST BOUNDARY (ADR 0016 / ADR 0033 §1): the bot asserts only WHO posted
|
|
30
|
+
// (the linked builder); the landing state is decided server-side from that
|
|
31
|
+
// builder's LIVE rank, never from anything in the message. The bot grants
|
|
32
|
+
// nothing. Every filing — and every rate-limit rejection — is audited with
|
|
33
|
+
// surface='discord_bot'.
|
|
34
|
+
//
|
|
35
|
+
// Requires (go-live): the Message Content PRIVILEGED intent + a pinned
|
|
36
|
+
// DISCORD_BUGS_CHANNEL_ID. Inert until both are set.
|
|
37
|
+
|
|
38
|
+
const db = require('./db');
|
|
39
|
+
const { auditDiscordBot } = require('./discord-audit');
|
|
40
|
+
const auth = require('./auth');
|
|
41
|
+
const attachments = require('./bug-attachments');
|
|
42
|
+
const { combineParts, createMessageAggregator } = require('./discord-message-buffer');
|
|
43
|
+
const { LIMITS } = require('../../src/module-api');
|
|
44
|
+
|
|
45
|
+
// Ranks whose bugs land 'ready' (instantly claimable). Everyone else linked
|
|
46
|
+
// lands 'backlog'. Metic+ — matches REQUIRES_RANK_TIER >= metic in db.js.
|
|
47
|
+
const READY_RANKS = new Set(['metic', 'archon']);
|
|
48
|
+
|
|
49
|
+
// Smallest message we'll treat as a real bug report. Anything shorter is noise.
|
|
50
|
+
const MIN_BUG_CHARS = 8;
|
|
51
|
+
|
|
52
|
+
// Default rate-limit: bug-tasks one builder may create per rolling hour. Env
|
|
53
|
+
// DISCORD_BUGS_RATE_PER_HOUR overrides (a number); falls back to this.
|
|
54
|
+
const DEFAULT_RATE_PER_HOUR = 5;
|
|
55
|
+
|
|
56
|
+
// Default GDS version a filed bug is filed under. Most builder-filed bugs are
|
|
57
|
+
// about the dev system; an Archon can re-home a game bug to a product version.
|
|
58
|
+
// Env DISCORD_BUGS_VERSION_ID overrides.
|
|
59
|
+
const DEFAULT_BUGS_VERSION_ID = 'GDS-V3';
|
|
60
|
+
|
|
61
|
+
// Modest reward for fixing a drive-by bug. Env DISCORD_BUGS_CREDITS overrides.
|
|
62
|
+
const DEFAULT_BUGS_CREDITS = 10;
|
|
63
|
+
|
|
64
|
+
// Auto-pairing window: consecutive #bugs messages from the SAME author within
|
|
65
|
+
// this window are combined into ONE bug-task (so "screenshot" + "here's what
|
|
66
|
+
// happened" posted seconds apart — in either order — become one report, not
|
|
67
|
+
// two). Env DISCORD_BUGS_PAIR_WINDOW_MS overrides.
|
|
68
|
+
const DEFAULT_PAIR_WINDOW_MS = 45 * 1000;
|
|
69
|
+
|
|
70
|
+
// Title for a bug that arrives as image(s) with no usable text.
|
|
71
|
+
const SCREENSHOT_TITLE = 'Bug report (screenshot)';
|
|
72
|
+
|
|
73
|
+
function rateLimitPerHour(env = process.env) {
|
|
74
|
+
const n = Number(env.DISCORD_BUGS_RATE_PER_HOUR);
|
|
75
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_RATE_PER_HOUR;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function bugsVersionId(env = process.env) {
|
|
79
|
+
return env.DISCORD_BUGS_VERSION_ID || DEFAULT_BUGS_VERSION_ID;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function bugsCredits(env = process.env) {
|
|
83
|
+
const n = Number(env.DISCORD_BUGS_CREDITS);
|
|
84
|
+
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : DEFAULT_BUGS_CREDITS;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function pairWindowMs(env = process.env) {
|
|
88
|
+
const n = Number(env.DISCORD_BUGS_PAIR_WINDOW_MS);
|
|
89
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : DEFAULT_PAIR_WINDOW_MS;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// PURE — decide where a bug lands from the reporter's LIVE rank. Metic+ →
|
|
93
|
+
// 'ready' (straight-through, claimable now); any other linked rank → 'backlog'
|
|
94
|
+
// (a Metic+ promotes it, the lightweight confirm gate). Unit-tested.
|
|
95
|
+
function decideBugLanding(rank) {
|
|
96
|
+
return READY_RANKS.has(String(rank || '').toLowerCase()) ? 'ready' : 'backlog';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// PURE — screen + shape a raw message into { ok, title, bodyMd } or
|
|
100
|
+
// { ok:false, reason }. The body screen rejects blank / too-short / text-free
|
|
101
|
+
// (no letters) messages before any DB write — UNLESS the message carries
|
|
102
|
+
// image(s): an image is content, so an image-only bug is allowed through with a
|
|
103
|
+
// default title. Title = first non-empty line (capped) or the screenshot
|
|
104
|
+
// title; body = full message text (capped, possibly empty). Unit-tested.
|
|
105
|
+
function screenBug(content, { hasImages = false } = {}) {
|
|
106
|
+
const trimmed = typeof content === 'string' ? content.trim() : '';
|
|
107
|
+
const hasText = trimmed.length >= MIN_BUG_CHARS && /[a-zA-Z]/.test(trimmed);
|
|
108
|
+
|
|
109
|
+
if (!hasText && !hasImages) {
|
|
110
|
+
if (!trimmed) return { ok: false, reason: 'empty' };
|
|
111
|
+
if (trimmed.length < MIN_BUG_CHARS) return { ok: false, reason: 'too_short' };
|
|
112
|
+
return { ok: false, reason: 'no_text' };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const cap = LIMITS.TITLE;
|
|
116
|
+
let title;
|
|
117
|
+
if (trimmed) {
|
|
118
|
+
const firstLine = (trimmed.split('\n').find((l) => l.trim()) || trimmed).trim();
|
|
119
|
+
title = firstLine.length > cap ? `${firstLine.slice(0, cap - 1)}…` : firstLine;
|
|
120
|
+
} else {
|
|
121
|
+
title = SCREENSHOT_TITLE;
|
|
122
|
+
}
|
|
123
|
+
const bodyMd = trimmed.length > LIMITS.DESCRIPTION ? trimmed.slice(0, LIMITS.DESCRIPTION) : trimmed;
|
|
124
|
+
return { ok: true, title, bodyMd };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const realDeps = {
|
|
128
|
+
getBuilderByDiscordUserId: db.getBuilderByDiscordUserId,
|
|
129
|
+
countRecentDiscordBugFilings: db.countRecentDiscordBugFilings,
|
|
130
|
+
createTask: db.createTask,
|
|
131
|
+
insertAuditLog: db.insertAuditLog,
|
|
132
|
+
persistImages: attachments.persistImages,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// File one inbound #bugs message as a GDS task (kind='bug'). NEVER throws (the
|
|
136
|
+
// caller is a gateway event handler). Returns a small status object for
|
|
137
|
+
// logging/tests. `config` is injectable so tests don't touch process.env.
|
|
138
|
+
async function recordInboundBug({ discordUserId, content, imageUrls = [], messageId }, deps = realDeps, config = {}) {
|
|
139
|
+
if (!auth.isValidDiscordUserId(discordUserId)) {
|
|
140
|
+
return { outcome: 'ignored', reason: 'bad_discord_user_id' };
|
|
141
|
+
}
|
|
142
|
+
const hasImages = Array.isArray(imageUrls) && imageUrls.length > 0;
|
|
143
|
+
const screened = screenBug(content, { hasImages });
|
|
144
|
+
if (!screened.ok) return { outcome: 'ignored', reason: screened.reason };
|
|
145
|
+
|
|
146
|
+
const limit = config.rateLimitPerHour ?? rateLimitPerHour();
|
|
147
|
+
const versionId = config.versionId ?? bugsVersionId();
|
|
148
|
+
const credits = config.credits ?? bugsCredits();
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// (1) LINKED-BUILDER-ONLY. No linked builder → reject (do not file). An
|
|
152
|
+
// unlinked author cannot inject claimable work.
|
|
153
|
+
const builder = await deps.getBuilderByDiscordUserId(discordUserId);
|
|
154
|
+
if (!builder) return { outcome: 'ignored', reason: 'unlinked' };
|
|
155
|
+
|
|
156
|
+
// (3) PER-BUILDER RATE-LIMIT. Count this builder's bug filings in the last
|
|
157
|
+
// hour; over the cap → reject AND audit (so a spammer is visible in
|
|
158
|
+
// forensics / the red-team view), but never throw.
|
|
159
|
+
const sinceIso = new Date(Date.now() - 60 * 60 * 1000).toISOString();
|
|
160
|
+
let recent = 0;
|
|
161
|
+
let countFailed = false;
|
|
162
|
+
try {
|
|
163
|
+
recent = await deps.countRecentDiscordBugFilings(builder.id, sinceIso);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
// L4 (red-team 2026-06-12): FAIL CLOSED on a count error. The old behaviour
|
|
166
|
+
// proceeded ("fail-open"), which let a DB error be used to slip past the
|
|
167
|
+
// per-builder limit. A failed count means we cannot prove the caller is
|
|
168
|
+
// under the cap, so treat it as rate-limited (deny) and audit it. Never
|
|
169
|
+
// throw — the caller is a gateway event handler.
|
|
170
|
+
console.error('[discord-bot] bug rate-limit count failed (denying — fail-closed):', e.message);
|
|
171
|
+
countFailed = true;
|
|
172
|
+
}
|
|
173
|
+
if (countFailed || recent >= limit) {
|
|
174
|
+
const reason = countFailed ? 'rate_limit_count_error' : 'rate_limited';
|
|
175
|
+
await auditDiscordBot(deps, {
|
|
176
|
+
builderId: builder.id,
|
|
177
|
+
route: 'discord:bugs',
|
|
178
|
+
requestBodyRedacted: { discord_user_id: discordUserId, message_id: messageId, rejected: reason, recent },
|
|
179
|
+
responseStatus: 429,
|
|
180
|
+
logLabel: 'bug rate-limit audit failed',
|
|
181
|
+
});
|
|
182
|
+
return { outcome: 'ignored', reason, recent };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// (2) RANK-AWARE LANDING — decided from the LIVE rank, never the message.
|
|
186
|
+
const status = decideBugLanding(builder.rank);
|
|
187
|
+
|
|
188
|
+
// Re-host any images on our server (durable — Discord CDN links expire). The
|
|
189
|
+
// guard (host allowlist + content-type + magic-byte sniff + size/count caps)
|
|
190
|
+
// lives in bug-attachments.persistImages; refused images are skipped, not
|
|
191
|
+
// fatal. A persist failure must not lose the report.
|
|
192
|
+
let stored = [];
|
|
193
|
+
if (hasImages) {
|
|
194
|
+
try {
|
|
195
|
+
stored = await deps.persistImages(imageUrls, { messageId });
|
|
196
|
+
} catch (e) {
|
|
197
|
+
console.error('[discord-bot] bug image persist failed (filing without images):', e.message);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Provenance footer so a claimer knows where the bug came from + who to ask.
|
|
202
|
+
// The raw Discord snowflake is NOT embedded (it lives in the Archon-only
|
|
203
|
+
// audit_log); only the builder login. Then the durable attachment links.
|
|
204
|
+
const origin = `\n\n— reported from Discord #bugs by ${builder.github_login}`;
|
|
205
|
+
let description = `${screened.bodyMd}${origin}`;
|
|
206
|
+
if (stored.length > 0) {
|
|
207
|
+
const imgs = stored.map((s, i) => ``).join('\n');
|
|
208
|
+
description += `\n\nAttachments (${stored.length}):\n${imgs}`;
|
|
209
|
+
}
|
|
210
|
+
description = description.slice(0, LIMITS.DESCRIPTION);
|
|
211
|
+
|
|
212
|
+
const task = await deps.createTask({
|
|
213
|
+
versionId,
|
|
214
|
+
title: screened.title,
|
|
215
|
+
description,
|
|
216
|
+
status,
|
|
217
|
+
kind: 'bug',
|
|
218
|
+
priority: 3,
|
|
219
|
+
creditsReward: credits,
|
|
220
|
+
source: 'discord:bugs',
|
|
221
|
+
sourceRef: `discord:${messageId}`,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await auditDiscordBot(deps, {
|
|
225
|
+
builderId: builder.id,
|
|
226
|
+
route: 'discord:bugs',
|
|
227
|
+
requestBodyRedacted: { discord_user_id: discordUserId, message_id: messageId, task_id: task.id, status, images: stored.length },
|
|
228
|
+
responseStatus: 201,
|
|
229
|
+
logLabel: 'bug audit-log write failed',
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return { outcome: 'filed', taskId: task.id, status, builderId: builder.id, images: stored.length };
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error('[discord-bot] recordInboundBug failed:', err.message);
|
|
235
|
+
return { outcome: 'error', error: err.message };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Auto-pairing aggregator for #bugs — a thin wrapper over the shared
|
|
240
|
+
// per-author burst aggregator (discord-message-buffer.js) that supplies the
|
|
241
|
+
// #bugs default window. onFlush files the combined burst as ONE bug.
|
|
242
|
+
function createBugAggregator({ onFlush, windowMs = pairWindowMs(), timers = global } = {}) {
|
|
243
|
+
return createMessageAggregator({ onFlush, windowMs, timers });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
module.exports = {
|
|
247
|
+
screenBug,
|
|
248
|
+
decideBugLanding,
|
|
249
|
+
recordInboundBug,
|
|
250
|
+
combineParts,
|
|
251
|
+
createBugAggregator,
|
|
252
|
+
realDeps,
|
|
253
|
+
rateLimitPerHour,
|
|
254
|
+
bugsVersionId,
|
|
255
|
+
bugsCredits,
|
|
256
|
+
pairWindowMs,
|
|
257
|
+
};
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Discord channel management — config-driven reconcile (ADR 0037, task #727).
|
|
4
|
+
//
|
|
5
|
+
// ONE-WAY: repo config -> Discord. The bot makes the guild's channel layout
|
|
6
|
+
// match docs/discord/channels.json: it matches existing channels by slug
|
|
7
|
+
// (emoji + separators stripped), edits their topic + view-permissions in place,
|
|
8
|
+
// and creates anything missing. It NEVER deletes a channel. Like role-sync
|
|
9
|
+
// (discord-role-sync.js / ADR 0033 §4) this only READS the repo and WRITES
|
|
10
|
+
// Discord, so a channel can never feed authority back into the GDS.
|
|
11
|
+
//
|
|
12
|
+
// TRUST BOUNDARY (ADR 0016): channels are a cosmetic reflection of an
|
|
13
|
+
// Archon-reviewed config file, never a key. The COMMAND surface that triggers a
|
|
14
|
+
// reconcile (routes/discord.js) is gated by requireRank('archon') against the
|
|
15
|
+
// live builders.rank — the same enforcement point every privileged route uses.
|
|
16
|
+
// This module performs no rank checks itself; it is invoked only by the bot's
|
|
17
|
+
// own passive sweep or by an already-authorized Archon command.
|
|
18
|
+
//
|
|
19
|
+
// KILL-SWITCH: the passive sweep in discord-bot.js is gated by
|
|
20
|
+
// DISCORD_CHANNELS_MANAGED (default OFF, fail-closed) so a server never
|
|
21
|
+
// surprise-edits a guild. With it off, channels are only ever touched by an
|
|
22
|
+
// explicit Archon command.
|
|
23
|
+
//
|
|
24
|
+
// FAIL-SAFE / best-effort: every Discord write is wrapped — a 403 (missing
|
|
25
|
+
// Manage Channels), a transient error, or a missing role id logs and the
|
|
26
|
+
// reconcile moves on. It must never throw into the bot's ready handler.
|
|
27
|
+
|
|
28
|
+
const fs = require('node:fs');
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
|
|
31
|
+
// Discord VIEW_CHANNEL permission bit (1 << 10). The only permission this
|
|
32
|
+
// module manages — every other bit on a channel is preserved on edit.
|
|
33
|
+
const VIEW_CHANNEL = 1024n;
|
|
34
|
+
|
|
35
|
+
// The rank ladder low->high. Canonical copy is db.LIVE_RANK_LADDER, but this
|
|
36
|
+
// module is kept dependency-free (no db.js → no pg Pool side-effect for a
|
|
37
|
+
// Discord-config CLI), so we keep a LOCAL copy rather than import — the same
|
|
38
|
+
// deliberate trade-off permission-path-check.js makes (D1: the ladder has three
|
|
39
|
+
// homes that cannot share one module; do not force-merge). A
|
|
40
|
+
// `view: "<rank>"` grants that rank and everyone above it.
|
|
41
|
+
const RANK_LADDER = ['xenos', 'thetes', 'metic', 'archon'];
|
|
42
|
+
|
|
43
|
+
const CONFIG_PATH = path.resolve(__dirname, '..', '..', 'docs', 'discord', 'channels.json');
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// PURE helpers (no Discord, no IO) — the load-bearing, fully-tested logic.
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
// Normalize a channel/category name to a stable slug for matching. Strips
|
|
50
|
+
// emoji and any non-alphanumeric run down to single hyphens, lowercased.
|
|
51
|
+
// "📯-ship-news" -> "ship-news"
|
|
52
|
+
// "👋-welcome-and-start"-> "welcome-and-start"
|
|
53
|
+
// "BUILDERS" -> "builders"
|
|
54
|
+
function slugify(name) {
|
|
55
|
+
return String(name || '')
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
58
|
+
.replace(/^-+|-+$/g, '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// The set of rank-role ids that should be ALLOWED to view a channel with the
|
|
62
|
+
// given `view`, plus whether @everyone is allowed. Returns role ids resolved
|
|
63
|
+
// through roleMap; ranks with no configured role id are reported in `missing`.
|
|
64
|
+
function resolveViewRoles(view, roleMap) {
|
|
65
|
+
if (!view || view === 'everyone') {
|
|
66
|
+
return { everyone: true, allowRoleIds: [], missing: [] };
|
|
67
|
+
}
|
|
68
|
+
const idx = RANK_LADDER.indexOf(view);
|
|
69
|
+
if (idx === -1) {
|
|
70
|
+
// Unknown view value: fail safe to most-restrictive (archon-only).
|
|
71
|
+
const aid = roleMap.archon;
|
|
72
|
+
return { everyone: false, allowRoleIds: aid ? [aid] : [], missing: aid ? [] : ['archon'], unknownView: view };
|
|
73
|
+
}
|
|
74
|
+
const ranks = RANK_LADDER.slice(idx); // this rank and every rank above it
|
|
75
|
+
const allowRoleIds = [];
|
|
76
|
+
const missing = [];
|
|
77
|
+
for (const r of ranks) {
|
|
78
|
+
if (roleMap[r]) allowRoleIds.push(roleMap[r]);
|
|
79
|
+
else missing.push(r);
|
|
80
|
+
}
|
|
81
|
+
return { everyone: false, allowRoleIds, missing };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Merge the desired VIEW decisions into a channel's CURRENT permission
|
|
85
|
+
// overwrites, preserving every non-VIEW bit and every unmanaged role's
|
|
86
|
+
// overwrite. `current` is an array of { id, type, allow, deny } where allow/deny
|
|
87
|
+
// are bitfield strings (Discord's REST shape). Returns the same shape.
|
|
88
|
+
// - everyoneId is allowed/denied VIEW per `decision.everyone`.
|
|
89
|
+
// - each id in decision.allowRoleIds gets VIEW allowed.
|
|
90
|
+
// - any managed rank role NOT in allowRoleIds (but present in roleMap) gets
|
|
91
|
+
// its VIEW overwrite cleared so a downgrade actually removes access.
|
|
92
|
+
function mergeViewOverwrites(current, decision, ctx) {
|
|
93
|
+
const { everyoneId, roleMap } = ctx;
|
|
94
|
+
const byId = new Map();
|
|
95
|
+
for (const o of current || []) {
|
|
96
|
+
byId.set(String(o.id), { id: String(o.id), type: o.type ?? 0, allow: BigInt(o.allow || 0), deny: BigInt(o.deny || 0) });
|
|
97
|
+
}
|
|
98
|
+
const ensure = (id, type) => {
|
|
99
|
+
if (!byId.has(id)) byId.set(id, { id, type, allow: 0n, deny: 0n });
|
|
100
|
+
return byId.get(id);
|
|
101
|
+
};
|
|
102
|
+
const setView = (id, type, allow) => {
|
|
103
|
+
const e = ensure(id, type);
|
|
104
|
+
if (allow) { e.allow |= VIEW_CHANNEL; e.deny &= ~VIEW_CHANNEL; }
|
|
105
|
+
else { e.deny |= VIEW_CHANNEL; e.allow &= ~VIEW_CHANNEL; }
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// @everyone (role id == guild id, type 0).
|
|
109
|
+
setView(everyoneId, 0, decision.everyone === true);
|
|
110
|
+
|
|
111
|
+
const allowSet = new Set(decision.allowRoleIds.map(String));
|
|
112
|
+
// Allow the rank roles that should see it.
|
|
113
|
+
for (const rid of allowSet) setView(rid, 0, true);
|
|
114
|
+
// For managed rank roles NOT in the allow set, clear any VIEW bit we own so a
|
|
115
|
+
// tighter `view` actually revokes a previously-granted role.
|
|
116
|
+
for (const r of RANK_LADDER) {
|
|
117
|
+
const rid = roleMap[r] ? String(roleMap[r]) : null;
|
|
118
|
+
if (rid && !allowSet.has(rid) && byId.has(rid)) {
|
|
119
|
+
const e = byId.get(rid);
|
|
120
|
+
e.allow &= ~VIEW_CHANNEL;
|
|
121
|
+
e.deny &= ~VIEW_CHANNEL;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Never lock the managing bot out of a channel it restricts. When @everyone is
|
|
126
|
+
// denied VIEW, the bot's own role is in no rank allow-list, so without this the
|
|
127
|
+
// bot loses access to the very channel it just edited — and every later
|
|
128
|
+
// reconcile fails with Discord 50001 "Missing Access" (exactly what locked the
|
|
129
|
+
// BUILDERS category after #727 shipped). Grant the bot an explicit member-level
|
|
130
|
+
// (type 1) VIEW overwrite on any RESTRICTED channel. We add it only when
|
|
131
|
+
// restricting (decision.everyone === false): on a public channel the bot
|
|
132
|
+
// already sees it via @everyone, and not touching public channels keeps the
|
|
133
|
+
// reconcile idempotent (no perpetual re-write).
|
|
134
|
+
if (ctx.botId && decision.everyone === false) {
|
|
135
|
+
setView(String(ctx.botId), 1, true);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Drop entries that ended up empty (no bits) to keep the payload clean —
|
|
139
|
+
// except @everyone, which we always state explicitly.
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const e of byId.values()) {
|
|
142
|
+
if (e.id !== String(everyoneId) && e.allow === 0n && e.deny === 0n) continue;
|
|
143
|
+
out.push({ id: e.id, type: e.type, allow: e.allow.toString(), deny: e.deny.toString() });
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// True when two overwrite arrays describe the same VIEW decisions for the
|
|
149
|
+
// managed ids (@everyone + every managed rank role). Non-VIEW bits and
|
|
150
|
+
// unmanaged ids are ignored — we only own VIEW.
|
|
151
|
+
function viewOverwritesEqual(a, b, ctx) {
|
|
152
|
+
const managed = new Set([String(ctx.everyoneId), ...RANK_LADDER.map((r) => ctx.roleMap[r]).filter(Boolean).map(String)]);
|
|
153
|
+
// The bot's own VIEW overwrite is managed too, so a missing one on a restricted
|
|
154
|
+
// channel registers as a needed edit (and a present one as already-applied).
|
|
155
|
+
if (ctx.botId) managed.add(String(ctx.botId));
|
|
156
|
+
const view = (arr) => {
|
|
157
|
+
const m = new Map();
|
|
158
|
+
for (const o of arr || []) {
|
|
159
|
+
const id = String(o.id);
|
|
160
|
+
if (!managed.has(id)) continue;
|
|
161
|
+
const allow = BigInt(o.allow || 0) & VIEW_CHANNEL ? 'allow' : (BigInt(o.deny || 0) & VIEW_CHANNEL ? 'deny' : 'none');
|
|
162
|
+
m.set(id, allow);
|
|
163
|
+
}
|
|
164
|
+
return m;
|
|
165
|
+
};
|
|
166
|
+
const ma = view(a);
|
|
167
|
+
const mb = view(b);
|
|
168
|
+
if (ma.size !== mb.size) return false;
|
|
169
|
+
for (const [k, v] of ma) if (mb.get(k) !== v) return false;
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Compute the reconcile plan. PURE: given a guild snapshot, the parsed config,
|
|
174
|
+
// and the context (everyone role id + rank->role-id map), return the ordered
|
|
175
|
+
// list of ops needed to make Discord match the config, plus any warnings.
|
|
176
|
+
//
|
|
177
|
+
// snapshot = {
|
|
178
|
+
// categories: [{ id, name }],
|
|
179
|
+
// channels: [{ id, name, type, parentId, topic, overwrites:[{id,type,allow,deny}] }]
|
|
180
|
+
// }
|
|
181
|
+
// Ops never include deletions. Categories are planned before their channels so
|
|
182
|
+
// the apply step can resolve a freshly-created parent id.
|
|
183
|
+
function planChannelReconcile(snapshot, config, ctx) {
|
|
184
|
+
const ops = [];
|
|
185
|
+
const warnings = [];
|
|
186
|
+
const catBySlug = new Map((snapshot.categories || []).map((c) => [slugify(c.name), c]));
|
|
187
|
+
const chBySlug = new Map((snapshot.channels || []).map((c) => [slugify(c.name), c]));
|
|
188
|
+
|
|
189
|
+
for (const cat of config.categories || []) {
|
|
190
|
+
const catSlug = slugify(cat.name);
|
|
191
|
+
const existingCat = catBySlug.get(catSlug);
|
|
192
|
+
const catDecision = resolveViewRoles(cat.view, ctx.roleMap);
|
|
193
|
+
if (catDecision.missing && catDecision.missing.length) {
|
|
194
|
+
warnings.push(`category "${cat.name}" view=${cat.view}: no Discord role id yet for ${catDecision.missing.join(', ')}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!existingCat) {
|
|
198
|
+
ops.push({
|
|
199
|
+
op: 'create-category',
|
|
200
|
+
slug: catSlug,
|
|
201
|
+
name: cat.name,
|
|
202
|
+
overwrites: mergeViewOverwrites([], catDecision, ctx),
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
const want = mergeViewOverwrites(existingCat.overwrites, catDecision, ctx);
|
|
206
|
+
if (!viewOverwritesEqual(existingCat.overwrites, want, ctx)) {
|
|
207
|
+
ops.push({ op: 'edit-category', id: existingCat.id, name: cat.name, overwrites: want });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const ch of cat.channels || []) {
|
|
212
|
+
const chSlug = slugify(ch.name);
|
|
213
|
+
const view = ch.view || cat.view;
|
|
214
|
+
const decision = resolveViewRoles(view, ctx.roleMap);
|
|
215
|
+
if (decision.missing && decision.missing.length) {
|
|
216
|
+
warnings.push(`channel "${ch.name}" view=${view}: no Discord role id yet for ${decision.missing.join(', ')}`);
|
|
217
|
+
}
|
|
218
|
+
const existing = chBySlug.get(chSlug);
|
|
219
|
+
if (!existing) {
|
|
220
|
+
ops.push({
|
|
221
|
+
op: 'create-channel',
|
|
222
|
+
slug: chSlug,
|
|
223
|
+
name: ch.name,
|
|
224
|
+
parentSlug: catSlug,
|
|
225
|
+
parentName: cat.name,
|
|
226
|
+
topic: ch.topic || '',
|
|
227
|
+
overwrites: mergeViewOverwrites([], decision, ctx),
|
|
228
|
+
});
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const want = mergeViewOverwrites(existing.overwrites, decision, ctx);
|
|
232
|
+
const topicDiffers = (existing.topic || '') !== (ch.topic || '');
|
|
233
|
+
const permsDiffer = !viewOverwritesEqual(existing.overwrites, want, ctx);
|
|
234
|
+
const parentDiffers = existing.parentId && catBySlug.get(catSlug) && String(existing.parentId) !== String(catBySlug.get(catSlug).id);
|
|
235
|
+
if (topicDiffers || permsDiffer || parentDiffers) {
|
|
236
|
+
const edit = { op: 'edit-channel', id: existing.id, name: ch.name };
|
|
237
|
+
if (topicDiffers) edit.topic = ch.topic || '';
|
|
238
|
+
if (permsDiffer) edit.overwrites = want;
|
|
239
|
+
if (parentDiffers) { edit.parentSlug = catSlug; edit.parentName = cat.name; }
|
|
240
|
+
ops.push(edit);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return { ops, warnings };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// IO + Discord REST (impure) — invoked by the bot sweep and the Archon command.
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
function loadConfig(configPath = CONFIG_PATH) {
|
|
252
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
253
|
+
return JSON.parse(raw);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// The managing bot's own user id, used to grant it an explicit VIEW overwrite on
|
|
257
|
+
// any channel it restricts so it never locks itself out. Best-effort: returns
|
|
258
|
+
// null if the client/user isn't resolvable (then the self-lockout guard is a
|
|
259
|
+
// no-op and behaviour is unchanged).
|
|
260
|
+
function botIdFromGuild(guild) {
|
|
261
|
+
const u = guild && guild.client && guild.client.user;
|
|
262
|
+
return u && u.id ? String(u.id) : null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Build a normalized snapshot from a discord.js guild's channel cache.
|
|
266
|
+
function snapshotFromGuild(guild) {
|
|
267
|
+
const categories = [];
|
|
268
|
+
const channels = [];
|
|
269
|
+
for (const ch of guild.channels.cache.values()) {
|
|
270
|
+
const overwrites = [];
|
|
271
|
+
if (ch.permissionOverwrites && ch.permissionOverwrites.cache) {
|
|
272
|
+
for (const ow of ch.permissionOverwrites.cache.values()) {
|
|
273
|
+
overwrites.push({ id: String(ow.id), type: ow.type === 'role' || ow.type === 0 ? 0 : 1, allow: ow.allow.bitfield.toString(), deny: ow.deny.bitfield.toString() });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Categories carry their own VIEW overwrites too — include them, or the
|
|
277
|
+
// planner compares the desired overwrites against `undefined` every run and
|
|
278
|
+
// re-emits an edit-category op forever (idea #202: type-4 perms never reach
|
|
279
|
+
// no-op even though the PATCH succeeds; channels were fine because they
|
|
280
|
+
// already carried overwrites here).
|
|
281
|
+
if (ch.type === 4) categories.push({ id: String(ch.id), name: ch.name, overwrites });
|
|
282
|
+
else if (ch.type === 0) channels.push({ id: String(ch.id), name: ch.name, type: 0, parentId: ch.parentId ? String(ch.parentId) : null, topic: ch.topic || '', overwrites });
|
|
283
|
+
}
|
|
284
|
+
return { categories, channels };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const realDeps = {
|
|
288
|
+
loadConfig,
|
|
289
|
+
loadGuildId: () => require('./discord-role-sync').loadGuildId(),
|
|
290
|
+
loadRoleMap: () => require('./discord-role-sync').loadRoleMap(),
|
|
291
|
+
loadBotToken: () => require('./auth').loadDiscordBotToken(),
|
|
292
|
+
// Identical to discord-role-sync.getGuild (same lazy bot→client→loadGuildId→
|
|
293
|
+
// cache.get(gid)||first() chain) — delegate to the canonical copy there
|
|
294
|
+
// rather than keep a line-for-line duplicate (task 1091, C7).
|
|
295
|
+
getGuild: () => require('./discord-role-sync').getGuild(),
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
async function discordRest(token, method, urlPath, body) {
|
|
299
|
+
const res = await fetch(`https://discord.com/api${urlPath}`, {
|
|
300
|
+
method,
|
|
301
|
+
headers: { Authorization: `Bot ${token}`, 'Content-Type': 'application/json' },
|
|
302
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
303
|
+
});
|
|
304
|
+
if (!res.ok) {
|
|
305
|
+
const text = await res.text().catch(() => '');
|
|
306
|
+
const err = new Error(`Discord ${method} ${urlPath} -> ${res.status}: ${text}`);
|
|
307
|
+
err.status = res.status;
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
return res.status === 204 ? null : res.json();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Compute the plan against the live guild WITHOUT writing — for the Archon
|
|
314
|
+
// `plan` (dry-run) command and for logging before an apply.
|
|
315
|
+
function computePlan(deps = realDeps) {
|
|
316
|
+
const guild = deps.getGuild();
|
|
317
|
+
if (!guild) return { ok: false, reason: 'no_guild', ops: [], warnings: [] };
|
|
318
|
+
const config = deps.loadConfig();
|
|
319
|
+
const roleMap = deps.loadRoleMap();
|
|
320
|
+
const ctx = { everyoneId: String(guild.id), roleMap, botId: botIdFromGuild(guild) };
|
|
321
|
+
const snapshot = snapshotFromGuild(guild);
|
|
322
|
+
const { ops, warnings } = planChannelReconcile(snapshot, config, ctx);
|
|
323
|
+
return { ok: true, ops, warnings, guildId: String(guild.id) };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Apply the plan to Discord. Best-effort, never throws. Returns a summary.
|
|
327
|
+
// Categories are created first so child channels can resolve their parent id.
|
|
328
|
+
async function reconcileChannels(deps = realDeps) {
|
|
329
|
+
const guild = deps.getGuild();
|
|
330
|
+
if (!guild) return { ok: false, reason: 'no_guild', applied: 0, failed: 0 };
|
|
331
|
+
const token = deps.loadBotToken();
|
|
332
|
+
if (!token) return { ok: false, reason: 'no_token', applied: 0, failed: 0 };
|
|
333
|
+
|
|
334
|
+
const config = deps.loadConfig();
|
|
335
|
+
const roleMap = deps.loadRoleMap();
|
|
336
|
+
const ctx = { everyoneId: String(guild.id), roleMap, botId: botIdFromGuild(guild) };
|
|
337
|
+
const snapshot = snapshotFromGuild(guild);
|
|
338
|
+
const { ops, warnings } = planChannelReconcile(snapshot, config, ctx);
|
|
339
|
+
for (const w of warnings) console.warn(`[discord-bot] channels: ${w}`);
|
|
340
|
+
|
|
341
|
+
let applied = 0, failed = 0;
|
|
342
|
+
// Track newly-created category ids so create-channel can set parent_id.
|
|
343
|
+
const catIdBySlug = new Map((snapshot.categories || []).map((c) => [slugify(c.name), String(c.id)]));
|
|
344
|
+
|
|
345
|
+
// Run one best-effort sub-operation. Each PATCH/POST is counted on its own so
|
|
346
|
+
// a failure in one facet (e.g. permission_overwrites needs Manage Permissions)
|
|
347
|
+
// does NOT suppress another that succeeded (e.g. topic needs only Manage
|
|
348
|
+
// Channels). Returns the created body on success, or null on failure.
|
|
349
|
+
const run = async (label, fn) => {
|
|
350
|
+
try {
|
|
351
|
+
const out = await fn();
|
|
352
|
+
applied++;
|
|
353
|
+
console.log(`[discord-bot] channels: ${label}`);
|
|
354
|
+
return out;
|
|
355
|
+
} catch (e) {
|
|
356
|
+
failed++;
|
|
357
|
+
console.error(`[discord-bot] channels: ${label} failed:`, e.message);
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
for (const op of ops) {
|
|
363
|
+
if (op.op === 'create-category') {
|
|
364
|
+
const created = await run(`created category ${op.name}`, () =>
|
|
365
|
+
discordRest(token, 'POST', `/guilds/${guild.id}/channels`, { name: op.name, type: 4, permission_overwrites: op.overwrites }));
|
|
366
|
+
if (created) catIdBySlug.set(op.slug, String(created.id));
|
|
367
|
+
} else if (op.op === 'edit-category') {
|
|
368
|
+
await run(`updated category ${op.name} permissions`, () =>
|
|
369
|
+
discordRest(token, 'PATCH', `/channels/${op.id}`, { permission_overwrites: op.overwrites }));
|
|
370
|
+
} else if (op.op === 'create-channel') {
|
|
371
|
+
const parentId = catIdBySlug.get(op.parentSlug) || null;
|
|
372
|
+
await run(`created #${op.name}`, () =>
|
|
373
|
+
discordRest(token, 'POST', `/guilds/${guild.id}/channels`, {
|
|
374
|
+
name: op.name, type: 0, parent_id: parentId, topic: op.topic, permission_overwrites: op.overwrites,
|
|
375
|
+
}));
|
|
376
|
+
} else if (op.op === 'edit-channel') {
|
|
377
|
+
// SPLIT the edit: topic + parent (Manage Channels) in one PATCH, the
|
|
378
|
+
// permission_overwrites (Manage Permissions) in another. On a private
|
|
379
|
+
// channel where the bot lacks Manage Permissions, the description still
|
|
380
|
+
// lands and only the per-rank view perms are deferred.
|
|
381
|
+
const base = {};
|
|
382
|
+
if (op.topic !== undefined) base.topic = op.topic;
|
|
383
|
+
if (op.parentSlug) base.parent_id = catIdBySlug.get(op.parentSlug) || null;
|
|
384
|
+
if (Object.keys(base).length) {
|
|
385
|
+
await run(`updated #${op.name} (${Object.keys(base).join(', ')})`, () =>
|
|
386
|
+
discordRest(token, 'PATCH', `/channels/${op.id}`, base));
|
|
387
|
+
}
|
|
388
|
+
if (op.overwrites !== undefined) {
|
|
389
|
+
await run(`updated #${op.name} (permissions)`, () =>
|
|
390
|
+
discordRest(token, 'PATCH', `/channels/${op.id}`, { permission_overwrites: op.overwrites }));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return { ok: true, applied, failed, total: applied + failed, warnings };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
module.exports = {
|
|
398
|
+
// pure (tested)
|
|
399
|
+
slugify,
|
|
400
|
+
resolveViewRoles,
|
|
401
|
+
mergeViewOverwrites,
|
|
402
|
+
viewOverwritesEqual,
|
|
403
|
+
planChannelReconcile,
|
|
404
|
+
// io
|
|
405
|
+
loadConfig,
|
|
406
|
+
snapshotFromGuild,
|
|
407
|
+
computePlan,
|
|
408
|
+
reconcileChannels,
|
|
409
|
+
CONFIG_PATH,
|
|
410
|
+
VIEW_CHANNEL,
|
|
411
|
+
RANK_LADDER,
|
|
412
|
+
};
|