hivecode-mcp 0.14.3 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HIVE_RULES.md +41 -98
- package/docs/TROUBLESHOOTING.md +3 -0
- package/hive-await.js +57 -21
- package/hive-gate.js +116 -2
- package/hive-instructions.js +110 -61
- package/hive-mailbox.js +74 -7
- package/hive-mcp.js +200 -42
- package/hive-notify.js +21 -5
- package/hive-receipts.js +56 -5
- package/hive-room-mode.js +13 -0
- package/hive-room-watcher.js +30 -12
- package/hive-seen.js +135 -0
- package/hive-slack.js +27 -2
- package/hive-stop-hook.js +33 -18
- package/hive-transport.js +39 -4
- package/package.json +3 -2
- package/sync.js +172 -194
- package/docs/HIVE_RULES.md +0 -41
package/sync.js
CHANGED
|
@@ -27,7 +27,8 @@ import ignore from 'ignore'
|
|
|
27
27
|
import { applyDiff, summarizeChange, changedRange, hasConflictMarkers, merge3 } from './core.js'
|
|
28
28
|
import { icrMerge3 } from './icr-merge.js' // structure/intent-aware merge; falls back to line merge3
|
|
29
29
|
import { makeCoordinator } from './hive-coord.js' // decentralized claim layer (collision PREVENTION)
|
|
30
|
-
import {
|
|
30
|
+
import { RULES_MD, AGENT_MANUAL } from './hive-instructions.js'
|
|
31
|
+
import { gateConfig, runGate, verdictRecord, refusalMessage, forcedMessage, gateApproved, gatesDir, writeApproval, notApprovedMessage, notApprovedLine, GATE_CMD, GATE_NOT_APPROVED } from './hive-gate.js' // the HANDOFF gate: a claim you cannot give back while your own check fails
|
|
31
32
|
import { fileRoom, decodeUnsafe, scopeForRoom, pathAllowed, writeAllowed, isSafeRelPath } from './token.js'
|
|
32
33
|
import { validateMemberName } from './hive-mailbox.js' // a name the mention parser cannot produce is not a name
|
|
33
34
|
import { genIdentity, authorChange, verifyReceipt, contentHash } from './substrate.js' // convergent-substrate: signed provenance for every landed change
|
|
@@ -229,149 +230,12 @@ const OWN_LOCAL = new Set([SHADOW_FILE, SHADOW_FILE + '.tmp', LOCK_FILE, '.hive-
|
|
|
229
230
|
// Written into every room folder on sync (like the rules). An AI agent that opens
|
|
230
231
|
// this project reads it to learn it's a Hivecode room and how to JOIN — via the
|
|
231
232
|
// .hive.json already in the folder, so no human has to paste a link.
|
|
232
|
-
const AGENT_MANUAL_TEXT =
|
|
233
|
-
|
|
234
|
-
This project is part of a live Hivecode room: humans and AI agents editing the same
|
|
235
|
-
codebase together, in real time, with no git push/pull. If you are an AI assistant
|
|
236
|
-
asked to collaborate here, follow this.
|
|
237
|
-
|
|
238
|
-
## Join the room
|
|
239
|
-
1. If you do NOT have a \`hive_join\` tool and you can run terminal commands, register
|
|
240
|
-
the Hivecode MCP server, then reload so the tools load:
|
|
241
|
-
claude mcp add hivecode -- npx -y hivecode-mcp@latest
|
|
242
|
-
2. Call: hive_join({ dir: ".", name: "<your name>", owner: "<the human you work for>" })
|
|
243
|
-
The room, relay, and token are already saved in .hive.json in this folder, so you
|
|
244
|
-
do NOT need a link — hive_join reads them automatically.
|
|
245
|
-
3. Read the HIVE_RULES it returns, and follow them.
|
|
246
|
-
|
|
247
|
-
## Work the loop
|
|
248
|
-
- hive_read_chat() and hive_read_board() BEFORE editing — see what others are doing.
|
|
249
|
-
- hive_claim("path/to/file") BEFORE you edit a file. If you get it, edit then
|
|
250
|
-
hive_release it. If you DON'T, someone else holds it — pick other work
|
|
251
|
-
(hive_claims() shows what's open). This is how agents avoid colliding.
|
|
252
|
-
- hive_say("taking X: doing Y") to announce intent before you edit.
|
|
253
|
-
- ADDRESS EVERY MESSAGE. Put @Name on it for each agent it is for -- hive_say("@Ana I need the churn numbers before I touch pricing.md"). Any agent may address any other agent directly; there is no lead to route through and nobody needs permission. A message that names nobody lands in every agent's "room activity", where they are told to skip it, so it will most likely be ignored. Use @all when you genuinely need the whole room -- it wakes everyone, so spending it on chatter is how a room learns to ignore you.
|
|
254
|
-
- ANSWER EVERY QUESTION AIMED AT YOU. "Not yet -- I am on X, I will come back to you" is a complete answer; silence is the one thing the asker cannot act on, because nothing tells them you stopped. hive_check lists anyone still waiting on you.
|
|
255
|
-
- BEFORE A LONG STRETCH OF WORK, call hive_busy("what you are doing"). It posts no message and silences nothing -- it tells anyone who @mentions you, at the moment they send, that you are mid-task rather than ignoring them. hive_busy(clear:true) when you finish.
|
|
256
|
-
- Edit ONLY inside the folders you were granted. Edits merge live; rewrites are logged.
|
|
257
|
-
- Block on hive_wait(); when it returns approved work, do it, then hive_complete(id).
|
|
258
|
-
- A task aimed at you stays PENDING until your owner approves it — act only on 'accepted'.
|
|
259
|
-
|
|
260
|
-
## Rules of the road
|
|
261
|
-
- Stay in your lane — never edit folders you weren't granted.
|
|
262
|
-
- Announce before editing; coordinate in chat; ask before anything destructive in others' areas.
|
|
263
|
-
- If a human PAUSES you, finish your current step and stop until resumed.
|
|
264
|
-
- Resolve <<<<<<< conflict markers properly — never blindly overwrite.
|
|
265
|
-
- A human can ROLL BACK your changes at any time; that's normal — re-read and continue.
|
|
266
|
-
|
|
267
|
-
## If something breaks
|
|
268
|
-
Support is support@getgrm.tech. Mention it only if the human asks, or if something
|
|
269
|
-
here is genuinely broken and they need somewhere to send it. Never volunteer it
|
|
270
|
-
otherwise, and never write it into a file.
|
|
271
|
-
`
|
|
233
|
+
const AGENT_MANUAL_TEXT = AGENT_MANUAL // hive-instructions.js: one source for every agent-facing text (0.14.4)
|
|
272
234
|
|
|
273
235
|
// The hive's law. Auto-written into every room folder so it is ALWAYS present —
|
|
274
236
|
// no setup, no relying on an agent to remember it. The sync layer enforces the
|
|
275
237
|
// hard parts (merge, board); this is the rest, in plain imperative language.
|
|
276
|
-
const HIVE_RULES_TEXT =
|
|
277
|
-
|
|
278
|
-
You are in a Hivecode room: a shared live workspace where humans and AI agents
|
|
279
|
-
edit ONE project together in real time. These rules keep anyone from destroying
|
|
280
|
-
another's work. The sync layer enforces the hard parts automatically; you do the rest.
|
|
281
|
-
|
|
282
|
-
## Identity
|
|
283
|
-
- You are automatically "human" (you ran the editor) or "ai" (you ran the agent
|
|
284
|
-
client). Nobody declares it — running the client is the declaration.
|
|
285
|
-
|
|
286
|
-
## Know who you are working with
|
|
287
|
-
- Read HIVE_MEMBERS.md — who (humans + AIs) is in the room right now, and how many.
|
|
288
|
-
|
|
289
|
-
## DESIGN BEFORE YOU WRITE — mandatory (Ganesh, 19 Sep 2026)
|
|
290
|
-
Before you build any new feature, post in the room, BEFORE your first edit:
|
|
291
|
-
1. WHO uses it, and when — name the person or agent and the moment they reach for it.
|
|
292
|
-
2. WHAT IT COSTS when it is wrong — one sentence someone who did not write it can act on at 3am.
|
|
293
|
-
3. EVERY EDGE CASE you can name — empty, missing, offline, refused, concurrent, already-done,
|
|
294
|
-
called twice, called by the wrong person.
|
|
295
|
-
Then build it.
|
|
296
|
-
This is not process for its own sake. Every defect this project has produced has been a FALSE
|
|
297
|
-
SUCCESS — nothing crashed, every signal was green, the result was wrong. That class is never
|
|
298
|
-
caught by testing the happy path afterwards; it is caught by asking first what "wrong but green"
|
|
299
|
-
would look like for this particular feature. If you cannot say what breaks, you do not understand
|
|
300
|
-
it well enough to write it — say so in the room and ask. That is a complete answer.
|
|
301
|
-
|
|
302
|
-
## Before you touch a file
|
|
303
|
-
1. Read HIVE_CHAT.md — what is everyone doing right now.
|
|
304
|
-
2. Read HIVE_BOARD.md — which files were just rewritten (and what was touched).
|
|
305
|
-
3. If a file you plan to edit appears there, RE-READ it before changing it.
|
|
306
|
-
|
|
307
|
-
## CLAIM before you edit (this is how the hive avoids collisions)
|
|
308
|
-
- Before editing a file, CLAIM it: call the hive_claim tool with the file path
|
|
309
|
-
(and a short intent). This is the most important coordination step.
|
|
310
|
-
• If you GOT it: edit it, then call hive_release when you finish.
|
|
311
|
-
• If you could NOT get it: someone else holds it — do NOT edit it. Pick other
|
|
312
|
-
work; call hive_claims to see what's open.
|
|
313
|
-
- Claims auto-expire, so a stalled agent never blocks the hive — but always
|
|
314
|
-
hive_release the moment you finish, so others can take it.
|
|
315
|
-
|
|
316
|
-
## While you work
|
|
317
|
-
4. ANNOUNCE first: post to chat what you are taking, e.g.
|
|
318
|
-
call hive_say with "taking auth.js: adding login validation"
|
|
319
|
-
5. PREFER SMALL PATCHES — grep to the spot, edit a few lines. Patches from
|
|
320
|
-
different agents merge automatically with no conflict.
|
|
321
|
-
6. AVOID full-file rewrites unless necessary. If you must rewrite, RE-READ the
|
|
322
|
-
file first so you build on the latest code (rewrites are auto-logged for all).
|
|
323
|
-
7. STAY IN YOUR LANE — if someone said "I own X" (e.g. the backend), read X but
|
|
324
|
-
do not edit it; leave that to the owner.
|
|
325
|
-
|
|
326
|
-
## When things collide
|
|
327
|
-
8. If you see <<<<<<< ======= >>>>>>> markers in a file, the system could not
|
|
328
|
-
auto-merge — RESOLVE it: keep the right code, delete the markers. Never ignore
|
|
329
|
-
them or blindly overwrite.
|
|
330
|
-
9. If your edit was merged/reworked, that is normal — re-read and continue.
|
|
331
|
-
|
|
332
|
-
## Talking
|
|
333
|
-
10. Coordinate in chat. ASK before anything destructive (delete, rename, big
|
|
334
|
-
refactor) that touches another participant's area.
|
|
335
|
-
|
|
336
|
-
## Directed work + permission (the asymmetric gate)
|
|
337
|
-
11. AI -> AI is COORDINATION: if another agent hands you work (via hive_assign), it is auto-accepted
|
|
338
|
-
and you may proceed. If there is no Lead PM setting global state, use hive_assign and hive_say
|
|
339
|
-
to assign tasks directly to other agents to divide and conquer. This is how a leaderless hive
|
|
340
|
-
plans and divides work by itself.
|
|
341
|
-
12. A HUMAN directing you is different. If a human who is NOT your owner assigns
|
|
342
|
-
you a task, you do NOT act on it — it stays PENDING until YOUR OWNER approves
|
|
343
|
-
("do it or ignore?"). Your own owner's requests proceed. Act only on tasks
|
|
344
|
-
whose status is 'accepted'.
|
|
345
|
-
13. If you are an MCP agent: loop on hive_wait — it blocks until accepted work
|
|
346
|
-
arrives, then you do it and call hive_complete. No need to poll. A pending
|
|
347
|
-
human request will NOT wake you until your owner approves it.
|
|
348
|
-
|
|
349
|
-
## Mission control (a human can pause/steer you)
|
|
350
|
-
- If you are PAUSED, finish your current step and STOP — do not start new work
|
|
351
|
-
until you are resumed. (MCP agents: hive_wait returns no work while paused.)
|
|
352
|
-
- A human may REASSIGN your focus at any time; treat a fresh directive from your
|
|
353
|
-
owner as the new priority.
|
|
354
|
-
|
|
355
|
-
## When a ping arrives while you are mid-task (interruptions)
|
|
356
|
-
14. hive_wait only checks BETWEEN steps, so a ping never interrupts mid-step.
|
|
357
|
-
Finish your current atomic step first (never abandon half-done work), then
|
|
358
|
-
handle queued coordination. If it's urgent (build broken, blocking others),
|
|
359
|
-
do it now.
|
|
360
|
-
15. ACKNOWLEDGE a ping as soon as you see it so the sender isn't left hanging,
|
|
361
|
-
e.g. say "got it — finishing X (~2 min), then on your fix".
|
|
362
|
-
16. YOU triage: announce do-now vs after-current. Your OWNER can override anytime
|
|
363
|
-
— if your owner says "do it now" or "skip that", that wins.
|
|
364
|
-
|
|
365
|
-
## State-Machine Prompting (SMP)
|
|
366
|
-
17. The Lead PM controls the [GLOBAL PROJECT STATE] via hive_set_state.
|
|
367
|
-
18. ONLY use short enum flags (e.g. "APPROVED", "LOCKED"). Do NOT store paragraphs of text in the state, it will bloat the context window.
|
|
368
|
-
19. If you are a worker agent, block on hive_wait until your specific state flag turns to APPROVED/READY. Do not write code if you are LOCKED.
|
|
369
|
-
|
|
370
|
-
Read → announce → patch → respect lanes → resolve conflicts → talk → triage pings → wait for approval.
|
|
371
|
-
|
|
372
|
-
---
|
|
373
|
-
Support: support@getgrm.tech
|
|
374
|
-
`
|
|
238
|
+
const HIVE_RULES_TEXT = RULES_MD // hive-instructions.js (0.14.4); HIVE_RULES.md in the package is generated from it
|
|
375
239
|
|
|
376
240
|
/**
|
|
377
241
|
* Where an account licence may come from, in precedence order — and where it may NOT.
|
|
@@ -461,7 +325,10 @@ export function acquireFolderLock(root, room, log = console.log) {
|
|
|
461
325
|
return release
|
|
462
326
|
}
|
|
463
327
|
|
|
464
|
-
export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir = '.', name = 'anon', kind = 'human', owner = '', token = '', license = '', log = console.log, syncFiles = true, passive = false, listener = false, app = '' }) {
|
|
328
|
+
export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir = '.', name = 'anon', kind = 'human', owner = '', token = '', license = '', log = console.log, syncFiles = true, passive = false, listener = false, app = '', gatesDir: gatesDirOpt = '' }) {
|
|
329
|
+
// Where this machine keeps its gate approvals (0.14.4 S-1): outside the synced folder, so no peer can write one.
|
|
330
|
+
const GATES_DIR = gatesDirOpt || gatesDir()
|
|
331
|
+
const gateSkipSaid = new Set() // (agent, command) pairs already announced as unchecked
|
|
465
332
|
// BEFORE ANY DISK OR SOCKET WORK. A name outside the mention grammar joins successfully and is
|
|
466
333
|
// then unreachable: its mail, its obligations and its wake-on-mention are all silently dead,
|
|
467
334
|
// and the only symptom is other people being told "@X is NOT in the room" about someone who is
|
|
@@ -960,7 +827,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
960
827
|
// Carry each side's SIGNED intent into the announcement: the WHY is what lets any
|
|
961
828
|
// agent in the room reconcile the fork (via hive_resolve) instead of picking a winner.
|
|
962
829
|
const why = rec.versions.map((v) => `${v.name}: "${versionIntent(fe, rec, v.hash) || 'no stated intent'}"`).join(' | ')
|
|
963
|
-
try {
|
|
830
|
+
try { postOnce(`fork:${relPath}:${contentHash(rec.versions.map((v) => v.hash).sort().join(',')).slice(0, 16)}`, `⚠ SILENT FORK in ${relPath}: ${who} changed the same region concurrently. Signed intents — ${why}. Surfaced ALL ${rec.versions.length} versions with <<<<<<< markers. ANY agent may reconcile BOTH intents: call hive_resolve with the full resolved file — it is validated (parses, no dangling refs) before it lands.`, { observer: true }) } catch { }
|
|
964
831
|
}
|
|
965
832
|
}
|
|
966
833
|
// A resolution landed elsewhere: the shared text is clean again. Adopt it to disk.
|
|
@@ -1258,7 +1125,8 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
1258
1125
|
// to everybody; this is what lets a join tell another agent with the same name (refused, see
|
|
1259
1126
|
// sameNameElsewhere) from this agent a moment ago, restarting in the same folder (allowed).
|
|
1260
1127
|
const IID = crypto.createHash('sha256').update(`${os.hostname()}|${ROOT}`).digest('hex').slice(0, 12)
|
|
1261
|
-
|
|
1128
|
+
const joinedAt = Date.now()
|
|
1129
|
+
provider.awareness.setLocalStateField('user', { name, kind, owner: owner || undefined, v: PROTOCOL_VERSION, hv: VERSION_LABEL, passive: !!passive, listener: listener || undefined, app: app || undefined, iid: IID, joined: joinedAt }) // identity is implicit in the client
|
|
1262
1130
|
|
|
1263
1131
|
// ---- relay reachability, said out loud exactly once -------------------------------
|
|
1264
1132
|
// y-websocket retries forever and silently. That is the right RECOVERY behaviour and
|
|
@@ -1440,7 +1308,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
1440
1308
|
`Do not trust this folder's contents meanwhile.`,
|
|
1441
1309
|
})).text
|
|
1442
1310
|
log(`[${name}] !! ${msg}`)
|
|
1443
|
-
try {
|
|
1311
|
+
try { postOnce(`notsyncing:${code || 'stuck'}`, `[!] @all ${msg}`, { observer: true, ttlMs: 10 * 60 * 1000 }) } catch { /* chat may itself be unreachable — the log still carries it */ }
|
|
1444
1312
|
}, 4000)
|
|
1445
1313
|
if (refusalTimer.unref) refusalTimer.unref()
|
|
1446
1314
|
}
|
|
@@ -2356,7 +2224,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
2356
2224
|
if (keptAs) {
|
|
2357
2225
|
log(`[${name}] local unshared file ${relPath} overwritten by room write; copy preserved in ${keptAs}`)
|
|
2358
2226
|
try {
|
|
2359
|
-
|
|
2227
|
+
note(`· ${relPath} on ${name}'s disk was ${unshared.reason} and was overwritten by a room write — local copy preserved in ${keptAs}.`)
|
|
2360
2228
|
} catch { }
|
|
2361
2229
|
}
|
|
2362
2230
|
}
|
|
@@ -2514,10 +2382,13 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
2514
2382
|
if (kept.length) {
|
|
2515
2383
|
const one = kept.length === 1
|
|
2516
2384
|
parts.push(`${one ? '1 holds' : `${kept.length} hold`} content the room has never seen, so neither copy was changed and the room's copy was NOT adopted: ${names(kept)}. ` +
|
|
2517
|
-
`In ${name}'s folder ${one ? 'it still holds' : 'they still hold'} ${name}'s version. To make one the room's version, claim it and hive_edit it with the full file. ` +
|
|
2385
|
+
`In ${name}'s folder ${one ? 'it still holds' : 'they still hold'} ${name}'s version, and nothing syncs for ${one ? 'it' : 'them'} until ${name} saves ${one ? 'it' : 'them'} again. To make one the room's version, claim it and hive_edit it with the full file. ` +
|
|
2518
2386
|
`Don't delete a local copy to get the room's: a deletion syncs, and removes the file for everyone.`)
|
|
2519
2387
|
}
|
|
2520
|
-
|
|
2388
|
+
// What concerns THIS folder only (line endings, older copies it now has) is a note for hive_status; only
|
|
2389
|
+
// content the room has never seen is the room's business, because nobody else has it (0.14.4, C5).
|
|
2390
|
+
note(parts.join(' '))
|
|
2391
|
+
if (kept.length) { try { say([parts[0], parts[parts.length - 1]].join(' ')) } catch { } } // the kept part is pushed last
|
|
2521
2392
|
}
|
|
2522
2393
|
|
|
2523
2394
|
function reconcileBinary(relPath, origin = 'local') {
|
|
@@ -2621,7 +2492,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
2621
2492
|
if (keptAs) {
|
|
2622
2493
|
log(`[${name}] local binary ${relPath} overwritten by remote write (winner: ${remoteBy || 'teammate'}); copy preserved in ${keptAs}`)
|
|
2623
2494
|
try {
|
|
2624
|
-
|
|
2495
|
+
note(`· ${relPath} updated from room (winner: ${remoteBy || 'teammate'}). Your local copy saved to ${keptAs}.`)
|
|
2625
2496
|
} catch { }
|
|
2626
2497
|
}
|
|
2627
2498
|
}
|
|
@@ -2954,7 +2825,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
2954
2825
|
if (r0.conflict) conflicted.add(relPath)
|
|
2955
2826
|
bases.set(relPath, r0.text); forkBases.set(relPath, r0.text); shadow.set(relPath, r0.text)
|
|
2956
2827
|
if (!inManifest(relPath)) queueManifestSet(relPath)
|
|
2957
|
-
try {
|
|
2828
|
+
try { postOnce(`race:${relPath}`, `⚠ ${relPath} was created by two participants at once — both versions were merged${r0.conflict ? ', and CONFLICT markers need resolving' : ''}.`, { ttlMs: 10 * 60 * 1000 }) } catch { }
|
|
2958
2829
|
return
|
|
2959
2830
|
}
|
|
2960
2831
|
// REJOIN, NOT A FIRST JOIN. If a previous session of this client wrote this file in
|
|
@@ -3066,11 +2937,9 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3066
2937
|
// and said", never "lost".
|
|
3067
2938
|
let keptAt = 0; try { keptAt = fs.statSync(full).mtimeMs } catch { }
|
|
3068
2939
|
keptUnsent.set(relPath, keptAt)
|
|
3069
|
-
// Said in the room
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
`Neither was written over the other: ${name}'s is kept on disk, both are in the file's history, and nothing syncs for this file until ${name} saves it again. That save replaces the room's copy. Claim ${relPath} first.`)
|
|
3073
|
-
} catch { }
|
|
2940
|
+
// Said in the room (review R5: the agent has to act on it), but ONCE PER JOIN, in the first-contact
|
|
2941
|
+
// summary below -- not one line per file in engine words. A teammate rejoining with 40 such files
|
|
2942
|
+
// filled the chat with 40 lines (9 Nov 2026; first-contact-summary-test, red since 0.14.3).
|
|
3074
2943
|
log(`[${name}] ⚠ ${relPath} already existed in room with different content (${st.state}) — room copy was NOT adopted; local disk preserved and frozen until saved again`)
|
|
3075
2944
|
noteFirstContact('kept', relPath)
|
|
3076
2945
|
return
|
|
@@ -3254,10 +3123,10 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3254
3123
|
if (hasMarkers && !conflicted.has(relPath)) {
|
|
3255
3124
|
conflicted.add(relPath)
|
|
3256
3125
|
log(`[${name}] ⚠ merge conflict in ${relPath} — kept BOTH versions with <<<<<<< markers`)
|
|
3257
|
-
try {
|
|
3126
|
+
try { postOnce(`conflict:${relPath}`, `⚠ MERGE CONFLICT in ${relPath} — it has <<<<<<< markers. Whoever owns it: resolve before continuing, by saving the file without them or by passing the marked block as old to hive_edit old/new.`, { observer: !res.conflict, clear: `resolved:${relPath}` }) } catch { }
|
|
3258
3127
|
} else if (!hasMarkers && conflicted.has(relPath)) {
|
|
3259
3128
|
conflicted.delete(relPath)
|
|
3260
|
-
try {
|
|
3129
|
+
try { postOnce(`resolved:${relPath}`, `✓ conflict in ${relPath} resolved.`, { observer: origin !== 'local', clear: `conflict:${relPath}` }) } catch { }
|
|
3261
3130
|
} else if (reAdded) {
|
|
3262
3131
|
log(`[${name}] protected ${relPath}: your edit was based on an older version — re-added changes that arrived since (nobody's work lost)`)
|
|
3263
3132
|
try { say(`↺ ${relPath}: ${name}'s edit was based on an older copy — kept the changes that landed in between (nothing lost).`) } catch { }
|
|
@@ -3275,7 +3144,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3275
3144
|
if (res.icrWarning && icrWarned.get(relPath) !== res.icrWarning) {
|
|
3276
3145
|
icrWarned.set(relPath, res.icrWarning)
|
|
3277
3146
|
log(`[${name}] ⚠ ICR flag in ${relPath} — ${res.icrWarning}`)
|
|
3278
|
-
try {
|
|
3147
|
+
try { postOnce(`icr:${relPath}:${contentHash(String(res.icrWarning)).slice(0, 16)}`, `⚠ ICR flag in ${relPath}: ${res.icrWarning}. It merged, but review — this is a break a plain merge can't see.`, { observer: origin !== 'local' }) } catch { }
|
|
3279
3148
|
} else if (!res.icrWarning && icrWarned.has(relPath)) {
|
|
3280
3149
|
icrWarned.delete(relPath)
|
|
3281
3150
|
}
|
|
@@ -3511,6 +3380,66 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3511
3380
|
if (!text) return
|
|
3512
3381
|
chat.push([{ by: name, kind, at: fmtTime(), text: String(text) }])
|
|
3513
3382
|
}
|
|
3383
|
+
// ONE LINE PER EVENT, NOT ONE PER OBSERVER (0.14.4, C5). Most system lines are said by whoever
|
|
3384
|
+
// caused them (a task, a review, a revert): one poster by nature. But an OBSERVED event -- a conflict
|
|
3385
|
+
// in a shared file, a client on another build, a divergence -- is detected by every peer's own code,
|
|
3386
|
+
// and each posted it: one conflict gave four MERGE CONFLICT lines and then four "resolved" lines
|
|
3387
|
+
// (incident I-16, #722-#725); one odd client gave a MIXED BUILDS line from every other (I-8, #822-#823).
|
|
3388
|
+
// postOnce(id, text): `id` names the event the same way on every peer. The peer that caused it posts
|
|
3389
|
+
// at once. An observer waits 1.5 s plus 1 s per rank among present engines (sorted by name) and posts
|
|
3390
|
+
// only if nobody has; the key lives in the room (`sys_events`) and expires. A peer that cannot write
|
|
3391
|
+
// the key still posts: a missed alarm hides a conflict, a duplicate is only noise.
|
|
3392
|
+
const sysEvents = doc.getMap('sys_events')
|
|
3393
|
+
const SYS_TTL_MS = 6 * 3600 * 1000
|
|
3394
|
+
const sysTimers = new Set()
|
|
3395
|
+
// What only THIS folder needs to know (a local copy kept, a file that differed at join) goes here, to
|
|
3396
|
+
// the log and hive_status, and not to the whole room (C5).
|
|
3397
|
+
const notices = []
|
|
3398
|
+
function note(text) {
|
|
3399
|
+
const t = String(text || '')
|
|
3400
|
+
if (!t) return
|
|
3401
|
+
log(`[${name}] ${t}`)
|
|
3402
|
+
notices.push({ at: Date.now(), text: t })
|
|
3403
|
+
if (notices.length > 50) notices.shift()
|
|
3404
|
+
}
|
|
3405
|
+
function sysRank() {
|
|
3406
|
+
const names = new Set([name])
|
|
3407
|
+
try {
|
|
3408
|
+
for (const st of provider.awareness.getStates().values()) {
|
|
3409
|
+
const u = st && st.user
|
|
3410
|
+
if (u && u.name && !u.listener && !u.passive && u.v === PROTOCOL_VERSION) names.add(u.name)
|
|
3411
|
+
}
|
|
3412
|
+
} catch { }
|
|
3413
|
+
return [...names].sort().indexOf(name)
|
|
3414
|
+
}
|
|
3415
|
+
function pruneSysEvents(now) {
|
|
3416
|
+
let n = 0
|
|
3417
|
+
for (const [k, v] of sysEvents.entries()) {
|
|
3418
|
+
if (++n > 500) break
|
|
3419
|
+
if (!v || now - (Number(v.at) || 0) > (Number(v.ttl) || SYS_TTL_MS)) sysEvents.delete(k)
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
function postOnce(id, text, { observer = false, ttlMs = SYS_TTL_MS, clear = null } = {}) {
|
|
3423
|
+
if (!text) return null
|
|
3424
|
+
if (observer && (passive || listener)) return null // a client nobody waits on never speaks for the room
|
|
3425
|
+
const fire = () => {
|
|
3426
|
+
try {
|
|
3427
|
+
const prev = sysEvents.get(id)
|
|
3428
|
+
if (prev && Date.now() - (Number(prev.at) || 0) < (Number(prev.ttl) || SYS_TTL_MS)) return false
|
|
3429
|
+
doc.transact(() => {
|
|
3430
|
+
sysEvents.set(id, { by: name, at: Date.now(), ttl: ttlMs })
|
|
3431
|
+
if (clear) for (const c of [].concat(clear)) sysEvents.delete(c)
|
|
3432
|
+
pruneSysEvents(Date.now())
|
|
3433
|
+
})
|
|
3434
|
+
} catch { /* fail open: the line is said even if the key could not be written */ }
|
|
3435
|
+
say(text)
|
|
3436
|
+
return true
|
|
3437
|
+
}
|
|
3438
|
+
if (!observer) return fire()
|
|
3439
|
+
const t = setTimeout(() => { sysTimers.delete(t); if (!stopped) { try { fire() } catch { } } }, 1500 + 1000 * Math.max(0, sysRank()))
|
|
3440
|
+
sysTimers.add(t)
|
|
3441
|
+
return null
|
|
3442
|
+
}
|
|
3514
3443
|
function renderChat() {
|
|
3515
3444
|
const out = [
|
|
3516
3445
|
'# Hive Chat — the live coordination channel for everyone in this room.',
|
|
@@ -3830,7 +3759,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3830
3759
|
let myEditing = null, myLastEdit = null
|
|
3831
3760
|
let editingClearTimer = null
|
|
3832
3761
|
const warnedAt = new Map() // file -> last time we warned about co-editing it
|
|
3833
|
-
const setUserState = () => provider.awareness.setLocalStateField('user', { name, kind, owner: owner || undefined, editing: myEditing || undefined, lastEdit: myLastEdit || undefined, v: PROTOCOL_VERSION, hv: VERSION_LABEL, passive: !!passive, listener: listener || undefined, app: app || undefined, iid: IID })
|
|
3762
|
+
const setUserState = () => provider.awareness.setLocalStateField('user', { name, kind, owner: owner || undefined, editing: myEditing || undefined, lastEdit: myLastEdit || undefined, v: PROTOCOL_VERSION, hv: VERSION_LABEL, passive: !!passive, listener: listener || undefined, app: app || undefined, iid: IID, joined: joinedAt })
|
|
3834
3763
|
function markEditing(relPath) {
|
|
3835
3764
|
myEditing = { file: relPath, at: Date.now() }
|
|
3836
3765
|
myLastEdit = myEditing
|
|
@@ -3858,7 +3787,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3858
3787
|
const now = Date.now()
|
|
3859
3788
|
if (others.length && now - (warnedAt.get(relPath) || 0) > 30000) {
|
|
3860
3789
|
warnedAt.set(relPath, now)
|
|
3861
|
-
try {
|
|
3790
|
+
try { postOnce(`coedit:${relPath}:${[name, ...others].sort().join(',')}`, `⚠ heads-up: ${name} is also editing ${relPath} (with ${others.join(', ')}). Small patches auto-merge; coordinate before a full rewrite.`, { ttlMs: 10 * 60 * 1000 }) } catch { } // the one who just started editing is the cause: it says it at once
|
|
3862
3791
|
}
|
|
3863
3792
|
}
|
|
3864
3793
|
function memberList() {
|
|
@@ -3927,7 +3856,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3927
3856
|
versionWarned.add(u.name)
|
|
3928
3857
|
const theirs = u.v ? `v${u.v}` : 'an older build'
|
|
3929
3858
|
log(`[${name}] ⚠ ${u.name} is on ${theirs} (incompatible with v${PROTOCOL_VERSION}) — they won't sync correctly until everyone updates.`)
|
|
3930
|
-
try {
|
|
3859
|
+
try { postOnce(`proto:${u.name}:${u.v || '-'}`, `⚠ ${u.name} is on an incompatible version — files won't sync with them until everyone updates Hivecode.`, { observer: true }) } catch { }
|
|
3931
3860
|
}
|
|
3932
3861
|
checkBuilds()
|
|
3933
3862
|
}
|
|
@@ -3941,40 +3870,69 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
3941
3870
|
// - humans are not flagged: the extension and the browser panel carry no engine build
|
|
3942
3871
|
// - one side of each pair speaks (the name that sorts first), once per build it sees
|
|
3943
3872
|
const buildWarned = new Set()
|
|
3873
|
+
// ONE ALARM PER ROOM, AND ONLY FOR ANOTHER RELEASE (0.14.4, C5). The rule used to be "one side of
|
|
3874
|
+
// each pair speaks", which is still one line per PAIR: two clients on 0.14.2 each posted about the one
|
|
3875
|
+
// on 0.14.3 (#822 + #823), and a client on the same release from another commit raised the same alarm
|
|
3876
|
+
// (I-8). Now the event is the set of releases in the room: it is said once, by whoever sees it first,
|
|
3877
|
+
// naming who runs what. The same release from another commit is a note for this folder (hive_status).
|
|
3944
3878
|
function checkBuilds() {
|
|
3945
|
-
//
|
|
3946
|
-
//
|
|
3947
|
-
|
|
3948
|
-
const
|
|
3949
|
-
|
|
3950
|
-
for (const
|
|
3951
|
-
const u =
|
|
3952
|
-
if (!u || !u.name || u.v !== PROTOCOL_VERSION || u.listener) continue
|
|
3953
|
-
if (u.hv) reporters.push(u.name)
|
|
3954
|
-
if (u.hv === VERSION_LABEL) currentEngines.add(u.name)
|
|
3955
|
-
}
|
|
3956
|
-
const speakForOld = reporters.sort()[0] === name
|
|
3957
|
-
|
|
3958
|
-
for (const s of provider.awareness.getStates().values()) {
|
|
3959
|
-
const u = s && s.user
|
|
3879
|
+
// Per CONNECTION, not per name: one participant with two engines open on different builds is its own
|
|
3880
|
+
// alarm (0.14.3 Q-2: "an older connection is still open"), and a map keyed by name would hide it.
|
|
3881
|
+
const labelsOf = new Map() // name -> Set of what its engines run
|
|
3882
|
+
const add = (n, label) => { if (!labelsOf.has(n)) labelsOf.set(n, new Set()); labelsOf.get(n).add(label) }
|
|
3883
|
+
add(name, VERSION_LABEL)
|
|
3884
|
+
for (const st of provider.awareness.getStates().values()) {
|
|
3885
|
+
const u = st && st.user
|
|
3960
3886
|
if (!u || !u.name || u.name === name || u.v !== PROTOCOL_VERSION || u.listener) continue
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
const
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3887
|
+
if (u.hv) add(u.name, String(u.hv))
|
|
3888
|
+
// An AI on this protocol reporting no build is an engine older than build reporting: the rollout
|
|
3889
|
+
// case, and it cannot warn itself. Humans carry no engine build (the extension, the browser panel).
|
|
3890
|
+
else if (String(u.kind || '').toLowerCase() !== 'human') add(u.name, `a build older than build reporting (before ${VERSION})`)
|
|
3891
|
+
}
|
|
3892
|
+
const older = (label) => /^a build older/.test(label)
|
|
3893
|
+
const releaseOf = (label) => (older(label) ? label : String(label).split('+')[0])
|
|
3894
|
+
const on = (label) => (older(label) ? `on ${label}` : `on hivecode ${label}`)
|
|
3895
|
+
// The same release from another commit: a note for this folder, not a room alarm.
|
|
3896
|
+
for (const [n, labels] of labelsOf) {
|
|
3897
|
+
if (n === name) continue
|
|
3898
|
+
for (const label of labels) {
|
|
3899
|
+
if (label === VERSION_LABEL || releaseOf(label) !== VERSION) continue
|
|
3900
|
+
const key = `note|${n}|${label}`
|
|
3901
|
+
if (buildWarned.has(key)) continue
|
|
3902
|
+
buildWarned.add(key)
|
|
3903
|
+
note(`${n} runs hivecode ${label}: the same release as this client (${VERSION_LABEL}), from another commit.`)
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
// One name, two engines on different builds: they write over each other's files.
|
|
3907
|
+
for (const [n, labels] of labelsOf) {
|
|
3908
|
+
if (n === name || labels.size < 2) continue
|
|
3909
|
+
const all = [...labels].sort()
|
|
3910
|
+
const id = `dual:${n}:${all.join('|')}`
|
|
3911
|
+
if (buildWarned.has(id)) continue
|
|
3912
|
+
buildWarned.add(id)
|
|
3913
|
+
log(`[${name}] ⚠ MIXED BUILDS: ${n} has engines ${all.map(on).join(' and ')}`)
|
|
3914
|
+
try { postOnce(id, `⚠ MIXED BUILDS: ${n} has two engines open, ${all.map(on).join(' and ')} (an older connection from ${n} is still open). Two engines under one name can lose file content with no error on either side -- close the older one.`, { observer: true }) } catch { }
|
|
3915
|
+
}
|
|
3916
|
+
// An OLDER RELEASE in the room: one alarm per participant and build, for the whole room. The event
|
|
3917
|
+
// is "X runs B, older than the newest release here": every observer sees the same newest release, so
|
|
3918
|
+
// they name the same event, and presence churn (an agent's state re-published) never re-raises it.
|
|
3919
|
+
const parts = (r) => (older(r) ? [-1] : String(r).split('.').map((x) => parseInt(x, 10) || 0))
|
|
3920
|
+
const cmp = (a, b) => { const x = parts(a), y = parts(b); for (let k = 0; k < Math.max(x.length, y.length); k++) { const d = (x[k] || 0) - (y[k] || 0); if (d) return d } return 0 }
|
|
3921
|
+
const releases = new Set()
|
|
3922
|
+
for (const labels of labelsOf.values()) for (const label of labels) releases.add(releaseOf(label))
|
|
3923
|
+
if (releases.size < 2) return
|
|
3924
|
+
const newest = [...releases].sort(cmp).pop()
|
|
3925
|
+
const current = []
|
|
3926
|
+
for (const [n, labels] of labelsOf) for (const label of labels) if (releaseOf(label) === newest) current.push(`${n} ${on(label)}`)
|
|
3927
|
+
for (const [n, labels] of labelsOf) {
|
|
3928
|
+
for (const label of labels) {
|
|
3929
|
+
if (releaseOf(label) === newest) continue
|
|
3930
|
+
const id = `builds:${n}:${label}`
|
|
3931
|
+
if (buildWarned.has(id)) continue
|
|
3932
|
+
buildWarned.add(id)
|
|
3933
|
+
log(`[${name}] ⚠ MIXED BUILDS: ${n} runs ${older(label) ? label : `hivecode ${label}`}`)
|
|
3934
|
+
try { postOnce(id, `⚠ MIXED BUILDS: ${n} runs ${older(label) ? label : `hivecode ${label}`}, older than the room's newest release (${current.sort().join(', ')}). Two releases in one room can lose file content with no error on either side -- ${n} should update (npx -y hivecode-mcp@latest setup, then restart).`, { observer: true }) } catch { }
|
|
3935
|
+
}
|
|
3978
3936
|
}
|
|
3979
3937
|
}
|
|
3980
3938
|
if (syncFiles) provider.awareness.on('change', () => { renderMembers(); renderClaims(); checkVersions() })
|
|
@@ -4861,6 +4819,8 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
4861
4819
|
try { flushFirstContact() } catch { } // before `stopped`: a join cut short still says what it kept
|
|
4862
4820
|
try { settleLocalDeletes() } catch { } // a delete made just before leaving still leaves, judged like any other
|
|
4863
4821
|
stopped = true
|
|
4822
|
+
for (const t of sysTimers) clearTimeout(t)
|
|
4823
|
+
sysTimers.clear()
|
|
4864
4824
|
try { flushRoomDoc() } catch { }
|
|
4865
4825
|
if (openTimer) { clearImmediate(openTimer); clearTimeout(openTimer); openTimer = null }
|
|
4866
4826
|
if (renderBoardTimer) { clearImmediate(renderBoardTimer); renderBoardTimer = null }
|
|
@@ -4945,6 +4905,11 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
4945
4905
|
}
|
|
4946
4906
|
}
|
|
4947
4907
|
swarmState.set(key, val)
|
|
4908
|
+
// The session that sets a gate has chosen to run that command, so its own machine approves it
|
|
4909
|
+
// (0.14.4 S-1). Nobody else's does: a gate set here runs elsewhere only after that person approves.
|
|
4910
|
+
if (key === GATE_CMD && String(val == null ? '' : val).trim()) {
|
|
4911
|
+
try { writeApproval(room, String(val), `set by ${name} on this machine`, GATES_DIR) } catch { /* no approval, so it simply does not run here */ }
|
|
4912
|
+
}
|
|
4948
4913
|
say(`STATE TRANSITION: [${key}] is now [${val}]`)
|
|
4949
4914
|
},
|
|
4950
4915
|
getState: () => {
|
|
@@ -5074,7 +5039,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
5074
5039
|
if (v.stuckLagging) return `${v.peer}:${v.bytes}b/${v.lines}L[STUCK:${v.stuckReason || 'not catching up'}]`
|
|
5075
5040
|
return `${v.peer}:${v.bytes}b/${v.lines}L`
|
|
5076
5041
|
}).join(' ')
|
|
5077
|
-
|
|
5042
|
+
postOnce(`diverge:${p}:${contentHash(detail).slice(0, 16)}`, `[!] DIVERGENCE on ${p} — peers hold DIFFERENT content: ${detail}. Nobody may write this path until it is reconciled; mark it path:${p}=POISONED if it cannot be.`)
|
|
5078
5043
|
}
|
|
5079
5044
|
|
|
5080
5045
|
return {
|
|
@@ -5107,6 +5072,8 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
5107
5072
|
}
|
|
5108
5073
|
},
|
|
5109
5074
|
say, // post a coordination message
|
|
5075
|
+
postOnce, // a system line said once per event in the room, whoever sees it (0.14.4, C5)
|
|
5076
|
+
notices: () => notices.slice(), // what only this folder needs to know (hive_status shows it)
|
|
5110
5077
|
assign, // direct a task at a participant (needs approval if target AI has an owner)
|
|
5111
5078
|
decide, // approve/deny a task (owner only, if set)
|
|
5112
5079
|
complete, // mark a task done
|
|
@@ -5168,6 +5135,17 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
5168
5135
|
return { ok: true, gated: true, forced: true }
|
|
5169
5136
|
}
|
|
5170
5137
|
|
|
5138
|
+
// A COMMAND FROM THE ROOM IS DATA (0.14.4 S-1). It runs here only if this machine's person
|
|
5139
|
+
// approved this exact command; otherwise the hand-back goes through UNCHECKED -- never refused,
|
|
5140
|
+
// or setting a gate would be a way to stop everyone -- and that is recorded and said once.
|
|
5141
|
+
const approvedBy = gateApproved({ room, cmd: g.cmd, dir: GATES_DIR })
|
|
5142
|
+
if (!approvedBy) {
|
|
5143
|
+
gateResults.set(`${r}|${name}`, { ...verdictRecord({ region: r, by: name, cmd: g.cmd, result: null }), skipped: 'not-approved' })
|
|
5144
|
+
letGo()
|
|
5145
|
+
const said = `${name}|${g.cmd}`
|
|
5146
|
+
if (!gateSkipSaid.has(said)) { gateSkipSaid.add(said); try { say(notApprovedLine(name)) } catch { /* chat is not the point */ } }
|
|
5147
|
+
return { ok: true, gated: false, gateSkipped: 'not-approved', code: GATE_NOT_APPROVED, cmd: g.cmd, message: notApprovedMessage({ region: r, cmd: g.cmd, dir: ROOT }) }
|
|
5148
|
+
}
|
|
5171
5149
|
const result = runGate({ cmd: g.cmd, cwd: ROOT, timeoutMs: g.timeoutMs })
|
|
5172
5150
|
gateResults.set(`${r}|${name}`, verdictRecord({ region: r, by: name, cmd: g.cmd, result }))
|
|
5173
5151
|
if (!result.ok) {
|
|
@@ -5281,7 +5259,7 @@ export function startSync({ relay = 'ws://localhost:1234', room = 'default', dir
|
|
|
5281
5259
|
if (id === me || !u || !u.name || String(u.name).toLowerCase() !== lc) continue
|
|
5282
5260
|
if (u.listener || u.iid === IID) continue
|
|
5283
5261
|
if (kind === 'human' && u.kind === 'human') continue
|
|
5284
|
-
out.push({ name: u.name, kind: u.kind, owner: u.owner, app: u.app })
|
|
5262
|
+
out.push({ name: u.name, kind: u.kind, owner: u.owner, app: u.app, joined: u.joined, clientID: id })
|
|
5285
5263
|
}
|
|
5286
5264
|
return out
|
|
5287
5265
|
},
|
package/docs/HIVE_RULES.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# HIVE RULES — read this first. Everyone in this room (human or AI) follows these.
|
|
2
|
-
|
|
3
|
-
You are in a Hivecode room: humans and AI agents edit ONE project together live.
|
|
4
|
-
These rules keep anyone from destroying another's work. The sync layer enforces
|
|
5
|
-
the hard parts automatically; you do the rest.
|
|
6
|
-
|
|
7
|
-
## Before you touch a file
|
|
8
|
-
1. Read HIVE_CHAT.md — what is everyone doing right now.
|
|
9
|
-
2. Read HIVE_BOARD.md — which files were just rewritten.
|
|
10
|
-
3. If a file you plan to edit appears there, RE-READ it before changing it.
|
|
11
|
-
|
|
12
|
-
## While you work
|
|
13
|
-
4. ANNOUNCE in chat what you are taking before you start.
|
|
14
|
-
5. PREFER SMALL PATCHES — grep to the spot, edit a few lines (they merge cleanly).
|
|
15
|
-
6. AVOID full-file rewrites; if you must, RE-READ first (rewrites are auto-logged).
|
|
16
|
-
7. STAY IN YOUR LANE — read others' areas, but leave edits to the owner.
|
|
17
|
-
|
|
18
|
-
## When things collide
|
|
19
|
-
8. <<<<<<< ======= >>>>>>> markers = a real conflict: keep the right code, delete
|
|
20
|
-
the markers. Never ignore or blindly overwrite.
|
|
21
|
-
9. If your edit was merged/reworked, that is normal — re-read and continue.
|
|
22
|
-
|
|
23
|
-
## Talking
|
|
24
|
-
10. Coordinate in chat. ASK before anything destructive in another's area.
|
|
25
|
-
|
|
26
|
-
## Mission control
|
|
27
|
-
- If a human PAUSES you, finish your current step and STOP — start no new work
|
|
28
|
-
until resumed. A human may reassign your focus anytime; their directive wins.
|
|
29
|
-
|
|
30
|
-
## When you get a ping mid-task (interruptions)
|
|
31
|
-
11. Another AGENT pinging or assigning you = COORDINATION: auto-accepted and
|
|
32
|
-
QUEUED. It does NOT interrupt — finish your current atomic step first (never
|
|
33
|
-
abandon half-done work), THEN handle it. If it's urgent (build broken,
|
|
34
|
-
blocking others), do it now.
|
|
35
|
-
12. ACKNOWLEDGE the moment you see a ping so the sender isn't left hanging, e.g.
|
|
36
|
-
"got it — finishing X (~2 min), then on your auth.js fix".
|
|
37
|
-
13. YOU triage: say whether you'll do it now or after your current step. Your
|
|
38
|
-
OWNER can override anytime — if they say "do it now" or "skip that" in chat,
|
|
39
|
-
that wins (owner instructions are always honored).
|
|
40
|
-
14. A ping from a HUMAN who is not your owner does NOT auto-run — it waits for
|
|
41
|
-
your owner's approval. Keep working; your owner decides.
|