You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #748. Its PR fixes the Stop nudge and scopes skill usage per scope. What it left out:
machine
├─ hook handlers read config from the process cwd, not the dispatcher's 5
├─ teamai pull in a project with an unreadable config → user scope, 2
│ or a legacy .teamai/ behind a broken partition (another team)
├─ ~/.teamai/dashboard/reported-*.json one snapshot set for every scope 1
│ report filter: cwd string prefix (no realpath, Copilot has no cwd)
│ user-scope report: excludeProjectRoots is always []
├─ ~/.teamai/votes/<user>.yaml one file for every scope 6
├─ <dataHome>/usage.jsonl of a scope that never reports → grows forever 3
└─ stats pushed before #748 → may already list other projects' skills 4
Order: 5 first (no overlap with #758), then 2 → 1+4 → 6 → 3 once #758 merges. One PR each; the last one closes this issue.
Dashboard reports per scope.events.jsonl is not truncated after a report (only compactEvents shrinks it, at 5,000 lines), so the fix(usage): scope skill-usage events by cwd so one project's usage never reaches another team's stats #750 loss does not apply to it. The cross-scope leak is the delta snapshots: reported-interventions.json, reported-prompt-tokens.json and reported-daily-sessions.json are one set per machine, keyed by sessionId (team-push.ts). A user-scope pull reports project P's session and marks it done; P's own pull then sends nothing for it.
Fix: keep events.jsonl machine-wide (the teamai dashboard UI, stats --by-repo, session save and the contribute Stop check read across scopes, and data-directory-layout.md documents it as a machine-level singleton). Instead:
stamp each event with its scope's <dataHome> key at write time; the dispatcher knows the scope even for Copilot, which withholds cwd;
filterEventsByScope filters by that key. Events written before the change: a project takes those whose realpath'd cwd is under its root; the user scope never reports them;
move the reported-*.json snapshots to <dataHome>/dashboard/. A project scope seeds its snapshot from the shared one on first use, so nothing already reported is sent again;
remove the dead excludeProjectRoots path in pull.ts.
pull with an unreadable project config resolves detectProjectConfig() ?? user. Two ways it picks the wrong config:
no other project config: it processes and reports the user scope;
a broken partition with a legacy <workspace>/.teamai/config.yaml behind it: detectProjectConfig() skips the partition and returns the legacy config, which may name another team. The session-start pull hook runs pull() without the config gate (src/hook-handlers.ts), so every session start syncs that team's skills, rules and docs into the project. Seen with the real CLI while verifying fix(usage): discard pre-upgrade usage and stop falling back past an unreadable config (#748) #758: reports-wt/ and learnings-wt/ were created in the legacy .teamai/.
teamai pull syncs no scope, prints the problem with BROKEN_CONFIG_ADVICE, and exits 1;
the session-start pull hook skips silently, and so do its package hint (pkg-hint.ts has the same fallback) and agent-root seeding.
The hint must not point at teamai doctor: resolveDoctorContext is detectProjectConfig() ?? loadLocalConfig() without the unreadable sink, so doctor diagnoses whichever config it falls back to.
Unbounded growth in scopes that never report (http, usageReport: false, or a remote that rejects every push). Dropping events at write time would empty teamai stats for them, since the local file is their only source. Fix: cap usage.jsonl (keep the newest 5,000 events) inside pull(), under the scope's sync lock and after the report's truncate. A cap applied by a hook could run between the report's read and truncateUsageAfterReport, which removes the first N lines, and would delete events never reported (the fix(usage): scope skill-usage events by cwd so one project's usage never reaches another team's stats #750 failure).
Retroactive cleanup. Old events carry no cwd, so nothing can tell which project a pushed skill came from. Document how to remove a skill entry from stats/<user>.yaml on teamai-reports by hand (usage guide, both languages). No command.
Handlers resolve their config from the process cwd, not from the dispatcher.hook-dispatch resolves the scope from the payload cwd (resolveConfigForDir), then chdirs there so handlers agree. Three handlers read their config again through autoDetectInit() on the process cwd: the correction keywords (teamCorrectionKeywords), votes-sync, and webhook-dispatch (loadWebhookConfig). The contribute hint is not affected: it already resolves from the payload cwd (fix(skills): one share gate, actionable refusals, and a louder stub deploy #747). When chdir fails, those three follow whichever directory the host spawned the hook in:
payload cwd: deleted worktree → dispatcher: user scope
process cwd: project A → keywords, votes, webhook: project A
Fix: pass the config the dispatcher resolved to every handler (execute(stdin, tool, config)), and drop autoDetectInit() from them. A chdir(HOME) fallback was considered and rejected: it misses a HOME that is itself a git repo set up as a teamai project. Regression test: a deleted cwd with the hook process inside project A, and a Stop hook whose keywords, votes and webhook must follow the user scope.
Votes are machine-level. Every scope records into ~/.teamai/votes/<user>.yaml, and whichever scope syncs next pushes the pending deltas to its team (votes-sync, and the vote staging in team-push.ts). It is the same shape as the old shared usage.jsonl. Fix: votes per scope (<dataHome>/votes/, and ~/.teamai/user-votes/ for the user scope, mirroring user-usage.jsonl). Pending votes left in the shared directory name no project and are not pushed.
Follow-up to #748. Its PR fixes the Stop nudge and scopes skill usage per scope. What it left out:
Order: 5 first (no overlap with #758), then 2 → 1+4 → 6 → 3 once #758 merges. One PR each; the last one closes this issue.
Dashboard reports per scope.
events.jsonlis not truncated after a report (onlycompactEventsshrinks it, at 5,000 lines), so the fix(usage): scope skill-usage events by cwd so one project's usage never reaches another team's stats #750 loss does not apply to it. The cross-scope leak is the delta snapshots:reported-interventions.json,reported-prompt-tokens.jsonandreported-daily-sessions.jsonare one set per machine, keyed by sessionId (team-push.ts). A user-scope pull reports project P's session and marks it done; P's own pull then sends nothing for it.Fix: keep
events.jsonlmachine-wide (theteamai dashboardUI,stats --by-repo,session saveand the contribute Stop check read across scopes, anddata-directory-layout.mddocuments it as a machine-level singleton). Instead:<dataHome>key at write time; the dispatcher knows the scope even for Copilot, which withholds cwd;filterEventsByScopefilters by that key. Events written before the change: a project takes those whose realpath'd cwd is under its root; the user scope never reports them;reported-*.jsonsnapshots to<dataHome>/dashboard/. A project scope seeds its snapshot from the shared one on first use, so nothing already reported is sent again;excludeProjectRootspath inpull.ts.pullwith an unreadable project config resolvesdetectProjectConfig() ?? user. Two ways it picks the wrong config:<workspace>/.teamai/config.yamlbehind it:detectProjectConfig()skips the partition and returns the legacy config, which may name another team. The session-start pull hook runspull()without the config gate (src/hook-handlers.ts), so every session start syncs that team's skills, rules and docs into the project. Seen with the real CLI while verifying fix(usage): discard pre-upgrade usage and stop falling back past an unreadable config (#748) #758:reports-wt/andlearnings-wt/were created in the legacy.teamai/.The hooks and usage already stop at any unreadable config (
resolveConfigForDir, which since fix(usage): discard pre-upgrade usage and stop falling back past an unreadable config (#748) #758 also refuses the lower-priority fallback);pullshould do the same. WhenfindUnreadableProjectConfig()reports a problem:teamai pullsyncs no scope, prints the problem withBROKEN_CONFIG_ADVICE, and exits 1;pkg-hint.tshas the same fallback) and agent-root seeding.The hint must not point at
teamai doctor:resolveDoctorContextisdetectProjectConfig() ?? loadLocalConfig()without the unreadable sink, so doctor diagnoses whichever config it falls back to.Unbounded growth in scopes that never report (http,
usageReport: false, or a remote that rejects every push). Dropping events at write time would emptyteamai statsfor them, since the local file is their only source. Fix: capusage.jsonl(keep the newest 5,000 events) insidepull(), under the scope's sync lock and after the report's truncate. A cap applied by a hook could run between the report's read andtruncateUsageAfterReport, which removes the first N lines, and would delete events never reported (the fix(usage): scope skill-usage events by cwd so one project's usage never reaches another team's stats #750 failure).Retroactive cleanup. Old events carry no cwd, so nothing can tell which project a pushed skill came from. Document how to remove a skill entry from
stats/<user>.yamlonteamai-reportsby hand (usage guide, both languages). No command.Handlers resolve their config from the process cwd, not from the dispatcher.
hook-dispatchresolves the scope from the payloadcwd(resolveConfigForDir), thenchdirs there so handlers agree. Three handlers read their config again throughautoDetectInit()on the process cwd: the correction keywords (teamCorrectionKeywords),votes-sync, andwebhook-dispatch(loadWebhookConfig). The contribute hint is not affected: it already resolves from the payload cwd (fix(skills): one share gate, actionable refusals, and a louder stub deploy #747). Whenchdirfails, those three follow whichever directory the host spawned the hook in:Fix: pass the config the dispatcher resolved to every handler (
execute(stdin, tool, config)), and dropautoDetectInit()from them. Achdir(HOME)fallback was considered and rejected: it misses a HOME that is itself a git repo set up as a teamai project. Regression test: a deletedcwdwith the hook process inside project A, and a Stop hook whose keywords, votes and webhook must follow the user scope.Votes are machine-level. Every scope records into
~/.teamai/votes/<user>.yaml, and whichever scope syncs next pushes the pending deltas to its team (votes-sync, and the vote staging inteam-push.ts). It is the same shape as the old sharedusage.jsonl. Fix: votes per scope (<dataHome>/votes/, and~/.teamai/user-votes/for the user scope, mirroringuser-usage.jsonl). Pending votes left in the shared directory name no project and are not pushed.Out of scope.
cwdonUsageEventagain (suggested in fix(usage): scope skill-usage events by cwd so one project's usage never reaches another team's stats #750). No reader needs it yet; add it together with one (worktrees,teamai stats --by-repo).