@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.
- package/.agents/AGENTS.md +8 -0
- package/.agents/nodes.json +5 -2
- package/.claude/hooks/conventional-commits.mjs +14 -15
- package/.claude/hooks/env-file-protection.mjs +14 -15
- package/.claude/hooks/go-gate.mjs +152 -10
- package/.claude/hooks/go-token.mjs +55 -0
- package/.claude/hooks/memb-inject.mjs +75 -62
- package/.claude/hooks/trail-autostart.mjs +27 -0
- package/.claude/hooks/trail-relay.mjs +1 -0
- package/.claude/settings.json +22 -4
- package/.claude/workflows/startcycle-dispatch.mjs +11 -4
- package/.opencode/plugins/bdb-aos.js +98 -121
- package/.opencode/plugins/lib/trail-autostart.js +38 -0
- package/CLAUDE.md +1 -1
- package/README.de.md +6 -6
- package/README.md +6 -6
- package/README.pt.md +6 -6
- package/THIRD_PARTY_NOTICES.md +19 -3
- package/assets/header-v5.png +0 -0
- package/bin/aos-acp.mjs +211 -0
- package/bin/aos-doctor.mjs +1 -1
- package/bin/aos-uninstall.mjs +2 -2
- package/docs/master-session-acp.md +51 -0
- package/installer.js +314 -42
- package/mcps/mcsc/packages/mcp/server.js +6 -7
- package/package.json +4 -3
- package/scripts/validate-skills.mjs +76 -0
- package/skills/basic/bdbmediastorm/SKILL.md +1 -1
- package/skills/basic/godmode-shipping/SKILL.md +3 -0
- package/skills/basic/master-session/SKILL.md +89 -0
- package/skills/basic/startcycle/SKILL.md +1 -1
- package/skills/basic/startcycle-graph/SKILL.md +2 -2
- package/skills/basic/startcycle-graph-user/SKILL.md +1 -1
- package/skills/basic/teamwork-preview/SKILL.md +1 -1
- package/skills/bdbrainstorm/SKILL.md +7 -1
- package/skills/global_config/agentic-harness-patterns/SKILL.md +257 -0
- package/skills/global_config/agentic-harness-patterns/metadata.json +10 -0
- package/skills/global_config/agentic-harness-patterns/references/agent-orchestration-pattern.md +97 -0
- package/skills/global_config/agentic-harness-patterns/references/bootstrap-sequence-pattern.md +106 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/compress-pattern.md +78 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/isolate-pattern.md +82 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering/select-pattern.md +86 -0
- package/skills/global_config/agentic-harness-patterns/references/context-engineering-pattern.md +29 -0
- package/skills/global_config/agentic-harness-patterns/references/hook-lifecycle-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/memory-persistence-pattern.md +109 -0
- package/skills/global_config/agentic-harness-patterns/references/permission-gate-pattern.md +111 -0
- package/skills/global_config/agentic-harness-patterns/references/skill-runtime-pattern.md +104 -0
- package/skills/global_config/agentic-harness-patterns/references/task-decomposition-pattern.md +92 -0
- package/skills/global_config/agentic-harness-patterns/references/tool-registry-pattern.md +101 -0
- package/skills/global_config/agenttrail/SKILL.md +8 -0
- package/skills/global_config/agenttrail/bin/agenttrail.mjs +14 -0
- package/skills/global_config/agenttrail/bin/ensure.mjs +118 -0
- package/skills/global_config/aos-setup/scripts/aos-doctor.mjs +1 -1
- package/skills/global_config/bdb-visual-edit/SKILL.md +51 -0
- package/skills/global_config/bdb-visual-edit/references/vite-react-source-attr.md +59 -0
- package/skills/global_config/bdb-visual-edit/scripts/pick-snippet.js +27 -0
- package/skills/global_config/bdb-visual-edit/scripts/sanitize-element.mjs +123 -0
- package/skills/global_config/factory-collect/SKILL.md +74 -0
- package/skills/global_config/factory-human-digest/SKILL.md +92 -0
- package/skills/global_config/factory-lookback/SKILL.md +95 -0
- package/skills/global_config/factory-review-prs/SKILL.md +63 -0
- package/skills/global_config/git-pr-review/SKILL.md +3 -0
- package/skills/global_config/grilling/SKILL.md +2 -0
- package/skills/global_config/mcsc/SKILL.md +1 -1
- package/skills/global_config/plan-arbiter/SKILL.md +125 -0
- package/skills/global_config/plan-canvas/SKILL.md +62 -5
- package/skills/global_config/plan-canvas/scripts/lib/loopback-guard.js +19 -3
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/README.md +285 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/agent-trail.js +129 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/board-client.js +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/canvas.mdx +19 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/demo-plan/plan.mdx +18 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/recap-demo/plan.mdx +72 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/README.md +29 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/architecture.json +30 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/00_architecture.html +14950 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/canvas.mdx +511 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/builder/plan.mdx +208 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/recap/plan.mdx +102 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/showcase/standard/plan.md +136 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/canvas.mdx +124 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/examples/signup-storyboard/plan.mdx +37 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/index.js +188 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/kit.js +123 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/mdx.js +411 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/render.js +1291 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/plan.mdx +195 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/architecture/standard.md +95 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/plan.mdx +105 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/bugfix/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/canvas.mdx +81 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/plan.mdx +145 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/feature/standard.md +76 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/plan.mdx +172 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/migration/standard.md +100 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/plan.mdx +67 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap/standard.md +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/canvas.mdx +63 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/plan.mdx +49 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-board/standard.md +39 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/plan.mdx +118 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/recap-review/standard.md +57 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/plan.mdx +173 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/release/standard.md +96 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/plan.mdx +91 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/research/standard.md +54 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/canvas.mdx +53 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/meta.json +1 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/plan.mdx +225 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/templates/show-control/standard.md +111 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/theme.css +472 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-builder/trail.js +216 -0
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/markdown.js +1 -1
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/server.js +37 -4
- package/skills/global_config/plan-canvas/scripts/lib/plan-canvas/ui.js +125 -29
- package/skills/global_config/plan-canvas/scripts/plan-canvas.js +196 -8
- package/skills/global_config/pr-recap/SKILL.md +47 -0
- package/skills/global_config/pr-recap/scripts/pr-recap.mjs +200 -0
- package/skills/global_config/quick-recap/SKILL.md +55 -0
- package/skills/global_config/stay-within-limits/SKILL.md +85 -0
- package/skills/global_config/triage/SKILL.md +3 -0
- package/skills/global_config/visual-edit/README.md +96 -0
- package/skills/global_config/visual-edit/SKILL.md +615 -0
- package/skills/global_config/visual-plan/README.md +93 -0
- package/skills/global_config/visual-plan/SKILL.md +544 -0
- package/skills/global_config/visual-plan/references/canvas.md +139 -0
- package/skills/global_config/visual-plan/references/connection.md +51 -0
- package/skills/global_config/visual-plan/references/document-quality.md +186 -0
- package/skills/global_config/visual-plan/references/exemplar.md +62 -0
- package/skills/global_config/visual-plan/references/local-files.md +99 -0
- package/skills/global_config/visual-plan/references/wireframe.md +319 -0
- package/skills/global_config/visual-recap/README.md +103 -0
- package/skills/global_config/visual-recap/SKILL.md +560 -0
- package/skills/global_config/visual-recap/references/connection.md +51 -0
- package/skills/global_config/visual-recap/references/local-files.md +99 -0
- package/skills/global_config/visual-recap/references/wireframe.md +319 -0
- package/skills/playbooks/pb-ci-fix/SKILL.md +49 -0
- package/skills/playbooks/pb-event-tracker/SKILL.md +45 -0
- package/skills/playbooks/pb-meeting-actions/SKILL.md +42 -0
- package/skills/playbooks/pb-project-new/SKILL.md +48 -0
- package/skills/playbooks/pb-week-plan/SKILL.md +45 -0
- 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.
|
|
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: --
|
|
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:
|
|
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
|
-
|
|
245
|
-
|
|
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 === '
|
|
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\n\n</Before>\n<After>\n\n\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
|
+
```
|