@hybridlabor-api/aos 4.16.0 → 4.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +20 -0
- package/.claude/hooks/aos-bus.mjs +8 -4
- package/.claude/hooks/env-file-protection.mjs +19 -1
- package/.claude/hooks/go-gate.mjs +788 -69
- package/.claude/hooks/go-grant.mjs +88 -0
- package/.claude/hooks/go-token.mjs +17 -2
- package/.claude/hooks/memb-inject.mjs +61 -37
- package/.claude/settings.json +8 -0
- package/.codex-plugin/plugin.json +36 -6
- package/.opencode/commands/bdb-aos-brainstorm.md +5 -0
- package/.opencode/commands/bdb-aos-doctor.md +5 -0
- package/.opencode/commands/bdb-aos-graph.md +5 -0
- package/.opencode/commands/bdb-aos-init.md +5 -0
- package/.opencode/commands/bdb-aos-loop.md +5 -0
- package/.opencode/commands/bdb-aos-mastersession.md +5 -0
- package/.opencode/commands/bdb-aos-memb.md +5 -0
- package/.opencode/commands/bdb-aos-orchestrator.md +5 -0
- package/.opencode/commands/bdb-aos-plan.md +5 -0
- package/.opencode/commands/bdb-aos-playbooks.md +5 -0
- package/.opencode/commands/bdb-aos-setup.md +5 -0
- package/.opencode/commands/bdb-aos-shipping.md +5 -0
- package/.opencode/commands/bdb-aos-startproject.md +5 -0
- package/.opencode/commands/bdb-aos-store.md +5 -0
- package/.opencode/plugins/bdb-aos.js +54 -10
- package/README.md +5 -4
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/agy-commands/loop.md +5 -0
- package/bin/aos-acp.mjs +27 -1
- package/bin/aos-doctor.mjs +41 -4
- package/bin/aos-uninstall.mjs +41 -3
- package/bin/go-check.mjs +79 -0
- package/bin/guarded-patterns.json +106 -0
- package/commands/brainstorm.md +5 -0
- package/commands/doctor.md +5 -0
- package/commands/graph.md +5 -0
- package/commands/init.md +5 -0
- package/commands/loop.md +5 -0
- package/commands/mastersession.md +5 -0
- package/commands/memb.md +5 -0
- package/commands/orchestrator.md +5 -0
- package/commands/plan.md +5 -0
- package/commands/playbooks.md +5 -0
- package/commands/setup.md +5 -0
- package/commands/shipping.md +5 -0
- package/commands/startproject.md +5 -0
- package/commands/store.md +5 -0
- package/docs/codenotch.md +44 -0
- package/docs/codex-agy-setup.md +16 -0
- package/docs/codex-gate-smoke.md +43 -0
- package/docs/delegation-routing.md +32 -0
- package/docs/go-check.md +60 -0
- package/docs/master-session-acp.md +2 -0
- package/docs/opencode-setup.md +54 -0
- package/docs/plugin-migration.md +61 -0
- package/installer.js +351 -121
- package/lib/codenotch.js +389 -0
- package/lib/plugin-migration.js +462 -0
- package/lib/retired-skills.js +101 -0
- package/lib/store-ui/index.html +9 -1
- package/lib/store-ui/server.mjs +2 -0
- package/mcps/mcsc/README.md +1 -1
- package/mcps/mcsc/packages/core/src/adapters/agy.js +3 -1
- package/mcps/mcsc/packages/core/src/adapters/codex.js +2 -1
- package/mcps/mcsc/packages/core/src/adapters/opencode.js +2 -1
- package/mcps/mcsc/packages/core/src/depth.js +16 -0
- package/mcps/mcsc/packages/mcp/server.js +15 -2
- package/package.json +7 -2
- package/plugin-commands.json +143 -0
- package/plugin.json +299 -0
- package/plugins/bdb-aos-codex/.codex-plugin/plugin.json +38 -0
- package/plugins/bdb-aos-codex/skills/brainstorm/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/doctor/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/graph/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/init/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/loop/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/mastersession/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/memb/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/orchestrator/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/plan/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/playbooks/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/setup/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/shipping/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/startproject/SKILL.md +6 -0
- package/plugins/bdb-aos-codex/skills/store/SKILL.md +6 -0
- package/scripts/build-plugin-manifest.mjs +202 -3
- package/scripts/codex-gate-smoke.mjs +73 -0
- package/skills/basic/master-session/SKILL.md +11 -0
- package/skills/bdb-aos/scripts/list-playbooks.mjs +72 -0
- package/skills/global_config/agenttrail/SKILL.md +3 -1
- package/skills/global_config/agenttrail/bin/agenttrail.mjs +255 -117
- package/skills/global_config/agenttrail/bin/ensure.mjs +60 -40
- package/skills/global_config/agenttrail/bin/repoid.mjs +70 -0
- package/skills/global_config/agenttrail/public/index.html +9 -1
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/bdb-memb-mcp/SKILL.md +5 -4
- package/skills/global_config/bdb-visual-edit/SKILL.md +28 -32
- package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +2 -2
- package/skills/global_config/bdb-visual-edit/scripts/locate-source.mjs +135 -0
- package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +30 -2
- package/skills/global_config/gogate/SKILL.md +123 -0
- package/skills/global_config/loop-templates/SKILL.md +27 -0
- package/skills/global_config/loop-templates/references/ci-until-green.md +27 -0
- package/skills/global_config/loop-templates/references/daily-summary.md +27 -0
- package/skills/global_config/loop-templates/references/pr-to-merge.md +27 -0
- package/skills/global_config/loop-templates/references/review-rounds.md +27 -0
- package/skills/global_config/mcsc/SKILL.md +9 -1
- package/skills/global_config/plan-arbiter/SKILL.md +1 -1
- package/skills/global_config/plan-canvas/SKILL.md +42 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +1 -1
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +2 -2
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/geometry.js +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/index.js +596 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/model.js +192 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/toolbar.js +99 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-server.js +282 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotation-schema.js +210 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/route.js +10 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sdk.js +6 -230
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +45 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sessions.js +60 -21
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/trail-on-approve.js +103 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +19 -7
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +118 -20
- package/skills/global_config/subagent-setup/SKILL.md +6 -0
- package/skills/global_config/subagent-setup/scripts/setup-subagents.mjs +20 -1
- package/skills/playbooks/pb-idea-to-launch/SKILL.md +2 -2
- package/skills/playbooks/pb-redesign-app/SKILL.md +3 -3
- package/skills/playbooks/pb-release-aos/SKILL.md +2 -2
- package/skills/playbooks/pb-ship/SKILL.md +2 -2
- package/skills/playbooks/pb-worktrees-land/SKILL.md +2 -2
- package/.codex-plugin/marketplace.json +0 -11
- package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +0 -27
- package/skills/global_config/visual-edit/README.md +0 -96
- package/skills/global_config/visual-edit/SKILL.md +0 -615
- package/skills/global_config/visual-plan/README.md +0 -93
- package/skills/global_config/visual-plan/SKILL.md +0 -544
- package/skills/global_config/visual-plan/references/canvas.md +0 -139
- package/skills/global_config/visual-plan/references/connection.md +0 -51
- package/skills/global_config/visual-plan/references/document-quality.md +0 -186
- package/skills/global_config/visual-plan/references/exemplar.md +0 -62
- package/skills/global_config/visual-plan/references/local-files.md +0 -99
- package/skills/global_config/visual-plan/references/wireframe.md +0 -319
- package/skills/global_config/visual-recap/README.md +0 -103
- package/skills/global_config/visual-recap/SKILL.md +0 -560
- package/skills/global_config/visual-recap/references/connection.md +0 -51
- package/skills/global_config/visual-recap/references/local-files.md +0 -99
- package/skills/global_config/visual-recap/references/wireframe.md +0 -319
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graph
|
|
3
|
+
description: "Run the startcycle-graph pipeline: durable state, repair loop, quality gate, escalation. Generated from plugin-commands.json; invoke as $bdb-aos:graph."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run the `startcycle-graph` skill; its contract defines the flow. Task: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: init
|
|
3
|
+
description: "Set up the current project folder for AOS: slug, wiki, memory, Synapse map, AGENTS.md. Generated from plugin-commands.json; invoke as $bdb-aos:init."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run the `aos-project-init` skill for the current project folder. This is project setup, not machine setup (that is `$bdb-aos:setup`). Arguments: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: loop
|
|
3
|
+
description: "Run a prompt repeatedly on an interval, using the harness built-in plus loop templates. Generated from plugin-commands.json; invoke as $bdb-aos:loop."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Codex has no /loop. Use `/goal` with the filled `loop-templates` template as the goal, keeping its limits and stop condition. Codex /goal is unverified in AOS docs; confirm with `codex --help`. Safety: a loop prompt never issues a GO, never types or runs `gogate`, never sets a mode or grant, and never merges, pushes or publishes unless a valid grant covers the action. Act on a guarded step only if the human's immediately preceding message is a literal GO or a valid grant covers it; a loop iteration never satisfies the first condition, so it must STOP and report instead of continuing a blocked guarded step. Templates (CI until green, follow a PR to merge, review rounds until no blockers, daily session summary) live in the `loop-templates` skill with run and duration limits and a stop condition each. Arguments: whatever the user wrote after the command name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mastersession
|
|
3
|
+
description: "Supervise other Claude Code sessions on this machine: roster, status requests, GO board, idle notices. Generated from plugin-commands.json; invoke as $bdb-aos:mastersession."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Act as the master session with the `master-session` skill (ACP bridge: `bin/aos-acp.mjs`, see docs/master-session-acp.md). Keep the overview, relay the human's decisions, never decide for the human. Arguments: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memb
|
|
3
|
+
description: "Query or store long-term memory with memB. Generated from plugin-commands.json; invoke as $bdb-aos:memb."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use `memb-skill` to search or remember and `memb-ingest` to import material. Never store secrets, tokens or credentials. Arguments: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orchestrator
|
|
3
|
+
description: "Coordinate parallel coding agents across repositories through the AO daemon. Generated from plugin-commands.json; invoke as $bdb-aos:orchestrator."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use the `ao-orchestrator` skill. It needs the AO daemon; check that it is running first and stop with a clear message if not. Do not confuse it with `agent-orchestrator`. Arguments: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plan
|
|
3
|
+
description: "Plan end to end: draft, render in plan-canvas or Plan Builder, annotate, await approval, then hand off. Generated from plugin-commands.json; invoke as $bdb-aos:plan."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run the full planning pipeline: 1) draft the plan with `concise-planning`; 2) always offer the choice between plan-canvas and Plan Builder (`aos-plan-canvas open <dir> --mode bdb-plan-builder`) for rendering; 3) collect annotations and wait for the human's approval; 4) only after approval continue with `writing-plans` or `$bdb-aos:graph`. Topic: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playbooks
|
|
3
|
+
description: "List all pb-* playbooks with duration, difficulty and requirements, and start one. Generated from plugin-commands.json; invoke as $bdb-aos:playbooks."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run `node <bdb-aos skill dir>/scripts/list-playbooks.mjs` and show its output (name, est_time, difficulty, requires of every `pb-*` skill). If the human names a playbook in the arguments or picks one, start that `pb-*` skill; start nothing otherwise. Each playbook keeps its own go-gate points. Arguments: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: setup
|
|
3
|
+
description: "Bring this machine to a complete, verified AOS installation (skills, MCP servers, gate hooks, memB, OpenWiki, Synapse). Generated from plugin-commands.json; invoke as $bdb-aos:setup."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Run the `aos-setup` skill for this machine. Report what is installed, what was repaired and what is still missing. Arguments: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: shipping
|
|
3
|
+
description: "Pre-ship framing and the technical release gate, with approvals under the go-gate rules. Generated from plugin-commands.json; invoke as $bdb-aos:shipping."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Use `bdb-shipping-skill` for the pre-flight and `godmode-shipping` for the gate. The playbook `pb-ship` can drive the whole flow. Push, merge and publish need the human's GO under the go-gate rules; never request one on the human's behalf. Arguments: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: startproject
|
|
3
|
+
description: "Start a new composition: project init, brainstorm, then ask whether graph or startcycle follows. Generated from plugin-commands.json; invoke as $bdb-aos:startproject."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Sequence: 1) `aos-project-init`; 2) `bdbrainstorm`; 3) stop and ask the human whether `$bdb-aos:graph` (startcycle-graph) or `startcycle` follows. Do not start a pipeline on your own. Idea: whatever the user wrote after the skill name
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: store
|
|
3
|
+
description: "Browse and preview AOS skills, agents and playbooks in the local store UI. Generated from plugin-commands.json; invoke as $bdb-aos:store."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Start the `aos-store` interface. Preview freely, but install nothing until the human says yes for that specific item. Arguments: whatever the user wrote after the skill name
|
|
@@ -21,6 +21,19 @@
|
|
|
21
21
|
// personaFile paths and regenerated by installer.js, so they cannot move). Both
|
|
22
22
|
// facts mean the plugin needs a generated layout, which is what this script is.
|
|
23
23
|
//
|
|
24
|
+
// Slash commands (/bdb-aos:<name>) come from plugin-commands.json: one commands/<name>.md
|
|
25
|
+
// per entry plus a "commands" array in both manifests. OpenCode gets flat hyphen names
|
|
26
|
+
// (.opencode/commands/bdb-aos-<name>.md, /bdb-aos-<name>) because a colon is invalid in
|
|
27
|
+
// Windows file names; bodies.opencode is optional and falls back to the claude body with
|
|
28
|
+
// /bdb-aos:<cmd> rewritten. AOS_PLUGIN_COMMANDS overrides the source path (tests only).
|
|
29
|
+
//
|
|
30
|
+
// Codex has no verified commands/ handling, so its entry points are generated wrapper
|
|
31
|
+
// SKILLS (plugins/bdb-aos-codex/skills/<cmd>/SKILL.md, invoked as $bdb-aos:<cmd>) in a real
|
|
32
|
+
// directory, because `codex plugin add` drops symlinks from the install copy. The plugin's
|
|
33
|
+
// .codex-plugin/plugin.json and .agents/plugins/marketplace.json point there.
|
|
34
|
+
// agy reads a plugin.json at the plugin root (/bdb-aos:<cmd>); bodies.codex / bodies.agy are
|
|
35
|
+
// optional, agy overrides land in agy-commands/ and are only wired when present.
|
|
36
|
+
//
|
|
24
37
|
// Usage: node scripts/build-plugin-manifest.mjs [--check]
|
|
25
38
|
|
|
26
39
|
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
@@ -81,6 +94,58 @@ if (!CHECK) {
|
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
|
|
97
|
+
const commandsSrc = process.env.AOS_PLUGIN_COMMANDS ?? join(ROOT, 'plugin-commands.json');
|
|
98
|
+
const commandDefs = JSON.parse(readFileSync(commandsSrc, 'utf8')).commands ?? {};
|
|
99
|
+
const skillNames = new Set(skillDirs.map((dir) => dir.split(/[\\/]/).pop()));
|
|
100
|
+
const commandErrors = [];
|
|
101
|
+
for (const [name, def] of Object.entries(commandDefs)) {
|
|
102
|
+
if (!/^[a-z][a-z0-9-]*$/.test(name) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/.test(name)) commandErrors.push(`${name}: invalid command name`);
|
|
103
|
+
if (skillNames.has(name)) commandErrors.push(`${name}: collides with a skill of the same name`);
|
|
104
|
+
if (!def.description?.trim()) commandErrors.push(`${name}: empty description`);
|
|
105
|
+
if (!def.bodies?.claude?.trim()) commandErrors.push(`${name}: no body for claude`);
|
|
106
|
+
for (const harness of ['opencode', 'codex', 'agy']) {
|
|
107
|
+
if (def.bodies?.[harness] !== undefined && !def.bodies[harness].trim()) commandErrors.push(`${name}: empty body for ${harness}`);
|
|
108
|
+
}
|
|
109
|
+
for (const ref of def.skills ?? []) {
|
|
110
|
+
const found = ref.endsWith('*')
|
|
111
|
+
? [...skillNames].some((n) => n.startsWith(ref.slice(0, -1)))
|
|
112
|
+
: skillNames.has(ref);
|
|
113
|
+
if (!found && def.pending === undefined) commandErrors.push(`${name}: unknown skill ${ref}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (commandErrors.length > 0) {
|
|
117
|
+
console.error(`plugin-commands.json invalid:\n ${commandErrors.join('\n ')}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
const commandNames = Object.keys(commandDefs).sort();
|
|
121
|
+
const commandFiles = Object.fromEntries(commandNames.map((name) => [
|
|
122
|
+
`${name}.md`,
|
|
123
|
+
`---\ndescription: ${JSON.stringify(commandDefs[name].description)}\n---\n\n${commandDefs[name].bodies.claude.trim()}\n`,
|
|
124
|
+
]));
|
|
125
|
+
const commands = commandNames.map((name) => `./commands/${name}.md`);
|
|
126
|
+
const opencodeBody = (def) =>
|
|
127
|
+
(def.bodies.opencode ?? def.bodies.claude.replace(/\/bdb-aos:([a-z][a-z0-9-]*)/g, '/bdb-aos-$1')).trim();
|
|
128
|
+
const opencodeFiles = Object.fromEntries(commandNames.map((name) => [
|
|
129
|
+
`bdb-aos-${name}.md`,
|
|
130
|
+
`---\ndescription: ${JSON.stringify(commandDefs[name].description)}\n---\n\n${opencodeBody(commandDefs[name])}\n`,
|
|
131
|
+
]));
|
|
132
|
+
|
|
133
|
+
// Codex has no per-user argument placeholder, so $ARGUMENTS becomes prose.
|
|
134
|
+
const codexBody = (def) =>
|
|
135
|
+
(def.bodies.codex ?? def.bodies.claude
|
|
136
|
+
.replace(/\/bdb-aos:([a-z][a-z0-9-]*)/g, '$$bdb-aos:$1')
|
|
137
|
+
.replace(/\$ARGUMENTS/g, 'whatever the user wrote after the skill name')).trim();
|
|
138
|
+
const codexFiles = Object.fromEntries(commandNames.map((name) => [
|
|
139
|
+
`${name}/SKILL.md`,
|
|
140
|
+
`---\nname: ${name}\ndescription: ${JSON.stringify(`${commandDefs[name].description} Generated from plugin-commands.json; invoke as $bdb-aos:${name}.`)}\n---\n\n${codexBody(commandDefs[name])}\n`,
|
|
141
|
+
]));
|
|
142
|
+
const agyOverrides = commandNames.filter((name) => commandDefs[name].bodies.agy !== undefined);
|
|
143
|
+
const agyFiles = Object.fromEntries(agyOverrides.map((name) => [
|
|
144
|
+
`${name}.md`,
|
|
145
|
+
`---\ndescription: ${JSON.stringify(commandDefs[name].description)}\n---\n\n${commandDefs[name].bodies.agy.trim()}\n`,
|
|
146
|
+
]));
|
|
147
|
+
const agyCommands = commandNames.map((name) => `./${agyOverrides.includes(name) ? 'agy-commands' : 'commands'}/${name}.md`);
|
|
148
|
+
|
|
84
149
|
const manifest = {
|
|
85
150
|
$schema: 'https://anthropic.com/claude-code/plugin.schema.json',
|
|
86
151
|
name: 'bdb-aos',
|
|
@@ -94,9 +159,96 @@ const manifest = {
|
|
|
94
159
|
license: 'Apache-2.0',
|
|
95
160
|
keywords: ['skills', 'agent-os', 'cicd', 'multiagent', 'startcycle', 'bdb'],
|
|
96
161
|
skills,
|
|
162
|
+
commands,
|
|
97
163
|
};
|
|
98
164
|
|
|
99
165
|
const serialized = `${JSON.stringify(manifest, null, 2)}\n`;
|
|
166
|
+
const json = (value) => `${JSON.stringify(value, null, 2)}\n`;
|
|
167
|
+
const codexManifest = {
|
|
168
|
+
name: manifest.name,
|
|
169
|
+
version: pkg.version,
|
|
170
|
+
description: 'Full BDB Agent OS suite for Codex: command entry points ($bdb-aos:setup, $bdb-aos:init, ...) generated from plugin-commands.json.',
|
|
171
|
+
author: manifest.author,
|
|
172
|
+
homepage: repoUrl,
|
|
173
|
+
repository: repoUrl,
|
|
174
|
+
license: manifest.license,
|
|
175
|
+
keywords: manifest.keywords,
|
|
176
|
+
skills: './skills/',
|
|
177
|
+
interface: {
|
|
178
|
+
displayName: 'BDB Agent OS',
|
|
179
|
+
shortDescription: 'AOS commands for setup, planning and pipelines',
|
|
180
|
+
longDescription: 'Entry-point skills for the BDB Agent OS suite: invoke $bdb-aos:<command> to run setup, project init, planning and the startcycle pipelines.',
|
|
181
|
+
developerName: 'Hybridlabor Global LLC',
|
|
182
|
+
category: 'Developer Tools',
|
|
183
|
+
capabilities: ['Interactive', 'Read', 'Write'],
|
|
184
|
+
websiteURL: repoUrl,
|
|
185
|
+
defaultPrompt: ['$bdb-aos:setup', '$bdb-aos:doctor'],
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const CODEX_ROOT = 'plugins/bdb-aos-codex';
|
|
189
|
+
const codexMarket = {
|
|
190
|
+
name: 'bdb-aos',
|
|
191
|
+
interface: { displayName: 'BDB Agent OS' },
|
|
192
|
+
plugins: [{
|
|
193
|
+
name: manifest.name,
|
|
194
|
+
source: { source: 'local', path: `./${CODEX_ROOT}` },
|
|
195
|
+
policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' },
|
|
196
|
+
category: 'Developer Tools',
|
|
197
|
+
}],
|
|
198
|
+
};
|
|
199
|
+
const { $schema: _schema, ...agyBase } = manifest;
|
|
200
|
+
const agyManifest = { ...agyBase, commands: agyCommands };
|
|
201
|
+
const generated = {
|
|
202
|
+
'.codex-plugin/plugin.json': json({ ...codexManifest, skills: `./${CODEX_ROOT}/skills/` }),
|
|
203
|
+
[`${CODEX_ROOT}/.codex-plugin/plugin.json`]: json(codexManifest),
|
|
204
|
+
'.agents/plugins/marketplace.json': json(codexMarket),
|
|
205
|
+
'plugin.json': json(agyManifest),
|
|
206
|
+
'plugins/bdb-aos/plugin.json': json(agyManifest),
|
|
207
|
+
...Object.fromEntries(Object.entries(codexFiles).map(([f, c]) => [`${CODEX_ROOT}/skills/${f}`, c])),
|
|
208
|
+
...Object.fromEntries(Object.entries(agyFiles).map(([f, c]) => [`agy-commands/${f}`, c])),
|
|
209
|
+
};
|
|
210
|
+
const ownedDirs = { [`${CODEX_ROOT}/skills`]: (e) => e.isDirectory(), 'agy-commands': (e) => e.isFile() };
|
|
211
|
+
const genDrift = Object.entries(generated)
|
|
212
|
+
.filter(([file, want]) => !existsSync(join(ROOT, file)) || readFileSync(join(ROOT, file), 'utf8') !== want)
|
|
213
|
+
.map(([file]) => file);
|
|
214
|
+
const filesUnder = (rel) => readdirSync(join(ROOT, rel), { withFileTypes: true })
|
|
215
|
+
.flatMap((e) => (e.isDirectory() ? filesUnder(`${rel}/${e.name}`) : [`${rel}/${e.name}`]));
|
|
216
|
+
const genStale = Object.entries(ownedDirs).flatMap(([dir, owned]) =>
|
|
217
|
+
existsSync(join(ROOT, dir))
|
|
218
|
+
? readdirSync(join(ROOT, dir), { withFileTypes: true }).filter(owned).flatMap((e) => {
|
|
219
|
+
const path = `${dir}/${e.name}`;
|
|
220
|
+
if (!Object.keys(generated).some((file) => file === path || file.startsWith(`${path}/`))) return [path];
|
|
221
|
+
return e.isDirectory() ? filesUnder(path).filter((f) => !(f in generated)) : [];
|
|
222
|
+
})
|
|
223
|
+
: []);
|
|
224
|
+
const legacyCodexMarket = join(ROOT, '.codex-plugin', 'marketplace.json');
|
|
225
|
+
|
|
226
|
+
// Release-gate validation: every generated manifest parses, versions track package.json,
|
|
227
|
+
// Codex outputs never carry the field its validator rejects, referenced files exist.
|
|
228
|
+
function validateGenerated() {
|
|
229
|
+
const problems = [];
|
|
230
|
+
for (const file of Object.keys(generated)) {
|
|
231
|
+
if (!file.endsWith('.json') || !existsSync(join(ROOT, file))) continue;
|
|
232
|
+
let doc;
|
|
233
|
+
try { doc = JSON.parse(readFileSync(join(ROOT, file), 'utf8')); } catch { problems.push(`${file}: does not parse`); continue; }
|
|
234
|
+
if (file.endsWith('plugin.json') && doc.version !== pkg.version) problems.push(`${file}: version ${doc.version} != package.json ${pkg.version}`);
|
|
235
|
+
const base = join(ROOT, dirname(file).replace(/\.codex-plugin$/, ''));
|
|
236
|
+
if (file.endsWith('plugin.json') && file !== '.agents/plugins/marketplace.json') {
|
|
237
|
+
for (const ref of [...(doc.commands ?? []), ...(Array.isArray(doc.skills) ? doc.skills : [])]) {
|
|
238
|
+
const target = join(base, ref, ref.endsWith('.md') ? '' : 'SKILL.md');
|
|
239
|
+
if (!existsSync(target)) problems.push(`${file}: missing ${ref}`);
|
|
240
|
+
}
|
|
241
|
+
if (typeof doc.skills === 'string' && !existsSync(join(base, doc.skills))) problems.push(`${file}: missing ${doc.skills}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const file of Object.keys(generated)) {
|
|
245
|
+
if (/^(plugins\/bdb-aos-codex\/|\.codex-plugin\/|\.agents\/plugins\/)/.test(file) &&
|
|
246
|
+
existsSync(join(ROOT, file)) && readFileSync(join(ROOT, file), 'utf8').includes('disable-model-invocation')) {
|
|
247
|
+
problems.push(`${file}: contains disable-model-invocation (rejected by Codex)`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return problems;
|
|
251
|
+
}
|
|
100
252
|
const SUBDIR = join(ROOT, 'plugins', 'bdb-aos');
|
|
101
253
|
// Both manifests come from the same data; the subfolder one resolves the same
|
|
102
254
|
// ./skills/... paths through its symlinks.
|
|
@@ -105,7 +257,12 @@ const targets = [
|
|
|
105
257
|
join(SUBDIR, '.claude-plugin', 'plugin.json'),
|
|
106
258
|
];
|
|
107
259
|
const stale = targets.filter((t) => !existsSync(t) || readFileSync(t, 'utf8') !== serialized);
|
|
108
|
-
const links = {
|
|
260
|
+
const links = {
|
|
261
|
+
skills: '../../skills',
|
|
262
|
+
agents: '../../agents',
|
|
263
|
+
commands: '../../commands',
|
|
264
|
+
...(agyOverrides.length > 0 ? { 'agy-commands': '../../agy-commands' } : {}),
|
|
265
|
+
};
|
|
109
266
|
const badLinks = Object.entries(links).filter(([name, to]) => {
|
|
110
267
|
const path = join(SUBDIR, name);
|
|
111
268
|
return !existsSync(path) || !lstatSync(path).isSymbolicLink() || readlinkSync(path) !== to;
|
|
@@ -120,6 +277,19 @@ const entry = market.plugins?.find((plugin) => plugin.name === manifest.name);
|
|
|
120
277
|
if (!entry) throw new Error(`marketplace.json has no entry named ${manifest.name}`);
|
|
121
278
|
entry.version = pkg.version;
|
|
122
279
|
const marketSerialized = `${JSON.stringify(market, null, 2)}\n`;
|
|
280
|
+
const commandsDst = join(ROOT, 'commands');
|
|
281
|
+
const commandDrift = Object.entries(commandFiles)
|
|
282
|
+
.filter(([file, want]) => !existsSync(join(commandsDst, file)) || readFileSync(join(commandsDst, file), 'utf8') !== want)
|
|
283
|
+
.map(([file]) => file);
|
|
284
|
+
const staleCommandFiles = existsSync(commandsDst)
|
|
285
|
+
? readdirSync(commandsDst).filter((file) => !(file in commandFiles))
|
|
286
|
+
: [];
|
|
287
|
+
const ocDst = join(ROOT, '.opencode', 'commands');
|
|
288
|
+
const ocOwned = (file) => /^bdb-aos-.+\.md$/.test(file);
|
|
289
|
+
const ocDrift = Object.entries(opencodeFiles)
|
|
290
|
+
.filter(([file, want]) => !existsSync(join(ocDst, file)) || readFileSync(join(ocDst, file), 'utf8') !== want)
|
|
291
|
+
.map(([file]) => file);
|
|
292
|
+
const ocStale = existsSync(ocDst) ? readdirSync(ocDst).filter((file) => ocOwned(file) && !(file in opencodeFiles)) : [];
|
|
123
293
|
const marketCurrent = readFileSync(marketPath, 'utf8');
|
|
124
294
|
|
|
125
295
|
if (CHECK) {
|
|
@@ -135,20 +305,49 @@ if (CHECK) {
|
|
|
135
305
|
console.error('marketplace.json version is out of date — run: node scripts/build-plugin-manifest.mjs');
|
|
136
306
|
process.exit(1);
|
|
137
307
|
}
|
|
308
|
+
if (commandDrift.length > 0 || staleCommandFiles.length > 0) {
|
|
309
|
+
console.error(`commands/ is out of date (${[...commandDrift, ...staleCommandFiles].join(', ')}) — run: node scripts/build-plugin-manifest.mjs`);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
if (ocDrift.length > 0 || ocStale.length > 0) {
|
|
313
|
+
console.error(`.opencode/commands/ is out of date (${[...ocDrift, ...ocStale].join(', ')}) — run: node scripts/build-plugin-manifest.mjs`);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
if (genDrift.length > 0 || genStale.length > 0 || existsSync(legacyCodexMarket)) {
|
|
317
|
+
console.error(`Codex/agy manifests are out of date (${[...genDrift, ...genStale, ...(existsSync(legacyCodexMarket) ? ['.codex-plugin/marketplace.json (legacy)'] : [])].join(', ')}) — run: node scripts/build-plugin-manifest.mjs`);
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
const problems = validateGenerated();
|
|
321
|
+
if (problems.length > 0) {
|
|
322
|
+
console.error(`generated manifests invalid:\n ${problems.join('\n ')}`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
138
325
|
if (drift.length > 0) {
|
|
139
326
|
console.error(`agents/ is out of date (${drift.join(', ')}) — run: node scripts/build-plugin-manifest.mjs`);
|
|
140
327
|
process.exit(1);
|
|
141
328
|
}
|
|
142
|
-
console.log(`plugin manifests up to date: ${skills.length} skills, ${agentNames.length} agents`);
|
|
329
|
+
console.log(`plugin manifests up to date: ${skills.length} skills, ${agentNames.length} agents, ${commandNames.length} commands (+ ${commandNames.length} OpenCode)`);
|
|
143
330
|
} else {
|
|
331
|
+
mkdirSync(commandsDst, { recursive: true });
|
|
332
|
+
for (const [file, content] of Object.entries(commandFiles)) writeFileSync(join(commandsDst, file), content);
|
|
333
|
+
for (const file of staleCommandFiles) rmSync(join(commandsDst, file));
|
|
334
|
+
mkdirSync(ocDst, { recursive: true });
|
|
335
|
+
for (const [file, content] of Object.entries(opencodeFiles)) writeFileSync(join(ocDst, file), content);
|
|
336
|
+
for (const file of ocStale) rmSync(join(ocDst, file));
|
|
144
337
|
for (const t of targets) {
|
|
145
338
|
mkdirSync(dirname(t), { recursive: true });
|
|
146
339
|
writeFileSync(t, serialized);
|
|
147
340
|
}
|
|
341
|
+
for (const [file, content] of Object.entries(generated)) {
|
|
342
|
+
mkdirSync(dirname(join(ROOT, file)), { recursive: true });
|
|
343
|
+
writeFileSync(join(ROOT, file), content);
|
|
344
|
+
}
|
|
345
|
+
for (const path of genStale) rmSync(join(ROOT, path), { recursive: true });
|
|
346
|
+
rmSync(legacyCodexMarket, { force: true });
|
|
148
347
|
for (const [name, to] of badLinks) {
|
|
149
348
|
rmSync(join(SUBDIR, name), { force: true });
|
|
150
349
|
symlinkSync(to, join(SUBDIR, name));
|
|
151
350
|
}
|
|
152
351
|
writeFileSync(marketPath, marketSerialized);
|
|
153
|
-
console.log(`Wrote 2 plugin.json files with ${skills.length} skills and ${
|
|
352
|
+
console.log(`Wrote 2 plugin.json files with ${skills.length} skills, ${agentNames.length} agents and ${commandNames.length} commands`);
|
|
154
353
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Plan and runner for the Codex hook smoke test. Default: print the plan, run nothing.
|
|
3
|
+
// `--run` calls Codex, which calls a paid API, so it also needs AOS_CODEX_SMOKE=1 and a login
|
|
4
|
+
// inside the throwaway CODEX_HOME (printed in the plan). Everything about firing under ACP
|
|
5
|
+
// is UNVERIFIED until this has been run; see docs/codex-gate-smoke.md.
|
|
6
|
+
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
+
|
|
15
|
+
const PROBE = `import { appendFileSync, readFileSync } from "node:fs";
|
|
16
|
+
let input = {};
|
|
17
|
+
try { input = JSON.parse(readFileSync(0, "utf8")); } catch {}
|
|
18
|
+
const cmd = input?.tool_input?.command ?? "";
|
|
19
|
+
appendFileSync(process.env.AOS_SMOKE_MARKER, JSON.stringify({ at: new Date().toISOString(), keys: Object.keys(input), cmd }) + "\\n");
|
|
20
|
+
if (String(cmd).includes("SMOKE_DENY")) { process.stderr.write("smoke: denied by probe hook\\n"); process.exit(2); }
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export function buildConfig(probePath) {
|
|
24
|
+
return [
|
|
25
|
+
"[features]",
|
|
26
|
+
"hooks = true",
|
|
27
|
+
"",
|
|
28
|
+
"[[hooks.PreToolUse]]",
|
|
29
|
+
'matcher = "^(Bash|run_command)$"',
|
|
30
|
+
"[[hooks.PreToolUse.hooks]]",
|
|
31
|
+
'type = "command"',
|
|
32
|
+
`command = ${JSON.stringify(`node "${probePath}"`)}`,
|
|
33
|
+
"timeout = 10",
|
|
34
|
+
"",
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function steps({ dir = "<tmpdir>", bypassTrust = false } = {}) {
|
|
39
|
+
const env = `CODEX_HOME=${dir}/codex-home AOS_SMOKE_MARKER=${dir}/marker.jsonl`;
|
|
40
|
+
const trust = bypassTrust ? " --dangerously-bypass-hook-trust" : "";
|
|
41
|
+
return [
|
|
42
|
+
{ id: "setup", text: `Throwaway CODEX_HOME ${dir}/codex-home with config.toml holding the [[hooks.PreToolUse]] stanza that runs ${dir}/probe-hook.mjs. Log in inside it (codex login, or OPENAI_API_KEY); never copy credentials from ~/.codex.` },
|
|
43
|
+
{ id: "exec-fires", cmd: `${env} codex exec${trust} --skip-git-repo-check -C ${dir} "Run the shell command: echo smoke-ok"`, expect: "marker.jsonl gains a line with cmd containing 'echo smoke-ok'. If not, hooks do not fire under codex exec (or the hook was not trusted)." },
|
|
44
|
+
{ id: "exec-denies", cmd: `${env} codex exec${trust} --skip-git-repo-check -C ${dir} "Run the shell command: echo SMOKE_DENY"`, expect: "marker line for SMOKE_DENY exists and the command did not run (the agent reports it was blocked)." },
|
|
45
|
+
{ id: "acp-fires", cmd: `${env} node ${join(root, "bin", "aos-acp.mjs")} codex --name smoke --cwd ${dir} --allow-default allow --timeout 180 --prompt "Run the shell command: echo smoke-acp"`, expect: "marker.jsonl gains a line with 'echo smoke-acp'. UNVERIFIED: if only the aos-acp permission request fires and the hook does not, aos-acp stays the only gate for Codex." },
|
|
46
|
+
{ id: "record", text: "Write the observed result of each step (and the Codex version) into docs/codex-gate-smoke.md, replacing UNVERIFIED with the date and the result. Hook input keys are in marker.jsonl ('keys')." },
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const invokedAs = (() => { try { return realpathSync(process.argv[1] || ""); } catch { return ""; } })();
|
|
51
|
+
if (invokedAs === fileURLToPath(import.meta.url)) {
|
|
52
|
+
const run = process.argv.includes("--run");
|
|
53
|
+
const bypassTrust = process.argv.includes("--bypass-trust");
|
|
54
|
+
if (!run) {
|
|
55
|
+
console.log("Codex hook smoke plan (nothing is executed; add --run with AOS_CODEX_SMOKE=1 to run it)\n");
|
|
56
|
+
for (const s of steps({ bypassTrust })) console.log(`[${s.id}] ${s.cmd ?? s.text}${s.expect ? `\n expect: ${s.expect}` : ""}`);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
if (process.env.AOS_CODEX_SMOKE !== "1") { console.error("refusing to run: this calls a paid API. Set AOS_CODEX_SMOKE=1."); process.exit(2); }
|
|
60
|
+
const dir = mkdtempSync(join(tmpdir(), "aos-codex-smoke-"));
|
|
61
|
+
writeFileSync(join(dir, "probe-hook.mjs"), PROBE);
|
|
62
|
+
const home = join(dir, "codex-home");
|
|
63
|
+
mkdirSync(home);
|
|
64
|
+
writeFileSync(join(home, "config.toml"), buildConfig(join(dir, "probe-hook.mjs")));
|
|
65
|
+
const marker = join(dir, "marker.jsonl");
|
|
66
|
+
for (const s of steps({ dir, bypassTrust }).filter((x) => x.cmd)) {
|
|
67
|
+
const before = existsSync(marker) ? readFileSync(marker, "utf8").split("\n").length : 0;
|
|
68
|
+
const r = spawnSync("sh", ["-c", s.cmd], { stdio: "inherit", timeout: 240000 });
|
|
69
|
+
const after = existsSync(marker) ? readFileSync(marker, "utf8").split("\n").length : 0;
|
|
70
|
+
console.log(`[${s.id}] exit ${r.status}; hook fired: ${after > before ? "YES" : "NO"}; expect: ${s.expect}`);
|
|
71
|
+
}
|
|
72
|
+
console.log(`marker and config kept in ${dir} (remove it yourself when done)`);
|
|
73
|
+
}
|
|
@@ -20,6 +20,17 @@ One session is the **master**: it keeps the overview, asks workers for status, a
|
|
|
20
20
|
- agy: no sanctioned ACP adapter (`antigravity-acp` breaches Google's Antigravity terms). Adopt agy sessions or delegate via the `mcsc` skill.
|
|
21
21
|
- If AO is installed, `ao-orchestrator` may run the same workers; it adds nothing the token protocol needs.
|
|
22
22
|
|
|
23
|
+
### Routing: which channel for which job
|
|
24
|
+
|
|
25
|
+
| Job | Channel |
|
|
26
|
+
|---|---|
|
|
27
|
+
| One-shot task for another harness | `mcsc` (one level deep, `delegate_agy` is read-only) |
|
|
28
|
+
| A worker that may need a GO | `aos-acp` |
|
|
29
|
+
| A message to a running OpenCode session | `aos-bus` |
|
|
30
|
+
| A durable fleet across repos | AO (optional; AOS must work without it) |
|
|
31
|
+
|
|
32
|
+
Details and limits (`opencode run --auto` auto-approves tool calls, a GO is never inherited): `docs/delegation-routing.md`.
|
|
33
|
+
|
|
23
34
|
### `aos-acp` in one paragraph
|
|
24
35
|
|
|
25
36
|
`bin/aos-acp.mjs` is a zero-dependency ACP client: it spawns the adapter (`npx -y @agentclientprotocol/codex-acp`, `opencode acp`, `npx -y @agentclientprotocol/claude-agent-acp`), runs `initialize` → `session/new` → `session/prompt`, streams the worker's text to stdout and logs every event to `~/.aos/acp/<name>.jsonl`. A `session/request_permission` for a guarded command (the go-gate list) is answered `allow_once` only with a valid GO token for `<name>`; with `--go-wait <sec>` the request is parked (log event `permission_pending`, show it as `GO needed`) until the token appears or the wait ends. Everything else follows `--allow-default deny|allow` (deny by default). `--model <id>` is sent as ACP `session/set_config_option` (`configId: "model"`, id is adapter-specific, e.g. `sonnet`, `provider/model`); `fable` ids are rejected (workers run opus, sonnet or haiku), default is the adapter default and is logged. ACP workers are not in `ListAgents`: the roster lists them from the logs.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const unquote = (s) => s.trim().replace(/^["']|["']$/g, '');
|
|
7
|
+
|
|
8
|
+
function frontmatter(text) {
|
|
9
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
10
|
+
const out = {};
|
|
11
|
+
let key = null;
|
|
12
|
+
for (const line of (m?.[1] ?? '').split(/\r?\n/)) {
|
|
13
|
+
const top = line.match(/^([A-Za-z_-]+):\s*(.*)$/);
|
|
14
|
+
if (top) {
|
|
15
|
+
key = top[1];
|
|
16
|
+
out[key] = /^(>-?|\|-?)$/.test(top[2]) ? '' : top[2];
|
|
17
|
+
} else if (key && line.trim()) out[key] = `${out[key]} ${line.trim()}`.trim();
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const flowList = (value, name) => {
|
|
23
|
+
const m = new RegExp(`(?:^|[\\s{,])${name}:\\s*\\[([^\\]]*)\\]`).exec(value ?? '');
|
|
24
|
+
return m ? m[1].split(',').map(unquote).filter(Boolean) : [];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function listPlaybooks(skillsRoot) {
|
|
28
|
+
const found = [];
|
|
29
|
+
const visit = (dir) => {
|
|
30
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
31
|
+
const full = join(dir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) visit(full);
|
|
33
|
+
else if (entry.name === 'SKILL.md' && /^pb-/.test(dirname(full).split(sep).pop())) {
|
|
34
|
+
const fm = frontmatter(readFileSync(full, 'utf8'));
|
|
35
|
+
const req = fm.requires ?? '';
|
|
36
|
+
found.push({
|
|
37
|
+
name: unquote(fm.name ?? ''),
|
|
38
|
+
description: unquote(fm.description ?? ''),
|
|
39
|
+
est_time: unquote(fm.est_time ?? ''),
|
|
40
|
+
difficulty: unquote(fm.difficulty ?? ''),
|
|
41
|
+
requires: {
|
|
42
|
+
skills: flowList(req, 'skills'), agents: flowList(req, 'agents'),
|
|
43
|
+
mcps: flowList(req, 'mcps'), store: flowList(req, 'store'),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
if (existsSync(skillsRoot)) visit(skillsRoot);
|
|
50
|
+
return found.sort((a, b) => a.name.localeCompare(b.name));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const requiresLabel = (r) => [...r.skills, ...r.agents.map((a) => `agent:${a}`), ...r.mcps.map((m) => `mcp:${m}`), ...r.store.map((s) => `store:${s}`)].join(', ');
|
|
54
|
+
|
|
55
|
+
function defaultRoot() {
|
|
56
|
+
const parts = dirname(fileURLToPath(import.meta.url)).split(sep);
|
|
57
|
+
const i = parts.lastIndexOf('skills');
|
|
58
|
+
return i < 0 ? resolve('skills') : parts.slice(0, i + 1).join(sep);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
62
|
+
const root = process.argv.slice(2).find((a) => !a.startsWith('--')) ?? defaultRoot();
|
|
63
|
+
const list = listPlaybooks(root);
|
|
64
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(list, null, 2));
|
|
65
|
+
else {
|
|
66
|
+
for (const p of list) {
|
|
67
|
+
console.log(`${p.name}\t${p.est_time}\t${p.difficulty}\trequires: ${requiresLabel(p.requires) || 'nothing'}`);
|
|
68
|
+
console.log(` ${p.description}`);
|
|
69
|
+
}
|
|
70
|
+
console.log(`\n${list.length} playbook(s).`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -33,7 +33,9 @@ It prints the URL (default http://localhost:5330, next free port if taken). Open
|
|
|
33
33
|
aos-trail --ensure [--cwd <dir>] [--plan <file>] [--session <id>] [--json]
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Starts the map detached if none runs for this repo (matched via `/whoami` `repoPath` on 127.0.0.1:5330-5344), opens it at most once per session (state in `$TMPDIR/aos-trail-ensure/`), and always exits 0. Plan: `--plan`, else `production_artifacts/00_execution_plan.md`, else the single `production_artifacts/*/00_execution_plan.md`; it needs a `{#id}` marker. No open under `CI`, SSH, or headless Linux; with `AO_BROWSER_CAPABILITY` it runs `ao preview <url>`. `--json` prints `{url,started,opened,reason,plan,hint}`. Test overrides: `AOS_TRAIL_OPENER` (opener command), `AOS_TRAIL_PORTS=lo-hi` (probe range).
|
|
36
|
+
Starts the map detached if none runs for this repo (matched via `/whoami` `repoPath` on 127.0.0.1:5330-5344). `repoPath` is the repo's main checkout, so every git worktree of a repo finds the same map; the map shows each worktree as its own lane (branch and folder) and files hook events by their `cwd`. `/whoami` also lists `worktrees`, opens it at most once per session (state in `$TMPDIR/aos-trail-ensure/`), and always exits 0. Plan: `--plan`, else `production_artifacts/00_execution_plan.md`, else the single `production_artifacts/*/00_execution_plan.md`; it needs a `{#id}` marker. No open under `CI`, SSH, or headless Linux; with `AO_BROWSER_CAPABILITY` it runs `ao preview <url>`. `--json` prints `{url,started,opened,reason,plan,hint}`. Test overrides: `AOS_TRAIL_OPENER` (opener command), `AOS_TRAIL_PORTS=lo-hi` (probe range).
|
|
37
|
+
|
|
38
|
+
Lanes: main first, then at most 11 worktrees; the slots go to the most recently active ones (a lane with events in the last 10 min keeps its slot), and events from any further worktree of the repo are filed in one `other worktrees` lane, never in main. `/whoami` carries `protocol: 2`. `--ensure` replaces a daemon on this repo (or one of its worktrees) that lacks it: the new map starts first, then the old one is asked to quit via `POST /shutdown` (JSON body naming its own `repoPath`); a daemon that cannot (older ones have no such API) keeps running and `hint` says which port to stop. Two concurrent `--ensure` runs share a lock file in `$TMPDIR/aos-trail-ensure/`. `/setup` on a map with several lanes needs `{"lane": "<key>"}` and writes into that checkout.
|
|
37
39
|
|
|
38
40
|
## Plan convention
|
|
39
41
|
|