@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.
Files changed (147) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/.claude/hooks/aos-bus.mjs +8 -4
  3. package/.claude/hooks/env-file-protection.mjs +19 -1
  4. package/.claude/hooks/go-gate.mjs +788 -69
  5. package/.claude/hooks/go-grant.mjs +88 -0
  6. package/.claude/hooks/go-token.mjs +17 -2
  7. package/.claude/hooks/memb-inject.mjs +61 -37
  8. package/.claude/settings.json +8 -0
  9. package/.codex-plugin/plugin.json +36 -6
  10. package/.opencode/commands/bdb-aos-brainstorm.md +5 -0
  11. package/.opencode/commands/bdb-aos-doctor.md +5 -0
  12. package/.opencode/commands/bdb-aos-graph.md +5 -0
  13. package/.opencode/commands/bdb-aos-init.md +5 -0
  14. package/.opencode/commands/bdb-aos-loop.md +5 -0
  15. package/.opencode/commands/bdb-aos-mastersession.md +5 -0
  16. package/.opencode/commands/bdb-aos-memb.md +5 -0
  17. package/.opencode/commands/bdb-aos-orchestrator.md +5 -0
  18. package/.opencode/commands/bdb-aos-plan.md +5 -0
  19. package/.opencode/commands/bdb-aos-playbooks.md +5 -0
  20. package/.opencode/commands/bdb-aos-setup.md +5 -0
  21. package/.opencode/commands/bdb-aos-shipping.md +5 -0
  22. package/.opencode/commands/bdb-aos-startproject.md +5 -0
  23. package/.opencode/commands/bdb-aos-store.md +5 -0
  24. package/.opencode/plugins/bdb-aos.js +54 -10
  25. package/README.md +5 -4
  26. package/THIRD_PARTY_NOTICES.md +2 -2
  27. package/agy-commands/loop.md +5 -0
  28. package/bin/aos-acp.mjs +27 -1
  29. package/bin/aos-doctor.mjs +41 -4
  30. package/bin/aos-uninstall.mjs +41 -3
  31. package/bin/go-check.mjs +79 -0
  32. package/bin/guarded-patterns.json +106 -0
  33. package/commands/brainstorm.md +5 -0
  34. package/commands/doctor.md +5 -0
  35. package/commands/graph.md +5 -0
  36. package/commands/init.md +5 -0
  37. package/commands/loop.md +5 -0
  38. package/commands/mastersession.md +5 -0
  39. package/commands/memb.md +5 -0
  40. package/commands/orchestrator.md +5 -0
  41. package/commands/plan.md +5 -0
  42. package/commands/playbooks.md +5 -0
  43. package/commands/setup.md +5 -0
  44. package/commands/shipping.md +5 -0
  45. package/commands/startproject.md +5 -0
  46. package/commands/store.md +5 -0
  47. package/docs/codenotch.md +44 -0
  48. package/docs/codex-agy-setup.md +16 -0
  49. package/docs/codex-gate-smoke.md +43 -0
  50. package/docs/delegation-routing.md +32 -0
  51. package/docs/go-check.md +60 -0
  52. package/docs/master-session-acp.md +2 -0
  53. package/docs/opencode-setup.md +54 -0
  54. package/docs/plugin-migration.md +61 -0
  55. package/installer.js +351 -121
  56. package/lib/codenotch.js +389 -0
  57. package/lib/plugin-migration.js +462 -0
  58. package/lib/retired-skills.js +101 -0
  59. package/lib/store-ui/index.html +9 -1
  60. package/lib/store-ui/server.mjs +2 -0
  61. package/mcps/mcsc/README.md +1 -1
  62. package/mcps/mcsc/packages/core/src/adapters/agy.js +3 -1
  63. package/mcps/mcsc/packages/core/src/adapters/codex.js +2 -1
  64. package/mcps/mcsc/packages/core/src/adapters/opencode.js +2 -1
  65. package/mcps/mcsc/packages/core/src/depth.js +16 -0
  66. package/mcps/mcsc/packages/mcp/server.js +15 -2
  67. package/package.json +7 -2
  68. package/plugin-commands.json +143 -0
  69. package/plugin.json +299 -0
  70. package/plugins/bdb-aos-codex/.codex-plugin/plugin.json +38 -0
  71. package/plugins/bdb-aos-codex/skills/brainstorm/SKILL.md +6 -0
  72. package/plugins/bdb-aos-codex/skills/doctor/SKILL.md +6 -0
  73. package/plugins/bdb-aos-codex/skills/graph/SKILL.md +6 -0
  74. package/plugins/bdb-aos-codex/skills/init/SKILL.md +6 -0
  75. package/plugins/bdb-aos-codex/skills/loop/SKILL.md +6 -0
  76. package/plugins/bdb-aos-codex/skills/mastersession/SKILL.md +6 -0
  77. package/plugins/bdb-aos-codex/skills/memb/SKILL.md +6 -0
  78. package/plugins/bdb-aos-codex/skills/orchestrator/SKILL.md +6 -0
  79. package/plugins/bdb-aos-codex/skills/plan/SKILL.md +6 -0
  80. package/plugins/bdb-aos-codex/skills/playbooks/SKILL.md +6 -0
  81. package/plugins/bdb-aos-codex/skills/setup/SKILL.md +6 -0
  82. package/plugins/bdb-aos-codex/skills/shipping/SKILL.md +6 -0
  83. package/plugins/bdb-aos-codex/skills/startproject/SKILL.md +6 -0
  84. package/plugins/bdb-aos-codex/skills/store/SKILL.md +6 -0
  85. package/scripts/build-plugin-manifest.mjs +202 -3
  86. package/scripts/codex-gate-smoke.mjs +73 -0
  87. package/skills/basic/master-session/SKILL.md +11 -0
  88. package/skills/bdb-aos/scripts/list-playbooks.mjs +72 -0
  89. package/skills/global_config/agenttrail/SKILL.md +3 -1
  90. package/skills/global_config/agenttrail/bin/agenttrail.mjs +255 -117
  91. package/skills/global_config/agenttrail/bin/ensure.mjs +60 -40
  92. package/skills/global_config/agenttrail/bin/repoid.mjs +70 -0
  93. package/skills/global_config/agenttrail/public/index.html +9 -1
  94. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
  95. package/skills/global_config/bdb-memb-mcp/SKILL.md +5 -4
  96. package/skills/global_config/bdb-visual-edit/SKILL.md +28 -32
  97. package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +2 -2
  98. package/skills/global_config/bdb-visual-edit/scripts/locate-source.mjs +135 -0
  99. package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +30 -2
  100. package/skills/global_config/gogate/SKILL.md +123 -0
  101. package/skills/global_config/loop-templates/SKILL.md +27 -0
  102. package/skills/global_config/loop-templates/references/ci-until-green.md +27 -0
  103. package/skills/global_config/loop-templates/references/daily-summary.md +27 -0
  104. package/skills/global_config/loop-templates/references/pr-to-merge.md +27 -0
  105. package/skills/global_config/loop-templates/references/review-rounds.md +27 -0
  106. package/skills/global_config/mcsc/SKILL.md +9 -1
  107. package/skills/global_config/plan-arbiter/SKILL.md +1 -1
  108. package/skills/global_config/plan-canvas/SKILL.md +42 -4
  109. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +1 -1
  110. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +2 -2
  111. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/geometry.js +76 -0
  112. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/index.js +596 -0
  113. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/model.js +192 -0
  114. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-client/toolbar.js +99 -0
  115. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotate-server.js +282 -0
  116. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/annotation-schema.js +210 -0
  117. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/route.js +10 -0
  118. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sdk.js +6 -230
  119. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +45 -4
  120. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/sessions.js +60 -21
  121. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/trail-on-approve.js +103 -0
  122. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +19 -7
  123. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +118 -20
  124. package/skills/global_config/subagent-setup/SKILL.md +6 -0
  125. package/skills/global_config/subagent-setup/scripts/setup-subagents.mjs +20 -1
  126. package/skills/playbooks/pb-idea-to-launch/SKILL.md +2 -2
  127. package/skills/playbooks/pb-redesign-app/SKILL.md +3 -3
  128. package/skills/playbooks/pb-release-aos/SKILL.md +2 -2
  129. package/skills/playbooks/pb-ship/SKILL.md +2 -2
  130. package/skills/playbooks/pb-worktrees-land/SKILL.md +2 -2
  131. package/.codex-plugin/marketplace.json +0 -11
  132. package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +0 -27
  133. package/skills/global_config/visual-edit/README.md +0 -96
  134. package/skills/global_config/visual-edit/SKILL.md +0 -615
  135. package/skills/global_config/visual-plan/README.md +0 -93
  136. package/skills/global_config/visual-plan/SKILL.md +0 -544
  137. package/skills/global_config/visual-plan/references/canvas.md +0 -139
  138. package/skills/global_config/visual-plan/references/connection.md +0 -51
  139. package/skills/global_config/visual-plan/references/document-quality.md +0 -186
  140. package/skills/global_config/visual-plan/references/exemplar.md +0 -62
  141. package/skills/global_config/visual-plan/references/local-files.md +0 -99
  142. package/skills/global_config/visual-plan/references/wireframe.md +0 -319
  143. package/skills/global_config/visual-recap/README.md +0 -103
  144. package/skills/global_config/visual-recap/SKILL.md +0 -560
  145. package/skills/global_config/visual-recap/references/connection.md +0 -51
  146. package/skills/global_config/visual-recap/references/local-files.md +0 -99
  147. package/skills/global_config/visual-recap/references/wireframe.md +0 -319
@@ -0,0 +1,88 @@
1
+ #!/usr/bin/env node
2
+ // UserPromptSubmit hook for the go-gate modes and grants. When the human types,
3
+ // as the whole message (plain form, primary):
4
+ // gogate grant <scope[,scope...]> <15m|2h|1d|session>
5
+ // gogate hard|soft|off|status
6
+ // or the slash form /bdb-aos:gogate ... (rarely verifies, the hook says so),
7
+ // it records the request in ~/.aos/gate/<session-key>.json with a pointer to
8
+ // the prompt (transcript, issue time, text hash). Nothing here is trusted on
9
+ // its own: go-gate.mjs re-reads the transcript at use time and honours an
10
+ // entry only if a human-typed (origin-checked) prompt with that hash exists.
11
+ // A loop, peer, bus or agent prompt may reach this hook, but its transcript
12
+ // entry is not human, so what it records never verifies.
13
+ //
14
+ // CLI: node go-grant.mjs --status [--session <id>] (read-only, prints status)
15
+
16
+ import { readFileSync, realpathSync } from "node:fs";
17
+ import { fileURLToPath } from "node:url";
18
+ import {
19
+ parseGogate, textHash, sessionKey, readState, writeState, gateLog, effectiveGate,
20
+ gogateCommandsFor, statusText, listStateKeys, OPENCODE_MAX_GRANT_MS,
21
+ } from "./go-gate.mjs";
22
+
23
+ const emptyState = (source) => ({ transcript: source, grants: [] });
24
+
25
+ // Applies a gogate prompt to the store. `source` is the transcript path or
26
+ // "opencode:<sessionID>"; `uuid` the message id when the harness knows it.
27
+ // Returns the text for the model (additionalContext), or null for non-gogate prompts.
28
+ export function applyGogate(prompt, { key, source, uuid = null, now = Date.now(), opencode = false }) {
29
+ const p = parseGogate(prompt);
30
+ if (!p) return null;
31
+ const slashNote = p.form === "slash"
32
+ ? ` Note: this was a slash command. ${opencode ? "OpenCode accepts only the plain form" : "Claude Code stores slash commands without human-origin data, so the gate will most likely ignore it"}; to make it count, type the plain form as the whole message: \`gogate ${p.cmd === "grant" ? `grant ${p.scopes.join(",")} ${p.session ? "session" : "<duration>"}` : p.cmd || "status"}\`.`
33
+ : "";
34
+ if (p.error) return `gogate: ${p.error}. Nothing changed.`;
35
+ if (!key || !source) return "gogate: no session id in hook input, nothing recorded.";
36
+ if (process.env.AOS_ACP_CLIENT) return "gogate: an aos-acp worker session cannot set modes or grants.";
37
+ if (p.cmd === "status") return status(key);
38
+ if (opencode && p.cmd === "off") return "gogate: `off` is not available on OpenCode (no human-origin signal there). Nothing changed.";
39
+ let st = readState(key);
40
+ if (!st || typeof st !== "object" || st.transcript !== source) st = emptyState(source);
41
+ const src = { transcript: source, uuid, issued_at: new Date(now).toISOString(), text_hash: textHash(prompt) };
42
+ if (p.cmd === "grant") {
43
+ const ms = opencode ? Math.min(p.ms, OPENCODE_MAX_GRANT_MS) : p.ms;
44
+ st.grants = (Array.isArray(st.grants) ? st.grants : []).filter((g) => Date.parse(g?.until) > now);
45
+ for (const scope of p.scopes) st.grants.push({ scope, until: new Date(now + ms).toISOString(), ...(p.session ? { session_key: key } : {}), source: src });
46
+ } else {
47
+ // Append, never overwrite: a later prompt that does not verify must not erase the
48
+ // store entry of the mode that is still in force.
49
+ st.modes = [...(Array.isArray(st.modes) ? st.modes : []), { mode: p.cmd, source: src }].slice(-20);
50
+ }
51
+ try {
52
+ writeState(key, st);
53
+ } catch (e) {
54
+ return `gogate: could NOT record ${p.cmd}: writing ~/.aos/gate failed (${e.code || e.message}). Nothing changed; the gate stays as it was. Tell the human.`;
55
+ }
56
+ gateLog(key, `recorded ${p.form} ${p.cmd}${p.scopes ? ` ${p.scopes.join(",")} ${p.session ? "session" : `${p.ms / 60000}m`}` : ""}`);
57
+ return [
58
+ `gogate: recorded ${p.cmd}${p.scopes ? ` for ${p.scopes.join(", ")}` : ""}${p.session ? " (session = this session id, at most 24h, survives --resume)" : ""}${opencode && p.cmd === "grant" && p.ms > OPENCODE_MAX_GRANT_MS ? " (capped at 2h on OpenCode)" : ""}.`,
59
+ "The gate verifies it against this prompt in the transcript on every use.",
60
+ slashNote,
61
+ "Tell the human what was recorded; never try to change modes or grants yourself.",
62
+ ].filter(Boolean).join(" ");
63
+ }
64
+
65
+ export function status(key) {
66
+ const st = readState(key);
67
+ const eff = st ? effectiveGate(key, { source: st.transcript, cmds: gogateCommandsFor(st.transcript), opencode: String(st.transcript).startsWith("opencode:") }) : { mode: "soft", grants: [], rejected: [] };
68
+ return statusText(key, eff);
69
+ }
70
+
71
+ const isMain = () => { try { return realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url)); } catch { return false; } };
72
+
73
+ if (isMain()) {
74
+ const argv = process.argv.slice(2);
75
+ if (argv.includes("--status")) {
76
+ const i = argv.indexOf("--session");
77
+ const id = i !== -1 ? argv[i + 1] : process.env.AOS_GATE_SESSION || process.env.CLAUDE_SESSION_ID;
78
+ const keys = id ? [sessionKey(id)] : listStateKeys();
79
+ console.log(keys.length ? keys.map(status).join("\n\n") : "AOS go-gate: no session state; every session runs in soft mode with no grants (behaves like hard).");
80
+ process.exit(0);
81
+ }
82
+ try {
83
+ const input = JSON.parse(readFileSync(0, "utf8"));
84
+ const text = applyGogate(input.prompt, { key: sessionKey(input.session_id), source: input.transcript_path });
85
+ if (text) console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: text } }));
86
+ } catch { /* a failing hook must never block the prompt */ }
87
+ process.exit(0);
88
+ }
@@ -3,11 +3,11 @@
3
3
  // master session, record a single-use token that go-gate.mjs in <session-name>
4
4
  // accepts. Writes nothing else and never blocks the prompt.
5
5
 
6
- import { readFileSync, writeFileSync, mkdirSync, realpathSync } from "node:fs";
6
+ import { readFileSync, writeFileSync, mkdirSync, realpathSync, readdirSync, unlinkSync } from "node:fs";
7
7
  import { homedir } from "node:os";
8
8
  import { join } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
- import { slug } from "./go-gate.mjs";
10
+ import { slug, TOKEN_TTL_MS } from "./go-gate.mjs";
11
11
 
12
12
  function sessionName(transcriptPath) {
13
13
  let name = "";
@@ -24,6 +24,20 @@ function sessionName(transcriptPath) {
24
24
  return name || process.env.AOS_SESSION_NAME || "";
25
25
  }
26
26
 
27
+ // Expired or unreadable tokens are never read again by anyone, so a write is the moment to drop them.
28
+ export function pruneExpiredTokens(dir, now = Date.now()) {
29
+ let files = [];
30
+ try { files = readdirSync(dir).filter((f) => f.endsWith(".token")); } catch { return 0; }
31
+ let removed = 0;
32
+ for (const f of files) {
33
+ let age = NaN;
34
+ try { age = now - Date.parse(JSON.parse(readFileSync(join(dir, f), "utf8"))?.issued_at); } catch { /* unreadable: drop */ }
35
+ if (age >= 0 && age < TOKEN_TTL_MS) continue;
36
+ try { unlinkSync(join(dir, f)); removed++; } catch { /* already gone */ }
37
+ }
38
+ return removed;
39
+ }
40
+
27
41
  // Returns the token path, or null. Never throws. An aos-acp worker is a worker
28
42
  // by definition and must not mint a token for itself.
29
43
  export function issueGoToken(prompt, { transcript_path, session_id, message_id } = {}) {
@@ -38,6 +52,7 @@ export function issueGoToken(prompt, { transcript_path, session_id, message_id }
38
52
  if (!target || !tok) return null;
39
53
  const dir = join(homedir(), ".aos", "go");
40
54
  mkdirSync(dir, { recursive: true });
55
+ pruneExpiredTokens(dir);
41
56
  const file = join(dir, `${target}.token`);
42
57
  writeFileSync(file, JSON.stringify(tok));
43
58
  return file;
@@ -1,32 +1,41 @@
1
1
  #!/usr/bin/env node
2
- // aos-hook-version: 7
2
+ // aos-hook-version: 8
3
3
  /**
4
4
  * memB ambient memory hook for Claude Code, Google Antigravity, and OpenAI Codex.
5
5
  *
6
6
  * Stamped with aos-hook-version for /aos-setup doctor validation.
7
7
  * Reads ~/.MemBDB/memb.db directly through node:sqlite and injects relevant
8
- * project & user memories as ephemeral context in tri-format JSON:
9
- * - hookSpecificOutput (Claude Code UserPromptSubmit)
8
+ * project & user memories as ephemeral context, emitting only the key the
9
+ * calling harness reads:
10
+ * - hookSpecificOutput.additionalContext (Claude Code, OpenAI Codex)
10
11
  * - injectSteps (Google Antigravity PreInvocation)
11
- * - systemMessage (OpenAI Codex UserPromptSubmit)
12
+ *
13
+ * Reads rows of the configured user (MEMB_USER_ID, fallback $USER) plus the
14
+ * group ids in MEMB_GROUP_IDS (comma list, default bdb_developer). Rows bound
15
+ * to the current project are injected whatever their category (max 5, 180
16
+ * chars each); global rows still need category `godmode`.
12
17
  *
13
18
  * Event-aware: SessionStart injects the persona file, the global godmode
14
19
  * identity rows, and the current project's cards once per session; every
15
20
  * other prompt (UserPromptSubmit) recalls only FTS keyword hits so 40
16
21
  * identity facts are not re-sent on every message. Harnesses without a
17
22
  * session hook (e.g. Antigravity PreInvocation) get the full block plus
18
- * identity on each invocation.
23
+ * identity on the first invocation of a conversation (cached per
24
+ * conversationId); later invocations recall FTS hits only.
19
25
  *
20
26
  * Fails open: any error exits 0 silently.
21
27
  */
22
28
 
23
- import { readFileSync, existsSync, realpathSync } from 'node:fs';
29
+ import { readFileSync, existsSync, realpathSync, mkdirSync, writeFileSync } from 'node:fs';
24
30
  import os from 'node:os';
25
31
  import path from 'node:path';
26
32
  import { fileURLToPath } from 'node:url';
27
33
  import { execFileSync } from 'node:child_process';
28
34
 
29
35
  const COLLECTION = 'bdb_agent_memory';
36
+ const DEFAULT_GROUP_IDS = 'bdb_developer';
37
+ const PROJECT_ROW_CAP = 5;
38
+ const ROW_CHARS = 180;
30
39
  const failOpen = () => process.exit(0);
31
40
 
32
41
  // `deja wip --json` run in the project root, rendered as context lines.
@@ -51,6 +60,14 @@ export function dejaWip(cwd, run = execFileSync) {
51
60
  }
52
61
  }
53
62
 
63
+ // Rows are read for the user plus the configured group ids; nothing is
64
+ // hard-coded beyond the documented default group.
65
+ export function resolveReaderIds({ userId, env = process.env } = {}) {
66
+ const user = env.MEMB_USER_ID || userId || env.USER || env.LOGNAME || '';
67
+ const groups = String(env.MEMB_GROUP_IDS ?? DEFAULT_GROUP_IDS).split(',').map((g) => g.trim());
68
+ return Array.from(new Set([user, ...groups].filter(Boolean)));
69
+ }
70
+
54
71
  // Filler words (German + English) that must never become FTS query terms —
55
72
  // they match virtually any document and would inject unrelated projects.
56
73
  const STOPWORDS = new Set([
@@ -160,14 +177,7 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
160
177
  const projectRoot = findProjectRoot(rawCandidateDir, home);
161
178
  const candidateProjectIds = resolveProjectIdentifiers(projectRoot, home);
162
179
 
163
- // User identification: reconcile active user and bdb_developer baseline
164
- const activeUser =
165
- process.env.MEMB_USER_ID ||
166
- userId ||
167
- process.env.USER ||
168
- process.env.LOGNAME ||
169
- 'bdb_developer';
170
- const candidateUsers = Array.from(new Set([activeUser, 'bdb_developer'])).filter(Boolean);
180
+ const candidateUsers = resolveReaderIds({ userId });
171
181
 
172
182
  const contextItems = [];
173
183
 
@@ -259,12 +269,10 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
259
269
  OR json_extract(payload, '$.user_id') IN (${uPlaceholders})
260
270
  OR json_extract(payload, '$.metadata.user_id') IN (${uPlaceholders})
261
271
  )
262
- AND (
263
- json_extract(payload, '$.category') IN ('project_card','godmode')
264
- OR json_extract(payload, '$.metadata.category') IN ('project_card','godmode')
265
- )
266
- ORDER BY rowid DESC
267
- LIMIT 5
272
+ ORDER BY (COALESCE(json_extract(payload, '$.category'),
273
+ json_extract(payload, '$.metadata.category')) = 'project_card') DESC,
274
+ rowid DESC
275
+ LIMIT ${PROJECT_ROW_CAP * 4}
268
276
  `;
269
277
  const params = [
270
278
  COLLECTION,
@@ -276,11 +284,14 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
276
284
  ...candidateUsers,
277
285
  ];
278
286
  const rows = db.prepare(sql).all(...params);
287
+ let taken = 0;
279
288
  for (const r of rows) {
289
+ if (taken >= PROJECT_ROW_CAP) break;
280
290
  const text = textOf(r.payload);
281
- if (text.length > 10) {
282
- contextItems.push(`- Project [${candidateProjectIds[0]}]: ${text.slice(0, 180)}`);
283
- }
291
+ if (text.length <= 10 || /^\[[^\]|]+\|[^\]|]+\|/.test(text)) continue; // too short / imported file chunk
292
+ if (contextItems.some((e) => e.includes(text.slice(0, 50)))) continue;
293
+ contextItems.push(`- Project [${candidateProjectIds[0]}]: ${text.slice(0, ROW_CHARS)}`);
294
+ taken++;
284
295
  }
285
296
  } catch { /* project query is best-effort */ }
286
297
  }
@@ -316,7 +327,7 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
316
327
  const hitProject = projectOf(r.payload);
317
328
  if (hitProject && !candidateProjectIds.includes(hitProject)) continue;
318
329
  const cat = categoryOf(r.payload);
319
- if (cat !== 'project_card' && cat !== 'godmode') continue;
330
+ if (!hitProject && cat !== 'project_card' && cat !== 'godmode') continue;
320
331
  if (!contextItems.some((e) => e.includes(text.slice(0, 50)))) {
321
332
  contextItems.push(`- Domain memory: ${text.slice(0, 180)}`);
322
333
  }
@@ -338,6 +349,19 @@ export async function buildMemoryBlock({ event = '', prompt = '', cwd, userId }
338
349
  }
339
350
  }
340
351
 
352
+ // Antigravity has no session hook: remember per conversation that identity and
353
+ // project rows were already sent, so later invocations do only the FTS recall
354
+ // and skip `deja wip`. Stamps under ~/.aos/cache are disposable.
355
+ export function agyIdentitySent(conversationId, base = path.join(os.homedir(), '.aos', 'cache')) {
356
+ const id = String(conversationId || '').replace(/[^\w.-]/g, '_').slice(0, 80);
357
+ if (!id) return false;
358
+ const dir = path.join(base, 'memb-agy');
359
+ const stamp = path.join(dir, id);
360
+ if (existsSync(stamp)) return true;
361
+ try { mkdirSync(dir, { recursive: true }); writeFileSync(stamp, ''); } catch {}
362
+ return false;
363
+ }
364
+
341
365
  // Main logic runs only when the file is executed directly as a hook; importing
342
366
  // the module (e.g. from tests) must stay side-effect free.
343
367
  async function main() {
@@ -351,26 +375,26 @@ async function main() {
351
375
 
352
376
  // Discover candidate directory across harnesses:
353
377
  // Claude / Codex: eventData.cwd; Antigravity: eventData.workspacePaths[0] or eventData.cwd
378
+ const isAgy = !eventData.hook_event_name && (Array.isArray(eventData.workspacePaths) || !!eventData.conversationId);
379
+ const event = isAgy && agyIdentitySent(eventData.conversationId)
380
+ ? 'UserPromptSubmit'
381
+ : (eventData.hook_event_name || '');
354
382
  const memoryBlock = await buildMemoryBlock({
355
- event: eventData.hook_event_name || '',
383
+ event,
356
384
  prompt: eventData.prompt || eventData.userPrompt || '',
357
385
  cwd: (Array.isArray(eventData.workspacePaths) && eventData.workspacePaths[0]) || eventData.cwd,
358
386
  userId: eventData.user_id || eventData.userId,
359
387
  });
360
388
 
361
389
  if (memoryBlock) {
362
- process.stdout.write(JSON.stringify({
363
- hookSpecificOutput: {
364
- hookEventName: eventData.hook_event_name || 'UserPromptSubmit',
365
- additionalContext: memoryBlock,
366
- },
367
- injectSteps: [
368
- {
369
- ephemeralMessage: memoryBlock,
370
- }
371
- ],
372
- systemMessage: memoryBlock,
373
- }) + '\n');
390
+ process.stdout.write(JSON.stringify(isAgy
391
+ ? { injectSteps: [{ ephemeralMessage: memoryBlock }] }
392
+ : {
393
+ hookSpecificOutput: {
394
+ hookEventName: eventData.hook_event_name || 'UserPromptSubmit',
395
+ additionalContext: memoryBlock,
396
+ },
397
+ }) + '\n');
374
398
  }
375
399
  process.exit(0);
376
400
  } catch {
@@ -123,6 +123,14 @@
123
123
  }
124
124
  ]
125
125
  },
126
+ {
127
+ "hooks": [
128
+ {
129
+ "type": "command",
130
+ "command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/go-grant.mjs\""
131
+ }
132
+ ]
133
+ },
126
134
  {
127
135
  "hooks": [
128
136
  {
@@ -1,8 +1,38 @@
1
1
  {
2
- "name": "bdb-dev-optimized-agent-skills",
3
- "version": "2.3.0",
4
- "description": "BDB DEV Optimized Agent Skills and MCP pack for Codex CLI, Claude, Cursor and Antigravity",
5
- "author": "hybridlabor-api",
6
- "skills_dir": "./skills/global_config",
7
- "mcp_config": "./.codex-plugin/.mcp.json"
2
+ "name": "bdb-aos",
3
+ "version": "4.18.0",
4
+ "description": "Full BDB Agent OS suite for Codex: command entry points ($bdb-aos:setup, $bdb-aos:init, ...) generated from plugin-commands.json.",
5
+ "author": {
6
+ "name": "Tim Rennings / Hybridlabor Global LLC",
7
+ "email": "hybridlabor@gmail.com"
8
+ },
9
+ "homepage": "https://github.com/hybridlabor-api/aos",
10
+ "repository": "https://github.com/hybridlabor-api/aos",
11
+ "license": "Apache-2.0",
12
+ "keywords": [
13
+ "skills",
14
+ "agent-os",
15
+ "cicd",
16
+ "multiagent",
17
+ "startcycle",
18
+ "bdb"
19
+ ],
20
+ "skills": "./plugins/bdb-aos-codex/skills/",
21
+ "interface": {
22
+ "displayName": "BDB Agent OS",
23
+ "shortDescription": "AOS commands for setup, planning and pipelines",
24
+ "longDescription": "Entry-point skills for the BDB Agent OS suite: invoke $bdb-aos:<command> to run setup, project init, planning and the startcycle pipelines.",
25
+ "developerName": "Hybridlabor Global LLC",
26
+ "category": "Developer Tools",
27
+ "capabilities": [
28
+ "Interactive",
29
+ "Read",
30
+ "Write"
31
+ ],
32
+ "websiteURL": "https://github.com/hybridlabor-api/aos",
33
+ "defaultPrompt": [
34
+ "$bdb-aos:setup",
35
+ "$bdb-aos:doctor"
36
+ ]
37
+ }
8
38
  }
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Brainstorm a topic with the matching skill: bdbrainstorm, bdbmediastorm for event tech, grill-me for a stress test."
3
+ ---
4
+
5
+ Pick by topic: `bdbmediastorm` for live event technology and show control, `grill-me` when the human wants an existing idea stress-tested, `bdbrainstorm` otherwise. Say which one you chose. Topic: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Check the machine and the current project, and print suggested permissions.allow entries without writing any settings."
3
+ ---
4
+
5
+ Run `aos-doctor` (machine check) and report the result. If the current folder is an AOS project, also run the project check `node <aos-project-init skill dir>/scripts/aos-project-doctor.mjs` (it covers `.aos/project.json`, AGENTS.md and the wiki). Print suggested `permissions.allow` entries as text for the human to copy. Never write to settings.json or settings.local.json. Arguments: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Run the startcycle-graph pipeline: durable state, repair loop, quality gate, escalation."
3
+ ---
4
+
5
+ Run the `startcycle-graph` skill; its contract defines the flow. Task: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Set up the current project folder for AOS: slug, wiki, memory, Synapse map, AGENTS.md."
3
+ ---
4
+
5
+ Run the `aos-project-init` skill for the current project folder. This is project setup, not machine setup (that is `/bdb-aos-setup`). Arguments: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Run a prompt repeatedly on an interval, using the harness built-in plus loop templates."
3
+ ---
4
+
5
+ OpenCode has no built-in loop. Use `opencode-loop`, the opt-in package (AOS_OPENCODE_OPTIONAL=loop, see docs/opencode-setup.md); without it, say so and stop. Do not schedule gated commands through its shell loop. Offer a `loop-templates` template when the human has no prompt yet. 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: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Supervise other Claude Code sessions on this machine: roster, status requests, GO board, idle notices."
3
+ ---
4
+
5
+ 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: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Query or store long-term memory with memB."
3
+ ---
4
+
5
+ Use `memb-skill` to search or remember and `memb-ingest` to import material. Never store secrets, tokens or credentials. Arguments: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Coordinate parallel coding agents across repositories through the AO daemon."
3
+ ---
4
+
5
+ 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: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Plan end to end: draft, render in plan-canvas or Plan Builder, annotate, await approval, then hand off."
3
+ ---
4
+
5
+ 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: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "List all pb-* playbooks with duration, difficulty and requirements, and start one."
3
+ ---
4
+
5
+ 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: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Bring this machine to a complete, verified AOS installation (skills, MCP servers, gate hooks, memB, OpenWiki, Synapse)."
3
+ ---
4
+
5
+ Run the `aos-setup` skill for this machine. Report what is installed, what was repaired and what is still missing. Arguments: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Pre-ship framing and the technical release gate, with approvals under the go-gate rules."
3
+ ---
4
+
5
+ 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: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Start a new composition: project init, brainstorm, then ask whether graph or startcycle follows."
3
+ ---
4
+
5
+ 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: $ARGUMENTS
@@ -0,0 +1,5 @@
1
+ ---
2
+ description: "Browse and preview AOS skills, agents and playbooks in the local store UI."
3
+ ---
4
+
5
+ Start the `aos-store` interface. Preview freely, but install nothing until the human says yes for that specific item. Arguments: $ARGUMENTS
@@ -23,8 +23,11 @@ import path from 'node:path';
23
23
  // so a broken installed hook fails loudly instead of silently falling back.
24
24
  const HOOKS = existsSync(new URL('./aos-hooks/go-gate.mjs', import.meta.url)) ? './aos-hooks/' : '../../.claude/hooks/';
25
25
  const hook = (f) => import(new URL(HOOKS + f, import.meta.url).href);
26
- const [{ GUARDED_PATTERNS, tokenGrantsGo }, { issueGoToken }, { checkConventionalCommit }, { envFileReason }, { buildMemoryBlock }, bus] =
27
- await Promise.all(['go-gate.mjs', 'go-token.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs', 'memb-inject.mjs', 'aos-bus.mjs'].map(hook));
26
+ // `opencode run "..."` is scriptable by an agent, so it never sets gogate modes or grants
27
+ const OPENCODE_RUN = process.argv.slice(1, 4).includes('run');
28
+ const [gate, { issueGoToken }, { checkConventionalCommit }, { envFileReason }, { buildMemoryBlock }, bus, { applyGogate }] =
29
+ await Promise.all(['go-gate.mjs', 'go-token.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs', 'memb-inject.mjs', 'aos-bus.mjs', 'go-grant.mjs'].map(hook));
30
+ const { GUARDED_PATTERNS, tokenGrantsGo, isHumanPart, parseGoText, goAllows, isGuardedCommand, gateStoreReason, effectiveGate, opencodeGogateCommands, grantsCover, sessionKey, gateLog } = gate;
28
31
 
29
32
  // ---------------------------------------------------------------------------
30
33
  // Graph gate (W-5, W-6)
@@ -131,9 +134,26 @@ async function ownSessionName(client, sessionID) {
131
134
  }
132
135
 
133
136
  // Only plain text parts are human turns: synthetic = model-only, ignored = display-only,
134
- // aos_bus = bus delivery (both copies). Each excluded on its own, so safety never hangs on one flag.
135
- const isHumanText = (p) => p && p.type === 'text' && typeof p.text === 'string' && !p.synthetic && !p.ignored && !p.metadata?.aos_bus;
136
- const isNonHuman = (p) => p && (p.synthetic || p.ignored || p.metadata?.aos_bus);
137
+ // aos_bus / aos_loop (any loop|aos_ metadata key) = plugin-injected. Each excluded on its own,
138
+ // so safety never hangs on one flag.
139
+ const isHumanText = isHumanPart;
140
+ const isNonHuman = (p) => !!p && !!(p.synthetic || p.ignored || (p.metadata && typeof p.metadata === 'object' && Object.keys(p.metadata).some((k) => /loop|aos_/i.test(k))));
141
+ const ocKey = (id) => sessionKey(`oc-${id}`);
142
+
143
+ // Root of a session tree (grants live on the root, subagents may use them). null when unknown.
144
+ async function rootSessionId(client, id, parentID) {
145
+ for (let i = 0; i < 10 && id; i++) {
146
+ if (parentID === null) return id;
147
+ try {
148
+ const res = await client?.session?.get?.({ path: { id } });
149
+ if (!res?.data) return null;
150
+ if (!res.data.parentID) return id;
151
+ id = res.data.parentID;
152
+ parentID = undefined;
153
+ } catch { return null; }
154
+ }
155
+ return null;
156
+ }
137
157
 
138
158
  // A plugin reload (SIGUSR2) re-runs init on the cached module in the same process:
139
159
  // stop the previous instance's timers and keep its bus names alive. Keyed by directory.
@@ -336,9 +356,10 @@ export default async function bdbAosPlugin(input) {
336
356
  if (nudges >= MAX_NUDGES) return;
337
357
 
338
358
  nudgeState.set(sessionID, { signature: gate.signature, nudges: nudges + 1 });
359
+ // Synthetic + aos_loop: the nudge is plugin-written and must never count as a human turn (GO, gogate).
339
360
  await input.client.session.prompt({
340
361
  path: { id: sessionID },
341
- body: { parts: [{ type: 'text', text: buildGraphNudge(gate) }] },
362
+ body: { parts: [{ type: 'text', text: buildGraphNudge(gate), synthetic: true, metadata: { aos_loop: { source: 'graph-gate', nudge: nudges + 1 } } }] },
342
363
  query: directory ? { directory } : undefined,
343
364
  });
344
365
  } catch {
@@ -361,6 +382,12 @@ export default async function bdbAosPlugin(input) {
361
382
  if (!s.parentID) issueGoToken(fullText, { session_id: msgInput.sessionID, message_id: msgInput.messageID || msgOutput.message?.id });
362
383
  } catch {}
363
384
 
385
+ // plain `gogate ...` message from the human in a root session: record it; the gate verifies it against opencode.db at use.
386
+ try {
387
+ const reply = s.parentID || OPENCODE_RUN ? null : applyGogate(fullText, { key: ocKey(msgInput.sessionID), source: `opencode:${msgInput.sessionID}`, uuid: msgInput.messageID || msgOutput.message?.id || null, opencode: true });
388
+ if (reply) msgOutput.parts.unshift({ id: `gogate-${Date.now()}`, sessionID: msgInput.sessionID, messageID: msgInput.messageID || '', type: 'text', text: reply, synthetic: true });
389
+ } catch {}
390
+
364
391
  try {
365
392
  const event = s.identitySent || s.parentID ? 'UserPromptSubmit' : 'SessionStart';
366
393
  s.identitySent = true;
@@ -415,7 +442,9 @@ export default async function bdbAosPlugin(input) {
415
442
  // Intercept bash, terminal, or command execution tools
416
443
  if (toolName === 'bash' || toolName === 'terminal' || toolName === 'shell' || toolName === 'exec' || toolName === 'run_command') {
417
444
  const cmd = toolOutput?.args?.command || toolOutput?.args?.cmd || toolOutput?.args?.script || '';
418
- if (typeof cmd === 'string' && GUARDED_PATTERNS.some((re) => re.test(cmd))) {
445
+ const storeReason = gateStoreReason(cmd);
446
+ if (storeReason) throw new Error(storeReason);
447
+ if (typeof cmd === 'string' && (GUARDED_PATTERNS.some((re) => re.test(cmd)) || isGuardedCommand(cmd))) {
419
448
  const s = sess(toolInput.sessionID);
420
449
  // An unanswered lookup is not proof of a root session: token-only for this call, not cached.
421
450
  let tokenOnly = !!s.parentID;
@@ -427,8 +456,9 @@ export default async function bdbAosPlugin(input) {
427
456
  } catch { tokenOnly = true; }
428
457
  tokenOnly = tokenOnly || !!s.parentID;
429
458
  }
430
- // Subagent prompts are agent-written, so only a token can open their gate.
431
- let authorized = !tokenOnly && s.prompt.toUpperCase() === 'GO';
459
+ // Subagent prompts are agent-written, so only a token or a grant can open their gate.
460
+ const goOk = (text) => goAllows(parseGoText(text), cmd);
461
+ let authorized = !tokenOnly && goOk(s.prompt);
432
462
 
433
463
  // If this session's cached prompt wasn't GO, query recent session messages via OpenCode client as fallback
434
464
  if (!authorized && !tokenOnly && input.client && toolInput.sessionID) {
@@ -440,13 +470,27 @@ export default async function bdbAosPlugin(input) {
440
470
  if ((m.info?.role ?? m.role) === 'user') {
441
471
  const parts = m.parts || [];
442
472
  const text = parts.filter(isHumanText).map((p) => p.text).join('\n').trim();
443
- authorized = text.toUpperCase() === 'GO';
473
+ authorized = goOk(text);
444
474
  break;
445
475
  }
446
476
  }
447
477
  } catch {}
448
478
  }
449
479
 
480
+ // Modes and grants of the root session (verified against opencode.db); usable by its subagents.
481
+ if (!authorized) {
482
+ const root = await rootSessionId(input.client, toolInput.sessionID, tokenOnly ? undefined : s.parentID);
483
+ if (root) {
484
+ const key = ocKey(root);
485
+ const eff = effectiveGate(key, { source: `opencode:${root}`, cmds: opencodeGogateCommands(root), opencode: true });
486
+ for (const r of eff.rejected) gateLog(key, `rejected: ${r}`);
487
+ if (eff.mode === 'soft' && grantsCover(cmd, eff.grants)) { // `off` is never honoured on OpenCode
488
+ gateLog(key, `grant: allowed ${JSON.stringify(cmd.slice(0, 200))}`);
489
+ authorized = true;
490
+ }
491
+ }
492
+ }
493
+
450
494
  if (!authorized) {
451
495
  authorized = tokenGrantsGo(await ownSessionName(input.client, toolInput.sessionID)).ok;
452
496
  }
package/README.md CHANGED
@@ -110,14 +110,15 @@ What the installer writes for each target. Paths are the defaults; the installer
110
110
  | Harness | Skills | Subagents | Hooks | Plugin / rules |
111
111
  |---|---|---|---|---|
112
112
  | Claude Code / Claude Desktop | `~/.claude/skills` | `~/.claude/agents` | `~/.claude/hooks` + `settings.json` (GO gate, graph gate, env-file protection, Conventional Commits, memB inject, trail relay) | `.claude-plugin/` manifest ships in the repo (see Contributing) |
113
- | Google Antigravity | `~/.gemini/config/skills` | `~/.gemini/config/agents` | `~/.gemini/config/hooks.json` and `~/.gemini/antigravity-cli/hooks.json` | — |
114
- | Codex CLI | `~/.codex/skills` | `~/.codex/agents` | `~/.codex/hooks` + `config.toml` | `.codex-plugin/` |
115
- | OpenCode | `~/.config/opencode/skills` | `~/.opencode/agents` | via plugin | `bdb-aos.js` plugin + `/startcycle-graph` command, registered in `opencode.jsonc`; keeps a `/startcycle-graph` run moving on `session.idle` |
113
+ | Google Antigravity | `~/.gemini/config/skills` | `~/.gemini/config/agents` | `~/.gemini/config/hooks.json` and `~/.gemini/antigravity-cli/hooks.json` | root `plugin.json` / `plugins/bdb-aos/plugin.json`, commands `/bdb-aos:<cmd>`, see [docs/codex-agy-setup.md](docs/codex-agy-setup.md) |
114
+ | Codex CLI | `~/.codex/skills` | `~/.codex/agents` | `~/.codex/hooks` + `config.toml` | `.codex-plugin/` + `.agents/plugins/marketplace.json`, commands `$bdb-aos:<cmd>`, see [docs/codex-agy-setup.md](docs/codex-agy-setup.md) |
115
+ | OpenCode | `~/.config/opencode/skills` | `~/.opencode/agents` | via plugin | `bdb-aos.js` plugin + `/startcycle-graph` command, registered in `opencode.jsonc`; keeps a `/startcycle-graph` run moving on `session.idle`; generated `/bdb-aos-<cmd>` commands; opt-in extras, see [docs/opencode-setup.md](docs/opencode-setup.md) |
116
116
  | Cursor | `~/.cursor/skills` | — | — | `.cursor/rules` (project) |
117
117
  | Windsurf | `~/.windsurf/bdb-skills` | — | — | `mcp.json` |
118
118
  | Roo Code / Cline | `~/.roo/skills` | — | — | `.roomodes` (project) |
119
119
  | Aider | `~/.aider/bdb-skills` | — | — | — |
120
120
  | AOS CLI (`pi`) | reads `~/.agents/skills` | `~/.agents/AGENTS.md` as system prompt | — | no MCP; separate install, Node >= 22.19 — see [packages/aos-cli](packages/aos-cli/README.md) |
121
+ | BDB AO Codenotch (macOS app, Windows installer) | macOS `/Applications` or `~/Applications`; Windows per-user NSIS install (`/S`, no admin) | — | — | installed by default on macOS and Windows (never Linux); opt out with `--no-codenotch` or `AOS_CODENOTCH=0`; failures only warn; DMG from the public releases repo created by Tim, SHA-256 verified, ad-hoc signed with quarantine removed, see [docs/codenotch.md](docs/codenotch.md) |
121
122
 
122
123
  Every install also writes the universal copy to `~/.agents/skills`, which is what the AOS CLI and the `skills` CLI read.
123
124
 
@@ -431,7 +432,7 @@ aos-uninstall --purge # also removes ~/.MemBDB, ~/.openwiki, ~/.synapse, ~/
431
432
  aos-uninstall --dry-run # list everything, delete nothing
432
433
  ```
433
434
 
434
- The uninstaller works from the install manifest: a file that still matches the hash AOS wrote is removed, a file you edited is backed up instead, a file AOS never wrote is not touched. The same action is in the installer menu.
435
+ The uninstaller works from the install manifest: a file that still matches the hash AOS wrote is removed, a file you edited is backed up instead, a file AOS never wrote is not touched. The same action is in the installer menu. `aos-uninstall --restore-plugin-backup` restores the loose skill copies the installer removed when it registered the plugin; see [docs/plugin-migration.md](docs/plugin-migration.md).
435
436
 
436
437
  ---
437
438