@bongos/core 1.19.715 → 1.19.725
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 +491 -421
- package/.claude/skills/backlog-review/SKILL.md +3 -3
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/blocker-solve/SKILL.md +2 -2
- package/.claude/skills/builder-reauth/SKILL.md +1 -1
- package/.claude/skills/builder-redteam/SKILL.md +1 -1
- package/.claude/skills/builder-ship/SKILL.md +1 -1
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/design/SKILL.md +2 -2
- package/.claude/skills/feedback/SKILL.md +1 -1
- package/.claude/skills/goal-create/SKILL.md +1 -1
- package/.claude/skills/goal-review/SKILL.md +2 -2
- package/.claude/skills/idea-triage/SKILL.md +5 -5
- package/.claude/skills/merge-mode/SKILL.md +4 -4
- package/.claude/skills/new-project/SKILL.md +17 -17
- package/.claude/skills/planning-session/SKILL.md +11 -11
- package/.claude/skills/recall/SKILL.md +2 -2
- package/.claude/skills/strand-fix/SKILL.md +1 -1
- package/.devcontainer/README.md +2 -2
- package/CONTRIBUTING.md +2 -2
- package/GOVERNANCE.md +4 -4
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -0
- package/clients/bongos-client/index.cjs +4 -0
- package/clients/bongos-client/index.d.ts +5 -0
- package/clients/bongos-client/index.mjs +4 -0
- package/config/branding.neutral.json +4 -2
- package/docs/adr/0001-stack-choice-single-vps.md +2 -2
- package/docs/adr/0002-digitalocean-over-hetzner.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0009-autotile-architecture.md +1 -1
- package/docs/adr/0011-terrain-id-rendering-model.md +3 -3
- package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +1 -1
- package/docs/adr/0014-families-canonical-over-vocabulary.md +1 -1
- package/docs/adr/0015-task-dependencies-and-auto-promotion.md +1 -1
- package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +1 -1
- package/docs/adr/0018-three-rank-model-goes-live.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +9 -9
- package/docs/adr/0021-per-builder-skill-model-preferences.md +2 -2
- package/docs/adr/0022-secrets-policy.md +5 -5
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +3 -3
- package/docs/adr/0024-multi-agent-system-architecture.md +10 -10
- package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +1 -1
- package/docs/adr/0025-structured-criterion-task-link.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +13 -13
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +14 -14
- package/docs/adr/0029-status-page-outage-resilience.md +1 -1
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +7 -7
- package/docs/adr/0032-discord-mirror-of-gds.md +3 -3
- package/docs/adr/0033-discord-bot-service-principal.md +4 -4
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0035-builder-onboarding-three-paths.md +4 -4
- package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +1 -1
- package/docs/adr/0037-discord-channel-management.md +1 -1
- package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +5 -5
- package/docs/adr/0039-setup-first-onboarding-ux.md +6 -6
- package/docs/adr/0040-remote-control-default-browser-onramp.md +3 -3
- package/docs/adr/0041-temporary-grader-bypass-killswitch.md +2 -2
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +5 -5
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +2 -2
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0046-sandbox-first-review-gate.md +1 -1
- package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +1 -1
- package/docs/adr/0049-split-parallel-safety-contract.md +1 -1
- package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +2 -2
- package/docs/adr/0051-full-session-transcript-corpus.md +2 -2
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +1 -1
- package/docs/adr/0053-scoped-dev-box-session.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +7 -7
- package/docs/adr/0055-server-mediated-branch-publish.md +2 -2
- package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +3 -3
- package/docs/adr/0057-container-cost-ledger.md +1 -1
- package/docs/adr/0057-discord-archon-approval-channels.md +2 -2
- package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +6 -6
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +11 -11
- package/docs/adr/0060-gds-retrieval-layer.md +3 -3
- package/docs/adr/0061-context-layer-decomposition.md +2 -2
- package/docs/adr/0062-medusa-instance-model-decoupling.md +5 -5
- package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +2 -2
- package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +3 -3
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +5 -5
- package/docs/adr/0066-generated-file-map-sections.md +2 -2
- package/docs/adr/0067-session-start-fast-forward-pull.md +1 -1
- package/docs/adr/0068-shared-art-key-for-newcomers.md +1 -1
- package/docs/adr/0069-hall-live-channel-sse-decouple.md +3 -3
- package/docs/adr/0070-deterministic-session-card-delivery.md +1 -1
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +2 -2
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +2 -2
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +2 -2
- package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +3 -3
- package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +2 -2
- package/docs/adr/0076-in-session-deterministic-compaction.md +2 -2
- package/docs/adr/0077-content-addressed-llm-cache.md +2 -2
- package/docs/adr/0078-deterministic-first-cron.md +3 -3
- package/docs/adr/0079-value-gated-autonomous-engagement.md +2 -2
- package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +3 -3
- package/docs/adr/0081-tool-agnostic-design-layer.md +3 -3
- package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +2 -2
- package/docs/adr/0083-modular-architecture-strangler-build-plan.md +3 -3
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +2 -2
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +15 -15
- package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +5 -5
- package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +1 -1
- package/docs/adr/0088-consistent-character-generative-animation.md +5 -5
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +15 -15
- package/docs/adr/0090-metic-task-authoring.md +8 -8
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +3 -3
- package/docs/adr/0092-hosting-module-and-on-prem-target.md +11 -11
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +5 -5
- package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +1 -1
- package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +6 -6
- package/docs/adr/0095-cross-agent-context-management.md +5 -5
- package/docs/adr/0096-require-reward-before-workable.md +5 -5
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -2
- package/docs/adr/0097-retroactive-reward-backfill.md +4 -4
- package/docs/adr/0098-public-mirror-publish-manifest.md +3 -3
- package/docs/adr/0099-delayed-redacted-mirror-export.md +1 -1
- package/docs/adr/0100-two-repo-instance-model.md +6 -6
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +9 -9
- package/docs/adr/0102-finish-bounding-the-kernel.md +1 -1
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +6 -6
- package/docs/adr/0103-gdsv4-bongos-consolidation.md +3 -3
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +3 -3
- package/docs/adr/0105-instance-seed-migrations-out-of-core.md +2 -2
- package/docs/adr/0106-goal-owner-manager-authority.md +2 -2
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +6 -6
- package/docs/adr/0108-instance-composition-configurable-root.md +6 -6
- package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +3 -3
- package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +1 -1
- package/docs/adr/0111-instance-hosting-provisioning-module.md +18 -18
- package/docs/adr/0112-private-goals-invitations-join-requests.md +7 -7
- package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +1 -1
- package/docs/adr/0114-instance-docs-discovery-contract.md +4 -4
- package/docs/adr/0115-scheduled-routines-portability.md +1 -1
- package/docs/adr/0117-api-versioning-and-deprecation-policy.md +1 -1
- package/docs/adr/0118-api-contract-source-of-truth-code-first.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +2 -2
- package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +1 -1
- package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +3 -3
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +1 -1
- package/docs/adr/0124-typed-api-response-bodies.md +2 -2
- package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +2 -2
- package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +3 -3
- package/docs/adr/0127-cross-box-fleet-observability.md +1 -1
- package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +3 -3
- package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +4 -4
- package/docs/adr/0130-provision-remote-exec-over-ssh.md +4 -4
- package/docs/adr/0131-rank-scoped-skill-visibility.md +2 -2
- package/docs/adr/0132-cohost-fleet-control-plane-migration.md +2 -2
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +1 -1
- package/docs/adr/0134-private-first-npm-distribution.md +3 -3
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +1 -1
- package/docs/adr/0136-update-channel-subscription-policy.md +2 -2
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +6 -6
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +4 -4
- package/docs/adr/0142-project-context-is-prose-only.md +2 -2
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +2 -2
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +2 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +2 -2
- package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +4 -4
- package/docs/adr/0147-standalone-live-docs-regen.md +1 -1
- package/docs/adr/0149-bongos-dev-local-launcher.md +1 -1
- package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +3 -3
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +20 -20
- package/docs/adr/0152-landing-gate-served-by-the-core-app.md +2 -2
- package/docs/adr/0152-metic-task-abandonment.md +7 -7
- package/docs/adr/0153-sequential-chain-runner.md +1 -1
- package/docs/adr/0154-goal-owner-criterion-authoring.md +7 -7
- package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +2 -2
- package/docs/adr/0156-user-set-display-name-wins.md +1 -1
- package/docs/adr/0157-archon-is-rank-and-identity-only.md +10 -10
- package/docs/adr/0158-choke-point-verdict-taxonomy.md +1 -1
- package/docs/adr/0159-merge-gate-reconciliation.md +1 -1
- package/docs/adr/0160-main-audit-repo-verifiable-caller.md +1 -1
- package/docs/adr/0161-publish-on-merge.md +2 -2
- package/docs/adr/0163-legacy-display-name-resolution.md +1 -1
- package/docs/adr/0165-instances-hookless-by-default.md +1 -1
- package/docs/adr/0166-module-maintenance-and-deprecation-policy.md +3 -3
- package/docs/adr/0167-gate-trust-link-agpl-default.md +2 -2
- package/docs/adr/0167-module-catalog-source-vs-provenance.md +3 -3
- package/docs/adr/0169-bongos-serve-server-bring-up.md +1 -1
- package/docs/adr/0171-profile-rollup-join-and-consent.md +1 -1
- package/docs/adr/0172-editable-rank-roles-substrate-axis.md +2 -2
- package/docs/adr/0172-per-craft-compensation-ideator-credit-lane.md +4 -4
- package/docs/adr/0173-legacy-multi-ship-shortfall-not-backfilled.md +1 -1
- package/docs/adr/0174-government-terminology-ranks-not-roles.md +1 -1
- package/docs/adr/0177-goal-membership-kind.md +3 -3
- package/docs/adr/0178-the-copy-desk-flag-and-queue.md +1 -1
- package/docs/adr/0179-board-membership-predicate-grammar.md +1 -1
- package/docs/adr/0181-teardown-state-gate-and-address-retention.md +1 -1
- package/docs/adr/0182-project-visibility-two-axes.md +1 -1
- package/docs/adr/0183-criteria-close-themselves.md +1 -1
- package/docs/adr/0184-ship-requires-an-assurance.md +1 -1
- package/docs/adr/0185-spark-handoff-credit-split.md +1 -1
- package/docs/adr/0186-per-project-idea-templates.md +1 -1
- package/docs/adr/0187-collab-four-decisions.md +1 -1
- package/docs/adr/0187-oauth-handshake-cookie-lifetime.md +1 -1
- package/docs/adr/0188-patrol-runs-on-ci-not-the-server.md +2 -2
- package/docs/adr/0189-baseline-floors-and-the-freeze-detector.md +2 -2
- package/docs/adr/0191-majority-and-the-author-who-may-not-decide-alone.md +1 -1
- package/docs/adr/0192-a-category-orients-and-authorises-nothing.md +2 -2
- package/docs/adr/0193-pause-task-scoped-box-slices.md +1 -1
- package/docs/adr/0195-adr-numbers-are-checked-like-migration-numbers.md +2 -2
- package/docs/adr/0196-chrome-world-token-contract-and-the-landing-sheets.md +2 -2
- package/docs/adr/0197-the-ui-design-module.md +1 -1
- package/docs/adr/0198-third-party-skill-vendoring-policy.md +1 -1
- package/docs/adr/0203-doorway-changelog-out-of-module-api.md +2 -2
- package/docs/adr/0204-the-apex-is-dark-only.md +2 -2
- package/docs/adr/0205-federated-checkin-needs-the-hubs-own-signin-witness.md +1 -1
- package/docs/adr/0206-own-scoped-session-revocation-no-absolute-ceiling.md +1 -1
- package/docs/adr/0207-connections-implementation-departures-and-honest-limits.md +1 -1
- package/docs/adr/0208-application-lifecycle-the-handle-waits-and-the-vouch-has-no-minter.md +2 -2
- package/docs/adr/0209-one-budget-for-the-account-existence-oracle.md +1 -1
- package/docs/adr/0210-scouting-graduates-on-an-instance-asserted-owner-claim.md +1 -1
- package/docs/adr/0212-connections-on-the-profile-surface-and-addressing.md +1 -1
- package/docs/adr/0213-platform-terms-acceptance-is-the-hubs-own-witness.md +2 -2
- package/docs/adr/0215-the-recruiter-sliver-is-a-ceiling-not-a-step.md +6 -6
- package/docs/adr/0216-a-fake-pool-interpreter-must-fail-loudly-not-silently-ignore.md +2 -2
- package/docs/adr/0217-rank-gates-inviting-not-viewing.md +1 -1
- package/docs/adr/0218-the-apex-root-web-surface.md +2 -2
- package/docs/adr/0219-a-look-is-a-branding-pack-the-style-library.md +1 -1
- package/docs/adr/0220-an-in-house-rebuilt-skill-is-a-first-party-skill.md +1 -1
- package/docs/adr/0221-the-impeccable-method-is-one-skill-with-sub-command-playbooks.md +2 -2
- package/docs/adr/0222-the-image-family-under-the-hero-only-rule.md +2 -2
- package/docs/adr/0223-the-detector-is-two-tiers-in-the-kit.md +3 -3
- package/docs/adr/0224-module-skills-reach-the-core-checkout-as-untracked-self-ignored-copies.md +1 -1
- package/docs/adr/0225-privacy-suppresses-cross-project-surfaces-only.md +1 -1
- package/docs/adr/0226-one-stale-timer-reporting-degrades-enforcement-refuses.md +1 -1
- package/docs/adr/0227-a-task-network-map-is-refused-the-goal-graph-is-the-map.md +1 -1
- package/docs/adr/0230-a-look-skill-is-a-composition-grammar-the-palette-is-the-pack.md +1 -1
- package/docs/adr/0231-the-style-session-an-owner-authors-a-look-inside-the-contract.md +2 -2
- package/docs/adr/0232-rot-is-derived-not-swept-and-water-is-the-only-new-verb.md +2 -2
- package/docs/adr/0233-a-copy-proposal-is-a-task-carrying-a-patch.md +2 -2
- package/docs/adr/0234-idea-routing-capture-time-promotion-landing-matrix-homeless-inbox.md +2 -2
- package/docs/adr/0235-a-fix-category-is-kindbug-under-a-goal-no-column-required.md +11 -11
- package/docs/adr/0236-many-ideas-one-task-the-lineage-key-is-the-pair.md +5 -5
- package/docs/adr/0237-a-starter-bundle-is-a-preset-over-the-always-on-core-keyed-by-declared-type.md +5 -5
- package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +1 -1
- package/docs/adr/0239-a-recommendation-is-a-table-that-carries-its-own-reason.md +4 -4
- package/docs/adr/0240-a-skipped-module-picker-stores-nothing-and-resolves-the-bundle-on-read.md +4 -4
- package/docs/adr/0241-the-artist-gate-is-a-per-project-deploy-gate-that-reads-a-state.md +1 -1
- package/docs/adr/0242-a-cascade-is-a-declaration-table-on-the-event-that-already-exists.md +2 -2
- package/docs/adr/0243-a-bundle-adjustment-belongs-to-the-bundle-not-the-advice-about-it.md +4 -4
- package/docs/adr/0244-a-catalog-search-is-a-view-over-the-rows-never-an-edit-to-the-selection.md +3 -3
- package/docs/adr/0245-the-way-back-into-an-optional-step-is-that-step-s-own-answer-route.md +7 -7
- package/docs/adr/0246-catalog-membership-is-a-projection-not-a-federation-side-effect.md +1 -1
- package/docs/adr/0247-the-join-door-is-one-composed-answer-and-dark-matter-has-no-live-verdict-yet.md +2 -2
- package/docs/adr/0248-a-hub-project-s-owner-is-the-provisioning-row-carried-on-a-port.md +3 -3
- package/docs/adr/0249-the-invite-step-is-the-done-panel-s-first-act-not-a-rail-step.md +3 -3
- package/docs/adr/0250-strict-versioning-the-version-boundary-is-the-scope-gate.md +6 -6
- package/docs/adr/0251-a-suggestion-of-who-to-invite-is-a-recruiting-surface-and-owes-the-opt-out.md +5 -5
- package/docs/adr/0252-the-self-hosted-visibility-report-is-a-pull-and-auto-appear-buys-a-retraction-lever.md +19 -19
- package/docs/adr/0253-the-takedown-lever-has-two-writers-and-provenance-is-a-column.md +2 -2
- package/docs/adr/0254-a-relayed-link-is-contained-downward-or-it-is-not-published.md +6 -6
- package/docs/adr/0255-a-public-lists-ordering-is-part-of-its-payload.md +2 -2
- package/docs/adr/0256-the-visibility-ceiling-is-the-sign-in-gate-too-and-the-reader-moves-to-the-composer.md +3 -3
- package/docs/adr/0257-auth-resolves-before-the-hall-mounts-anything.md +1 -1
- package/docs/adr/0258-the-public-cli-is-a-generated-client-package-not-the-published-core.md +1 -1
- package/docs/adr/0259-a-projects-departure-from-the-public-list-is-public.md +7 -7
- package/docs/adr/0260-the-application-is-the-consent-and-the-echo-is-the-gate.md +4 -4
- package/docs/adr/0261-a-preselect-always-carries-a-reason-the-bundle-summary-is-the-floor.md +1 -1
- package/docs/adr/0262-a-bug-never-lands-in-the-inbox.md +1 -1
- package/docs/adr/0263-how-a-version-closes.md +3 -3
- package/docs/adr/0264-the-ten-working-areas.md +3 -3
- package/docs/adr/0265-work-with-no-goal-and-the-order-the-cut-needs.md +5 -5
- package/docs/adr/0266-the-board-room-is-its-own-surface-reachable-by-whoever-may-vote.md +2 -2
- package/docs/adr/0267-unanimity-and-the-revise-and-re-sit-loop.md +1 -1
- package/docs/adr/0268-the-constitution-comes-from-two-roots.md +3 -3
- package/docs/adr/0269-the-cli-session-store-is-host-keyed-at-a-fixed-anchor.md +1 -1
- package/docs/adr/0270-rank-floor-derives-without-a-prediction.md +2 -2
- package/docs/adr/0271-the-ideator-surface-is-two-halves.md +3 -3
- package/docs/adr/0272-a-craft-is-an-identity-not-a-toolchain.md +4 -4
- package/docs/adr/0273-hall-nav-can-gate-on-a-permission.md +1 -1
- package/docs/adr/0274-one-kernel-three-role-packs.md +1 -1
- package/docs/adr/0275-one-source-for-a-written-role-responsibility.md +1 -1
- package/docs/adr/0276-the-skill-listing-budget-cannot-hold-every-trigger.md +1 -1
- package/docs/adr/0277-a-box-is-in-use-only-while-a-human-is-attached.md +1 -1
- package/docs/adr/0278-a-gated-project-still-takes-applications.md +2 -2
- package/docs/adr/0279-an-upgrade-is-proven-by-the-served-version-not-the-health-check.md +1 -1
- package/docs/adr/0280-the-unattended-lane-may-drive-a-co-tenant-because-the-roster-is-the-decision.md +1 -1
- package/docs/adr/0281-an-instance-identity-is-its-own-unix-account-and-pg-role.md +2 -2
- package/docs/adr/README.md +294 -294
- package/docs/api/openapi.json +72 -5
- package/docs/api-reference.md +8 -2
- package/docs/architecture.md +22 -22
- package/docs/branding-contract.md +5 -5
- package/docs/canonical-permissions.md +22 -22
- package/docs/design/hall-direction-v2.md +3 -3
- package/docs/design/landing-page-direction-v2.md +1 -1
- package/docs/design/projects-hub-direction-v2.md +1 -1
- package/docs/file-map.md +3 -1
- package/docs/module-api-changelog.md +36 -1
- package/docs/modules-contract.md +25 -25
- package/docs/onboarding/browser-terminal-guide.md +1 -1
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/drachmae-and-karma.md +13 -13
- package/docs/onboarding/primer.md +7 -7
- package/docs/packs/artist.md +1 -1
- package/docs/packs/ideator.md +1 -1
- package/docs/project-context.template.md +3 -3
- package/docs/recipes/builder-box-lifecycle.md +5 -5
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/core-release-pipeline.md +55 -14
- package/docs/recipes/figma-design-loop.md +1 -1
- package/docs/recipes/gds-db-backup.md +2 -2
- package/docs/recipes/instance-deploy.md +2 -2
- package/docs/recipes/instance-service-restart.md +3 -3
- package/docs/recipes/local-dev.md +2 -2
- package/docs/recipes/managed-settings-remote-control.md +3 -3
- package/docs/recipes/memory-backup.md +4 -4
- package/docs/recipes/memory-sync-conflicts.md +2 -2
- package/docs/recipes/multi-builder-merge.md +2 -2
- package/docs/recipes/ops-gotchas.md +4 -4
- package/docs/recipes/overnight-builder.md +1 -1
- package/docs/recipes/packaging-the-core.md +6 -6
- package/docs/recipes/private-npm-distribution.md +5 -5
- package/docs/recipes/search-index-ingestion.md +3 -3
- package/docs/recipes/self-host.md +5 -5
- package/docs/recipes/server-bring-up.md +1 -1
- package/docs/recipes/ship-and-regrade.md +2 -2
- package/docs/recipes/standalone-live-docs.md +1 -1
- package/docs/recipes/ui-look-before-you-ship.md +1 -1
- package/docs/recipes/upgrading-the-core.md +2 -2
- package/docs/recipes/work-on-a-task-from-your-dev-box.md +6 -6
- package/docs/research/cursor-competitive-analysis.md +3 -3
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +7 -7
- package/migrations/core_240_artist_gate_indexes.sql +48 -0
- package/migrations/core_240_goal_members_drop_legacy_role.sql +79 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +4 -0
- package/modules/lifecycle/cascade.js +37 -9
- package/modules/lifecycle/db-claims.js +5 -2
- package/modules/lifecycle/db-goals.js +4 -3
- package/modules/lifecycle/db-overrides.js +117 -4
- package/modules/lifecycle/db-tasks.js +63 -0
- package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +12 -4
- package/modules/lifecycle/module.json +2 -1
- package/modules/lifecycle/pg-constraint-fail.js +49 -0
- package/modules/lifecycle/routes/artist-gate.js +101 -0
- package/modules/lifecycle/routes/claims.js +13 -1
- package/modules/lifecycle/routes/lifecycle.js +6 -0
- package/modules/lifecycle/routes/task-write-routes.js +7 -0
- package/modules/lifecycle/routes/tasks.js +24 -0
- package/modules/lifecycle/task-classifier.js +10 -1
- package/modules/lifecycle/tests/cascade.mjs +43 -0
- package/modules/onboarding/approval-broadcast.js +27 -0
- package/modules/provisioning/provisioning.js +79 -3
- package/modules/provisioning/routes/provisioning.js +6 -1
- package/modules/provisioning/tests/provisioning.mjs +8 -8
- package/modules/security/secret-scrub.js +40 -4
- package/modules/status-ui/public/README.md +1 -1
- package/modules/ui-design/docs/design-contract.md +2 -2
- package/modules/ui-design/kit/README.md +1 -1
- package/modules/ui-design/kit/serve.js +1 -0
- package/modules/ui-design/skills/README.md +6 -6
- package/modules/ui-design/skills/brandkit/SKILL.md +1 -1
- package/modules/ui-design/skills/design-taste-frontend/SKILL.md +1 -1
- package/modules/ui-design/skills/design-taste-frontend-v1/SKILL.md +1 -1
- package/modules/ui-design/skills/gpt-taste/SKILL.md +1 -1
- package/modules/ui-design/skills/high-end-visual-design/SKILL.md +1 -1
- package/modules/ui-design/skills/image-to-code/SKILL.md +1 -1
- package/modules/ui-design/skills/imagegen-frontend-mobile/SKILL.md +1 -1
- package/modules/ui-design/skills/imagegen-frontend-web/SKILL.md +1 -1
- package/modules/ui-design/skills/impeccable/SKILL.md +1 -1
- package/modules/ui-design/skills/industrial-brutalist-ui/SKILL.md +1 -1
- package/modules/ui-design/skills/minimalist-ui/SKILL.md +1 -1
- package/modules/ui-design/skills/redesign-existing-projects/SKILL.md +1 -1
- package/modules/ui-design/skills/stitch-design-taste/SKILL.md +1 -1
- package/modules/ui-design/skills/style/SKILL.md +2 -2
- package/modules/ui-design/styles/README.md +3 -3
- package/modules/ui-design/styles/blueprint/DESIGN.md +1 -1
- package/modules/ui-design/styles/chrome-world/DESIGN.md +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/gds/adopt-preflight.js +68 -2
- package/scripts/gds/audit-rank-roles.js +172 -0
- package/scripts/gds/build-cli-package.js +2 -0
- package/scripts/gds/claim.js +94 -9
- package/scripts/gds/claude-md-template.js +18 -0
- package/scripts/gds/device-flow-help.js +72 -0
- package/scripts/gds/fitness.js +8 -8
- package/scripts/gds/go-live.js +139 -5
- package/scripts/gds/init.js +64 -8
- package/scripts/gds/land-watch.js +13 -5
- package/scripts/gds/login.js +18 -0
- package/scripts/gds/mirror-redact.js +13 -2
- package/scripts/gds/package-core.js +11 -3
- package/scripts/gds/release.js +55 -0
- package/scripts/gds/setup.js +5 -39
- package/scripts/gds/ship-land.js +10 -2
- package/scripts/gds/ship-regen.js +41 -8
- package/scripts/gds/start.js +7 -4
- package/scripts/gds/worktree-claim-guard.js +27 -1
- package/scripts/public-mirror-export.js +17 -4
- package/src/bongos/api-errors.js +9 -0
- package/src/bongos/artist-gate.js +204 -0
- package/src/bongos/repo-info.js +37 -6
- package/src/branding.js +15 -0
- package/src/module-api.js +41 -1
- package/tests/adopt_preflight.mjs +72 -0
- package/tests/api_path_404.mjs +28 -0
- package/tests/artist_gate.mjs +234 -0
- package/tests/artist_gate_release.mjs +283 -0
- package/tests/artist_gate_route.mjs +174 -0
- package/tests/bongos_login.mjs +69 -2
- package/tests/box_scope_predicate.mjs +117 -0
- package/tests/claim_error_surface.mjs +163 -1
- package/tests/claude_materialize.mjs +9 -0
- package/tests/cli_exit_no_abort.mjs +256 -3
- package/tests/currency_label.mjs +11 -4
- package/tests/discord_approvals.mjs +45 -0
- package/tests/go_live.mjs +144 -0
- package/tests/init.mjs +52 -2
- package/tests/main_worktree_busy_reason.mjs +167 -0
- package/tests/membership_kind_lockstep.mjs +88 -0
- package/tests/mirror_redact.mjs +14 -0
- package/tests/module_api.mjs +7 -0
- package/tests/provision_settings_apply.mjs +13 -5
- package/tests/provisioning_settings.mjs +11 -7
- package/tests/provisioning_settings_apply.mjs +4 -4
- package/tests/provisioning_settings_env.mjs +59 -7
- package/tests/public_mirror_export.mjs +12 -0
- package/tests/rank_roles_audit.mjs +114 -0
- package/tests/repo_info_binding.mjs +82 -0
- package/tests/scrubber_corpus.mjs +70 -2
- package/tests/setup_device_flow_fallback.mjs +16 -0
- package/tests/start_rebase_warning.mjs +50 -0
- package/tests/task_field_bounds_match_db.mjs +140 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// scripts/gds/device-flow-help.js — the one explanation of a Device-Flow refusal, shared
|
|
4
|
+
// by `bongos setup` and `bongos login` (task 1003400).
|
|
5
|
+
//
|
|
6
|
+
// WHY IT EXISTS AT ALL. GitHub creates an App from a manifest with Device Flow OFF and
|
|
7
|
+
// exposes no API to switch it on — only a checkbox in the App's settings. So on every
|
|
8
|
+
// wizard-born instance the documented terminal sign-in is hard-broken from birth, for
|
|
9
|
+
// every builder, until the owner ticks that box. Nothing in the product can do it for
|
|
10
|
+
// them; the least we can do is say so precisely, and name a route that works meanwhile.
|
|
11
|
+
//
|
|
12
|
+
// WHY IT IS ITS OWN FILE. setup.js grew the detector + the guidance first; login.js threw
|
|
13
|
+
// a bare "could not start sign-in (HTTP 424)" — a dead end sitting directly downstream of
|
|
14
|
+
// the CLI's OWN wrong-instance advice to run `bongos login`. setup.js already requires
|
|
15
|
+
// login.js for runDeviceFlow, so having login.js require setup.js back would close a
|
|
16
|
+
// require cycle. Both now depend on this leaf instead: one source of truth, no cycle.
|
|
17
|
+
//
|
|
18
|
+
// PURE — no fs, no network, no process state; both functions unit-test standalone.
|
|
19
|
+
|
|
20
|
+
// Does this /auth/device/start response carry GitHub's device_flow_disabled refusal?
|
|
21
|
+
//
|
|
22
|
+
// Matches on TEXT, not status, deliberately: the CLI talks to many core versions, and the
|
|
23
|
+
// server envelope moved from 502 to 424 (a 502 body was being eaten by the CDN). Both
|
|
24
|
+
// register. Must NOT match auth_not_configured or any other upstream failure — those have
|
|
25
|
+
// different cures, and a wrong cure is worse than a generic error.
|
|
26
|
+
function isDeviceFlowDisabled(start) {
|
|
27
|
+
if (!start || start.ok) return false;
|
|
28
|
+
const err = start.data && start.data.error;
|
|
29
|
+
const text = [
|
|
30
|
+
typeof err === 'string' ? err : '',
|
|
31
|
+
err && typeof err === 'object' ? `${err.code || ''} ${err.message || ''}` : '',
|
|
32
|
+
start.data && start.data.message ? String(start.data.message) : '',
|
|
33
|
+
].join(' ');
|
|
34
|
+
return /device_flow_disabled/i.test(text);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The plain-language guidance: what happened, the web route that works RIGHT NOW, and the
|
|
38
|
+
// owner's one-checkbox repair. Returns an array of lines (the callers join them).
|
|
39
|
+
//
|
|
40
|
+
// `finish` names the caller, because only the closing move differs: setup prompts for the
|
|
41
|
+
// token inline ("paste it below"), login has no such prompt and must tell you to run the
|
|
42
|
+
// one-liner instead. Everything above that step is genuinely shared and stays shared.
|
|
43
|
+
function deviceFlowFallbackLines(base, { finish = 'setup' } = {}) {
|
|
44
|
+
const settings = `${base}/builders/settings`;
|
|
45
|
+
const close = finish === 'login'
|
|
46
|
+
? [
|
|
47
|
+
' 3. Copy the one-liner it shows and run it in this shell — that writes',
|
|
48
|
+
' the same session `bongos login` would have.',
|
|
49
|
+
]
|
|
50
|
+
: [
|
|
51
|
+
' 3. Copy the token it shows and paste it below (or run the printed',
|
|
52
|
+
' one-liner in another shell, then re-run setup).',
|
|
53
|
+
];
|
|
54
|
+
return [
|
|
55
|
+
'',
|
|
56
|
+
"This project's GitHub App has Device Flow switched off, so terminal sign-in",
|
|
57
|
+
'cannot start. Apps created by the one-click setup begin this way — GitHub',
|
|
58
|
+
'only offers the switch as a checkbox in the App settings; no setup flow can',
|
|
59
|
+
'tick it automatically.',
|
|
60
|
+
'',
|
|
61
|
+
'Sign in through the web instead (works right now):',
|
|
62
|
+
` 1. Open ${settings} and sign in with GitHub.`,
|
|
63
|
+
' 2. Under "CLI Access", click "Re-issue CLI token".',
|
|
64
|
+
...close,
|
|
65
|
+
'',
|
|
66
|
+
'To repair terminal sign-in for everyone (the project owner, once):',
|
|
67
|
+
' GitHub → Settings → Developer settings → GitHub Apps → your app →',
|
|
68
|
+
' check "Enable Device Flow" → Save. Web sign-in is unaffected either way.',
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { isDeviceFlowDisabled, deviceFlowFallbackLines };
|
package/scripts/gds/fitness.js
CHANGED
|
@@ -520,17 +520,17 @@ const KERNEL_FILES = [
|
|
|
520
520
|
// KERNEL (already in the module-scope-map kernel key) and imports only the
|
|
521
521
|
// kernel (instance-config) + node built-ins, so it joins the roster cleanly.
|
|
522
522
|
'src/bongos/repo-info.js',
|
|
523
|
-
// task 1002331 (ADR 0182 D6 / ADR 0247): the project's composed join/apply door —
|
|
524
|
-
//
|
|
523
|
+
// task 1002331 (ADR 0182 D6 / ADR 0247): the project's composed join/apply door — a
|
|
524
|
+
// deterministic classifier over three policy values, the same category as
|
|
525
525
|
// route-rank-check.js and goal-scope-check.js above (a trust decision the doorway
|
|
526
526
|
// hands to modules so no module re-derives it). The doorway re-exports it, so
|
|
527
527
|
// leaving it off this roster reads as a kernel->non-kernel leak; it imports only
|
|
528
|
-
// branding.js, instance-config.js and logger.js — all on this roster — so
|
|
529
|
-
//
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
'src/bongos/project-door.js',
|
|
528
|
+
// branding.js, instance-config.js and logger.js — all on this roster — so joining
|
|
529
|
+
// costs nothing and buys the import-direction guarantee on the file that decides
|
|
530
|
+
// who may knock. (Task 1003579 moved joinabilityMode the other way: the gate reads
|
|
531
|
+
// the composed door, so reader and composer had to sit together or the two would
|
|
532
|
+
// cycle.) The ARTIST GATE joins for the same reason — task 1003575, ADR 0241.
|
|
533
|
+
'src/bongos/project-door.js', 'src/bongos/artist-gate.js',
|
|
534
534
|
];
|
|
535
535
|
|
|
536
536
|
// Kernel files exempt from the no-module-keys scan (Check 10) because naming a
|
package/scripts/gds/go-live.js
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
const fs = require('node:fs');
|
|
45
45
|
const path = require('node:path');
|
|
46
46
|
const { spawnSync } = require('node:child_process');
|
|
47
|
-
const { arg, hasFlag } = require('./cli-lib');
|
|
47
|
+
const { arg, hasFlag, apiCall } = require('./cli-lib');
|
|
48
48
|
const divergence = require('./deploy-divergence.js');
|
|
49
49
|
|
|
50
50
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
@@ -370,6 +370,57 @@ function refreshMain({ run = spawnSync } = {}) {
|
|
|
370
370
|
return { ok: true };
|
|
371
371
|
}
|
|
372
372
|
|
|
373
|
+
// Ask the LIVE instance about its own artist gate (ADR 0241, task 1003575).
|
|
374
|
+
//
|
|
375
|
+
// The instance answers because it is the only party that can: the policy arrives
|
|
376
|
+
// in its web.env and its board holds the reviews. This script only decides what to
|
|
377
|
+
// do with the answer, and the refusal sentence is composed there rather than here
|
|
378
|
+
// so one wording serves every caller.
|
|
379
|
+
//
|
|
380
|
+
// NEVER THROWS. Every failure — no session, a 401, an instance too old to carry
|
|
381
|
+
// the route, a network error — resolves to { unread: <why> }, which renders as a
|
|
382
|
+
// note. See artistGateEntries for why an unread gate is not a halt.
|
|
383
|
+
// The FULL path, not a bare `/artist-gate`. cli-lib's apiCall composes
|
|
384
|
+
// `${base}${urlPath}` verbatim and `base` is the instance ORIGIN — every other
|
|
385
|
+
// caller in this repo spells `/api/bongos/…` by hand for the same reason. Getting
|
|
386
|
+
// this wrong is silent in the worst way: the request lands on the origin's
|
|
387
|
+
// catch-all, the gate reports itself unread on every instance including the ones
|
|
388
|
+
// that fully support it, and the deploy check never fires. Exported so a test can
|
|
389
|
+
// pin it against the route's REAL mount point in the generated OpenAPI spec.
|
|
390
|
+
const ARTIST_GATE_PATH = '/api/bongos/artist-gate';
|
|
391
|
+
// The levels a real answer can carry — mirrored from src/bongos/artist-gate.js
|
|
392
|
+
// rather than imported, because this script also runs from an INSTALLED core on a
|
|
393
|
+
// box where that path is inside the package, and a require of it here would make
|
|
394
|
+
// the deploy tool depend on the server's own tree. Pinned by the same test that
|
|
395
|
+
// pins the path.
|
|
396
|
+
const ARTIST_GATE_LEVELS = ['off', 'advisory', 'strict'];
|
|
397
|
+
|
|
398
|
+
async function readArtistGate(call = apiCall) {
|
|
399
|
+
try {
|
|
400
|
+
const res = await call('GET', ARTIST_GATE_PATH, null, { allowUnauthenticated: true, allowBoxScope: true });
|
|
401
|
+
if (res.status === 404) return { unread: `this instance does not serve ${ARTIST_GATE_PATH} — it predates ADR 0241` };
|
|
402
|
+
// A 5xx is a gate that EXISTS and BROKE, which is not the same class of
|
|
403
|
+
// unknown as "no session" or "too old to have the route". The gate is
|
|
404
|
+
// fail-closed by design, so the one case where we know it is there and
|
|
405
|
+
// cannot get an answer from it HALTS rather than degrading to a note —
|
|
406
|
+
// otherwise a broken read is a quieter way past the gate than the owner's
|
|
407
|
+
// own override, which is the asymmetry worth refusing.
|
|
408
|
+
if (res.status >= 500) return { broken: `GET ${ARTIST_GATE_PATH} returned ${res.status}` };
|
|
409
|
+
if (!res.ok) return { unread: `GET ${ARTIST_GATE_PATH} returned ${res.status}` };
|
|
410
|
+
const d = res.data || {};
|
|
411
|
+
// A 200 that is not this route's answer — an SPA index.html served by a
|
|
412
|
+
// catch-all, a proxy's own page — must read as UNREAD, never as a gate that
|
|
413
|
+
// holds nothing. "I got a 200 from something" is the one failure that would
|
|
414
|
+
// wave a deploy through in silence, which is worse than any halt.
|
|
415
|
+
if (!ARTIST_GATE_LEVELS.includes(d.mode)) {
|
|
416
|
+
return { unread: `GET ${ARTIST_GATE_PATH} answered ${res.status} but not with a gate — got mode ${JSON.stringify(d.mode)}` };
|
|
417
|
+
}
|
|
418
|
+
return { mode: d.mode, since: d.since || null, holds: !!d.holds, reviews: d.reviews || [], halt: d.halt || null };
|
|
419
|
+
} catch (e) {
|
|
420
|
+
return { unread: (e && e.message) || 'request failed' };
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
373
424
|
// Parse a `key=value` marker into its parts. Kept pure + exported: the marker is
|
|
374
425
|
// the answer to the trap that the release current when your fix merges usually
|
|
375
426
|
// does NOT contain your fix (ADR 0161 carriers bump on their own cadence).
|
|
@@ -394,12 +445,68 @@ const HALTS = {
|
|
|
394
445
|
MARKER_UNCHECKED: 'could not inspect the target version — the content proof did not run',
|
|
395
446
|
BOX_TREE_DIRTY: 'the instance repo on the box has uncommitted changes',
|
|
396
447
|
LIVE_AHEAD: 'live is AHEAD of the target',
|
|
448
|
+
ARTIST_REVIEW_UNRESOLVED: 'an unresolved artist review holds this deploy (ADR 0241)',
|
|
449
|
+
ARTIST_GATE_UNREADABLE: 'the artist gate is there and answering with an error — this deploy cannot tell whether a review holds it',
|
|
397
450
|
};
|
|
398
451
|
|
|
399
|
-
|
|
452
|
+
// The artist gate (ADR 0241, task 1003575). This is the ONE place in the deploy
|
|
453
|
+
// path that the artist's authority touches, and the boundary is the whole point:
|
|
454
|
+
// `strict` may delay a PIN MOVE and nothing else. Nothing in the ship, grade,
|
|
455
|
+
// merge or land path consults this — a builder's work is credited and on `main`
|
|
456
|
+
// long before a go-live runs, which is what lets this gate coexist with ADR 0162
|
|
457
|
+
// instead of recreating the strand it retired.
|
|
458
|
+
//
|
|
459
|
+
// It reads a STATE, not a verdict: the instance reports which artist review tasks
|
|
460
|
+
// are still open, never whether an artist approved anything. See
|
|
461
|
+
// src/bongos/artist-gate.js, which composes the refusal sentence this renders.
|
|
462
|
+
// The halt and/or note one gate read contributes. Shared by planGoLive and by the
|
|
463
|
+
// gitless (on-box, no checkout) path, so a lane that skips the git-derived checks
|
|
464
|
+
// does not also silently skip the gate — the deploy is the same deploy either way.
|
|
465
|
+
// PURE.
|
|
466
|
+
function artistGateEntries(gate) {
|
|
467
|
+
const halts = [];
|
|
468
|
+
const notes = [];
|
|
469
|
+
if (gate && gate.halt) {
|
|
470
|
+
halts.push({ code: 'ARTIST_REVIEW_UNRESOLVED', message: gate.halt.message, detail: gate.halt.detail });
|
|
471
|
+
}
|
|
472
|
+
// A gate that is THERE and BROKEN halts. This is the fail-closed half: we know
|
|
473
|
+
// the instance has the route, so "it 500s" is a gate we cannot get an answer
|
|
474
|
+
// from, not a gate that does not apply. Its remedy is the instance's own logs,
|
|
475
|
+
// and it is named as such rather than as an artist problem.
|
|
476
|
+
if (gate && gate.broken) {
|
|
477
|
+
halts.push({
|
|
478
|
+
code: 'ARTIST_GATE_UNREADABLE',
|
|
479
|
+
message: HALTS.ARTIST_GATE_UNREADABLE,
|
|
480
|
+
detail: `${gate.broken}. The route exists, so this is a fault on the instance, not a missing feature — read its logs for the GET /artist-gate failure. Deploying past it would mean moving the pin without knowing whether a review holds it.`,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
// NOT KNOWING is a different thing again, and it must not render as "nothing
|
|
484
|
+
// holds". It is a note rather than a halt on purpose: a go-live is already
|
|
485
|
+
// deliberate and reversible, and halting every deploy because there is no CLI
|
|
486
|
+
// session — or because the instance predates the route entirely — would brick
|
|
487
|
+
// the pin with no task id to release against, which is the blocked-state-that-
|
|
488
|
+
// does-not-explain-itself failure ADR 0241 §4 names.
|
|
489
|
+
if (gate && gate.unread) {
|
|
490
|
+
notes.push({
|
|
491
|
+
code: 'ARTIST_GATE_UNREAD',
|
|
492
|
+
message: `could not read this instance's artist gate (${gate.unread}) — if the project runs artist_gate 'strict', an unresolved artist review may be holding this deploy and this plan cannot see it`,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return { halts, notes };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased = false, artistGate = null }) {
|
|
400
499
|
const halts = [];
|
|
401
500
|
const notes = [];
|
|
402
501
|
|
|
502
|
+
// Evaluated FIRST, before the git-derived checks, because it is the halt whose
|
|
503
|
+
// remedy takes longest: a review someone has to do is not a thing you fix in the
|
|
504
|
+
// next ten seconds, and finding it out after the other five checks passed is
|
|
505
|
+
// strictly worse than finding it out now.
|
|
506
|
+
const gate = artistGateEntries(artistGate);
|
|
507
|
+
halts.push(...gate.halts);
|
|
508
|
+
notes.push(...gate.notes);
|
|
509
|
+
|
|
403
510
|
if (dirtyCheckout) halts.push({ code: 'DIRTY_CHECKOUT', message: HALTS.DIRTY_CHECKOUT, detail: dirtyCheckout });
|
|
404
511
|
|
|
405
512
|
if (!div || div.undetermined) {
|
|
@@ -446,6 +553,13 @@ function renderPlan(p, { targetName, configPath } = {}) {
|
|
|
446
553
|
L.push(` target ${p.targetVersion || '(unresolved)'}`);
|
|
447
554
|
if (p.marker) L.push(` verify ${p.marker.path} contains "${p.marker.needle}"`);
|
|
448
555
|
if (p.registry) L.push(` registry ${p.registry.ok ? `carries ${p.targetVersion}` : 'MISSING'}`);
|
|
556
|
+
// The gate's state is worth a line even when it holds nothing: "artist gate
|
|
557
|
+
// advisory" is the answer to "why did nothing stop?", and a reader who cannot
|
|
558
|
+
// see the level has to go and find it.
|
|
559
|
+
if (p.artistGate && p.artistGate.mode) {
|
|
560
|
+
const open = (p.artistGate.reviews || []).length;
|
|
561
|
+
L.push(` artist gate ${p.artistGate.mode}${open ? ` · ${open} unresolved review${open === 1 ? '' : 's'}` : ' · nothing open'}`);
|
|
562
|
+
}
|
|
449
563
|
L.push('');
|
|
450
564
|
for (const n of p.notes || []) L.push(` ⚠ ${n.code} — ${n.message}`);
|
|
451
565
|
if (!p.halts.length) {
|
|
@@ -790,9 +904,28 @@ async function runGoLive({ target, targetName, configPath, json, apply, allowUnr
|
|
|
790
904
|
const dirtyRes = gitless ? { status: 1 } : spawnSync('git', ['status', '--porcelain'], { cwd: ROOT, encoding: 'utf8' });
|
|
791
905
|
const dirtyCheckout = dirtyRes.status === 0 && (dirtyRes.stdout || '').trim() ? (dirtyRes.stdout || '').trim().split('\n').slice(0, 5).join(', ') : null;
|
|
792
906
|
|
|
907
|
+
// Always read, and deliberately NOT skippable by a flag. ADR 0241 §3 names one
|
|
908
|
+
// escape hatch — the project OWNER's override-request, rationale required and
|
|
909
|
+
// audit-logged — and a `--skip` here would be a second authority path that any
|
|
910
|
+
// builder with the shell could take silently, which is exactly the shape that
|
|
911
|
+
// ADR rejects. An unread gate already degrades to a note; that is the only
|
|
912
|
+
// by-pass, and it says so on screen.
|
|
913
|
+
const artistGate = await readArtistGate();
|
|
914
|
+
|
|
915
|
+
const gitlessGate = artistGateEntries(artistGate);
|
|
793
916
|
const plan = gitless
|
|
794
|
-
? {
|
|
795
|
-
|
|
917
|
+
? {
|
|
918
|
+
halts: gitlessGate.halts,
|
|
919
|
+
notes: [
|
|
920
|
+
{ code: 'NO_CORE_CHECKOUT', message: `no core git checkout here — target ${targetVersion} was given explicitly, so the git-derived checks (unreleased-on-main, dirty tree) are skipped` },
|
|
921
|
+
...gitlessGate.notes,
|
|
922
|
+
],
|
|
923
|
+
from: null,
|
|
924
|
+
targetVersion,
|
|
925
|
+
target,
|
|
926
|
+
}
|
|
927
|
+
: planGoLive({ div, target, targetVersion, dirtyCheckout, allowUnreleased, artistGate });
|
|
928
|
+
plan.artistGate = artistGate;
|
|
796
929
|
plan.marker = marker;
|
|
797
930
|
if (!fetched.ok && !fetched.skipped) {
|
|
798
931
|
// Say so rather than silently planning off a ref of unknown age.
|
|
@@ -860,7 +993,8 @@ if (require.main === module) {
|
|
|
860
993
|
}
|
|
861
994
|
|
|
862
995
|
module.exports = {
|
|
863
|
-
loadConfig, selectTarget, validateTarget, planGoLive,
|
|
996
|
+
loadConfig, selectTarget, validateTarget, planGoLive, artistGateEntries, readArtistGate, ARTIST_GATE_PATH, ARTIST_GATE_LEVELS,
|
|
997
|
+
parseMarker, parseMarkerHits,
|
|
864
998
|
renderPlan, renderReport, failureExcerpt, asOwner, isSafeToken, NPM_AUTH_PREAMBLE, refreshMain, isLocalTarget, gitRefExists, SUDO_PREAMBLE,
|
|
865
999
|
applyGoLive, upgradeCommand, pinStep, pinModeOf,
|
|
866
1000
|
CONFIG_TEMPLATE, HALTS, SEMVER_RE, PIN_MODES, DEFAULT_PIN_MODE,
|
package/scripts/gds/init.js
CHANGED
|
@@ -525,6 +525,62 @@ function buildReadme(branding = {}) {
|
|
|
525
525
|
].join('\n');
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
+
// The START-HERE content as an APPENDABLE section, for a repo that already has a
|
|
529
|
+
// README of its own. Fenced by a stable marker so re-running init is idempotent and so
|
|
530
|
+
// an owner can find (and delete) the block we added.
|
|
531
|
+
//
|
|
532
|
+
// WHY this is not just buildReadme: an ADOPTED repo always has a README, and the
|
|
533
|
+
// write-if-absent rule below therefore skipped the first-run guide on every single adopt
|
|
534
|
+
// — silently, logging "already present — left untouched" as though nothing were owed.
|
|
535
|
+
// That removed the one human-facing instruction to run `npm install`, without which the
|
|
536
|
+
// repo has no `bongos` command at all. Appending mirrors how the adopt path already
|
|
537
|
+
// treats .gitignore: keep what the owner wrote, add only what the instance needs.
|
|
538
|
+
const README_MARKER = '<!-- cloud-bongos:start-here -->';
|
|
539
|
+
|
|
540
|
+
function buildReadmeSection(branding = {}) {
|
|
541
|
+
const guide = buildReadme(branding);
|
|
542
|
+
// Reuse the canonical guide, demoted one heading level so it nests under the owner's
|
|
543
|
+
// own README, and drop its H1 title (their README already has one).
|
|
544
|
+
const body = guide
|
|
545
|
+
.split('\n')
|
|
546
|
+
.slice(1) // drop "# <product>"
|
|
547
|
+
.join('\n')
|
|
548
|
+
.replace(/^## /gm, '### ')
|
|
549
|
+
.trim();
|
|
550
|
+
return `${README_MARKER}\n\n## Working on this project (Cloud Bongos)\n\n${body}\n`;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Append the START-HERE section to an existing README unless it is already there.
|
|
554
|
+
// PURE (text in → text out) so the idempotence is unit-testable without fs.
|
|
555
|
+
// Returns { text, action } where action is 'appended' | 'present'.
|
|
556
|
+
function appendReadmeSection(existing, section) {
|
|
557
|
+
const prev = String(existing == null ? '' : existing);
|
|
558
|
+
if (prev.includes(README_MARKER)) return { text: prev, action: 'present' };
|
|
559
|
+
return { text: `${prev.replace(/\n*$/, '\n')}\n---\n\n${section}`, action: 'appended' };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Put the first-run guide in the repo's README whichever state it is in: mint the whole
|
|
563
|
+
// guide when there is no README, else append the marked section. Shared by the greenfield
|
|
564
|
+
// and adopt paths so neither can silently owe it. Returns the action taken.
|
|
565
|
+
function layerReadme(branding, { dir, dryRun, log = console.log } = {}) {
|
|
566
|
+
const readmePath = path.join(dir, 'README.md');
|
|
567
|
+
if (!fs.existsSync(readmePath)) {
|
|
568
|
+
if (dryRun) { log(` [dry-run] would write README.md (first-run START-HERE)`); return 'created'; }
|
|
569
|
+
fs.writeFileSync(readmePath, buildReadme(branding));
|
|
570
|
+
log(` ✓ wrote README.md (first-run START-HERE)`);
|
|
571
|
+
return 'created';
|
|
572
|
+
}
|
|
573
|
+
const { text, action } = appendReadmeSection(fs.readFileSync(readmePath, 'utf8'), buildReadmeSection(branding));
|
|
574
|
+
if (action === 'present') {
|
|
575
|
+
log(' • README.md already carries the Cloud Bongos start-here section — left untouched.');
|
|
576
|
+
return 'present';
|
|
577
|
+
}
|
|
578
|
+
if (dryRun) { log(' [dry-run] would append the Cloud Bongos start-here section to the existing README.md'); return 'appended'; }
|
|
579
|
+
fs.writeFileSync(readmePath, text);
|
|
580
|
+
log(' ✓ appended the Cloud Bongos start-here section to README.md (your own content is untouched)');
|
|
581
|
+
return 'appended';
|
|
582
|
+
}
|
|
583
|
+
|
|
528
584
|
function writeConfigs(spec, { dir, force, dryRun, coreDep, log = console.log }) {
|
|
529
585
|
const configDir = path.join(dir, 'config');
|
|
530
586
|
const brandingPath = path.join(configDir, 'branding.json');
|
|
@@ -572,14 +628,9 @@ function writeConfigs(spec, { dir, force, dryRun, coreDep, log = console.log })
|
|
|
572
628
|
log(` ✓ wrote ${path.relative(dir, modulesPath)}`);
|
|
573
629
|
log(` ✓ wrote ${path.relative(dir, hierarchyPath)}`);
|
|
574
630
|
log(` ✓ wrote ${path.relative(dir, pkgPath)} (pins ${CORE_PKG} → ${pinNote})`);
|
|
575
|
-
// README START-HERE (task 1002404):
|
|
576
|
-
// owner's own README
|
|
577
|
-
|
|
578
|
-
fs.writeFileSync(readmePath, buildReadme(branding));
|
|
579
|
-
log(` ✓ wrote ${path.relative(dir, readmePath)} (first-run START-HERE)`);
|
|
580
|
-
} else {
|
|
581
|
-
log(` • ${path.relative(dir, readmePath)} already present — left untouched`);
|
|
582
|
-
}
|
|
631
|
+
// README START-HERE (task 1002404): mint it when absent, else APPEND the marked
|
|
632
|
+
// section. Never a blind overwrite — an owner's own README content always survives.
|
|
633
|
+
layerReadme(branding, { dir, dryRun: false, log });
|
|
583
634
|
return { brandingPath, modulesPath, hierarchyPath, pkgPath, readmePath, wrote: true };
|
|
584
635
|
}
|
|
585
636
|
|
|
@@ -816,6 +867,10 @@ async function runAdopt(spec, { dir, dryRun, noSeed = false, accept = false, cor
|
|
|
816
867
|
|
|
817
868
|
const cfg = layerConfigs(spec, { dir, dryRun, log });
|
|
818
869
|
const pkg = layerPackageJson(spec, { dir, dryRun, log, ...(coreDep ? { coreRange: coreDep } : {}) });
|
|
870
|
+
// The first-run guide, which adopt used to skip entirely: an adopted repo always has a
|
|
871
|
+
// README, so the greenfield write-if-absent rule never fired and the one instruction to
|
|
872
|
+
// run `npm install` was silently owed on every adopt.
|
|
873
|
+
layerReadme(buildBrandingConfig(spec), { dir, dryRun, log });
|
|
819
874
|
|
|
820
875
|
// Vendor the pinned core tarball + generate the lockfile, mirroring the greenfield
|
|
821
876
|
// --vendor-core steps, so the adopted repo is `npm ci --omit=dev`-installable (ADR 0108).
|
|
@@ -1061,6 +1116,7 @@ module.exports = {
|
|
|
1061
1116
|
provisioningSlug, provisioningRequest, productionTopologyKickoff,
|
|
1062
1117
|
// --adopt (brownfield layering, ADR 0121 / task 2042)
|
|
1063
1118
|
mergeCoreDependency, coreDepRange, layerConfigs, layerPackageJson, runAdopt,
|
|
1119
|
+
buildReadmeSection, appendReadmeSection, layerReadme, README_MARKER,
|
|
1064
1120
|
KNOWN_MODULES, HOSTING_SHAPES, CORE_PKG,
|
|
1065
1121
|
// greenfield installability (ADR 0108 gap 1/4, task 2053)
|
|
1066
1122
|
buildInstancePackageJson, coreVersionSafe, INSTANCE_START_SCRIPT, INSTANCE_MIGRATE_SCRIPT,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
const fs = require('node:fs');
|
|
23
23
|
const path = require('node:path');
|
|
24
|
-
const { SESSION_PATH, cliClient } = require('./cli-lib');
|
|
24
|
+
const { SESSION_PATH, cliClient, cliExit } = require('./cli-lib');
|
|
25
25
|
const shipCard = require('../../modules/lifecycle/ship-card');
|
|
26
26
|
|
|
27
27
|
// Breadcrumbs live beside the session token (per-machine, per-instance config dir —
|
|
@@ -174,14 +174,22 @@ module.exports = {
|
|
|
174
174
|
if (require.main === module) {
|
|
175
175
|
const hookMode = process.argv.includes('--hook');
|
|
176
176
|
(async () => {
|
|
177
|
+
// task 1002592: every exit below routes through cliExit (task 1002734's rule).
|
|
178
|
+
// This script exits 0 on ALL paths on purpose — it is hook-invoked and must
|
|
179
|
+
// never report failure — but on Windows the post-request teardown aborted with
|
|
180
|
+
// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)" and handed the hook
|
|
181
|
+
// 127, which conventionally reads as "command not found". Measured 3/3 before
|
|
182
|
+
// this change: the one script written never to fail always reported failure.
|
|
183
|
+
// Draining also lets the --hook stdout write flush, which a hard exit can cut
|
|
184
|
+
// mid-JSON — the card hook parses that stdout.
|
|
177
185
|
// Cheap idle fast-path: no breadcrumbs → do nothing (no network).
|
|
178
|
-
if (readBreadcrumbs().length === 0) { if (!hookMode) console.error('land-watch: no pending lands.');
|
|
186
|
+
if (readBreadcrumbs().length === 0) { if (!hookMode) console.error('land-watch: no pending lands.'); return cliExit(0); }
|
|
179
187
|
const envelope = await watch();
|
|
180
188
|
if (hookMode) {
|
|
181
189
|
if (envelope) process.stdout.write(JSON.stringify(envelope));
|
|
182
|
-
|
|
190
|
+
return cliExit(0);
|
|
183
191
|
}
|
|
184
192
|
console.error(envelope ? `land-watch: a task landed — shipped card ready (${envelope.title}).` : 'land-watch: nothing landed this pass.');
|
|
185
|
-
|
|
186
|
-
})().catch(() =>
|
|
193
|
+
return cliExit(0);
|
|
194
|
+
})().catch(() => cliExit(0)); // never throw from a hook-invoked script
|
|
187
195
|
}
|
package/scripts/gds/login.js
CHANGED
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
const readline = require('node:readline');
|
|
24
24
|
const { spawnSync } = require('node:child_process');
|
|
25
25
|
const { saveSession, loadStoredSession, listStoredSessions } = require('./cli-lib');
|
|
26
|
+
// The device-flow refusal detector + its plain-language cure (task 1003400). A leaf
|
|
27
|
+
// module, NOT setup.js: setup requires this file for runDeviceFlow, so requiring it
|
|
28
|
+
// back would close a require cycle. Both callers depend on the leaf instead.
|
|
29
|
+
const { isDeviceFlowDisabled, deviceFlowFallbackLines } = require('./device-flow-help');
|
|
26
30
|
const { branding } = require('../../src/branding');
|
|
27
31
|
|
|
28
32
|
// --- pure helpers (unit-tested) ---------------------------------------------
|
|
@@ -102,6 +106,20 @@ async function runDeviceFlow(base, { log, idp = null }) {
|
|
|
102
106
|
if (start.status === 503 || (start.data && (start.data.error === 'auth_not_configured' || (start.data.error && start.data.error.code === 'idp_signing_unconfigured')))) {
|
|
103
107
|
throw new Error('that instance has no sign-in configured yet — ask its Archon to set it up.');
|
|
104
108
|
}
|
|
109
|
+
// Device Flow is OFF on every GitHub App born from a manifest, and GitHub exposes no
|
|
110
|
+
// API to enable it — so terminal sign-in is broken from birth on a fresh instance,
|
|
111
|
+
// for everyone, until its owner ticks one checkbox. This threw a bare
|
|
112
|
+
// "could not start sign-in (HTTP 424)", which is where the CLI's OWN wrong-instance
|
|
113
|
+
// refusal sends every multi-project user: a dead end at the end of our own advice.
|
|
114
|
+
// In federated mode the device flow runs against the HUB, so the cure is on the hub's
|
|
115
|
+
// app — point the guidance at whichever origin actually refused.
|
|
116
|
+
if (isDeviceFlowDisabled(start)) {
|
|
117
|
+
const refusedBy = idp ? idp.origin : base;
|
|
118
|
+
throw new Error([
|
|
119
|
+
'GitHub refused the device-code request for this project.',
|
|
120
|
+
...deviceFlowFallbackLines(refusedBy, { finish: 'login' }),
|
|
121
|
+
].join('\n'));
|
|
122
|
+
}
|
|
105
123
|
if (!start.ok || !start.data || !start.data.device_code) {
|
|
106
124
|
throw new Error(`could not start sign-in (HTTP ${start.status}).`);
|
|
107
125
|
}
|
|
@@ -286,9 +286,20 @@ function findLeaks(text, sensitiveLiterals) {
|
|
|
286
286
|
// findSecretLeak — true if the generic secret scrubber WOULD change this text, i.e. a
|
|
287
287
|
// secret shape is present. The export gate runs this in addition to findLeaks so a
|
|
288
288
|
// stray credential in an old commit fails the publish closed rather than leaking.
|
|
289
|
-
|
|
289
|
+
//
|
|
290
|
+
// `opts` MUST be whatever the scrub being audited was given (task 1003873). Unlike
|
|
291
|
+
// findLeaks — which is deliberately STRICTER than redaction, and independent of it by
|
|
292
|
+
// design — this one is DIFFERENTIAL: it re-scrubs and asks "did anything change?". An
|
|
293
|
+
// option the audit does not share therefore makes it disagree with its own subject by
|
|
294
|
+
// construction, and reads that disagreement as a leak. That is not a safety margin, it
|
|
295
|
+
// is a false positive: task 1002460's knownDocNames allowlist spares a long ADR
|
|
296
|
+
// filename from the entropy sweep, an audit without the allowlist re-redacts it, the
|
|
297
|
+
// text differs, and EVERY published doc reports a phantom secret shape. Because the
|
|
298
|
+
// gate is fail-closed the packager then produces nothing — it took the publish lane
|
|
299
|
+
// down for seven consecutive core releases before anyone read past the symptom.
|
|
300
|
+
function findSecretLeak(text, extraSecrets = [], opts = undefined) {
|
|
290
301
|
if (typeof text !== 'string' || text.length === 0) return false;
|
|
291
|
-
return scrubSecrets(text, extraSecrets) !== text;
|
|
302
|
+
return scrubSecrets(text, extraSecrets, opts) !== text;
|
|
292
303
|
}
|
|
293
304
|
|
|
294
305
|
module.exports = {
|
|
@@ -193,7 +193,7 @@ function functionalSecretLeaks(view, envSecrets) {
|
|
|
193
193
|
// * FUNCTIONAL: VERBATIM buffer. `leaks` = exact live-credential matches only (FATAL).
|
|
194
194
|
// `advisories` = brand-identity substrings + public IPs that ship as-is (non-fatal, for
|
|
195
195
|
// human awareness) — REUSES findLeaks so the detector is defined once.
|
|
196
|
-
function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets }) {
|
|
196
|
+
function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets, knownDocNames }) {
|
|
197
197
|
// An auto-loaded agent doc (CLAUDE.md/AGENTS.md) → ship a brand-free stub, never the
|
|
198
198
|
// core's own project memory (task 2121). The stub is brand-free by construction, so it
|
|
199
199
|
// needs no redaction and can never leak — it short-circuits ahead of the doc path.
|
|
@@ -201,7 +201,7 @@ function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets })
|
|
|
201
201
|
return { buf: Buffer.from(CORE_AGENT_DOC_STUB, 'utf8'), leaks: [], advisories: [], kind: 'stub' };
|
|
202
202
|
}
|
|
203
203
|
if (isDocPath(pathRel)) {
|
|
204
|
-
const r = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets });
|
|
204
|
+
const r = redactBlob({ buf: raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
|
|
205
205
|
const leaks = r.leaks.map((l) => ({ label: `doc:${l.label}`, sample: l.sample }));
|
|
206
206
|
if (r.secret) leaks.push({ label: 'doc:secret-shape' });
|
|
207
207
|
return { buf: r.buf, leaks, advisories: [], kind: 'doc' };
|
|
@@ -817,6 +817,14 @@ function main() {
|
|
|
817
817
|
// 4. Process every file IN MEMORY (processCoreFile: docs redacted, functional verbatim)
|
|
818
818
|
// + the fail-closed no-leak gate. Nothing touches dist/ until the ENTIRE set is proven
|
|
819
819
|
// clean (atomic abort). Brand/topology in functional files are non-fatal ADVISORIES.
|
|
820
|
+
// task 1002460 — the doc-link allowlist. The entropy sweep cannot tell a long
|
|
821
|
+
// ADR filename from a secret by shape, so it collapsed every one of them to
|
|
822
|
+
// <redacted>.md on the way out. This is the set of names actually being
|
|
823
|
+
// published, so a name in it is a FILE and not a guess; anything else, secrets
|
|
824
|
+
// included, is redacted exactly as before.
|
|
825
|
+
const knownDocNames = new Set(
|
|
826
|
+
publishable.map((e) => e.path.split('/').pop()).filter((n) => /\.(md|sql)$/i.test(n)),
|
|
827
|
+
);
|
|
820
828
|
const staged = []; // { name, mode, buf } (name includes TAR_ROOT)
|
|
821
829
|
const filesMeta = []; // { path, mode, sha256 } (source files only — the pin domain)
|
|
822
830
|
const leaks = [];
|
|
@@ -829,7 +837,7 @@ function main() {
|
|
|
829
837
|
try { raw = gitBuf(['cat-file', 'blob', e.sha]); }
|
|
830
838
|
catch (err) { leaks.push({ path: e.path, label: `read-failed:${err.message}` }); continue; }
|
|
831
839
|
const { buf, leaks: fileLeaks, advisories: fileAdvisories, kind } =
|
|
832
|
-
processCoreFile({ pathRel: e.path, raw, rules, sensitiveLiterals, envSecrets });
|
|
840
|
+
processCoreFile({ pathRel: e.path, raw, rules, sensitiveLiterals, envSecrets, knownDocNames });
|
|
833
841
|
for (const l of fileLeaks) leaks.push({ path: e.path, label: l.label, sample: l.sample });
|
|
834
842
|
for (const a of fileAdvisories) advisories.push({ path: e.path, label: a.label, sample: a.sample });
|
|
835
843
|
if (kind === 'doc') docCount++; else if (kind === 'stub') stubCount++; else funcCount++;
|
package/scripts/gds/release.js
CHANGED
|
@@ -6,7 +6,46 @@
|
|
|
6
6
|
//
|
|
7
7
|
// The task returns to 'ready' status and is claimable by another session.
|
|
8
8
|
|
|
9
|
+
const { execFileSync } = require('node:child_process');
|
|
10
|
+
const path = require('node:path');
|
|
9
11
|
const { cliClient, requireSession, arg } = require('./cli-lib');
|
|
12
|
+
const { parkedMainCheckoutWarning } = require('./worktree-claim-guard');
|
|
13
|
+
|
|
14
|
+
// gitAt(args, cwd) — run git with an ARGV ARRAY and no shell.
|
|
15
|
+
//
|
|
16
|
+
// Deliberately not `execSync('git -C "<path>" …')`. A resolved filesystem path is
|
|
17
|
+
// not trusted input: a directory name containing a quote, backtick or `$()` would
|
|
18
|
+
// break out of the quoting and execute under cmd.exe/sh. execFileSync never
|
|
19
|
+
// spawns a shell, so the path cannot be interpreted as anything but a path — a
|
|
20
|
+
// stronger guarantee than the `{ cwd }` form, which still shells the command
|
|
21
|
+
// string. (Found by the grader on this task, and it was right: the first cut
|
|
22
|
+
// interpolated the path into a shell string.)
|
|
23
|
+
//
|
|
24
|
+
// Kept local rather than imported from ship-git.js on purpose: release.js ships
|
|
25
|
+
// in the PUBLIC client-only @cloudbongos/cli package, and nothing in the ship-*
|
|
26
|
+
// family is in that manifest. tests/cli_package.mjs installs the tarball and RUNS
|
|
27
|
+
// every verb, so an import from outside it is a crash at require time, not a
|
|
28
|
+
// lint-level nit — that is exactly how this file broke once already.
|
|
29
|
+
function gitAt(args, cwd) {
|
|
30
|
+
try {
|
|
31
|
+
return execFileSync('git', args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The MAIN checkout, not this worktree — from a linked worktree they differ, and
|
|
38
|
+
// the parked-tree warning is only ever about the main one.
|
|
39
|
+
//
|
|
40
|
+
// Resolved with git rather than by importing ship-state's REPO_ROOT for the same
|
|
41
|
+
// packaging reason: ship-state pulls in modules/lifecycle/ship-preflight, which
|
|
42
|
+
// would drag server-side lifecycle code into a client-only package.
|
|
43
|
+
// `--git-common-dir` is the shared .git of the whole worktree family, so its
|
|
44
|
+
// parent is the main checkout from anywhere.
|
|
45
|
+
function mainCheckoutRoot() {
|
|
46
|
+
const common = gitAt(['rev-parse', '--path-format=absolute', '--git-common-dir'], process.cwd());
|
|
47
|
+
return common ? path.dirname(common) : null;
|
|
48
|
+
}
|
|
10
49
|
|
|
11
50
|
|
|
12
51
|
(async () => {
|
|
@@ -65,6 +104,22 @@ const { cliClient, requireSession, arg } = require('./cli-lib');
|
|
|
65
104
|
}
|
|
66
105
|
console.log(`Released task #${r.data.taskId}: ${r.data.taskTitle}`);
|
|
67
106
|
console.log(`Status returned to 'ready'. No credits awarded.`);
|
|
107
|
+
// task 1002871: a claim worked under the `--here` hatch leaves the MAIN
|
|
108
|
+
// checkout on the feature branch, and giving the claim up is exactly when that
|
|
109
|
+
// tree stops being watched. Warn, never restore — the branch may hold real
|
|
110
|
+
// uncommitted work, and release.js cannot tell that from debris. Strictly
|
|
111
|
+
// best-effort: a diagnostic must never fail a release that already succeeded.
|
|
112
|
+
try {
|
|
113
|
+
const root = mainCheckoutRoot();
|
|
114
|
+
const branch = root && gitAt(['rev-parse', '--abbrev-ref', 'HEAD'], root);
|
|
115
|
+
// gitAt already trims, so a non-empty string IS dirt.
|
|
116
|
+
const dirty = root && gitAt(['status', '--porcelain'], root);
|
|
117
|
+
const lines = parkedMainCheckoutWarning({ branch, dirty: !!dirty });
|
|
118
|
+
if (lines.length) {
|
|
119
|
+
console.log('');
|
|
120
|
+
for (const line of lines) console.log(line);
|
|
121
|
+
}
|
|
122
|
+
} catch (_) { /* never let a warning break a successful release */ }
|
|
68
123
|
})().catch((err) => {
|
|
69
124
|
console.error('fatal:', err);
|
|
70
125
|
process.exit(1);
|