checkpoint: pre-split branch cleanup
This commit is contained in:
parent
4c2ae2e5b7
commit
b5db7a7753
276 changed files with 35912 additions and 60119 deletions
|
|
@ -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 you’re 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.
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
|
|
|
|||
85
skills/beadboard-driver/project.template.md
Normal file
85
skills/beadboard-driver/project.template.md
Normal 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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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>]`
|
||||
|
|
|
|||
121
skills/beadboard-driver/references/coord-events-sessions-ack.md
Normal file
121
skills/beadboard-driver/references/coord-events-sessions-ack.md
Normal 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.
|
||||
|
|
@ -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`.
|
||||
|
|
|
|||
110
skills/beadboard-driver/references/memory-system.md
Normal file
110
skills/beadboard-driver/references/memory-system.md
Normal 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.
|
||||
96
skills/beadboard-driver/references/missions-realtime.md
Normal file
96
skills/beadboard-driver/references/missions-realtime.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
79
skills/beadboard-driver/scripts/diagnose-env.mjs
Normal file
79
skills/beadboard-driver/scripts/diagnose-env.mjs
Normal 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();
|
||||
85
skills/beadboard-driver/scripts/ensure-project-context.mjs
Normal file
85
skills/beadboard-driver/scripts/ensure-project-context.mjs
Normal 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();
|
||||
104
skills/beadboard-driver/scripts/heal-common-issues.mjs
Normal file
104
skills/beadboard-driver/scripts/heal-common-issues.mjs
Normal 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();
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
24
skills/beadboard-driver/tests/diagnose-env.contract.test.mjs
Normal file
24
skills/beadboard-driver/tests/diagnose-env.contract.test.mjs
Normal 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);
|
||||
});
|
||||
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
|
@ -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 });
|
||||
}
|
||||
});
|
||||
|
|
@ -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], {
|
||||
|
|
|
|||
|
|
@ -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') },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue