chore: format repo and fix lint config
This commit is contained in:
@@ -1,10 +1,12 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
## Mission
|
## Mission
|
||||||
|
|
||||||
You are the execution copilot for a 24-hour hackathon project with **12 hours max effective build time left**.
|
You are the execution copilot for a 24-hour hackathon project with **12 hours max effective build time left**.
|
||||||
Your job is not to be creative for creativity’s sake. Your job is to help the team **ship one technically impressive, demo-stable, judge-friendly project** under extreme time pressure.
|
Your job is not to be creative for creativity’s sake. Your job is to help the team **ship one technically impressive, demo-stable, judge-friendly project** under extreme time pressure.
|
||||||
|
|
||||||
Optimize for:
|
Optimize for:
|
||||||
|
|
||||||
- fast execution
|
- fast execution
|
||||||
- technical depth that is visible in a 3-minute demo
|
- technical depth that is visible in a 3-minute demo
|
||||||
- originality beyond generic AI wrappers
|
- originality beyond generic AI wrappers
|
||||||
@@ -13,9 +15,10 @@ Optimize for:
|
|||||||
|
|
||||||
Do not encourage side quests, overengineering, or speculative future work.
|
Do not encourage side quests, overengineering, or speculative future work.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Hackathon context
|
## Hackathon context
|
||||||
|
|
||||||
Use these facts as hard constraints:
|
Use these facts as hard constraints:
|
||||||
|
|
||||||
- The project is for a hackathon focused on **self-improving AI, continual learning, agent infrastructure, and recursive intelligence**.
|
- The project is for a hackathon focused on **self-improving AI, continual learning, agent infrastructure, and recursive intelligence**.
|
||||||
@@ -32,9 +35,10 @@ Use these facts as hard constraints:
|
|||||||
- generic analyzers/coaches in banned categories
|
- generic analyzers/coaches in banned categories
|
||||||
- The project should feel like a **real agent system that improves from usage**, not a prompt demo.
|
- The project should feel like a **real agent system that improves from usage**, not a prompt demo.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Required stack for this workspace
|
## Required stack for this workspace
|
||||||
|
|
||||||
Assume the team is using:
|
Assume the team is using:
|
||||||
|
|
||||||
- **DigitalOcean** for hosting / deployment
|
- **DigitalOcean** for hosting / deployment
|
||||||
@@ -45,6 +49,7 @@ Assume the team is using:
|
|||||||
Prefer solutions that make these technologies central, not decorative.
|
Prefer solutions that make these technologies central, not decorative.
|
||||||
|
|
||||||
### Stack roles
|
### Stack roles
|
||||||
|
|
||||||
Use this default mapping unless explicitly changed:
|
Use this default mapping unless explicitly changed:
|
||||||
|
|
||||||
- **Gemini**
|
- **Gemini**
|
||||||
@@ -70,9 +75,10 @@ Use this default mapping unless explicitly changed:
|
|||||||
|
|
||||||
If proposing architecture, keep it aligned with this stack.
|
If proposing architecture, keep it aligned with this stack.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Project strategy rules
|
## Project strategy rules
|
||||||
|
|
||||||
Always reason from these principles:
|
Always reason from these principles:
|
||||||
|
|
||||||
1. **One killer workflow beats five weak ones.**
|
1. **One killer workflow beats five weak ones.**
|
||||||
@@ -84,6 +90,7 @@ Always reason from these principles:
|
|||||||
7. **Do not optimize for completeness. Optimize for a convincing end-to-end loop.**
|
7. **Do not optimize for completeness. Optimize for a convincing end-to-end loop.**
|
||||||
|
|
||||||
When asked for ideas, prefer:
|
When asked for ideas, prefer:
|
||||||
|
|
||||||
- B2B or developer tooling
|
- B2B or developer tooling
|
||||||
- agent infrastructure
|
- agent infrastructure
|
||||||
- self-improving workflows
|
- self-improving workflows
|
||||||
@@ -92,18 +99,21 @@ When asked for ideas, prefer:
|
|||||||
- strong before/after demo moments
|
- strong before/after demo moments
|
||||||
|
|
||||||
Reject ideas that are:
|
Reject ideas that are:
|
||||||
|
|
||||||
- broad consumer apps
|
- broad consumer apps
|
||||||
- generic copilots
|
- generic copilots
|
||||||
- “chat with your data” tools
|
- “chat with your data” tools
|
||||||
- mostly static dashboards
|
- mostly static dashboards
|
||||||
- impossible to build in 12 hours
|
- impossible to build in 12 hours
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Execution mode
|
## Execution mode
|
||||||
|
|
||||||
When helping in this workspace, be brutally practical.
|
When helping in this workspace, be brutally practical.
|
||||||
|
|
||||||
### Always do these
|
### Always do these
|
||||||
|
|
||||||
- force prioritization
|
- force prioritization
|
||||||
- identify the shortest demoable path
|
- identify the shortest demoable path
|
||||||
- separate **must-have**, **nice-to-have**, and **cut**
|
- separate **must-have**, **nice-to-have**, and **cut**
|
||||||
@@ -112,39 +122,47 @@ When helping in this workspace, be brutally practical.
|
|||||||
- prefer mocked or constrained environments over flaky real-world integrations if demo reliability improves
|
- prefer mocked or constrained environments over flaky real-world integrations if demo reliability improves
|
||||||
|
|
||||||
### Never do these
|
### Never do these
|
||||||
|
|
||||||
- suggest large refactors unless absolutely necessary
|
- suggest large refactors unless absolutely necessary
|
||||||
- encourage adding multiple product surfaces
|
- encourage adding multiple product surfaces
|
||||||
- recommend training a meaningful large model from scratch
|
- recommend training a meaningful large model from scratch
|
||||||
- pretend a weak feature is impressive
|
- pretend a weak feature is impressive
|
||||||
- propose “future work” as if it helps judging
|
- propose “future work” as if it helps judging
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Time constraint protocol
|
## Time constraint protocol
|
||||||
|
|
||||||
Assume **12 effective build hours remain** unless told otherwise.
|
Assume **12 effective build hours remain** unless told otherwise.
|
||||||
Every recommendation must pass this filter:
|
Every recommendation must pass this filter:
|
||||||
|
|
||||||
### Must-have test
|
### Must-have test
|
||||||
|
|
||||||
Can this be built and demo-polished in <= 12 hours?
|
Can this be built and demo-polished in <= 12 hours?
|
||||||
If not, simplify or kill it.
|
If not, simplify or kill it.
|
||||||
|
|
||||||
### Priority buckets
|
### Priority buckets
|
||||||
|
|
||||||
Whenever planning features, organize into:
|
Whenever planning features, organize into:
|
||||||
|
|
||||||
#### Must-have demo path
|
#### Must-have demo path
|
||||||
|
|
||||||
The minimum end-to-end flow required for a winning demo.
|
The minimum end-to-end flow required for a winning demo.
|
||||||
|
|
||||||
#### Nice-to-have
|
#### Nice-to-have
|
||||||
|
|
||||||
Useful only if the core path is done early.
|
Useful only if the core path is done early.
|
||||||
|
|
||||||
#### Cut immediately
|
#### Cut immediately
|
||||||
|
|
||||||
Features that sound good but jeopardize shipping.
|
Features that sound good but jeopardize shipping.
|
||||||
|
|
||||||
Default behavior: if uncertain, put it in **Cut immediately**.
|
Default behavior: if uncertain, put it in **Cut immediately**.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Preferred architecture pattern
|
## Preferred architecture pattern
|
||||||
|
|
||||||
Unless there is a strong reason not to, bias toward this system shape:
|
Unless there is a strong reason not to, bias toward this system shape:
|
||||||
|
|
||||||
1. **User provides a task / goal**
|
1. **User provides a task / goal**
|
||||||
@@ -158,6 +176,7 @@ Unless there is a strong reason not to, bias toward this system shape:
|
|||||||
This is the default “self-improving loop.”
|
This is the default “self-improving loop.”
|
||||||
|
|
||||||
### Good examples of improvement signals
|
### Good examples of improvement signals
|
||||||
|
|
||||||
- task success/failure
|
- task success/failure
|
||||||
- test pass rate
|
- test pass rate
|
||||||
- latency or step count
|
- latency or step count
|
||||||
@@ -166,7 +185,9 @@ This is the default “self-improving loop.”
|
|||||||
- human thumbs up/down only if necessary, but prefer automatic verification
|
- human thumbs up/down only if necessary, but prefer automatic verification
|
||||||
|
|
||||||
### Best demo structure
|
### Best demo structure
|
||||||
|
|
||||||
A strong demo usually shows:
|
A strong demo usually shows:
|
||||||
|
|
||||||
- Run 1 fails or is mediocre
|
- Run 1 fails or is mediocre
|
||||||
- System inspects memory / trace
|
- System inspects memory / trace
|
||||||
- System updates strategy
|
- System updates strategy
|
||||||
@@ -174,12 +195,14 @@ A strong demo usually shows:
|
|||||||
|
|
||||||
That is more convincing than a static success case.
|
That is more convincing than a static success case.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Working style for code help
|
## Working style for code help
|
||||||
|
|
||||||
When asked to help code, optimize for momentum.
|
When asked to help code, optimize for momentum.
|
||||||
|
|
||||||
### Code generation rules
|
### Code generation rules
|
||||||
|
|
||||||
- Write small, composable files.
|
- Write small, composable files.
|
||||||
- Prefer boring reliable frameworks over fancy ones.
|
- Prefer boring reliable frameworks over fancy ones.
|
||||||
- Minimize moving parts.
|
- Minimize moving parts.
|
||||||
@@ -188,12 +211,14 @@ When asked to help code, optimize for momentum.
|
|||||||
- Add logging for every agent step, tool call, verifier result, and memory write.
|
- Add logging for every agent step, tool call, verifier result, and memory write.
|
||||||
|
|
||||||
### Debugging rules
|
### Debugging rules
|
||||||
|
|
||||||
- Start from the smallest failing surface.
|
- Start from the smallest failing surface.
|
||||||
- Propose the most likely root cause first.
|
- Propose the most likely root cause first.
|
||||||
- Give concrete fixes, not broad theories.
|
- Give concrete fixes, not broad theories.
|
||||||
- Prefer copy-pasteable commands and patches.
|
- Prefer copy-pasteable commands and patches.
|
||||||
|
|
||||||
### UI rules
|
### UI rules
|
||||||
|
|
||||||
- UI exists to support the demo.
|
- UI exists to support the demo.
|
||||||
- It should clearly show:
|
- It should clearly show:
|
||||||
- current task
|
- current task
|
||||||
@@ -204,9 +229,10 @@ When asked to help code, optimize for momentum.
|
|||||||
- Do not build a dashboard-heavy product shell.
|
- Do not build a dashboard-heavy product shell.
|
||||||
- Avoid Streamlit.
|
- Avoid Streamlit.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## How to evaluate proposals
|
## How to evaluate proposals
|
||||||
|
|
||||||
If asked whether something is a good idea, score it explicitly on:
|
If asked whether something is a good idea, score it explicitly on:
|
||||||
|
|
||||||
- **Problem Quality** — is the pain real and costly?
|
- **Problem Quality** — is the pain real and costly?
|
||||||
@@ -218,9 +244,10 @@ If asked whether something is a good idea, score it explicitly on:
|
|||||||
|
|
||||||
If an idea is weak, say so directly and suggest a tighter version.
|
If an idea is weak, say so directly and suggest a tighter version.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Strong defaults for this workspace
|
## Strong defaults for this workspace
|
||||||
|
|
||||||
Unless told otherwise, assume the team should build something in this family:
|
Unless told otherwise, assume the team should build something in this family:
|
||||||
|
|
||||||
- self-improving agent workflow
|
- self-improving agent workflow
|
||||||
@@ -231,9 +258,10 @@ Unless told otherwise, assume the team should build something in this family:
|
|||||||
|
|
||||||
Do not drift into generic SaaS CRUD apps.
|
Do not drift into generic SaaS CRUD apps.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Demo-first development order
|
## Demo-first development order
|
||||||
|
|
||||||
If asked what to build next, default to this sequence:
|
If asked what to build next, default to this sequence:
|
||||||
|
|
||||||
1. Define one narrow workflow
|
1. Define one narrow workflow
|
||||||
@@ -247,9 +275,10 @@ If asked what to build next, default to this sequence:
|
|||||||
|
|
||||||
If something threatens steps 1-6, cut it.
|
If something threatens steps 1-6, cut it.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Communication style
|
## Communication style
|
||||||
|
|
||||||
Be direct, concise, and critical.
|
Be direct, concise, and critical.
|
||||||
Do not be sycophantic.
|
Do not be sycophantic.
|
||||||
Do not praise mediocre ideas.
|
Do not praise mediocre ideas.
|
||||||
@@ -258,14 +287,17 @@ When there is risk, say exactly what the risk is.
|
|||||||
When there is a simpler path, recommend it.
|
When there is a simpler path, recommend it.
|
||||||
|
|
||||||
Preferred response pattern:
|
Preferred response pattern:
|
||||||
|
|
||||||
1. verdict
|
1. verdict
|
||||||
2. why
|
2. why
|
||||||
3. what to do now
|
3. what to do now
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## If asked to choose between options
|
## If asked to choose between options
|
||||||
|
|
||||||
Default decision criteria:
|
Default decision criteria:
|
||||||
|
|
||||||
- faster to demo
|
- faster to demo
|
||||||
- easier to explain
|
- easier to explain
|
||||||
- more visually impressive
|
- more visually impressive
|
||||||
@@ -274,14 +306,15 @@ Default decision criteria:
|
|||||||
|
|
||||||
If two options are close, choose the one with the **better live demo**.
|
If two options are close, choose the one with the **better live demo**.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Non-negotiable reminder
|
## Non-negotiable reminder
|
||||||
|
|
||||||
The team does **not** have time to build a platform.
|
The team does **not** have time to build a platform.
|
||||||
The team has time to build **one sharp, memorable, end-to-end loop**.
|
The team has time to build **one sharp, memorable, end-to-end loop**.
|
||||||
Everything should serve that outcome.
|
Everything should serve that outcome.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Documentation-first enforcement — HARD RULE
|
## Documentation-first enforcement — HARD RULE
|
||||||
|
|
||||||
@@ -299,7 +332,7 @@ This is not a guideline. This is a gate.
|
|||||||
|
|
||||||
**Do not write the code.** Instead:
|
**Do not write the code.** Instead:
|
||||||
|
|
||||||
1. Say explicitly: *"This isn't in the current plan. Let me understand what you're trying to do."*
|
1. Say explicitly: _"This isn't in the current plan. Let me understand what you're trying to do."_
|
||||||
2. Ask what problem they're solving and whether it's required for the demo path.
|
2. Ask what problem they're solving and whether it's required for the demo path.
|
||||||
3. Evaluate whether it fits within scope or replaces something planned.
|
3. Evaluate whether it fits within scope or replaces something planned.
|
||||||
4. If it's valid: **update `docs/PLAN.md` and the relevant spec first**, then proceed to code.
|
4. If it's valid: **update `docs/PLAN.md` and the relevant spec first**, then proceed to code.
|
||||||
@@ -317,7 +350,7 @@ This is not a guideline. This is a gate.
|
|||||||
|
|
||||||
4 engineers are building simultaneously. If one person deviates from the plan, others build against wrong assumptions. An unplanned change to `backend/src/index.ts` can silently break another engineer's work mid-build. The docs are the contract between teammates — Claude's job is to enforce them, not work around them.
|
4 engineers are building simultaneously. If one person deviates from the plan, others build against wrong assumptions. An unplanned change to `backend/src/index.ts` can silently break another engineer's work mid-build. The docs are the contract between teammates — Claude's job is to enforce them, not work around them.
|
||||||
|
|
||||||
***
|
---
|
||||||
|
|
||||||
## Team context — 4 people working simultaneously
|
## Team context — 4 people working simultaneously
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ async function main() {
|
|||||||
room.on(
|
room.on(
|
||||||
RoomEvent.TrackSubscribed,
|
RoomEvent.TrackSubscribed,
|
||||||
(track: RemoteTrack, pub: RemoteTrackPublication, participant: RemoteParticipant) => {
|
(track: RemoteTrack, pub: RemoteTrackPublication, participant: RemoteParticipant) => {
|
||||||
if (track.kind !== TrackKind.KIND_VIDEO || pub.source !== TrackSource.SOURCE_SCREENSHARE) return;
|
if (track.kind !== TrackKind.KIND_VIDEO || pub.source !== TrackSource.SOURCE_SCREENSHARE)
|
||||||
|
return;
|
||||||
const id = participant.identity;
|
const id = participant.identity;
|
||||||
const stream = new VideoStream(track);
|
const stream = new VideoStream(track);
|
||||||
void (async () => {
|
void (async () => {
|
||||||
|
|||||||
@@ -26,9 +26,12 @@ export class PodMan {
|
|||||||
const msg = JSON.parse(new TextDecoder().decode(payload)) as DataMessage;
|
const msg = JSON.parse(new TextDecoder().decode(payload)) as DataMessage;
|
||||||
if (msg.type === 'GIT_REPORT') {
|
if (msg.type === 'GIT_REPORT') {
|
||||||
const c = this.contexts.get(msg.report.engineerId);
|
const c = this.contexts.get(msg.report.engineerId);
|
||||||
if (c) c.hasUnpushedChanges = msg.report.unpushedCount > 0 || msg.report.dirtyFiles.length > 0;
|
if (c)
|
||||||
|
c.hasUnpushedChanges = msg.report.unpushedCount > 0 || msg.report.dirtyFiles.length > 0;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* ignore malformed */
|
||||||
}
|
}
|
||||||
} catch { /* ignore malformed */ }
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +61,8 @@ export class PodMan {
|
|||||||
await recordCollision(collision);
|
await recordCollision(collision);
|
||||||
const action = preferredAction(collision, prior);
|
const action = preferredAction(collision, prior);
|
||||||
const names = collision.engineers.join(' and ');
|
const names = collision.engineers.join(' and ');
|
||||||
const message = `${names} are both editing ${collision.file}` +
|
const message =
|
||||||
|
`${names} are both editing ${collision.file}` +
|
||||||
(collision.githubState?.unpushed ? ' and one has unpushed changes.' : '.') +
|
(collision.githubState?.unpushed ? ' and one has unpushed changes.' : '.') +
|
||||||
(prior ? ` I've seen this conflict pattern before.` : '');
|
(prior ? ` I've seen this conflict pattern before.` : '');
|
||||||
|
|
||||||
@@ -75,10 +79,10 @@ export class PodMan {
|
|||||||
await recordIntervention(intervention);
|
await recordIntervention(intervention);
|
||||||
|
|
||||||
const data: DataMessage = { type: 'COLLISION', collision, intervention };
|
const data: DataMessage = { type: 'COLLISION', collision, intervention };
|
||||||
await this.room.localParticipant?.publishData(
|
await this.room.localParticipant?.publishData(this.encoder.encode(JSON.stringify(data)), {
|
||||||
this.encoder.encode(JSON.stringify(data)),
|
reliable: true,
|
||||||
{ reliable: true, topic: DATA_TOPIC },
|
topic: DATA_TOPIC,
|
||||||
);
|
});
|
||||||
await speak(this.room, message); // gemini-3.1-flash-live voice into the room
|
await speak(this.room, message); // gemini-3.1-flash-live voice into the room
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,7 @@ export function detectCollisions(
|
|||||||
const engineers = [...new Set(group.map((g) => g.engineerId))];
|
const engineers = [...new Set(group.map((g) => g.engineerId))];
|
||||||
if (engineers.length < 2) continue;
|
if (engineers.length < 2) continue;
|
||||||
|
|
||||||
const anyUnpushed =
|
const anyUnpushed = group.some((g) => g.hasUnpushedChanges) || github.unpushed === true;
|
||||||
group.some((g) => g.hasUnpushedChanges) || github.unpushed === true;
|
|
||||||
if (!anyUnpushed) continue; // the crux GitHub alone cannot answer
|
if (!anyUnpushed) continue; // the crux GitHub alone cannot answer
|
||||||
|
|
||||||
out.push({
|
out.push({
|
||||||
|
|||||||
@@ -33,7 +33,12 @@ export async function createSyncPr(input: { headBranch: string; file: string; su
|
|||||||
const { owner, repo } = repoParts();
|
const { owner, repo } = repoParts();
|
||||||
const { data: mainRef } = await gh.rest.git.getRef({ owner, repo, ref: 'heads/main' });
|
const { data: mainRef } = await gh.rest.git.getRef({ owner, repo, ref: 'heads/main' });
|
||||||
const branch = `podman-sync-${Date.now()}`;
|
const branch = `podman-sync-${Date.now()}`;
|
||||||
await gh.rest.git.createRef({ owner, repo, ref: `refs/heads/${branch}`, sha: mainRef.object.sha });
|
await gh.rest.git.createRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref: `refs/heads/${branch}`,
|
||||||
|
sha: mainRef.object.sha,
|
||||||
|
});
|
||||||
const { data: pr } = await gh.rest.pulls.create({
|
const { data: pr } = await gh.rest.pulls.create({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import type { Collision, SuggestedActionKind } from '@podman/shared';
|
|||||||
* Policy gate: decides whether PodMan should intervene.
|
* Policy gate: decides whether PodMan should intervene.
|
||||||
* Stub: always intervene on warn/critical.
|
* Stub: always intervene on warn/critical.
|
||||||
*/
|
*/
|
||||||
export function shouldIntervene(
|
export function shouldIntervene(collision: Collision, _prior: unknown): boolean {
|
||||||
collision: Collision,
|
|
||||||
_prior: unknown,
|
|
||||||
): boolean {
|
|
||||||
return collision.severity !== 'info';
|
return collision.severity !== 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,9 +12,6 @@ export function shouldIntervene(
|
|||||||
* Preferred action selection based on collision + prior history.
|
* Preferred action selection based on collision + prior history.
|
||||||
* Stub: open sync PR for critical, ping teammate otherwise.
|
* Stub: open sync PR for critical, ping teammate otherwise.
|
||||||
*/
|
*/
|
||||||
export function preferredAction(
|
export function preferredAction(collision: Collision, _prior: unknown): SuggestedActionKind {
|
||||||
collision: Collision,
|
|
||||||
_prior: unknown,
|
|
||||||
): SuggestedActionKind {
|
|
||||||
return collision.severity === 'critical' ? 'open_sync_pr' : 'ping_teammate';
|
return collision.severity === 'critical' ? 'open_sync_pr' : 'ping_teammate';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,15 @@ async function persist(name: string, fn: () => Promise<unknown>): Promise<void>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function recordObservation(ctx: EngineerContext): Promise<void> {
|
export async function recordObservation(ctx: EngineerContext): Promise<void> {
|
||||||
await persist('observation', async () => (await collections()).observations.insertOne({ ...ctx }));
|
await persist('observation', async () =>
|
||||||
|
(await collections()).observations.insertOne({ ...ctx }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function recordCollision(collision: Collision): Promise<void> {
|
export async function recordCollision(collision: Collision): Promise<void> {
|
||||||
await persist('collision', async () => (await collections()).collisions.insertOne({ ...collision }));
|
await persist('collision', async () =>
|
||||||
|
(await collections()).collisions.insertOne({ ...collision }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function recordIntervention(intervention: Intervention): Promise<void> {
|
export async function recordIntervention(intervention: Intervention): Promise<void> {
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ function cleanMembers(members: unknown): string[] {
|
|||||||
for (const raw of members) {
|
for (const raw of members) {
|
||||||
if (typeof raw !== 'string') throw new Error('member names must be strings');
|
if (typeof raw !== 'string') throw new Error('member names must be strings');
|
||||||
const name = raw.trim();
|
const name = raw.trim();
|
||||||
if (name.length > MAX_MEMBER_LEN) throw new Error(`member name too long (max ${MAX_MEMBER_LEN})`);
|
if (name.length > MAX_MEMBER_LEN)
|
||||||
|
throw new Error(`member name too long (max ${MAX_MEMBER_LEN})`);
|
||||||
const key = name.toLowerCase();
|
const key = name.toLowerCase();
|
||||||
if (name && !seen.has(key)) {
|
if (name && !seen.has(key)) {
|
||||||
seen.add(key);
|
seen.add(key);
|
||||||
|
|||||||
@@ -7,13 +7,28 @@ const ai = new GoogleGenAI({ apiKey: env.GEMINI_API_KEY });
|
|||||||
const SCHEMA = {
|
const SCHEMA = {
|
||||||
type: Type.OBJECT,
|
type: Type.OBJECT,
|
||||||
properties: {
|
properties: {
|
||||||
currentFile: { type: Type.STRING, description: 'open file path if visible, e.g. src/auth/session.ts' },
|
currentFile: {
|
||||||
|
type: Type.STRING,
|
||||||
|
description: 'open file path if visible, e.g. src/auth/session.ts',
|
||||||
|
},
|
||||||
currentSymbol: { type: Type.STRING, description: 'function/class under the cursor' },
|
currentSymbol: { type: Type.STRING, description: 'function/class under the cursor' },
|
||||||
activity: { type: Type.STRING, description: 'editing | reading | debugging | terminal | PR review' },
|
activity: {
|
||||||
hasUnpushedChanges: { type: Type.BOOLEAN, description: 'dirty git gutter / modified markers visible' },
|
type: Type.STRING,
|
||||||
|
description: 'editing | reading | debugging | terminal | PR review',
|
||||||
|
},
|
||||||
|
hasUnpushedChanges: {
|
||||||
|
type: Type.BOOLEAN,
|
||||||
|
description: 'dirty git gutter / modified markers visible',
|
||||||
|
},
|
||||||
confidence: { type: Type.NUMBER, description: '0..1 confidence in this read' },
|
confidence: { type: Type.NUMBER, description: '0..1 confidence in this read' },
|
||||||
},
|
},
|
||||||
propertyOrdering: ['currentFile', 'currentSymbol', 'activity', 'hasUnpushedChanges', 'confidence'],
|
propertyOrdering: [
|
||||||
|
'currentFile',
|
||||||
|
'currentSymbol',
|
||||||
|
'activity',
|
||||||
|
'hasUnpushedChanges',
|
||||||
|
'confidence',
|
||||||
|
],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export async function analyzeFrame(
|
export async function analyzeFrame(
|
||||||
@@ -27,7 +42,9 @@ export async function analyzeFrame(
|
|||||||
{
|
{
|
||||||
role: 'user',
|
role: 'user',
|
||||||
parts: [
|
parts: [
|
||||||
{ text: "You are PodMan watching an engineer's screen. Identify what file/symbol they are working on and whether there are uncommitted edits. JSON only." },
|
{
|
||||||
|
text: "You are PodMan watching an engineer's screen. Identify what file/symbol they are working on and whether there are uncommitted edits. JSON only.",
|
||||||
|
},
|
||||||
{ inlineData: { mimeType: 'image/jpeg', data: jpeg.toString('base64') } },
|
{ inlineData: { mimeType: 'image/jpeg', data: jpeg.toString('base64') } },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ This is what makes PodMan "more useful the more you use it" — the ownership ma
|
|||||||
## Collections
|
## Collections
|
||||||
|
|
||||||
| Collection | Holds | Notes |
|
| Collection | Holds | Notes |
|
||||||
|---|---|---|
|
| ----------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||||
| `engineer_states` | Latest `EngineerContext` per engineer | Upserted on every `/ingest` call |
|
| `engineer_states` | Latest `EngineerContext` per engineer | Upserted on every `/ingest` call |
|
||||||
| `ownership_map` | File → primary owner + contributors | Persists across sessions — the continual learning artifact |
|
| `ownership_map` | File → primary owner + contributors | Persists across sessions — the continual learning artifact |
|
||||||
| `events` | Detected coordination events (DEPENDENCY_READY, BLOCKER_DETECTED, DUPLICATE_WORK) | Append-only |
|
| `events` | Detected coordination events (DEPENDENCY_READY, BLOCKER_DETECTED, DUPLICATE_WORK) | Append-only |
|
||||||
|
|||||||
+7
-2
@@ -26,7 +26,9 @@ async function main() {
|
|||||||
name: 'vector_index',
|
name: 'vector_index',
|
||||||
type: 'vectorSearch',
|
type: 'vectorSearch',
|
||||||
definition: {
|
definition: {
|
||||||
fields: [{ type: 'vector', path: 'embedding', numDimensions: 1024, similarity: 'cosine' }],
|
fields: [
|
||||||
|
{ type: 'vector', path: 'embedding', numDimensions: 1024, similarity: 'cosine' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -38,4 +40,7 @@ async function main() {
|
|||||||
console.log('PodMan DB initialized.');
|
console.log('PodMan DB initialized.');
|
||||||
await client.close();
|
await client.close();
|
||||||
}
|
}
|
||||||
main().catch((e) => { console.error(e); process.exit(1); });
|
main().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|||||||
+7
-1
@@ -18,18 +18,21 @@ Pre-stage checklist for the 3-minute live demo. Do this on all 3 laptops before
|
|||||||
## Laptop setup (all 3 machines)
|
## Laptop setup (all 3 machines)
|
||||||
|
|
||||||
### Editor settings
|
### Editor settings
|
||||||
|
|
||||||
- Font size: **18pt or larger** — Gemini Vision must read file names and code
|
- Font size: **18pt or larger** — Gemini Vision must read file names and code
|
||||||
- Single editor window — no split panes, no overlapping terminals
|
- Single editor window — no split panes, no overlapping terminals
|
||||||
- File tab visible with full file name shown (not truncated)
|
- File tab visible with full file name shown (not truncated)
|
||||||
- Light or dark theme is fine — avoid low-contrast themes
|
- Light or dark theme is fine — avoid low-contrast themes
|
||||||
|
|
||||||
### Browser
|
### Browser
|
||||||
|
|
||||||
- Chrome (best `getDisplayMedia` support)
|
- Chrome (best `getDisplayMedia` support)
|
||||||
- PWA tab open and joined to `demo-pod`
|
- PWA tab open and joined to `demo-pod`
|
||||||
- Earbuds / headphones plugged in and tested
|
- Earbuds / headphones plugged in and tested
|
||||||
- Volume: medium — PodMan voice should be clearly audible but not startle
|
- Volume: medium — PodMan voice should be clearly audible but not startle
|
||||||
|
|
||||||
### Screen layout
|
### Screen layout
|
||||||
|
|
||||||
- Editor takes 2/3 of screen
|
- Editor takes 2/3 of screen
|
||||||
- Terminal takes bottom 1/3 (always visible)
|
- Terminal takes bottom 1/3 (always visible)
|
||||||
- No other windows on top
|
- No other windows on top
|
||||||
@@ -41,14 +44,17 @@ Pre-stage checklist for the 3-minute live demo. Do this on all 3 laptops before
|
|||||||
Pre-create these files in the demo repo before the demo:
|
Pre-create these files in the demo repo before the demo:
|
||||||
|
|
||||||
**Alice's machine:**
|
**Alice's machine:**
|
||||||
|
|
||||||
- Open `auth/middleware.ts` — has visible function stubs
|
- Open `auth/middleware.ts` — has visible function stubs
|
||||||
- Terminal shows nothing running initially, then `Server running on :3001` at the right moment
|
- Terminal shows nothing running initially, then `Server running on :3001` at the right moment
|
||||||
|
|
||||||
**Bob's machine:**
|
**Bob's machine:**
|
||||||
|
|
||||||
- Open `frontend/login.tsx` — has visible form component code
|
- Open `frontend/login.tsx` — has visible form component code
|
||||||
- Terminal idle
|
- Terminal idle
|
||||||
|
|
||||||
**Carol's machine:**
|
**Carol's machine:**
|
||||||
|
|
||||||
- Open `frontend/integration.ts` or similar
|
- Open `frontend/integration.ts` or similar
|
||||||
- Terminal shows: `curl http://localhost:3001/auth` → `curl: (7) Failed to connect`
|
- Terminal shows: `curl http://localhost:3001/auth` → `curl: (7) Failed to connect`
|
||||||
|
|
||||||
@@ -57,7 +63,7 @@ Pre-create these files in the demo repo before the demo:
|
|||||||
## Demo script timing
|
## Demo script timing
|
||||||
|
|
||||||
| Time | Action | Who |
|
| Time | Action | Who |
|
||||||
|---|---|---|
|
| ----- | ----------------------------------------------- | ----------- |
|
||||||
| 0:00 | All three join `demo-pod` | All |
|
| 0:00 | All three join `demo-pod` | All |
|
||||||
| 0:05 | PodMan greets by voice | Hermes auto |
|
| 0:05 | PodMan greets by voice | Hermes auto |
|
||||||
| 0:20 | Alice opens `auth/middleware.ts`, starts typing | Alice |
|
| 0:20 | Alice opens `auth/middleware.ts`, starts typing | Alice |
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ static_sites:
|
|||||||
## Fallback plan (if App Platform deploy fails on stage)
|
## Fallback plan (if App Platform deploy fails on stage)
|
||||||
|
|
||||||
Run Hermes locally:
|
Run Hermes locally:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd backend && pnpm dev
|
cd backend && pnpm dev
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ Respond with the message text only.
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Example output:**
|
**Example output:**
|
||||||
|
|
||||||
> "Carol — Alice just got the auth endpoint running. You're clear to integrate."
|
> "Carol — Alice just got the auth endpoint running. You're clear to integrate."
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -115,6 +116,7 @@ Respond with the message text only.
|
|||||||
**Integration:** LiveKit Agents framework — Hermes runs as a LiveKit Agent with Gemini Live 2.5 as the voice provider
|
**Integration:** LiveKit Agents framework — Hermes runs as a LiveKit Agent with Gemini Live 2.5 as the voice provider
|
||||||
|
|
||||||
**Flow:**
|
**Flow:**
|
||||||
|
|
||||||
1. Nudge message text generated (step 3)
|
1. Nudge message text generated (step 3)
|
||||||
2. Hermes passes text to Gemini Live via LiveKit Agents
|
2. Hermes passes text to Gemini Live via LiveKit Agents
|
||||||
3. Gemini Live streams audio back in real-time
|
3. Gemini Live streams audio back in real-time
|
||||||
@@ -122,6 +124,7 @@ Respond with the message text only.
|
|||||||
5. All participants hear it through their audio output
|
5. All participants hear it through their audio output
|
||||||
|
|
||||||
**Why Gemini Live (not plain TTS):**
|
**Why Gemini Live (not plain TTS):**
|
||||||
|
|
||||||
- Streams audio directly — no intermediate WAV file conversion
|
- Streams audio directly — no intermediate WAV file conversion
|
||||||
- Latency ~300–500ms from text to first audio packet
|
- Latency ~300–500ms from text to first audio packet
|
||||||
- Natural-sounding voice
|
- Natural-sounding voice
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ Each engineer opens a browser PWA on their laptop. The PWA captures a screen fra
|
|||||||
## Prize alignment
|
## Prize alignment
|
||||||
|
|
||||||
| Prize | How PodMan earns it |
|
| Prize | How PodMan earns it |
|
||||||
|---|---|
|
| --------------------- | ----------------------------------------------------------------------------------------------------- |
|
||||||
| Best Gemini 3.5 / 2.5 | Gemini Vision for screen understanding + Gemini Live 2.5 for voice output |
|
| Best Gemini 3.5 / 2.5 | Gemini Vision for screen understanding + Gemini Live 2.5 for voice output |
|
||||||
| Best LiveKit | LiveKit is the real-time backbone for room presence and voice delivery — load-bearing, not decorative |
|
| Best LiveKit | LiveKit is the real-time backbone for room presence and voice delivery — load-bearing, not decorative |
|
||||||
| Best DigitalOcean | Hermes deployed on DigitalOcean App Platform; MongoDB Atlas on DO-adjacent infrastructure |
|
| Best DigitalOcean | Hermes deployed on DigitalOcean App Platform; MongoDB Atlas on DO-adjacent infrastructure |
|
||||||
|
|||||||
@@ -16,17 +16,20 @@ LiveKit is the real-time backbone for PodMan. It handles room presence and voice
|
|||||||
## Engineer side (PWA)
|
## Engineer side (PWA)
|
||||||
|
|
||||||
**Joining:**
|
**Joining:**
|
||||||
|
|
||||||
1. PWA calls `POST /pods/:podId/token` → receives `{ token, url }`
|
1. PWA calls `POST /pods/:podId/token` → receives `{ token, url }`
|
||||||
2. LiveKit client connects to the room with the token
|
2. LiveKit client connects to the room with the token
|
||||||
3. PWA publishes screen track via `getDisplayMedia` (used client-side for frame capture — Hermes does NOT subscribe to this track)
|
3. PWA publishes screen track via `getDisplayMedia` (used client-side for frame capture — Hermes does NOT subscribe to this track)
|
||||||
4. PWA sets mic enabled for ambient presence
|
4. PWA sets mic enabled for ambient presence
|
||||||
|
|
||||||
**Receiving:**
|
**Receiving:**
|
||||||
|
|
||||||
- LiveKit client automatically receives Hermes audio track
|
- LiveKit client automatically receives Hermes audio track
|
||||||
- No special subscription needed — LiveKit delivers audio to all participants
|
- No special subscription needed — LiveKit delivers audio to all participants
|
||||||
- PWA also listens for data channel messages from Hermes for UI card updates
|
- PWA also listens for data channel messages from Hermes for UI card updates
|
||||||
|
|
||||||
**Data channel listener (PWA):**
|
**Data channel listener (PWA):**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
room.on(RoomEvent.DataReceived, (payload, participant) => {
|
room.on(RoomEvent.DataReceived, (payload, participant) => {
|
||||||
if (participant?.identity !== 'podman-hermes') return;
|
if (participant?.identity !== 'podman-hermes') return;
|
||||||
@@ -43,17 +46,20 @@ room.on(RoomEvent.DataReceived, (payload, participant) => {
|
|||||||
**Framework:** LiveKit Agents (Node.js)
|
**Framework:** LiveKit Agents (Node.js)
|
||||||
|
|
||||||
**Startup:**
|
**Startup:**
|
||||||
|
|
||||||
1. Hermes mints its own token via the same `createPodToken` function with `identity: 'podman-hermes'`
|
1. Hermes mints its own token via the same `createPodToken` function with `identity: 'podman-hermes'`
|
||||||
2. Connects to the room on pod creation / first engineer joining
|
2. Connects to the room on pod creation / first engineer joining
|
||||||
3. Registers as a LiveKit Agent with Gemini Live 2.5 as voice provider
|
3. Registers as a LiveKit Agent with Gemini Live 2.5 as voice provider
|
||||||
|
|
||||||
**Voice delivery:**
|
**Voice delivery:**
|
||||||
|
|
||||||
1. Nudge message text is ready (from Gemini text generation)
|
1. Nudge message text is ready (from Gemini text generation)
|
||||||
2. Hermes passes text to Gemini Live 2.5 via LiveKit Agents voice pipeline
|
2. Hermes passes text to Gemini Live 2.5 via LiveKit Agents voice pipeline
|
||||||
3. Audio streams into the room in real-time
|
3. Audio streams into the room in real-time
|
||||||
4. All participants hear it
|
4. All participants hear it
|
||||||
|
|
||||||
**Data channel message (sent alongside audio):**
|
**Data channel message (sent alongside audio):**
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const nudge = {
|
const nudge = {
|
||||||
type: 'DEPENDENCY_READY' | 'BLOCKER_DETECTED' | 'DUPLICATE_WORK',
|
type: 'DEPENDENCY_READY' | 'BLOCKER_DETECTED' | 'DUPLICATE_WORK',
|
||||||
@@ -75,6 +81,7 @@ room.localParticipant.publishData(
|
|||||||
Already implemented at `POST /pods/:podId/token`.
|
Already implemented at `POST /pods/:podId/token`.
|
||||||
|
|
||||||
Hermes uses the same endpoint. Grants:
|
Hermes uses the same endpoint. Grants:
|
||||||
|
|
||||||
- `roomJoin: true`
|
- `roomJoin: true`
|
||||||
- `canPublish: true` (for audio track)
|
- `canPublish: true` (for audio track)
|
||||||
- `canPublishData: true` (for data channel)
|
- `canPublishData: true` (for data channel)
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ Tracks who works on which files. Built up over the session. **Persists across se
|
|||||||
**Index:** `{ podId: 1, file: 1 }` (unique)
|
**Index:** `{ podId: 1, file: 1 }` (unique)
|
||||||
|
|
||||||
**Upsert logic:**
|
**Upsert logic:**
|
||||||
|
|
||||||
- On each context update where `currentFile` is non-null:
|
- On each context update where `currentFile` is non-null:
|
||||||
- Increment `observationCount`
|
- Increment `observationCount`
|
||||||
- Update `primaryOwner` to the engineer with the most recent `lastSeenAt` on this file
|
- Update `primaryOwner` to the engineer with the most recent `lastSeenAt` on this file
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ PodMan is a real-time AI team coordination agent for software teams. Engineers j
|
|||||||
## Components
|
## Components
|
||||||
|
|
||||||
### PWA (local agent)
|
### PWA (local agent)
|
||||||
|
|
||||||
- Joins LiveKit room via existing `joinPod` flow
|
- Joins LiveKit room via existing `joinPod` flow
|
||||||
- Captures frame every 30s via `getDisplayMedia`, compresses to JPEG (1280×720, quality 0.7)
|
- Captures frame every 30s via `getDisplayMedia`, compresses to JPEG (1280×720, quality 0.7)
|
||||||
- POSTs `{ engineerId, podId, screenshotBase64, capturedAt }` to `POST /ingest`
|
- POSTs `{ engineerId, podId, screenshotBase64, capturedAt }` to `POST /ingest`
|
||||||
@@ -54,6 +55,7 @@ PodMan is a real-time AI team coordination agent for software teams. Engineers j
|
|||||||
- Two screens: join screen (built), active session screen (to build)
|
- Two screens: join screen (built), active session screen (to build)
|
||||||
|
|
||||||
### Hermes (orchestrator)
|
### Hermes (orchestrator)
|
||||||
|
|
||||||
- Express server + LiveKit Agent on DigitalOcean
|
- Express server + LiveKit Agent on DigitalOcean
|
||||||
- `POST /ingest`: receives frame, queues for vision
|
- `POST /ingest`: receives frame, queues for vision
|
||||||
- Vision pipeline: Gemini 2.0 Flash → `EngineerContext`
|
- Vision pipeline: Gemini 2.0 Flash → `EngineerContext`
|
||||||
@@ -66,18 +68,21 @@ PodMan is a real-time AI team coordination agent for software teams. Engineers j
|
|||||||
- Cooldown: 3 min between nudges per pod
|
- Cooldown: 3 min between nudges per pod
|
||||||
|
|
||||||
### Gemini usage
|
### Gemini usage
|
||||||
|
|
||||||
- **Vision:** `gemini-2.0-flash` — screen → `{ currentFile, inferredTask, terminalVisible, recentTerminalOutput, confidence }`
|
- **Vision:** `gemini-2.0-flash` — screen → `{ currentFile, inferredTask, terminalVisible, recentTerminalOutput, confidence }`
|
||||||
- **Event detection:** `gemini-2.0-flash` — all engineer states → `{ event, involvedEngineers, file, reason }`
|
- **Event detection:** `gemini-2.0-flash` — all engineer states → `{ event, involvedEngineers, file, reason }`
|
||||||
- **Nudge generation:** `gemini-2.0-flash` — event → spoken message text
|
- **Nudge generation:** `gemini-2.0-flash` — event → spoken message text
|
||||||
- **Voice:** `gemini-live-2.5-flash` via LiveKit Agents — text → streaming audio
|
- **Voice:** `gemini-live-2.5-flash` via LiveKit Agents — text → streaming audio
|
||||||
|
|
||||||
### MongoDB Atlas (4 collections)
|
### MongoDB Atlas (4 collections)
|
||||||
|
|
||||||
- `engineer_states`: latest context per engineer, upserted each ingest
|
- `engineer_states`: latest context per engineer, upserted each ingest
|
||||||
- `ownership_map`: file → primaryOwner + contributors, persists across sessions (continual learning)
|
- `ownership_map`: file → primaryOwner + contributors, persists across sessions (continual learning)
|
||||||
- `events`: all detected coordination events
|
- `events`: all detected coordination events
|
||||||
- `nudges`: all voice nudges sent + cooldown history
|
- `nudges`: all voice nudges sent + cooldown history
|
||||||
|
|
||||||
### LiveKit
|
### LiveKit
|
||||||
|
|
||||||
- One room per pod
|
- One room per pod
|
||||||
- Engineers publish screen track (used client-side for capture — Hermes does not subscribe)
|
- Engineers publish screen track (used client-side for capture — Hermes does not subscribe)
|
||||||
- Hermes joins as `podman-hermes`, publishes audio + data channel messages
|
- Hermes joins as `podman-hermes`, publishes audio + data channel messages
|
||||||
@@ -88,7 +93,7 @@ PodMan is a real-time AI team coordination agent for software teams. Engineers j
|
|||||||
## Event types
|
## Event types
|
||||||
|
|
||||||
| Event | Trigger | Example nudge |
|
| Event | Trigger | Example nudge |
|
||||||
|---|---|---|
|
| ------------------ | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|
||||||
| `BLOCKER_DETECTED` | Engineer stuck (error in terminal, same file N frames) + teammate can help | "Carol, looks like you're waiting on auth. Alice is actively building it — hang tight." |
|
| `BLOCKER_DETECTED` | Engineer stuck (error in terminal, same file N frames) + teammate can help | "Carol, looks like you're waiting on auth. Alice is actively building it — hang tight." |
|
||||||
| `DEPENDENCY_READY` | Engineer A completes work that Engineer B was waiting on | "Carol, Bob — Alice just got the auth endpoint running. You're clear to integrate." |
|
| `DEPENDENCY_READY` | Engineer A completes work that Engineer B was waiting on | "Carol, Bob — Alice just got the auth endpoint running. You're clear to integrate." |
|
||||||
| `DUPLICATE_WORK` | 2+ engineers on same file simultaneously | "Alice and Bob — you're both in login.tsx. Coordinate before pushing." |
|
| `DUPLICATE_WORK` | 2+ engineers on same file simultaneously | "Alice and Bob — you're both in login.tsx. Coordinate before pushing." |
|
||||||
@@ -98,6 +103,7 @@ PodMan is a real-time AI team coordination agent for software teams. Engineers j
|
|||||||
## Continual learning story
|
## Continual learning story
|
||||||
|
|
||||||
The `ownership_map` collection persists across sessions. On Hermes startup:
|
The `ownership_map` collection persists across sessions. On Hermes startup:
|
||||||
|
|
||||||
1. Load ownership map for this pod from Atlas
|
1. Load ownership map for this pod from Atlas
|
||||||
2. Build in-memory cache: `Map<file, { primaryOwner, contributors }>`
|
2. Build in-memory cache: `Map<file, { primaryOwner, contributors }>`
|
||||||
3. Event detection uses priors immediately — no ramp-up phase
|
3. Event detection uses priors immediately — no ramp-up phase
|
||||||
@@ -121,7 +127,7 @@ The `ownership_map` collection persists across sessions. On Hermes startup:
|
|||||||
## Key risks
|
## Key risks
|
||||||
|
|
||||||
| Risk | Mitigation |
|
| Risk | Mitigation |
|
||||||
|---|---|
|
| -------------------------------------------- | ------------------------------------------------- |
|
||||||
| Gemini Vision accuracy | Large font, single editor window, confidence gate |
|
| Gemini Vision accuracy | Large font, single editor window, confidence gate |
|
||||||
| Gemini Live 2.5 + LiveKit Agents integration | Build together hour 5–7, have TTS fallback |
|
| Gemini Live 2.5 + LiveKit Agents integration | Build together hour 5–7, have TTS fallback |
|
||||||
| Frame POST latency | JPEG compression, target < 500ms |
|
| Frame POST latency | JPEG compression, target < 500ms |
|
||||||
|
|||||||
@@ -7,6 +7,17 @@ export default tseslint.config(
|
|||||||
},
|
},
|
||||||
js.configs.recommended,
|
js.configs.recommended,
|
||||||
...tseslint.configs.recommended,
|
...tseslint.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ['scripts/**/*.mjs'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
console: 'readonly',
|
||||||
|
process: 'readonly',
|
||||||
|
setTimeout: 'readonly',
|
||||||
|
URL: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
rules: {
|
rules: {
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
|||||||
+2
-11
@@ -192,20 +192,11 @@ export default function App() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{joinedPod ? (
|
{joinedPod ? (
|
||||||
<PodView
|
<PodView team={joinedPod} me={member} room={room} devMode={devMode} onLeave={handleLeave} />
|
||||||
team={joinedPod}
|
|
||||||
me={member}
|
|
||||||
room={room}
|
|
||||||
devMode={devMode}
|
|
||||||
onLeave={handleLeave}
|
|
||||||
/>
|
|
||||||
) : showReconnecting ? (
|
) : showReconnecting ? (
|
||||||
<div className="flex flex-col items-start gap-3">
|
<div className="flex flex-col items-start gap-3">
|
||||||
<p className="text-sm text-slate-400">Reconnecting to your pod…</p>
|
<p className="text-sm text-slate-400">Reconnecting to your pod…</p>
|
||||||
<button
|
<button className="text-xs text-slate-500 hover:text-slate-300" onClick={handleLeave}>
|
||||||
className="text-xs text-slate-500 hover:text-slate-300"
|
|
||||||
onClick={handleLeave}
|
|
||||||
>
|
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -170,9 +170,7 @@ export function PodView({
|
|||||||
onClick={toggleBeat}
|
onClick={toggleBeat}
|
||||||
disabled={!room}
|
disabled={!room}
|
||||||
className={`rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${
|
className={`rounded-md px-4 py-2 text-sm font-medium disabled:opacity-50 ${
|
||||||
playingBeat
|
playingBeat ? 'bg-red-600 hover:bg-red-500' : 'bg-emerald-600 hover:bg-emerald-500'
|
||||||
? 'bg-red-600 hover:bg-red-500'
|
|
||||||
: 'bg-emerald-600 hover:bg-emerald-500'
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{playingBeat ? '⏹ Stop beat' : '▶ Play beat'}
|
{playingBeat ? '⏹ Stop beat' : '▶ Play beat'}
|
||||||
@@ -185,7 +183,9 @@ export function PodView({
|
|||||||
{sharing ? '🛑 Stop sharing' : '📺 Share my screen'}
|
{sharing ? '🛑 Stop sharing' : '📺 Share my screen'}
|
||||||
</button>
|
</button>
|
||||||
<span className="ml-auto text-xs text-slate-400">
|
<span className="ml-auto text-xs text-slate-400">
|
||||||
{playingBeat ? '🔊 broadcasting beat to the pod' : 'press “Play beat” — everyone should hear it'}
|
{playingBeat
|
||||||
|
? '🔊 broadcasting beat to the pod'
|
||||||
|
: 'press “Play beat” — everyone should hear it'}
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
{note && <p className="text-sm text-amber-400">{note}</p>}
|
{note && <p className="text-sm text-amber-400">{note}</p>}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ export function useInterventions(room: Room | null) {
|
|||||||
if (msg.type === 'COLLISION') setActive(msg.intervention);
|
if (msg.type === 'COLLISION') setActive(msg.intervention);
|
||||||
};
|
};
|
||||||
room.on(RoomEvent.DataReceived, onData);
|
room.on(RoomEvent.DataReceived, onData);
|
||||||
return () => { room.off(RoomEvent.DataReceived, onData); };
|
return () => {
|
||||||
|
room.off(RoomEvent.DataReceived, onData);
|
||||||
|
};
|
||||||
}, [room]);
|
}, [room]);
|
||||||
|
|
||||||
const respond = useCallback(
|
const respond = useCallback(
|
||||||
|
|||||||
@@ -7,14 +7,17 @@ export function useScreenPublish() {
|
|||||||
const [connected, setConnected] = useState(false);
|
const [connected, setConnected] = useState(false);
|
||||||
const [sharing, setSharing] = useState(false);
|
const [sharing, setSharing] = useState(false);
|
||||||
|
|
||||||
const join = useCallback(async (pod: string, identity: string, name: string, githubLogin?: string) => {
|
const join = useCallback(
|
||||||
|
async (pod: string, identity: string, name: string, githubLogin?: string) => {
|
||||||
const { token, url } = await fetchToken({ room: pod, identity, name, githubLogin });
|
const { token, url } = await fetchToken({ room: pod, identity, name, githubLogin });
|
||||||
const room = new Room({ adaptiveStream: true, dynacast: true });
|
const room = new Room({ adaptiveStream: true, dynacast: true });
|
||||||
await room.connect(url, token);
|
await room.connect(url, token);
|
||||||
roomRef.current = room;
|
roomRef.current = room;
|
||||||
setConnected(true);
|
setConnected(true);
|
||||||
return room;
|
return room;
|
||||||
}, []);
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
const startSharing = useCallback(async () => {
|
const startSharing = useCallback(async () => {
|
||||||
const room = roomRef.current;
|
const room = roomRef.current;
|
||||||
|
|||||||
Reference in New Issue
Block a user