From 13a7d6d4c02e2b8269c7e3958158726605faebfc Mon Sep 17 00:00:00 2001 From: jdalton Date: Fri, 17 Jul 2026 22:43:28 -0400 Subject: [PATCH 1/8] fix(fleet): add wheelhouse marker --- .config/socket-wheelhouse.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .config/socket-wheelhouse.json diff --git a/.config/socket-wheelhouse.json b/.config/socket-wheelhouse.json new file mode 100644 index 0000000..224fb57 --- /dev/null +++ b/.config/socket-wheelhouse.json @@ -0,0 +1,12 @@ +{ + "$schema": "./socket-wheelhouse-schema.json", + "schemaVersion": 1, + "repoName": "node-smol", + "repo": { + "type": "solo" + }, + "build": { + "from": "github-release", + "type": "binary" + } +} From 7d7b6b41313badcc233a93ccf53a15795a03ea3b Mon Sep 17 00:00:00 2001 From: jdalton Date: Sat, 18 Jul 2026 10:48:16 -0400 Subject: [PATCH 2/8] chore(wheelhouse): cascade template@6d839a174 Auto-applied by socket-wheelhouse sync-scaffolding into node-smol. 1977 file(s) touched: - .cargo/config.toml - .claude/agents/fleet/code-reviewer.md - .claude/agents/fleet/fix.md - .claude/agents/fleet/refactor-cleaner.md - .claude/agents/fleet/security-reviewer.md - .claude/commands/fleet/audit-gha-settings.md - .claude/commands/fleet/codifying-disciplines.md - .claude/commands/fleet/green-ci-local.md - .claude/commands/fleet/green-ci.md - .claude/commands/fleet/looping-quality.md - .claude/commands/fleet/researching-recency.md - .claude/commands/fleet/scanning-quality.md - .claude/commands/fleet/security-scan.md - .claude/commands/fleet/setup-security-tools.md - .claude/commands/fleet/squash-history.md - .claude/commands/fleet/update-coverage.md - .claude/commands/fleet/update-hooks-dry.md - .claude/commands/fleet/update-pricing.md - .claude/commands/fleet/update-security.md - .claude/hooks/fleet/_dispatch/.clangd ... and 1957 more --- .cargo/config.toml | 47 + .claude/agents/fleet/code-reviewer.md | 92 + .claude/agents/fleet/fix.md | 70 + .claude/agents/fleet/refactor-cleaner.md | 62 + .claude/agents/fleet/security-reviewer.md | 36 + .claude/commands/fleet/audit-gha-settings.md | 56 + .../commands/fleet/codifying-disciplines.md | 12 + .claude/commands/fleet/green-ci-local.md | 32 + .claude/commands/fleet/green-ci.md | 63 + .claude/commands/fleet/looping-quality.md | 8 + .claude/commands/fleet/researching-recency.md | 10 + .claude/commands/fleet/scanning-quality.md | 9 + .claude/commands/fleet/security-scan.md | 7 + .../commands/fleet/setup-security-tools.md | 46 + .claude/commands/fleet/squash-history.md | 7 + .claude/commands/fleet/update-coverage.md | 9 + .claude/commands/fleet/update-hooks-dry.md | 11 + .claude/commands/fleet/update-pricing.md | 9 + .claude/commands/fleet/update-security.md | 16 + .claude/hooks/fleet/_dispatch/.clangd | 15 + .claude/hooks/fleet/_dispatch/.gitignore | 15 + .../hooks/fleet/_dispatch/SNAPSHOT-NOTES.md | 400 ++ .../hooks/fleet/_dispatch/dispatch-entry.mts | 17 + .../fleet/_dispatch/dispatch-launcher-win.c | 181 + .../hooks/fleet/_dispatch/dispatch-launcher.c | 130 + .../_dispatch/dispatch-snapshot-entry.mts | 127 + .../_dispatch/dispatch-table-excluded.mts | 26 + .../_dispatch/dispatch-table-snapshot.mts | 466 +++ .../hooks/fleet/_dispatch/dispatch-table.mts | 467 +++ .claude/hooks/fleet/_dispatch/dispatch.mts | 303 ++ .../hooks/fleet/_dispatch/excluded-entry.mts | 14 + .claude/hooks/fleet/_dispatch/index.cjs | 78 + .../fleet/_dispatch/snapshot-cache-path.cjs | 93 + .../hooks/fleet/_dispatch/snapshot-loader.cjs | 87 + .claude/hooks/fleet/_shared/README.md | 43 + .claude/hooks/fleet/_shared/acorn/README.md | 65 + .../hooks/fleet/_shared/acorn/acorn-sync.mts | 152 + .claude/hooks/fleet/_shared/acorn/index.mts | 1146 ++++++ .../fleet/_shared/active-edits-ledger.mts | 394 ++ .../hooks/fleet/_shared/ai-attribution.mts | 51 + .../hooks/fleet/_shared/brew-supply-chain.mts | 182 + .claude/hooks/fleet/_shared/cdn-allowlist.mts | 130 + .../hooks/fleet/_shared/commit-command.mts | 104 + .../hooks/fleet/_shared/dated-citation.mts | 115 + .../fleet/_shared/dispatch-manifest.json | 611 ++++ .claude/hooks/fleet/_shared/dispatch.mts | 120 + .../fleet/_shared/doc-location-guard.mts | 228 ++ .claude/hooks/fleet/_shared/edit-content.mts | 67 + .claude/hooks/fleet/_shared/entrypoint.mts | 56 + .../hooks/fleet/_shared/ephemeral-path.mts | 40 + .../fleet/_shared/error-message-quality.mts | 111 + .claude/hooks/fleet/_shared/es-polyfills.mts | 616 ++++ .../hooks/fleet/_shared/evasion-normalize.mts | 60 + .claude/hooks/fleet/_shared/fleet-context.mts | 170 + .claude/hooks/fleet/_shared/fleet-env.mts | 76 + .claude/hooks/fleet/_shared/fleet-markers.mts | 124 + .claude/hooks/fleet/_shared/fleet-repo.mts | 77 + .claude/hooks/fleet/_shared/fleet-repos.mts | 160 + .claude/hooks/fleet/_shared/fleet-roster.mts | 135 + .../hooks/fleet/_shared/foreign-linters.mts | 288 ++ .claude/hooks/fleet/_shared/foreign-paths.mts | 450 +++ .claude/hooks/fleet/_shared/gh-pr-command.mts | 45 + .claude/hooks/fleet/_shared/git-branch.mts | 71 + .claude/hooks/fleet/_shared/git-cwd.mts | 77 + .claude/hooks/fleet/_shared/git-identity.mts | 80 + .claude/hooks/fleet/_shared/git-state.mts | 48 + .claude/hooks/fleet/_shared/guard.mts | 395 ++ .../hooks/fleet/_shared/honesty-framing.mts | 38 + .claude/hooks/fleet/_shared/known-names.mts | 237 ++ .claude/hooks/fleet/_shared/landable.mts | 61 + .../hooks/fleet/_shared/learning-ledger.mts | 324 ++ .claude/hooks/fleet/_shared/markers.mts | 70 + .claude/hooks/fleet/_shared/named-blocks.mts | 272 ++ .claude/hooks/fleet/_shared/npmrc-trust.mts | 185 + .../_shared/package-manager-auto-update.mts | 416 +++ .claude/hooks/fleet/_shared/parked-paths.mts | 157 + .claude/hooks/fleet/_shared/paths.mts | 18 + .claude/hooks/fleet/_shared/payload.mts | 292 ++ .claude/hooks/fleet/_shared/private-paths.mts | 244 ++ .../hooks/fleet/_shared/public-surfaces.mts | 88 + .claude/hooks/fleet/_shared/push-refspec.mts | 254 ++ .../hooks/fleet/_shared/repo-test-home.mts | 22 + .claude/hooks/fleet/_shared/shell-command.mts | 429 +++ .../fleet/_shared/sparkle-auto-update.mts | 175 + .claude/hooks/fleet/_shared/spawn-timeout.mts | 22 + .../hooks/fleet/_shared/squash-sentinel.mts | 146 + .claude/hooks/fleet/_shared/stop-nudge.mts | 332 ++ .claude/hooks/fleet/_shared/test/fixtures.mts | 14 + .../hooks/fleet/_shared/token-patterns.mts | 305 ++ .claude/hooks/fleet/_shared/transcript.mts | 865 +++++ .claude/hooks/fleet/_shared/trust-gates.mts | 165 + .../hooks/fleet/_shared/unbacked-claims.mts | 175 + .claude/hooks/fleet/_shared/uv-config.mts | 102 + .../hooks/fleet/_shared/wheelhouse-root.mts | 87 + .../actionlint-on-workflow-edit/README.md | 30 + .../actionlint-on-workflow-edit/index.mts | 145 + .../actionlint-on-workflow-edit/package.json | 15 + .../actionlint-on-workflow-edit/tsconfig.json | 16 + .../hooks/fleet/active-edits-ledger/index.mts | 88 + .../fleet/adversarial-review-nudge/README.md | 59 + .../fleet/adversarial-review-nudge/index.mts | 145 + .../adversarial-review-nudge/package.json | 12 + .../adversarial-review-nudge/tsconfig.json | 16 + .../agents-skills-mirror-nudge/README.md | 33 + .../agents-skills-mirror-nudge/index.mts | 130 + .../agents-skills-mirror-nudge/package.json | 15 + .../agents-skills-mirror-nudge/tsconfig.json | 16 + .../fleet/ai-config-drift-nudge/README.md | 36 + .../fleet/ai-config-drift-nudge/index.mts | 102 + .../fleet/ai-config-drift-nudge/package.json | 18 + .../fleet/ai-config-drift-nudge/tsconfig.json | 16 + .../fleet/ai-config-poisoning-guard/README.md | 48 + .../fleet/ai-config-poisoning-guard/index.mts | 222 ++ .../ai-config-poisoning-guard/package.json | 18 + .../ai-config-poisoning-guard/tsconfig.json | 16 + .../hooks/fleet/alpha-sort-nudge/README.md | 46 + .../hooks/fleet/alpha-sort-nudge/index.mts | 228 ++ .../hooks/fleet/alpha-sort-nudge/package.json | 15 + .../fleet/alpha-sort-nudge/tsconfig.json | 16 + .../fleet/answer-questions-nudge/README.md | 24 + .../fleet/answer-questions-nudge/index.mts | 170 + .../fleet/answer-questions-nudge/package.json | 15 + .../answer-questions-nudge/tsconfig.json | 16 + .../answer-status-requests-nudge/README.md | 26 + .../answer-status-requests-nudge/index.mts | 195 + .../answer-status-requests-nudge/package.json | 15 + .../tsconfig.json | 16 + .../hooks/fleet/anti-prose-guard/README.md | 40 + .../hooks/fleet/anti-prose-guard/index.mts | 115 + .../hooks/fleet/anti-prose-guard/package.json | 15 + .../hooks/fleet/anti-prose-guard/patterns.mts | 118 + .../fleet/anti-prose-guard/tsconfig.json | 16 + .../fleet/ask-suppression-nudge/README.md | 35 + .../fleet/ask-suppression-nudge/index.mts | 173 + .../fleet/ask-suppression-nudge/package.json | 15 + .../fleet/ask-suppression-nudge/tsconfig.json | 16 + .../fleet/attribution-rewrite-nudge/index.mts | 63 + .../hooks/fleet/auth-rotation-nudge/README.md | 141 + .../hooks/fleet/auth-rotation-nudge/index.mts | 485 +++ .../fleet/auth-rotation-nudge/package.json | 18 + .../fleet/auth-rotation-nudge/services.mts | 142 + .../fleet/auth-rotation-nudge/tsconfig.json | 16 + .../hooks/fleet/auto-land-on-stop/hold.mts | 112 + .../hooks/fleet/auto-land-on-stop/index.mts | 217 ++ .claude/hooks/fleet/avoid-cd-nudge/index.mts | 107 + .../fleet/brew-supply-chain-guard/README.md | 55 + .../fleet/brew-supply-chain-guard/index.mts | 85 + .../brew-supply-chain-guard/package.json | 15 + .../brew-supply-chain-guard/tsconfig.json | 16 + .../fleet/broken-hook-detector/README.md | 33 + .../fleet/broken-hook-detector/index.mts | 509 +++ .../fleet/broken-hook-detector/package.json | 18 + .../fleet/broken-hook-detector/tsconfig.json | 16 + .../bump-defers-to-release-guard/README.md | 24 + .../bump-defers-to-release-guard/index.mts | 178 + .../bump-defers-to-release-guard/package.json | 19 + .../hooks/fleet/bundle-flags-guard/README.md | 84 + .../hooks/fleet/bundle-flags-guard/index.mts | 282 ++ .../fleet/bundle-flags-guard/package.json | 15 + .../fleet/bundle-flags-guard/tsconfig.json | 16 + .../fleet/bundle-stale-reminder/README.md | 31 + .../fleet/bundle-stale-reminder/index.mts | 246 ++ .../fleet/bundle-stale-reminder/package.json | 15 + .../fleet/c8-ignore-reason-guard/README.md | 23 + .../fleet/c8-ignore-reason-guard/index.mts | 167 + .../fleet/c8-ignore-reason-guard/package.json | 15 + .../c8-ignore-reason-guard/tsconfig.json | 16 + .../cascade-first-triage-nudge/README.md | 34 + .../cascade-first-triage-nudge/index.mts | 100 + .../cascade-first-triage-nudge/package.json | 15 + .../cascade-first-triage-nudge/tsconfig.json | 16 + .../hooks/fleet/catch-message-guard/README.md | 85 + .../hooks/fleet/catch-message-guard/index.mts | 390 ++ .../fleet/catch-message-guard/package.json | 15 + .../fleet/catch-message-guard/tsconfig.json | 16 + .../hooks/fleet/cdn-allowlist-guard/README.md | 35 + .../hooks/fleet/cdn-allowlist-guard/index.mts | 57 + .../fleet/cdn-allowlist-guard/package.json | 16 + .../fleet/cdn-allowlist-guard/tsconfig.json | 16 + .../changelog-entry-shape-nudge/README.md | 34 + .../changelog-entry-shape-nudge/index.mts | 86 + .../changelog-entry-shape-nudge/package.json | 16 + .../changelog-entry-shape-nudge/tsconfig.json | 16 + .../fleet/changelog-no-empty-guard/README.md | 33 + .../fleet/changelog-no-empty-guard/index.mts | 217 ++ .../changelog-no-empty-guard/package.json | 15 + .../changelog-no-empty-guard/tsconfig.json | 16 + .claude/hooks/fleet/check-new-deps/README.md | 177 + .claude/hooks/fleet/check-new-deps/audit.mts | 454 +++ .claude/hooks/fleet/check-new-deps/index.mts | 805 ++++ .../hooks/fleet/check-new-deps/package.json | 20 + .../hooks/fleet/check-new-deps/tsconfig.json | 16 + .claude/hooks/fleet/check-new-deps/types.mts | 80 + .../README.md | 47 + .../index.mts | 132 + .../package.json | 15 + .../tsconfig.json | 16 + .../fleet/claude-lockdown-guard/README.md | 30 + .../fleet/claude-lockdown-guard/index.mts | 189 + .../fleet/claude-lockdown-guard/package.json | 18 + .../fleet/claude-lockdown-guard/tsconfig.json | 16 + .../claude-md-defer-detail-nudge/README.md | 52 + .../claude-md-defer-detail-nudge/index.mts | 314 ++ .../claude-md-defer-detail-nudge/package.json | 15 + .../tsconfig.json | 16 + .../fleet/claude-md-rule-add-guard/README.md | 42 + .../fleet/claude-md-rule-add-guard/index.mts | 142 + .../claude-md-rule-add-guard/package.json | 15 + .../claude-md-rule-add-guard/tsconfig.json | 16 + .../claude-md-section-size-guard/README.md | 46 + .../claude-md-section-size-guard/index.mts | 393 ++ .../claude-md-section-size-guard/package.json | 18 + .../tsconfig.json | 16 + .../fleet/claude-md-size-guard/README.md | 33 + .../fleet/claude-md-size-guard/index.mts | 185 + .../fleet/claude-md-size-guard/package.json | 15 + .../fleet/claude-md-size-guard/tsconfig.json | 16 + .../fleet/claude-segmentation-guard/README.md | 48 + .../fleet/claude-segmentation-guard/index.mts | 125 + .../claude-segmentation-guard/package.json | 16 + .../claude-segmentation-guard/tsconfig.json | 16 + .../fleet/clipboard-snippet-nudge/README.md | 18 + .../fleet/clipboard-snippet-nudge/index.mts | 64 + .../fleet/clone-reviewed-repo-nudge/README.md | 45 + .../clone-reviewed-repo-nudge/detect.mts | 140 + .../fleet/clone-reviewed-repo-nudge/index.mts | 96 + .../clone-reviewed-repo-nudge/package.json | 16 + .../clone-reviewed-repo-nudge/tsconfig.json | 16 + .../fleet/codex-no-write-guard/README.md | 38 + .../fleet/codex-no-write-guard/index.mts | 200 + .../fleet/codex-no-write-guard/package.json | 18 + .../fleet/codex-no-write-guard/tsconfig.json | 16 + .../codex-session-budget-guard/README.md | 15 + .../codex-session-budget-guard/index.mts | 172 + .../codex-session-budget-guard/package.json | 15 + .../codex-session-budget-guard/tsconfig.json | 16 + .../hooks/fleet/commit-author-guard/README.md | 49 + .../hooks/fleet/commit-author-guard/index.mts | 190 + .../fleet/commit-author-guard/package.json | 15 + .../fleet/commit-author-guard/tsconfig.json | 16 + .../fleet/commit-cadence-nudge/README.md | 26 + .../fleet/commit-cadence-nudge/index.mts | 101 + .../fleet/commit-cadence-nudge/package.json | 15 + .../fleet/commit-cadence-nudge/tsconfig.json | 16 + .../commit-message-format-guard/README.md | 54 + .../commit-message-format-guard/index.mts | 185 + .../commit-message-format-guard/package.json | 15 + .../commit-message-format-guard/tsconfig.json | 16 + .claude/hooks/fleet/commit-pr-nudge/README.md | 19 + .claude/hooks/fleet/commit-pr-nudge/index.mts | 67 + .../hooks/fleet/commit-pr-nudge/package.json | 15 + .../hooks/fleet/commit-pr-nudge/tsconfig.json | 16 + .../hooks/fleet/commit-size-nudge/README.md | 36 + .../hooks/fleet/commit-size-nudge/index.mts | 139 + .../fleet/commit-size-nudge/package.json | 15 + .../fleet/commit-size-nudge/tsconfig.json | 16 + .../fleet/compound-lessons-nudge/README.md | 73 + .../fleet/compound-lessons-nudge/index.mts | 435 +++ .../fleet/compound-lessons-nudge/package.json | 15 + .../compound-lessons-nudge/tsconfig.json | 16 + .../hooks/fleet/consumer-grep-nudge/README.md | 45 + .../hooks/fleet/consumer-grep-nudge/index.mts | 191 + .../fleet/consumer-grep-nudge/package.json | 15 + .../fleet/consumer-grep-nudge/tsconfig.json | 16 + .../hooks/fleet/convo-prose-nudge/index.mts | 252 ++ .../fleet/convo-prose-nudge/package.json | 16 + .../fleet/convo-prose-nudge/tsconfig.json | 16 + .../fleet/copy-on-select-hint-nudge/README.md | 37 + .../fleet/copy-on-select-hint-nudge/index.mts | 108 + .../hooks/fleet/cross-repo-guard/README.md | 87 + .../hooks/fleet/cross-repo-guard/index.mts | 169 + .../hooks/fleet/cross-repo-guard/package.json | 18 + .../fleet/cross-repo-guard/tsconfig.json | 16 + .../fleet/dated-citation-guard/README.md | 39 + .../fleet/dated-citation-guard/index.mts | 108 + .../fleet/dated-citation-guard/package.json | 15 + .../fleet/dated-citation-guard/tsconfig.json | 16 + .../fleet/default-branch-guard/README.md | 34 + .../fleet/default-branch-guard/index.mts | 185 + .../fleet/default-branch-guard/package.json | 15 + .../fleet/default-branch-guard/tsconfig.json | 16 + .../fleet/defer-to-script-nudge/README.md | 11 + .../fleet/defer-to-script-nudge/index.mts | 83 + .../fleet/dep-derived-source-nudge/README.md | 53 + .../fleet/dep-derived-source-nudge/index.mts | 89 + .../dep-derived-source-nudge/package.json | 15 + .../dep-derived-source-nudge/tsconfig.json | 16 + .../fleet/dirty-lockfile-nudge/README.md | 33 + .../fleet/dirty-lockfile-nudge/index.mts | 241 ++ .../fleet/dirty-lockfile-nudge/package.json | 15 + .../fleet/dirty-lockfile-nudge/tsconfig.json | 16 + .../fleet/dirty-worktree-stop-guard/README.md | 28 + .../fleet/dirty-worktree-stop-guard/index.mts | 628 ++++ .../dirty-worktree-stop-guard/package.json | 15 + .../dirty-worktree-stop-guard/tsconfig.json | 16 + .../fleet/dogfood-cascade-nudge/README.md | 34 + .../fleet/dogfood-cascade-nudge/index.mts | 194 + .../fleet/dogfood-cascade-nudge/package.json | 15 + .../fleet/dogfood-cascade-nudge/tsconfig.json | 16 + .../hooks/fleet/dont-blame-nudge/README.md | 30 + .../hooks/fleet/dont-blame-nudge/index.mts | 102 + .../hooks/fleet/dont-blame-nudge/package.json | 15 + .../fleet/dont-blame-nudge/tsconfig.json | 16 + .../fleet/dont-stop-mid-queue-nudge/README.md | 43 + .../fleet/dont-stop-mid-queue-nudge/index.mts | 261 ++ .../dont-stop-mid-queue-nudge/package.json | 15 + .../dont-stop-mid-queue-nudge/tsconfig.json | 16 + .../hooks/fleet/drift-check-nudge/README.md | 21 + .../hooks/fleet/drift-check-nudge/index.mts | 97 + .../fleet/drift-check-nudge/package.json | 15 + .../fleet/drift-check-nudge/tsconfig.json | 16 + .../fleet/enqueue-dont-pivot-nudge/README.md | 38 + .../fleet/enqueue-dont-pivot-nudge/index.mts | 201 + .../enqueue-dont-pivot-nudge/package.json | 15 + .../enqueue-dont-pivot-nudge/tsconfig.json | 16 + .../fleet/enterprise-push-nudge/README.md | 50 + .../fleet/enterprise-push-nudge/index.mts | 231 ++ .../fleet/enterprise-push-nudge/package.json | 18 + .../fleet/enterprise-push-nudge/tsconfig.json | 16 + .../error-message-quality-nudge/README.md | 49 + .../error-message-quality-nudge/index.mts | 132 + .../error-message-quality-nudge/package.json | 18 + .../error-message-quality-nudge/tsconfig.json | 16 + .claude/hooks/fleet/excuse-detector/README.md | 50 + .claude/hooks/fleet/excuse-detector/index.mts | 386 ++ .../hooks/fleet/excuse-detector/package.json | 15 + .../hooks/fleet/excuse-detector/tsconfig.json | 16 + .claude/hooks/fleet/file-size-nudge/README.md | 48 + .claude/hooks/fleet/file-size-nudge/index.mts | 211 ++ .../hooks/fleet/file-size-nudge/package.json | 15 + .../hooks/fleet/file-size-nudge/tsconfig.json | 16 + .../follow-direct-imperative-nudge/README.md | 38 + .../follow-direct-imperative-nudge/index.mts | 212 ++ .../package.json | 15 + .../tsconfig.json | 16 + .../fleet/gh-token-hygiene-guard/README.md | 238 ++ .../fleet/gh-token-hygiene-guard/index.mts | 564 +++ .../fleet/gh-token-hygiene-guard/package.json | 18 + .../gh-token-hygiene-guard/tsconfig.json | 16 + .../fleet/git-config-write-guard/README.md | 51 + .../fleet/git-config-write-guard/index.mts | 552 +++ .../fleet/git-config-write-guard/package.json | 15 + .../git-config-write-guard/tsconfig.json | 16 + .../fleet/git-identity-drift-nudge/README.md | 36 + .../fleet/git-identity-drift-nudge/index.mts | 95 + .../git-identity-drift-nudge/package.json | 15 + .../git-identity-drift-nudge/tsconfig.json | 16 + .../fleet/gitmodules-comment-guard/README.md | 79 + .../fleet/gitmodules-comment-guard/index.mts | 118 + .../gitmodules-comment-guard/package.json | 12 + .../gitmodules-comment-guard/tsconfig.json | 16 + .../golden-fixture-naming-guard/README.md | 27 + .../golden-fixture-naming-guard/index.mts | 88 + .../golden-fixture-naming-guard/package.json | 15 + .../golden-fixture-naming-guard/tsconfig.json | 16 + .../fleet/headroom-proxy-start/README.md | 71 + .../fleet/headroom-proxy-start/index.mts | 317 ++ .../fleet/headroom-proxy-start/package.json | 15 + .../fleet/headroom-proxy-start/tsconfig.json | 16 + .../fleet/immutable-release-guard/README.md | 57 + .../fleet/immutable-release-guard/index.mts | 148 + .../immutable-release-guard/package.json | 15 + .../immutable-release-guard/tsconfig.json | 16 + .../fleet/inline-script-defer-guard/README.md | 53 + .../fleet/inline-script-defer-guard/index.mts | 145 + .../inline-script-defer-guard/package.json | 15 + .../inline-script-defer-guard/tsconfig.json | 16 + .../fleet/issue-autolink-nudge/index.mts | 70 + .claude/hooks/fleet/judgment-nudge/README.md | 58 + .claude/hooks/fleet/judgment-nudge/index.mts | 214 ++ .../hooks/fleet/judgment-nudge/package.json | 18 + .../hooks/fleet/judgment-nudge/tsconfig.json | 16 + .claude/hooks/fleet/land-fast-nudge/README.md | 22 + .claude/hooks/fleet/land-fast-nudge/index.mts | 133 + .../hooks/fleet/land-fast-nudge/package.json | 16 + .../hooks/fleet/land-fast-nudge/tsconfig.json | 16 + .../fleet/live-edit-collision-guard/index.mts | 203 ++ .../hooks/fleet/lock-step-ref-nudge/README.md | 62 + .../hooks/fleet/lock-step-ref-nudge/index.mts | 352 ++ .../fleet/lock-step-ref-nudge/package.json | 15 + .../fleet/lock-step-ref-nudge/tsconfig.json | 16 + .claude/hooks/fleet/logger-guard/README.md | 104 + .claude/hooks/fleet/logger-guard/index.mts | 265 ++ .claude/hooks/fleet/logger-guard/package.json | 18 + .../hooks/fleet/logger-guard/tsconfig.json | 16 + .../fleet/markdown-filename-guard/README.md | 39 + .../fleet/markdown-filename-guard/index.mts | 401 ++ .../markdown-filename-guard/package.json | 18 + .../markdown-filename-guard/tsconfig.json | 16 + .../fleet/marketplace-comment-guard/README.md | 103 + .../fleet/marketplace-comment-guard/index.mts | 270 ++ .../marketplace-comment-guard/package.json | 12 + .../marketplace-comment-guard/tsconfig.json | 16 + .../hooks/fleet/mass-delete-guard/README.md | 23 + .../hooks/fleet/mass-delete-guard/index.mts | 192 + .../fleet/mass-delete-guard/package.json | 18 + .../fleet/memory-discovery-nudge/README.md | 40 + .../fleet/memory-discovery-nudge/index.mts | 126 + .../fleet/minimum-release-age-guard/README.md | 50 + .../fleet/minimum-release-age-guard/index.mts | 175 + .../minimum-release-age-guard/package.json | 15 + .../minimum-release-age-guard/tsconfig.json | 16 + .../fleet/module-noun-name-guard/README.md | 20 + .../fleet/module-noun-name-guard/index.mts | 213 ++ .../fleet/module-noun-name-guard/package.json | 18 + .../module-noun-name-guard/tsconfig.json | 16 + .../fleet/new-hook-claude-md-guard/README.md | 50 + .../fleet/new-hook-claude-md-guard/index.mts | 226 ++ .../new-hook-claude-md-guard/package.json | 15 + .../new-hook-claude-md-guard/tsconfig.json | 16 + .../no-amend-foreign-commit-guard/README.md | 30 + .../no-amend-foreign-commit-guard/index.mts | 171 + .../no-blanket-file-exclusion-guard/README.md | 26 + .../no-blanket-file-exclusion-guard/index.mts | 196 + .../package.json | 15 + .../tsconfig.json | 16 + .../no-blind-keychain-read-guard/README.md | 65 + .../no-blind-keychain-read-guard/index.mts | 233 ++ .../no-blind-keychain-read-guard/package.json | 15 + .../tsconfig.json | 16 + .../fleet/no-boolean-trap-guard/README.md | 47 + .../fleet/no-boolean-trap-guard/index.mts | 226 ++ .../fleet/no-boolean-trap-guard/package.json | 15 + .../fleet/no-boolean-trap-guard/tsconfig.json | 16 + .../fleet/no-branch-reuse-nudge/README.md | 39 + .../fleet/no-branch-reuse-nudge/index.mts | 128 + .../fleet/no-branch-reuse-nudge/package.json | 15 + .../fleet/no-branch-reuse-nudge/tsconfig.json | 16 + .../no-cascade-transient-git-guard/index.mts | 101 + .../fleet/no-clipboard-access-guard/README.md | 40 + .../fleet/no-clipboard-access-guard/index.mts | 184 + .../hooks/fleet/no-corepack-guard/README.md | 33 + .../hooks/fleet/no-corepack-guard/index.mts | 121 + .../fleet/no-corepack-guard/package.json | 15 + .../fleet/no-corepack-guard/tsconfig.json | 16 + .../fleet/no-direct-linter-guard/README.md | 52 + .../fleet/no-direct-linter-guard/index.mts | 193 + .../fleet/no-direct-linter-guard/package.json | 16 + .../no-direct-linter-guard/tsconfig.json | 16 + .../no-disable-lint-rule-guard/README.md | 32 + .../no-disable-lint-rule-guard/index.mts | 179 + .../no-disable-lint-rule-guard/package.json | 15 + .../no-disable-lint-rule-guard/tsconfig.json | 16 + .../fleet/no-empty-commit-guard/README.md | 40 + .../fleet/no-empty-commit-guard/index.mts | 125 + .../fleet/no-empty-commit-guard/package.json | 18 + .../fleet/no-empty-commit-guard/tsconfig.json | 16 + .../fleet/no-env-kill-switch-guard/README.md | 31 + .../fleet/no-env-kill-switch-guard/index.mts | 126 + .../no-env-kill-switch-guard/package.json | 15 + .../no-env-kill-switch-guard/tsconfig.json | 16 + .../fleet/no-ext-issue-ref-guard/README.md | 42 + .../fleet/no-ext-issue-ref-guard/index.mts | 221 ++ .../fleet/no-ext-issue-ref-guard/package.json | 18 + .../no-ext-issue-ref-guard/tsconfig.json | 16 + .../no-file-oxlint-disable-guard/README.md | 24 + .../no-file-oxlint-disable-guard/index.mts | 128 + .../no-file-oxlint-disable-guard/package.json | 15 + .../tsconfig.json | 16 + .../hooks/fleet/no-fleet-fork-guard/README.md | 70 + .../hooks/fleet/no-fleet-fork-guard/index.mts | 285 ++ .../fleet/no-fleet-fork-guard/package.json | 18 + .../fleet/no-fleet-fork-guard/tsconfig.json | 16 + .../hooks/fleet/no-force-push-guard/README.md | 58 + .../hooks/fleet/no-force-push-guard/index.mts | 205 ++ .../fleet/no-force-push-guard/package.json | 15 + .../fleet/no-force-push-guard/tsconfig.json | 16 + .../no-github-ai-attribution-guard/README.md | 33 + .../no-github-ai-attribution-guard/index.mts | 200 + .../package.json | 18 + .../tsconfig.json | 16 + .../fleet/no-hook-cmd-regex-guard/README.md | 48 + .../fleet/no-hook-cmd-regex-guard/index.mts | 179 + .../no-hook-cmd-regex-guard/package.json | 15 + .../no-hook-cmd-regex-guard/tsconfig.json | 16 + .../fleet/no-meta-comments-guard/README.md | 34 + .../fleet/no-meta-comments-guard/index.mts | 314 ++ .../fleet/no-meta-comments-guard/package.json | 18 + .../no-meta-comments-guard/tsconfig.json | 16 + .../hooks/fleet/no-new-config-guard/README.md | 16 + .../hooks/fleet/no-new-config-guard/index.mts | 101 + .../fleet/no-new-config-guard/package.json | 18 + .../fleet/no-new-config-guard/tsconfig.json | 16 + .../fleet/no-non-fleet-push-guard/README.md | 81 + .../fleet/no-non-fleet-push-guard/index.mts | 133 + .../no-non-fleet-push-guard/package.json | 18 + .../no-non-fleet-push-guard/tsconfig.json | 16 + .../fleet/no-npm-otp-flag-guard/README.md | 45 + .../fleet/no-npm-otp-flag-guard/index.mts | 114 + .../fleet/no-npm-otp-flag-guard/package.json | 18 + .../fleet/no-npm-otp-flag-guard/tsconfig.json | 16 + .../hooks/fleet/no-orphaned-staging/README.md | 49 + .../hooks/fleet/no-orphaned-staging/index.mts | 95 + .../fleet/no-orphaned-staging/package.json | 15 + .../fleet/no-orphaned-staging/tsconfig.json | 16 + .../fleet/no-other-linters-guard/README.md | 100 + .../fleet/no-other-linters-guard/index.mts | 135 + .../fleet/no-other-linters-guard/package.json | 15 + .../no-other-linters-guard/tsconfig.json | 16 + .../no-pkgjson-pnpm-overrides-guard/README.md | 55 + .../no-pkgjson-pnpm-overrides-guard/index.mts | 138 + .../package.json | 15 + .../tsconfig.json | 16 + .../README.md | 56 + .../index.mts | 102 + .../package.json | 18 + .../tsconfig.json | 16 + .../fleet/no-platform-import-guard/README.md | 37 + .../fleet/no-platform-import-guard/index.mts | 140 + .../no-platform-import-guard/package.json | 15 + .../no-platform-import-guard/tsconfig.json | 16 + .../hooks/fleet/no-pm-exec-guard/README.md | 36 + .../hooks/fleet/no-pm-exec-guard/index.mts | 124 + .../hooks/fleet/no-pm-exec-guard/package.json | 16 + .../fleet/no-pm-exec-guard/tsconfig.json | 16 + .../no-pr-from-default-branch-guard/README.md | 48 + .../no-pr-from-default-branch-guard/index.mts | 107 + .../package.json | 15 + .../tsconfig.json | 16 + .../README.md | 48 + .../index.mts | 119 + .../package.json | 15 + .../tsconfig.json | 16 + .../no-pr-review-verdict-guard/README.md | 30 + .../no-pr-review-verdict-guard/index.mts | 114 + .../no-pr-review-verdict-guard/package.json | 18 + .../no-pr-review-verdict-guard/tsconfig.json | 16 + .../no-premature-commit-kill-guard/README.md | 41 + .../no-premature-commit-kill-guard/index.mts | 258 ++ .../package.json | 16 + .../tsconfig.json | 16 + .../no-private-path-in-source-guard/README.md | 28 + .../no-private-path-in-source-guard/index.mts | 188 + .../package.json | 18 + .../tsconfig.json | 16 + .../README.md | 16 + .../index.mts | 112 + .../package.json | 18 + .../tsconfig.json | 16 + .../README.md | 24 + .../index.mts | 82 + .../package.json | 15 + .../tsconfig.json | 16 + .../fleet/no-removal-comment-nudge/README.md | 60 + .../fleet/no-removal-comment-nudge/index.mts | 312 ++ .../no-removal-comment-nudge/package.json | 15 + .../no-removal-comment-nudge/tsconfig.json | 16 + .../README.md | 35 + .../index.mts | 157 + .../package.json | 15 + .../tsconfig.json | 16 + .claude/hooks/fleet/no-revert-guard/README.md | 54 + .claude/hooks/fleet/no-revert-guard/index.mts | 609 ++++ .../hooks/fleet/no-revert-guard/package.json | 18 + .../hooks/fleet/no-revert-guard/tsconfig.json | 16 + .../hooks/fleet/no-screenshot-guard/README.md | 37 + .../hooks/fleet/no-screenshot-guard/index.mts | 116 + .../no-shell-injection-bypass-guard/README.md | 40 + .../no-shell-injection-bypass-guard/index.mts | 194 + .../fleet/no-strip-types-guard/README.md | 34 + .../fleet/no-strip-types-guard/index.mts | 87 + .../fleet/no-strip-types-guard/package.json | 18 + .../fleet/no-strip-types-guard/tsconfig.json | 16 + .../fleet/no-subagent-commit-guard/README.md | 39 + .../fleet/no-subagent-commit-guard/index.mts | 87 + .../no-subagent-commit-guard/package.json | 15 + .../no-subagent-commit-guard/tsconfig.json | 16 + .../fleet/no-tail-install-out-guard/README.md | 47 + .../fleet/no-tail-install-out-guard/index.mts | 242 ++ .../no-tail-install-out-guard/package.json | 18 + .../no-tail-install-out-guard/tsconfig.json | 16 + .../fleet/no-test-in-scripts-guard/README.md | 46 + .../fleet/no-test-in-scripts-guard/index.mts | 88 + .../no-test-in-scripts-guard/package.json | 16 + .../no-test-in-scripts-guard/tsconfig.json | 16 + .../fleet/no-token-in-dotenv-guard/README.md | 32 + .../fleet/no-token-in-dotenv-guard/index.mts | 190 + .../no-token-in-dotenv-guard/package.json | 15 + .../no-token-in-dotenv-guard/tsconfig.json | 16 + .claude/hooks/fleet/no-tsx-guard/README.md | 30 + .claude/hooks/fleet/no-tsx-guard/index.mts | 168 + .claude/hooks/fleet/no-tsx-guard/package.json | 15 + .../hooks/fleet/no-tsx-guard/tsconfig.json | 16 + .../fleet/no-underscore-ident-guard/README.md | 38 + .../fleet/no-underscore-ident-guard/index.mts | 210 ++ .../no-underscore-ident-guard/package.json | 15 + .../no-underscore-ident-guard/tsconfig.json | 16 + .../no-unisolated-git-fixture-guard/README.md | 35 + .../no-unisolated-git-fixture-guard/index.mts | 152 + .../package.json | 15 + .../tsconfig.json | 16 + .../fleet/no-unmocked-ai-guard/README.md | 15 + .../fleet/no-unmocked-ai-guard/index.mts | 124 + .../fleet/no-unmocked-net-guard/README.md | 32 + .../fleet/no-unmocked-net-guard/index.mts | 131 + .../fleet/no-unmocked-net-guard/package.json | 15 + .../fleet/no-unmocked-net-guard/tsconfig.json | 16 + .../fleet/no-verify-format-nudge/README.md | 32 + .../fleet/no-verify-format-nudge/index.mts | 158 + .../fleet/no-verify-format-nudge/package.json | 16 + .../no-verify-format-nudge/tsconfig.json | 16 + .../no-vitest-double-dash-guard/README.md | 46 + .../no-vitest-double-dash-guard/index.mts | 144 + .../node-modules-staging-guard/README.md | 46 + .../node-modules-staging-guard/index.mts | 154 + .../node-modules-staging-guard/package.json | 15 + .../node-modules-staging-guard/tsconfig.json | 16 + .../non-fleet-pr-issue-ask-guard/README.md | 32 + .../non-fleet-pr-issue-ask-guard/index.mts | 194 + .../non-fleet-pr-issue-ask-guard/package.json | 15 + .../tsconfig.json | 16 + .../hooks/fleet/npm-otp-flow-nudge/README.md | 45 + .../hooks/fleet/npm-otp-flow-nudge/index.mts | 82 + .../fleet/npm-otp-flow-nudge/package.json | 18 + .../fleet/npm-otp-flow-nudge/tsconfig.json | 16 + .../fleet/npmrc-trust-optout-guard/README.md | 62 + .../fleet/npmrc-trust-optout-guard/index.mts | 182 + .../npmrc-trust-optout-guard/package.json | 15 + .../npmrc-trust-optout-guard/tsconfig.json | 16 + .../operate-from-repo-root-guard/README.md | 26 + .../operate-from-repo-root-guard/index.mts | 126 + .../options-param-naming-guard/README.md | 101 + .../options-param-naming-guard/index.mts | 199 + .../options-param-naming-guard/package.json | 15 + .../options-param-naming-guard/tsconfig.json | 16 + .../fleet/overeager-staging-guard/README.md | 43 + .../fleet/overeager-staging-guard/index.mts | 341 ++ .../overeager-staging-guard/package.json | 18 + .../overeager-staging-guard/tsconfig.json | 16 + .../fleet/oxlint-plugin-load-nudge/README.md | 24 + .../fleet/oxlint-plugin-load-nudge/index.mts | 88 + .../is-plugin-path.mts | 8 + .../oxlint-plugin-load-nudge/package.json | 15 + .../oxlint-plugin-load-nudge/tsconfig.json | 16 + .../README.md | 50 + .../index.mts | 74 + .../package.json | 16 + .../tsconfig.json | 16 + .../fleet/parallel-agent-edit-guard/README.md | 50 + .../fleet/parallel-agent-edit-guard/index.mts | 140 + .../parallel-agent-edit-guard/package.json | 15 + .../parallel-agent-edit-guard/tsconfig.json | 16 + .../parallel-agent-on-stop-nudge/README.md | 42 + .../parallel-agent-on-stop-nudge/index.mts | 102 + .../parallel-agent-on-stop-nudge/package.json | 15 + .../tsconfig.json | 16 + .../parallel-agent-removal-nudge/README.md | 54 + .../parallel-agent-removal-nudge/index.mts | 304 ++ .../parallel-agent-removal-nudge/package.json | 15 + .../tsconfig.json | 16 + .../parallel-agent-spawn-nudge/README.md | 22 + .../parallel-agent-spawn-nudge/index.mts | 128 + .../parallel-agent-staging-guard/README.md | 47 + .../parallel-agent-staging-guard/index.mts | 217 ++ .../parallel-agent-staging-guard/package.json | 18 + .../tsconfig.json | 16 + .claude/hooks/fleet/path-guard/README.md | 113 + .claude/hooks/fleet/path-guard/index.mts | 328 ++ .claude/hooks/fleet/path-guard/package.json | 15 + .claude/hooks/fleet/path-guard/segments.mts | 74 + .claude/hooks/fleet/path-guard/tsconfig.json | 16 + .../path-regex-normalize-nudge/README.md | 31 + .../path-regex-normalize-nudge/index.mts | 211 ++ .../path-regex-normalize-nudge/package.json | 18 + .../path-regex-normalize-nudge/tsconfig.json | 16 + .../fleet/paths-mts-inherit-guard/README.md | 58 + .../fleet/paths-mts-inherit-guard/index.mts | 211 ++ .../paths-mts-inherit-guard/package.json | 18 + .../paths-mts-inherit-guard/tsconfig.json | 16 + .../hooks/fleet/personal-path-guard/README.md | 53 + .../hooks/fleet/personal-path-guard/index.mts | 148 + .../fleet/personal-path-guard/package.json | 15 + .../fleet/personal-path-guard/tsconfig.json | 16 + .../hooks/fleet/plan-location-guard/README.md | 55 + .../hooks/fleet/plan-location-guard/index.mts | 138 + .../fleet/plan-location-guard/package.json | 18 + .../fleet/plan-location-guard/tsconfig.json | 16 + .../hooks/fleet/plan-review-nudge/README.md | 19 + .../hooks/fleet/plan-review-nudge/index.mts | 151 + .../fleet/plan-review-nudge/package.json | 15 + .../fleet/plan-review-nudge/tsconfig.json | 16 + .../fleet/plugin-patch-format-guard/README.md | 37 + .../fleet/plugin-patch-format-guard/index.mts | 253 ++ .../plugin-patch-format-guard/package.json | 18 + .../plugin-patch-format-guard/tsconfig.json | 16 + .../pnpm-filter-zero-match-nudge/README.md | 17 + .../pnpm-filter-zero-match-nudge/index.mts | 88 + .../pnpm-filter-zero-match-nudge/package.json | 15 + .../tsconfig.json | 16 + .../fleet/pointer-comment-nudge/README.md | 55 + .../fleet/pointer-comment-nudge/index.mts | 260 ++ .../fleet/pointer-comment-nudge/package.json | 18 + .../fleet/pointer-comment-nudge/tsconfig.json | 16 + .../post-push-ci-monitor-nudge/README.md | 27 + .../post-push-ci-monitor-nudge/index.mts | 92 + .../post-push-ci-monitor-nudge/package.json | 15 + .../post-push-ci-monitor-nudge/tsconfig.json | 16 + .../fleet/pr-vs-push-default-nudge/README.md | 61 + .../fleet/pr-vs-push-default-nudge/index.mts | 434 +++ .../pr-vs-push-default-nudge/package.json | 15 + .../pr-vs-push-default-nudge/tsconfig.json | 16 + .../fleet/pre-commit-race-nudge/README.md | 25 + .../fleet/pre-commit-race-nudge/index.mts | 80 + .../fleet/pre-commit-race-nudge/package.json | 15 + .../fleet/pre-commit-race-nudge/tsconfig.json | 16 + .../fleet/prefer-async-spawn-guard/README.md | 52 + .../fleet/prefer-async-spawn-guard/index.mts | 167 + .../prefer-async-spawn-guard/package.json | 15 + .../prefer-async-spawn-guard/tsconfig.json | 16 + .../prefer-evergreen-target-nudge/README.md | 24 + .../prefer-evergreen-target-nudge/index.mts | 129 + .../package.json | 15 + .../tsconfig.json | 16 + .../fleet/prefer-fff-search-nudge/README.md | 33 + .../fleet/prefer-fff-search-nudge/index.mts | 152 + .../fleet/prefer-fn-decl-guard/index.mts | 199 + .../fleet/prefer-fn-decl-guard/package.json | 15 + .../fleet/prefer-fn-decl-guard/tsconfig.json | 16 + .../fleet/prefer-json-clone-guard/README.md | 112 + .../fleet/prefer-json-clone-guard/index.mts | 140 + .../prefer-json-clone-guard/package.json | 15 + .../prefer-json-clone-guard/tsconfig.json | 16 + .../prefer-pipx-over-pip-guard/README.md | 93 + .../prefer-pipx-over-pip-guard/index.mts | 266 ++ .../prefer-pipx-over-pip-guard/package.json | 15 + .../prefer-pipx-over-pip-guard/tsconfig.json | 16 + .../prefer-rebase-over-revert-nudge/README.md | 29 + .../prefer-rebase-over-revert-nudge/index.mts | 179 + .../package.json | 18 + .../tsconfig.json | 16 + .../fleet/prefer-type-import-guard/README.md | 35 + .../fleet/prefer-type-import-guard/index.mts | 101 + .../prefer-type-import-guard/package.json | 15 + .../prefer-type-import-guard/tsconfig.json | 16 + .../hooks/fleet/prefer-vitest-guard/README.md | 35 + .../hooks/fleet/prefer-vitest-guard/index.mts | 361 ++ .../fleet/prefer-vitest-guard/package.json | 15 + .../fleet/prefer-vitest-guard/tsconfig.json | 16 + .../primary-checkout-branch-guard/README.md | 44 + .../primary-checkout-branch-guard/index.mts | 191 + .../package.json | 15 + .../tsconfig.json | 16 + .../hooks/fleet/private-name-nudge/README.md | 77 + .../hooks/fleet/private-name-nudge/index.mts | 71 + .../fleet/private-name-nudge/package.json | 12 + .../fleet/private-name-nudge/tsconfig.json | 16 + .../fleet/proc-environ-exfil-guard/README.md | 55 + .../fleet/proc-environ-exfil-guard/index.mts | 208 ++ .../proc-environ-exfil-guard/package.json | 15 + .../proc-environ-exfil-guard/tsconfig.json | 16 + .../fleet/prompt-injection-guard/README.md | 94 + .../fleet/prompt-injection-guard/index.mts | 423 +++ .../fleet/prompt-injection-guard/package.json | 15 + .../prompt-injection-guard/test/payloads.mts | 94 + .../prompt-injection-guard/tsconfig.json | 16 + .../fleet/prose-code-format-nudge/index.mts | 81 + .../fleet/provenance-publish-nudge/README.md | 52 + .../fleet/provenance-publish-nudge/index.mts | 247 ++ .../provenance-publish-nudge/package.json | 18 + .../provenance-publish-nudge/tsconfig.json | 16 + .../fleet/public-surface-nudge/README.md | 86 + .../fleet/public-surface-nudge/index.mts | 53 + .../fleet/public-surface-nudge/package.json | 12 + .../fleet/public-surface-nudge/tsconfig.json | 16 + .../fleet/pull-request-target-guard/README.md | 25 + .../fleet/pull-request-target-guard/index.mts | 298 ++ .../pull-request-target-guard/package.json | 15 + .../pull-request-target-guard/tsconfig.json | 16 + .../push-protected-branch-guard/README.md | 49 + .../push-protected-branch-guard/index.mts | 148 + .../push-protected-branch-guard/package.json | 15 + .../push-protected-branch-guard/tsconfig.json | 16 + .../fleet/read-orientation-nudge/index.mts | 139 + .../fleet/readme-fleet-shape-guard/README.md | 36 + .../fleet/readme-fleet-shape-guard/index.mts | 457 +++ .../readme-fleet-shape-guard/package.json | 18 + .../readme-fleet-shape-guard/tsconfig.json | 16 + .../fleet/release-tag-tied-guard/README.md | 32 + .../fleet/release-tag-tied-guard/index.mts | 175 + .../fleet/release-tag-tied-guard/package.json | 15 + .../release-tag-tied-guard/tsconfig.json | 16 + .../fleet/release-workflow-guard/README.md | 107 + .../fleet/release-workflow-guard/index.mts | 923 +++++ .../fleet/release-workflow-guard/package.json | 19 + .../release-workflow-guard/tsconfig.json | 16 + .../hooks/fleet/reply-prose-nudge/README.md | 40 + .../hooks/fleet/reply-prose-nudge/index.mts | 275 ++ .../fleet/reply-prose-nudge/package.json | 15 + .../fleet/reply-prose-nudge/tsconfig.json | 16 + .../hooks/fleet/repo-map-refresh/index.mts | 85 + .../fleet/report-location-guard/README.md | 40 + .../fleet/report-location-guard/index.mts | 136 + .../fleet/report-location-guard/package.json | 18 + .../fleet/report-location-guard/tsconfig.json | 16 + .../fleet/reserved-script-dir-guard/README.md | 42 + .../fleet/reserved-script-dir-guard/index.mts | 94 + .../reserved-script-dir-guard/package.json | 16 + .../reserved-script-dir-guard/tsconfig.json | 16 + .../scan-label-in-commit-guard/README.md | 53 + .../scan-label-in-commit-guard/index.mts | 231 ++ .../scan-label-in-commit-guard/package.json | 18 + .../scan-label-in-commit-guard/tsconfig.json | 16 + .../fleet/secret-content-guard/README.md | 32 + .../fleet/secret-content-guard/index.mts | 65 + .../fleet/secret-content-guard/package.json | 16 + .../fleet/secret-content-guard/tsconfig.json | 16 + .../hooks/fleet/sed-in-place-guard/README.md | 36 + .../hooks/fleet/sed-in-place-guard/index.mts | 133 + .../fleet/sed-in-place-guard/package.json | 12 + .../fleet/session-handoff-nudge/README.md | 17 + .../fleet/session-handoff-nudge/index.mts | 117 + .../fleet/session-handoff-nudge/package.json | 15 + .../fleet/session-handoff-nudge/tsconfig.json | 16 + .../hooks/fleet/setup-basics-tools/README.md | 23 + .../fleet/setup-basics-tools/install.mts | 54 + .../fleet/setup-basics-tools/package.json | 16 + .../fleet/setup-basics-tools/tsconfig.json | 16 + .../fleet/setup-claude-scanners/README.md | 39 + .../fleet/setup-claude-scanners/install.mts | 46 + .../fleet/setup-claude-scanners/package.json | 16 + .../fleet/setup-claude-scanners/tsconfig.json | 16 + .claude/hooks/fleet/setup-firewall/README.md | 40 + .../hooks/fleet/setup-firewall/install.mts | 80 + .../hooks/fleet/setup-firewall/package.json | 16 + .../hooks/fleet/setup-firewall/tsconfig.json | 16 + .../hooks/fleet/setup-misc-tools/README.md | 21 + .../hooks/fleet/setup-misc-tools/install.mts | 49 + .../hooks/fleet/setup-misc-tools/package.json | 16 + .../fleet/setup-misc-tools/tsconfig.json | 16 + .../fleet/setup-security-tools/README.md | 149 + .../setup-security-tools/external-tools.json | 331 ++ .../headroom/pyproject.toml | 55 + .../setup-security-tools/headroom/uv.lock | 3248 +++++++++++++++++ .../fleet/setup-security-tools/index.mts | 377 ++ .../fleet/setup-security-tools/install.mts | 256 ++ .../setup-security-tools/lib/api-token.mts | 89 + .../setup-security-tools/lib/headroom.mts | 295 ++ .../setup-security-tools/lib/installers.mts | 1135 ++++++ .../lib/operator-prompts.mts | 262 ++ .../lib/shell-rc-bridge.mts | 268 ++ .../fleet/setup-security-tools/lib/shims.mts | 172 + .../lib/token-storage.mts | 455 +++ .../fleet/setup-security-tools/package.json | 11 + .../skillspector/pyproject.toml | 46 + .../setup-security-tools/skillspector/uv.lock | 2068 +++++++++++ .../fleet/setup-security-tools/tsconfig.json | 16 + .../fleet/setup-security-tools/update.mts | 542 +++ .claude/hooks/fleet/setup-signing/README.md | 60 + .claude/hooks/fleet/setup-signing/install.mts | 290 ++ .../hooks/fleet/setup-signing/package.json | 15 + .../hooks/fleet/setup-signing/tsconfig.json | 16 + .../hooks/fleet/shallow-clone-guard/index.mts | 125 + .../fleet/shallow-clone-guard/package.json | 15 + .../fleet/shallow-clone-guard/tsconfig.json | 16 + .../hooks/fleet/skill-usage-logger/README.md | 66 + .../hooks/fleet/skill-usage-logger/index.mts | 164 + .../fleet/skill-usage-logger/package.json | 15 + .../fleet/skill-usage-logger/tsconfig.json | 16 + .claude/hooks/fleet/small-pr-nudge/README.md | 51 + .claude/hooks/fleet/small-pr-nudge/index.mts | 167 + .../hooks/fleet/small-pr-nudge/package.json | 15 + .../hooks/fleet/small-pr-nudge/tsconfig.json | 16 + .../fleet/soak-exclude-date-guard/README.md | 92 + .../fleet/soak-exclude-date-guard/index.mts | 182 + .../soak-exclude-date-guard/package.json | 12 + .../soak-exclude-date-guard/tsconfig.json | 16 + .../fleet/soak-exclude-scope-guard/README.md | 82 + .../fleet/soak-exclude-scope-guard/index.mts | 194 + .../soak-exclude-scope-guard/package.json | 15 + .../soak-exclude-scope-guard/tsconfig.json | 16 + .../soak-pin-needs-annotation-guard/README.md | 24 + .../soak-pin-needs-annotation-guard/index.mts | 126 + .../package.json | 15 + .../tsconfig.json | 16 + .../fleet/squash-history-nudge/README.md | 33 + .../fleet/squash-history-nudge/index.mts | 126 + .../fleet/squash-history-nudge/package.json | 18 + .../fleet/squash-history-nudge/tsconfig.json | 16 + .../fleet/stale-node-modules-nudge/README.md | 32 + .../fleet/stale-node-modules-nudge/index.mts | 179 + .../fleet/stale-process-sweeper/README.md | 123 + .../fleet/stale-process-sweeper/index.mts | 652 ++++ .../fleet/stale-process-sweeper/package.json | 12 + .../fleet/stale-process-sweeper/tsconfig.json | 16 + .../fleet/stop-claim-verify-nudge/README.md | 40 + .../fleet/stop-claim-verify-nudge/index.mts | 73 + .../stop-claim-verify-nudge/package.json | 15 + .../stop-claim-verify-nudge/tsconfig.json | 16 + .claude/hooks/fleet/sweep-ds-store/README.md | 45 + .claude/hooks/fleet/sweep-ds-store/index.mts | 156 + .../hooks/fleet/sweep-ds-store/package.json | 15 + .../hooks/fleet/sweep-ds-store/tsconfig.json | 16 + .../synthesized-script-edit-guard/README.md | 37 + .../synthesized-script-edit-guard/index.mts | 159 + .../fleet/target-arch-env-guard/README.md | 79 + .../fleet/target-arch-env-guard/index.mts | 146 + .../fleet/target-arch-env-guard/package.json | 15 + .../fleet/target-arch-env-guard/tsconfig.json | 16 + .../test-platform-coverage-nudge/index.mts | 103 + .../fleet/test-script-defers-guard/README.md | 33 + .../fleet/test-script-defers-guard/index.mts | 190 + .../test-script-defers-guard/package.json | 15 + .../test-script-defers-guard/tsconfig.json | 16 + .claude/hooks/fleet/token-guard/README.md | 90 + .claude/hooks/fleet/token-guard/index.mts | 264 ++ .claude/hooks/fleet/token-guard/package.json | 12 + .claude/hooks/fleet/token-guard/tsconfig.json | 16 + .../hooks/fleet/token-spend-guard/README.md | 27 + .../hooks/fleet/token-spend-guard/index.mts | 159 + .../fleet/token-spend-guard/package.json | 15 + .../fleet/token-spend-guard/tsconfig.json | 16 + .../fleet/trust-downgrade-guard/README.md | 58 + .../fleet/trust-downgrade-guard/index.mts | 395 ++ .../fleet/trust-downgrade-guard/package.json | 15 + .../fleet/trust-downgrade-guard/tsconfig.json | 16 + .../unbacked-claim-commit-guard/README.md | 34 + .../unbacked-claim-commit-guard/index.mts | 122 + .../unbacked-claim-commit-guard/package.json | 18 + .../unbacked-claim-commit-guard/tsconfig.json | 16 + .../fleet/uncodified-lesson-nudge/README.md | 37 + .../fleet/uncodified-lesson-nudge/index.mts | 148 + .../uncodified-lesson-nudge/package.json | 15 + .../uncodified-lesson-nudge/tsconfig.json | 16 + .../hooks/fleet/unpushed-main-nudge/README.md | 32 + .../hooks/fleet/unpushed-main-nudge/index.mts | 153 + .../fleet/unpushed-main-nudge/package.json | 16 + .../fleet/unpushed-main-nudge/tsconfig.json | 16 + .../fleet/untrusted-coauthor-guard/README.md | 50 + .../fleet/untrusted-coauthor-guard/index.mts | 154 + .../untrusted-coauthor-guard/package.json | 15 + .../untrusted-coauthor-guard/tsconfig.json | 16 + .../fleet/uses-sha-verify-guard/README.md | 47 + .../fleet/uses-sha-verify-guard/index.mts | 221 ++ .../fleet/uses-sha-verify-guard/lib/bash.mts | 215 ++ .../fleet/uses-sha-verify-guard/lib/cache.mts | 84 + .../uses-sha-verify-guard/lib/gitmodules.mts | 126 + .../uses-sha-verify-guard/lib/issue-types.mts | 29 + .../lib/package-json.mts | 36 + .../uses-sha-verify-guard/lib/regexes.mts | 70 + .../lib/validate-ref.mts | 52 + .../uses-sha-verify-guard/lib/workflow.mts | 36 + .../fleet/uses-sha-verify-guard/package.json | 12 + .../fleet/uses-sha-verify-guard/tsconfig.json | 16 + .../fleet/variant-analysis-nudge/README.md | 40 + .../fleet/variant-analysis-nudge/index.mts | 141 + .../fleet/variant-analysis-nudge/package.json | 15 + .../variant-analysis-nudge/tsconfig.json | 16 + .../verify-before-publish-guard/README.md | 20 + .../verify-before-publish-guard/index.mts | 460 +++ .../verify-render-pre-commit-nudge/README.md | 40 + .../verify-render-pre-commit-nudge/index.mts | 235 ++ .../package.json | 15 + .../tsconfig.json | 16 + .../fleet/version-bump-order-guard/README.md | 27 + .../fleet/version-bump-order-guard/index.mts | 561 +++ .../version-bump-order-guard/package.json | 18 + .../version-bump-order-guard/tsconfig.json | 16 + .../fleet/vitest-vs-node-test-guard/README.md | 46 + .../fleet/vitest-vs-node-test-guard/index.mts | 282 ++ .../vitest-vs-node-test-guard/package.json | 15 + .../vitest-vs-node-test-guard/tsconfig.json | 16 + .../vscode-folder-open-task-guard/README.md | 38 + .../vscode-folder-open-task-guard/index.mts | 97 + .../workflow-agent-task-tools-nudge/README.md | 30 + .../workflow-agent-task-tools-nudge/index.mts | 112 + .../workflow-multiline-body-guard/README.md | 44 + .../workflow-multiline-body-guard/index.mts | 156 + .../package.json | 15 + .../tsconfig.json | 16 + .../workflow-uses-comment-guard/README.md | 83 + .../workflow-uses-comment-guard/index.mts | 156 + .../workflow-uses-comment-guard/package.json | 12 + .../workflow-uses-comment-guard/tsconfig.json | 16 + .../worktree-remove-relink-nudge/README.md | 43 + .../worktree-remove-relink-nudge/index.mts | 86 + .../worktree-remove-relink-nudge/package.json | 18 + .../tsconfig.json | 16 + .../fleet/zsh-word-split-nudge/index.mts | 99 + .../fleet/claude-md-is-a-bullet-index.md | 28 + .claude/rules/fleet/code-first-then-ai.md | 54 + .../rules/fleet/prose-style-and-doctrine.md | 83 + .../rules/fleet/verify-state-before-acting.md | 44 + .claude/settings.json | 133 + .../skills/fleet/_shared/compound-lessons.md | 45 + .claude/skills/fleet/_shared/env-check.md | 28 + .../fleet/_shared/multi-agent-backends.md | 137 + .../skills/fleet/_shared/path-guard-rule.md | 39 + .claude/skills/fleet/_shared/report-format.md | 50 + .../fleet/_shared/scripts/checkpoint.mts | 320 ++ .../fleet/_shared/scripts/fleet-roster.mts | 66 + .../_shared/scripts/git-default-branch.mts | 122 + .../_shared/scripts/logger-guardrails.mts | 235 ++ .../fleet/_shared/scripts/resolve-tools.mts | 253 ++ .../fleet/_shared/scripts/run-helpers.mts | 93 + .../skills/fleet/_shared/security-tools.md | 45 + .../skills/fleet/_shared/skill-authoring.md | 173 + .../skills/fleet/_shared/variant-analysis.md | 53 + .claude/skills/fleet/_shared/verify-build.md | 22 + .claude/skills/fleet/_shared/visual-verify.md | 59 + .claude/skills/fleet/agent-ci/SKILL.md | 63 + .claude/skills/fleet/agent-ci/reference.md | 60 + .../fleet/auditing-api-surface/SKILL.md | 90 + .../lib/audit-api-surface.mts | 486 +++ .claude/skills/fleet/auditing-gha/SKILL.md | 122 + .claude/skills/fleet/auditing-gha/run.mts | 505 +++ .claude/skills/fleet/authoring-spec/SKILL.md | 66 + .claude/skills/fleet/building-tdd/SKILL.md | 57 + .claude/skills/fleet/cascading-fleet/SKILL.md | 106 + .../cascading-fleet/lib/cascade-template.mts | 491 +++ .../cascading-fleet/lib/fleet-repos.json | 112 + .../fleet/cascading-fleet/lib/fleet-repos.txt | 14 + .../lib/reconcile-lockfiles.mts | 288 ++ .claude/skills/fleet/cleaning-ci/SKILL.md | 128 + .../skills/fleet/cleaning-ci/lib/clean-ci.mts | 202 + .../fleet/codifying-disciplines/SKILL.md | 131 + .../fleet/consolidating-commits/SKILL.md | 40 + .claude/skills/fleet/creating-guards/SKILL.md | 55 + .../skills/fleet/decomposing-tickets/SKILL.md | 60 + .../fleet/deduping-dependencies/SKILL.md | 85 + .../fleet/delegating-execution/SKILL.md | 65 + .../skills/fleet/designing-interfaces/LICENSE | 21 + .../fleet/designing-interfaces/SKILL.md | 475 +++ .../references/anti-ai-slop.md | 318 ++ .../designing-interfaces/references/color.md | 572 +++ .../references/copywriting.md | 222 ++ .../references/craft-details.md | 533 +++ .../references/delivery-and-craft.md | 110 + .../references/example-workflow.md | 264 ++ .../designing-interfaces/references/icons.md | 166 + .../references/interface-copy.md | 32 + .../references/mcp-tools.md | 258 ++ .../designing-interfaces/references/motion.md | 513 +++ .../references/refinement-modes.md | 36 + .../references/typography.md | 745 ++++ .../references/visual-workflow.md | 105 + .claude/skills/fleet/diagnosing-bugs/SKILL.md | 68 + .../fleet/driving-cursor-bugbot/SKILL.md | 99 + .../driving-cursor-bugbot/lib/bugbot.mts | 495 +++ .../fleet/driving-cursor-bugbot/reference.md | 112 + .../fleet/extracting-design-systems/SKILL.md | 34 + .../skills/fleet/greening-ci-local/SKILL.md | 106 + .../skills/fleet/greening-ci-local/run.mts | 341 ++ .claude/skills/fleet/greening-ci/SKILL.md | 123 + .claude/skills/fleet/greening-ci/run.mts | 396 ++ .claude/skills/fleet/grilling-plan/SKILL.md | 57 + .../skills/fleet/grooming-backlog/SKILL.md | 71 + .claude/skills/fleet/guarding-paths/SKILL.md | 121 + .../skills/fleet/guarding-paths/reference.md | 170 + .../templates/check-paths.mts.tmpl | 892 +++++ .claude/skills/fleet/handing-off/SKILL.md | 49 + .claude/skills/fleet/improve/SKILL.md | 122 + .../improve/references/audit-playbook.md | 130 + .../improve/references/closing-the-loop.md | 96 + .../fleet/improve/references/plan-template.md | 197 + .../fleet/improving-web-interfaces/SKILL.md | 56 + .../references/implementation.md | 22 + .../skills/fleet/locking-down-claude/SKILL.md | 123 + .claude/skills/fleet/looping-quality/SKILL.md | 47 + .../fleet/managing-pnpm-workspaces/SKILL.md | 22 + .../references/catalog-policy.md | 13 + .../skills/fleet/managing-worktrees/SKILL.md | 120 + .../fleet/managing-worktrees/lib/land.mts | 461 +++ .claude/skills/fleet/map/SKILL.md | 47 + .../fleet/migrating-rule-packs/SKILL.md | 145 + .../lib/run-migration.mts | 604 +++ .claude/skills/fleet/opening-pr/SKILL.md | 63 + .../optimizing-react-interfaces/SKILL.md | 43 + .../references/react-performance.md | 16 + .../fleet/optimizing-submodules/SKILL.md | 91 + .../skills/fleet/patching-findings/SKILL.md | 67 + .../patching-findings/references/procedure.md | 390 ++ .../fleet/plugging-promise-race/SKILL.md | 59 + .claude/skills/fleet/prose/SKILL.md | 127 + .../fleet/prose/references/conversational.md | 114 + .../skills/fleet/prose/references/examples.md | 69 + .../skills/fleet/prose/references/phrases.md | 154 + .../fleet/prose/references/structures.md | 201 + .claude/skills/fleet/pushing/SKILL.md | 42 + .../skills/fleet/refreshing-history/SKILL.md | 76 + .../skills/fleet/refreshing-history/run.mts | 186 + .../fleet/regenerating-patches/SKILL.md | 91 + .../lib/regen-patches.mts | 467 +++ .../skills/fleet/releasing-a-package/SKILL.md | 62 + .../fleet/rendering-chromium-to-png/SKILL.md | 68 + .../rendering-chromium-to-png/screenshot.mts | 152 + .../fleet/reordering-release-bump/SKILL.md | 66 + .../lib/reorder-bump.mts | 315 ++ .../skills/fleet/researching-recency/SKILL.md | 93 + .../fleet/researching-recency/reference.md | 106 + .claude/skills/fleet/reviewing-code/SKILL.md | 103 + .claude/skills/fleet/reviewing-code/run.mts | 885 +++++ .../fleet/reviewing-web-interfaces/SKILL.md | 33 + .../references/review-checklist.md | 48 + .claude/skills/fleet/running-test262/SKILL.md | 134 + .../skills/fleet/scanning-quality/SKILL.md | 129 + .../scanning-quality/scans/bundle-trim.md | 63 + .../scans/deadcode-removal.md | 126 + .../scanning-quality/scans/differential.md | 83 + .../scans/insecure-defaults.md | 59 + .../scans/variant-analysis.md | 61 + .../skills/fleet/scanning-security/SKILL.md | 73 + .claude/skills/fleet/scanning-vulns/SKILL.md | 55 + .../scanning-vulns/references/procedure.md | 268 ++ .claude/skills/fleet/setup-repo/SKILL.md | 177 + .../skills/fleet/squashing-history/SKILL.md | 76 + .../fleet/squashing-history/reference.md | 259 ++ .../skills/fleet/squashing-history/run.mts | 631 ++++ .../fleet/testing-web-interfaces/SKILL.md | 44 + .../references/test-layering.md | 15 + .claude/skills/fleet/threat-modeling/SKILL.md | 155 + .../skills/fleet/threat-modeling/bootstrap.md | 314 ++ .../skills/fleet/threat-modeling/interview.md | 192 + .../skills/fleet/threat-modeling/schema.md | 188 + .claude/skills/fleet/tidying-files/SKILL.md | 68 + .../fleet/tidying-files/lib/tidy-files.mts | 287 ++ .../fleet/tidying-rolldown-bundles/SKILL.md | 79 + .../lib/tidy-rolldown-bundles.mts | 309 ++ .../skills/fleet/tidying-worktrees/SKILL.md | 94 + .../tidying-worktrees/lib/tidy-worktrees.mts | 403 ++ .../skills/fleet/triaging-findings/SKILL.md | 175 + .../fixtures/canary-findings.json | 68 + .../triaging-findings/fixtures/vulnerable.js | 42 + .../triaging-findings/references/procedure.md | 621 ++++ .claude/skills/fleet/trimming-bundle/SKILL.md | 190 + .../fleet/trimming-bundle/lib/trim-loop.mts | 287 ++ .../skills/fleet/updating-coverage/SKILL.md | 93 + .claude/skills/fleet/updating-daily/SKILL.md | 49 + .../skills/fleet/updating-hooks-dry/SKILL.md | 62 + .../skills/fleet/updating-lockstep/SKILL.md | 86 + .../fleet/updating-lockstep/reference.md | 173 + .../skills/fleet/updating-pricing/SKILL.md | 89 + .../skills/fleet/updating-security/SKILL.md | 89 + .../fleet/updating-security/reference.md | 537 +++ .claude/skills/fleet/updating/SKILL.md | 92 + .../skills/fleet/updating/lib/discover.mts | 294 ++ .claude/skills/fleet/updating/reference.md | 183 + .claude/workflows/delegating-execution.js | 282 ++ .../workflows/reconcile-fleet-lockfiles.js | 155 + .claude/workflows/refresh-repo-map.js | 103 + .codex/config.toml | 30 + .config/fleet/.markdownlint-cli2.jsonc | 96 + .config/fleet/.prettierignore | 66 + .config/fleet/git-authors.json | 19 + .../_shared/freeform-readme-optin.mts | 46 + .../_shared/root-readme.mts | 25 + .../markdownlint-rules/_shared/rule-types.mts | 49 + .../_shared/wheelhouse-self-skip.mts | 40 + .../socket-details-summary-blank-line.mts | 85 + .../socket-gfm-alert-keywords.mts | 93 + .../socket-no-empty-changelog-sections.mts | 100 + .../socket-no-private-wheelhouse-leak.mts | 78 + .../socket-no-relative-sibling-script.mts | 66 + .../socket-readme-required-sections.mts | 85 + .../socket-readme-social-badges.mts | 59 + .../socket-task-list-syntax.mts | 84 + .config/fleet/oxfmtrc.json | 54 + .../oxlint-plugin/_shared/inject-import.mts | 153 + .../export-top-level-functions/index.mts | 176 + .../export-top-level-functions/package.json | 12 + .../fleet/guard-contract/index.mts | 101 + .../fleet/guard-contract/package.json | 12 + .../fleet/inclusive-language/index.mts | 438 +++ .../fleet/inclusive-language/package.json | 12 + .../fleet/max-file-lines/index.mts | 110 + .../fleet/max-file-lines/package.json | 12 + .../fleet/no-agent-brand-assumption/index.mts | 108 + .../no-agent-brand-assumption/package.json | 5 + .../no-bare-crypto-named-usage/index.mts | 319 ++ .../no-bare-crypto-named-usage/package.json | 12 + .../no-bare-spawn-childproc-access/index.mts | 143 + .../package.json | 12 + .../fleet/no-boolean-trap-param/index.mts | 92 + .../fleet/no-boolean-trap-param/package.json | 12 + .../fleet/no-cached-for-on-iterable/index.mts | 256 ++ .../no-cached-for-on-iterable/package.json | 12 + .../no-comment-glob-star-slash/README.md | 29 + .../no-comment-glob-star-slash/index.mts | 137 + .../no-comment-glob-star-slash/package.json | 12 + .../fleet/no-console-prefer-logger/index.mts | 131 + .../no-console-prefer-logger/package.json | 12 + .../fleet/no-default-export/index.mts | 156 + .../fleet/no-default-export/package.json | 12 + .../index.mts | 79 + .../package.json | 12 + .../no-eslint-biome-config-ref/index.mts | 127 + .../no-eslint-biome-config-ref/package.json | 12 + .../no-fetch-prefer-http-request/index.mts | 77 + .../no-fetch-prefer-http-request/package.json | 12 + .../no-file-scope-oxlint-disable/index.mts | 102 + .../no-file-scope-oxlint-disable/package.json | 12 + .../no-fileoverview-prefer-file/index.mts | 111 + .../no-fileoverview-prefer-file/package.json | 12 + .../fleet/no-inline-defer-async/index.mts | 181 + .../fleet/no-inline-defer-async/package.json | 12 + .../fleet/no-inline-logger/index.mts | 113 + .../fleet/no-inline-logger/package.json | 12 + .../fleet/no-lib-barrel-import/index.mts | 132 + .../fleet/no-lib-barrel-import/package.json | 12 + .../fleet/no-logger-newline-literal/index.mts | 573 +++ .../no-logger-newline-literal/package.json | 12 + .../no-malformed-bypass-marker/index.mts | 116 + .../no-malformed-bypass-marker/package.json | 12 + .../no-minified-bundler-output/index.mts | 214 ++ .../no-minified-bundler-output/package.json | 12 + .../no-module-eval-side-effects/README.md | 143 + .../no-module-eval-side-effects/index.mts | 528 +++ .../no-module-eval-side-effects/package.json | 12 + .../fleet/no-namespace-import/index.mts | 98 + .../fleet/no-namespace-import/package.json | 12 + .../oxlint-plugin/fleet/no-npx-dlx/index.mts | 203 ++ .../fleet/no-npx-dlx/package.json | 12 + .../fleet/no-options-param-mutation/index.mts | 148 + .../no-options-param-mutation/package.json | 12 + .../index.mts | 149 + .../package.json | 12 + .../fleet/no-placeholders/index.mts | 274 ++ .../fleet/no-placeholders/package.json | 12 + .../no-platform-specific-import/index.mts | 113 + .../no-platform-specific-import/package.json | 12 + .../fleet/no-private-path-in-source/index.mts | 122 + .../no-private-path-in-source/package.json | 12 + .../fleet/no-process-chdir/index.mts | 77 + .../fleet/no-process-chdir/package.json | 12 + .../no-process-cwd-in-scripts-hooks/index.mts | 93 + .../package.json | 12 + .../fleet/no-promise-race-in-loop/index.mts | 101 + .../no-promise-race-in-loop/package.json | 12 + .../fleet/no-promise-race/index.mts | 90 + .../fleet/no-promise-race/package.json | 12 + .../index.mts | 265 ++ .../package.json | 12 + .../fleet/no-source-sniffing/index.mts | 111 + .../fleet/no-source-sniffing/package.json | 12 + .../no-spawn-stream-double-consume/index.mts | 232 ++ .../package.json | 12 + .../fleet/no-spawnsync-code-field/index.mts | 160 + .../no-spawnsync-code-field/package.json | 12 + .../no-src-import-in-test-expect/index.mts | 256 ++ .../no-src-import-in-test-expect/package.json | 12 + .../fleet/no-status-emoji/index.mts | 202 + .../fleet/no-status-emoji/package.json | 12 + .../no-structured-clone-prefer-json/index.mts | 75 + .../package.json | 12 + .../no-sync-rm-in-test-lifecycle/index.mts | 167 + .../no-sync-rm-in-test-lifecycle/package.json | 12 + .../fleet/no-top-level-await/index.mts | 97 + .../fleet/no-top-level-await/package.json | 12 + .../fleet/no-underscore-identifier/index.mts | 142 + .../no-underscore-identifier/package.json | 12 + .../fleet/no-use-strict-in-esm/index.mts | 80 + .../fleet/no-use-strict-in-esm/package.json | 12 + .../fleet/no-vitest-empty-test/index.mts | 184 + .../fleet/no-vitest-empty-test/package.json | 12 + .../fleet/no-vitest-focused-tests/index.mts | 74 + .../no-vitest-focused-tests/package.json | 12 + .../fleet/no-vitest-identical-title/index.mts | 141 + .../no-vitest-identical-title/package.json | 12 + .../fleet/no-vitest-skipped-tests/index.mts | 114 + .../no-vitest-skipped-tests/package.json | 12 + .../no-vitest-standalone-expect/index.mts | 135 + .../no-vitest-standalone-expect/package.json | 12 + .../fleet/no-which-for-local-bin/index.mts | 115 + .../fleet/no-which-for-local-bin/package.json | 12 + .../normalize-path-before-match/index.mts | 308 ++ .../normalize-path-before-match/package.json | 12 + .../optional-explicit-undefined/index.mts | 186 + .../optional-explicit-undefined/package.json | 12 + .../fleet/options-null-proto/index.mts | 386 ++ .../fleet/options-null-proto/package.json | 12 + .../fleet/options-param-naming/index.mts | 271 ++ .../fleet/options-param-naming/package.json | 12 + .../personal-path-placeholders/index.mts | 231 ++ .../personal-path-placeholders/package.json | 12 + .../fleet/prefer-async-spawn/index.mts | 207 ++ .../fleet/prefer-async-spawn/package.json | 12 + .../fleet/prefer-cached-for-loop/index.mts | 509 +++ .../fleet/prefer-cached-for-loop/package.json | 12 + .../fleet/prefer-ellipsis-char/index.mts | 126 + .../fleet/prefer-ellipsis-char/package.json | 12 + .../fleet/prefer-env-as-boolean/index.mts | 191 + .../fleet/prefer-env-as-boolean/package.json | 12 + .../prefer-error-message-helper/index.mts | 265 ++ .../prefer-error-message-helper/package.json | 12 + .../fleet/prefer-error-message/index.mts | 128 + .../fleet/prefer-error-message/package.json | 12 + .../fleet/prefer-exists-sync/index.mts | 187 + .../fleet/prefer-exists-sync/package.json | 12 + .../fleet/prefer-find-repo-root/index.mts | 108 + .../fleet/prefer-find-repo-root/package.json | 12 + .../prefer-find-up-package-json/index.mts | 114 + .../prefer-find-up-package-json/package.json | 12 + .../prefer-function-declaration/index.mts | 267 ++ .../prefer-function-declaration/package.json | 12 + .../fleet/prefer-mock-import/index.mts | 88 + .../fleet/prefer-mock-import/package.json | 12 + .../prefer-node-builtin-imports/index.mts | 429 +++ .../prefer-node-builtin-imports/package.json | 12 + .../prefer-node-modules-dot-cache/index.mts | 248 ++ .../package.json | 12 + .../prefer-non-capturing-group/index.mts | 251 ++ .../prefer-non-capturing-group/package.json | 12 + .../fleet/prefer-normalize-path/index.mts | 110 + .../fleet/prefer-normalize-path/package.json | 12 + .../fleet/prefer-optional-chain/index.mts | 138 + .../fleet/prefer-optional-chain/package.json | 12 + .../fleet/prefer-pure-call-form/index.mts | 139 + .../fleet/prefer-pure-call-form/package.json | 12 + .../fleet/prefer-safe-delete/index.mts | 196 + .../fleet/prefer-safe-delete/package.json | 12 + .../prefer-separate-type-import/index.mts | 200 + .../prefer-separate-type-import/package.json | 12 + .../fleet/prefer-shell-win32/index.mts | 124 + .../fleet/prefer-shell-win32/package.json | 12 + .../prefer-spawn-over-execsync/index.mts | 140 + .../prefer-spawn-over-execsync/package.json | 12 + .../prefer-stable-external-semver/index.mts | 94 + .../package.json | 12 + .../fleet/prefer-stable-self-import/index.mts | 166 + .../prefer-stable-self-import/package.json | 12 + .../fleet/prefer-static-type-import/index.mts | 146 + .../prefer-static-type-import/package.json | 12 + .../fleet/prefer-typebox-schema/index.mts | 72 + .../fleet/prefer-typebox-schema/package.json | 12 + .../prefer-undefined-over-null/index.mts | 455 +++ .../prefer-undefined-over-null/package.json | 12 + .../prefer-windows-test-helpers/index.mts | 228 ++ .../prefer-windows-test-helpers/package.json | 12 + .../fleet/require-async-iife-entry/index.mts | 182 + .../require-async-iife-entry/package.json | 12 + .../fleet/require-regex-comment/index.mts | 324 ++ .../fleet/require-regex-comment/package.json | 15 + .../require-vitest-globals-import/index.mts | 100 + .../package.json | 12 + .../fleet/socket-api-token-env/index.mts | 171 + .../fleet/socket-api-token-env/package.json | 12 + .../fleet/sort-array-literals/index.mts | 138 + .../fleet/sort-array-literals/package.json | 12 + .../fleet/sort-boolean-chains/index.mts | 211 ++ .../fleet/sort-boolean-chains/package.json | 12 + .../sort-equality-disjunctions/index.mts | 242 ++ .../sort-equality-disjunctions/package.json | 12 + .../fleet/sort-named-imports/index.mts | 162 + .../fleet/sort-named-imports/package.json | 12 + .../sort-object-literal-properties/index.mts | Bin 0 -> 11504 bytes .../package.json | 12 + .../fleet/sort-regex-alternations/index.mts | 322 ++ .../sort-regex-alternations/package.json | 12 + .../fleet/sort-set-args/index.mts | 120 + .../fleet/sort-set-args/package.json | 12 + .../fleet/sort-source-methods/index.mts | 407 +++ .../fleet/sort-source-methods/package.json | 12 + .../index.mts | 128 + .../package.json | 12 + .config/fleet/oxlint-plugin/index.mts | 223 ++ .../oxlint-plugin/lib/comment-checks.mts | 35 + .../oxlint-plugin/lib/comment-markers.mts | 153 + .../fleet/oxlint-plugin/lib/comparators.mts | 38 + .../oxlint-plugin/lib/detect-source-type.mts | 707 ++++ .../fleet/oxlint-plugin/lib/fleet-paths.mts | 70 + .../fleet/oxlint-plugin/lib/iterable-kind.mts | 391 ++ .../fleet/oxlint-plugin/lib/logical-chain.mts | 23 + .../fleet/oxlint-plugin/lib/rule-tester.mts | 581 +++ .../fleet/oxlint-plugin/lib/rule-types.mts | 34 + .../lib/runtime-feature-floors.mts | 132 + .config/fleet/oxlint-plugin/lib/test-file.mts | 13 + .../oxlint-plugin/lib/vitest-fn-call.mts | 310 ++ .config/fleet/oxlint-plugin/package.json | 13 + .config/fleet/oxlint.config.mts | 126 + .config/fleet/oxlintrc.json | 264 ++ .config/fleet/pnpm-workspace.fleet.yaml | 91 + .config/fleet/rolldown/hook-bundle.config.mts | 96 + .config/fleet/sfw-bypass-list.txt | 150 + .config/fleet/taze.config.mts | 32 + .config/fleet/tsconfig.base.json | 30 + .config/fleet/tsconfig.check.base.json | 13 + .../fleet/vitest.coverage.fleet.config.mts | 138 + .config/lockstep.schema.json | 463 +++ .editorconfig | 23 + .git-hooks/_shared/ai-attribution.mts | 48 + .git-hooks/_shared/commit-format.mts | 142 + .git-hooks/_shared/commit-subject.mts | 56 + .git-hooks/_shared/cross-repo.mts | 111 + .git-hooks/_shared/external-issue-ref.mts | 108 + .git-hooks/_shared/git-identity.mts | 137 + .git-hooks/_shared/helpers.mts | 1803 +++++++++ .git-hooks/_shared/isolate-git-env.mts | 73 + .git-hooks/_shared/logger-leaks.mts | 189 + .git-hooks/_shared/personal-path.mts | 56 + .git-hooks/_shared/resolve-node.sh | 47 + .git-hooks/commit-msg | 21 + .git-hooks/fleet/commit-msg | 21 + .git-hooks/fleet/commit-msg.mts | 275 ++ .git-hooks/fleet/pre-commit | 155 + .git-hooks/fleet/pre-commit.mts | 630 ++++ .git-hooks/fleet/pre-push | 25 + .git-hooks/fleet/pre-push.mts | 933 +++++ .git-hooks/post-commit | 31 + .git-hooks/pre-commit | 24 + .git-hooks/pre-push | 27 + .gitattributes | 95 + .../actions/fleet/cache-pnpm-store/action.yml | 98 + .github/actions/fleet/checkout/action.yml | 215 ++ .../fleet/cleanup-git-signing/action.yml | 28 + .github/actions/fleet/debug/action.yml | 49 + .../fleet/github-pr-app-token/action.yml | 50 + .../mint-app-installation-token.mjs | 228 ++ .../fleet/github-release-app-token/action.yml | 50 + .../mint-app-installation-token.mjs | 228 ++ .../actions/fleet/github-release/action.yml | 128 + .../fleet/github-status-check/action.yml | 133 + .github/actions/fleet/install/action.yml | 221 ++ .github/actions/fleet/lib/external-tools.json | 310 ++ .github/actions/fleet/lib/install-tool.mjs | 187 + .github/actions/fleet/lib/jq.mjs | 84 + .github/actions/fleet/lib/platform.mjs | 58 + .github/actions/fleet/lib/semver.mjs | 59 + .github/actions/fleet/run-script/action.yml | 68 + .../fleet/setup-and-install/action.yml | 114 + .../fleet/setup-git-signing/action.yml | 55 + .../fleet/setup-go-toolchain/action.yml | 132 + .github/actions/fleet/setup/action.yml | 708 ++++ .github/dependabot.yml | 12 + .../_local-not-for-reuse-get-green.yml | 46 + .github/workflows/ci.yml | 131 + .github/workflows/get-green.lock.yml | 1732 +++++++++ .github/workflows/get-green.md | 130 + .github/workflows/get-green.yml | 107 + .github/workflows/github-release.yml | 71 + .github/workflows/npm-publish.yml | 47 + .github/workflows/prune-workflow-runs.yml | 76 + .github/workflows/publish-npm.yml | 487 +++ .../weekly-update-non-gh-aw.yml.disabled | 63 + .github/workflows/weekly-update.lock.yml | 1798 +++++++++ .github/workflows/weekly-update.md | 158 + .gitignore | 90 + .gitmodules | 2 + .kimi-code/mcp.json | 38 + .mcp.json | 35 + .node-version | 1 + .npmrc | 35 + assets/repo/badges/coverage.svg | 1 + bootstrap/fleet.mjs | 1560 ++++++++ bootstrap/prepare.mts | 257 ++ bootstrap/src/dispatch-wiring.mts | 151 + bootstrap/src/fleet.mts | 437 +++ bootstrap/src/helpers.mts | 421 +++ bootstrap/src/install.mts | 498 +++ bootstrap/src/lockstep.mts | 332 ++ bootstrap/src/resolve.mts | 139 + bootstrap/src/settings.mts | 229 ++ bootstrap/src/status.mts | 113 + .../fleet/adversarial-self-review.md | 50 + docs/agents.md/fleet/agent-delegation.md | 286 ++ docs/agents.md/fleet/agents-and-skills.md | 66 + docs/agents.md/fleet/binary-vs-napi-naming.md | 84 + docs/agents.md/fleet/bypass-phrases.md | 91 + docs/agents.md/fleet/c8-ignore-directives.md | 92 + .../fleet/cascade-file-classification.md | 85 + docs/agents.md/fleet/cascade-is-a-unit.md | 45 + docs/agents.md/fleet/cascaded-hook-catalog.md | 205 ++ docs/agents.md/fleet/code-is-law.md | 39 + docs/agents.md/fleet/code-style.md | 113 + docs/agents.md/fleet/commit-cadence-format.md | 118 + docs/agents.md/fleet/commit-signing.md | 82 + docs/agents.md/fleet/conformance-runners.md | 202 + docs/agents.md/fleet/cross-tool-agents.md | 110 + docs/agents.md/fleet/database.md | 118 + docs/agents.md/fleet/delegating-execution.md | 70 + docs/agents.md/fleet/diagnosing-bugs.md | 73 + docs/agents.md/fleet/disabled-seam-pattern.md | 31 + docs/agents.md/fleet/drift-watch.md | 160 + docs/agents.md/fleet/error-messages.md | 164 + docs/agents.md/fleet/export-and-no-any.md | 66 + docs/agents.md/fleet/fable-fallback.md | 63 + docs/agents.md/fleet/file-size.md | 35 + docs/agents.md/fleet/fleet-doctor.md | 91 + docs/agents.md/fleet/gh-token-hygiene.md | 173 + .../agents.md/fleet/git-config-write-guard.md | 78 + .../fleet/github-token-limitations.md | 20 + docs/agents.md/fleet/golden-fixtures.md | 49 + docs/agents.md/fleet/history-rewrites.md | 108 + docs/agents.md/fleet/hook-bundle.md | 55 + docs/agents.md/fleet/hook-registry.md | 254 ++ docs/agents.md/fleet/immutable-releases.md | 85 + docs/agents.md/fleet/inclusive-language.md | 34 + .../fleet/judgment-and-self-evaluation.md | 96 + docs/agents.md/fleet/lint-rules.md | 81 + docs/agents.md/fleet/lockstep.md | 122 + .../fleet/max-file-lines-hard-cap-only.md | 29 + docs/agents.md/fleet/memory-codification.md | 71 + docs/agents.md/fleet/module-naming.md | 30 + docs/agents.md/fleet/multi-ecosystem-soak.md | 81 + docs/agents.md/fleet/multi-janus-mcp-shim.md | 60 + docs/agents.md/fleet/no-disable-lint-rule.md | 95 + .../fleet/no-live-network-in-tests.md | 76 + docs/agents.md/fleet/no-local-fork.md | 118 + .../fleet/normalize-path-before-match.md | 54 + docs/agents.md/fleet/options-object.md | 52 + .../fleet/parallel-claude-sessions.md | 212 ++ docs/agents.md/fleet/parser-comments.md | 174 + docs/agents.md/fleet/path-hygiene.md | 38 + docs/agents.md/fleet/plan-storage.md | 113 + docs/agents.md/fleet/plugin-cache-patches.md | 110 + docs/agents.md/fleet/pnpm-patching.md | 60 + docs/agents.md/fleet/pr-review-comments.md | 124 + docs/agents.md/fleet/precommit-time-gate.md | 45 + docs/agents.md/fleet/prompt-injection.md | 223 ++ .../fleet/prose-style-and-doctrine.md | 79 + .../agents.md/fleet/public-surface-hygiene.md | 98 + docs/agents.md/fleet/pull-request-target.md | 36 + docs/agents.md/fleet/push-policy.md | 58 + docs/agents.md/fleet/release-vs-cascade.md | 57 + docs/agents.md/fleet/repo-map.md | 99 + docs/agents.md/fleet/researching-recency.md | 48 + .../agents.md/fleet/runtime-feature-floors.md | 72 + .../fleet/runtime-state-and-caches.md | 50 + docs/agents.md/fleet/rust-members.md | 46 + docs/agents.md/fleet/script-aggregation.md | 38 + docs/agents.md/fleet/security-stack.md | 149 + .../fleet/shared-workflow-cascade.md | 94 + .../agents.md/fleet/single-source-of-truth.md | 54 + docs/agents.md/fleet/skill-model-routing.md | 125 + docs/agents.md/fleet/socket-bypass-markers.md | 43 + docs/agents.md/fleet/sorting.md | 168 + docs/agents.md/fleet/stop-the-bleeding.md | 27 + docs/agents.md/fleet/stranded-cascades.md | 103 + docs/agents.md/fleet/telemetry-lockdown.md | 78 + docs/agents.md/fleet/test-layout.md | 43 + .../fleet/test-scripts-defer-to-mts.md | 58 + docs/agents.md/fleet/thin-distribution.md | 128 + docs/agents.md/fleet/token-hygiene.md | 105 + docs/agents.md/fleet/token-minification.md | 18 + docs/agents.md/fleet/token-spend.md | 25 + docs/agents.md/fleet/tooling.md | 261 ++ docs/agents.md/fleet/untracked-by-default.md | 61 + docs/agents.md/fleet/upstream-references.md | 62 + .../fleet/verify-state-before-acting.md | 51 + docs/agents.md/fleet/version-bumps.md | 135 + docs/agents.md/fleet/vocabulary.md | 29 + .../agents.md/fleet/weekly-update-fallback.md | 56 + docs/agents.md/fleet/windows-gotchas.md | 128 + .../agents.md/fleet/workflow-run-retention.md | 49 + docs/agents.md/fleet/worktree-hygiene.md | 98 + docs/agents.md/fleet/writing-skills-well.md | 107 + docs/references/sfw-local-install.md | 156 + opencode.json | 43 + pnpm-workspace.yaml | 116 + scripts/fleet/_shared/active-run-marker.mts | 108 + scripts/fleet/_shared/check-steps-hooks.mts | 250 ++ scripts/fleet/_shared/check-steps-paths.mts | 363 ++ scripts/fleet/_shared/check-steps-release.mts | 237 ++ scripts/fleet/_shared/check-steps.mts | 89 + scripts/fleet/_shared/format-scope.mts | 243 ++ scripts/fleet/_shared/git-porcelain.mts | 96 + scripts/fleet/_shared/gitmodules.mts | 106 + scripts/fleet/_shared/is-main-module.mts | 32 + scripts/fleet/_shared/repo-checks.mts | 22 + scripts/fleet/_shared/repo-setup.mts | 24 + scripts/fleet/_shared/run-main.mts | 60 + scripts/fleet/_shared/scope-flags.mts | 48 + scripts/fleet/_shared/tar-executable.mts | 14 + scripts/fleet/_shared/tracked-globs.mts | 126 + scripts/fleet/agent-ci-skip-locks.mts | 115 + scripts/fleet/ai-backends-status.mts | 198 + scripts/fleet/ai-codify/cli.mts | 274 ++ scripts/fleet/ai-codify/codify-guidance.mts | 166 + scripts/fleet/ai-lint-fix.mts | 252 ++ scripts/fleet/ai-lint-fix/claude.mts | 47 + scripts/fleet/ai-lint-fix/health.mts | 150 + scripts/fleet/ai-lint-fix/oxlint-json.mts | 148 + scripts/fleet/ai-lint-fix/prompt.mts | 150 + scripts/fleet/ai-lint-fix/rule-guidance.mts | 268 ++ scripts/fleet/audit-transcript.mts | 462 +++ .../fleet/auditing-history/lib/patch-id.mts | 99 + scripts/fleet/auditing-history/lib/types.mts | 112 + scripts/fleet/build-hook-bundle.mts | 150 + scripts/fleet/build-hook-snapshot.mts | 173 + scripts/fleet/build-snapshot-launcher.mts | 268 ++ scripts/fleet/bump.mts | 376 ++ scripts/fleet/cargo-publish.mts | 187 + scripts/fleet/check.mts | 244 ++ .../fleet/check/action-pins-are-current.mts | 620 ++++ scripts/fleet/check/actions-are-segmented.mts | 86 + .../check/agent-ci-skip-locks-is-guarded.mts | 82 + .../check/ai-spawns-have-paired-effort.mts | 450 +++ .../check/app-token-minters-are-identical.mts | 133 + scripts/fleet/check/app-tokens-are-scoped.mts | 185 + .../fleet/check/backend-routing-is-legal.mts | 152 + .../baseline-catalog-deps-are-covered.mts | 160 + .../fleet/check/brew-install-is-pinned.mts | 218 ++ .../check/brew-supply-chain-is-hardened.mts | 54 + scripts/fleet/check/bundle-is-installable.mts | 76 + .../check/cargo-soak-config-is-current.mts | 185 + .../cascaded-fleet-trees-have-no-tests.mts | 135 + .../check/cdn-allowlist-is-respected.mts | 99 + .../check/changelog-is-commit-derived.mts | 216 ++ .../check/check-names-are-assertions.mts | 144 + .../check/check-registrations-resolve.mts | 158 + scripts/fleet/check/ci-local-is-canonical.mts | 107 + .../fleet/check/claude-config-is-hardened.mts | 98 + .../fleet/check/claude-dirs-are-segmented.mts | 291 ++ .../check/claude-md-citations-resolve.mts | 182 + .../check/claude-md-rules-are-enforced.mts | 466 +++ .../check/claude-md-rules-are-informative.mts | 183 + ...onvention-guards-consult-fleet-context.mts | 161 + .../fleet/check/coverage-badge-is-current.mts | 142 + .../check/dedup-patches-are-justified.mts | 118 + .../fleet/check/dependencies-are-deduped.mts | 546 +++ .../design-skill-cluster-is-connected.mts | 93 + .../dispatch-matchers-cover-hook-tools.mts | 271 ++ .../fleet/check/dispatch-table-is-current.mts | 111 + .../fleet/check/doc-references-resolve.mts | 213 ++ .../egress-allowlist-is-gh-aw-subset.mts | 183 + .../check/enforcers-have-thorough-tests.mts | 373 ++ .../check/entry-scripts-are-fail-soft.mts | 137 + .../check/env-kill-switches-are-absent.mts | 163 + .../check/error-messages-are-thorough.mts | 204 ++ .../fleet/check/external-tools-are-valid.mts | 125 + .../check/external-tools-match-wheelhouse.mts | 194 + .../check/fable-spawns-have-opus-fallback.mts | 348 ++ .../fleet/check/fleet-soak-exclude-parity.mts | 477 +++ .../check/generated-globs-are-consistent.mts | 179 + .../fleet/check/gh-aw-locks-are-current.mts | 127 + .../gh-aw-workflow-models-are-canonical.mts | 113 + scripts/fleet/check/go-deps-are-soaked.mts | 136 + .../golden-fixtures-are-named-golden.mts | 80 + .../headroom-is-telemetry-locked-down.mts | 102 + .../check/headroom-pin-is-consistent.mts | 170 + .../check/headroom-proxy-is-lossless.mts | 87 + .../fleet/check/hook-dirs-are-not-husks.mts | 114 + .../fleet/check/hook-imports-are-declared.mts | 291 ++ .../check/hook-main-is-entrypoint-guarded.mts | 250 ++ .../fleet/check/hook-names-are-accurate.mts | 165 + .../fleet/check/hook-registry-is-current.mts | 153 + .../fleet/check/hook-snapshot-is-wired.mts | 244 ++ .../hooks-have-no-guard-nudge-overlap.mts | 177 + .../check/lint-configs-protect-vendored.mts | 153 + .../check/linters-are-oxlint-oxfmt-only.mts | 87 + scripts/fleet/check/llms-txt-is-current.mts | 69 + .../fleet/check/lock-step-headers-match.mts | 382 ++ .../fleet/check/lock-step-refs-resolve.mts | 328 ++ .../check/markdown-doc-headers-are-plain.mts | 142 + .../markdown-filenames-are-canonical.mts | 88 + .../check/mcp-client-configs-are-current.mts | 94 + scripts/fleet/check/memories-are-codified.mts | 160 + .../check/mutating-skills-have-model.mts | 123 + .../fleet/check/name-rename-is-complete.mts | 243 ++ ...npmrc-socket-soak-excludes-are-derived.mts | 148 + scripts/fleet/check/oxlint-plugin-loads.mts | 89 + .../fleet/check/pack-contents-are-clean.mts | 232 ++ .../package-deps-have-no-link-protocol.mts | 137 + .../check/package-files-are-allowlisted.mts | 466 +++ ...ackage-manager-auto-update-is-disabled.mts | 60 + .../package-manager-node-is-continuous.mts | 88 + .../check/package-manager-pins-are-synced.mts | 112 + .../path-tools-are-at-pinned-version.mts | 305 ++ scripts/fleet/check/paths-are-canonical.mts | 179 + .../paths-are-normalized-before-match.mts | 226 ++ scripts/fleet/check/paths/allowlist.mts | 165 + scripts/fleet/check/paths/exempt.mts | 38 + scripts/fleet/check/paths/rules.mts | 58 + scripts/fleet/check/paths/scan-code.mts | 249 ++ scripts/fleet/check/paths/scan-script.mts | 93 + scripts/fleet/check/paths/scan-workflow.mts | 149 + scripts/fleet/check/paths/state.mts | 25 + scripts/fleet/check/paths/types.mts | 24 + scripts/fleet/check/paths/walk.mts | 48 + .../platform-tails-match-naming-domain.mts | 451 +++ .../check/pnpm-run-citations-resolve.mts | 247 ++ .../check/precommit-steps-are-bounded.mts | 129 + .../prettierignore-globs-are-anchored.mts | 109 + .../fleet/check/pricing-data-is-current.mts | 180 + .../fleet/check/private-paths-are-absent.mts | 273 ++ .../fleet/check/provenance-is-attested.mts | 197 + .../fleet/check/public-files-are-exported.mts | 314 ++ .../check/publish-config-is-hardened.mts | 383 ++ ...publishable-version-is-prerelease-hint.mts | 145 + .../published-packages-have-files-field.mts | 175 + .../check/release-and-cascade-are-paired.mts | 175 + ...blish-scripts-are-conventionally-named.mts | 239 ++ ...esearching-recency-contract-is-current.mts | 100 + .../fleet/check/review-stages-are-ordered.mts | 119 + .../check/root-scripts-are-segregated.mts | 50 + .../check/rule-citations-are-generic.mts | 167 + scripts/fleet/check/scanner-parity.mts | 503 +++ scripts/fleet/check/script-paths-resolve.mts | 194 + scripts/fleet/check/setup-is-prompt-less.mts | 433 +++ .../check/shared-hook-helpers-are-used.mts | 215 ++ .../fleet/check/skill-system-is-coherent.mts | 76 + .../fleet/check/skills-are-well-formed.mts | 275 ++ .../check/skillspector-pin-is-consistent.mts | 184 + .../fleet/check/soak-excludes-have-dates.mts | 357 ++ .../fleet/check/soak-time-is-consistent.mts | 114 + .../check/source-is-windows-portable.mts | 383 ++ .../check/sparkle-auto-update-is-disabled.mts | 74 + .../fleet/check/stable-aliases-match-base.mts | 98 + .../check/subagent-status-doc-is-current.mts | 147 + .../submodules-are-sparse-or-annotated.mts | 102 + .../check/telemetry-deps-are-reviewed.mts | 70 + .../fleet/check/telemetry-env-is-disabled.mts | 77 + .../fleet/check/test-scripts-are-deferred.mts | 171 + .../fleet/check/tests-are-mirror-named.mts | 315 ++ .../tracked-files-are-within-size-cap.mts | 263 ++ .../fleet/check/tracked-symlinks-are-safe.mts | 113 + .../check/trust-gates-are-not-weakened.mts | 230 ++ ...m-submodules-are-shallow-single-branch.mts | 109 + .../fleet/check/uv-lockfiles-are-current.mts | 72 + .../fleet/check/vite-is-rolldown-native.mts | 150 + .../weekly-update-fallback-is-disabled.mts | 97 + .../workflow-scripts-are-explicit-scope.mts | 128 + scripts/fleet/codify-rule.mts | 210 ++ scripts/fleet/codify-scan/inventory.mts | 109 + scripts/fleet/compress.mts | 99 + scripts/fleet/consolidate-commits.mts | 360 ++ scripts/fleet/constants/bot-identity.mts | 40 + scripts/fleet/constants/brew-tap-pins.mts | 33 + scripts/fleet/constants/generated-globs.mts | 81 + scripts/fleet/constants/model-pricing.json | 141 + scripts/fleet/constants/npm-registry.mts | 19 + scripts/fleet/constants/plugin-patch.mts | 35 + scripts/fleet/constants/soak-excludes.mts | 45 + scripts/fleet/constants/soak.mts | 21 + scripts/fleet/constants/socket-scopes.mts | 214 ++ scripts/fleet/cover.mts | 943 +++++ scripts/fleet/cover/discovery.mts | 156 + scripts/fleet/crate-release-sha.mts | 291 ++ scripts/fleet/create-release.mts | 326 ++ scripts/fleet/depot-ci.mts | 250 ++ scripts/fleet/doctor.mts | 677 ++++ scripts/fleet/estimate-ai-cost.mts | 331 ++ scripts/fleet/external-tools/_shared.mts | 190 + scripts/fleet/external-tools/add.mts | 337 ++ scripts/fleet/external-tools/delete.mts | 136 + scripts/fleet/external-tools/edit.mts | 285 ++ scripts/fleet/external-tools/github.mts | 405 ++ scripts/fleet/external-tools/list.mts | 84 + scripts/fleet/external-tools/prune.mts | 182 + scripts/fleet/external-tools/schema.mts | 116 + scripts/fleet/external-tools/show.mts | 92 + scripts/fleet/external-tools/update.mts | 618 ++++ scripts/fleet/fetch-fleet-bundle.mts | 218 ++ scripts/fleet/fix.mts | 212 ++ scripts/fleet/fmt-rust.mts | 146 + scripts/fleet/format.mts | 128 + scripts/fleet/fsync-dist.mts | 53 + scripts/fleet/gen-agents-skills-mirror.mts | 331 ++ scripts/fleet/gen-gitmodules-hash.mts | 405 ++ scripts/fleet/gen-harness-adapters.mts | 193 + scripts/fleet/gh-heartbeat.mts | 94 + .../fleet/git-partial-submodule-commands.mts | 441 +++ .../fleet/git-partial-submodule-internal.mts | 231 ++ scripts/fleet/git-partial-submodule.mts | 150 + scripts/fleet/go-publish.mts | 440 +++ scripts/fleet/install-claude-plugins.mts | 906 +++++ scripts/fleet/install-git-hooks.mts | 102 + scripts/fleet/install-sfw.mts | 320 ++ scripts/fleet/janus-multi-mcp.mts | 320 ++ scripts/fleet/janus-multi-runner.mts | 95 + scripts/fleet/janus-multi-workspace.mts | 119 + scripts/fleet/janus.mts | 149 + scripts/fleet/land-work.mts | 438 +++ scripts/fleet/land-work/ai-summary.mts | 168 + scripts/fleet/land-work/message.mts | 98 + scripts/fleet/lib/attribution.mts | 38 + scripts/fleet/lib/backup-branch.mts | 34 + scripts/fleet/lib/changelog.mts | 473 +++ scripts/fleet/lib/claude-md-trim.mts | 204 ++ scripts/fleet/lib/commit-via-github-api.mts | 112 + scripts/fleet/lib/coverage-badge.mts | 313 ++ .../lib/delegating-execution/prompts.mts | 113 + .../fleet/lib/delegating-execution/route.mts | 108 + .../fleet/lib/delegating-execution/types.mts | 15 + scripts/fleet/lib/doctor/brewfile-gap.mts | 80 + scripts/fleet/lib/doctor/catalog-gap.mts | 255 ++ scripts/fleet/lib/doctor/git-gap.mts | 248 ++ .../fleet/lib/doctor/lockfile-catalog-gap.mts | 159 + scripts/fleet/lib/doctor/pin-shadow-gap.mts | 141 + scripts/fleet/lib/doctor/secret-scan-gap.mts | 185 + scripts/fleet/lib/doctor/soak-gap.mts | 106 + .../fleet/lib/doctor/stranded-cascade-gap.mts | 153 + scripts/fleet/lib/enforcer-inventory.mts | 153 + scripts/fleet/lib/ensure-node.mts | 166 + scripts/fleet/lib/external-tools-schema.mts | 201 + scripts/fleet/lib/gh-aw-action-pin-soak.mts | 246 ++ scripts/fleet/lib/github-bots.mts | 54 + scripts/fleet/lib/known-models.mts | 100 + scripts/fleet/lib/llms-txt/ai.mts | 166 + scripts/fleet/lib/llms-txt/extract.mts | 147 + scripts/fleet/lib/llms-txt/render.mts | 168 + scripts/fleet/lib/llms-txt/sections.mts | 340 ++ scripts/fleet/lib/llms-txt/slots.mts | 111 + scripts/fleet/lib/llms-txt/types.mts | 110 + scripts/fleet/lib/oxlint-plugin-loads.mts | 102 + scripts/fleet/lib/security-report.mts | 126 + .../fleet/lib/self-referential-symlink.mts | 84 + scripts/fleet/lib/skill-system.mts | 125 + scripts/fleet/lib/stable-alias.mts | 182 + scripts/fleet/lib/taze-output.mts | 26 + scripts/fleet/lib/telemetry-scan.mts | 185 + scripts/fleet/lib/verify-release-hashes.mts | 295 ++ scripts/fleet/lib/workspace-yaml.mts | 248 ++ scripts/fleet/lint-actions.mts | 75 + scripts/fleet/lint-github-settings-detect.mts | 306 ++ .../fleet/lint-github-settings-evaluate.mts | 362 ++ scripts/fleet/lint-github-settings-types.mts | 153 + scripts/fleet/lint-github-settings.mts | 348 ++ scripts/fleet/lint-pr-comment.mts | 481 +++ scripts/fleet/lint.mts | 550 +++ scripts/fleet/lockstep-emit-schema.mts | 6 + scripts/fleet/lockstep-schema.mts | 6 + scripts/fleet/lockstep.mts | 13 + scripts/fleet/lockstep/auto-bump.mts | 798 ++++ scripts/fleet/lockstep/checks.mts | 467 +++ scripts/fleet/lockstep/cli.mts | 149 + scripts/fleet/lockstep/emit-schema.mts | 59 + scripts/fleet/lockstep/git.mts | 96 + scripts/fleet/lockstep/manifest.mts | 145 + scripts/fleet/lockstep/report.mts | 129 + scripts/fleet/lockstep/scan.mts | 90 + scripts/fleet/lockstep/schema.mts | 459 +++ scripts/fleet/lockstep/types.mts | 81 + scripts/fleet/make-coverage-badge.mts | 116 + scripts/fleet/make-hook-dispatch.mts | 476 +++ scripts/fleet/make-llms-txt.mts | 181 + scripts/fleet/make-package-exports.mts | 497 +++ scripts/fleet/make-repo-map.mts | 361 ++ scripts/fleet/mcp-config.mts | 352 ++ scripts/fleet/normalize-backup-branches.mts | 111 + scripts/fleet/npm-publish.mts | 233 ++ .../collect-submodule-consumers.mts | 294 ++ scripts/fleet/patching-findings/cli.mts | 87 + .../patching-findings/lib/patch-parse.mts | 211 ++ scripts/fleet/paths.mts | 383 ++ .../scripts/lib/read-stdin-sync.mjs | 39 + .../codex-1.0.1-stdin-eagain.patch | 79 + scripts/fleet/power-state.mts | 178 + scripts/fleet/pre-push-gate.mts | 109 + scripts/fleet/prepare.mts | 49 + scripts/fleet/prune-workflow-runs.mts | 308 ++ scripts/fleet/publish-infra/cargo/approve.mts | 161 + scripts/fleet/publish-infra/cargo/bump.mts | 340 ++ .../fleet/publish-infra/cargo/placeholder.mts | 323 ++ .../fleet/publish-infra/cargo/registry.mts | 132 + scripts/fleet/publish-infra/cargo/shared.mts | 137 + scripts/fleet/publish-infra/cargo/staged.mts | 210 ++ scripts/fleet/publish-infra/go/shared.mts | 308 ++ scripts/fleet/publish-infra/npm/approve.mts | 410 +++ scripts/fleet/publish-infra/npm/bump.mts | 118 + .../fleet/publish-infra/npm/pin-readme.mts | 120 + .../fleet/publish-infra/npm/placeholder.mts | 323 ++ scripts/fleet/publish-infra/npm/registry.mts | 156 + scripts/fleet/publish-infra/npm/scan.mts | 87 + scripts/fleet/publish-infra/npm/shared.mts | 220 ++ scripts/fleet/publish-infra/npm/staged.mts | 412 +++ scripts/fleet/publish-infra/reconcile.mts | 141 + scripts/fleet/publish-infra/release.mts | 199 + .../fleet/publish-infra/remote-dispatch.mts | 117 + .../publish-infra/remote-github-release.mts | 139 + .../publish-infra/remote-npm-publish.mts | 130 + scripts/fleet/publish-infra/shared.mts | 179 + scripts/fleet/publish-pipeline.mts | 185 + scripts/fleet/publish-shared.mts | 10 + scripts/fleet/registry-publish-date.mts | 55 + scripts/fleet/release-pipeline.mts | 351 ++ .../fleet/release-pipeline/gate-runners.mts | 395 ++ .../release-pipeline/release-runners.mts | 259 ++ scripts/fleet/release-pipeline/seams.mts | 92 + scripts/fleet/release-pipeline/stages.mts | 239 ++ scripts/fleet/release-pipeline/state.mts | 185 + scripts/fleet/release-pipeline/summary.mts | 106 + scripts/fleet/researching-recency/cli.mts | 206 ++ .../fleet/researching-recency/lib/dedupe.mts | 173 + .../fleet/researching-recency/lib/fetch.mts | 148 + .../fleet/researching-recency/lib/markers.mts | 40 + .../fleet/researching-recency/lib/plan.mts | 232 ++ .../fleet/researching-recency/lib/rank.mts | 316 ++ .../researching-recency/lib/relevance.mts | 277 ++ .../lib/render/compact.mts | 97 + .../researching-recency/lib/render/footer.mts | 46 + .../fleet/researching-recency/lib/signals.mts | 269 ++ .../lib/sources/bluesky.mts | 150 + .../researching-recency/lib/sources/devto.mts | 141 + .../lib/sources/github.mts | 136 + .../lib/sources/hackernews.mts | 110 + .../lib/sources/lobsters.mts | 149 + .../lib/sources/reddit.mts | 137 + .../researching-recency/lib/sources/web.mts | 67 + .../researching-recency/lib/sources/x.mts | 297 ++ .../fleet/researching-recency/lib/types.mts | 153 + scripts/fleet/researching-recency/paths.mts | 26 + scripts/fleet/resolve-security-pin.mts | 239 ++ .../fleet/scanning-quality/lib/findings.mts | 107 + scripts/fleet/scanning-vulns/cli.mts | 192 + scripts/fleet/scanning-vulns/lib/collate.mts | 268 ++ scripts/fleet/security.mts | 429 +++ scripts/fleet/setup/activate-node.mts | 111 + .../setup/bootstrap-zero-dep-packages.mjs | 247 ++ scripts/fleet/setup/claude-config.mts | 109 + scripts/fleet/setup/ecosystems.mts | 199 + scripts/fleet/setup/external-tools.json | 310 ++ scripts/fleet/setup/hook-snapshot.mts | 197 + scripts/fleet/setup/index.mts | 169 + .../fleet/setup/lib/bootstrap-common.d.mts | 8 + scripts/fleet/setup/lib/bootstrap-common.mjs | 309 ++ scripts/fleet/setup/lib/check-firewall.mjs | 89 + scripts/fleet/setup/lib/install-fff.mjs | 58 + scripts/fleet/setup/lib/install-janus.mjs | 57 + scripts/fleet/setup/lib/install-npm.mjs | 51 + scripts/fleet/setup/lib/install-pnpm.mjs | 69 + scripts/fleet/setup/lib/install-sfw.mjs | 64 + scripts/fleet/setup/lib/install-smithers.mjs | 58 + scripts/fleet/setup/lib/install-tool.mjs | 187 + scripts/fleet/setup/lib/install-uv.mjs | 67 + scripts/fleet/setup/lib/jq.mjs | 31 + scripts/fleet/setup/lib/platform.mjs | 58 + .../setup/lib/read-package-integrity.mjs | 88 + .../fleet/setup/lib/read-pinned-version.mjs | 114 + scripts/fleet/setup/setup-brew.mts | 213 ++ scripts/fleet/setup/setup-go.mts | 96 + .../fleet/setup/setup-kimi-user-config.mts | 227 ++ scripts/fleet/setup/setup-mcp.mts | 83 + scripts/fleet/setup/setup-python.mts | 133 + scripts/fleet/setup/setup-refero.mts | 145 + scripts/fleet/setup/setup-rust.mts | 236 ++ scripts/fleet/setup/setup-tools-sfw.d.mts | 17 + scripts/fleet/setup/setup-tools-sfw.mjs | 164 + scripts/fleet/setup/setup-tools.mjs | 262 ++ scripts/fleet/setup/token.mts | 74 + scripts/fleet/soak-bypass.mts | 178 + scripts/fleet/soak-rules.mts | 127 + .../fleet/socket-wheelhouse-emit-schema.mts | 64 + scripts/fleet/socket-wheelhouse-schema.mts | 688 ++++ scripts/fleet/source-pricing-feed.mts | 115 + scripts/fleet/strip-ai-attribution.mts | 199 + scripts/fleet/sync-gh-aw-action-pins.mts | 422 +++ scripts/fleet/sync-oxlint-rules.mts | 485 +++ scripts/fleet/sync-oxlint-schema-pin.mts | 183 + scripts/fleet/sync-package-manager-pins.mts | 366 ++ scripts/fleet/team-activity/cli.mts | 112 + scripts/fleet/team-activity/lib/config.mts | 130 + scripts/fleet/team-activity/lib/discover.mts | 214 ++ scripts/fleet/team-activity/lib/filter.mts | 149 + .../fleet/team-activity/lib/follow-ups.mts | 206 ++ scripts/fleet/team-activity/lib/paths.mts | 48 + scripts/fleet/team-activity/lib/render.mts | 68 + scripts/fleet/team-activity/lib/scan.mts | 38 + scripts/fleet/team-activity/lib/state.mts | 39 + scripts/fleet/team-activity/lib/types.mts | 107 + scripts/fleet/test.mts | 788 ++++ scripts/fleet/triaging-findings/cli.mts | 132 + .../fleet/triaging-findings/lib/ingest.mts | 251 ++ .../fleet/triaging-findings/lib/report.mts | 197 + scripts/fleet/trim-claude-md.mts | 98 + .../fleet/trimming-bundle/measure-bundle.mts | 148 + scripts/fleet/update-model-pricing.mts | 247 ++ scripts/fleet/update.mts | 292 ++ scripts/fleet/update/_shared.mts | 119 + scripts/fleet/update/brew-parse.mts | 501 +++ scripts/fleet/update/brew.mts | 218 ++ scripts/fleet/update/cargo.mts | 307 ++ scripts/fleet/update/docker.mts | 673 ++++ scripts/fleet/update/go.mts | 474 +++ scripts/fleet/update/node.mts | 323 ++ scripts/fleet/util/coverage-merge.mts | 331 ++ .../util/multi-package-publish-verify.mts | 215 ++ scripts/fleet/util/multi-package-publish.mts | 473 +++ scripts/fleet/util/napi-targets.mts | 234 ++ scripts/fleet/util/pack-app-triplets.mts | 236 ++ scripts/fleet/util/parse-args.mts | 79 + scripts/fleet/util/run-command.mts | 266 ++ scripts/fleet/util/source-allowlist.mts | 278 ++ scripts/fleet/validate-bundle-deps.mts | 479 +++ scripts/fleet/verify-submodule-sparse.mts | 270 ++ scripts/fleet/weekly-update-workflow.mts | 158 + scripts/fleet/weekly-update.mts | 334 ++ .../weekly-update/deterministic-chain.mts | 308 ++ scripts/fleet/whose-work.mts | 224 ++ 1977 files changed, 250244 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .claude/agents/fleet/code-reviewer.md create mode 100644 .claude/agents/fleet/fix.md create mode 100644 .claude/agents/fleet/refactor-cleaner.md create mode 100644 .claude/agents/fleet/security-reviewer.md create mode 100644 .claude/commands/fleet/audit-gha-settings.md create mode 100644 .claude/commands/fleet/codifying-disciplines.md create mode 100644 .claude/commands/fleet/green-ci-local.md create mode 100644 .claude/commands/fleet/green-ci.md create mode 100644 .claude/commands/fleet/looping-quality.md create mode 100644 .claude/commands/fleet/researching-recency.md create mode 100644 .claude/commands/fleet/scanning-quality.md create mode 100644 .claude/commands/fleet/security-scan.md create mode 100644 .claude/commands/fleet/setup-security-tools.md create mode 100644 .claude/commands/fleet/squash-history.md create mode 100644 .claude/commands/fleet/update-coverage.md create mode 100644 .claude/commands/fleet/update-hooks-dry.md create mode 100644 .claude/commands/fleet/update-pricing.md create mode 100644 .claude/commands/fleet/update-security.md create mode 100644 .claude/hooks/fleet/_dispatch/.clangd create mode 100644 .claude/hooks/fleet/_dispatch/.gitignore create mode 100644 .claude/hooks/fleet/_dispatch/SNAPSHOT-NOTES.md create mode 100644 .claude/hooks/fleet/_dispatch/dispatch-entry.mts create mode 100644 .claude/hooks/fleet/_dispatch/dispatch-launcher-win.c create mode 100644 .claude/hooks/fleet/_dispatch/dispatch-launcher.c create mode 100644 .claude/hooks/fleet/_dispatch/dispatch-snapshot-entry.mts create mode 100644 .claude/hooks/fleet/_dispatch/dispatch-table-excluded.mts create mode 100644 .claude/hooks/fleet/_dispatch/dispatch-table-snapshot.mts create mode 100644 .claude/hooks/fleet/_dispatch/dispatch-table.mts create mode 100644 .claude/hooks/fleet/_dispatch/dispatch.mts create mode 100644 .claude/hooks/fleet/_dispatch/excluded-entry.mts create mode 100644 .claude/hooks/fleet/_dispatch/index.cjs create mode 100644 .claude/hooks/fleet/_dispatch/snapshot-cache-path.cjs create mode 100644 .claude/hooks/fleet/_dispatch/snapshot-loader.cjs create mode 100644 .claude/hooks/fleet/_shared/README.md create mode 100644 .claude/hooks/fleet/_shared/acorn/README.md create mode 100644 .claude/hooks/fleet/_shared/acorn/acorn-sync.mts create mode 100644 .claude/hooks/fleet/_shared/acorn/index.mts create mode 100644 .claude/hooks/fleet/_shared/active-edits-ledger.mts create mode 100644 .claude/hooks/fleet/_shared/ai-attribution.mts create mode 100644 .claude/hooks/fleet/_shared/brew-supply-chain.mts create mode 100644 .claude/hooks/fleet/_shared/cdn-allowlist.mts create mode 100644 .claude/hooks/fleet/_shared/commit-command.mts create mode 100644 .claude/hooks/fleet/_shared/dated-citation.mts create mode 100644 .claude/hooks/fleet/_shared/dispatch-manifest.json create mode 100644 .claude/hooks/fleet/_shared/dispatch.mts create mode 100644 .claude/hooks/fleet/_shared/doc-location-guard.mts create mode 100644 .claude/hooks/fleet/_shared/edit-content.mts create mode 100644 .claude/hooks/fleet/_shared/entrypoint.mts create mode 100644 .claude/hooks/fleet/_shared/ephemeral-path.mts create mode 100644 .claude/hooks/fleet/_shared/error-message-quality.mts create mode 100644 .claude/hooks/fleet/_shared/es-polyfills.mts create mode 100644 .claude/hooks/fleet/_shared/evasion-normalize.mts create mode 100644 .claude/hooks/fleet/_shared/fleet-context.mts create mode 100644 .claude/hooks/fleet/_shared/fleet-env.mts create mode 100644 .claude/hooks/fleet/_shared/fleet-markers.mts create mode 100644 .claude/hooks/fleet/_shared/fleet-repo.mts create mode 100644 .claude/hooks/fleet/_shared/fleet-repos.mts create mode 100644 .claude/hooks/fleet/_shared/fleet-roster.mts create mode 100644 .claude/hooks/fleet/_shared/foreign-linters.mts create mode 100644 .claude/hooks/fleet/_shared/foreign-paths.mts create mode 100644 .claude/hooks/fleet/_shared/gh-pr-command.mts create mode 100644 .claude/hooks/fleet/_shared/git-branch.mts create mode 100644 .claude/hooks/fleet/_shared/git-cwd.mts create mode 100644 .claude/hooks/fleet/_shared/git-identity.mts create mode 100644 .claude/hooks/fleet/_shared/git-state.mts create mode 100644 .claude/hooks/fleet/_shared/guard.mts create mode 100644 .claude/hooks/fleet/_shared/honesty-framing.mts create mode 100644 .claude/hooks/fleet/_shared/known-names.mts create mode 100644 .claude/hooks/fleet/_shared/landable.mts create mode 100644 .claude/hooks/fleet/_shared/learning-ledger.mts create mode 100644 .claude/hooks/fleet/_shared/markers.mts create mode 100644 .claude/hooks/fleet/_shared/named-blocks.mts create mode 100644 .claude/hooks/fleet/_shared/npmrc-trust.mts create mode 100644 .claude/hooks/fleet/_shared/package-manager-auto-update.mts create mode 100644 .claude/hooks/fleet/_shared/parked-paths.mts create mode 100644 .claude/hooks/fleet/_shared/paths.mts create mode 100644 .claude/hooks/fleet/_shared/payload.mts create mode 100644 .claude/hooks/fleet/_shared/private-paths.mts create mode 100644 .claude/hooks/fleet/_shared/public-surfaces.mts create mode 100644 .claude/hooks/fleet/_shared/push-refspec.mts create mode 100644 .claude/hooks/fleet/_shared/repo-test-home.mts create mode 100644 .claude/hooks/fleet/_shared/shell-command.mts create mode 100644 .claude/hooks/fleet/_shared/sparkle-auto-update.mts create mode 100644 .claude/hooks/fleet/_shared/spawn-timeout.mts create mode 100644 .claude/hooks/fleet/_shared/squash-sentinel.mts create mode 100644 .claude/hooks/fleet/_shared/stop-nudge.mts create mode 100644 .claude/hooks/fleet/_shared/test/fixtures.mts create mode 100644 .claude/hooks/fleet/_shared/token-patterns.mts create mode 100644 .claude/hooks/fleet/_shared/transcript.mts create mode 100644 .claude/hooks/fleet/_shared/trust-gates.mts create mode 100644 .claude/hooks/fleet/_shared/unbacked-claims.mts create mode 100644 .claude/hooks/fleet/_shared/uv-config.mts create mode 100644 .claude/hooks/fleet/_shared/wheelhouse-root.mts create mode 100644 .claude/hooks/fleet/actionlint-on-workflow-edit/README.md create mode 100644 .claude/hooks/fleet/actionlint-on-workflow-edit/index.mts create mode 100644 .claude/hooks/fleet/actionlint-on-workflow-edit/package.json create mode 100644 .claude/hooks/fleet/actionlint-on-workflow-edit/tsconfig.json create mode 100644 .claude/hooks/fleet/active-edits-ledger/index.mts create mode 100644 .claude/hooks/fleet/adversarial-review-nudge/README.md create mode 100644 .claude/hooks/fleet/adversarial-review-nudge/index.mts create mode 100644 .claude/hooks/fleet/adversarial-review-nudge/package.json create mode 100644 .claude/hooks/fleet/adversarial-review-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/agents-skills-mirror-nudge/README.md create mode 100644 .claude/hooks/fleet/agents-skills-mirror-nudge/index.mts create mode 100644 .claude/hooks/fleet/agents-skills-mirror-nudge/package.json create mode 100644 .claude/hooks/fleet/agents-skills-mirror-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/ai-config-drift-nudge/README.md create mode 100644 .claude/hooks/fleet/ai-config-drift-nudge/index.mts create mode 100644 .claude/hooks/fleet/ai-config-drift-nudge/package.json create mode 100644 .claude/hooks/fleet/ai-config-drift-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/ai-config-poisoning-guard/README.md create mode 100644 .claude/hooks/fleet/ai-config-poisoning-guard/index.mts create mode 100644 .claude/hooks/fleet/ai-config-poisoning-guard/package.json create mode 100644 .claude/hooks/fleet/ai-config-poisoning-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/alpha-sort-nudge/README.md create mode 100644 .claude/hooks/fleet/alpha-sort-nudge/index.mts create mode 100644 .claude/hooks/fleet/alpha-sort-nudge/package.json create mode 100644 .claude/hooks/fleet/alpha-sort-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/answer-questions-nudge/README.md create mode 100644 .claude/hooks/fleet/answer-questions-nudge/index.mts create mode 100644 .claude/hooks/fleet/answer-questions-nudge/package.json create mode 100644 .claude/hooks/fleet/answer-questions-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/answer-status-requests-nudge/README.md create mode 100644 .claude/hooks/fleet/answer-status-requests-nudge/index.mts create mode 100644 .claude/hooks/fleet/answer-status-requests-nudge/package.json create mode 100644 .claude/hooks/fleet/answer-status-requests-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/anti-prose-guard/README.md create mode 100644 .claude/hooks/fleet/anti-prose-guard/index.mts create mode 100644 .claude/hooks/fleet/anti-prose-guard/package.json create mode 100644 .claude/hooks/fleet/anti-prose-guard/patterns.mts create mode 100644 .claude/hooks/fleet/anti-prose-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/ask-suppression-nudge/README.md create mode 100644 .claude/hooks/fleet/ask-suppression-nudge/index.mts create mode 100644 .claude/hooks/fleet/ask-suppression-nudge/package.json create mode 100644 .claude/hooks/fleet/ask-suppression-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/attribution-rewrite-nudge/index.mts create mode 100644 .claude/hooks/fleet/auth-rotation-nudge/README.md create mode 100644 .claude/hooks/fleet/auth-rotation-nudge/index.mts create mode 100644 .claude/hooks/fleet/auth-rotation-nudge/package.json create mode 100644 .claude/hooks/fleet/auth-rotation-nudge/services.mts create mode 100644 .claude/hooks/fleet/auth-rotation-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/auto-land-on-stop/hold.mts create mode 100644 .claude/hooks/fleet/auto-land-on-stop/index.mts create mode 100644 .claude/hooks/fleet/avoid-cd-nudge/index.mts create mode 100644 .claude/hooks/fleet/brew-supply-chain-guard/README.md create mode 100644 .claude/hooks/fleet/brew-supply-chain-guard/index.mts create mode 100644 .claude/hooks/fleet/brew-supply-chain-guard/package.json create mode 100644 .claude/hooks/fleet/brew-supply-chain-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/broken-hook-detector/README.md create mode 100644 .claude/hooks/fleet/broken-hook-detector/index.mts create mode 100644 .claude/hooks/fleet/broken-hook-detector/package.json create mode 100644 .claude/hooks/fleet/broken-hook-detector/tsconfig.json create mode 100644 .claude/hooks/fleet/bump-defers-to-release-guard/README.md create mode 100644 .claude/hooks/fleet/bump-defers-to-release-guard/index.mts create mode 100644 .claude/hooks/fleet/bump-defers-to-release-guard/package.json create mode 100644 .claude/hooks/fleet/bundle-flags-guard/README.md create mode 100644 .claude/hooks/fleet/bundle-flags-guard/index.mts create mode 100644 .claude/hooks/fleet/bundle-flags-guard/package.json create mode 100644 .claude/hooks/fleet/bundle-flags-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/bundle-stale-reminder/README.md create mode 100644 .claude/hooks/fleet/bundle-stale-reminder/index.mts create mode 100644 .claude/hooks/fleet/bundle-stale-reminder/package.json create mode 100644 .claude/hooks/fleet/c8-ignore-reason-guard/README.md create mode 100644 .claude/hooks/fleet/c8-ignore-reason-guard/index.mts create mode 100644 .claude/hooks/fleet/c8-ignore-reason-guard/package.json create mode 100644 .claude/hooks/fleet/c8-ignore-reason-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/cascade-first-triage-nudge/README.md create mode 100644 .claude/hooks/fleet/cascade-first-triage-nudge/index.mts create mode 100644 .claude/hooks/fleet/cascade-first-triage-nudge/package.json create mode 100644 .claude/hooks/fleet/cascade-first-triage-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/catch-message-guard/README.md create mode 100644 .claude/hooks/fleet/catch-message-guard/index.mts create mode 100644 .claude/hooks/fleet/catch-message-guard/package.json create mode 100644 .claude/hooks/fleet/catch-message-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/cdn-allowlist-guard/README.md create mode 100644 .claude/hooks/fleet/cdn-allowlist-guard/index.mts create mode 100644 .claude/hooks/fleet/cdn-allowlist-guard/package.json create mode 100644 .claude/hooks/fleet/cdn-allowlist-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/changelog-entry-shape-nudge/README.md create mode 100644 .claude/hooks/fleet/changelog-entry-shape-nudge/index.mts create mode 100644 .claude/hooks/fleet/changelog-entry-shape-nudge/package.json create mode 100644 .claude/hooks/fleet/changelog-entry-shape-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/changelog-no-empty-guard/README.md create mode 100644 .claude/hooks/fleet/changelog-no-empty-guard/index.mts create mode 100644 .claude/hooks/fleet/changelog-no-empty-guard/package.json create mode 100644 .claude/hooks/fleet/changelog-no-empty-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/check-new-deps/README.md create mode 100644 .claude/hooks/fleet/check-new-deps/audit.mts create mode 100644 .claude/hooks/fleet/check-new-deps/index.mts create mode 100644 .claude/hooks/fleet/check-new-deps/package.json create mode 100644 .claude/hooks/fleet/check-new-deps/tsconfig.json create mode 100644 .claude/hooks/fleet/check-new-deps/types.mts create mode 100644 .claude/hooks/fleet/claude-code-action-lockdown-guard/README.md create mode 100644 .claude/hooks/fleet/claude-code-action-lockdown-guard/index.mts create mode 100644 .claude/hooks/fleet/claude-code-action-lockdown-guard/package.json create mode 100644 .claude/hooks/fleet/claude-code-action-lockdown-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/claude-lockdown-guard/README.md create mode 100644 .claude/hooks/fleet/claude-lockdown-guard/index.mts create mode 100644 .claude/hooks/fleet/claude-lockdown-guard/package.json create mode 100644 .claude/hooks/fleet/claude-lockdown-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/claude-md-defer-detail-nudge/README.md create mode 100644 .claude/hooks/fleet/claude-md-defer-detail-nudge/index.mts create mode 100644 .claude/hooks/fleet/claude-md-defer-detail-nudge/package.json create mode 100644 .claude/hooks/fleet/claude-md-defer-detail-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/claude-md-rule-add-guard/README.md create mode 100644 .claude/hooks/fleet/claude-md-rule-add-guard/index.mts create mode 100644 .claude/hooks/fleet/claude-md-rule-add-guard/package.json create mode 100644 .claude/hooks/fleet/claude-md-rule-add-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/claude-md-section-size-guard/README.md create mode 100644 .claude/hooks/fleet/claude-md-section-size-guard/index.mts create mode 100644 .claude/hooks/fleet/claude-md-section-size-guard/package.json create mode 100644 .claude/hooks/fleet/claude-md-section-size-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/claude-md-size-guard/README.md create mode 100644 .claude/hooks/fleet/claude-md-size-guard/index.mts create mode 100644 .claude/hooks/fleet/claude-md-size-guard/package.json create mode 100644 .claude/hooks/fleet/claude-md-size-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/claude-segmentation-guard/README.md create mode 100644 .claude/hooks/fleet/claude-segmentation-guard/index.mts create mode 100644 .claude/hooks/fleet/claude-segmentation-guard/package.json create mode 100644 .claude/hooks/fleet/claude-segmentation-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/clipboard-snippet-nudge/README.md create mode 100644 .claude/hooks/fleet/clipboard-snippet-nudge/index.mts create mode 100644 .claude/hooks/fleet/clone-reviewed-repo-nudge/README.md create mode 100644 .claude/hooks/fleet/clone-reviewed-repo-nudge/detect.mts create mode 100644 .claude/hooks/fleet/clone-reviewed-repo-nudge/index.mts create mode 100644 .claude/hooks/fleet/clone-reviewed-repo-nudge/package.json create mode 100644 .claude/hooks/fleet/clone-reviewed-repo-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/codex-no-write-guard/README.md create mode 100644 .claude/hooks/fleet/codex-no-write-guard/index.mts create mode 100644 .claude/hooks/fleet/codex-no-write-guard/package.json create mode 100644 .claude/hooks/fleet/codex-no-write-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/codex-session-budget-guard/README.md create mode 100644 .claude/hooks/fleet/codex-session-budget-guard/index.mts create mode 100644 .claude/hooks/fleet/codex-session-budget-guard/package.json create mode 100644 .claude/hooks/fleet/codex-session-budget-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/commit-author-guard/README.md create mode 100644 .claude/hooks/fleet/commit-author-guard/index.mts create mode 100644 .claude/hooks/fleet/commit-author-guard/package.json create mode 100644 .claude/hooks/fleet/commit-author-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/commit-cadence-nudge/README.md create mode 100644 .claude/hooks/fleet/commit-cadence-nudge/index.mts create mode 100644 .claude/hooks/fleet/commit-cadence-nudge/package.json create mode 100644 .claude/hooks/fleet/commit-cadence-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/commit-message-format-guard/README.md create mode 100644 .claude/hooks/fleet/commit-message-format-guard/index.mts create mode 100644 .claude/hooks/fleet/commit-message-format-guard/package.json create mode 100644 .claude/hooks/fleet/commit-message-format-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/commit-pr-nudge/README.md create mode 100644 .claude/hooks/fleet/commit-pr-nudge/index.mts create mode 100644 .claude/hooks/fleet/commit-pr-nudge/package.json create mode 100644 .claude/hooks/fleet/commit-pr-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/commit-size-nudge/README.md create mode 100644 .claude/hooks/fleet/commit-size-nudge/index.mts create mode 100644 .claude/hooks/fleet/commit-size-nudge/package.json create mode 100644 .claude/hooks/fleet/commit-size-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/compound-lessons-nudge/README.md create mode 100644 .claude/hooks/fleet/compound-lessons-nudge/index.mts create mode 100644 .claude/hooks/fleet/compound-lessons-nudge/package.json create mode 100644 .claude/hooks/fleet/compound-lessons-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/consumer-grep-nudge/README.md create mode 100644 .claude/hooks/fleet/consumer-grep-nudge/index.mts create mode 100644 .claude/hooks/fleet/consumer-grep-nudge/package.json create mode 100644 .claude/hooks/fleet/consumer-grep-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/convo-prose-nudge/index.mts create mode 100644 .claude/hooks/fleet/convo-prose-nudge/package.json create mode 100644 .claude/hooks/fleet/convo-prose-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/copy-on-select-hint-nudge/README.md create mode 100644 .claude/hooks/fleet/copy-on-select-hint-nudge/index.mts create mode 100644 .claude/hooks/fleet/cross-repo-guard/README.md create mode 100644 .claude/hooks/fleet/cross-repo-guard/index.mts create mode 100644 .claude/hooks/fleet/cross-repo-guard/package.json create mode 100644 .claude/hooks/fleet/cross-repo-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/dated-citation-guard/README.md create mode 100644 .claude/hooks/fleet/dated-citation-guard/index.mts create mode 100644 .claude/hooks/fleet/dated-citation-guard/package.json create mode 100644 .claude/hooks/fleet/dated-citation-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/default-branch-guard/README.md create mode 100644 .claude/hooks/fleet/default-branch-guard/index.mts create mode 100644 .claude/hooks/fleet/default-branch-guard/package.json create mode 100644 .claude/hooks/fleet/default-branch-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/defer-to-script-nudge/README.md create mode 100644 .claude/hooks/fleet/defer-to-script-nudge/index.mts create mode 100644 .claude/hooks/fleet/dep-derived-source-nudge/README.md create mode 100644 .claude/hooks/fleet/dep-derived-source-nudge/index.mts create mode 100644 .claude/hooks/fleet/dep-derived-source-nudge/package.json create mode 100644 .claude/hooks/fleet/dep-derived-source-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/dirty-lockfile-nudge/README.md create mode 100644 .claude/hooks/fleet/dirty-lockfile-nudge/index.mts create mode 100644 .claude/hooks/fleet/dirty-lockfile-nudge/package.json create mode 100644 .claude/hooks/fleet/dirty-lockfile-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/dirty-worktree-stop-guard/README.md create mode 100644 .claude/hooks/fleet/dirty-worktree-stop-guard/index.mts create mode 100644 .claude/hooks/fleet/dirty-worktree-stop-guard/package.json create mode 100644 .claude/hooks/fleet/dirty-worktree-stop-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/dogfood-cascade-nudge/README.md create mode 100644 .claude/hooks/fleet/dogfood-cascade-nudge/index.mts create mode 100644 .claude/hooks/fleet/dogfood-cascade-nudge/package.json create mode 100644 .claude/hooks/fleet/dogfood-cascade-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/dont-blame-nudge/README.md create mode 100644 .claude/hooks/fleet/dont-blame-nudge/index.mts create mode 100644 .claude/hooks/fleet/dont-blame-nudge/package.json create mode 100644 .claude/hooks/fleet/dont-blame-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/dont-stop-mid-queue-nudge/README.md create mode 100644 .claude/hooks/fleet/dont-stop-mid-queue-nudge/index.mts create mode 100644 .claude/hooks/fleet/dont-stop-mid-queue-nudge/package.json create mode 100644 .claude/hooks/fleet/dont-stop-mid-queue-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/drift-check-nudge/README.md create mode 100644 .claude/hooks/fleet/drift-check-nudge/index.mts create mode 100644 .claude/hooks/fleet/drift-check-nudge/package.json create mode 100644 .claude/hooks/fleet/drift-check-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/enqueue-dont-pivot-nudge/README.md create mode 100644 .claude/hooks/fleet/enqueue-dont-pivot-nudge/index.mts create mode 100644 .claude/hooks/fleet/enqueue-dont-pivot-nudge/package.json create mode 100644 .claude/hooks/fleet/enqueue-dont-pivot-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/enterprise-push-nudge/README.md create mode 100644 .claude/hooks/fleet/enterprise-push-nudge/index.mts create mode 100644 .claude/hooks/fleet/enterprise-push-nudge/package.json create mode 100644 .claude/hooks/fleet/enterprise-push-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/error-message-quality-nudge/README.md create mode 100644 .claude/hooks/fleet/error-message-quality-nudge/index.mts create mode 100644 .claude/hooks/fleet/error-message-quality-nudge/package.json create mode 100644 .claude/hooks/fleet/error-message-quality-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/excuse-detector/README.md create mode 100644 .claude/hooks/fleet/excuse-detector/index.mts create mode 100644 .claude/hooks/fleet/excuse-detector/package.json create mode 100644 .claude/hooks/fleet/excuse-detector/tsconfig.json create mode 100644 .claude/hooks/fleet/file-size-nudge/README.md create mode 100644 .claude/hooks/fleet/file-size-nudge/index.mts create mode 100644 .claude/hooks/fleet/file-size-nudge/package.json create mode 100644 .claude/hooks/fleet/file-size-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/follow-direct-imperative-nudge/README.md create mode 100644 .claude/hooks/fleet/follow-direct-imperative-nudge/index.mts create mode 100644 .claude/hooks/fleet/follow-direct-imperative-nudge/package.json create mode 100644 .claude/hooks/fleet/follow-direct-imperative-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/gh-token-hygiene-guard/README.md create mode 100644 .claude/hooks/fleet/gh-token-hygiene-guard/index.mts create mode 100644 .claude/hooks/fleet/gh-token-hygiene-guard/package.json create mode 100644 .claude/hooks/fleet/gh-token-hygiene-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/git-config-write-guard/README.md create mode 100644 .claude/hooks/fleet/git-config-write-guard/index.mts create mode 100644 .claude/hooks/fleet/git-config-write-guard/package.json create mode 100644 .claude/hooks/fleet/git-config-write-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/git-identity-drift-nudge/README.md create mode 100644 .claude/hooks/fleet/git-identity-drift-nudge/index.mts create mode 100644 .claude/hooks/fleet/git-identity-drift-nudge/package.json create mode 100644 .claude/hooks/fleet/git-identity-drift-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/gitmodules-comment-guard/README.md create mode 100644 .claude/hooks/fleet/gitmodules-comment-guard/index.mts create mode 100644 .claude/hooks/fleet/gitmodules-comment-guard/package.json create mode 100644 .claude/hooks/fleet/gitmodules-comment-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/golden-fixture-naming-guard/README.md create mode 100644 .claude/hooks/fleet/golden-fixture-naming-guard/index.mts create mode 100644 .claude/hooks/fleet/golden-fixture-naming-guard/package.json create mode 100644 .claude/hooks/fleet/golden-fixture-naming-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/headroom-proxy-start/README.md create mode 100644 .claude/hooks/fleet/headroom-proxy-start/index.mts create mode 100644 .claude/hooks/fleet/headroom-proxy-start/package.json create mode 100644 .claude/hooks/fleet/headroom-proxy-start/tsconfig.json create mode 100644 .claude/hooks/fleet/immutable-release-guard/README.md create mode 100644 .claude/hooks/fleet/immutable-release-guard/index.mts create mode 100644 .claude/hooks/fleet/immutable-release-guard/package.json create mode 100644 .claude/hooks/fleet/immutable-release-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/inline-script-defer-guard/README.md create mode 100644 .claude/hooks/fleet/inline-script-defer-guard/index.mts create mode 100644 .claude/hooks/fleet/inline-script-defer-guard/package.json create mode 100644 .claude/hooks/fleet/inline-script-defer-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/issue-autolink-nudge/index.mts create mode 100644 .claude/hooks/fleet/judgment-nudge/README.md create mode 100644 .claude/hooks/fleet/judgment-nudge/index.mts create mode 100644 .claude/hooks/fleet/judgment-nudge/package.json create mode 100644 .claude/hooks/fleet/judgment-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/land-fast-nudge/README.md create mode 100644 .claude/hooks/fleet/land-fast-nudge/index.mts create mode 100644 .claude/hooks/fleet/land-fast-nudge/package.json create mode 100644 .claude/hooks/fleet/land-fast-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/live-edit-collision-guard/index.mts create mode 100644 .claude/hooks/fleet/lock-step-ref-nudge/README.md create mode 100644 .claude/hooks/fleet/lock-step-ref-nudge/index.mts create mode 100644 .claude/hooks/fleet/lock-step-ref-nudge/package.json create mode 100644 .claude/hooks/fleet/lock-step-ref-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/logger-guard/README.md create mode 100644 .claude/hooks/fleet/logger-guard/index.mts create mode 100644 .claude/hooks/fleet/logger-guard/package.json create mode 100644 .claude/hooks/fleet/logger-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/markdown-filename-guard/README.md create mode 100644 .claude/hooks/fleet/markdown-filename-guard/index.mts create mode 100644 .claude/hooks/fleet/markdown-filename-guard/package.json create mode 100644 .claude/hooks/fleet/markdown-filename-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/marketplace-comment-guard/README.md create mode 100644 .claude/hooks/fleet/marketplace-comment-guard/index.mts create mode 100644 .claude/hooks/fleet/marketplace-comment-guard/package.json create mode 100644 .claude/hooks/fleet/marketplace-comment-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/mass-delete-guard/README.md create mode 100644 .claude/hooks/fleet/mass-delete-guard/index.mts create mode 100644 .claude/hooks/fleet/mass-delete-guard/package.json create mode 100644 .claude/hooks/fleet/memory-discovery-nudge/README.md create mode 100644 .claude/hooks/fleet/memory-discovery-nudge/index.mts create mode 100644 .claude/hooks/fleet/minimum-release-age-guard/README.md create mode 100644 .claude/hooks/fleet/minimum-release-age-guard/index.mts create mode 100644 .claude/hooks/fleet/minimum-release-age-guard/package.json create mode 100644 .claude/hooks/fleet/minimum-release-age-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/module-noun-name-guard/README.md create mode 100644 .claude/hooks/fleet/module-noun-name-guard/index.mts create mode 100644 .claude/hooks/fleet/module-noun-name-guard/package.json create mode 100644 .claude/hooks/fleet/module-noun-name-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/new-hook-claude-md-guard/README.md create mode 100644 .claude/hooks/fleet/new-hook-claude-md-guard/index.mts create mode 100644 .claude/hooks/fleet/new-hook-claude-md-guard/package.json create mode 100644 .claude/hooks/fleet/new-hook-claude-md-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-amend-foreign-commit-guard/README.md create mode 100644 .claude/hooks/fleet/no-amend-foreign-commit-guard/index.mts create mode 100644 .claude/hooks/fleet/no-blanket-file-exclusion-guard/README.md create mode 100644 .claude/hooks/fleet/no-blanket-file-exclusion-guard/index.mts create mode 100644 .claude/hooks/fleet/no-blanket-file-exclusion-guard/package.json create mode 100644 .claude/hooks/fleet/no-blanket-file-exclusion-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-blind-keychain-read-guard/README.md create mode 100644 .claude/hooks/fleet/no-blind-keychain-read-guard/index.mts create mode 100644 .claude/hooks/fleet/no-blind-keychain-read-guard/package.json create mode 100644 .claude/hooks/fleet/no-blind-keychain-read-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-boolean-trap-guard/README.md create mode 100644 .claude/hooks/fleet/no-boolean-trap-guard/index.mts create mode 100644 .claude/hooks/fleet/no-boolean-trap-guard/package.json create mode 100644 .claude/hooks/fleet/no-boolean-trap-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-branch-reuse-nudge/README.md create mode 100644 .claude/hooks/fleet/no-branch-reuse-nudge/index.mts create mode 100644 .claude/hooks/fleet/no-branch-reuse-nudge/package.json create mode 100644 .claude/hooks/fleet/no-branch-reuse-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/no-cascade-transient-git-guard/index.mts create mode 100644 .claude/hooks/fleet/no-clipboard-access-guard/README.md create mode 100644 .claude/hooks/fleet/no-clipboard-access-guard/index.mts create mode 100644 .claude/hooks/fleet/no-corepack-guard/README.md create mode 100644 .claude/hooks/fleet/no-corepack-guard/index.mts create mode 100644 .claude/hooks/fleet/no-corepack-guard/package.json create mode 100644 .claude/hooks/fleet/no-corepack-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-direct-linter-guard/README.md create mode 100644 .claude/hooks/fleet/no-direct-linter-guard/index.mts create mode 100644 .claude/hooks/fleet/no-direct-linter-guard/package.json create mode 100644 .claude/hooks/fleet/no-direct-linter-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-disable-lint-rule-guard/README.md create mode 100644 .claude/hooks/fleet/no-disable-lint-rule-guard/index.mts create mode 100644 .claude/hooks/fleet/no-disable-lint-rule-guard/package.json create mode 100644 .claude/hooks/fleet/no-disable-lint-rule-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-empty-commit-guard/README.md create mode 100644 .claude/hooks/fleet/no-empty-commit-guard/index.mts create mode 100644 .claude/hooks/fleet/no-empty-commit-guard/package.json create mode 100644 .claude/hooks/fleet/no-empty-commit-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-env-kill-switch-guard/README.md create mode 100644 .claude/hooks/fleet/no-env-kill-switch-guard/index.mts create mode 100644 .claude/hooks/fleet/no-env-kill-switch-guard/package.json create mode 100644 .claude/hooks/fleet/no-env-kill-switch-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-ext-issue-ref-guard/README.md create mode 100644 .claude/hooks/fleet/no-ext-issue-ref-guard/index.mts create mode 100644 .claude/hooks/fleet/no-ext-issue-ref-guard/package.json create mode 100644 .claude/hooks/fleet/no-ext-issue-ref-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-file-oxlint-disable-guard/README.md create mode 100644 .claude/hooks/fleet/no-file-oxlint-disable-guard/index.mts create mode 100644 .claude/hooks/fleet/no-file-oxlint-disable-guard/package.json create mode 100644 .claude/hooks/fleet/no-file-oxlint-disable-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-fleet-fork-guard/README.md create mode 100644 .claude/hooks/fleet/no-fleet-fork-guard/index.mts create mode 100644 .claude/hooks/fleet/no-fleet-fork-guard/package.json create mode 100644 .claude/hooks/fleet/no-fleet-fork-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-force-push-guard/README.md create mode 100644 .claude/hooks/fleet/no-force-push-guard/index.mts create mode 100644 .claude/hooks/fleet/no-force-push-guard/package.json create mode 100644 .claude/hooks/fleet/no-force-push-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-github-ai-attribution-guard/README.md create mode 100644 .claude/hooks/fleet/no-github-ai-attribution-guard/index.mts create mode 100644 .claude/hooks/fleet/no-github-ai-attribution-guard/package.json create mode 100644 .claude/hooks/fleet/no-github-ai-attribution-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-hook-cmd-regex-guard/README.md create mode 100644 .claude/hooks/fleet/no-hook-cmd-regex-guard/index.mts create mode 100644 .claude/hooks/fleet/no-hook-cmd-regex-guard/package.json create mode 100644 .claude/hooks/fleet/no-hook-cmd-regex-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-meta-comments-guard/README.md create mode 100644 .claude/hooks/fleet/no-meta-comments-guard/index.mts create mode 100644 .claude/hooks/fleet/no-meta-comments-guard/package.json create mode 100644 .claude/hooks/fleet/no-meta-comments-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-new-config-guard/README.md create mode 100644 .claude/hooks/fleet/no-new-config-guard/index.mts create mode 100644 .claude/hooks/fleet/no-new-config-guard/package.json create mode 100644 .claude/hooks/fleet/no-new-config-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-non-fleet-push-guard/README.md create mode 100644 .claude/hooks/fleet/no-non-fleet-push-guard/index.mts create mode 100644 .claude/hooks/fleet/no-non-fleet-push-guard/package.json create mode 100644 .claude/hooks/fleet/no-non-fleet-push-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-npm-otp-flag-guard/README.md create mode 100644 .claude/hooks/fleet/no-npm-otp-flag-guard/index.mts create mode 100644 .claude/hooks/fleet/no-npm-otp-flag-guard/package.json create mode 100644 .claude/hooks/fleet/no-npm-otp-flag-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-orphaned-staging/README.md create mode 100644 .claude/hooks/fleet/no-orphaned-staging/index.mts create mode 100644 .claude/hooks/fleet/no-orphaned-staging/package.json create mode 100644 .claude/hooks/fleet/no-orphaned-staging/tsconfig.json create mode 100644 .claude/hooks/fleet/no-other-linters-guard/README.md create mode 100644 .claude/hooks/fleet/no-other-linters-guard/index.mts create mode 100644 .claude/hooks/fleet/no-other-linters-guard/package.json create mode 100644 .claude/hooks/fleet/no-other-linters-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-pkgjson-pnpm-overrides-guard/README.md create mode 100644 .claude/hooks/fleet/no-pkgjson-pnpm-overrides-guard/index.mts create mode 100644 .claude/hooks/fleet/no-pkgjson-pnpm-overrides-guard/package.json create mode 100644 .claude/hooks/fleet/no-pkgjson-pnpm-overrides-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-placeholder-commit-subject-guard/README.md create mode 100644 .claude/hooks/fleet/no-placeholder-commit-subject-guard/index.mts create mode 100644 .claude/hooks/fleet/no-placeholder-commit-subject-guard/package.json create mode 100644 .claude/hooks/fleet/no-placeholder-commit-subject-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-platform-import-guard/README.md create mode 100644 .claude/hooks/fleet/no-platform-import-guard/index.mts create mode 100644 .claude/hooks/fleet/no-platform-import-guard/package.json create mode 100644 .claude/hooks/fleet/no-platform-import-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-pm-exec-guard/README.md create mode 100644 .claude/hooks/fleet/no-pm-exec-guard/index.mts create mode 100644 .claude/hooks/fleet/no-pm-exec-guard/package.json create mode 100644 .claude/hooks/fleet/no-pm-exec-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-pr-from-default-branch-guard/README.md create mode 100644 .claude/hooks/fleet/no-pr-from-default-branch-guard/index.mts create mode 100644 .claude/hooks/fleet/no-pr-from-default-branch-guard/package.json create mode 100644 .claude/hooks/fleet/no-pr-from-default-branch-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-pr-from-default-checkout-guard/README.md create mode 100644 .claude/hooks/fleet/no-pr-from-default-checkout-guard/index.mts create mode 100644 .claude/hooks/fleet/no-pr-from-default-checkout-guard/package.json create mode 100644 .claude/hooks/fleet/no-pr-from-default-checkout-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-pr-review-verdict-guard/README.md create mode 100644 .claude/hooks/fleet/no-pr-review-verdict-guard/index.mts create mode 100644 .claude/hooks/fleet/no-pr-review-verdict-guard/package.json create mode 100644 .claude/hooks/fleet/no-pr-review-verdict-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-premature-commit-kill-guard/README.md create mode 100644 .claude/hooks/fleet/no-premature-commit-kill-guard/index.mts create mode 100644 .claude/hooks/fleet/no-premature-commit-kill-guard/package.json create mode 100644 .claude/hooks/fleet/no-premature-commit-kill-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-private-path-in-source-guard/README.md create mode 100644 .claude/hooks/fleet/no-private-path-in-source-guard/index.mts create mode 100644 .claude/hooks/fleet/no-private-path-in-source-guard/package.json create mode 100644 .claude/hooks/fleet/no-private-path-in-source-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-private-ref-in-tests-docs-guard/README.md create mode 100644 .claude/hooks/fleet/no-private-ref-in-tests-docs-guard/index.mts create mode 100644 .claude/hooks/fleet/no-private-ref-in-tests-docs-guard/package.json create mode 100644 .claude/hooks/fleet/no-private-ref-in-tests-docs-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-registry-mutation-in-repo-script-nudge/README.md create mode 100644 .claude/hooks/fleet/no-registry-mutation-in-repo-script-nudge/index.mts create mode 100644 .claude/hooks/fleet/no-registry-mutation-in-repo-script-nudge/package.json create mode 100644 .claude/hooks/fleet/no-registry-mutation-in-repo-script-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/no-removal-comment-nudge/README.md create mode 100644 .claude/hooks/fleet/no-removal-comment-nudge/index.mts create mode 100644 .claude/hooks/fleet/no-removal-comment-nudge/package.json create mode 100644 .claude/hooks/fleet/no-removal-comment-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/no-repo-scope-in-fleet-config-guard/README.md create mode 100644 .claude/hooks/fleet/no-repo-scope-in-fleet-config-guard/index.mts create mode 100644 .claude/hooks/fleet/no-repo-scope-in-fleet-config-guard/package.json create mode 100644 .claude/hooks/fleet/no-repo-scope-in-fleet-config-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-revert-guard/README.md create mode 100644 .claude/hooks/fleet/no-revert-guard/index.mts create mode 100644 .claude/hooks/fleet/no-revert-guard/package.json create mode 100644 .claude/hooks/fleet/no-revert-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-screenshot-guard/README.md create mode 100644 .claude/hooks/fleet/no-screenshot-guard/index.mts create mode 100644 .claude/hooks/fleet/no-shell-injection-bypass-guard/README.md create mode 100644 .claude/hooks/fleet/no-shell-injection-bypass-guard/index.mts create mode 100644 .claude/hooks/fleet/no-strip-types-guard/README.md create mode 100644 .claude/hooks/fleet/no-strip-types-guard/index.mts create mode 100644 .claude/hooks/fleet/no-strip-types-guard/package.json create mode 100644 .claude/hooks/fleet/no-strip-types-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-subagent-commit-guard/README.md create mode 100644 .claude/hooks/fleet/no-subagent-commit-guard/index.mts create mode 100644 .claude/hooks/fleet/no-subagent-commit-guard/package.json create mode 100644 .claude/hooks/fleet/no-subagent-commit-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-tail-install-out-guard/README.md create mode 100644 .claude/hooks/fleet/no-tail-install-out-guard/index.mts create mode 100644 .claude/hooks/fleet/no-tail-install-out-guard/package.json create mode 100644 .claude/hooks/fleet/no-tail-install-out-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-test-in-scripts-guard/README.md create mode 100644 .claude/hooks/fleet/no-test-in-scripts-guard/index.mts create mode 100644 .claude/hooks/fleet/no-test-in-scripts-guard/package.json create mode 100644 .claude/hooks/fleet/no-test-in-scripts-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-token-in-dotenv-guard/README.md create mode 100644 .claude/hooks/fleet/no-token-in-dotenv-guard/index.mts create mode 100644 .claude/hooks/fleet/no-token-in-dotenv-guard/package.json create mode 100644 .claude/hooks/fleet/no-token-in-dotenv-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-tsx-guard/README.md create mode 100644 .claude/hooks/fleet/no-tsx-guard/index.mts create mode 100644 .claude/hooks/fleet/no-tsx-guard/package.json create mode 100644 .claude/hooks/fleet/no-tsx-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-underscore-ident-guard/README.md create mode 100644 .claude/hooks/fleet/no-underscore-ident-guard/index.mts create mode 100644 .claude/hooks/fleet/no-underscore-ident-guard/package.json create mode 100644 .claude/hooks/fleet/no-underscore-ident-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-unisolated-git-fixture-guard/README.md create mode 100644 .claude/hooks/fleet/no-unisolated-git-fixture-guard/index.mts create mode 100644 .claude/hooks/fleet/no-unisolated-git-fixture-guard/package.json create mode 100644 .claude/hooks/fleet/no-unisolated-git-fixture-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-unmocked-ai-guard/README.md create mode 100644 .claude/hooks/fleet/no-unmocked-ai-guard/index.mts create mode 100644 .claude/hooks/fleet/no-unmocked-net-guard/README.md create mode 100644 .claude/hooks/fleet/no-unmocked-net-guard/index.mts create mode 100644 .claude/hooks/fleet/no-unmocked-net-guard/package.json create mode 100644 .claude/hooks/fleet/no-unmocked-net-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/no-verify-format-nudge/README.md create mode 100644 .claude/hooks/fleet/no-verify-format-nudge/index.mts create mode 100644 .claude/hooks/fleet/no-verify-format-nudge/package.json create mode 100644 .claude/hooks/fleet/no-verify-format-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/no-vitest-double-dash-guard/README.md create mode 100644 .claude/hooks/fleet/no-vitest-double-dash-guard/index.mts create mode 100644 .claude/hooks/fleet/node-modules-staging-guard/README.md create mode 100644 .claude/hooks/fleet/node-modules-staging-guard/index.mts create mode 100644 .claude/hooks/fleet/node-modules-staging-guard/package.json create mode 100644 .claude/hooks/fleet/node-modules-staging-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/non-fleet-pr-issue-ask-guard/README.md create mode 100644 .claude/hooks/fleet/non-fleet-pr-issue-ask-guard/index.mts create mode 100644 .claude/hooks/fleet/non-fleet-pr-issue-ask-guard/package.json create mode 100644 .claude/hooks/fleet/non-fleet-pr-issue-ask-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/npm-otp-flow-nudge/README.md create mode 100644 .claude/hooks/fleet/npm-otp-flow-nudge/index.mts create mode 100644 .claude/hooks/fleet/npm-otp-flow-nudge/package.json create mode 100644 .claude/hooks/fleet/npm-otp-flow-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/npmrc-trust-optout-guard/README.md create mode 100644 .claude/hooks/fleet/npmrc-trust-optout-guard/index.mts create mode 100644 .claude/hooks/fleet/npmrc-trust-optout-guard/package.json create mode 100644 .claude/hooks/fleet/npmrc-trust-optout-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/operate-from-repo-root-guard/README.md create mode 100644 .claude/hooks/fleet/operate-from-repo-root-guard/index.mts create mode 100644 .claude/hooks/fleet/options-param-naming-guard/README.md create mode 100644 .claude/hooks/fleet/options-param-naming-guard/index.mts create mode 100644 .claude/hooks/fleet/options-param-naming-guard/package.json create mode 100644 .claude/hooks/fleet/options-param-naming-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/overeager-staging-guard/README.md create mode 100644 .claude/hooks/fleet/overeager-staging-guard/index.mts create mode 100644 .claude/hooks/fleet/overeager-staging-guard/package.json create mode 100644 .claude/hooks/fleet/overeager-staging-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/oxlint-plugin-load-nudge/README.md create mode 100644 .claude/hooks/fleet/oxlint-plugin-load-nudge/index.mts create mode 100644 .claude/hooks/fleet/oxlint-plugin-load-nudge/is-plugin-path.mts create mode 100644 .claude/hooks/fleet/oxlint-plugin-load-nudge/package.json create mode 100644 .claude/hooks/fleet/oxlint-plugin-load-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/package-manager-auto-update-guard/README.md create mode 100644 .claude/hooks/fleet/package-manager-auto-update-guard/index.mts create mode 100644 .claude/hooks/fleet/package-manager-auto-update-guard/package.json create mode 100644 .claude/hooks/fleet/package-manager-auto-update-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/parallel-agent-edit-guard/README.md create mode 100644 .claude/hooks/fleet/parallel-agent-edit-guard/index.mts create mode 100644 .claude/hooks/fleet/parallel-agent-edit-guard/package.json create mode 100644 .claude/hooks/fleet/parallel-agent-edit-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/parallel-agent-on-stop-nudge/README.md create mode 100644 .claude/hooks/fleet/parallel-agent-on-stop-nudge/index.mts create mode 100644 .claude/hooks/fleet/parallel-agent-on-stop-nudge/package.json create mode 100644 .claude/hooks/fleet/parallel-agent-on-stop-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/parallel-agent-removal-nudge/README.md create mode 100644 .claude/hooks/fleet/parallel-agent-removal-nudge/index.mts create mode 100644 .claude/hooks/fleet/parallel-agent-removal-nudge/package.json create mode 100644 .claude/hooks/fleet/parallel-agent-removal-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/parallel-agent-spawn-nudge/README.md create mode 100644 .claude/hooks/fleet/parallel-agent-spawn-nudge/index.mts create mode 100644 .claude/hooks/fleet/parallel-agent-staging-guard/README.md create mode 100644 .claude/hooks/fleet/parallel-agent-staging-guard/index.mts create mode 100644 .claude/hooks/fleet/parallel-agent-staging-guard/package.json create mode 100644 .claude/hooks/fleet/parallel-agent-staging-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/path-guard/README.md create mode 100644 .claude/hooks/fleet/path-guard/index.mts create mode 100644 .claude/hooks/fleet/path-guard/package.json create mode 100644 .claude/hooks/fleet/path-guard/segments.mts create mode 100644 .claude/hooks/fleet/path-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/path-regex-normalize-nudge/README.md create mode 100644 .claude/hooks/fleet/path-regex-normalize-nudge/index.mts create mode 100644 .claude/hooks/fleet/path-regex-normalize-nudge/package.json create mode 100644 .claude/hooks/fleet/path-regex-normalize-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/paths-mts-inherit-guard/README.md create mode 100644 .claude/hooks/fleet/paths-mts-inherit-guard/index.mts create mode 100644 .claude/hooks/fleet/paths-mts-inherit-guard/package.json create mode 100644 .claude/hooks/fleet/paths-mts-inherit-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/personal-path-guard/README.md create mode 100644 .claude/hooks/fleet/personal-path-guard/index.mts create mode 100644 .claude/hooks/fleet/personal-path-guard/package.json create mode 100644 .claude/hooks/fleet/personal-path-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/plan-location-guard/README.md create mode 100644 .claude/hooks/fleet/plan-location-guard/index.mts create mode 100644 .claude/hooks/fleet/plan-location-guard/package.json create mode 100644 .claude/hooks/fleet/plan-location-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/plan-review-nudge/README.md create mode 100644 .claude/hooks/fleet/plan-review-nudge/index.mts create mode 100644 .claude/hooks/fleet/plan-review-nudge/package.json create mode 100644 .claude/hooks/fleet/plan-review-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/plugin-patch-format-guard/README.md create mode 100644 .claude/hooks/fleet/plugin-patch-format-guard/index.mts create mode 100644 .claude/hooks/fleet/plugin-patch-format-guard/package.json create mode 100644 .claude/hooks/fleet/plugin-patch-format-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/pnpm-filter-zero-match-nudge/README.md create mode 100644 .claude/hooks/fleet/pnpm-filter-zero-match-nudge/index.mts create mode 100644 .claude/hooks/fleet/pnpm-filter-zero-match-nudge/package.json create mode 100644 .claude/hooks/fleet/pnpm-filter-zero-match-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/pointer-comment-nudge/README.md create mode 100644 .claude/hooks/fleet/pointer-comment-nudge/index.mts create mode 100644 .claude/hooks/fleet/pointer-comment-nudge/package.json create mode 100644 .claude/hooks/fleet/pointer-comment-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/post-push-ci-monitor-nudge/README.md create mode 100644 .claude/hooks/fleet/post-push-ci-monitor-nudge/index.mts create mode 100644 .claude/hooks/fleet/post-push-ci-monitor-nudge/package.json create mode 100644 .claude/hooks/fleet/post-push-ci-monitor-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/pr-vs-push-default-nudge/README.md create mode 100644 .claude/hooks/fleet/pr-vs-push-default-nudge/index.mts create mode 100644 .claude/hooks/fleet/pr-vs-push-default-nudge/package.json create mode 100644 .claude/hooks/fleet/pr-vs-push-default-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/pre-commit-race-nudge/README.md create mode 100644 .claude/hooks/fleet/pre-commit-race-nudge/index.mts create mode 100644 .claude/hooks/fleet/pre-commit-race-nudge/package.json create mode 100644 .claude/hooks/fleet/pre-commit-race-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-async-spawn-guard/README.md create mode 100644 .claude/hooks/fleet/prefer-async-spawn-guard/index.mts create mode 100644 .claude/hooks/fleet/prefer-async-spawn-guard/package.json create mode 100644 .claude/hooks/fleet/prefer-async-spawn-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-evergreen-target-nudge/README.md create mode 100644 .claude/hooks/fleet/prefer-evergreen-target-nudge/index.mts create mode 100644 .claude/hooks/fleet/prefer-evergreen-target-nudge/package.json create mode 100644 .claude/hooks/fleet/prefer-evergreen-target-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-fff-search-nudge/README.md create mode 100644 .claude/hooks/fleet/prefer-fff-search-nudge/index.mts create mode 100644 .claude/hooks/fleet/prefer-fn-decl-guard/index.mts create mode 100644 .claude/hooks/fleet/prefer-fn-decl-guard/package.json create mode 100644 .claude/hooks/fleet/prefer-fn-decl-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-json-clone-guard/README.md create mode 100644 .claude/hooks/fleet/prefer-json-clone-guard/index.mts create mode 100644 .claude/hooks/fleet/prefer-json-clone-guard/package.json create mode 100644 .claude/hooks/fleet/prefer-json-clone-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-pipx-over-pip-guard/README.md create mode 100644 .claude/hooks/fleet/prefer-pipx-over-pip-guard/index.mts create mode 100644 .claude/hooks/fleet/prefer-pipx-over-pip-guard/package.json create mode 100644 .claude/hooks/fleet/prefer-pipx-over-pip-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-rebase-over-revert-nudge/README.md create mode 100644 .claude/hooks/fleet/prefer-rebase-over-revert-nudge/index.mts create mode 100644 .claude/hooks/fleet/prefer-rebase-over-revert-nudge/package.json create mode 100644 .claude/hooks/fleet/prefer-rebase-over-revert-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-type-import-guard/README.md create mode 100644 .claude/hooks/fleet/prefer-type-import-guard/index.mts create mode 100644 .claude/hooks/fleet/prefer-type-import-guard/package.json create mode 100644 .claude/hooks/fleet/prefer-type-import-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/prefer-vitest-guard/README.md create mode 100644 .claude/hooks/fleet/prefer-vitest-guard/index.mts create mode 100644 .claude/hooks/fleet/prefer-vitest-guard/package.json create mode 100644 .claude/hooks/fleet/prefer-vitest-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/primary-checkout-branch-guard/README.md create mode 100644 .claude/hooks/fleet/primary-checkout-branch-guard/index.mts create mode 100644 .claude/hooks/fleet/primary-checkout-branch-guard/package.json create mode 100644 .claude/hooks/fleet/primary-checkout-branch-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/private-name-nudge/README.md create mode 100644 .claude/hooks/fleet/private-name-nudge/index.mts create mode 100644 .claude/hooks/fleet/private-name-nudge/package.json create mode 100644 .claude/hooks/fleet/private-name-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/proc-environ-exfil-guard/README.md create mode 100644 .claude/hooks/fleet/proc-environ-exfil-guard/index.mts create mode 100644 .claude/hooks/fleet/proc-environ-exfil-guard/package.json create mode 100644 .claude/hooks/fleet/proc-environ-exfil-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/prompt-injection-guard/README.md create mode 100644 .claude/hooks/fleet/prompt-injection-guard/index.mts create mode 100644 .claude/hooks/fleet/prompt-injection-guard/package.json create mode 100644 .claude/hooks/fleet/prompt-injection-guard/test/payloads.mts create mode 100644 .claude/hooks/fleet/prompt-injection-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/prose-code-format-nudge/index.mts create mode 100644 .claude/hooks/fleet/provenance-publish-nudge/README.md create mode 100644 .claude/hooks/fleet/provenance-publish-nudge/index.mts create mode 100644 .claude/hooks/fleet/provenance-publish-nudge/package.json create mode 100644 .claude/hooks/fleet/provenance-publish-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/public-surface-nudge/README.md create mode 100644 .claude/hooks/fleet/public-surface-nudge/index.mts create mode 100644 .claude/hooks/fleet/public-surface-nudge/package.json create mode 100644 .claude/hooks/fleet/public-surface-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/pull-request-target-guard/README.md create mode 100644 .claude/hooks/fleet/pull-request-target-guard/index.mts create mode 100644 .claude/hooks/fleet/pull-request-target-guard/package.json create mode 100644 .claude/hooks/fleet/pull-request-target-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/push-protected-branch-guard/README.md create mode 100644 .claude/hooks/fleet/push-protected-branch-guard/index.mts create mode 100644 .claude/hooks/fleet/push-protected-branch-guard/package.json create mode 100644 .claude/hooks/fleet/push-protected-branch-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/read-orientation-nudge/index.mts create mode 100644 .claude/hooks/fleet/readme-fleet-shape-guard/README.md create mode 100644 .claude/hooks/fleet/readme-fleet-shape-guard/index.mts create mode 100644 .claude/hooks/fleet/readme-fleet-shape-guard/package.json create mode 100644 .claude/hooks/fleet/readme-fleet-shape-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/release-tag-tied-guard/README.md create mode 100644 .claude/hooks/fleet/release-tag-tied-guard/index.mts create mode 100644 .claude/hooks/fleet/release-tag-tied-guard/package.json create mode 100644 .claude/hooks/fleet/release-tag-tied-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/release-workflow-guard/README.md create mode 100644 .claude/hooks/fleet/release-workflow-guard/index.mts create mode 100644 .claude/hooks/fleet/release-workflow-guard/package.json create mode 100644 .claude/hooks/fleet/release-workflow-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/reply-prose-nudge/README.md create mode 100644 .claude/hooks/fleet/reply-prose-nudge/index.mts create mode 100644 .claude/hooks/fleet/reply-prose-nudge/package.json create mode 100644 .claude/hooks/fleet/reply-prose-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/repo-map-refresh/index.mts create mode 100644 .claude/hooks/fleet/report-location-guard/README.md create mode 100644 .claude/hooks/fleet/report-location-guard/index.mts create mode 100644 .claude/hooks/fleet/report-location-guard/package.json create mode 100644 .claude/hooks/fleet/report-location-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/reserved-script-dir-guard/README.md create mode 100644 .claude/hooks/fleet/reserved-script-dir-guard/index.mts create mode 100644 .claude/hooks/fleet/reserved-script-dir-guard/package.json create mode 100644 .claude/hooks/fleet/reserved-script-dir-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/scan-label-in-commit-guard/README.md create mode 100644 .claude/hooks/fleet/scan-label-in-commit-guard/index.mts create mode 100644 .claude/hooks/fleet/scan-label-in-commit-guard/package.json create mode 100644 .claude/hooks/fleet/scan-label-in-commit-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/secret-content-guard/README.md create mode 100644 .claude/hooks/fleet/secret-content-guard/index.mts create mode 100644 .claude/hooks/fleet/secret-content-guard/package.json create mode 100644 .claude/hooks/fleet/secret-content-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/sed-in-place-guard/README.md create mode 100644 .claude/hooks/fleet/sed-in-place-guard/index.mts create mode 100644 .claude/hooks/fleet/sed-in-place-guard/package.json create mode 100644 .claude/hooks/fleet/session-handoff-nudge/README.md create mode 100644 .claude/hooks/fleet/session-handoff-nudge/index.mts create mode 100644 .claude/hooks/fleet/session-handoff-nudge/package.json create mode 100644 .claude/hooks/fleet/session-handoff-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/setup-basics-tools/README.md create mode 100644 .claude/hooks/fleet/setup-basics-tools/install.mts create mode 100644 .claude/hooks/fleet/setup-basics-tools/package.json create mode 100644 .claude/hooks/fleet/setup-basics-tools/tsconfig.json create mode 100644 .claude/hooks/fleet/setup-claude-scanners/README.md create mode 100644 .claude/hooks/fleet/setup-claude-scanners/install.mts create mode 100644 .claude/hooks/fleet/setup-claude-scanners/package.json create mode 100644 .claude/hooks/fleet/setup-claude-scanners/tsconfig.json create mode 100644 .claude/hooks/fleet/setup-firewall/README.md create mode 100644 .claude/hooks/fleet/setup-firewall/install.mts create mode 100644 .claude/hooks/fleet/setup-firewall/package.json create mode 100644 .claude/hooks/fleet/setup-firewall/tsconfig.json create mode 100644 .claude/hooks/fleet/setup-misc-tools/README.md create mode 100644 .claude/hooks/fleet/setup-misc-tools/install.mts create mode 100644 .claude/hooks/fleet/setup-misc-tools/package.json create mode 100644 .claude/hooks/fleet/setup-misc-tools/tsconfig.json create mode 100644 .claude/hooks/fleet/setup-security-tools/README.md create mode 100644 .claude/hooks/fleet/setup-security-tools/external-tools.json create mode 100644 .claude/hooks/fleet/setup-security-tools/headroom/pyproject.toml create mode 100644 .claude/hooks/fleet/setup-security-tools/headroom/uv.lock create mode 100644 .claude/hooks/fleet/setup-security-tools/index.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/install.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/lib/api-token.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/lib/headroom.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/lib/installers.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/lib/operator-prompts.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/lib/shell-rc-bridge.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/lib/shims.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/lib/token-storage.mts create mode 100644 .claude/hooks/fleet/setup-security-tools/package.json create mode 100644 .claude/hooks/fleet/setup-security-tools/skillspector/pyproject.toml create mode 100644 .claude/hooks/fleet/setup-security-tools/skillspector/uv.lock create mode 100644 .claude/hooks/fleet/setup-security-tools/tsconfig.json create mode 100644 .claude/hooks/fleet/setup-security-tools/update.mts create mode 100644 .claude/hooks/fleet/setup-signing/README.md create mode 100644 .claude/hooks/fleet/setup-signing/install.mts create mode 100644 .claude/hooks/fleet/setup-signing/package.json create mode 100644 .claude/hooks/fleet/setup-signing/tsconfig.json create mode 100644 .claude/hooks/fleet/shallow-clone-guard/index.mts create mode 100644 .claude/hooks/fleet/shallow-clone-guard/package.json create mode 100644 .claude/hooks/fleet/shallow-clone-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/skill-usage-logger/README.md create mode 100644 .claude/hooks/fleet/skill-usage-logger/index.mts create mode 100644 .claude/hooks/fleet/skill-usage-logger/package.json create mode 100644 .claude/hooks/fleet/skill-usage-logger/tsconfig.json create mode 100644 .claude/hooks/fleet/small-pr-nudge/README.md create mode 100644 .claude/hooks/fleet/small-pr-nudge/index.mts create mode 100644 .claude/hooks/fleet/small-pr-nudge/package.json create mode 100644 .claude/hooks/fleet/small-pr-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/soak-exclude-date-guard/README.md create mode 100644 .claude/hooks/fleet/soak-exclude-date-guard/index.mts create mode 100644 .claude/hooks/fleet/soak-exclude-date-guard/package.json create mode 100644 .claude/hooks/fleet/soak-exclude-date-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/soak-exclude-scope-guard/README.md create mode 100644 .claude/hooks/fleet/soak-exclude-scope-guard/index.mts create mode 100644 .claude/hooks/fleet/soak-exclude-scope-guard/package.json create mode 100644 .claude/hooks/fleet/soak-exclude-scope-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/soak-pin-needs-annotation-guard/README.md create mode 100644 .claude/hooks/fleet/soak-pin-needs-annotation-guard/index.mts create mode 100644 .claude/hooks/fleet/soak-pin-needs-annotation-guard/package.json create mode 100644 .claude/hooks/fleet/soak-pin-needs-annotation-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/squash-history-nudge/README.md create mode 100644 .claude/hooks/fleet/squash-history-nudge/index.mts create mode 100644 .claude/hooks/fleet/squash-history-nudge/package.json create mode 100644 .claude/hooks/fleet/squash-history-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/stale-node-modules-nudge/README.md create mode 100644 .claude/hooks/fleet/stale-node-modules-nudge/index.mts create mode 100644 .claude/hooks/fleet/stale-process-sweeper/README.md create mode 100644 .claude/hooks/fleet/stale-process-sweeper/index.mts create mode 100644 .claude/hooks/fleet/stale-process-sweeper/package.json create mode 100644 .claude/hooks/fleet/stale-process-sweeper/tsconfig.json create mode 100644 .claude/hooks/fleet/stop-claim-verify-nudge/README.md create mode 100644 .claude/hooks/fleet/stop-claim-verify-nudge/index.mts create mode 100644 .claude/hooks/fleet/stop-claim-verify-nudge/package.json create mode 100644 .claude/hooks/fleet/stop-claim-verify-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/sweep-ds-store/README.md create mode 100644 .claude/hooks/fleet/sweep-ds-store/index.mts create mode 100644 .claude/hooks/fleet/sweep-ds-store/package.json create mode 100644 .claude/hooks/fleet/sweep-ds-store/tsconfig.json create mode 100644 .claude/hooks/fleet/synthesized-script-edit-guard/README.md create mode 100644 .claude/hooks/fleet/synthesized-script-edit-guard/index.mts create mode 100644 .claude/hooks/fleet/target-arch-env-guard/README.md create mode 100644 .claude/hooks/fleet/target-arch-env-guard/index.mts create mode 100644 .claude/hooks/fleet/target-arch-env-guard/package.json create mode 100644 .claude/hooks/fleet/target-arch-env-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/test-platform-coverage-nudge/index.mts create mode 100644 .claude/hooks/fleet/test-script-defers-guard/README.md create mode 100644 .claude/hooks/fleet/test-script-defers-guard/index.mts create mode 100644 .claude/hooks/fleet/test-script-defers-guard/package.json create mode 100644 .claude/hooks/fleet/test-script-defers-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/token-guard/README.md create mode 100644 .claude/hooks/fleet/token-guard/index.mts create mode 100644 .claude/hooks/fleet/token-guard/package.json create mode 100644 .claude/hooks/fleet/token-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/token-spend-guard/README.md create mode 100644 .claude/hooks/fleet/token-spend-guard/index.mts create mode 100644 .claude/hooks/fleet/token-spend-guard/package.json create mode 100644 .claude/hooks/fleet/token-spend-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/trust-downgrade-guard/README.md create mode 100644 .claude/hooks/fleet/trust-downgrade-guard/index.mts create mode 100644 .claude/hooks/fleet/trust-downgrade-guard/package.json create mode 100644 .claude/hooks/fleet/trust-downgrade-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/unbacked-claim-commit-guard/README.md create mode 100644 .claude/hooks/fleet/unbacked-claim-commit-guard/index.mts create mode 100644 .claude/hooks/fleet/unbacked-claim-commit-guard/package.json create mode 100644 .claude/hooks/fleet/unbacked-claim-commit-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/uncodified-lesson-nudge/README.md create mode 100644 .claude/hooks/fleet/uncodified-lesson-nudge/index.mts create mode 100644 .claude/hooks/fleet/uncodified-lesson-nudge/package.json create mode 100644 .claude/hooks/fleet/uncodified-lesson-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/unpushed-main-nudge/README.md create mode 100644 .claude/hooks/fleet/unpushed-main-nudge/index.mts create mode 100644 .claude/hooks/fleet/unpushed-main-nudge/package.json create mode 100644 .claude/hooks/fleet/unpushed-main-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/untrusted-coauthor-guard/README.md create mode 100644 .claude/hooks/fleet/untrusted-coauthor-guard/index.mts create mode 100644 .claude/hooks/fleet/untrusted-coauthor-guard/package.json create mode 100644 .claude/hooks/fleet/untrusted-coauthor-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/README.md create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/index.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/bash.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/cache.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/gitmodules.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/issue-types.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/package-json.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/regexes.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/validate-ref.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/lib/workflow.mts create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/package.json create mode 100644 .claude/hooks/fleet/uses-sha-verify-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/variant-analysis-nudge/README.md create mode 100644 .claude/hooks/fleet/variant-analysis-nudge/index.mts create mode 100644 .claude/hooks/fleet/variant-analysis-nudge/package.json create mode 100644 .claude/hooks/fleet/variant-analysis-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/verify-before-publish-guard/README.md create mode 100644 .claude/hooks/fleet/verify-before-publish-guard/index.mts create mode 100644 .claude/hooks/fleet/verify-render-pre-commit-nudge/README.md create mode 100644 .claude/hooks/fleet/verify-render-pre-commit-nudge/index.mts create mode 100644 .claude/hooks/fleet/verify-render-pre-commit-nudge/package.json create mode 100644 .claude/hooks/fleet/verify-render-pre-commit-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/version-bump-order-guard/README.md create mode 100644 .claude/hooks/fleet/version-bump-order-guard/index.mts create mode 100644 .claude/hooks/fleet/version-bump-order-guard/package.json create mode 100644 .claude/hooks/fleet/version-bump-order-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/vitest-vs-node-test-guard/README.md create mode 100644 .claude/hooks/fleet/vitest-vs-node-test-guard/index.mts create mode 100644 .claude/hooks/fleet/vitest-vs-node-test-guard/package.json create mode 100644 .claude/hooks/fleet/vitest-vs-node-test-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/vscode-folder-open-task-guard/README.md create mode 100644 .claude/hooks/fleet/vscode-folder-open-task-guard/index.mts create mode 100644 .claude/hooks/fleet/workflow-agent-task-tools-nudge/README.md create mode 100644 .claude/hooks/fleet/workflow-agent-task-tools-nudge/index.mts create mode 100644 .claude/hooks/fleet/workflow-multiline-body-guard/README.md create mode 100644 .claude/hooks/fleet/workflow-multiline-body-guard/index.mts create mode 100644 .claude/hooks/fleet/workflow-multiline-body-guard/package.json create mode 100644 .claude/hooks/fleet/workflow-multiline-body-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/workflow-uses-comment-guard/README.md create mode 100644 .claude/hooks/fleet/workflow-uses-comment-guard/index.mts create mode 100644 .claude/hooks/fleet/workflow-uses-comment-guard/package.json create mode 100644 .claude/hooks/fleet/workflow-uses-comment-guard/tsconfig.json create mode 100644 .claude/hooks/fleet/worktree-remove-relink-nudge/README.md create mode 100644 .claude/hooks/fleet/worktree-remove-relink-nudge/index.mts create mode 100644 .claude/hooks/fleet/worktree-remove-relink-nudge/package.json create mode 100644 .claude/hooks/fleet/worktree-remove-relink-nudge/tsconfig.json create mode 100644 .claude/hooks/fleet/zsh-word-split-nudge/index.mts create mode 100644 .claude/rules/fleet/claude-md-is-a-bullet-index.md create mode 100644 .claude/rules/fleet/code-first-then-ai.md create mode 100644 .claude/rules/fleet/prose-style-and-doctrine.md create mode 100644 .claude/rules/fleet/verify-state-before-acting.md create mode 100644 .claude/settings.json create mode 100644 .claude/skills/fleet/_shared/compound-lessons.md create mode 100644 .claude/skills/fleet/_shared/env-check.md create mode 100644 .claude/skills/fleet/_shared/multi-agent-backends.md create mode 100644 .claude/skills/fleet/_shared/path-guard-rule.md create mode 100644 .claude/skills/fleet/_shared/report-format.md create mode 100644 .claude/skills/fleet/_shared/scripts/checkpoint.mts create mode 100644 .claude/skills/fleet/_shared/scripts/fleet-roster.mts create mode 100644 .claude/skills/fleet/_shared/scripts/git-default-branch.mts create mode 100644 .claude/skills/fleet/_shared/scripts/logger-guardrails.mts create mode 100644 .claude/skills/fleet/_shared/scripts/resolve-tools.mts create mode 100644 .claude/skills/fleet/_shared/scripts/run-helpers.mts create mode 100644 .claude/skills/fleet/_shared/security-tools.md create mode 100644 .claude/skills/fleet/_shared/skill-authoring.md create mode 100644 .claude/skills/fleet/_shared/variant-analysis.md create mode 100644 .claude/skills/fleet/_shared/verify-build.md create mode 100644 .claude/skills/fleet/_shared/visual-verify.md create mode 100644 .claude/skills/fleet/agent-ci/SKILL.md create mode 100644 .claude/skills/fleet/agent-ci/reference.md create mode 100644 .claude/skills/fleet/auditing-api-surface/SKILL.md create mode 100644 .claude/skills/fleet/auditing-api-surface/lib/audit-api-surface.mts create mode 100644 .claude/skills/fleet/auditing-gha/SKILL.md create mode 100644 .claude/skills/fleet/auditing-gha/run.mts create mode 100644 .claude/skills/fleet/authoring-spec/SKILL.md create mode 100644 .claude/skills/fleet/building-tdd/SKILL.md create mode 100644 .claude/skills/fleet/cascading-fleet/SKILL.md create mode 100644 .claude/skills/fleet/cascading-fleet/lib/cascade-template.mts create mode 100644 .claude/skills/fleet/cascading-fleet/lib/fleet-repos.json create mode 100644 .claude/skills/fleet/cascading-fleet/lib/fleet-repos.txt create mode 100644 .claude/skills/fleet/cascading-fleet/lib/reconcile-lockfiles.mts create mode 100644 .claude/skills/fleet/cleaning-ci/SKILL.md create mode 100644 .claude/skills/fleet/cleaning-ci/lib/clean-ci.mts create mode 100644 .claude/skills/fleet/codifying-disciplines/SKILL.md create mode 100644 .claude/skills/fleet/consolidating-commits/SKILL.md create mode 100644 .claude/skills/fleet/creating-guards/SKILL.md create mode 100644 .claude/skills/fleet/decomposing-tickets/SKILL.md create mode 100644 .claude/skills/fleet/deduping-dependencies/SKILL.md create mode 100644 .claude/skills/fleet/delegating-execution/SKILL.md create mode 100644 .claude/skills/fleet/designing-interfaces/LICENSE create mode 100644 .claude/skills/fleet/designing-interfaces/SKILL.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/anti-ai-slop.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/color.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/copywriting.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/craft-details.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/delivery-and-craft.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/example-workflow.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/icons.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/interface-copy.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/mcp-tools.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/motion.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/refinement-modes.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/typography.md create mode 100644 .claude/skills/fleet/designing-interfaces/references/visual-workflow.md create mode 100644 .claude/skills/fleet/diagnosing-bugs/SKILL.md create mode 100644 .claude/skills/fleet/driving-cursor-bugbot/SKILL.md create mode 100644 .claude/skills/fleet/driving-cursor-bugbot/lib/bugbot.mts create mode 100644 .claude/skills/fleet/driving-cursor-bugbot/reference.md create mode 100644 .claude/skills/fleet/extracting-design-systems/SKILL.md create mode 100644 .claude/skills/fleet/greening-ci-local/SKILL.md create mode 100644 .claude/skills/fleet/greening-ci-local/run.mts create mode 100644 .claude/skills/fleet/greening-ci/SKILL.md create mode 100644 .claude/skills/fleet/greening-ci/run.mts create mode 100644 .claude/skills/fleet/grilling-plan/SKILL.md create mode 100644 .claude/skills/fleet/grooming-backlog/SKILL.md create mode 100644 .claude/skills/fleet/guarding-paths/SKILL.md create mode 100644 .claude/skills/fleet/guarding-paths/reference.md create mode 100644 .claude/skills/fleet/guarding-paths/templates/check-paths.mts.tmpl create mode 100644 .claude/skills/fleet/handing-off/SKILL.md create mode 100644 .claude/skills/fleet/improve/SKILL.md create mode 100644 .claude/skills/fleet/improve/references/audit-playbook.md create mode 100644 .claude/skills/fleet/improve/references/closing-the-loop.md create mode 100644 .claude/skills/fleet/improve/references/plan-template.md create mode 100644 .claude/skills/fleet/improving-web-interfaces/SKILL.md create mode 100644 .claude/skills/fleet/improving-web-interfaces/references/implementation.md create mode 100644 .claude/skills/fleet/locking-down-claude/SKILL.md create mode 100644 .claude/skills/fleet/looping-quality/SKILL.md create mode 100644 .claude/skills/fleet/managing-pnpm-workspaces/SKILL.md create mode 100644 .claude/skills/fleet/managing-pnpm-workspaces/references/catalog-policy.md create mode 100644 .claude/skills/fleet/managing-worktrees/SKILL.md create mode 100644 .claude/skills/fleet/managing-worktrees/lib/land.mts create mode 100644 .claude/skills/fleet/map/SKILL.md create mode 100644 .claude/skills/fleet/migrating-rule-packs/SKILL.md create mode 100644 .claude/skills/fleet/migrating-rule-packs/lib/run-migration.mts create mode 100644 .claude/skills/fleet/opening-pr/SKILL.md create mode 100644 .claude/skills/fleet/optimizing-react-interfaces/SKILL.md create mode 100644 .claude/skills/fleet/optimizing-react-interfaces/references/react-performance.md create mode 100644 .claude/skills/fleet/optimizing-submodules/SKILL.md create mode 100644 .claude/skills/fleet/patching-findings/SKILL.md create mode 100644 .claude/skills/fleet/patching-findings/references/procedure.md create mode 100644 .claude/skills/fleet/plugging-promise-race/SKILL.md create mode 100644 .claude/skills/fleet/prose/SKILL.md create mode 100644 .claude/skills/fleet/prose/references/conversational.md create mode 100644 .claude/skills/fleet/prose/references/examples.md create mode 100644 .claude/skills/fleet/prose/references/phrases.md create mode 100644 .claude/skills/fleet/prose/references/structures.md create mode 100644 .claude/skills/fleet/pushing/SKILL.md create mode 100644 .claude/skills/fleet/refreshing-history/SKILL.md create mode 100644 .claude/skills/fleet/refreshing-history/run.mts create mode 100644 .claude/skills/fleet/regenerating-patches/SKILL.md create mode 100644 .claude/skills/fleet/regenerating-patches/lib/regen-patches.mts create mode 100644 .claude/skills/fleet/releasing-a-package/SKILL.md create mode 100644 .claude/skills/fleet/rendering-chromium-to-png/SKILL.md create mode 100644 .claude/skills/fleet/rendering-chromium-to-png/screenshot.mts create mode 100644 .claude/skills/fleet/reordering-release-bump/SKILL.md create mode 100644 .claude/skills/fleet/reordering-release-bump/lib/reorder-bump.mts create mode 100644 .claude/skills/fleet/researching-recency/SKILL.md create mode 100644 .claude/skills/fleet/researching-recency/reference.md create mode 100644 .claude/skills/fleet/reviewing-code/SKILL.md create mode 100644 .claude/skills/fleet/reviewing-code/run.mts create mode 100644 .claude/skills/fleet/reviewing-web-interfaces/SKILL.md create mode 100644 .claude/skills/fleet/reviewing-web-interfaces/references/review-checklist.md create mode 100644 .claude/skills/fleet/running-test262/SKILL.md create mode 100644 .claude/skills/fleet/scanning-quality/SKILL.md create mode 100644 .claude/skills/fleet/scanning-quality/scans/bundle-trim.md create mode 100644 .claude/skills/fleet/scanning-quality/scans/deadcode-removal.md create mode 100644 .claude/skills/fleet/scanning-quality/scans/differential.md create mode 100644 .claude/skills/fleet/scanning-quality/scans/insecure-defaults.md create mode 100644 .claude/skills/fleet/scanning-quality/scans/variant-analysis.md create mode 100644 .claude/skills/fleet/scanning-security/SKILL.md create mode 100644 .claude/skills/fleet/scanning-vulns/SKILL.md create mode 100644 .claude/skills/fleet/scanning-vulns/references/procedure.md create mode 100644 .claude/skills/fleet/setup-repo/SKILL.md create mode 100644 .claude/skills/fleet/squashing-history/SKILL.md create mode 100644 .claude/skills/fleet/squashing-history/reference.md create mode 100644 .claude/skills/fleet/squashing-history/run.mts create mode 100644 .claude/skills/fleet/testing-web-interfaces/SKILL.md create mode 100644 .claude/skills/fleet/testing-web-interfaces/references/test-layering.md create mode 100644 .claude/skills/fleet/threat-modeling/SKILL.md create mode 100644 .claude/skills/fleet/threat-modeling/bootstrap.md create mode 100644 .claude/skills/fleet/threat-modeling/interview.md create mode 100644 .claude/skills/fleet/threat-modeling/schema.md create mode 100644 .claude/skills/fleet/tidying-files/SKILL.md create mode 100644 .claude/skills/fleet/tidying-files/lib/tidy-files.mts create mode 100644 .claude/skills/fleet/tidying-rolldown-bundles/SKILL.md create mode 100644 .claude/skills/fleet/tidying-rolldown-bundles/lib/tidy-rolldown-bundles.mts create mode 100644 .claude/skills/fleet/tidying-worktrees/SKILL.md create mode 100644 .claude/skills/fleet/tidying-worktrees/lib/tidy-worktrees.mts create mode 100644 .claude/skills/fleet/triaging-findings/SKILL.md create mode 100644 .claude/skills/fleet/triaging-findings/fixtures/canary-findings.json create mode 100644 .claude/skills/fleet/triaging-findings/fixtures/vulnerable.js create mode 100644 .claude/skills/fleet/triaging-findings/references/procedure.md create mode 100644 .claude/skills/fleet/trimming-bundle/SKILL.md create mode 100644 .claude/skills/fleet/trimming-bundle/lib/trim-loop.mts create mode 100644 .claude/skills/fleet/updating-coverage/SKILL.md create mode 100644 .claude/skills/fleet/updating-daily/SKILL.md create mode 100644 .claude/skills/fleet/updating-hooks-dry/SKILL.md create mode 100644 .claude/skills/fleet/updating-lockstep/SKILL.md create mode 100644 .claude/skills/fleet/updating-lockstep/reference.md create mode 100644 .claude/skills/fleet/updating-pricing/SKILL.md create mode 100644 .claude/skills/fleet/updating-security/SKILL.md create mode 100644 .claude/skills/fleet/updating-security/reference.md create mode 100644 .claude/skills/fleet/updating/SKILL.md create mode 100644 .claude/skills/fleet/updating/lib/discover.mts create mode 100644 .claude/skills/fleet/updating/reference.md create mode 100644 .claude/workflows/delegating-execution.js create mode 100644 .claude/workflows/reconcile-fleet-lockfiles.js create mode 100644 .claude/workflows/refresh-repo-map.js create mode 100644 .codex/config.toml create mode 100644 .config/fleet/.markdownlint-cli2.jsonc create mode 100644 .config/fleet/.prettierignore create mode 100644 .config/fleet/git-authors.json create mode 100644 .config/fleet/markdownlint-rules/_shared/freeform-readme-optin.mts create mode 100644 .config/fleet/markdownlint-rules/_shared/root-readme.mts create mode 100644 .config/fleet/markdownlint-rules/_shared/rule-types.mts create mode 100644 .config/fleet/markdownlint-rules/_shared/wheelhouse-self-skip.mts create mode 100644 .config/fleet/markdownlint-rules/socket-details-summary-blank-line.mts create mode 100644 .config/fleet/markdownlint-rules/socket-gfm-alert-keywords.mts create mode 100644 .config/fleet/markdownlint-rules/socket-no-empty-changelog-sections.mts create mode 100644 .config/fleet/markdownlint-rules/socket-no-private-wheelhouse-leak.mts create mode 100644 .config/fleet/markdownlint-rules/socket-no-relative-sibling-script.mts create mode 100644 .config/fleet/markdownlint-rules/socket-readme-required-sections.mts create mode 100644 .config/fleet/markdownlint-rules/socket-readme-social-badges.mts create mode 100644 .config/fleet/markdownlint-rules/socket-task-list-syntax.mts create mode 100644 .config/fleet/oxfmtrc.json create mode 100644 .config/fleet/oxlint-plugin/_shared/inject-import.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/export-top-level-functions/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/export-top-level-functions/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/guard-contract/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/guard-contract/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/inclusive-language/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/inclusive-language/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/max-file-lines/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/max-file-lines/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-agent-brand-assumption/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-agent-brand-assumption/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-bare-crypto-named-usage/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-bare-crypto-named-usage/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-bare-spawn-childproc-access/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-bare-spawn-childproc-access/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-boolean-trap-param/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-boolean-trap-param/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-cached-for-on-iterable/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-cached-for-on-iterable/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-comment-glob-star-slash/README.md create mode 100644 .config/fleet/oxlint-plugin/fleet/no-comment-glob-star-slash/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-comment-glob-star-slash/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-console-prefer-logger/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-console-prefer-logger/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-default-export/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-default-export/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-dynamic-import-outside-bundle/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-dynamic-import-outside-bundle/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-eslint-biome-config-ref/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-eslint-biome-config-ref/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-fetch-prefer-http-request/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-fetch-prefer-http-request/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-file-scope-oxlint-disable/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-file-scope-oxlint-disable/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-fileoverview-prefer-file/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-fileoverview-prefer-file/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-inline-defer-async/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-inline-defer-async/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-inline-logger/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-inline-logger/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-lib-barrel-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-lib-barrel-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-logger-newline-literal/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-logger-newline-literal/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-malformed-bypass-marker/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-malformed-bypass-marker/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-minified-bundler-output/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-minified-bundler-output/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-module-eval-side-effects/README.md create mode 100644 .config/fleet/oxlint-plugin/fleet/no-module-eval-side-effects/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-module-eval-side-effects/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-namespace-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-namespace-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-npx-dlx/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-npx-dlx/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-options-param-mutation/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-options-param-mutation/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-package-manager-auto-update-reenable/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-package-manager-auto-update-reenable/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-placeholders/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-placeholders/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-platform-specific-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-platform-specific-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-private-path-in-source/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-private-path-in-source/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-process-chdir/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-process-chdir/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-process-cwd-in-scripts-hooks/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-process-cwd-in-scripts-hooks/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-promise-race-in-loop/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-promise-race-in-loop/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-promise-race/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-promise-race/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-runtime-features-below-engine-floor/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-runtime-features-below-engine-floor/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-source-sniffing/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-source-sniffing/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-spawn-stream-double-consume/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-spawn-stream-double-consume/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-spawnsync-code-field/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-spawnsync-code-field/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-src-import-in-test-expect/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-src-import-in-test-expect/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-status-emoji/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-status-emoji/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-structured-clone-prefer-json/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-structured-clone-prefer-json/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-sync-rm-in-test-lifecycle/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-sync-rm-in-test-lifecycle/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-top-level-await/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-top-level-await/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-underscore-identifier/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-underscore-identifier/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-use-strict-in-esm/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-use-strict-in-esm/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-empty-test/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-empty-test/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-focused-tests/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-focused-tests/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-identical-title/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-identical-title/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-skipped-tests/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-skipped-tests/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-standalone-expect/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-vitest-standalone-expect/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/no-which-for-local-bin/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/no-which-for-local-bin/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/normalize-path-before-match/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/normalize-path-before-match/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/optional-explicit-undefined/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/optional-explicit-undefined/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/options-null-proto/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/options-null-proto/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/options-param-naming/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/options-param-naming/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/personal-path-placeholders/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/personal-path-placeholders/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-async-spawn/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-async-spawn/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-cached-for-loop/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-cached-for-loop/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-ellipsis-char/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-ellipsis-char/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-env-as-boolean/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-env-as-boolean/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-error-message-helper/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-error-message-helper/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-error-message/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-error-message/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-exists-sync/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-exists-sync/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-find-repo-root/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-find-repo-root/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-find-up-package-json/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-find-up-package-json/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-function-declaration/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-function-declaration/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-mock-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-mock-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-node-builtin-imports/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-node-builtin-imports/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-node-modules-dot-cache/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-node-modules-dot-cache/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-non-capturing-group/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-non-capturing-group/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-normalize-path/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-normalize-path/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-optional-chain/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-optional-chain/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-pure-call-form/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-pure-call-form/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-safe-delete/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-safe-delete/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-separate-type-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-separate-type-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-shell-win32/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-shell-win32/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-spawn-over-execsync/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-spawn-over-execsync/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-stable-external-semver/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-stable-external-semver/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-stable-self-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-stable-self-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-static-type-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-static-type-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-typebox-schema/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-typebox-schema/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-undefined-over-null/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-undefined-over-null/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-windows-test-helpers/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/prefer-windows-test-helpers/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/require-async-iife-entry/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/require-async-iife-entry/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/require-regex-comment/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/require-regex-comment/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/require-vitest-globals-import/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/require-vitest-globals-import/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/socket-api-token-env/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/socket-api-token-env/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-array-literals/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-array-literals/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-boolean-chains/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-boolean-chains/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-equality-disjunctions/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-equality-disjunctions/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-named-imports/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-named-imports/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-object-literal-properties/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-object-literal-properties/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-regex-alternations/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-regex-alternations/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-set-args/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-set-args/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-source-methods/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/sort-source-methods/package.json create mode 100644 .config/fleet/oxlint-plugin/fleet/use-fleet-canonical-api-token-getter/index.mts create mode 100644 .config/fleet/oxlint-plugin/fleet/use-fleet-canonical-api-token-getter/package.json create mode 100644 .config/fleet/oxlint-plugin/index.mts create mode 100644 .config/fleet/oxlint-plugin/lib/comment-checks.mts create mode 100644 .config/fleet/oxlint-plugin/lib/comment-markers.mts create mode 100644 .config/fleet/oxlint-plugin/lib/comparators.mts create mode 100644 .config/fleet/oxlint-plugin/lib/detect-source-type.mts create mode 100644 .config/fleet/oxlint-plugin/lib/fleet-paths.mts create mode 100644 .config/fleet/oxlint-plugin/lib/iterable-kind.mts create mode 100644 .config/fleet/oxlint-plugin/lib/logical-chain.mts create mode 100644 .config/fleet/oxlint-plugin/lib/rule-tester.mts create mode 100644 .config/fleet/oxlint-plugin/lib/rule-types.mts create mode 100644 .config/fleet/oxlint-plugin/lib/runtime-feature-floors.mts create mode 100644 .config/fleet/oxlint-plugin/lib/test-file.mts create mode 100644 .config/fleet/oxlint-plugin/lib/vitest-fn-call.mts create mode 100644 .config/fleet/oxlint-plugin/package.json create mode 100644 .config/fleet/oxlint.config.mts create mode 100644 .config/fleet/oxlintrc.json create mode 100644 .config/fleet/pnpm-workspace.fleet.yaml create mode 100644 .config/fleet/rolldown/hook-bundle.config.mts create mode 100644 .config/fleet/sfw-bypass-list.txt create mode 100644 .config/fleet/taze.config.mts create mode 100644 .config/fleet/tsconfig.base.json create mode 100644 .config/fleet/tsconfig.check.base.json create mode 100644 .config/fleet/vitest.coverage.fleet.config.mts create mode 100644 .config/lockstep.schema.json create mode 100644 .editorconfig create mode 100644 .git-hooks/_shared/ai-attribution.mts create mode 100644 .git-hooks/_shared/commit-format.mts create mode 100644 .git-hooks/_shared/commit-subject.mts create mode 100644 .git-hooks/_shared/cross-repo.mts create mode 100644 .git-hooks/_shared/external-issue-ref.mts create mode 100644 .git-hooks/_shared/git-identity.mts create mode 100644 .git-hooks/_shared/helpers.mts create mode 100644 .git-hooks/_shared/isolate-git-env.mts create mode 100644 .git-hooks/_shared/logger-leaks.mts create mode 100644 .git-hooks/_shared/personal-path.mts create mode 100644 .git-hooks/_shared/resolve-node.sh create mode 100755 .git-hooks/commit-msg create mode 100755 .git-hooks/fleet/commit-msg create mode 100644 .git-hooks/fleet/commit-msg.mts create mode 100755 .git-hooks/fleet/pre-commit create mode 100644 .git-hooks/fleet/pre-commit.mts create mode 100755 .git-hooks/fleet/pre-push create mode 100644 .git-hooks/fleet/pre-push.mts create mode 100755 .git-hooks/post-commit create mode 100755 .git-hooks/pre-commit create mode 100755 .git-hooks/pre-push create mode 100644 .gitattributes create mode 100644 .github/actions/fleet/cache-pnpm-store/action.yml create mode 100644 .github/actions/fleet/checkout/action.yml create mode 100644 .github/actions/fleet/cleanup-git-signing/action.yml create mode 100644 .github/actions/fleet/debug/action.yml create mode 100644 .github/actions/fleet/github-pr-app-token/action.yml create mode 100644 .github/actions/fleet/github-pr-app-token/mint-app-installation-token.mjs create mode 100644 .github/actions/fleet/github-release-app-token/action.yml create mode 100644 .github/actions/fleet/github-release-app-token/mint-app-installation-token.mjs create mode 100644 .github/actions/fleet/github-release/action.yml create mode 100644 .github/actions/fleet/github-status-check/action.yml create mode 100644 .github/actions/fleet/install/action.yml create mode 100644 .github/actions/fleet/lib/external-tools.json create mode 100644 .github/actions/fleet/lib/install-tool.mjs create mode 100644 .github/actions/fleet/lib/jq.mjs create mode 100644 .github/actions/fleet/lib/platform.mjs create mode 100644 .github/actions/fleet/lib/semver.mjs create mode 100644 .github/actions/fleet/run-script/action.yml create mode 100644 .github/actions/fleet/setup-and-install/action.yml create mode 100644 .github/actions/fleet/setup-git-signing/action.yml create mode 100644 .github/actions/fleet/setup-go-toolchain/action.yml create mode 100644 .github/actions/fleet/setup/action.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/_local-not-for-reuse-get-green.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/get-green.lock.yml create mode 100644 .github/workflows/get-green.md create mode 100644 .github/workflows/get-green.yml create mode 100644 .github/workflows/github-release.yml create mode 100644 .github/workflows/npm-publish.yml create mode 100644 .github/workflows/prune-workflow-runs.yml create mode 100644 .github/workflows/publish-npm.yml create mode 100644 .github/workflows/weekly-update-non-gh-aw.yml.disabled create mode 100644 .github/workflows/weekly-update.lock.yml create mode 100644 .github/workflows/weekly-update.md create mode 100644 .kimi-code/mcp.json create mode 100644 .mcp.json create mode 100644 .node-version create mode 100644 .npmrc create mode 100644 assets/repo/badges/coverage.svg create mode 100644 bootstrap/fleet.mjs create mode 100644 bootstrap/prepare.mts create mode 100644 bootstrap/src/dispatch-wiring.mts create mode 100644 bootstrap/src/fleet.mts create mode 100644 bootstrap/src/helpers.mts create mode 100644 bootstrap/src/install.mts create mode 100644 bootstrap/src/lockstep.mts create mode 100644 bootstrap/src/resolve.mts create mode 100644 bootstrap/src/settings.mts create mode 100644 bootstrap/src/status.mts create mode 100644 docs/agents.md/fleet/adversarial-self-review.md create mode 100644 docs/agents.md/fleet/agent-delegation.md create mode 100644 docs/agents.md/fleet/agents-and-skills.md create mode 100644 docs/agents.md/fleet/binary-vs-napi-naming.md create mode 100644 docs/agents.md/fleet/bypass-phrases.md create mode 100644 docs/agents.md/fleet/c8-ignore-directives.md create mode 100644 docs/agents.md/fleet/cascade-file-classification.md create mode 100644 docs/agents.md/fleet/cascade-is-a-unit.md create mode 100644 docs/agents.md/fleet/cascaded-hook-catalog.md create mode 100644 docs/agents.md/fleet/code-is-law.md create mode 100644 docs/agents.md/fleet/code-style.md create mode 100644 docs/agents.md/fleet/commit-cadence-format.md create mode 100644 docs/agents.md/fleet/commit-signing.md create mode 100644 docs/agents.md/fleet/conformance-runners.md create mode 100644 docs/agents.md/fleet/cross-tool-agents.md create mode 100644 docs/agents.md/fleet/database.md create mode 100644 docs/agents.md/fleet/delegating-execution.md create mode 100644 docs/agents.md/fleet/diagnosing-bugs.md create mode 100644 docs/agents.md/fleet/disabled-seam-pattern.md create mode 100644 docs/agents.md/fleet/drift-watch.md create mode 100644 docs/agents.md/fleet/error-messages.md create mode 100644 docs/agents.md/fleet/export-and-no-any.md create mode 100644 docs/agents.md/fleet/fable-fallback.md create mode 100644 docs/agents.md/fleet/file-size.md create mode 100644 docs/agents.md/fleet/fleet-doctor.md create mode 100644 docs/agents.md/fleet/gh-token-hygiene.md create mode 100644 docs/agents.md/fleet/git-config-write-guard.md create mode 100644 docs/agents.md/fleet/github-token-limitations.md create mode 100644 docs/agents.md/fleet/golden-fixtures.md create mode 100644 docs/agents.md/fleet/history-rewrites.md create mode 100644 docs/agents.md/fleet/hook-bundle.md create mode 100644 docs/agents.md/fleet/hook-registry.md create mode 100644 docs/agents.md/fleet/immutable-releases.md create mode 100644 docs/agents.md/fleet/inclusive-language.md create mode 100644 docs/agents.md/fleet/judgment-and-self-evaluation.md create mode 100644 docs/agents.md/fleet/lint-rules.md create mode 100644 docs/agents.md/fleet/lockstep.md create mode 100644 docs/agents.md/fleet/max-file-lines-hard-cap-only.md create mode 100644 docs/agents.md/fleet/memory-codification.md create mode 100644 docs/agents.md/fleet/module-naming.md create mode 100644 docs/agents.md/fleet/multi-ecosystem-soak.md create mode 100644 docs/agents.md/fleet/multi-janus-mcp-shim.md create mode 100644 docs/agents.md/fleet/no-disable-lint-rule.md create mode 100644 docs/agents.md/fleet/no-live-network-in-tests.md create mode 100644 docs/agents.md/fleet/no-local-fork.md create mode 100644 docs/agents.md/fleet/normalize-path-before-match.md create mode 100644 docs/agents.md/fleet/options-object.md create mode 100644 docs/agents.md/fleet/parallel-claude-sessions.md create mode 100644 docs/agents.md/fleet/parser-comments.md create mode 100644 docs/agents.md/fleet/path-hygiene.md create mode 100644 docs/agents.md/fleet/plan-storage.md create mode 100644 docs/agents.md/fleet/plugin-cache-patches.md create mode 100644 docs/agents.md/fleet/pnpm-patching.md create mode 100644 docs/agents.md/fleet/pr-review-comments.md create mode 100644 docs/agents.md/fleet/precommit-time-gate.md create mode 100644 docs/agents.md/fleet/prompt-injection.md create mode 100644 docs/agents.md/fleet/prose-style-and-doctrine.md create mode 100644 docs/agents.md/fleet/public-surface-hygiene.md create mode 100644 docs/agents.md/fleet/pull-request-target.md create mode 100644 docs/agents.md/fleet/push-policy.md create mode 100644 docs/agents.md/fleet/release-vs-cascade.md create mode 100644 docs/agents.md/fleet/repo-map.md create mode 100644 docs/agents.md/fleet/researching-recency.md create mode 100644 docs/agents.md/fleet/runtime-feature-floors.md create mode 100644 docs/agents.md/fleet/runtime-state-and-caches.md create mode 100644 docs/agents.md/fleet/rust-members.md create mode 100644 docs/agents.md/fleet/script-aggregation.md create mode 100644 docs/agents.md/fleet/security-stack.md create mode 100644 docs/agents.md/fleet/shared-workflow-cascade.md create mode 100644 docs/agents.md/fleet/single-source-of-truth.md create mode 100644 docs/agents.md/fleet/skill-model-routing.md create mode 100644 docs/agents.md/fleet/socket-bypass-markers.md create mode 100644 docs/agents.md/fleet/sorting.md create mode 100644 docs/agents.md/fleet/stop-the-bleeding.md create mode 100644 docs/agents.md/fleet/stranded-cascades.md create mode 100644 docs/agents.md/fleet/telemetry-lockdown.md create mode 100644 docs/agents.md/fleet/test-layout.md create mode 100644 docs/agents.md/fleet/test-scripts-defer-to-mts.md create mode 100644 docs/agents.md/fleet/thin-distribution.md create mode 100644 docs/agents.md/fleet/token-hygiene.md create mode 100644 docs/agents.md/fleet/token-minification.md create mode 100644 docs/agents.md/fleet/token-spend.md create mode 100644 docs/agents.md/fleet/tooling.md create mode 100644 docs/agents.md/fleet/untracked-by-default.md create mode 100644 docs/agents.md/fleet/upstream-references.md create mode 100644 docs/agents.md/fleet/verify-state-before-acting.md create mode 100644 docs/agents.md/fleet/version-bumps.md create mode 100644 docs/agents.md/fleet/vocabulary.md create mode 100644 docs/agents.md/fleet/weekly-update-fallback.md create mode 100644 docs/agents.md/fleet/windows-gotchas.md create mode 100644 docs/agents.md/fleet/workflow-run-retention.md create mode 100644 docs/agents.md/fleet/worktree-hygiene.md create mode 100644 docs/agents.md/fleet/writing-skills-well.md create mode 100644 docs/references/sfw-local-install.md create mode 100644 opencode.json create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/fleet/_shared/active-run-marker.mts create mode 100644 scripts/fleet/_shared/check-steps-hooks.mts create mode 100644 scripts/fleet/_shared/check-steps-paths.mts create mode 100644 scripts/fleet/_shared/check-steps-release.mts create mode 100644 scripts/fleet/_shared/check-steps.mts create mode 100644 scripts/fleet/_shared/format-scope.mts create mode 100644 scripts/fleet/_shared/git-porcelain.mts create mode 100644 scripts/fleet/_shared/gitmodules.mts create mode 100644 scripts/fleet/_shared/is-main-module.mts create mode 100644 scripts/fleet/_shared/repo-checks.mts create mode 100644 scripts/fleet/_shared/repo-setup.mts create mode 100644 scripts/fleet/_shared/run-main.mts create mode 100644 scripts/fleet/_shared/scope-flags.mts create mode 100644 scripts/fleet/_shared/tar-executable.mts create mode 100644 scripts/fleet/_shared/tracked-globs.mts create mode 100644 scripts/fleet/agent-ci-skip-locks.mts create mode 100644 scripts/fleet/ai-backends-status.mts create mode 100644 scripts/fleet/ai-codify/cli.mts create mode 100644 scripts/fleet/ai-codify/codify-guidance.mts create mode 100644 scripts/fleet/ai-lint-fix.mts create mode 100644 scripts/fleet/ai-lint-fix/claude.mts create mode 100644 scripts/fleet/ai-lint-fix/health.mts create mode 100644 scripts/fleet/ai-lint-fix/oxlint-json.mts create mode 100644 scripts/fleet/ai-lint-fix/prompt.mts create mode 100644 scripts/fleet/ai-lint-fix/rule-guidance.mts create mode 100644 scripts/fleet/audit-transcript.mts create mode 100644 scripts/fleet/auditing-history/lib/patch-id.mts create mode 100644 scripts/fleet/auditing-history/lib/types.mts create mode 100644 scripts/fleet/build-hook-bundle.mts create mode 100644 scripts/fleet/build-hook-snapshot.mts create mode 100644 scripts/fleet/build-snapshot-launcher.mts create mode 100644 scripts/fleet/bump.mts create mode 100644 scripts/fleet/cargo-publish.mts create mode 100644 scripts/fleet/check.mts create mode 100644 scripts/fleet/check/action-pins-are-current.mts create mode 100644 scripts/fleet/check/actions-are-segmented.mts create mode 100644 scripts/fleet/check/agent-ci-skip-locks-is-guarded.mts create mode 100644 scripts/fleet/check/ai-spawns-have-paired-effort.mts create mode 100644 scripts/fleet/check/app-token-minters-are-identical.mts create mode 100644 scripts/fleet/check/app-tokens-are-scoped.mts create mode 100644 scripts/fleet/check/backend-routing-is-legal.mts create mode 100644 scripts/fleet/check/baseline-catalog-deps-are-covered.mts create mode 100644 scripts/fleet/check/brew-install-is-pinned.mts create mode 100644 scripts/fleet/check/brew-supply-chain-is-hardened.mts create mode 100644 scripts/fleet/check/bundle-is-installable.mts create mode 100644 scripts/fleet/check/cargo-soak-config-is-current.mts create mode 100644 scripts/fleet/check/cascaded-fleet-trees-have-no-tests.mts create mode 100644 scripts/fleet/check/cdn-allowlist-is-respected.mts create mode 100644 scripts/fleet/check/changelog-is-commit-derived.mts create mode 100644 scripts/fleet/check/check-names-are-assertions.mts create mode 100644 scripts/fleet/check/check-registrations-resolve.mts create mode 100644 scripts/fleet/check/ci-local-is-canonical.mts create mode 100644 scripts/fleet/check/claude-config-is-hardened.mts create mode 100644 scripts/fleet/check/claude-dirs-are-segmented.mts create mode 100644 scripts/fleet/check/claude-md-citations-resolve.mts create mode 100644 scripts/fleet/check/claude-md-rules-are-enforced.mts create mode 100644 scripts/fleet/check/claude-md-rules-are-informative.mts create mode 100644 scripts/fleet/check/convention-guards-consult-fleet-context.mts create mode 100644 scripts/fleet/check/coverage-badge-is-current.mts create mode 100644 scripts/fleet/check/dedup-patches-are-justified.mts create mode 100644 scripts/fleet/check/dependencies-are-deduped.mts create mode 100644 scripts/fleet/check/design-skill-cluster-is-connected.mts create mode 100644 scripts/fleet/check/dispatch-matchers-cover-hook-tools.mts create mode 100644 scripts/fleet/check/dispatch-table-is-current.mts create mode 100644 scripts/fleet/check/doc-references-resolve.mts create mode 100644 scripts/fleet/check/egress-allowlist-is-gh-aw-subset.mts create mode 100644 scripts/fleet/check/enforcers-have-thorough-tests.mts create mode 100644 scripts/fleet/check/entry-scripts-are-fail-soft.mts create mode 100644 scripts/fleet/check/env-kill-switches-are-absent.mts create mode 100644 scripts/fleet/check/error-messages-are-thorough.mts create mode 100644 scripts/fleet/check/external-tools-are-valid.mts create mode 100644 scripts/fleet/check/external-tools-match-wheelhouse.mts create mode 100644 scripts/fleet/check/fable-spawns-have-opus-fallback.mts create mode 100644 scripts/fleet/check/fleet-soak-exclude-parity.mts create mode 100644 scripts/fleet/check/generated-globs-are-consistent.mts create mode 100644 scripts/fleet/check/gh-aw-locks-are-current.mts create mode 100644 scripts/fleet/check/gh-aw-workflow-models-are-canonical.mts create mode 100644 scripts/fleet/check/go-deps-are-soaked.mts create mode 100644 scripts/fleet/check/golden-fixtures-are-named-golden.mts create mode 100644 scripts/fleet/check/headroom-is-telemetry-locked-down.mts create mode 100644 scripts/fleet/check/headroom-pin-is-consistent.mts create mode 100644 scripts/fleet/check/headroom-proxy-is-lossless.mts create mode 100644 scripts/fleet/check/hook-dirs-are-not-husks.mts create mode 100644 scripts/fleet/check/hook-imports-are-declared.mts create mode 100644 scripts/fleet/check/hook-main-is-entrypoint-guarded.mts create mode 100644 scripts/fleet/check/hook-names-are-accurate.mts create mode 100644 scripts/fleet/check/hook-registry-is-current.mts create mode 100644 scripts/fleet/check/hook-snapshot-is-wired.mts create mode 100644 scripts/fleet/check/hooks-have-no-guard-nudge-overlap.mts create mode 100644 scripts/fleet/check/lint-configs-protect-vendored.mts create mode 100644 scripts/fleet/check/linters-are-oxlint-oxfmt-only.mts create mode 100644 scripts/fleet/check/llms-txt-is-current.mts create mode 100644 scripts/fleet/check/lock-step-headers-match.mts create mode 100644 scripts/fleet/check/lock-step-refs-resolve.mts create mode 100644 scripts/fleet/check/markdown-doc-headers-are-plain.mts create mode 100644 scripts/fleet/check/markdown-filenames-are-canonical.mts create mode 100644 scripts/fleet/check/mcp-client-configs-are-current.mts create mode 100644 scripts/fleet/check/memories-are-codified.mts create mode 100644 scripts/fleet/check/mutating-skills-have-model.mts create mode 100644 scripts/fleet/check/name-rename-is-complete.mts create mode 100644 scripts/fleet/check/npmrc-socket-soak-excludes-are-derived.mts create mode 100644 scripts/fleet/check/oxlint-plugin-loads.mts create mode 100644 scripts/fleet/check/pack-contents-are-clean.mts create mode 100644 scripts/fleet/check/package-deps-have-no-link-protocol.mts create mode 100644 scripts/fleet/check/package-files-are-allowlisted.mts create mode 100644 scripts/fleet/check/package-manager-auto-update-is-disabled.mts create mode 100644 scripts/fleet/check/package-manager-node-is-continuous.mts create mode 100644 scripts/fleet/check/package-manager-pins-are-synced.mts create mode 100644 scripts/fleet/check/path-tools-are-at-pinned-version.mts create mode 100644 scripts/fleet/check/paths-are-canonical.mts create mode 100644 scripts/fleet/check/paths-are-normalized-before-match.mts create mode 100644 scripts/fleet/check/paths/allowlist.mts create mode 100644 scripts/fleet/check/paths/exempt.mts create mode 100644 scripts/fleet/check/paths/rules.mts create mode 100644 scripts/fleet/check/paths/scan-code.mts create mode 100644 scripts/fleet/check/paths/scan-script.mts create mode 100644 scripts/fleet/check/paths/scan-workflow.mts create mode 100644 scripts/fleet/check/paths/state.mts create mode 100644 scripts/fleet/check/paths/types.mts create mode 100644 scripts/fleet/check/paths/walk.mts create mode 100644 scripts/fleet/check/platform-tails-match-naming-domain.mts create mode 100644 scripts/fleet/check/pnpm-run-citations-resolve.mts create mode 100644 scripts/fleet/check/precommit-steps-are-bounded.mts create mode 100644 scripts/fleet/check/prettierignore-globs-are-anchored.mts create mode 100644 scripts/fleet/check/pricing-data-is-current.mts create mode 100644 scripts/fleet/check/private-paths-are-absent.mts create mode 100644 scripts/fleet/check/provenance-is-attested.mts create mode 100644 scripts/fleet/check/public-files-are-exported.mts create mode 100644 scripts/fleet/check/publish-config-is-hardened.mts create mode 100644 scripts/fleet/check/publishable-version-is-prerelease-hint.mts create mode 100644 scripts/fleet/check/published-packages-have-files-field.mts create mode 100644 scripts/fleet/check/release-and-cascade-are-paired.mts create mode 100644 scripts/fleet/check/release-publish-scripts-are-conventionally-named.mts create mode 100644 scripts/fleet/check/researching-recency-contract-is-current.mts create mode 100644 scripts/fleet/check/review-stages-are-ordered.mts create mode 100644 scripts/fleet/check/root-scripts-are-segregated.mts create mode 100644 scripts/fleet/check/rule-citations-are-generic.mts create mode 100644 scripts/fleet/check/scanner-parity.mts create mode 100644 scripts/fleet/check/script-paths-resolve.mts create mode 100644 scripts/fleet/check/setup-is-prompt-less.mts create mode 100644 scripts/fleet/check/shared-hook-helpers-are-used.mts create mode 100644 scripts/fleet/check/skill-system-is-coherent.mts create mode 100644 scripts/fleet/check/skills-are-well-formed.mts create mode 100644 scripts/fleet/check/skillspector-pin-is-consistent.mts create mode 100644 scripts/fleet/check/soak-excludes-have-dates.mts create mode 100644 scripts/fleet/check/soak-time-is-consistent.mts create mode 100644 scripts/fleet/check/source-is-windows-portable.mts create mode 100644 scripts/fleet/check/sparkle-auto-update-is-disabled.mts create mode 100644 scripts/fleet/check/stable-aliases-match-base.mts create mode 100644 scripts/fleet/check/subagent-status-doc-is-current.mts create mode 100644 scripts/fleet/check/submodules-are-sparse-or-annotated.mts create mode 100644 scripts/fleet/check/telemetry-deps-are-reviewed.mts create mode 100644 scripts/fleet/check/telemetry-env-is-disabled.mts create mode 100644 scripts/fleet/check/test-scripts-are-deferred.mts create mode 100644 scripts/fleet/check/tests-are-mirror-named.mts create mode 100644 scripts/fleet/check/tracked-files-are-within-size-cap.mts create mode 100644 scripts/fleet/check/tracked-symlinks-are-safe.mts create mode 100644 scripts/fleet/check/trust-gates-are-not-weakened.mts create mode 100644 scripts/fleet/check/upstream-submodules-are-shallow-single-branch.mts create mode 100644 scripts/fleet/check/uv-lockfiles-are-current.mts create mode 100644 scripts/fleet/check/vite-is-rolldown-native.mts create mode 100644 scripts/fleet/check/weekly-update-fallback-is-disabled.mts create mode 100644 scripts/fleet/check/workflow-scripts-are-explicit-scope.mts create mode 100644 scripts/fleet/codify-rule.mts create mode 100644 scripts/fleet/codify-scan/inventory.mts create mode 100644 scripts/fleet/compress.mts create mode 100644 scripts/fleet/consolidate-commits.mts create mode 100644 scripts/fleet/constants/bot-identity.mts create mode 100644 scripts/fleet/constants/brew-tap-pins.mts create mode 100644 scripts/fleet/constants/generated-globs.mts create mode 100644 scripts/fleet/constants/model-pricing.json create mode 100644 scripts/fleet/constants/npm-registry.mts create mode 100644 scripts/fleet/constants/plugin-patch.mts create mode 100644 scripts/fleet/constants/soak-excludes.mts create mode 100644 scripts/fleet/constants/soak.mts create mode 100644 scripts/fleet/constants/socket-scopes.mts create mode 100644 scripts/fleet/cover.mts create mode 100644 scripts/fleet/cover/discovery.mts create mode 100644 scripts/fleet/crate-release-sha.mts create mode 100644 scripts/fleet/create-release.mts create mode 100644 scripts/fleet/depot-ci.mts create mode 100644 scripts/fleet/doctor.mts create mode 100644 scripts/fleet/estimate-ai-cost.mts create mode 100644 scripts/fleet/external-tools/_shared.mts create mode 100644 scripts/fleet/external-tools/add.mts create mode 100644 scripts/fleet/external-tools/delete.mts create mode 100644 scripts/fleet/external-tools/edit.mts create mode 100644 scripts/fleet/external-tools/github.mts create mode 100644 scripts/fleet/external-tools/list.mts create mode 100644 scripts/fleet/external-tools/prune.mts create mode 100644 scripts/fleet/external-tools/schema.mts create mode 100644 scripts/fleet/external-tools/show.mts create mode 100644 scripts/fleet/external-tools/update.mts create mode 100644 scripts/fleet/fetch-fleet-bundle.mts create mode 100644 scripts/fleet/fix.mts create mode 100644 scripts/fleet/fmt-rust.mts create mode 100644 scripts/fleet/format.mts create mode 100644 scripts/fleet/fsync-dist.mts create mode 100644 scripts/fleet/gen-agents-skills-mirror.mts create mode 100644 scripts/fleet/gen-gitmodules-hash.mts create mode 100644 scripts/fleet/gen-harness-adapters.mts create mode 100644 scripts/fleet/gh-heartbeat.mts create mode 100644 scripts/fleet/git-partial-submodule-commands.mts create mode 100644 scripts/fleet/git-partial-submodule-internal.mts create mode 100644 scripts/fleet/git-partial-submodule.mts create mode 100644 scripts/fleet/go-publish.mts create mode 100644 scripts/fleet/install-claude-plugins.mts create mode 100644 scripts/fleet/install-git-hooks.mts create mode 100644 scripts/fleet/install-sfw.mts create mode 100644 scripts/fleet/janus-multi-mcp.mts create mode 100644 scripts/fleet/janus-multi-runner.mts create mode 100644 scripts/fleet/janus-multi-workspace.mts create mode 100644 scripts/fleet/janus.mts create mode 100644 scripts/fleet/land-work.mts create mode 100644 scripts/fleet/land-work/ai-summary.mts create mode 100644 scripts/fleet/land-work/message.mts create mode 100644 scripts/fleet/lib/attribution.mts create mode 100644 scripts/fleet/lib/backup-branch.mts create mode 100644 scripts/fleet/lib/changelog.mts create mode 100644 scripts/fleet/lib/claude-md-trim.mts create mode 100644 scripts/fleet/lib/commit-via-github-api.mts create mode 100644 scripts/fleet/lib/coverage-badge.mts create mode 100644 scripts/fleet/lib/delegating-execution/prompts.mts create mode 100644 scripts/fleet/lib/delegating-execution/route.mts create mode 100644 scripts/fleet/lib/delegating-execution/types.mts create mode 100644 scripts/fleet/lib/doctor/brewfile-gap.mts create mode 100644 scripts/fleet/lib/doctor/catalog-gap.mts create mode 100644 scripts/fleet/lib/doctor/git-gap.mts create mode 100644 scripts/fleet/lib/doctor/lockfile-catalog-gap.mts create mode 100644 scripts/fleet/lib/doctor/pin-shadow-gap.mts create mode 100644 scripts/fleet/lib/doctor/secret-scan-gap.mts create mode 100644 scripts/fleet/lib/doctor/soak-gap.mts create mode 100644 scripts/fleet/lib/doctor/stranded-cascade-gap.mts create mode 100644 scripts/fleet/lib/enforcer-inventory.mts create mode 100644 scripts/fleet/lib/ensure-node.mts create mode 100644 scripts/fleet/lib/external-tools-schema.mts create mode 100644 scripts/fleet/lib/gh-aw-action-pin-soak.mts create mode 100644 scripts/fleet/lib/github-bots.mts create mode 100644 scripts/fleet/lib/known-models.mts create mode 100644 scripts/fleet/lib/llms-txt/ai.mts create mode 100644 scripts/fleet/lib/llms-txt/extract.mts create mode 100644 scripts/fleet/lib/llms-txt/render.mts create mode 100644 scripts/fleet/lib/llms-txt/sections.mts create mode 100644 scripts/fleet/lib/llms-txt/slots.mts create mode 100644 scripts/fleet/lib/llms-txt/types.mts create mode 100644 scripts/fleet/lib/oxlint-plugin-loads.mts create mode 100644 scripts/fleet/lib/security-report.mts create mode 100644 scripts/fleet/lib/self-referential-symlink.mts create mode 100644 scripts/fleet/lib/skill-system.mts create mode 100644 scripts/fleet/lib/stable-alias.mts create mode 100644 scripts/fleet/lib/taze-output.mts create mode 100644 scripts/fleet/lib/telemetry-scan.mts create mode 100644 scripts/fleet/lib/verify-release-hashes.mts create mode 100644 scripts/fleet/lib/workspace-yaml.mts create mode 100644 scripts/fleet/lint-actions.mts create mode 100644 scripts/fleet/lint-github-settings-detect.mts create mode 100644 scripts/fleet/lint-github-settings-evaluate.mts create mode 100644 scripts/fleet/lint-github-settings-types.mts create mode 100644 scripts/fleet/lint-github-settings.mts create mode 100644 scripts/fleet/lint-pr-comment.mts create mode 100644 scripts/fleet/lint.mts create mode 100644 scripts/fleet/lockstep-emit-schema.mts create mode 100644 scripts/fleet/lockstep-schema.mts create mode 100644 scripts/fleet/lockstep.mts create mode 100644 scripts/fleet/lockstep/auto-bump.mts create mode 100644 scripts/fleet/lockstep/checks.mts create mode 100644 scripts/fleet/lockstep/cli.mts create mode 100644 scripts/fleet/lockstep/emit-schema.mts create mode 100644 scripts/fleet/lockstep/git.mts create mode 100644 scripts/fleet/lockstep/manifest.mts create mode 100644 scripts/fleet/lockstep/report.mts create mode 100644 scripts/fleet/lockstep/scan.mts create mode 100644 scripts/fleet/lockstep/schema.mts create mode 100644 scripts/fleet/lockstep/types.mts create mode 100644 scripts/fleet/make-coverage-badge.mts create mode 100644 scripts/fleet/make-hook-dispatch.mts create mode 100644 scripts/fleet/make-llms-txt.mts create mode 100644 scripts/fleet/make-package-exports.mts create mode 100644 scripts/fleet/make-repo-map.mts create mode 100644 scripts/fleet/mcp-config.mts create mode 100644 scripts/fleet/normalize-backup-branches.mts create mode 100644 scripts/fleet/npm-publish.mts create mode 100644 scripts/fleet/optimizing-submodules/collect-submodule-consumers.mts create mode 100644 scripts/fleet/patching-findings/cli.mts create mode 100644 scripts/fleet/patching-findings/lib/patch-parse.mts create mode 100644 scripts/fleet/paths.mts create mode 100644 scripts/fleet/plugin-patches/codex-1.0.1-stdin-eagain.files/scripts/lib/read-stdin-sync.mjs create mode 100644 scripts/fleet/plugin-patches/codex-1.0.1-stdin-eagain.patch create mode 100644 scripts/fleet/power-state.mts create mode 100644 scripts/fleet/pre-push-gate.mts create mode 100644 scripts/fleet/prepare.mts create mode 100644 scripts/fleet/prune-workflow-runs.mts create mode 100644 scripts/fleet/publish-infra/cargo/approve.mts create mode 100644 scripts/fleet/publish-infra/cargo/bump.mts create mode 100644 scripts/fleet/publish-infra/cargo/placeholder.mts create mode 100644 scripts/fleet/publish-infra/cargo/registry.mts create mode 100644 scripts/fleet/publish-infra/cargo/shared.mts create mode 100644 scripts/fleet/publish-infra/cargo/staged.mts create mode 100644 scripts/fleet/publish-infra/go/shared.mts create mode 100644 scripts/fleet/publish-infra/npm/approve.mts create mode 100644 scripts/fleet/publish-infra/npm/bump.mts create mode 100644 scripts/fleet/publish-infra/npm/pin-readme.mts create mode 100644 scripts/fleet/publish-infra/npm/placeholder.mts create mode 100644 scripts/fleet/publish-infra/npm/registry.mts create mode 100644 scripts/fleet/publish-infra/npm/scan.mts create mode 100644 scripts/fleet/publish-infra/npm/shared.mts create mode 100644 scripts/fleet/publish-infra/npm/staged.mts create mode 100644 scripts/fleet/publish-infra/reconcile.mts create mode 100644 scripts/fleet/publish-infra/release.mts create mode 100644 scripts/fleet/publish-infra/remote-dispatch.mts create mode 100644 scripts/fleet/publish-infra/remote-github-release.mts create mode 100644 scripts/fleet/publish-infra/remote-npm-publish.mts create mode 100644 scripts/fleet/publish-infra/shared.mts create mode 100644 scripts/fleet/publish-pipeline.mts create mode 100644 scripts/fleet/publish-shared.mts create mode 100644 scripts/fleet/registry-publish-date.mts create mode 100644 scripts/fleet/release-pipeline.mts create mode 100644 scripts/fleet/release-pipeline/gate-runners.mts create mode 100644 scripts/fleet/release-pipeline/release-runners.mts create mode 100644 scripts/fleet/release-pipeline/seams.mts create mode 100644 scripts/fleet/release-pipeline/stages.mts create mode 100644 scripts/fleet/release-pipeline/state.mts create mode 100644 scripts/fleet/release-pipeline/summary.mts create mode 100644 scripts/fleet/researching-recency/cli.mts create mode 100644 scripts/fleet/researching-recency/lib/dedupe.mts create mode 100644 scripts/fleet/researching-recency/lib/fetch.mts create mode 100644 scripts/fleet/researching-recency/lib/markers.mts create mode 100644 scripts/fleet/researching-recency/lib/plan.mts create mode 100644 scripts/fleet/researching-recency/lib/rank.mts create mode 100644 scripts/fleet/researching-recency/lib/relevance.mts create mode 100644 scripts/fleet/researching-recency/lib/render/compact.mts create mode 100644 scripts/fleet/researching-recency/lib/render/footer.mts create mode 100644 scripts/fleet/researching-recency/lib/signals.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/bluesky.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/devto.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/github.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/hackernews.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/lobsters.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/reddit.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/web.mts create mode 100644 scripts/fleet/researching-recency/lib/sources/x.mts create mode 100644 scripts/fleet/researching-recency/lib/types.mts create mode 100644 scripts/fleet/researching-recency/paths.mts create mode 100644 scripts/fleet/resolve-security-pin.mts create mode 100644 scripts/fleet/scanning-quality/lib/findings.mts create mode 100644 scripts/fleet/scanning-vulns/cli.mts create mode 100644 scripts/fleet/scanning-vulns/lib/collate.mts create mode 100644 scripts/fleet/security.mts create mode 100644 scripts/fleet/setup/activate-node.mts create mode 100644 scripts/fleet/setup/bootstrap-zero-dep-packages.mjs create mode 100644 scripts/fleet/setup/claude-config.mts create mode 100644 scripts/fleet/setup/ecosystems.mts create mode 100644 scripts/fleet/setup/external-tools.json create mode 100644 scripts/fleet/setup/hook-snapshot.mts create mode 100644 scripts/fleet/setup/index.mts create mode 100644 scripts/fleet/setup/lib/bootstrap-common.d.mts create mode 100644 scripts/fleet/setup/lib/bootstrap-common.mjs create mode 100644 scripts/fleet/setup/lib/check-firewall.mjs create mode 100644 scripts/fleet/setup/lib/install-fff.mjs create mode 100644 scripts/fleet/setup/lib/install-janus.mjs create mode 100644 scripts/fleet/setup/lib/install-npm.mjs create mode 100644 scripts/fleet/setup/lib/install-pnpm.mjs create mode 100644 scripts/fleet/setup/lib/install-sfw.mjs create mode 100644 scripts/fleet/setup/lib/install-smithers.mjs create mode 100644 scripts/fleet/setup/lib/install-tool.mjs create mode 100644 scripts/fleet/setup/lib/install-uv.mjs create mode 100644 scripts/fleet/setup/lib/jq.mjs create mode 100644 scripts/fleet/setup/lib/platform.mjs create mode 100644 scripts/fleet/setup/lib/read-package-integrity.mjs create mode 100644 scripts/fleet/setup/lib/read-pinned-version.mjs create mode 100644 scripts/fleet/setup/setup-brew.mts create mode 100644 scripts/fleet/setup/setup-go.mts create mode 100644 scripts/fleet/setup/setup-kimi-user-config.mts create mode 100644 scripts/fleet/setup/setup-mcp.mts create mode 100644 scripts/fleet/setup/setup-python.mts create mode 100644 scripts/fleet/setup/setup-refero.mts create mode 100644 scripts/fleet/setup/setup-rust.mts create mode 100644 scripts/fleet/setup/setup-tools-sfw.d.mts create mode 100644 scripts/fleet/setup/setup-tools-sfw.mjs create mode 100644 scripts/fleet/setup/setup-tools.mjs create mode 100644 scripts/fleet/setup/token.mts create mode 100644 scripts/fleet/soak-bypass.mts create mode 100644 scripts/fleet/soak-rules.mts create mode 100644 scripts/fleet/socket-wheelhouse-emit-schema.mts create mode 100644 scripts/fleet/socket-wheelhouse-schema.mts create mode 100644 scripts/fleet/source-pricing-feed.mts create mode 100644 scripts/fleet/strip-ai-attribution.mts create mode 100644 scripts/fleet/sync-gh-aw-action-pins.mts create mode 100644 scripts/fleet/sync-oxlint-rules.mts create mode 100644 scripts/fleet/sync-oxlint-schema-pin.mts create mode 100644 scripts/fleet/sync-package-manager-pins.mts create mode 100644 scripts/fleet/team-activity/cli.mts create mode 100644 scripts/fleet/team-activity/lib/config.mts create mode 100644 scripts/fleet/team-activity/lib/discover.mts create mode 100644 scripts/fleet/team-activity/lib/filter.mts create mode 100644 scripts/fleet/team-activity/lib/follow-ups.mts create mode 100644 scripts/fleet/team-activity/lib/paths.mts create mode 100644 scripts/fleet/team-activity/lib/render.mts create mode 100644 scripts/fleet/team-activity/lib/scan.mts create mode 100644 scripts/fleet/team-activity/lib/state.mts create mode 100644 scripts/fleet/team-activity/lib/types.mts create mode 100644 scripts/fleet/test.mts create mode 100644 scripts/fleet/triaging-findings/cli.mts create mode 100644 scripts/fleet/triaging-findings/lib/ingest.mts create mode 100644 scripts/fleet/triaging-findings/lib/report.mts create mode 100644 scripts/fleet/trim-claude-md.mts create mode 100644 scripts/fleet/trimming-bundle/measure-bundle.mts create mode 100644 scripts/fleet/update-model-pricing.mts create mode 100644 scripts/fleet/update.mts create mode 100644 scripts/fleet/update/_shared.mts create mode 100644 scripts/fleet/update/brew-parse.mts create mode 100644 scripts/fleet/update/brew.mts create mode 100644 scripts/fleet/update/cargo.mts create mode 100644 scripts/fleet/update/docker.mts create mode 100644 scripts/fleet/update/go.mts create mode 100644 scripts/fleet/update/node.mts create mode 100644 scripts/fleet/util/coverage-merge.mts create mode 100644 scripts/fleet/util/multi-package-publish-verify.mts create mode 100644 scripts/fleet/util/multi-package-publish.mts create mode 100644 scripts/fleet/util/napi-targets.mts create mode 100644 scripts/fleet/util/pack-app-triplets.mts create mode 100644 scripts/fleet/util/parse-args.mts create mode 100644 scripts/fleet/util/run-command.mts create mode 100644 scripts/fleet/util/source-allowlist.mts create mode 100644 scripts/fleet/validate-bundle-deps.mts create mode 100644 scripts/fleet/verify-submodule-sparse.mts create mode 100644 scripts/fleet/weekly-update-workflow.mts create mode 100644 scripts/fleet/weekly-update.mts create mode 100644 scripts/fleet/weekly-update/deterministic-chain.mts create mode 100644 scripts/fleet/whose-work.mts diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..fd1b1f1 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,47 @@ +# Fleet-canonical cargo soak config. A repository that needs target runners, +# rustflags, or other local Cargo settings puts them in `config.repo.toml`. +# Rust-capable repos also receive the fleet's optional Darwin signing runner. +# This split lets the cascade stay byte-identical without deleting legitimate +# repository configuration. +include = [ + { path = "config.darwin-signing.toml", optional = true }, + { path = "config.repo.toml", optional = true }, +] + +# Owned by +# scripts/fleet/update/cargo.mts (renderMinPublishAgeConfig derives this exact +# body from SOAK_DAYS, scripts/fleet/constants/soak.mts); gated by +# scripts/fleet/check/cargo-soak-config-is-current.mts, which asserts this +# file's values still equal SOAK_DAYS (a data file cannot import the +# constant, so the gate is the parity check). Do not hand-edit the values +# below — bump SOAK_DAYS and re-cascade instead. +# +# Semantics: these `[unstable]` / `[registry]` / `[resolver]` keys are a +# NIGHTLY-only cargo feature (`-Zmin-publish-age`, rust-lang/cargo#15973, +# RFC 3923) and are INERT on the stable toolchain fleet repos build with +# (e.g. ultrathink pins 1.95.0) — stable cargo ignores unknown `[unstable]` +# keys rather than enforcing them. Build-time soak enforcement therefore +# comes from the committed `Cargo.lock`: a build must use the lock, and the +# lock only ever contains versions the nightly updater (below) allowed in. +# Update-time enforcement is the nightly updater +# (scripts/fleet/update/cargo.mts, pinned to RUST_UPDATER_TOOLCHAIN), which +# runs `cargo + update -Zmin-publish-age` against this config and +# can only move the lock to versions that clear the soak window. Any nightly +# cargo run by hand in this repo also honors this file natively. + +# Enable the unstable feature (equivalent to the -Zmin-publish-age flag). +[unstable] +min-publish-age = true + +# Soak threshold: reject any registry version whose publish time is newer +# than this window. Duration is " " +# (seconds|minutes|hours|days|weeks|months). +# Source: rust-lang/cargo#15973, RFC 3923 (registry.global-min-publish-age). +[registry] +global-min-publish-age = "7 days" + +# Make the age a HARD gate: the resolver ignores too-new versions unless +# they are already pinned in Cargo.lock. +# Source: RFC 3923 (resolver.incompatible-publish-age). +[resolver] +incompatible-publish-age = "deny" diff --git a/.claude/agents/fleet/code-reviewer.md b/.claude/agents/fleet/code-reviewer.md new file mode 100644 index 0000000..e2b708e --- /dev/null +++ b/.claude/agents/fleet/code-reviewer.md @@ -0,0 +1,92 @@ +--- +name: code-reviewer +description: Reviews code in this repository against the rules in CLAUDE.md and reports style violations, logic bugs, and test gaps. Spawned by the quality-scan skill or invoked directly on a diff. +tools: Read, Grep, Glob, Bash(git:*), Bash(rg:*), Bash(grep:*), Bash(find:*), Bash(ls:*), Bash(wc:*), Bash(cat:*), Bash(head:*), Bash(tail:*) +--- + + +You are the code reviewer for this repository. The project's CLAUDE.md defines the style rules, conventions, and forbidden patterns. Read CLAUDE.md before every review — that's the source of truth. + + + + +Apply the rules from the project's CLAUDE.md exactly. The structural review checklist below is universal; the per-rule details (filename casing, import patterns, forbidden libraries, naming conventions, etc.) come from CLAUDE.md. + +## Read first + +Before reviewing any file, load CLAUDE.md. Pay attention to the sections covering: + +- **File structure** — naming conventions, layout, language extensions. +- **TypeScript / JavaScript style** — type rules, import patterns, `null` vs `undefined`, prototype-pollution defenses. +- **Imports** — what's cherry-picked, what's default-imported, what's banned. +- **File operations** — file existence checks, deletion helpers, forbidden raw filesystem APIs. +- **Object construction** — when to use `{ __proto__: null, ... }`. +- **HTTP / network** — sanctioned clients, forbidden patterns. +- **Comments** — when to add them, what to avoid. +- **Promise.race in loops** — the leaky pattern called out in the fleet's CLAUDE.md. +- **Backward compatibility** — typically forbidden to maintain. +- **Build commands** — script naming convention. +- **Tests** — functional vs source-text scanning. + +If a finding hinges on a rule, cite the CLAUDE.md section so the author can look it up. + +## Review checklist + +For each file in the diff, walk these categories: + +### 1. Style violations + +Apply CLAUDE.md style rules. Common categories: + +- File extensions, filename casing, file headers. +- Import sorting / grouping / cherry-picking. +- `any` usage (typically forbidden — use `unknown` or specific types). +- Type imports (typically `import type`, separate statements). +- `null` vs `undefined` (varies per repo — read CLAUDE.md). +- Object literal shape for config / return / internal-state objects. +- Comment style (default no, only for non-obvious _why_). +- Naming conventions (constants, helpers, exports). +- Sorting (lists, properties, exports, destructuring). + +Flag each violation with `path:line` + the CLAUDE.md rule it violates. + +### 2. Logic issues + +- Bugs (off-by-one, wrong operator, missing edge case). +- Missing error handling on async / I/O operations. +- Race conditions, particularly `Promise.race` in loops with persistent pools. +- Resource leaks (unclosed handles, uncleared timers, retained listeners). +- Type coercion that could silently fail. +- Untrusted input merged into objects or interpolated into shell commands. + +Flag with `path:line` + a one-sentence description. + +### 3. Test gaps + +- Code paths the test suite doesn't cover. +- New exports without corresponding test cases. +- Tests that read source files and assert on contents instead of calling the function (typically forbidden). + +Flag with `path:line` + a suggested test. + +## Cross-fleet rules to enforce + +These apply across the fleet regardless of CLAUDE.md specifics: + +- No `npx`, `pnpm dlx`, or `yarn dlx`. Flag any of these in scripts, hooks, package.json, or CI YAML. +- No `process.chdir`. Pass `cwd:` to spawn or resolve paths from a known root. +- Don't write a real customer / company name into commits, PRs, GitHub comments, or release notes — replace with `Acme Inc` or drop. Don't reference issue-tracker IDs (Linear / Sentry / etc.) in code or PR titles. +- Don't introduce a new HTTP client without explicit user approval. + +## Output + +For each file you review, report: + +- **Style violations**: list with `path:line` + the rule violated (cite CLAUDE.md section if applicable). +- **Logic issues**: bugs, edge cases, missing error handling — `path:line` + a one-sentence description. +- **Test gaps**: code paths the test suite doesn't cover — `path:line` + suggested test. +- **Suggested fix** for each finding, in one sentence. + +If the diff has zero findings, say so explicitly — don't pad with non-actionable observations. + + diff --git a/.claude/agents/fleet/fix.md b/.claude/agents/fleet/fix.md new file mode 100644 index 0000000..5205bb3 --- /dev/null +++ b/.claude/agents/fleet/fix.md @@ -0,0 +1,70 @@ +--- +name: fix +description: Applies fixes for a findings report from scanning-quality / reviewing-code. Deterministic fixers (lint/format/the finding's named script) run FIRST; AI patches only the residue, one finding at a time, verifying + committing each. Spawned to make a findings report actionable headlessly. +tools: Read, Edit, Write, Grep, Glob, Bash(git:*), Bash(rg:*), Bash(grep:*), Bash(find:*), Bash(ls:*), Bash(pnpm run:*), Bash(pnpm test:*), Bash(pnpm exec:*), Bash(node:*), Bash(cat:*), Bash(head:*), Bash(tail:*) +--- + + +You apply fixes for a structured findings report (from `scanning-quality`, +`reviewing-code`, or a check script). You are the mutating counterpart to the +read-only `code-reviewer` — it finds, you fix. The project's CLAUDE.md is the +source of truth for style and conventions; read it before patching. + + + + +The governing rule is `code-first-then-ai`: a deterministic fixer runs FIRST; +AI authors a patch ONLY for the residue the script can't resolve. Never hand-fix +something a script owns. + +## Procedure + +1. **Deterministic pass first.** Before any AI patch, run the fixers that own the + mechanical findings: + - `pnpm run fix` — oxlint autofix (lint findings). + - `pnpm run format` — oxfmt (format findings). + - The exact script named in a finding's `fix` field, if it's a check-script + finding (e.g. a `sync`/`reconcile`/`gen` script). Run that script — do not + hand-edit the artifact it owns. + Re-run the relevant check (`pnpm run lint` / `pnpm run check` / `pnpm test + `) and remove every finding the deterministic pass cleared. +2. **Residue, one finding at a time.** For each remaining finding, apply the + smallest AI patch that resolves it. After EACH patch, re-run the relevant check + / test to confirm the fix works and broke nothing else. A patch that turns + another check red is reverted, not stacked on. +3. **Commit per fix.** Each fix is its own commit (`fix(): `) — never + bundle unrelated fixes. The root cause goes in the message. +4. **Stop on ambiguity.** If a finding looks misdiagnosed (the "fix" would mask a + real bug, or the finding contradicts the code), do NOT patch it — report it back + as a disputed finding. A wrong fix for a wrong finding is worse than an open one. + +## Scope protocol + +Fix only what the findings report names. Don't add features, refactor unrelated +code, or make improvements beyond the findings. Simplest patch that resolves the +finding. + +## Verification protocol + +Run the actual check/test after every patch and state what you verified — never +claim a fix without a tool result that shows the check now passes. Re-read every +file you modified; confirm nothing references something that no longer exists. +Run `pnpm run build` only if the change touches `src/` or `tsconfig.json`. + +## Cross-fleet rules + +- No `npx` / `pnpm dlx` / `yarn dlx`; use `pnpm run +``` + +Or, for code that genuinely belongs in an external file: + +```html + +``` + +## What it covers + +| File extension | Checked? | +| -------------------------------------------------------- | --------------- | +| `.html` / `.htm` | full text | +| `.njk` / `.ejs` / `.hbs` / `.handlebars` | full text | +| `.svelte` / `.vue` / `.astro` | full text | +| `.ts` / `.tsx` / `.mts` / `.cts` / `.js` / `.jsx` / etc. | new_string only | +| anything else | not checked | + +## Bypass + +Type the canonical phrase in a new message: + + Allow inline-defer bypass + +Use sparingly — the bug is silent in production. + +## Companion: oxlint rule + +`socket/no-inline-defer-async` catches the same shape at commit time +even when edits happened outside Claude. diff --git a/.claude/hooks/fleet/inline-script-defer-guard/index.mts b/.claude/hooks/fleet/inline-script-defer-guard/index.mts new file mode 100644 index 0000000..f16a1ca --- /dev/null +++ b/.claude/hooks/fleet/inline-script-defer-guard/index.mts @@ -0,0 +1,145 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — inline-script-defer-guard. +// +// Blocks Edit/Write operations that add a script tag with defer or +// async attribute to an HTML / template file when the same tag lacks a +// `src=` attribute. Per HTML spec, `defer` and `async` are no-ops on +// inline (no-src) script tags — the script executes immediately, +// even though the author intent is "wait for DOMContentLoaded." Browsers +// don't warn; the failure mode is a silent broken page (e.g. unstyled +// `
` blocks when the script that styles them runs before its
+// targets exist).
+//
+// Detection: regex over the after-edit text. Find script openers with
+// defer or async in their attrs, check the same tag for `src=`.
+// If absent → block.
+//
+// Fix: wrap the script body in
+//
+//     
+//
+// Files covered: `*.html` / `*.htm` / `*.njk` / `*.ejs` / `*.hbs` /
+// `*.handlebars` / `*.svelte` / `*.vue` / `*.astro`. Also fires on TS/JS
+// source files that contain HTML string literals matching the pattern —
+// SSR / static-gen code paths.
+//
+// Bypass: `Allow inline-defer bypass` typed verbatim in a recent user turn.
+
+import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts'
+import { resolveEditedText } from '../_shared/payload.mts'
+import { bypassPhrasePresent } from '../_shared/transcript.mts'
+
+const BYPASS_PHRASE = 'Allow inline-defer bypass'
+
+// File extensions where we check the full text content. For other
+// extensions, only the new_string is checked (template strings embedded
+// in TS/JS source).
+const HTML_EXT_RE = /\.(?:astro|ejs|handlebars|hbs|htm|html|njk|svelte|vue)$/i
+
+// JS/TS source extensions: optional `m` or `c` prefix, then `js`/`ts`
+// with an optional `x` suffix (JSX/TSX), anchored to the end of the path.
+const SOURCE_EXT_RE = /\.(?:m?[jt]sx?|cts|cjs)$/i
+
+// Match each `',
+      '',
+      '  Or — if the script DOES belong in an external file:',
+      '',
+      '      ',
+      '',
+      `  Bypass: type "${BYPASS_PHRASE}" in a new message, then retry.`,
+      '',
+    ].join('\n'),
+  )
+})
+
+export const hook = defineHook({
+  check,
+  event: 'PreToolUse',
+  matcher: ['Edit', 'Write', 'MultiEdit'],
+  type: 'guard',
+})
+
+void runHook(hook, import.meta.url)
diff --git a/.claude/hooks/fleet/inline-script-defer-guard/package.json b/.claude/hooks/fleet/inline-script-defer-guard/package.json
new file mode 100644
index 0000000..43b2da5
--- /dev/null
+++ b/.claude/hooks/fleet/inline-script-defer-guard/package.json
@@ -0,0 +1,15 @@
+{
+  "name": "hook-inline-script-defer-guard",
+  "private": true,
+  "type": "module",
+  "main": "./index.mts",
+  "exports": {
+    ".": "./index.mts"
+  },
+  "scripts": {
+    "test": "node --test test/*.test.mts"
+  },
+  "devDependencies": {
+    "@types/node": "catalog:"
+  }
+}
diff --git a/.claude/hooks/fleet/inline-script-defer-guard/tsconfig.json b/.claude/hooks/fleet/inline-script-defer-guard/tsconfig.json
new file mode 100644
index 0000000..19458cf
--- /dev/null
+++ b/.claude/hooks/fleet/inline-script-defer-guard/tsconfig.json
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "declarationMap": false,
+    "erasableSyntaxOnly": true,
+    "module": "nodenext",
+    "moduleResolution": "nodenext",
+    "noEmit": true,
+    "rewriteRelativeImportExtensions": true,
+    "skipLibCheck": true,
+    "sourceMap": false,
+    "strict": true,
+    "target": "esnext",
+    "types": ["node"],
+    "verbatimModuleSyntax": true
+  }
+}
diff --git a/.claude/hooks/fleet/issue-autolink-nudge/index.mts b/.claude/hooks/fleet/issue-autolink-nudge/index.mts
new file mode 100644
index 0000000..969bbf4
--- /dev/null
+++ b/.claude/hooks/fleet/issue-autolink-nudge/index.mts
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+// Claude Code PreToolUse hook — issue-autolink-nudge.
+//
+// On a Bash command that writes to a public Git or GitHub surface (a commit,
+// or a pr, issue, comment, or release body), warn when the text contains a
+// bare `#N`. GitHub auto-links `#3` into a reference to issue or PR 3 of the
+// target repo — so a `#3` that meant "list item 3" or "task 3" silently turns
+// into a cross-reference to an unrelated issue. Suggest backticking it
+// (`` `#3` ``) or reshaping ("item 3").
+//
+// Advisory only — a bare `#N` is sometimes a deliberate, correct reference.
+// The nudge just prompts the author to confirm intent before it sends. Never
+// blocks (notify, exit 0). Universal: bare `#N` auto-links on ANY GitHub repo,
+// so this is not fleet-scoped. Internal task lists in the agent's own prose
+// (never sent to a public surface) are unaffected.
+
+import { bashGuard, defineHook, notify, runHook } from '../_shared/guard.mts'
+import { isPublicSurface } from '../_shared/public-surfaces.mts'
+
+export const triggers: readonly string[] = ['gh', 'git']
+
+// A bare `#N`: a `#` immediately followed by digits, NOT preceded by a backtick
+// (already code-formatted) or a word char (so `abc#3` and `v1.2.3#4` don't
+// count). A `#L12` line anchor starts with a letter, so it never matches. This
+// scans message text for a display artifact, not shell-command structure.
+const BARE_ISSUE_REF_RE = /(?()
+  let match: RegExpExecArray | null = BARE_ISSUE_REF_RE.exec(text)
+  while (match) {
+    seen.add(`#${match[1]!}`)
+    match = BARE_ISSUE_REF_RE.exec(text)
+  }
+  return [...seen]
+}
+
+export const check = bashGuard(command => {
+  if (!isPublicSurface(command)) {
+    return undefined
+  }
+  const refs = findBareIssueRefs(command)
+  if (!refs.length) {
+    return undefined
+  }
+  return notify(
+    [
+      '[issue-autolink-nudge] This command writes to a public GitHub surface',
+      `  and contains a bare reference: ${refs.join(', ')}.`,
+      '',
+      '  GitHub auto-links `#N` to issue or PR N of the target repo. If you',
+      '  meant a list item or task number (not that issue), it becomes a wrong',
+      '  cross-reference once it sends.',
+      '',
+      '  • A real issue or PR reference? Leave it — that is the intended link.',
+      '  • Otherwise wrap it in backticks (`#3`) or reshape ("item 3", "task 3")',
+      '    before the command runs.',
+    ].join('\n'),
+  )
+})
+
+export const hook = defineHook({
+  check,
+  event: 'PreToolUse',
+  matcher: ['Bash'],
+  triggers,
+  type: 'nudge',
+})
+
+void runHook(hook, import.meta.url)
diff --git a/.claude/hooks/fleet/judgment-nudge/README.md b/.claude/hooks/fleet/judgment-nudge/README.md
new file mode 100644
index 0000000..9c1f52f
--- /dev/null
+++ b/.claude/hooks/fleet/judgment-nudge/README.md
@@ -0,0 +1,58 @@
+# judgment-nudge
+
+Stop hook that flags hedging language in the assistant's most-recent turn. Two-layer detection: regex for fixed phrases, compromise.js for modal-verb judgment hedges.
+
+## Why
+
+CLAUDE.md "Judgment & self-evaluation":
+
+- "Default to perfectionist when you have latitude."
+- "If a fix fails twice: stop, re-read top-down, state where the mental model was wrong, try something fundamentally different."
+
+Hedging undermines those rules — it offloads judgment back to the user instead of executing the perfectionist default.
+
+## What it catches
+
+### Fixed-phrase regex layer
+
+| Phrase                                       | Why it's flagged                                         |
+| -------------------------------------------- | -------------------------------------------------------- |
+| `I'm not sure` / `I am not sure`             | Hedge; state a recommendation with rationale instead.    |
+| `you decide` / `your call` / `up to you`     | Offloads judgment. Pick the recommended path.            |
+| `either approach works` / `either way works` | False-equivalence hedging. Pick one.                     |
+| `let me know` / `your preference`            | Hand-off phrasing. Ask one specific question or execute. |
+| `maybe X` / `perhaps X` (sentence-initial)   | Front-loaded uncertainty user didn't ask for.            |
+
+### Modal-verb NLP layer (compromise.js)
+
+Flags first-person modals in judgment contexts:
+
+- `I could go either way`
+- `we might want to consider`
+- `I may pick the simpler approach`
+
+The compromise.js library tags verbs with POS so we can distinguish judgment hedges ("I could go") from technical conditionals ("the parser could throw") — regex alone would false-positive on the latter.
+
+**Fail-open**: if compromise.js fails to load, the hook degrades to a regex-only fallback that catches the most common shape but misses some context.
+
+## Why it doesn't block
+
+Stop hooks fire after the assistant has produced its response. Blocking would truncate. The warning surfaces alongside the response so the user reads both and can push back next turn.
+
+## Relationship to other reminders
+
+- `excuse-detector` — catches fix-vs-defer choice menus
+- `reply-prose-nudge` (perfectionist group) — catches speed-vs-depth choice menus
+- `judgment-nudge` (this) — catches hedging within a single position
+
+All three address the same underlying anti-pattern: offloading judgment the assistant should have made.
+
+## Dependencies
+
+- `compromise@14.15.1` — NLP library for POS-tagged modal-verb detection. Lazy-loaded; optional.
+
+## Test
+
+```sh
+pnpm test
+```
diff --git a/.claude/hooks/fleet/judgment-nudge/index.mts b/.claude/hooks/fleet/judgment-nudge/index.mts
new file mode 100644
index 0000000..e3e5c2f
--- /dev/null
+++ b/.claude/hooks/fleet/judgment-nudge/index.mts
@@ -0,0 +1,214 @@
+#!/usr/bin/env node
+// Claude Code Stop hook — judgment-nudge.
+//
+// Flags hedging language in the assistant's most recent turn.
+// CLAUDE.md "Judgment & self-evaluation":
+//   - "If the request is based on a misconception, say so..."
+//   - "Default to perfectionist when you have latitude."
+//   - "If a fix fails twice: stop, re-read top-down..."
+//
+// Hedging ("I'm not sure", "you decide", "either approach works",
+// modal "might/could/may") undermines those rules — it offloads the
+// judgment back onto the user instead of executing the perfectionist
+// default.
+//
+// What this catches:
+//
+//   - Fixed phrases (regex): "I'm not sure", "you decide", "either
+//     approach works", "your call", "up to you", "let me know", etc.
+//   - Modal verbs (compromise.js POS): could / might / may / perhaps /
+//     maybe, when used as judgment hedges rather than technical
+//     conditionals.
+//
+// The compromise.js NLP layer is what makes modal detection useful:
+// "this could throw" (technical conditional, OK) vs "I could go either
+// way" (judgment hedge, flag). The library tags each token with POS
+// and lets us inspect the verb context. Regex alone gets too many
+// false positives on the technical use.
+//
+// Fail-open contract: if compromise.js fails to load (or its data
+// initializer throws), fall back to regex-only detection — the hook
+// still flags fixed phrases, just misses the modal-verb signal.
+//
+// This is a NUDGE (never blocks): when hits are found it returns
+// `notify(message)` so the runner prints to stderr and exits 0.
+
+import { defineHook, notify, runHook } from '../_shared/guard.mts'
+import type { GuardResult } from '../_shared/guard.mts'
+import type { ToolCallPayload } from '../_shared/payload.mts'
+import {
+  extractSnippet,
+  formatReminderBlock,
+  scanReminderText,
+} from '../_shared/stop-nudge.mts'
+import type { ReminderHit, RuleViolation } from '../_shared/stop-nudge.mts'
+import {
+  readLastAssistantText,
+  stripCodeFences,
+} from '../_shared/transcript.mts'
+
+// Try-require compromise.js for modal-verb detection. Lazy + optional
+// because the dep is heavy (~2.5 MB unpacked) and the fixed-phrase
+// regex catches the most common hedging patterns without it. Modal
+// detection is an enhancement, not a requirement — if compromise is
+// missing (e.g. downstream repo didn't pnpm install the hook's deps
+// yet), the hook degrades gracefully to regex-only.
+interface NlpDoc {
+  readonly verbs: () => {
+    readonly out: (mode: 'array') => readonly string[]
+  }
+  readonly sentences: () => {
+    readonly out: (mode: 'array') => readonly string[]
+  }
+}
+type NlpFn = (text: string) => NlpDoc
+
+let cachedNlp: NlpFn | undefined
+export async function loadCompromise(): Promise {
+  if (cachedNlp !== undefined) {
+    return cachedNlp
+  }
+  try {
+    const mod = await import('compromise')
+    /* c8 ignore start - defensive fallbacks for non-standard compromise export shapes */
+    const candidate = (mod as { default?: unknown | undefined }).default ?? mod
+    cachedNlp =
+      typeof candidate === 'function' ? (candidate as NlpFn) : undefined
+    /* c8 ignore stop */
+  } catch {
+    /* c8 ignore start - only reachable when compromise is not installed */
+    cachedNlp = undefined
+    /* c8 ignore stop */
+  }
+  return cachedNlp
+}
+
+// Sentence-starting hedge modals — "I could go either way", "this
+// might be the better path", "perhaps we should..." These read as
+// the assistant deferring judgment rather than stating a position.
+//
+// We filter to hedge contexts (first-person subject + modal + judgment
+// verb) so technical conditionals like "the parser could throw if X"
+// don't false-positive. The compromise pattern matches:
+//   - (i|we) + (could|might|may)
+//   - sentence-initial perhaps/maybe + we/I/it
+const HEDGE_VERB_REGEX =
+  /\b(?:i|we)\s+(?:could|may|might)\s+(?:approach|choose|consider|do|go|pick|try|use)\b/i
+
+export async function detectModalHedges(
+  text: string,
+): Promise {
+  const nlp = await loadCompromise()
+  /* c8 ignore start - only reachable when compromise is not installed */
+  if (!nlp) {
+    // Fallback: regex-only. We still catch the most common shape.
+    const match = HEDGE_VERB_REGEX.exec(text)
+    if (!match) {
+      return []
+    }
+    return [
+      {
+        label: 'modal-verb hedge (regex fallback)',
+        why: "Modal verbs (could/might/may) used in first-person judgment context. State the position; don't hedge.",
+        snippet: extractSnippet(text, match.index, match[0].length),
+      },
+    ]
+    /* c8 ignore stop */
+  }
+
+  // Compromise.js path: walk sentences, flag any that contain a
+  // first-person modal in a judgment context. The library tags each
+  // verb with POS; we check sentence-by-sentence so the snippet is
+  // useful (a single sentence rather than the whole turn).
+  const doc = nlp(text)
+  const sentences = doc.sentences().out('array')
+  const hits: ReminderHit[] = []
+  for (let i = 0, { length } = sentences; i < length; i += 1) {
+    const sentence = sentences[i]!
+    if (!HEDGE_VERB_REGEX.test(sentence)) {
+      continue
+    }
+    // Compromise gives us POS-aware verb detection; we use it to
+    // confirm the modal isn't part of a code-shape conditional like
+    // "could throw" / "might return" (technical, not judgment).
+    const sentenceDoc = nlp(sentence)
+    const verbs = sentenceDoc.verbs().out('array')
+    const hasJudgmentVerb = verbs.some(v =>
+      /\b(?:approach|choose|consider|do|go|pick|try|use)\b/i.test(v),
+    )
+    if (!hasJudgmentVerb) {
+      continue
+    }
+    hits.push({
+      label: 'modal-verb hedge',
+      why: "First-person modal (could/might/may) used in judgment context. State the position; don't hedge.",
+      snippet: sentence.length > 80 ? sentence.slice(0, 77) + '…' : sentence,
+    })
+    // One hit per turn is enough — flag and move on.
+    break
+  }
+  return hits
+}
+
+const FIXED_HEDGE_PATTERNS: readonly RuleViolation[] = [
+  {
+    label: 'I’m not sure / I am not sure',
+    // Match "I’m not sure" or "I am not sure" with straight or curly apostrophe,
+    // as a whole word, case-insensitive.
+    regex: /\bi['‘’]?m\s+not\s+sure\b|\bi\s+am\s+not\s+sure\b/i,
+    why: 'Hedging. State a recommendation with rationale, or say "I need to verify X" and then do it.',
+  },
+  {
+    label: 'you decide / your call / up to you',
+    // Match any of three judgment-offload phrases as whole words, case-insensitive.
+    regex: /\b(?:you\s+decide|your\s+call|up\s+to\s+you)\b/i,
+    why: 'Offloads judgment. Default-perfectionist: pick the recommended path and execute.',
+  },
+  {
+    label: 'either approach works / either way works',
+    // Match false-equivalence phrases: "either  works"
+    // or "either is fine", as whole words, case-insensitive.
+    regex:
+      /\b(?:either\s+(?:approach|option|path|way)\s+works|either\s+is\s+fine)\b/i,
+    why: 'False-equivalence hedging. Even when paths are close, name the one with the smaller blast radius and pick it.',
+  },
+  {
+    label: 'let me know / your preference',
+    // Match hand-off phrases ("let me know", "your preference", "tell me what")
+    // as whole words, case-insensitive.
+    regex: /\b(?:let\s+me\s+know|your\s+preference|tell\s+me\s+what)\b/i,
+    why: 'Hand-off phrasing. If the user already gave intent, execute; if not, ask one specific question, not "let me know."',
+  },
+  {
+    label: 'maybe / perhaps as judgment hedge',
+    regex: /^(?:maybe|perhaps)\s+/im,
+    why: 'Sentence-initial hedge. State the position; "maybe" at the front signals uncertainty the user didn\'t ask for.',
+  },
+]
+
+const CLOSING_HINT =
+  'CLAUDE.md "Judgment & self-evaluation": default to perfectionist; state the recommendation, name the trade-off, then execute. Hedging asks the user to think for you.'
+
+export async function check(payload: ToolCallPayload): Promise {
+  const rawText = readLastAssistantText(payload?.transcript_path)
+  if (!rawText) {
+    return undefined
+  }
+  const text = stripCodeFences(rawText)
+  const hits = await scanReminderText(
+    text,
+    FIXED_HEDGE_PATTERNS,
+    detectModalHedges,
+  )
+  if (hits.length === 0) {
+    return undefined
+  }
+  return notify(formatReminderBlock('judgment-nudge', hits, CLOSING_HINT))
+}
+
+export const hook = defineHook({
+  check,
+  event: 'Stop',
+  type: 'nudge',
+})
+void runHook(hook, import.meta.url)
diff --git a/.claude/hooks/fleet/judgment-nudge/package.json b/.claude/hooks/fleet/judgment-nudge/package.json
new file mode 100644
index 0000000..d020633
--- /dev/null
+++ b/.claude/hooks/fleet/judgment-nudge/package.json
@@ -0,0 +1,18 @@
+{
+  "name": "hook-judgment-nudge",
+  "private": true,
+  "type": "module",
+  "main": "./index.mts",
+  "exports": {
+    ".": "./index.mts"
+  },
+  "scripts": {
+    "test": "node --test test/*.test.mts"
+  },
+  "dependencies": {
+    "compromise": "catalog:"
+  },
+  "devDependencies": {
+    "@types/node": "catalog:"
+  }
+}
diff --git a/.claude/hooks/fleet/judgment-nudge/tsconfig.json b/.claude/hooks/fleet/judgment-nudge/tsconfig.json
new file mode 100644
index 0000000..19458cf
--- /dev/null
+++ b/.claude/hooks/fleet/judgment-nudge/tsconfig.json
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "declarationMap": false,
+    "erasableSyntaxOnly": true,
+    "module": "nodenext",
+    "moduleResolution": "nodenext",
+    "noEmit": true,
+    "rewriteRelativeImportExtensions": true,
+    "skipLibCheck": true,
+    "sourceMap": false,
+    "strict": true,
+    "target": "esnext",
+    "types": ["node"],
+    "verbatimModuleSyntax": true
+  }
+}
diff --git a/.claude/hooks/fleet/land-fast-nudge/README.md b/.claude/hooks/fleet/land-fast-nudge/README.md
new file mode 100644
index 0000000..76b39c5
--- /dev/null
+++ b/.claude/hooks/fleet/land-fast-nudge/README.md
@@ -0,0 +1,22 @@
+# land-fast-nudge
+
+Stop hook. Nudges at turn-end when the checkout is on the default branch
+(main / master) and local HEAD has **diverged** from origin — it is BOTH
+ahead AND behind `origin/`.
+
+A diverged default branch is the state where a direct `git push` is
+rejected and a `reset --hard` would discard local work. In a
+parallel-session fleet it happens routinely: another session squashes your
+commits onto origin via PR while your local keeps the unsquashed
+originals. Rather than hand-roll a cherry-pick + force, the reminder points
+at the `managing-worktrees land` engine
+(`.claude/skills/fleet/managing-worktrees/lib/land.mts`): it re-asserts the
+lint gate (the fleet lints as it edits — no heavy re-run), cherry-picks the
+local-only commits onto a throwaway `origin/` worktree, and
+fast-forwards (never force).
+
+Only fires when BOTH ahead AND behind — ahead-only is the
+`unpushed-main-nudge`'s job, behind-only just needs a pull.
+
+Fails open: any hook error is swallowed so a reminder bug never disrupts
+the turn.
diff --git a/.claude/hooks/fleet/land-fast-nudge/index.mts b/.claude/hooks/fleet/land-fast-nudge/index.mts
new file mode 100644
index 0000000..423e719
--- /dev/null
+++ b/.claude/hooks/fleet/land-fast-nudge/index.mts
@@ -0,0 +1,133 @@
+#!/usr/bin/env node
+// Claude Code Stop hook — land-fast-nudge.
+//
+// Fires at turn-end. When the current checkout is ON the default branch
+// (main / master) and local HEAD has DIVERGED from origin (it is BOTH
+// ahead AND behind origin/), it nudges toward the fast-land path
+// instead of a hand-rolled cherry-pick + force dance.
+//
+// Why: a diverged default branch is the state where a direct `git push`
+// is rejected (non-fast-forward) and a `reset --hard origin/`
+// would discard local work. It happens routinely in a parallel-session
+// fleet: another session squashes your commits onto origin via PR (so
+// origin gained commits your local doesn't have as the same SHAs), while
+// your local kept the unsquashed originals. Hand-resolving this — manual
+// cherry-pick onto a fresh worktree, verify fast-forward, push — is the
+// exact friction the `managing-worktrees land` engine (lib/land.mts)
+// automates: it re-asserts the lint gate (the fleet lints as it edits, so
+// no heavy re-run), cherry-picks onto a throwaway origin/ worktree,
+// and fast-forwards (never force). This reminder points there at the turn
+// the divergence is visible.
+//
+// Only fires on the default branch when BOTH ahead AND behind: an
+// ahead-only main is the unpushed-main-nudge's job (just push); a
+// behind-only main just needs a pull; the diverged case is the one the
+// fast-land path exists for.
+//
+// Exit codes: 0 — always (informational Stop hook). Fails open.
+
+import process from 'node:process'
+
+import {
+  currentBranch,
+  gitOut,
+  isDefaultBranch,
+} from '../_shared/git-branch.mts'
+import { isSquashOptIn } from '../_shared/fleet-roster.mts'
+import { defineHook, notify, runHook } from '../_shared/guard.mts'
+
+export function getProjectDir(): string {
+  return process.env['CLAUDE_PROJECT_DIR'] || process.cwd()
+}
+
+// Ahead / behind counts vs origin/. `git rev-list --left-right
+// --count origin/...HEAD` prints "\t". Returns
+// undefined when there's no upstream to compare against.
+export function aheadBehind(
+  repoDir: string,
+  branch: string,
+): { ahead: number; behind: number } | undefined {
+  const out = gitOut(repoDir, [
+    'rev-list',
+    '--left-right',
+    '--count',
+    `origin/${branch}...HEAD`,
+  ])
+  if (out === undefined) {
+    return undefined
+  }
+  const parts = out.split(/\s+/)
+  /* c8 ignore start - parts[0]/parts[1] are always defined for valid git output; ?? '' fallback and NaN guard are defensive-only and unreachable from a real git repo */
+  const behind = Number.parseInt(parts[0] ?? '', 10)
+  const ahead = Number.parseInt(parts[1] ?? '', 10)
+  if (!Number.isFinite(behind) || !Number.isFinite(ahead)) {
+    return undefined
+  }
+  /* c8 ignore stop */
+  return { ahead, behind }
+}
+
+// Diverged = BOTH ahead and behind. That's the non-fast-forward state the
+// fast-land path is for; ahead-only / behind-only are not.
+export function isDiverged(counts: { ahead: number; behind: number }): boolean {
+  return counts.ahead > 0 && counts.behind > 0
+}
+
+export const check = () => {
+  const repoDir = getProjectDir()
+  const branch = currentBranch(repoDir)
+  if (!branch || !isDefaultBranch(repoDir, branch)) {
+    return undefined
+  }
+  const counts = aheadBehind(repoDir, branch)
+  if (!counts || !isDiverged(counts)) {
+    return undefined
+  }
+  // A squash-history repo's default branch is INTENTIONALLY diverged from
+  // origin: origin holds the pre-squash history and local  is
+  // canonical. The fast-land cherry-pick-onto-origin path does NOT apply —
+  // landing is a force-push. Point there instead of the fast-forward path.
+  if (isSquashOptIn(repoDir)) {
+    return notify(
+      [
+        `[land-fast-nudge] ${branch} is diverged from origin/${branch} ` +
+          `(${counts.ahead} ahead, ${counts.behind} behind), but this is a`,
+        `squash-history repo — local ${branch} is canonical and origin holds`,
+        'the pre-squash history. Do NOT fast-land / cherry-pick onto origin.',
+        'Land via the squashing-history force-push:',
+        `    SQUASH_HISTORY=1 git push --force-with-lease origin ${branch}`,
+        '',
+        'The SQUASH_HISTORY sentinel must be in the hook process ENV (export it),',
+        'not just an inline shell assignment — an inline `SQUASH_HISTORY=1 git`',
+        'lives in the command string, which the PreToolUse no-force-push-guard',
+        'does not read, so it still blocks. If it does, the bypass phrase is',
+        '`Allow force-push bypass`.',
+        '',
+      ].join('\n'),
+    )
+  }
+  return notify(
+    [
+      `[land-fast-nudge] ${branch} has DIVERGED from origin/${branch}: ` +
+        `${counts.ahead} ahead, ${counts.behind} behind.`,
+      '',
+      'A direct push will be rejected, and `reset --hard` would discard local',
+      'work (a parallel session likely squashed onto origin). Do NOT hand-roll',
+      'a cherry-pick + force. Fast-land the local-only commits instead:',
+      '    node .claude/skills/fleet/managing-worktrees/lib/land.mts --last ',
+      '    node .claude/skills/fleet/managing-worktrees/lib/land.mts --last  --push',
+      '',
+      'It re-asserts the lint gate, cherry-picks onto a throwaway origin/' +
+        `${branch} worktree, and fast-forwards (never force).`,
+      '',
+    ].join('\n'),
+  )
+}
+
+export const hook = defineHook({
+  check,
+  event: 'Stop',
+  type: 'nudge',
+})
+
+void runHook(hook, import.meta.url)
diff --git a/.claude/hooks/fleet/land-fast-nudge/package.json b/.claude/hooks/fleet/land-fast-nudge/package.json
new file mode 100644
index 0000000..899a6aa
--- /dev/null
+++ b/.claude/hooks/fleet/land-fast-nudge/package.json
@@ -0,0 +1,16 @@
+{
+  "name": "hook-land-fast-nudge",
+  "private": true,
+  "type": "module",
+  "main": "./index.mts",
+  "exports": {
+    ".": "./index.mts"
+  },
+  "scripts": {
+    "test": "node --test test/*.test.mts"
+  },
+  "devDependencies": {
+    "@socketsecurity/lib-stable": "catalog:",
+    "@types/node": "catalog:"
+  }
+}
diff --git a/.claude/hooks/fleet/land-fast-nudge/tsconfig.json b/.claude/hooks/fleet/land-fast-nudge/tsconfig.json
new file mode 100644
index 0000000..19458cf
--- /dev/null
+++ b/.claude/hooks/fleet/land-fast-nudge/tsconfig.json
@@ -0,0 +1,16 @@
+{
+  "compilerOptions": {
+    "declarationMap": false,
+    "erasableSyntaxOnly": true,
+    "module": "nodenext",
+    "moduleResolution": "nodenext",
+    "noEmit": true,
+    "rewriteRelativeImportExtensions": true,
+    "skipLibCheck": true,
+    "sourceMap": false,
+    "strict": true,
+    "target": "esnext",
+    "types": ["node"],
+    "verbatimModuleSyntax": true
+  }
+}
diff --git a/.claude/hooks/fleet/live-edit-collision-guard/index.mts b/.claude/hooks/fleet/live-edit-collision-guard/index.mts
new file mode 100644
index 0000000..4b7b0e6
--- /dev/null
+++ b/.claude/hooks/fleet/live-edit-collision-guard/index.mts
@@ -0,0 +1,203 @@
+/*
+ * @file Claude Code PreToolUse hook — live-edit-collision-guard.
+ *
+ * Blocks an Edit / Write / NotebookEdit operation when the target path was
+ * written by a DIFFERENT live actor within the last 5 minutes. "Live" means
+ * the other actor's ledger file has an updatedAt within the 15-minute TTL.
+ *
+ * Problem observed live (#239): while a background workflow edited extension
+ * src files, the interactive session blind-edited the same files (survived
+ * by luck) and was then blocked for three consecutive turns by
+ * dirty-worktree-stop-guard because the dirty paths belonged to the live run.
+ * The collision is better caught HERE, before the write lands.
+ *
+ * Actor key: sha256(transcript_path).slice(0,16). The transcript_path
+ * discriminates actors — each subagent / workflow-agent gets its own JSONL
+ * while the main session has a different one. Keying by its hash gives a
+ * stable, content-free filesystem key. See _shared/active-edits-ledger.mts.
+ *
+ * Block message shape: What / Where / Saw-vs-wanted / Fix — three sanctioned
+ * moves:
+ *   (a) stop the other run first (TaskStop, then resume via its journal),
+ *   (b) queue the edit for after the run lands,
+ *   (c) user types `Allow live-edit-collision bypass` verbatim.
+ *
+ * Fail-open: any IO / parse error falls through (no block), per the fleet's
+ * hook contract — "a buggy hook silently allows" beats "a buggy hook wedges
+ * the session."
+ */
+
+import path from 'node:path'
+import process from 'node:process'
+
+import {
+  COLLISION_WINDOW_MS,
+  computeActorId,
+  isActorLive,
+  LEDGER_TTL_MS,
+  ledgerFilePath,
+  listOtherActorLedgerPaths,
+  lookupPath,
+  normalizeForLedger,
+  pruneLedger,
+  readActorLedger,
+  resolveStoreRoot,
+} from '../_shared/active-edits-ledger.mts'
+import { block, defineHook, runHook } from '../_shared/guard.mts'
+import { readFilePath } from '../_shared/payload.mts'
+import type { ToolCallPayload } from '../_shared/payload.mts'
+import type { GuardResult } from '../_shared/guard.mts'
+import { bypassPhrasePresent } from '../_shared/transcript.mts'
+
+const BYPASS_PHRASE = 'Allow live-edit-collision bypass'
+
+function getProjectDir(): string {
+  return process.env['CLAUDE_PROJECT_DIR'] ?? process.cwd()
+}
+
+/**
+ * Pure decision core — injectable clock, no disk IO. Returns the collision
+ * result when the target path was written by a live foreign actor within the
+ * collision window; `undefined` (allow) otherwise.
+ *
+ * Factored out so tests can call it directly without real filesystem or
+ * transcript IO.
+ */
+export interface CollisionResult {
+  readonly otherActorId: string
+  readonly secondsAgo: number
+}
+
+export function detectCollision(
+  ownActorId: string,
+  normalizedPath: string,
+  otherLedgerPaths: readonly string[],
+  options: {
+    now: number
+    collisionWindowMs: number
+    ttlMs: number
+    // Own actor's last-write timestamp for this path, if any. When defined and
+    // strictly newer than a foreign write, the foreign write is ignored — we
+    // already re-claimed the file after the foreign actor wrote it.
+    ownWriteTs?: number | undefined
+  },
+): CollisionResult | undefined {
+  const { now, collisionWindowMs, ttlMs, ownWriteTs } = {
+    __proto__: null,
+    ...options,
+  } as typeof options
+  for (let i = 0, { length } = otherLedgerPaths; i < length; i += 1) {
+    const fp = otherLedgerPaths[i]!
+    const raw = readActorLedger(fp)
+    if (!raw) {
+      continue
+    }
+    if (raw.actorId === ownActorId) {
+      continue
+    }
+    if (!isActorLive(raw, { now, ttlMs })) {
+      continue
+    }
+    const ledger = pruneLedger(raw, { now, ttlMs })
+    if (!ledger) {
+      continue
+    }
+    const lastWrite = lookupPath(ledger, normalizedPath)
+    if (lastWrite === undefined) {
+      continue
+    }
+    if (now - lastWrite > collisionWindowMs) {
+      continue
+    }
+    // Own actor wrote this file MORE RECENTLY than the foreign actor → we
+    // re-claimed it; the foreign write is superseded. Allow the re-edit.
+    if (ownWriteTs !== undefined && ownWriteTs >= lastWrite) {
+      continue
+    }
+    return {
+      otherActorId: raw.actorId,
+      secondsAgo: Math.round((now - lastWrite) / 1000),
+    }
+  }
+  return undefined
+}
+
+export function check(payload: ToolCallPayload): GuardResult {
+  const tool = payload?.tool_name
+  if (tool !== 'Edit' && tool !== 'NotebookEdit' && tool !== 'Write') {
+    return undefined
+  }
+
+  // Bypass check FIRST — transcript IO before ledger IO, exactly like the
+  // other guards (ai-config-poisoning-guard etc.).
+  if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE)) {
+    return undefined
+  }
+
+  const filePath = readFilePath(payload)
+  if (!filePath) {
+    return undefined
+  }
+
+  const ownActorId = computeActorId(payload.transcript_path)
+  if (!ownActorId) {
+    return undefined
+  }
+
+  const projectDir = getProjectDir()
+  const storeRoot = resolveStoreRoot(projectDir)
+  const absPath = path.resolve(projectDir, filePath)
+  const normalizedPath = normalizeForLedger(absPath)
+
+  // Fail-open: if own ledger lookup is unavailable, skip any self-check.
+  const ownFp = ledgerFilePath(storeRoot, ownActorId)
+  const ownLedger = readActorLedger(ownFp)
+  const now = Date.now()
+  // Our own write timestamp for this path — used to determine recency vs foreign.
+  const ownWrite = ownLedger ? lookupPath(ownLedger, normalizedPath) : undefined
+
+  const otherPaths = listOtherActorLedgerPaths(storeRoot, ownActorId)
+  if (!otherPaths.length) {
+    return undefined
+  }
+
+  const collision = detectCollision(ownActorId, normalizedPath, otherPaths, {
+    now,
+    collisionWindowMs: COLLISION_WINDOW_MS,
+    ttlMs: LEDGER_TTL_MS,
+    ownWriteTs: ownWrite,
+  })
+  if (!collision) {
+    return undefined
+  }
+
+  const shortPath = path.relative(projectDir, absPath)
+  return block(
+    [
+      `🚨 live-edit-collision-guard: another live session last wrote this path`,
+      `${collision.secondsAgo}s ago — editing now risks a blind overwrite.`,
+      ``,
+      `File:         ${shortPath}`,
+      `Other actor:  ${collision.otherActorId}`,
+      `Last write:   ${collision.secondsAgo}s ago (within ${COLLISION_WINDOW_MS / 1000 / 60}-min collision window)`,
+      ``,
+      `Sanctioned moves:`,
+      `  (a) Stop the other run first — use TaskStop, then resume via its`,
+      `      journal (.claude/plans/...) once it has landed.`,
+      `  (b) Queue this edit for after the other run completes; work on a`,
+      `      different file in the meantime.`,
+      `  (c) The other run is already finished or abandoned — the user types`,
+      `      "${BYPASS_PHRASE}" verbatim to proceed.`,
+      ``,
+    ].join('\n'),
+  )
+}
+
+export const hook = defineHook({
+  check,
+  event: 'PreToolUse',
+  matcher: ['Edit', 'NotebookEdit', 'Write'],
+  type: 'guard',
+})
+
+void runHook(hook, import.meta.url)
diff --git a/.claude/hooks/fleet/lock-step-ref-nudge/README.md b/.claude/hooks/fleet/lock-step-ref-nudge/README.md
new file mode 100644
index 0000000..02964a1
--- /dev/null
+++ b/.claude/hooks/fleet/lock-step-ref-nudge/README.md
@@ -0,0 +1,62 @@
+# lock-step-ref-nudge
+
+PreToolUse hook (informational; never blocks) that flags malformed and stale `Lock-step` comments at the moment they land in a file.
+
+## Why
+
+Per CLAUDE.md's _Code style → Cross-port files_ rule, files that ship in multiple language implementations use a `Lock-step` comment convention to cross-reference the canonical impl. The full forms live in [`docs/agents.md/fleet/parser-comments.md`](../../../docs/agents.md/fleet/parser-comments.md) §5–6.
+
+The CI gate (`scripts/fleet/check/lock-step-refs-resolve.mts`) catches stale `` references at commit time, but two classes of bugs slip past it:
+
+1. **Typos in the `Lock-step` shape itself** — `lockstep`, `Lock step`, `Lock-step Rust:` (missing `with`/`from`), `Lock-step with: ` (missing ``). The CI regex doesn't match these, so they silently rot forever as illegitimate comments.
+2. **Same-keystroke staleness** — a porter typing `// Lock-step with Rust: crates/parser-stmt/src/foo.rs` after `parser-stmt/` was renamed last week. The CI gate catches it at commit; the hook catches it at the keystroke so the porter sees the breadcrumb before committing.
+
+## What it catches
+
+**Malformed:**
+
+```rust
+// lockstep with Go: parser.go:42         // wrong: hyphen missing
+// Lock step with Go: parser.go:42        // wrong: hyphen missing
+// Lock-step Rust: src/foo.rs             // wrong: missing with/from
+// Lock-step with: src/foo.rs             // wrong: missing 
+// Lock-step with Go, parser.go           // wrong: comma instead of colon
+```
+
+**Stale (when `.config/lock-step-refs.json` is present):**
+
+```rust
+// Lock-step with Rust: crates/parser-stmt/src/foo.rs   // crate doesn't exist
+//! Lock-step from Go: src/parser-old/class.go          // dir was renamed
+```
+
+**Accepted:**
+
+```rust
+//! Lock-step with Go: src/parser/class.go
+//! Lock-step from Rust: crates/parser/src/class.rs
+// Lock-step with Go: parser.go:6450-6457
+// Lock-step note: reshaped for borrowck — Zig's `defer s.restore()` ...
+```
+
+## Scope
+
+- Source-file extensions: `.rs`, `.go`, `.cpp`, `.hpp`, `.h`, `.ts`, `.mts`, `.cts`, `.tsx`, `.py`, `.zig`, `.js`, `.mjs`, `.cjs`, `.jsx`.
+- Skips `test/` directories and `*.test.*` files — illustrative example refs are common in tests and don't represent real port-tracking claims.
+- Stale-path checking is **opt-in per repo**: requires `.config/lock-step-refs.json` to declare `` → impl-root mappings. Without the config, only malformed-shape detection runs.
+- Malformed-shape detection always runs, regardless of opt-in. Typos are typos.
+
+## Behavior
+
+- Exit code 0 in all cases. Hook is informational; the next turn sees the stderr breadcrumb and can fix.
+- The blocking layer is the CI gate `scripts/fleet/check/lock-step-refs-resolve.mts`, run by `pnpm check`.
+
+## Bypass
+
+- Type `Allow lock-step bypass` in a recent user message (also accepts `Allow lockstep bypass` / `Allow lock step bypass`).
+
+## Test
+
+```sh
+pnpm test
+```
diff --git a/.claude/hooks/fleet/lock-step-ref-nudge/index.mts b/.claude/hooks/fleet/lock-step-ref-nudge/index.mts
new file mode 100644
index 0000000..27e83c8
--- /dev/null
+++ b/.claude/hooks/fleet/lock-step-ref-nudge/index.mts
@@ -0,0 +1,352 @@
+#!/usr/bin/env node
+// Claude Code PreToolUse hook — lock-step-ref-nudge.
+//
+// renamed-from: lock-step-ref-guard
+//
+// Flags two failure modes in `Lock-step` comments at the moment they
+// land in a file, before they reach CI (which is gated separately by
+// `scripts/fleet/check/lock-step-refs-resolve.mts`):
+//
+//   1. STALE — the comment names a path that no longer exists in the
+//      target impl. The CI gate also catches this; the hook catches it
+//      one keystroke earlier so the porter can fix as they type.
+//   2. MALFORMED — the comment uses an almost-right shape (`lockstep`,
+//      `Lock step`, `Lock-step Go:` missing `with`/`from`, missing the
+//      `: ` separator). These wouldn't be matched by the
+//      CI scanner at all — they'd silently rot forever. The hook is
+//      the only place that catches the typo class.
+//
+// Convention spec: `docs/agents.md/fleet/parser-comments.md` §5–6.
+// Recognized forms:
+//
+//   //! Lock-step with :                (canonical side)
+//   //! Lock-step from :                (port side)
+//   // Lock-step with : [:]     (inline cross-ref)
+//   // Lock-step note:                    (rationale; not validated)
+//
+// Behavior:
+//   - Never blocks. Hook is informational; the breadcrumb in stderr is
+//     the next-turn nudge. The blocking layer is the CI gate in
+//     `pnpm check`.
+//   - Opt-in per repo: when `.config/lock-step-refs.json` is absent,
+//     STALE checks are skipped (the gate is disabled at the repo
+//     level). MALFORMED checks always run — they detect typos
+//     regardless of whether the repo has opted into validation.
+//   - Only fires for the new content the edit introduces. Comments
+//     that were already in the file but unchanged aren't re-flagged.
+//
+// Scope:
+//   - Source-file extensions: .rs, .go, .cpp, .hpp, .h, .ts, .mts,
+//     .cts, .tsx, .py, .zig, .js, .mjs, .cjs, .jsx.
+//   - Skips test/ directories and *.test.* files — illustrative
+//     example refs are common in tests.
+//
+// Bypass: type `Allow lock-step bypass` in a recent user message.
+
+import { existsSync, readFileSync } from 'node:fs'
+import path from 'node:path'
+import process from 'node:process'
+
+import { normalizePath } from '@socketsecurity/lib-stable/paths/normalize'
+
+import { defineHook, editGuard, notify, runHook } from '../_shared/guard.mts'
+import { bypassPhrasePresent } from '../_shared/transcript.mts'
+
+interface LockStepConfig {
+  readonly roots: Readonly>
+  readonly scan: readonly string[]
+  readonly extensions: readonly string[]
+}
+
+const BYPASS_PHRASES = [
+  'Allow lock-step bypass',
+  'Allow lockstep bypass',
+  'Allow lock step bypass',
+] as const
+
+const SOURCE_EXT_RE =
+  /\.(?:cjs|cpp|cts|go|h|hh|hpp|js|jsx|mjs|mts|py|rs|ts|tsx|zig)$/
+
+// Canonical form: `Lock-step (with|from) : [:]`.
+// Path must contain `.` or `/` so prose like "Lock-step with Go: JSON
+// parser" doesn't false-positive.
+const CANONICAL_RE =
+  /Lock-step (?
from|with) (?[A-Za-z][A-Za-z0-9+#-]*): (?[^\s:,]*[./][^\s:,]*)(?::(?:\d+(?:-\d+)?))?/g + +// Note form is rationale-only; we accept it but don't validate. +const NOTE_RE = /Lock-step note:/ + +// Common typos / near-misses we catch as MALFORMED. Each pattern is a +// shape that LOOKS like a lock-step comment but isn't quite right. +// +// 1. Lowercased / unhyphenated: `lockstep`, `lock step`, `Lockstep`. +// 2. Missing `with`/`from`/`note` discriminator: `Lock-step Rust: …`. +// 3. Hyphen-in-Lang gone wrong: `Lock-step with: …` (no lang). +// 4. Comma instead of colon: `Lock-step with Rust, src/foo.rs`. +const MALFORMED_PATTERNS: ReadonlyArray<{ + readonly re: RegExp + readonly hint: string +}> = [ + { + re: /\blockstep\b/i, + hint: + 'spell it "Lock-step" with a hyphen — the canonical form ' + + 'matches `grep -r "Lock-step"`', + }, + { + re: /\bLock[ _]step\b/, + hint: + 'use a hyphen — write "Lock-step" not "Lock step" or "Lock_step" ' + + 'so the audit grep is uniform', + }, + { + // "Lock-step" followed by an uppercase letter that isn't the start of an + // allowed discriminator keyword (from, note, with) — catches bare "Lock-step Go:" + // or similar missing-keyword forms. + re: /Lock-step (?!(?:from|note|with)\b)[A-Z]/, + hint: + 'missing discriminator — write "Lock-step with :" or ' + + '"Lock-step from :" or "Lock-step note:"', + }, + { + re: /Lock-step (?:from|with) :/, + hint: + 'missing token — write "Lock-step with Go: " ' + + 'not "Lock-step with : "', + }, + { + re: /Lock-step (?:from|with) [A-Za-z][A-Za-z0-9+#-]*,\s/, + hint: + 'use ":" between and , not "," — ' + + '"Lock-step with Go: parser.go" not "Lock-step with Go, parser.go"', + }, +] + +export function checkStale( + refs: readonly MatchedRef[], + config: LockStepConfig, + repoRoot: string, +): StaleHit[] { + const hits: StaleHit[] = [] + for (let i = 0, { length } = refs; i < length; i += 1) { + const ref = refs[i]! + const roots = config.roots[ref.lang] + if (!roots || !roots.length) { + hits.push({ + lineNumber: ref.lineNumber, + preview: ref.preview, + reason: 'unknown-lang', + lang: ref.lang, + refPath: ref.refPath, + }) + continue + } + let found = false + if (existsSync(path.join(repoRoot, ref.refPath))) { + found = true + } else { + for (let r = 0, { length: rLen } = roots; r < rLen; r += 1) { + if (existsSync(path.join(repoRoot, roots[r]!, ref.refPath))) { + found = true + break + } + } + } + if (!found) { + hits.push({ + lineNumber: ref.lineNumber, + preview: ref.preview, + reason: 'path-not-found', + lang: ref.lang, + refPath: ref.refPath, + }) + } + } + return hits +} + +interface MatchedRef { + readonly form: 'with' | 'from' + readonly lang: string + readonly refPath: string + readonly lineNumber: number + readonly preview: string +} + +interface MalformedHit { + readonly lineNumber: number + readonly preview: string + readonly hint: string +} + +interface StaleHit { + readonly lineNumber: number + readonly preview: string + readonly reason: 'unknown-lang' | 'path-not-found' + readonly lang: string + readonly refPath: string +} + +export function findCanonicalRefs(content: string): MatchedRef[] { + const hits: MatchedRef[] = [] + const lines = content.split('\n') + for (let i = 0, { length } = lines; i < length; i += 1) { + const line = lines[i]! + CANONICAL_RE.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = CANONICAL_RE.exec(line)) !== null) { + hits.push({ + form: match.groups!.form as 'with' | 'from', + lang: match.groups!.lang!, + refPath: match.groups!.refPath!, + lineNumber: i + 1, + preview: line.trim().slice(0, 100), + }) + } + } + return hits +} + +export function findMalformed( + content: string, + canonical: readonly MatchedRef[], + noteLines: ReadonlySet, +): MalformedHit[] { + const canonicalLines = new Set(canonical.map(h => h.lineNumber)) + const hits: MalformedHit[] = [] + const lines = content.split('\n') + for (let i = 0, { length } = lines; i < length; i += 1) { + const lineNumber = i + 1 + // If a line already contains a canonical ref or a Lock-step note, + // don't also flag it as malformed. Heuristic: a single line can + // have BOTH a canonical ref and a typo elsewhere, but in practice + // the typos we catch are alternative spellings on the SAME phrase + // — flagging both would be noise. + if (canonicalLines.has(lineNumber) || noteLines.has(lineNumber)) { + continue + } + const line = lines[i]! + for (let p = 0, { length: pLen } = MALFORMED_PATTERNS; p < pLen; p += 1) { + const { re, hint } = MALFORMED_PATTERNS[p]! + if (re.test(line)) { + hits.push({ + lineNumber, + preview: line.trim().slice(0, 100), + hint, + }) + break + } + } + } + return hits +} + +export function findNoteLines(content: string): Set { + const out = new Set() + const lines = content.split('\n') + for (let i = 0, { length } = lines; i < length; i += 1) { + if (NOTE_RE.test(lines[i]!)) { + out.add(i + 1) + } + } + return out +} + +export function loadConfig(repoRoot: string): LockStepConfig | undefined { + const configFile = path.join(repoRoot, '.config', 'lock-step-refs.json') + if (!existsSync(configFile)) { + return undefined + } + let raw: string + try { + raw = readFileSync(configFile, 'utf8') + } catch { + return undefined + } + try { + const parsed = JSON.parse(raw) as unknown + if ( + parsed && + typeof parsed === 'object' && + 'roots' in parsed && + 'scan' in parsed && + 'extensions' in parsed + ) { + return parsed as LockStepConfig + } + } catch { + // Malformed config — let the CI gate report it; hook stays silent. + } + return undefined +} + +export const check = editGuard((filePath, content, payload) => { + const normalizedFilePath = normalizePath(filePath) + if (!SOURCE_EXT_RE.test(normalizedFilePath)) { + return undefined + } + // Skip tests — illustrative example refs are common. + if ( + /(^|\/)test\//.test(normalizedFilePath) || + /\.test\.[a-z]+$/.test(normalizedFilePath) + ) { + return undefined + } + if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASES)) { + return undefined + } + if (!content) { + return undefined + } + const refs = findCanonicalRefs(content) + const noteLines = findNoteLines(content) + const malformed = findMalformed(content, refs, noteLines) + + const repoRoot = payload.cwd ?? process.cwd() + const config = loadConfig(repoRoot) + const stale = config ? checkStale(refs, config, repoRoot) : [] + + if (malformed.length === 0 && stale.length === 0) { + return undefined + } + + const out: string[] = [`[lock-step-ref-nudge] ${filePath}:`, ''] + if (malformed.length > 0) { + out.push(' Malformed Lock-step comment(s) — fix the shape:') + for (let i = 0, { length } = malformed; i < length; i += 1) { + const h = malformed[i]! + out.push(` • line ${h.lineNumber}: "${h.preview}"`) + out.push(` → ${h.hint}`) + } + out.push('') + } + if (stale.length > 0) { + out.push(' Stale Lock-step reference(s) — fix or remove:') + for (let i = 0, { length } = stale; i < length; i += 1) { + const h = stale[i]! + const tag = + h.reason === 'unknown-lang' + ? `unknown "${h.lang}" (add to .config/lock-step-refs.json roots)` + : `path not found: ${h.refPath}` + out.push(` • line ${h.lineNumber}: ${tag}`) + out.push(` "${h.preview}"`) + } + out.push('') + } + out.push(' Spec: docs/agents.md/fleet/parser-comments.md §5–6.') + out.push( + ' CI gate: scripts/fleet/check/lock-step-refs-resolve.mts (run via `pnpm check`).', + ) + out.push(' Bypass: "Allow lock-step bypass" in a recent user message.') + out.push('') + return notify(out.join('\n')) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + scope: 'convention', + type: 'nudge', +}) +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/lock-step-ref-nudge/package.json b/.claude/hooks/fleet/lock-step-ref-nudge/package.json new file mode 100644 index 0000000..f9a039b --- /dev/null +++ b/.claude/hooks/fleet/lock-step-ref-nudge/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-lock-step-ref-nudge", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/lock-step-ref-nudge/tsconfig.json b/.claude/hooks/fleet/lock-step-ref-nudge/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/lock-step-ref-nudge/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/logger-guard/README.md b/.claude/hooks/fleet/logger-guard/README.md new file mode 100644 index 0000000..198fc92 --- /dev/null +++ b/.claude/hooks/fleet/logger-guard/README.md @@ -0,0 +1,104 @@ +# logger-guard + +A **Claude Code hook** that runs before `Edit` or `Write` tool calls +on TypeScript source files and **blocks** edits that introduce direct +stream writes — `process.stderr.write`, `process.stdout.write`, +`console.log` / `error` / `warn` / `info` / `debug` — into source +code that's supposed to use a logger. + +> If you haven't worked with Claude Code hooks before: hooks are tiny +> scripts that run at specific lifecycle points. A `PreToolUse` hook +> like this one fires _before_ Claude calls a tool. It can either +> **prime** (write to stderr, exit 0, model carries on) or **block** +> (exit 2, edit never happens). This one blocks. + +## Why a logger and not console.log + +Source code in this fleet uses `getDefaultLogger()` from +`@socketsecurity/lib-stable/logger` for all output. That logger handles: + +- **Color and theme.** Terminal colors honor the user's environment + (no-color, light/dark, etc.). Direct `console.log` doesn't. +- **Indentation tracking.** Nested operations indent their output. + Direct writes don't, so you get unaligned messages. +- **Stream redirection in tests.** Vitest captures and asserts on + logger output. Direct writes go to the real stdout/stderr and + pollute test reports. +- **Layout-sensitive features.** Spinners, progress bars, and footer + rendering all increment counters the logger maintains. Bypassing + the logger leaves those counters wrong, which produces visual + artifacts (a spinner that doesn't clear, a footer that + duplicates). + +The block is what keeps the logger as the single source of truth. +If even one file directly writes to stdout, the next person on a +related file sees the precedent and follows it; the convention +erodes. + +## Scope + +The hook is intentionally narrow: + +- **Fires** on `Edit` and `Write` calls. +- **Inspects** files matching `*.{ts,mts,tsx,cts}` under repo source. +- **Exempts** `.claude/hooks/`, `.git-hooks/`, `scripts/`, tests, + fixtures, and external/vendored code — those have legitimate + reasons to write directly. +- **Exempts** lines tagged `# socket-lint: allow console` (canonical + per-line opt-out — names the construct being allowed, not the + recommended replacement). The bare form `# socket-lint: allow` + also works for blanket suppression. Legacy `allow logger` is + accepted as an alias for one deprecation cycle. +- **Exempts** lines that look like documentation: lines starting + with `*`, `//`, or `#`; JSDoc tags; fully-backticked code spans. + +## Suggested replacements + +When the hook blocks, it surfaces a concrete rewrite per hit so the +agent can apply it directly: + +| Direct call | Logger equivalent | +| ------------------------- | ------------------- | +| `process.stderr.write(s)` | `logger.error(s)` | +| `process.stdout.write(s)` | `logger.info(s)` | +| `console.error(...)` | `logger.error(...)` | +| `console.warn(...)` | `logger.warn(...)` | +| `console.info(...)` | `logger.info(...)` | +| `console.debug(...)` | `logger.debug(...)` | +| `console.log(...)` | `logger.info(...)` | + +## Wiring + +`.claude/settings.json`: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "node .claude/hooks/fleet/logger-guard/index.mts" + } + ] + } + ] + } +} +``` + +## Testing + +```bash +cd .claude/hooks/logger-guard +node --test test/*.test.mts +``` + +## Cross-fleet sync + +This README and the hook itself live in +[`socket-wheelhouse`](https://github.com/SocketDev/socket-wheelhouse/tree/main/template/.claude/hooks/logger-guard) +and are required to be byte-identical across every fleet repo. +`scripts/sync-scaffolding.mts` flags drift; `--fix` rewrites it. diff --git a/.claude/hooks/fleet/logger-guard/index.mts b/.claude/hooks/fleet/logger-guard/index.mts new file mode 100644 index 0000000..3b2bda9 --- /dev/null +++ b/.claude/hooks/fleet/logger-guard/index.mts @@ -0,0 +1,265 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — logger-guard. +// +// Blocks Edit/Write tool calls that would introduce direct calls to +// `process.stderr.write`, `process.stdout.write`, `console.log`, +// `console.error`, `console.warn`, `console.info`, or `console.debug` +// in source files. Exit code 2 makes Claude Code refuse the tool call +// so the diff never lands. The model sees the rejection reason on +// stderr and retries using the lib's logger. +// +// Why this rule: +// +// The fleet's source code uses `getDefaultLogger()` from +// `@socketsecurity/lib-stable/logger/default` for every output. Direct stream +// writes bypass color/theme handling, indentation tracking, stream +// redirection in tests, and spinner-counter increments — producing +// inconsistent output that breaks layout-sensitive workflows. +// +// Scope: +// +// - Fires only on `Edit` and `Write` tool calls. +// - Only inspects `.ts` / `.mts` / `.cts` / `.tsx` source files. +// Hooks, git-hooks, scripts, tests, fixtures, external/vendored +// code are exempt — see EXEMPT_PATH_PATTERNS. +// - Lines marked `// socket-lint: allow console` are exempt. +// +// AST-based detector (vendored acorn-wasm in `../_shared/acorn/`). +// Replaced the regex implementation that had to compensate for +// string-literal / comment / template-literal false positives via +// `looksLikeDocumentation` heuristics — the parser handles all of +// that intrinsically because it only reaches CallExpression nodes +// for actual calls, not text-shapes that look like calls. +// +// The hook fails OPEN on its own bugs (exit 0 + stderr log) so a bad +// hook deploy can't brick the session. + +// Logger-leak detection (the FORBIDDEN_LOGGER_CALLS table + the AST walk) is +// shared with the commit-time scanLoggerLeaks via the gate-free +// _shared/logger-leaks.mts, so the edit-time and commit-time gates agree. +import { + findLoggerDecoration, + findLoggerLeaks, +} from '../../../../.git-hooks/_shared/logger-leaks.mts' +import type { LoggerDecoration } from '../../../../.git-hooks/_shared/logger-leaks.mts' +import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts' +import { lineIsSuppressed } from '../_shared/markers.mts' + +const EXEMPT_PATH_PATTERNS: RegExp[] = [ + /\.claude\/hooks\//, + /\.git-hooks\//, + // The dep-0 bootstrap (`bootstrap/fleet.mjs`, `bootstrap/prepare.mts`) is + // bare by design — it never imports socket-lib (it's the fetcher that runs + // before any dep exists), so it must call `console.*` directly. Relocating it + // out of `scripts/` lost the `scripts/` exemption below; restore it here. + /(?:^|\/)bootstrap\//, + /(?:^|\/)scripts\//, + /\.(?:spec|test)\.(?:m?[jt]s|tsx?|cts|mts)$/, + /(?:^|\/)tests?\//, + /(?:^|\/)fixtures\//, + /(?:^|\/)external\//, + /(?:^|\/)vendor\//, + /(?:^|\/)upstream\//, + // The logger is its own owner — these files implement the Logger + // class + its browser shim and must call console.* directly. + /(?:^|\/)src\/logger\//, +] + +// The forbidden-call table + AST detector live in the shared +// _shared/logger-leaks.mts (FORBIDDEN_LOGGER_CALLS / findLoggerLeaks), so the +// commit-time scanLoggerLeaks and this edit-time guard use one source. + +export function emitBlock(filePath: string, hits: Hit[]): string { + const out: string[] = [] + out.push('') + out.push('[logger-guard] Blocked: direct stream write found') + out.push( + ' Use `getDefaultLogger()` from `@socketsecurity/lib-stable/logger/default` instead.', + ) + out.push(` File: ${filePath}`) + for (const h of hits.slice(0, 3)) { + out.push(` Line ${h.line}: ${h.text}`) + out.push( + ` Fix: replace \`${h.fullCall}(\` with \`${h.replacement}(\``, + ) + } + if (hits.length > 3) { + out.push(` …and ${hits.length - 3} more.`) + } + out.push( + ' Opt-out for one line (rare): append `// socket-lint: allow console` for a ' + + '`console.*` call, or `// socket-lint: allow process-stdio` for a raw ' + + '`process.std{out,err}.write` (the id must match the call kind).', + ) + out.push('') + return out.join('\n') +} + +interface Hit { + line: number + text: string + fullCall: string + replacement: string +} + +export function isInScope(filePath: string): boolean { + if (!filePath) { + return false + } + // Match TypeScript source extensions: .ts, .mts, .cts, and .tsx. + if (!/\.(?:m?ts|tsx|cts)$/.test(filePath)) { + return false + } + for (let i = 0, { length } = EXEMPT_PATH_PATTERNS; i < length; i += 1) { + const re = EXEMPT_PATH_PATTERNS[i]! + if (re.test(filePath)) { + return false + } + } + return true +} + +export function scan(source: string): Hit[] { + const lines = source.split('\n') + const hits: Hit[] = [] + for (const leak of findLoggerLeaks(source)) { + // Per-line allow marker, keyed by leak kind so the edit-time guard agrees + // with the pre-push `scanLoggerLeaks`: `console.*` waives with + // `// socket-lint: allow console`, raw `process.std*.write` waives with the + // more deliberate `// socket-lint: allow process-stdio`. The marker must be + // on the same source line as the call. + const rule = leak.fullCall.startsWith('process.') + ? 'process-stdio' + : 'console' + /* c8 ignore next - AST line numbers are always within the source range */ + const sourceLine = lines[leak.line - 1] ?? '' + if (lineIsSuppressed(sourceLine, rule)) { + continue + } + hits.push({ + line: leak.line, + text: leak.text, + fullCall: leak.fullCall, + replacement: leak.replacement, + }) + } + hits.sort((a, b) => a.line - b.line) + return hits +} + +// Decoration applies more broadly than the console-leak rule: scripts/ and +// .claude/hooks/ legitimately call console in a few spots (hence exempt above) +// but must NOT hand-roll logger prefixes. So decoration has its own, narrower +// exempt set — external/vendored code, test files (which build expected-output +// fixtures with glyphs), and the logger's own implementation. +const DECORATION_EXEMPT_PATTERNS: RegExp[] = [ + /(?:^|\/)external\//, + /(?:^|\/)vendor\//, + /(?:^|\/)upstream\//, + /(?:^|\/)fixtures\//, + /(?:^|\/)src\/logger\//, + /\.(?:spec|test)\.(?:m?[jt]s|tsx?|cts|mts)$/, +] + +export function isInDecorationScope(filePath: string): boolean { + // Same extension gate as isInScope: only .ts, .mts, .cts, and .tsx files. + if (!filePath || !/\.(?:m?ts|tsx|cts)$/.test(filePath)) { + return false + } + for (let i = 0, { length } = DECORATION_EXEMPT_PATTERNS; i < length; i += 1) { + if (DECORATION_EXEMPT_PATTERNS[i]!.test(filePath)) { + return false + } + } + return true +} + +export function scanDecoration(source: string): LoggerDecoration[] { + const lines = source.split('\n') + const out: LoggerDecoration[] = [] + for (const deco of findLoggerDecoration(source)) { + /* c8 ignore next - AST line numbers are always within the source range */ + const sourceLine = lines[deco.line - 1] ?? '' + if (lineIsSuppressed(sourceLine, 'logger-decoration')) { + continue + } + out.push(deco) + } + return out +} + +export function emitDecorationBlock( + filePath: string, + decos: readonly LoggerDecoration[], +): string { + const out: string[] = [] + out.push('') + out.push('[logger-guard] Blocked: hand-rolled logger decoration') + out.push( + ' The logger method owns its glyph; group()/substep() own indentation.', + ) + out.push(` File: ${filePath}`) + for (let i = 0, { length } = decos; i < length && i < 3; i += 1) { + const d = decos[i]! + out.push(` Line ${d.line}: ${d.text}`) + if (d.kind === 'glyph') { + out.push( + /* c8 ignore next - glyph is always a GLYPH_OWNER key so ownerMethod is always defined */ + ` Fix: drop the \`${d.glyph}\` and call \`logger.${d.ownerMethod ?? 'fail'}(...)\` (the method renders the glyph).`, + ) + } else if (d.kind === 'indent') { + out.push( + ' Fix: wrap items in `logger.group()`/`logger.groupEnd()` (or use `logger.substep()`) — drop the leading spaces.', + ) + } else { + out.push( + ' Fix: use `logger.substep(...)` for an indented sub-item — drop the leading bullet.', + ) + } + } + if (decos.length > 3) { + out.push(` …and ${decos.length - 3} more.`) + } + out.push( + ' Opt-out for one line (rare): append `// socket-lint: allow logger-decoration`.', + ) + out.push('') + return out.join('\n') +} + +export const check = editGuard( + (filePath, content) => { + const source = content ?? '' + if (!source) { + return undefined + } + const blocks: string[] = [] + if (isInScope(filePath)) { + const hits = scan(source) + if (hits.length > 0) { + blocks.push(emitBlock(filePath, hits)) + } + } + if (isInDecorationScope(filePath)) { + const decos = scanDecoration(source) + if (decos.length > 0) { + blocks.push(emitDecorationBlock(filePath, decos)) + } + } + if (blocks.length === 0) { + return undefined + } + return block(blocks.join('\n')) + }, + { fleetOnly: true }, +) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + scope: 'convention', + type: 'guard', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/logger-guard/package.json b/.claude/hooks/fleet/logger-guard/package.json new file mode 100644 index 0000000..9f33762 --- /dev/null +++ b/.claude/hooks/fleet/logger-guard/package.json @@ -0,0 +1,18 @@ +{ + "name": "hook-logger-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + }, + "dependencies": { + "@ultrathink/acorn.wasm": "catalog:" + } +} diff --git a/.claude/hooks/fleet/logger-guard/tsconfig.json b/.claude/hooks/fleet/logger-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/logger-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/markdown-filename-guard/README.md b/.claude/hooks/fleet/markdown-filename-guard/README.md new file mode 100644 index 0000000..3e8cce1 --- /dev/null +++ b/.claude/hooks/fleet/markdown-filename-guard/README.md @@ -0,0 +1,39 @@ +# markdown-filename-guard + +PreToolUse Edit/Write hook that blocks markdown files with non-canonical filenames. + +## What it enforces + +| Filename shape | Allowed at | Notes | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- | ------------------------------------------------------------- | +| `README.md`, `LICENSE` | anywhere | Special-cased by GitHub. | +| `AUTHORS.md`, `CHANGELOG.md`, `CITATION.md`, `CLAUDE.md`, `CODE_OF_CONDUCT.md`, `CONTRIBUTING.md`, `CONTRIBUTORS.md`, `COPYING`, `CREDITS.md`, `GOVERNANCE.md`, `MAINTAINERS.md`, `NOTICE.md`, `SECURITY.md`, `SUPPORT.md`, `TRADEMARK.md` | repo root, `docs/` (top level), or `.claude/` (top level) | The SCREAMING_CASE allowlist. GitHub renders these specially. | +| `lowercase-with-hyphens.md` | inside `docs/` or `.claude/` (any depth) | All other docs. | + +Blocked: + +- Custom SCREAMING_CASE filenames (`NOTES.md`, `MY_DESIGN.md`, etc.) — rename to `notes.md` / `my-design.md`. +- `.MD` extension — use `.md`. +- `camelCase.md` / `snake_case.md` / `Spaces In Filename.md` — convert to lowercase-with-hyphens. +- Lowercase-hyphenated docs at repo root — move to `docs/` or `.claude/`. + +## Why + +SCREAMING_CASE doc filenames optimize for "noticeable in a repo root" but read as shouty + opaque inside body text and TOC links. Lowercase-with-hyphens reads naturally and matches the rest of the fleet's slug-style identifiers (URLs, CSS classes, CLI flags, package names). The narrow SCREAMING_CASE allowlist is the set GitHub renders specially — adding more dilutes the signal. + +The fleet's `scripts/fleet/check/markdown-filenames-are-canonical.mts` does the same check at commit time; this hook catches it earlier, at edit time, so the model gets immediate feedback when it picks a wrong name. + +## Companion files + +- `index.mts` — the hook itself. +- `test/index.test.mts` — node:test specs (15 cases). +- `package.json` — workspace declaration so `taze` can see the hook's deps. +- `tsconfig.json` — fleet-canonical TS config. + +## Adding a new allowed filename + +If GitHub adds a new specially-rendered file (e.g. `FUNDING.md`), update `ALLOWED_SCREAMING_CASE` in `index.mts` and the table above. Don't add custom project-specific SCREAMING_CASE filenames here — those break the convention. + +## Failing open + +The hook fails open on its own bugs (exit 0 + stderr log) so a bad deploy can't brick the session. The `scripts/fleet/check/markdown-filenames-are-canonical.mts` gate at commit time is the second line of defense. diff --git a/.claude/hooks/fleet/markdown-filename-guard/index.mts b/.claude/hooks/fleet/markdown-filename-guard/index.mts new file mode 100644 index 0000000..c0d5ce4 --- /dev/null +++ b/.claude/hooks/fleet/markdown-filename-guard/index.mts @@ -0,0 +1,401 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — markdown-filename-guard. +// +// Blocks Edit/Write tool calls that would create a markdown file +// with a non-canonical filename. Per the fleet's docs convention: +// +// - Allowed everywhere: README.md, LICENSE. +// - Allowed at root, docs/, .claude/ (top level only), or any +// package root (a directory holding package.json — npm renders +// these files from there): the conventional SCREAMING_CASE set +// (AUTHORS, CHANGELOG, CLAUDE, CODE_OF_CONDUCT, CONTRIBUTING, +// GOVERNANCE, MAINTAINERS, NOTICE, SECURITY, SUPPORT, etc.). +// - Everything else must be lowercase-with-hyphens AND placed +// under `docs/` or `.claude/` (at any depth). +// +// Why: SCREAMING_CASE doc filenames optimize for "noticeable in a +// repo root" but read as shouty + opaque inside body text and TOC +// links. Hyphenated lowercase reads naturally and matches every +// other slug-style identifier the fleet uses (URLs, CSS classes, +// CLI flags, package names). The narrow SCREAMING_CASE allowlist is +// the set GitHub renders specially — adding more would dilute the +// signal. +// +// The fleet's `scripts/fleet/check/markdown-filenames-are-canonical.mts` +// does the same check at commit time; this hook catches it earlier, at +// edit time, so the model gets immediate feedback when it picks a wrong +// name. +// +// Exit code 2 makes Claude Code refuse the tool call. +// +// Reads a Claude Code PreToolUse JSON payload from stdin: +// { "tool_name": "Edit"|"Write", +// "tool_input": { "file_path": "...", "content"|"new_string": "..." } } +// +// Fails open on hook bugs (exit 0 + stderr log). + +import { existsSync } from 'node:fs' +import path from 'node:path' + +import { normalizePath } from '@socketsecurity/lib-stable/paths/normalize' + +import { isEphemeralPath } from '../_shared/ephemeral-path.mts' +import { isFleetTarget } from '../_shared/fleet-context.mts' +import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' + +const BYPASS_PHRASE = 'Allow markdown-filename bypass' + +// SCREAMING_CASE files allowed at root / docs/ / .claude/ (top level). +const ALLOWED_SCREAMING_CASE: ReadonlySet = new Set([ + 'AUTHORS', + 'CHANGELOG', + 'CITATION', + 'CLAUDE', + 'CODE_OF_CONDUCT', + 'CONTRIBUTING', + 'CONTRIBUTORS', + 'COPYING', + 'CREDITS', + 'GOVERNANCE', + 'LICENSE', + 'MAINTAINERS', + 'NOTICE', + 'README', + 'SECURITY', + 'SUPPORT', + 'TRADEMARK', +]) + +type Verdict = { + ok: boolean + message?: string | undefined + suggestion?: string | undefined +} + +export function classifyMarkdownPath(absPath: string): Verdict { + const filename = path.basename(absPath) + if (!/\.(MD|markdown|md)$/.test(filename)) { + return { ok: true } + } + + // Scratchpad / temp-dir drafts are not repo docs — exempt them before any + // naming or location rule applies. + if (isEphemeralPath(absPath)) { + return { ok: true } + } + + // A markdown file inside a vendored-source payload tree mirrors an + // EXTERNAL project's layout (node-smol's additions/source-patched/doc/api, + // vendor/, third_party/, upstream/, …): its name and location are + // upstream-dictated source-tree content, not fleet docs. + const payloadNorm = normalizePath(absPath) + if ( + /\/(?:additions\/source-patched|external|third_party|upstream|vendor)\//.test( + payloadNorm, + ) + ) { + return { ok: true } + } + + // Anything under a `.claude/` segment is off-limits to doc-filename + // rules: that tree is owned by Claude Code (auto-memory, skills, + // hooks, settings) and each tool inside picks its own filename + // convention. The hook's job is to keep human-facing docs canonical, + // not police runtime/tooling artifacts. + // + // Cheap-substring pre-check: if the path doesn't even contain the + // literal `.claude` token, skip the normalize call. Saves the + // normalization on the overwhelmingly-common non-`.claude` path. + if (absPath.includes('.claude')) { + const normalized = normalizePath(absPath) + if (normalized.includes('/.claude/') || normalized.endsWith('/.claude')) { + return { ok: true } + } + } + + // A `.md` under `.github/workflows/` is a GitHub Agentic Workflows (gh-aw) + // source, not a doc — `gh aw compile` turns it into a sibling `.lock.yml`. + // It owns its own naming (lowercase-hyphenated, matching the workflow), so + // the human-docs filename convention doesn't apply. + if (absPath.includes('.github')) { + const normalized = normalizePath(absPath) + if (normalized.includes('/.github/workflows/')) { + return { ok: true } + } + // GitHub Copilot reads `.github/copilot-instructions.md` as its repo + // instruction file (a host-dictated name, not a human doc). + if (normalized.endsWith('/.github/copilot-instructions.md')) { + return { ok: true } + } + } + + // Cross-harness agent rule adapters: each AI host reads its rules from a + // host-named path (Cursor, Windsurf, Cline, Kiro). These are tool config the + // host dictates, not human docs, so the doc-filename convention does not + // apply. + const harnessNorm = normalizePath(absPath) + if ( + harnessNorm.includes('/.cursor/rules/') || + harnessNorm.includes('/.windsurf/rules/') || + harnessNorm.includes('/.clinerules/') || + harnessNorm.includes('/.kiro/steering/') + ) { + return { ok: true } + } + + const relPath = normalizePath(toRepoRelative(absPath)) + // For docs that describe a specific code file (e.g. `smol-ffi.js.md`), + // strip the source-file hint before validating the stem. + const stemRaw = filename.replace(/\.(MD|markdown|md)$/, '') + const nameWithoutExt = stripCodeFileHintExt(stemRaw) + // A stripped hint means the stem IS a source filename, quoted verbatim + // (mirror docs: `version_subset.js.md` documents `version_subset.js`). + // Source filenames follow the source tree's convention, so lowercase + // underscores are fine there — renaming the doc would break the mirror. + const mirrorsSourceFile = nameWithoutExt !== stemRaw + + // README / LICENSE — anywhere. + if (nameWithoutExt === 'LICENSE' || nameWithoutExt === 'README') { + return { ok: true } + } + + // SCREAMING_CASE allowlist. + if (ALLOWED_SCREAMING_CASE.has(nameWithoutExt)) { + if (isAtAllowedScreamingLocation(relPath)) { + return { ok: true } + } + // A package root gets the repo-root allowance: npm force-includes + // CHANGELOG/LICENSE/AUTHORS-style files from the directory holding + // package.json, so these names are ecosystem-dictated there (e.g. a + // workspace sub-package's CHANGELOG.md rendered on its npm page). + if (existsSync(path.join(path.dirname(absPath), 'package.json'))) { + return { ok: true } + } + const lowered = filename.toLowerCase().replace(/_/g, '-') + return { + ok: false, + message: `${filename} (SCREAMING_CASE) is allowed only at the repo root, docs/, .claude/, or a package root (a directory with package.json). This path puts it deeper.`, + suggestion: `Either move to root / docs/ / .claude/ / the package root, or rename to ${lowered}.`, + } + } + + // Wrong-case extension `.MD`. + if (filename.endsWith('.MD')) { + return { + ok: false, + message: `Extension is .MD; the fleet uses .md.`, + suggestion: filename.replace(/\.MD$/, '.md'), + } + } + + // SCREAMING_CASE not in the allowlist — never allowed. + if (isScreamingCase(nameWithoutExt)) { + return { + ok: false, + message: `${filename}: SCREAMING_CASE markdown filenames are limited to the canonical allowlist (AUTHORS, CHANGELOG, CLAUDE, README, SECURITY, etc.). Custom doc names should be lowercase-with-hyphens.`, + suggestion: filename.toLowerCase().replace(/_/g, '-'), + } + } + + // Must be lowercase-with-hyphens (a source-mirroring stem may also carry + // the source file's underscores). + if ( + !isLowercaseHyphenated(nameWithoutExt) && + !(mirrorsSourceFile && isLowercaseSourceName(nameWithoutExt)) + ) { + const suggested = nameWithoutExt + .toLowerCase() + .replace(/[_\s]+/g, '-') + .replace(/[^a-z0-9-]/g, '') + return { + ok: false, + message: `${filename}: doc filenames must be lowercase-with-hyphens (no underscores, no camelCase, no spaces).`, + suggestion: `${suggested}.md`, + } + } + + // Lowercase-hyphenated docs must live under docs/ or .claude/. + if (!isAtAllowedRegularLocation(relPath)) { + return { + ok: false, + /* c8 ignore next - path.posix.dirname never returns '' so the || '.' fallback is unreachable */ + message: `${filename}: per-repo docs live under docs/ or .claude/, not at ${path.posix.dirname(relPath) || '.'}.`, + suggestion: `Move to docs/${filename} or .claude/${filename}.`, + } + } + + return { ok: true } +} + +export function emitBlock(filePath: string, verdict: Verdict): string { + const lines: string[] = [] + lines.push('[markdown-filename-guard] Blocked: non-canonical doc filename.') + lines.push(` File: ${filePath}`) + if (verdict.message) { + lines.push(` Issue: ${verdict.message}`) + } + if (verdict.suggestion) { + lines.push(` Suggestion: ${verdict.suggestion}`) + } + lines.push('') + lines.push(' Fleet doc-filename rules:') + lines.push(' - README.md / LICENSE — allowed anywhere.') + lines.push( + ' - SCREAMING_CASE allowlist (AUTHORS, CHANGELOG, CLAUDE, CONTRIBUTING,', + ) + lines.push( + ' GOVERNANCE, MAINTAINERS, NOTICE, README, SECURITY, SUPPORT, …) —', + ) + lines.push(' allowed at root / docs/ / .claude/ (top level only).') + lines.push( + ' - Everything else: lowercase-with-hyphens, in docs/ or .claude/.', + ) + lines.push('') + lines.push(` Deliberate exception? Type "${BYPASS_PHRASE}".`) + return lines.join('\n') + '\n' +} + +export function isAtAllowedRegularLocation(relPath: string): boolean { + const dir = path.posix.dirname(relPath) + if (dir === '.claude' || dir.startsWith('.claude/')) { + return true + } + // Accept any path segment named `docs` so per-package doc trees like + // `packages//docs/.md` and + // `packages//lang//docs/.md` resolve to the same "in + // a docs/ directory" rule as repo-root docs/. Segment-equality (not + // substring) so `foo-docs/`, `docs-old/`, `.docs/` don't match. + const segments = normalizePath(dir).split('/') + return segments.includes('docs') +} + +export function isAtAllowedScreamingLocation(relPath: string): boolean { + const dir = path.posix.dirname(relPath) + // Repo-root-equivalent locations for SCREAMING_CASE allowlist files. + // `template/` is the wheelhouse's scaffolding seed: its CLAUDE.md / + // README.md / docs/ / .claude/ are the canonical sources each fleet repo's + // OWN root copies derive from, so they get the same allowance as the + // repo-root forms (template/CLAUDE.md → /CLAUDE.md after cascade). + return ( + dir === '.' || + dir === '.claude' || + dir === 'docs' || + dir === 'template' || + dir === 'template/.claude' || + dir === 'template/base' || + dir === 'template/base/.claude' || + dir === 'template/base/docs' || + dir === 'template/docs' + ) +} + +export function isLowercaseHyphenated(nameWithoutExt: string): boolean { + return /^[a-z0-9]+(-[a-z0-9]+)*$/.test(nameWithoutExt) +} + +/** + * Loose stem rule for docs that mirror a source file verbatim: lowercase + * segments joined by hyphens or underscores (Node internals use + * `version_subset.js`-style names). Only consulted when the stem carried a + * code-file hint extension. Underscores fold to hyphens so the base + * predicate stays the single shape authority. + */ +export function isLowercaseSourceName(nameWithoutExt: string): boolean { + return isLowercaseHyphenated(nameWithoutExt.replace(/_/g, '-')) +} + +export function isScreamingCase(nameWithoutExt: string): boolean { + return /^[A-Z0-9_]+$/.test(nameWithoutExt) && /[A-Z]/.test(nameWithoutExt) +} + +/** + * Strip a single trailing "source-file extension" hint from a doc-filename + * stem. Canonical fleet pattern for docs describing a specific code file is + * `.md` (e.g. `smol-ffi.js.md` describes `smol-ffi.js`). Without this + * strip, `smol-ffi.js.md` is parsed as stem `smol-ffi.js` which fails + * `isLowercaseHyphenated` on the embedded `.`. The accepted hint extensions + * match the language set the fleet documents code in. + */ +export function stripCodeFileHintExt(stem: string): string { + return stem.replace( + /\.(?:[cm]?[jt]sx?|json|ya?ml|toml|sh|py|rs|go|cc|cpp|h|hpp)$/, + '', + ) +} + +/** + * Strip a leading repo-absolute prefix (anything up through and including a + * `/` segment) so we get the in-repo relative path. Falls back to + * the input if no recognizable prefix. + * + * Special case: socket-wheelhouse keeps the fleet-canonical doc tree under + * `template/`, which acts as the "repo root" from the fleet perspective. Strip + * that extra prefix so doc-location rules apply the same way as in a downstream + * repo (where the docs live at actual root). Without this carve-out, every + * SCREAMING_CASE doc in `template/` (CLAUDE.md, README.md at template root) + * would trip the SCREAMING_CASE-only-at-repo-root rule. + */ +export function toRepoRelative(filePath: string): string { + const normalized = normalizePath(filePath) + // socket-wheelhouse treats template/ as the effective repo root. Anchor on + // the LAST `template/` segment so the carve-out holds for any checkout + // location — `~/projects/`, a `/private/tmp` worktree, or CI's + // `/home/runner/work///` — not only paths under `/projects/`. + // The fleet-canonical content now lives under the `base/` archetype layer + // (`template/base/...`), so peel that segment too: `template/base/CLAUDE.md` + // resolves to `CLAUDE.md`, exactly as the flat `template/CLAUDE.md` does. + const templateIdx = normalized.lastIndexOf('/template/') + if (templateIdx !== -1) { + const afterTemplate = normalized.slice(templateIdx + '/template/'.length) + return afterTemplate.startsWith('base/') + ? afterTemplate.slice('base/'.length) + : afterTemplate + } + // Otherwise strip up through the recognizable repo-checkout prefix. + // `~/projects//` and CI's `.../work///` both collapse to + // the in-repo relative path; fall back to the input when neither matches. + const projectsMatch = normalized.match(/\/projects\/[^/]+\/(.+)$/) + if (projectsMatch) { + return projectsMatch[1]! + } + const ciMatch = normalized.match(/\/work\/[^/]+\/[^/]+\/(.+)$/) + if (ciMatch) { + return ciMatch[1]! + } + return filePath +} + +export const check = editGuard((filePath, content, payload) => { + void content + const verdict = classifyMarkdownPath(filePath) + if (verdict.ok) { + return undefined + } + // The fleet doc-filename convention only governs fleet repos — an external / + // sibling clone (e.g. a GitHub wiki where `Home.md` is the page slug) owns + // its own naming. + if (!isFleetTarget(payload)) { + return undefined + } + // Only block CREATION of a new non-canonical name. Editing a file that + // already exists on disk — whose name predates this rule and which we are + // not renaming — must never be blocked. + if (existsSync(filePath)) { + return undefined + } + // Recoverable override for a deliberate exception. + if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE)) { + return undefined + } + return block(emitBlock(filePath, verdict)) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + type: 'guard', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/markdown-filename-guard/package.json b/.claude/hooks/fleet/markdown-filename-guard/package.json new file mode 100644 index 0000000..35a1a1a --- /dev/null +++ b/.claude/hooks/fleet/markdown-filename-guard/package.json @@ -0,0 +1,18 @@ +{ + "name": "hook-markdown-filename-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "dependencies": { + "@socketsecurity/lib-stable": "catalog:" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/markdown-filename-guard/tsconfig.json b/.claude/hooks/fleet/markdown-filename-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/markdown-filename-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/marketplace-comment-guard/README.md b/.claude/hooks/fleet/marketplace-comment-guard/README.md new file mode 100644 index 0000000..45dd258 --- /dev/null +++ b/.claude/hooks/fleet/marketplace-comment-guard/README.md @@ -0,0 +1,103 @@ +# marketplace-comment-guard + +A **Claude Code PreToolUse hook** that blocks Edit/Write tool calls +which would land a `.claude-plugin/marketplace.json` or sibling +`.claude-plugin/README.md` in an inconsistent state — every plugin +pinned in marketplace.json must have a row in the README's pin table +with matching `version` (= `source.ref`), matching `sha`, and an +ISO-8601 `date`. + +## Why this rule + +JSON has no comments, so marketplace.json can't carry the human-readable +pin metadata (pin date, pinner, free-form notes) that the GHA `uses:` +SHA-pin convention puts inline. The fleet handles this by putting the +machine-readable pin in `marketplace.json` and the human metadata in a +sibling README, then enforcing consistency at edit time. + +Without the guard the two surfaces drift: someone bumps `sha` in JSON +but forgets the README, or the README's `date` rots while pretending +the pin is fresh. Same failure mode the workflow `uses:` rule guards +against — opaque pins look fine and stay broken for months. + +## Conventional shape + +```jsonc +// .claude-plugin/marketplace.json +{ + "plugins": [ + { + "name": "codex", + "source": { + "source": "git-subdir", + "url": "https://github.com/openai/codex-plugin-cc.git", + "ref": "v1.0.1", + "sha": "9cb4fe4099195b2587c402117a3efce6ab5aac78", + }, + }, + ], +} +``` + +```markdown + + +| plugin | version | sha | date | notes | +| ------ | ------- | ---------------------------------------- | ---------- | ------------------------------- | +| codex | v1.0.1 | 9cb4fe4099195b2587c402117a3efce6ab5aac78 | 2026-05-18 | upstream openai/codex-plugin-cc | +``` + +The first four columns are required and inspected. Any trailing column +(e.g. free-form `notes`) is accepted but not validated. `git blame` is the +authoritative record of _who_ bumped a pin, so a `by` column is deliberately +absent — duplicating personal identifiers into fleet-canonical files is a +public-surface-hygiene mistake. + +## What's enforced + +- Every `plugins[].source.sha` in marketplace.json has a row in the + README table keyed by plugin name. +- The row's `version` cell matches `source.ref`. +- The row's `sha` cell matches `source.sha`. +- The row's `date` cell matches ISO-8601 `YYYY-MM-DD`. +- Either file edited without the sibling existing blocks — the pair + must be created and maintained together. + +## What's not enforced + +- The accuracy of `date` — that's a human-review concern (same as the + GHA `uses:` rule). +- Any trailing `notes` column — free-form metadata. +- Source types other than `git-subdir` carrying a `ref` field — if you + add a new source type that doesn't have `ref`, the guard skips that + entry rather than blocking. Add explicit support if the new type + warrants it. + +## Wiring + +In `.claude/settings.json`: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Edit|Write", + "hooks": [ + { + "type": "command", + "command": "node .claude/hooks/fleet/marketplace-comment-guard/index.mts" + } + ] + } + ] + } +} +``` + +## Cross-fleet sync + +This hook lives in +[`socket-wheelhouse`](https://github.com/SocketDev/socket-wheelhouse/tree/main/template/.claude/hooks/marketplace-comment-guard) +and is required to be byte-identical across every fleet repo. +`scripts/sync-scaffolding.mts` flags drift; `--fix` rewrites it. diff --git a/.claude/hooks/fleet/marketplace-comment-guard/index.mts b/.claude/hooks/fleet/marketplace-comment-guard/index.mts new file mode 100644 index 0000000..8464245 --- /dev/null +++ b/.claude/hooks/fleet/marketplace-comment-guard/index.mts @@ -0,0 +1,270 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — marketplace-comment-guard. +// +// Enforces consistency between `.claude-plugin/marketplace.json` and its +// sibling `.claude-plugin/README.md`. Every plugin pinned in +// marketplace.json must have a row in the README's pin table with a +// matching `version` (= `source.ref`) AND `sha`, plus an ISO-8601 `date`. +// +// JSON can't carry comments and Claude Code's marketplace.json parser +// would reject them anyway, so the human-readable pin metadata (pin +// date, pinner, notes) lives in the README. The guard keeps the two +// files honest — same shape as the GHA `uses:` SHA-pin comment rule, +// which uses an inline `# v6.4.0 (YYYY-MM-DD)` to carry the staleness +// signal. +// +// Scope: +// - Fires on Edit and Write tool calls. +// - Only inspects paths ending in `.claude-plugin/marketplace.json` +// or `.claude-plugin/README.md`. +// - When marketplace.json is being edited, the post-edit JSON is +// reconstructed from disk + the proposed change and checked against +// the on-disk README. +// - When README is being edited, the post-edit README is reconstructed +// and checked against the on-disk marketplace.json. +// +// The hook fails OPEN on its own bugs (allow + stderr log) so a bad +// hook deploy can't brick the session. + +import { readFileSync } from 'node:fs' +import path from 'node:path' +import { normalizePath } from '@socketsecurity/lib-stable/paths/normalize' + +import { resolveEditedText } from '../_shared/payload.mts' +import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts' +import type { GuardResult } from '../_shared/guard.mts' +import type { ToolCallPayload } from '../_shared/payload.mts' + +interface PluginPin { + name: string + ref: string + sha: string +} + +interface BadPin { + name: string + expected: PluginPin + reason: string +} + +const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/ + +export function extractPluginPins( + marketplaceJson: string, +): PluginPin[] | undefined { + let parsed: unknown + try { + parsed = JSON.parse(marketplaceJson) + } catch { + return undefined + } + if (!parsed || typeof parsed !== 'object') { + return undefined + } + const plugins = (parsed as { plugins?: unknown | undefined }).plugins + if (!Array.isArray(plugins)) { + return [] + } + const pins: PluginPin[] = [] + for (let i = 0, { length } = plugins; i < length; i += 1) { + const entry = plugins[i]! + if (!entry || typeof entry !== 'object') { + continue + } + const e = entry as Record + const name = typeof e['name'] === 'string' ? e['name'] : undefined + const src = e['source'] + if (!src || typeof src !== 'object') { + continue + } + const s = src as Record + const ref = typeof s['ref'] === 'string' ? s['ref'] : undefined + const sha = typeof s['sha'] === 'string' ? s['sha'] : undefined + if (name && ref && sha) { + pins.push({ name, ref, sha }) + } + } + return pins +} + +interface ReadmeRow { + plugin: string + version: string + sha: string + date: string +} + +// Parse the README's markdown pin table. We look for any line matching +// the pipe-separated table shape with at least 4 columns; the first +// four are plugin / version / sha / date. Trailing columns (by, notes) +// are ignored by the guard. +export function extractReadmeRows(readme: string): ReadmeRow[] { + const rows: ReadmeRow[] = [] + for (const rawLine of readme.split('\n')) { + const line = rawLine.trim() + if (!line.startsWith('|') || !line.endsWith('|')) { + continue + } + // Strip leading + trailing | and split. + const cells = line + .slice(1, -1) + .split('|') + .map(c => c.trim()) + if (cells.length < 4) { + continue + } + const [plugin, version, sha, date] = cells + if (!plugin || !version || !sha || !date) { + continue + } + // Skip header row and divider row. + if (plugin === 'plugin' || /^-+$/.test(plugin.replace(/[\s:-]/g, '-'))) { + continue + } + rows.push({ plugin, version, sha, date }) + } + return rows +} + +export function isGuardedPath( + filePath: string, +): { kind: 'json' | 'readme' } | undefined { + const p = normalizePath(filePath) + if (p.endsWith('/.claude-plugin/marketplace.json')) { + return { kind: 'json' } + } + if (p.endsWith('/.claude-plugin/README.md')) { + return { kind: 'readme' } + } + return undefined +} + +export function reconstructAfterEdit( + payload: ToolCallPayload, +): string | undefined { + // Write → `content` verbatim; Edit/MultiEdit → the on-disk content with the + // proposed change folded in (1 path, 1 reference for the Edit-replay idiom). + return resolveEditedText(payload) +} + +export function siblingPath(filePath: string, kind: 'json' | 'readme'): string { + const dir = path.dirname(filePath) + return kind === 'json' + ? path.join(dir, 'README.md') + : path.join(dir, 'marketplace.json') +} + +export function validate(pins: PluginPin[], rows: ReadmeRow[]): BadPin[] { + const bad: BadPin[] = [] + const byPlugin = new Map() + for (let i = 0, { length } = rows; i < length; i += 1) { + const row = rows[i]! + byPlugin.set(row.plugin, row) + } + for (let i = 0, { length } = pins; i < length; i += 1) { + const pin = pins[i]! + const row = byPlugin.get(pin.name) + if (!row) { + bad.push({ + name: pin.name, + expected: pin, + reason: `no row in README pin table for plugin "${pin.name}"`, + }) + continue + } + if (row.version !== pin.ref) { + bad.push({ + name: pin.name, + expected: pin, + reason: `README version "${row.version}" does not match marketplace.json source.ref "${pin.ref}"`, + }) + } + if (row.sha !== pin.sha) { + bad.push({ + name: pin.name, + expected: pin, + reason: `README sha "${row.sha}" does not match marketplace.json source.sha "${pin.sha}"`, + }) + } + if (!ISO_DATE_RE.test(row.date)) { + bad.push({ + name: pin.name, + expected: pin, + reason: `README date "${row.date}" is not ISO-8601 YYYY-MM-DD`, + }) + } + } + return bad +} + +export const check = editGuard((filePath, _content, payload): GuardResult => { + const kind = isGuardedPath(filePath) + if (!kind) { + return undefined + } + + const reconstructed = reconstructAfterEdit(payload) + if (reconstructed === undefined) { + return undefined + } + + const sibling = siblingPath(filePath, kind.kind) + let siblingContent: string + try { + siblingContent = readFileSync(sibling, 'utf8') + } catch { + // Sibling missing — block, the pair must exist together. + return block( + `[marketplace-comment-guard] refusing edit: sibling file missing.\n` + + ` Edited: ${filePath}\n` + + ` Missing: ${sibling}\n\n` + + `marketplace.json and its sibling README.md must exist together.\n` + + `Create the missing file before editing the other.\n`, + ) + } + + const marketplaceJson = kind.kind === 'json' ? reconstructed : siblingContent + const readme = kind.kind === 'readme' ? reconstructed : siblingContent + + const pins = extractPluginPins(marketplaceJson) + if (pins === undefined) { + return block( + `[marketplace-comment-guard] refusing edit: marketplace.json is not parseable JSON.\n` + + ` File: ${kind.kind === 'json' ? filePath : sibling}\n\n` + + `Fix the JSON syntax before editing either side of the pair.\n`, + ) + } + + const rows = extractReadmeRows(readme) + const bad = validate(pins, rows) + if (bad.length === 0) { + return undefined + } + + return block( + `[marketplace-comment-guard] refusing edit: ` + + `${bad.length} plugin pin(s) drift between marketplace.json and README.md.\n` + + bad + .map( + b => + ` ${b.name}: ${b.reason}\n` + + ` expected row: | ${b.expected.name} | ${b.expected.ref} | ${b.expected.sha} | YYYY-MM-DD | ... |`, + ) + .join('\n') + + '\n\nFix: update the README pin table so every plugin in marketplace.json\n' + + 'has a row with matching version + sha + an ISO-8601 date.\n' + + 'Bump the SHA → bump the row. Same discipline as the GHA `uses:`\n' + + 'SHA-pin comments — the date column is the staleness signal.\n', + ) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + scope: 'convention', + type: 'guard', +}) +/* c8 ignore start - standalone entrypoint only; in-process tests import the hook directly */ +void runHook(hook, import.meta.url) +/* c8 ignore stop */ diff --git a/.claude/hooks/fleet/marketplace-comment-guard/package.json b/.claude/hooks/fleet/marketplace-comment-guard/package.json new file mode 100644 index 0000000..f03d43b --- /dev/null +++ b/.claude/hooks/fleet/marketplace-comment-guard/package.json @@ -0,0 +1,12 @@ +{ + "name": "hook-marketplace-comment-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + } +} diff --git a/.claude/hooks/fleet/marketplace-comment-guard/tsconfig.json b/.claude/hooks/fleet/marketplace-comment-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/marketplace-comment-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/mass-delete-guard/README.md b/.claude/hooks/fleet/mass-delete-guard/README.md new file mode 100644 index 0000000..c283d1e --- /dev/null +++ b/.claude/hooks/fleet/mass-delete-guard/README.md @@ -0,0 +1,23 @@ +# mass-delete-guard + +PreToolUse hook. Blocks a `git commit` whose staged tree would delete a catastrophic fraction of the repo — **≥ 50 files**, or **> 75% of the tracked tree**. + +## Why + +That deletion shape is almost never intentional. It's the fingerprint of a clobbered index: a stray `git read-tree`, a `git commit` fired against a near-empty or foreign index, a leftover rename/test artifact, or a misfired scripted commit. The commit records a tiny tree plus tens of thousands of deletions; once pushed, recovery is painful. + +A session committed `2396 files / 329k deletions` from a 1-file index **twice in a row** (the second on top of the first), and only recovered because nothing had been pushed — `git reset --mixed` to the prior good commit, worktree intact. This gate catches it before the bad commit exists. + +## How + +On a `git commit` (detected via the shared shell-command AST parser, not a regex), it counts: + +- staged deletions — `git diff --cached --diff-filter=D --name-only` +- tracked files — `git ls-files` + +and blocks (exit 2) when deletions ≥ 50 OR deletions / tracked > 0.75. Commits with zero staged deletions, or below both thresholds, pass untouched. Fails **open** on any error — a guard bug must never wedge commits. + +## Bypass + +- `Allow mass-delete bypass` in a recent user turn — for a genuine large removal (dropping a vendored tree, deleting a retired package). +- `FLEET_SYNC=1` command prefix — cascade commits legitimately replace whole fleet directories. diff --git a/.claude/hooks/fleet/mass-delete-guard/index.mts b/.claude/hooks/fleet/mass-delete-guard/index.mts new file mode 100644 index 0000000..a1b9fed --- /dev/null +++ b/.claude/hooks/fleet/mass-delete-guard/index.mts @@ -0,0 +1,192 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — mass-delete-guard. +// +// Blocks a `git commit` whose STAGED tree would delete a catastrophic +// fraction of the repo: ≥ 50 deleted files, OR > 75% of the tree's tracked +// files. That shape is almost never an intentional change — it's a clobbered +// index: a `git read-tree`, a `git commit` fired against a near-empty or +// foreign index, a stray rename/test artifact left in the worktree, or a +// misfired scripted commit. The commit lands a tree with a handful of files +// and tens of thousands of deletions; if it gets pushed, recovery is ugly. +// +// Why a guard and not just "be careful": a session committed +// `2396 files / 329k deletions` from a 1-file index TWICE in a row (the second +// on top of the first), and only recovered because nothing had been pushed — +// `git reset --mixed` to the prior good commit, worktree intact. A pre-commit +// gate catches it before the bad commit exists. +// +// Detection: on a `git commit`, count staged deletions +// (`git diff --cached --diff-filter=D --name-only`) and the tree's tracked +// file count (`git ls-files`). Block when deletions ≥ DELETE_FLOOR or +// deletions / max(tracked, 1) > DELETE_RATIO. +// +// Fails OPEN on any hook error (exit 0 + stderr note) — a guard bug must never +// wedge commits. +// +// Bypass: +// - `Allow mass-delete bypass` in a recent user turn — for a genuine large +// removal (dropping a vendored tree, deleting a retired package). +// - `FLEET_SYNC=1` prefix — cascade commits legitimately replace whole +// fleet dirs and are trusted. +// +// Reads a Claude Code PreToolUse JSON payload from stdin: +// { "tool_name": "Bash", +// "tool_input": { "command": "..." }, +// "transcript_path": "/.../session.jsonl" } + +import { spawnSync } from '@socketsecurity/lib-stable/process/spawn/child' +import process from 'node:process' + +import { isGitCommit } from '../_shared/commit-command.mts' +import { bashGuard, block, defineHook, runHook } from '../_shared/guard.mts' +import { isFleetSyncCommand } from '../_shared/shell-command.mts' +import { spawnTimeoutMs } from '../_shared/spawn-timeout.mts' +import { squashSentinelAllows } from '../_shared/squash-sentinel.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' + +const BYPASS_PHRASES = ['Allow mass-delete bypass'] as const + +// Pre-flight triggers: the dispatcher skips importing this guard unless the raw +// payload contains one of these substrings. The guard can only ever block when +// `isGitCommit(command)` is true, and that detection (the shared +// `_shared/commit-command.mts` segment parse) requires `commit` as the +// subcommand verb of a real `git` segment. So `commit` is a necessary +// substring of every blocking command — safe to gate on. +export const triggers: readonly string[] = ['commit'] + +// A commit deleting at least this many files is catastrophic regardless of +// repo size — catches a wipe in a large repo where the ratio alone wouldn't +// trip until far too late. +const DELETE_FLOOR = 50 +// …or deleting more than this fraction of the tracked tree — catches a wipe +// in a small repo where 50 files is most of it. +const DELETE_RATIO = 0.75 + +export function getRepoDir(): string { + return process.env['CLAUDE_PROJECT_DIR'] || process.cwd() +} + +export { isGitCommit } + +/** + * Count files staged for DELETION in the index (vs HEAD). + */ +export function countStagedDeletions(repoDir: string): number { + const r = spawnSync( + 'git', + ['diff', '--cached', '--diff-filter=D', '--name-only'], + { cwd: repoDir, timeout: spawnTimeoutMs(5000) }, + ) + if (r.status !== 0) { + return 0 + } + return String(r.stdout) + .split('\n') + .map((s: string) => s.trim()) + .filter(Boolean).length +} + +/** + * Count files tracked in HEAD's tree (the denominator for the ratio test). + */ +export function countTrackedFiles(repoDir: string): number { + const r = spawnSync('git', ['ls-files'], { + cwd: repoDir, + timeout: spawnTimeoutMs(5000), + }) + if (r.status !== 0) { + return 0 + } + return String(r.stdout) + .split('\n') + .map((s: string) => s.trim()) + .filter(Boolean).length +} + +/** + * Decide whether a staged deletion count is catastrophic for a tree of the + * given size. Returns the reason string when it is, or undefined. + */ +export function catastrophicReason( + deletions: number, + tracked: number, +): string | undefined { + if (deletions >= DELETE_FLOOR) { + return `${deletions} files staged for deletion (≥ ${DELETE_FLOOR})` + } + const ratio = deletions / Math.max(tracked, 1) + if (ratio > DELETE_RATIO) { + return `${deletions} of ${tracked} tracked files staged for deletion (> ${Math.round( + DELETE_RATIO * 100, + )}%)` + } + return undefined +} + +export const check = bashGuard((command, payload) => { + if (!isGitCommit(command)) { + return undefined + } + // Cascade commits legitimately replace whole fleet directories; the + // FLEET_SYNC sentinel marks a trusted cascade run (same opt-in the + // no-revert / overeager-staging guards honor). + if (isFleetSyncCommand(command)) { + return undefined + } + // The squashing-history collapse commit deletes files removed since the root + // commit; the hardened SQUASH_HISTORY=1 sentinel authorizes it (no phrase). + if (squashSentinelAllows(command)) { + return undefined + } + + const repoDir = getRepoDir() + const deletions = countStagedDeletions(repoDir) + if (deletions === 0) { + return undefined + } + const tracked = countTrackedFiles(repoDir) + const reason = catastrophicReason(deletions, tracked) + if (!reason) { + return undefined + } + + const transcriptPath = payload.transcript_path + if ( + transcriptPath && + bypassPhrasePresent(transcriptPath, BYPASS_PHRASES, 3) + ) { + return undefined + } + + return block( + [ + `[mass-delete-guard] Blocked: this commit would wipe most of the repo.`, + '', + ` ${reason}.`, + '', + ' A commit that deletes this much is almost always a clobbered index', + ' (a stray git read-tree, a commit fired against a near-empty or', + ' foreign index, a misfired scripted commit), not an intentional', + ' change. Pushing it makes recovery ugly.', + '', + ' Check first:', + ' git status # is the worktree actually intact?', + ' git diff --cached --stat | tail -1', + ' If the index is wrong, reset it (worktree is usually fine):', + ' git reset --mixed HEAD', + ' then stage only what you meant: git add …', + '', + ' Genuinely removing a large tree (vendored dir, retired package)?', + ' Type "Allow mass-delete bypass" in chat, then retry.', + ].join('\n'), + ) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Bash'], + triggers, + type: 'guard', +}) +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/mass-delete-guard/package.json b/.claude/hooks/fleet/mass-delete-guard/package.json new file mode 100644 index 0000000..1cfe390 --- /dev/null +++ b/.claude/hooks/fleet/mass-delete-guard/package.json @@ -0,0 +1,18 @@ +{ + "name": "hook-mass-delete-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "dependencies": { + "shell-quote": "catalog:" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/memory-discovery-nudge/README.md b/.claude/hooks/fleet/memory-discovery-nudge/README.md new file mode 100644 index 0000000..0a2c914 --- /dev/null +++ b/.claude/hooks/fleet/memory-discovery-nudge/README.md @@ -0,0 +1,40 @@ +# memory-discovery-nudge + +**Type:** SessionStart hook (NUDGE — informational, never blocks). + +## Trigger + +Fires once at session start when a discoverable memory store exists. It resolves: + +1. **This repo's** store: `~/.claude/projects//memory/`, where `` is + the session cwd's absolute path with every `/` (including the leading one) + replaced by `-`. +2. **The shared fleet (wheelhouse) store**: the same scheme applied to the + sibling `socket-wheelhouse` checkout (`/socket-wheelhouse`). + +If either has a `MEMORY.md` index, it prints — as SessionStart +`additionalContext` — where the store(s) are and the filing convention. Silent +when neither store has an index (new/empty projects add no noise). When the +session is already in the wheelhouse, the "fleet store" line is omitted (it would +point at itself). + +## Why + +Persistent memory lives in `~/.claude`, keyed to cwd — **not committed, not +shared across checkouts, not inherited by spawned subagents**. A session +therefore has no way to know its memory exists, nor that a *different* repo's +store (the fleet-wide wheelhouse one) is the correct home for a given fact. The +result, without this hook: fleet-wide lessons get siloed under whatever repo the +session happened to be standing in, invisible to a session in another fleet repo +doing the same fleet work. + +This hook surfaces both stores and the rule: **remember a fact in the store of +the repo that OWNS it** — fleet/cross-repo facts → the wheelhouse store; this-repo +facts → here — resolving any repo's store generically as +`~/.claude/projects//memory/`. So every fleet session (and any +agent reading this) knows where to look and where to file. + +## Bypass + +None — it only prints informational text and cannot block or mutate anything. +It stays silent on its own when no memory store with a `MEMORY.md` index exists. diff --git a/.claude/hooks/fleet/memory-discovery-nudge/index.mts b/.claude/hooks/fleet/memory-discovery-nudge/index.mts new file mode 100644 index 0000000..1a58295 --- /dev/null +++ b/.claude/hooks/fleet/memory-discovery-nudge/index.mts @@ -0,0 +1,126 @@ +#!/usr/bin/env node +// Claude Code SessionStart hook — memory-discovery-nudge. +// +// Persistent file-based memory lives OUTSIDE the repo, under the user's home: +// ~/.claude/projects//memory/ (slug = absolute project path, "/" → "-") +// keyed to the session's cwd. It is per-user, per-cwd — NOT committed, NOT shared +// across checkouts, NOT inherited by spawned subagents. So a session has no way to +// know it exists, or that a DIFFERENT repo's memory (e.g. the fleet-wide wheelhouse +// store) is the right place for a given fact, unless told. +// +// This hook tells it, at session start: +// 1. Where THIS repo's memory store is (resolved generically from cwd). +// 2. Where the shared FLEET/wheelhouse store is (the cross-repo brain) — so a +// fact owned by the fleet gets filed there, not siloed under whatever repo +// the session happens to be standing in. +// 3. The filing convention: remember a fact in the store of the repo that OWNS +// it; resolve any repo's store as ~/.claude/projects//memory/. +// +// Only surfaces a store that actually exists + has a MEMORY.md index (silent +// otherwise, so empty/new projects add no noise). Pure-informational: never +// blocks, never writes, never fails the session. + +import { existsSync } from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import process from 'node:process' + +import { defineHook, notify, runHook } from '../_shared/guard.mts' + +// The wheelhouse is the fleet's shared memory store — facts that apply to every +// fleet repo (canonical rules, cascade mechanics, cross-repo standards) belong +// here, NOT under the repo a session happens to be in. Resolved by the wheelhouse +// checkout's conventional sibling location relative to the current repo's parent. +const WHEELHOUSE_DIR_NAME = 'socket-wheelhouse' + +// Slugify an absolute project path the way the harness keys its memory store: +// every "/" (including the leading one) becomes "-". +export function projectSlug(absPath: string): string { + return absPath.replace(/\//g, '-') +} + +// The memory dir for a given absolute project path, or undefined if the path is +// not absolute (can't be slugified into a stable key). +export function memoryDirFor(absPath: string): string | undefined { + if (!absPath || !path.isAbsolute(absPath)) { + return undefined + } + return path.join( + os.homedir(), + '.claude', + 'projects', + projectSlug(absPath), + 'memory', + ) +} + +// A store counts as "present" only when it has a MEMORY.md index to read. +export function storeHasIndex(memoryDir: string | undefined): boolean { + return ( + memoryDir !== undefined && existsSync(path.join(memoryDir, 'MEMORY.md')) + ) +} + +// Resolve the sibling wheelhouse checkout's path from the current cwd. Fleet +// repos are checked out as siblings (…/projects/socket-btm, …/projects/socket- +// wheelhouse), so the wheelhouse is /socket-wheelhouse. +export function wheelhousePathFrom(cwd: string): string | undefined { + if (!cwd || !path.isAbsolute(cwd)) { + return undefined + } + return path.join(path.dirname(cwd), WHEELHOUSE_DIR_NAME) +} + +// Build the session-start hint, or undefined when neither store is discoverable. +// Pure — the test drives it directly. +export function memoryHint(cwd: string): string | undefined { + const repoMemory = memoryDirFor(cwd) + const wheelhousePath = wheelhousePathFrom(cwd) + const fleetMemory = wheelhousePath ? memoryDirFor(wheelhousePath) : undefined + + const repoPresent = storeHasIndex(repoMemory) + // The fleet store is only "other" when this session isn't already IN the + // wheelhouse (else repo == fleet and we'd point at ourselves). + const inWheelhouse = + repoMemory !== undefined && + fleetMemory !== undefined && + repoMemory === fleetMemory + const fleetPresent = !inWheelhouse && storeHasIndex(fleetMemory) + + if (!repoPresent && !fleetPresent) { + return undefined + } + + const lines: string[] = [ + 'This repo has persistent file-based memory (per-user, per-cwd, NOT committed). ' + + 'Convention: remember a fact in the store of the repo that OWNS it — ' + + 'fleet/cross-repo facts go in the wheelhouse store, this-repo facts go here. ' + + 'Resolve any repo’s store as ~/.claude/projects//memory/.', + ] + if (repoPresent) { + lines.push(`This repo's memory: ${repoMemory} (read its MEMORY.md index).`) + } + if (fleetPresent) { + lines.push( + `Shared FLEET (wheelhouse) memory: ${fleetMemory} (read its MEMORY.md) — ` + + 'file fleet-wide facts THERE, not under this repo.', + ) + } + return lines.join(' ') +} + +export const check = () => { + const hint = memoryHint(process.cwd()) + if (!hint) { + return undefined + } + return notify(`[memory-discovery] ${hint}`) +} + +export const hook = defineHook({ + check, + event: 'SessionStart', + type: 'nudge', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/minimum-release-age-guard/README.md b/.claude/hooks/fleet/minimum-release-age-guard/README.md new file mode 100644 index 0000000..c5152ae --- /dev/null +++ b/.claude/hooks/fleet/minimum-release-age-guard/README.md @@ -0,0 +1,50 @@ +# minimum-release-age-guard + +PreToolUse Edit/Write hook that blocks additions to `pnpm-workspace.yaml` +`minimumReleaseAge.exclude[]`. + +## Why + +`pnpm`'s `minimumReleaseAge` (typically set to `7d`) refuses to install +packages whose npm publish date is younger than the cap. The cap is +malware-soak protection: packages published within the last week are still +in the suspicion window for typosquats, postinstall malware, and supply-chain +attacks that haven't yet been caught by Socket / npm / community signal. + +`minimumReleaseAge.exclude[]` opts specific packages OUT of the soak. Every +entry is a malware-protection hole — and most attempts to add to it are +quick-fix shortcuts to install a package that just published, not legitimate +emergency CVE patches. + +## What it blocks + +| Pattern | Block? | +| ------------------------------------------------------------------- | ------ | +| Edit/Write that adds a name to `minimumReleaseAge.exclude[]` | yes | +| Edit/Write that removes a name from `minimumReleaseAge.exclude[]` | no | +| Edit/Write touching `pnpm-workspace.yaml` but not the exclude array | no | +| Edit/Write to any other file | no | + +## Bypass + +Type the canonical phrase in a new message: + + Allow soak-time bypass + +`Allow minimumReleaseAge bypass` still works as an alias. The matcher folds +hyphens to spaces, so `Allow soak time bypass` matches too. + +Use sparingly. The legitimate cases are: + +- Emergency CVE patch published in the last 7 days. +- First-party package you control (lower attack-surface risk). + +## Detection + +The hook parses both the current file contents and the after-edit contents +as YAML (permissive, narrow to the `minimumReleaseAge.exclude` block), then +computes the set difference. Names added → block. Names removed or unchanged +→ pass. + +Fails open on YAML parse errors — better to under-block than to brick edits +when the file is in a transient bad state. diff --git a/.claude/hooks/fleet/minimum-release-age-guard/index.mts b/.claude/hooks/fleet/minimum-release-age-guard/index.mts new file mode 100644 index 0000000..82184aa --- /dev/null +++ b/.claude/hooks/fleet/minimum-release-age-guard/index.mts @@ -0,0 +1,175 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — minimum-release-age-guard. +// +// Blocks Edit/Write operations that add entries to a `pnpm-workspace.yaml` +// file's `minimumReleaseAge.exclude[]` array. The 7-day soak is intentional +// malware-soak protection — packages on npm <7 days are still in the +// suspicion window for typosquats / postinstall-script malware / etc. +// Adding to the exclude list bypasses that protection. +// +// Detection model: +// - Fires only on Edit / Write to files named `pnpm-workspace.yaml`. +// - For Edit: applies new_string-over-old_string to current file contents, +// parses before+after as YAML, computes the set difference of the +// `minimumReleaseAge.exclude` array. New names → block. +// - For Write: compares against current contents (absent file = empty +// exclude array). +// +// Bypass: `Allow soak-time bypass` (alias: `Allow minimumReleaseAge bypass`) +// typed verbatim in a recent user turn — for emergency CVE patches where a +// legitimately-published-yesterday fix must be installed before the 7-day +// window closes. The matcher folds hyphens to spaces, so `soak-time` and +// `soak time` both match the same phrase. +// +// Fails open on parse errors (better to under-block than to brick edits +// when the file isn't parseable YAML). + +import path from 'node:path' + +import { safeReadFileSync } from '@socketsecurity/lib-stable/fs/read-file' + +import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts' +import { resolveEditedText } from '../_shared/payload.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' + +// `soak-time` is the canonical phrase; `minimumReleaseAge` is kept as an alias +// so older transcripts / muscle memory still authorize the bypass. Both fold +// through normalizeBypassText, so spacing/hyphen variants of each also match. +const BYPASS_PHRASES = [ + 'Allow soak-time bypass', + 'Allow minimumReleaseAge bypass', +] + +// Permissive YAML extraction tailored to the `minimumReleaseAge.exclude` +// block. We don't pull in a full YAML library — the block shape is narrow: +// +// minimumReleaseAge: +// exclude: +// - pkg-a +// - "@scope/pkg-b" +// +// Returns the set of `- ` entries under the exclude list. Empty set +// when the block isn't present. +export function extractExcludeNames(yamlText: string): Set { + const lines = yamlText.split(/\r?\n/) + const out = new Set() + let inMra = false + let mraIndent = -1 + let inExclude = false + let excludeIndent = -1 + for (let i = 0, { length } = lines; i < length; i += 1) { + const raw = lines[i]! + const line = raw.replace(/\s+#.*$/, '') + const trimmed = line.trim() + if (!trimmed) { + continue + } + const indent = line.length - line.trimStart().length + + if (!inMra) { + if (/^minimumReleaseAge\s*:\s*$/.test(trimmed)) { + inMra = true + mraIndent = indent + } + continue + } + + if (indent <= mraIndent && trimmed.length > 0) { + inMra = false + inExclude = false + continue + } + + if (!inExclude) { + if (/^exclude\s*:\s*$/.test(trimmed)) { + inExclude = true + excludeIndent = indent + } + continue + } + + if (indent <= excludeIndent && trimmed.length > 0) { + inExclude = false + continue + } + + const itemMatch = /^-\s+(?.+)$/.exec(trimmed) + if (!itemMatch) { + continue + } + let name = itemMatch.groups!.item!.trim() + // Strip a surrounding pair of single or double quotes from a YAML scalar value. + name = name.replace(/^["']|["']$/g, '') + if (name) { + out.add(name) + } + } + return out +} + +export const check = editGuard((filePath, _content, payload) => { + if (path.basename(filePath) !== 'pnpm-workspace.yaml') { + return undefined + } + const currentText = safeReadFileSync(filePath) ?? '' + const afterText = resolveEditedText(payload) + if (afterText === undefined) { + return undefined + } + + const beforeNames = extractExcludeNames(currentText) + const afterNames = extractExcludeNames(afterText) + + const added: string[] = [] + for (const name of afterNames) { + if (!beforeNames.has(name)) { + added.push(name) + } + } + if (added.length === 0) { + return undefined + } + + if ( + payload.transcript_path && + bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASES) + ) { + return undefined + } + + added.sort() + return block( + [ + '[minimum-release-age-guard] Blocked: minimumReleaseAge.exclude additions', + '', + ` File: ${filePath}`, + ` New entries: ${added.map(n => `\`${n}\``).join(', ')}`, + '', + ' The 7-day `minimumReleaseAge` soak is intentional malware-soak', + ' protection. Packages on npm < 7 days are still in the typosquat /', + ' postinstall-malware suspicion window. Adding to `exclude[]`', + ' bypasses that protection for the listed packages.', + '', + ' Legitimate cases (rare):', + ' - Emergency CVE patch published < 7 days ago.', + ' - First-party package you control.', + '', + " Don't hand-edit the exclude list — run the canonical helper, which", + ' looks up the npm publish date and writes the dated annotation for you:', + ' node scripts/fleet/soak-bypass.mts @', + ' (the daily updating-daily job removes the entry once its soak clears).', + '', + ` Bypass (to hand-edit anyway): type "${BYPASS_PHRASES[0]}" in a new message, then retry.`, + '', + ].join('\n'), + ) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + type: 'guard', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/minimum-release-age-guard/package.json b/.claude/hooks/fleet/minimum-release-age-guard/package.json new file mode 100644 index 0000000..addbcab --- /dev/null +++ b/.claude/hooks/fleet/minimum-release-age-guard/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-minimum-release-age-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/minimum-release-age-guard/tsconfig.json b/.claude/hooks/fleet/minimum-release-age-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/minimum-release-age-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/module-noun-name-guard/README.md b/.claude/hooks/fleet/module-noun-name-guard/README.md new file mode 100644 index 0000000..79f62e3 --- /dev/null +++ b/.claude/hooks/fleet/module-noun-name-guard/README.md @@ -0,0 +1,20 @@ +# module-noun-name-guard + +**Event:** PreToolUse (`Edit`, `Write`, `MultiEdit`) · **Type:** guard (blocks) · **Scope:** fleet repos only + +Blocks **creating** a new `src/` module whose filename is a verb-phrase action — `trim-publish-manifest.ts`, `create-release.ts`, `fetch-packument.ts`. Fleet/socket-lib modules are concise **NOUN** names that group the related functions for a domain (`manifest.ts` holds `trimPublishManifest` + `createPackageJson`, reachable via one `exports` entry); we don't do one-method-per-file. + +## Allowed (passes) + +- Single-word names — `manifest.ts`, `normalize.ts` (a one-word verb reads as the domain). +- Noun-phrase names — `package-json.ts` (first segment isn't an action verb). +- Predicate prefixes — `is-number.ts`, `has-foo.ts`. +- Exempt stems — `index`, `types`, `constants`, `primordials`; `.test.mts`; `.d.ts`. +- Anything outside a `src/` segment (scripts, config). +- Editing a file that already exists (creation-only; never disturbs prior layout). + +## Bypass + +Type `Allow module-noun-name bypass` for a deliberate exception. + +See [`docs/agents.md/fleet/module-naming.md`](../../../../docs/agents.md/fleet/module-naming.md). diff --git a/.claude/hooks/fleet/module-noun-name-guard/index.mts b/.claude/hooks/fleet/module-noun-name-guard/index.mts new file mode 100644 index 0000000..ade4e4a --- /dev/null +++ b/.claude/hooks/fleet/module-noun-name-guard/index.mts @@ -0,0 +1,213 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — module-noun-name-guard. +// +// Blocks creating a new `src/` module file whose name is a verb-phrase +// (an ACTION), e.g. `trim-publish-manifest.ts`, `create-release.ts`, +// `generate-notes.ts`, `fetch-packument.ts`. +// +// Fleet/socket-lib convention: a module is a concise NOUN that names a +// DOMAIN and groups the related functions for it — `manifest.ts`, +// `exports.ts`, `tarball.ts`, `normalize.ts`. The trim/create/fetch +// helpers live INSIDE the relevant noun module (trimPublishManifest + +// createPackageJson both sit in `manifest.ts`), reachable through that +// module's single `exports` entry. We do NOT do one-method-per-file, and +// we do NOT name a file after the verb phrase of the one function it holds. +// +// Why: verb-phrase filenames fragment a domain across a dozen tiny files, +// multiply the hand-maintained `exports` map, and bury the noun a reader +// is actually looking for. Grouping by noun keeps the public surface small +// and the related code co-located. +// +// The check is filename-only and fires on CREATION (Write of a new path) +// so it never disturbs files that predate the rule. Single-word names are +// always allowed (a one-word verb like `normalize` reads as the domain); +// only a multi-segment kebab phrase LED BY an action verb is blocked. +// +// Exit code 2 makes Claude Code refuse the tool call. +// +// Reads a Claude Code PreToolUse JSON payload from stdin: +// { "tool_name": "Write", +// "tool_input": { "file_path": "...", "content": "..." } } +// +// Fails open on hook bugs (exit 0 + stderr log). + +import { existsSync } from 'node:fs' +import path from 'node:path' + +import { normalizePath } from '@socketsecurity/lib-stable/paths/normalize' + +import { isFleetTarget } from '../_shared/fleet-context.mts' +import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' + +const BYPASS_PHRASE = 'Allow module-noun-name bypass' + +// Leading action verbs that mark a filename as an ACTION rather than a +// domain noun. Deliberately excludes predicate prefixes (is/has/can/should) +// and stand-alone single-word names — those are handled by the +// single-segment carve-out, so `normalize.ts` / `validate.ts` stay legal. +const ACTION_VERBS: ReadonlySet = new Set([ + 'add', + 'apply', + 'build', + 'bump', + 'calculate', + 'check', + 'clean', + 'clear', + 'collect', + 'compile', + 'compute', + 'convert', + 'copy', + 'create', + 'delete', + 'detect', + 'download', + 'ensure', + 'extract', + 'fetch', + 'filter', + 'find', + 'fix', + 'format', + 'gather', + 'generate', + 'get', + 'handle', + 'init', + 'initialize', + 'install', + 'load', + 'make', + 'merge', + 'parse', + 'print', + 'process', + 'read', + 'remove', + 'render', + 'resolve', + 'run', + 'save', + 'scan', + 'send', + 'set', + 'sort', + 'split', + 'strip', + 'sync', + 'trim', + 'update', + 'upload', + 'validate', + 'verify', + 'write', +]) + +// Basenames (without extension) that are structural, not domain modules. +const EXEMPT_STEMS: ReadonlySet = new Set([ + 'constants', + 'index', + 'primordials', + 'types', +]) + +export type Verdict = { + ok: boolean + message?: string | undefined + suggestion?: string | undefined +} + +export function classifyModulePath(absPath: string): Verdict { + const normalized = normalizePath(absPath) + const filename = path.basename(normalized) + + // Source modules only: TypeScript under a `src/` segment. + if (!/\.(?:mts|ts)$/.test(filename) || filename.endsWith('.d.ts')) { + return { ok: true } + } + const segments = normalized.split('/') + if (!segments.includes('src')) { + return { ok: true } + } + // Tests own their own naming (`.test.mts`); never govern them. + if (segments.includes('test') || segments.includes('__tests__')) { + return { ok: true } + } + + const stem = filename.replace(/\.(?:mts|ts)$/, '') + if (EXEMPT_STEMS.has(stem) || stem.endsWith('.test')) { + return { ok: true } + } + + // Single-word names are domain nouns by construction (`manifest`, + // `normalize`, `tarball`) — always allowed. + const parts = stem.split('-') + if (parts.length < 2) { + return { ok: true } + } + + const lead = parts[0]!.toLowerCase() + if (!ACTION_VERBS.has(lead)) { + return { ok: true } + } + + const domain = parts[parts.length - 1]! + return { + ok: false, + message: `${filename} is a verb-phrase (an action), not a domain noun. Fleet modules are concise NOUN names that group the related functions for a domain.`, + suggestion: `Add this function to an existing noun module (e.g. \`${domain}.ts\`), or name the file after its domain noun — not \`${lead}-…\`.`, + } +} + +export function emitBlock(filePath: string, verdict: Verdict): string { + const lines: string[] = [] + lines.push('[module-noun-name-guard] Blocked: verb-phrase module name.') + lines.push(` File: ${filePath}`) + if (verdict.message) { + lines.push(` Issue: ${verdict.message}`) + } + if (verdict.suggestion) { + lines.push(` Suggestion: ${verdict.suggestion}`) + } + lines.push('') + lines.push(' Fleet module-naming convention:') + lines.push(' - A module is a NOUN naming a domain (manifest, tarball).') + lines.push(' - It GROUPS the related functions; not one method per file.') + lines.push(' - trimPublishManifest + createPackageJson both live in') + lines.push(' manifest.ts, reachable via one `exports` entry.') + lines.push('') + lines.push(` Deliberate exception? Type "${BYPASS_PHRASE}".`) + return lines.join('\n') + '\n' +} + +export const check = editGuard((filePath, content, payload) => { + void content + const verdict = classifyModulePath(filePath) + if (verdict.ok) { + return undefined + } + // Fleet convention only — a sibling/external clone owns its own layout. + if (!isFleetTarget(payload)) { + return undefined + } + // Only block CREATION of a new verb-phrase module. Editing a file that + // already exists predates this rule and must never be blocked. + if (existsSync(filePath)) { + return undefined + } + if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE)) { + return undefined + } + return block(emitBlock(filePath, verdict)) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + type: 'guard', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/module-noun-name-guard/package.json b/.claude/hooks/fleet/module-noun-name-guard/package.json new file mode 100644 index 0000000..dfb6a0a --- /dev/null +++ b/.claude/hooks/fleet/module-noun-name-guard/package.json @@ -0,0 +1,18 @@ +{ + "name": "hook-module-noun-name-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "dependencies": { + "@socketsecurity/lib-stable": "catalog:" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/module-noun-name-guard/tsconfig.json b/.claude/hooks/fleet/module-noun-name-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/module-noun-name-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/new-hook-claude-md-guard/README.md b/.claude/hooks/fleet/new-hook-claude-md-guard/README.md new file mode 100644 index 0000000..4c3d52a --- /dev/null +++ b/.claude/hooks/fleet/new-hook-claude-md-guard/README.md @@ -0,0 +1,50 @@ +# new-hook-claude-md-guard + +**Wheelhouse-only** PreToolUse hook. Blocks `Write` / `Edit` to a hook's `index.mts` unless `template/CLAUDE.md` contains an `(enforced by `.claude/hooks//`)` reference for that hook. + +## Why + +Fleet repos read `template/CLAUDE.md` as the source of truth for behavioral rules. A hook without a corresponding CLAUDE.md entry is policy that exists in code but not on paper — users get blocked by a rule they never read. + +This hook closes that drift the moment it would land. Without the CLAUDE.md entry, the hook commit is refused. + +## What it requires + +Adding a new hook (`template/.claude/hooks/my-rule/index.mts`) must be accompanied by an entry in `template/CLAUDE.md`: + +```markdown +🚨 Never do bad thing X — explanation here (enforced by `.claude/hooks/my-rule/`). +``` + +The pattern: **one minimal line, attached to the rule it enforces**, with the parenthetical hook reference in `(enforced by `.claude/hooks//`)` form. Don't add prose; the hook's README carries the detail. + +Accepted variants: + +- ``(enforced by `.claude/hooks/my-rule/`)`` — preferred +- ``(enforced by `.claude/hooks/my-rule`)`` — trailing slash optional +- `` enforced by `.claude/hooks/my-rule/` `` — without parens (less common but accepted) + +## Why wheelhouse-only + +Downstream fleet repos receive their CLAUDE.md and hook code via `sync-scaffolding`. They consume the canonical version; they shouldn't be re-policing the source-of-truth mapping. This hook lives in `template/.claude/hooks/fleet/new-hook-claude-md-guard/` but is **NOT** listed in `scripts/sync-scaffolding/manifest.mts`'s `IDENTICAL_FILES`, so the cascade skips it. + +## Skipped paths + +- `template/.claude/hooks/_shared/...` — helpers, not hooks +- `test/*.test.mts` — test files +- `new-hook-claude-md-guard` itself — chicken-and-egg +- Any hook listed in `WHEELHOUSE_ONLY_HOOKS` in index.mts + +## Bypass + +For follow-up commits on the same PR where the CLAUDE.md entry lands separately, type any of these in a user message: + +- `Allow new-hook bypass` +- `Allow new hook bypass` +- `Allow newhook bypass` + +## Test + +```sh +pnpm test +``` diff --git a/.claude/hooks/fleet/new-hook-claude-md-guard/index.mts b/.claude/hooks/fleet/new-hook-claude-md-guard/index.mts new file mode 100644 index 0000000..d75036f --- /dev/null +++ b/.claude/hooks/fleet/new-hook-claude-md-guard/index.mts @@ -0,0 +1,226 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — new-hook-claude-md-guard. +// +// Blocks Write/Edit operations that create or modify a hook's +// `index.mts` unless the relevant CLAUDE.md contains a backticked +// `(`.claude/hooks//`)` citation (minimal form — no prose +// wrapper required). +// +// Two-mode behavior: +// +// 1. In socket-wheelhouse (path matches `template/base/.claude/hooks/`): +// checks `template/base/CLAUDE.md` — the fleet-canonical source. +// Forces any new hook to land alongside a documented rule. +// +// 2. In every fleet repo (path matches `.claude/hooks/` at repo +// root): checks the repo's `CLAUDE.md`. Catches downstream +// forks — if someone adds a hook locally (against the +// no-fleet-fork rule), the missing citation in the cascaded +// fleet block blocks the edit. Defense in depth on top of +// no-fleet-fork-guard. +// +// Fires on: +// - Write to `/template/base/.claude/hooks//index.mts` (wheelhouse) +// - Edit to `/template/base/.claude/hooks//index.mts` (wheelhouse) +// - Write/Edit to `/.claude/hooks//index.mts` (any fleet repo) +// +// Skips: +// - `_shared/` (not a hook, just helpers) +// - Test files (`test/*.test.mts`) +// - This hook itself (chicken-and-egg) +// +// Bypass: `Allow new-hook bypass` in a recent user turn. + +import { existsSync, readFileSync } from 'node:fs' +import path from 'node:path' +import { normalizePath } from '@socketsecurity/lib-stable/paths/normalize' + +import { bypassPhrasePresent } from '../_shared/transcript.mts' +import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts' + +const BYPASS_PHRASES = [ + 'Allow new-hook bypass', + 'Allow new hook bypass', + 'Allow newhook bypass', +] as const + +// Match either: +// /template/.claude/hooks//index.mts (wheelhouse) +// /.claude/hooks//index.mts (any fleet repo) +// +// Captures the hook name in group 1. The optional `template/` segment +// covers the wheelhouse path; the optional `fleet/` or `repo/` segment +// covers the docs-style `.claude/hooks/{fleet,repo}//` layout +// (matches the parallel docs/agents.md/{fleet,repo}/ convention). +// hookName is the LEAF name (e.g. `avoid-cd-nudge`), not the +// segment-qualified path — citations and registry refs use the full +// canonical path (`\`.claude/hooks/fleet//\``) so the guard's +// expectedRefs uses that path verbatim when checking. +const HOOK_INDEX_PATH_RE = + /.*?(?:\/template)?\/\.claude\/hooks\/(?:(fleet|repo)\/)?([^/]+)\/index\.mts$/ + +// Hooks that are themselves wheelhouse-only — they don't need a +// CLAUDE.md entry because they're internal tooling, not policy rules +// the fleet should know about. Update when adding more. +const WHEELHOUSE_ONLY_HOOKS: ReadonlySet = new Set([ + 'drift-check-nudge', + 'new-hook-claude-md-guard', +]) + +export function findCanonicalClaudeMd( + filePath: string, + cwd: string | undefined, +): string | undefined { + const normalizedFilePath = normalizePath(filePath) + // Wheelhouse mode: `/template/base/.claude/hooks//index.mts` + // → check `/template/base/CLAUDE.md` (the fleet-canonical source). + const tplIdx = normalizedFilePath.indexOf('/template/base/.claude/hooks/') + if (tplIdx >= 0) { + return normalizedFilePath.slice(0, tplIdx) + '/template/base/CLAUDE.md' + } + // Downstream mode: `/.claude/hooks//index.mts` + // → check `/CLAUDE.md` (the cascaded fleet block lives here). + const repoIdx = normalizedFilePath.indexOf('/.claude/hooks/') + if (repoIdx >= 0) { + return normalizedFilePath.slice(0, repoIdx) + '/CLAUDE.md' + } + // Fallback: try cwd-relative. Prefer template/ if present, else + // fall back to repo-root CLAUDE.md. + if (cwd) { + const tplCandidate = path.join(cwd, 'template', 'base', 'CLAUDE.md') + if (existsSync(tplCandidate)) { + return tplCandidate + } + const rootCandidate = path.join(cwd, 'CLAUDE.md') + if (existsSync(rootCandidate)) { + return rootCandidate + } + } + return undefined +} + +export const check = editGuard((filePath, _content, payload) => { + const toolName = payload.tool_name + const normalizedFilePath = normalizePath(filePath) + const match = HOOK_INDEX_PATH_RE.exec(normalizedFilePath) + if (!match) { + return undefined + } + // match[1] = "fleet" | "repo" | undefined (legacy top-level layout). + // match[2] = leaf hook name. + const segment = match[1] + const hookName = match[2]! + // hookPathSuffix is the canonical path under .claude/hooks/, used + // verbatim in CLAUDE.md citations: + // fleet → `fleet/` + // repo → `repo/` (per-repo, normally exempt — see below) + // (none) → `` (legacy top-level) + const hookPathSuffix = segment ? `${segment}/${hookName}` : hookName + // Skip _shared (helpers, not a hook) and wheelhouse-only hooks. + if (hookName === '_shared' || WHEELHOUSE_ONLY_HOOKS.has(hookName)) { + return undefined + } + // Per-repo hooks at `.claude/hooks/repo//` are NOT cascaded + // and live entirely in the host repo. Skip the CLAUDE.md citation + // requirement — repo hooks document themselves in their own README + // + the host repo's CLAUDE.md decides whether to cite them. + if (segment === 'repo') { + return undefined + } + // Bypass via canonical user phrase. + if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASES)) { + return undefined + } + const claudeMdPath = findCanonicalClaudeMd(normalizedFilePath, payload.cwd) + if (!claudeMdPath || !existsSync(claudeMdPath)) { + // Can't find CLAUDE.md; fail-open rather than blocking on + // infrastructure problems. + return undefined + } + let content: string + try { + content = readFileSync(claudeMdPath, 'utf8') + } catch { + return undefined + } + // Three citation shapes recognized (the backticked path is the citation — + // no prose wrapper required; minimal `(\`.claude/hooks/fleet//\`)` is + // the canonical form): + // 1. Inline rule: `(\`.claude/hooks/fleet//\`)` + // 2. Comma-listed: `(\`.claude/hooks/fleet/a/\`, \`.../b/\`)` + // 3. Brace-grouped: `(\`.claude/hooks/fleet/{a,b,c}/\`)` + // 1+2 contain the literal backticked path; 3 is a brace expansion + // — the leaf name appears between `{...}`. + const literalSlashed = `\`.claude/hooks/${hookPathSuffix}/\`` + const literalBare = `\`.claude/hooks/${hookPathSuffix}\`` + const lastSlash = hookPathSuffix.lastIndexOf('/') + const prefix = lastSlash >= 0 ? hookPathSuffix.slice(0, lastSlash + 1) : '' + const leaf = + lastSlash >= 0 ? hookPathSuffix.slice(lastSlash + 1) : hookPathSuffix + const escape = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + const braceRe = new RegExp( + `\`\\.claude/hooks/${escape(prefix)}\\{[^}]*\\b${escape(leaf)}\\b[^}]*\\}/\``, + ) + const citedIn = (text: string): boolean => + text.includes(literalSlashed) || + text.includes(literalBare) || + braceRe.test(text) + + // A citation in the linked hook-registry doc counts too. CLAUDE.md is + // size-capped (claude-md-size-guard), so the registry — which CLAUDE.md's + // `### Hook registry` section explicitly points at as the "full listing" + // — is the canonical low-cost home for per-hook associations. The registry + // lists each fleet hook as a `- \`\` — description` bullet, so a + // backticked leaf there satisfies the gate (in addition to the path forms). + const registryPath = claudeMdPath.replace( + /CLAUDE\.md$/, + 'docs/agents.md/fleet/hook-registry.md', + ) + let registryCited = false + if (registryPath !== claudeMdPath && existsSync(registryPath)) { + try { + const registry = readFileSync(registryPath, 'utf8') + const bulletRe = new RegExp(`^\\s*-\\s*\`${escape(leaf)}\``, 'm') + registryCited = citedIn(registry) || bulletRe.test(registry) + } catch { + // Registry unreadable — fall back to the CLAUDE.md result. + } + } + + if (citedIn(content) || registryCited) { + return undefined + } + + const lines = [ + `[new-hook-claude-md-guard] Hook "${hookPathSuffix}" missing its enforcement reference.`, + '', + ` ${toolName} blocked: the hook needs a one-line association before it`, + ' lands, in EITHER place:', + '', + ` - the hook-registry doc (preferred — CLAUDE.md is size-capped):`, + ` docs/agents.md/fleet/hook-registry.md, as a bullet:`, + ` - \`${leaf}\` — `, + ` - or inline in CLAUDE.md, attached to the rule it enforces:`, + ` (\`.claude/hooks/${hookPathSuffix}/\`)`, + '', + ' Why: fleet repos read CLAUDE.md + its linked docs as the source of', + " truth. A hook with no entry is policy that doesn't exist on paper —", + " users won't know why they got blocked. Prefer the registry bullet;", + ' it keeps CLAUDE.md under the 40 KB cap.', + '', + ' Bypass (use sparingly, e.g. when adding the entry in a follow-up', + ' commit on the same PR): type "Allow new-hook bypass" in a recent', + ' message.', + '', + ] + return block(lines.join('\n') + '\n') +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + scope: 'convention', + type: 'guard', +}) +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/new-hook-claude-md-guard/package.json b/.claude/hooks/fleet/new-hook-claude-md-guard/package.json new file mode 100644 index 0000000..86de182 --- /dev/null +++ b/.claude/hooks/fleet/new-hook-claude-md-guard/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-new-hook-claude-md-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/new-hook-claude-md-guard/tsconfig.json b/.claude/hooks/fleet/new-hook-claude-md-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/new-hook-claude-md-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/no-amend-foreign-commit-guard/README.md b/.claude/hooks/fleet/no-amend-foreign-commit-guard/README.md new file mode 100644 index 0000000..58db184 --- /dev/null +++ b/.claude/hooks/fleet/no-amend-foreign-commit-guard/README.md @@ -0,0 +1,30 @@ +# no-amend-foreign-commit-guard + +**Type:** PreToolUse(Bash) hook (BLOCK — exit 2). + +## Trigger + +Blocks `git commit --amend` when **both** hold for the target repo's HEAD: + +1. HEAD is **ahead of the remote default branch** (`origin/..HEAD ≥ 1`) — the amend rewrites local-only, unpushed history; and +2. HEAD's commit timestamp is **older than ~10 min** — it isn't a commit you authored this turn. + +Together those mean you're amending an unpushed commit a **parallel session** authored. The amend would fold your change + message into their feature commit, with no remote copy to recover from. + +Allowed (not blocked): amending the commit you made this turn (fresh tip, within the threshold), and amending a commit that's already pushed (HEAD == remote tip — that's a force-push concern handled by other guards). + +Detection reads git state from the repo (`extractGitCwd`); the block decision is the pure `shouldBlockAmend(info, nowMs)`. + +## Why + +A session amended a parallel session's unpushed feature commit while landing an unrelated change — it swept the change into the wrong commit and rewrote its message, costing a reflog recovery. A `git status` HEAD-check before amending catches it; this enforces that check at the Bash layer so no amend path skips it. + +## Bypass + +The rare intentional amend of an older own-commit: + +``` +Allow amend-foreign bypass +``` + +Fails open on a malformed payload or unreadable git state. diff --git a/.claude/hooks/fleet/no-amend-foreign-commit-guard/index.mts b/.claude/hooks/fleet/no-amend-foreign-commit-guard/index.mts new file mode 100644 index 0000000..d850793 --- /dev/null +++ b/.claude/hooks/fleet/no-amend-foreign-commit-guard/index.mts @@ -0,0 +1,171 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-amend-foreign-commit-guard. +// +// Blocks `git commit --amend` when HEAD is an UNPUSHED commit that this session +// almost certainly did NOT author — i.e. a parallel Claude session's in-flight +// work sitting on the shared checkout's branch. Amending it rewrites someone +// else's commit (folding your change + message into their feature commit), and +// because it's unpushed there's no remote copy to recover from. +// +// The safe, common case — amending the commit you JUST made — is allowed: a +// freshly-authored tip has a commit time within minutes of now. The dangerous +// case — amending a commit that has been sitting unpushed for a while (another +// session made it) — is blocked. Two conditions must BOTH hold to block: +// 1. HEAD is ahead of the remote default branch (origin/..HEAD ≥ 1), +// so the amend rewrites local-only history; AND +// 2. HEAD's commit timestamp is older than a freshly-made-tip threshold +// (it isn't a commit you just created this turn). +// +// Detection reads git state from the target repo (extractGitCwd); the +// block/allow decision is the pure `shouldBlockAmend`, which the test drives. +// +// Why: a session amended a parallel session's unpushed feature commit while +// trying to quickly land an unrelated change — it swept the change into the +// wrong commit and rewrote its message. A `git status` HEAD-check before +// amending would have caught it; this enforces that check. +// +// Squash-history repos (roster opt-in) are EXEMPT: their lone `chore: initial +// commit` is amended by every session by design (the collaborative land model), +// so the unpushed+old heuristic always mis-fires there and stands down. +// +// Bypass: `Allow amend-foreign bypass` (the rare intentional amend of an older +// own-commit). Exit 0 allow / 2 block. Fails open on any internal error. + +// oxlint-disable-next-line socket/prefer-async-spawn -- PreToolUse hook needs a sync git read to gate the command before it runs; typed string return. +import { spawnSync } from '@socketsecurity/lib-stable/process/spawn/child' + +import { isSquashOptIn } from '../_shared/fleet-roster.mts' +import { bashGuard, block, defineHook, runHook } from '../_shared/guard.mts' +import type { GuardResult } from '../_shared/guard.mts' +import { resolveDefaultBranch } from '../_shared/git-branch.mts' +import { extractGitCwd } from '../_shared/git-cwd.mts' +import type { ToolCallPayload } from '../_shared/payload.mts' +import { commandsFor } from '../_shared/shell-command.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' + +const BYPASS_PHRASE = 'Allow amend-foreign bypass' + +// Pre-flight skip set. The only path to a block is gated by `isAmendCommit`, +// which requires a `git` segment whose args include both `commit` and +// `--amend`; `--amend` is therefore present verbatim in EVERY blocking command. +// The dispatcher skips importing this guard when the payload lacks it. +export const triggers: readonly string[] = ['--amend'] + +// A commit younger than this is treated as "freshly authored this turn" — safe +// to amend. Older + unpushed → likely a parallel session's commit. +const FRESH_TIP_MS = 10 * 60 * 1000 + +// Read-only snapshot of the git state the amend decision needs. +export interface AmendHeadInfo { + // HEAD commits ahead of the remote default branch (origin/..HEAD). + aheadOfRemote: number + // HEAD committer timestamp in epoch ms, or undefined when unreadable. + headCommitMs: number | undefined +} + +// Is this command a `git commit --amend`? (any git segment carrying both). +export function isAmendCommit(command: string): boolean { + for (const c of commandsFor(command, 'git')) { + if (c.args.includes('commit') && c.args.includes('--amend')) { + return true + } + } + return false +} + +// The pure block decision. Blocks only when the amend rewrites an unpushed, +// not-freshly-made tip (a parallel session's commit). `nowMs` is injected so +// the test is deterministic. Returns a reason when blocking, else undefined. +export function shouldBlockAmend( + info: AmendHeadInfo, + nowMs: number, +): string | undefined { + if (info.aheadOfRemote < 1) { + // HEAD matches the remote tip — amending re-authors a pushed commit, a + // force-push concern handled elsewhere, not a foreign-commit one. + return undefined + } + if (info.headCommitMs === undefined) { + return undefined + } + const ageMs = nowMs - info.headCommitMs + if (ageMs <= FRESH_TIP_MS) { + // Freshly authored this turn — the safe, common amend. + return undefined + } + const ageMin = Math.round(ageMs / 60_000) + return `HEAD is an unpushed commit from ~${ageMin} min ago (not one you made this turn) — amending it rewrites a parallel session's work` +} + +// Read the git state for the decision from `repoDir`. Sync so the PreToolUse +// hook can decide before the command runs. +export function readAmendHeadInfo(repoDir: string): AmendHeadInfo { + const run = (args: readonly string[]): string => { + const r = spawnSync('git', ['-C', repoDir, ...args], { encoding: 'utf8' }) + /* c8 ignore next - spawnSync with encoding:'utf8' always returns a string; ?? '' is a structural guard */ + return String(r.stdout ?? '').trim() + } + const base = resolveDefaultBranch(repoDir) + const aheadOfRemote = Number( + run(['rev-list', '--count', `origin/${base}..HEAD`]), + ) + const tsSec = Number(run(['log', '-1', '--format=%ct', 'HEAD'])) + return { + /* c8 ignore start - git --count and %ct always output digits or empty string (→ 0); non-integer fallbacks are structural guards */ + aheadOfRemote: Number.isInteger(aheadOfRemote) ? aheadOfRemote : 0, + headCommitMs: Number.isInteger(tsSec) ? tsSec * 1000 : undefined, + /* c8 ignore stop */ + } +} + +export const check = bashGuard( + (command: string, payload: ToolCallPayload): GuardResult => { + if (!isAmendCommit(command)) { + return undefined + } + const repoDir = extractGitCwd(command, { subcommand: 'commit' }) + // A squash-history repo collapses to ONE shared `chore: initial commit` that + // every session AMENDS by design (the collaborative land model — see the + // squashing-history skill / fleet-roster.isSquashOptIn). The foreign-commit + // heuristic (unpushed + not-freshly-made) ALWAYS trips on that canonical + // commit, so it must stand down here — amending it is the expected land, not + // a parallel session's clobber. Recognized by the repo's roster opt-in. + if (isSquashOptIn(repoDir)) { + return undefined + } + const reason = shouldBlockAmend(readAmendHeadInfo(repoDir), Date.now()) + if (!reason) { + return undefined + } + if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE)) { + return undefined + } + return block( + [ + '[no-amend-foreign-commit-guard] Blocked: `git commit --amend` onto a foreign unpushed commit.', + '', + ` Repo: ${repoDir}`, + ` ${reason}.`, + '', + ' Amending an unpushed commit you did not author this turn folds your', + " change into a parallel session's commit (and rewrites its message),", + ' with no remote copy to recover from.', + '', + ' Fix: verify HEAD first — `git log -1 --format=%s` +', + ' `git rev-list --count origin/..HEAD`. If the tip is another', + " session's, commit your change as a NEW commit, not an amend.", + '', + ` If you truly mean to amend this older own-commit, type: ${BYPASS_PHRASE}`, + ].join('\n'), + ) + }, +) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Bash'], + triggers, + type: 'guard', +}) +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-blanket-file-exclusion-guard/README.md b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/README.md new file mode 100644 index 0000000..d1793fa --- /dev/null +++ b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/README.md @@ -0,0 +1,26 @@ +# no-blanket-file-exclusion-guard + +PreToolUse hook that blocks Edit/Write tool calls introducing a `max-file-lines:` file-size exemption marker that does not name a real category. + +A file may not wave itself past the soft/hard line cap by asserting it deems itself acceptable. The only valid marker is `max-file-lines: `: a single hyphenated category word naming WHAT the file is, plus a separated reason for WHY it can't split. A self-judgment word (`legitimate`, `ok`, `exempt`, `acceptable`, …) is not a description and does not exempt. + +This is the edit-time layer of a three-layer defense: the `socket/max-file-lines` oxlint rule catches the same shape at lint time, and the soft/hard caps fire at every commit. + +The marker is **hard-cap-only** (>1000 lines): a file in the soft band (501–1000) gets no exemption and must split. The oxlint rule ignores any marker in the soft band and reports anyway; this hook enforces the shape contract on whatever marker does land. In almost every case the answer is to split along a natural seam — reach for a marker only for a genuine single cohesive unit past 1000 lines (or a generated file). + +## Allowed + +- `max-file-lines: parser — recursive-descent grammar` +- `max-file-lines: state-machine — exhaustive transition table` +- `max-file-lines: integration-test — one end-to-end scenario per file` + +## Blocked + +- `max-file-lines: legitimate` (self-judgment, no category) +- `max-file-lines: legitimate — one cohesive module` (self-judgment leads) +- `max-file-lines: ok — it's fine` (self-judgment word as category) +- `max-file-lines: parser` (category present, no `— reason`) + +## Bypass + +No bypass — name a real category (or, better, split the file along a natural seam so it no longer needs an exemption). diff --git a/.claude/hooks/fleet/no-blanket-file-exclusion-guard/index.mts b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/index.mts new file mode 100644 index 0000000..247ab9a --- /dev/null +++ b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/index.mts @@ -0,0 +1,196 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-blanket-file-exclusion-guard. +// +// Blocks Edit/Write tool calls that introduce a file-size exemption +// marker that does NOT name a real category. The only valid marker is +// `max-file-lines: `: a single hyphenated category +// word naming WHAT the file is (parser, state-machine, table, cli, +// integration-test, vendored, …) plus a separated reason for WHY it +// can't split. +// +// Why: "no blanket file exclusions". A file may not wave itself past the +// soft/hard line cap by asserting it deems itself acceptable — a marker +// like `max-file-lines: legitimate` (or `ok` / `exempt` / `acceptable`) +// is a self-judgment, not a description. It says "trust me" where the +// rule asks "what is this file". Naming a real category forces the +// author to admit the file's shape, which a reviewer can sanity-check, +// and steers the default toward SPLITTING rather than exempting. +// +// This is the edit-time layer of a three-layer defense: the +// `socket/max-file-lines` oxlint rule catches the same shape at lint +// time, and the soft/hard caps fire at every commit. Catching it at +// Write time means the padded marker never lands in the first place. +// +// HARD-CAP-ONLY: the exemption marker exempts a file only past the +// 1000-line HARD cap (the rare genuine cohesive-unit / generated case). +// A file in the SOFT band (501–1000) gets NO exemption — it must split, +// so the `socket/max-file-lines` rule ignores any marker there and reports +// anyway. This hook can't see the line count from a single Edit's +// new_string, so it enforces only the shape contract here (a marker that +// lands must name a real category + reason); the rule enforces the size +// gate. Splitting is the soft-band answer in every case — the block +// message says so. +// +// Recognized banned shapes (a size-exemption marker that fails the +// `` contract): +// max-file-lines: legitimate (self-judgment, no category) +// max-file-lines: legitimate — one cohesive module (self-judgment leads) +// max-file-lines: ok — it's fine (self-judgment word) +// max-file-lines: parser (category, no reason) +// +// Allowed shapes (pass through): +// max-file-lines: parser — recursive-descent grammar +// max-file-lines: state-machine — exhaustive transition table +// max-file-lines: integration-test — one end-to-end scenario +// +// The valid-marker regex is kept in lock-step with the +// `socket/max-file-lines` oxlint rule's BYPASS_RE — both must agree on +// what a real marker looks like. +// +// Only leading comments (lines 1–5) are scanned, matching the rule: a +// file-level exemption has to communicate intent at the file level, not +// buried mid-file. +// +// Reads PreToolUse JSON payload from stdin: +// { "tool_name": "Edit"|"Write"|"MultiEdit", +// "tool_input": { "file_path": "...", "content"|"new_string": "..." } } +// +// Exit codes: +// 0 — pass. +// 2 — block (a blanket / self-judgment marker found). +// +// Fails open on malformed payloads (exit 0 + stderr log). + +import { block, defineHook, editGuard, runHook } from '../_shared/guard.mts' + +// A size-exemption marker is present at all (any category text after it). +const MARKER_RE = /max-file-lines:\s*\S/i + +// A VALID marker: ``. The category is one hyphenated +// token immediately after the colon, immediately followed by a `—`/`-`/`:` +// separator and a non-empty reason. The self-judgment word `legitimate` +// is explicitly NOT a category. Lock-step with the `socket/max-file-lines` +// oxlint rule's BYPASS_RE — keep both in sync. +const VALID_MARKER_RE = + /max-file-lines:\s*(?!legitimate\b)[a-z][a-z-]*\s*[—:-]\s*\S/i + +// Self-judgment words that are never a real category. Reported by name so +// the fix message can point at the offending word. `legitimate` is caught +// by VALID_MARKER_RE's negative lookahead; the rest fail the contract for +// other reasons (e.g. `ok — fine` has a category-shaped `ok` that passes +// the regex), so they get an explicit denylist check. +const SELF_JUDGMENT_WORDS: readonly string[] = [ + 'acceptable', + 'allowed', + 'exempt', + 'fine', + 'legit', + 'legitimate', + 'okay', + 'ok', + 'valid', +] + +interface Finding { + readonly line: number + readonly text: string + readonly selfJudgmentWord: string | undefined +} + +export function findSelfJudgmentWord(markerLine: string): string | undefined { + const m = /max-file-lines:\s*([a-z][a-z-]*)/i.exec(markerLine) + if (!m) { + return undefined + } + const category = m[1]!.toLowerCase() + for (let i = 0, { length } = SELF_JUDGMENT_WORDS; i < length; i += 1) { + if (category === SELF_JUDGMENT_WORDS[i]) { + return category + } + } + return undefined +} + +export function findBlanketExclusions(text: string): Finding[] { + const findings: Finding[] = [] + const lines = text.split('\n') + // Scan leading comments only — match the rule's first-5-lines window so a + // marker buried mid-file is not treated as a file-level exemption. + const limit = Math.min(lines.length, 5) + for (let i = 0; i < limit; i += 1) { + const line = lines[i]! + if (!MARKER_RE.test(line)) { + continue + } + const selfJudgmentWord = findSelfJudgmentWord(line) + // A marker is banned if it leads with a self-judgment word OR fails the + // `` contract entirely (e.g. category with no reason). + if (selfJudgmentWord !== undefined || !VALID_MARKER_RE.test(line)) { + findings.push({ line: i + 1, text: line.trim(), selfJudgmentWord }) + } + } + return findings +} + +export const check = editGuard((filePath, content) => { + const newContent = content ?? '' + const findings = findBlanketExclusions(newContent) + if (findings.length === 0) { + return undefined + } + const out: string[] = [] + out.push( + '🚨 no-blanket-file-exclusion-guard: blocked Edit/Write — a `max-file-lines:` marker must name a real category.', + ) + out.push('') + /* c8 ignore next - editGuard returns undefined for an empty filePath before this runs */ + out.push(`File: ${filePath || ''}`) + out.push('') + for (let i = 0, { length } = findings; i < length; i += 1) { + const f = findings[i]! + out.push(` Line ${f.line}: ${f.text}`) + if (f.selfJudgmentWord !== undefined) { + out.push( + ` \`${f.selfJudgmentWord}\` is a self-judgment, not a category.`, + ) + } + } + out.push('') + out.push('The only valid exemption marker is:') + out.push(' max-file-lines: ') + out.push('') + out.push( + 'where is ONE hyphenated word naming WHAT the file is (parser,', + ) + out.push( + 'state-machine, table, cli, integration-test, vendored, …) and says', + ) + out.push( + 'WHY it cannot split. No blanket file exclusions — say what the file is,', + ) + out.push('not that you deem it acceptable.') + out.push('') + out.push( + 'And the marker is HARD-CAP-ONLY (>1000 lines): a file in the soft band', + ) + out.push( + '(501–1000) gets NO exemption — it MUST split. So in almost every case the', + ) + out.push( + 'answer is the same: SPLIT along a natural seam. Reach for the marker only', + ) + out.push( + 'for a genuine single cohesive unit past 1000 lines (or a generated file).', + ) + return block(out.join('\n') + '\n') +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + scope: 'convention', + type: 'guard', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-blanket-file-exclusion-guard/package.json b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/package.json new file mode 100644 index 0000000..c553c38 --- /dev/null +++ b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-no-blanket-file-exclusion-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/no-blanket-file-exclusion-guard/tsconfig.json b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/no-blanket-file-exclusion-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/no-blind-keychain-read-guard/README.md b/.claude/hooks/fleet/no-blind-keychain-read-guard/README.md new file mode 100644 index 0000000..22a1796 --- /dev/null +++ b/.claude/hooks/fleet/no-blind-keychain-read-guard/README.md @@ -0,0 +1,65 @@ +# no-blind-keychain-read-guard + +`PreToolUse(Bash)` blocker that refuses direct keychain READ calls +from Bash. The keychain APIs surface a UI auth prompt per call; +reading three times costs three prompts. The fleet's canonical +in-process resolver (`api-token.mts.findApiToken()`) caches the +value module-scoped after the first hit, so subsequent code paths +should never need to re-read the keychain. + +## Detected reads + +| Platform | Pattern | +| -------------- | ---------------------------------------------- | +| macOS | `security find-{generic,internet}-password` | +| Linux | `secret-tool lookup` / `secret-tool search` | +| Windows | `Get-StoredCredential` | +| Windows | `Get-Credential … \| ConvertFrom-SecureString` | +| cross-platform | `keyring get` | + +## Allowed (not flagged) + +Writes and deletes — these only happen during operator-driven +setup / rotation, never on hot paths: + +- `security add-generic-password` / `security delete-generic-password` +- `secret-tool store` / `secret-tool clear` +- `New-StoredCredential` / `Remove-StoredCredential` +- `keyring set` / `keyring del` + +## Bypass + +Type the canonical phrase verbatim in your next user turn: + +``` +Allow blind-keychain-read bypass +``` + +Use when you genuinely need a fresh keychain read — operator-invoked +diagnostics, verifying an entry exists, etc. + +## Why + +`security find-generic-password` on macOS prompts the user every call +unless the calling process is on the entry's ACL. Claude Code's Bash +tool spawns a fresh process per call, so each `security` invocation +re-prompts. The same shape exists on Linux (`secret-tool` against +gnome-keyring / kwallet) and Windows (`Get-StoredCredential` against +the CredentialManager UI). + +The right answer is to read the cached value from process state: + +```ts +import { findApiToken } from '../setup-security-tools/lib/api-token.mts' +const { token } = findApiToken() // module-cached after first call +``` + +Or from a child process spawned by hooks: + +```bash +echo "$SOCKET_API_KEY" # populated by wheelhouse shell-rc bridge +``` + +The bridge writes the token to `~/.zshenv` (or platform equivalent) +so every new shell exports `SOCKET_API_KEY` + `SOCKET_API_TOKEN` +without a keychain read. diff --git a/.claude/hooks/fleet/no-blind-keychain-read-guard/index.mts b/.claude/hooks/fleet/no-blind-keychain-read-guard/index.mts new file mode 100644 index 0000000..619c4a0 --- /dev/null +++ b/.claude/hooks/fleet/no-blind-keychain-read-guard/index.mts @@ -0,0 +1,233 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-blind-keychain-read-guard. +// +// Blocks Bash invocations that READ a credential from the OS +// keychain. Reading via the platform CLI surfaces a per-call UI auth +// prompt on the user's screen ("this app wants to access your +// keychain"), and the prompt fires once per call — a hook chain that +// reads the keychain three times costs three prompts. Tokens are +// already cached in process memory after the first resolution; the +// fleet's canonical resolver (`api-token.mts.findApiToken()`) hits +// the cache, then env, then keychain, in that order. Bash callers +// that go straight to `security find-generic-password` skip all of +// that and re-prompt the user every time. +// +// Detects (case-sensitive, structural — not just substring): +// +// macOS: +// security find-generic-password +// security find-internet-password +// +// Linux: +// secret-tool lookup +// secret-tool search +// +// Windows (PowerShell): +// Get-StoredCredential (CredentialManager module) +// Get-Credential (when piping to ConvertFrom-SecureString) +// +// Cross-platform (Python keyring CLI): +// keyring get +// +// Allowed (writes / deletes — necessary for operator-driven setup / +// rotation, never on hot paths): +// +// security add-generic-password security delete-generic-password +// secret-tool store secret-tool clear +// New-StoredCredential Remove-StoredCredential +// keyring set keyring del +// +// Bypass: `Allow blind-keychain-read bypass` in a recent user turn. +// Use when you genuinely need to verify a keychain entry exists +// (e.g. operator-invoked diagnostics). +// +// Verdict (uniform guard contract): `check` returns `block(message)` to block +// (the runner prints the message + sets exitCode 2) or `undefined` to allow. +// `runGuard` fails open on malformed payloads — the fleet's hook contract. + +import { block, defineHook, runHook } from '../_shared/guard.mts' +import { readCommand } from '../_shared/payload.mts' +import type { ToolCallPayload } from '../_shared/payload.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' + +interface Hit { + readonly tool: string + readonly platform: 'macos' | 'linux' | 'windows' | 'cross-platform' + readonly snippet: string +} + +const BYPASS_PHRASE = 'Allow blind-keychain-read bypass' + +// Pre-flight triggers — the dispatcher imports + runs this guard only +// when the raw command contains at least one of these substrings. Each +// is the literal anchor a `READ_PATTERNS` entry requires, so no command +// can match a pattern without containing one of them: `find-*-password` +// (macOS `security`), `secret-tool` (Linux), `Get-StoredCredential` and +// `ConvertFrom-SecureString` (Windows readback pipe), `keyring` (Python +// CLI). Writes/deletes share these substrings too, so the guard still +// runs for them and correctly returns no hit. +export const triggers: readonly string[] = [ + 'ConvertFrom-SecureString', + 'Get-StoredCredential', + 'find-generic-password', + 'find-internet-password', + 'keyring', + 'secret-tool', +] + +// Token-bearing read patterns. Each entry: the literal verb that +// surfaces a UI prompt + a label for the error message. Writes / +// deletes are intentionally absent from this list. +const READ_PATTERNS: ReadonlyArray<{ + readonly re: RegExp + readonly tool: string + readonly platform: Hit['platform'] +}> = [ + // macOS — `security(1)`. The `-w` flag prints the password to + // stdout, but even the metadata-only form triggers the ACL prompt. + { + re: /\bsecurity\s+(?:find-generic-password|find-internet-password)\b/, + tool: 'security find-*-password', + platform: 'macos', + }, + // Linux — `secret-tool`. `lookup` returns the password; `search` + // lists matches (also surfaces the libsecret prompt). + { + re: /\bsecret-tool\s+(?:lookup|search)\b/, + tool: 'secret-tool lookup/search', + platform: 'linux', + }, + // Windows PowerShell — CredentialManager module. The + // `Get-StoredCredential` cmdlet returns a PSCredential; reading + // `.Password | ConvertFrom-SecureString` is the read pattern. + { + re: /\bGet-StoredCredential\b/, + tool: 'Get-StoredCredential', + platform: 'windows', + }, + // PowerShell `Get-Credential -Credential` piped to + // `ConvertFrom-SecureString -AsPlainText` is the readback shape. + // The bare `Get-Credential` (no pipe) is a fresh-prompt-the-user + // flow and not the issue here — match only the readback pipe. + { + re: /\bGet-Credential\b[^|]*\|\s*ConvertFrom-SecureString\b/, + tool: 'Get-Credential | ConvertFrom-SecureString', + platform: 'windows', + }, + // Python `keyring` CLI — `keyring get `. + { + re: /\bkeyring\s+get\b/, + tool: 'keyring get', + platform: 'cross-platform', + }, +] + +/** + * Scan a Bash command string for keychain READ patterns. Returns one hit per + * matching subcommand so the error message can name them all (a `&&`-chained + * command might have multiple). + */ +export function findKeychainReads(command: string): Hit[] { + const hits: Hit[] = [] + for (let i = 0, { length } = READ_PATTERNS; i < length; i += 1) { + const entry = READ_PATTERNS[i]! + const m = entry.re.exec(command) + if (!m) { + continue + } + // Pull a short snippet around the match (up to 80 chars) so the + // operator can see the context. Centered on the match start. + const start = Math.max(0, m.index - 10) + const end = Math.min(command.length, m.index + m[0].length + 50) + const snippet = command.slice(start, end) + hits.push({ + tool: entry.tool, + platform: entry.platform, + snippet: snippet.length < command.length ? `…${snippet}…` : snippet, + }) + } + return hits +} + +/** + * Pure detection. Returns the exact block message when the payload is a Bash + * call whose command reads the keychain; `undefined` otherwise (non-Bash tool, + * absent command, or no keychain read). The internal `typeof === 'string'` + * narrow in `readCommand` keeps the `unknown` payload fields safe. + */ +export function keychainReadMessage( + payload: ToolCallPayload, +): string | undefined { + if (payload?.tool_name !== 'Bash') { + return undefined + } + const command = readCommand(payload) + if (!command) { + return undefined + } + const hits = findKeychainReads(command) + if (hits.length === 0) { + return undefined + } + const lines: string[] = [] + lines.push( + '[no-blind-keychain-read-guard] Blocked: direct keychain READ from Bash.', + ) + lines.push('') + for (let i = 0, { length } = hits; i < length; i += 1) { + const h = hits[i]! + lines.push(` ${h.platform.padEnd(15)} ${h.tool}`) + lines.push(` Saw: ${h.snippet}`) + } + lines.push('') + lines.push(' Reading the keychain via the platform CLI surfaces a UI auth') + lines.push(" prompt on the user's screen — and the prompt fires once per") + lines.push(' call. A hook chain that reads three times costs three prompts.') + lines.push('') + lines.push(' The token is almost certainly already available without a') + lines.push(' keychain read:') + lines.push('') + lines.push(' - In-process: call findApiToken() from setup-security-tools/') + lines.push(' lib/api-token.mts. It returns the module-cached value from') + lines.push(' the first call onward, then env, then keychain.') + lines.push('') + lines.push(' - From Bash: read process.env.SOCKET_API_KEY or') + lines.push( + ' process.env.SOCKET_API_TOKEN. The wheelhouse shell-rc bridge', + ) + lines.push(' exports both for every new shell session.') + lines.push('') + lines.push(' Writes / deletes (security add-generic-password / secret-tool') + lines.push(' store / New-StoredCredential / etc.) are allowed — they only') + lines.push(' happen during operator-driven setup / rotation.') + lines.push('') + lines.push(' Bypass (e.g. operator-invoked diagnostics that need a fresh') + lines.push(' keychain read):') + lines.push(` Type "${BYPASS_PHRASE}" in your next message.`) + return lines.join('\n') + '\n' +} + +// The block logic. Blocks when a keychain read is found without a +// bypass phrase; allows (returns undefined) otherwise. +export const check = (payload: ToolCallPayload) => { + const message = keychainReadMessage(payload) + if (!message) { + return undefined + } + if ( + payload.transcript_path && + bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE) + ) { + return undefined + } + return block(message) +} + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Bash'], + triggers, + type: 'guard', +}) +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-blind-keychain-read-guard/package.json b/.claude/hooks/fleet/no-blind-keychain-read-guard/package.json new file mode 100644 index 0000000..819429b --- /dev/null +++ b/.claude/hooks/fleet/no-blind-keychain-read-guard/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-no-blind-keychain-read-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/no-blind-keychain-read-guard/tsconfig.json b/.claude/hooks/fleet/no-blind-keychain-read-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/no-blind-keychain-read-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/no-boolean-trap-guard/README.md b/.claude/hooks/fleet/no-boolean-trap-guard/README.md new file mode 100644 index 0000000..e48c438 --- /dev/null +++ b/.claude/hooks/fleet/no-boolean-trap-guard/README.md @@ -0,0 +1,47 @@ +# no-boolean-trap-guard + +PreToolUse Write/Edit guard that blocks introducing a boolean positional +parameter in a TypeScript function signature — the +[boolean-trap](https://ariya.io/2011/08/hall-of-api-shame-boolean-trap) +antipattern. + +## Why + +`function foo(x: T, dry: boolean)` forces callers to write +`foo(x, true)` where the `true` is silent and meaningless. Six months +later nobody knows what it means. An options object names the flag at +the call site: `foo(x, { dry: true })`. + +## The fleet options-object pattern + +```ts +// Declaration +export interface FooOptions { + dry?: boolean | undefined + verbose?: boolean | undefined +} +export function foo(x: T, options?: FooOptions | undefined): void { + // Null-prototype spread — immune to poisoned Object.prototype. + const opts = { __proto__: null, ...options } as FooOptions + const dry = opts.dry === true + … +} +``` + +Key invariants: field types `?: T | undefined` (both `?` AND `| undefined`); +options param `?: TypedOptions | undefined`; body resolves via the +`{ __proto__: null, ...options }` spread. Full recipe in +[`docs/agents.md/fleet/options-object.md`](../../../docs/agents.md/fleet/options-object.md). + +## Allowed + +- A function with a **single** boolean param and no other params — + predicate pattern (`isEnabled(value: boolean)`). +- `boolean` fields inside an interface body (not params). +- Generated / dist / build files. +- Bypass: `Allow boolean-trap bypass`. + +## Cross-fleet sync + +Lives in `socket-wheelhouse/template/.claude/hooks/fleet/` and is +byte-identical across every fleet repo. diff --git a/.claude/hooks/fleet/no-boolean-trap-guard/index.mts b/.claude/hooks/fleet/no-boolean-trap-guard/index.mts new file mode 100644 index 0000000..7b15309 --- /dev/null +++ b/.claude/hooks/fleet/no-boolean-trap-guard/index.mts @@ -0,0 +1,226 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-boolean-trap-guard. +// +// Blocks Write/Edit ops that introduce a boolean positional parameter +// in a TypeScript function signature — the "boolean trap" antipattern +// (https://ariya.io/2011/08/hall-of-api-shame-boolean-trap). +// +// A boolean positional forces callers to write `foo(x, true)` where the +// `true` is meaningless at the call site. The fix: take an options +// object instead. Fleet pattern: +// +// options?: TypedOptions | undefined // param declaration +// TypedOptions = { foo?: bar | undefined } // interface definition +// const opts = { __proto__: null, ...options } as TypedOptions // body +// +// Banned shapes: +// function f(x: string, flag: boolean) { … } +// function f(a: T, b: boolean, c: boolean) { … } +// async function f(x: T, dry?: boolean) { … } +// export function f(x: T, verbose: boolean | undefined) { … } +// +// Allowed (passes through): +// - A single boolean param with NO other params — pure predicate +// (`function isValid(value: boolean): boolean`). +// - Overload signatures (no body — these are type-only contracts and +// are resolved by the implementation). +// - Generated / vendor files (dist/, build/, node_modules/). +// - This guard's own source + tests. +// - Bypass: `Allow boolean-trap bypass` in a recent turn. +// +// Exit codes: 0 pass, 2 block. Fails open on malformed payloads. + +import { + block, + defineHook, + editGuard, + notify, + runHook, +} from '../_shared/guard.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' +import { isRepoTestHome } from '../_shared/repo-test-home.mts' +import { normalizePath } from '@socketsecurity/lib-stable/paths/normalize' + +const BYPASS_PHRASE = 'Allow boolean-trap bypass' + +interface Finding { + readonly line: number + readonly text: string + readonly param: string +} + +// Match a function signature line that has AT LEAST TWO params and at +// least one of them is typed boolean/boolean|undefined/boolean?. +// Pattern: `function name(` or `)(` continuation — we scan per line for +// the inline single-line case; multi-line signatures are flagged when +// a line contains a boolean param AND the enclosing paren context has +// other params on the same line (simple heuristic). +// +// We detect: a parameter name followed by `?:` or `:` and then +// `boolean` (optionally `| undefined` or `| null`), when the line +// also contains a comma (other params present) or is a multi-param +// function header. +const BOOL_PARAM_RE = + /\b([A-Za-z_$][A-Za-z0-9_$]*)\??:\s*boolean(?:\s*\|\s*(?:undefined|null))?\b/g + +// Detect that a line is a function/method header with params. +const FUNC_HEADER_RE = + /\b(?:async\s+)?(?:function\s*\*?\s*[A-Za-z_$][A-Za-z0-9_$]*|(?:export\s+(?:default\s+)?|private\s+|protected\s+|public\s+|static\s+|abstract\s+|override\s+)*(?:async\s+)?function|(?:export\s+(?:default\s+)?)?(?:async\s+)?\b[A-Za-z_$][A-Za-z0-9_$]*)\s*[<(]/ + +/** + * The substring inside the first balanced `(...)` on a line — the parameter + * list, excluding the return-type annotation that follows `)`. Returns + * undefined when the line has no `(` or the parens don't close on this line + * (a multi-line signature). Balances `()[]{}` so a nested object-type param + * or default value doesn't end the list early. This is what stops a + * return-type field (`): { ok: boolean }`) from being read as a param. + */ +export function paramListSpan(line: string): string | undefined { + const open = line.indexOf('(') + if (open === -1) { + return undefined + } + let depth = 0 + for (let i = open, { length } = line; i < length; i += 1) { + const ch = line[i]! + if (ch === '(' || ch === '[' || ch === '{') { + depth += 1 + } else if (ch === ')' || ch === ']' || ch === '}') { + depth -= 1 + if (depth === 0) { + return line.slice(open + 1, i) + } + } + } + return undefined +} + +/** + * Blank out every character nested inside a `{...}`, `[...]`, or `(...)` group + * within a parameter-list span, preserving length and the top-level structure. + * A boolean that is a PROPERTY of an object-type literal + * (`props: { active?: boolean }`), an ELEMENT of a tuple type + * (`pair: [boolean, string]`), or a PARAMETER of a callback-type + * (`cb: (x: boolean) => void`) is not a top-level positional boolean trap of + * the function being declared — only a bare `name: boolean` at the param + * list's top level is. Blanking nested groups also drops their inner commas so + * the multi-param check counts only real param separators. + */ +export function stripNestedTypeGroups(paramList: string): string { + let depth = 0 + let out = '' + for (let i = 0, { length } = paramList; i < length; i += 1) { + const ch = paramList[i]! + if (ch === '(' || ch === '[' || ch === '{') { + out += depth === 0 ? ch : ' ' + depth += 1 + } else if (ch === ')' || ch === ']' || ch === '}') { + depth = Math.max(0, depth - 1) + out += depth === 0 ? ch : ' ' + } else { + out += depth === 0 ? ch : ' ' + } + } + return out +} + +export function findBooleanTrapParams(text: string): Finding[] { + const findings: Finding[] = [] + const lines = text.split('\n') + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]! + // Only flag lines that look like a function/method parameter list. + if (!FUNC_HEADER_RE.test(line) && !line.trim().startsWith('(')) { + continue + } + // Scan ONLY the parameter list, never the return-type annotation after + // `)` — a `{ ok: boolean }` return type is not a boolean-trap param. + // Then blank nested type groups so a boolean PROPERTY/element/callback-param + // inside a param's type (`opts: { active?: boolean }`) is never read as a + // top-level positional boolean. + const scanText = stripNestedTypeGroups(paramListSpan(line) ?? line) + // Count commas to know whether there are multiple params. A boolean + // as the ONLY param is a predicate pattern — leave it alone. + const commaCount = (scanText.match(/,/g) ?? []).length + if (commaCount === 0) { + continue + } + BOOL_PARAM_RE.lastIndex = 0 + let m: RegExpExecArray | null + while ((m = BOOL_PARAM_RE.exec(scanText)) !== null) { + const param = m[1]! + findings.push({ line: i + 1, text: line.trim(), param }) + } + } + return findings +} + +export function isExemptPath(filePath: string): boolean { + return ( + normalizePath(filePath).includes('/dist/') || + normalizePath(filePath).includes('/build/') || + normalizePath(filePath).includes('/node_modules/') || + normalizePath(filePath).includes( + '/.claude/hooks/fleet/no-boolean-trap-guard/', + ) || + isRepoTestHome(filePath) + ) +} + +export const check = editGuard( + (filePath, content, payload) => { + if (isExemptPath(filePath)) { + return undefined + } + // Match TypeScript file extensions: .ts, .mts, .cts, .tsx, .mtsx, .ctsx. + if (!/\.(?:c|m)?tsx?$/.test(filePath)) { + return undefined + } + const text = content ?? '' + if (!text) { + return undefined + } + const findings = findBooleanTrapParams(text) + if (findings.length === 0) { + return undefined + } + if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE)) { + return notify( + `no-boolean-trap-guard: ${findings.length} boolean-trap param(s) — bypassed via "${BYPASS_PHRASE}"\n`, + ) + } + const lines = findings + .map(f => ` ${filePath}:${f.line} param \`${f.param}\`\n ${f.text}`) + .join('\n') + return block( + `no-boolean-trap-guard: refusing to introduce a boolean positional parameter.\n` + + `\n` + + `${lines}\n` + + `\n` + + `A boolean positional forces callers to write foo(x, true) where\n` + + `the \`true\` is meaningless at the call site. Use an options object:\n` + + `\n` + + ` // instead of: function foo(x: T, dry: boolean)\n` + + ` export interface FooOptions { dry?: boolean | undefined }\n` + + ` export function foo(x: T, options?: FooOptions | undefined): void {\n` + + ` const opts = { __proto__: null, ...options } as FooOptions\n` + + ` const dry = opts.dry === true\n` + + ` …\n` + + ` }\n` + + `\n` + + `See docs/agents.md/fleet/options-object.md for the full recipe.\n` + + `Bypass: type "${BYPASS_PHRASE}" in a recent message.\n`, + ) + }, + { fleetOnly: true }, +) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Edit', 'Write', 'MultiEdit'], + scope: 'convention', + type: 'guard', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-boolean-trap-guard/package.json b/.claude/hooks/fleet/no-boolean-trap-guard/package.json new file mode 100644 index 0000000..fa3f97a --- /dev/null +++ b/.claude/hooks/fleet/no-boolean-trap-guard/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-no-boolean-trap-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/no-boolean-trap-guard/tsconfig.json b/.claude/hooks/fleet/no-boolean-trap-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/no-boolean-trap-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/no-branch-reuse-nudge/README.md b/.claude/hooks/fleet/no-branch-reuse-nudge/README.md new file mode 100644 index 0000000..9a93020 --- /dev/null +++ b/.claude/hooks/fleet/no-branch-reuse-nudge/README.md @@ -0,0 +1,39 @@ +# no-branch-reuse-nudge + +PreToolUse Bash hook (reminder, NOT a block) that fires on `git commit` +when the current branch already has upstream history — meaning the agent +is committing onto a shared/existing branch rather than cutting a fresh +one for the current logical change. + +## Why + +Reusing a branch mixes unrelated commits into one PR, complicates code +review, and causes rebase pain when the branch is already on the remote. +The shape this rule prevents: a session cuts a `feat/` branch +because it assumes a PR workflow, then has to work around the +feature-branch instead of pushing straight to main. The correct move +was `git push origin feat/:main` — which would have been obvious +if the branch hadn't been created at all. + +## When it fires + +On `git commit` (not `--amend`) when: + +- The current branch is NOT the default (`main`/`master`), AND +- The branch already has an upstream tracking ref with commits. + +A branch with no upstream (freshly cut this session) is never flagged. + +## Suggested actions + +- If the change belongs on main: `git push origin :` +- If a fresh branch is needed: `git checkout -b ` + +## Bypass + +Type `Allow branch-reuse bypass` in a recent message to proceed. + +## Cross-fleet sync + +Lives in `socket-wheelhouse/template/.claude/hooks/fleet/` and is +byte-identical across every fleet repo. diff --git a/.claude/hooks/fleet/no-branch-reuse-nudge/index.mts b/.claude/hooks/fleet/no-branch-reuse-nudge/index.mts new file mode 100644 index 0000000..947ff4e --- /dev/null +++ b/.claude/hooks/fleet/no-branch-reuse-nudge/index.mts @@ -0,0 +1,128 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-branch-reuse-nudge. +// +// renamed-from: no-branch-reuse-guard +// +// Reminder (NOT a block) on `git commit` when the current branch is NOT +// the default branch (main/master) AND the branch already has an upstream +// tracking ref on the remote — meaning the agent is committing onto an +// existing shared branch rather than cutting a fresh one per logical +// change. +// +// Per CLAUDE.md "Smallest chunks / branch discipline": cut a FRESH branch +// per logical change, never reuse or commit onto an existing branch that +// belongs to a different logical unit of work. +// +// Why this matters: reusing a branch merges unrelated commits into a +// single PR / push, complicates code review, and causes rebase pain when +// the branch is already on the remote. The incident that prompted this +// rule: 2026-06-02 a session cut `feat/spawn-kill-tree` on socket-lib +// (assuming PR workflow), then had to create a PR to land the work — the +// correct move was `git push origin feat/spawn-kill-tree:main` directly, +// which would have been obvious if the branch hadn't been created at all. +// +// Allowed (passes through): +// - Committing on main/master (direct-push-to-main is the fleet default). +// - A branch with NO remote upstream (freshly cut this session). +// - Bypass: `Allow branch-reuse bypass` in a recent turn. +// +// Fires as a PreToolUse Bash hook; exits 0 always (reminder-only). + +import process from 'node:process' + +import { spawnSync } from '@socketsecurity/lib-stable/process/spawn/child' + +import { currentBranch, resolveDefaultBranch } from '../_shared/git-branch.mts' +import { bashGuard, defineHook, notify, runHook } from '../_shared/guard.mts' +import { spawnTimeoutMs } from '../_shared/spawn-timeout.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' +import { gitCommitSegments } from '../_shared/commit-command.mts' + +const BYPASS_PHRASE = 'Allow branch-reuse bypass' + +// Amend excluded on purpose: amending the tip is not branch reuse. The +// segment parse is the shared one — a positional arg that merely CONTAINS +// the word commit (a path, `git log commit`) never matches. +export function isGitCommit(command: string): boolean { + return gitCommitSegments(command).some(c => !c.args.includes('--amend')) +} + +// True when the branch has a remote upstream tracking ref AND that +// upstream already has commits (i.e. the branch was pushed to the remote +// before this session started). A branch with no upstream is freshly cut +// this session — leave it alone. +export function hasExistingRemoteHistory(cwd: string, branch: string): boolean { + // Does the branch have an upstream configured? + const upstreamRef = spawnSync( + 'git', + ['rev-parse', '--abbrev-ref', `${branch}@{upstream}`], + { cwd, timeout: spawnTimeoutMs(5000) }, + ) + if (upstreamRef.status !== 0) { + return false + } + // Does the upstream have at least one commit? + const upstream = String(upstreamRef.stdout).trim() + const revParse = spawnSync('git', ['rev-parse', '--verify', upstream], { + cwd, + timeout: spawnTimeoutMs(5000), + }) + return revParse.status === 0 +} + +export const check = bashGuard((command, payload) => { + if (!isGitCommit(command)) { + return undefined + } + const cwd = payload.cwd ?? process.cwd() + const branch = currentBranch(cwd) + if (!branch) { + return undefined + } + const defaultBranch = resolveDefaultBranch(cwd) + // Committing on the default branch is fine — direct-push-to-main. + if (branch === defaultBranch) { + return undefined + } + // A branch with no remote history was cut fresh this session — fine. + if (!hasExistingRemoteHistory(cwd, branch)) { + return undefined + } + if (bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE)) { + return notify( + `no-branch-reuse-nudge: committing onto existing remote branch "${branch}" — bypassed via "${BYPASS_PHRASE}"\n`, + ) + } + return notify( + [ + `no-branch-reuse-nudge: committing onto an existing remote branch`, + ``, + ` Branch: ${branch} (already has history on origin)`, + ``, + ` Per CLAUDE.md "branch discipline" — cut a FRESH branch per logical`, + ` change; never reuse an existing branch for unrelated work. Reusing`, + ` mixes commits into one PR, complicates review, and causes rebase pain.`, + ``, + ` If this is the right branch for this change, push straight to main:`, + ``, + ` git push origin ${branch}:${defaultBranch}`, + ``, + ` If you need a new branch: git checkout -b `, + ``, + ` Bypass: type "${BYPASS_PHRASE}" to proceed anyway.`, + ``, + ` Reminder-only; not a block.`, + ``, + ].join('\n'), + ) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Bash'], + scope: 'convention', + type: 'nudge', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-branch-reuse-nudge/package.json b/.claude/hooks/fleet/no-branch-reuse-nudge/package.json new file mode 100644 index 0000000..06cc581 --- /dev/null +++ b/.claude/hooks/fleet/no-branch-reuse-nudge/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-no-branch-reuse-nudge", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/no-branch-reuse-nudge/tsconfig.json b/.claude/hooks/fleet/no-branch-reuse-nudge/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/no-branch-reuse-nudge/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/no-cascade-transient-git-guard/index.mts b/.claude/hooks/fleet/no-cascade-transient-git-guard/index.mts new file mode 100644 index 0000000..89edda3 --- /dev/null +++ b/.claude/hooks/fleet/no-cascade-transient-git-guard/index.mts @@ -0,0 +1,101 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-cascade-transient-git-guard. +// +// Blocks a cascade-shaped `git commit` when the target repo is in a +// transient git state — detached HEAD or in-progress rebase / merge / +// cherry-pick. Committing in that state lands the cascade on a stale or +// throwaway ref instead of the branch tip, stranding the commit and +// corrupting another session's in-flight operation. +// +// Why this exists: 2026-06-02 a fleet cascade's manual commit loop ran +// `git commit -m "chore(wheelhouse): cascade template@"` across +// every fleet repo. socket-lib was mid-`git cherry-pick` on a detached +// HEAD (another session's work); the loop ignored that and committed the +// cascade onto the detached HEAD, breaking the cherry-pick sequencer. +// sync-scaffolding's own auto-commit already skips this state — but a +// hand-typed loop bypassed that check. This hook enforces it at the Bash +// layer so NO commit path (script, loop, or manual) can land a cascade on +// a transient ref. +// +// Skipped silently: +// - tool_name !== 'Bash'. +// - Command isn't a cascade-prefixed `git commit`. +// - Target repo is on a normal branch tip (the common case). +// +// No bypass: there is never a legitimate reason to land a cascade commit +// on a transient ref. Finish (or abort) the in-progress operation first. +// +// Exit codes: +// 0 — allow. +// 2 — block. Stderr carries the operator-facing message. +// +// Fails open on any internal error (exit 0 + stderr log). + +import { extractGitCwd } from '../_shared/git-cwd.mts' +import { isInTransientGitState } from '../_shared/git-state.mts' +import { bashGuard, block, defineHook, runHook } from '../_shared/guard.mts' +import { commandsFor } from '../_shared/shell-command.mts' + +const CASCADE_PREFIX = 'chore(wheelhouse): cascade template@' + +/** + * Extract the `-m` / `--message` value from a `git commit` invocation, if any. + * Returns the first message argument or undefined. + */ +export function commitMessage(command: string): string | undefined { + for (const c of commandsFor(command, 'git')) { + if (!c.args.includes('commit')) { + continue + } + for (let i = 0, { length } = c.args; i < length; i += 1) { + const a = c.args[i] + if ((a === '--message' || a === '-m') && c.args[i + 1] !== undefined) { + return c.args[i + 1] + } + if (a?.startsWith('--message=')) { + return a.slice('--message='.length) + } + } + } + return undefined +} + +export const check = bashGuard(command => { + const message = commitMessage(command) + if (message === undefined || !message.startsWith(CASCADE_PREFIX)) { + return undefined + } + // Scope the cwd lookup to the `git commit` invocation itself — a `-C` on + // an unrelated invocation (e.g. a `rev-parse` inside a `$(…)` substitution) + // must not redirect the transient-state probe to a different repo. + const repoDir = extractGitCwd(command, { subcommand: 'commit' }) + if (!isInTransientGitState(repoDir)) { + return undefined + } + return block( + [ + '[no-cascade-transient-git-guard] Blocked: cascade commit on a transient git ref.', + '', + ` Repo: ${repoDir}`, + ' State: detached HEAD or in-progress rebase / merge / cherry-pick.', + '', + ' Committing a cascade here lands it on a stale or throwaway ref,', + " strands the commit, and can corrupt another session's in-flight", + ' operation (this stranded a cascade on socket-lib mid cherry-pick', + ' on 2026-06-02).', + '', + ' Fix: finish or abort the in-progress operation, get the repo back', + ' on its branch tip, then re-run the cascade. No bypass.', + '', + ].join('\n'), + ) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Bash'], + type: 'guard', +}) + +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-clipboard-access-guard/README.md b/.claude/hooks/fleet/no-clipboard-access-guard/README.md new file mode 100644 index 0000000..92e8c9a --- /dev/null +++ b/.claude/hooks/fleet/no-clipboard-access-guard/README.md @@ -0,0 +1,40 @@ +# no-clipboard-access-guard + +`PreToolUse(Bash | Edit | Write)` blocker that refuses clipboard access from a +script, hook, or Bash command. The system clipboard is a cross-process exfil + +overwrite surface: a secret copied there leaks to every app, and an OSC-52 +escape written to the terminal can silently overwrite (or, on permissive +terminals, read) it. Fleet tooling never needs the clipboard, so any attempt is +a mistake or a poisoning fingerprint. + +## Detected + +| Surface | Pattern | +| ----------- | --------------------------------------------------------------- | +| Bash | `pbcopy` / `pbpaste` (macOS) | +| Bash | `xclip` / `xsel` / `wl-copy` / `wl-paste` (Linux) | +| Bash | `clip` / `clip.exe` (Windows) | +| Edit /Write | source emitting an OSC-52 escape (`ESC ] 52 ;`, any spelling) | + +Bash detection is AST-parsed via the fleet shell parser (`findInvocation`), not +a loose regex, so a path fragment or quoted literal doesn't false-fire. The +OSC-52 match covers the raw ESC byte and the `\x1b` / `\033` / `` / `\e` +escaped spellings. + +## Bypass + +Type the canonical phrase verbatim in your next user turn: + +``` +Allow clipboard-access bypass +``` + +Use only for a genuine, operator-driven clipboard need (rare). + +## Why + +The terminal "attempted to access the clipboard but it was denied" banner comes +from an OSC-52 escape reaching the emulator. The denial is the safe default; this +hook stops fleet code from emitting one (or shelling out to a clipboard CLI) in +the first place, so the attempt never happens rather than relying on the +terminal to refuse it. diff --git a/.claude/hooks/fleet/no-clipboard-access-guard/index.mts b/.claude/hooks/fleet/no-clipboard-access-guard/index.mts new file mode 100644 index 0000000..3254a5a --- /dev/null +++ b/.claude/hooks/fleet/no-clipboard-access-guard/index.mts @@ -0,0 +1,184 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-clipboard-access-guard. +// +// Blocks a script / hook / Bash command from READING the system clipboard, and +// blocks an OSC-52 escape emitted from source. Reading is a cross-process exfil +// surface (a secret on the clipboard, or another app's copied data, pulled into +// the agent's context), and a source-embedded OSC-52 escape is a silent +// overwrite / poisoning fingerprint. Explicit WRITES (an operator's `pbcopy` to +// hand a snippet to the clipboard) are allowed — putting data ONTO the clipboard +// is a deliberate, visible operator action, not an exfil. +// +// Two surfaces, gated on tool_name: +// +// 1. Bash — a clipboard READ CLI in the command line. AST-parsed via the +// fleet shell parser (commandsFor), not a loose regex, so a path fragment +// like `pbpasterc` or a quoted literal doesn't false-fire: +// macOS: pbpaste (read-only) +// Linux: wl-paste (read-only) +// xclip -o / -out / -output (xclip writes by default) +// xsel (default outputs) unless a write flag (-i/-a/-c/-k) +// Write-only tools (`pbcopy`, `wl-copy`, `clip`/`clip.exe`) and a writing +// `xclip` / `xsel -i` are NOT blocked — writing to the clipboard is fine. +// +// 2. Edit / Write — source that emits an OSC-52 clipboard escape +// (`ESC ] 52 ; ...`) in any of its literal spellings (\x1b / \033 / +// / the raw control byte). That's the sequence the earlier +// Terminal "attempted to access the clipboard" denial came from — a silent +// escape in committed source is blocked regardless of read/write intent. +// +// Bypass: `Allow clipboard-access bypass` in a recent user turn — for a +// genuine, operator-driven clipboard READ (rare). + +import { block, defineHook, runHook } from '../_shared/guard.mts' +import type { ToolCallPayload } from '../_shared/payload.mts' +import { commandsFor } from '../_shared/shell-command.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' + +const BYPASS_PHRASE = 'Allow clipboard-access bypass' + +// Pre-flight skip set: the dispatcher only imports this guard when the raw +// payload contains one of these. Every block path requires one — a clipboard +// READ binary name for the Bash arm (write-only `pbcopy`/`wl-copy`/`clip` are +// deliberately absent so a write never even imports the guard), or the `]52;` +// OSC-52 prefix (present under every escape spelling) for the Edit/Write arm. +export const triggers: readonly string[] = [ + ']52;', + 'pbpaste', + 'wl-paste', + 'xclip', + 'xsel', +] + +// xsel flags that mean the invocation WRITES (or clears/keeps) rather than +// prints the selection. xsel with none of these prints the current selection — +// i.e. it reads. Presence of any means it is not a read. +const XSEL_WRITE_FLAGS = new Set([ + '--append', + '--clear', + '--input', + '--keep', + '-a', + '-c', + '-i', + '-k', +]) + +// Clipboard READ CLIs, by platform, each with a predicate over the matched +// command segment's args deciding whether THIS invocation reads the clipboard. +const CLIPBOARD_READERS: ReadonlyArray<{ + readonly binary: string + readonly platform: string + readonly reads: (args: readonly string[]) => boolean +}> = [ + // pbpaste / wl-paste are read-only tools — every invocation reads. + { binary: 'pbpaste', platform: 'macOS', reads: () => true }, + { binary: 'wl-paste', platform: 'Linux', reads: () => true }, + // xclip writes from stdin by default (-i / -in); it READS only with an out + // flag (`-o` / `-out` / `-output`). + { + binary: 'xclip', + platform: 'Linux', + reads: args => + args.some(a => a === '-o' || a === '-out' || a === '-output'), + }, + // xsel prints the selection by default (a read); a write/clear/keep flag + // means it is not reading. + { + binary: 'xsel', + platform: 'Linux', + reads: args => !args.some(a => XSEL_WRITE_FLAGS.has(a)), + }, +] + +// OSC-52 clipboard escape in any literal spelling a source file might carry: +// the raw ESC byte, or an escaped \x1b / \033 / , immediately followed +// by `]52;`. Matching the prefix is enough — the payload after `52;` is the +// clipboard data and need not be parsed. +const OSC52_RE = /(?:\x1b|\\x1b|\\u001b|\\033|\\e)\]52;/i + +// The clipboard READ CLI invoked in a Bash command line, or undefined when none +// (a write-only tool, or a writing xclip/xsel, is not a read → undefined). +export function clipboardReadIn(command: string): string | undefined { + for (let i = 0, { length } = CLIPBOARD_READERS; i < length; i += 1) { + const reader = CLIPBOARD_READERS[i]! + const segments = commandsFor(command, reader.binary) + for (let j = 0, segLen = segments.length; j < segLen; j += 1) { + if (reader.reads(segments[j]!.args)) { + return reader.binary + } + } + } + return undefined +} + +// True when `text` emits an OSC-52 clipboard escape. +export function hasOsc52(text: string): boolean { + return OSC52_RE.test(text) +} + +// Decide what (if anything) to block for a payload. Returns the block reason, +// or undefined to pass. Pure — the test drives it directly. +export function clipboardViolation( + payload: ToolCallPayload, +): string | undefined { + const toolName = payload.tool_name + const input = payload.tool_input + if (!input) { + return undefined + } + if (toolName === 'Bash') { + const command = input.command + if (typeof command === 'string') { + const binary = clipboardReadIn(command) + if (binary) { + return `Bash command READS the clipboard via \`${binary}\`` + } + } + return undefined + } + if (toolName === 'Edit' || toolName === 'MultiEdit' || toolName === 'Write') { + const text = input.content ?? input.new_string + if (typeof text === 'string' && hasOsc52(text)) { + return 'content writes an OSC-52 clipboard escape sequence' + } + } + return undefined +} + +export const check = (payload: ToolCallPayload) => { + const reason = clipboardViolation(payload) + if (!reason) { + return undefined + } + if ( + payload.transcript_path && + bypassPhrasePresent(payload.transcript_path, BYPASS_PHRASE) + ) { + return undefined + } + return block( + [ + '[no-clipboard-access-guard] Blocked: clipboard read', + '', + ` ${reason}.`, + '', + ' READING the clipboard is a cross-process exfil surface — it pulls a', + ' secret or another app’s copied data into the agent’s context; a', + ' source-embedded OSC-52 escape can silently overwrite/read it. Writing', + ' TO the clipboard (e.g. `pbcopy`) is allowed — that is not blocked.', + '', + ` If you genuinely need a clipboard read, type the phrase in a new`, + ` message: ${BYPASS_PHRASE}`, + ].join('\n'), + ) +} + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Bash'], + triggers, + type: 'guard', +}) +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-corepack-guard/README.md b/.claude/hooks/fleet/no-corepack-guard/README.md new file mode 100644 index 0000000..43576fc --- /dev/null +++ b/.claude/hooks/fleet/no-corepack-guard/README.md @@ -0,0 +1,33 @@ +# no-corepack-guard + +**Type:** PreToolUse guard (Bash) — BLOCKS (exit 2). + +**Trigger:** a Bash command that activates corepack to provision a package +manager — `corepack enable`, `corepack prepare` (e.g. `corepack prepare +pnpm@9 --activate`), `corepack use`, or `corepack install`. Detected by +AST-parsing the command (`commandsFor`), not a raw regex. `corepack --version` +/ `corepack --help` / `corepack disable` provision nothing and are left alone. + +**Why:** corepack is verboten fleet-wide. The fleet pins pnpm in +`external-tools.json` and installs it from that exact version via download + +Subresource-Integrity — `scripts/fleet/setup/setup-tools.mjs` locally, the +SocketDev/socket-registry `setup` composite action in CI — so the bytes are +integrity-checked before they run. corepack instead fetches a package manager +from the npm registry at activation time, outside that gate, keyed off a +mutable `packageManager` field: a second, un-pinned provisioning path that +bypasses the fleet's supply-chain controls. CLAUDE.md already bans +`npx`/`dlx`/`tsx` for adjacent reasons; this guard closes the corepack hole. + +**Not the `packageManager` field:** that field stays in package.json as a +declared-version RECORD, kept in lockstep with `external-tools.json` (see +`scripts/repo/tools/pnpm.mts`). This guard blocks only the corepack COMMANDS +that would act on it, never the field itself. + +**Fix the message gives:** +- local bootstrap: `node scripts/fleet/setup/setup-tools.mjs` +- CI: the same step runs via the socket-registry `setup` action (no caller change) + +**Bypass:** `Allow corepack bypass` typed verbatim in a recent user turn. + +**Fails open** on parse / payload errors (exit 0) — a guard bug must not wedge +every Bash call. diff --git a/.claude/hooks/fleet/no-corepack-guard/index.mts b/.claude/hooks/fleet/no-corepack-guard/index.mts new file mode 100644 index 0000000..4ace86f --- /dev/null +++ b/.claude/hooks/fleet/no-corepack-guard/index.mts @@ -0,0 +1,121 @@ +#!/usr/bin/env node +// Claude Code PreToolUse hook — no-corepack-guard. +// +// BLOCKS any Bash command that activates corepack to provision a package +// manager: `corepack enable`, `corepack prepare`, `corepack use`, or +// `corepack install` (with or without a `pnpm@` / `--activate` argument). +// +// Why corepack is verboten fleet-wide: the fleet installs pnpm from a pinned +// version via download + Subresource-Integrity (the `setup-tools.mjs` +// bootstrap locally, the SocketDev/socket-registry `setup` composite action in +// CI) so the exact bytes are integrity-checked before they run. corepack +// instead fetches a package manager from the npm registry at activation time, +// outside that gate, and keys off a mutable `packageManager` field — a second, +// un-pinned provisioning path that bypasses the fleet's supply-chain controls. +// The `packageManager` field stays in package.json as a declared-version +// RECORD (kept in lockstep with external-tools.json); this guard only blocks +// the corepack COMMANDS that would activate it. +// +// Detection (AST-parsed via the shared shell-command helper, not a raw regex): +// the command runs the `corepack` binary with an activating subcommand. +// `corepack --version` / `corepack --help` are allowed (they activate nothing). +// +// Bypass: `Allow corepack bypass` typed verbatim in a recent user turn. +// +// Fails open on parse / payload errors — a guard bug must not wedge every Bash +// call. + +import { isFleetTarget } from '../_shared/fleet-context.mts' +import { bashGuard, block, defineHook, runHook } from '../_shared/guard.mts' +import { bypassPhrasePresent } from '../_shared/transcript.mts' +import { commandsFor } from '../_shared/shell-command.mts' + +const BYPASS_PHRASE = 'Allow corepack bypass' as const + +// corepack subcommands that fetch + activate a package manager. `enable` +// shims the PMs onto PATH; `prepare`/`use`/`install` download a specific +// version. Anything else (`--version`, `--help`, `disable`) provisions +// nothing and is left alone. +const ACTIVATING_SUBCOMMANDS = ['enable', 'install', 'prepare', 'use'] as const + +// Pre-flight skip hint for the dispatcher: detection only ever fires when the +// `corepack` binary is invoked, so a command that lacks the substring +// `corepack` can never block. The activating subcommands (enable/install/ +// prepare/use) are subordinate — they matter only once `corepack` is present — +// so the binary name is the single necessary-and-sufficient trigger. +export const triggers: readonly string[] = ['corepack'] + +export interface CorepackDetection { + readonly detected: boolean + // The activating subcommand seen (enable / prepare / use / install), for + // the message. Empty when nothing was detected. + readonly subcommand: string +} + +export function detectCorepack(command: string): CorepackDetection { + const corepackCmds = commandsFor(command, 'corepack') + for (const { args } of corepackCmds) { + // The first non-flag token is the subcommand (`corepack enable`, + // `corepack prepare pnpm@9`). A leading flag (`corepack --version`) + // means no activating subcommand. + for (let i = 0, { length } = args; i < length; i += 1) { + const arg = args[i]! + if (arg.startsWith('-')) { + continue + } + if ((ACTIVATING_SUBCOMMANDS as readonly string[]).includes(arg)) { + return { detected: true, subcommand: arg } + } + // First bare token is some other subcommand (e.g. `disable`) — stop. + break + } + } + return { detected: false, subcommand: '' } +} + +export function formatBlock(d: CorepackDetection): string { + return ( + [ + `[no-corepack-guard] Blocked: \`corepack ${d.subcommand}\` activates a package manager outside the fleet's supply-chain gate.`, + '', + ' The fleet pins pnpm in external-tools.json and installs it from that', + ' exact version via download + SRI-integrity — never corepack:', + '', + ' node scripts/fleet/setup/setup-tools.mjs (local bootstrap)', + ' # CI runs the same step via the socket-registry `setup` action', + '', + ' The package.json `packageManager` field is a declared-version record', + ' kept in lockstep with external-tools.json; leave it in place, just', + ' do not invoke corepack to act on it.', + '', + ` Bypass: type "${BYPASS_PHRASE}" to allow it for this invocation.`, + ].join('\n') + '\n' + ) +} + +export const check = bashGuard((command, payload) => { + const detection = detectCorepack(command) + if (!detection.detected) { + return undefined + } + // The corepack ban is a fleet tooling CONVENTION — the fleet pins its pnpm + // version (corepack would auto-update past the pin + soak). Outside a fleet + // repo, corepack is the standard Node way to manage the package-manager + // version and is the project's own choice. No supply-chain fetch-exec angle. + if (!isFleetTarget(payload)) { + return undefined + } + if (bypassPhrasePresent(payload.transcript_path, [BYPASS_PHRASE], 3)) { + return undefined + } + return block(formatBlock(detection)) +}) + +export const hook = defineHook({ + check, + event: 'PreToolUse', + matcher: ['Bash'], + triggers, + type: 'guard', +}) +void runHook(hook, import.meta.url) diff --git a/.claude/hooks/fleet/no-corepack-guard/package.json b/.claude/hooks/fleet/no-corepack-guard/package.json new file mode 100644 index 0000000..da94f2a --- /dev/null +++ b/.claude/hooks/fleet/no-corepack-guard/package.json @@ -0,0 +1,15 @@ +{ + "name": "hook-no-corepack-guard", + "private": true, + "type": "module", + "main": "./index.mts", + "exports": { + ".": "./index.mts" + }, + "scripts": { + "test": "node --test test/*.test.mts" + }, + "devDependencies": { + "@types/node": "catalog:" + } +} diff --git a/.claude/hooks/fleet/no-corepack-guard/tsconfig.json b/.claude/hooks/fleet/no-corepack-guard/tsconfig.json new file mode 100644 index 0000000..19458cf --- /dev/null +++ b/.claude/hooks/fleet/no-corepack-guard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "declarationMap": false, + "erasableSyntaxOnly": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noEmit": true, + "rewriteRelativeImportExtensions": true, + "skipLibCheck": true, + "sourceMap": false, + "strict": true, + "target": "esnext", + "types": ["node"], + "verbatimModuleSyntax": true + } +} diff --git a/.claude/hooks/fleet/no-direct-linter-guard/README.md b/.claude/hooks/fleet/no-direct-linter-guard/README.md new file mode 100644 index 0000000..b253b2a --- /dev/null +++ b/.claude/hooks/fleet/no-direct-linter-guard/README.md @@ -0,0 +1,52 @@ +# no-direct-linter-guard + +PreToolUse(Bash) hook that blocks invoking a linter or formatter binary +directly. The fleet runs lint/format only through the repo scripts (`pnpm run +lint` / `fix` / `check` / `format`) and the `scripts/fleet/*` wrappers — those +own the explicit `-c .config/fleet/` flag and the ignore set. + +## What it catches + +A Bash command whose resolved binary is one of `oxlint`, `oxfmt`, `eslint`, +`prettier`, `biome`, `dprint`, `rustfmt`, or `gofmt` (including the +`node_modules/.bin/` path form), or a `cargo fmt` / `cargo clippy` +subcommand. Detected by AST-parsing the command +(`shell-command.mts`/`findInvocation`), so it matches across pipes, `&&` chains, +and leading env vars and never false-matches a substring. `pnpm run …` and a +`node scripts/fleet/…` invocation pass; non-format `cargo` subcommands +(`cargo build`, `cargo test`) pass. + +This is a CONVENTION guard: it consults `isFleetTarget` and fires ONLY inside a +fleet repo. In a non-fleet repo (a sibling clone, an external checkout, a Rust +project formatted with native `cargo fmt`) the native binary or the project's +own script is the sanctioned path, so the guard no-ops. A fleet-rooted session +acting on a non-fleet repo via a leading `cd && ` is +judged against that repo. + +## Why + +A bare formatter run is a double hazard. Configless `oxfmt`/`oxlint` falls back +to its own defaults (double-quote + semicolon) and corrupts fleet files; the +scripts always pass `-c .config/fleet/…`. A bare formatter also has no ignore +scoping and will reformat vendored `upstream/` trees the fleet must never touch +(the fleet `oxlintrc`/`oxfmtrc` ignore lists exclude `upstream/`, +`third_party/`, `vendor/`, `external/`). `eslint` / `prettier` / `biome` / +`dprint` are not fleet tools at all (see `no-other-linters-guard`); `cargo fmt` +/ `rustfmt` / `gofmt` reflow hand-formatted code. Reaching past the scripts +re-introduces every one of these. The committed-state companion is +`scripts/fleet/check/linters-are-oxlint-oxfmt-only.mts`; the source-ref companion is +`socket/no-other-linters-guard`. + +The scripts' own internal `node_modules/.bin/oxlint` spawns are child processes, +not Claude Bash invocations, so this hook never sees them — only a top-level +direct call is blocked. + +## Bypass + +Type `Allow direct-linter bypass` in a recent turn (for a genuine one-off). + +## Exit codes + +- `0` — pass (not Bash, a script wrapper, a non-format command, or bypassed) +- `2` — block +- Fails open on any internal error. diff --git a/.claude/hooks/fleet/no-direct-linter-guard/index.mts b/.claude/hooks/fleet/no-direct-linter-guard/index.mts new file mode 100644 index 0000000..d339877 --- /dev/null +++ b/.claude/hooks/fleet/no-direct-linter-guard/index.mts @@ -0,0 +1,193 @@ +#!/usr/bin/env node +// Claude Code PreToolUse(Bash) hook — no-direct-linter-guard. +// +// Blocks invoking a linter, formatter, or TypeScript compiler binary directly. +// The fleet runs lint/format/type-check ONLY through the repo scripts +// (`pnpm run lint` / `fix` / `check` / `format`) and the `scripts/fleet/*` +// wrappers — those own the explicit `-c .config/fleet/` / +// `-p .config/fleet/tsconfig.check.json` flag and the ignore set. A bare binary +// call is a double hazard (for `tsc`/`tsgo`: the default tsconfig misses +// `allowImportingTsExtensions` → bogus TS5097 on every `.mts` import): +// +// 1. Configless `oxfmt`/`oxlint` falls back to its own defaults (double-quote +// + semicolon) and corrupts fleet files. The scripts always pass `-c`. +// 2. A bare formatter has no ignore scoping and will reformat vendored +// `upstream/` trees the fleet must never touch. +// +// Foreign tools (`eslint`/`prettier`/`biome`/`dprint`) are not fleet tools at +// all (see no-other-linters-guard); `cargo fmt` / `rustfmt` / `gofmt` reflow +// hand-formatted code. Runner-wrapped forms (`yarn prettier`, `npx prettier`, +// `pnpm exec prettier`, `bunx prettier`) are caught too; only +// ` run