evaluation(rsl-siege-manager): capture dry-run results + verdict

Adds the rsl-siege-manager evaluation under a new `evaluations/` convention:

- `evaluations/README.md` — directory index for future evaluations.
- `evaluations/rsl-siege-manager/runbook.md` — methodology used (relocated
  from siege-web's `docs/experiments/graphify-dry-run.md`).
- `evaluations/rsl-siege-manager/results.md` — decision log with quoted
  findings and acid-test scoring. Verdict: do not adopt.
- `evaluations/rsl-siege-manager/graphify-out/` and `…-with-tests/` — raw
  artifacts (GRAPH_REPORT.md, graph.html, graph.json, manifest.json) so
  quoted findings stay auditable. AST cache excluded.

Also extends `.gitignore` with a scoped exception so the rule that hides
fresh `graphify-out/` from a developer's working checkout still applies,
while committed evaluation artifacts under `evaluations/` are preserved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Christopher Beaulieu
2026-05-15 11:08:27 -04:00
co-authored by Claude Opus 4.7
parent 77bb10c682
commit 5685984110
12 changed files with 87352 additions and 0 deletions
+5
View File
@@ -14,6 +14,11 @@ build/
*.egg
.graphify/
graphify-out/
# Committed evaluation artifacts under evaluations/ are intentional evidence — keep them
!evaluations/**/graphify-out/
!evaluations/**/graphify-out-with-tests/
!evaluations/**/graphify-out/**
!evaluations/**/graphify-out-with-tests/**
.graphify_*.json
.graphify_python
.claude/
+17
View File
@@ -0,0 +1,17 @@
# Graphify Evaluations
Per-codebase evaluations of [graphify](https://github.com/safishamsi/graphify) — runbook + captured artifacts + a written verdict.
Each subdirectory is one evaluation:
- A `runbook.md` capturing the methodology used at the time.
- A `results.md` documenting the run output, the acid-test scoring, and the adopt / don't-adopt decision.
- The raw `graphify-out/` directories (often two: one with tests excluded, one with tests included) so quoted findings remain auditable.
## Evaluations
| Codebase | Verdict | Date |
|---|---|---|
| [`rsl-siege-manager/`](./rsl-siege-manager/) | Do not adopt | 2026-05-15 |
🤖 _Generated by Claude Code on behalf of @cbeaulieu-gt_
@@ -0,0 +1,467 @@
# Graph Report - . (2026-05-13)
## Corpus Check
- cluster-only mode — file stats not available
## Summary
- 1886 nodes · 3876 edges · 141 communities (89 shown, 52 thin omitted)
- Extraction: 90% EXTRACTED · 10% INFERRED · 0% AMBIGUOUS · INFERRED: 393 edges (avg confidence: 0.62)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `6085fd66`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 8|Community 8]]
- [[_COMMUNITY_Community 9|Community 9]]
- [[_COMMUNITY_Community 10|Community 10]]
- [[_COMMUNITY_Community 11|Community 11]]
- [[_COMMUNITY_Community 12|Community 12]]
- [[_COMMUNITY_Community 13|Community 13]]
- [[_COMMUNITY_Community 14|Community 14]]
- [[_COMMUNITY_Community 15|Community 15]]
- [[_COMMUNITY_Community 16|Community 16]]
- [[_COMMUNITY_Community 17|Community 17]]
- [[_COMMUNITY_Community 18|Community 18]]
- [[_COMMUNITY_Community 19|Community 19]]
- [[_COMMUNITY_Community 20|Community 20]]
- [[_COMMUNITY_Community 21|Community 21]]
- [[_COMMUNITY_Community 22|Community 22]]
- [[_COMMUNITY_Community 23|Community 23]]
- [[_COMMUNITY_Community 24|Community 24]]
- [[_COMMUNITY_Community 25|Community 25]]
- [[_COMMUNITY_Community 26|Community 26]]
- [[_COMMUNITY_Community 27|Community 27]]
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_COMMUNITY_Community 33|Community 33]]
- [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 35|Community 35]]
- [[_COMMUNITY_Community 36|Community 36]]
- [[_COMMUNITY_Community 37|Community 37]]
- [[_COMMUNITY_Community 38|Community 38]]
- [[_COMMUNITY_Community 39|Community 39]]
- [[_COMMUNITY_Community 40|Community 40]]
- [[_COMMUNITY_Community 41|Community 41]]
- [[_COMMUNITY_Community 42|Community 42]]
- [[_COMMUNITY_Community 43|Community 43]]
- [[_COMMUNITY_Community 44|Community 44]]
- [[_COMMUNITY_Community 45|Community 45]]
- [[_COMMUNITY_Community 46|Community 46]]
- [[_COMMUNITY_Community 47|Community 47]]
- [[_COMMUNITY_Community 48|Community 48]]
- [[_COMMUNITY_Community 49|Community 49]]
- [[_COMMUNITY_Community 50|Community 50]]
- [[_COMMUNITY_Community 51|Community 51]]
- [[_COMMUNITY_Community 52|Community 52]]
- [[_COMMUNITY_Community 53|Community 53]]
- [[_COMMUNITY_Community 54|Community 54]]
- [[_COMMUNITY_Community 55|Community 55]]
- [[_COMMUNITY_Community 56|Community 56]]
- [[_COMMUNITY_Community 57|Community 57]]
- [[_COMMUNITY_Community 58|Community 58]]
- [[_COMMUNITY_Community 59|Community 59]]
- [[_COMMUNITY_Community 60|Community 60]]
- [[_COMMUNITY_Community 61|Community 61]]
- [[_COMMUNITY_Community 62|Community 62]]
- [[_COMMUNITY_Community 63|Community 63]]
- [[_COMMUNITY_Community 64|Community 64]]
- [[_COMMUNITY_Community 65|Community 65]]
- [[_COMMUNITY_Community 66|Community 66]]
- [[_COMMUNITY_Community 67|Community 67]]
- [[_COMMUNITY_Community 68|Community 68]]
- [[_COMMUNITY_Community 69|Community 69]]
- [[_COMMUNITY_Community 70|Community 70]]
- [[_COMMUNITY_Community 71|Community 71]]
- [[_COMMUNITY_Community 72|Community 72]]
- [[_COMMUNITY_Community 73|Community 73]]
- [[_COMMUNITY_Community 76|Community 76]]
- [[_COMMUNITY_Community 77|Community 77]]
- [[_COMMUNITY_Community 78|Community 78]]
- [[_COMMUNITY_Community 79|Community 79]]
- [[_COMMUNITY_Community 80|Community 80]]
- [[_COMMUNITY_Community 81|Community 81]]
- [[_COMMUNITY_Community 82|Community 82]]
- [[_COMMUNITY_Community 83|Community 83]]
- [[_COMMUNITY_Community 84|Community 84]]
- [[_COMMUNITY_Community 85|Community 85]]
- [[_COMMUNITY_Community 86|Community 86]]
- [[_COMMUNITY_Community 87|Community 87]]
- [[_COMMUNITY_Community 88|Community 88]]
- [[_COMMUNITY_Community 89|Community 89]]
- [[_COMMUNITY_Community 90|Community 90]]
- [[_COMMUNITY_Community 91|Community 91]]
- [[_COMMUNITY_Community 92|Community 92]]
- [[_COMMUNITY_Community 93|Community 93]]
- [[_COMMUNITY_Community 94|Community 94]]
- [[_COMMUNITY_Community 95|Community 95]]
- [[_COMMUNITY_Community 96|Community 96]]
- [[_COMMUNITY_Community 97|Community 97]]
- [[_COMMUNITY_Community 98|Community 98]]
- [[_COMMUNITY_Community 101|Community 101]]
- [[_COMMUNITY_Community 102|Community 102]]
- [[_COMMUNITY_Community 103|Community 103]]
- [[_COMMUNITY_Community 104|Community 104]]
- [[_COMMUNITY_Community 105|Community 105]]
- [[_COMMUNITY_Community 106|Community 106]]
- [[_COMMUNITY_Community 107|Community 107]]
- [[_COMMUNITY_Community 108|Community 108]]
- [[_COMMUNITY_Community 109|Community 109]]
- [[_COMMUNITY_Community 110|Community 110]]
- [[_COMMUNITY_Community 111|Community 111]]
- [[_COMMUNITY_Community 112|Community 112]]
- [[_COMMUNITY_Community 113|Community 113]]
- [[_COMMUNITY_Community 114|Community 114]]
- [[_COMMUNITY_Community 115|Community 115]]
- [[_COMMUNITY_Community 116|Community 116]]
- [[_COMMUNITY_Community 117|Community 117]]
- [[_COMMUNITY_Community 118|Community 118]]
- [[_COMMUNITY_Community 119|Community 119]]
- [[_COMMUNITY_Community 120|Community 120]]
- [[_COMMUNITY_Community 121|Community 121]]
- [[_COMMUNITY_Community 122|Community 122]]
- [[_COMMUNITY_Community 123|Community 123]]
- [[_COMMUNITY_Community 124|Community 124]]
- [[_COMMUNITY_Community 125|Community 125]]
## God Nodes (most connected - your core abstractions)
1. `_make_siege()` - 124 edges
2. `_make_member()` - 92 edges
3. `_make_position()` - 85 edges
4. `SiegeMember` - 78 edges
5. `_make_building()` - 64 edges
6. `_make_group()` - 60 edges
7. `BoardPage()` - 55 edges
8. `makeSiegeMember()` - 55 edges
9. `PostsPage()` - 37 edges
10. `_session_with_siege_and_configs()` - 35 edges
## Surprising Connections (you probably didn't know these)
- `PostPriorityResponse` --uses--> `PostPriorityConfig` [INFERRED]
backend/app/api/post_priority_config.py → frontend/src/api/posts.ts
- `PostPriorityUpdate` --uses--> `PostPriorityConfig` [INFERRED]
backend/app/api/post_priority_config.py → frontend/src/api/posts.ts
- `TestStartupValidation` --uses--> `MemberRole` [INFERRED]
backend/tests/test_auth.py → frontend/src/api/types.ts
- `AuthError` --uses--> `Member` [INFERRED]
backend/app/api/auth.py → frontend/src/api/types.ts
- `import_file()` --calls--> `seed_post_priority_config()` [INFERRED]
scripts/excel-import/import_excel.py → backend/app/db/seeds.py
## Communities (141 total, 52 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.05
Nodes (84): BuildingUpdate, _make_group(), _make_config(), Service-layer tests for update_building — focus on unbreak restoration., A Mana Shrine at level 5 has 13 teams = 4 full groups + 1 slot last group (4, Stronghold level 6: 10 groups × 3 slots. Break → reduces to base config (4 g, Adding a post-type building must use PostPriorityConfig to set the Post prio, Return a MagicMock shaped like scalars().all() → items. (+76 more)
### Community 1 - "Community 1"
Cohesion: 0.05
Nodes (45): Carousel(), CarouselProps, CarouselSlide, dot0, renderCarousel(), track, viewport, renderDropdown() (+37 more)
### Community 2 - "Community 2"
Cohesion: 0.07
Nodes (61): makeBoard(), _build_assignments_html(), _build_reserves_html(), generate_assignments_image(), generate_reserves_image(), Image generation service — renders HTML/CSS to PNG via Playwright., Build the reserves/members list HTML string. Args: members: Siege m, Render an HTML string to PNG bytes using headless Chromium. (+53 more)
### Community 3 - "Community 3"
Cohesion: 0.05
Nodes (41): activateSiege(), applyAttackDay(), cloneSiege(), completeSiege(), deleteSiege(), getSiege(), getSiegeMembers(), reopenSiege() (+33 more)
### Community 4 - "Community 4"
Cohesion: 0.06
Nodes (60): build_member_notification_message(), _build_section(), _position_label(), _position_sort_key(), PositionInfo, _positions_from_keys(), _positions_to_key_set(), Build rich per-member Discord DM messages for siege assignment notifications. (+52 more)
### Community 5 - "Community 5"
Cohesion: 0.05
Nodes (54): lifespan(), main(), FastAPI application factory and middleware wiring., Run the FastAPI/uvicorn HTTP sidecar on port 8001., Connect and run the Discord client., Start both the Discord client and HTTP server concurrently., Application lifespan — runs startup guards before serving requests., run_discord_client() (+46 more)
### Community 6 - "Community 6"
Cohesion: 0.08
Nodes (53): _make_member(), Return a minimal Member-like namespace., _preview(), Regression for #381. Setup: one member matches a condition that is active o, Invoke preview_post_suggestions with a mocked session., AC: single post with one matching member → suggestion targets that member., AC #6: post with no matching member → skip_reason='no_match'., Charge #1: is_reserve=True on the position → skip_reason='reserve'. (+45 more)
### Community 7 - "Community 7"
Cohesion: 0.09
Nodes (40): compareSiegesSpecific(), createSiege(), getSieges(), CONFIDENCE_LABEL, ConfidenceVariant, ComparisonPage(), formatPosition(), PositionTag() (+32 more)
### Community 8 - "Community 8"
Cohesion: 0.07
Nodes (40): getBoard(), PostPriorityResponse, PostPriorityUpdate, SiegeMemberCreate, AutofillApplyResult, AutofillAssignment, AutofillPreviewResult, BuildingGroupResponse (+32 more)
### Community 9 - "Community 9"
Cohesion: 0.05
Nodes (43): _make_mock_bot_with_guild(), _make_mock_member(), patch_guild_id(), Tests for GET /api/members/{discord_user_id} — guild member lookup endpoint., The @everyone role must never appear in the roles list., When Discord returns NotFound, respond 200 with is_member=false., When Discord raises an unexpected HTTPException, respond 503., When get_guild() returns None (bot not in guild), respond 503. (+35 more)
### Community 10 - "Community 10"
Cohesion: 0.05
Nodes (20): get_config(), Public config endpoint — exposes non-sensitive runtime flags to the frontend., Return public runtime configuration flags. This endpoint is intentionally u, Settings, BaseSettings, Tests for the /api/config public endpoint and startup guards. Covers: - /api/, Backend must refuse to start when SESSION_SECRET is missing and auth is enabled., GET /api/config returns the current auth_disabled flag. (+12 more)
### Community 11 - "Community 11"
Cohesion: 0.06
Nodes (42): Tests for the attack day auto-assign algorithm., Heavy hitters and advanced always get Day 2 regardless of threshold., Medium members are promoted to Day 2 (by power desc) when count < 10., Novice promoted by power desc after medium if still < 10., Overridden Day 2 members count toward the 10-threshold., attack_day_override=True members keep their existing attack_day., Exactly 10 HH/ADV → all remaining go to Day 1., Apply reads stored preview and updates siege_member attack_day values. (+34 more)
### Community 12 - "Community 12"
Cohesion: 0.08
Nodes (42): makeSiegeMember(), _make_batch(), _make_batch_result(), _make_db_session(), Endpoint tests for notification and post-to-channel routes., POST /notify must return 400 with a clear message when siege.date is None., POST /post-to-channel must return 400 with a clear message when siege.date is No, Build a mock DB session that returns the given objects. (+34 more)
### Community 13 - "Community 13"
Cohesion: 0.05
Nodes (43): Endpoints for Discord guild member ↔ clan member sync., Return proposed Discord ↔ clan member matches without writing to the DB., Apply accepted sync matches, updating discord_username and discord_id., applyDiscordSync(), previewDiscordSync(), SyncApply, SyncApplyResponse, Service logic for Discord guild member → clan member matching. (+35 more)
### Community 14 - "Community 14"
Cohesion: 0.06
Nodes (31): PostSuggestionStaleEntry, ChangeCell(), Classification, classify(), ExpiryCountdown, OutcomeFilter, Pill(), PRIORITY_META (+23 more)
### Community 15 - "Community 15"
Cohesion: 0.08
Nodes (30): createMember(), getMember(), getMemberRoles(), getPostConditions(), updateMember(), updateMemberPreferences(), MemberRoleInfo, SyncApplyItem (+22 more)
### Community 16 - "Community 16"
Cohesion: 0.17
Nodes (35): PostPriorityConfig, Building, Member, Post, PostCondition, Siege, SiegeStatus, Base (+27 more)
### Community 17 - "Community 17"
Cohesion: 0.09
Nodes (23): _cors_headers(), _make_app(), Integration tests for the CORS middleware wired in app/main.py. The middleware, Entries that are only whitespace after stripping must be dropped., A request from an explicitly allowed origin must get CORS response headers., The echoed origin must match the request, not be a wildcard., allow_credentials=True means the vary/credentials header is set., The default value from Settings must allow the dev frontend. (+15 more)
### Community 18 - "Community 18"
Cohesion: 0.05
Nodes (24): Tests for backend/app/telemetry.py. Verifies: - configure_telemetry() is a no-o, If configure_azure_monitor raises, the exception is swallowed., Regression tests: FastAPIInstrumentor.instrument_app() must be called. Issu, FastAPIInstrumentor.instrument_app() is called with the FastAPI app. Wh, When APPLICATIONINSIGHTS_CONNECTION_STRING is absent, nothing should happen., configure_azure_monitor() is called when both env vars are present. Reg, No exception and configure_azure_monitor is never imported or called., FastAPIInstrumentor.instrument_app() is NOT called when app argument is None. (+16 more)
### Community 19 - "Community 19"
Cohesion: 0.08
Nodes (36): _get(), Tests for rate limiting on the Discord OAuth2 auth endpoints. Covers: - /api/au, 3rd rapid request to /api/auth/login returns 429 when limit is 2/minute. Th, Two different X-Forwarded-For IPs have independent rate-limit buckets., 3rd rapid request to /api/auth/callback returns 429 when limit is 2/minute., When AUTH_DISABLED=true, 50 rapid requests to /login never return 429., Pathologically long X-Forwarded-For header is handled without error. Constr, Garbage XFF value falls back to the ASGI remote-address bucket. Sending ``X (+28 more)
### Community 20 - "Community 20"
Cohesion: 0.12
Nodes (34): _make_position(), _apply(), _entry_dict(), _preview_data(), Unit tests for the Suggest Post Assignments service. All tests use SimpleNamesp, Null suggested_member_id entries are skipped; no error raised., Position disabled since preview → 409 with reason position_disabled., Position set to reserve since preview → 409 with reason position_reserve. (+26 more)
### Community 21 - "Community 21"
Cohesion: 0.09
Nodes (31): build_group_structure(), compute_building_group_structure(), create_building_with_groups_and_positions(), import_file(), infer_building_level(), map_building_alias(), map_role(), parse_assignments_sheet() (+23 more)
### Community 22 - "Community 22"
Cohesion: 0.1
Nodes (17): apiClient, AppConfig, fetchConfig(), Layout(), navLinkClass(), renderLayout(), RequireAuth(), postsLink (+9 more)
### Community 23 - "Community 23"
Cohesion: 0.1
Nodes (17): downgrade(), initial schema Revision ID: 0001 Revises: Create Date: 2026-03-16, upgrade(), add autofill and attack day preview columns to siege Revision ID: 0002 Revises:, make siege date nullable Revision ID: 0003 Revises: 0002 Create Date: 2026-03-1, Add post_priority_config table, Add description to post_priority_config, Replace power with power_level, drop sort_value (+9 more)
### Community 24 - "Community 24"
Cohesion: 0.1
Nodes (24): API router for the Suggest Post Assignments feature. Routes: POST /sieges/{, Generate a greedy post-assignment suggestion preview. Args: siege_i, Apply a caller-filtered subset of the stored preview atomically. Uses SELEC, applyAutofill(), applyPostSuggestions(), previewAttackDay(), previewPostSuggestions(), AttackDayApplyResult (+16 more)
### Community 25 - "Community 25"
Cohesion: 0.1
Nodes (24): compareSieges(), get_most_recent_completed(), _load_assignments(), _load_member_names(), Return {member_id: [PositionKey, ...]} for non-reserve, non-disabled assigned po, _build_siege_assignments(), Tests for siege comparison service and endpoints., Build (Position, BuildingGroup, Building) rows for mock execute results. (+16 more)
### Community 26 - "Community 26"
Cohesion: 0.1
Nodes (24): AuthError, callback(), _check_guild_membership(), _error_redirect(), _exchange_code_for_token(), _get_discord_user(), login(), logout() (+16 more)
### Community 27 - "Community 27"
Cohesion: 0.13
Nodes (22): _async_client_that_raises(), _async_client_that_returns(), _make_ok_response(), Tests for bot HTTP client graceful degradation., get_member() returns the member dict when sidecar responds 200., get_member() returns the dict as-is when sidecar responds with is_member=False., get_member() raises httpx.HTTPError when the sidecar is unreachable., get_member() raises httpx.HTTPStatusError when sidecar returns 503. (+14 more)
### Community 28 - "Community 28"
Cohesion: 0.09
Nodes (9): Tests for scripts/import_excel.py parsing logic. All tests are pure-function te, Stronghold: 4 groups all with 3 slots., Only assignments for the specified (type, number) pair are counted., Returns None when the extracted date components don't form a valid date., 1 total position in a post -> level 1., test_build_group_structure_stronghold(), test_compute_building_group_structure_filters_by_building_number(), test_infer_building_level_post() (+1 more)
### Community 29 - "Community 29"
Cohesion: 0.09
Nodes (22): Tests for GET /api/version endpoint and _read_backend_version helper., GET /api/version responds 200 with all required fields., backend_version includes build metadata when env vars are present., backend_version is bare semver when BUILD_NUMBER/GIT_SHA are absent., git_sha top-level field is still returned for backward compatibility., Force re-import of version module so env-var reads pick up monkeypatches., bot_version is null when the bot sidecar is unreachable., When BUILD_NUMBER / GIT_SHA are absent, return bare semver. (+14 more)
### Community 30 - "Community 30"
Cohesion: 0.1
Nodes (16): get_team_count(), Return the theoretical total team slots for a building type at a given level., compute_scroll_count(), Compute total scroll count from the theoretical capacity of every building., Enable SQLite foreign key enforcement (no-op on PostgreSQL)., _enable_sqlite_fk(), Enable SQLite foreign key enforcement., compute_scroll_count returns the sum of _LEVEL_TEAMS capacities for all building (+8 more)
### Community 31 - "Community 31"
Cohesion: 0.11
Nodes (21): _make_mock_db(), A correct Bearer token grants access as the bot-service principal., Full valid callback flow issues a session cookie and redirects to /., Guild member WITH the required role passes the role check and proceeds normally., Return an AsyncMock session whose db.get() returns `member`., test_callback_happy_path(), test_callback_with_required_role_proceeds_to_member_lookup(), test_valid_service_token_allows_access() (+13 more)
### Community 32 - "Community 32"
Cohesion: 0.15
Nodes (20): PostSuggestionApplyRequest, Request body for POST /post-suggestions/apply. Attributes: apply_po, engine(), Integration tests for the Suggest Post Assignments feature. These tests use a r, Charge #12: selectinload chain works against real DB. Verifies that no Miss, Second preview within TTL overwrites first in the DB JSON column. Asserts b, Charge #17: apply → re-read position from DB → matched_condition_id set. Wi, Charge #22: member_changed reason fires when position written between preview an (+12 more)
### Community 33 - "Community 33"
Cohesion: 0.1
Nodes (17): _make_expired_jwt(), Tests for Discord OAuth2 auth middleware and endpoints., A wrong Bearer token is rejected with 401., An expired JWT cookie is rejected with 401., /api/version is accessible without any credentials., Mismatched OAuth state redirects to /login?error=invalid_state., Guild member response without role_names key is treated as no roles (rejected)., Bot sidecar connection error redirects to /login?error=service_unavailable. (+9 more)
### Community 34 - "Community 34"
Cohesion: 0.22
Nodes (17): generate_images(), Image generation endpoints., Generate PNG images for siege assignments and members list., getNotificationBatch(), notifySiegeMembers(), postToChannel(), Notification endpoints — send DMs and post images to Discord., Send DM notifications to all siege members asynchronously. (+9 more)
### Community 35 - "Community 35"
Cohesion: 0.12
Nodes (17): apiAddBuilding(), apiCreateMember(), apiCreateSiege(), autofillBtn, buildingsTab, chevron, firstPositionSpan, positionCell (+9 more)
### Community 36 - "Community 36"
Cohesion: 0.11
Nodes (13): PostSuggestionPreviewResult, applyBtn, board, btn, chip, labels, makePostBoard(), makeTwoPostBoard() (+5 more)
### Community 37 - "Community 37"
Cohesion: 0.15
Nodes (14): Send DMs for each member and record results in a fresh DB session., _send_dms(), Find member by username in the guild, open DM, send message., Find text channel by name, post message., Find text channel by name, post image as Discord file attachment. Retur, Discord client for the Siege Assignment System., SiegeBot, _get_bot() (+6 more)
### Community 38 - "Community 38"
Cohesion: 0.14
Nodes (13): getPostPriorities(), getPosts(), updatePostPriority(), getSiegeMemberPreferences(), DuplicateConditionMap, MemberWithMatches, postsTab, priorityBadgeColor() (+5 more)
### Community 39 - "Community 39"
Cohesion: 0.19
Nodes (15): bulk_update_positions(), list_posts(), Build a PostResponse-compatible dict, denormalizing building_number from the rel, _serialize_post(), setPostConditions(), updatePost(), Update a single position's assignment. Raises: 404 if position not, Apply multiple position updates in a single transaction. Each update dict m (+7 more)
### Community 40 - "Community 40"
Cohesion: 0.17
Nodes (14): VersionInfo, _fetch_bot_version(), getVersion(), Return a version string for the backend. When both BUILD_NUMBER and GIT_SHA, Call the bot sidecar's /version endpoint. Returns None if unreachable., Return version information for all components., _read_backend_version(), useVersion() (+6 more)
### Community 41 - "Community 41"
Cohesion: 0.15
Nodes (11): ChangelogStatus, fetchChangelogStatus(), DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioItem, DropdownMenuSeparator (+3 more)
### Community 42 - "Community 42"
Cohesion: 0.16
Nodes (14): list_buildings(), list_members(), list_post_priorities(), list_siege_members(), delete_siege(), get_siege(), list_sieges(), previewAutofill() (+6 more)
### Community 43 - "Community 43"
Cohesion: 0.13
Nodes (14): db_session(), Schema constraint and seed data tests using in-memory SQLite., seed_building_type_config populates exactly 5 rows., Inserting a duplicate (siege_id, member_id) pair raises IntegrityError., Inserting two members with the same name must raise IntegrityError., The check constraint preventing is_reserve=True with a member_id is defined., slot_count check constraints (1–3) are declared on the table., seed_post_conditions populates exactly 36 rows. (+6 more)
### Community 44 - "Community 44"
Cohesion: 0.14
Nodes (13): Schema-aware tests for Member.last_seen_changelog_at (issue #295, AC 1). These, Mapped type annotation allows a datetime value. Assigning a real datetime m, Member mapper exposes last_seen_changelog_at as a mapped attribute., last_seen_changelog_at column is defined nullable=True. Null is the sentine, last_seen_changelog_at has no server-side default. Null is the intentional, last_seen_changelog_at uses SQLAlchemy DateTime (no timezone). Matches the, Mapped type annotation allows None (datetime | None). Constructing a Member, test_last_seen_changelog_at_accepts_datetime_at_python_level() (+5 more)
### Community 45 - "Community 45"
Cohesion: 0.23
Nodes (13): add_building(), add_group(), delete_group(), deleteBuilding(), getBuildings(), updateBuilding(), _create_groups_and_positions(), _get_building_type_config() (+5 more)
### Community 46 - "Community 46"
Cohesion: 0.2
Nodes (11): _make_post_priority_config(), _make_post_ns(), _make_src_building_ns(), _make_src_group_ns(), _make_src_position_ns(), Endpoint tests for siege lifecycle transitions: activate, complete, clone., Cloning a siege with stale priority=0 posts must use PostPriorityConfig.priority, test_activate_planning_siege() (+3 more)
### Community 47 - "Community 47"
Cohesion: 0.36
Nodes (12): _make_bot(), _make_guild(), _make_text_channel(), Unit tests for SiegeBot Discord client methods using mock guild/member objects., Create a SiegeBot instance with a pre-loaded guild (bypasses Discord connect)., test_get_members_returns_correct_dict_format(), test_post_image_returns_cdn_url(), test_post_message_finds_channel_and_sends() (+4 more)
### Community 48 - "Community 48"
Cohesion: 0.15
Nodes (13): _make_worksheet(), Create a mock openpyxl worksheet that yields the given rows., Parses name, power_level bucket, and role from correct column positions., Column E is parsed as a slash-separated list of keyword strings., Empty or None column E results in an empty list., test_parse_members_sheet_basic(), test_parse_members_sheet_post_preferences(), test_parse_members_sheet_skips_empty_rows() (+5 more)
### Community 49 - "Community 49"
Cohesion: 0.25
Nodes (10): get_changelog_status(), markChangelogSeen(), Changelog status and mark-seen endpoints. These endpoints let the frontend trac, Raise HTTP 400 if the caller is a service principal. Args: current_, Return the authenticated user's last-seen changelog timestamp. Args:, Set the authenticated user's last-seen changelog timestamp to now. Idempote, _require_member_session(), ChangelogStatusResponse (+2 more)
### Community 50 - "Community 50"
Cohesion: 0.18
Nodes (9): health(), get_guild_member(), NotifyRequest, PostMessageRequest, Look up a single guild member by Discord user ID. Returns ``{"is_member": f, Validate the Bearer token against the configured bot API key., Health check — no authentication required., set_bot() (+1 more)
### Community 51 - "Community 51"
Cohesion: 0.2
Nodes (8): Exception, disable_auth_for_tests(), _FakeClient, _FakeHTTPException, _FakeNotFound, _FakeTextChannel, Shared pytest configuration for backend tests. Sets required environment variab, Bypass auth middleware for all tests by default. Individual tests that exer
### Community 52 - "Community 52"
Cohesion: 0.33
Nodes (9): MemberRole, SyncMatch, SyncPreviewResponse, PowerLevel, MemberBase, MemberCreate, MemberPreferencesUpdate, MemberResponse (+1 more)
### Community 53 - "Community 53"
Cohesion: 0.24
Nodes (9): _get_client_ip(), _parse_retry_after_seconds(), rate_limit_exceeded_handler(), _rate_limit_key(), Rate-limiting utilities shared across the application. This module owns the sin, Composite key function that honours the AUTH_DISABLED bypass. When ``AUTH_D, Parse a slowapi rate-limit detail string into a window size in seconds. slo, Return a JSON 429 response with a ``Retry-After`` header. Replaces slowapi' (+1 more)
### Community 54 - "Community 54"
Cohesion: 0.24
Nodes (9): _build_valid_siege_graph(), Integration test: full siege lifecycle — create → validate → assign → activate →, Build a siege with exactly the right building counts and assigned active members, Return a mock async session that serves the siege + configs across multiple exec, Happy-path: validate (errors) → assign → validate (0 errors) → activate → comple, test_full_siege_lifecycle(), _make_session(), Return a minimal AsyncSession mock that scalar_one_or_none returns siege. (+1 more)
### Community 55 - "Community 55"
Cohesion: 0.2
Nodes (10): _make_jwt(), A valid JWT session cookie with an existing member grants access., A JWT signed with a different secret is rejected with 401., A valid JWT whose member no longer exists in the DB returns 401., GET /api/auth/me returns member info when authenticated via session cookie., test_jwt_with_deleted_member_returns_401(), test_jwt_with_wrong_secret_returns_401(), test_me_with_valid_session() (+2 more)
### Community 56 - "Community 56"
Cohesion: 0.22
Nodes (10): _make_empty_worksheet(), _make_session_mock(), _make_workbook_mock(), Worksheet that yields no rows., Minimal openpyxl workbook mock. Members / Assignments / Reserves are empty, AsyncMock session whose execute() returns a result whose scalars().all() is []., When is_most_recent=False, section 3c must not run even if the Posts sheet p, When is_most_recent=True and the DB has no PostPriorityConfig rows yet, sect (+2 more)
### Community 57 - "Community 57"
Cohesion: 0.32
Nodes (6): get_member(), getMemberPreferences(), Return list of guild members as dicts with id, username, and display_name., Retrieve guild member list., Check guild membership via bot sidecar. Returns the member dict (includi, deactivate_member()
### Community 58 - "Community 58"
Cohesion: 0.25
Nodes (8): _make_assignments_worksheet(), Build a mock assignments worksheet with the proper two header rows prepended., Rows with a None group cell are skipped., Whitespace-only cell values normalise to None., test_parse_assignments_sheet_empty_value_is_none(), test_parse_assignments_sheet_member_assignment(), test_parse_assignments_sheet_skips_incomplete_rows(), test_parse_assignments_sheet_skips_unknown_building_type()
### Community 59 - "Community 59"
Cohesion: 0.25
Nodes (8): _make_posts_config_worksheet(), Create a mock worksheet for parse_posts_sheet_config. The function calls ws, One high-priority section with two posts: both get priority=3 and correct descri, Rows before any priority header default to priority=1 (Low)., Posts fall into the correct priority section based on their position., test_parse_posts_sheet_config_basic(), test_parse_posts_sheet_config_default_priority(), test_parse_posts_sheet_config_multiple_sections()
### Community 60 - "Community 60"
Cohesion: 0.29
Nodes (3): test_create_member_returns_201(), test_delete_member_returns_204(), Endpoint tests for /api/sieges — mocks the service layer directly.
### Community 61 - "Community 61"
Cohesion: 0.4
Nodes (5): notify(), Send a DM notification to a guild member., BotClient, HTTP client for the Discord bot sidecar API., Send DM via bot. Returns True on success, False on error.
### Community 63 - "Community 63"
Cohesion: 0.33
Nodes (5): Tests for enum definitions and associated constants in app.models.enums., BUILDING_TYPE_LABELS values must be non-empty title-cased display strings., Every BuildingType member must have an entry in BUILDING_TYPE_LABELS. This, test_building_type_labels_are_friendly_strings(), test_building_type_labels_covers_all_values()
### Community 64 - "Community 64"
Cohesion: 0.33
Nodes (6): _make_posts_conditions_worksheet(), Create a mock worksheet for parse_posts_sheet_conditions. The function enum, Three post rows with 1–3 keywords each are parsed correctly., A row with all-None cells is not included in the result., test_parse_posts_sheet_conditions_basic(), test_parse_posts_sheet_conditions_skips_empty()
### Community 65 - "Community 65"
Cohesion: 0.4
Nodes (3): _make_post_condition(), Endpoint tests for /api reference data endpoints., test_get_post_conditions_returns_list()
### Community 67 - "Community 67"
Cohesion: 0.4
Nodes (5): Mana shrine with 2 groups: position 3 is an empty trailing sheet column for grou, Stronghold level 2 = 16 total slots = 5 full groups (3 slots each) + 1 last grou, Stronghold level 4 = 22 total slots = 7 full groups + 1 last group with 1 slot., Defense Tower level 4 = 6 total slots = 2 full groups × 3 slots each. At lev, test_compute_building_group_structure_basic()
### Community 69 - "Community 69"
Cohesion: 0.5
Nodes (3): configure_telemetry(), Application Insights / OpenTelemetry initialisation for the backend. Call ``con, Initialise Azure Monitor OpenTelemetry and instrument the app. The ``azure-
### Community 71 - "Community 71"
Cohesion: 0.5
Nodes (4): Magic tower (base_last_slots=2): position 3 is a trailing empty sheet column and, Magic tower at a higher level where position 3 is genuinely filled: slot_cou, Magic Tower level 3 = 4 total slots = 1 full group (3 slots) + 1 last group with, test_compute_building_group_structure_magic_tower_level3()
### Community 72 - "Community 72"
Cohesion: 0.67
Nodes (3): Mana Shrine level 2 = 7 total slots = 2 full groups + 1 last group with 1 slot., Mana Shrine level 4 = 11 total slots = 3 full groups + 1 last group with 2 slots, test_compute_building_group_structure_mana_shrine_level2()
### Community 73 - "Community 73"
Cohesion: 0.67
Nodes (3): Returns None for a filename that does not match the pattern., Returns None for a completely unrelated filename., test_parse_filename_invalid_random()
## Knowledge Gaps
- **752 isolated node(s):** `initial schema Revision ID: 0001 Revises: Create Date: 2026-03-16`, `add autofill and attack day preview columns to siege Revision ID: 0002 Revises:`, `make siege date nullable Revision ID: 0003 Revises: 0002 Create Date: 2026-03-1`, `Add post_priority_config table`, `Add description to post_priority_config` (+747 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **52 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `_make_siege()` connect `Community 12` to `Community 0`, `Community 1`, `Community 3`, `Community 36`, `Community 6`, `Community 11`, `Community 46`, `Community 20`, `Community 54`, `Community 22`, `Community 25`, `Community 30`?**
_High betweenness centrality (0.183) - this node is a cross-community bridge._
- **Why does `SiegeMember` connect `Community 3` to `Community 1`, `Community 34`, `Community 35`, `Community 36`, `Community 6`, `Community 38`, `Community 8`, `Community 7`, `Community 42`, `Community 12`, `Community 14`, `Community 15`, `Community 16`, `Community 22`, `Community 24`, `Community 57`?**
_High betweenness centrality (0.167) - this node is a cross-community bridge._
- **Why does `list` connect `Community 42` to `Community 0`, `Community 1`, `Community 56`, `Community 5`, `Community 6`, `Community 39`, `Community 38`, `Community 45`, `Community 13`, `Community 15`, `Community 21`, `Community 24`, `Community 57`, `Community 58`?**
_High betweenness centrality (0.089) - this node is a cross-community bridge._
- **Are the 17 inferred relationships involving `SiegeMember` (e.g. with `Base` and `TestSeedDemoMembers`) actually correct?**
_`SiegeMember` has 17 INFERRED edges - model-reasoned connections that need verification._
- **What connects `initial schema Revision ID: 0001 Revises: Create Date: 2026-03-16`, `add autofill and attack day preview columns to siege Revision ID: 0002 Revises:`, `make siege date nullable Revision ID: 0003 Revises: 0002 Create Date: 2026-03-1` to the rest of the system?**
_752 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._
- **Should `Community 1` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,233 @@
# Graph Report - . (2026-05-13)
## Corpus Check
- cluster-only mode — file stats not available
## Summary
- 833 nodes · 1758 edges · 52 communities (48 shown, 4 thin omitted)
- Extraction: 85% EXTRACTED · 15% INFERRED · 0% AMBIGUOUS · INFERRED: 266 edges (avg confidence: 0.59)
- Token cost: 0 input · 0 output
## Graph Freshness
- Built from commit: `6085fd66`
- Run `git rev-parse HEAD` and compare to check if the graph is stale.
- Run `graphify update .` after code changes (no API cost).
## Community Hubs (Navigation)
- [[_COMMUNITY_Community 0|Community 0]]
- [[_COMMUNITY_Community 1|Community 1]]
- [[_COMMUNITY_Community 2|Community 2]]
- [[_COMMUNITY_Community 3|Community 3]]
- [[_COMMUNITY_Community 4|Community 4]]
- [[_COMMUNITY_Community 5|Community 5]]
- [[_COMMUNITY_Community 6|Community 6]]
- [[_COMMUNITY_Community 7|Community 7]]
- [[_COMMUNITY_Community 8|Community 8]]
- [[_COMMUNITY_Community 9|Community 9]]
- [[_COMMUNITY_Community 10|Community 10]]
- [[_COMMUNITY_Community 11|Community 11]]
- [[_COMMUNITY_Community 12|Community 12]]
- [[_COMMUNITY_Community 13|Community 13]]
- [[_COMMUNITY_Community 14|Community 14]]
- [[_COMMUNITY_Community 15|Community 15]]
- [[_COMMUNITY_Community 16|Community 16]]
- [[_COMMUNITY_Community 17|Community 17]]
- [[_COMMUNITY_Community 18|Community 18]]
- [[_COMMUNITY_Community 19|Community 19]]
- [[_COMMUNITY_Community 20|Community 20]]
- [[_COMMUNITY_Community 21|Community 21]]
- [[_COMMUNITY_Community 22|Community 22]]
- [[_COMMUNITY_Community 23|Community 23]]
- [[_COMMUNITY_Community 24|Community 24]]
- [[_COMMUNITY_Community 25|Community 25]]
- [[_COMMUNITY_Community 26|Community 26]]
- [[_COMMUNITY_Community 27|Community 27]]
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_COMMUNITY_Community 33|Community 33]]
- [[_COMMUNITY_Community 34|Community 34]]
- [[_COMMUNITY_Community 37|Community 37]]
## God Nodes (most connected - your core abstractions)
1. `SiegeMember` - 55 edges
2. `BoardPage()` - 53 edges
3. `Post Suggestions Modal Handoff` - 40 edges
4. `postsTab` - 29 edges
5. `cn()` - 29 edges
6. `PostsPage()` - 28 edges
7. `BuildingType` - 27 edges
8. `MembersPage()` - 27 edges
9. `MemberRole` - 25 edges
10. `Self-Host on Azure Wiki Page` - 23 edges
## Surprising Connections (you probably didn't know these)
- `A single post assignment suggestion produced by the greedy algorithm. Attri` --rationale_for--> `PostSuggestionEntry Schema` [EXTRACTED]
backend/app/schemas/post_suggestions.py → docs/superpowers/plans/2026-05-09-post-suggestions.md
- `Post Suggestions Modal Handoff` --calls--> `cn()` [EXTRACTED]
docs/design-refs/post-suggestions/README.md → frontend/src/lib/utils.ts
- `Discord OAuth2 Authentication Spec` --semantically_similar_to--> `Discord OAuth2 Auth Flow` [INFERRED] [semantically similar]
docs/WEB_DESIGN_DOCUMENT.md → CLAUDE.md
- `AuthError` --uses--> `Member` [INFERRED]
backend/app/api/auth.py → frontend/src/api/types.ts
- `PostPriorityResponse` --uses--> `PostPriorityConfig` [INFERRED]
backend/app/api/post_priority_config.py → frontend/src/api/posts.ts
## Communities (52 total, 4 thin omitted)
### Community 0 - "Community 0"
Cohesion: 0.05
Nodes (112): getBoard(), add_group(), generate_images(), Image generation endpoints., Generate PNG images for siege assignments and members list., getNotificationBatch(), notifySiegeMembers(), postToChannel() (+104 more)
### Community 1 - "Community 1"
Cohesion: 0.06
Nodes (67): getMember(), getMemberRoles(), getPostConditions(), updateMemberPreferences(), getSiege(), getSiegeMembers(), getSieges(), MemberRoleInfo (+59 more)
### Community 2 - "Community 2"
Cohesion: 0.05
Nodes (55): API router for the Suggest Post Assignments feature. Routes: POST /sieges/{, Generate a greedy post-assignment suggestion preview. Args: siege_i, Apply a caller-filtered subset of the stored preview atomically. Uses SELEC, activateSiege(), applyAttackDay(), applyAutofill(), applyPostSuggestions(), compareSiegesSpecific() (+47 more)
### Community 3 - "Community 3"
Cohesion: 0.05
Nodes (52): asyncpg, FastAPI, OpenTelemetry / Azure Monitor, Playwright (backend dep), PyJWT, Backend Requirements, slowapi, SQLAlchemy (+44 more)
### Community 4 - "Community 4"
Cohesion: 0.06
Nodes (41): PostSuggestionStaleEntry, ChangeCell(), Classification, classify(), ExpiryCountdown, OutcomeFilter, Pill(), PRIORITY_META (+33 more)
### Community 5 - "Community 5"
Cohesion: 0.09
Nodes (45): Application Insights, scripts/bootstrap-images.ps1, Caddy Reverse Proxy, .github/workflows/deploy.yml, Discord OAuth2, GitHub Actions CI/CD, .github/workflows/infra-deploy.yml, Azure Managed Identity (+37 more)
### Community 6 - "Community 6"
Cohesion: 0.06
Nodes (36): health(), createMember(), get_member(), getMemberPreferences(), updateMember(), Send DMs for each member and record results in a fresh DB session., _send_dms(), Find member by username in the guild, open DM, send message. (+28 more)
### Community 7 - "Community 7"
Cohesion: 0.08
Nodes (27): lifespan(), main(), FastAPI application factory and middleware wiring., Run the FastAPI/uvicorn HTTP sidecar on port 8001., Connect and run the Discord client., Start both the Discord client and HTTP server concurrently., Application lifespan — runs startup guards before serving requests., run_discord_client() (+19 more)
### Community 8 - "Community 8"
Cohesion: 0.1
Nodes (17): downgrade(), initial schema Revision ID: 0001 Revises: Create Date: 2026-03-16, upgrade(), add autofill and attack day preview columns to siege Revision ID: 0002 Revises:, make siege date nullable Revision ID: 0003 Revises: 0002 Create Date: 2026-03-1, Add post_priority_config table, Add description to post_priority_config, Replace power with power_level, drop sort_value (+9 more)
### Community 9 - "Community 9"
Cohesion: 0.1
Nodes (24): AuthError, callback(), _check_guild_membership(), _error_redirect(), _exchange_code_for_token(), _get_discord_user(), login(), logout() (+16 more)
### Community 10 - "Community 10"
Cohesion: 0.12
Nodes (17): apiAddBuilding(), apiCreateMember(), apiCreateSiege(), autofillBtn, buildingsTab, chevron, firstPositionSpan, positionCell (+9 more)
### Community 11 - "Community 11"
Cohesion: 0.17
Nodes (14): add_building(), delete_group(), deleteBuilding(), getBuildings(), updateBuilding(), get_team_count(), Return the theoretical total team slots for a building type at a given level., _create_groups_and_positions() (+6 more)
### Community 12 - "Community 12"
Cohesion: 0.16
Nodes (15): VersionInfo, _fetch_bot_version(), getVersion(), Return a version string for the backend. When both BUILD_NUMBER and GIT_SHA, Call the bot sidecar's /version endpoint. Returns None if unreachable., Return version information for all components., _read_backend_version(), useVersion() (+7 more)
### Community 13 - "Community 13"
Cohesion: 0.13
Nodes (9): BUILDING_LABELS, BuildingColorClass, ActiveTab, BoardPage(), BUILDING_TYPE_ORDER, ROLE_CHIP_COLORS, ROLE_COLORS, ROLE_FILTER_OPTIONS (+1 more)
### Community 14 - "Community 14"
Cohesion: 0.16
Nodes (11): getSiegeMemberPreferences(), DuplicateConditionMap, findPostPosition(), MemberWithMatches, postsTab, priorityBadgeColor(), priorityLabel(), POWER_LABELS (+3 more)
### Community 15 - "Community 15"
Cohesion: 0.19
Nodes (13): build_member_notification_message(), _build_section(), _position_label(), _position_sort_key(), _positions_from_keys(), _positions_to_key_set(), Build rich per-member Discord DM messages for siege assignment notifications., Convert a list of PositionInfo objects to a set of comparable tuples. (+5 more)
### Community 16 - "Community 16"
Cohesion: 0.26
Nodes (12): list_posts(), Build a PostResponse-compatible dict, denormalizing building_number from the rel, _serialize_post(), setPostConditions(), updatePost(), Update a single position's assignment. Raises: 404 if position not, _validate_member_active(), _get_post_for_siege_or_404() (+4 more)
### Community 17 - "Community 17"
Cohesion: 0.23
Nodes (11): _build_assignments_html(), _build_reserves_html(), generate_assignments_image(), generate_reserves_image(), Image generation service — renders HTML/CSS to PNG via Playwright., Build the reserves/members list HTML string. Args: members: Siege m, Render an HTML string to PNG bytes using headless Chromium., Render the assignments board as a PNG. Returns raw PNG bytes. (+3 more)
### Community 18 - "Community 18"
Cohesion: 0.25
Nodes (10): get_changelog_status(), markChangelogSeen(), Changelog status and mark-seen endpoints. These endpoints let the frontend trac, Raise HTTP 400 if the caller is a service principal. Args: current_, Return the authenticated user's last-seen changelog timestamp. Args:, Set the authenticated user's last-seen changelog timestamp to now. Idempote, _require_member_session(), ChangelogStatusResponse (+2 more)
### Community 19 - "Community 19"
Cohesion: 0.22
Nodes (5): Carousel(), CarouselProps, CarouselSlide, COLORS, SLIDES
### Community 20 - "Community 20"
Cohesion: 0.24
Nodes (7): AppConfig, fetchConfig(), Layout(), navLinkClass(), RequireAuth(), useAuth(), LandingOrSieges()
### Community 21 - "Community 21"
Cohesion: 0.22
Nodes (7): compareSieges(), ComparisonResult, MemberDiff, PositionKey, _load_assignments(), _load_member_names(), Return {member_id: [PositionKey, ...]} for non-reserve, non-disabled assigned po
### Community 22 - "Community 22"
Cohesion: 0.24
Nodes (9): _get_client_ip(), _parse_retry_after_seconds(), rate_limit_exceeded_handler(), _rate_limit_key(), Rate-limiting utilities shared across the application. This module owns the sin, Composite key function that honours the AUTH_DISABLED bypass. When ``AUTH_D, Parse a slowapi rate-limit detail string into a window size in seconds. slo, Return a JSON 429 response with a ``Retry-After`` header. Replaces slowapi' (+1 more)
### Community 23 - "Community 23"
Cohesion: 0.22
Nodes (8): DropdownMenuCheckboxItem, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut(), DropdownMenuSubContent, DropdownMenuSubTrigger
### Community 24 - "Community 24"
Cohesion: 0.22
Nodes (8): Endpoints for Discord guild member ↔ clan member sync., Return proposed Discord ↔ clan member matches without writing to the DB., Apply accepted sync matches, updating discord_username and discord_id., applyDiscordSync(), previewDiscordSync(), Service logic for Discord guild member → clan member matching., Return proposed matches between Discord guild members and clan members. Mat, Apply accepted sync matches, writing discord_username and discord_id. Unkno
### Community 25 - "Community 25"
Cohesion: 0.33
Nodes (5): AuthContext, AuthContextValue, AuthProvider(), AuthUser, queryClient
### Community 26 - "Community 26"
Cohesion: 0.4
Nodes (3): ChangelogStatus, fetchChangelogStatus(), DropdownMenuContent
### Community 27 - "Community 27"
Cohesion: 0.33
Nodes (4): apiClient, ERROR_MESSAGES, LoginPage(), MEMBERSHIP_ERRORS
### Community 28 - "Community 28"
Cohesion: 0.33
Nodes (5): get_config(), Public config endpoint — exposes non-sensitive runtime flags to the frontend., Return public runtime configuration flags. This endpoint is intentionally u, Settings, BaseSettings
### Community 31 - "Community 31"
Cohesion: 0.5
Nodes (3): configure_telemetry(), Application Insights / OpenTelemetry initialisation for the backend. Call ``con, Initialise Azure Monitor OpenTelemetry and instrument the app. The ``azure-
### Community 33 - "Community 33"
Cohesion: 0.5
Nodes (3): getPostPriorities(), getPosts(), updatePostPriority()
### Community 34 - "Community 34"
Cohesion: 0.5
Nodes (4): bulk_update_positions(), Apply multiple position updates in a single transaction. Each update dict m, Validate the logical consistency of position flag combinations., _validate_position_state()
## Ambiguous Edges - Review These
- `bootstrap-images.ps1` → `scripts/bootstrap-images.ps1` [AMBIGUOUS]
wiki/Self-Host-on-Azure.md · relation: semantically_similar_to
## Knowledge Gaps
- **259 isolated node(s):** `initial schema Revision ID: 0001 Revises: Create Date: 2026-03-16`, `add autofill and attack day preview columns to siege Revision ID: 0002 Revises:`, `make siege date nullable Revision ID: 0003 Revises: 0002 Create Date: 2026-03-1`, `Add post_priority_config table`, `Add description to post_priority_config` (+254 more)
These have ≤1 connection - possible missing edges or undocumented components.
- **4 thin communities (<3 nodes) omitted from report** — run `graphify query` to explore isolated nodes.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **What is the exact relationship between `bootstrap-images.ps1` and `scripts/bootstrap-images.ps1`?**
_Edge tagged AMBIGUOUS (relation: semantically_similar_to) - confidence is low._
- **Why does `Post Suggestions Modal Handoff` connect `Community 4` to `Community 0`, `Community 1`, `Community 2`, `Community 3`, `Community 14`?**
_High betweenness centrality (0.257) - this node is a cross-community bridge._
- **Why does `Web Design Document` connect `Community 4` to `Community 3`?**
_High betweenness centrality (0.131) - this node is a cross-community bridge._
- **Are the 13 inferred relationships involving `SiegeMember` (e.g. with `Base` and `Member`) actually correct?**
_`SiegeMember` has 13 INFERRED edges - model-reasoned connections that need verification._
- **Are the 3 inferred relationships involving `Post Suggestions Modal Handoff` (e.g. with `Suggest Post Assignments Feature` and `Post Data Model`) actually correct?**
_`Post Suggestions Modal Handoff` has 3 INFERRED edges - model-reasoned connections that need verification._
- **What connects `initial schema Revision ID: 0001 Revises: Create Date: 2026-03-16`, `add autofill and attack day preview columns to siege Revision ID: 0002 Revises:`, `make siege date nullable Revision ID: 0003 Revises: 0002 Create Date: 2026-03-1` to the rest of the system?**
_259 weakly-connected nodes found - possible documentation gaps or missing edges._
- **Should `Community 0` be split into smaller, more focused modules?**
_Cohesion score 0.05 - nodes in this community are weakly interconnected._
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,918 @@
{
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\env.py": {
"mtime": 1778710280.615952,
"hash": "731374a0c60b7a3d7ab4fe969af77cdc"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0001_initial_schema.py": {
"mtime": 1778710280.6174564,
"hash": "89da22558cc25f69a85fdb95c5226094"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0002_add_preview_columns.py": {
"mtime": 1778710280.6174564,
"hash": "7272088a42b67a8e4808eb3c9b7dd696"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0003_make_siege_date_nullable.py": {
"mtime": 1778710280.6174564,
"hash": "2e9c3427061e4be701fe13af654dff62"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0004_add_post_priority_config.py": {
"mtime": 1778710280.6174564,
"hash": "6dfb516e649bc98b4af75c6d72217b55"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0005_add_description_to_post_priority_config.py": {
"mtime": 1778710280.6184616,
"hash": "610068cb10304ae4dbcc7bc71a32e78c"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0006_power_level_and_drop_sort_value.py": {
"mtime": 1778710280.6184616,
"hash": "8182ba42f31e35ef28f5c4d9dabfde1c"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0007_fix_group_number_max.py": {
"mtime": 1778710280.6184616,
"hash": "4b47bdeebb46156e514a5b067df94235"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0008_add_matched_condition_id_to_position.py": {
"mtime": 1778710280.6184616,
"hash": "c954ca95fdadd4a7ef20ffa633d12fed"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0009_add_discord_id_to_member.py": {
"mtime": 1778710280.6194618,
"hash": "dcf300cf8ed80c1b8b3825bcca67e245"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0010_add_last_seen_changelog_at_to_member.py": {
"mtime": 1778710280.6194618,
"hash": "611bcc51cacb45fa38a397483251978e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\alembic\\versions\\0011_add_post_suggest_preview.py": {
"mtime": 1778710280.6194618,
"hash": "c2911c522068032d232ebdda418ae402"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\config.py": {
"mtime": 1778710280.6260061,
"hash": "af7166f36bc6603c51a31821b4169166"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\main.py": {
"mtime": 1778710280.6275196,
"hash": "ef3a111d022f595379cdfc5e4949daf0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\middleware.py": {
"mtime": 1778710280.6275196,
"hash": "4f8742627daa35dd41d30076e24f6215"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\rate_limit.py": {
"mtime": 1778710280.631044,
"hash": "d19e5a629177df85c43e3c14f4dc0d46"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\telemetry.py": {
"mtime": 1778710280.639125,
"hash": "0c58ed46b46a65c553da095108b27bfd"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\__init__.py": {
"mtime": 1778710280.6194618,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\attack_day.py": {
"mtime": 1778710280.6204622,
"hash": "03d01cbf514354d406ed91fbc65b35a1"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\auth.py": {
"mtime": 1778710280.6204622,
"hash": "a5f33d846d98152318daec3a46d0d248"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\autofill.py": {
"mtime": 1778710280.6204622,
"hash": "a09d58b4a1d3c5af8f5c8a3ba6a7029e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\board.py": {
"mtime": 1778710280.621461,
"hash": "aaeb8712e563ba3dec103df4a3fc04ac"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\buildings.py": {
"mtime": 1778710280.621461,
"hash": "ffdd155eb162bceae062773746e4a736"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\changelog.py": {
"mtime": 1778710280.621461,
"hash": "46c813936a455f15a7b9c81d21193278"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\comparison.py": {
"mtime": 1778710280.621461,
"hash": "ddca1ab6be65dc3372ad48159b99f019"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\config.py": {
"mtime": 1778710280.6224627,
"hash": "4cfef35cbf17cda161b15128a218884a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\discord_sync.py": {
"mtime": 1778710280.6224627,
"hash": "4af25fe7f78cdd7ed0a19b43a6c0d944"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\health.py": {
"mtime": 1778710280.6224627,
"hash": "7e3eb64485f05fe2e50e5681ca1f0932"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\images.py": {
"mtime": 1778710280.6224627,
"hash": "bdbf492718a5d11dd6b98c1d639931ac"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\lifecycle.py": {
"mtime": 1778710280.6234608,
"hash": "224fbb28b68c1d85ac358d96f4d8d307"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\members.py": {
"mtime": 1778710280.6234608,
"hash": "fade885c4c9b9ac20cccbc9b1022ea02"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\notifications.py": {
"mtime": 1778710280.6234608,
"hash": "ff2badd250e6e4abd60f76c17bf197a9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\posts.py": {
"mtime": 1778710280.6234608,
"hash": "8f342e23f193f2ba528ee0f2ea924710"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\post_priority_config.py": {
"mtime": 1778710280.6234608,
"hash": "6cbb8addbb8fbbf729b2938cd64e4077"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\post_suggestions.py": {
"mtime": 1778710280.6234608,
"hash": "57ba4074cf0d1413e5604e6c20c458ba"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\reference.py": {
"mtime": 1778710280.6249774,
"hash": "e723050f84384dc9adcdb7796fd5a105"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\sieges.py": {
"mtime": 1778710280.6249774,
"hash": "e2f544c55968478ef4b80943fcda6ccc"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\siege_members.py": {
"mtime": 1778710280.6249774,
"hash": "f2eb1fd29b959b826008f6a627da9c0d"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\validation.py": {
"mtime": 1778710280.6249774,
"hash": "05d95e278dfb5f2da15598253b2ec32e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\version.py": {
"mtime": 1778710280.6249774,
"hash": "72b877fef624fbab48f6170475e4482b"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\api\\__init__.py": {
"mtime": 1778710280.6204622,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\db\\base.py": {
"mtime": 1778710280.6260061,
"hash": "16deb2759f7de2cf86f328dce6b2d38b"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\db\\seeds.py": {
"mtime": 1778710280.6260061,
"hash": "c1238d95afe54852c9f8e4946bcd1184"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\db\\session.py": {
"mtime": 1778710280.6260061,
"hash": "d83e8d560833942da095e4e77ef715a0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\db\\__init__.py": {
"mtime": 1778710280.6260061,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\dependencies\\auth.py": {
"mtime": 1778710280.6260061,
"hash": "1e13ad83f41440240858e379bc350e32"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\dependencies\\__init__.py": {
"mtime": 1778710280.6260061,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\building.py": {
"mtime": 1778710280.6275196,
"hash": "264dd9c1b5b9af8261e39ef40bb56d0d"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\building_group.py": {
"mtime": 1778710280.6285377,
"hash": "9e4fe949454ac06e8919c54d17cd053f"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\building_type_config.py": {
"mtime": 1778710280.6285377,
"hash": "8d46f6dce4eefc61342e1a5084563f76"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\enums.py": {
"mtime": 1778710280.6285377,
"hash": "bbcd730b23b6ae966f00a7b209dfa1ba"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\member.py": {
"mtime": 1778710280.6285377,
"hash": "00c8c2af6e5087bcdf9b485d8f32afab"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\member_post_preference.py": {
"mtime": 1778710280.6295295,
"hash": "9b9b1f5de349977ad8897398d6698ef8"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\notification_batch.py": {
"mtime": 1778710280.6295295,
"hash": "3ad5726f0ca19a456eb02ac12dbb4f89"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\notification_batch_result.py": {
"mtime": 1778710280.6295295,
"hash": "c4fd788d9d88b1b994a35cf770b7d875"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\position.py": {
"mtime": 1778710280.6300344,
"hash": "d1bafc8772229db93351d7e179c15ea3"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\post.py": {
"mtime": 1778710280.6300344,
"hash": "6d62df6899ba21737f61bb917210dad0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\post_active_condition.py": {
"mtime": 1778710280.6300344,
"hash": "ab9b5ba959ad8c87635a5565cc7a6857"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\post_condition.py": {
"mtime": 1778710280.6300344,
"hash": "9fc05e2a8819db37bc7bd224f8c2db67"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\post_priority_config.py": {
"mtime": 1778710280.6300344,
"hash": "21b1c5860d8e42fef07463b5bf52ceab"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\siege.py": {
"mtime": 1778710280.631044,
"hash": "ac548c74c451dca2c84d9d039de46b6a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\siege_member.py": {
"mtime": 1778710280.631044,
"hash": "24f74c67df2a8375175a818e2ef592be"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\models\\__init__.py": {
"mtime": 1778710280.6275196,
"hash": "002c14a2213747661b77b5a30b8d3110"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\attack_day.py": {
"mtime": 1778710280.631044,
"hash": "6636af44aa1f3f3079f864b3c8bb9cac"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\autofill.py": {
"mtime": 1778710280.6320436,
"hash": "261012c1a7cdae9c401576016a1e0535"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\board.py": {
"mtime": 1778710280.6320436,
"hash": "c69579e0609dceba1fafa087eda2405e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\building.py": {
"mtime": 1778710280.6320436,
"hash": "17a76e00a22acfa311063cf3bf0aceb1"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\changelog.py": {
"mtime": 1778710280.6320436,
"hash": "f86a9e09fe755643a15a7f7b06a4f7e7"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\common.py": {
"mtime": 1778710280.6320436,
"hash": "011d46dc2203c3ed97184cec370ac179"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\comparison.py": {
"mtime": 1778710280.6320436,
"hash": "48ffa2ab0f4f95040236b6611ed375d0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\member.py": {
"mtime": 1778710280.6320436,
"hash": "726dad9ff8c7a8bffd365ddcfd543645"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\post.py": {
"mtime": 1778710280.6335557,
"hash": "ff0149eb24572e4ba21fa191a73c762f"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\post_condition.py": {
"mtime": 1778710280.6335557,
"hash": "6fec8cfb3b2eea536953026b0116b4af"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\post_suggestions.py": {
"mtime": 1778710280.6335557,
"hash": "7db30a2f2f5a9232d8a783a85ee2df6a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\siege.py": {
"mtime": 1778710280.6335557,
"hash": "1178a2cb1bc13ec3defa148e36991683"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\siege_member.py": {
"mtime": 1778710280.6335557,
"hash": "45b79b07f1d489b2daef9b719a0a0929"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\validation.py": {
"mtime": 1778710280.6335557,
"hash": "0221b358c7f777442cde715c227ac6ca"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\version.py": {
"mtime": 1778710280.6335557,
"hash": "b2fbcf0d416ddb9f1997acd6cb62bcf3"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\schemas\\__init__.py": {
"mtime": 1778710280.631044,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\attack_day.py": {
"mtime": 1778710280.6350994,
"hash": "997ffc05e8c23b0a544979ae0ed8dc7a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\autofill.py": {
"mtime": 1778710280.6350994,
"hash": "e9ce9ddf48273c0d9932f75ea8e8b0be"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\board.py": {
"mtime": 1778710280.6350994,
"hash": "7222c72b83f1091828c689f490ebcfe6"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\bot_client.py": {
"mtime": 1778710280.6350994,
"hash": "b699c8adf0b08521c02f3e6a4072c1ef"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\buildings.py": {
"mtime": 1778710280.6361103,
"hash": "22d3b48b747d63585084db712f7526ff"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\building_capacity.py": {
"mtime": 1778710280.6361103,
"hash": "5b463b5af305f54cd34e75e568756db9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\comparison.py": {
"mtime": 1778710280.6361103,
"hash": "17d8b0f29ef13d8a469dcbe79e5e0b18"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\discord_sync.py": {
"mtime": 1778710280.6361103,
"hash": "0fdbe1441877c11241501e6f37e6874a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\image_gen.py": {
"mtime": 1778710280.6361103,
"hash": "42d66ff5cc72422b6cffdce18e7b88e9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\lifecycle.py": {
"mtime": 1778710280.6376143,
"hash": "c6850f4b0ee921550d23553aa50db9ac"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\members.py": {
"mtime": 1778710280.6376143,
"hash": "1586e16427694996d81ce4d54812c5f0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\notification_message.py": {
"mtime": 1778710280.6376143,
"hash": "514ad0558f51c114a96e8316cd4392bf"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\posts.py": {
"mtime": 1778710280.63862,
"hash": "56792826d430eaa6cdd86796ddb43c10"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\post_suggestions.py": {
"mtime": 1778710280.63862,
"hash": "4a62a36d881bf55a4f73b623b95738bf"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\reference.py": {
"mtime": 1778710280.639125,
"hash": "e86f9864edb3e0eaa1a5e00cd2b903a9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\sieges.py": {
"mtime": 1778710280.639125,
"hash": "4f43c0760d92d03cc1062ba4b7610dba"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\siege_members.py": {
"mtime": 1778710280.639125,
"hash": "444d8aba42f567d23a8b1a5a12e51200"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\validation.py": {
"mtime": 1778710280.639125,
"hash": "bf8431f5d8b0c8dc793ed927f6de075f"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\app\\services\\__init__.py": {
"mtime": 1778710280.6335557,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\scripts\\seed.py": {
"mtime": 1778710280.640529,
"hash": "08609621e397256df55a763eb98530c3"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\scripts\\seed_demo.py": {
"mtime": 1778710280.6415303,
"hash": "ef5c21e671c3184b5cbb2240979c0690"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\app\\config.py": {
"mtime": 1778710280.65144,
"hash": "b80130299a391da986cfd02386e5dea7"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\app\\discord_client.py": {
"mtime": 1778710280.65144,
"hash": "99db4ab6cf0ecc3d30602e2b8db66a70"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\app\\http_api.py": {
"mtime": 1778710280.65144,
"hash": "936ed9c6f1e4443f5035d0de3340329e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\app\\main.py": {
"mtime": 1778710280.6519608,
"hash": "e05678b35f14c576d1cd2ee7f0ee86b0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\app\\telemetry.py": {
"mtime": 1778710280.6519608,
"hash": "a42e02bf2fddd81cca77baa570cdc1ac"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\app\\__init__.py": {
"mtime": 1778710280.650921,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\eslint.config.js": {
"mtime": 1778710280.6638145,
"hash": "820d67ddf4af94ab7a60b247ef568493"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\playwright.config.ts": {
"mtime": 1778710280.6663249,
"hash": "c0c6f46da1f83e4dad062bf96a1645ce"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\postcss.config.js": {
"mtime": 1778710280.6663249,
"hash": "33fad9c02cb0ec6d6030369ef6347d57"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\tailwind.config.ts": {
"mtime": 1778710280.6950474,
"hash": "ab2458852a94f3918fc4dc8eec8ee34a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\vite.config.ts": {
"mtime": 1778710280.6955535,
"hash": "f0b8bf87fac76c40d6423f702e2a122d"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\vitest.config.ts": {
"mtime": 1778710280.6955535,
"hash": "e102be79d7bf6fd8b83320963d3d4d00"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\e2e\\board.spec.ts": {
"mtime": 1778710280.6618135,
"hash": "2e1ab122df18ca111301f5244c1b4903"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\e2e\\members.spec.ts": {
"mtime": 1778710280.6618135,
"hash": "012b10ee19afe7952752bdb34d947596"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\e2e\\siege-lifecycle.spec.ts": {
"mtime": 1778710280.6638145,
"hash": "d3c27ac7e90b3bb5f190694f8d692f6c"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\e2e\\smoke.spec.ts": {
"mtime": 1778710280.6638145,
"hash": "7ffc073a7b7ce29603e7b1dda85b101b"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\App.tsx": {
"mtime": 1778710280.6731784,
"hash": "dc94be0210c5307affde3c5e0e5412b7"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\main.tsx": {
"mtime": 1778710280.6834679,
"hash": "5310a6bf9e3bdf98dc615af3ceff1537"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\vite-env.d.ts": {
"mtime": 1778710280.6950474,
"hash": "0352474ba2918efe13895edbc3780d94"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\board.ts": {
"mtime": 1778710280.6731784,
"hash": "997fae78645cd70b5c2fc3476313e4c1"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\changelog.ts": {
"mtime": 1778710280.6731784,
"hash": "9baa9e3d2be3e4bb566ba86923829a17"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\client.ts": {
"mtime": 1778710280.6746829,
"hash": "10a76391bc7690246d448e867cf7af57"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\config.ts": {
"mtime": 1778710280.6746829,
"hash": "3715d4f6dd51e505949f4b0f7ec9b309"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\members.ts": {
"mtime": 1778710280.6752062,
"hash": "385a652bab3624eb1a556666b369c7be"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\notifications.ts": {
"mtime": 1778710280.6752062,
"hash": "b202ca64e4f5445d309fa8cede22ff0c"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\posts.ts": {
"mtime": 1778710280.6752062,
"hash": "42847a5a2b91ef16e25b1be64c9c5955"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\sieges.ts": {
"mtime": 1778710280.6752062,
"hash": "f1014577b76ed9125141f290e8fb7578"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\types.ts": {
"mtime": 1778710280.6752062,
"hash": "f83637e41fcfc597e14c140b81e74a63"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\api\\version.ts": {
"mtime": 1778710280.6762116,
"hash": "3c320e1ae650ccfaf0fcc58db6f0ad8c"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\Carousel.tsx": {
"mtime": 1778710280.6772118,
"hash": "9c1e5964f8b5e400f963606ee0cd4eb9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ChangelogDropdown.tsx": {
"mtime": 1778710280.6772118,
"hash": "32af80944e867e66ec8cf25f6391e233"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\DiscordSyncModal.tsx": {
"mtime": 1778710280.6772118,
"hash": "52ea2cf5bf019ad4e9c376a447e87eaa"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\GroupByToggle.tsx": {
"mtime": 1778710280.6772118,
"hash": "78856592b8aaeb706087c4ef1e6c843b"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\Layout.tsx": {
"mtime": 1778710280.6772118,
"hash": "e813042f31449a0de61f906090164004"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\PostsTab.tsx": {
"mtime": 1778710280.678212,
"hash": "3f11fa6d290cff08dffcc9262a092b3a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\PostSuggestionsModal.tsx": {
"mtime": 1778710280.678212,
"hash": "9c16b7f40b7352b2f423f7ce46a68b11"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\RequireAuth.tsx": {
"mtime": 1778710280.678212,
"hash": "cd2b2e1cd2d319fc3f3977f7589f291e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\SiegeLayout.tsx": {
"mtime": 1778710280.679212,
"hash": "5dc89034188b6823763f9c6852f5f03a"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\badge.tsx": {
"mtime": 1778710280.679212,
"hash": "639942830535b4f2bb8e21e8d3a674e4"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\button.tsx": {
"mtime": 1778710280.679212,
"hash": "99376e90207bb72645519879a6ad87aa"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\checkbox.tsx": {
"mtime": 1778710280.679212,
"hash": "596afb4c85e5b432c24e2606e9623bf8"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\dialog.tsx": {
"mtime": 1778710280.679212,
"hash": "b917e9be88d5dc6b1b6d2660df3be9a0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\dropdown-menu.tsx": {
"mtime": 1778710280.6804683,
"hash": "8f8f3621f203e9d7d566ed3827ddb95c"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\input.tsx": {
"mtime": 1778710280.6804683,
"hash": "4f418a1677a7fa837b7a5f16b05eabea"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\label.tsx": {
"mtime": 1778710280.6804683,
"hash": "ede7445a0bd4c9380bff1fb664d21763"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\select.tsx": {
"mtime": 1778710280.6804683,
"hash": "594f45397eb2ac6bd77df7b7e37d5644"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\table.tsx": {
"mtime": 1778710280.681468,
"hash": "0e1676ab2a771ea388b5fa63036cd502"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\components\\ui\\textarea.tsx": {
"mtime": 1778710280.681468,
"hash": "4118508b3f819ceaee3b0d5f16bbb649"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\context\\AuthContext.tsx": {
"mtime": 1778710280.681468,
"hash": "44ab8c9079dffb96a8f0826540377cb3"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\lib\\buildingColors.ts": {
"mtime": 1778710280.6824672,
"hash": "b348eff15681381b572787a373b909ce"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\lib\\groupPostConditions.ts": {
"mtime": 1778710280.6824672,
"hash": "788b0f8427b263370cdab41d8850cf41"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\lib\\post-priority.ts": {
"mtime": 1778710280.6824672,
"hash": "94e7a97549b23802f43bb3f6ac6c28cc"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\lib\\postConditionTypes.ts": {
"mtime": 1778710280.6834679,
"hash": "3b06353374f1df46ea8de1cfd4afd788"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\lib\\useGroupByPreference.ts": {
"mtime": 1778710280.6834679,
"hash": "bb6b5032d31d41e56bfa0c1462240482"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\lib\\utils.ts": {
"mtime": 1778710280.6834679,
"hash": "d9837f38cc05303254571985e3164050"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\BoardPage.tsx": {
"mtime": 1778710280.6834679,
"hash": "ca60bdc402bf7fc73d45bf4a9786efa2"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\ComparisonPage.tsx": {
"mtime": 1778710280.6849875,
"hash": "5f40cf11f1c285fcc713c66f6b70cc7e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\LandingPage.tsx": {
"mtime": 1778710280.6849875,
"hash": "0592d360b4d150e643f2ed7d853cec37"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\LoginPage.tsx": {
"mtime": 1778710280.6849875,
"hash": "a7f86666a5e860fa06370cd44d5b2009"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\MemberDetailPage.tsx": {
"mtime": 1778710280.6849875,
"hash": "91d2961273fc9f2065dd82b95a4dd2e6"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\MembersPage.tsx": {
"mtime": 1778710280.6860092,
"hash": "0e20169e021eddba33936ff03cb09ed6"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\PostPrioritiesPage.tsx": {
"mtime": 1778710280.6860092,
"hash": "44ba6c946c49e4491f401a05ada7b0ce"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\PostsPage.tsx": {
"mtime": 1778710280.6860092,
"hash": "6299292c6d99e49321c62ebd458b5a3f"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\SiegeCreatePage.tsx": {
"mtime": 1778710280.6860092,
"hash": "2889e8903df72e1ab56a1a1a13ef0519"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\SiegeMembersPage.tsx": {
"mtime": 1778710280.687007,
"hash": "864268a2bc137e9b39782eb85260adff"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\SiegeSettingsPage.tsx": {
"mtime": 1778710280.687007,
"hash": "f594078ade4735208a55056c7cf4dd5f"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\SiegesPage.tsx": {
"mtime": 1778710280.687007,
"hash": "f93156052c5124138148122147b74acb"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\pages\\SystemPage.tsx": {
"mtime": 1778710280.687007,
"hash": "9332c96da0f1502dbb14d6f1d7164aaa"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\src\\types\\changelog.d.ts": {
"mtime": 1778710280.6940484,
"hash": "29f6e6e4d1a48ca3686c20d21c03dc3c"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\bootstrap-db.ps1": {
"mtime": 1778710280.7000616,
"hash": "2963ef492dbaa1e8aed3c7d0b42cb2bf"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\bootstrap-excel-import.ps1": {
"mtime": 1778710280.7010694,
"hash": "833c292e832a70a4d642e969418cec01"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\bootstrap-images.ps1": {
"mtime": 1778710280.701412,
"hash": "770e3450dec21a6498dd9380f2070049"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\bootstrap-keyvault.ps1": {
"mtime": 1778710280.701412,
"hash": "571c851084af0db873bdc82226b59797"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\bootstrap-reimport.ps1": {
"mtime": 1778710280.701412,
"hash": "39d7a16633e94d6cefeb8aca40862cff"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\generate-origin-pfx.ps1": {
"mtime": 1778710280.7034204,
"hash": "b0900217644486cdf9adf852befa4fde"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\rebuild.ps1": {
"mtime": 1778710280.7034204,
"hash": "1b5123763516e1ee3dbc308f2638eb4f"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\excel-import\\import_excel.py": {
"mtime": 1778710280.70243,
"hash": "4151fee1079d2b495340976e008542dd"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\excel-import\\tests\\__init__.py": {
"mtime": 1778710280.70243,
"hash": "d41d8cd98f00b204e9800998ecf8427e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\experiments\\run-graphify-dry-run.ps1": {
"mtime": 1778715097.451517,
"hash": "8a66f56768d8c9b2b04c9d03452a8285"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\CHANGELOG.md": {
"mtime": 1778710280.6144302,
"hash": "0c5a48d5f48933acad20d931797847b0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\CLAUDE.md": {
"mtime": 1778710280.614946,
"hash": "efb936d1d3711ed3ca65b394f9d6b9b2"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\CODE_OF_CONDUCT.md": {
"mtime": 1778710280.614946,
"hash": "9b2d51a163a531cb2f62e72697b0ccc1"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\CONTRIBUTING.md": {
"mtime": 1778710280.614946,
"hash": "f1099f1c0582333df2fabef4dccad223"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docker-compose.prod.yml": {
"mtime": 1778710280.6545522,
"hash": "e55ffd8443f4dd9cf1acb386a7956bfb"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docker-compose.yml": {
"mtime": 1778710280.6550682,
"hash": "376b6eb4a3cfab39144103477cea42cf"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\README.md": {
"mtime": 1778710280.615952,
"hash": "8b4ba27465aa3d5dfd16ed64c6cbf45f"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\SUPPORT.md": {
"mtime": 1778710280.615952,
"hash": "64c8848cde6a11b59d25bcc9b621e546"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\requirements-dev.txt": {
"mtime": 1778710280.640529,
"hash": "61ce4838a8959c3c183db9bab83b737d"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\backend\\requirements.txt": {
"mtime": 1778710280.640529,
"hash": "b06051b552c2ea4732940c0dbc2b9db0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\requirements-dev.txt": {
"mtime": 1778710280.6524813,
"hash": "cf6fc69a9af8b95f5758cac0a94bb9e6"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\bot\\requirements.txt": {
"mtime": 1778710280.6524813,
"hash": "fbf3dc8cb3581931efa8dc526f44f7ef"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\IMPLEMENTATION_PLAN.md": {
"mtime": 1778710280.6550682,
"hash": "5e4f9f914ca2ce9d67d6705a5933b7ba"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\RUNBOOK.md": {
"mtime": 1778710280.6555874,
"hash": "50fc656921246e9004108c9b114a09e5"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\siege_levels.md": {
"mtime": 1778710280.6587057,
"hash": "07d9f028f42a8b94ce3365a6f72bafc4"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\WEB_DESIGN_DOCUMENT.md": {
"mtime": 1778710280.656117,
"hash": "83b709cc8ac4aa14b027eda0a0c562b8"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\design-refs\\post-suggestions\\README.md": {
"mtime": 1778710280.6566384,
"hash": "de10b175d0df9256e5daeca315f7b252"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\experiments\\graphify-dry-run.md": {
"mtime": 1778711028.4433894,
"hash": "496df0deb7da1e88640f0a8bcdcb7e3b"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\mockups\\mockup-landing.html": {
"mtime": 1778710280.6571586,
"hash": "0548e8204463ddf92a09c5ce6f97fe34"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\mockups\\mockup-login.html": {
"mtime": 1778710280.6571586,
"hash": "7e1759c2d6a5fa6a3c4e5ed58777c289"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\mockups\\README.md": {
"mtime": 1778710280.6566384,
"hash": "fe28480a5cc9c486f4dc5d6be6c5f368"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\plans\\public-launch-self-hostable-and-landing-page.md": {
"mtime": 1778710280.657677,
"hash": "afed648bab4d1590d9b833607a4f7502"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\self-host\\anywhere.md": {
"mtime": 1778710280.6581955,
"hash": "2efab5a64706d9964b8fa13de3037449"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\self-host\\azure.md": {
"mtime": 1778710280.6587057,
"hash": "c9ee5c5fc8721e7c0ca2bb54da0782c9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\superpowers\\plans\\2026-04-08-discord-auth-implementation.md": {
"mtime": 1778710280.6592517,
"hash": "01ee8f99120035b952a2699b8a01eb6b"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\superpowers\\plans\\2026-05-08-component-versioning.md": {
"mtime": 1778710280.6597755,
"hash": "4eea9e7828ef6f2c88737f1f2feb1ee2"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\superpowers\\plans\\2026-05-09-post-suggestions.md": {
"mtime": 1778710280.6597755,
"hash": "3c89d2df41aebf5c5bc5add19cbd6281"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\superpowers\\plans\\discord-auth-plan.md": {
"mtime": 1778710280.6597755,
"hash": "bab1c00cda76f8ccaf26079a049ecf40"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\docs\\superpowers\\plans\\v1-release-plan.md": {
"mtime": 1778710280.6607814,
"hash": "f7ebc80091b9fc6eb0a5bf8661c0300e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\index.html": {
"mtime": 1778710280.6638145,
"hash": "dde055b245027a69a68049238ed823b3"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\graphify-out-with-tests\\graph.html": {
"mtime": 1778718921.8433201,
"hash": "b6226c2e30e0ccfbc56b15297319fa65"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\graphify-out-with-tests\\GRAPH_REPORT.md": {
"mtime": 1778718921.7281322,
"hash": "2c56ab55dda9d94cb5aab4e1a21a2cf1"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\infra\\README.md": {
"mtime": 1778710280.696556,
"hash": "3e76fbd727bcceeb02eb75d416c64aff"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\excel-import\\README.md": {
"mtime": 1778710280.701412,
"hash": "4a0e8a05091bd257fe417671858923a2"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\scripts\\excel-import\\requirements.txt": {
"mtime": 1778710280.70243,
"hash": "5e740ca3e2b112ec37fea64eae859ae0"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\wiki\\FAQ.md": {
"mtime": 1778710280.7034204,
"hash": "5e52ba23a226df064a14d75b1fd0d715"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\wiki\\Getting-Started.md": {
"mtime": 1778710280.7034204,
"hash": "f9dea57e10cfeada6f10255144249741"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\wiki\\Home.md": {
"mtime": 1778710280.7034204,
"hash": "32652bdc5aadfc11288cc5a4df5fe4a3"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\wiki\\Self-Host-on-Any-VPS.md": {
"mtime": 1778710280.7034204,
"hash": "178c7e9323276c57b1ce23786431f634"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\wiki\\Self-Host-on-Azure.md": {
"mtime": 1778710280.7049341,
"hash": "170d3ef1fbc3b5d52c18a8b6470951f5"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\wiki\\_Sidebar.md": {
"mtime": 1778710280.7049341,
"hash": "d92afc34965b5e470b581ce61e53b6ca"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\favicon.svg": {
"mtime": 1778710280.6663249,
"hash": "1544a9686a82728b10dd2d54a4c539b4"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\carousel-assignment-board.png": {
"mtime": 1778710280.6673253,
"hash": "138b785969e45c8eeec46b97c86dbfec"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\carousel-discord-image.png": {
"mtime": 1778710280.6673253,
"hash": "490ecc84ff80e40ca12651e2626bc424"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\carousel-member-management.png": {
"mtime": 1778710280.6688316,
"hash": "e87aa520a2aaecf3e9a1eea306182d0d"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\carousel-post-assignments.png": {
"mtime": 1778710280.6698372,
"hash": "79bdc954a2b7803d178cc4b3f2d7fbd8"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\carousel-siege-comparison.png": {
"mtime": 1778710280.6711512,
"hash": "913d494ae2e7465514441dec70f864c9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\carousel-validation-errors.png": {
"mtime": 1778710280.6721778,
"hash": "a6f43ec1ae7d66d5580b8b8d9d8c154e"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\hero-board.png": {
"mtime": 1778710280.6721778,
"hash": "4640413678efc867d27bceed64b60ea9"
},
"I:\\games\\raid\\siege-web\\.worktrees\\experiment-graphify-dry-run-doc\\frontend\\public\\landing\\og-image.png": {
"mtime": 1778710280.6731784,
"hash": "4640413678efc867d27bceed64b60ea9"
}
}
+194
View File
@@ -0,0 +1,194 @@
# Graphify Dry-Run — Results & Decision
**Date:** 2026-05-15
**Tool:** [`safishamsi/graphify`](https://github.com/safishamsi/graphify) (PyPI `graphifyy`)
**Tracking issue:** #397
**Runbook:** [`runbook.md`](./runbook.md)
**Source codebase:** [`glitchwerks/rsl-siege-manager`](https://github.com/glitchwerks/rsl-siege-manager) @ `6085fd66`
**Decision:** Do not adopt.
---
## TL;DR
The dry-run was executed twice against rsl-siege-manager at commit `6085fd66` — once excluding tests, once including them. Evaluated against the acid-test criteria from #397 (god nodes, surprising connections, and suggested questions should reflect things the user would nominate themselves), the tool failed on all three headline outputs. The without-tests run surfaces utilities and React page components alongside legitimate domain models; the with-tests run is dominated by test fixtures. Neither output justifies integrating a `PreToolUse` hook.
---
## What was run
Two runs at commit `6085fd66`:
| Run | Scope | Nodes | Edges | Communities | Extraction quality |
|-----|-------|-------|-------|-------------|-------------------|
| A (codebase only) | `.graphifyignore` excluded `backend/tests/`, `frontend/src/**/__tests__/` | 833 | 1758 | 52 | 85% EXTRACTED / 15% INFERRED (avg confidence 0.59) |
| B (codebase + tests) | No exclusions beyond vendored deps | 1886 | 3876 | 141 | 90% EXTRACTED / 10% INFERRED (avg confidence 0.62) |
Run cost: $0 token cost in both cases. graphify uses tree-sitter for code extraction; LLM API calls fire only for non-code files. After `.graphifyignore`, there were effectively none.
---
## Evaluation against the acid test
Issue #397 set this bar:
> "whether GRAPH_REPORT.md's named extractions (god nodes, surprising connections, suggested questions) reflect things the user would nominate themselves — generic graph-theory output ('`index.ts` is highly connected') means it didn't work."
Each finding below quotes the report directly. The raw artifacts are not committed (see "Artifact handling"), but the quotes are verifiable by re-running the dry-run against the same commit.
### Finding 1 — Test fixtures dominate "core abstractions" with tests included
Run B's god-node list, verbatim (`graphify-out-with-tests/GRAPH_REPORT.md`, lines 141–150):
```
1. `_make_siege()` - 124 edges
2. `_make_member()` - 92 edges
3. `_make_position()` - 85 edges
4. `SiegeMember` - 78 edges
5. `_make_building()` - 64 edges
6. `_make_group()` - 60 edges
7. `BoardPage()` - 55 edges
8. `makeSiegeMember()` - 55 edges
9. `PostsPage()` - 37 edges
10. `_session_with_siege_and_configs()` - 35 edges
```
Six of the top ten — positions 1, 2, 3, 5, 6, and 8 — are test factory functions (`_make_siege`, `_make_member`, `_make_position`, `_make_building`, `_make_group`, `makeSiegeMember`). Position 10 is a test session fixture. These are the highest-connectivity nodes in the graph, and they are infrastructure for verifying the codebase, not the codebase itself.
This is the cleanest failure signal in the dataset. The god-node list is explicitly labeled "your core abstractions." No developer would nominate `_make_siege()` as a core abstraction of a siege-assignment web app. The metric — degree centrality — is measuring test coverage, not architecture. The inversion is structural: the better your test coverage, the more your test factories saturate every part of the codebase, and the higher their degree count climbs. Codebases that most need architectural insight (low coverage, high complexity) will produce the cleanest god-node lists. Codebases that least need it (high coverage, known domain) will look like run B.
### Finding 2 — Codebase-only god nodes are surface-level
Run A's god-node list, verbatim (`graphify-out/GRAPH_REPORT.md`, lines 55–64):
```
1. `SiegeMember` - 55 edges
2. `BoardPage()` - 53 edges
3. `Post Suggestions Modal Handoff` - 40 edges
4. `postsTab` - 29 edges
5. `cn()` - 29 edges
6. `PostsPage()` - 28 edges
7. `BuildingType` - 27 edges
8. `MembersPage()` - 27 edges
9. `MemberRole` - 25 edges
10. `Self-Host on Azure Wiki Page` - 23 edges
```
`SiegeMember`, `BuildingType`, and `MemberRole` at positions 1, 7, and 9 are legitimate domain models a developer would identify as important. That is three of ten.
The remaining seven are less useful. `BoardPage()`, `PostsPage()`, and `MembersPage()` at positions 2, 6, and 8 are React top-level page components — they import many things because they are entry points, not because they encode domain logic. `cn()` at position 5 is a one-line Tailwind class-merging utility (`src/lib/utils.ts`). It scores 29 edges because every component that merges conditional classes imports it. This is the canonical generic-graph-theory false positive the runbook warned about: `cn()` is "highly connected" in the same way `/dev/null` would be if files wrote to it — the connection count reflects a structural pattern, not semantic importance. `Post Suggestions Modal Handoff` at position 3 is a planning document node extracted from `docs/design-refs/`, not a code entity at all. `Self-Host on Azure Wiki Page` at position 10 is a wiki article.
The acid test asks whether these are things the user would nominate. They are not.
### Finding 3 — Surprising connections cross language boundaries spuriously
Both runs surface INFERRED edges between Python backend types and TypeScript frontend types, presented under the heading "Surprising Connections (you probably didn't know these)." From run A (`graphify-out/GRAPH_REPORT.md`, lines 73–74):
```
- `AuthError` --uses--> `Member` [INFERRED]
backend/app/api/auth.py → frontend/src/api/types.ts
```
And from run B (`graphify-out-with-tests/GRAPH_REPORT.md`, lines 158–159):
```
- `TestStartupValidation` --uses--> `MemberRole` [INFERRED]
backend/tests/test_auth.py → frontend/src/api/types.ts
```
A Python class does not "use" a TypeScript type at runtime. These are name-based similarity matches — `AuthError` and `Member` share a namespace with identically-named constructs in the frontend — presented as semantic relationships. The confidence is low (avg INFERRED confidence 0.59 in run A, 0.62 in run B), but the framing ("you probably didn't know these") presents them as insights worth investigating. They are not.
The one genuinely plausible INFERRED connection in run A is:
```
- `PostPriorityResponse` --uses--> `PostPriorityConfig` [INFERRED]
backend/app/api/post_priority_config.py → frontend/src/api/posts.ts
```
The backend schema and the frontend type do mirror each other by contract. But that relationship is a deliberate design decision well-known to anyone who wrote it — not a discovery.
### Finding 4 — Community cohesion is universally weak
The majority of rsl-siege-manager's 52 communities (run A) score between 0.05 and 0.17 on cohesion. Run A community distribution:
- Communities 0, 2, 3, 6: cohesion 0.05–0.06 (the four largest communities, 36–112 nodes each)
- Most remaining communities: cohesion 0.08–0.17
- Cohesion above 0.25: six communities, all small (3–10 nodes)
The report itself flags cohesion 0.05 as "weakly interconnected" in the Suggested Questions section: "Should `Community 0` be split into smaller, more focused modules? — Cohesion score 0.05 - nodes in this community are weakly interconnected." Community 0 has 112 nodes.
rsl-siege-manager has a clean three-service architecture (backend / frontend / bot) that any developer can describe in a sentence. The graph algorithm is not finding that structure — it is producing communities with no legible semantic identity and flagging most of them as internally incoherent.
### Finding 5 — Noise inflated into knowledge gaps
Run A reports 259 isolated nodes; run B reports 752. The leading examples in both are identical (`graphify-out/GRAPH_REPORT.md`, line 213; `graphify-out-with-tests/GRAPH_REPORT.md`, line 447):
```
`initial schema Revision ID: 0001 Revises: Create Date: 2026-03-16`,
`add autofill and attack day preview columns to siege Revision ID: 0002 Revises:`,
`make siege date nullable Revision ID: 0003 Revises: 0002 Create Date: 2026-03-1`,
`Add post_priority_config table`, ...
```
These are Alembic migration revision docstrings parsed as standalone graph nodes. A migration docstring like `"make siege date nullable"` is a change-tracking annotation, not an architectural entity. There are 17 Alembic migration files in this repo. The isolated-node count growing from 259 to 752 when tests are added is almost entirely test docstrings and test function descriptions parsed the same way.
The report labels these as "possible documentation gaps or missing edges" — a framing that implies they are meaningful silences the developer should investigate. They are not gaps; they are noise.
### Finding 6 — Suggested questions are self-referential
The Suggested Questions section in both runs consists primarily of two kinds of questions:
1. **Betweenness centrality prompts:** "Why does `_make_siege()` connect Community 12 to Community 0, Community 1, Community 3...?" These ask the developer to explain why a test fixture has high betweenness. The answer is obvious: it creates the primary domain object; every test uses it.
2. **Inferred-edge audits:** "Are the 17 inferred relationships involving `SiegeMember` (e.g. with `Base` and `TestSeedDemoMembers`) actually correct?" This is the tool asking the developer to validate the tool's own low-confidence connections. It is not the tool answering questions about the codebase; it is the tool outsourcing its uncertainty.
Run A's most interesting question — "What is the exact relationship between `bootstrap-images.ps1` and `scripts/bootstrap-images.ps1`?" — is itself tagged AMBIGUOUS. The answer is: they are the same script referenced by two slightly different paths in different contexts. This is a file-naming question, not an architectural one.
---
## What graphify is good at
The throughput is fine: 833 nodes in a few minutes, $0 LLM cost on a code-heavy repo. The tree-sitter extraction is genuinely fast and does pick up real entities — `SiegeMember`, `BuildingType`, and `MemberRole` in the god-node list are correct. The `graph.html` interactive visualization is well-built and easy to navigate. The EXTRACTED cross-service connections are occasionally real: `PostPriorityResponse --uses--> PostPriorityConfig` between `backend/app/api/post_priority_config.py` and `frontend/src/api/posts.ts` reflects an actual contract.
The problem is not the underlying graph build. The problem is what the report does with it.
---
## Why this is a rejection, not a tuning problem
The headline outputs — god nodes, surprising connections, suggested questions — are what would justify wiring a `PreToolUse` hook that nudges the assistant to consult `GRAPH_REPORT.md` on every `grep`/`rg`/`find` invocation. If those outputs are mostly test fixtures (run B), surface-level import counts (run A), low-confidence cross-language inferences, and self-referential audit requests, then the hook would be steering the assistant's attention toward noise on every search. Tightening `.graphifyignore` further would fix run B's test-fixture problem, but it would not fix the architecture-of-the-metric problem visible in run A: degree centrality will always surface utilities and entry points over domain logic, regardless of what the ignore file excludes. The tool is measuring the right thing (connectivity) for the wrong purpose (identifying abstractions worth knowing about).
---
## Cost incurred vs cost avoided
**Cost incurred:**
- ~10 minutes of CLI time across two runs.
- $0 LLM cost (tree-sitter handled all code; no non-code files survived the ignore filter in any meaningful quantity).
**Cost avoided by not adopting:**
The two harness mutations from `graphify claude install` documented in #397:
1. **Global CLAUDE.md append** — a `# graphify` block appended to `~/.claude/CLAUDE.md` without a parseable fence marker, requiring manual cleanup on backout.
2. **Per-project `PreToolUse` hook** — fires on `Bash` commands containing `grep`/`rg`/`find` substrings. As noted in the runbook's Windows-specific gotchas section: the hook matcher is substring-based (`*grep*`), creating false-positive nudges on `pg_dump | grep`, `kubectl get pods | grep`, and similar. The hook also uses `python3` (not `python.exe`) which may silently no-op on Windows.
---
## Artifact handling
The raw outputs from both runs — `graph.html`, `graph.json`, `GRAPH_REPORT.md`, `cache/`, `manifest.json` (approximately 12 MB total) — are committed alongside this writeup under [`./graphify-out/`](./graphify-out/) and [`./graphify-out-with-tests/`](./graphify-out-with-tests/). The quoted findings above are auditable against the originals; the interactive `graph.html` files render the underlying force-directed graph if you want to explore.
Reproducing the run against a different revision: see [`runbook.md`](./runbook.md) for the methodology and acid-test criteria.
---
## Follow-up
- Issue tracking is disabled on this fork, so the PR that lands this evaluation is the durable record of the decision.
- No upstream contribution intended — this is an evaluation of graphify as a consumer, not a change request to the tool. The fork exists as a stable point to reference if upstream evolves.
---
🤖 _Generated by Claude Code on behalf of @cbeaulieu-gt_
+160
View File
@@ -0,0 +1,160 @@
# Graphify Dry-Run for rsl-siege-manager
> **Captured artifact.** This is the runbook as used during the [rsl-siege-manager evaluation](./results.md). Originally lived at `docs/experiments/graphify-dry-run.md` in that repo; relocated here so methodology and results stay together. References to siege-web-specific paths and cleanup commands are kept verbatim — they reflect the original context.
A scoped evaluation plan for [`safishamsi/graphify`](https://github.com/safishamsi/graphify) — a tool that builds a queryable knowledge graph (code + docs + PDFs + images) from a project folder and exposes it to AI coding assistants.
**Goal of this dry-run:** produce graphify's artifacts inside `rsl-siege-manager/graphify-out/` so I can judge whether the graph genuinely illuminates this codebase, **without** modifying my global `~/.claude/CLAUDE.md`, this repo's `.claude/settings.json`, or installing any harness hooks. Everything below is reversible by deleting one folder.
---
## Why this approach
Graphify's full install does two things:
1. **Global**: drops a skill at `~/.claude/skills/graphify/SKILL.md` and **appends a `# graphify` block to `~/.claude/CLAUDE.md`** (string-match guarded — not a fenced/marker block, so manual cleanup is needed if I back it out).
2. **Per-project**: appends a `## graphify` section to this repo's `CLAUDE.md` and registers a `PreToolUse` hook in `.claude/settings.json` that nudges the assistant to consult `GRAPH_REPORT.md` whenever a `Bash` command contains `grep`/`rg`/`find`/etc.
Both are reasonable but non-trivial mutations to harness configuration I tune carefully. The dry-run skips both, runs only the CLI, and lets me decide whether the artifact is worth the integration cost **after** seeing it.
---
## Step 1 — Install the CLI globally
```powershell
uv tool install graphifyy
```
> The PyPI package is `graphifyy` (double-y). The CLI command is `graphify`.
Verify it's on PATH:
```powershell
graphify --version
```
If "not recognized," open a new PowerShell window — `uv tool` updates PATH but the current session won't see it.
---
## Step 2 — Optional: gate the first build with `.graphifyignore`
Cheap insurance. Each non-code file (markdown, PDF, image) becomes an LLM API call during the first build. A `.graphifyignore` (same syntax as `.gitignore`, including `!` negation) keeps vendored deps and build output out of scope.
From the rsl-siege-manager root:
```powershell
@'
node_modules/
dist/
build/
.next/
.venv/
coverage/
*.generated.*
*.min.js
'@ | Set-Content -Encoding UTF8 .graphifyignore
```
Adjust to rsl-siege-manager's actual layout. If unsure what to ignore, skip this step — the build will still work, just bigger.
---
## Step 3 — Build the graph (the only step with $ cost)
From the rsl-siege-manager root:
```powershell
graphify extract .
```
> **Estimated cost:** `$0.10–$2.00` depending on the number of docs, PDFs, and images that survive the ignore filter. Watch terminal output; `Ctrl+C` is safe at any point.
What happens under the hood:
| Content type | Mechanism | Cost |
| --- | --- | --- |
| Code (29 languages, AST via tree-sitter) | Local | Free |
| Docs, markdown, PDFs, images, videos | LLM API call per file | $$ |
Watch terminal output. If it's processing far more files than expected, `Ctrl+C` and tighten `.graphifyignore` before re-running.
Check the cost summary when it finishes:
```powershell
Get-Content .\graphify-out\cost.json
```
---
## Step 4 — Inspect the three artifacts
```powershell
# The headline summary the assistant would read on every query
code .\graphify-out\GRAPH_REPORT.md
# The interactive graph (click nodes, filter, search)
Start-Process .\graphify-out\graph.html
# The raw graph data (used by the query commands below)
Get-Content .\graphify-out\graph.json | Select-Object -First 50
```
**The honest evaluation lives in `GRAPH_REPORT.md`, not `graph.html`.** The viz is dopamine — colorful and clickable on any codebase. The report's "god nodes," "surprising connections," and "suggested questions" sections are what tell me whether the LLM extraction actually *understood* rsl-siege-manager. If those sections name files/concepts I'd nominate myself, it worked. If they're generic ("`index.ts` is highly connected") or wrong, it didn't.
---
## Step 5 — Try query commands without any hook wiring
These work directly off `graphify-out/graph.json` — no skill registration, no settings.json mutation:
```powershell
graphify query "how does authentication flow through the app?"
graphify query "what connects the database layer to the API routes?"
graphify path "<some-component-name>" "<some-service-name>"
graphify explain "<a-concept-i-want-mapped>"
```
**Acid test:** ask 3–5 questions I already know the answer to. If it answers them correctly, the tool can help with the ones I don't.
---
## Step 6 — Decide
| Outcome | Next move |
| --- | --- |
| Graph is genuinely illuminating | Run `graphify claude install` (accept the CLAUDE.md / `.claude/settings.json` mutations) |
| Useful but not always-on | Keep the CLI, rebuild manually when useful, query from terminal — no install |
| Generic or wrong | Delete and move on |
---
## Abort / full cleanup
Nothing outside `rsl-siege-manager/graphify-out/` was touched. To roll back completely:
```powershell
Remove-Item -Recurse -Force .\graphify-out
Remove-Item .\.graphifyignore # if added and unwanted
Remove-Item .\docs\experiments\graphify-dry-run.md # this file
uv tool uninstall graphifyy # remove the CLI itself
```
After this, the repo is byte-identical to before the dry-run, and `~/.claude/CLAUDE.md` was never modified.
---
## Windows-specific gotchas to watch for
- **`/graphify extract .` will fail in PowerShell.** PowerShell treats `/` as a path separator. Use `graphify extract .` (no leading slash). The slash form only works inside an AI coding assistant's chat prompt.
- **The PreToolUse hook graphify installs uses `python3`.** Windows installers typically register `python.exe`, not `python3.exe`. If I do eventually run `graphify claude install`, the hook's `python3 -c "..."` may silently no-op (it's wrapped in `2>/dev/null || true`) — meaning the nudge never fires and I won't know.
- **The hook also relies on Bash `case` syntax.** Claude Code on Windows routes hook commands through Git Bash, so this works — but it's an implicit dependency on Git Bash being present.
- **Hook matcher is substring-based on `*grep*`.** That catches `pg_dump | grep`, `kubectl get pods | grep`, anything-grep. False-positive nudges are low cost but worth knowing.
---
## Reference
- Repo: https://github.com/safishamsi/graphify
- PyPI: https://pypi.org/project/graphifyy/
- Repo signals at evaluation time (2026-05-13): 47k stars, 5k forks, MIT, ~5 weeks old. Star count is anomalously high for age — treat it as marketing signal, not quality signal. Evaluate the artifact, not the badges.