@hybridlabor-api/aos 4.13.2 → 4.14.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 (150) hide show
  1. package/.agents/AGENTS.md +8 -0
  2. package/.agents/nodes.json +5 -2
  3. package/.claude/hooks/conventional-commits.mjs +14 -15
  4. package/.claude/hooks/env-file-protection.mjs +14 -15
  5. package/.claude/hooks/go-gate.mjs +152 -10
  6. package/.claude/hooks/go-token.mjs +55 -0
  7. package/.claude/hooks/memb-inject.mjs +75 -62
  8. package/.claude/hooks/trail-autostart.mjs +27 -0
  9. package/.claude/settings.json +18 -0
  10. package/.claude/workflows/startcycle-dispatch.mjs +11 -4
  11. package/.opencode/plugins/bdb-aos.js +98 -121
  12. package/.opencode/plugins/lib/trail-autostart.js +38 -0
  13. package/CLAUDE.md +1 -1
  14. package/README.de.md +6 -6
  15. package/README.md +6 -6
  16. package/README.pt.md +6 -6
  17. package/THIRD_PARTY_NOTICES.md +19 -3
  18. package/assets/header-v5.png +0 -0
  19. package/bin/aos-acp.mjs +211 -0
  20. package/bin/aos-doctor.mjs +1 -1
  21. package/bin/aos-uninstall.mjs +2 -2
  22. package/docs/master-session-acp.md +51 -0
  23. package/installer.js +252 -35
  24. package/mcps/mcsc/packages/mcp/server.js +6 -7
  25. package/package.json +4 -3
  26. package/scripts/validate-skills.mjs +76 -0
  27. package/skills/basic/bdbmediastorm/SKILL.md +1 -1
  28. package/skills/basic/godmode-shipping/SKILL.md +3 -0
  29. package/skills/basic/master-session/SKILL.md +89 -0
  30. package/skills/basic/startcycle/SKILL.md +1 -1
  31. package/skills/basic/startcycle-graph/SKILL.md +2 -2
  32. package/skills/basic/startcycle-graph-user/SKILL.md +1 -1
  33. package/skills/basic/teamwork-preview/SKILL.md +1 -1
  34. package/skills/bdbrainstorm/SKILL.md +7 -1
  35. package/skills/global_config/agentic-harness-patterns/SKILL.md +257 -0
  36. package/skills/global_config/agentic-harness-patterns/metadata.json +10 -0
  37. package/skills/global_config/agentic-harness-patterns/references/agent-orchestration-pattern.md +97 -0
  38. package/skills/global_config/agentic-harness-patterns/references/bootstrap-sequence-pattern.md +106 -0
  39. package/skills/global_config/agentic-harness-patterns/references/context-engineering/compress-pattern.md +78 -0
  40. package/skills/global_config/agentic-harness-patterns/references/context-engineering/isolate-pattern.md +82 -0
  41. package/skills/global_config/agentic-harness-patterns/references/context-engineering/select-pattern.md +86 -0
  42. package/skills/global_config/agentic-harness-patterns/references/context-engineering-pattern.md +29 -0
  43. package/skills/global_config/agentic-harness-patterns/references/hook-lifecycle-pattern.md +111 -0
  44. package/skills/global_config/agentic-harness-patterns/references/memory-persistence-pattern.md +109 -0
  45. package/skills/global_config/agentic-harness-patterns/references/permission-gate-pattern.md +111 -0
  46. package/skills/global_config/agentic-harness-patterns/references/skill-runtime-pattern.md +104 -0
  47. package/skills/global_config/agentic-harness-patterns/references/task-decomposition-pattern.md +92 -0
  48. package/skills/global_config/agentic-harness-patterns/references/tool-registry-pattern.md +101 -0
  49. package/skills/global_config/agenttrail/SKILL.md +8 -0
  50. package/skills/global_config/agenttrail/bin/agenttrail.mjs +14 -0
  51. package/skills/global_config/agenttrail/bin/ensure.mjs +118 -0
  52. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
  53. package/skills/global_config/bdb-visual-edit/SKILL.md +51 -0
  54. package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +59 -0
  55. package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +27 -0
  56. package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +123 -0
  57. package/skills/global_config/factory-collect/SKILL.md +74 -0
  58. package/skills/global_config/factory-human-digest/SKILL.md +92 -0
  59. package/skills/global_config/factory-lookback/SKILL.md +95 -0
  60. package/skills/global_config/factory-review-prs/SKILL.md +63 -0
  61. package/skills/global_config/git-pr-review/SKILL.md +3 -0
  62. package/skills/global_config/grilling/SKILL.md +2 -0
  63. package/skills/global_config/mcsc/SKILL.md +1 -1
  64. package/skills/global_config/plan-arbiter/SKILL.md +125 -0
  65. package/skills/global_config/plan-canvas/SKILL.md +62 -5
  66. package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +19 -3
  67. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +285 -0
  68. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/agent-trail.js +129 -0
  69. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/board-client.js +124 -0
  70. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/canvas.mdx +19 -0
  71. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/plan.mdx +18 -0
  72. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/recap-demo/plan.mdx +72 -0
  73. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/README.md +29 -0
  74. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/architecture.json +30 -0
  75. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/00_architecture.html +14950 -0
  76. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/canvas.mdx +511 -0
  77. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/plan.mdx +208 -0
  78. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/recap/plan.mdx +102 -0
  79. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/standard/plan.md +136 -0
  80. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/canvas.mdx +124 -0
  81. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/plan.mdx +37 -0
  82. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/index.js +188 -0
  83. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/kit.js +123 -0
  84. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/mdx.js +411 -0
  85. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +1291 -0
  86. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/meta.json +1 -0
  87. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/plan.mdx +195 -0
  88. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/standard.md +95 -0
  89. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/meta.json +1 -0
  90. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/plan.mdx +105 -0
  91. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/standard.md +76 -0
  92. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/canvas.mdx +81 -0
  93. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/meta.json +1 -0
  94. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/plan.mdx +145 -0
  95. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md +76 -0
  96. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json +1 -0
  97. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx +172 -0
  98. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md +100 -0
  99. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/meta.json +1 -0
  100. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/plan.mdx +67 -0
  101. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/standard.md +49 -0
  102. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/canvas.mdx +63 -0
  103. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/meta.json +1 -0
  104. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/plan.mdx +49 -0
  105. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/standard.md +39 -0
  106. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/meta.json +1 -0
  107. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/plan.mdx +118 -0
  108. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/standard.md +57 -0
  109. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/meta.json +1 -0
  110. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/plan.mdx +173 -0
  111. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/standard.md +96 -0
  112. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/meta.json +1 -0
  113. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/plan.mdx +91 -0
  114. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/standard.md +54 -0
  115. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/canvas.mdx +53 -0
  116. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/meta.json +1 -0
  117. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/plan.mdx +225 -0
  118. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/standard.md +111 -0
  119. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/theme.css +472 -0
  120. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/trail.js +216 -0
  121. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +1 -1
  122. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +37 -4
  123. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +125 -29
  124. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +196 -8
  125. package/skills/global_config/pr-recap/SKILL.md +47 -0
  126. package/skills/global_config/pr-recap/scripts/pr-recap.mjs +200 -0
  127. package/skills/global_config/quick-recap/SKILL.md +55 -0
  128. package/skills/global_config/stay-within-limits/SKILL.md +85 -0
  129. package/skills/global_config/triage/SKILL.md +3 -0
  130. package/skills/global_config/visual-edit/README.md +96 -0
  131. package/skills/global_config/visual-edit/SKILL.md +615 -0
  132. package/skills/global_config/visual-plan/README.md +93 -0
  133. package/skills/global_config/visual-plan/SKILL.md +544 -0
  134. package/skills/global_config/visual-plan/references/canvas.md +139 -0
  135. package/skills/global_config/visual-plan/references/connection.md +51 -0
  136. package/skills/global_config/visual-plan/references/document-quality.md +186 -0
  137. package/skills/global_config/visual-plan/references/exemplar.md +62 -0
  138. package/skills/global_config/visual-plan/references/local-files.md +99 -0
  139. package/skills/global_config/visual-plan/references/wireframe.md +319 -0
  140. package/skills/global_config/visual-recap/README.md +103 -0
  141. package/skills/global_config/visual-recap/SKILL.md +560 -0
  142. package/skills/global_config/visual-recap/references/connection.md +51 -0
  143. package/skills/global_config/visual-recap/references/local-files.md +99 -0
  144. package/skills/global_config/visual-recap/references/wireframe.md +319 -0
  145. package/skills/playbooks/pb-ci-fix/SKILL.md +49 -0
  146. package/skills/playbooks/pb-event-tracker/SKILL.md +45 -0
  147. package/skills/playbooks/pb-meeting-actions/SKILL.md +42 -0
  148. package/skills/playbooks/pb-project-new/SKILL.md +48 -0
  149. package/skills/playbooks/pb-week-plan/SKILL.md +45 -0
  150. package/assets/header-v4.jpg +0 -0
package/installer.js CHANGED
@@ -2609,10 +2609,7 @@ async function installOSAgentWorkspace() {
2609
2609
  if (daemonBin !== binTarget) {
2610
2610
  installStep('place the ao binary', () => {
2611
2611
  fs.mkdirSync(localBinDir, { recursive: true });
2612
- fs.copyFileSync(daemonBin, binTarget);
2613
- if (!isWin) {
2614
- fs.chmodSync(binTarget, 0o755);
2615
- }
2612
+ installBinaryAtomically(daemonBin, binTarget);
2616
2613
  log.step(`Installed ao to ${binTarget}`);
2617
2614
  }, 'AO cannot be started without its binary.');
2618
2615
  }
@@ -3320,7 +3317,16 @@ async function installMcpsForTarget(paths, ctx) {
3320
3317
  // spaces) rather than matching the first alphanumeric run anywhere in the
3321
3318
  // block, so it can't accidentally pick up a word from prose if the heading
3322
3319
  // format changes.
3323
- function parseAgentsMd(content) {
3320
+ function loadCommonSkills(dir = srcDir) {
3321
+ try {
3322
+ const reg = JSON.parse(fs.readFileSync(path.join(dir, '.agents', 'nodes.json'), 'utf8'));
3323
+ return Array.isArray(reg.common) ? reg.common.filter((s) => typeof s === 'string') : [];
3324
+ } catch {
3325
+ return [];
3326
+ }
3327
+ }
3328
+
3329
+ function parseAgentsMd(content, commonSkills = loadCommonSkills()) {
3324
3330
  const blocks = content.split(/\n## /).slice(1);
3325
3331
  const agents = [];
3326
3332
  for (const raw of blocks) {
@@ -3350,7 +3356,7 @@ function parseAgentsMd(content) {
3350
3356
  name,
3351
3357
  role: roleMatch[1].trim(),
3352
3358
  model: modelMatch ? modelMatch[1].trim() : null,
3353
- skills: extractListAfter('Primary Skills'),
3359
+ skills: [...new Set([...extractListAfter('Primary Skills'), ...commonSkills])],
3354
3360
  mcpServers: extractListAfter('MCP Servers'),
3355
3361
  output: outputMatch ? outputMatch[1].trim() : null,
3356
3362
  systemPrompt: raw.trim(),
@@ -3768,6 +3774,21 @@ function installOpencodePlugin({ targetHome = homeDir, configPath = null, data =
3768
3774
  }
3769
3775
  }
3770
3776
 
3777
+ // The plugin imports its decision logic from ./aos-hooks/ next to itself.
3778
+ const hooksSrc = path.join(srcDir, '.claude', 'hooks');
3779
+ if (fs.existsSync(hooksSrc)) {
3780
+ try {
3781
+ copyDirRecursiveSync(hooksSrc, path.join(opencodeDir, 'plugins', 'aos-hooks'));
3782
+ } catch (e) {
3783
+ log.error(`Could not install OpenCode plugin hooks (OpenCode gates inactive): ${e.message}`);
3784
+ }
3785
+ }
3786
+
3787
+ const trailLibSrc = path.join(srcDir, '.opencode', 'plugins', 'lib');
3788
+ if (fs.existsSync(trailLibSrc)) {
3789
+ try { copyDirRecursiveSync(trailLibSrc, path.join(opencodeDir, 'plugins', 'lib')); } catch (e) { log.warn(`Could not install OpenCode plugin lib: ${e.message}`); }
3790
+ }
3791
+
3771
3792
  // Slash-command payloads. Without these the /startcycle-graph command has no
3772
3793
  // native resolution and only survives as a raw-text match in the plugin.
3773
3794
  const commandsSrc = path.join(srcDir, '.opencode', 'commands');
@@ -3966,14 +3987,14 @@ function installGlobalBinaries() {
3966
3987
  // merged result goes to a .bdb-new.json sidecar -- the same recovery pattern
3967
3988
  // the MCP config merge in installMcpsForTarget uses.
3968
3989
  function mergeBdbSettingsHooks(settingsPath, { projectLocal = false } = {}) {
3969
- const bdbHookScripts = ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'trail-relay.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs'];
3990
+ const bdbHookScripts = ['go-gate.mjs', 'go-token.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'trail-relay.mjs', 'trail-autostart.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs'];
3970
3991
  // memb-inject reads the machine-global memB store under $HOME and is
3971
3992
  // installed once per machine, so it stays $HOME-anchored even inside a
3972
3993
  // project harness -- unlike the two gate hooks, which are per-checkout by
3973
3994
  // design. Pointing it at $CLAUDE_PROJECT_DIR would make it fail on every
3974
3995
  // prompt in any project the harness was never installed into. trail-relay
3975
3996
  // talks to machine-global live-map daemons, so it is anchored the same way.
3976
- const machineGlobalHooks = ['memb-inject.mjs', 'trail-relay.mjs'];
3997
+ const machineGlobalHooks = ['memb-inject.mjs', 'trail-relay.mjs', 'trail-autostart.mjs'];
3977
3998
  const isBdbEntry = (entry) => {
3978
3999
  const cmds = (entry && Array.isArray(entry.hooks) ? entry.hooks : [])
3979
4000
  .map((h) => (h && typeof h.command === 'string' ? h.command : ''))
@@ -4050,60 +4071,64 @@ function mergeBdbSettingsHooks(settingsPath, { projectLocal = false } = {}) {
4050
4071
  // ~/.gemini/config/hooks.json), preserving user-defined foreign hooks.
4051
4072
  function mergeAntigravityHooks(hooksPath, { projectLocal = false } = {}) {
4052
4073
  const bdbHookScripts = ['go-gate.mjs', 'graph-gate.mjs', 'memb-inject.mjs', 'startcycle-dispatch.mjs', 'trail-relay.mjs', 'conventional-commits.mjs', 'env-file-protection.mjs'];
4053
- const isBdbEntry = (entry) => {
4054
- const cmds = (entry && Array.isArray(entry.hooks) ? entry.hooks : [entry])
4055
- .map((h) => (h && typeof h.command === 'string' ? h.command : (typeof h === 'string' ? h : '')))
4056
- .join(' ');
4057
- return bdbHookScripts.some((name) => cmds.includes(name));
4058
- };
4074
+ const isBdbHandler = (h) => typeof h?.command === 'string' && bdbHookScripts.some((name) => h.command.includes(name));
4059
4075
 
4060
4076
  const baseDir = path.dirname(hooksPath);
4061
4077
  const hooksDir = projectLocal ? path.join(currentDir, '.agents', 'hooks') : path.join(baseDir, 'hooks');
4062
4078
  const workflowsDir = projectLocal ? path.join(currentDir, '.agents', 'workflows') : path.join(baseDir, 'workflows');
4063
4079
  const globalHooksDir = projectLocal ? path.join(homeDir, '.gemini', 'config', 'hooks') : hooksDir;
4064
4080
 
4081
+ const cmd = (dir, script, args = '') => `node "${path.join(dir, script)}"${args}`;
4065
4082
  const bdbHooks = {
4066
4083
  PreToolUse: [
4067
4084
  {
4068
4085
  matcher: "run_command|Bash",
4069
4086
  hooks: [
4070
- { type: "command", command: `node "${path.join(hooksDir, 'go-gate.mjs')}"`, timeout: 10000 },
4071
- { type: "command", command: `node "${path.join(hooksDir, 'conventional-commits.mjs')}"`, timeout: 10000 }
4087
+ { type: "command", command: cmd(hooksDir, 'go-gate.mjs'), timeout: 10 },
4088
+ { type: "command", command: cmd(hooksDir, 'conventional-commits.mjs'), timeout: 10 }
4072
4089
  ]
4073
4090
  },
4074
4091
  {
4075
4092
  matcher: "write_file|edit_file|replace|Write|Edit|MultiEdit",
4076
- hooks: [{ type: "command", command: `node "${path.join(hooksDir, 'env-file-protection.mjs')}"`, timeout: 10000 }]
4093
+ hooks: [{ type: "command", command: cmd(hooksDir, 'env-file-protection.mjs'), timeout: 10 }]
4077
4094
  },
4078
4095
  {
4079
- hooks: [{ type: "command", command: `node "${path.join(globalHooksDir, 'trail-relay.mjs')}" --agent agy --event PreToolUse`, timeout: 2000 }]
4096
+ matcher: "*",
4097
+ hooks: [{ type: "command", command: cmd(globalHooksDir, 'trail-relay.mjs', ' --agent agy --event PreToolUse'), timeout: 2 }]
4080
4098
  }
4081
4099
  ],
4082
4100
  Stop: [
4083
- {
4084
- hooks: [{ type: "command", command: `node "${path.join(hooksDir, 'graph-gate.mjs')}"`, timeout: 10000 }]
4085
- },
4086
- {
4087
- hooks: [{ type: "command", command: `node "${path.join(globalHooksDir, 'trail-relay.mjs')}" --agent agy --event Stop`, timeout: 2000 }]
4088
- }
4101
+ { type: "command", command: cmd(hooksDir, 'graph-gate.mjs'), timeout: 10 },
4102
+ { type: "command", command: cmd(globalHooksDir, 'trail-relay.mjs', ' --agent agy --event Stop'), timeout: 2 }
4089
4103
  ],
4090
4104
  PreInvocation: [
4091
- {
4092
- hooks: [
4093
- { type: "command", command: `node "${path.join(globalHooksDir, 'memb-inject.mjs')}"`, timeout: 8000 },
4094
- { type: "command", command: `node "${path.join(workflowsDir, 'startcycle-dispatch.mjs')}"`, timeout: 30000 }
4095
- ]
4096
- }
4105
+ { type: "command", command: cmd(globalHooksDir, 'memb-inject.mjs'), timeout: 8 },
4106
+ { type: "command", command: cmd(workflowsDir, 'startcycle-dispatch.mjs'), timeout: 30 }
4097
4107
  ]
4098
4108
  };
4099
4109
 
4110
+ // Drops BDB handlers (old wrapped or current shape), keeps foreign ones, and
4111
+ // rewraps flat events so previously written `{ hooks: [...] }` entries migrate.
4112
+ const keepForeign = (event, entries) => {
4113
+ const out = [];
4114
+ for (const e of Array.isArray(entries) ? entries : []) {
4115
+ const nested = e && Array.isArray(e.hooks);
4116
+ if (bdbHooks[event][0].hooks) {
4117
+ if (!nested) { if (!isBdbHandler(e)) out.push(e); continue; }
4118
+ const kept = e.hooks.filter((h) => !isBdbHandler(h));
4119
+ if (kept.length) out.push({ ...e, hooks: kept });
4120
+ } else {
4121
+ out.push(...(nested ? e.hooks : [e]).filter((h) => !isBdbHandler(h)));
4122
+ }
4123
+ }
4124
+ return out;
4125
+ };
4126
+
4100
4127
  const buildMerged = (existing) => {
4101
4128
  const merged = existing && typeof existing === 'object' ? existing : {};
4102
4129
  merged.hooks = merged.hooks && typeof merged.hooks === 'object' ? merged.hooks : {};
4103
4130
  for (const [event, entries] of Object.entries(bdbHooks)) {
4104
- const foreignEntries = (Array.isArray(merged.hooks[event]) ? merged.hooks[event] : [])
4105
- .filter((e) => !isBdbEntry(e));
4106
- merged.hooks[event] = [...foreignEntries, ...entries];
4131
+ merged.hooks[event] = [...keepForeign(event, merged.hooks[event]), ...entries];
4107
4132
  }
4108
4133
  return merged;
4109
4134
  };
@@ -4135,7 +4160,9 @@ function mergeAntigravityHooks(hooksPath, { projectLocal = false } = {}) {
4135
4160
 
4136
4161
  try {
4137
4162
  fs.mkdirSync(path.dirname(hooksPath), { recursive: true });
4138
- fs.writeFileSync(hooksPath, JSON.stringify(buildMerged(existing), null, 2) + '\n');
4163
+ const tmpPath = `${hooksPath}.${process.pid}.tmp`;
4164
+ fs.writeFileSync(tmpPath, JSON.stringify(buildMerged(existing), null, 2) + '\n');
4165
+ fs.renameSync(tmpPath, hooksPath);
4139
4166
  } catch (e) {
4140
4167
  log.warn(`Could not write ${hooksPath}: ${e.message}`);
4141
4168
  }
@@ -4510,6 +4537,107 @@ function shouldAutostartLaunchpad(overrides = {}) {
4510
4537
  return isDevEnvironment(overrides);
4511
4538
  }
4512
4539
 
4540
+ // Plan Canvas autostart is strictly opt-in (--autostart-plan-canvas): unlike
4541
+ // the launchpad opener it is NOT implied by a dev checkout. The server never
4542
+ // idle-exits there (AOS_PLAN_CANVAS_IDLE_MS=0 is honoured by server.js).
4543
+ const PLAN_CANVAS_LABEL = 'com.bdb.plan-canvas';
4544
+
4545
+ function shouldAutostartPlanCanvas(overrides = {}) {
4546
+ const argv = overrides.argv || process.argv;
4547
+ return argv.includes('--autostart-plan-canvas');
4548
+ }
4549
+
4550
+ function buildPlanCanvasPlist({ nodeBin, scriptPath, home, logDir }) {
4551
+ return `<?xml version="1.0" encoding="UTF-8"?>
4552
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4553
+ <plist version="1.0">
4554
+ <dict>
4555
+ <key>Label</key>
4556
+ <string>${PLAN_CANVAS_LABEL}</string>
4557
+ <key>ProgramArguments</key>
4558
+ <array>
4559
+ <string>${nodeBin}</string>
4560
+ <string>${scriptPath}</string>
4561
+ <string>server</string>
4562
+ </array>
4563
+ <key>EnvironmentVariables</key>
4564
+ <dict>
4565
+ <key>AOS_PLAN_CANVAS_IDLE_MS</key>
4566
+ <string>0</string>
4567
+ </dict>
4568
+ <key>WorkingDirectory</key>
4569
+ <string>${home}</string>
4570
+ <key>RunAtLoad</key>
4571
+ <true/>
4572
+ <key>KeepAlive</key>
4573
+ <true/>
4574
+ <key>ThrottleInterval</key>
4575
+ <integer>30</integer>
4576
+ <key>StandardOutPath</key>
4577
+ <string>${path.join(logDir, 'plan-canvas.stdout.log')}</string>
4578
+ <key>StandardErrorPath</key>
4579
+ <string>${path.join(logDir, 'plan-canvas.stderr.log')}</string>
4580
+ </dict>
4581
+ </plist>`;
4582
+ }
4583
+
4584
+ // Same bat + hidden-vbs-in-Startup mechanism as the OpenWiki visualizer.
4585
+ function buildPlanCanvasWinFiles({ nodeBin, scriptPath, home, logDir, batPath }) {
4586
+ const out = path.join(logDir, 'plan-canvas.stdout.log');
4587
+ const err = path.join(logDir, 'plan-canvas.stderr.log');
4588
+ return {
4589
+ bat: `@echo off\r\ncd /d "${home}"\r\nset AOS_PLAN_CANVAS_IDLE_MS=0\r\n"${nodeBin}" "${scriptPath}" server >> "${out}" 2>> "${err}"\r\n`,
4590
+ vbs: `Set WshShell = CreateObject("WScript.Shell")\r\nWshShell.CurrentDirectory = "${home}"\r\nWshShell.Run """${batPath}""", 0, False\r\n`,
4591
+ };
4592
+ }
4593
+
4594
+ function installPlanCanvasAutostart(overrides = {}) {
4595
+ if (!shouldAutostartPlanCanvas(overrides)) return false;
4596
+ if ((overrides.dryRun ?? DRY_RUN)) {
4597
+ log.step('[dry-run] would register the Plan Canvas background server (Port 4519, no idle exit)');
4598
+ return false;
4599
+ }
4600
+ const home = overrides.homeDir || homeDir;
4601
+ const platform = overrides.platform || process.platform;
4602
+ const scriptPath = [
4603
+ path.join(home, '.agents', 'skills', 'plan-canvas', 'scripts', 'plan-canvas.js'),
4604
+ path.join(srcDir, 'skills', 'global_config', 'plan-canvas', 'scripts', 'plan-canvas.js'),
4605
+ ].find(p => fs.existsSync(p));
4606
+ if (!scriptPath) {
4607
+ log.warn('Plan Canvas autostart skipped: plan-canvas.js not found. Start it with: aos-plan-canvas server');
4608
+ return false;
4609
+ }
4610
+ const nodeBin = overrides.nodeBin || process.execPath;
4611
+ const logDir = path.join(home, '.agents', 'logs');
4612
+ try {
4613
+ fs.mkdirSync(logDir, { recursive: true });
4614
+ if (platform === 'darwin') {
4615
+ const plistPath = path.join(home, 'Library', 'LaunchAgents', `${PLAN_CANVAS_LABEL}.plist`);
4616
+ fs.mkdirSync(path.dirname(plistPath), { recursive: true });
4617
+ fs.writeFileSync(plistPath, buildPlanCanvasPlist({ nodeBin, scriptPath, home, logDir }));
4618
+ if (!overrides.skipLaunchctl) {
4619
+ execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
4620
+ execSync(`launchctl load -w "${plistPath}" 2>/dev/null || true`, { stdio: 'ignore' });
4621
+ }
4622
+ log.success('Plan Canvas LaunchAgent registered (Port 4519)');
4623
+ } else if (platform === 'win32') {
4624
+ const startupDir = path.join(overrides.appData || process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'Microsoft', 'Windows', 'Start Menu', 'Programs', 'Startup');
4625
+ fs.mkdirSync(startupDir, { recursive: true });
4626
+ const batPath = path.join(home, '.agents', 'run-plan-canvas.bat');
4627
+ const vbsPath = path.join(startupDir, `${PLAN_CANVAS_LABEL}.vbs`);
4628
+ const files = buildPlanCanvasWinFiles({ nodeBin, scriptPath, home, logDir, batPath });
4629
+ fs.writeFileSync(batPath, files.bat, 'utf-8');
4630
+ fs.writeFileSync(vbsPath, files.vbs, 'utf-8');
4631
+ if (!overrides.skipLaunchctl) spawn('wscript.exe', [vbsPath], { detached: true, stdio: 'ignore' }).unref();
4632
+ log.success('Plan Canvas Startup entry registered (Port 4519)');
4633
+ } else {
4634
+ log.step('Plan Canvas autostart is only wired for macOS and Windows. Start it with: aos-plan-canvas server');
4635
+ return false;
4636
+ }
4637
+ return true;
4638
+ } catch (e) { logDebug(e, 'plan canvas autostart'); return false; }
4639
+ }
4640
+
4513
4641
  function generateAndOpenLaunchpad(installedModules = []) {
4514
4642
  if (DRY_RUN) {
4515
4643
  log.step('[dry-run] would generate & open BDB Launchpad HTML');
@@ -4645,6 +4773,16 @@ function generateAndOpenLaunchpad(installedModules = []) {
4645
4773
  .card.offline .card-hint {
4646
4774
  display: block;
4647
4775
  }
4776
+ .card-hint code {
4777
+ font-family: var(--mono);
4778
+ font-style: normal;
4779
+ color: #ffffff;
4780
+ background: #0a0a0a;
4781
+ border: 1px solid var(--accent);
4782
+ border-radius: 4px;
4783
+ padding: 1px 6px;
4784
+ cursor: copy;
4785
+ }
4648
4786
  .footer {
4649
4787
  margin-top: 28px;
4650
4788
  text-align: center;
@@ -4835,6 +4973,25 @@ function generateAndOpenLaunchpad(installedModules = []) {
4835
4973
  <span class="status-dot" id="dot-store" title="Checking..."></span>
4836
4974
  </div>
4837
4975
  </a>
4976
+ <a class="card" href="http://127.0.0.1:4519/" target="_blank">
4977
+ <div class="card-content">
4978
+ <div class="card-icon"><svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
4979
+ <defs><linearGradient id="grad-plancanvas" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#FFFFFF"/><stop offset="100%" stop-color="#9b30c4"/></linearGradient></defs>
4980
+ <rect x="18" y="22" width="64" height="56" rx="4" fill="url(#grad-plancanvas)" opacity="0.85"/>
4981
+ <rect x="26" y="30" width="22" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/><rect x="52" y="30" width="22" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/>
4982
+ <line x1="26" y1="54" x2="74" y2="54" stroke="#0a0a0a" stroke-width="2.5" opacity="0.5"/><line x1="26" y1="64" x2="56" y2="64" stroke="#0a0a0a" stroke-width="2.5" opacity="0.5"/>
4983
+ </svg></div>
4984
+ <div class="card-info">
4985
+ <h2>Plan Canvas</h2>
4986
+ <p>Review plans in the browser: annotate, chat, approve</p>
4987
+ <div class="card-hint">offline — run: <code data-copy="aos-plan-canvas server" title="Click to copy">aos-plan-canvas server</code></div>
4988
+ </div>
4989
+ </div>
4990
+ <div class="card-meta">
4991
+ <span class="port-pill">:4519</span>
4992
+ <span class="status-dot" id="dot-plancanvas" title="Checking..."></span>
4993
+ </div>
4994
+ </a>
4838
4995
  </div>
4839
4996
  <div class="footer">
4840
4997
  <span>Autostart Daemons • 127.0.0.1</span>
@@ -4862,7 +5019,26 @@ function generateAndOpenLaunchpad(installedModules = []) {
4862
5019
  checkHealth('http://127.0.0.1:9080', 'dot-remote');
4863
5020
  checkHealth('http://127.0.0.1:4321', 'dot-openwiki');
4864
5021
  checkHealth('http://127.0.0.1:4322', 'dot-store');
4865
- }
5022
+ checkHealth('http://127.0.0.1:4519', 'dot-plancanvas');
5023
+ }
5024
+ document.addEventListener('click', (ev) => {
5025
+ const el = ev.target.closest && ev.target.closest('[data-copy]');
5026
+ if (!el) return;
5027
+ ev.preventDefault();
5028
+ ev.stopPropagation();
5029
+ const text = el.getAttribute('data-copy');
5030
+ const fallback = () => {
5031
+ const ta = document.createElement('textarea');
5032
+ ta.value = text;
5033
+ document.body.appendChild(ta);
5034
+ ta.select();
5035
+ try { document.execCommand('copy'); } catch (e) {}
5036
+ ta.remove();
5037
+ };
5038
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).catch(fallback);
5039
+ else fallback();
5040
+ el.title = 'Copied';
5041
+ }, true);
4866
5042
  checkAllHealth();
4867
5043
  setInterval(checkAllHealth, 5000);
4868
5044
  </script>
@@ -5075,6 +5251,7 @@ async function universalHarnessSync(primaryMcpConfigPath, installedModules = [])
5075
5251
  }
5076
5252
  log.success('Universal Sync Complete!');
5077
5253
  generateAndOpenLaunchpad(installedModules);
5254
+ installPlanCanvasAutostart();
5078
5255
  }
5079
5256
 
5080
5257
  async function runQuickUpdate(installState) {
@@ -5241,7 +5418,41 @@ function installAosCli() {
5241
5418
  }
5242
5419
  }
5243
5420
 
5421
+ // A running daemon may execute dest; overwriting its inode keeps stale code-signature
5422
+ // state on macOS and every new exec dies with SIGKILL. Rename gives dest a fresh inode.
5423
+ function installBinaryAtomically(src, dest) {
5424
+ const tmp = path.join(path.dirname(dest), `.${path.basename(dest)}.${process.pid}.${Date.now()}.tmp`);
5425
+ try {
5426
+ fs.copyFileSync(src, tmp);
5427
+ if (process.platform !== 'win32') fs.chmodSync(tmp, 0o755);
5428
+ fs.renameSync(tmp, dest);
5429
+ } catch (e) {
5430
+ try { fs.unlinkSync(tmp); } catch (_) {}
5431
+ throw e;
5432
+ }
5433
+ }
5434
+
5244
5435
  async function main() {
5436
+ const args = process.argv.slice(2);
5437
+ if (args.includes('--version') || args.includes('-V')) {
5438
+ console.log(pkg.version);
5439
+ return;
5440
+ }
5441
+ if (args.includes('--help') || args.includes('-h')) {
5442
+ console.log(`Usage: aos [command] [options]
5443
+
5444
+ Commands:
5445
+ store Browse and install skills
5446
+ doctor|checkup Check the installation
5447
+
5448
+ Options:
5449
+ -y, --yes Non-interactive install (implied without a TTY)
5450
+ --dry-run Show what would change
5451
+ --verbose, -v Verbose output
5452
+ -V, --version Print the version and exit
5453
+ -h, --help Print this help and exit`);
5454
+ return;
5455
+ }
5245
5456
  if (process.argv[2] === 'store') {
5246
5457
  const storeScript = path.join(srcDir, 'bin', 'aos-store.mjs');
5247
5458
  const result = spawnSync(process.execPath, [storeScript, ...process.argv.slice(3)], { stdio: 'inherit' });
@@ -5695,6 +5906,7 @@ if (require.main === module) {
5695
5906
 
5696
5907
  // Exported for tests -- requiring installer.js must not launch the TUI.
5697
5908
  module.exports = {
5909
+ installBinaryAtomically,
5698
5910
  installOSAgentWorkspace,
5699
5911
  downloadOrUpdateModule,
5700
5912
  detectPlatforms,
@@ -5726,6 +5938,7 @@ module.exports = {
5726
5938
  INSTALL_MANIFEST_PATH,
5727
5939
  // Agent compilers & pipeline helpers
5728
5940
  parseAgentsMd,
5941
+ loadCommonSkills,
5729
5942
  compileClaudeAgents,
5730
5943
  compileOpenCodeAgents,
5731
5944
  compileCodexAgents,
@@ -5736,6 +5949,10 @@ module.exports = {
5736
5949
  generateAndOpenLaunchpad,
5737
5950
  shouldOpenLaunchpad,
5738
5951
  shouldAutostartLaunchpad,
5952
+ shouldAutostartPlanCanvas,
5953
+ buildPlanCanvasPlist,
5954
+ buildPlanCanvasWinFiles,
5955
+ installPlanCanvasAutostart,
5739
5956
  isDevEnvironment,
5740
5957
  LAUNCHPAD_WEB_MODULES,
5741
5958
  LAUNCHPAD_OPEN_FLAGS,
@@ -64,16 +64,15 @@ async function init() {
64
64
 
65
65
  const customRulebook = path.resolve(process.cwd(), 'rulebook.yaml');
66
66
  const defaultRulebook = path.resolve(__dirname, '../core/rulebook.default.yaml');
67
- const rulebookPath = fs.existsSync(customRulebook) ? customRulebook : defaultRulebook;
68
67
 
69
68
  try {
70
- if (fs.existsSync(rulebookPath)) {
71
- rules = loadRulebook(rulebookPath);
72
- const now = Date.now();
73
- offerTable = await buildOfferTable(rules, inventory, now);
74
- }
69
+ rules = await loadRulebook({
70
+ defaultsPath: defaultRulebook,
71
+ overridePath: fs.existsSync(customRulebook) ? customRulebook : undefined,
72
+ });
73
+ offerTable = await buildOfferTable(rules, inventory, Date.now());
75
74
  } catch (e) {
76
- console.error(`[mcsc-mcp] Warning: could not load rulebook from ${rulebookPath}: ${e.message}`);
75
+ console.error(`[mcsc-mcp] Warning: could not load rulebook from ${defaultRulebook}: ${e.message}`);
77
76
  }
78
77
  }
79
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hybridlabor-api/aos",
3
- "version": "4.13.2",
3
+ "version": "4.14.0",
4
4
  "description": "AOS — A Curated AI AGENT OS. Optimized agent skills and add-ons like memB, OpenWiki, Heimdall Token Saver, and Godmode architectures.",
5
5
  "main": "installer.js",
6
6
  "engines": {
@@ -18,7 +18,8 @@
18
18
  "aos-config": "bin/aos-config.mjs",
19
19
  "aos-store": "bin/aos-store.mjs",
20
20
  "aos-doctor": "bin/aos-doctor.mjs",
21
- "aos-uninstall": "bin/aos-uninstall.mjs"
21
+ "aos-uninstall": "bin/aos-uninstall.mjs",
22
+ "aos-acp": "bin/aos-acp.mjs"
22
23
  },
23
24
  "scripts": {
24
25
  "validate": "node scripts/validate-skills.mjs",
@@ -27,7 +28,7 @@
27
28
  "plugin:build": "node scripts/build-plugin-manifest.mjs",
28
29
  "plugin:check": "node scripts/build-plugin-manifest.mjs --check",
29
30
  "doctor": "node bin/aos-doctor.mjs",
30
- "test": "node scripts/validate-skills.mjs --selftest && node scripts/validate-skills.mjs && node scripts/build-plugin-manifest.mjs --check && node --test tests/opencode-graph-gate.test.js && node --test tests/cross-harness-hooks.test.js && node tests/aos-store.test.mjs && node --test tests/aos-store-ui.test.mjs && node tests/aos-store-scenario.test.mjs && node tests/aos-doctor.test.mjs && node tests/build-plugin-manifest.test.mjs && node --test tests/agent-models.test.js && node --test tests/installer-p3.test.js"
31
+ "test": "node scripts/validate-skills.mjs --selftest && node scripts/validate-skills.mjs && node scripts/build-plugin-manifest.mjs --check && node --test tests/opencode-graph-gate.test.js && node --test tests/cross-harness-hooks.test.js && node --test tests/plan-canvas-security.test.mjs && node --test tests/plan-canvas-modes.test.mjs && node --test tests/swarm-wiring.test.js && node tests/aos-store.test.mjs && node --test tests/aos-store-ui.test.mjs && node tests/aos-store-scenario.test.mjs && node tests/aos-doctor.test.mjs && node tests/build-plugin-manifest.test.mjs && node --test tests/agent-models.test.js && node --test tests/installer-p3.test.js && node --test tests/installer-binary.test.js && node --test tests/installer-version.test.js && node --test tests/mcsc-server.test.js && node --test tests/antigravity-hooks.test.js && node --test tests/plan-builder.test.mjs && node --test tests/plan-builder-trail.test.mjs && node --test tests/plan-builder-showcase.test.mjs && node --test tests/plan-builder-templates.test.mjs && node --test tests/plan-canvas-home.test.mjs && node --test tests/launchpad-installer.test.js && node --test tests/launchpad-plan-canvas.test.js && node --test tests/go-token.test.js && node --test tests/master-session-skill.test.js && node --test tests/aos-acp.test.js && node --test tests/opencode-go-token.test.js && node --test tests/opencode-adapter.test.js && node --test tests/pr-recap.test.mjs && node --test tests/bdb-visual-edit.test.mjs && node --test tests/opencode-trail-autostart.test.mjs && node --test tests/trail-autostart-hook.test.mjs && node --test tests/agenttrail-ensure.test.mjs"
31
32
  },
32
33
  "publishConfig": {
33
34
  "access": "public"
@@ -241,6 +241,45 @@ function checkSkillRefs(text, file, known, self, findings) {
241
241
  }
242
242
  }
243
243
 
244
+ // Playbooks are skills with `kind: playbook`; skills without `kind` are untouched.
245
+ const PB_REQUIRED = ['trigger', 'inputs', 'requires', 'go_points', 'outputs', 'verify', 'difficulty', 'est_time', 'disable-model-invocation'];
246
+ const DIFFICULTY = new Set(['beginner', 'intermediate', 'advanced']);
247
+ const EXTERNAL = ' (external)';
248
+
249
+ export function checkPlaybook(byKey, known) {
250
+ const out = [];
251
+ if (!byKey.has('kind')) return out;
252
+ const add = (code, msg, key) => out.push({ level: 'error', code, line: byKey.get(key)?.line, msg });
253
+ const kind = unquote(byKey.get('kind').value);
254
+ if (kind !== 'playbook') {
255
+ add('E-PB01', `\`kind: ${kind}\` is unknown — the only defined kind is \`playbook\``, 'kind');
256
+ return out;
257
+ }
258
+ for (const k of PB_REQUIRED) if (!byKey.has(k)) add('E-PB03', `playbook is missing required key \`${k}\``, 'kind');
259
+
260
+ const req = byKey.get('requires');
261
+ if (req) {
262
+ const m = /(?:^|\s)skills:\s*\[([^\]]*)\]/.exec(req.value);
263
+ if (!m) add('E-PB05', '`requires.skills` must be a flow-style list, e.g. `skills: [github, ci-pipeline]`', 'requires');
264
+ else {
265
+ for (const raw of m[1].split(',').map((e) => unquote(e)).filter(Boolean)) {
266
+ if (!raw.endsWith(EXTERNAL) && !known.has(raw)) {
267
+ add('E-PB02', `requires skill \`${raw}\`, which is not a skill in this repo — fix the name or mark it \`${raw}${EXTERNAL}\``, 'requires');
268
+ }
269
+ }
270
+ }
271
+ }
272
+ const diff = byKey.get('difficulty');
273
+ if (diff && !DIFFICULTY.has(unquote(diff.value))) {
274
+ add('E-PB04', `\`difficulty: ${unquote(diff.value)}\` is not one of: ${[...DIFFICULTY].join(', ')}`, 'difficulty');
275
+ }
276
+ const dmi = byKey.get('disable-model-invocation');
277
+ if (dmi && unquote(dmi.value) !== 'true') {
278
+ add('E-PB06', 'playbooks are user-invoked only — set `disable-model-invocation: true`', 'disable-model-invocation');
279
+ }
280
+ return out;
281
+ }
282
+
244
283
  function validate() {
245
284
  const { skills, strays } = collect();
246
285
  const known = new Set(skills.map((f) => basename(dirname(f))));
@@ -311,6 +350,8 @@ function validate() {
311
350
  }
312
351
  }
313
352
 
353
+ for (const f of checkPlaybook(byKey, known)) findings.push({ ...f, file: rel });
354
+
314
355
  checkLeakedPaths(text, rel, findings);
315
356
  checkLocalRefs(text, dir, rel, findings);
316
357
  checkSkillRefs(text, rel, known, dirName, findings);
@@ -461,6 +502,41 @@ function selftest() {
461
502
  assert.ok(isValidSkillName('a'.repeat(NAME_MAX)), 'a 64-char name is at the limit and valid');
462
503
  assert.ok(!isValidSkillName('a'.repeat(NAME_MAX + 1)), 'a 65-char name is over the limit');
463
504
 
505
+ // Playbook frontmatter (kind: playbook).
506
+ const pb = (over = {}, drop = []) => {
507
+ const base = {
508
+ name: 'pb-x', description: 'd', category: 'bdb-core', kind: 'playbook',
509
+ trigger: '["a"]', inputs: '[repo]',
510
+ requires: '\n skills: [github, gh (external)]\n agents: []\n mcps: []\n store: []',
511
+ go_points: '[]', outputs: '["x"]', verify: '"x"', difficulty: 'beginner',
512
+ est_time: '5 min', 'disable-model-invocation': 'true', ...over,
513
+ };
514
+ const lines = Object.entries(base).filter(([k]) => !drop.includes(k)).map(([k, v]) => `${k}: ${v}`);
515
+ const fm = scanFrontmatter(['---', ...lines, '---'].join('\n'));
516
+ return new Map(fm.keys.map((k) => [k.key, k]));
517
+ };
518
+ const known = new Set(['github', 'ci-pipeline']);
519
+ assert.deepEqual(checkPlaybook(pb(), known), [], 'valid playbook is clean');
520
+ const miss = checkPlaybook(pb({}, ['go_points']), known);
521
+ assert.equal(miss.length, 1);
522
+ assert.equal(miss[0].code, 'E-PB03');
523
+ assert.match(miss[0].msg, /go_points/);
524
+ assert.equal(checkPlaybook(pb({ difficulty: 'expert' }), known)[0].code, 'E-PB04');
525
+ const unk = checkPlaybook(pb({ requires: '\n skills: [github, nope-skill, gh (external)]' }), known);
526
+ assert.equal(unk.length, 1);
527
+ assert.equal(unk[0].code, 'E-PB02');
528
+ assert.match(unk[0].msg, /nope-skill/);
529
+ const kind = checkPlaybook(pb({ kind: 'recipe' }), known);
530
+ assert.equal(kind.length, 1);
531
+ assert.equal(kind[0].code, 'E-PB01');
532
+ assert.equal(checkPlaybook(pb({ requires: '\n skills:\n - github' }), known)[0].code, 'E-PB05');
533
+ assert.equal(checkPlaybook(pb({ 'disable-model-invocation': 'false' }), known)[0].code, 'E-PB06');
534
+ assert.deepEqual(checkPlaybook(pb({}, ['kind', 'trigger', 'inputs', 'requires', 'go_points', 'outputs', 'verify', 'difficulty', 'est_time', 'disable-model-invocation']), known).length, 0, 'no kind, no playbook rules');
535
+ assert.deepEqual(
536
+ checkPlaybook(pb({ requires: '\n skills: ["gh (external)", github,]' }), known), [],
537
+ 'quoted item and trailing comma survive',
538
+ );
539
+
464
540
  console.log('✓ selftest passed');
465
541
  }
466
542
 
@@ -76,7 +76,7 @@ Define hardware and software nodes as strict bounded contexts:
76
76
 
77
77
  ## 5. Mandatory — Plan Canvas Review
78
78
 
79
- Before this session concludes, run `aos-plan-canvas open <file>` against `signal-flow.md` (or the combined show-control spec), then `aos-plan-canvas await <file>` and leave it running. The user reviews the signal flow diagram, hardware topology, and failover matrix in the browser (Mermaid renders live, click-to-annotate, chat rail). Do not consider the show architecture finalized before an `approve` verdict. A `request_changes` verdict means revise the artifact and reopen — it live-reloads. This is a plain CLI tool, identical regardless of which agent harness runs this skill. See the `plan-canvas` skill. If a build follows the approve, Trigger A starts the live map (`agenttrail` skill); `aos-archify` can render the signal flow as a workflow or architecture diagram for the review.
79
+ Before this session concludes, choose the planning mode by running `aos-plan-canvas modes` and presenting available modes to the user (see the "Planning mode choice" section in the `plan-canvas` skill). Then run `aos-plan-canvas open <file> --mode <choice>` against `signal-flow.md` (or the combined show-control spec), then `aos-plan-canvas await <file>` and leave it running. The user reviews the signal flow diagram, hardware topology, and failover matrix in the browser (Mermaid renders live, click-to-annotate, chat rail). Do not consider the show architecture finalized before an `approve` verdict. A `request_changes` verdict means revise the artifact and reopen — it live-reloads. This is a plain CLI tool, identical regardless of which agent harness runs this skill. See the `plan-canvas` skill. If a build follows the approve, Trigger A starts the live map (`agenttrail` skill); `aos-archify` can render the signal flow as a workflow or architecture diagram for the review.
80
80
 
81
81
  ---
82
82
 
@@ -41,6 +41,9 @@ This Godmode extends beyond standard software development. It STRICTLY governs c
41
41
  * **MediaStorm Deployments:** When executing `/bdbmediastorm` for TouchDesigner, Unreal Engine, or Adobe Suite workflows, you must enforce rigorous release management for `.tox` files, Unreal Blueprints, and showfiles.
42
42
  * **Show-Ready Validation:** Never push a creative-tech update to a live production environment (e.g., a running installation or live show) without a verified fallback or backup showfile.
43
43
 
44
+ ## Visual recap
45
+ After the gates pass, `/pr-recap` can build a Plan Builder recap page of the diff with the Verified and Not verified checks. It is informational and does not gate the release. Posting it anywhere stays a human decision.
46
+
44
47
  ## Universal Agent Harness Integration
45
48
  This Godmode is universally compatible and governs all extensions, including the BDB Creator Engine.
46
49
  * **Cursor:** Auto-injected via `.cursor/rules/godmode-shipping.mdc`.