@workerstack/repo 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/README.md +0 -3
- package/agents/global-settings/antigravity.json +3 -0
- package/agents/global-settings/claude.json +6 -0
- package/agents/global-settings/codex.toml +2 -0
- package/agents/global-settings/cursor.json +3 -0
- package/agents/global-settings/grok.toml +36 -0
- package/agents/global-settings/manifest.json +36 -0
- package/agents/grok/hooks/orchestrate-parent.json +60 -0
- package/agents/grok/hooks/rtk.json +16 -0
- package/agents/grok/hooks/rulesync.json +3 -0
- package/agents/grok/roles/scout.toml +5 -0
- package/agents/hooks.jsonc +67 -0
- package/agents/mcp-policy.jsonc +16 -0
- package/agents/rules/overview.md +21 -0
- package/agents/settings/claude.json +5 -0
- package/agents/skills/ask/SKILL.md +13 -0
- package/agents/skills/auto/SKILL.md +13 -0
- package/agents/skills/away/SKILL.md +19 -0
- package/agents/skills/blast-radius/SKILL.md +54 -0
- package/agents/skills/bro/SKILL.md +9 -0
- package/agents/skills/browser/SKILL.md +16 -0
- package/agents/skills/clasp/SKILL.md +11 -0
- package/agents/skills/commit/SKILL.md +35 -0
- package/agents/skills/commit-and-push/SKILL.md +9 -0
- package/agents/skills/commit-push/SKILL.md +59 -0
- package/agents/skills/commit-push-deploy/SKILL.md +74 -0
- package/agents/skills/commit-push-release/SKILL.md +75 -0
- package/agents/skills/correct/SKILL.md +39 -0
- package/agents/skills/do/SKILL.md +21 -0
- package/agents/skills/explain/SKILL.md +13 -0
- package/agents/skills/google-workspace/SKILL.md +12 -0
- package/agents/skills/handoff/SKILL.md +81 -0
- package/agents/skills/implement-plan/SKILL.md +29 -0
- package/agents/skills/implement-roadmap/SKILL.md +27 -0
- package/agents/skills/jev/SKILL.md +17 -0
- package/agents/skills/listen/SKILL.md +15 -0
- package/agents/skills/pickup/SKILL.md +58 -0
- package/agents/skills/principle-attack-the-premise/SKILL.md +24 -0
- package/agents/skills/principle-boundary-discipline/SKILL.md +38 -0
- package/agents/skills/principle-build-the-lever/SKILL.md +28 -0
- package/agents/skills/principle-encode-lessons-in-structure/SKILL.md +15 -0
- package/agents/skills/principle-exhaust-the-design-space/SKILL.md +12 -0
- package/agents/skills/principle-experience-first/SKILL.md +22 -0
- package/agents/skills/principle-explain-the-number/SKILL.md +21 -0
- package/agents/skills/principle-fix-root-causes/SKILL.md +24 -0
- package/agents/skills/principle-foundational-thinking/SKILL.md +25 -0
- package/agents/skills/principle-guard-the-context-window/SKILL.md +16 -0
- package/agents/skills/principle-laziness-protocol/SKILL.md +13 -0
- package/agents/skills/principle-make-operations-idempotent/SKILL.md +25 -0
- package/agents/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +18 -0
- package/agents/skills/principle-minimize-reader-load/SKILL.md +22 -0
- package/agents/skills/principle-model-the-domain/SKILL.md +26 -0
- package/agents/skills/principle-never-block-on-the-human/SKILL.md +17 -0
- package/agents/skills/principle-outcome-oriented-execution/SKILL.md +25 -0
- package/agents/skills/principle-prove-it-works/SKILL.md +25 -0
- package/agents/skills/principle-redesign-from-first-principles/SKILL.md +18 -0
- package/agents/skills/principle-separate-before-serializing-shared-state/SKILL.md +20 -0
- package/agents/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
- package/agents/skills/principle-subtract-before-you-add/SKILL.md +21 -0
- package/agents/skills/principle-test-behavior-not-implementation/SKILL.md +24 -0
- package/agents/skills/principle-type-system-discipline/SKILL.md +17 -0
- package/agents/skills/review-plan/SKILL.md +28 -0
- package/agents/skills/review-roadmap/SKILL.md +28 -0
- package/agents/skills/show-me/SKILL.md +21 -0
- package/agents/skills/speak/SKILL.md +15 -0
- package/agents/skills/su/SKILL.md +11 -0
- package/agents/skills/teach/SKILL.md +25 -0
- package/agents/skills/tmp/SKILL.md +54 -0
- package/agents/skills/typescript-best-practices/SKILL.md +32 -0
- package/agents/skills/typescript-best-practices/references/patterns.md +328 -0
- package/agents/skills/unslop/SKILL.md +62 -0
- package/agents/skills/write-plan/SKILL.md +33 -0
- package/agents/skills/write-roadmap/SKILL.md +66 -0
- package/agents/subagents/implementer-deep.md +57 -0
- package/agents/subagents/implementer-fast.md +57 -0
- package/agents/subagents/orchestrator.md +43 -0
- package/agents/subagents/researcher.md +47 -0
- package/agents/subagents/scout.md +44 -0
- package/bin/repo.ts +4 -0
- package/cli/actionlint-default.test.ts +38 -0
- package/cli/actionlint-download.test.ts +162 -0
- package/cli/actionlint-real.test.ts +33 -0
- package/cli/actionlint.constants.ts +43 -0
- package/cli/actionlint.test.ts +108 -0
- package/cli/actionlint.ts +178 -0
- package/cli/bin.test.ts +54 -0
- package/cli/bin.ts +40 -0
- package/cli/check-changelog.test.ts +40 -0
- package/cli/check-changelog.ts +12 -0
- package/cli/check-comments.test.ts +157 -0
- package/cli/check-comments.ts +123 -0
- package/cli/check-env-modules.test.ts +85 -0
- package/cli/check-env-modules.ts +56 -0
- package/cli/check-magic.test.ts +139 -0
- package/cli/check-magic.ts +120 -0
- package/cli/check-manifests.constants.ts +4 -0
- package/cli/check-manifests.test.ts +210 -0
- package/cli/check-manifests.ts +71 -0
- package/cli/check-nightly.test.ts +103 -0
- package/cli/check-nightly.ts +37 -0
- package/cli/check-ratchets-missing.test.ts +46 -0
- package/cli/check-ratchets-mutation.test.ts +49 -0
- package/cli/check-ratchets.constants.ts +20 -0
- package/cli/check-ratchets.test.ts +190 -0
- package/cli/check-ratchets.ts +193 -0
- package/cli/check-rejections.test.ts +220 -0
- package/cli/check-rejections.ts +153 -0
- package/cli/check-reports.ts +37 -0
- package/cli/check-tsconfig.test.ts +507 -0
- package/cli/check-tsconfig.ts +205 -0
- package/cli/commands.test.ts +114 -0
- package/cli/commands.ts +218 -0
- package/cli/commit-changelog.ts +27 -0
- package/cli/commit-msg-changelog.test.ts +93 -0
- package/cli/commit-msg-range.test.ts +126 -0
- package/cli/commit-msg-red-proof.test.ts +147 -0
- package/cli/commit-msg-repo.test.ts +153 -0
- package/cli/commit-msg.test.ts +101 -0
- package/cli/commit-msg.ts +194 -0
- package/cli/commit-range.constants.ts +17 -0
- package/cli/commit-range.ts +57 -0
- package/cli/config-knip.test.ts +70 -0
- package/cli/config-nightly.test.ts +84 -0
- package/cli/config-tdd.test.ts +46 -0
- package/cli/config.test.ts +576 -0
- package/cli/config.ts +261 -0
- package/cli/conventional.ts +9 -0
- package/cli/coverage-config.ts +62 -0
- package/cli/coverage.constants.ts +6 -0
- package/cli/coverage.test.ts +199 -0
- package/cli/coverage.ts +127 -0
- package/cli/env.constants.ts +10 -0
- package/cli/env.test.ts +56 -0
- package/cli/env.ts +72 -0
- package/cli/fixtures/base-config.ts +13 -0
- package/cli/fixtures/gate-world.ts +48 -0
- package/cli/fixtures/knip-run.ts +38 -0
- package/cli/fixtures/mutate-fakes.ts +137 -0
- package/cli/fixtures/planted-repo.ts +50 -0
- package/cli/fixtures/publish-world.ts +71 -0
- package/cli/fixtures/red-proof-repo.ts +72 -0
- package/cli/fixtures/release-context.ts +15 -0
- package/cli/fixtures/release-world.ts +111 -0
- package/cli/fixtures/stub-bin.ts +40 -0
- package/cli/guard-diagnostics.test.ts +157 -0
- package/cli/guard-diagnostics.ts +195 -0
- package/cli/init.test.ts +487 -0
- package/cli/init.ts +275 -0
- package/cli/knip-check-ignores.test.ts +106 -0
- package/cli/knip-check-real.test.ts +65 -0
- package/cli/knip-check.constants.ts +46 -0
- package/cli/knip-check.test.ts +114 -0
- package/cli/knip-check.ts +169 -0
- package/cli/knip-schema.ts +67 -0
- package/cli/known-failures.constants.ts +22 -0
- package/cli/known-failures.test.ts +138 -0
- package/cli/known-failures.ts +131 -0
- package/cli/lint/as-needs-reason.ts +41 -0
- package/cli/lint/boundaries.ts +73 -0
- package/cli/lint/class-file.ts +39 -0
- package/cli/lint/config-file.ts +32 -0
- package/cli/lint/entry-file.ts +54 -0
- package/cli/lint/env-in-config.ts +66 -0
- package/cli/lint/filename-matches-export.ts +63 -0
- package/cli/lint/index-file.ts +22 -0
- package/cli/lint/index.ts +47 -0
- package/cli/lint/is-reason.ts +6 -0
- package/cli/lint/manifest.ts +46 -0
- package/cli/lint/names.ts +59 -0
- package/cli/lint/no-commented-out-code.ts +105 -0
- package/cli/lint/no-mock-of-subject.ts +64 -0
- package/cli/lint/no-module-side-effects.ts +94 -0
- package/cli/lint/no-module-state.ts +58 -0
- package/cli/lint/no-side-effect-import.ts +17 -0
- package/cli/lint/no-v8-ignore.ts +18 -0
- package/cli/lint/script-rules.ts +9 -0
- package/cli/lint/skip-on-platform.constants.ts +8 -0
- package/cli/lint/skip-on-platform.ts +118 -0
- package/cli/lint/todo-needs-id.ts +63 -0
- package/cli/lint/types-file.ts +28 -0
- package/cli/lint-schema.ts +32 -0
- package/cli/magic/fixtures/magic-repo.ts +64 -0
- package/cli/magic/magic-hit.ts +2 -0
- package/cli/magic/magic-nodes.ts +44 -0
- package/cli/magic/magic-numbers.test.ts +99 -0
- package/cli/magic/magic-numbers.ts +117 -0
- package/cli/magic/magic-strings.test.ts +38 -0
- package/cli/magic/magic-strings.ts +81 -0
- package/cli/main.test.ts +853 -0
- package/cli/main.ts +292 -0
- package/cli/mutate-args.ts +79 -0
- package/cli/mutate-check.test.ts +147 -0
- package/cli/mutate-deps.ts +42 -0
- package/cli/mutate-edges.test.ts +150 -0
- package/cli/mutate-files.test.ts +100 -0
- package/cli/mutate-files.ts +31 -0
- package/cli/mutate-floors.test.ts +91 -0
- package/cli/mutate-floors.ts +104 -0
- package/cli/mutate-real-deps.ts +57 -0
- package/cli/mutate-real.test.ts +96 -0
- package/cli/mutate-report.ts +96 -0
- package/cli/mutate-run.ts +128 -0
- package/cli/mutate-shards.ts +64 -0
- package/cli/mutate.constants.ts +67 -0
- package/cli/mutate.test.ts +182 -0
- package/cli/mutate.ts +64 -0
- package/cli/nested-vitest-flags.ts +8 -0
- package/cli/nightly-config.constants.ts +14 -0
- package/cli/nightly-config.ts +62 -0
- package/cli/prompt.test.ts +202 -0
- package/cli/prompt.ts +1 -0
- package/cli/red-proof-edges.test.ts +78 -0
- package/cli/red-proof-failures.test.ts +82 -0
- package/cli/red-proof-tree.test.ts +157 -0
- package/cli/red-proof-verdict.test.ts +44 -0
- package/cli/red-proof.constants.ts +14 -0
- package/cli/red-proof.ts +202 -0
- package/cli/release/changelog-parse.ts +83 -0
- package/cli/release/changelog.test.ts +141 -0
- package/cli/release/changelog.ts +106 -0
- package/cli/release/gate-refusals.test.ts +85 -0
- package/cli/release/gate.test.ts +63 -0
- package/cli/release/gate.ts +70 -0
- package/cli/release/git.ts +66 -0
- package/cli/release/github.test.ts +128 -0
- package/cli/release/github.ts +46 -0
- package/cli/release/list-dir.test.ts +11 -0
- package/cli/release/list-dir.ts +6 -0
- package/cli/release/main.test.ts +48 -0
- package/cli/release/main.ts +51 -0
- package/cli/release/manifest.test.ts +24 -0
- package/cli/release/manifest.ts +34 -0
- package/cli/release/pack.test.ts +107 -0
- package/cli/release/pack.ts +76 -0
- package/cli/release/plan.ts +127 -0
- package/cli/release/publish-run.test.ts +98 -0
- package/cli/release/publish.test.ts +127 -0
- package/cli/release/publish.ts +125 -0
- package/cli/release/refuse.ts +5 -0
- package/cli/release/release-messages.constants.ts +96 -0
- package/cli/release/release-refusals.test.ts +149 -0
- package/cli/release/release.constants.ts +128 -0
- package/cli/release/release.test.ts +199 -0
- package/cli/release/release.ts +145 -0
- package/cli/release/version.test.ts +118 -0
- package/cli/release/version.ts +69 -0
- package/cli/release/wait.test.ts +101 -0
- package/cli/release/wait.ts +95 -0
- package/cli/script-diagnostics.test.ts +76 -0
- package/cli/script-diagnostics.ts +62 -0
- package/cli/scripts/board.ts +519 -0
- package/cli/scripts/browser.ts +994 -0
- package/cli/scripts/change-set.ts +105 -0
- package/cli/scripts/check-globals.ts +199 -0
- package/cli/scripts/ci-skips.constants.ts +17 -0
- package/cli/scripts/ci-skips.ts +118 -0
- package/cli/scripts/ci-wait-run.ts +28 -0
- package/cli/scripts/ci-wait.constants.ts +44 -0
- package/cli/scripts/ci-wait.ts +175 -0
- package/cli/scripts/ci.constants.ts +15 -0
- package/cli/scripts/ci.ts +208 -0
- package/cli/scripts/clean.ts +363 -0
- package/cli/scripts/comments-step.ts +15 -0
- package/cli/scripts/doctor.ts +828 -0
- package/cli/scripts/emit-agent-shell-tools.ts +91 -0
- package/cli/scripts/emit-mcp-policy.ts +402 -0
- package/cli/scripts/folder-map.ts +353 -0
- package/cli/scripts/gate-fat-tools.ts +1095 -0
- package/cli/scripts/gitignore-guard.ts +338 -0
- package/cli/scripts/google-workspace.ts +779 -0
- package/cli/scripts/hook-command.ts +72 -0
- package/cli/scripts/hook-input.ts +178 -0
- package/cli/scripts/hook-io.constants.ts +5 -0
- package/cli/scripts/hook-io.ts +46 -0
- package/cli/scripts/hooks-union.ts +469 -0
- package/cli/scripts/interactive.ts +30 -0
- package/cli/scripts/jev.ts +420 -0
- package/cli/scripts/job.ts +934 -0
- package/cli/scripts/jsonc.ts +9 -0
- package/cli/scripts/lessons.ts +564 -0
- package/cli/scripts/lint-baseline.ts +233 -0
- package/cli/scripts/lint-main.ts +24 -0
- package/cli/scripts/lint-skips.ts +139 -0
- package/cli/scripts/lint.constants.ts +6 -0
- package/cli/scripts/lint.ts +142 -0
- package/cli/scripts/listen.ts +962 -0
- package/cli/scripts/managed-text.ts +47 -0
- package/cli/scripts/orchestrate-parent.ts +1155 -0
- package/cli/scripts/parse-need.ts +81 -0
- package/cli/scripts/persist-handoff.ts +228 -0
- package/cli/scripts/persist-implementation.ts +276 -0
- package/cli/scripts/persist-plan.ts +888 -0
- package/cli/scripts/persist-roadmap.ts +478 -0
- package/cli/scripts/persist-tmp.ts +177 -0
- package/cli/scripts/persist.ts +54 -0
- package/cli/scripts/phase-files.ts +121 -0
- package/cli/scripts/prompt.ts +38 -0
- package/cli/scripts/purge-global-mcps.ts +313 -0
- package/cli/scripts/rejections-step.ts +37 -0
- package/cli/scripts/rtk-grok.ts +102 -0
- package/cli/scripts/scrub-spawn-prompt.ts +103 -0
- package/cli/scripts/scrub-text-lib.ts +58 -0
- package/cli/scripts/session-map.ts +69 -0
- package/cli/scripts/setup.ts +862 -0
- package/cli/scripts/skill-conflicts.ts +82 -0
- package/cli/scripts/speak.ts +370 -0
- package/cli/scripts/strategy.ts +168 -0
- package/cli/scripts/sync-global-settings.ts +316 -0
- package/cli/scripts/turn-steps.ts +249 -0
- package/cli/scripts/turn.ts +157 -0
- package/cli/scripts/typecheck-step.ts +43 -0
- package/cli/scripts/vscode-tasks.ts +175 -0
- package/cli/scripts/workspace-root.ts +160 -0
- package/cli/scripts/worktree.ts +806 -0
- package/cli/spawn.test.ts +34 -0
- package/cli/spawn.ts +27 -0
- package/cli/staged-change.constants.ts +11 -0
- package/cli/staged-change.ts +79 -0
- package/cli/stryker-preload.test.ts +50 -0
- package/cli/stryker-preload.ts +9 -0
- package/cli/sync/agents/frontmatter.test.ts +197 -0
- package/cli/sync/agents/frontmatter.ts +140 -0
- package/cli/sync/agents/hooks.test.ts +517 -0
- package/cli/sync/agents/hooks.ts +401 -0
- package/cli/sync/agents/rules.test.ts +229 -0
- package/cli/sync/agents/rules.ts +84 -0
- package/cli/sync/agents/sources.test.ts +375 -0
- package/cli/sync/agents/sources.ts +244 -0
- package/cli/sync/agents/subagents.test.ts +149 -0
- package/cli/sync/agents/subagents.ts +158 -0
- package/cli/sync/agents-text.test.ts +31 -0
- package/cli/sync/agents-text.ts +36 -0
- package/cli/sync/fixtures/copied.README.md +35 -0
- package/cli/sync/fixtures/copied.tgz +0 -0
- package/cli/sync/fixtures/maps/golden/INDEX.md +22 -0
- package/cli/sync/fixtures/maps-ws.tgz +0 -0
- package/cli/sync/git-files.test.ts +340 -0
- package/cli/sync/git-files.ts +197 -0
- package/cli/sync/git-hooks.test.ts +94 -0
- package/cli/sync/golden.test.ts +15 -0
- package/cli/sync/hooks-mode.test.ts +99 -0
- package/cli/sync/hosts.ts +28 -0
- package/cli/sync/knip-config.constants.ts +4 -0
- package/cli/sync/knip-config.test.ts +147 -0
- package/cli/sync/knip-config.ts +108 -0
- package/cli/sync/manifest.test.ts +78 -0
- package/cli/sync/manifest.ts +60 -0
- package/cli/sync/maps.test.ts +304 -0
- package/cli/sync/maps.ts +297 -0
- package/cli/sync/migrate.test.ts +2000 -0
- package/cli/sync/migrate.ts +1104 -0
- package/cli/sync/nightly-jobs.test.ts +63 -0
- package/cli/sync/nightly.constants.ts +48 -0
- package/cli/sync/nightly.test.ts +100 -0
- package/cli/sync/nightly.ts +75 -0
- package/cli/sync/outputs.test.ts +351 -0
- package/cli/sync/outputs.ts +168 -0
- package/cli/sync/oxlint-config.test.ts +178 -0
- package/cli/sync/oxlint-config.ts +168 -0
- package/cli/sync/release-jobs.test.ts +29 -0
- package/cli/sync/release-reports.test.ts +31 -0
- package/cli/sync/release-workflow.constants.ts +61 -0
- package/cli/sync/release-workflow.test.ts +192 -0
- package/cli/sync/release-workflow.ts +86 -0
- package/cli/sync/release.constants.ts +93 -0
- package/cli/sync/release.test.ts +187 -0
- package/cli/sync/release.ts +112 -0
- package/cli/sync/repo-dir.test.ts +371 -0
- package/cli/sync/repo-dir.ts +205 -0
- package/cli/sync/standards.constants.ts +8 -0
- package/cli/sync/standards.test.ts +206 -0
- package/cli/sync/standards.ts +179 -0
- package/cli/sync/stock-history.json +2796 -0
- package/cli/sync/stock-history.test.ts +66 -0
- package/cli/sync/stock-history.ts +84 -0
- package/cli/sync/stock.test.ts +47 -0
- package/cli/sync/stock.ts +24 -0
- package/cli/sync/trees.test.ts +621 -0
- package/cli/sync/trees.ts +280 -0
- package/cli/sync/validate.test.ts +299 -0
- package/cli/sync/validate.ts +402 -0
- package/cli/sync/workspace.test.ts +180 -0
- package/cli/sync/workspace.ts +138 -0
- package/cli/sync/write.test.ts +341 -0
- package/cli/sync/write.ts +231 -0
- package/cli/sync.test.ts +1495 -0
- package/cli/sync.ts +360 -0
- package/cli/tdd-scope.test.ts +36 -0
- package/cli/tdd-scope.ts +41 -0
- package/cli/test-files.ts +16 -0
- package/cli/ts-program.ts +69 -0
- package/cli/tsconfig.constants.ts +8 -0
- package/cli/workerstack-config.types.ts +21 -0
- package/dist/cli/env.d.ts +10 -0
- package/dist/cli/env.js +64 -0
- package/dist/presets/temp-root-env.d.ts +10 -0
- package/dist/presets/temp-root-env.js +20 -0
- package/dist/presets/temp-root.constants.d.ts +6 -0
- package/dist/presets/temp-root.constants.js +6 -0
- package/dist/presets/temp-root.d.ts +2 -0
- package/dist/presets/temp-root.js +60 -0
- package/dist/presets/vitest.constants.d.ts +16 -0
- package/dist/presets/vitest.constants.js +16 -0
- package/dist/presets/vitest.d.ts +11 -0
- package/dist/presets/vitest.js +67 -0
- package/dist/presets/workers-plugin.d.ts +10 -0
- package/dist/presets/workers-plugin.js +54 -0
- package/package.json +141 -4
- package/references/INDEX.md +100 -0
- package/references/browser.md +66 -0
- package/references/cascade.md +15 -0
- package/references/clasp.md +21 -0
- package/references/cloudflare.md +36 -0
- package/references/google-workspace.md +79 -0
- package/references/hooks.md +103 -0
- package/references/implementation-format.md +119 -0
- package/references/jev.md +56 -0
- package/references/lessons.md +73 -0
- package/references/listen.md +80 -0
- package/references/map.md +107 -0
- package/references/output.md +47 -0
- package/references/plan-format.md +264 -0
- package/references/review-loop.md +48 -0
- package/references/roadmap-format.md +104 -0
- package/references/rtk.md +27 -0
- package/references/scope.md +76 -0
- package/references/setup.md +42 -0
- package/references/speak.md +59 -0
- package/tsconfig/base.json +28 -0
- package/tsconfig/bun.d.ts +1 -0
- package/tsconfig/bun.json +8 -0
- package/tsconfig/reset.d.ts +1 -0
- package/tsconfig/strict.json +12 -0
- package/tsconfig/web.json +7 -0
- package/tsconfig/worker.d.ts +1 -0
- package/tsconfig/worker.json +8 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Merge each manifest host payload (`agents/global-settings/*`) into its
|
|
4
|
+
* dotfile under $HOME: claude, codex, grok, antigravity and cursor full-permission
|
|
5
|
+
* keys. Payload keys win; unrelated dest keys survive. Applies by default;
|
|
6
|
+
* `--dry-run` only reports, `--check` reports drift per host.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { checkHint, checkHosts, checkLine, projectConfigError } from "./check-globals.ts";
|
|
12
|
+
import { isRecord } from "./hook-input.ts";
|
|
13
|
+
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
14
|
+
import { readEnv } from "../env.ts";
|
|
15
|
+
|
|
16
|
+
/** The package root, two levels above `cli/scripts/`: where the stock manifest and payloads live. */
|
|
17
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
18
|
+
const MANIFEST_REL = "agents/global-settings/manifest.json";
|
|
19
|
+
|
|
20
|
+
export type JsonObject = Record<string, unknown>;
|
|
21
|
+
export type SettingsFormat = "json" | "toml";
|
|
22
|
+
|
|
23
|
+
/** One host in the manifest: the payload file in the repo and the dotfile under $HOME it merges into. */
|
|
24
|
+
export interface ManifestHost {
|
|
25
|
+
id: string;
|
|
26
|
+
format: SettingsFormat;
|
|
27
|
+
payload: string;
|
|
28
|
+
dest: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Manifest {
|
|
32
|
+
skipKeyPattern: string;
|
|
33
|
+
hosts: ManifestHost[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ParsedArgv = { ok: true; dryRun: boolean; check: boolean; host: string } | { ok: false; error: string };
|
|
37
|
+
|
|
38
|
+
export interface ReportRow {
|
|
39
|
+
action: string;
|
|
40
|
+
id: string;
|
|
41
|
+
dest: string;
|
|
42
|
+
line: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SyncResult {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
report: ReportRow[];
|
|
48
|
+
error?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const errorText = (err: unknown): string => (err instanceof Error ? err.message : String(err));
|
|
52
|
+
|
|
53
|
+
export function parseArgv(argv: unknown): ParsedArgv {
|
|
54
|
+
const args: unknown[] = Array.isArray(argv) ? argv : [];
|
|
55
|
+
let dryRun = false;
|
|
56
|
+
let check = false;
|
|
57
|
+
let host = "all";
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
const a = args[i];
|
|
60
|
+
if (a === "--dry-run") {
|
|
61
|
+
dryRun = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (a === "--check") {
|
|
65
|
+
check = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (a === "--host") {
|
|
69
|
+
const v = args[++i];
|
|
70
|
+
if (typeof v !== "string" || !v) {
|
|
71
|
+
return { ok: false, error: "unknown host: " };
|
|
72
|
+
}
|
|
73
|
+
host = v;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (typeof a === "string" && a.startsWith("--host=")) {
|
|
77
|
+
const v = a.slice("--host=".length);
|
|
78
|
+
if (!v) {
|
|
79
|
+
return { ok: false, error: "unknown host: " };
|
|
80
|
+
}
|
|
81
|
+
host = v;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
return { ok: false, error: `unknown flag: ${a}` };
|
|
85
|
+
}
|
|
86
|
+
if (check && dryRun) return { ok: false, error: "--check and --dry-run do not combine" };
|
|
87
|
+
return { ok: true, dryRun, check, host };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function resolveDest(home: string | null | undefined, dest: string | null | undefined): string {
|
|
91
|
+
if (!home) throw new Error("home required");
|
|
92
|
+
if (dest == null || dest === "") throw new Error("dest required");
|
|
93
|
+
const resolvedHome = path.resolve(home);
|
|
94
|
+
const resolved = path.resolve(resolvedHome, dest);
|
|
95
|
+
const prefix = resolvedHome.endsWith(path.sep) ? resolvedHome : resolvedHome + path.sep;
|
|
96
|
+
if (resolved !== resolvedHome && !resolved.startsWith(prefix)) {
|
|
97
|
+
throw new Error(`dest not under home: ${dest}`);
|
|
98
|
+
}
|
|
99
|
+
return resolved;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function shouldSkip(segment: unknown, skipRe: RegExp | null | undefined): boolean {
|
|
103
|
+
if (!skipRe) return false;
|
|
104
|
+
return skipRe.test(String(segment));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isPlainObject(v: unknown): v is JsonObject {
|
|
108
|
+
return v != null && typeof v === "object" && !Array.isArray(v) && !(v instanceof Date);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function deepMerge(
|
|
112
|
+
dest: unknown,
|
|
113
|
+
source: unknown,
|
|
114
|
+
skipRe: RegExp | null | undefined,
|
|
115
|
+
keyPath: string[] = [],
|
|
116
|
+
): JsonObject {
|
|
117
|
+
const out: JsonObject = isPlainObject(dest) ? { ...dest } : {};
|
|
118
|
+
if (!isPlainObject(source)) return out;
|
|
119
|
+
for (const [k, v] of Object.entries(source)) {
|
|
120
|
+
const nextPath = [...keyPath, k];
|
|
121
|
+
if (nextPath.some((seg) => shouldSkip(seg, skipRe))) continue;
|
|
122
|
+
const prev = out[k];
|
|
123
|
+
if (isPlainObject(v) && isPlainObject(prev)) {
|
|
124
|
+
out[k] = deepMerge(prev, v, skipRe, nextPath);
|
|
125
|
+
} else {
|
|
126
|
+
out[k] = v;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function loadManifest(workspaceRoot: string): Manifest {
|
|
133
|
+
const p = path.join(workspaceRoot, MANIFEST_REL);
|
|
134
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
135
|
+
const manifest: unknown = JSON.parse(raw);
|
|
136
|
+
if (!isRecord(manifest)) {
|
|
137
|
+
throw new Error("invalid manifest");
|
|
138
|
+
}
|
|
139
|
+
const { skipKeyPattern, hosts } = manifest;
|
|
140
|
+
if (typeof skipKeyPattern !== "string") {
|
|
141
|
+
throw new Error("manifest skipKeyPattern required");
|
|
142
|
+
}
|
|
143
|
+
if (!Array.isArray(hosts)) {
|
|
144
|
+
throw new Error("manifest hosts required");
|
|
145
|
+
}
|
|
146
|
+
const checked: ManifestHost[] = [];
|
|
147
|
+
for (const h of hosts) {
|
|
148
|
+
if (!isRecord(h) || typeof h.id !== "string" || (h.format !== "json" && h.format !== "toml")) {
|
|
149
|
+
throw new Error("manifest host needs id and format json|toml");
|
|
150
|
+
}
|
|
151
|
+
if (typeof h.payload !== "string" || typeof h.dest !== "string") {
|
|
152
|
+
throw new Error(`manifest host ${h.id} needs payload and dest`);
|
|
153
|
+
}
|
|
154
|
+
checked.push({ ...h, id: h.id, format: h.format, payload: h.payload, dest: h.dest });
|
|
155
|
+
}
|
|
156
|
+
return { ...manifest, skipKeyPattern, hosts: checked };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function hostIdsOf(manifest: Manifest): Set<string> {
|
|
160
|
+
return new Set([...manifest.hosts.map((h) => h.id), "all"]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Parse payload or dest text as `format`; empty text is `{}`. Throws on bad syntax. */
|
|
164
|
+
export function parseText(raw: unknown, format: SettingsFormat): JsonObject {
|
|
165
|
+
const s = String(raw ?? "");
|
|
166
|
+
if (!s.trim()) return {};
|
|
167
|
+
const v: unknown = format === "json" ? JSON.parse(s) : parseToml(s);
|
|
168
|
+
if (!isPlainObject(v)) throw new Error("not an object");
|
|
169
|
+
return v;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Read a dest file; a missing file is `{}`, anything unreadable throws `unreadable dest: <label>`. */
|
|
173
|
+
export function readDest(destAbs: string, format: SettingsFormat, label: string): JsonObject {
|
|
174
|
+
let raw: string;
|
|
175
|
+
try {
|
|
176
|
+
raw = fs.readFileSync(destAbs, "utf8");
|
|
177
|
+
} catch (err) {
|
|
178
|
+
if (isRecord(err) && err.code === "ENOENT") return {};
|
|
179
|
+
throw new Error(`unreadable dest: ${label}`);
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
return parseText(raw, format);
|
|
183
|
+
} catch {
|
|
184
|
+
throw new Error(`unreadable dest: ${label}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export const OVERLAY_DIR = ".repo/global-settings";
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Read a host payload; a missing file throws `missing payload: <rel>`, bad syntax `unreadable payload: <rel>`. A file of
|
|
192
|
+
* the payload's name under `<consumerRoot>/.repo/global-settings/` replaces the stock payload whole.
|
|
193
|
+
*/
|
|
194
|
+
export function readPayload(workspaceRoot: string, host: ManifestHost, consumerRoot?: string): JsonObject {
|
|
195
|
+
const overlay = consumerRoot === undefined ? null : path.join(consumerRoot, OVERLAY_DIR, path.basename(host.payload));
|
|
196
|
+
const abs = overlay !== null && fs.existsSync(overlay) ? overlay : path.join(workspaceRoot, host.payload);
|
|
197
|
+
let raw: string;
|
|
198
|
+
try {
|
|
199
|
+
raw = fs.readFileSync(abs, "utf8");
|
|
200
|
+
} catch (err) {
|
|
201
|
+
if (isRecord(err) && err.code === "ENOENT") throw new Error(`missing payload: ${host.payload}`);
|
|
202
|
+
throw new Error(`unreadable payload: ${host.payload}`);
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
return parseText(raw, host.format);
|
|
206
|
+
} catch {
|
|
207
|
+
throw new Error(`unreadable payload: ${host.payload}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function stringifyDest(value: JsonObject, format: SettingsFormat): string {
|
|
212
|
+
const body = format === "json" ? JSON.stringify(value, null, 2) : stringifyToml(value);
|
|
213
|
+
return body.endsWith("\n") ? body : body + "\n";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function reportRow({ action, id, dest }: { action: string; id: string; dest: string }): ReportRow {
|
|
217
|
+
return { action, id, dest, line: `${action} ${id} ${dest}` };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Manifest and payloads come from `sourceRoot` (default `workspaceRoot`); a payload under `consumerRoot`'s
|
|
222
|
+
* `.repo/global-settings/` (default `workspaceRoot`) replaces the stock one. Only `home` is written.
|
|
223
|
+
*/
|
|
224
|
+
export function syncGlobals({
|
|
225
|
+
workspaceRoot,
|
|
226
|
+
sourceRoot,
|
|
227
|
+
consumerRoot,
|
|
228
|
+
home,
|
|
229
|
+
apply = false,
|
|
230
|
+
host = "all",
|
|
231
|
+
}: {
|
|
232
|
+
workspaceRoot?: string;
|
|
233
|
+
sourceRoot?: string;
|
|
234
|
+
consumerRoot?: string;
|
|
235
|
+
home?: string;
|
|
236
|
+
apply?: boolean;
|
|
237
|
+
host?: string;
|
|
238
|
+
} = {}): SyncResult {
|
|
239
|
+
const report: ReportRow[] = [];
|
|
240
|
+
const action = apply ? "apply" : "dry-run";
|
|
241
|
+
try {
|
|
242
|
+
const consumer = consumerRoot || workspaceRoot || process.cwd();
|
|
243
|
+
const ws = sourceRoot || workspaceRoot || process.cwd();
|
|
244
|
+
const manifest = loadManifest(ws);
|
|
245
|
+
if (!hostIdsOf(manifest).has(host)) {
|
|
246
|
+
return { ok: false, report, error: `unknown host: ${host}` };
|
|
247
|
+
}
|
|
248
|
+
const skipRe = new RegExp(manifest.skipKeyPattern, "i");
|
|
249
|
+
for (const h of manifest.hosts.filter((x) => host === "all" || x.id === host)) {
|
|
250
|
+
const source = readPayload(ws, h, consumer);
|
|
251
|
+
const destAbs = resolveDest(home, h.dest);
|
|
252
|
+
const merged = deepMerge(readDest(destAbs, h.format, h.dest), source, skipRe);
|
|
253
|
+
if (apply) {
|
|
254
|
+
fs.mkdirSync(path.dirname(destAbs), { recursive: true });
|
|
255
|
+
fs.writeFileSync(destAbs, stringifyDest(merged, h.format));
|
|
256
|
+
}
|
|
257
|
+
report.push(reportRow({ action, id: h.id, dest: h.dest }));
|
|
258
|
+
}
|
|
259
|
+
return { ok: true, report };
|
|
260
|
+
} catch (err) {
|
|
261
|
+
return { ok: false, report, error: errorText(err) };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** `--check`: one line of per-host status, the hint on drift, then any project config error. */
|
|
266
|
+
function runCheck(home: string, host: string): number {
|
|
267
|
+
const cwd = process.cwd();
|
|
268
|
+
let statuses: ReturnType<typeof checkHosts>;
|
|
269
|
+
try {
|
|
270
|
+
statuses = checkHosts({ home, workspaceRoot: cwd, sourceRoot: PACKAGE_ROOT, consumerRoot: cwd, host });
|
|
271
|
+
} catch (err) {
|
|
272
|
+
process.stderr.write(errorText(err) + "\n");
|
|
273
|
+
return 1;
|
|
274
|
+
}
|
|
275
|
+
process.stdout.write(checkLine(statuses) + "\n");
|
|
276
|
+
const hint = checkHint(statuses);
|
|
277
|
+
if (hint) process.stdout.write(hint + "\n");
|
|
278
|
+
const projectError = projectConfigError(cwd, home);
|
|
279
|
+
if (projectError) process.stderr.write(projectError + "\n");
|
|
280
|
+
return hint || projectError || statuses.some((s) => s.state !== "ok") ? 1 : 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function main(argv: string[]): number {
|
|
284
|
+
try {
|
|
285
|
+
const parsed = parseArgv(argv);
|
|
286
|
+
if (!parsed.ok) {
|
|
287
|
+
process.stderr.write(parsed.error + "\n");
|
|
288
|
+
return 1;
|
|
289
|
+
}
|
|
290
|
+
const home = readEnv().HOME;
|
|
291
|
+
if (!home) {
|
|
292
|
+
process.stderr.write("HOME required\n");
|
|
293
|
+
return 1;
|
|
294
|
+
}
|
|
295
|
+
if (parsed.check) return runCheck(home, parsed.host);
|
|
296
|
+
const result = syncGlobals({
|
|
297
|
+
workspaceRoot: process.cwd(),
|
|
298
|
+
sourceRoot: PACKAGE_ROOT,
|
|
299
|
+
consumerRoot: process.cwd(),
|
|
300
|
+
home,
|
|
301
|
+
apply: !parsed.dryRun,
|
|
302
|
+
host: parsed.host,
|
|
303
|
+
});
|
|
304
|
+
for (const row of result.report) {
|
|
305
|
+
process.stdout.write(row.line + "\n");
|
|
306
|
+
}
|
|
307
|
+
if (!result.ok) {
|
|
308
|
+
process.stderr.write(result.error + "\n");
|
|
309
|
+
return 1;
|
|
310
|
+
}
|
|
311
|
+
return 0;
|
|
312
|
+
} catch (err) {
|
|
313
|
+
process.stderr.write(errorText(err) + "\n");
|
|
314
|
+
return 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The steps of `repo turn`, each over the change set: fmt, lint with the baseline gate, typecheck, related tests,
|
|
3
|
+
* then sync and check when their inputs changed. A step reports a summary label, whether it passed, the tool output
|
|
4
|
+
* to print when it failed, and the files it rewrote, which `--staged` restages.
|
|
5
|
+
*/
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { CONFIG_FILE, type LintSkip } from "../config.ts";
|
|
10
|
+
import { withoutSkipped } from "../check-magic.ts";
|
|
11
|
+
import { scriptDiagnostics } from "../script-diagnostics.ts";
|
|
12
|
+
import { readManifest } from "../sync/manifest.ts";
|
|
13
|
+
import type { Change } from "./change-set.ts";
|
|
14
|
+
import { captureBun, captureTool, type Captured } from "./lint.ts";
|
|
15
|
+
import {
|
|
16
|
+
BASELINE_PATH,
|
|
17
|
+
countsOf,
|
|
18
|
+
type Counts as Baseline,
|
|
19
|
+
excessText,
|
|
20
|
+
gate,
|
|
21
|
+
lintReport,
|
|
22
|
+
readBaseline,
|
|
23
|
+
writeBaseline,
|
|
24
|
+
type Diagnostic,
|
|
25
|
+
type Excess,
|
|
26
|
+
type Result,
|
|
27
|
+
} from "./lint-baseline.ts";
|
|
28
|
+
|
|
29
|
+
/** typescript, a dependency, and vitest, an optional peer: null when the package at `cwd` cannot resolve it. */
|
|
30
|
+
export type Bins = {
|
|
31
|
+
tsc: (args: string[], cwd: string) => Captured;
|
|
32
|
+
vitest: (args: string[], cwd: string) => Captured | null;
|
|
33
|
+
};
|
|
34
|
+
/** A change set to gate, with the root's packages, the bins it runs and the config's `lint.skip` entries. */
|
|
35
|
+
export type Turn = {
|
|
36
|
+
root: string;
|
|
37
|
+
changes: readonly Change[];
|
|
38
|
+
packages: readonly string[];
|
|
39
|
+
bins: Bins;
|
|
40
|
+
skips: readonly LintSkip[];
|
|
41
|
+
};
|
|
42
|
+
export type StepResult = { label: string; ok: boolean; output: string[]; touched: string[] };
|
|
43
|
+
/** A step; null when its inputs did not change. */
|
|
44
|
+
export type Step = (turn: Turn) => StepResult | null;
|
|
45
|
+
|
|
46
|
+
const LINTABLE = /\.(?:[cm]?[jt]sx?|vue|astro|svelte)$/;
|
|
47
|
+
const LOCKFILES = new Set(["bun.lock", "bun.lockb", "package-lock.json", "yarn.lock", "pnpm-lock.yaml"]);
|
|
48
|
+
const BIN = path.join(import.meta.dirname, "../../bin/repo.ts");
|
|
49
|
+
|
|
50
|
+
const present = (turn: Turn) => turn.changes.filter((change) => !change.deleted).map((change) => change.rel);
|
|
51
|
+
const text = (root: string, rel: string) => fs.readFileSync(path.join(root, rel), { encoding: "utf8", flag: "r" });
|
|
52
|
+
const plural = (n: number, word: string) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
53
|
+
export const passed = (label: string, touched: string[] = []): StepResult => ({ label, ok: true, output: [], touched });
|
|
54
|
+
export const failed = (label: string, output: string[], touched: string[] = []): StepResult => ({
|
|
55
|
+
label,
|
|
56
|
+
ok: false,
|
|
57
|
+
output,
|
|
58
|
+
touched,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/** The files of `files` whose text a tool rewrote between this call and the returned one. */
|
|
62
|
+
function watch(root: string, files: readonly string[]): () => string[] {
|
|
63
|
+
const before = new Map(files.map((rel) => [rel, text(root, rel)]));
|
|
64
|
+
return () => files.filter((rel) => text(root, rel) !== before.get(rel));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const fmtStep: Step = (turn) => {
|
|
68
|
+
const files = present(turn);
|
|
69
|
+
if (files.length === 0) return passed("fmt ok");
|
|
70
|
+
const rewritten = watch(turn.root, files);
|
|
71
|
+
const run = captureTool("oxfmt", ["--no-error-on-unmatched-pattern", ...files], turn.root);
|
|
72
|
+
const touched = rewritten();
|
|
73
|
+
if (run.code !== 0) return failed("fmt failed", [`${run.stdout}${run.stderr}`], touched);
|
|
74
|
+
return passed(touched.length === 0 ? "fmt ok" : `fmt ${touched.length} fixed`, touched);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** The lint lines of each excess's rule in its file, then what to do about it. */
|
|
78
|
+
function overOutput(over: readonly Excess[], diagnostics: readonly Diagnostic[]): string[] {
|
|
79
|
+
const lines = diagnostics
|
|
80
|
+
.filter((d) => over.some((e) => e.file === d.file && e.rule === d.rule))
|
|
81
|
+
.map((d) => `${d.line}\n`);
|
|
82
|
+
const fixes = over.map((e) => {
|
|
83
|
+
const added = e.has - e.allows;
|
|
84
|
+
return `repo turn: ${excessText(e)}; fix the ${added === 1 ? "new one" : `${added} new ones`}\n`;
|
|
85
|
+
});
|
|
86
|
+
return [...lines, ...fixes];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** `lint --fix -f json` on `files`, then oxfmt on any file the fix rewrote. */
|
|
90
|
+
function lintFix(root: string, files: readonly string[]): { report: Result<Diagnostic[]>; fixed: string[] } {
|
|
91
|
+
if (files.length === 0) return { report: { ok: true, value: [] }, fixed: [] };
|
|
92
|
+
const rewritten = watch(root, files);
|
|
93
|
+
const report = lintReport(root, ["--fix", ...files]);
|
|
94
|
+
const fixed = rewritten();
|
|
95
|
+
if (fixed.length > 0) captureTool("oxfmt", fixed, root);
|
|
96
|
+
return { report, fixed };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* oxlint's `report` with the script hits of `files` after it, those a `lint.skip` entry turns off left out. The hits
|
|
101
|
+
* include the guards of sources a changed test imports, which may lie outside `files`.
|
|
102
|
+
*/
|
|
103
|
+
function withScripts(turn: Turn, files: readonly string[], report: Result<Diagnostic[]>): Result<Diagnostic[]> {
|
|
104
|
+
const scripts = report.ok ? scriptDiagnostics(turn.root, files) : report;
|
|
105
|
+
if (!report.ok || !scripts.ok) return scripts;
|
|
106
|
+
const reported = [...new Set([...files, ...scripts.value.map((d) => d.file)])];
|
|
107
|
+
return { ok: true, value: [...report.value, ...withoutSkipped(scripts.value, turn.skips, reported)] };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The excesses of the files outside the change set a changed test re-checked: only their script hits are known, so
|
|
112
|
+
* they can fail the turn but never lower the baseline.
|
|
113
|
+
*/
|
|
114
|
+
function outsideOver(baseline: Baseline, diagnostics: readonly Diagnostic[], linted: readonly string[]): Excess[] {
|
|
115
|
+
const outside = diagnostics.filter((d) => !linted.includes(d.file));
|
|
116
|
+
const files = [...new Set(outside.map((d) => d.file))];
|
|
117
|
+
return gate(baseline, countsOf(outside), files).over;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** `lint --fix` on the lintable files, then the baseline gate over them and the deleted ones. */
|
|
121
|
+
export const lintStep: Step = (turn) => {
|
|
122
|
+
const files = present(turn).filter((rel) => LINTABLE.test(rel));
|
|
123
|
+
const { report: oxlint, fixed } = lintFix(turn.root, files);
|
|
124
|
+
const report = withScripts(turn, files, oxlint);
|
|
125
|
+
const baseline = readBaseline(turn.root);
|
|
126
|
+
if (!report.ok || !baseline.ok) {
|
|
127
|
+
const errors = [report, baseline].flatMap((read) => (read.ok ? [] : read.errors));
|
|
128
|
+
return failed(
|
|
129
|
+
"lint failed",
|
|
130
|
+
errors.map((line) => `repo turn: ${line}\n`),
|
|
131
|
+
fixed,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
const linted = turn.changes
|
|
135
|
+
.filter((change) => change.deleted || LINTABLE.test(change.rel))
|
|
136
|
+
.map((change) => change.rel);
|
|
137
|
+
const result = gate(baseline.value, countsOf(report.value), linted);
|
|
138
|
+
if (result.lowered.length > 0) writeBaseline(turn.root, result.next);
|
|
139
|
+
const touched = result.lowered.length > 0 ? [...fixed, BASELINE_PATH] : fixed;
|
|
140
|
+
const over = [...result.over, ...outsideOver(baseline.value, report.value, linted)];
|
|
141
|
+
if (over.length > 0) {
|
|
142
|
+
const count = over.reduce((sum, e) => sum + e.has - e.allows, 0);
|
|
143
|
+
return failed(`lint ${count} over baseline`, overOutput(over, report.value), touched);
|
|
144
|
+
}
|
|
145
|
+
const lowered = result.lowered.map((l) => `${l.file} ${l.rule} ${l.from} → ${l.to}`).join(", ");
|
|
146
|
+
return passed(lowered === "" ? "lint ok" : `lint ok, baseline lowered (${lowered})`, touched);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const owners = (turn: Turn): string[] => [...new Set(turn.changes.map((change) => change.owner))].sort();
|
|
150
|
+
export const dirName = (owner: string): string => owner || ".";
|
|
151
|
+
|
|
152
|
+
type Counts = { passed: number; failed: number; total: number };
|
|
153
|
+
|
|
154
|
+
/** vitest's `Tests 1 failed | 3 passed (4)` line, read from its default reporter. */
|
|
155
|
+
export function countsOfRun(run: Captured): Counts {
|
|
156
|
+
const line = /^\s*Tests\s+(.*)$/m.exec(run.stdout)?.[1] ?? "";
|
|
157
|
+
const number = (pattern: RegExp) => Number(pattern.exec(line)?.[1] ?? 0);
|
|
158
|
+
return { passed: number(/(\d+) passed/), failed: number(/(\d+) failed/), total: number(/\((\d+)\)/) };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function testsLabel(counts: Counts, skipped: readonly string[], broke: boolean): string {
|
|
162
|
+
const none = skipped.length > 0 ? ` (no vitest in ${skipped.join(", ")})` : "";
|
|
163
|
+
if (counts.failed > 0) return `${plural(counts.total, "related test")}, ${counts.failed} failed${none}`;
|
|
164
|
+
if (broke) return `related tests failed${none}`;
|
|
165
|
+
if (counts.passed > 0) return `${plural(counts.passed, "related test")} passed${none}`;
|
|
166
|
+
return none === "" || counts.total > 0 ? `0 related tests${none}` : `related tests skipped${none}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** One `vitest related --run` per owning package on its changed code files; null where vitest is not installed. */
|
|
170
|
+
function relatedRuns(turn: Turn): { owner: string; run: Captured | null }[] {
|
|
171
|
+
return owners(turn).flatMap((owner) => {
|
|
172
|
+
const own = turn.changes
|
|
173
|
+
.filter((change) => !change.deleted && change.owner === owner && LINTABLE.test(change.rel))
|
|
174
|
+
.map((change) => path.posix.relative(owner, change.rel));
|
|
175
|
+
if (own.length === 0) return [];
|
|
176
|
+
const args = ["related", "--run", "--passWithNoTests", ...own];
|
|
177
|
+
return [{ owner, run: turn.bins.vitest(args, path.join(turn.root, owner)) }];
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export const testsStep: Step = (turn) => {
|
|
182
|
+
const runs = relatedRuns(turn);
|
|
183
|
+
const skipped = runs.filter(({ run }) => run === null).map(({ owner }) => dirName(owner));
|
|
184
|
+
const ran = runs.flatMap(({ run }) => (run === null ? [] : [run]));
|
|
185
|
+
const counts = ran.map(countsOfRun).reduce(
|
|
186
|
+
(sum, next) => ({
|
|
187
|
+
passed: sum.passed + next.passed,
|
|
188
|
+
failed: sum.failed + next.failed,
|
|
189
|
+
total: sum.total + next.total,
|
|
190
|
+
}),
|
|
191
|
+
{ passed: 0, failed: 0, total: 0 },
|
|
192
|
+
);
|
|
193
|
+
const bad = ran.filter((run) => run.code !== 0);
|
|
194
|
+
const label = testsLabel(counts, skipped, bad.length > 0);
|
|
195
|
+
return bad.length === 0
|
|
196
|
+
? passed(label)
|
|
197
|
+
: failed(
|
|
198
|
+
label,
|
|
199
|
+
bad.map((run) => `${run.stdout}${run.stderr}`),
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const isManifest = (turn: Turn, rel: string) =>
|
|
204
|
+
rel === "package.json" || turn.packages.some((dir) => rel === `${dir}/package.json`);
|
|
205
|
+
|
|
206
|
+
/** Sync reads `.repo/` (but not the baseline, which turn itself writes), `references/`, the manifests and the config. */
|
|
207
|
+
function syncReads(turn: Turn, rel: string): boolean {
|
|
208
|
+
const repoDir = rel.startsWith(".repo/") && rel !== BASELINE_PATH;
|
|
209
|
+
return repoDir || rel.startsWith("references/") || isManifest(turn, rel) || rel === CONFIG_FILE;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Check holds the manifests, lockfiles, `bunfig.toml`, the config and every tracked file sync writes. */
|
|
213
|
+
function checkReads(turn: Turn, rel: string, outputs: ReadonlySet<string>): boolean {
|
|
214
|
+
const named = LOCKFILES.has(path.posix.basename(rel)) || rel === "bunfig.toml" || rel === CONFIG_FILE;
|
|
215
|
+
return named || isManifest(turn, rel) || outputs.has(rel);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** What `git add -A` would stage now, each with its text (null when deleted). */
|
|
219
|
+
function dirty(root: string): Map<string, string | null> {
|
|
220
|
+
const git = (args: string[]) =>
|
|
221
|
+
spawnSync("git", [...args, "-z"], { cwd: root, encoding: "utf8" })
|
|
222
|
+
.stdout.split("\0")
|
|
223
|
+
.filter(Boolean);
|
|
224
|
+
const rels = [...git(["diff", "--name-only"]), ...git(["ls-files", "--others", "--exclude-standard"])];
|
|
225
|
+
return new Map(rels.map((rel) => [rel, fs.existsSync(path.join(root, rel)) ? text(root, rel) : null]));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** `repo sync` in the root, and the files it changed that git would stage. */
|
|
229
|
+
function sync(root: string): { run: Captured; touched: string[] } {
|
|
230
|
+
const before = dirty(root);
|
|
231
|
+
const run = captureBun([BIN, "sync"], root);
|
|
232
|
+
const after = dirty(root);
|
|
233
|
+
return { run, touched: [...after].filter(([rel, now]) => before.get(rel) !== now).map(([rel]) => rel) };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** `repo sync` when a file it reads changed; the files it rewrote are restaged under `--staged`. */
|
|
237
|
+
export const syncStep: Step = (turn) => {
|
|
238
|
+
if (!turn.changes.some((change) => syncReads(turn, change.rel))) return null;
|
|
239
|
+
const { run, touched } = sync(turn.root);
|
|
240
|
+
return run.code === 0 ? passed("sync ok", touched) : failed("sync failed", [`${run.stdout}${run.stderr}`], touched);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
/** `repo check` when a file it holds changed. */
|
|
244
|
+
export const checkStep: Step = (turn) => {
|
|
245
|
+
const outputs = new Set(readManifest(turn.root).paths);
|
|
246
|
+
if (!turn.changes.some((change) => checkReads(turn, change.rel, outputs))) return null;
|
|
247
|
+
const run = captureBun([BIN, "check"], turn.root);
|
|
248
|
+
return run.code === 0 ? passed("check ok") : failed("check failed", [`${run.stdout}${run.stderr}`]);
|
|
249
|
+
};
|