Collaborating Skill
You are driving /forge:collaborate. The primitives live in scripts/forge-collab.cjs (exported by require into a local Node script or invoked via node -e). This skill translates user intent into calls against those primitives, honoring the invariants from spec-collab:
- Session ID is derived from
git remote get-url origin; all participants
in the same repo auto-join the same session.
- During the
executingphase, AI decisions never block humans: they
become flags that humans can review and override async.
- Writes to shared coordination state (consolidated.md, categories.json)
are gated by a short-lived consolidation lease to prevent concurrent overwrites.
- All brainstorm + consolidated + categories + questions + flags
artifacts live under .forge/collab/ and are committed to git so late joiners get full context via git pull.
Subcommand Dispatch
Read the first word of the user's arguments. Route to the matching phase below. If no subcommand is given, default to status.
start -- initialize a session
- Derive the session ID by calling
sessionIdFromOrigin()via a small
inline Node script: ``bash node -e "console.log(require('./scripts/forge-collab.cjs').sessionIdFromOrigin())" ` If this throws because there is no origin` remote, tell the user to add one and stop.
- Resolve the participant handle: prefer
gh api user --jq .login,
fall back to git config user.email, finally $USER.
- Ensure
.forge/collab/exists (mkdir -p .forge/collab).
- Write
.forge/collab/participant.jsonwith `{handle, session_id,
started: <iso>}`. Write this FIRST; it is the heavy artifact that holds the session metadata.
- Write
.forge/collab/.enabledas an empty marker file. **This MUST be
the final filesystem action**; it is the atomic signal that collab mode is fully on. Use the writeEnabledMarker primitive: ``bash node -e "require('./scripts/forge-collab.cjs').writeEnabledMarker('.forge')" ` Invariant: if start crashes between step 4 and step 5, crash recovery will classify the state as stale_participant` and offer a reset. If it crashes before step 4 there is nothing to recover. Do not reorder.
- Resolve the transport mode. Call
selectTransportModewith
process.env and any --polling flag the user passed. Report:
ably: "Realtime transport will use Ably (ABLY_KEY detected)."
Prerequisite (forge-self-fixes-2 R013): the ably npm package is declared an OPTIONAL peerDependency in the plugin package.json and must be installed before createAblyTransport will succeed. If it isn't, start throws "forge:collab realtime mode requires the ably peer dependency." Surface this check BEFORE calling createTransport('ably', ...): ``bash node -e "require.resolve('ably')" 2>/dev/null \ || echo 'Run: npm install ably (or pass --polling to skip realtime)' ` If the probe fails, print the install command and stop with a non-zero exit unless the user also passed --polling`.
polling: "Zero-setup mode via git polling on forge/collab-state
branch every ~2.5s." No install needed. The pull target branch is auto-resolved per R011 (origin/HEAD → upstream → current branch → main fallback); no hardcoded main assumption.
setup-required: print the setup guide returned byrenderSetupGuide()
and exit (unless --polling was passed).
- Report the session code (the full 12-hex session ID) and tell the user
to share it out-of-band with teammates (though strictly speaking any teammate with the repo will derive the same code automatically).
join -- sync with an existing session
Call lateJoinBootstrap with the transport and the current unblocked task IDs (read from .forge/plans/*-frontier.md):
- Do a git pull on the current branch.
- Read the plan frontier to list task IDs.
- Call
lateJoinBootstrap({ transport, unblockedTaskIds: ids, cwd })
to get the list of claimable task IDs.
- Print a tidy status: how many tasks total, how many already claimed
by others, how many available for this joiner.
brainstorm -- chat-mode brain-dump
This is the interactive loop that replaces the single-user brainstorm. Runs in place in the current session.
- Greet: "Starting a collaborative brain-dump. I'll ask questions to
help you structure your thoughts. When you're happy, say 'accept' and I'll write your refined input to .forge/collab/brainstorm/."
- Ask 3-5 clarifying questions one at a time to draw out the user's
ideas, priorities, and constraints. Match the tone of /forge:brainstorm but scope to the user's own section -- don't try to design the whole product; the AI-driven consolidation across users will merge everyone's ideas later.
- After each answer, summarize what you've captured.
- When the user says
accept(or "looks good", "write it", etc.), call
the primitive via an inline Node script: ``bash node -e "const c = require('./scripts/forge-collab.cjs'); \ const p = c.brainstormDump('.forge/collab', process.env.FORGE_HANDLE, \ process.env.FORGE_BRAIN_DUMP); console.log(p);" ` (Or just use Write against .forge/collab/brainstorm/inputs-<handle>.md` directly with the standard frontmatter -- either path is equivalent.)
- Stage + commit + push the new file per the project's auto_push
setting (read via readAutoPushConfig('.forge')).
- If a consolidated.md already exists, tell the user "Your input has
joined the brainstorm. When all contributors are in, run /forge:collaborate consolidate to merge everyone's input."
consolidate -- run consolidation + categorization under lease
Optional subcommand for whoever takes the consolidation step. Calls writeConsolidatedUnderLease(transport, '.forge/collab', handle, inputs, opts) where inputs is the result of readAllInputs('.forge/collab').
- If the call returns
{ held: true }: report the task count and
categories breakdown (coding vs research vs decision).
- If
{ held: false, holder }: tell the user "Consolidation in progress
by <holder>. Try again in a moment."
status -- show session state
Call the primitives to build a status report:
listActiveTaskClaims(transport, {now: Date.now()})-> active claimslistFlags('.forge/collab', {status: 'open'})-> open flags- Read
.forge/collab/participant.json-> current handle - Read
.forge/collab/categories.jsonif it exists -> task count
Print a tidy summary:
Session: <sessionId>
Mode: ably | polling
You: <handle>
Claims:
T004 daniel (expires in 85s)
T007 lucas (expires in 43s)
Open flags (3):
F0123abc T005 "use redis pubsub" (daniel)
F0456def T008 "retry with backoff" (sarah)
Next claimable:
T009, T011, T012
claim <taskId> -- explicit claim
Call claimTask(transport, taskId, handle, { ttlSeconds: 120 }). Report the outcome:
{ acquired: true }: "Reserved T004 for you. Lease expires in 120s;
heartbeats auto-refresh while you work."
{ acquired: false, holder }: "T004 is already held by<holder>.
Wait for them to finish or run /forge:collaborate status to see what else is claimable."
flags -- list forward-motion flags
Call listFlags('.forge/collab', { status }) with an optional status filter. Print a table.
override <flagId> <new-decision> -- override an AI decision
Call overrideFlag('.forge/collab', flagId, newDecision, { author: handle }).
{ overridden: true, previousDecision }: confirm, note previous
decision, and tell the user that dependent tasks will be re-triggered on the next execute iteration.
{ overridden: false, reason }: surface the reason (not_found,
already_overridden, malformed) so the user can diagnose.