feat: fuse engineer_states git truth into collision detector

Add getGitStates(podId) to db.ts — queries the engineer_states collection
written by scripts/podman-agent.mjs and returns a map keyed by engineer name.

In PodMan.onScreenFrame(), fuse before detectCollisions(): if an engineer has
changedFiles in engineer_states, force hasUnpushedChanges=true on their context.
This overrides Gemini vision's unreliable pixel-based guess with deterministic
local git state — preventing the money-moment collision from silently not firing.

Key assumption: LiveKit participant identity matches the --name arg used when
running the git watcher (e.g. identity "alice" → --name alice).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AaCFWMkYQmTcuPsxaaACft
This commit is contained in:
Ramis
2026-06-27 16:52:50 -07:00
parent 3598dce89e
commit a853ef7709
2 changed files with 41 additions and 0 deletions
+9
View File
@@ -5,6 +5,7 @@ import { analyzeFrame } from '../vision/gemini.js';
import { detectCollisions } from '../collision/detector.js';
import { getGithubState } from '../github/client.js';
import { recordObservation, recordCollision, recordIntervention } from '../memory/store.js';
import { getGitStates } from '../memory/db.js';
import { recallSimilar } from '../memory/vectors.js';
import { shouldIntervene, preferredAction } from '../memory/policy.js';
import { speak } from '../voice/live.js';
@@ -36,6 +37,14 @@ export class PodMan {
this.contexts.set(engineerId, ctx);
await recordObservation(ctx);
// Fuse git ground truth: engineer_states written by scripts/podman-agent.mjs.
// Keyed by name (matches --name arg), same as LiveKit participant identity.
const gitStates = await getGitStates(this.podId);
for (const [id, c] of this.contexts) {
const git = gitStates.get(id);
if (git && git.changedFiles.length > 0) c.hasUnpushedChanges = true;
}
const github = await getGithubState(); // cached
const collisions = detectCollisions([...this.contexts.values()], github);
for (const collision of collisions) await this.handle(collision);
+32
View File
@@ -43,6 +43,38 @@ export async function collections(): Promise<PodCollections> {
};
}
export interface GitState {
changedFiles: string[];
branch: string | null;
recentCommit: string | null;
gitUpdatedAt: Date | null;
}
/** Fetch latest git state per engineer for a pod from the engineer_states collection.
* Returns a map keyed by engineer name (matches --name arg used in podman-agent.mjs). */
export async function getGitStates(podId: string): Promise<Map<string, GitState>> {
const db = await getDb();
const col = db.collection<{
_id: string;
name: string;
changedFiles?: string[];
branch?: string | null;
recentCommit?: string | null;
gitUpdatedAt?: Date;
}>('engineer_states');
const docs = await col.find({ podId }).toArray();
const map = new Map<string, GitState>();
for (const doc of docs) {
map.set(doc.name, {
changedFiles: doc.changedFiles ?? [],
branch: doc.branch ?? null,
recentCommit: doc.recentCommit ?? null,
gitUpdatedAt: doc.gitUpdatedAt ?? null,
});
}
return map;
}
/** Connect, verify reachability, and create helpful indexes. Call once on startup. */
export async function initMemory(): Promise<void> {
const db = await getDb();