checkpoint: pre-split branch cleanup

This commit is contained in:
ZenchantLive 2026-03-03 16:43:42 -08:00
parent 4c2ae2e5b7
commit b5db7a7753
276 changed files with 35912 additions and 60119 deletions

View file

@ -1,78 +1,158 @@
---
name: beadboard-driver
description: Drive BeadBoard agent workflows with strict Operative Protocol v1 compliance. Use when handling bead lifecycle work that combines bd status commands with bb agent coordination (register/adopt, activity-lease, reserve/release, send/ack), especially in multi-agent sessions requiring silent observability and collision avoidance.
description: Complete operating manual for agents running work in external repos while humans orchestrate from BeadBoard.
---
# Beadboard Driver (Operative Protocol v1)
# BeadBoard Driver
## Overview
BeadBoard is for teams that want autonomous agents without losing control of the work.
Use this skill to run repeatable `bd` + `bb` workflows under the **Activity Lease** (Parking Permit) model. Resolve `bb` safely, bootstrap via `bb-init`, coordinate via traceable incursions, and maintain liveness through real work.
Most agent setups break down the same way: work happens quickly, but visibility collapses, handoffs get fuzzy, and “done” starts meaning “probably done.” This skill fixes that operating problem.
## Core Workflow
With BeadBoard Driver, agents execute inside the target project repo, while humans orchestrate from BeadBoard as the control plane: assign, redirect, intervene, verify, and keep a durable coordination record.
BeadBoard project:
- GitHub: `https://github.com/zenchantlive/beadboard`
## What This Changes
- Work becomes observable, not performative.
- Ownership stays explicit at bead level.
- Handoffs and blockers become machine-readable events.
- Completion claims require evidence, not confidence.
- Multi-agent execution stays coordinated instead of chaotic.
## Operating Reality
- Agents usually run in a non-BeadBoard target repo.
- The user controls project scope from BeadBoard UI.
- Agents execute the current repo context they were assigned.
- `bd` remains source of truth for task/memory state.
## Start Here
Run this quick confidence check before you start a session:
1. **Bootstrap & Handshake**:
Run `bb-init` to resolve paths and identify yourself. Use `--adopt` if resuming a task with uncommitted changes.
```bash
node scripts/bb-init.mjs --register <agent-name> --role <role> --json
# OR
node scripts/bb-init.mjs --adopt <prior-agent-id> --non-interactive --json
bd --version
node skills/beadboard-driver/scripts/session-preflight.mjs
node skills/beadboard-driver/scripts/resolve-bb.mjs
```
2. **Claim Territory**:
Reserve your work surface before making edits to prevent silent collisions.
If discovery fails, install/repair from:
- `https://github.com/zenchantlive/beadboard`
## Session Runbook
1. Diagnose environment.
2. Confirm preflight/discovery.
3. Establish session identity.
4. Read memory + ready work.
5. Claim bead with assignee.
6. Execute and coordinate via events.
7. Run verification gates.
8. Publish evidence and close.
9. Perform memory review.
## Core Commands
```bash
& "$env:BB_REPO\bb.ps1" agent reserve --agent <agent-id> --scope "src/lib/*" --bead <bead-id>
bd update <bead-id> --status in_progress --claim
# Diagnostics and discovery
node skills/beadboard-driver/scripts/diagnose-env.mjs
node skills/beadboard-driver/scripts/session-preflight.mjs
node skills/beadboard-driver/scripts/resolve-bb.mjs
# Ensure project context exists in the target repository
node skills/beadboard-driver/scripts/ensure-project-context.mjs --project-root <repo>
# Identity helper
node skills/beadboard-driver/scripts/generate-agent-name.mjs
# Closeout evidence envelope
node skills/beadboard-driver/scripts/readiness-report.mjs --checks '<json>' --artifacts '<json>'
# Safe self-healing (dry-run default)
node skills/beadboard-driver/scripts/heal-common-issues.mjs --project-root <repo>
node skills/beadboard-driver/scripts/heal-common-issues.mjs --project-root <repo> --apply --fix-git-index-lock
```
3. **Physical Change -> Contextual Lookup**:
If you encounter uncommitted changes in a file you didn't personally edit: **STOP and Query**.
## Bead Lifecycle (Minimum Contract)
```bash
& "$env:BB_REPO\bb.ps1" agent status --agent <agent-id>
& "$env:BB_REPO\bb.ps1" agent inbox --agent <agent-id> --state unread
# Read context
bd show <memory-or-task-id>
bd ready
# Claim explicitly
bd update <bead-id> --status in_progress --assignee <agent-bead-id>
# Record evidence and close
bd update <bead-id> --notes "<commands + outputs>"
bd close <bead-id> --reason "<completed outcome>"
```
4. **Explain Deltas**:
Send high-fidelity signals when you hit milestones or incursions.
## Use-The-Right-Doc Map
### `references/memory-system.md`
Use when you need to query/apply/create canonical memory, validate provenance, or decide whether a lesson belongs in memory vs task notes.
### `references/coord-events-sessions-ack.md`
Use when youre coordinating handoffs/blockers/incursions and need correct inbox/read/ack behavior.
### `references/session-lifecycle.md`
Use for end-to-end session choreography and closeout hygiene.
### `references/archetypes-templates-swarms.md`
Use when choosing team shape, role boundaries, and swarm ownership patterns.
### `references/missions-realtime.md`
Use when assigning work and troubleshooting stale/live-update behavior from mission/event flow.
### `references/command-matrix.md`
Use when you need exact command surfaces and argument shape.
### `references/failure-modes.md`
Use when preflight/discovery/coordination fails and you need deterministic recovery.
## Project Context Template
The skill ships a source template file: `project.template.md`.
Runtime contract:
- Agents should use `<target-repo>/project.md` for project context.
- If `<target-repo>/project.md` is missing, create it from `project.template.md`.
- If `<target-repo>/project.md` already exists, do not overwrite it.
Helper command:
```bash
& "$env:BB_REPO\bb.ps1" agent send --from <agent-id> --to <peer> --bead <bead-id> --category INFO --subject "Patched parser.ts for UI sync" --body "..."
node skills/beadboard-driver/scripts/ensure-project-context.mjs --project-root <repo>
```
5. **Liveness Maintenance**:
Liveness is **Passive**. Any `bb agent` command extends your lease. Use `activity-lease` if you haven't run a command in > 10 minutes.
## Tests
Skill-local contracts:
- `skills/beadboard-driver/tests/run-tests.mjs`
- `skills/beadboard-driver/tests/*.contract.test.mjs`
Repo-level coverage:
- `tests/skills/beadboard-driver/*.test.ts`
## Verification Gates
```bash
& "$env:BB_REPO\bb.ps1" agent activity-lease --agent <agent-id> --json
npm run typecheck
npm run lint
npm run test
```
6. **Closeout Evidence**:
```bash
node skills/beadboard-driver/scripts/readiness-report.mjs --checks '[{"name":"typecheck","ok":true}]' --artifacts '[{"path":"artifacts/final.png","required":true}]'
bd close <bead-id> --reason "..."
```
If failures are outside your scope, cite exact failing files/tests and continue transparently.
## Identity & Adoption Policy
## Bottom Line
- **Uniqueness**: Create one unique `adjective-noun` identity per session unless adopting.
- **Adoption Guardrails**: Adoption is ONLY allowed if uncommitted changes exist in the scope OR you own an `in_progress` bead.
- **Audit**: Every adoption triggers a `RESUME` event in the audit feed.
## Activity Lease (Parking Permit)
- **Active (0-15m)**: Lease is valid. You are protected from takeover.
- **Stale (15-30m)**: Lease expired. Others can takeover with `--takeover-stale`.
- **Evicted (30m+)**: Lease dead. Others should takeover and archive your reservation.
- **Idle (60m+)**: Ghost state. You are considered gone.
## Red Flags - STOP and Start Over
- **Silent Incursion**: Editing a reserved file without sending an `INFO` message.
- **Identity Reuse**: Reusing an agent ID from a previous session without an adoption handshake.
- **Mocking**: Implementing mocks instead of coordinating with the domain owner.
- **Terminal Pop-ups**: Spawning background workers that disrupt the user's desktop.
## References
- Command and argument contracts: `references/command-matrix.md`
- End-to-end session choreography: `references/session-lifecycle.md`
- Protocol Specification: `docs/protocols/operative-protocol-v1.md`
This skill is the bridge between fast autonomous execution and human operator trust. Use it when speed matters, but coordination quality matters more.

View file

@ -1,4 +1,4 @@
interface:
display_name: "Beadboard Driver"
short_description: "Safe bd+bb agent workflow orchestration"
default_prompt: "Use Beadboard Driver to resolve bb path, register a unique session agent, coordinate via bb agent commands, and produce verification-backed closeout notes."
short_description: "BeadBoard control-plane workflow for agents in external repos"
default_prompt: "Use BeadBoard Driver v4 to run evidence-backed bd workflow in the target repo while the user orchestrates via BeadBoard UI; do not mutate project scope, use coordination events/inbox acks, and publish verification-backed closeout notes."

View file

@ -0,0 +1,85 @@
# Project Driver Template
Use this file to define project-specific operating notes for agents using the BeadBoard Driver skill.
## Project Identity
- Project name:
- Repository root:
- Primary language/runtime:
- Primary package manager:
## BeadBoard Relationship
- BeadBoard host/UI location:
- Project registration identifier (if used):
- Notes about how this project appears in BeadBoard UI:
## Scope and Authority
- User controls project scope selection in BeadBoard UI.
- Agents do not change scope.
- Agent execution context in this repo:
## Command Baseline
- Install command:
- Build command:
- Typecheck command:
- Lint command:
- Test command:
- Smoke command (optional):
## Verification Policy Overrides
- Required gates for this project:
- Known slow gates and timeout guidance:
- Evidence format expected in bead notes:
## Environment Constraints
- OS/platform expectations:
- Required environment variables:
- Secrets handling guidance:
- Known path/shell quirks:
## Known Workarounds
Document only stable, repeatable workarounds.
1. Trigger:
- Symptom:
- Workaround:
- Verification:
- Owner:
2. Trigger:
- Symptom:
- Workaround:
- Verification:
- Owner:
## Coordination Defaults
- Default role/archetype mapping used by this project:
- Default handoff style:
- Blocker escalation policy:
- Ack expectations for blocker/handoff messages:
## Safety Guardrails
- Forbidden commands/actions for this repo:
- Files/paths requiring explicit reservation before edit:
- External systems that require human approval:
## Session Closeout Checklist
- [ ] Bead status/assignee updated
- [ ] Verification commands executed and recorded
- [ ] Artifacts attached/linked
- [ ] Memory review performed
- [ ] Follow-up beads created (if needed)
## Change Log
- YYYY-MM-DD: Initial project template completed.

View file

@ -0,0 +1,96 @@
# Archetypes, Templates, and Swarms
## Purpose
Define reusable team structure for multi-agent work so assignments are predictable, auditable, and easy for users to orchestrate from BeadBoard.
## Core Principle
Archetypes and templates define team composition. Missions define task execution.
Keep these concerns separate.
## Archetypes (Role Contracts)
An archetype is a role with clear responsibilities and deliverable expectations.
Baseline archetypes:
- `coder`: implements scoped changes and provides evidence.
- `reviewer`: validates quality, regressions, and acceptance criteria.
- `writer`: maintains user-facing docs, memory docs, and operator notes.
Optional archetypes may exist per project, but every archetype should specify:
- primary responsibilities,
- quality gates,
- handoff inputs/outputs,
- escalation triggers.
## Team Templates (Composition Contracts)
A template is a named role composition for repeatable work patterns.
Examples:
- Fast lane: `coder + reviewer`
- Documentation lane: `writer + reviewer`
- Parallel lane: `orchestrator + coder + reviewer + writer`
Template quality rules:
- keep composition minimal,
- avoid duplicate authority,
- define ownership boundaries,
- define expected handoff order.
## Swarms (Runtime Team Instances)
A swarm is a live team instance operating on specific beads/epics.
Lifecycle:
1. Create swarm instance from a template or manual composition.
2. Join agents into explicit roles.
3. Assign beads with ownership.
4. Coordinate via events and inbox.
5. Leave or close swarm cleanly when complete.
## Command Surface (Representative)
Use your environment's swarm commands to manage lifecycle.
Expected operations:
- create swarm
- list/show swarm
- join swarm with role
- leave swarm
- close swarm
All swarm actions should produce observable state changes in BeadBoard views.
## Ownership Rules
- Every in-progress bead should have one clear assignee.
- Swarms may collaborate on an epic, but each bead needs an explicit owner.
- Multi-agent edits require reservation and coordination signals.
## User Orchestration Relationship
Users control orchestration from BeadBoard UI:
- choose team shape/template,
- assign or reassign roles,
- intervene on blockers,
- monitor throughput and liveness.
Agents execute according to assigned role and bead ownership.
## Anti-Patterns
- Role ambiguity (multiple agents assuming same responsibility).
- Oversized swarms with no clear ownership boundaries.
- Using templates as mission definitions.
- Running unassigned parallel work with no bead claim.
- Treating swarm closure as optional housekeeping.

View file

@ -1,38 +1,41 @@
# Command Matrix
## Bootstrapping and Handshake
- `node scripts/bb-init.mjs --register <name> --role <role> --json`
- Output: `{ ok, agent_id, mode, lease, timestamp }`
- `node scripts/bb-init.mjs --adopt <id> [--non-interactive] --json`
- Output: `{ ok, agent_id, mode, lease, timestamp }` or `{ ok:false, error }`
## Coordination Commands (`bb`)
- `bb agent register --name <agent> --role <role>`
- `bb agent activity-lease --agent <agent> [--json]`
- Output: `{ ok, command, data: AgentRecord }`
- `bb agent list [--role <role>] [--status <status>]`
- `bb agent show --agent <agent>`
- `bb agent send --from <agent> --to <agent> --bead <id> --category <HANDOFF|BLOCKED|DECISION|INFO> --subject <text> --body <text>`
- `bb agent inbox --agent <agent> [--state unread|read|acked] [--bead <id>]`
- `bb agent read --agent <agent> --message <message-id>`
- `bb agent ack --agent <agent> --message <message-id>`
- `bb agent reserve --agent <agent> --scope <path> --bead <id> [--ttl <minutes>] [--takeover-stale]`
- `bb agent release --agent <agent> --scope <path>`
- `bb agent status [--bead <id>] [--agent <agent>]`
## Lifecycle Commands (`bd`)
- `bd ready`
- `bd show <bead-id>`
- `bd update <bead-id> --status in_progress --claim`
- `bd update <bead-id> --notes "<evidence>"`
- `bd close <bead-id> --reason "<summary>"`
## Legacy/Internal Scripts
# Command Matrix
## Bootstrapping and Handshake
- `node scripts/bb-init.mjs --register <name> --role <role> --json`
- Output: `{ ok, agent_id, mode, lease, timestamp }`
- `node scripts/bb-init.mjs --adopt <id> [--non-interactive] --json`
- Output: `{ ok, agent_id, mode, lease, timestamp }` or `{ ok:false, error }`
## Coordination Commands (`bb`)
- `bb agent register --name <agent> --role <role>`
- `bb agent activity-lease --agent <agent> [--json]`
- Output: `{ ok, command, data: AgentRecord }`
- `bb agent list [--role <role>] [--status <status>]`
- `bb agent show --agent <agent>`
- `bb agent send --from <agent> --to <agent> --bead <id> --category <HANDOFF|BLOCKED|DECISION|INFO> --subject <text> --body <text>`
- `bb agent inbox --agent <agent> [--state unread|read|acked] [--bead <id>]`
- `bb agent read --agent <agent> --message <message-id>`
- `bb agent ack --agent <agent> --message <message-id>`
- `bb agent reserve --agent <agent> --scope <path> --bead <id> [--ttl <minutes>] [--takeover-stale]`
- `bb agent release --agent <agent> --scope <path>`
- `bb agent status [--bead <id>] [--agent <agent>]`
## Lifecycle Commands (`bd`)
- `bd ready`
- `bd show <bead-id>`
- `bd update <bead-id> --status in_progress --claim`
- `bd update <bead-id> --notes "<evidence>"`
- `bd close <bead-id> --reason "<summary>"`
## Legacy/Internal Scripts
- `node skills/beadboard-driver/scripts/resolve-bb.mjs`
- `node skills/beadboard-driver/scripts/session-preflight.mjs`
- `node skills/beadboard-driver/scripts/generate-agent-name.mjs`
- `node skills/beadboard-driver/scripts/readiness-report.mjs --checks <json> --artifacts <json>`
- `node skills/beadboard-driver/scripts/readiness-report.mjs --checks <json> --artifacts <json>`
- `node skills/beadboard-driver/scripts/diagnose-env.mjs`
- `node skills/beadboard-driver/scripts/heal-common-issues.mjs [--project-root <path>] [--apply] [--fix-git-index-lock]`
- `node skills/beadboard-driver/scripts/ensure-project-context.mjs [--project-root <path>]`

View file

@ -0,0 +1,121 @@
# Coordination Events, Sessions, and Acknowledgment
## Purpose
Define how agents communicate status, blockers, incursions, and handoffs in a machine-readable way that BeadBoard can render and users can act on.
## Operating Model
- Agent works in a target repository.
- User watches and orchestrates from BeadBoard UI.
- Agent communication must flow through coordination events and inbox state transitions, not ad-hoc notes.
## Event Categories
Use explicit categories with clear intent:
- `HANDOFF`: transfer ownership or next action.
- `BLOCKED`: explicit dependency or missing input.
- `RESUME`: adoption/resumption event.
- `INFO`: milestone or important context.
- `INCURSION`: overlap/collision signal for reserved scope.
## Session Stream Expectations
Session feeds should be audit-friendly:
- Every coordination event has sender, recipient/system target, bead id, and timestamp.
- `INCURSION` and `RESUME` are first-class timeline rows, not hidden diagnostics.
- Events should be understandable by humans without reading implementation code.
## Message Lifecycle
Inbox state machine:
1. `unread` when message is delivered.
2. `read` when recipient opens/reads message.
3. `acked` when recipient explicitly acknowledges.
Required behavior:
- Only recipient may ack.
- Acks are explicit, not implied by read.
- Blocker and handoff flows should request ack when coordination certainty is required.
## Recommended Command Patterns
Send structured coordination event:
```bash
bb agent send \
--from <agent-id> \
--to <peer-agent-id> \
--bead <bead-id> \
--category <HANDOFF|BLOCKED|RESUME|INFO|INCURSION> \
--subject "<short summary>" \
--body "<actionable details>"
```
Read inbox for current bead/session work:
```bash
bb agent inbox --agent <agent-id> --state unread --bead <bead-id>
bb agent read --agent <agent-id> --message <message-id>
bb agent ack --agent <agent-id> --message <message-id>
```
## Coordination Contracts
### Handoff
A `HANDOFF` should include:
- what is done,
- what remains,
- concrete next action,
- whether ack is required.
### Blocked
A `BLOCKED` should include:
- blocker description,
- requested action,
- urgency,
- ack requirement.
### Incursion
An `INCURSION` should include:
- overlap kind (`exact` or `partial`),
- owner identity,
- incoming identity,
- owner liveness,
- resolution hint.
### Resume
A `RESUME` should include:
- resume reason,
- prior session identity,
- adopted identity,
- evidence summary for safe adoption.
## UX Alignment
Session UI should map event semantics to plain-language actions:
- Handoff label: "Passed to"
- Blocked label: "Needs input"
- Read action: "Seen"
- Ack action: "Accepted"
## Anti-Patterns
- Using comments instead of coordination events for handoffs.
- Silent reservation collisions with no `INCURSION`/`INFO` signal.
- Treating read as ack.
- Sending vague events with no actionable payload.
- Closing a blocked bead without tracking unblock communication.

View file

@ -38,3 +38,10 @@
- Do not write `.beads/issues.jsonl` directly.
- Do not close beads without verification evidence.
- Do not bypass `BB_REPO` when it is set but invalid; fix it explicitly.
## Local Environment Repair Signals
- `GIT_INDEX_LOCK_PRESENT`: stale git lock can block local operations.
- Recovery:
- confirm no active git process is using the repository,
- run `node skills/beadboard-driver/scripts/heal-common-issues.mjs --project-root <repo> --apply --fix-git-index-lock`.

View file

@ -0,0 +1,110 @@
# Memory System
## Purpose
Use BeadBoard memory to preserve reusable operating rules across sessions.
Memory is tracked in `bd` decision beads, not markdown notes. Task notes are for local execution context; canonical memory is for reusable rules.
## Execution Context
- Agents usually run in a target project repository, not the BeadBoard repository.
- Project scope is controlled by the user in the BeadBoard UI.
- Agents do not select or mutate project scope.
## Core Objects
- Anchor: domain parent bead (for example architecture, workflow, agent ops, reliability).
- Canonical memory: `type=decision` bead with memory labels.
- Provenance links: relations from memory to source evidence beads.
## Canonical Memory Contract
Create canonical memory only when the rule is reusable.
Required labels:
- `mem-canonical`
- `mem-hard` or `mem-soft`
- `memory`
- domain label such as `memory-agent`, `memory-arch`, `memory-workflow`, `memory-reliability`, `memory-ui`
Required description sections:
- `Scope:`
- `Out of Scope:`
- `Rule:`
- `Rationale:`
- `Failure Mode:`
Required acceptance style:
- Given/When/Then invariant
- Verification commands
## Workflow
1. Query existing memory first.
2. Validate the memory provenance before relying on it.
3. Apply existing canonical memory to current task design.
4. If a new reusable rule appears, create canonical memory.
5. Link anchor, evidence, and related work with `bd dep relate`.
6. Ratify by closing the memory bead once complete.
7. For changes to an existing rule, supersede; do not rewrite history.
## Query and Validation Commands
```bash
bd query "type=decision label:mem-canonical"
bd show <memory-id>
bd dep list <memory-id>
```
Interpretation checklist:
- Is the memory closed and canonical?
- Are provenance links present (2-5 evidence beads preferred)?
- Is the domain anchor relationship present?
## Create and Index Canonical Memory
```bash
bd create --title="[MEMORY][<DOMAIN>][HARD|SOFT] <rule sentence>" \
--description="Scope: ...\nOut of Scope: ...\nRule: ...\nRationale: ...\nFailure Mode: ..." \
--type=decision --priority=1 \
--label="mem-canonical,mem-hard,memory,memory-<domain>"
bd dep relate <anchor-id> <memory-id>
bd dep relate <memory-id> <source-bead-id>
```
Use `mem-soft` when the rule is guidance and `mem-hard` when it is non-negotiable.
## Evolve Memory Safely
Use supersession when changing canonical rules:
```bash
bd supersede <old-memory-id> --with <new-memory-id>
```
Do not edit historical memory beads to represent new policy.
## Noise Budget
Apply memory sparingly per active task:
- 3-7 related memory nodes
- 0-2 blocker contracts
- 1 primary anchor domain per canonical memory
- 2-5 source-bead provenance links
If the lesson is not reusable, record it in task notes instead of creating memory.
## Anti-Patterns
- Writing policy in ad-hoc markdown only.
- Using blocker edges for memory indexing.
- Creating duplicate canonical memory for the same rule.
- Creating memory for one-off incidents without recurrence.
- Claiming memory-backed completion without verification evidence.

View file

@ -0,0 +1,96 @@
# Missions and Realtime
## Purpose
Define how work assignments (missions) and realtime updates should behave so users can orchestrate external-repo execution from BeadBoard with confidence.
## Mission Model
A mission is an execution assignment bound to clear ownership and expected outputs.
Mission essentials:
- bead/epic scope,
- assigned owner,
- expected deliverable,
- dependency awareness,
- handoff path.
## Assignment Rules
- One active owner per bead-level mission.
- Multi-agent support is achieved through parallel missions, not shared ambiguous ownership.
- Mission assignment must be visible in BeadBoard and reflected in bead assignee/status fields.
## Mission Topology
Missions should align with dependency graph semantics:
- dependencies model execution order,
- independent missions can run in parallel,
- blocked missions must not be represented as ready work.
When topology changes, update bead dependency links first, then assignment communication.
## Realtime Contract
Realtime is the user visibility layer.
Expected sources:
- bead status updates,
- coordination events,
- reservation/lease changes,
- watcher/SSE refresh signals.
Expected outcomes:
- UI updates without manual refresh,
- consistent state across social/graph/session surfaces,
- event timeline continuity for audits.
## SSE/Event Behavior
Realtime streams should provide:
- monotonic event ids where supported,
- heartbeat behavior for long-lived connections,
- resilience to brief write bursts and file-watch jitter,
- eventual consistency with bead source of truth.
If stale-state is suspected, triage in this order:
1. Source-of-truth parity.
2. Read-path validation.
3. Watcher input coverage.
4. Event emission/subscription path.
## Agent Responsibilities
Agents must:
- emit meaningful coordination events during mission lifecycle,
- keep bead status and assignee current,
- provide verification evidence before close,
- avoid implicit/unlogged handoffs.
Agents must not:
- change BeadBoard UI project scope,
- rely on local assumptions not visible in event/state outputs.
## User Responsibilities
Users orchestrate control-plane actions in BeadBoard UI:
- scope selection,
- priority/assignment changes,
- intervention on blocked missions,
- monitoring mission and realtime health.
## Anti-Patterns
- Mission start without bead claim/assignee update.
- Hidden handoffs outside coordination events.
- Treating stale UI as resolved without parity checks.
- Closing missions without verification evidence.

View file

@ -2,10 +2,13 @@
## 1) Start Session
1. Run preflight.
2. Resolve bb path and confirm `bd` availability.
3. Generate unique session agent name.
4. Register agent identity.
1. Run environment diagnosis.
2. Run preflight.
3. Resolve bb path and confirm `bd` availability.
4. Generate unique session agent name.
5. Register agent identity.
6. Confirm you are operating in the assigned target repository.
7. Do not change project scope (scope is user-controlled in BeadBoard UI).
## 2) Pick and Claim Work

View file

@ -0,0 +1,79 @@
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
import { findCommandInPath, resolveBbPath } from './lib/driver-lib.mjs';
async function pathExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function main() {
const timestamp = new Date().toISOString();
const cwd = process.cwd();
const gitIndexLock = path.join(cwd, '.git', 'index.lock');
const findings = [];
const recommendations = [];
const bdPath = await findCommandInPath('bd');
const bb = await resolveBbPath();
const hasGitIndexLock = await pathExists(gitIndexLock);
if (!bdPath) {
findings.push({
code: 'BD_NOT_FOUND',
severity: 'high',
message: 'bd command not found in PATH.',
});
recommendations.push(
'Install BeadBoard tooling from https://github.com/zenchantlive/beadboard or add bd executable directory to PATH.',
);
}
if (!bb.ok) {
findings.push({
code: 'BB_NOT_FOUND',
severity: 'high',
message: bb.reason,
});
if (bb.remediation) {
recommendations.push(bb.remediation);
}
}
if (hasGitIndexLock) {
findings.push({
code: 'GIT_INDEX_LOCK_PRESENT',
severity: 'medium',
message: `Potential stale git lock detected at ${gitIndexLock}`,
});
recommendations.push('Run heal-common-issues.mjs with --apply --fix-git-index-lock if no git process is active.');
}
const payload = {
ok: findings.filter((item) => item.severity === 'high').length === 0,
timestamp,
environment: {
cwd,
project_root: cwd,
platform: process.platform,
node_version: process.version,
},
tools: {
bd: { available: Boolean(bdPath), path: bdPath || null },
bb,
},
findings,
recommendations,
};
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
}
void main();

View file

@ -0,0 +1,85 @@
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
function parseArgs(argv) {
const args = new Set();
const values = {};
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (!token.startsWith('--')) {
continue;
}
const key = token.slice(2);
const next = argv[i + 1];
if (!next || next.startsWith('--')) {
args.add(key);
continue;
}
values[key] = next;
i += 1;
}
return { args, values };
}
async function exists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function main() {
const { values } = parseArgs(process.argv.slice(2));
const projectRoot = values['project-root'] ? path.resolve(values['project-root']) : process.cwd();
const thisFile = fileURLToPath(import.meta.url);
const skillRoot = path.resolve(path.dirname(thisFile), '..');
const templatePath = path.join(skillRoot, 'project.template.md');
const targetPath = path.join(projectRoot, 'project.md');
const targetExists = await exists(targetPath);
if (targetExists) {
process.stdout.write(
`${JSON.stringify(
{
ok: true,
created: false,
used_existing: true,
project_root: projectRoot,
target_path: targetPath,
template_path: templatePath,
},
null,
2,
)}\n`,
);
return;
}
const template = await fs.readFile(templatePath, 'utf8');
await fs.writeFile(targetPath, template, 'utf8');
process.stdout.write(
`${JSON.stringify(
{
ok: true,
created: true,
used_existing: false,
project_root: projectRoot,
target_path: targetPath,
template_path: templatePath,
},
null,
2,
)}\n`,
);
}
void main();

View file

@ -0,0 +1,104 @@
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
function parseArgs(argv) {
const args = new Set();
const values = {};
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (!token.startsWith('--')) {
continue;
}
const key = token.slice(2);
const next = argv[i + 1];
if (!next || next.startsWith('--')) {
args.add(key);
continue;
}
values[key] = next;
i += 1;
}
return { args, values };
}
async function pathExists(filePath) {
try {
await fs.access(filePath);
return true;
} catch {
return false;
}
}
async function removeIfExists(filePath) {
if (!(await pathExists(filePath))) {
return false;
}
await fs.rm(filePath, { force: true });
return true;
}
async function main() {
const { args, values } = parseArgs(process.argv.slice(2));
const apply = args.has('apply');
const fixGitIndexLock = args.has('fix-git-index-lock');
const projectRoot = values['project-root'] ? path.resolve(values['project-root']) : process.cwd();
const actions = [];
const warnings = [];
const lockPath = path.join(projectRoot, '.git', 'index.lock');
const lockExists = await pathExists(lockPath);
if (fixGitIndexLock && lockExists) {
if (apply) {
const removed = await removeIfExists(lockPath);
actions.push({
id: 'fix-git-index-lock',
attempted: true,
applied: removed,
target: lockPath,
});
} else {
actions.push({
id: 'fix-git-index-lock',
attempted: true,
applied: false,
target: lockPath,
});
warnings.push('Dry-run mode enabled. Re-run with --apply to perform fixes.');
}
} else if (fixGitIndexLock && !lockExists) {
actions.push({
id: 'fix-git-index-lock',
attempted: true,
applied: false,
target: lockPath,
note: 'No index.lock found.',
});
}
if (!fixGitIndexLock && lockExists) {
warnings.push('Stale git index.lock detected. Use --fix-git-index-lock to target it.');
}
process.stdout.write(
`${JSON.stringify(
{
ok: true,
mode: apply ? 'apply' : 'dry-run',
project_root: projectRoot,
actions,
warnings,
},
null,
2,
)}\n`,
);
}
void main();

View file

@ -1,114 +1,114 @@
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
function parseArgs(argv) {
const output = {};
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith('--')) {
continue;
}
const key = token.slice(2);
const value = argv[index + 1];
if (!value || value.startsWith('--')) {
output[key] = 'true';
continue;
}
output[key] = value;
index += 1;
}
return output;
}
function parseJsonArray(raw, fallback) {
if (!raw) {
return fallback;
}
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : fallback;
} catch {
return fallback;
}
}
async function withArtifactExistence(artifacts) {
const output = [];
for (const artifact of artifacts) {
const item = {
path: artifact.path,
required: Boolean(artifact.required),
exists: false,
};
if (typeof artifact.path === 'string' && artifact.path.trim()) {
try {
const resolved = path.resolve(artifact.path);
await fs.access(resolved);
item.exists = true;
} catch {
item.exists = false;
}
}
output.push(item);
}
return output;
}
async function main() {
try {
const args = parseArgs(process.argv.slice(2));
const checks = parseJsonArray(args.checks, []);
const artifacts = parseJsonArray(args.artifacts, []);
const dependencySanity = args['dependency-note'] || '';
const normalizedChecks = checks.map((check) => ({
name: check.name || 'unnamed-check',
ok: Boolean(check.ok),
details: check.details || '',
}));
const normalizedArtifacts = await withArtifactExistence(artifacts);
const allChecksPass = normalizedChecks.every((check) => check.ok);
const requiredArtifactsPresent = normalizedArtifacts.every((artifact) => !artifact.required || artifact.exists);
const ready = allChecksPass && requiredArtifactsPresent;
process.stdout.write(
`${JSON.stringify(
{
ok: true,
generated_at: new Date().toISOString(),
checks: normalizedChecks,
artifacts: normalizedArtifacts,
dependency_sanity: dependencySanity,
summary: {
checks_passed: allChecksPass,
required_artifacts_present: requiredArtifactsPresent,
ready,
},
},
null,
2,
)}\n`,
);
} catch (error) {
process.stdout.write(
`${JSON.stringify(
{
ok: false,
reason: error instanceof Error ? error.message : String(error),
summary: {
checks_passed: false,
required_artifacts_present: false,
ready: false,
},
},
null,
2,
)}\n`,
);
}
}
void main();
#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
function parseArgs(argv) {
const output = {};
for (let index = 0; index < argv.length; index += 1) {
const token = argv[index];
if (!token.startsWith('--')) {
continue;
}
const key = token.slice(2);
const value = argv[index + 1];
if (!value || value.startsWith('--')) {
output[key] = 'true';
continue;
}
output[key] = value;
index += 1;
}
return output;
}
function parseJsonArray(raw, fallback) {
if (!raw) {
return fallback;
}
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : fallback;
} catch {
return fallback;
}
}
async function withArtifactExistence(artifacts) {
const output = [];
for (const artifact of artifacts) {
const item = {
path: artifact.path,
required: Boolean(artifact.required),
exists: false,
};
if (typeof artifact.path === 'string' && artifact.path.trim()) {
try {
const resolved = path.resolve(artifact.path);
await fs.access(resolved);
item.exists = true;
} catch {
item.exists = false;
}
}
output.push(item);
}
return output;
}
async function main() {
try {
const args = parseArgs(process.argv.slice(2));
const checks = parseJsonArray(args.checks, []);
const artifacts = parseJsonArray(args.artifacts, []);
const dependencySanity = args['dependency-note'] || '';
const normalizedChecks = checks.map((check) => ({
name: check.name || 'unnamed-check',
ok: Boolean(check.ok),
details: check.details || '',
}));
const normalizedArtifacts = await withArtifactExistence(artifacts);
const allChecksPass = normalizedChecks.every((check) => check.ok);
const requiredArtifactsPresent = normalizedArtifacts.every((artifact) => !artifact.required || artifact.exists);
const ready = allChecksPass && requiredArtifactsPresent;
process.stdout.write(
`${JSON.stringify(
{
ok: true,
generated_at: new Date().toISOString(),
checks: normalizedChecks,
artifacts: normalizedArtifacts,
dependency_sanity: dependencySanity,
summary: {
checks_passed: allChecksPass,
required_artifacts_present: requiredArtifactsPresent,
ready,
},
},
null,
2,
)}\n`,
);
} catch (error) {
process.stdout.write(
`${JSON.stringify(
{
ok: false,
reason: error instanceof Error ? error.message : String(error),
summary: {
checks_passed: false,
required_artifacts_present: false,
ready: false,
},
},
null,
2,
)}\n`,
);
}
}
void main();

View file

@ -0,0 +1,24 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import path from 'node:path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
const execFileAsync = promisify(execFile);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'diagnose-env.mjs');
test('diagnose-env contract: returns stable schema', async () => {
const { stdout } = await execFileAsync(process.execPath, [scriptPath], {
env: { ...process.env, PATH: '' },
});
const result = JSON.parse(stdout);
assert.equal(typeof result.ok, 'boolean');
assert.equal(typeof result.timestamp, 'string');
assert.equal(result.environment !== null, true);
assert.equal(Array.isArray(result.findings), true);
assert.equal(Array.isArray(result.recommendations), true);
});

View file

@ -0,0 +1,47 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
const execFileAsync = promisify(execFile);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'ensure-project-context.mjs');
test('ensure-project-context creates project.md when missing', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-project-context-'));
try {
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--project-root', root]);
const result = JSON.parse(stdout);
const content = await fs.readFile(path.join(root, 'project.md'), 'utf8');
assert.equal(result.ok, true);
assert.equal(result.created, true);
assert.match(content, /Project Driver Template/);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test('ensure-project-context preserves existing project.md', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-project-context-'));
try {
const target = path.join(root, 'project.md');
await fs.writeFile(target, '# existing\n', 'utf8');
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--project-root', root]);
const result = JSON.parse(stdout);
const content = await fs.readFile(target, 'utf8');
assert.equal(result.ok, true);
assert.equal(result.created, false);
assert.equal(result.used_existing, true);
assert.equal(content, '# existing\n');
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});

View file

@ -0,0 +1,69 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
const execFileAsync = promisify(execFile);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'heal-common-issues.mjs');
test('heal-common-issues contract: dry-run does not mutate git index.lock', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-heal-'));
try {
const lockDir = path.join(root, '.git');
const lockPath = path.join(lockDir, 'index.lock');
await fs.mkdir(lockDir, { recursive: true });
await fs.writeFile(lockPath, 'locked', 'utf8');
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--project-root', root], {
env: process.env,
});
const result = JSON.parse(stdout);
const lockStillExists = await fs
.access(lockPath)
.then(() => true)
.catch(() => false);
assert.equal(result.ok, true);
assert.equal(result.mode, 'dry-run');
assert.equal(lockStillExists, true);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});
test('heal-common-issues contract: apply removes stale git index.lock when opted in', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-heal-'));
try {
const lockDir = path.join(root, '.git');
const lockPath = path.join(lockDir, 'index.lock');
await fs.mkdir(lockDir, { recursive: true });
await fs.writeFile(lockPath, 'locked', 'utf8');
const { stdout } = await execFileAsync(
process.execPath,
[scriptPath, '--project-root', root, '--apply', '--fix-git-index-lock'],
{
env: process.env,
},
);
const result = JSON.parse(stdout);
const lockStillExists = await fs
.access(lockPath)
.then(() => true)
.catch(() => false);
assert.equal(result.ok, true);
assert.equal(result.mode, 'apply');
assert.equal(lockStillExists, false);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});

View file

@ -0,0 +1,35 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { fileURLToPath } from 'node:url';
const execFileAsync = promisify(execFile);
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const scriptPath = path.resolve(__dirname, '..', 'scripts', 'readiness-report.mjs');
test('readiness-report contract: returns ready true for passing checks and present artifacts', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'bb-skill-readiness-'));
try {
const artifact = path.join(root, 'artifact.txt');
await fs.writeFile(artifact, 'ok', 'utf8');
const checks = JSON.stringify([
{ name: 'typecheck', ok: true, details: 'pass' },
{ name: 'lint', ok: true, details: 'pass' },
]);
const artifacts = JSON.stringify([{ path: artifact, required: true }]);
const { stdout } = await execFileAsync(process.execPath, [scriptPath, '--checks', checks, '--artifacts', artifacts]);
const result = JSON.parse(stdout);
assert.equal(result.ok, true);
assert.equal(result.summary.ready, true);
} finally {
await fs.rm(root, { recursive: true, force: true });
}
});

View file

@ -11,6 +11,10 @@ const tests = [
path.join(__dirname, 'resolve-bb.contract.test.mjs'),
path.join(__dirname, 'generate-agent-name.contract.test.mjs'),
path.join(__dirname, 'session-preflight.contract.test.mjs'),
path.join(__dirname, 'readiness-report.contract.test.mjs'),
path.join(__dirname, 'diagnose-env.contract.test.mjs'),
path.join(__dirname, 'heal-common-issues.contract.test.mjs'),
path.join(__dirname, 'ensure-project-context.contract.test.mjs'),
];
const child = spawn(process.execPath, ['--test', ...tests], {

View file

@ -26,10 +26,17 @@ test('session-preflight contract: succeeds with bd + BB_REPO', async () => {
try {
const repo = path.join(root, 'beadboard');
const toolsDir = path.join(root, 'tools');
const bdExecutable = process.platform === 'win32' ? 'bd.cmd' : 'bd';
const bdPath = path.join(toolsDir, bdExecutable);
await fs.mkdir(path.join(repo, 'tools'), { recursive: true });
await fs.mkdir(toolsDir, { recursive: true });
await fs.writeFile(path.join(repo, 'bb.ps1'), 'echo ok', 'utf8');
await fs.writeFile(path.join(toolsDir, 'bd.cmd'), '@echo off\r\necho beads\r\n', 'utf8');
if (process.platform === 'win32') {
await fs.writeFile(bdPath, '@echo off\r\necho beads\r\n', 'utf8');
} else {
await fs.writeFile(bdPath, '#!/usr/bin/env sh\necho beads\n', 'utf8');
await fs.chmod(bdPath, 0o755);
}
const { stdout } = await execFileAsync(process.execPath, [scriptPath], {
env: { ...process.env, PATH: toolsDir, BB_REPO: repo, BB_SKILL_HOME: path.join(root, 'home') },