Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
379 commits
Select commit Hold shift + click to select a range
f7df1c0
refactor(ui): move device badges from model section to header, add re…
JayantDevkar Mar 9, 2026
a331070
fix(sync): remove hostname-based device acceptance, improve pending r…
JayantDevkar Mar 9, 2026
bd10832
feat(sync): add pending device accept/dismiss UI, fix member name val…
JayantDevkar Mar 9, 2026
3c223e3
fix(sync): detect own outbox using both user_id and machine_id
JayantDevkar Mar 9, 2026
5dc837f
fix(sync): trigger immediate remote reindex after sync actions
JayantDevkar Mar 9, 2026
f213e5c
feat(sync): remote skill inheritance — fix classification, extract co…
JayantDevkar Mar 9, 2026
121db03
feat(analytics): per-device color breakdown in charts
JayantDevkar Mar 9, 2026
e37d2b7
fix(schema): remove COALESCE expression from skill_definitions PRIMAR…
JayantDevkar Mar 9, 2026
21b137b
fix(sync): render remote sessions immediately + watch for Syncthing a…
JayantDevkar Mar 9, 2026
4715ea0
refactor(sync): code review — extract modules, fix error handling, co…
JayantDevkar Mar 9, 2026
076bdde
feat(skills): fix classification, extraction, per-device analytics, a…
JayantDevkar Mar 9, 2026
4bbed10
feat(types): add TeamSessionStat interface for team session stats
JayantDevkar Mar 9, 2026
ce735d2
feat(team): expand member color palette from 8 to 16 colors
JayantDevkar Mar 9, 2026
b415042
feat(api): add team session-stats endpoint for per-member daily aggre…
JayantDevkar Mar 9, 2026
82b8475
feat(team): add TeamOverviewTab component with stats and sent/receive…
JayantDevkar Mar 9, 2026
460e9e2
feat(team): add TeamMembersTab with sparkline charts and color-coded …
JayantDevkar Mar 9, 2026
635e61b
feat(team): add TeamProjectsTab with per-project member contribution …
JayantDevkar Mar 9, 2026
e90eeea
feat(team): add TeamActivityTab with line chart, period selector, and…
JayantDevkar Mar 9, 2026
109c880
feat(team): convert team detail page to tabbed layout with Overview/M…
JayantDevkar Mar 9, 2026
cd17e0e
fix(team): address code review findings
JayantDevkar Mar 9, 2026
715ec40
feat(team): fix runtime errors, data display, and UX improvements for…
JayantDevkar Mar 9, 2026
b6f9b6d
perf(api): optimize project detail endpoint to prevent JSONL fallback…
JayantDevkar Mar 9, 2026
7db5c2c
fix(skills): correct remote-only tagging and enable Inherit Skill for…
JayantDevkar Mar 9, 2026
49c0853
feat(member): add member detail page with tabbed layout at /members/[…
JayantDevkar Mar 9, 2026
2927921
feat(skills): inherit remote plugin skills as local invocable skills
JayantDevkar Mar 9, 2026
d368bc8
fix(frontend): consistent session card grid width and prevent footer …
JayantDevkar Mar 9, 2026
8e858aa
fix(sync): resolve clean user_id from manifest and persist remote ses…
JayantDevkar Mar 9, 2026
4970acd
feat(team): redesign project Team tab with rich data from SQLite
JayantDevkar Mar 9, 2026
95f91a4
refactor(sync): address code review findings for remote user_id resol…
JayantDevkar Mar 9, 2026
f651b3b
fix(team): resolve remote user directory mismatch, revert activity gr…
JayantDevkar Mar 9, 2026
770f978
fix(frontend): fix By Member tab on skills/agents/tools pages
JayantDevkar Mar 9, 2026
9a09a64
feat(members): promote members page to top-level route with device_id…
JayantDevkar Mar 9, 2026
49c0ed8
fix(members): extract shared identifier resolver, restore input valid…
JayantDevkar Mar 9, 2026
d25f94e
feat(members): add "You" badge for local user on members page and pro…
JayantDevkar Mar 9, 2026
0c86683
feat(members): redesign sessions tab with rich cards, search, and fil…
JayantDevkar Mar 9, 2026
7a8b77e
fix(members): redesign profile header, fix breadcrumb nav, add skelet…
JayantDevkar Mar 10, 2026
acf3623
feat(home): add Members card with rose theme, remove About, rearrange…
JayantDevkar Mar 10, 2026
a14d304
fix(team): poll session stats and align pending folders filter
JayantDevkar Mar 10, 2026
1946889
fix(sync): exclude live sessions from packaging to prevent syncing in…
JayantDevkar Mar 10, 2026
a709f28
fix(team): optimize member profile queries and add session pagination
JayantDevkar Mar 10, 2026
fdbb506
fix(team): correct session stats semantics for sent/received counts
JayantDevkar Mar 10, 2026
e282ae2
fix(team): fix team overview bar chart to use combined session counts
JayantDevkar Mar 10, 2026
111088b
feat(team): add in/out session bars to team and member overview charts
JayantDevkar Mar 10, 2026
0e516c9
fix(sync): accurate per-session event logging, local timezone dates, …
JayantDevkar Mar 10, 2026
b478a34
fix(sync): address code review findings from bf5f0b8
JayantDevkar Mar 10, 2026
0ebca1f
fix(sync): use device_id in manifest for reliable member identity map…
JayantDevkar Mar 10, 2026
1972d21
refactor(frontend): consolidate initial prompt display utilities
JayantDevkar Mar 10, 2026
cad5253
fix(sync): deduplicate session_received events inflating team session…
JayantDevkar Mar 10, 2026
7a34ebd
ui(team): make join code more prominent with invite card redesign
JayantDevkar Mar 10, 2026
ef304f3
docs: rewrite about pages for clarity, remove IPFS, add About to nav
JayantDevkar Mar 10, 2026
c32d2d4
fix(sync): enable mesh device discovery and fix broken accept flow
JayantDevkar Mar 10, 2026
fe8ba2e
fix(sync): resolve hyphenated usernames correctly in folder ID parsing
JayantDevkar Mar 10, 2026
39fb184
fix(sync): fix dead code in folder ID parsing and reduce N+1 queries
JayantDevkar Mar 10, 2026
31323b9
test(sync): update add_device mock assertion for introducer parameter
JayantDevkar Mar 10, 2026
06d9c2b
fix(sync): prevent unbound proxy variable and simplify exception catch
JayantDevkar Mar 10, 2026
e5dd4f5
refactor(sync): consolidate folder ID parsing and fix lossy path reso…
JayantDevkar Mar 10, 2026
be9fb41
feat(sync): add proper cleanup when removing a team member
JayantDevkar Mar 10, 2026
cd79e21
fix(sync): include reconciled device count in pending-devices response
JayantDevkar Mar 10, 2026
b9468a7
fix(sync): address 5 code review findings in reconciliation and membe…
JayantDevkar Mar 10, 2026
5c418c1
feat(sync): add sync settings table and policy enforcement layer
JayantDevkar Mar 10, 2026
262b262
feat(sync): add member-level sync direction settings UI
JayantDevkar Mar 10, 2026
e6866c2
feat(sync): redesign settings UI and default auto-accept to off
JayantDevkar Mar 10, 2026
ba3c964
refactor(sync): rewrite folder IDs with double-dash delimiter and add…
JayantDevkar Mar 10, 2026
8893006
refactor(sync): split sync_status.py god router into 7 routers + 3 se…
JayantDevkar Mar 10, 2026
1cbe015
fix(sync): harden policy defaults and add missing team endpoint
JayantDevkar Mar 10, 2026
8eee731
refactor(sync): simplify control layer — remove redundancy and dead code
JayantDevkar Mar 10, 2026
64b712e
fix(sync): reconcile handshake folders from already-paired devices
JayantDevkar Mar 10, 2026
7183e11
fix(sync): make query layer multi-team aware for devices in multiple …
JayantDevkar Mar 10, 2026
f5ead02
fix(sync): address code review findings from multi-team commit
JayantDevkar Mar 10, 2026
797cab3
fix(ci): resolve API test and frontend lint failures
JayantDevkar Mar 10, 2026
88652c1
fix(sync): clear sync_settings and sync_removed_members tables on reset
JayantDevkar Mar 11, 2026
52051d1
fix(sync): heal hostname-style member names and fix frontend issues
JayantDevkar Mar 11, 2026
4c92601
fix(sync): sanitize hostname fallback and strengthen healing logic
JayantDevkar Mar 11, 2026
f595295
feat(sync): backfill session titles and share plans via Syncthing
JayantDevkar Mar 11, 2026
5e244dc
fix(ui): use consistent member colors and remove misleading sync pill
JayantDevkar Mar 11, 2026
1ba15d4
fix(sync): correct introduced device naming and prevent duplicate mem…
JayantDevkar Mar 11, 2026
0a1e2df
fix(sync): Phase 0 — six independent bug fixes for sync layer
JayantDevkar Mar 11, 2026
35ec5f7
feat(sync): add machine_tag and member_tag to SyncConfig
JayantDevkar Mar 11, 2026
2714ebc
feat(sync): schema v17 — add device identity columns to sync_members
JayantDevkar Mar 11, 2026
6d9823c
feat(sync): add parse_member_tag to folder_id.py
JayantDevkar Mar 11, 2026
09b636e
feat(sync): switch sync_folders, reconciliation, and pending to membe…
JayantDevkar Mar 11, 2026
4acae6a
feat(sync): update all routers to use member_tag identity
JayantDevkar Mar 11, 2026
34d1358
feat(sync): update remote_sessions and packager for member_tag identity
JayantDevkar Mar 11, 2026
5328968
fix(sync): trust DB over manifest for user_id resolution + cleanup
JayantDevkar Mar 11, 2026
3568063
feat(sync): add sync_metadata.py for team metadata folder helpers
JayantDevkar Mar 11, 2026
4940314
feat(sync): create karma-meta--{team} folder on team create/join
JayantDevkar Mar 11, 2026
5c31b9d
feat(sync): write member state + removal signals to metadata folder
JayantDevkar Mar 11, 2026
adcce7a
feat(sync): metadata folder reconciliation
JayantDevkar Mar 11, 2026
b25d233
feat(sync): auto-leave on removal + watcher metadata reconciliation
JayantDevkar Mar 11, 2026
d072be3
fix(sync): harden Phase 2 metadata layer — atomic writes, path saniti…
JayantDevkar Mar 11, 2026
72ae20f
fix(tests): fix 6 syncthing_proxy tests failing when Syncthing runs l…
JayantDevkar Mar 11, 2026
5f5b4f0
feat(sync): Phase 3 UX polish — persistent rejection, subscriptions, …
JayantDevkar Mar 11, 2026
9f5637a
fix(sync): use member_tag instead of user_id for folder IDs in CLI paths
JayantDevkar Mar 11, 2026
68d7f4c
fix(sync): reset endpoint now cleans v2 artifacts + stale v1 DBs
JayantDevkar Mar 11, 2026
d3148d1
fix(sync): patch sync_members columns before creating member_tag index
JayantDevkar Mar 11, 2026
a1676c1
fix(sync): resolve 3 bugs preventing third member from receiving fold…
JayantDevkar Mar 11, 2026
c803e80
fix: display total session count from API when not all sessions are l…
the-non-expert Mar 11, 2026
30af10d
fix(sync): filter configured folders from pending + fix duplicate key…
JayantDevkar Mar 11, 2026
8a93cc6
fix(sync): deduplicate remote sessions across paginated responses
JayantDevkar Mar 11, 2026
19cf903
fix(sync): check all indexed UUIDs when deduping remote sessions
JayantDevkar Mar 11, 2026
5c26c35
fix(sync): correct member count and online status across sync/team pages
JayantDevkar Mar 11, 2026
babbda8
feat(members): improve member page UX and link remote user badges
JayantDevkar Mar 11, 2026
03aee4b
fix(members): populate received session counts in member profile
JayantDevkar Mar 11, 2026
9dcf4c0
fix(charts): resolve CSS variables for Chart.js dark mode support
JayantDevkar Mar 11, 2026
bc273cf
fix(plans): improve remote plan detection, UX, and session linking
JayantDevkar Mar 11, 2026
f64d482
fix(plugins): detect skills from manifest custom paths
JayantDevkar Mar 12, 2026
8c3e6fc
fix(ui): polish Team, Members, and Sync pages for consistent dashboar…
JayantDevkar Mar 12, 2026
e61e297
feat(sync): share skill definition content via manifest
JayantDevkar Mar 13, 2026
5200f7e
fix(api): return full initial_prompt in session detail endpoint
JayantDevkar Mar 13, 2026
ccae4eb
audit file
JayantDevkar Mar 13, 2026
64dabea
docs: add v2 sync testing findings from ayush
the-non-expert Mar 13, 2026
1a037c6
docs(sync): add v3 architecture design, fix handshake auto-accept bypass
JayantDevkar Mar 13, 2026
3c731bd
feat(sync): implement v3 Phase 1 — declarative device lists
JayantDevkar Mar 13, 2026
0db22ac
feat(sync): implement v3 Phase 2 — explicit mesh pairing
JayantDevkar Mar 13, 2026
40c0991
fix(sync): team-scope unreject_folder to prevent cross-team unrejection
JayantDevkar Mar 13, 2026
a2848fa
feat(sync): implement v3 Phase 3 — cross-team safe cleanup
JayantDevkar Mar 13, 2026
34db387
feat(sync): implement v3 Phase 4 — edge cases & hardening
JayantDevkar Mar 13, 2026
db1b396
fix(sync): hoist N+1 API call, reorder member removal for immediate c…
JayantDevkar Mar 13, 2026
25e46c1
fix(sync): resolve Svelte 5 warnings and auto-sync on watcher start
JayantDevkar Mar 13, 2026
4520292
feat(sync): handle non-git projects and missing-member edge cases (AD…
JayantDevkar Mar 14, 2026
fdcd95b
feat(sync): add sync philosophy docs, setup wizard visuals, and auto-…
JayantDevkar Mar 14, 2026
aaeef39
fix(sync): wire full 6-phase reconciliation into 60s timer
JayantDevkar Mar 14, 2026
84a36a1
fix(sync): package all existing sessions on watcher start
JayantDevkar Mar 14, 2026
b2e0ea9
fix(sync): show projects from all teams on sync overview page
JayantDevkar Mar 14, 2026
2c952a7
fix(sync): sync project list via metadata to fix cross-team attribution
JayantDevkar Mar 14, 2026
606bfaa
fix(sync): reconcile projects from metadata on pending endpoint
JayantDevkar Mar 14, 2026
19d173e
refactor(sync): replace watcher with manual sync-now model
JayantDevkar Mar 14, 2026
e0ef9ad
fix(sync): harden sync logic — 4 defensive fixes from code review
JayantDevkar Mar 14, 2026
dccf3fa
fix(sync): resolve duplicate pending requests caused by wrong member …
JayantDevkar Mar 14, 2026
a12f75b
fix(sync): address code review — deduplicate hostname suffixes, add .…
JayantDevkar Mar 14, 2026
07d5efe
fix(sync): resolve inbox to local project when metadata has remote en…
JayantDevkar Mar 14, 2026
22641be
feat(sync): support remote-only projects in listing and detail views
JayantDevkar Mar 14, 2026
04c192e
fix(sync): nuke now cleans orphaned project rows and title caches
JayantDevkar Mar 14, 2026
5b0ceaf
docs: add sync v4 domain models design spec
JayantDevkar Mar 18, 2026
dcd511d
docs: address spec review — fix PK to git_identity, add missing sections
JayantDevkar Mar 18, 2026
76e51a1
docs: fix remaining spec review gaps (N1, N3, N5-N7)
JayantDevkar Mar 18, 2026
e5f6560
docs: add sync v4 implementation plan — master + 4 phases
JayantDevkar Mar 18, 2026
a4912de
feat(sync-v4): add Team domain model
JayantDevkar Mar 18, 2026
0bf4786
feat(sync-v4): add Member domain model
JayantDevkar Mar 18, 2026
7458fe3
feat(sync-v4): add SharedProject domain model
JayantDevkar Mar 18, 2026
6ca6355
feat(sync-v4): add Subscription domain model
JayantDevkar Mar 18, 2026
276ba14
feat(sync-v4): add SyncEvent domain model
JayantDevkar Mar 18, 2026
877cc36
feat(sync-v4): add Syncthing abstraction layer + PairingService
JayantDevkar Mar 18, 2026
1022e54
fix(sync-v4): relax pairing code block size assertion (last block can…
JayantDevkar Mar 18, 2026
8ecea29
feat(sync-v4): add v19 schema migration — clean slate sync tables
JayantDevkar Mar 18, 2026
0278f4a
fix(sync-v4): align domain models with spec + add repositories
JayantDevkar Mar 18, 2026
a77e3eb
feat(sync-v4): add MetadataService — read/write team metadata folders
JayantDevkar Mar 18, 2026
2f80280
feat(sync-v4): add TeamService + ProjectService — business orchestration
JayantDevkar Mar 18, 2026
5cec66b
feat(sync-v4): add ReconciliationService — 3-phase pipeline
JayantDevkar Mar 18, 2026
ce58538
test(sync-v4): add E2E smoke test — complete sync lifecycle
JayantDevkar Mar 18, 2026
b90b720
feat(sync-v4): rewrite FastAPI routers — 4 thin v4 routers + shared deps
JayantDevkar Mar 18, 2026
a4d5741
feat(sync-v4): rewrite WatcherManager — uses ReconciliationService
JayantDevkar Mar 18, 2026
16fec93
chore(sync-v4): delete v3 sync files — replaced by domain model archi…
JayantDevkar Mar 18, 2026
4bf17b8
fix(sync-v4): address code review — 7 critical + important issues
JayantDevkar Mar 18, 2026
f95dcfc
feat(sync-v4): add missing endpoints + align frontend with v4 API
JayantDevkar Mar 18, 2026
55f00be
docs: update about pages for sync v4 — subscriptions, join codes, ful…
JayantDevkar Mar 18, 2026
8a958a3
fix(sync-v4): remove v3 join-team flow and join-code UI
JayantDevkar Mar 18, 2026
c73ed13
feat(sync-v4): add "Add Member" button with pairing code input
JayantDevkar Mar 18, 2026
7ab68d3
feat(sync-v4): redesign sync UI/UX — pairing codes, onboarding, subsc…
JayantDevkar Mar 18, 2026
8978aba
fix(sync-v4): switch pairing codes from base32 to base64url — 22% sho…
JayantDevkar Mar 18, 2026
51f981b
fix(sync-v4): fix Syncthing API URLs, activity endpoint, and a11y war…
JayantDevkar Mar 18, 2026
f9dee84
feat(sync-v4): complete joiner flow — immediate sharing, team discove…
JayantDevkar Mar 18, 2026
751d9f4
feat(sync-v4): add joiner pending invitations UI — device card, proje…
JayantDevkar Mar 18, 2026
75ed4a8
fix(sync-v4): fix team page data issues — leave endpoint, project-sta…
JayantDevkar Mar 18, 2026
333e292
fix(sync-v4): role-gate leader-only actions in Members and Projects tabs
JayantDevkar Mar 18, 2026
56395ff
refactor(sync-v4): address code review — 11 issues across CRITICAL/HI…
JayantDevkar Mar 18, 2026
73dfb63
fix(sync-v4): move PendingInvitationCard fetch into onMount — fix SSR…
JayantDevkar Mar 18, 2026
0065b36
fix(sync-v4): team list returns members/projects, detect returns expe…
JayantDevkar Mar 18, 2026
9e408e5
fix(sync-v4): exclude self from all member counts across sync/team pages
JayantDevkar Mar 18, 2026
2f3fba7
fix(sync-v4): add member_count to /sync/status team data
JayantDevkar Mar 18, 2026
966d1d0
fix(sync-v4): register metadata folder with Syncthing on team create
JayantDevkar Mar 18, 2026
cacec01
fix(sync-v4): fix pending invitation detection — offeredBy parsing + …
JayantDevkar Mar 18, 2026
937df4c
fix(sync-v4): fix "team not found" after accepting invitation — recon…
JayantDevkar Mar 18, 2026
27284ae
fix(sync-v4): fix infinite $effect loop on team page — read data.team…
JayantDevkar Mar 18, 2026
ae3ebd3
fix(sync-v4): make add_member Syncthing calls best-effort
JayantDevkar Mar 18, 2026
c4c22f3
fix(sync-v4): unified metadata writer + positive project discovery
JayantDevkar Mar 18, 2026
93b7d59
fix(sync-v4): use DB subscriptions for project notifications, add dir…
JayantDevkar Mar 18, 2026
f34ee7c
fix(sync-v4): redesign Projects tab — remove v3 pending folders, add …
JayantDevkar Mar 18, 2026
79d2d60
fix(sync-v4): use git remote URL as git_identity for cross-machine ma…
JayantDevkar Mar 18, 2026
a4bd85f
fix(sync-v4): fix each_key_duplicate crash on /sync OverviewTab
JayantDevkar Mar 18, 2026
2461a40
fix(sync-v4): fix second each_key_duplicate crash in sync OverviewTab
JayantDevkar Mar 18, 2026
48fa6fd
fix(sync-v4): backfill OFFERED subscriptions when member activated
JayantDevkar Mar 18, 2026
7a8b36a
fix(sync-v4): enable bidirectional session sync — outbox packaging + …
JayantDevkar Mar 18, 2026
40c4112
fix(sync-v4): create ACCEPTED subscription for leader when sharing pr…
JayantDevkar Mar 18, 2026
4d297b9
fix(sync-v4): sync subscription status from peer metadata in Phase 1
JayantDevkar Mar 19, 2026
c31c59e
fix(sync-v4): resolve remote sessions to correct local project via DB…
JayantDevkar Mar 19, 2026
4efc6b6
fix(sync-v4): add cross-team safety to folder cleanup — skip folders …
JayantDevkar Mar 19, 2026
26efae5
fix(sync-v4): Phase 3 ensures outbox folders exist before setting dev…
JayantDevkar Mar 19, 2026
0d30952
refactor(sync-v4): remove dead CLI sync code + fix pre-existing test
JayantDevkar Mar 19, 2026
502d632
test(sync-v4): add multi-team overlap E2E test + fix unpair cross-tea…
JayantDevkar Mar 19, 2026
f3534dd
fix(sync-v4): drop sync_projects in v19 migration to fix fresh-instal…
JayantDevkar Mar 19, 2026
28f43bf
docs: add sync v4 cleanup & cross-team safety implementation plan
JayantDevkar Mar 19, 2026
0c746c7
feat(sync-v4): add /sync/members endpoints and session counts to proj…
JayantDevkar Mar 19, 2026
f8481b6
fix(sync-v4): add missing schema columns + update stale test assertions
JayantDevkar Mar 19, 2026
88eaf00
fix(sync-v4): fix sessions_sent count and self member profile lookup
JayantDevkar Mar 19, 2026
4649af5
fix(sync-v4): fix 404 when deleting already-removed project from team
JayantDevkar Mar 19, 2026
3ae3f45
docs(sync): add unsynced sessions design spec and implementation plan
JayantDevkar Mar 19, 2026
7b16608
refactor(sync): extract PackagingService from watcher_manager
JayantDevkar Mar 19, 2026
021cf8f
feat(sync): add POST /sync/package endpoint for on-demand packaging
JayantDevkar Mar 19, 2026
a3e307e
fix(sync): exclude active sessions from gap calculation, add active_c…
JayantDevkar Mar 19, 2026
a422f9c
feat(sync): break SyncProjectStatus into own interface with active_count
JayantDevkar Mar 19, 2026
e36cbe5
feat(sync): add sync badges and per-project/team sync buttons to Team…
JayantDevkar Mar 19, 2026
4dcce46
fix(sync): Sync Now button now packages sessions + reconciles devices
JayantDevkar Mar 19, 2026
08ea692
fix(sync-v4): resolve 5 bugs from feature definition review
JayantDevkar Mar 19, 2026
2a2abdc
docs(sync): add member page improvements design spec
JayantDevkar Mar 20, 2026
37f60f3
docs(sync): add member page improvements implementation plan
JayantDevkar Mar 20, 2026
2a442ba
feat(sync): add get_all_by_member_tag and get_by_user_id to MemberRep…
JayantDevkar Mar 20, 2026
ef1df77
fix(sync): normalize remote_user_id to always store member_tag
JayantDevkar Mar 20, 2026
e1c3f35
feat(sync): switch member API from device_id to member_tag identifier
JayantDevkar Mar 20, 2026
ee9b5a4
feat(sync): add MemberProjectSync and sync health fields to MemberPro…
JayantDevkar Mar 20, 2026
aa0549f
feat(sync): rename member route to [member_tag] and fix remote sessio…
JayantDevkar Mar 20, 2026
3b03485
feat(sync): add clickable navigation from team members tab to member …
JayantDevkar Mar 20, 2026
5a8a9d3
feat(sync): update member page header with sync health metadata
JayantDevkar Mar 20, 2026
dd0b39d
feat(sync): add Sync Health card and unsynced stat to member overview
JayantDevkar Mar 20, 2026
604db81
Merge remote-tracking branch 'origin/main' into worktree-syncthing-sy…
JayantDevkar Mar 20, 2026
866dc71
chore: remove accidentally added git-radio subdir, add to .gitignore
JayantDevkar Mar 20, 2026
cedbf4f
refactor: address 6 systemic design issues from code review
JayantDevkar Mar 20, 2026
66ae91f
fix: skip empty prompts in get_initial_prompt for bare command invoca…
JayantDevkar Mar 20, 2026
ed007b6
fix: apply extract_prompt_from_content across all 6 extraction points
JayantDevkar Mar 20, 2026
d4a6f29
fix(perf): SQLite fast path never returned when no remote sessions
JayantDevkar Mar 20, 2026
ec25c5a
fix(ui): link commands to /commands page instead of broken modal
JayantDevkar Mar 20, 2026
179ba6b
feat: add git-radio submodule with sync-v4 cross-machine test scenario
JayantDevkar Mar 21, 2026
f82621e
fix: add check_same_thread=False to all SQLite connection factories
JayantDevkar Mar 21, 2026
c5c4b73
fix(sync): centralized folder path resolver + soft-delete on team dis…
JayantDevkar Mar 21, 2026
998b560
chore: update git-radio submodule — browser action type for UI testing
JayantDevkar Mar 21, 2026
0e2cb65
fix(sync): purge stale removal signals on team creation
JayantDevkar Mar 21, 2026
dd7edd3
fix(sync): skip stale removal signals from previous team incarnations
JayantDevkar Mar 21, 2026
b3e97f4
fix(sync): scan pending folders in phase_team_discovery
JayantDevkar Mar 21, 2026
48a03e0
fix(sync): poll for team after Accept & Pair
JayantDevkar Mar 21, 2026
bcb64be
fix(sync): add 60s grace period to staleness check
JayantDevkar Mar 21, 2026
1a4267e
chore: update git-radio submodule — field report from browser test
JayantDevkar Mar 21, 2026
24fb5f1
chore: update git-radio submodule — value report from sync v4 testing
JayantDevkar Mar 21, 2026
b6176b2
chore: update git-radio submodule — subscription lifecycle scenario
JayantDevkar Mar 21, 2026
733af47
chore: update git-radio submodule — subscription lifecycle scenario v2
JayantDevkar Mar 21, 2026
b87ad0f
fix(sync): resolve 5 subscription lifecycle bugs from cross-machine test
JayantDevkar Mar 21, 2026
e676ac7
chore: update git-radio submodule — member page verification scenario
JayantDevkar Mar 23, 2026
0db3941
fix(sync): resolve 6 member page bugs from cross-machine walkie test
JayantDevkar Mar 23, 2026
24ce468
fix(sync): strengthen reset — outbox dirs, remote sessions, quarantin…
JayantDevkar Mar 23, 2026
b0490d4
fix(sync): auto-resolve encoded_name when sharing project
JayantDevkar Mar 23, 2026
bfcab6c
fix(sync): robust encoded_name resolution — exact suffix match
JayantDevkar Mar 23, 2026
dcc1242
refactor(sync): 4 architectural improvements from review + status report
JayantDevkar Mar 23, 2026
c6aca24
docs: add PM-facing timeline to sync v4 status report
JayantDevkar Mar 23, 2026
b9a71aa
chore: update git-radio submodule — team/members/overview test scenario
JayantDevkar Mar 23, 2026
2ed8c8f
fix(sync): add team_id column to v19 migration CREATE TABLE
JayantDevkar Mar 23, 2026
d1a1bb6
fix(sync): 2 additional team_id bugs from analyst review
JayantDevkar Mar 23, 2026
2efd4f7
docs: update sync v4 status report — all blocking tests complete
JayantDevkar Mar 23, 2026
26e95b9
chore: update git-radio submodule — project management lifecycle scen…
JayantDevkar Mar 24, 2026
f899547
fix: dedup /sync/members by device_id, filter dissolved teams, add ma…
JayantDevkar Mar 24, 2026
ef4d110
fix(sync): 4 bugs from proj-mgmt walkie scenario
JayantDevkar Mar 24, 2026
b124fe5
fix(sync): cleanup architecture — FK pragma, events cleanup, device u…
JayantDevkar Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ coverage/
# package-lock.json
# yarn.lock
.worktrees/
cli/claude_karma_cli.egg-info/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "git-radio"]
path = git-radio
url = https://github.com/JayantDevkar/git-radio.git
141 changes: 96 additions & 45 deletions api/collectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
from datetime import datetime
from typing import Any, Dict, List, Optional, Set

from command_helpers import classify_invocation, is_command_category, is_skill_category
from command_helpers import category_from_base_directory, classify_invocation, is_command_category, is_skill_category
from config import FILE_TOOL_MAPPINGS
from models import AssistantMessage, Session, ToolUseBlock, UserMessage
from utils import extract_prompt_from_content
from models.conversation import ConversationEntity
from utils import FileOperation, extract_file_operation, normalize_key

Expand Down Expand Up @@ -45,6 +46,12 @@ class ConversationData:
skills: Counter = field(default_factory=Counter)
commands: Counter = field(default_factory=Counter)

# Skill categories extracted from JSONL path ("Base directory for this skill:" lines).
# Maps skill_name → category string (e.g. "custom_skill", "user_command", "plugin_skill").
# This is a secondary source of truth — more reliable than local classify_invocation()
# for remote sessions where the plugin may not be installed locally.
skill_categories: Dict[str, str] = field(default_factory=dict)

# File activity
file_operations: List[FileOperation] = field(default_factory=list)

Expand All @@ -54,6 +61,7 @@ class ConversationData:

# Initial prompt
initial_prompt: Optional[str] = None
initial_prompt_images: List[Dict[str, str]] = field(default_factory=list)


@dataclass
Expand Down Expand Up @@ -84,6 +92,7 @@ class SessionData:

# Subagent spawning info
task_tool_to_type: Dict[str, str] = field(default_factory=dict) # tool_use_id -> subagent_type
task_tool_to_name: Dict[str, str] = field(default_factory=dict) # tool_use_id -> display_name
task_descriptions: Dict[str, str] = field(
default_factory=dict
) # normalized_desc -> subagent_type
Expand Down Expand Up @@ -148,6 +157,7 @@ def _extract_file_operation(
)



def _collect_conversation_data_core(
entity: ConversationEntity,
actor: str,
Expand All @@ -170,6 +180,10 @@ def _collect_conversation_data_core(
"""
data = ConversationData()

# Track the last Skill tool invocation so we can look for the base directory
# in the next UserMessage (Claude Code injects skill content as a user turn).
_pending_skill_name: Optional[str] = None

for msg in entity.iter_messages():
# Extract context from any message
git_branch = getattr(msg, "git_branch", None)
Expand All @@ -180,13 +194,37 @@ def _collect_conversation_data_core(
if cwd:
data.working_directories.add(cwd)

# User message - get initial prompt
# User message - get initial prompt and check for skill base directory
if isinstance(msg, UserMessage):
if data.initial_prompt is None:
content = msg.content or ""
# Skip tool result and internal messages
if not msg.is_tool_result and not msg.is_internal_message:
data.initial_prompt = content[:5000] if content else None
prompt = extract_prompt_from_content(content) if content else None
# Skip empty prompts (e.g., bare command invocations without args)
if prompt:
data.initial_prompt = prompt[:5000]
if msg.image_attachments:
data.initial_prompt_images = list(msg.image_attachments)

# If we had a pending Skill invocation, check if this message carries
# the base directory line injected by Claude Code.
if _pending_skill_name is not None:
content = msg.content or ""
if "Base directory for this skill:" in content:
try:
# Extract first line after the marker
marker = "Base directory for this skill:"
idx = content.index(marker)
after = content[idx + len(marker):]
first_line = after.strip().splitlines()[0].strip() if after.strip() else ""
if first_line:
cat = category_from_base_directory(first_line)
if cat:
data.skill_categories[_pending_skill_name] = cat
except (ValueError, IndexError):
pass
_pending_skill_name = None

# Assistant message - extract tools and file operations
elif isinstance(msg, AssistantMessage):
Expand All @@ -204,11 +242,17 @@ def _collect_conversation_data_core(
data.skills[skill_name] += 1
elif is_command_category(kind):
data.commands[skill_name] += 1
# Track for base-directory lookahead in next UserMessage
_pending_skill_name = skill_name

# Extract file operations using shared utility
file_op = _extract_file_operation(block, msg.timestamp, actor, actor_type)
if file_op:
data.file_operations.append(file_op)
else:
# Non-user, non-assistant message clears the pending skill tracker
# (the injected content always comes in the very next message)
_pending_skill_name = None

return data

Expand Down Expand Up @@ -239,8 +283,10 @@ def collect_agent_data(agent: ConversationEntity) -> ConversationData:
if data.initial_prompt is None:
for msg in agent.iter_messages():
if isinstance(msg, UserMessage) and msg.content:
data.initial_prompt = msg.content[:5000]
break
prompt = extract_prompt_from_content(msg.content)
if prompt:
data.initial_prompt = prompt[:5000]
break

return data

Expand Down Expand Up @@ -291,7 +337,12 @@ def collect_session_data(session: Session, include_subagents: bool = False) -> S
content = msg.content or ""
# Skip tool result messages
if not (content.strip().startswith("{") and "'tool_use_id':" in content):
data.initial_prompt = content[:5000] if content else None
prompt = extract_prompt_from_content(content) if content else None
# Skip empty prompts (e.g., bare command invocations without args)
if prompt:
data.initial_prompt = prompt[:5000]
if msg.image_attachments:
data.initial_prompt_images = list(msg.image_attachments)

# Assistant message processing
elif isinstance(msg, AssistantMessage):
Expand Down Expand Up @@ -330,21 +381,24 @@ def collect_session_data(session: Session, include_subagents: bool = False) -> S
if file_op:
data.file_operations.append(file_op)

# Extract Task -> subagent_type mapping
# Extract Task -> subagent_type and name mappings
if tool_name in ("Task", "Agent"):
subagent_type = block.input.get("subagent_type")
if subagent_type:
data.task_tool_to_type[block.id] = subagent_type
# Store both description and prompt for fallback matching
# The subagent's initial_prompt comes from Task's "prompt" field,
# not the "description" field, so we need to match by prompt
prompt = block.input.get("prompt", "")[:100]
if prompt:
data.task_descriptions[normalize_key(prompt)] = subagent_type
# Also store description as secondary fallback
desc = block.input.get("description", "")[:100]
if desc:
data.task_descriptions[normalize_key(desc)] = subagent_type
subagent_type = block.input.get("subagent_type") or "general-purpose"
data.task_tool_to_type[block.id] = subagent_type
# Store both description and prompt for fallback matching
# The subagent's initial_prompt comes from Task's "prompt" field,
# not the "description" field, so we need to match by prompt
prompt = block.input.get("prompt", "")[:100]
if prompt:
data.task_descriptions[normalize_key(prompt)] = subagent_type
# Also store description as secondary fallback
desc = block.input.get("description", "")[:100]
if desc:
data.task_descriptions[normalize_key(desc)] = subagent_type
# Extract display name from `name` input field
agent_display_name = block.input.get("name")
if agent_display_name:
data.task_tool_to_name[block.id] = agent_display_name

# Collect subagent data if requested
if include_subagents:
Expand Down Expand Up @@ -404,7 +458,9 @@ class SubagentInfo:
skills_used: Counter
commands_used: Counter
initial_prompt: Optional[str]
initial_prompt_images: List[Dict[str, str]]
subagent_type: Optional[str]
display_name: Optional[str]
message_count: int


Expand All @@ -422,8 +478,9 @@ def collect_subagent_info(
Returns:
List of SubagentInfo for each subagent
"""
# Build agent_id -> subagent_type mapping from tool results
# Build agent_id -> subagent_type and agent_id -> display_name mappings from tool results
agent_id_to_type: Dict[str, str] = {}
agent_id_to_name: Dict[str, str] = {}

for tool_use_id, subagent_type in session_data.task_tool_to_type.items():
result_data = tool_results.get(tool_use_id)
Expand All @@ -434,33 +491,25 @@ def collect_subagent_info(
):
agent_id_to_type[result_data.spawned_agent_id] = subagent_type

for tool_use_id, display_name in session_data.task_tool_to_name.items():
result_data = tool_results.get(tool_use_id)
if (
result_data
and hasattr(result_data, "spawned_agent_id")
and result_data.spawned_agent_id
):
agent_id_to_name[result_data.spawned_agent_id] = display_name

subagents_info: List[SubagentInfo] = []

for subagent in session.list_subagents():
# Count tools, skills, and commands for this subagent - single pass
tool_counts: Counter = Counter()
skill_counts: Counter = Counter()
command_counts: Counter = Counter()
initial_prompt = None

for msg in subagent.iter_messages():
if isinstance(msg, UserMessage):
if initial_prompt is None:
initial_prompt = msg.content[:5000] if msg.content else None
elif isinstance(msg, AssistantMessage):
for block in msg.content_blocks:
if isinstance(block, ToolUseBlock):
tool_counts[block.name] += 1

# Extract skill/command names from Skill tool inputs
if block.name == "Skill" and block.input:
skill_name = block.input.get("skill")
if skill_name:
kind = classify_invocation(skill_name, source="skill_tool")
if is_skill_category(kind):
skill_counts[skill_name] += 1
elif is_command_category(kind):
command_counts[skill_name] += 1
# Collect all subagent data in a single pass via collect_agent_data
agent_data = collect_agent_data(subagent)
tool_counts = agent_data.tool_counts
skill_counts = agent_data.skills
command_counts = agent_data.commands
initial_prompt = agent_data.initial_prompt
initial_prompt_images = list(agent_data.initial_prompt_images)

# Match subagent to Task invocation
subagent_type = agent_id_to_type.get(subagent.agent_id)
Expand Down Expand Up @@ -489,7 +538,9 @@ def collect_subagent_info(
skills_used=skill_counts,
commands_used=command_counts,
initial_prompt=initial_prompt,
initial_prompt_images=initial_prompt_images,
subagent_type=subagent_type,
display_name=agent_id_to_name.get(subagent.agent_id),
message_count=subagent.message_count,
)
)
Expand Down
7 changes: 7 additions & 0 deletions api/command_helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

from .categories import (
InvocationCategory,
category_from_base_directory,
is_command_category,
is_skill_category,
)
Expand All @@ -42,16 +43,20 @@
_entry_map_cache,
_entry_type_cache,
_expand_name_cache,
_is_custom_skill,
_is_plugin_skill,
_plugin_skill_cache,
classify_invocation,
expand_plugin_short_name,
is_custom_skill_local,
is_plugin_installed_locally,
is_plugin_skill,
)

__all__ = [
# categories
"InvocationCategory",
"category_from_base_directory",
"is_skill_category",
"is_command_category",
# cli_js
Expand All @@ -64,6 +69,8 @@
# plugins
"classify_invocation",
"expand_plugin_short_name",
"is_custom_skill_local",
"is_plugin_installed_locally",
"is_plugin_skill",
# parsing
"parse_command_from_content",
Expand Down
25 changes: 24 additions & 1 deletion api/command_helpers/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
"plugin_skill",
"plugin_command",
"custom_skill",
"inherited_skill",
"user_command",
"agent",
]

# Categories that go into session_skills table
_SKILL_CATEGORIES: frozenset[str] = frozenset(
{"bundled_skill", "plugin_skill", "custom_skill"}
{"bundled_skill", "plugin_skill", "custom_skill", "inherited_skill"}
)
# Categories that go into session_commands table
_COMMAND_CATEGORIES: frozenset[str] = frozenset({"builtin_command", "user_command", "plugin_command"})
Expand All @@ -39,3 +40,25 @@ def is_skill_category(kind: str) -> bool:
def is_command_category(kind: str) -> bool:
"""Return True for any category that belongs in the commands bucket."""
return kind in _COMMAND_CATEGORIES


def category_from_base_directory(base_dir: str) -> str | None:
"""Infer skill category from a 'Base directory for this skill:' path.

Claude Code injects this line into the UserMessage that follows a Skill
tool invocation. The path segment reliably identifies the category:
~/.claude/plugins/cache/.../commands/ → plugin_command
~/.claude/plugins/cache/... → plugin_skill
~/.claude/skills/... → custom_skill
~/.claude/commands/... → user_command
"""
# Check plugin paths first — they also contain /skills/ or /commands/
if "/plugins/cache/" in base_dir:
if "/commands/" in base_dir:
return "plugin_command"
return "plugin_skill"
if "/skills/" in base_dir:
return "custom_skill"
if "/commands/" in base_dir:
return "user_command"
return None
22 changes: 15 additions & 7 deletions api/command_helpers/cli_js.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,21 @@ def _find_cli_js_path() -> Path | None:
if claude_bin:
try:
resolved = Path(claude_bin).resolve()
# npm global: .../bin/claude → .../lib/node_modules/@anthropic-ai/claude-code/cli.js
cli_js = resolved.parent.parent / "lib" / "node_modules" / "@anthropic-ai" / "claude-code" / "cli.js"
if cli_js.is_file():
return cli_js
# Direct symlink to cli.js (e.g., Homebrew)
if resolved.name == "cli.js" and resolved.is_file():
return resolved
if "Caskroom" in str(resolved):
logger.debug(
"Homebrew Cask install detected (%s) — no cli.js available; "
"using hardcoded command sets as fallback",
resolved,
)
# Cask distributes a native binary, not Node.js — skip npm traversal
else:
# npm global: .../bin/claude → .../lib/node_modules/@anthropic-ai/claude-code/cli.js
cli_js = resolved.parent.parent / "lib" / "node_modules" / "@anthropic-ai" / "claude-code" / "cli.js"
if cli_js.is_file():
return cli_js
# Direct symlink to cli.js (e.g., Homebrew)
if resolved.name == "cli.js" and resolved.is_file():
return resolved
except (OSError, ValueError):
pass

Expand Down
Loading
Loading