@open-agent-toolkit/cli 0.2.20 → 0.2.22
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/assets/docs/workflows/projects/artifacts.md +1 -1
- package/assets/docs/workflows/skills/explainer-kit.md +188 -23
- package/assets/docs/workflows/skills/index.md +1 -1
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/explainer-kit/SKILL.md +87 -25
- package/assets/skills/explainer-kit/briefs/deep-dive.md +35 -0
- package/assets/skills/explainer-kit/briefs/engineer-tour.md +45 -0
- package/assets/skills/explainer-kit/briefs/program-recap.md +41 -0
- package/assets/skills/explainer-kit/briefs/project-explainer.md +38 -0
- package/assets/skills/explainer-kit/briefs/project-page.md +38 -0
- package/assets/skills/explainer-kit/briefs/project-recap.md +47 -0
- package/assets/skills/explainer-kit/briefs/supporting-diagram.md +28 -0
- package/assets/skills/explainer-kit/briefs/walkthrough-deck.md +35 -0
- package/assets/skills/explainer-kit/examples/project-recap/content.md +57 -14
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.json +104 -0
- package/assets/skills/explainer-kit/examples/project-recap/fact-base.md +26 -4
- package/assets/skills/explainer-kit/recipes/engineer-tour.json +27 -10
- package/assets/skills/explainer-kit/recipes/program-recap.json +35 -11
- package/assets/skills/explainer-kit/recipes/project-explainer.json +27 -10
- package/assets/skills/explainer-kit/recipes/project-recap.json +43 -11
- package/assets/skills/explainer-kit/references/contracts.md +45 -19
- package/assets/skills/explainer-kit/schemas/author-request.v2.schema.json +41 -0
- package/assets/skills/explainer-kit/schemas/author-result.v2.schema.json +52 -0
- package/assets/skills/explainer-kit/scripts/lib/browser-runtime.mjs +442 -0
- package/assets/skills/explainer-kit/scripts/lib/content-approval.mjs +223 -10
- package/assets/skills/explainer-kit/scripts/lib/contracts.mjs +28 -43
- package/assets/skills/explainer-kit/scripts/lib/diagram.mjs +237 -0
- package/assets/skills/explainer-kit/scripts/lib/html-safety.mjs +687 -0
- package/assets/skills/explainer-kit/scripts/lib/markdown.mjs +414 -0
- package/assets/skills/explainer-kit/scripts/lib/qa.mjs +313 -14
- package/assets/skills/explainer-kit/scripts/lib/recipes.mjs +314 -41
- package/assets/skills/explainer-kit/scripts/lib/records.mjs +61 -0
- package/assets/skills/explainer-kit/scripts/lib/render.mjs +166 -12
- package/assets/skills/explainer-kit/scripts/render-qa.mjs +152 -2
- package/assets/skills/explainer-kit/scripts/run.mjs +789 -272
- package/assets/skills/explainer-kit/templates/deck-shell.html +25 -5
- package/assets/skills/explainer-kit/templates/diagram-shell.html +29 -7
- package/assets/skills/explainer-kit/templates/engineer-tour.html +133 -9
- package/assets/skills/explainer-kit/templates/house-style.html +82 -0
- package/assets/skills/oat-brainstorm/SKILL.md +1 -1
- package/assets/skills/oat-brainstorm/scripts/helper.js +18 -11
- package/assets/skills/oat-brainstorm/scripts/server.cjs +109 -55
- package/assets/skills/oat-explainer-kit/SKILL.md +16 -9
- package/assets/skills/oat-explainer-kit/references/author-callback.md +51 -0
- package/assets/skills/oat-explainer-kit/references/lifecycle-contract.md +10 -8
- package/assets/skills/oat-explainer-kit/scripts/resolve-intent.mjs +14 -0
- package/assets/skills/oat-explainer-kit/scripts/run.mjs +7 -11
- package/assets/skills/oat-project-complete/SKILL.md +18 -2
- package/assets/skills/oat-project-implement/SKILL.md +1 -1
- package/assets/skills/oat-project-implement/references/completion-and-closeout.md +7 -1
- package/assets/skills/oat-wave-execute/SKILL.md +12 -19
- package/assets/skills/oat-wave-program/SKILL.md +12 -13
- package/dist/commands/init/tools/index.d.ts.map +1 -1
- package/dist/commands/init/tools/index.js +4 -4
- package/dist/engine/compute-plan.js +3 -3
- package/package.json +2 -2
- package/assets/skills/explainer-kit/schemas/author-request.schema.json +0 -85
- package/assets/skills/explainer-kit/schemas/author-result.schema.json +0 -65
|
@@ -5,11 +5,14 @@ const path = require('path');
|
|
|
5
5
|
|
|
6
6
|
// ========== WebSocket Protocol (RFC 6455) ==========
|
|
7
7
|
|
|
8
|
-
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG:
|
|
8
|
+
const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0a };
|
|
9
9
|
const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
|
10
10
|
|
|
11
11
|
function computeAcceptKey(clientKey) {
|
|
12
|
-
return crypto
|
|
12
|
+
return crypto
|
|
13
|
+
.createHash('sha1')
|
|
14
|
+
.update(clientKey + WS_MAGIC)
|
|
15
|
+
.digest('base64');
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
function encodeFrame(opcode, payload) {
|
|
@@ -40,9 +43,9 @@ function decodeFrame(buffer) {
|
|
|
40
43
|
if (buffer.length < 2) return null;
|
|
41
44
|
|
|
42
45
|
const secondByte = buffer[1];
|
|
43
|
-
const opcode = buffer[0] &
|
|
46
|
+
const opcode = buffer[0] & 0x0f;
|
|
44
47
|
const masked = (secondByte & 0x80) !== 0;
|
|
45
|
-
let payloadLen = secondByte &
|
|
48
|
+
let payloadLen = secondByte & 0x7f;
|
|
46
49
|
let offset = 2;
|
|
47
50
|
|
|
48
51
|
if (!masked) throw new Error('Client frames must be masked');
|
|
@@ -73,18 +76,29 @@ function decodeFrame(buffer) {
|
|
|
73
76
|
|
|
74
77
|
// ========== Configuration ==========
|
|
75
78
|
|
|
76
|
-
const PORT =
|
|
79
|
+
const PORT =
|
|
80
|
+
process.env.BRAINSTORM_PORT || 49152 + Math.floor(Math.random() * 16383);
|
|
77
81
|
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
|
|
78
|
-
const URL_HOST =
|
|
82
|
+
const URL_HOST =
|
|
83
|
+
process.env.BRAINSTORM_URL_HOST ||
|
|
84
|
+
(HOST === '127.0.0.1' ? 'localhost' : HOST);
|
|
79
85
|
const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
|
|
80
86
|
const CONTENT_DIR = path.join(SESSION_DIR, 'content');
|
|
81
87
|
const STATE_DIR = path.join(SESSION_DIR, 'state');
|
|
82
|
-
let ownerPid = process.env.BRAINSTORM_OWNER_PID
|
|
88
|
+
let ownerPid = process.env.BRAINSTORM_OWNER_PID
|
|
89
|
+
? Number(process.env.BRAINSTORM_OWNER_PID)
|
|
90
|
+
: null;
|
|
83
91
|
|
|
84
92
|
const MIME_TYPES = {
|
|
85
|
-
'.html': 'text/html',
|
|
86
|
-
'.
|
|
87
|
-
'.
|
|
93
|
+
'.html': 'text/html',
|
|
94
|
+
'.css': 'text/css',
|
|
95
|
+
'.js': 'application/javascript',
|
|
96
|
+
'.json': 'application/json',
|
|
97
|
+
'.png': 'image/png',
|
|
98
|
+
'.jpg': 'image/jpeg',
|
|
99
|
+
'.jpeg': 'image/jpeg',
|
|
100
|
+
'.gif': 'image/gif',
|
|
101
|
+
'.svg': 'image/svg+xml',
|
|
88
102
|
};
|
|
89
103
|
|
|
90
104
|
// ========== Templates and Constants ==========
|
|
@@ -98,9 +112,15 @@ h1 { color: #333; } p { color: #666; }</style>
|
|
|
98
112
|
<body><h1>Brainstorm Companion</h1>
|
|
99
113
|
<p>Waiting for the agent to push a screen...</p></body></html>`;
|
|
100
114
|
|
|
101
|
-
const frameTemplate = fs.readFileSync(
|
|
102
|
-
|
|
103
|
-
|
|
115
|
+
const frameTemplate = fs.readFileSync(
|
|
116
|
+
path.join(__dirname, 'frame-template.html'),
|
|
117
|
+
'utf-8',
|
|
118
|
+
);
|
|
119
|
+
const helperScript = fs.readFileSync(
|
|
120
|
+
path.join(__dirname, 'helper.js'),
|
|
121
|
+
'utf-8',
|
|
122
|
+
);
|
|
123
|
+
const helperInjection = `<script>\n${helperScript}\n</script>`;
|
|
104
124
|
|
|
105
125
|
// ========== Helper Functions ==========
|
|
106
126
|
|
|
@@ -114,9 +134,10 @@ function wrapInFrame(content) {
|
|
|
114
134
|
}
|
|
115
135
|
|
|
116
136
|
function getNewestScreen() {
|
|
117
|
-
const files = fs
|
|
118
|
-
.
|
|
119
|
-
.
|
|
137
|
+
const files = fs
|
|
138
|
+
.readdirSync(CONTENT_DIR)
|
|
139
|
+
.filter((f) => f.endsWith('.html'))
|
|
140
|
+
.map((f) => {
|
|
120
141
|
const fp = path.join(CONTENT_DIR, f);
|
|
121
142
|
return { path: fp, mtime: fs.statSync(fp).mtime.getTime() };
|
|
122
143
|
})
|
|
@@ -131,11 +152,13 @@ function handleRequest(req, res) {
|
|
|
131
152
|
if (req.method === 'GET' && req.url === '/') {
|
|
132
153
|
const screenFile = getNewestScreen();
|
|
133
154
|
let html = screenFile
|
|
134
|
-
? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(
|
|
155
|
+
? ((raw) => (isFullDocument(raw) ? raw : wrapInFrame(raw)))(
|
|
156
|
+
fs.readFileSync(screenFile, 'utf-8'),
|
|
157
|
+
)
|
|
135
158
|
: WAITING_PAGE;
|
|
136
159
|
|
|
137
160
|
if (html.includes('</body>')) {
|
|
138
|
-
html = html.replace('</body>', helperInjection
|
|
161
|
+
html = html.replace('</body>', `${helperInjection}\n</body>`);
|
|
139
162
|
} else {
|
|
140
163
|
html += helperInjection;
|
|
141
164
|
}
|
|
@@ -166,14 +189,17 @@ const clients = new Set();
|
|
|
166
189
|
|
|
167
190
|
function handleUpgrade(req, socket) {
|
|
168
191
|
const key = req.headers['sec-websocket-key'];
|
|
169
|
-
if (!key) {
|
|
192
|
+
if (!key) {
|
|
193
|
+
socket.destroy();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
170
196
|
|
|
171
197
|
const accept = computeAcceptKey(key);
|
|
172
198
|
socket.write(
|
|
173
199
|
'HTTP/1.1 101 Switching Protocols\r\n' +
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
200
|
+
'Upgrade: websocket\r\n' +
|
|
201
|
+
'Connection: Upgrade\r\n' +
|
|
202
|
+
`Sec-WebSocket-Accept: ${accept}\r\n\r\n`,
|
|
177
203
|
);
|
|
178
204
|
|
|
179
205
|
let buffer = Buffer.alloc(0);
|
|
@@ -185,7 +211,7 @@ function handleUpgrade(req, socket) {
|
|
|
185
211
|
let result;
|
|
186
212
|
try {
|
|
187
213
|
result = decodeFrame(buffer);
|
|
188
|
-
} catch
|
|
214
|
+
} catch {
|
|
189
215
|
socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0)));
|
|
190
216
|
clients.delete(socket);
|
|
191
217
|
return;
|
|
@@ -233,14 +259,18 @@ function handleMessage(text) {
|
|
|
233
259
|
console.log(JSON.stringify({ source: 'user-event', ...event }));
|
|
234
260
|
if (event.choice) {
|
|
235
261
|
const eventsFile = path.join(STATE_DIR, 'events');
|
|
236
|
-
fs.appendFileSync(eventsFile, JSON.stringify(event)
|
|
262
|
+
fs.appendFileSync(eventsFile, `${JSON.stringify(event)}\n`);
|
|
237
263
|
}
|
|
238
264
|
}
|
|
239
265
|
|
|
240
266
|
function broadcast(msg) {
|
|
241
267
|
const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg)));
|
|
242
268
|
for (const socket of clients) {
|
|
243
|
-
try {
|
|
269
|
+
try {
|
|
270
|
+
socket.write(frame);
|
|
271
|
+
} catch {
|
|
272
|
+
clients.delete(socket);
|
|
273
|
+
}
|
|
244
274
|
}
|
|
245
275
|
}
|
|
246
276
|
|
|
@@ -260,14 +290,15 @@ const debounceTimers = new Map();
|
|
|
260
290
|
// ========== Server Startup ==========
|
|
261
291
|
|
|
262
292
|
function startServer() {
|
|
263
|
-
if (!fs.existsSync(CONTENT_DIR))
|
|
293
|
+
if (!fs.existsSync(CONTENT_DIR))
|
|
294
|
+
fs.mkdirSync(CONTENT_DIR, { recursive: true });
|
|
264
295
|
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
265
296
|
|
|
266
297
|
// Track known files to distinguish new screens from updates.
|
|
267
298
|
// macOS fs.watch reports 'rename' for both new files and overwrites,
|
|
268
299
|
// so we can't rely on eventType alone.
|
|
269
300
|
const knownFiles = new Set(
|
|
270
|
-
fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html'))
|
|
301
|
+
fs.readdirSync(CONTENT_DIR).filter((f) => f.endsWith('.html')),
|
|
271
302
|
);
|
|
272
303
|
|
|
273
304
|
const server = http.createServer(handleRequest);
|
|
@@ -276,25 +307,31 @@ function startServer() {
|
|
|
276
307
|
const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => {
|
|
277
308
|
if (!filename || !filename.endsWith('.html')) return;
|
|
278
309
|
|
|
279
|
-
if (debounceTimers.has(filename))
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
310
|
+
if (debounceTimers.has(filename))
|
|
311
|
+
clearTimeout(debounceTimers.get(filename));
|
|
312
|
+
debounceTimers.set(
|
|
313
|
+
filename,
|
|
314
|
+
setTimeout(() => {
|
|
315
|
+
debounceTimers.delete(filename);
|
|
316
|
+
const filePath = path.join(CONTENT_DIR, filename);
|
|
317
|
+
|
|
318
|
+
if (!fs.existsSync(filePath)) return; // file was deleted
|
|
319
|
+
touchActivity();
|
|
320
|
+
|
|
321
|
+
if (!knownFiles.has(filename)) {
|
|
322
|
+
knownFiles.add(filename);
|
|
323
|
+
const eventsFile = path.join(STATE_DIR, 'events');
|
|
324
|
+
if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
|
|
325
|
+
console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
|
|
326
|
+
} else {
|
|
327
|
+
console.log(
|
|
328
|
+
JSON.stringify({ type: 'screen-updated', file: filePath }),
|
|
329
|
+
);
|
|
330
|
+
}
|
|
295
331
|
|
|
296
|
-
|
|
297
|
-
|
|
332
|
+
broadcast({ type: 'reload' });
|
|
333
|
+
}, 100),
|
|
334
|
+
);
|
|
298
335
|
});
|
|
299
336
|
watcher.on('error', (err) => console.error('fs.watch error:', err.message));
|
|
300
337
|
|
|
@@ -304,7 +341,7 @@ function startServer() {
|
|
|
304
341
|
if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile);
|
|
305
342
|
fs.writeFileSync(
|
|
306
343
|
path.join(STATE_DIR, 'server-stopped'),
|
|
307
|
-
JSON.stringify({ reason, timestamp: Date.now() })
|
|
344
|
+
`${JSON.stringify({ reason, timestamp: Date.now() })}\n`,
|
|
308
345
|
);
|
|
309
346
|
watcher.close();
|
|
310
347
|
clearInterval(lifecycleCheck);
|
|
@@ -313,13 +350,19 @@ function startServer() {
|
|
|
313
350
|
|
|
314
351
|
function ownerAlive() {
|
|
315
352
|
if (!ownerPid) return true;
|
|
316
|
-
try {
|
|
353
|
+
try {
|
|
354
|
+
process.kill(ownerPid, 0);
|
|
355
|
+
return true;
|
|
356
|
+
} catch (e) {
|
|
357
|
+
return e.code === 'EPERM';
|
|
358
|
+
}
|
|
317
359
|
}
|
|
318
360
|
|
|
319
361
|
// Check every 60s: exit if owner process died or idle for 30 minutes
|
|
320
362
|
const lifecycleCheck = setInterval(() => {
|
|
321
363
|
if (!ownerAlive()) shutdown('owner process exited');
|
|
322
|
-
else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS)
|
|
364
|
+
else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS)
|
|
365
|
+
shutdown('idle timeout');
|
|
323
366
|
}, 60 * 1000);
|
|
324
367
|
lifecycleCheck.unref();
|
|
325
368
|
|
|
@@ -327,10 +370,17 @@ function startServer() {
|
|
|
327
370
|
// was wrong (common on WSL, Tailscale SSH, and cross-user scenarios).
|
|
328
371
|
// Disable monitoring and rely on the idle timeout instead.
|
|
329
372
|
if (ownerPid) {
|
|
330
|
-
try {
|
|
331
|
-
|
|
373
|
+
try {
|
|
374
|
+
process.kill(ownerPid, 0);
|
|
375
|
+
} catch (e) {
|
|
332
376
|
if (e.code !== 'EPERM') {
|
|
333
|
-
console.log(
|
|
377
|
+
console.log(
|
|
378
|
+
JSON.stringify({
|
|
379
|
+
type: 'owner-pid-invalid',
|
|
380
|
+
pid: ownerPid,
|
|
381
|
+
reason: 'dead at startup',
|
|
382
|
+
}),
|
|
383
|
+
);
|
|
334
384
|
ownerPid = null;
|
|
335
385
|
}
|
|
336
386
|
}
|
|
@@ -338,12 +388,16 @@ function startServer() {
|
|
|
338
388
|
|
|
339
389
|
server.listen(PORT, HOST, () => {
|
|
340
390
|
const info = JSON.stringify({
|
|
341
|
-
type: 'server-started',
|
|
342
|
-
|
|
343
|
-
|
|
391
|
+
type: 'server-started',
|
|
392
|
+
port: Number(PORT),
|
|
393
|
+
host: HOST,
|
|
394
|
+
url_host: URL_HOST,
|
|
395
|
+
url: `http://${URL_HOST}:${PORT}`,
|
|
396
|
+
screen_dir: CONTENT_DIR,
|
|
397
|
+
state_dir: STATE_DIR,
|
|
344
398
|
});
|
|
345
399
|
console.log(info);
|
|
346
|
-
fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info
|
|
400
|
+
fs.writeFileSync(path.join(STATE_DIR, 'server-info'), `${info}\n`);
|
|
347
401
|
});
|
|
348
402
|
}
|
|
349
403
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oat-explainer-kit
|
|
3
|
-
version: 1.0.
|
|
3
|
+
version: 1.0.3
|
|
4
4
|
description: Use when building project explainers or recaps from OAT configuration, state, and lifecycle artifacts.
|
|
5
5
|
disable-model-invocation: false
|
|
6
6
|
user-invocable: true
|
|
@@ -19,6 +19,7 @@ Adapt OAT project context into the versioned request consumed by the canonical
|
|
|
19
19
|
- Derive canonical project or repository output roots.
|
|
20
20
|
- Bind OAT lifecycle artifacts to generic recipe source roles.
|
|
21
21
|
- Resolve project explainer and recap intent before invoking the core.
|
|
22
|
+
- Require lifecycle callers to construct a brief-aware author seam.
|
|
22
23
|
|
|
23
24
|
## Dependency Direction
|
|
24
25
|
|
|
@@ -33,7 +34,7 @@ rollback, and operator-owned real-wrapper gate, use `references/migration.md`.
|
|
|
33
34
|
|
|
34
35
|
Before reading OAT config or invoking the core, call
|
|
35
36
|
`scripts/check-core.mjs#checkCoreCompatibility` with this installed skill
|
|
36
|
-
directory and minimum core version `
|
|
37
|
+
directory and minimum core version `2.0.0`. Continue only when it returns
|
|
37
38
|
`ok: true`.
|
|
38
39
|
|
|
39
40
|
- Missing core: stop and show
|
|
@@ -54,7 +55,7 @@ Call `scripts/run.mjs#runOatExplainer` with the repository root, project
|
|
|
54
55
|
invocation, active project path, recipe, slug, lifecycle mode, and any explicit
|
|
55
56
|
runtime overrides. The adapter:
|
|
56
57
|
|
|
57
|
-
1. checks the user-scoped installed core at minimum version `
|
|
58
|
+
1. checks the user-scoped installed core at minimum version `2.0.0`;
|
|
58
59
|
2. resolves only the public `explainers.*` and `workflow.explainers.*` keys;
|
|
59
60
|
3. derives the canonical project output root;
|
|
60
61
|
4. binds approved OAT artifacts to the recipe's single `project` source set;
|
|
@@ -68,13 +69,19 @@ Missing optional artifacts are omitted, but at least one approved lifecycle
|
|
|
68
69
|
artifact is required. An explicit supplied fact-base path bypasses artifact
|
|
69
70
|
federation and is passed through as `factBase.mode: supplied`.
|
|
70
71
|
|
|
72
|
+
Before invocation, read `references/author-callback.md` and construct exactly
|
|
73
|
+
one provider-neutral author seam in both modes: in-process callers supply
|
|
74
|
+
`author`, while JSON/CLI callers supply `authorModulePath` naming a module with
|
|
75
|
+
an `author` function export. The callback consumes `author-request/v2` with the
|
|
76
|
+
recipe brief and fact base inlined, plus theme and any recipe-selected artistic
|
|
77
|
+
shell. It may propose expansion only through recipe profile IDs. The adapter
|
|
78
|
+
validates and resolves that executable input before passing it to
|
|
79
|
+
`core.runExplainer`; callbacks and module paths never enter the persisted run
|
|
80
|
+
request.
|
|
81
|
+
|
|
71
82
|
Unattended project runs pass `approved-oat-artifacts` provenance to the core's
|
|
72
|
-
content-approval seam and never prompt.
|
|
73
|
-
|
|
74
|
-
while JSON/CLI callers supply `authorModulePath` naming a module with an
|
|
75
|
-
`author` function export. The adapter validates and resolves that executable
|
|
76
|
-
input before passing it to `core.runExplainer`; callbacks and module paths never
|
|
77
|
-
enter the persisted run request.
|
|
83
|
+
content-approval seam and never prompt. Automated completion and
|
|
84
|
+
implementation-tail recaps always use `mode: unattended`.
|
|
78
85
|
|
|
79
86
|
Federated runs still require an explicit provider-neutral critic callback.
|
|
80
87
|
In-process callers may supply `critic` (or `coreOptions.critic` for
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Author callback contract
|
|
2
|
+
|
|
3
|
+
Lifecycle callers own the executable author seam; the explainer core owns
|
|
4
|
+
construction of each author request. In-process callers pass an `author`
|
|
5
|
+
callback to `runOatExplainer`. JSON-only and official CLI callers pass
|
|
6
|
+
`authorModulePath`, which must name a module with an `author` function export.
|
|
7
|
+
Provide exactly one of those inputs in both interactive and unattended modes.
|
|
8
|
+
The callback or module may use any provider, but its request and result remain
|
|
9
|
+
provider-neutral.
|
|
10
|
+
|
|
11
|
+
## Request handling
|
|
12
|
+
|
|
13
|
+
The callback receives one `explainer-kit.author-request/v2` per artifact. The
|
|
14
|
+
core constructs that request from recipe-owned policy:
|
|
15
|
+
|
|
16
|
+
- `brief` contains the prose loaded from the floor entry or expansion profile's
|
|
17
|
+
`briefRef`; the author must treat the inlined brief as its editorial contract.
|
|
18
|
+
- `factBase` contains the reconciled evidence for the run. Ground claims in that
|
|
19
|
+
fact base and do not replace it with ambient project context.
|
|
20
|
+
- `authoring` selects the output path. Return Markdown for `markdown` and a
|
|
21
|
+
complete HTML document for `html`.
|
|
22
|
+
- `theme` is attached to every request. HTML requests also carry the
|
|
23
|
+
recipe-selected `shell`; use it as the starting canvas while following the
|
|
24
|
+
brief's license to elaborate.
|
|
25
|
+
- A floor request may include `floor.requiredNarrative`, which identifies the
|
|
26
|
+
narrative coverage checked later as non-blocking guidelines.
|
|
27
|
+
|
|
28
|
+
Return one matching `explainer-kit.author-result/v2`: preserve `artifactId`,
|
|
29
|
+
set exactly one of `content.markdown` or `content.html`, and include
|
|
30
|
+
`provenance`. Do not persist provider credentials or callback configuration in
|
|
31
|
+
the result.
|
|
32
|
+
|
|
33
|
+
## Expansion
|
|
34
|
+
|
|
35
|
+
Only a floor result may propose expansion. Put each proposal in
|
|
36
|
+
`proposedArtifacts` as `{id, profileId, rationale}`. Do not choose an artifact
|
|
37
|
+
type, authoring mode, brief, or shell in the proposal; the referenced recipe
|
|
38
|
+
profile, selected by `profileId`, owns those values.
|
|
39
|
+
|
|
40
|
+
The core validates proposal IDs, profile membership, collisions, and finite
|
|
41
|
+
caps. It rejects malformed proposals, records over-limit proposals as warnings,
|
|
42
|
+
and issues a new author request for every accepted artifact. Handle each
|
|
43
|
+
follow-up request independently and do not recursively propose more artifacts.
|
|
44
|
+
|
|
45
|
+
## Lifecycle invocation
|
|
46
|
+
|
|
47
|
+
Automated project-completion and implementation-tail recaps always call
|
|
48
|
+
`runOatExplainer` with `mode: unattended`. They construct the author seam before
|
|
49
|
+
invocation and supply it alongside the existing provider-neutral `critic` or
|
|
50
|
+
validated `criticModulePath`. Interactive invocations use the same author
|
|
51
|
+
contract; only the later approval behavior differs.
|
|
@@ -71,18 +71,20 @@ On a stale-write conflict, the caller must re-read state, resolve precedence
|
|
|
71
71
|
again, and decide whether a write is still required. It must not retry the old
|
|
72
72
|
record blindly.
|
|
73
73
|
|
|
74
|
-
##
|
|
74
|
+
## Author execution
|
|
75
75
|
|
|
76
|
-
Every
|
|
77
|
-
seam. In-process callers pass `author`; JSON-only
|
|
78
|
-
`authorModulePath` in the adapter context, naming
|
|
79
|
-
is a function. Missing module files, invalid
|
|
80
|
-
conflicts fail at the adapter boundary.
|
|
76
|
+
Every adapter run in both interactive and unattended modes must provide exactly
|
|
77
|
+
one provider-neutral author seam. In-process callers pass `author`; JSON-only
|
|
78
|
+
and official CLI callers put `authorModulePath` in the adapter context, naming
|
|
79
|
+
a module whose `author` export is a function. Missing module files, invalid
|
|
80
|
+
exports, and direct-plus-module conflicts fail at the adapter boundary. See
|
|
81
|
+
`author-callback.md` for brief, fact-base, shell, theme, result, and expansion
|
|
82
|
+
handling.
|
|
81
83
|
|
|
82
84
|
The resolved callback is passed only as the `author` option to
|
|
83
85
|
`core.runExplainer`. It is never copied into `ExplainerRunRequestV1`,
|
|
84
|
-
`run-request.json`, or another retained data contract. Interactive runs
|
|
85
|
-
|
|
86
|
+
`run-request.json`, or another retained data contract. Interactive runs use the
|
|
87
|
+
same author contract and differ only at the later approval gate.
|
|
86
88
|
|
|
87
89
|
## Tracked-run finalization
|
|
88
90
|
|
|
@@ -130,6 +130,20 @@ export function validateIntentRecord(product, record) {
|
|
|
130
130
|
return record;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
export function explainerModeForIntent(intent) {
|
|
134
|
+
if (
|
|
135
|
+
!intent ||
|
|
136
|
+
typeof intent !== 'object' ||
|
|
137
|
+
intent.product !== 'projectRecap' ||
|
|
138
|
+
intent.decision !== 'generate'
|
|
139
|
+
) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
'Only a generated projectRecap intent can select completion-chain explainer mode.',
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return 'unattended';
|
|
145
|
+
}
|
|
146
|
+
|
|
133
147
|
function resolveAutonomous({
|
|
134
148
|
product,
|
|
135
149
|
state,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from './resolve-config.mjs';
|
|
13
13
|
import { resolveExplainerOutputRoot } from './resolve-paths.mjs';
|
|
14
14
|
|
|
15
|
-
const MINIMUM_CORE_VERSION = '
|
|
15
|
+
const MINIMUM_CORE_VERSION = '2.0.0';
|
|
16
16
|
const ADAPTER_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
17
17
|
|
|
18
18
|
export async function runOatExplainer({
|
|
@@ -114,7 +114,6 @@ export async function runOatExplainer({
|
|
|
114
114
|
author,
|
|
115
115
|
authorModulePath,
|
|
116
116
|
coreOptions,
|
|
117
|
-
mode: request.mode,
|
|
118
117
|
});
|
|
119
118
|
const lifecycleCritic = await resolveLifecycleCritic({
|
|
120
119
|
critic,
|
|
@@ -141,6 +140,7 @@ export async function runOatExplainer({
|
|
|
141
140
|
request,
|
|
142
141
|
manifest,
|
|
143
142
|
result,
|
|
143
|
+
marking: result.marking ?? null,
|
|
144
144
|
outputRoot,
|
|
145
145
|
};
|
|
146
146
|
}
|
|
@@ -149,7 +149,6 @@ async function resolveLifecycleAuthor({
|
|
|
149
149
|
author,
|
|
150
150
|
authorModulePath,
|
|
151
151
|
coreOptions,
|
|
152
|
-
mode,
|
|
153
152
|
}) {
|
|
154
153
|
if (coreOptions?.author !== undefined) {
|
|
155
154
|
throw new TypeError(
|
|
@@ -166,14 +165,11 @@ async function resolveLifecycleAuthor({
|
|
|
166
165
|
}
|
|
167
166
|
|
|
168
167
|
if (author === undefined && authorModulePath === undefined) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
176
|
-
return null;
|
|
168
|
+
const error = new Error(
|
|
169
|
+
'Unattended and interactive OAT explainer runs require exactly one provider-neutral author callback or author module entry point.',
|
|
170
|
+
);
|
|
171
|
+
error.code = 'E_AUTHOR_REQUIRED';
|
|
172
|
+
throw error;
|
|
177
173
|
}
|
|
178
174
|
if (authorModulePath === undefined) {
|
|
179
175
|
return author;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oat-project-complete
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.6.0
|
|
4
4
|
description: Use when all implementation work is finished and the project is ready to close. Marks the OAT project lifecycle as complete.
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
user-invocable: true
|
|
@@ -282,7 +282,23 @@ When `SHOULD_GENERATE_RECAP="true"`, inspect manifests under
|
|
|
282
282
|
`{PROJECT_PATH}/explainers/` before generating. A fresh `project-recap` manifest for the current completed implementation is reused without invoking the adapter again. Fresh means the manifest identifies recipe `project-recap`, belongs to this project, has a terminal outcome, and its recorded source hashes match the current approved implementation inputs, including the refreshed summary when present.
|
|
283
283
|
|
|
284
284
|
If no fresh recap exists, invoke `scripts/run.mjs#runOatExplainer` exactly once with recipe `project-recap`, project invocation, the active project, and unattended lifecycle mode so approved OAT artifacts do not trigger a second content prompt. A failed adapter run warns but does not block completion. Use a returned valid terminal `project-recap` manifest as the selected run; do not rerun to improve its outcome.
|
|
285
|
-
|
|
285
|
+
Before that invocation, construct exactly one brief-aware, provider-neutral
|
|
286
|
+
author seam as documented by
|
|
287
|
+
`oat-explainer-kit/references/author-callback.md`. In-process callers pass
|
|
288
|
+
`author`; JSON/CLI callers pass a validated `authorModulePath`. Supply it
|
|
289
|
+
alongside the existing `critic` callback (or validated
|
|
290
|
+
`criticModulePath`), and invoke the recap with `mode: unattended`.
|
|
291
|
+
|
|
292
|
+
The author seam is the recap's quality mechanism, so derive its output from the
|
|
293
|
+
request rather than from ambient context or a stock recap shape. Cover every
|
|
294
|
+
`floor.requiredNarrative` section, ground each claim in the supplied `factBase`,
|
|
295
|
+
and follow the inlined `brief` for structure — evidence tables for the
|
|
296
|
+
implementation and validation sections, at least one high-level architecture
|
|
297
|
+
diagram, and lists where material is enumerable. A recap whose warnings include
|
|
298
|
+
`guideline-narrative-coverage-missing`, `guideline-structured-depth-missing`, or
|
|
299
|
+
`guideline-architecture-diagram-missing` is thin: it still completes, but treat
|
|
300
|
+
those warnings as the signal that the authored content did not use the evidence
|
|
301
|
+
it was given.
|
|
286
302
|
|
|
287
303
|
Set `SELECTED_PROJECT_RECAP_RUN` only to the final selected `project-recap` run. The value must be project-relative in the form `explainers/<run-slug>` so it can be passed safely to the archive CLI. An incomplete, stale, wrong-project, or `project-explainer` manifest is never selected as the final recap.
|
|
288
304
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oat-project-implement
|
|
3
|
-
version: 2.
|
|
3
|
+
version: 2.2.0
|
|
4
4
|
description: Use when plan.md is ready for execution. Dispatches one phase implementer per phase, owns independent phase review and bounded fix routing, and supports plan-declared worktree-isolated parallel phases.
|
|
5
5
|
oat_gateable: true
|
|
6
6
|
argument-hint: '[--retry-limit <N>] [--dry-run]'
|
|
@@ -751,7 +751,13 @@ Before generating, inspect the active project's explainer runs. A fresh `project
|
|
|
751
751
|
Resolve recap intent through `oat-explainer-kit`. When `OAT_AUTONOMOUS=1` and no fresh recap exists, attempt `project-recap` exactly once; missing or stale persisted intent cannot suppress this autonomous attempt. Interactive mode honors the adapter's resolved persisted or workflow intent.
|
|
752
752
|
|
|
753
753
|
Invoke the `oat-explainer-kit` adapter first, then run its shared tracked-run finalizer in `dedicated` mode for a successful build. Use the adapter result and finalizer result as returned; do not improvise commits, durability evidence, or reruns. Outcomes `failed` and `built-not-durable` are recorded warnings, never blockers for final HiLL approval, completion reporting, or later PR steps.
|
|
754
|
-
|
|
754
|
+
For an adapter invocation, construct exactly one brief-aware,
|
|
755
|
+
provider-neutral author seam using
|
|
756
|
+
`oat-explainer-kit/references/author-callback.md`. In-process callers pass
|
|
757
|
+
`author`; JSON/CLI callers pass a validated `authorModulePath`. Supply it
|
|
758
|
+
alongside the existing `critic` callback (or validated
|
|
759
|
+
`criticModulePath`), and always invoke this implementation-tail recap with
|
|
760
|
+
`mode: unattended`.
|
|
755
761
|
|
|
756
762
|
Always include the selected or attempted recap's outcome and run path in the
|
|
757
763
|
implementation completion report. If `summary.md` exists, append or refresh its
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: oat-wave-execute
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.8.0
|
|
4
4
|
description: Use when executing a wave of external implementation plans as a wrapper OAT project — scaffolding, drift refresh, parallel worktree groups, briefs, gates, merge choreography, and closeout.
|
|
5
5
|
argument-hint: '<wave-id> [plan-names...] (e.g. wave-2 http-listener-before-indexing ...)'
|
|
6
6
|
disable-model-invocation: false
|
|
@@ -411,24 +411,17 @@ exactly:
|
|
|
411
411
|
`schemaVersion, generatedAt, mode, freshnessPolicy, sources, claims, unresolvedClaims, overrides`.
|
|
412
412
|
|
|
413
413
|
The caller also owns CONTENT AUTHORING, exactly as it owns critic execution and
|
|
414
|
-
fact-base synthesis: the kit
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
the author is absent, returns an invalid result, or copies excessive verbatim
|
|
426
|
-
source text. Recap callers MUST satisfy that seam by authoring content from
|
|
427
|
-
the synthesized fact base plus the recipe outline (LLM-authored from
|
|
428
|
-
summary/synthesis material, as the operator-approved W6 rebuild demonstrates)
|
|
429
|
-
or NOT run the unattended build, recording the skip disposition per the
|
|
430
|
-
optional-step rule. Callbacks and module paths never enter the persisted run
|
|
431
|
-
request.
|
|
414
|
+
fact-base synthesis: the kit validates structure and fact consistency, but
|
|
415
|
+
nothing in it owns prose quality. Every recap run requires exactly one
|
|
416
|
+
provider-neutral author seam — in-process callers supply an `author(request)`
|
|
417
|
+
callback; JSON/CLI callers supply `authorModulePath` naming a module with an
|
|
418
|
+
`author` function export. The core invokes it for every floor and accepted
|
|
419
|
+
expansion artifact with `author-request/v2`: the artifact brief is inlined,
|
|
420
|
+
the fact base is attached, and artistic requests include the resolved theme
|
|
421
|
+
and hash-pinned shell. Authors may propose only recipe-declared expansion
|
|
422
|
+
profiles and return `author-result/v2`; runs fail on absent or invalid author
|
|
423
|
+
results or excessive verbatim source copying. Callbacks and module paths never
|
|
424
|
+
enter persisted run requests.
|
|
432
425
|
|
|
433
426
|
The mechanical caller constructs an `explainer-kit.run-request/v1` document whose
|
|
434
427
|
required keys are exactly:
|