diff --git a/CHANGELOG.md b/CHANGELOG.md index 13bae4d..5ccf77a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## Unreleased + +- Skill packs are host-universal: standard SKILL.md frontmatter (the + `compatibility: opencode` field is removed from universal packs; + `customize-labwired-agent` remains opencode-only). +- New `harness-portable` lane simulates a foreign install and a universality + gate in `skills-verify-all` prevents host lock-in regressions. +- Docs: "Use with other agents" (skills CLI + AGENTS.md + MCP snippet). + ## 0.3.11 — 2026-08-12 — Tighten release paths - Doctor only reports **signed in** after live hosted probe succeeds (no false green). diff --git a/README.md b/README.md index c67a3d4..81f405f 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ installations and data stay in place. - [Embedder parity](docs/EMBEDDER_PARITY.md) - [Security](docs/SECURITY.md) - [Self-host / airgap](docs/SELF_HOST.md) +- [Use with other agents](docs/INSTALL.md#use-with-other-agents) ## Skills (domain packs) diff --git a/config/AGENTS.md b/config/AGENTS.md index bffe162..19bd39c 100644 --- a/config/AGENTS.md +++ b/config/AGENTS.md @@ -172,6 +172,8 @@ They **do not** mint `model_verified` and **do not** replace knowledge MCP. `list` / `describe` → **`labwired_part`** → **`labwired_datasheet`**. Never invent. Contract: `docs/KNOWLEDGE.md`. +Internal docs (specs, plans, product docs, QA reports) live in the private `LabWired/agent-internal` repository, not in this repository. + ## Tool allowlist ### MCP tools (agent may call) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 03e70a0..7ceb6b7 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -33,6 +33,11 @@ This test uses a temporary install prefix. It may download tools. ## Run a live twin test +The gate scripts (`scripts/live-gate1.sh`, `scripts/import-multi-smoke.sh`, +`scripts/ship-gate.sh`) run from a source checkout of the repository, not +from the installed npm package. They need `tests/` and `fixtures/`, which +the package does not ship. + ```bash bash scripts/live-gate1.sh ``` diff --git a/docs/INSTALL.md b/docs/INSTALL.md index da115d7..877e765 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -73,3 +73,87 @@ unknown files in the LabWired directory. `PATH`. Add that directory manually if the command is still unavailable. - Run `labwired agent doctor` and follow the first reported error. - Run the install command again to repair or refresh the Agent. + +## Use with other agents + +The LabWired harness is a standard Agent Skills pack (SKILL.md format). Any +agent host that understands the format can load it. + +### Skills + +With the Vercel skills CLI (supports 40+ agents; needs Node.js 22.20 or +later): + +```bash +npx skills add LabWired/agent +``` + +Or copy the `skills/` directory into your host's skills directory by hand; +that also copies `customize-labwired-agent`, which you can skip. + +`customize-labwired-agent` documents the LabWired Agent's own runtime +configuration and is not useful to other hosts. + +The `observe` and `desk-hw` packs additionally need the LabWired Agent CLI +installed (`labwired agent …` on PATH). Every pack's guidance loads in any +host, but firmware tool calls need either the hosted MCP endpoint (after +`labwired agent login`) or the `labwired` CLI on PATH (local MCP, see +below). The develop workflow's first tool call, `labwired_context`, is +served only by the hosted endpoint. + +### Instructions + +`config/AGENTS.md` is host-agnostic. Use it as your host's instruction file +(AGENTS.md, CLAUDE.md, or equivalent), or merge its claim-vocabulary section +into your existing one: twin verification comes only from `labwired_verify`, +desk hardware claims only from physical hardware with independently captured +evidence, and a build is never a behavior proof. The exact evidence statuses +are defined in `config/AGENTS.md` and [docs/VERIFY.md](VERIFY.md). + +### MCP server + +Firmware tooling is served over MCP, in two variants. + +**Hosted (full tool surface).** Compile, twin verification, and the +knowledge tools (`labwired_context`, `labwired_part`, `labwired_datasheet`, +`labwired_import`) are served only by the hosted endpoint. Install the +LabWired Agent, run `labwired agent login`, then point your host at the +hosted endpoint: + +```json +{ + "mcpServers": { + "labwired": { + "url": "https://api.labwired.com/mcp?toolNames=unprefixed", + "headers": { "Authorization": "Bearer " } + } + } +} +``` + +Hosts differ in envelope: Claude Code expects `"type": "http"` on the entry, +and opencode uses its own top-level `mcp` key with the same URL. The token is +read from the session file at `~/.labwired/session/cloud.json` after +`labwired agent login`. It expires after about an hour, and a foreign host +cannot refresh it; re-run the LabWired Agent to renew. + +**Local (artifact tools only).** This variant serves the artifact tools +(`labwired_search`, `labwired_list`, `labwired_describe`, +`labwired_validate`, `labwired_export`, `labwired_run`, `labwired_inspect`, +`labwired_verify`, `labwired_fuzz`, `labwired_ingest_svd`, +`labwired_validate_device`). It shells out to the `labwired` CLI, so the +LabWired CLI must be on PATH. It does not serve compile or knowledge tools. + +```json +{ + "mcpServers": { + "labwired": { + "command": "npx", + "args": ["-y", "@labwired/mcp"] + } + } +} +``` + +Hosts without MCP support can still use the skills and instructions, but no +firmware tool calls are possible there. diff --git a/docs/TESTING.md b/docs/TESTING.md index ec4293b..c57d70d 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -38,6 +38,9 @@ npx --yes node@18.0.0 tests/hardware-runner.test.mjs - `run-bounded.sh` checks the bounded-run helper itself. - Skill tests check the installed skill set and instructions (`skills-inventory.sh`, `skills-verify-all.sh`, `develop-skill.sh`). + `skills-verify-all.sh` also gates pack universality (no host-locked + frontmatter or opencode-only references in universal packs), and + `harness-portable.sh` simulates a foreign install of the universal pack. - Develop lanes check the `develop` workflow: `develop-acceptance-smoke.sh` (npm `test:develop:acceptance`) and the grounded hosted-agent certification `develop-agent-e2e.sh` (npm `test:develop:agent`; `test:develop:release` diff --git a/package.json b/package.json index a71bc8b..bef30c0 100644 --- a/package.json +++ b/package.json @@ -131,10 +131,8 @@ "docs/KNOWLEDGE.md", "docs/GOLDEN_PATH.md", "scripts/ship-gate.sh", - "scripts/live-gate1.sh", "scripts/knowledge-mcp-smoke.sh", "scripts/import-diagram-smoke.sh", - "scripts/import-multi-smoke.sh", "scripts/desk-hw-smoke.sh", "scripts/knowledge-top-parts.py", "docs/LEGAL.md", diff --git a/scripts/ship-gate.sh b/scripts/ship-gate.sh index b7a3c03..54c5d7c 100755 --- a/scripts/ship-gate.sh +++ b/scripts/ship-gate.sh @@ -265,9 +265,11 @@ else bad "golden-path pack" fi fi -if run_stage "develop-default" "$OUT/develop-default.txt" grep -qi \ - 'develop first\|load \`develop\` first\|START with skill develop' \ - "$ROOT/config/AGENTS.md" "$ROOT/config/opencode.hosted.json"; then +if run_stage "develop-default" "$OUT/develop-default.txt" bash -c ' + for f in "$@"; do + grep -qi '"'"'develop first\|load \`develop\` first\|START with skill develop'"'"' "$f" || exit 1 + done +' _ "$ROOT/config/AGENTS.md" "$ROOT/config/opencode.hosted.json"; then pass "AGENTS/opencode default develop first" else if [[ "$stage_timed_out" -eq 0 ]]; then diff --git a/skills/bringup/SKILL.md b/skills/bringup/SKILL.md index 59ae714..ddca977 100644 --- a/skills/bringup/SKILL.md +++ b/skills/bringup/SKILL.md @@ -5,7 +5,6 @@ description: >- (list/describe/part, then datasheet tool for grounded text). Never invent pinouts or I2C addresses. license: MIT -compatibility: opencode metadata: gate: "workflow" labwired: "true" diff --git a/skills/desk-hw/SKILL.md b/skills/desk-hw/SKILL.md index 59812de..c61cb7e 100644 --- a/skills/desk-hw/SKILL.md +++ b/skills/desk-hw/SKILL.md @@ -4,7 +4,6 @@ description: >- Plan and execute confirmed physical hardware runs with explicit identities, safe wiring, exact flash, and behavior-specific evidence. Prefer twin prove first. license: MIT -compatibility: opencode metadata: gate: "workflow" labwired: "true" diff --git a/skills/develop/SKILL.md b/skills/develop/SKILL.md index e635086..8e5b186 100644 --- a/skills/develop/SKILL.md +++ b/skills/develop/SKILL.md @@ -3,7 +3,6 @@ name: develop description: >- Default firmware workflow for greenfield and existing projects: inspect, ground hardware facts, edit, compile, check on the twin, repair, and report. license: MIT -compatibility: opencode metadata: gate: "workflow" labwired: "true" diff --git a/skills/golden-path/SKILL.md b/skills/golden-path/SKILL.md index 4329222..bab7846 100644 --- a/skills/golden-path/SKILL.md +++ b/skills/golden-path/SKILL.md @@ -4,7 +4,6 @@ description: >- First-session guide that delegates firmware development to develop, with optional circuit import, requested plots, and physical-board evidence. license: MIT -compatibility: opencode metadata: gate: "1" labwired: "true" diff --git a/skills/import-circuit/SKILL.md b/skills/import-circuit/SKILL.md index 6600d83..2a4c585 100644 --- a/skills/import-circuit/SKILL.md +++ b/skills/import-circuit/SKILL.md @@ -4,7 +4,6 @@ description: >- Ingest customer circuit sources (PDF schematic, KiCad, netlist, BOM, image, diagram.json) into a catalog-honest LabWired twin diagram. Never invent pins. license: MIT -compatibility: opencode metadata: gate: "workflow" labwired: "true" diff --git a/skills/observe/SKILL.md b/skills/observe/SKILL.md index aa7a5f5..07fadda 100644 --- a/skills/observe/SKILL.md +++ b/skills/observe/SKILL.md @@ -5,7 +5,6 @@ description: >- One job: ask → recipe → compose → present. Never invent series; never claim twin or desk green from a plot. license: MIT -compatibility: opencode metadata: gate: "1" labwired: "true" diff --git a/skills/prove/SKILL.md b/skills/prove/SKILL.md index af28571..d09a4bd 100644 --- a/skills/prove/SKILL.md +++ b/skills/prove/SKILL.md @@ -5,7 +5,6 @@ description: >- evidence report. model_verified ONLY from labwired_verify. On red fail-first then repair; never soft-pass or invent green. license: MIT -compatibility: opencode metadata: gate: "1" labwired: "true" diff --git a/skills/using-superpowers/SKILL.md b/skills/using-superpowers/SKILL.md index 505a93d..31ffadb 100644 --- a/skills/using-superpowers/SKILL.md +++ b/skills/using-superpowers/SKILL.md @@ -5,7 +5,6 @@ description: >- process skills (TDD, plans, debug method) AND LabWired domain packs. LabWired claim rules and MCP tools always win over generic process advice. license: MIT -compatibility: opencode metadata: gate: "process" labwired: "true" diff --git a/tests/all.sh b/tests/all.sh index 6542940..62073fd 100755 --- a/tests/all.sh +++ b/tests/all.sh @@ -72,6 +72,7 @@ run "harness" "$ROOT/tests/harness.sh" run "run-bounded" "$ROOT/tests/run-bounded.sh" run "skills-inventory" "$ROOT/tests/skills-inventory.sh" run "skills-verify-all" "$ROOT/tests/skills-verify-all.sh" +run "harness-portable" "$ROOT/tests/harness-portable.sh" run "develop-skill" "$ROOT/tests/develop-skill.sh" run_hermetic "develop-acceptance-smoke" bash "$ROOT/tests/develop-acceptance-smoke.sh" run "hosted-config" "$ROOT/tests/hosted-config.sh" diff --git a/tests/harness-portable.sh b/tests/harness-portable.sh new file mode 100755 index 0000000..d846645 --- /dev/null +++ b/tests/harness-portable.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Foreign-install simulation: the universal pack must be self-contained when +# copied into a bare directory with no opencode config (what `npx skills add` +# effectively does for a foreign agent host). +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +fail=0 +pass() { echo "ok $*"; } +bad() { echo "FAIL $*"; fail=1; } + +# shellcheck source=lib/pack-classification.sh +source "$ROOT/tests/lib/pack-classification.sh" + +TMP="$(mktemp -d "${TMPDIR:-/tmp}/labwired-harness-portable.XXXXXX")" +trap 'rm -rf "$TMP"' EXIT + +for s in "${UNIVERSAL_PACKS[@]}"; do + cp -R "$ROOT/skills/$s" "$TMP/$s" +done + +# 1. opencode-only packs must not be in the universal set (disjoint lists). +for s in "${OPENCODE_ONLY_PACKS[@]}"; do + if [[ -d "$TMP/$s" ]]; then + bad "opencode-only pack leaked into universal set: $s" + else + pass "opencode-only excluded: $s" + fi +done + +# 2. Every copied SKILL.md has a frontmatter block with name + description. +for s in "${UNIVERSAL_PACKS[@]}"; do + f="$TMP/$s/SKILL.md" + [[ -f "$f" ]] || { bad "portable $s missing SKILL.md"; continue; } + frontmatter="$(awk '/^---$/{c++; next} c==1' "$f")" + if grep -q '^name:' <<<"$frontmatter" && grep -q '^description:' <<<"$frontmatter"; then + pass "portable $s frontmatter name+description" + else + bad "portable $s frontmatter missing name/description" + fi +done + +# 3. No symlink escapes the copied tree (a foreign host receives plain files). +escapes="$(find "$TMP" -type l | while read -r l; do + target="$(readlink "$l")" + case "$target" in (/*|*..*) echo "$l" ;; esac +done)" +if [[ -z "$escapes" ]]; then + pass "no escaping symlinks" +else + bad "escaping symlinks: $escapes" +fi + +# 4. Universal + opencode-only lists together equal the shipped set. +shipped="$(find "$ROOT/skills" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort)" +classified="$(printf '%s\n' "${UNIVERSAL_PACKS[@]}" "${OPENCODE_ONLY_PACKS[@]}" | sort)" +if [[ "$shipped" == "$classified" ]]; then + pass "classification partitions shipped set" +else + bad "classification mismatch: $(diff <(echo "$shipped") <(echo "$classified") | head -10)" +fi + +if [[ "$fail" -ne 0 ]]; then + echo "harness-portable FAILED" + exit 1 +fi +echo "ok harness-portable PASS" +exit 0 diff --git a/tests/lib/pack-classification.sh b/tests/lib/pack-classification.sh new file mode 100644 index 0000000..aa70e8d --- /dev/null +++ b/tests/lib/pack-classification.sh @@ -0,0 +1,16 @@ +# shellcheck shell=bash +# Canonical pack classification for the labwired harness. +# Sourced by tests/skills-inventory.sh and tests/harness-portable.sh. +# UNIVERSAL_PACKS: SKILL.md-standard packs installable into any agent host. +# OPENCODE_ONLY_PACKS: packs about the opencode config surface; never +# installed by a universal/foreign path. +UNIVERSAL_PACKS=( + golden-path develop bringup prove observe desk-hw import-circuit + using-superpowers brainstorming test-driven-development systematic-debugging + verification-before-completion writing-plans executing-plans writing-skills + dispatching-parallel-agents subagent-driven-development requesting-code-review + receiving-code-review finishing-a-development-branch using-git-worktrees +) +OPENCODE_ONLY_PACKS=( + customize-labwired-agent +) diff --git a/tests/skills-inventory.sh b/tests/skills-inventory.sh index 25cb261..14dc6db 100755 --- a/tests/skills-inventory.sh +++ b/tests/skills-inventory.sh @@ -4,6 +4,39 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" fail=0 +# shellcheck source=lib/pack-classification.sh +source "$ROOT/tests/lib/pack-classification.sh" + +# Pack classification must partition the shipped skills exactly. +for s in "${UNIVERSAL_PACKS[@]}" "${OPENCODE_ONLY_PACKS[@]}"; do + if [[ -f "$ROOT/skills/$s/SKILL.md" ]]; then + echo "ok classified $s" + else + echo "FAIL classified pack missing: $s" + fail=1 + fi +done +for d in "$ROOT"/skills/*/; do + s="$(basename "$d")" + found=0 + for p in "${UNIVERSAL_PACKS[@]}"; do [[ "$p" == "$s" ]] && found=1 && break; done + for p in "${OPENCODE_ONLY_PACKS[@]}"; do [[ "$p" == "$s" ]] && found=2 && break; done + case "$found" in + 1) echo "ok universal $s" ;; + 2) echo "ok opencode-only $s" ;; + 0) echo "FAIL unclassified skill dir: $s"; fail=1 ;; + esac +done + +for p in "${UNIVERSAL_PACKS[@]}"; do + for q in "${OPENCODE_ONLY_PACKS[@]}"; do + if [[ "$p" == "$q" ]]; then + echo "FAIL pack in both lists: $p" + fail=1 + fi + done +done + need_skill() { local s="$1" if [[ -f "$ROOT/skills/$s/SKILL.md" ]]; then diff --git a/tests/skills-verify-all.sh b/tests/skills-verify-all.sh index 9064b87..901a2f1 100755 --- a/tests/skills-verify-all.sh +++ b/tests/skills-verify-all.sh @@ -74,12 +74,33 @@ deepinfra_base="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]) [[ "$deepinfra_base" == "https://api.deepinfra.com/v1" ]] \ && pass "DeepInfra OpenCode base URL" || bad "DeepInfra OpenCode base URL: $deepinfra_base" +# Universality gate: universal packs must stay host-neutral (SKILL.md standard). +# shellcheck source=lib/pack-classification.sh +source "$ROOT/tests/lib/pack-classification.sh" + +for s in "${UNIVERSAL_PACKS[@]}"; do + f="$ROOT/skills/$s/SKILL.md" + [[ -f "$f" ]] || { bad "universal $s missing SKILL.md"; continue; } + frontmatter="$(awk '/^---$/{c++; next} c==1' "$f")" + if grep -qiE '^[[:space:]]*compatibility[[:space:]]*:' <<<"$frontmatter"; then + bad "universal $s has host-locked frontmatter (compatibility:)" + else + pass "universal $s frontmatter standard" + fi + if grep -rniE 'permission\.skill|default_agent|tui\.json|OPENCODE_CONFIG_DIR|\.config/labwired-agent' \ + "$ROOT/skills/$s/" >/dev/null 2>&1; then + bad "universal $s references opencode-only mechanisms" + else + pass "universal $s host-neutral body" + fi +done + n=$(find "$ROOT/skills" -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ') # 7 domain packs + customize-labwired-agent + 14 superpowers = 22 # golden-path is the entry router; develop is the firmware workflow it delegates # to. Both ship — see config/AGENTS.md "Default loop". if [[ "$n" -eq 22 ]]; then - pass "skill dir count $n (6 packs + customize + 14 superpowers)" + pass "skill dir count $n (7 packs + customize + 14 superpowers)" else bad "skill dir count $n expected 22" fi