Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
408 changes: 408 additions & 0 deletions .agents/skills/fallow/SKILL.md

Large diffs are not rendered by default.

1,904 changes: 1,904 additions & 0 deletions .agents/skills/fallow/references/cli-reference.md

Large diffs are not rendered by default.

644 changes: 644 additions & 0 deletions .agents/skills/fallow/references/gotchas.md

Large diffs are not rendered by default.

791 changes: 791 additions & 0 deletions .agents/skills/fallow/references/patterns.md

Large diffs are not rendered by default.

81 changes: 81 additions & 0 deletions .fallowrc.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",

// Ridge generated artifacts and build output should not skew duplication/health.
"ignorePatterns": [
"**/*.generated.ts",
"dist/**",
"storybook-static/**",
"coverage/**"
],

// PostCSS/Tailwind pipeline; referenced from config, not imported in TS.
"ignoreDependencies": ["autoprefixer"],

// Scene barrels and tuning exports are often same-file or test-facing API.
"ignoreExportsUsedInFile": {
"type": true,
"interface": true
},

// Phaser scenes and lazy React overlays load through dynamic import() strings.
"dynamicallyLoaded": [
"src/game/scenes/**/runtime/*Scene.ts",
"src/game/overlays/**/*.tsx",
"src/dev/**/*.tsx"
],

"entry": [
"scripts/generate-ridge-blockout-sources.test.mjs"
],

"health": {
"ignore": ["**/*.generated.ts", "dist/**", "storybook-static/**"]
},

"duplicates": {
"ignore": ["**/*.generated.ts"]
},

// PR gate: only new findings in changed files fail CI (see docs/agents/fallow.md).
"audit": {
"gate": "new-only",
"deadCodeBaseline": "fallow-baselines/dead-code.json",
"healthBaseline": "fallow-baselines/health.json",
"dupesBaseline": "fallow-baselines/dupes.json"
},

// Incremental adoption: enforce architecture, warn on cleanup debt.
"rules": {
"unused-files": "warn",
"unused-exports": "warn",
"unused-types": "off",
"unused-class-members": "warn",
"unused-dev-dependencies": "warn",
"boundary-violation": "error",
"circular-dependencies": "warn",
"re-export-cycle": "warn",
Comment thread
DaniloNovakovic marked this conversation as resolved.
"stale-suppressions": "warn"
},

// Match `.agents/rules/10-architecture.md`: core stays pure; shared stays portable.
"boundaries": {
"zones": [
{ "name": "core", "patterns": ["src/game/core/**"] },
{ "name": "shared", "patterns": ["src/shared/**"] },
{ "name": "adapters", "patterns": ["src/game/adapters/**"] },
{ "name": "bridge", "patterns": ["src/game/bridge/**"] },
{ "name": "scene-lifecycle", "patterns": ["src/game/sceneLifecycle/**"] },
{ "name": "shared-runtime", "patterns": ["src/game/sharedSceneRuntime/**"] },
{ "name": "scenes", "patterns": ["src/game/scenes/**"] },
{ "name": "overlays", "patterns": ["src/game/overlays/**"] },
{ "name": "scene-ui", "patterns": ["src/game/sceneUi/**"] },
{ "name": "shell", "patterns": ["src/game/shell/**"] },
{ "name": "dev", "patterns": ["src/dev/**"] }
],
"rules": [
{ "from": "core", "allow": ["core", "shared"], "allowTypeOnly": ["shared"] },
Comment thread
DaniloNovakovic marked this conversation as resolved.
{ "from": "shared", "allow": ["shared"] }
]
}
}
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with:
version: 11.1.2
Expand All @@ -21,5 +23,6 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm docs:check
- run: pnpm fallow:ci
- run: pnpm test
- run: pnpm build
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ storybook-static
.turbo
.cache
.eslintcache
.fallow/
*.local
*.local.json

Expand All @@ -40,6 +41,13 @@ storybook-static

# Local AI/tooling state
.codex/
.cursor/
.claude/
.playwright-cli/
.antigravitycli/

# Cursor: ignore machine-local state; keep shared project config committable
.cursor/*
!.cursor/rules/
!.cursor/rules/**
!.cursor/mcp.json
!.cursor/hooks.json
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ When Danilo invokes the Visual Direction Artist or Character Designer specifical

When generating or converting sprite assets for Phaser scenes, use [`.agents/skills/sketchbook-sprite-pipeline/SKILL.md`](.agents/skills/sketchbook-sprite-pipeline/SKILL.md) to preserve the Digital Sketchbook style while producing normalized runtime frames, manifests, and QA sheets.

When running codebase health, dead-code, duplication, complexity, or architecture-boundary analysis, use [`.agents/skills/fallow/SKILL.md`](.agents/skills/fallow/SKILL.md). Workflow and CI policy: [`docs/agents/fallow.md`](docs/agents/fallow.md). Config: [`.fallowrc.jsonc`](.fallowrc.jsonc); baselines: [`fallow-baselines/`](fallow-baselines/).

## Tech stack

- **Frontend Framework:** React (Vite)
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ pnpm dev
## Quality checks

```bash
pnpm check
pnpm check # includes fallow:audit on changed files since main
pnpm lint
pnpm test
pnpm build
pnpm fallow:health # complexity + duplication overview
pnpm fallow:baseline # regenerate committed CI baselines after cleanup
```

Fallow CI policy and agent workflow: [`docs/agents/fallow.md`](docs/agents/fallow.md).

Current Bridge staging lives in
`src/game/scenes/ridge/bridge/stageComposition.ts`. In development, open the
Ridge Stage Debugger with `?mode=ridge-stage-debugger` after starting
Expand Down
69 changes: 69 additions & 0 deletions docs/agents/fallow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Fallow workflow

Fallow is the repo's codebase-intelligence gate: dead code, duplication, complexity, and architecture boundaries. Config lives in [`.fallowrc.jsonc`](../../.fallowrc.jsonc). Agent commands and JSON contracts live in [`.agents/skills/fallow/SKILL.md`](../../.agents/skills/fallow/SKILL.md).

## Daily commands

| Command | When |
| --- | --- |
| `pnpm fallow:audit` | Before pushing a branch; scopes to files changed since `main` |
| `pnpm fallow:health` | Periodic review of complexity hotspots and duplication rate (advisory; always exits 0) |
| `pnpm fallow` | Full-repo snapshot when planning cleanup or refactors |
| `pnpm check` | Local parity with CI (includes `fallow:audit`) |

CI runs `pnpm fallow:ci`, which is the same audit gate with baselines and `origin/main` as the diff base.

## What CI enforces

- **Gate:** `new-only` — PRs fail only on findings **introduced** in changed files.
- **Baselines:** committed under [`fallow-baselines/`](../../fallow-baselines/) so existing debt does not block merges.
- **Severity:** `boundary-violation` is **error**; unused code and duplication are **warn** until debt is reduced.

## Architecture boundaries

Fallow enforces the hardest rules from [`.agents/rules/10-architecture.md`](../../.agents/rules/10-architecture.md):

- `src/game/core/**` may import only from `core` and `shared`
- `src/shared/**` may import only from `shared`

Add more zones/rules one at a time after fixing any violations they surface.

## Exit codes

| Command | Exit 1 means |
| --- | --- |
| `fallow:health` | Never (uses `--report-only`; findings are informational) |
Comment thread
DaniloNovakovic marked this conversation as resolved.
| `fallow:audit` / `fallow:ci` | New error-severity findings in changed files |
| `fallow` (bare) | Issues found anywhere in the repo |

Plain `fallow health` without `--report-only` exits 1 when any function exceeds complexity thresholds — that is normal Fallow behavior, not a runtime crash.
Comment thread
DaniloNovakovic marked this conversation as resolved.

## Baseline maintenance

Regenerate baselines after intentional cleanup (not after every PR):

```bash
pnpm fallow:baseline
git add fallow-baselines/
```

Commit the updated JSON when issue counts drop on purpose.

## Known warn-tier debt (actionable, not suppressed)

Fallow currently warns on:

- **Scene `index.ts` barrels** that nothing imports yet. Either route cross-folder imports through those barrels (preferred by folder-ownership rules) or delete the dead barrels.
- **Public tuning/constants exports** used mainly in tests or reserved for future gameplay tuning.
- **Test duplication** in large integration tests such as `InteractiveApp.test.tsx`.

Do not add blanket ignores for these unless the finding is a proven false positive. Prefer `// fallow-ignore-next-line <issue-type>` on stable, intentional exceptions.

## Agent usage

When analyzing or cleaning code:

1. Read [`.agents/skills/fallow/SKILL.md`](../../.agents/skills/fallow/SKILL.md).
2. Use `--format json --quiet 2>/dev/null` and append `|| true` for shell calls.
3. Run `fallow fix --dry-run` before any auto-fix.
4. Never enable Fallow telemetry on the user's behalf.
19 changes: 19 additions & 0 deletions fallow-baselines/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Fallow baselines

Committed snapshots for incremental adoption. CI compares PR changes against these files so existing cleanup debt does not block merges.

| File | Produced by |
| --- | --- |
| `dead-code.json` | `fallow dead-code --save-baseline` |
| `health.json` | `fallow health --save-baseline` |
| `dupes.json` | `fallow dupes --save-baseline` |

Paths are referenced from [`.fallowrc.jsonc`](../.fallowrc.jsonc) under `audit.*Baseline`.

Regenerate all three after intentional cleanup:

```bash
pnpm fallow:baseline
```

See [`docs/agents/fallow.md`](../docs/agents/fallow.md) for the full workflow.
141 changes: 141 additions & 0 deletions fallow-baselines/dead-code.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
{
"unused_files": [
"src/game/scenes/basement/index.ts",
"src/game/scenes/basement/runtime/index.ts",
"src/game/scenes/hobbies/index.ts",
"src/game/scenes/hobbies/runtime/index.ts",
"src/game/scenes/overworld/index.ts",
"src/game/scenes/potassiumSlip/index.ts",
"src/game/scenes/potassiumSlip/runtime/index.ts",
"src/game/scenes/ridge/index.ts",
"src/game/scenes/ridge/runtime/ridgeBlockoutPresentation.ts",
"src/game/scenes/stampedeSketch/index.ts",
"src/game/scenes/stampedeSketch/runtime/index.ts"
],
"unused_exports": [
"src/game/bridge/store.ts:INVENTORY_ITEM_IDS",
"src/game/bridge/store.ts:SECRET_DISCOVERY_IDS",
"src/game/overlays/overlayIds.ts:PORTFOLIO_OVERLAY_IDS",
"src/game/overlays/overlayIds.ts:BASEMENT_OVERLAY_IDS",
"src/game/overlays/overlayIds.ts:GLOBAL_OVERLAY_IDS",
"src/game/overlays/overlayIds.ts:INVENTORY_OVERLAY_ID",
"src/game/overlays/overlayIds.ts:TRAIL_CARD_OVERLAY_ID",
"src/game/overlays/overlayIds.ts:MANUAL_PAGE_OVERLAY_ID",
"src/game/overlays/overlayIds.ts:isOverlayId",
"src/game/sceneUi/registry.ts:SCENE_UI_DEFINITIONS",
"src/game/sceneUi/types.ts:SCENE_UI_SURFACE_IDS",
"src/game/scenes/hobbies/roomLayout.ts:HOBBIES_PROXIMITY_ANCHOR_Y",
"src/game/scenes/hobbies/roomLayout.ts:HOBBIES_INTERACT_PROMPT_Y",
"src/game/scenes/potassiumSlip/runtime/boss.ts:POTASSIUM_BOSS_PHASE_3_DRIFT",
"src/game/scenes/potassiumSlip/runtime/combat.ts:POTASSIUM_RECALL_DAMAGE",
"src/game/scenes/potassiumSlip/runtime/combat.ts:POTASSIUM_FIRE_TICK_COOLDOWN_MS",
"src/game/scenes/potassiumSlip/runtime/combat.ts:POTASSIUM_DUPLICATE_CLONE_LIFETIME_MS",
"src/game/scenes/potassiumSlip/runtime/enemyFactory.ts:POTASSIUM_SPLITTER_CHILD_SPEED",
"src/game/scenes/potassiumSlip/runtime/waves.ts:POTASSIUM_ROW_SPAWN_DELAY_MS",
"src/game/scenes/potassiumSlip/runtime/waves.ts:POTASSIUM_MID_GAME_ROW_SPAWN_DELAY_MS",
"src/game/scenes/potassiumSlip/runtime/waves.ts:POTASSIUM_GENERIC_UPGRADES",
"src/game/scenes/potassiumSlip/runtime/waves.ts:getPotassiumDraftPool",
"src/game/scenes/ridge/blockout/geometry.ts:deriveRidgeBlockoutBounds",
"src/game/scenes/ridge/blockout/geometry.ts:findRidgeBlockoutAnchorPoint",
"src/game/scenes/ridge/blockout/geometry.ts:getRidgeBlockoutSpawnPoint",
"src/game/scenes/ridge/blockout/parser.ts:RIDGE_BLOCKOUT_RUNTIME_SYMBOLS",
"src/game/scenes/ridge/blockout/parser.ts:RIDGE_BLOCKOUT_DESIGN_SYMBOLS",
"src/game/scenes/ridge/blockout/parser.ts:validateRidgeBlockout",
"src/game/scenes/ridge/blockout/parser.ts:isRuntimeActiveRidgeBlockoutSymbol",
"src/game/scenes/ridge/blockout/ridgeBlockout.ts:RIDGE_BLOCKOUT_SOURCE",
"src/game/scenes/ridge/blockout/ridgeBlockout.ts:RIDGE_BLOCKOUT_TILE_REGISTRY",
"src/game/scenes/ridge/blockout/ridgeBlockout.ts:RIDGE_BLOCKOUT_RUNTIME_TILE_ROOMS",
"src/game/scenes/ridge/blockout/sourceCompiler.ts:RidgeBlockoutSourceValidationError",
"src/game/scenes/ridge/blockout/sourceCompiler.ts:toRidgeBlockoutMap",
"src/game/scenes/ridge/blockout/sourceCompiler.ts:isRidgeBlockoutSourceTraversalMovement",
"src/game/scenes/ridge/blockout/sources/contract-fixture.source.ts:default",
"src/game/scenes/ridge/blockout/sources/folded-desk-ridge.source.ts:FOLDED_DESK_RIDGE_TILE_REGISTRY",
"src/game/scenes/ridge/blockout/sources/folded-desk-ridge.source.ts:default",
"src/game/scenes/ridge/bridge/bridgeTracerSlice.ts:BRIDGE_TRACER_CAMERA_BOUNDS",
"src/game/scenes/ridge/cicka/assets.ts:CICKA_ASSET_PATH",
"src/game/scenes/ridge/cicka/assets.ts:CICKA_FRAME_WIDTH",
"src/game/scenes/ridge/cicka/assets.ts:CICKA_FRAME_HEIGHT",
"src/game/scenes/ridge/cicka/walkBy.ts:CICKA_WALK_BY_RADIUS",
"src/game/scenes/ridge/runtime/ridgeDebugOverlay.ts:createRidgeDebugOverlay",
"src/game/scenes/ridge/runtime/ridgeDevControls.ts:RIDGE_DEV_MIN_CAMERA_ZOOM",
"src/game/scenes/ridge/runtime/ridgeDevControls.ts:RIDGE_DEV_MAX_CAMERA_ZOOM",
"src/game/scenes/ridge/runtime/ridgeDevControls.ts:RIDGE_DEV_DEFAULT_DEBUG_SETTINGS",
"src/game/scenes/ridge/runtime/ridgeLandmarkPresentation.ts:createRidgeLandmarkPresentation",
"src/game/scenes/ridge/runtime/traversalComfort.ts:isPointInsideTraversalZone",
"src/game/scenes/stampedeSketch/runtime/autoAttack.ts:STAMPEDE_AUTO_ATTACK_RANGE",
"src/game/scenes/stampedeSketch/runtime/autoAttack.ts:STAMPEDE_AUTO_ATTACK_MAX_HITS",
"src/game/scenes/stampedeSketch/runtime/movement.ts:STAMPEDE_STICK_DEAD_ZONE",
"src/game/scenes/stampedeSketch/runtime/movement.ts:STAMPEDE_STICK_FULL_DISTANCE",
"src/game/scenes/stampedeSketch/runtime/progression.ts:STAMPEDE_SCRAP_PICKUP_RADIUS",
"src/game/scenes/stampedeSketch/runtime/progression.ts:STAMPEDE_UPGRADE_SCRAP_GOAL",
"src/game/scenes/stampedeSketch/runtime/upgrades.ts:STAMPEDE_UPGRADE_IDS",
"src/game/sharedSceneRuntime/config.ts:HOBBIES_WALK_SPEED",
"src/game/shell/devRidgeProgressParams.ts:DEV_RIDGE_STAMP_IDS",
"src/game/shell/index.ts:InteractiveApp",
"src/game/shell/notebookFocusLayout.ts:NOTEBOOK_STAGE_MAX_WIDTH",
"src/shared/i18n/index.ts:enMessages",
"src/shared/i18n/index.ts:getCurrentLocale",
"src/shared/i18n/index.ts:setCurrentLocale",
"src/shared/i18n/index.ts:DEFAULT_LOCALE",
"src/shared/i18n/index.ts:LOCALE_QUERY_PARAM",
"src/shared/i18n/index.ts:SUPPORTED_LOCALES",
"src/shared/i18n/index.ts:isLocale",
"src/shared/i18n/index.ts:readLocaleFromBrowser",
"src/shared/i18n/index.ts:readStoredLocale",
"src/shared/i18n/index.ts:resolveLocale",
"src/shared/i18n/index.ts:writeLocaleToBrowserUrl",
"src/shared/i18n/index.ts:writeStoredLocale",
"src/shared/i18n/locale.ts:SUPPORTED_LOCALES",
"src/shared/i18n/locale.ts:LOCALE_QUERY_PARAM",
"src/shared/i18n/locale.ts:isLocale",
"src/shared/i18n/locale.ts:readStoredLocale",
"src/shared/ui/index.ts:NotebookMenuSheet",
"src/shared/ui/index.ts:NotebookHeaderChrome",
"src/shared/ui/index.ts:NotebookScrapNote",
"src/shared/ui/index.ts:NotebookSpread",
"src/shared/ui/index.ts:SceneChoiceCard",
"src/shared/ui/index.ts:SceneChoiceGrid",
"src/shared/ui/index.ts:ScenePanelSheet",
"src/shared/ui/index.ts:SceneStatusSlip",
"src/shared/ui/index.ts:SectionHeader",
"src/shared/ui/index.ts:notebookShadowRoles",
"src/shared/ui/index.ts:sketchBorders",
"src/shared/ui/index.ts:sketchColors",
"src/shared/ui/index.ts:sketchShadows",
"src/shared/ui/index.ts:cn",
"src/shared/ui/tokens.ts:sketchColors"
],
"unused_types": [],
"private_type_leaks": [],
"unused_dependencies": [],
"unused_dev_dependencies": [],
"circular_dependencies": [],
"re_export_cycles": [],
"unused_optional_dependencies": [],
"unused_enum_members": [],
"unused_class_members": [
"src/game/core/ecs/world.ts:EcsWorld.reset",
"src/game/sceneLifecycle/SceneLifecycleController.ts:SceneLifecycleController.getEvents",
"src/game/sceneLifecycle/SceneManager.ts:SceneManager.captureResume",
"src/game/sceneLifecycle/events.ts:SceneLifecycleEventQueue.clear",
"src/game/scenes/potassiumSlip/runtime/renderer.ts:PotassiumSlipRenderer.showOutcomeOverlay",
"src/game/scenes/potassiumSlip/runtime/renderer.ts:PotassiumSlipRenderer.showUpgradeChoices",
"src/game/scenes/potassiumSlip/runtime/renderer.ts:PotassiumSlipRenderer.showTerminal"
],
"unresolved_imports": [],
"unlisted_dependencies": [],
"duplicate_exports": [
"PlayerStepResult|src/game/core/ecs/systems/playerSystems.ts|src/game/core/player/PlayerController.ts",
"PotassiumTerminalAction|src/game/scenes/potassiumSlip/runtime/renderer.ts|src/game/scenes/potassiumSlip/runtime/session.ts",
"getPotassiumShieldSide|src/game/scenes/potassiumSlip/runtime/phaserData.ts|src/game/scenes/potassiumSlip/runtime/waves.ts"
],
"type_only_dependencies": [],
"test_only_dependencies": [],
"boundary_violations": [],
"stale_suppressions": [],
"unused_catalog_entries": [],
"empty_catalog_groups": [],
"unresolved_catalog_references": [],
"unused_dependency_overrides": [],
"misconfigured_dependency_overrides": []
}
Loading
Loading