@hybridlabor-api/aos 4.13.2 → 4.14.1

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 (151) 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/hooks/trail-relay.mjs +1 -0
  10. package/.claude/settings.json +22 -4
  11. package/.claude/workflows/startcycle-dispatch.mjs +11 -4
  12. package/.opencode/plugins/bdb-aos.js +98 -121
  13. package/.opencode/plugins/lib/trail-autostart.js +38 -0
  14. package/CLAUDE.md +1 -1
  15. package/README.de.md +6 -6
  16. package/README.md +6 -6
  17. package/README.pt.md +6 -6
  18. package/THIRD_PARTY_NOTICES.md +19 -3
  19. package/assets/header-v5.png +0 -0
  20. package/bin/aos-acp.mjs +211 -0
  21. package/bin/aos-doctor.mjs +1 -1
  22. package/bin/aos-uninstall.mjs +2 -2
  23. package/docs/master-session-acp.md +51 -0
  24. package/installer.js +314 -42
  25. package/mcps/mcsc/packages/mcp/server.js +6 -7
  26. package/package.json +4 -3
  27. package/scripts/validate-skills.mjs +76 -0
  28. package/skills/basic/bdbmediastorm/SKILL.md +1 -1
  29. package/skills/basic/godmode-shipping/SKILL.md +3 -0
  30. package/skills/basic/master-session/SKILL.md +89 -0
  31. package/skills/basic/startcycle/SKILL.md +1 -1
  32. package/skills/basic/startcycle-graph/SKILL.md +2 -2
  33. package/skills/basic/startcycle-graph-user/SKILL.md +1 -1
  34. package/skills/basic/teamwork-preview/SKILL.md +1 -1
  35. package/skills/bdbrainstorm/SKILL.md +7 -1
  36. package/skills/global_config/agentic-harness-patterns/SKILL.md +257 -0
  37. package/skills/global_config/agentic-harness-patterns/metadata.json +10 -0
  38. package/skills/global_config/agentic-harness-patterns/references/agent-orchestration-pattern.md +97 -0
  39. package/skills/global_config/agentic-harness-patterns/references/bootstrap-sequence-pattern.md +106 -0
  40. package/skills/global_config/agentic-harness-patterns/references/context-engineering/compress-pattern.md +78 -0
  41. package/skills/global_config/agentic-harness-patterns/references/context-engineering/isolate-pattern.md +82 -0
  42. package/skills/global_config/agentic-harness-patterns/references/context-engineering/select-pattern.md +86 -0
  43. package/skills/global_config/agentic-harness-patterns/references/context-engineering-pattern.md +29 -0
  44. package/skills/global_config/agentic-harness-patterns/references/hook-lifecycle-pattern.md +111 -0
  45. package/skills/global_config/agentic-harness-patterns/references/memory-persistence-pattern.md +109 -0
  46. package/skills/global_config/agentic-harness-patterns/references/permission-gate-pattern.md +111 -0
  47. package/skills/global_config/agentic-harness-patterns/references/skill-runtime-pattern.md +104 -0
  48. package/skills/global_config/agentic-harness-patterns/references/task-decomposition-pattern.md +92 -0
  49. package/skills/global_config/agentic-harness-patterns/references/tool-registry-pattern.md +101 -0
  50. package/skills/global_config/agenttrail/SKILL.md +8 -0
  51. package/skills/global_config/agenttrail/bin/agenttrail.mjs +14 -0
  52. package/skills/global_config/agenttrail/bin/ensure.mjs +118 -0
  53. package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
  54. package/skills/global_config/bdb-visual-edit/SKILL.md +51 -0
  55. package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +59 -0
  56. package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +27 -0
  57. package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +123 -0
  58. package/skills/global_config/factory-collect/SKILL.md +74 -0
  59. package/skills/global_config/factory-human-digest/SKILL.md +92 -0
  60. package/skills/global_config/factory-lookback/SKILL.md +95 -0
  61. package/skills/global_config/factory-review-prs/SKILL.md +63 -0
  62. package/skills/global_config/git-pr-review/SKILL.md +3 -0
  63. package/skills/global_config/grilling/SKILL.md +2 -0
  64. package/skills/global_config/mcsc/SKILL.md +1 -1
  65. package/skills/global_config/plan-arbiter/SKILL.md +125 -0
  66. package/skills/global_config/plan-canvas/SKILL.md +62 -5
  67. package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +19 -3
  68. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +285 -0
  69. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/agent-trail.js +129 -0
  70. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/board-client.js +124 -0
  71. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/canvas.mdx +19 -0
  72. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/plan.mdx +18 -0
  73. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/recap-demo/plan.mdx +72 -0
  74. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/README.md +29 -0
  75. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/architecture.json +30 -0
  76. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/00_architecture.html +14950 -0
  77. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/canvas.mdx +511 -0
  78. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/plan.mdx +208 -0
  79. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/recap/plan.mdx +102 -0
  80. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/standard/plan.md +136 -0
  81. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/canvas.mdx +124 -0
  82. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/plan.mdx +37 -0
  83. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/index.js +188 -0
  84. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/kit.js +123 -0
  85. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/mdx.js +411 -0
  86. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +1291 -0
  87. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/meta.json +1 -0
  88. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/plan.mdx +195 -0
  89. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/standard.md +95 -0
  90. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/meta.json +1 -0
  91. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/plan.mdx +105 -0
  92. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/standard.md +76 -0
  93. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/canvas.mdx +81 -0
  94. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/meta.json +1 -0
  95. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/plan.mdx +145 -0
  96. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md +76 -0
  97. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json +1 -0
  98. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx +172 -0
  99. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md +100 -0
  100. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/meta.json +1 -0
  101. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/plan.mdx +67 -0
  102. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/standard.md +49 -0
  103. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/canvas.mdx +63 -0
  104. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/meta.json +1 -0
  105. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/plan.mdx +49 -0
  106. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/standard.md +39 -0
  107. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/meta.json +1 -0
  108. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/plan.mdx +118 -0
  109. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/standard.md +57 -0
  110. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/meta.json +1 -0
  111. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/plan.mdx +173 -0
  112. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/standard.md +96 -0
  113. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/meta.json +1 -0
  114. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/plan.mdx +91 -0
  115. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/standard.md +54 -0
  116. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/canvas.mdx +53 -0
  117. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/meta.json +1 -0
  118. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/plan.mdx +225 -0
  119. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/standard.md +111 -0
  120. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/theme.css +472 -0
  121. package/skills/global_config/plan-canvas/scripts/lib/plan-builder/trail.js +216 -0
  122. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +1 -1
  123. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +37 -4
  124. package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +125 -29
  125. package/skills/global_config/plan-canvas/scripts/plan-canvas.js +196 -8
  126. package/skills/global_config/pr-recap/SKILL.md +47 -0
  127. package/skills/global_config/pr-recap/scripts/pr-recap.mjs +200 -0
  128. package/skills/global_config/quick-recap/SKILL.md +55 -0
  129. package/skills/global_config/stay-within-limits/SKILL.md +85 -0
  130. package/skills/global_config/triage/SKILL.md +3 -0
  131. package/skills/global_config/visual-edit/README.md +96 -0
  132. package/skills/global_config/visual-edit/SKILL.md +615 -0
  133. package/skills/global_config/visual-plan/README.md +93 -0
  134. package/skills/global_config/visual-plan/SKILL.md +544 -0
  135. package/skills/global_config/visual-plan/references/canvas.md +139 -0
  136. package/skills/global_config/visual-plan/references/connection.md +51 -0
  137. package/skills/global_config/visual-plan/references/document-quality.md +186 -0
  138. package/skills/global_config/visual-plan/references/exemplar.md +62 -0
  139. package/skills/global_config/visual-plan/references/local-files.md +99 -0
  140. package/skills/global_config/visual-plan/references/wireframe.md +319 -0
  141. package/skills/global_config/visual-recap/README.md +103 -0
  142. package/skills/global_config/visual-recap/SKILL.md +560 -0
  143. package/skills/global_config/visual-recap/references/connection.md +51 -0
  144. package/skills/global_config/visual-recap/references/local-files.md +99 -0
  145. package/skills/global_config/visual-recap/references/wireframe.md +319 -0
  146. package/skills/playbooks/pb-ci-fix/SKILL.md +49 -0
  147. package/skills/playbooks/pb-event-tracker/SKILL.md +45 -0
  148. package/skills/playbooks/pb-meeting-actions/SKILL.md +42 -0
  149. package/skills/playbooks/pb-project-new/SKILL.md +48 -0
  150. package/skills/playbooks/pb-week-plan/SKILL.md +45 -0
  151. package/assets/header-v4.jpg +0 -0
@@ -21,6 +21,7 @@
21
21
 
22
22
  const fs = require('fs');
23
23
  const http = require('http');
24
+ const os = require('os');
24
25
  const path = require('path');
25
26
  const { spawn } = require('child_process');
26
27
 
@@ -37,7 +38,7 @@ const {
37
38
  resolvePort
38
39
  } = require('./lib/plan-canvas/server');
39
40
 
40
- const VERSION = '1.0.1'; // vendored Plan Canvas protocol version; matches SKILL.md metadata.version.
41
+ const VERSION = '1.0.2'; // vendored Plan Canvas protocol version; matches SKILL.md metadata.version.
41
42
  // Bump when the vendored JS changes, to force a stale detached server to restart.
42
43
 
43
44
  const SAFE_REQUEST_PATHS = new Set([
@@ -56,7 +57,11 @@ function usage() {
56
57
  '',
57
58
  'Usage:',
58
59
  ' aos-plan-canvas Show server status and sessions',
60
+ ' aos-plan-canvas modes List available planning modes as JSON',
61
+ ' aos-plan-canvas templates List plan templates as JSON ({id,label,description,useWhen,hasBoard})',
62
+ ' aos-plan-canvas new <template-id> <target-dir> Copy a plan template into a new folder',
59
63
  ' aos-plan-canvas open <file> Open (or resume) a review session',
64
+ ' aos-plan-canvas trail <plan-dir|plan.mdx> Write an agenttrail plan file from a plan folder',
60
65
  ' aos-plan-canvas await <file> Block until the human sends feedback',
61
66
  ' aos-plan-canvas pending Show feedback queued for no listener',
62
67
  ' aos-plan-canvas typing <file> Show a thinking/typing indicator in chat',
@@ -65,14 +70,21 @@ function usage() {
65
70
  ' aos-plan-canvas server Run the server in the foreground',
66
71
  '',
67
72
  'Options:',
68
- ' open: --no-open Do not launch a browser window',
73
+ ' open: --mode <id> Select planning mode (default: standard)',
74
+ ' bdb-plan-builder builds <plan-dir>/plan.builder.html from',
75
+ ' plan.mdx and opens that file instead',
76
+ ' --no-open Do not launch a browser window',
69
77
  ' --reopen Reopen a session the user ended from the browser',
78
+ ' new: --mode <id> bdb-plan-builder (default: plan.mdx, canvas.mdx) or standard (plan.md);',
79
+ ' refuses a non-empty target (exit 2)',
80
+ ' trail: --out <file> Output inside the workspace (default production_artifacts/00_execution_plan.md)',
81
+ ' --force Overwrite an existing output file',
70
82
  ' await: --reply <msg> Show an agent reply in the canvas chat before waiting',
71
83
  ' --timeout-ms <n> Return {status:"waiting"} after n ms (tests/debug only)',
72
84
  ' typing: --state <thinking|typing|idle> Defaults to typing',
73
85
  ' server: --port <n> --host <h>',
74
86
  '',
75
- 'Environment: AOS_PLAN_CANVAS_PORT, AOS_PLAN_CANVAS_STATE_DIR, AOS_PLAN_CANVAS_IDLE_MS'
87
+ 'Environment: AOS_PLAN_CANVAS_PORT, AOS_PLAN_CANVAS_STATE_DIR, AOS_PLAN_CANVAS_IDLE_MS, AOS_PLAN_CANVAS_SKILL_DIRS'
76
88
  ].join('\n');
77
89
  }
78
90
 
@@ -225,12 +237,99 @@ async function cmdStatus({ stateDir, port }) {
225
237
  return { server: `http://${DEFAULT_HOST}:${port}`, version: health.version, sessions: sessions.body.sessions };
226
238
  }
227
239
 
240
+ function cmdModes() {
241
+ return resolveModes();
242
+ }
243
+
244
+ function skillDirList() {
245
+ return (process.env.AOS_PLAN_CANVAS_SKILL_DIRS || [
246
+ path.join(process.env.HOME || os.homedir(), '.claude', 'skills'),
247
+ path.join(process.env.HOME || os.homedir(), '.agents', 'skills'),
248
+ path.join(process.env.HOME || os.homedir(), '.codex', 'skills'),
249
+ path.join(process.env.HOME || os.homedir(), '.config', 'opencode', 'skills'),
250
+ path.join(process.env.HOME || os.homedir(), '.gemini', 'config', 'skills')
251
+ ].join(':')).split(':');
252
+ }
253
+
254
+ function findSkillMd(name) {
255
+ for (const dir of skillDirList()) {
256
+ const p = path.join(dir, name, 'SKILL.md');
257
+ if (fs.existsSync(p)) return p;
258
+ }
259
+ return null;
260
+ }
261
+
262
+ function resolveModes() {
263
+ const modes = [
264
+ {
265
+ id: 'standard',
266
+ label: 'Standard Plan Canvas',
267
+ available: true,
268
+ reason: null
269
+ }
270
+ ];
271
+
272
+ // Check for bdb-plan-builder
273
+ const planBuilderPath = path.resolve(__dirname, 'lib', 'plan-builder', 'index.js');
274
+ modes.push({
275
+ id: 'bdb-plan-builder',
276
+ label: 'BDB Plan Builder',
277
+ available: fs.existsSync(planBuilderPath),
278
+ reason: fs.existsSync(planBuilderPath) ? null : 'bdb-plan-builder not installed'
279
+ });
280
+
281
+ // Check for visual-plan skill
282
+ const visualPlanFound = Boolean(findSkillMd('visual-plan'));
283
+
284
+ modes.push({
285
+ id: 'builder',
286
+ label: 'Builder.io Visual Plan',
287
+ available: visualPlanFound,
288
+ reason: visualPlanFound ? null : 'visual-plan skill not found'
289
+ });
290
+
291
+ return {
292
+ default: 'standard',
293
+ modes
294
+ };
295
+ }
296
+
228
297
  async function cmdOpen(file, args, { stateDir, port }) {
229
298
  if (!file) throw new Error('open requires a file path');
230
299
  if (!fs.existsSync(path.resolve(file))) throw new Error(`artifact not found: ${file}`);
300
+
301
+ const mode = valueAfter(args, '--mode') || 'standard';
302
+ const modesInfo = resolveModes();
303
+ const modeConfig = modesInfo.modes.find(m => m.id === mode);
304
+
305
+ if (!modeConfig) {
306
+ process.stderr.write(`Unknown mode: ${mode}\n`);
307
+ return { error: `Unknown mode: ${mode}` };
308
+ }
309
+
310
+ if (!modeConfig.available) {
311
+ process.stderr.write(`Mode not available: ${mode} (${modeConfig.reason})\n`);
312
+ return { error: `Mode not available: ${mode} (${modeConfig.reason})` };
313
+ }
314
+
315
+ // bdb-plan-builder owns the artifact: it renders the plan folder to one
316
+ // self-contained HTML file next to plan.mdx, then that file goes through the
317
+ // normal session path unchanged.
318
+ let artifact = path.resolve(file);
319
+ let built = null;
320
+ if (mode === 'bdb-plan-builder') {
321
+ built = require('./lib/plan-builder').renderPlanFolder(file);
322
+ if (!built.outFile) {
323
+ const message = built.error || 'plan builder produced no output';
324
+ process.stderr.write(`${message}\n`);
325
+ return { error: message, mode };
326
+ }
327
+ artifact = built.outFile;
328
+ }
329
+
231
330
  await ensureServer({ stateDir, port });
232
331
  const res = await request(port, 'POST', '/api/sessions', {
233
- file: path.resolve(file),
332
+ file: artifact,
234
333
  reopen: args.includes('--reopen')
235
334
  });
236
335
  if (res.statusCode === 409) return res.body;
@@ -241,8 +340,11 @@ async function cmdOpen(file, args, { stateDir, port }) {
241
340
  status: 'open',
242
341
  url,
243
342
  browser: launched ? 'opened' : 'not opened',
244
- next_step:
245
- 'Run `aos-plan-canvas await <file>` and leave it running; it returns when the human sends feedback, a verdict, or ends the session.'
343
+ mode,
344
+ ...(built ? { artifact: built.outFile, warnings: built.warnings } : {}),
345
+ next_step: built && built.warnings.length
346
+ ? `Plan built with ${built.warnings.length} unreadable block(s), listed at the top of the artifact. Fix the MDX, then re-run \`open <dir> --mode bdb-plan-builder\` to rebuild. Then run \`aos-plan-canvas await <dir>/plan.builder.html\` and leave it running.`
347
+ : 'Run `aos-plan-canvas await <file>` and leave it running; it returns when the human sends feedback, a verdict, or ends the session.'
246
348
  };
247
349
  }
248
350
 
@@ -273,8 +375,26 @@ function awaitRequest(port, key, timeoutMs) {
273
375
  });
274
376
  }
275
377
 
378
+ // A bdb-plan-builder session is keyed by the built plan.builder.html, but agents
379
+ // know the plan by its folder or plan.mdx; follow either to the built artifact.
380
+ function resolveArtifactArg(file) {
381
+ if (!file) return file;
382
+ const abs = path.resolve(file);
383
+ try {
384
+ const dir = fs.statSync(abs).isDirectory() ? abs : path.basename(abs) === 'plan.mdx' ? path.dirname(abs) : null;
385
+ if (dir) {
386
+ const built = path.join(dir, 'plan.builder.html');
387
+ if (fs.existsSync(built)) return built;
388
+ }
389
+ } catch (_) {
390
+ // fall through: a missing path is reported by the command itself
391
+ }
392
+ return file;
393
+ }
394
+
276
395
  async function cmdAwait(file, args, { stateDir, port }) {
277
396
  if (!file) throw new Error('await requires a file path');
397
+ file = resolveArtifactArg(file);
278
398
  if (!(await healthCheck(port))) {
279
399
  return { status: 'no-server', hint: 'no canvas server is running; use `open` first', stateDir };
280
400
  }
@@ -303,6 +423,7 @@ async function cmdAwait(file, args, { stateDir, port }) {
303
423
  // Show the human an activity indicator in the canvas chat. Cheap and
304
424
  // fire-and-forget: a failed signal must never derail the actual work.
305
425
  async function cmdTyping(file, args, { port }) {
426
+ file = resolveArtifactArg(file);
306
427
  if (!file) throw new Error('typing requires a file path');
307
428
  const state = valueAfter(args, '--state') || 'typing';
308
429
  if (!(await healthCheck(port))) return { status: 'no-server' };
@@ -330,6 +451,7 @@ function cmdPending({ stateDir }) {
330
451
  }
331
452
 
332
453
  async function cmdEnd(file, { port }) {
454
+ file = resolveArtifactArg(file);
333
455
  if (!file) throw new Error('end requires a file path');
334
456
  if (!(await healthCheck(port))) return { status: 'no-server' };
335
457
  const res = await request(port, 'POST', '/api/end', { file: path.resolve(file) });
@@ -381,6 +503,51 @@ async function cmdServer(args, { stateDir, port }) {
381
503
  return new Promise(() => {}); // run until a signal or idle shutdown
382
504
  }
383
505
 
506
+ const TEMPLATES_DIR = path.join(__dirname, 'lib', 'plan-builder', 'templates');
507
+
508
+ function cmdTemplates() {
509
+ let ids = [];
510
+ try { ids = fs.readdirSync(TEMPLATES_DIR); } catch { /* no templates dir */ }
511
+ const list = [];
512
+ for (const id of ids.sort()) {
513
+ try {
514
+ const m = JSON.parse(fs.readFileSync(path.join(TEMPLATES_DIR, id, 'meta.json'), 'utf8'));
515
+ list.push({ id: m.id || id, label: m.label, description: m.description, useWhen: m.useWhen, hasBoard: Boolean(m.hasBoard) });
516
+ } catch { /* skip templates without readable meta.json */ }
517
+ }
518
+ return list;
519
+ }
520
+
521
+ function cmdNew(args) {
522
+ const fail = (error) => {
523
+ process.stderr.write(`${error}\n`);
524
+ output({ error });
525
+ return 2;
526
+ };
527
+ const mode = valueAfter(args, '--mode') || 'bdb-plan-builder';
528
+ const [id, target] = args.filter((a, i) => !a.startsWith('--') && args[i - 1] !== '--mode');
529
+ const ids = cmdTemplates().map(t => t.id);
530
+ if (!id || !ids.includes(id)) return fail(`Unknown template "${id || ''}". Valid ids: ${ids.join(', ') || '(none)'}`);
531
+ if (!target) return fail('Usage: aos-plan-canvas new <template-id> <target-dir> [--mode standard|bdb-plan-builder]');
532
+ if (mode !== 'standard' && mode !== 'bdb-plan-builder') return fail(`Unknown mode "${mode}". Valid modes: standard, bdb-plan-builder`);
533
+ const dir = path.resolve(target);
534
+ if (fs.existsSync(dir) && (!fs.statSync(dir).isDirectory() || fs.readdirSync(dir).length)) {
535
+ return fail(`Refusing to overwrite non-empty target: ${dir}`);
536
+ }
537
+ const src = path.join(TEMPLATES_DIR, id);
538
+ const files = mode === 'standard' ? [['standard.md', 'plan.md']] : [['plan.mdx', 'plan.mdx'], ['canvas.mdx', 'canvas.mdx']];
539
+ fs.mkdirSync(dir, { recursive: true });
540
+ const written = [];
541
+ for (const [from, to] of files) {
542
+ if (!fs.existsSync(path.join(src, from))) continue;
543
+ fs.copyFileSync(path.join(src, from), path.join(dir, to));
544
+ written.push(path.join(dir, to));
545
+ }
546
+ const entry = mode === 'standard' ? 'plan.md' : 'plan.mdx';
547
+ output({ template: id, mode, dir, files: written, next_step: `aos-plan-canvas open ${path.join(dir, entry)}${mode === 'standard' ? '' : ' --mode bdb-plan-builder'}` });
548
+ return 0;
549
+ }
550
+
384
551
  async function main(argv = process.argv.slice(2)) {
385
552
  const args = argv.slice();
386
553
  if (args.includes('--help') || args.includes('-h')) {
@@ -394,7 +561,28 @@ async function main(argv = process.argv.slice(2)) {
394
561
  const context = { stateDir, port: (recorded && recorded.port) || resolvePort() };
395
562
  try {
396
563
  if (command === null) output(await cmdStatus(context));
397
- else if (command === 'open') output(await cmdOpen(args[0], args, context));
564
+ else if (command === 'modes') output(cmdModes());
565
+ else if (command === 'templates') output(cmdTemplates());
566
+ else if (command === 'new') return cmdNew(args);
567
+ else if (command === 'open') {
568
+ const result = await cmdOpen(args[0], args, context);
569
+ if (result.error) {
570
+ output(result);
571
+ return 2;
572
+ }
573
+ output(result);
574
+ }
575
+ else if (command === 'trail') {
576
+ const { writeTrail, TrailError } = require('./lib/plan-builder/trail');
577
+ try {
578
+ output(writeTrail(args[0], { out: valueAfter(args, '--out'), force: args.includes('--force') }));
579
+ } catch (error) {
580
+ if (!(error instanceof TrailError)) throw error;
581
+ process.stderr.write(`${error.message}\n`);
582
+ output({ error: error.message });
583
+ return 2;
584
+ }
585
+ }
398
586
  else if (command === 'await') output(await cmdAwait(args[0], args, context));
399
587
  else if (command === 'pending') output(cmdPending(context));
400
588
  else if (command === 'typing') output(await cmdTyping(args[0], args, context));
@@ -418,4 +606,4 @@ if (require.main === module) {
418
606
  });
419
607
  }
420
608
 
421
- module.exports = { main, ensureServer, healthCheck };
609
+ module.exports = { main, ensureServer, healthCheck, cmdTemplates, findSkillMd };
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: pr-recap
3
+ description: Use when a PR or git range needs a visual recap page for a reviewer, with a changed-files tree, per-file notes, a Verified vs Not verified table, risks, and optional before/after images. Local and informational; never posts to GitHub.
4
+ category: engineering-method
5
+ user-invocable: true
6
+ ---
7
+
8
+ # PR Recap
9
+
10
+ Turns a PR or a git range into a Plan Builder recap page (`kind: recap`, the `recap-review` shape). The page is a local review aid. It is informational and does not gate anything.
11
+
12
+ ## When to use
13
+
14
+ - **Shipping:** after every gate has passed, to show the reviewer what is about to ship and what was not checked.
15
+ - **Triage:** to get oriented on an incoming PR (`--pr <n>`).
16
+ - **git-pr-review:** next to the text PR description, on the same range.
17
+
18
+ Skip it for a one-file typo fix. The page earns its cost when a human has to decide.
19
+
20
+ ## Flow
21
+
22
+ 1. Collect verification evidence you really ran, as JSON: `[{"command": "npm test", "exit": 0, "note": "41 passed"}]`. Do not invent entries.
23
+ 2. Run the script from this skill's `scripts/` directory:
24
+
25
+ ```bash
26
+ node pr-recap.mjs --range origin/main..HEAD --verification checks.json
27
+ node pr-recap.mjs --pr 87 --before before.png --after after.png --open
28
+ ```
29
+
30
+ Options: `--out <dir>` (default `production_artifacts/pr-recap/<slug>/`), `--title <t>`, `--open` (runs `aos-plan-canvas open <folder> --mode bdb-plan-builder`).
31
+ 3. The script writes `plan.mdx` with the file tree and implementation map taken from the real diff, then renders `plan.builder.html` with the Plan Builder. The renderer comes from the sibling `plan-canvas` skill. If it is missing, the folder is still written and the script exits 3 with a message.
32
+ 4. Open `plan.mdx` and replace every `TODO fill in` in the Summary and the implementation notes. The script cannot know the intent of the change.
33
+ 5. Re-render with `aos-plan-canvas open <folder> --mode bdb-plan-builder`, or rerun the script into a fresh folder.
34
+
35
+ ## Honesty rules
36
+
37
+ - A check is **Verified** only if you ran it and its exit code was 0. Failed checks are listed as **Not verified (failed)**.
38
+ - Never add a row for a check you did not run. The page always says that everything not in the table was not run.
39
+ - If there is no `--verification` file, the page says nothing was recorded. Do not claim the PR was tested.
40
+ - Screenshots must be real captures of the before and after states. If you cannot capture them, omit the Compare block.
41
+ - File names, titles and branch names come from git and GitHub and are untrusted. The script escapes them for MDX; do not paste them into the plan by hand.
42
+
43
+ ## Posting is never automatic
44
+
45
+ The script reads git locally and, for `--pr`, runs read-only `gh pr view`. It never calls `gh pr comment`, `gh pr edit` or anything that writes to GitHub. It may print the `gh pr comment` command for the human to run.
46
+
47
+ Do not post the recap, upload its images, or push it anywhere unless the user explicitly asks for that exact action. A subagent does not inherit that permission. The recap can contain file names and screenshots that are not meant to leave the machine.
@@ -0,0 +1,200 @@
1
+ #!/usr/bin/env node
2
+ // pr-recap: turn a git range or PR into a Plan Builder recap folder (plan.mdx + HTML).
3
+ // Reads git locally and, for --pr, `gh pr view` read-only. Never posts anywhere.
4
+
5
+ import { execFileSync, spawnSync } from 'node:child_process';
6
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
7
+ import { createRequire } from 'node:module';
8
+ import { dirname, extname, join, resolve } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+
11
+ const HERE = dirname(fileURLToPath(import.meta.url));
12
+ const USAGE = `usage: node pr-recap.mjs (--range <base..head> | --pr <n>) [--out <dir>] [--verification <file.json>]
13
+ [--before <img> --after <img>] [--title <t>] [--open]`;
14
+ const IMG_EXT = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp']);
15
+ const TREE_CAP = 200;
16
+ const MAP_CAP = 60;
17
+
18
+ class UsageError extends Error {}
19
+ const fail = (msg) => { throw new UsageError(msg); };
20
+
21
+ function parseArgs(argv) {
22
+ const opts = { open: false };
23
+ const valued = new Set(['range', 'pr', 'out', 'verification', 'before', 'after', 'title']);
24
+ for (let i = 0; i < argv.length; i++) {
25
+ const a = argv[i];
26
+ if (!a.startsWith('--')) fail(`unexpected argument: ${a}`);
27
+ const key = a.slice(2);
28
+ if (key === 'open') { opts.open = true; continue; }
29
+ if (!valued.has(key)) fail(`unknown option: ${a}`);
30
+ const v = argv[++i];
31
+ if (v === undefined || v.startsWith('--')) fail(`${a} needs a value`);
32
+ opts[key] = v;
33
+ }
34
+ if (!opts.range === !opts.pr) fail('give exactly one of --range or --pr');
35
+ if (opts.range && (opts.range.startsWith('-') || !/^[^\s]+\.\.\.?[^\s]+$/.test(opts.range))) fail(`--range must look like base..head, got: ${opts.range}`);
36
+ if (opts.pr && !/^\d+$/.test(opts.pr)) fail(`--pr must be a number, got: ${opts.pr}`);
37
+ if (Boolean(opts.before) !== Boolean(opts.after)) fail('--before and --after must be given together');
38
+ for (const k of ['before', 'after']) {
39
+ if (!opts[k]) continue;
40
+ if (!IMG_EXT.has(extname(opts[k]).toLowerCase())) fail(`--${k} must be one of ${[...IMG_EXT].join(' ')}`);
41
+ if (!existsSync(opts[k])) fail(`--${k} file not found: ${opts[k]}`);
42
+ }
43
+ return opts;
44
+ }
45
+
46
+ const run = (cmd, args) => execFileSync(cmd, args, { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] });
47
+ const git = (...args) => run('git', args);
48
+
49
+ // Git and GitHub text is untrusted. `line` is for frontmatter and markdown
50
+ // prose: single line, no characters MDX or markdown would act on.
51
+ const line = (s, max = 160) => String(s ?? '').replace(/[\u0000-\u001f\u007f\u2028\u2029]+/g, ' ').replace(/[<>{}`"\\|]/g, '·').trim().slice(0, max);
52
+ // `lit` is for JSX props: a JSON string literal with < > { } also \u-escaped.
53
+ const lit = (s) => JSON.stringify(String(s ?? '')).replace(/[<>{}]/g, (c) => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'));
54
+
55
+ function diffFiles(range) {
56
+ const status = git('diff', '--name-status', '-z', '-M', range, '--').split('\0').filter((x) => x !== '');
57
+ const files = new Map();
58
+ for (let i = 0; i < status.length;) {
59
+ const code = status[i++][0];
60
+ const from = code === 'R' || code === 'C' ? status[i++] : null;
61
+ const path = status[i++];
62
+ files.set(path, { path, change: code === 'A' || code === 'C' ? 'added' : code === 'D' ? 'deleted' : 'modified', from, add: 0, del: 0 });
63
+ }
64
+ const num = git('diff', '--numstat', '-z', '-M', range, '--').split('\0');
65
+ for (let i = 0; i < num.length; i++) {
66
+ const m = /^(\d+|-)\t(\d+|-)\t(.*)$/.exec(num[i]);
67
+ if (!m) continue;
68
+ let path = m[3];
69
+ if (path === '') { i += 2; path = num[i]; }
70
+ const f = files.get(path);
71
+ if (f) { f.add = m[1] === '-' ? 0 : Number(m[1]); f.del = m[2] === '-' ? 0 : Number(m[2]); }
72
+ }
73
+ return [...files.values()];
74
+ }
75
+
76
+ function fromGh(n) {
77
+ let pr;
78
+ try {
79
+ pr = JSON.parse(run('gh', ['pr', 'view', n, '--json', 'title,headRefName,baseRefName,headRefOid,baseRefOid,author,files,additions,deletions']));
80
+ } catch (e) {
81
+ throw new Error(`gh pr view ${n} failed (is gh installed and authenticated?): ${String(e.stderr || e.message).trim()}`);
82
+ }
83
+ let files;
84
+ try {
85
+ files = diffFiles(`${pr.baseRefOid}..${pr.headRefOid}`);
86
+ } catch {
87
+ // ponytail: commits not fetched locally, so fall back to gh's file list (no rename info)
88
+ files = (pr.files || []).map((f) => ({ path: f.path, change: f.changeType === 'ADDED' ? 'added' : f.changeType === 'DELETED' ? 'deleted' : 'modified', from: null, add: f.additions || 0, del: f.deletions || 0 }));
89
+ }
90
+ return { files, title: pr.title, branch: pr.headRefName, base: pr.baseRefName, commit: String(pr.headRefOid || '').slice(0, 7), author: pr.author?.login, pr: `#${n}` };
91
+ }
92
+
93
+ function fromRange(range) {
94
+ const files = diffFiles(range);
95
+ const head = range.split(/\.\.\.?/)[1];
96
+ const last = git('log', '-1', '--format=%h%x1f%an%x1f%s', head).trim().split('\x1f');
97
+ return { files, title: last[2], branch: head, base: range.split(/\.\.\.?/)[0], commit: last[0], author: last[1] };
98
+ }
99
+
100
+ function readVerification(file) {
101
+ let rows;
102
+ try { rows = JSON.parse(readFileSync(file, 'utf8')); } catch (e) { fail(`--verification is not readable JSON: ${e.message}`); }
103
+ if (!Array.isArray(rows) || rows.some((r) => !r || typeof r.command !== 'string' || !Number.isInteger(r.exit))) fail('--verification must be an array of {command, exit (integer), note?}');
104
+ return rows;
105
+ }
106
+
107
+ function treeEntries(files) {
108
+ const sorted = [...files].sort((a, b) => (a.path < b.path ? -1 : 1));
109
+ const out = [];
110
+ const seen = new Set();
111
+ for (const f of sorted) {
112
+ const parts = f.path.split('/');
113
+ for (let d = 0; d < parts.length - 1; d++) {
114
+ const dir = parts.slice(0, d + 1).join('/');
115
+ if (!seen.has(dir)) { seen.add(dir); out.push(`{ path: ${lit(dir)}, depth: ${d}, change: "modified" }`); }
116
+ }
117
+ const note = (f.from ? `renamed from ${f.from}; ` : '') + `+${f.add} -${f.del}`;
118
+ out.push(`{ path: ${lit(f.path)}, depth: ${parts.length - 1}, change: ${lit(f.change)}, note: ${lit(note)} }`);
119
+ }
120
+ return out;
121
+ }
122
+
123
+ function buildPlan({ info, verification, images, title }) {
124
+ const files = info.files;
125
+ const shown = files.slice(0, TREE_CAP);
126
+ const adds = files.reduce((s, f) => s + f.add, 0);
127
+ const dels = files.reduce((s, f) => s + f.del, 0);
128
+ const fm = [['title', title || info.title || 'Change recap'], ['subtitle', 'Recap generated from the diff. The Summary needs a human or agent to fill it in.'], ['kind', 'recap'],
129
+ ['pr', info.pr], ['branch', info.branch], ['base', info.base], ['commit', info.commit], ['files', files.length], ['additions', adds], ['deletions', dels],
130
+ ['author', info.author], ['date', new Date().toISOString().slice(0, 10)]]
131
+ .filter(([, v]) => v !== undefined && v !== '')
132
+ .map(([k, v]) => `${k}: ${typeof v === 'number' ? v : `"${line(v)}"`}`);
133
+
134
+ const rows = verification.map((v) => `[${lit(line(v.command, 200))}, ${lit(String(v.exit))}, ${lit(v.exit === 0 ? 'Verified' : 'Not verified (failed)')}, ${lit(line(v.note || '', 200))}]`);
135
+ const ok = verification.filter((v) => v.exit === 0).map((v) => `- \`${line(v.command, 120)}\` exited 0${v.note ? `: ${line(v.note, 120)}` : ''}`);
136
+ const bad = verification.filter((v) => v.exit !== 0).map((v) => `- \`${line(v.command, 120)}\` exited ${v.exit}${v.note ? `: ${line(v.note, 120)}` : ''}`);
137
+ const verified = ok.length ? ok.join('\n') : '- Nothing was recorded as passing';
138
+ const notVerified = [...bad, '- Everything not listed in the table above was not run'].join('\n');
139
+
140
+ const parts = [`---\n${fm.join('\n')}\n---\n`,
141
+ '## Summary\n\n- **What:** TODO fill in: what changed, in one sentence\n- **Why:** TODO fill in: the reason for the change\n- **Scope:** TODO fill in: what is deliberately untouched\n',
142
+ `## Changed areas\n\n<FileTree title="Files" entries={[\n ${treeEntries(shown).join(',\n ')},\n]} />\n`,
143
+ `<ImplementationMap title="What each file does now" files={[\n ${files.slice(0, MAP_CAP).map((f) => `{ path: ${lit(f.path)}, change: ${lit(f.change)}, note: "TODO fill in: what this file does now" }`).join(',\n ')},\n]} />\n`];
144
+ if (files.length > TREE_CAP || files.length > MAP_CAP) parts.push(`<Callout tone="note" title="Large diff">\n\nThe diff touches ${files.length} files. The tree shows the first ${Math.min(files.length, TREE_CAP)} and the implementation map the first ${Math.min(files.length, MAP_CAP)}. The rest are not listed.\n\n</Callout>\n`);
145
+ if (images) parts.push(`### Most important change\n\n<Compare>\n<Before>\n\n![Before](${images.before})\n\n</Before>\n<After>\n\n![After](${images.after})\n\n</After>\n</Compare>\n`);
146
+ parts.push(`## Verification\n\n<Table title="Commands run" columns={["Command", "Exit code", "Status", "Note"]} rows={[\n ${rows.length ? rows.join(',\n ') : '["none recorded", "-", "Not verified", "no --verification file given"]'},\n]} />\n`,
147
+ `<Columns columns={[\n { label: "Verified", blocks: ${lit(verified)} },\n { label: "Not verified", blocks: ${lit(notVerified)} },\n]} />\n`,
148
+ '## Risks and follow-ups\n\n<Checklist title="Risks and follow-ups">\n- [ ] Fill in the Summary and the implementation notes before sharing\n- [ ] Review every item under Not verified\n- [ ] Informational recap: it does not gate the merge\n</Checklist>\n');
149
+ return parts.join('\n');
150
+ }
151
+
152
+ function loadBuilder() {
153
+ const req = createRequire(import.meta.url);
154
+ for (const rel of ['../../plan-canvas/scripts/lib/plan-builder', '../../../global_config/plan-canvas/scripts/lib/plan-builder']) {
155
+ const p = resolve(HERE, rel);
156
+ if (existsSync(join(p, 'index.js'))) return req(p);
157
+ }
158
+ return null;
159
+ }
160
+
161
+ function main() {
162
+ const opts = parseArgs(process.argv.slice(2));
163
+ const verification = opts.verification ? readVerification(opts.verification) : [];
164
+ let info;
165
+ try { info = opts.pr ? fromGh(opts.pr) : fromRange(opts.range); } catch (e) { if (e instanceof UsageError) throw e; throw new Error(`git failed: ${String(e.stderr || e.message).trim()}`); }
166
+
167
+ const slug = (opts.pr ? `pr-${opts.pr}` : opts.range.replace(/[^A-Za-z0-9._-]+/g, '-')).slice(0, 80);
168
+ const out = resolve(opts.out || join('production_artifacts', 'pr-recap', slug));
169
+ mkdirSync(out, { recursive: true });
170
+ let images = null;
171
+ if (opts.before) {
172
+ images = { before: `before${extname(opts.before).toLowerCase()}`, after: `after${extname(opts.after).toLowerCase()}` };
173
+ copyFileSync(opts.before, join(out, images.before));
174
+ copyFileSync(opts.after, join(out, images.after));
175
+ }
176
+ writeFileSync(join(out, 'plan.mdx'), buildPlan({ info, verification, images, title: opts.title }));
177
+ console.log(`plan folder: ${out}`);
178
+
179
+ const builder = loadBuilder();
180
+ if (!builder) {
181
+ console.error('Plan Builder renderer not found (expected plan-canvas next to pr-recap). plan.mdx was written but not rendered; install the plan-canvas skill and rerun.');
182
+ return 3;
183
+ }
184
+ const r = builder.renderPlanFolder(out);
185
+ console.log(`rendered: ${r.outFile} (${r.warnings.length} warnings)`);
186
+ for (const w of r.warnings) console.error(`warning: ${w}`);
187
+ if (opts.open) {
188
+ const o = spawnSync('aos-plan-canvas', ['open', out, '--mode', 'bdb-plan-builder'], { stdio: 'inherit' });
189
+ if (o.status !== 0) { console.error('aos-plan-canvas open failed or is not installed'); return 1; }
190
+ }
191
+ if (opts.pr) console.log(`Not posted. To share it yourself, review the page first, then run:\n gh pr comment ${opts.pr} --body "Visual recap (informational): <paste summary>"`);
192
+ return 0;
193
+ }
194
+
195
+ try {
196
+ process.exitCode = main();
197
+ } catch (e) {
198
+ console.error(e instanceof UsageError ? `error: ${e.message}\n${USAGE}` : `error: ${e.message}`);
199
+ process.exitCode = e instanceof UsageError ? 2 : 1;
200
+ }
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: quick-recap
3
+ description: Use when the user wants each agent response to end with a red/yellow/green status line showing whether the work is finished, pending a follow-up, or blocked on input.
4
+ category: bdb-core
5
+ source: BuilderIO/skills
6
+ ---
7
+
8
+ # Quick Recap
9
+
10
+ Make completion state obvious at the end of every response.
11
+
12
+ ## Status Block
13
+
14
+ Every response that completes a unit of work must end with:
15
+
16
+ ```md
17
+ 🟢 Actual concise status sentence
18
+ ```
19
+
20
+ Rules:
21
+
22
+ - Keep the status line under 100 characters.
23
+ - Use `🟢` when the requested work is finished.
24
+ - Use `🟡` when non-routine follow-up remains; name the pending item.
25
+ - Use `🔴` only when blocked on user input.
26
+ - Put the status line at the very end of the response.
27
+ - Do not add `---`, spacer lines, or any content after the status line.
28
+
29
+ ## Activation
30
+
31
+ The convention applies only while this skill is loaded or the user asks for it.
32
+ AOS does not inject a managed `AGENTS.md` / `CLAUDE.md` block for it.
33
+
34
+ Choose the status from the user's perspective: finished, pending a specific
35
+ non-routine step, or blocked.
36
+
37
+ ## Examples
38
+
39
+ Finished work:
40
+
41
+ ```md
42
+ 🟢 Updated quick recap docs with output examples
43
+ ```
44
+
45
+ Non-routine follow-up remains:
46
+
47
+ ```md
48
+ 🟡 Code updated, set PROVIDER_WEBHOOK_SECRET before testing webhooks
49
+ ```
50
+
51
+ Blocked on user input:
52
+
53
+ ```md
54
+ 🔴 Need the production API key to continue
55
+ ```