27bf3d5855
- Add scripts/podman-agent.mjs — polls git every 15s, upserts changedFiles/ diffStat/recentCommit/branch into MongoDB engineer_states collection - README: add "Git watcher" section with per-laptop demo commands for each team member (alice/bob/carol --pod demo-pod) - PLAN.md: mark scheduled git truth ✅ partial, update acceptance checklist, clarify remaining fusion step (GIT_REPORT data channel wire-up still needed) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AaCFWMkYQmTcuPsxaaACft
153 lines
4.7 KiB
JavaScript
153 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Local git watcher — polls working tree every 15s and upserts into MongoDB.
|
|
* Usage: node scripts/podman-agent.mjs --name alice --pod demo-pod
|
|
*/
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import { MongoClient } from 'mongodb';
|
|
|
|
// ── CLI args ──────────────────────────────────────────────────────────────────
|
|
|
|
function parseArgs() {
|
|
const args = process.argv.slice(2);
|
|
const get = (flag) => {
|
|
const i = args.indexOf(flag);
|
|
return i !== -1 ? args[i + 1] : null;
|
|
};
|
|
const name = get('--name');
|
|
const pod = get('--pod');
|
|
if (!name || !pod) {
|
|
console.error('Usage: node scripts/podman-agent.mjs --name <name> --pod <podId>');
|
|
process.exit(1);
|
|
}
|
|
return { name, podId: pod };
|
|
}
|
|
|
|
// ── Environment ───────────────────────────────────────────────────────────────
|
|
|
|
async function loadEnv() {
|
|
if (!process.env.MONGODB_URI) {
|
|
try {
|
|
const dotenv = await import('dotenv');
|
|
dotenv.config({ path: new URL('../backend/.env', import.meta.url).pathname });
|
|
} catch {
|
|
// dotenv not available — rely on process.env
|
|
}
|
|
}
|
|
const uri = process.env.MONGODB_URI;
|
|
if (!uri) {
|
|
console.error('Error: MONGODB_URI not set. Export it or add it to backend/.env');
|
|
process.exit(1);
|
|
}
|
|
return uri;
|
|
}
|
|
|
|
// ── Git helpers ───────────────────────────────────────────────────────────────
|
|
|
|
function git(cmd) {
|
|
try {
|
|
return execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function collectGitState() {
|
|
const statusRaw = git('git status --short');
|
|
const changedFiles = statusRaw
|
|
.split('\n')
|
|
.map((l) => l.trim())
|
|
.filter(Boolean);
|
|
|
|
const diffRaw = git('git diff --stat HEAD').trim();
|
|
const diffStat = diffRaw || null;
|
|
|
|
const logRaw = git('git log --oneline -1').trim();
|
|
const recentCommit = logRaw || null;
|
|
|
|
const branchRaw = git('git branch --show-current').trim();
|
|
const branch = branchRaw || null;
|
|
|
|
return { changedFiles, diffStat, recentCommit, branch };
|
|
}
|
|
|
|
// ── Poll ──────────────────────────────────────────────────────────────────────
|
|
|
|
async function poll(col, engineerId, podId, name) {
|
|
const { changedFiles, diffStat, recentCommit, branch } = collectGitState();
|
|
const now = new Date();
|
|
|
|
try {
|
|
await col.updateOne(
|
|
{ _id: engineerId },
|
|
{
|
|
$set: {
|
|
changedFiles,
|
|
diffStat,
|
|
recentCommit,
|
|
branch,
|
|
gitUpdatedAt: now,
|
|
updatedAt: now,
|
|
},
|
|
$setOnInsert: {
|
|
podId,
|
|
name,
|
|
confidence: 0,
|
|
},
|
|
},
|
|
{ upsert: true },
|
|
);
|
|
|
|
const ts = now.toISOString().slice(11, 19);
|
|
const files = changedFiles.length;
|
|
const br = branch ?? '(detached)';
|
|
const commit = recentCommit ? recentCommit.slice(0, 50) : '(no commits)';
|
|
console.log(`[${ts}] ${name}@${podId} | branch=${br} files=${files} commit="${commit}"`);
|
|
} catch (err) {
|
|
console.error(`[poll error] ${err.message}`);
|
|
}
|
|
}
|
|
|
|
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
|
|
async function main() {
|
|
const { name, podId } = parseArgs();
|
|
const engineerId = `${podId}::${name}`;
|
|
const uri = await loadEnv();
|
|
|
|
const client = new MongoClient(uri, { serverSelectionTimeoutMS: 8_000 });
|
|
await client.connect();
|
|
|
|
const col = client.db().collection('engineer_states');
|
|
await col.createIndex({ podId: 1, updatedAt: -1 }, { background: true });
|
|
|
|
console.log(`podman-agent started | id=${engineerId} | polling every 15s`);
|
|
console.log('Press Ctrl+C to stop.\n');
|
|
|
|
let running = true;
|
|
|
|
const shutdown = async () => {
|
|
running = false;
|
|
console.log('\nShutting down...');
|
|
await client.close();
|
|
process.exit(0);
|
|
};
|
|
|
|
process.on('SIGINT', shutdown);
|
|
process.on('SIGTERM', shutdown);
|
|
|
|
// Run immediately, then loop
|
|
await poll(col, engineerId, podId, name);
|
|
|
|
while (running) {
|
|
await new Promise((r) => setTimeout(r, 15_000));
|
|
if (running) await poll(col, engineerId, podId, name);
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('Fatal:', err.message);
|
|
process.exit(1);
|
|
});
|