wildcrane-runner 0.6.0 → 0.6.2
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/README.md +3 -6
- package/dist/akb/dist/kanban.mjs +1388 -763
- package/dist/launcher.mjs +2 -2
- package/dist/runner.mjs +555 -543
- package/package.json +1 -1
package/dist/akb/dist/kanban.mjs
CHANGED
|
@@ -29,9 +29,9 @@ var __commonJS = (cb, mod) => function __require2() {
|
|
|
29
29
|
throw mod = 0, e;
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
-
var __export = (target,
|
|
33
|
-
for (var name in
|
|
34
|
-
__defProp(target, name, { get:
|
|
32
|
+
var __export = (target, all2) => {
|
|
33
|
+
for (var name in all2)
|
|
34
|
+
__defProp(target, name, { get: all2[name], enumerable: true });
|
|
35
35
|
};
|
|
36
36
|
var __copyProps = (to, from, except, desc) => {
|
|
37
37
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -50,9 +50,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
50
50
|
mod
|
|
51
51
|
));
|
|
52
52
|
|
|
53
|
-
//
|
|
53
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/error.js
|
|
54
54
|
var require_error = __commonJS({
|
|
55
|
-
"
|
|
55
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/error.js"(exports) {
|
|
56
56
|
var CommanderError2 = class extends Error {
|
|
57
57
|
/**
|
|
58
58
|
* Constructs the CommanderError class
|
|
@@ -85,9 +85,9 @@ var require_error = __commonJS({
|
|
|
85
85
|
}
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
//
|
|
88
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/argument.js
|
|
89
89
|
var require_argument = __commonJS({
|
|
90
|
-
"
|
|
90
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/argument.js"(exports) {
|
|
91
91
|
var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
92
92
|
var Argument2 = class {
|
|
93
93
|
/**
|
|
@@ -212,9 +212,9 @@ var require_argument = __commonJS({
|
|
|
212
212
|
}
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
-
//
|
|
215
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/help.js
|
|
216
216
|
var require_help = __commonJS({
|
|
217
|
-
"
|
|
217
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/help.js"(exports) {
|
|
218
218
|
var { humanReadableArgName } = require_argument();
|
|
219
219
|
var Help2 = class {
|
|
220
220
|
constructor() {
|
|
@@ -781,9 +781,9 @@ ${itemIndentStr}`);
|
|
|
781
781
|
}
|
|
782
782
|
});
|
|
783
783
|
|
|
784
|
-
//
|
|
784
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/option.js
|
|
785
785
|
var require_option = __commonJS({
|
|
786
|
-
"
|
|
786
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/option.js"(exports) {
|
|
787
787
|
var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
788
788
|
var Option2 = class {
|
|
789
789
|
/**
|
|
@@ -1082,9 +1082,9 @@ var require_option = __commonJS({
|
|
|
1082
1082
|
}
|
|
1083
1083
|
});
|
|
1084
1084
|
|
|
1085
|
-
//
|
|
1085
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/suggestSimilar.js
|
|
1086
1086
|
var require_suggestSimilar = __commonJS({
|
|
1087
|
-
"
|
|
1087
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/suggestSimilar.js"(exports) {
|
|
1088
1088
|
var maxDistance = 3;
|
|
1089
1089
|
function editDistance2(a, b) {
|
|
1090
1090
|
if (Math.abs(a.length - b.length) > maxDistance)
|
|
@@ -1162,13 +1162,13 @@ var require_suggestSimilar = __commonJS({
|
|
|
1162
1162
|
}
|
|
1163
1163
|
});
|
|
1164
1164
|
|
|
1165
|
-
//
|
|
1165
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/command.js
|
|
1166
1166
|
var require_command = __commonJS({
|
|
1167
|
-
"
|
|
1167
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/command.js"(exports) {
|
|
1168
1168
|
var EventEmitter = __require("node:events").EventEmitter;
|
|
1169
1169
|
var childProcess = __require("node:child_process");
|
|
1170
|
-
var
|
|
1171
|
-
var
|
|
1170
|
+
var path83 = __require("node:path");
|
|
1171
|
+
var fs88 = __require("node:fs");
|
|
1172
1172
|
var process2 = __require("node:process");
|
|
1173
1173
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1174
1174
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2149,7 +2149,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2149
2149
|
* @param {string} subcommandName
|
|
2150
2150
|
*/
|
|
2151
2151
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2152
|
-
if (
|
|
2152
|
+
if (fs88.existsSync(executableFile)) return;
|
|
2153
2153
|
const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
|
|
2154
2154
|
const executableMissing = `'${executableFile}' does not exist
|
|
2155
2155
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2167,11 +2167,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2167
2167
|
let launchWithNode = false;
|
|
2168
2168
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2169
2169
|
function findFile(baseDir, baseName) {
|
|
2170
|
-
const localBin =
|
|
2171
|
-
if (
|
|
2172
|
-
if (sourceExt.includes(
|
|
2170
|
+
const localBin = path83.resolve(baseDir, baseName);
|
|
2171
|
+
if (fs88.existsSync(localBin)) return localBin;
|
|
2172
|
+
if (sourceExt.includes(path83.extname(baseName))) return void 0;
|
|
2173
2173
|
const foundExt = sourceExt.find(
|
|
2174
|
-
(ext) =>
|
|
2174
|
+
(ext) => fs88.existsSync(`${localBin}${ext}`)
|
|
2175
2175
|
);
|
|
2176
2176
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2177
2177
|
return void 0;
|
|
@@ -2183,21 +2183,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2183
2183
|
if (this._scriptPath) {
|
|
2184
2184
|
let resolvedScriptPath;
|
|
2185
2185
|
try {
|
|
2186
|
-
resolvedScriptPath =
|
|
2186
|
+
resolvedScriptPath = fs88.realpathSync(this._scriptPath);
|
|
2187
2187
|
} catch {
|
|
2188
2188
|
resolvedScriptPath = this._scriptPath;
|
|
2189
2189
|
}
|
|
2190
|
-
executableDir =
|
|
2191
|
-
|
|
2190
|
+
executableDir = path83.resolve(
|
|
2191
|
+
path83.dirname(resolvedScriptPath),
|
|
2192
2192
|
executableDir
|
|
2193
2193
|
);
|
|
2194
2194
|
}
|
|
2195
2195
|
if (executableDir) {
|
|
2196
2196
|
let localFile = findFile(executableDir, executableFile);
|
|
2197
2197
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2198
|
-
const legacyName =
|
|
2198
|
+
const legacyName = path83.basename(
|
|
2199
2199
|
this._scriptPath,
|
|
2200
|
-
|
|
2200
|
+
path83.extname(this._scriptPath)
|
|
2201
2201
|
);
|
|
2202
2202
|
if (legacyName !== this._name) {
|
|
2203
2203
|
localFile = findFile(
|
|
@@ -2208,7 +2208,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2208
2208
|
}
|
|
2209
2209
|
executableFile = localFile || executableFile;
|
|
2210
2210
|
}
|
|
2211
|
-
launchWithNode = sourceExt.includes(
|
|
2211
|
+
launchWithNode = sourceExt.includes(path83.extname(executableFile));
|
|
2212
2212
|
let proc;
|
|
2213
2213
|
if (process2.platform !== "win32") {
|
|
2214
2214
|
if (launchWithNode) {
|
|
@@ -3055,7 +3055,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3055
3055
|
* @return {Command}
|
|
3056
3056
|
*/
|
|
3057
3057
|
nameFromFilename(filename) {
|
|
3058
|
-
this._name =
|
|
3058
|
+
this._name = path83.basename(filename, path83.extname(filename));
|
|
3059
3059
|
return this;
|
|
3060
3060
|
}
|
|
3061
3061
|
/**
|
|
@@ -3069,9 +3069,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3069
3069
|
* @param {string} [path]
|
|
3070
3070
|
* @return {(string|null|Command)}
|
|
3071
3071
|
*/
|
|
3072
|
-
executableDir(
|
|
3073
|
-
if (
|
|
3074
|
-
this._executableDir =
|
|
3072
|
+
executableDir(path84) {
|
|
3073
|
+
if (path84 === void 0) return this._executableDir;
|
|
3074
|
+
this._executableDir = path84;
|
|
3075
3075
|
return this;
|
|
3076
3076
|
}
|
|
3077
3077
|
/**
|
|
@@ -3321,9 +3321,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3321
3321
|
}
|
|
3322
3322
|
});
|
|
3323
3323
|
|
|
3324
|
-
//
|
|
3324
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/index.js
|
|
3325
3325
|
var require_commander = __commonJS({
|
|
3326
|
-
"
|
|
3326
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/index.js"(exports) {
|
|
3327
3327
|
var { Argument: Argument2 } = require_argument();
|
|
3328
3328
|
var { Command: Command2 } = require_command();
|
|
3329
3329
|
var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
@@ -3343,10 +3343,10 @@ var require_commander = __commonJS({
|
|
|
3343
3343
|
}
|
|
3344
3344
|
});
|
|
3345
3345
|
|
|
3346
|
-
//
|
|
3346
|
+
// ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/esm.mjs
|
|
3347
3347
|
var import_index, program, createCommand, createArgument, createOption, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Command, Argument, Option, Help;
|
|
3348
3348
|
var init_esm = __esm({
|
|
3349
|
-
"
|
|
3349
|
+
"../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/esm.mjs"() {
|
|
3350
3350
|
import_index = __toESM(require_commander(), 1);
|
|
3351
3351
|
({
|
|
3352
3352
|
program,
|
|
@@ -3637,11 +3637,11 @@ function boardPath() {
|
|
|
3637
3637
|
return rel(KANBAN).split(path3.sep).join("/");
|
|
3638
3638
|
}
|
|
3639
3639
|
function boardText(text5) {
|
|
3640
|
-
const
|
|
3640
|
+
const posix3 = (p) => rel(p).split(path3.sep).join("/");
|
|
3641
3641
|
const swaps = [
|
|
3642
|
-
[ASSETS_PATH_IN_TEXT,
|
|
3643
|
-
[MOCKUPS_PATH_IN_TEXT,
|
|
3644
|
-
[BOARD_STATE_IN_TEXT,
|
|
3642
|
+
[ASSETS_PATH_IN_TEXT, posix3(ASSETS)],
|
|
3643
|
+
[MOCKUPS_PATH_IN_TEXT, posix3(MOCKUPS)],
|
|
3644
|
+
[BOARD_STATE_IN_TEXT, posix3(BOARD_STATE)],
|
|
3645
3645
|
[BOARD_PATH_IN_TEXT, boardPath()]
|
|
3646
3646
|
];
|
|
3647
3647
|
const swap = (part, i) => i === swaps.length ? part : part.split(swaps[i][0]).map((p) => swap(p, i + 1)).join(swaps[i][1]);
|
|
@@ -3850,8 +3850,8 @@ var init_question_block = __esm({
|
|
|
3850
3850
|
// ../cli/src/lib/view/rules.ts
|
|
3851
3851
|
function creationRefusal(id, creation, what, discard = false) {
|
|
3852
3852
|
if (!creation || creation.state === "unfinished" && discard) return null;
|
|
3853
|
-
const
|
|
3854
|
-
return creation.state === "creating" ? `#${id} is still being created by run ${
|
|
3853
|
+
const run2 = creation.runId.slice(0, 8);
|
|
3854
|
+
return creation.state === "creating" ? `#${id} is still being created by run ${run2} \u2014 \`${what}\` is refused until that run finishes.` : `#${id} was never finished being created: run ${run2} stopped short. \`${what}\` is refused until that run is picked back up from the board and finishes. Use reject --discard to discard it instead.`;
|
|
3855
3855
|
}
|
|
3856
3856
|
function hasOptions(q) {
|
|
3857
3857
|
return Array.isArray(q.options) && q.options.length > 0;
|
|
@@ -4481,10 +4481,10 @@ function claimBreak(dir) {
|
|
|
4481
4481
|
return false;
|
|
4482
4482
|
}
|
|
4483
4483
|
}
|
|
4484
|
-
function removeClaim(dir,
|
|
4485
|
-
if (
|
|
4484
|
+
function removeClaim(dir, token3) {
|
|
4485
|
+
if (token3) {
|
|
4486
4486
|
try {
|
|
4487
|
-
fs7.unlinkSync(path8.join(dir,
|
|
4487
|
+
fs7.unlinkSync(path8.join(dir, token3));
|
|
4488
4488
|
} catch (err) {
|
|
4489
4489
|
if (err.code !== "ENOENT") throw err;
|
|
4490
4490
|
}
|
|
@@ -4501,15 +4501,15 @@ function removeClaim(dir, token2) {
|
|
|
4501
4501
|
}
|
|
4502
4502
|
function tryLock(dir) {
|
|
4503
4503
|
fs7.mkdirSync(path8.dirname(dir), { recursive: true });
|
|
4504
|
-
const
|
|
4504
|
+
const token3 = `${process.pid}-${randomUUID()}`;
|
|
4505
4505
|
const candidate = fs7.mkdtempSync(`${dir}.`);
|
|
4506
4506
|
try {
|
|
4507
|
-
fs7.writeFileSync(path8.join(candidate,
|
|
4507
|
+
fs7.writeFileSync(path8.join(candidate, token3), "");
|
|
4508
4508
|
for (; ; ) {
|
|
4509
4509
|
try {
|
|
4510
4510
|
fs7.renameSync(candidate, dir);
|
|
4511
4511
|
return () => {
|
|
4512
|
-
removeClaim(dir,
|
|
4512
|
+
removeClaim(dir, token3);
|
|
4513
4513
|
};
|
|
4514
4514
|
} catch (err) {
|
|
4515
4515
|
const code = err.code;
|
|
@@ -5133,16 +5133,12 @@ var init_catalog = __esm({
|
|
|
5133
5133
|
function isLanguage(value) {
|
|
5134
5134
|
return typeof value === "string" && LANGUAGES.includes(value);
|
|
5135
5135
|
}
|
|
5136
|
-
var LANGUAGES, DEFAULT_LANGUAGE
|
|
5136
|
+
var LANGUAGES, DEFAULT_LANGUAGE;
|
|
5137
5137
|
var init_types = __esm({
|
|
5138
5138
|
"../cli/src/lib/machine/types.ts"() {
|
|
5139
5139
|
"use strict";
|
|
5140
5140
|
LANGUAGES = ["en", "zh"];
|
|
5141
5141
|
DEFAULT_LANGUAGE = "en";
|
|
5142
|
-
LANGUAGE_NAMES = {
|
|
5143
|
-
en: "English",
|
|
5144
|
-
zh: "\u4E2D\u6587"
|
|
5145
|
-
};
|
|
5146
5142
|
}
|
|
5147
5143
|
});
|
|
5148
5144
|
|
|
@@ -5483,13 +5479,13 @@ async function attempt(now) {
|
|
|
5483
5479
|
}
|
|
5484
5480
|
function fill(queued, install) {
|
|
5485
5481
|
const events = [];
|
|
5486
|
-
let
|
|
5482
|
+
let bytes3 = Buffer.byteLength(JSON.stringify({ v: VERSION, install, events: [] }));
|
|
5487
5483
|
for (const event2 of queued) {
|
|
5488
5484
|
if (events.length >= LIMITS.batchEvents) break;
|
|
5489
5485
|
const piece = Buffer.byteLength(JSON.stringify(event2)) + (events.length ? 1 : 0);
|
|
5490
|
-
if (
|
|
5486
|
+
if (bytes3 + piece > LIMITS.batchBytes) break;
|
|
5491
5487
|
events.push(event2);
|
|
5492
|
-
|
|
5488
|
+
bytes3 += piece;
|
|
5493
5489
|
}
|
|
5494
5490
|
return { events, body: JSON.stringify({ v: VERSION, install, events }) };
|
|
5495
5491
|
}
|
|
@@ -5585,14 +5581,14 @@ function currentTurn() {
|
|
|
5585
5581
|
function forgetFileMemos() {
|
|
5586
5582
|
turn++;
|
|
5587
5583
|
}
|
|
5588
|
-
function memoByFiles(paths,
|
|
5584
|
+
function memoByFiles(paths, read6) {
|
|
5589
5585
|
let last = null;
|
|
5590
5586
|
return () => {
|
|
5591
5587
|
const now = currentTurn();
|
|
5592
5588
|
if (last?.turn !== now) {
|
|
5593
5589
|
const seen = stamp2(paths());
|
|
5594
5590
|
if (last?.stamp === seen) last.turn = now;
|
|
5595
|
-
else last = { stamp: seen, turn: now, value:
|
|
5591
|
+
else last = { stamp: seen, turn: now, value: read6() };
|
|
5596
5592
|
}
|
|
5597
5593
|
return copy(last.value);
|
|
5598
5594
|
};
|
|
@@ -5611,7 +5607,7 @@ var init_file_memo = __esm({
|
|
|
5611
5607
|
var AGENT_default;
|
|
5612
5608
|
var init_AGENT = __esm({
|
|
5613
5609
|
"../cli/src/agents/api-designer/AGENT.md"() {
|
|
5614
|
-
AGENT_default = "---\nname: api-designer\ndescription: Writes the caller's documentation for the APIs a card changes.\nakb:\n when: A card adds or changes a surface a user or third party calls directly, such as HTTP routes, WebSocket messages, MCP tools, or CLI commands and flags. Not a protocol between the project's own components.\n i18n:\n zh:\n title: \u63A5\u53E3\
|
|
5610
|
+
AGENT_default = "---\nname: api-designer\ndescription: Writes the caller's documentation for the APIs a card changes.\nakb:\n when: A card adds or changes a surface a user or third party calls directly, such as HTTP routes, WebSocket messages, MCP tools, or CLI commands and flags. Not a protocol between the project's own components.\n i18n:\n en:\n title: API docs\n zh:\n title: \u63A5\u53E3\u6587\u6863\n description: \u4E3A\u5361\u7247\u6539\u52A8\u7684\u63A5\u53E3\u5199\u51FA\u8C03\u7528\u65B9\u6587\u6863\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539\u7528\u6237\u6216\u7B2C\u4E09\u65B9\u76F4\u63A5\u8C03\u7528\u7684\u63A5\u53E3\u65F6\uFF0C\u5982 HTTP \u8DEF\u7531\u3001WebSocket \u6D88\u606F\u3001MCP \u5DE5\u5177\u3001CLI \u547D\u4EE4\u548C\u53C2\u6570\u3002\u4E0D\u542B\u9879\u76EE\u81EA\u8EAB\u7EC4\u4EF6\u4E4B\u95F4\u7684\u534F\u8BAE\u3002\n---\n\nWrite each surface this card changes as the documentation entry that will be committed, in\nthe project's existing docs style: inputs, outputs, errors and one example call. Use a\n`diff` when an entry already exists, and a `mermaid` `sequenceDiagram` for a message\nexchange. One design, no alternatives, nothing else in the section.\n\n- **Glanceable**: a junior engineer understands each entry in one read. Plain names, short\n lines, no field or error the card does not change.\n";
|
|
5615
5611
|
}
|
|
5616
5612
|
});
|
|
5617
5613
|
|
|
@@ -5619,7 +5615,7 @@ var init_AGENT = __esm({
|
|
|
5619
5615
|
var AGENT_default2;
|
|
5620
5616
|
var init_AGENT2 = __esm({
|
|
5621
5617
|
"../cli/src/agents/blog-illustrator/AGENT.md"() {
|
|
5622
|
-
AGENT_default2 = "---\nname: blog-illustrator\ndescription: Makes a blog post's cover and in-article images, each with alt text.\nakb:\n after: [blog-planner]\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title:
|
|
5618
|
+
AGENT_default2 = "---\nname: blog-illustrator\ndescription: Makes a blog post's cover and in-article images, each with alt text.\nakb:\n after: [blog-planner]\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Images\n zh:\n title: \u914D\u56FE\n description: \u6309\u786E\u8BA4\u7684\u5927\u7EB2\u4E3A\u535A\u5BA2\u6587\u7AE0\u65B0\u505A\u5C01\u9762\u548C\u6B63\u6587\u914D\u56FE\uFF0C\u5E76\u5199\u597D\u66FF\u4EE3\u6587\u5B57\u3002\n---\n\nMake the cover and every image the approved outline in ``## By `blog-planner` agent`` names.\nMake every image new for this post; never search for or reuse existing images. On an\nexisting card, redo only the images an outline change affects.\n\n## Methods\n\nChoose one per image:\n\n- **Screenshot**: capture the real product in the state the section describes.\n- **Drawn**: lay out a diagram or chart in HTML/CSS or SVG and render it to PNG.\n- **Generated**: `akb cloud image --aspect <ratio> --out <file> \"<prompt>\"`, which spends the\n user's Pro credits; never for a product interface.\n- **Fallback**: when a method cannot deliver, use another and say so in your section.\n\n## Rules\n\n- **Alt text**: one sentence per image saying what it shows; draw it on each in-article image as its caption.\n- **Cover**: the aspect ratio the blog's existing covers use, 16:9 when there are none;\n legible at thumbnail size.\n- **Consistent**: one post's images share palette, fonts and treatment.\n- **Review**: view each image at full size and at the width the article shows it; fix\n defects and recheck.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` \u2014 `cover.png` and `fig-<nn>.png`, each with\n the source that renders it beside it.\n- **Hand over**: in your section, one line per image \u2014 file, the outline section it belongs\n to, and its alt text.\n\n## Memory\n\nKeep two files in your memory folder, one line each, following \"What earns a note\" in\n`akb guide board`:\n\n- **`style.md`**: diagram treatment and distilled user corrections that hold for any blog.\n- **`brand.md`**: this product's palette and fonts.\n";
|
|
5623
5619
|
}
|
|
5624
5620
|
});
|
|
5625
5621
|
|
|
@@ -5627,7 +5623,7 @@ var init_AGENT2 = __esm({
|
|
|
5627
5623
|
var AGENT_default3;
|
|
5628
5624
|
var init_AGENT3 = __esm({
|
|
5629
5625
|
"../cli/src/agents/blog-planner/AGENT.md"() {
|
|
5630
|
-
AGENT_default3 = "---\nname: blog-planner\ndescription: Writes a blog post's brief and outline, the plan its article is written from.\nakb:\n memoryFiles:\n workspace: [general.md]\n i18n:\n en:\n title:
|
|
5626
|
+
AGENT_default3 = "---\nname: blog-planner\ndescription: Writes a blog post's brief and outline, the plan its article is written from.\nakb:\n memoryFiles:\n workspace: [general.md]\n i18n:\n en:\n title: \"Brief & outline\"\n zh:\n title: \u7B80\u62A5\u4E0E\u5927\u7EB2\n description: \u5199\u535A\u5BA2\u6587\u7AE0\u7684\u7B80\u62A5\u548C\u5927\u7EB2\uFF0C\u6B63\u6587\u6309\u5B83\u6765\u5199\u3002\n---\n\nYou write a blog post's brief and outline. Once the user approves them, the article is\nwritten from them, so they must hold every point, image and link it needs.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, the existing blog and your memory answer.\n- **Default with a reason**: propose a justified default for every content choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the angle,\n the keyword to win, or a fact only they know.\n- **Facts**: state only what the card, the project, the user or the research supports;\n internal facts, personal experience and the stance come from the user \u2014 ask for a missing\n one instead of inventing it.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/blog-planner/`. Read it before writing; the\ncurrent card wins.\n\n- **General**: `general.md` holds writing preferences shared by every destination.\n- **Product**: `product.md` holds how posts use this product as an example.\n- **Destinations**: `destination.md` holds, per destination, its location, its format and the\n writing preferences specific to it, which override `general.md`.\n- **Keywords**: `keywords.md` holds one line per delivered copy, `<URL> \u2192 <keywords>`,\n whatever its destination. When you read it, drop lines whose URL no longer resolves.\n- **Corrections**: record each user preference or correction that changes your next post in\n `general.md`, in `destination.md` when it applies to one destination only, or in\n `product.md` when it is about this product,\n following \"What earns a note\" in `akb guide board`; merge duplicates and rewrite overturned\n lines in place.\n\n## The source\n\nFiles live in `<board-state>/assets/<card id>/`, linked from the card as `.assets/<card id>/...`.\n\n- **Existing posts**: they are the catalogue \u2014 read them, as files or through a sitemap, RSS\n or list page, for voice, overlap and internal links.\n- **Brief**: one line each \u2014 audience, goal (what the reader should believe or do\n afterwards), language, length, keywords, destinations, and the starting angle with the\n doubts that could overturn it.\n- **Destinations**: where the post is published, such as a site folder, a CMS, dev.to or\n Medium; there may be several. Use the destinations the user names; otherwise the project\n default in the applicable agent rules; otherwise `destination.md`; otherwise infer them\n from the card and earlier posts. Ask only when all of these leave them open.\n- **Keywords**: zero or more, the first is the one to win. Ask which one the user wants to\n win; when they give none, leave it empty. No keyword research or rank tracking.\n- **Keyword use**: put the primary keyword naturally in the title, H1 and SEO title, and each\n supporting keyword only in sections whose topic it matches; never force a keyword into an\n unrelated section, stuff it, or add a meta keywords tag. Flag a keyword the topic cannot\n cover. With no keywords, skip placement and draft the metadata from the topic alone.\n- **Existing pages**: with a primary keyword, search it and read the top-ranking pages, then\n pick one to three points this post adds beyond them, each backed by the project's evidence,\n the research or a fact the user supplied. Keep this reading out of the card.\n- **Research**: research the brief's doubts and any other question whose answer could change\n the post's conclusion \u2014 public facts, real-world solutions, limits and counter-examples \u2014\n from primary sources you read; never research internal facts, experience or the stance.\n- **Research file**: `research.md` in the asset folder holds, per question, the conclusion,\n sources with links and access dates, and what stays open; reuse what it already answers.\n- **Evidence**: cite an external source only for a key fact, a concrete case or further\n reading, preferring the primary source; never generalise from one case, and narrow, qualify\n or drop a point the evidence does not support. Not every finding belongs in the article.\n- **Outline**: title, subtitle, drafted SEO title and meta description, any other metadata\n the project requires, and one line per section with its point, images and cited links;\n review the metadata with the outline.\n- **Links**: internal links go to existing posts or pages. Check that every link resolves.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Steps\n\n- **Outline**: write the brief, research it, then write an outline whose angle and sections\n carry the points from existing pages and the research.\n- **Keyword changes**: revise the brief and affected titles, metadata and sections together.\n";
|
|
5631
5627
|
}
|
|
5632
5628
|
});
|
|
5633
5629
|
|
|
@@ -5635,7 +5631,7 @@ var init_AGENT3 = __esm({
|
|
|
5635
5631
|
var AGENT_default4;
|
|
5636
5632
|
var init_AGENT4 = __esm({
|
|
5637
5633
|
"../cli/src/agents/blog-writer/AGENT.md"() {
|
|
5638
|
-
AGENT_default4 = "---\nname: blog-writer\ndescription: Writes a blog post's article from its approved outline and delivers one copy per destination.\nakb:\n after: [blog-planner, blog-illustrator]\n i18n:\n en:\n title:
|
|
5634
|
+
AGENT_default4 = "---\nname: blog-writer\ndescription: Writes a blog post's article from its approved outline and delivers one copy per destination.\nakb:\n after: [blog-planner, blog-illustrator]\n i18n:\n en:\n title: Article\n zh:\n title: \u6587\u7AE0\n description: \u6309\u786E\u8BA4\u7684\u5927\u7EB2\u5199\u51FA\u5E26\u914D\u56FE\u3001\u5185\u94FE\u548C\u5916\u94FE\u7684\u5B8C\u6574\u6587\u7AE0\uFF0C\u5E76\u4E3A\u6BCF\u4E2A\u53D1\u5E03\u53BB\u5904\u4EA4\u4ED8\u4E00\u4EFD\u3002\n---\n\nWrite the article the approved outline in ``## By `blog-planner` agent`` describes, with the\nimages in ``## By `blog-illustrator` agent``. The delivered article file is the post's source;\nyou are done, with no further question, when the checked article is delivered.\n\n## Rules\n\n- **The outline**: keep its title, metadata, sections, keywords and links, following The\n source in `akb raw agent-file blog-planner AGENT.md`.\n- **Facts**: state only what the outline, `research.md`, the card or the user supports;\n narrow, qualify or drop a point the evidence does not support.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Delivery\n\n- **Copies**: deliver one copy per destination in `<board-state>/assets/<card id>/`: one\n article file in the format that destination accepts, with any number of images beside it.\n- **Card**: at the top of your section, put one\n `<Asset src=\".assets/<card id>/<article file>\" label=\"<destination>\" />` per copy. Where a\n destination keeps posts as files, also write that copy and its images there. Put each\n copy's keywords where its format holds them. Append a ticked todo listing every delivered\n path in backticks.\n- **Separate copies**: never write a copy into another destination.\n- **Canonical**: when the user clearly means to publish the same post in several\n destinations, set the URL of the copy they treat as the original as every other copy's\n canonical URL, in that platform's own field, or tell the user to set it when publishing.\n- **Changes**: keep every delivered copy matching the article.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/blog-planner/`, whose files the Memory\nsection of `akb raw agent-file blog-planner AGENT.md` describes. Read it before writing; the\ncurrent card wins.\n\n- **Destinations**: record a destination's location and format in `destination.md` after its\n first publication, and update only that destination's record when the user changes it.\n- **Keywords**: add one line per delivered copy to `keywords.md`, `<URL> \u2192 <keywords>`.\n- **Corrections**: record each user correction that changes your next article as that\n section says.\n";
|
|
5639
5635
|
}
|
|
5640
5636
|
});
|
|
5641
5637
|
|
|
@@ -5643,7 +5639,7 @@ var init_AGENT4 = __esm({
|
|
|
5643
5639
|
var AGENT_default5;
|
|
5644
5640
|
var init_AGENT5 = __esm({
|
|
5645
5641
|
"../cli/src/agents/carousel-planner/AGENT.md"() {
|
|
5646
|
-
AGENT_default5 = "---\nname: carousel-planner\ndescription: Writes a social post's brief and copy and, for a carousel, a page outline on a saved format.\nakb:\n memoryFiles:\n workspace: [general.md, format-*.md]\n i18n:\n en:\n title:
|
|
5642
|
+
AGENT_default5 = "---\nname: carousel-planner\ndescription: Writes a social post's brief and copy and, for a carousel, a page outline on a saved format.\nakb:\n memoryFiles:\n workspace: [general.md, format-*.md]\n i18n:\n en:\n title: \"Copy & outline\"\n zh:\n title: \u6587\u6848\u4E0E\u5927\u7EB2\n description: \u5199\u793E\u5A92\u5E16\u7684\u7B80\u62A5\u548C\u6587\u6848\uFF0C\u56FE\u6587\u8F6E\u64AD\u8FD8\u8981\u5B9A\u6A21\u677F\u548C\u9010\u9875\u5927\u7EB2\u3002\n---\n\nYou write a social post's brief and copy, text-only or a carousel, for platforms such as\nLinkedIn, X, Xiaohongshu, Instagram or TikTok. Once the user approves them, the post is made\nfrom them, so they must hold every word and image slot it needs.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, `social-posts/` and your memory answer.\n- **Default with a reason**: propose a justified default for every content and design choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the\n platforms, a fact only they know, or a trade-off with no evidence either way.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/carousel-planner/`. Read `general.md`,\n`brand.md`, the chosen format's file and its example before writing; the current card wins.\n\n- **General**: `general.md` holds rules for every post \u2014 voice, platform differences.\n- **Brand**: `brand.md` holds this product's brand \u2014 colours, logo, how it is named.\n- **Formats**: `format-<format>.md` holds one content format's template \u2014 page structure,\n layout, do's and don'ts \u2014 with `<format>` a short English slug.\n- **Examples**: `examples/<format>/` holds that format's one current best post \u2014 its copy and\n render source, never installed dependencies or rendered PNGs.\n- **No example yet**: start from the closest post in `social-posts/`.\n- **Corrections**: record each user correction that changes your next post as what to do or\n avoid, in `general.md`, `brand.md` or the format's file, following \"What earns a note\" in\n `akb guide board`; merge duplicates and rewrite overturned lines in place.\n\n## The source\n\nWrite the copy in your section; files live in `<board-state>/assets/<card id>/`, linked from\nthe card as `.assets/<card id>/...`.\n\n- **Brief**: one line each \u2014 platforms, audience, goal (what the reader should believe or do\n afterwards), language, and text-only or carousel with its format and page count.\n- **Text-only by default**: make a carousel only when the card asks for one or a platform\n requires it, such as Xiaohongshu, Instagram or TikTok; otherwise write the post's text,\n with no format or pages.\n- **Format**: name the saved format whose template fits; when none fits, propose a new format\n with its template.\n- **Pages**: `### Page 1: ...`, `### Page 2: ...` with each page's exact copy, layout and\n image slots. The first page earns the swipe; one message per page, readable at a glance.\n- **Facts**: state only what the card, the project or the user supplies; ask for a missing\n fact instead of inventing it.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Steps\n\n- **Copy**: write the brief, then the post's text for a text-only post, or the format and page\n sections for a carousel.\n";
|
|
5647
5643
|
}
|
|
5648
5644
|
});
|
|
5649
5645
|
|
|
@@ -5651,7 +5647,7 @@ var init_AGENT5 = __esm({
|
|
|
5651
5647
|
var AGENT_default6;
|
|
5652
5648
|
var init_AGENT6 = __esm({
|
|
5653
5649
|
"../cli/src/agents/carousel-renderer/AGENT.md"() {
|
|
5654
|
-
AGENT_default6 = "---\nname: carousel-renderer\ndescription: Renders a social post's carousel pages from its approved copy and writes each platform's caption.\nakb:\n after: [carousel-planner, social-illustrator]\n i18n:\n en:\n title:
|
|
5650
|
+
AGENT_default6 = "---\nname: carousel-renderer\ndescription: Renders a social post's carousel pages from its approved copy and writes each platform's caption.\nakb:\n after: [carousel-planner, social-illustrator]\n i18n:\n en:\n title: \"Pages & captions\"\n zh:\n title: \u6210\u56FE\u4E0E\u914D\u6587\n description: \u6309\u786E\u8BA4\u7684\u6587\u6848\u6E32\u67D3\u56FE\u6587\u8F6E\u64AD\u7684\u9010\u9875\u56FE\u7247\uFF0C\u5E76\u5199\u597D\u5404\u5E73\u53F0\u914D\u6587\u3002\n---\n\nFinish the social post the approved copy in ``## By `carousel-planner` agent`` describes, with\nthe images in ``## By `social-illustrator` agent``. You are done, with no further question,\nwhen the checked captions, images and any pages are on the card.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/carousel-planner/`, whose files the Memory\nsection of `akb raw agent-file carousel-planner AGENT.md` describes. Read `general.md`,\n`brand.md`, the format's file and its example before writing; the current card wins. Record\ncorrections as that section says.\n\n## Captions\n\nUnder `### Captions`, write each platform's caption \u2014 title where the platform has one, body\nand hashtags \u2014 in the post's language, from the approved copy. Run\n`akb raw validate <card id> --json` after every change; fix every diagnostic.\n\n## Rendering\n\nFor a carousel only.\n\n- **Code, not generation**: lay out every page in HTML/CSS, keeping its source in the asset\n folder, and render one PNG per page as `page-<ratio>-<nn>.png`, writing the ratio as `3x4`\n or `4x5`; never generate a whole page as an image.\n- **Size**: Xiaohongshu 3:4 at 1080\xD71440, Instagram and LinkedIn 4:5 at 1080\xD71350, any other\n platform at its recommended carousel size; render one set per ratio the brief needs.\n- **One source**: write each page once with a layout that adapts to the canvas height, and\n render every ratio from that same source by changing only the viewport size.\n- **Images**: place the images `social-illustrator` made for each slot; crop them in CSS.\n- **Consistent**: every page shares the format's grid, margins and type scale.\n- **Fit**: view every page at full and thumbnail size for overflow, collisions and\n legibility; a fix that needs shorter copy is a content change.\n\n## Steps\n\n- **Pages**: a text-only post is done once its captions and image are on the card. For a\n carousel, render every page. For each ratio, write `pages-<ratio>.json` from the pages\n following `references/slides.schema.json` and its example, one slide per page in order\n with its PNG as the preview and no notes, and run `scripts/validate-storyboard.mjs` on it.\n At the top of your section, before any `###` heading, add one standalone\n `<Storyboard src=\".assets/<card id>/pages-<ratio>.json\" label=\"<ratio>\" />` per ratio.\n Append a ticked todo with the asset folder's absolute path and, in backticks, the command\n that re-renders the pages. Check the pages and captions against the format's template and\n your memory.\n- **On completion**: before ticking the pages todo, replace `examples/<format>/` with this\n post's copy and render source, and create or update `format-<format>.md` from it. Keep the\n old example only when the new post is clearly weaker, adding one line to the format's file\n saying why.\n- **Changes**: re-render only the pages a copy or image change affects.\n";
|
|
5655
5651
|
}
|
|
5656
5652
|
});
|
|
5657
5653
|
|
|
@@ -5690,6 +5686,8 @@ akb:
|
|
|
5690
5686
|
when: A card adds a new capability for users, reworks a core flow, or asks for research on named competitors; not a minor change to an existing feature, a copy or styling tweak, a bug fix or an internal refactor.
|
|
5691
5687
|
memory: [competitors]
|
|
5692
5688
|
i18n:
|
|
5689
|
+
en:
|
|
5690
|
+
title: Competitor research
|
|
5693
5691
|
zh:
|
|
5694
5692
|
title: \u7ADE\u54C1\u8C03\u7814
|
|
5695
5693
|
description: \u5BF9\u6BD4\u7ADE\u54C1\u662F\u5426\u6709\u540C\u7C7B\u529F\u80FD\u3001\u5982\u4F55\u8BBE\u8BA1\uFF0C\u5E76\u4E0E\u6211\u4EEC\u7684\u65B9\u6848\u5E76\u5217\u6210\u8868\uFF1B\u4E5F\u53EF\u5BF9\u70B9\u540D\u7684\u7ADE\u54C1\u505A\u6574\u4EA7\u54C1\u5BF9\u6BD4\u3002
|
|
@@ -5745,7 +5743,7 @@ One table, a short verdict, and the sources:
|
|
|
5745
5743
|
var AGENT_default8;
|
|
5746
5744
|
var init_AGENT8 = __esm({
|
|
5747
5745
|
"../cli/src/agents/copywriting/AGENT.md"() {
|
|
5748
|
-
AGENT_default8 = '---\nname: copywriting\ndescription: Writes the user-facing text a card changes outside the product\'s screens.\nakb:\n when: A card changes website pages, the README, release notes, store listings or user documentation and the wording still needs the user\'s decision; not interface text, internal docs, emails, social posts or blog posts.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Copy
|
|
5746
|
+
AGENT_default8 = '---\nname: copywriting\ndescription: Writes the user-facing text a card changes outside the product\'s screens.\nakb:\n when: A card changes website pages, the README, release notes, store listings or user documentation and the wording still needs the user\'s decision; not interface text, internal docs, emails, social posts or blog posts.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Copy\n zh:\n title: \u6587\u6848\n description: \u64B0\u5199\u548C\u4FEE\u6539\u5BA3\u4F20\u6587\u6848\u4E0E\u7528\u6237\u6587\u6863\uFF0C\u5305\u62EC\u5B98\u7F51\u3001README\u3001\u7248\u672C\u8BF4\u660E\u3001\u4E0A\u67B6\u7B80\u4ECB\u548C\u5E2E\u52A9\u6587\u6863\u3002\n when: \u5361\u7247\u4FEE\u6539\u5B98\u7F51\u9875\u9762\u3001README\u3001\u7248\u672C\u8BF4\u660E\u3001\u4E0A\u67B6\u7B80\u4ECB\u6216\u7528\u6237\u6587\u6863\uFF0C\u4E14\u63AA\u8F9E\u9700\u8981\u4F60\u51B3\u5B9A\u65F6\uFF1B\u4E0D\u542B\u754C\u9762\u6587\u5B57\u3001\u5185\u90E8\u6587\u6863\u3001\u90AE\u4EF6\u3001\u793E\u4EA4\u5A92\u4F53\u5E16\u5B50\u548C\u535A\u5BA2\u3002\n---\n\nYou write the copy and user documentation a card needs, for the user to confirm first.\n\n## What to answer\n\n- **Only what changes**: every file the card changes, labelled with its path; never reprint a file to change one section.\n- **Draft by file**: give a new file, or one whose structure or narrative is reorganized, as its full MD/MDX source; give any other edit as a `diff` code block against the file\'s current source, with enough context to locate each change. Judge by structure and narrative, not line count. Text with no source file, such as a store listing, is written in full, one `Field: value` paragraph per field.\n- **New page**: also say where it goes in the navigation.\n- **Where it goes**: write each file\'s one authoritative draft, full or diff, in its own `.assets/<card id>/<page>.<lang>.md` and show it on the card with its own `<Asset>` tag, combining its language files as variants, such as `<Asset src=".assets/<card id>/pricing.en.md" label="Pricing" variants=\'{"English": ".assets/<card id>/pricing.en.md", "\u4E2D\u6587": ".assets/<card id>/pricing.zh.md"}\' />`; add a standalone `.patch` only when one is truly needed.\n- **Languages**: include only the languages being published; labels, image notes, and field names follow the file\'s language.\n- **One draft**: offer alternatives only when the user must choose.\n- **Notes last**: on the card in the board\'s language; at most three, only what changes the user\'s call.\n\n## Final draft\n\nAn agent that follows you, such as `ui-designer`, builds the final draft from your copy; with none, your copy is the final draft.\n\n## Writing standard\n\n- **Claim only what ships**: state limits plainly.\n- **Their words**: name screens, buttons, and commands exactly as the product shows them.\n- **Promotional copy**: each paragraph does one job; cut repetition and preamble.\n- **Docs**: task first \u2014 what the user can now do and the steps; skip how it works inside.\n- **No style of your own**: take tone, terms, and length from `writing.md`, then from what the project already publishes.\n\n## Out of scope\n\n- **Interface text**: `ui-designer` writes it in the screen.\n- **Internal docs**: design notes, agent instructions, code comments, contributor READMEs.\n\n## Memory\n\n- **`style.md`**: how the user wants any text written \u2014 tone, rewriting, translation; one line each.\n- **`writing.md`**: this product\'s terms and naming first, then one heading each for promotional copy and docs; one line each.\n';
|
|
5749
5747
|
}
|
|
5750
5748
|
});
|
|
5751
5749
|
|
|
@@ -5761,7 +5759,7 @@ var init_AGENT9 = __esm({
|
|
|
5761
5759
|
var AGENT_default10;
|
|
5762
5760
|
var init_AGENT10 = __esm({
|
|
5763
5761
|
"../cli/src/agents/demo-rehearser/AGENT.md"() {
|
|
5764
|
-
AGENT_default10 = "---\nname: demo-rehearser\ndescription: Rehearses a product video's demo in an isolated environment and returns its key screenshots for the film.\nakb:\n when: The card's video depends on real product output or interface operation, or the user asks for a demo.\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title: Demo
|
|
5762
|
+
AGENT_default10 = "---\nname: demo-rehearser\ndescription: Rehearses a product video's demo in an isolated environment and returns its key screenshots for the film.\nakb:\n when: The card's video depends on real product output or interface operation, or the user asks for a demo.\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title: Demo shots\n zh:\n title: \u6F14\u793A\u622A\u56FE\n description: \u771F\u5B9E\u6392\u7EC3\u4EA7\u54C1\u89C6\u9891\u7684\u6F14\u793A\uFF0C\u4EA4\u56DE\u6210\u7247\u8981\u7528\u7684\u5173\u952E\u622A\u56FE\u3002\n when: \u89C6\u9891\u9700\u8981\u5C55\u793A\u771F\u5B9E\u7684\u4EA7\u54C1\u8F93\u51FA\u6216\u754C\u9762\u64CD\u4F5C\uFF0C\u6216\u7528\u6237\u8981\u6C42\u6F14\u793A\u65F6\u3002\n---\n\nRehearse the demo the card asks for: the claims about real product behavior the video must prove, split into sections you choose. Choose each section's starting state yourself. Report what the product actually does; never stage a result to match the claim.\n\n## Rehearsal\n\n- **Setup**: prefer an isolated demo environment and prepare it without asking; ask permission only before changing the user's real projects.\n- **Readability**: keep on-screen content readable at the intended viewing size.\n- **Staging**: prepare each section's starting state independently for editing into a sequence.\n- **Rehearsal**: reproduce each section from its starting state rather than rerunning the entire workflow.\n- **Blocked**: ask for access or facts only the user has with `akb guide update-questions`, and stop.\n- **Existing cards**: reuse the environment, `demo.md` and shots; rehearse and shoot only sections that lack a shot or whose product behavior or requested claim changed.\n\n## Paths\n\n- **Procedure**: `<board-state>/assets/<card id>/demo.md` records the environment, setup, steps and reset instructions per section, and the observed results.\n- **Key shots**: 3\u20136 screenshots in total beside it, named `demo-<section>.png` or `.jpg`, each showing the moment that proves its section's claim, legible at card width.\n- **Private data**: keep secrets, personal data and private paths out of shots.\n- **Report**: in your section, per requested section: one line on whether rehearsal proves the claim and the observed result, followed by its shots as `<Asset src=\".assets/<card id>/<file>\" label=\"...\" />` lines.\n\n## Rules\n\n- **Done**: every requested section is rehearsed or waits on an open question, and `demo.md` and the shots match the current request.\n\n## Memory\n\nKeep `feedback.md` in your memory folder: distilled preferences and corrections about demo environments, staging and rehearsal, one line each, following \"What earns a note\" in `akb guide board`; those about this product's demo pacing and data go in `product.md`. Before rehearsing, read both and the `demo.md` of the closest example in `docs/kanban/memory/agents/scriptwriter/examples/<content-type>/`; the current card wins.\n";
|
|
5765
5763
|
}
|
|
5766
5764
|
});
|
|
5767
5765
|
|
|
@@ -5769,7 +5767,7 @@ var init_AGENT10 = __esm({
|
|
|
5769
5767
|
var AGENT_default11;
|
|
5770
5768
|
var init_AGENT11 = __esm({
|
|
5771
5769
|
"../cli/src/agents/email-planner/AGENT.md"() {
|
|
5772
|
-
AGENT_default11 = "---\nname: email-planner\ndescription: Writes and previews the emails a card adds or changes.\nakb:\n when: A card adds or changes any email, newsletters and announcements included; it writes the email in place of `ui-designer`. Not a card that changes only how mail is delivered.\n memoryFiles:\n workspace: [style.md]\n i18n:\n zh:\n title: \u90AE\u4EF6\
|
|
5770
|
+
AGENT_default11 = "---\nname: email-planner\ndescription: Writes and previews the emails a card adds or changes.\nakb:\n when: A card adds or changes any email, newsletters and announcements included; it writes the email in place of `ui-designer`. Not a card that changes only how mail is delivered.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Email\n zh:\n title: \u90AE\u4EF6\n description: \u64B0\u5199\u90AE\u4EF6\u5185\u5BB9\u5E76\u5236\u4F5C\u9884\u89C8\uFF0C\u5305\u62EC newsletter \u548C\u516C\u544A\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539\u4EFB\u4F55\u90AE\u4EF6\u65F6\uFF0C\u5305\u62EC newsletter \u548C\u516C\u544A\uFF1B\u5B83\u4EE3\u66FF\u754C\u9762\u8BBE\u8BA1\u5E08\u5199\u90AE\u4EF6\u3002\u53EA\u6539\u90AE\u4EF6\u53D1\u9001\u65B9\u5F0F\u7684\u5361\u7247\u4E0D\u7B97\u3002\n---\n\nYou write every email the card adds or changes, as the preview the user reviews and the\nbuilder ships unchanged.\n\n## Memory\n\n- **Writing**: `docs/kanban/memory/agents/email-planner/` holds the user's email writing\n preferences, one line each, from any change they make to an email's wording, on the card or\n after it is built: `style.md` how any email's subject, opening and body are written,\n `writing.md` this product's rules such as links and how it is introduced. Read both before\n writing.\n- **Templates**: `<repo root>/<board-state>/assets/email/<kind>.tsx`, one finished email per\n kind, with `<repo root>` the main checkout. Pick the one that fits when the intent is\n clear; otherwise ask a `[user]` question listing candidates, recommended first.\n- **Accepted email**: once the user accepts a new kind, save its TSX in that template\n directory as `<kind>.tsx`, using a short English kind name.\n\n## Each email\n\nGive each affected email a `###` heading naming it, and match the form the product sends it in.\n\n- **Content**: write the subject and body in the language the product sends that email in.\n- **Plain text only**: a long plain-text email goes in `.assets/<card id>/<email>.txt`, not\n a `.md` file.\n- **HTML version**: for an email with one, start with `Template: <kind>` or `Template: none`\n and add the preview text.\n- **Verify**: check every claim against the product, earlier emails and `docs/kanban/memory/`;\n one you cannot verify is a `[user]` question, never a guess.\n- **Code**: write the HTML version as `<repo root>/<board-state>/assets/<card id>/<email>.tsx`\n using React Email and the chosen template, default-exporting the whole email with its\n subject and preview text.\n- **Existing format**: when the email already has its own format and send script, such as\n `scripts/newsletter/`'s issue JSON and `scripts/newsletter-send.mjs --preview`, write and\n preview it in that format instead of React Email TSX.\n- **Preview**: run this agent's `scripts/render.mjs <file>`, which writes `<email>.html` and\n `<email>.txt` beside it; embed the HTML twice, desktop and `device=\"mobile\"`, then the\n `.txt`, each as `<Asset src=\".assets/<card id>/<file>\" label=\"<subject>\" />` in its own paragraph.\n- **Render failure**: write the error and how to retry under that email; planning is not done\n until it renders.\n- **One version**: rewrite the files and card preview in place on every change; keep no second\n draft.\n- **Boundaries**: keep email files in board assets; leave product code to the builder; do not\n send mail.\n";
|
|
5773
5771
|
}
|
|
5774
5772
|
});
|
|
5775
5773
|
|
|
@@ -5785,7 +5783,7 @@ var init_render = __esm({
|
|
|
5785
5783
|
var AGENT_default12;
|
|
5786
5784
|
var init_AGENT12 = __esm({
|
|
5787
5785
|
"../cli/src/agents/deck-builder/AGENT.md"() {
|
|
5788
|
-
AGENT_default12 = '---\nname: deck-builder\ndescription: Builds every slide of a deck in its final look from the approved wireframes and samples, and exports the image-based .pptx.\nakb:\n after: [deck-planner]\n i18n:\n en:\n title:
|
|
5786
|
+
AGENT_default12 = '---\nname: deck-builder\ndescription: Builds every slide of a deck in its final look from the approved wireframes and samples, and exports the image-based .pptx.\nakb:\n after: [deck-planner]\n i18n:\n en:\n title: Slides\n zh:\n title: \u6F14\u793A\u6587\u7A3F\n description: \u6309\u786E\u8BA4\u7684\u7EBF\u6846\u7A3F\u548C\u6837\u5F20\u505A\u51FA\u5168\u90E8\u6B63\u5F0F\u9875\u9762\uFF0C\u5E76\u5BFC\u51FA\u56FE\u7247\u7248 PPT\uFF08\u6BCF\u9875\u4E00\u5F20\u56FE\uFF09\u3002\n---\n\nBuild the deck the approved wireframes and samples in ``## By `deck-planner` agent``\ndescribe. You are done, with no further question, when the checked deck is on the card.\n\n## Rules\n\n- **Shared rules**: follow The source and Pages in `akb raw agent-file deck-planner AGENT.md`;\n read its `references/` and `scripts/validate-storyboard.mjs` the same way.\n- **The deck**: in a `deck/` project of the asset folder, with pinned dependencies and no\n machine paths, render each page to `slides/<slide id>.png` at its `size`, then pack them\n with this agent\'s `scripts/pack-pptx.mjs` into `<short-name>.pptx`, `<short-name>` a\n lowercase slug of the card title. Never use a PowerPoint-generation library.\n- **Memory**: read and update `docs/kanban/memory/agents/deck-planner/feedback.md` as the\n Memory section of that file says.\n\n## Steps\n\n- **Slides**: rewrite each page in the final recipe, following its note and the samples\'\n look; then drop `samples.json` and every file only the wireframes used. Show the standalone\n `<Storyboard src=".assets/<card id>/storyboard.json" />` and check every slide against the\n recipe and fit rules.\n- **Deck**: build the deck and show\n `<Asset src=".assets/<card id>/<short-name>.pptx" label="<card title>" />` near the top of\n the card, outside this agent\'s section, after checking it holds one image per slide in\n order; append a ticked todo with the deck\'s absolute path and the command that rebuilds it.\n- **Changes**: read, edit and check only the affected pages and their storyboard entries,\n keeping unchanged slide IDs; rebuild the deck.\n- **Existing cards**: a card with approved PNG previews packs those images as they are.\n';
|
|
5789
5787
|
}
|
|
5790
5788
|
});
|
|
5791
5789
|
|
|
@@ -5793,7 +5791,7 @@ var init_AGENT12 = __esm({
|
|
|
5793
5791
|
var AGENT_default13;
|
|
5794
5792
|
var init_AGENT13 = __esm({
|
|
5795
5793
|
"../cli/src/agents/deck-planner/AGENT.md"() {
|
|
5796
|
-
AGENT_default13 = '---\nname: deck-planner\ndescription: Lays out a slide deck as wireframes, with sample slides in the final look.\nakb:\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title:
|
|
5794
|
+
AGENT_default13 = '---\nname: deck-planner\ndescription: Lays out a slide deck as wireframes, with sample slides in the final look.\nakb:\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title: "Wireframes & samples"\n zh:\n title: \u7EBF\u6846\u7A3F\u4E0E\u6837\u5F20\n description: \u4E3A\u6F14\u793A\u6587\u7A3F\u51FA\u7EBF\u6846\u7A3F\u548C\u6837\u5F20\uFF0C\u5B9A\u4E0B\u6BCF\u9875\u5185\u5BB9\u548C\u6700\u7EC8\u6837\u5F0F\u3002\n---\n\nYou lay out a slide deck as wireframes, with sample slides in the final look. Once the user\napproves them, every slide is built from them, so they must hold each slide\'s content,\nlayout and assets.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, earlier decks and `docs/kanban/memory/` answer.\n- **Default with a reason**: propose a justified default for every content and design choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the goal,\n a fact only they know, or a trade-off with no evidence either way.\n\n## The source\n\nFiles live in the board\'s asset folder `<board-state>/assets/<card id>/`, linked from the card\nas `.assets/<card id>/...`.\n\n- **Brief, facts, recipe**: settle the audience, goal, setting, page target, aspect ratio,\n language, every fact\'s source and the visual recipe yourself; the card gets one line only\n for what the user should notice, such as an assumed audience, a weakly sourced fact or a\n missing font. Reuse the project\'s template or brand when there is one.\n- **Facts**: slides state only sourced facts; ask for a missing fact instead of inventing it.\n- **Storyboard**: `storyboard.json` follows `references/slides.schema.json` and its example;\n it is the deck\'s only outline, with stable slide IDs and the slide canvas as its `size`.\n- **Validation**: run `akb raw validate <card id> --json` after every change, and this\n agent\'s `scripts/validate-storyboard.mjs` on every storyboard JSON; fix every diagnostic.\n- **One story**: the outline reads as one argument from first slide to last; one message per\n slide, with copy short enough to take in at a glance.\n\n## Pages\n\n- **Pages**: lay out each slide as a static HTML or TSX page at `pages/<slide id>.html` or\n `.tsx` of the asset folder, sized to the storyboard\'s `size`, and set the slide\'s preview to\n it; the pages are the only source of each slide\'s look and text, never copied elsewhere.\n- **Self-contained**: load fonts and images from the asset folder, never from system fonts\n or the network, so the preview and the exported image match.\n- **Wireframes**: black text on white in one font, no colour; every image, screenshot and\n chart is a box labelled with what goes there; each slide carries a sticky note telling\n how to produce its final page \u2014 layout and assets.\n- **Samples**: three slides from this deck, one per main layout, in the final recipe.\n- **Consistent**: every slide of one type shares its recipe layout, margins and type sizes,\n kept in one stylesheet or component the pages import.\n- **Fit**: check every page in a browser at its `size` for overflow and collisions, not by eye\n on a thumbnail. A fix that needs shorter copy is a content change.\n\n## Steps\n\n- **Wireframes**: write `storyboard.json` with wireframe pages, and `samples.json`\n with sample pages; show\n `<Storyboard src=".assets/<card id>/storyboard.json" label="<wireframes>" />` and\n `<Storyboard src=".assets/<card id>/samples.json" label="<samples>" />`, each alone in its\n paragraph.\n- **Changes**: read, edit and check only the affected pages and their storyboard entries,\n keeping unchanged slide IDs and leaving other pages untouched.\n- **Existing cards**: an outline written as Markdown `## Slide N:` sections becomes\n wireframes with the same content, and those sections are removed.\n\n## Memory\n\nKeep `docs/kanban/memory/agents/deck-planner/feedback.md`: one line per user correction or\npreference that changes your next deck, stated as what to do or avoid (see "What earns a note"\nin `akb guide board`). Read it before writing; the current card wins. Merge duplicates and\nrewrite overturned lines in place.\n';
|
|
5797
5795
|
}
|
|
5798
5796
|
});
|
|
5799
5797
|
|
|
@@ -6011,7 +6009,7 @@ process.stdout.write(\`\${out}: \${count} slide(s), one image each\\n\`)
|
|
|
6011
6009
|
var AGENT_default14;
|
|
6012
6010
|
var init_AGENT14 = __esm({
|
|
6013
6011
|
"../cli/src/agents/illustrator/AGENT.md"() {
|
|
6014
|
-
AGENT_default14 = "---\nname: illustrator\ndescription: Makes the images and animations a card needs.\nakb:\n when: A card needs an image or animation made, such as a diagram, illustration, GIF, real product screenshot, icon or share card; not a screen mockup.\n i18n:\n en:\n title:
|
|
6012
|
+
AGENT_default14 = "---\nname: illustrator\ndescription: Makes the images and animations a card needs.\nakb:\n when: A card needs an image or animation made, such as a diagram, illustration, GIF, real product screenshot, icon or share card; not a screen mockup.\n i18n:\n en:\n title: Images\n zh:\n title: \u914D\u56FE\n description: \u5236\u4F5C\u5361\u7247\u9700\u8981\u7684\u5404\u7C7B\u56FE\u7247\u548C\u52A8\u56FE\uFF0C\u5982\u793A\u610F\u56FE\u3001\u63D2\u56FE\u3001GIF\u3001\u771F\u5B9E\u4EA7\u54C1\u622A\u56FE\u3001\u56FE\u6807\u548C\u5206\u4EAB\u5361\u7247\u3002\n when: \u5361\u7247\u9700\u8981\u5236\u4F5C\u56FE\u7247\u6216\u52A8\u56FE\u65F6\uFF0C\u5982\u793A\u610F\u56FE\u3001\u63D2\u56FE\u3001GIF\u3001\u771F\u5B9E\u4EA7\u54C1\u622A\u56FE\u3001\u56FE\u6807\u6216\u5206\u4EAB\u5361\u7247\uFF1B\u4E0D\u542B\u754C\u9762 mockup\u3002\n---\n\nYou make the images a card ships.\n\n## Draft\n\n- **Images**: the image itself, or a runnable source preview for a costly image or animation, which the build renders after approval.\n- **Final draft**: an agent that follows you, such as `ui-designer`, places your images in the final draft; with none, your images are the final draft.\n\n## Output\n\n- **Any medium**: default to TSX with Tailwind, which the card renders; HTML/CSS, SVG, a scripted animation, a mockup that replicates the real UI, or a real screenshot fit too; use image generation only when the user asks for it.\n- **Script**: TSX runs CSS animation. Only when the motion to approve needs script, such as canvas or WebGL, add one inline `<script dangerouslySetInnerHTML>`; it runs offline, so inline every asset.\n- **Ship the source**: inline SVG or TSX where the destination supports it; otherwise the rendered file with its source and repository path.\n- **Animations**: render a muted, looping H.264 MP4 with a first-frame poster image, embedded with `<Asset ... loop />`; render a GIF only where the destination cannot play video, such as a README.\n- **Just the image**: above `<!-- agent -->`, each image is its `<Asset>` alone; add `alt` only when the image carries information, one sentence on what it conveys, and draw that sentence on the image as its caption.\n- **Look**: colours and fonts from the app's `design.md`.\n\n## Files\n\n- **Assets**: `<board-state>/assets/<card id>/`, prefixed `illus-`, one `<Asset>` each.\n- **Reruns**: replace only your own files; keep other agents' assets.\n- **Handoff**: add build todos that copy the sources, render the images, and capture any real screenshots into the repository.\n\n## Memory\n\n- **`style.md`**: illustration style and corrections, one line each.\n";
|
|
6015
6013
|
}
|
|
6016
6014
|
});
|
|
6017
6015
|
|
|
@@ -6019,7 +6017,7 @@ var init_AGENT14 = __esm({
|
|
|
6019
6017
|
var AGENT_default15;
|
|
6020
6018
|
var init_AGENT15 = __esm({
|
|
6021
6019
|
"../cli/src/agents/hyperframes-editor/AGENT.md"() {
|
|
6022
|
-
AGENT_default15 = "---\nname: hyperframes-editor\ndescription: Produces and checks a product video from its script, using the production method that suits it.\nakb:\n after: [scriptwriter, demo-rehearser]\n i18n:\n en:\n title: Video
|
|
6020
|
+
AGENT_default15 = "---\nname: hyperframes-editor\ndescription: Produces and checks a product video from its script, using the production method that suits it.\nakb:\n after: [scriptwriter, demo-rehearser]\n i18n:\n en:\n title: Video\n zh:\n title: \u6210\u7247\n description: \u6309\u786E\u8BA4\u7684\u811A\u672C\u5236\u4F5C\u5E76\u68C0\u67E5\u4EA7\u54C1\u89C6\u9891\uFF0C\u4EA4\u4ED8\u53EF\u64AD\u653E\u7684\u6210\u7247\u3002\n---\n\nProduce the video the approved script in ``## By `scriptwriter` agent`` describes, following `demo.md` when present. Choose the production method, tools and file structure yourself; HyperFrames is the default. Continue usable projects and previews on existing cards.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` in the project, the board's asset folder.\n- **Video**: `<short-name>.mp4` in the same folder, `<short-name>` a lowercase slug of the card title.\n\n## Media\n\nUse the first available source for each file:\n\n1. **Asset folder**: reuse a suitable file.\n2. **Memory**: copy a suitable asset from `assets.md`.\n3. **Repository**: copy an existing asset.\n4. **Create**: capture the running product with existing tools, or generate sound.\n5. **Human**: name what you cannot prepare, including an outside asset without an open license, in one `[user]` question and stop.\n\n- **Recorder**: obtain your bundled `record.mjs` with `akb raw agent-file hyperframes-editor record.mjs` and keep it in the project. Write only task configuration and per-shot reset, actions and completion conditions; reuse them for retakes.\n- **Capture**: follow the demo procedure; restore and verify the starting state before every take and preserve unrelated work. A result that differs from the script's expected result is a `[user]` question, never a silent change.\n- **Narration**: generate it from the exact scripted lines with the approved voice: `akb cloud tts` for a hosted voice, local Kokoro for a Kokoro ID, `af_heart` / `zf_xiaobei` when the source names none. Never switch voice or service silently; regenerate only narration whose voice changed. For a human voice, reuse the supplied recording or ask for it.\n- **Checks**: assets you make or generate and repository assets are trusted; use an outside asset only under an open license, checked for quality and match with the script. Remove secrets, personal data and private paths.\n\n## Templates\n\n- **Named video template**: when the card names a catalog video template, `akb cloud template get` it and build the whole video on it, replacing its content with the approved script's.\n- **Props and motions**: otherwise, use a prop or motion where the card names one; for any other shot, build it yourself or, when a catalog piece may save work, look for one as `template-selection.md` says.\n\n## Build\n\n- **Pinned**: keep every tool the video uses as a project dependency with a lockfile. Name an unavailable system dependency in one `[user]` question and stop.\n- **Real product**: show real product interfaces; never fabricate one.\n- **Previews**: preview as you need; they need no user approval.\n- **Words stand**: keep the scripted narration and on-screen text; fit timing and sound around them, with narration clear over music and in sync.\n- **Render locally**: keep a project command that re-renders the same video from a clean checkout plus the asset folder. Do not use hosted rendering. Report a failed render without claiming delivery.\n- **Review**: watch the rendered film and listen to its audio; check factual claims, readability, pacing, section flow, complete interactions, sound and private data. Fix defects, re-render and recheck; report checks you could not run.\n- **Record it**: put the finished video's absolute path in code format on its own line below the card's opening paragraph, with one playable `<Asset src=\".assets/<card id>/<short-name>.mp4\" label=\"...\" />` beneath it, updating both in place. Record the re-render command, in backticks, in a checked todo under `## Todo` after `<!-- agent -->`.\n\n## Rules\n\n- **No code bar**: tests, diff size and code review do not apply.\n- **Done**: the playable MP4 exists at the recorded path, its checks pass, and the render command is recorded. A run that renders nothing has not delivered.\n\n## Memory\n\nKeep `assets.md` in your memory folder: one line per reusable asset \u2014 its path, what it shows, what it suits, its limits, and what product change makes it need recapturing. Add what this card made; skip an entry whose file is missing on this machine.\n\nKeep `feedback.md` beside it: distilled preferences and corrections about capture, sound and production. Read relevant guidance before building; the current script wins. Follow \"What earns a note\" in `akb guide board`, one line each; one video's change is not a general rule.\n";
|
|
6023
6021
|
}
|
|
6024
6022
|
});
|
|
6025
6023
|
|
|
@@ -6043,7 +6041,7 @@ var init_template_selection = __esm({
|
|
|
6043
6041
|
var AGENT_default16;
|
|
6044
6042
|
var init_AGENT16 = __esm({
|
|
6045
6043
|
"../cli/src/agents/prompt-writer/AGENT.md"() {
|
|
6046
|
-
AGENT_default16 = "---\nname: prompt-writer\ndescription: Writes the instruction text a card changes, as a diff per file.\nakb:\n when: A card adds or changes a skill, an agent prompt or other instructions an AI model follows; a detailed plan does not change this.\n memoryFiles:\n workspace: [style.md]\n i18n:\n zh:\n title: \u63D0\u793A\u8BCD\
|
|
6044
|
+
AGENT_default16 = "---\nname: prompt-writer\ndescription: Writes the instruction text a card changes, as a diff per file.\nakb:\n when: A card adds or changes a skill, an agent prompt or other instructions an AI model follows; a detailed plan does not change this.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Prompts\n zh:\n title: \u63D0\u793A\u8BCD\n description: \u64B0\u5199\u548C\u4FEE\u6539 Skill\u3001Agent \u63D0\u793A\u8BCD\u7B49\u4EA4\u7ED9 AI \u6A21\u578B\u6267\u884C\u7684\u6307\u4EE4\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539 Skill\u3001Agent \u63D0\u793A\u8BCD\u6216\u5176\u4ED6\u4EA4\u7ED9 AI \u6A21\u578B\u6267\u884C\u7684\u6307\u4EE4\u65F6\uFF1B\u5361\u7247\u5DF2\u6709\u8BE6\u7EC6\u65B9\u6848\u4E5F\u4E00\u6837\u3002\n---\n\nYou write the instruction text a card needs: the exact edit each target needs, shown as a\ndiff.\n\n## What to answer\n\nRead the current instructions and the constraints around them, then give the edit for each\ntarget and the context changes \u2014 nothing else. Label each target with its file only.\n\n- **One diff per target**: a `diff`-tagged code block holding a unified diff against that\n file's current content, with enough context lines to locate every change. A new file is\n all `+` lines.\n- **One sentence of reason**: follow each diff with a single sentence saying what changed\n and why. No other summary or explanation.\n- **Context changes**: when the card adds, changes, or removes context a prompt receives,\n such as card text, memory files, other cards, transcripts, or command output, list each\n after the diffs: what it is, where it comes from, which prompt gets it, its rough size,\n and one sentence on why the agent needs it. Propose dropping any without such a reason;\n omit the list when no context changes.\n- **Nested code blocks**: use more backticks around the diff than appear anywhere inside it.\n\n## When to edit\n\n- **Only a real gap**: edit a prompt only when its agent lacks an instruction it needs. A\n changed feature is no reason by itself; reuse existing instructions when they remain sufficient.\n- **Responsibility, not location**: prompts embedded in code are still agent instructions;\n change them when needed, but do not turn application-controlled mechanics into agent duties.\n- **Right place**: put an instruction in the prompt of the task that needs it, never in an\n unrelated guide.\n- **Stay in scope**: change only the prompts the card names.\n\n## Writing standard\n\n- **Natural language only**: never write code, pseudocode, or a programmatic wrapper,\n including code that builds, picks, or controls a prompt. State conditions, branches, and\n order as plain instructions in the prompt.\n- **English**: the prompt text is ALWAYS written in English.\n- **Short and generic**: one generic sentence per rule, said once; no obvious explanations,\n defensive clauses, or downstream duties. Trim what is there while you add.\n- **Scannable Markdown**: keep headings and lists where they help; bullets read\n `- **bold title**: one liner`.\n- **Professional and comprehensible**: plain, clear language.\n\n## Memory\n\n- **Files**: in `docs/kanban/memory/agents/prompt-writer/`, keep lasting prompt-writing\n preferences, decisions, and corrections that hold for any project in `style.md`, and this\n project's skill, guide and agent conventions in `rules.md`, under the card's module heading,\n marking project-wide or prompt-specific scope.\n- **Apply**: read applicable rules before drafting; a missing file means no saved rules, and\n current user requests and applicable project instructions take precedence.\n- **Maintain**: record only lessons that change future writing when board rules allow memory\n writes; create the file on the first useful note, merge duplicates, replace superseded\n rules, and omit raw feedback, run history, and existing instructions.\n";
|
|
6047
6045
|
}
|
|
6048
6046
|
});
|
|
6049
6047
|
|
|
@@ -6059,7 +6057,7 @@ var init_AGENT17 = __esm({
|
|
|
6059
6057
|
var AGENT_default18;
|
|
6060
6058
|
var init_AGENT18 = __esm({
|
|
6061
6059
|
"../cli/src/agents/schema-designer/AGENT.md"() {
|
|
6062
|
-
AGENT_default18 = "---\nname: schema-designer\ndescription: Previews the database schema change a card makes.\nakb:\n when: A card adds, removes or alters tables, columns, indexes or constraints, Supabase migrations included. Not a change that only reads or writes rows.\n i18n:\n zh:\n title: \u6570\u636E\u5E93\
|
|
6060
|
+
AGENT_default18 = "---\nname: schema-designer\ndescription: Previews the database schema change a card makes.\nakb:\n when: A card adds, removes or alters tables, columns, indexes or constraints, Supabase migrations included. Not a change that only reads or writes rows.\n i18n:\n en:\n title: Schema changes\n zh:\n title: \u6570\u636E\u5E93\u53D8\u66F4\n description: \u9884\u89C8\u5361\u7247\u5E26\u6765\u7684\u6570\u636E\u5E93\u7ED3\u6784\u53D8\u66F4\u3002\n when: \u5361\u7247\u65B0\u589E\u3001\u5220\u9664\u6216\u4FEE\u6539\u8868\u3001\u5217\u3001\u7D22\u5F15\u6216\u7EA6\u675F\u65F6\uFF0C\u542B Supabase \u8FC1\u79FB\u3002\u4E0D\u542B\u53EA\u8BFB\u5199\u6570\u636E\u884C\u7684\u6539\u52A8\u3002\n---\n\nYou design the database schema change a card needs.\n\n## What you own\n\nThe schema of the tables this card touches. The migration file and any backfill script are\nbuild work, not yours.\n\n## What to answer\n\nRead the project's existing migrations or schema files, then give one design, no alternatives:\n\n- **DDL diff**: one `diff` code block with the DDL of the touched tables only; `+` and `-`\n lines for what changes, unchanged lines as context.\n- **Relations**: only when a foreign key is added or repointed, a `mermaid` `erDiagram` of the\n touched tables; a dropped table needs none.\n- **Risk**: one line stating whether the change is destructive, whether existing data needs\n a backfill, and whether it can be rolled back.\n";
|
|
6063
6061
|
}
|
|
6064
6062
|
});
|
|
6065
6063
|
|
|
@@ -6067,7 +6065,7 @@ var init_AGENT18 = __esm({
|
|
|
6067
6065
|
var AGENT_default19;
|
|
6068
6066
|
var init_AGENT19 = __esm({
|
|
6069
6067
|
"../cli/src/agents/scriptwriter/AGENT.md"() {
|
|
6070
|
-
AGENT_default19 = "---\nname: scriptwriter\ndescription: Writes a product video's script, the one its film and cover are made from.\nakb:\n memoryFiles:\n workspace: [feedback.md, feedback/]\n i18n:\n zh:\n title: \u811A\u672C\
|
|
6068
|
+
AGENT_default19 = "---\nname: scriptwriter\ndescription: Writes a product video's script, the one its film and cover are made from.\nakb:\n memoryFiles:\n workspace: [feedback.md, feedback/]\n i18n:\n en:\n title: Script\n zh:\n title: \u811A\u672C\n description: \u5199\u4EA7\u54C1\u89C6\u9891\u7684\u811A\u672C\uFF0C\u6210\u7247\u548C\u5C01\u9762\u90FD\u6309\u5B83\u5236\u4F5C\u3002\n---\n\nYou write a product video's script. Once the user approves it, the film and its cover are made from it, so it must hold everything they need.\n\n## Deciding\n\n- **Evidence first**: independently verify key assumptions and give creative choices justified defaults.\n- **Ask little**: ask only for decisions or access the user owns.\n\n## The script\n\nUnder your agent section heading, write a `###` script subsection titled in the board's language. Use `####` or lower for any heading inside it.\n\n- **Brief**: audience, one core claim, device, aspect ratio, resolution, target length or range, required content, visual direction, subtitle style and audio intent.\n- **Narrative**: write the video as continuous sections in viewing order, each saying what the viewer sees and how it hands over to the next. Number shots `S<n>` only where a section needs them.\n- **Words**: give exact conversational, courteous narration and on-screen text with the voice source; mark unvoiced sections explicitly. Missing narration never means silence.\n- **Voice**: for TTS, pick voices from `references/voices.md`. Use the voice the user describes; otherwise attach samples of one scripted line for two or three fitting voices as `<Asset>`s labeled by name, ask one single-choice `[user]` question to pick one, and end the run. Put the chosen name in the voice source.\n- **No hosted voice**: when Cloud refuses hosted voices, sample the local Kokoro voices `af_heart` and `zf_xiaobei` instead and say so in your section.\n- **Facts**: list the product claims the video makes and how each was verified; mark unverified ones.\n- **Direction**: specify only what affects the story. Recipes in `references/index.md` are optional references, never a requirement.\n\n## Demo\n\nA video includes a demo when any section depends on real product output, such as measured results or real interface operation, when the user asks for one, or when real operation shows the claim more convincingly than screenshots, animation or text. `demo-rehearser` rehearses it from the card.\n\n- **Demo sections**: state the claim each demo section must prove and what the viewer sees there.\n\n## Steps\n\n- **Cover method**: when the Brief records no cover method, ask one single-choice `[user]` question whether the cover is laid out from existing material or generated by an image model, recommending the method on the first line of `docs/kanban/memory/agents/cover-designer/preferences.md` when present. Record the answer in the Brief.\n- **Existing cards**: keep usable JSON scripts, `demo.md`, projects and previews; do not recreate unaffected work.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/scriptwriter/`. Before writing or revising, read `feedback.md` and the example for this video's content type; the current card wins.\n\n- **Feedback**: `feedback.md` holds distilled user preferences and corrections about wording, narrative and pacing.\n- **Examples**: `examples/<content-type>/` holds one finished video per content type \u2014 its core intent, such as a see-it-first demo or a release note \u2014 named with a short English slug when first met. With no example yet, start from the closest earlier video.\n- **Example contents**: the script section, `demo.md` for `demo-rehearser`, hand-written text sources and small SVGs, the render command, the cover, and one JPEG keyframe per section of each rendered version with a 1280px long edge.\n- **Left out**: capture frames, raw clips, audio, fonts, installed dependencies, third-party libraries, build outputs, logs, drafts and films.\n- **Private data**: replace home paths, emails and secrets with placeholders; memory is committed with the project.\n- **Size**: keep one example under 5MB, dropping keyframes first.\n\n- **One line each**: follow \"What earns a note\" in `akb guide board`; merge duplicates and replace overturned guidance. Never infer preferences from ambiguous feedback.\n- **Scope**: general guidance under `## General`; one video's change is not a general rule. Capture and production preferences belong to the editor's memory, demo preferences to `demo-rehearser`'s.\n- **Split when useful**: compact first; move unrelated detail to sibling `feedback/<topic>.md` files with a scoped index in feedback.md. Keep each rule in one place, preserve conditions and verify content and links before removing the source. Read the index and relevant files; repair broken links before use.\n- **Never rewrite recipes**: project preferences stay in project memory.\n";
|
|
6071
6069
|
}
|
|
6072
6070
|
});
|
|
6073
6071
|
|
|
@@ -6075,7 +6073,7 @@ var init_AGENT19 = __esm({
|
|
|
6075
6073
|
var AGENT_default20;
|
|
6076
6074
|
var init_AGENT20 = __esm({
|
|
6077
6075
|
"../cli/src/agents/social-illustrator/AGENT.md"() {
|
|
6078
|
-
AGENT_default20 = '---\nname: social-illustrator\ndescription: "Makes a social post\'s images: the image slots a carousel\'s pages name, or one image for a text-only post."\nakb:\n after: [carousel-planner]\n i18n:\n en:\n title:
|
|
6076
|
+
AGENT_default20 = '---\nname: social-illustrator\ndescription: "Makes a social post\'s images: the image slots a carousel\'s pages name, or one image for a text-only post."\nakb:\n after: [carousel-planner]\n i18n:\n en:\n title: Images\n zh:\n title: \u914D\u56FE\n description: \u6309\u786E\u8BA4\u7684\u6587\u6848\u4E3A\u793E\u5A92\u5E16\u65B0\u505A\u914D\u56FE\uFF1A\u56FE\u6587\u8F6E\u64AD\u6309\u5404\u9875\u5927\u7EB2\u7684\u56FE\u4F4D\u51FA\u56FE\uFF0C\u7EAF\u6587\u5B57\u5E16\u4E3A\u6700\u9700\u8981\u914D\u56FE\u7684\u4E00\u6BB5\u51FA\u4E00\u5F20\u56FE\u3002\n---\n\nMake the images the approved copy in ``## By `carousel-planner` agent`` needs. Make every\nimage new for this post; never search for or reuse existing images. On an existing card,\nredo only the images a copy change affects.\n\n## What to make\n\n- **Carousel**: one image per slot the pages name.\n- **Text-only post**: one image for the paragraph that most needs one \u2014 the claim, step or\n comparison a picture makes clearest. Skip it when the card says no image.\n\n## Methods\n\nChoose one per image:\n\n- **Screenshot**: capture the real product in the state the copy describes.\n- **Drawn**: lay out a diagram or chart in HTML/CSS or SVG and render it to PNG.\n- **Generated**: `akb cloud image --aspect <ratio> --out <file> "<prompt>"`, which spends the\n user\'s Pro credits; never for a product interface.\n- **Fallback**: when a method cannot deliver, use another and say so in your section.\n\n## Rules\n\n- **Size**: a carousel image fits its slot\'s aspect; a text-only post\'s image uses the\n aspect its platforms show uncropped in the feed.\n- **Consistent**: one post\'s images share palette, fonts and treatment.\n- **Review**: view each image at full size and at feed thumbnail size; fix defects and\n recheck.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` \u2014 `img-<nn>.png`, each with the source that\n renders it beside it.\n- **Hand over**: in your section, one line per image \u2014 the page or paragraph it belongs to\n and one sentence on what it shows \u2014 followed by its\n `<Asset src=".assets/<card id>/img-<nn>.png" label="..." />`.\n\n## Memory\n\nKeep `style.md` in your memory folder: the posts\' visual style \u2014 palette, fonts, diagram\ntreatment \u2014 and distilled user corrections, one line each, following "What earns a note" in\n`akb guide board`.\n';
|
|
6079
6077
|
}
|
|
6080
6078
|
});
|
|
6081
6079
|
|
|
@@ -6266,7 +6264,7 @@ writeFileSync(resolve(project, 'index.html'), \`<!DOCTYPE html>
|
|
|
6266
6264
|
var AGENT_default21;
|
|
6267
6265
|
var init_AGENT21 = __esm({
|
|
6268
6266
|
"../cli/src/agents/ui-designer/AGENT.md"() {
|
|
6269
|
-
AGENT_default21 = "---\nname: ui-designer\ndescription: Draws the screens a card changes, with every word on them.\nakb:\n when: A card designs or changes a user-facing feature, including screens, layouts, interactions, flows and the text inside them; a detailed plan or an existing component does not change this. Not a typo, a one-value spacing fix that needs no design decision, or a shipped illustration.\n after: [copywriting, illustrator]\n memoryFiles:\n workspace: [style.md]\n i18n:\n zh:\n title: \u754C\u9762\u8BBE\u8BA1\
|
|
6267
|
+
AGENT_default21 = "---\nname: ui-designer\ndescription: Draws the screens a card changes, with every word on them.\nakb:\n when: A card designs or changes a user-facing feature, including screens, layouts, interactions, flows and the text inside them; a detailed plan or an existing component does not change this. Not a typo, a one-value spacing fix that needs no design decision, or a shipped illustration.\n after: [copywriting, illustrator]\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: UI design\n zh:\n title: \u754C\u9762\u8BBE\u8BA1\n description: \u8BBE\u8BA1\u9762\u5411\u7528\u6237\u7684\u529F\u80FD\uFF0C\u5305\u62EC\u9875\u9762\u3001\u5E03\u5C40\u3001\u4EA4\u4E92\u548C\u6D41\u7A0B\u3002\n when: \u5361\u7247\u8BBE\u8BA1\u6216\u4FEE\u6539\u9762\u5411\u7528\u6237\u7684\u529F\u80FD\u65F6\uFF0C\u5305\u62EC\u9875\u9762\u3001\u5E03\u5C40\u3001\u4EA4\u4E92\u3001\u6D41\u7A0B\u53CA\u5176\u4E2D\u7684\u6587\u5B57\uFF1B\u5361\u7247\u5DF2\u6709\u8BE6\u7EC6\u65B9\u6848\u6216\u73B0\u6210\u7EC4\u4EF6\u4E5F\u4E00\u6837\u3002\u4E0D\u542B\u9519\u522B\u5B57\u3001\u65E0\u9700\u8BBE\u8BA1\u51B3\u5B9A\u7684\u5355\u4E2A\u95F4\u8DDD\u8C03\u6574\u548C\u5DF2\u6709\u7684\u914D\u56FE\u3002\n---\n\nYou draw the screen a card needs.\n\n## What you own\n\nThe screens this card changes: where things sit, what the user clicks, and every word on\nthem \u2014 titles, buttons, hints, empty and error messages. Nothing else on the card is yours.\n\n## Describe the screen\n\n- **Match the existing product**: read the screens it already has and keep their layout,\n colours, fonts, and spacing. If the product has no style yet, use a plain, neutral one.\n- **Say what the user sees and does**: not the parts the screen is built from. \"Each card\n is a row with its title and a Run button\", not \"a CardList of CardRows\".\n- **Cover empty and failure states**: say what the user sees and can do.\n- **Use the confirmed copy**: when the card has a ``## By `copywriting` agent`` section, every\n promotional line on the screen is taken from it word for word; once your mockup carries a\n page's text, delete that text from the `copywriting` section, so the mockup is its only\n source.\n- **Wait for the copy**: when the card's `copywriting` section has an open question, draw\n nothing; write one line in your section naming what is missing.\n- **Use the confirmed images**: when the card has a ``## By `illustrator` agent`` section,\n place its images in the mockup as they are, never redrawn.\n\n## What to answer\n\nGive one design. Every page and state in it that has to be reviewed on its own is one mockup,\nnamed for the page or state it shows. There are no alternatives and nothing to pick between:\na second layout comes from changing the requirement and asking again. Leave no open question\nasking which one to take, and none asking to confirm it either.\n\nPut nothing else in the section. The drawings are the answer; do not describe them.\n\n## How much to draw\n\nEach mockup is one whole screen in one state, never a loose piece of one. Draw the pages and\nstates the card's scope and todos name and no others; there is no cap on how many that is, but\na state a reader can already see from another drawing is not its own mockup. Do not handle\nclicks, load from the network, or read board data.\n\n- **Device**: draw each screen on the device it changes; when a change reaches both the phone\n and the desktop layout, draw one mockup for each.\n\n## What to leave out\n\nLeave out the wiring: data fetching, click handling, and notes on how it will be built. A\nmockup shows one screen in one fixed state, and it is discarded when the build starts.\n\n## Where the drawing goes\n\nRead `references/rendered-screen.md` before you draw, and follow it exactly. Every mockup is a\nrendered screen and there is no other format. One design, one screen per mockup, each named\nfor what it shows.\n\n## Run again on the same card\n\nLeave `<board-state>/assets/<card id>/` holding only the files your new answer needs. Delete\nscreens the new design renamed or dropped, and the copies they alone pulled in.\n\nMockup files are keyed by card id, so they survive track changes. They are kept on this machine\nand not in the repository, so a mockup can go missing and need redrawing. Record the final\ndesign in the card itself.\n\n## What you remember\n\n- **`style.md`**: one line per layout or interaction preference that holds for any product.\n- **`redesign.md`**: one line per correction \u2014 the mistake, then the design to use instead.\n- **`decisions.md`**: one line per durable choice the user made about your designs.\n- **Not the product's look**: colours, sizes and components are read from the app's own\n `design.md`, never written here.\n";
|
|
6270
6268
|
}
|
|
6271
6269
|
});
|
|
6272
6270
|
|
|
@@ -6282,7 +6280,7 @@ var init_rendered_screen = __esm({
|
|
|
6282
6280
|
var AGENT_default22;
|
|
6283
6281
|
var init_AGENT22 = __esm({
|
|
6284
6282
|
"../cli/src/agents/tech-stack-advisor/AGENT.md"() {
|
|
6285
|
-
AGENT_default22 = "---\nname: tech-stack-advisor\ndescription: Compares the outside libraries, tools or services a card could pick.\nakb:\n when: A card makes a new pick of an outside library, tool or service.\n i18n:\n zh:\n title: \u6280\u672F\u9009\u578B\
|
|
6283
|
+
AGENT_default22 = "---\nname: tech-stack-advisor\ndescription: Compares the outside libraries, tools or services a card could pick.\nakb:\n when: A card makes a new pick of an outside library, tool or service.\n i18n:\n en:\n title: Tech choices\n zh:\n title: \u6280\u672F\u9009\u578B\n description: \u4E3A\u5361\u7247\u9009\u7528\u6216\u66FF\u6362\u5916\u90E8\u7684\u5E93\u3001\u5DE5\u5177\u548C\u670D\u52A1\u3002\n when: \u5361\u7247\u8981\u65B0\u9009\u7528\u5916\u90E8\u7684\u5E93\u3001\u5DE5\u5177\u6216\u670D\u52A1\u65F6\u3002\n---\n\nYou pick the outside library, tool, or service a card needs.\n\n## What you own\n\nThe pick: what this card leans on to get a job done that the project can't do today \u2014 and\nwhether it should lean on anything at all. Nothing else on the card is yours.\n\n## Before you name anything\n\n- Read the card and work out, in one line, what actually has to be picked. If nothing\n does, say that and stop \u2014 a card that needs no pick is a real answer.\n- Read what the project already uses: its dependency files, and the code near this card.\n Something already installed beats a better thing that isn't.\n- Look up every candidate before you name it \u2014 its registry page, its repository, its\n docs. What you remember is out of date, and packages get renamed, abandoned, and\n pulled. Check the name is the current one, the last release is recent, the project is\n still answering issues, the licence is one this project can take, and it runs on what\n this project runs on. Drop the ones that fail, and never name one you could not check.\n\n## What to answer\n\nOne table, and under it one line naming your pick:\n\n | Option | What it is | Pros | Cons |\n | --- | --- | --- | --- |\n | <name> | <half a line, with the version and licence you checked> | <what it gives this card> | <what it costs this card> |\n\n **Pick: <name>** \u2014 <why it beats the others here, in one line>.\n\n- **Two or three rows**: one per candidate, nothing you did not weigh.\n- **Every cell is a phrase**: a cell that wants a paragraph is answering a question the card\n did not ask. Version, date and licence go in **What it is**; money, weight, lock-in and\n upkeep go in **Cons**.\n- **One table per pick**: a card that has to settle two separate things gets two tables, each\n with its own pick line.\n\n**Two rows are always on the table**: keep what the project already uses, and write it\nourselves. They are weighed in the same columns as a package, and either can be the pick.\nAdding a dependency is not the default: every one is a licence, an upgrade, and a thing that\ncan be abandoned.\n\n## What to leave out\n\nEverything that is not the table and the pick line. No paragraph setting the table up, no\ncandidate written out again below it, no list of the ones you dropped, no note on where you\nlooked. Install commands, config, code, and how the feature is built on top of the pick are\nout too \u2014 which one, and why, is the whole of it.\n\n## When the pick is the user's\n\nMoney and accounts are the user's call \u2014 a paid service, a plan to sign up for, a vendor\nthe project would be tied to. Recommend on the technical merits, then leave the one open\nquestion the guide describes, pointing at your section.\n";
|
|
6286
6284
|
}
|
|
6287
6285
|
});
|
|
6288
6286
|
|
|
@@ -6290,7 +6288,7 @@ var init_AGENT22 = __esm({
|
|
|
6290
6288
|
var AGENT_default23;
|
|
6291
6289
|
var init_AGENT23 = __esm({
|
|
6292
6290
|
"../cli/src/agents/video-cloner/AGENT.md"() {
|
|
6293
|
-
AGENT_default23 = "---\nname: video-cloner\ndescription: Breaks a video made with code down beat by beat and drafts its clone with the user's content.\nakb:\n memoryFiles:\n workspace: [motions/, props/, motions.md, props.md, feedback.md]\n session: new\n i18n:\n en:\n title:
|
|
6291
|
+
AGENT_default23 = "---\nname: video-cloner\ndescription: Breaks a video made with code down beat by beat and drafts its clone with the user's content.\nakb:\n memoryFiles:\n workspace: [motions/, props/, motions.md, props.md, feedback.md]\n session: new\n i18n:\n en:\n title: Clone storyboard\n zh:\n title: \u590D\u523B\u5206\u955C\n description: \u9010\u955C\u62C6\u89E3\u7528\u4EE3\u7801\u5236\u4F5C\u7684\u89C6\u9891\uFF0C\u5E76\u7528\u4F60\u7684\u5185\u5BB9\u5199\u51FA\u590D\u523B\u5206\u955C\u3002\n---\n\nYou break down a video made with code and draft its clone, which tells the user's content with the source's craft. Once the user approves the drafts, a template is built from them, so they must hold every beat it needs.\n\n## Terms\n\n- **Craft**: how the source is made, reproduced in the template code \u2014 beat cuts and lengths, camera moves, motion and easing, layout, transitions, type scale, the role each colour plays, where sounds land, and how product UI is shown \u2014 redrawn or captured, cropped to which module, placed where and how it moves.\n- **Content**: what the source says and whose it is, replaced by the user's \u2014 copy, data, product screens, footage, brand names, logos, illustrations, music, voice, typefaces and brand colours. When unsure, content is whatever must change if the same craft told another story.\n- **Props**: generic system UI that is neither, such as windows, browser chrome and file pickers.\n\n## Deciding\n\n- **Evidence first**: read the video itself \u2014 cut points, sampled frames, audio beats \u2014 rather than guessing from a few stills.\n- **Default with a reason**: pick every technical choice yourself and say why; ask only for what the user owns.\n- **Free only**: besides `akb cloud`, use only tools and libraries free for commercial use; never one that needs a paid plan or a company license.\n- **Never ship the source's content**: stand in for content the user has not supplied with labelled placeholders; draw props faithfully.\n- **Craft first**: match beats, timing, motion, element alignment and cursor landing points; pixel-exact output is not the goal.\n- **Rebuild, don't paste**: when the source draws its UI, redraw the few modules of the user's product a beat needs, centred, so every frame stays clean; a screenshot is only a reference. A beat that moves in the source never becomes a still.\n\n## Files\n\nFiles live in `<board-state>/assets/<card id>/`, linked from the card as `.assets/<card id>/...`; the template goes elsewhere only when the card names a folder for it. When the card names a video outside that folder, copy it in first; with no video, or none of the user's content to fill it, ask for it.\n\n## Steps\n\n- **Drafts**: write two storyboards in the files folder, following `akb raw agent-file scriptwriter references/storyboard-contract.md` and passing that agent's validator: `breakdown.json` takes the source apart, one shot per beat with its source frame, and in details its motion, props, content slot, transition and sound, marking the skeleton beats the video cannot lose; `clone.json` tells the user's content beat by beat, so its story holds on its own. Show both on the card as one `<Compare>`: the source video as `a`, `clone.json` as `b` and `breakdown.json` as `shots`.\n- **Music**: in the drafts, also follow `akb raw agent-file video-cloner references/music.md`.\n- **Changes**: a change to a beat updates that beat in both drafts.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/video-cloner/`. Read it before the drafts; the current card wins.\n\n- **Motion library**: `motions/<slug>/` holds one effect that a finished template reproduced well \u2014 its source code and a `motion.md` with one line on the style it matches and one keyframe. Add one after the check only when it is reusable beyond this video.\n- **Prop library**: `props/<slug>/` holds one high-fidelity system UI piece, such as a file-picker selection box, a browser window or a window frame \u2014 its source code and a `prop.md` with one line on what it draws and one keyframe. Add one after the check.\n- **Self-contained**: a motion or prop folder carries every style and helper it uses, so it runs with only the template engine and the content file.\n- **Match first**: before writing a motion or drawing a prop, run `akb cloud template list-tags --category motion` or `--category prop`, search within the best tag, then check your own library; use your library alone when signed out or offline. Use a candidate when its style and timing match the source; build one when nothing does.\n- **Motion fit**: a motion that differs only in what the beat passes in, the theme or the beat's length is the same motion; one that differs in a value written into its code, such as a step's duration, easing or distance, gains a parameter defaulting to the old value; one that differs in its steps or what moves is a new motion.\n- **Cache**: `akb cloud template get` a catalog match into a new empty folder, make it that `<slug>/` folder in your library, replacing a local one of the same name, and update the index.\n- **Index**: `motions.md` and `props.md` list each entry as `- [<slug>](<folder>/<slug>/<file>.md) \u2014 <one line>`.\n- **Corrections**: `feedback.md` holds user corrections that change your next recipe, following \"What earns a note\" in `akb guide board`; `product.md` holds those about this product's real screens and flows.\n- **Size**: keep one entry under 1MB; leave out renders, dependencies and source videos.\n";
|
|
6294
6292
|
}
|
|
6295
6293
|
});
|
|
6296
6294
|
|
|
@@ -6306,7 +6304,7 @@ var init_music = __esm({
|
|
|
6306
6304
|
var AGENT_default24;
|
|
6307
6305
|
var init_AGENT24 = __esm({
|
|
6308
6306
|
"../cli/src/agents/video-template-builder/AGENT.md"() {
|
|
6309
|
-
AGENT_default24 = "---\nname: video-template-builder\ndescription: Builds a video clone's reusable beat-table template from its approved drafts, renders it once and checks it against the source beat by beat.\nakb:\n after: [video-cloner]\n session: new\n i18n:\n en:\n title: Video template
|
|
6307
|
+
AGENT_default24 = "---\nname: video-template-builder\ndescription: Builds a video clone's reusable beat-table template from its approved drafts, renders it once and checks it against the source beat by beat.\nakb:\n after: [video-cloner]\n session: new\n i18n:\n en:\n title: Video template\n zh:\n title: \u89C6\u9891\u6A21\u677F\n description: \u6309\u786E\u8BA4\u7684\u590D\u523B\u5206\u955C\u505A\u6210\u4FDD\u7559\u62CD\u6CD5\u3001\u53EF\u6362\u5185\u5BB9\u7684 beat \u8868\u6A21\u677F\uFF0C\u6E32\u67D3\u4E00\u652F\u5E76\u4E0E\u539F\u7247\u9010\u955C\u5BF9\u6BD4\u3002\n---\n\nBuild the template the approved drafts in ``## By `video-cloner` agent`` describe: it reproduces the source's craft and renders the user's content in place of the source's. You are done, with no further question, when the clone's comparison with the source and the render command are recorded on the card.\n\nFollow Terms, Deciding, Files and Memory in `akb raw agent-file video-cloner AGENT.md`; its memory folder is yours.\n\n## Steps\n\n- **Recipe**: write the format (size, frame rate, length, fonts, colours) to `recipe.md` in the files folder; it is your working file, never shown on the card.\n- **Template**: build a project that renders `clone.json` beat by beat, picking for each motion an animation library that fits its style; one project may combine several. The beat table and all content live in one file, holding the user's content from the start, so a beat can be removed or reordered and the content swapped without code changes; the code holds only craft and props. Its `template.md` states that the content in that file is to be replaced with the user's own and that the template reproduces only the source's craft.\n- **Cached clips**: render each beat to its own cached clip and splice the clips into the full video; a change re-renders only the clips it touches.\n- **Check**: render it once and compare it with the source beat by beat side by side on rhythm, motion and layout, and check every on-screen step makes sense to a viewer. A beat that misses the source's structure means the template is wrong: fix it and render again, never hand over the breakdown instead.\n- **Music**: in the template and check, also follow `akb raw agent-file video-cloner references/music.md`.\n- **Card**: under your section heading, show only the clone against the source as a `<Compare>` with `breakdown.json` as `shots`, and each difference left in one line, naming its beat id; append a ticked todo with the template folder's absolute path and, in backticks, its render command.\n- **Changes**: a change to a beat updates only that beat in the template.\n";
|
|
6310
6308
|
}
|
|
6311
6309
|
});
|
|
6312
6310
|
|
|
@@ -6314,7 +6312,7 @@ var init_AGENT24 = __esm({
|
|
|
6314
6312
|
var AGENT_default25;
|
|
6315
6313
|
var init_AGENT25 = __esm({
|
|
6316
6314
|
"../cli/src/agents/workflow-writer/AGENT.md"() {
|
|
6317
|
-
AGENT_default25 = "---\nname: workflow-writer\ndescription: Writes a new workflow, or changes one of the user's own, as a `WORKFLOW.md` and the agents it names.\nakb:\n i18n:\n en:\n title: Workflow
|
|
6315
|
+
AGENT_default25 = "---\nname: workflow-writer\ndescription: Writes a new workflow, or changes one of the user's own, as a `WORKFLOW.md` and the agents it names.\nakb:\n i18n:\n en:\n title: Workflow\n zh:\n title: \u5DE5\u4F5C\u6D41\n description: \u6309\u4F60\u7684\u63CF\u8FF0\u5199\u51FA\u4E00\u4E2A\u5DE5\u4F5C\u6D41\u53CA\u5176 agent\uFF0C\u6216\u4FEE\u6539\u4F60\u5DF2\u6709\u7684\u5DE5\u4F5C\u6D41\u3002\n---\n\nYou write a workflow from the user's request: a kind of work the user runs again and again, where each task shows a person previews to approve before the work goes on.\n\n## Read first\n\n- **The project**: when the board belongs to a project folder, read its README and agent instruction files (such as `AGENTS.md` or `CLAUDE.md`) for what it makes, and its existing outputs of this kind for their format and destination.\n- **This board**: run `akb workflow list` and read `docs/kanban/agents/`, so a new workflow does not repeat one the board already has.\n\n## What to write\n\n- **The workflow**: when the card names a workflow folder, the workflow is that folder: keep its id and change it in place. Otherwise create it with `akb workflow new`. Name it with `akb workflow rename`, and list its agents in its `WORKFLOW.md`: `previews` the agents whose drafts a person approves, `land` the agents that run unattended in order after the last approval, and `scheduled`. Its body is how a card of this kind is written.\n- **Its agents**: write each agent it names as `akb guide write-agent` says.\n- **Approvals**: default to previews a person approves once. Give an agent `after` only when its preview is too costly to make before another is approved, such as a video before its script; it then waits for that approval.\n- **Previews**: give each part a person must see its own agent, such as the screens of a UI change or the script of a video. Show only what the person decides on, and keep previews from overlapping: a UI preview already shows its text, so add no separate copy preview.\n- **Land**: add agents to `land` only for work that needs no review, such as building and merging an approved change; leave it empty when the last approved preview is the deliverable.\n- **Memory**: for each agent, decide whether a user's correction should change its next output, such as a writer's tone or a designer's look; if so, declare its memory as `akb guide write-agent` says, otherwise give it none.\n- **Changing a workflow**: edit its `WORKFLOW.md` and its agents under `docs/kanban/agents/` in place, and keep what the request does not ask to change.\n\n## Check it\n\nRun `akb spec` and `akb workflow list`, and fix every problem reported for this workflow or its agents before finishing.\n\n## Card\n\nUnder your section heading, say in a few lines, for a reader who has never seen a workflow file, what the workflow is for, what each approval shows, what runs after the last one, and what each agent remembers; then embed each file you wrote as an `<Asset>`.\n";
|
|
6318
6316
|
}
|
|
6319
6317
|
});
|
|
6320
6318
|
|
|
@@ -6653,16 +6651,16 @@ function readCatalog() {
|
|
|
6653
6651
|
const agents = [];
|
|
6654
6652
|
const problems = [];
|
|
6655
6653
|
const refused = [];
|
|
6656
|
-
const take = (
|
|
6657
|
-
if ("problem" in
|
|
6658
|
-
if (!
|
|
6659
|
-
if (!
|
|
6660
|
-
const { file, name: name2, old } =
|
|
6661
|
-
const cause =
|
|
6654
|
+
const take = (read6, folder) => {
|
|
6655
|
+
if ("problem" in read6) {
|
|
6656
|
+
if (!read6.missing) problems.push(read6.problem);
|
|
6657
|
+
if (!read6.file) return;
|
|
6658
|
+
const { file, name: name2, old } = read6;
|
|
6659
|
+
const cause = read6.missing ? "noFile" : old ? "oldKeys" : "file";
|
|
6662
6660
|
refused.push({ folder, file, cause, ...name2 ? { name: name2 } : {}, ...old ? { keys: old.keys } : {} });
|
|
6663
6661
|
return;
|
|
6664
6662
|
}
|
|
6665
|
-
const { name, from, builtIn } =
|
|
6663
|
+
const { name, from, builtIn } = read6.agent;
|
|
6666
6664
|
const refuse3 = (cause, problem) => {
|
|
6667
6665
|
problems.push(problem);
|
|
6668
6666
|
if (!builtIn) refused.push({ folder, name, file: from, cause });
|
|
@@ -6686,14 +6684,14 @@ function readCatalog() {
|
|
|
6686
6684
|
`${from}: its folder is \`${folder}\` but it calls itself \`${name}\`. An agent is asked for by its folder name \u2014 make the two match.`
|
|
6687
6685
|
);
|
|
6688
6686
|
}
|
|
6689
|
-
agents.push(
|
|
6687
|
+
agents.push(read6.agent);
|
|
6690
6688
|
};
|
|
6691
6689
|
for (const folder of bundledFolders()) take(readBundled(folder), folder);
|
|
6692
6690
|
for (const folder of projectFolders(AGENTS)) {
|
|
6693
|
-
const
|
|
6694
|
-
take(
|
|
6695
|
-
if ("agent" in
|
|
6696
|
-
problems.push(`${
|
|
6691
|
+
const read6 = readProject(AGENTS, folder);
|
|
6692
|
+
take(read6, folder);
|
|
6693
|
+
if ("agent" in read6 && read6.agent.from.endsWith(LEGACY_AGENT_FILE)) {
|
|
6694
|
+
problems.push(`${read6.agent.from}: rename it to ${AGENT_FILE}`);
|
|
6697
6695
|
}
|
|
6698
6696
|
}
|
|
6699
6697
|
for (const folder of projectFolders(LEGACY_AGENTS)) {
|
|
@@ -6795,10 +6793,10 @@ function readProject(root, folder) {
|
|
|
6795
6793
|
const missing = rel(path17.join(dir, AGENT_FILE));
|
|
6796
6794
|
if (!found2) return { problem: `${missing} is missing`, file: missing, missing: true };
|
|
6797
6795
|
const from = rel(path17.join(dir, found2.name));
|
|
6798
|
-
const
|
|
6799
|
-
if ("problem" in
|
|
6800
|
-
if ("agent" in
|
|
6801
|
-
return
|
|
6796
|
+
const read6 = parseSpecAgent(found2.text, from, file, false, () => ownFiles(walkFolder(dir, "")));
|
|
6797
|
+
if ("problem" in read6) return { ...read6, file: from };
|
|
6798
|
+
if ("agent" in read6) Object.assign(read6.agent, { dir, text: found2.text });
|
|
6799
|
+
return read6;
|
|
6802
6800
|
}
|
|
6803
6801
|
var specAgentCatalog, bundledFolders, AGENT_FILE, LEGACY_AGENT_FILE, AGENT_FILES, ownFiles;
|
|
6804
6802
|
var init_catalog2 = __esm({
|
|
@@ -7602,10 +7600,10 @@ function findRollout(threadId) {
|
|
|
7602
7600
|
}
|
|
7603
7601
|
for (const name of names5) {
|
|
7604
7602
|
if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
|
|
7605
|
-
const
|
|
7603
|
+
const path83 = join(dir, name);
|
|
7606
7604
|
try {
|
|
7607
|
-
const at = statSync(
|
|
7608
|
-
if (!newest || at > newest.at) newest = { path:
|
|
7605
|
+
const at = statSync(path83).mtimeMs;
|
|
7606
|
+
if (!newest || at > newest.at) newest = { path: path83, at };
|
|
7609
7607
|
} catch {
|
|
7610
7608
|
}
|
|
7611
7609
|
}
|
|
@@ -7613,11 +7611,11 @@ function findRollout(threadId) {
|
|
|
7613
7611
|
return newest?.path;
|
|
7614
7612
|
}
|
|
7615
7613
|
function readCodexRunFacts(threadId) {
|
|
7616
|
-
const
|
|
7617
|
-
if (!
|
|
7614
|
+
const path83 = findRollout(threadId);
|
|
7615
|
+
if (!path83) return void 0;
|
|
7618
7616
|
let raw;
|
|
7619
7617
|
try {
|
|
7620
|
-
raw = readFileSync(
|
|
7618
|
+
raw = readFileSync(path83, "utf8");
|
|
7621
7619
|
} catch {
|
|
7622
7620
|
return void 0;
|
|
7623
7621
|
}
|
|
@@ -7745,8 +7743,8 @@ function renderCompleted(item) {
|
|
|
7745
7743
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
7746
7744
|
return changes.map((raw) => {
|
|
7747
7745
|
const change2 = obj(raw);
|
|
7748
|
-
const
|
|
7749
|
-
return
|
|
7746
|
+
const path83 = str2(change2.path);
|
|
7747
|
+
return path83 ? `\u23FA ${str2(change2.kind) || "change"}(${path83})
|
|
7750
7748
|
` : "";
|
|
7751
7749
|
}).join("");
|
|
7752
7750
|
}
|
|
@@ -8005,10 +8003,10 @@ function readKimiRunFacts(sessionId) {
|
|
|
8005
8003
|
const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
|
|
8006
8004
|
let model;
|
|
8007
8005
|
let holding = 0;
|
|
8008
|
-
for (const { path:
|
|
8006
|
+
for (const { path: path83, main } of wireFiles(entry.sessionDir)) {
|
|
8009
8007
|
let raw;
|
|
8010
8008
|
try {
|
|
8011
|
-
raw = readFileSync2(
|
|
8009
|
+
raw = readFileSync2(path83, "utf8");
|
|
8012
8010
|
} catch {
|
|
8013
8011
|
continue;
|
|
8014
8012
|
}
|
|
@@ -8289,8 +8287,8 @@ var init_tail = __esm({
|
|
|
8289
8287
|
import fs20 from "node:fs";
|
|
8290
8288
|
function whereHint(update2, title) {
|
|
8291
8289
|
const locations = Array.isArray(update2.locations) ? update2.locations : [];
|
|
8292
|
-
const
|
|
8293
|
-
return
|
|
8290
|
+
const path83 = str2(obj(locations[0]).path);
|
|
8291
|
+
return path83 && !title.includes(path83) ? hint(path83) : "";
|
|
8294
8292
|
}
|
|
8295
8293
|
function saidBy(update2) {
|
|
8296
8294
|
const content = Array.isArray(update2.content) ? update2.content : [];
|
|
@@ -8761,8 +8759,8 @@ async function tokensOf(rpc, sessionId) {
|
|
|
8761
8759
|
}
|
|
8762
8760
|
async function contextOf(rpc, sessionId) {
|
|
8763
8761
|
try {
|
|
8764
|
-
const
|
|
8765
|
-
const state = obj(
|
|
8762
|
+
const read6 = await rpc.call("session/read", { sessionId, messageLimit: 1 });
|
|
8763
|
+
const state = obj(read6.projection);
|
|
8766
8764
|
const used = num(state.contextUsed);
|
|
8767
8765
|
const limit = num(state.contextWindow);
|
|
8768
8766
|
return used > 0 ? { used, limit: limit > 0 ? limit : void 0 } : void 0;
|
|
@@ -9259,9 +9257,9 @@ function outsideBoardForAdd(cwd = REPO_ROOT) {
|
|
|
9259
9257
|
);
|
|
9260
9258
|
return ["--", ".", ...kept.map((p) => `:(exclude)${p}`)];
|
|
9261
9259
|
}
|
|
9262
|
-
function boardPathsAmong(
|
|
9260
|
+
function boardPathsAmong(files2) {
|
|
9263
9261
|
const owned = boardManagedPaths();
|
|
9264
|
-
return
|
|
9262
|
+
return files2.filter((file) => {
|
|
9265
9263
|
const p = posix(file);
|
|
9266
9264
|
return owned.some((dir) => p === dir || p.startsWith(`${dir}/`));
|
|
9267
9265
|
});
|
|
@@ -9375,9 +9373,9 @@ function treeMark(base, cwd = REPO_ROOT) {
|
|
|
9375
9373
|
const index = path22.join(ensureAkbDir(), `index-${process.pid}-${Date.now()}`);
|
|
9376
9374
|
try {
|
|
9377
9375
|
const env = { ...process.env, GIT_INDEX_FILE: index };
|
|
9378
|
-
const
|
|
9379
|
-
if (!
|
|
9380
|
-
if (!
|
|
9376
|
+
const run2 = (args) => spawnSync2("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env }).status === 0;
|
|
9377
|
+
if (!run2(["read-tree", base])) return null;
|
|
9378
|
+
if (!run2(["add", "-A", ...outsideBoardForAdd(cwd)])) return null;
|
|
9381
9379
|
const tree = spawnSync2("git", ["write-tree"], {
|
|
9382
9380
|
cwd,
|
|
9383
9381
|
encoding: "utf8",
|
|
@@ -9467,7 +9465,7 @@ function ignoredInTheWay(commit, cwd) {
|
|
|
9467
9465
|
}
|
|
9468
9466
|
function blockedBy(why) {
|
|
9469
9467
|
let kind;
|
|
9470
|
-
const
|
|
9468
|
+
const files2 = [];
|
|
9471
9469
|
for (const line2 of why.split("\n")) {
|
|
9472
9470
|
if (!kind) {
|
|
9473
9471
|
kind = headingKind(line2);
|
|
@@ -9475,9 +9473,9 @@ function blockedBy(why) {
|
|
|
9475
9473
|
}
|
|
9476
9474
|
if (!/^\s/.test(line2)) break;
|
|
9477
9475
|
const file = line2.trim();
|
|
9478
|
-
if (file)
|
|
9476
|
+
if (file) files2.push(file);
|
|
9479
9477
|
}
|
|
9480
|
-
return kind &&
|
|
9478
|
+
return kind && files2.length ? { kind, files: files2 } : void 0;
|
|
9481
9479
|
}
|
|
9482
9480
|
function branchWorktree(branch) {
|
|
9483
9481
|
const out = git(["worktree", "list", "--porcelain"]);
|
|
@@ -9523,7 +9521,7 @@ var init_worktree = __esm({
|
|
|
9523
9521
|
return out === null ? null : out.split("\n").filter(Boolean);
|
|
9524
9522
|
};
|
|
9525
9523
|
branchPatch = (base, branch, cwd = REPO_ROOT) => gitDiff(["diff", "--binary", base, branch, ...outsideBoard()], cwd);
|
|
9526
|
-
lastCommitTouching = (ref,
|
|
9524
|
+
lastCommitTouching = (ref, files2, cwd = REPO_ROOT) => git(["log", "-1", "--format=%H", ref, "--", ...files2], cwd)?.trim() || null;
|
|
9527
9525
|
continueRebase = (dir) => scripted(["rebase", "--continue"], dir);
|
|
9528
9526
|
abortRebase = (dir) => void scripted(["rebase", "--abort"], dir);
|
|
9529
9527
|
conflictedPaths = (dir) => (git(["diff", "--name-only", "--diff-filter=U"], dir) ?? "").split("\n").filter(Boolean);
|
|
@@ -9591,8 +9589,8 @@ function modified(file) {
|
|
|
9591
9589
|
function byPath(a, b) {
|
|
9592
9590
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
9593
9591
|
}
|
|
9594
|
-
function byNewest(
|
|
9595
|
-
return
|
|
9592
|
+
function byNewest(files2) {
|
|
9593
|
+
return files2.map((file) => ({ file, at: modified(file) })).sort((a, b) => b.at - a.at || byPath(a.file, b.file)).map((one) => one.file);
|
|
9596
9594
|
}
|
|
9597
9595
|
function versionOf(name) {
|
|
9598
9596
|
const found2 = /(\d+(?:\.\d+)+)/.exec(name);
|
|
@@ -11058,17 +11056,17 @@ function noteRefineTried(store, cardId2) {
|
|
|
11058
11056
|
}
|
|
11059
11057
|
function cardsBeingCreated(runs = readRuns()) {
|
|
11060
11058
|
const newest = /* @__PURE__ */ new Map();
|
|
11061
|
-
for (const
|
|
11062
|
-
for (const id of
|
|
11063
|
-
if (
|
|
11059
|
+
for (const run2 of runs) {
|
|
11060
|
+
for (const id of run2.createdCardIds ?? []) {
|
|
11061
|
+
if (run2.cardId === id) continue;
|
|
11064
11062
|
const best = newest.get(id);
|
|
11065
|
-
if (!best ||
|
|
11063
|
+
if (!best || run2.startedAt >= best.startedAt) newest.set(id, run2);
|
|
11066
11064
|
}
|
|
11067
11065
|
}
|
|
11068
11066
|
const out = /* @__PURE__ */ new Map();
|
|
11069
|
-
for (const [id,
|
|
11070
|
-
if (runIsLive(
|
|
11071
|
-
else if (
|
|
11067
|
+
for (const [id, run2] of newest) {
|
|
11068
|
+
if (runIsLive(run2)) out.set(id, { state: "creating", runId: run2.sessionId });
|
|
11069
|
+
else if (run2.status !== "done") out.set(id, { state: "unfinished", runId: run2.sessionId });
|
|
11072
11070
|
}
|
|
11073
11071
|
return out;
|
|
11074
11072
|
}
|
|
@@ -11156,8 +11154,8 @@ function readTouched(raw) {
|
|
|
11156
11154
|
}
|
|
11157
11155
|
function readPictures(raw) {
|
|
11158
11156
|
if (!Array.isArray(raw)) return void 0;
|
|
11159
|
-
const
|
|
11160
|
-
return
|
|
11157
|
+
const files2 = raw.filter((p) => typeof p === "string" && p.length > 0);
|
|
11158
|
+
return files2.length ? files2 : void 0;
|
|
11161
11159
|
}
|
|
11162
11160
|
function readBlocker(raw) {
|
|
11163
11161
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
@@ -11214,18 +11212,18 @@ function readLandingWait(raw) {
|
|
|
11214
11212
|
if (!raw || typeof raw !== "object") return void 0;
|
|
11215
11213
|
const box2 = raw;
|
|
11216
11214
|
if (box2.kind !== "overwrite" && box2.kind !== "untracked") return void 0;
|
|
11217
|
-
const
|
|
11218
|
-
return
|
|
11215
|
+
const files2 = Array.isArray(box2.files) ? box2.files.filter((f) => typeof f === "string") : [];
|
|
11216
|
+
return files2.length ? { kind: box2.kind, files: files2 } : void 0;
|
|
11219
11217
|
}
|
|
11220
11218
|
function readLandingReason(raw) {
|
|
11221
11219
|
if (!raw || typeof raw !== "object") return void 0;
|
|
11222
11220
|
const box2 = raw;
|
|
11223
11221
|
if (!LANDING_REASONS.has(box2.kind)) return void 0;
|
|
11224
|
-
const
|
|
11222
|
+
const files2 = Array.isArray(box2.files) ? box2.files.filter((f) => typeof f === "string") : [];
|
|
11225
11223
|
return {
|
|
11226
11224
|
kind: box2.kind,
|
|
11227
11225
|
...text2(box2.behind) ? { behind: text2(box2.behind) } : {},
|
|
11228
|
-
...
|
|
11226
|
+
...files2.length ? { files: files2 } : {}
|
|
11229
11227
|
};
|
|
11230
11228
|
}
|
|
11231
11229
|
function readAnswers(raw) {
|
|
@@ -11249,7 +11247,7 @@ function readReview(raw) {
|
|
|
11249
11247
|
if (!raw || typeof raw !== "object") return void 0;
|
|
11250
11248
|
const box2 = raw;
|
|
11251
11249
|
const rounds = Array.isArray(box2.rounds) ? box2.rounds : [];
|
|
11252
|
-
const
|
|
11250
|
+
const stop2 = box2.stopped;
|
|
11253
11251
|
return {
|
|
11254
11252
|
rounds: rounds.flatMap(
|
|
11255
11253
|
(r) => r && typeof r.sessionId === "string" && isVerdict(r.verdict) ? [
|
|
@@ -11263,11 +11261,11 @@ function readReview(raw) {
|
|
|
11263
11261
|
}
|
|
11264
11262
|
] : []
|
|
11265
11263
|
),
|
|
11266
|
-
stopped:
|
|
11267
|
-
reason: asStopReason(
|
|
11268
|
-
why:
|
|
11269
|
-
at: typeof
|
|
11270
|
-
...Array.isArray(
|
|
11264
|
+
stopped: stop2 && typeof stop2 === "object" && typeof stop2.why === "string" ? {
|
|
11265
|
+
reason: asStopReason(stop2.reason),
|
|
11266
|
+
why: stop2.why,
|
|
11267
|
+
at: typeof stop2.at === "number" ? stop2.at : 0,
|
|
11268
|
+
...Array.isArray(stop2.paths) ? { paths: stop2.paths.filter((p) => typeof p === "string") } : {}
|
|
11271
11269
|
} : void 0
|
|
11272
11270
|
};
|
|
11273
11271
|
}
|
|
@@ -11330,9 +11328,9 @@ function recordCreatedCards(sessionId, ids) {
|
|
|
11330
11328
|
const valid = ids.filter((id) => Number.isInteger(id) && id > 0);
|
|
11331
11329
|
if (!valid.length) return false;
|
|
11332
11330
|
return withRuns((runs) => {
|
|
11333
|
-
const
|
|
11334
|
-
if (!
|
|
11335
|
-
|
|
11331
|
+
const run2 = runs.find((entry) => entry.sessionId === sessionId && entry.status === "running");
|
|
11332
|
+
if (!run2) return false;
|
|
11333
|
+
run2.createdCardIds = [.../* @__PURE__ */ new Set([...run2.createdCardIds ?? [], ...valid])];
|
|
11336
11334
|
return true;
|
|
11337
11335
|
});
|
|
11338
11336
|
}
|
|
@@ -11382,7 +11380,7 @@ var init_store = __esm({
|
|
|
11382
11380
|
stringRecord = (raw) => raw && typeof raw === "object" && !Array.isArray(raw) && Object.values(raw).every((v) => typeof v === "string") ? raw : void 0;
|
|
11383
11381
|
readRuns = () => readStore().runs;
|
|
11384
11382
|
PENDING_MS = 3e4;
|
|
11385
|
-
runIsLive = (
|
|
11383
|
+
runIsLive = (run2) => run2.status === "running" && (run2.pid ? pidAlive(run2.pid) : Date.now() - run2.startedAt < PENDING_MS);
|
|
11386
11384
|
creationOf = (runs, cardId2) => cardsBeingCreated(runs).get(cardId2);
|
|
11387
11385
|
cardCreation = (cardId2, strict = false) => {
|
|
11388
11386
|
const runs = readRuns();
|
|
@@ -11553,24 +11551,24 @@ var init_flows = __esm({
|
|
|
11553
11551
|
});
|
|
11554
11552
|
|
|
11555
11553
|
// ../cli/src/lib/agent/language.ts
|
|
11556
|
-
function
|
|
11557
|
-
return
|
|
11554
|
+
function languageNote() {
|
|
11555
|
+
return process.env[LANGUAGE_LINE_ENV] ?? LINES[readLanguage()];
|
|
11558
11556
|
}
|
|
11559
|
-
function
|
|
11560
|
-
|
|
11561
|
-
|
|
11562
|
-
return [
|
|
11563
|
-
`Write this board's prose in ${name}: card titles and bodies, open questions and their options, memory notes, and what you say back to me.`,
|
|
11564
|
-
`Keep English whatever the language: frontmatter keys and their fixed values, \`##\` and \`###\` section headings, the \`<!-- agent -->\` boundary, todo checkboxes, the \`[user]\` tag, track and module names, and card filenames. Never rename or translate a section title \u2014 the board matches those by literal English text, so a changed one is a card it can no longer read, and a title that is not English needs \`akb raw create --slug\` a short English slug.`,
|
|
11565
|
-
`Rewriting a card or a memory file that already exists keeps the language that file is already in; only what you write for me to read personally \u2014 an open question and its options \u2014 follows ${name} on every card, and so does the first note in a memory file still holding nothing but its seeded header.`,
|
|
11566
|
-
`This governs the board's prose alone: code, comments, commit messages and the repository's own documents follow the repository, not me. \`akb guide board\` carries the rule in full.`
|
|
11567
|
-
].join(" ");
|
|
11557
|
+
function translating() {
|
|
11558
|
+
const line2 = languageNote();
|
|
11559
|
+
return line2 !== "" && !/\bEnglish\b/.test(line2);
|
|
11568
11560
|
}
|
|
11561
|
+
var LANGUAGE_LINE_ENV, LINES, inLanguage;
|
|
11569
11562
|
var init_language = __esm({
|
|
11570
11563
|
"../cli/src/lib/agent/language.ts"() {
|
|
11571
11564
|
"use strict";
|
|
11572
11565
|
init_settings();
|
|
11573
|
-
|
|
11566
|
+
LANGUAGE_LINE_ENV = "AI4KANBAN_LANGUAGE_LINE";
|
|
11567
|
+
LINES = {
|
|
11568
|
+
en: "",
|
|
11569
|
+
zh: "\u8BF7\u7528\u4E2D\u6587\u56DE\u590D\u548C\u5199\u4F5C\u3002"
|
|
11570
|
+
};
|
|
11571
|
+
inLanguage = (prompt) => [prompt, languageNote()].filter(Boolean).join("\n\n");
|
|
11574
11572
|
}
|
|
11575
11573
|
});
|
|
11576
11574
|
|
|
@@ -12497,11 +12495,11 @@ function modelsRun() {
|
|
|
12497
12495
|
const ran = /* @__PURE__ */ new Map();
|
|
12498
12496
|
const { runs } = readStore();
|
|
12499
12497
|
for (let i = runs.length - 1; i >= 0; i--) {
|
|
12500
|
-
const
|
|
12501
|
-
if (!
|
|
12502
|
-
const seen = ran.get(
|
|
12503
|
-
if (!seen.includes(
|
|
12504
|
-
ran.set(
|
|
12498
|
+
const run2 = runs[i];
|
|
12499
|
+
if (!run2.harness || !run2.model) continue;
|
|
12500
|
+
const seen = ran.get(run2.harness) ?? [];
|
|
12501
|
+
if (!seen.includes(run2.model)) seen.push(run2.model);
|
|
12502
|
+
ran.set(run2.harness, seen);
|
|
12505
12503
|
}
|
|
12506
12504
|
return ran;
|
|
12507
12505
|
}
|
|
@@ -13346,9 +13344,9 @@ function boardWorkflowDefs() {
|
|
|
13346
13344
|
problems.push(`${from} is missing`);
|
|
13347
13345
|
continue;
|
|
13348
13346
|
}
|
|
13349
|
-
const
|
|
13350
|
-
if ("problem" in
|
|
13351
|
-
else defs.push(
|
|
13347
|
+
const read6 = parseWorkflowFile(id, source, from);
|
|
13348
|
+
if ("problem" in read6) problems.push(read6.problem);
|
|
13349
|
+
else defs.push(read6.def);
|
|
13352
13350
|
}
|
|
13353
13351
|
return { defs, problems };
|
|
13354
13352
|
}
|
|
@@ -13392,9 +13390,9 @@ var init_workflow_file = __esm({
|
|
|
13392
13390
|
plain = (agents) => agents.every((h) => !h.extra && !h.off);
|
|
13393
13391
|
flowList = (agents) => `[${agents.map((h) => scalar(h.agent)).join(", ")}]`;
|
|
13394
13392
|
BUILTIN_DEFS = BUNDLED.map(([id, source]) => {
|
|
13395
|
-
const
|
|
13396
|
-
if ("problem" in
|
|
13397
|
-
return
|
|
13393
|
+
const read6 = parseWorkflowFile(id, source, `the built-in \`${id}\` workflow`);
|
|
13394
|
+
if ("problem" in read6) throw new Error(read6.problem);
|
|
13395
|
+
return read6.def;
|
|
13398
13396
|
});
|
|
13399
13397
|
fileOf = (id) => path30.join(WORKFLOWS, id, WORKFLOW_FILE);
|
|
13400
13398
|
}
|
|
@@ -13465,8 +13463,8 @@ function foldOldStages(cfg) {
|
|
|
13465
13463
|
if (!savedOldStages(cfg)) return false;
|
|
13466
13464
|
const { ok } = writeConfig((raw) => {
|
|
13467
13465
|
const block = configBlock(raw.workflows);
|
|
13468
|
-
const
|
|
13469
|
-
for (const [id, flow] of Object.entries(
|
|
13466
|
+
const all2 = configBlock(block.stages);
|
|
13467
|
+
for (const [id, flow] of Object.entries(all2)) {
|
|
13470
13468
|
const mine = { ...configBlock(flow) };
|
|
13471
13469
|
const plan = readRows(mine.plan);
|
|
13472
13470
|
const lead = configBlock(mine.execute).lead;
|
|
@@ -13476,10 +13474,10 @@ function foldOldStages(cfg) {
|
|
|
13476
13474
|
if (typeof lead === "string" && lead.trim() && isBuiltinWorkflow(id) && !BUILTINS.find((w) => w.id === id).land.length) {
|
|
13477
13475
|
mine[LAND] = { helpers: [{ agent: canonicalSpecAgent(lead), extra: "" }] };
|
|
13478
13476
|
}
|
|
13479
|
-
if (Object.keys(mine).length)
|
|
13480
|
-
else delete
|
|
13477
|
+
if (Object.keys(mine).length) all2[id] = mine;
|
|
13478
|
+
else delete all2[id];
|
|
13481
13479
|
}
|
|
13482
|
-
if (Object.keys(
|
|
13480
|
+
if (Object.keys(all2).length) block.stages = all2;
|
|
13483
13481
|
else delete block.stages;
|
|
13484
13482
|
for (const key of ["added", "agentsOwned", "shipped", "qaScheduled"]) delete block[key];
|
|
13485
13483
|
if (Object.keys(block).length) raw.workflows = block;
|
|
@@ -13753,17 +13751,17 @@ function forgetWorkflowAgent(agent) {
|
|
|
13753
13751
|
}
|
|
13754
13752
|
}
|
|
13755
13753
|
return save2((block) => {
|
|
13756
|
-
const
|
|
13757
|
-
for (const [id, flow] of Object.entries(
|
|
13754
|
+
const all2 = configBlock(block.stages);
|
|
13755
|
+
for (const [id, flow] of Object.entries(all2)) {
|
|
13758
13756
|
const mine = configBlock(flow);
|
|
13759
13757
|
for (const [key, value] of Object.entries(mine)) {
|
|
13760
13758
|
const one = configBlock(value);
|
|
13761
13759
|
if (Array.isArray(one.helpers)) one.helpers = one.helpers.filter((h) => configBlock(h).agent !== agent);
|
|
13762
13760
|
mine[key] = one;
|
|
13763
13761
|
}
|
|
13764
|
-
|
|
13762
|
+
all2[id] = mine;
|
|
13765
13763
|
}
|
|
13766
|
-
if (Object.keys(
|
|
13764
|
+
if (Object.keys(all2).length) block.stages = all2;
|
|
13767
13765
|
});
|
|
13768
13766
|
}
|
|
13769
13767
|
function scheduledMembers(flow) {
|
|
@@ -14042,11 +14040,11 @@ function agentFilesBlock(agent) {
|
|
|
14042
14040
|
}
|
|
14043
14041
|
function agentMemoryBlock(agent) {
|
|
14044
14042
|
const dir = rel(agentMemoryDir(agent.name));
|
|
14045
|
-
const
|
|
14046
|
-
if (!
|
|
14043
|
+
const files2 = readAgentMemory(agent.name);
|
|
14044
|
+
if (!files2.length) return `Your memory folder is \`${dir}/\`. Nothing is in it yet.`;
|
|
14047
14045
|
return [
|
|
14048
14046
|
`Your memory, from \`${dir}/\` \u2014 what you kept on this board in earlier runs. Follow it here:`,
|
|
14049
|
-
...
|
|
14047
|
+
...files2.map((file) => `### \`${file.name}\`
|
|
14050
14048
|
|
|
14051
14049
|
${file.text || "_(empty)_"}`)
|
|
14052
14050
|
].join("\n\n");
|
|
@@ -14145,8 +14143,8 @@ var init_agents = __esm({
|
|
|
14145
14143
|
return RETIRED_SETTINGS.filter((r) => r.agent === agent.name && r.key in entry.extra).map((r) => r.note);
|
|
14146
14144
|
};
|
|
14147
14145
|
agentsOf = (names5) => {
|
|
14148
|
-
const
|
|
14149
|
-
return names5.map((name) =>
|
|
14146
|
+
const all2 = specAgents();
|
|
14147
|
+
return names5.map((name) => all2.find((a) => a.name === name)).filter((a) => !!a);
|
|
14150
14148
|
};
|
|
14151
14149
|
specAgentAssigned = (name, workflow) => {
|
|
14152
14150
|
const flow = workflowFor(workflow);
|
|
@@ -14558,12 +14556,12 @@ function candidateFileStats(candidate) {
|
|
|
14558
14556
|
const change2 = added === "-" || removed === "-" ? "binary" : `+${added} -${removed}`;
|
|
14559
14557
|
return `${file} (${change2}${fresh ? ", new" : ""})`;
|
|
14560
14558
|
};
|
|
14561
|
-
const
|
|
14559
|
+
const files2 = tracked.split("\n").filter(Boolean).map((line2) => format(line2));
|
|
14562
14560
|
for (const file of untrackedFiles(candidate)) {
|
|
14563
14561
|
const stat = gitDiff(["diff", "--no-index", "--numstat", "--", "/dev/null", file], candidate.cwd)?.trim();
|
|
14564
|
-
|
|
14562
|
+
files2.push(stat ? format(stat, true) : `${file} (new)`);
|
|
14565
14563
|
}
|
|
14566
|
-
return
|
|
14564
|
+
return files2;
|
|
14567
14565
|
}
|
|
14568
14566
|
function candidateStat(candidate) {
|
|
14569
14567
|
if (!candidate.base) return null;
|
|
@@ -14644,7 +14642,7 @@ var init_outputs = __esm({
|
|
|
14644
14642
|
return m ? [{ done: m[1] !== " ", text: m[2].trim() }] : [];
|
|
14645
14643
|
});
|
|
14646
14644
|
plannedTodos = (cardId2) => todos(cardId2).map((t) => t.text);
|
|
14647
|
-
looksLikePath = (
|
|
14645
|
+
looksLikePath = (token3) => !/\s/.test(token3) && !token3.includes("://") && !token3.startsWith("-") && token3.includes("/");
|
|
14648
14646
|
resolveOutput = (file) => {
|
|
14649
14647
|
if (file.startsWith("~/")) return path35.join(os4.homedir(), file.slice(2));
|
|
14650
14648
|
if (path35.isAbsolute(file)) return file;
|
|
@@ -14681,11 +14679,11 @@ function noWorktreeWhy() {
|
|
|
14681
14679
|
if (!currentBranch()) return "this checkout is on a detached HEAD, so a build has nowhere to land";
|
|
14682
14680
|
return void 0;
|
|
14683
14681
|
}
|
|
14684
|
-
function prepareDelivery(cardId2, wants,
|
|
14682
|
+
function prepareDelivery(cardId2, wants, files2 = false) {
|
|
14685
14683
|
const deliveryId = newDeliveryId();
|
|
14686
14684
|
const gated = cardId2 !== null;
|
|
14687
14685
|
const base = inGitRepo() ? headCommit() : null;
|
|
14688
|
-
if (
|
|
14686
|
+
if (files2) return { start: { deliveryId, commitMode: "files", base: base ?? void 0 } };
|
|
14689
14687
|
if (gated && cardWorkflow(cardId2)?.needsArtifact) {
|
|
14690
14688
|
return {
|
|
14691
14689
|
start: {
|
|
@@ -14805,15 +14803,15 @@ var init_commit_mode = __esm({
|
|
|
14805
14803
|
ID_LENGTH2 = 8;
|
|
14806
14804
|
MAX_NAMED = 5;
|
|
14807
14805
|
deliveryCwd = (delivery) => delivery.worktree ? worktreeDir(delivery.worktree) : REPO_ROOT;
|
|
14808
|
-
dirtyRefusal = (
|
|
14806
|
+
dirtyRefusal = (files2) => ({
|
|
14809
14807
|
error: [
|
|
14810
14808
|
"Commit or stash your changes before starting.",
|
|
14811
14809
|
"",
|
|
14812
|
-
...
|
|
14813
|
-
...
|
|
14810
|
+
...files2.slice(0, MAX_NAMED),
|
|
14811
|
+
...files2.length > MAX_NAMED ? [`and ${files2.length - MAX_NAMED} more`] : []
|
|
14814
14812
|
].join("\n"),
|
|
14815
14813
|
reason: "dirty",
|
|
14816
|
-
paths:
|
|
14814
|
+
paths: files2.slice(0, MAX_NAMED)
|
|
14817
14815
|
});
|
|
14818
14816
|
MAX_SUBJECT = 72;
|
|
14819
14817
|
deliveryName = (delivery) => delivery.cardId === null ? `delivery ${delivery.deliveryId}` : `#${delivery.cardId}`;
|
|
@@ -14899,19 +14897,19 @@ function deliveryState(delivery, questions) {
|
|
|
14899
14897
|
};
|
|
14900
14898
|
}
|
|
14901
14899
|
if (landing?.conflictFiles?.length || landing?.fitAt) {
|
|
14902
|
-
const
|
|
14900
|
+
const files2 = landing.conflictFiles ?? [];
|
|
14903
14901
|
const attempt2 = (landing.conflictFails ?? 0) + 1;
|
|
14904
14902
|
const branch2 = `\`${delivery.targetBranch ?? "the target branch"}\``;
|
|
14905
|
-
const where2 =
|
|
14903
|
+
const where2 = files2.length ? `${some(files2)} against ${branch2}` : `the combined result with ${branch2}`;
|
|
14906
14904
|
if (landing.status === "waiting" && landing.conflictAt) {
|
|
14907
14905
|
const left = Math.max(0, Math.round((landing.conflictAt - Date.now()) / 1e3));
|
|
14908
14906
|
return {
|
|
14909
14907
|
stage: "retry",
|
|
14910
14908
|
label: "Waiting to retry",
|
|
14911
|
-
line: `Attempt ${attempt2 - 1} left ${where2} ${
|
|
14909
|
+
line: `Attempt ${attempt2 - 1} left ${where2} ${files2.length ? "conflicted" : "unchecked"}. It gave the landing slot up and opens attempt ${attempt2} ${left ? `in ${left}s` : "now"} \u2014 another delivery can land while it waits.`,
|
|
14912
14910
|
paused: false,
|
|
14913
14911
|
kind: "conflict-wait",
|
|
14914
|
-
files,
|
|
14912
|
+
files: files2,
|
|
14915
14913
|
branch: delivery.targetBranch,
|
|
14916
14914
|
attempt: attempt2,
|
|
14917
14915
|
retryIn: left
|
|
@@ -14920,11 +14918,11 @@ function deliveryState(delivery, questions) {
|
|
|
14920
14918
|
if (landing.status === "landing") {
|
|
14921
14919
|
return {
|
|
14922
14920
|
stage: "conflict",
|
|
14923
|
-
label:
|
|
14924
|
-
line: `Attempt ${attempt2}: ${
|
|
14921
|
+
label: files2.length ? "Resolving a conflict" : "Checking the combined result",
|
|
14922
|
+
line: `Attempt ${attempt2}: ${files2.length ? "resolving" : "checking"} ${where2}. It lands by itself once ${files2.length ? "the conflict is out" : "that is done"} \u2014 nothing is asked of you.`,
|
|
14925
14923
|
paused: false,
|
|
14926
14924
|
kind: "conflict",
|
|
14927
|
-
files,
|
|
14925
|
+
files: files2,
|
|
14928
14926
|
branch: delivery.targetBranch,
|
|
14929
14927
|
attempt: attempt2
|
|
14930
14928
|
};
|
|
@@ -14965,6 +14963,16 @@ function deliveryState(delivery, questions) {
|
|
|
14965
14963
|
...reason ? { kind: reason.kind, files: reason.files, branch: delivery.targetBranch, worktree: delivery.worktree } : { kind: "refused", raw: line2 }
|
|
14966
14964
|
};
|
|
14967
14965
|
}
|
|
14966
|
+
if (landing?.status === "landing" || landing?.status === "waiting") {
|
|
14967
|
+
return {
|
|
14968
|
+
stage: "landing",
|
|
14969
|
+
label: "Landing",
|
|
14970
|
+
line: `Landing on \`${delivery.targetBranch ?? "the target branch"}\` \u2014 nothing is asked of you.`,
|
|
14971
|
+
paused: false,
|
|
14972
|
+
kind: "landing",
|
|
14973
|
+
branch: delivery.targetBranch
|
|
14974
|
+
};
|
|
14975
|
+
}
|
|
14968
14976
|
const branch = delivery.commitMode === "auto" ? delivery.targetBranch : void 0;
|
|
14969
14977
|
const where = branch ? `, to land on \`${branch}\`` : "";
|
|
14970
14978
|
return {
|
|
@@ -14986,7 +14994,7 @@ var init_pause = __esm({
|
|
|
14986
14994
|
HELD_ON_QUESTIONS = "held on an open question";
|
|
14987
14995
|
IN_LINE = "in line behind";
|
|
14988
14996
|
count2 = (n) => `${n} open question${n === 1 ? "" : "s"}`;
|
|
14989
|
-
some = (
|
|
14997
|
+
some = (files2) => files2.length === 1 ? `\`${files2[0]}\`` : `${files2.length} files`;
|
|
14990
14998
|
upper = (text5) => text5 ? text5[0].toUpperCase() + text5.slice(1) : text5;
|
|
14991
14999
|
end = (text5) => /[.!?)]$/.test(text5) ? text5 : `${text5}.`;
|
|
14992
15000
|
isHold = (why) => why.startsWith(HELD_ON_QUESTIONS);
|
|
@@ -15863,7 +15871,7 @@ var init_stage_end = __esm({
|
|
|
15863
15871
|
const flow = flowByAction(action);
|
|
15864
15872
|
return flow ? stageOfFlow(flow.command) : void 0;
|
|
15865
15873
|
};
|
|
15866
|
-
askedFor = (cardId2, agent) => readStore().runs.some((
|
|
15874
|
+
askedFor = (cardId2, agent) => readStore().runs.some((run2) => run2.action === "spec" && run2.cardId === cardId2 && run2.specAgent === agent);
|
|
15867
15875
|
}
|
|
15868
15876
|
});
|
|
15869
15877
|
|
|
@@ -15872,10 +15880,10 @@ function reviewOf(delivery) {
|
|
|
15872
15880
|
if (!delivery.review) delivery.review = { rounds: [] };
|
|
15873
15881
|
return delivery.review;
|
|
15874
15882
|
}
|
|
15875
|
-
function nextAfterSession(delivery,
|
|
15883
|
+
function nextAfterSession(delivery, run2) {
|
|
15876
15884
|
if (delivery.status !== "active") return HOLD;
|
|
15877
|
-
if (
|
|
15878
|
-
if (
|
|
15885
|
+
if (run2.action === "scheduled") return run2.status === "done" ? { finish: true } : HOLD;
|
|
15886
|
+
if (run2.action !== "implement" || run2.status !== "done") return HOLD;
|
|
15879
15887
|
return stageShortfall("build", delivery) ?? { finish: true };
|
|
15880
15888
|
}
|
|
15881
15889
|
function stageShortfall(stage, delivery) {
|
|
@@ -15913,23 +15921,23 @@ import path41 from "node:path";
|
|
|
15913
15921
|
function writeAudit(delivery, runs) {
|
|
15914
15922
|
const known = new Map(readAudit(delivery.deliveryId)?.sessions.map((s) => [s.sessionId, s]) ?? []);
|
|
15915
15923
|
const sessions = delivery.sessions.flatMap((id) => {
|
|
15916
|
-
const
|
|
15917
|
-
if (!
|
|
15924
|
+
const run2 = runs.find((r) => r.sessionId === id);
|
|
15925
|
+
if (!run2) {
|
|
15918
15926
|
const before = known.get(id);
|
|
15919
15927
|
return before ? [before] : [];
|
|
15920
15928
|
}
|
|
15921
15929
|
return [
|
|
15922
15930
|
{
|
|
15923
|
-
sessionId:
|
|
15924
|
-
action:
|
|
15925
|
-
status:
|
|
15926
|
-
startedAt:
|
|
15927
|
-
endedAt:
|
|
15928
|
-
harness:
|
|
15929
|
-
model:
|
|
15930
|
-
costUsd:
|
|
15931
|
-
resumedFrom:
|
|
15932
|
-
log: rel(
|
|
15931
|
+
sessionId: run2.sessionId,
|
|
15932
|
+
action: run2.action,
|
|
15933
|
+
status: run2.status,
|
|
15934
|
+
startedAt: run2.startedAt,
|
|
15935
|
+
endedAt: run2.endedAt,
|
|
15936
|
+
harness: run2.harness || void 0,
|
|
15937
|
+
model: run2.model,
|
|
15938
|
+
costUsd: run2.costUsd,
|
|
15939
|
+
resumedFrom: run2.resumedFrom,
|
|
15940
|
+
log: rel(run2.logPath)
|
|
15933
15941
|
}
|
|
15934
15942
|
];
|
|
15935
15943
|
});
|
|
@@ -16007,7 +16015,7 @@ function settleOrphanedDeliveries() {
|
|
|
16007
16015
|
const recovered = [];
|
|
16008
16016
|
withStore((store) => {
|
|
16009
16017
|
const known = new Set(store.deliveries.map((d) => d.deliveryId));
|
|
16010
|
-
for (const
|
|
16018
|
+
for (const run2 of store.runs) if (run2.deliveryId) known.add(run2.deliveryId);
|
|
16011
16019
|
for (const audit of audits) {
|
|
16012
16020
|
if (known.has(audit.deliveryId) || !worktreeExists(audit.worktree)) continue;
|
|
16013
16021
|
if (lastSessionRunning(audit)) {
|
|
@@ -16056,13 +16064,13 @@ function endedDelivery(id) {
|
|
|
16056
16064
|
const row = auditRow(audit);
|
|
16057
16065
|
if (row) rows.set(row.deliveryId, row);
|
|
16058
16066
|
}
|
|
16059
|
-
const
|
|
16067
|
+
const all2 = [...rows.values()].sort((a, b) => a.startedAt - b.startedAt);
|
|
16060
16068
|
if (/^#?\d+$/.test(key)) {
|
|
16061
16069
|
const cardId2 = Number(key.replace("#", ""));
|
|
16062
|
-
const mine =
|
|
16070
|
+
const mine = all2.filter((d) => d.cardId === cardId2);
|
|
16063
16071
|
return mine.filter((d) => d.status !== "active").pop() ?? mine.pop();
|
|
16064
16072
|
}
|
|
16065
|
-
return
|
|
16073
|
+
return all2.find((d) => d.deliveryId === key) ?? all2.find((d) => d.deliveryId.startsWith(key));
|
|
16066
16074
|
}
|
|
16067
16075
|
function resumeRefusal(delivery, store) {
|
|
16068
16076
|
const id = delivery.deliveryId;
|
|
@@ -16094,11 +16102,11 @@ function resumeRefusal(delivery, store) {
|
|
|
16094
16102
|
);
|
|
16095
16103
|
}
|
|
16096
16104
|
if (!worktreeExists(delivery.worktree)) {
|
|
16097
|
-
const
|
|
16105
|
+
const path83 = delivery.worktree ?? "";
|
|
16098
16106
|
return refusal(
|
|
16099
16107
|
"deliveryWorktreeGone",
|
|
16100
|
-
`delivery ${id}'s worktree ${
|
|
16101
|
-
{ id, task, path:
|
|
16108
|
+
`delivery ${id}'s worktree ${path83} is gone, so there is nothing to carry on. ${rebuild}`,
|
|
16109
|
+
{ id, task, path: path83, command: discard }
|
|
16102
16110
|
);
|
|
16103
16111
|
}
|
|
16104
16112
|
if (!branchExists(delivery.branch)) {
|
|
@@ -16275,8 +16283,8 @@ function activeDelivery(cardId2) {
|
|
|
16275
16283
|
function findDelivery(id) {
|
|
16276
16284
|
const key = id.trim();
|
|
16277
16285
|
if (!key) return void 0;
|
|
16278
|
-
const
|
|
16279
|
-
return
|
|
16286
|
+
const all2 = readStore().deliveries;
|
|
16287
|
+
return all2.find((d) => d.deliveryId === key) ?? all2.find((d) => d.deliveryId.startsWith(key));
|
|
16280
16288
|
}
|
|
16281
16289
|
function namedDelivery(id) {
|
|
16282
16290
|
const key = id.trim();
|
|
@@ -16292,8 +16300,8 @@ function deliveryFor(req) {
|
|
|
16292
16300
|
}
|
|
16293
16301
|
return req.id === void 0 ? void 0 : activeDelivery(req.id);
|
|
16294
16302
|
}
|
|
16295
|
-
function joinDelivery(store,
|
|
16296
|
-
const cardId2 =
|
|
16303
|
+
function joinDelivery(store, run2, title, step, start, direct) {
|
|
16304
|
+
const cardId2 = run2.cardId;
|
|
16297
16305
|
let delivery = cardId2 === null ? void 0 : activeIn(store, cardId2);
|
|
16298
16306
|
if (!delivery) {
|
|
16299
16307
|
delivery = {
|
|
@@ -16301,7 +16309,7 @@ function joinDelivery(store, run, title, step, start, direct) {
|
|
|
16301
16309
|
cardId: cardId2,
|
|
16302
16310
|
title,
|
|
16303
16311
|
status: "active",
|
|
16304
|
-
startedAt:
|
|
16312
|
+
startedAt: run2.startedAt,
|
|
16305
16313
|
sessions: [],
|
|
16306
16314
|
// The one read of the card this delivery will ever make for its requirements — or,
|
|
16307
16315
|
// with no card, the sentence or the plan's words it was handed, which can never move
|
|
@@ -16346,21 +16354,21 @@ function joinDelivery(store, run, title, step, start, direct) {
|
|
|
16346
16354
|
} else if (step === "implement" && delivery.review?.stopped) {
|
|
16347
16355
|
delivery.review.stopped = void 0;
|
|
16348
16356
|
}
|
|
16349
|
-
delivery.sessions.push(
|
|
16350
|
-
delivery.steps.push({ step, at:
|
|
16351
|
-
joinFlow(
|
|
16357
|
+
delivery.sessions.push(run2.sessionId);
|
|
16358
|
+
delivery.steps.push({ step, at: run2.startedAt });
|
|
16359
|
+
joinFlow(run2, delivery);
|
|
16352
16360
|
writeAudit(delivery, store.runs);
|
|
16353
16361
|
return delivery;
|
|
16354
16362
|
}
|
|
16355
16363
|
function adoptDirectCard(sessionId, cardId2) {
|
|
16356
16364
|
return withStore((store) => {
|
|
16357
|
-
const
|
|
16358
|
-
if (!
|
|
16359
|
-
const delivery =
|
|
16360
|
-
if (
|
|
16365
|
+
const run2 = store.runs.find((r) => r.sessionId === sessionId && r.status === "running");
|
|
16366
|
+
if (!run2 || run2.action !== "implement" || run2.cardId !== null) return false;
|
|
16367
|
+
const delivery = run2.deliveryId ? store.deliveries.find((d) => d.deliveryId === run2.deliveryId && d.status === "active") : void 0;
|
|
16368
|
+
if (run2.deliveryId && (!delivery || delivery.cardId !== null)) return false;
|
|
16361
16369
|
const idle = "ready";
|
|
16362
|
-
|
|
16363
|
-
|
|
16370
|
+
run2.cardId = cardId2;
|
|
16371
|
+
run2.priorStatus = idle;
|
|
16364
16372
|
if (!delivery) return true;
|
|
16365
16373
|
delivery.cardId = cardId2;
|
|
16366
16374
|
delivery.priorStatus = idle;
|
|
@@ -16368,21 +16376,21 @@ function adoptDirectCard(sessionId, cardId2) {
|
|
|
16368
16376
|
return true;
|
|
16369
16377
|
});
|
|
16370
16378
|
}
|
|
16371
|
-
function joinActive(store,
|
|
16372
|
-
const delivery = deliveryId ? store.deliveries.find((d) => d.status === "active" && d.deliveryId === deliveryId) :
|
|
16379
|
+
function joinActive(store, run2, step, deliveryId) {
|
|
16380
|
+
const delivery = deliveryId ? store.deliveries.find((d) => d.status === "active" && d.deliveryId === deliveryId) : run2.cardId === null ? void 0 : activeIn(store, run2.cardId);
|
|
16373
16381
|
if (!delivery) return void 0;
|
|
16374
|
-
delivery.sessions.push(
|
|
16375
|
-
delivery.steps.push({ step, at:
|
|
16382
|
+
delivery.sessions.push(run2.sessionId);
|
|
16383
|
+
delivery.steps.push({ step, at: run2.startedAt });
|
|
16376
16384
|
if (delivery.review?.stopped) delivery.review.stopped = void 0;
|
|
16377
16385
|
const now = Date.now();
|
|
16378
16386
|
for (const answer2 of delivery.answers ?? []) if (!answer2.actedAt) answer2.actedAt = now;
|
|
16379
|
-
joinFlow(
|
|
16387
|
+
joinFlow(run2, delivery);
|
|
16380
16388
|
writeAudit(delivery, store.runs);
|
|
16381
16389
|
return delivery;
|
|
16382
16390
|
}
|
|
16383
|
-
function joinFlow(
|
|
16384
|
-
|
|
16385
|
-
|
|
16391
|
+
function joinFlow(run2, delivery) {
|
|
16392
|
+
run2.deliveryId = delivery.deliveryId;
|
|
16393
|
+
run2.flowId = delivery.deliveryId;
|
|
16386
16394
|
}
|
|
16387
16395
|
function endDelivery(deliveryId, status) {
|
|
16388
16396
|
const ended = withStore((store) => {
|
|
@@ -16398,18 +16406,18 @@ function endDelivery(deliveryId, status) {
|
|
|
16398
16406
|
}
|
|
16399
16407
|
return ended;
|
|
16400
16408
|
}
|
|
16401
|
-
async function settleDelivery(
|
|
16402
|
-
if (!
|
|
16403
|
-
const before = readStore().deliveries.find((d) => d.deliveryId ===
|
|
16409
|
+
async function settleDelivery(run2) {
|
|
16410
|
+
if (!run2.deliveryId) return;
|
|
16411
|
+
const before = readStore().deliveries.find((d) => d.deliveryId === run2.deliveryId);
|
|
16404
16412
|
if (!before) return;
|
|
16405
|
-
const built =
|
|
16406
|
-
const passed =
|
|
16413
|
+
const built = run2.status === "done" && run2.action === "implement";
|
|
16414
|
+
const passed = run2.status === "done" && run2.action === "scheduled";
|
|
16407
16415
|
const commit = (built || passed) && before.status === "active" ? commitDeliveryWork(before) : { ok: true };
|
|
16408
16416
|
const uncommitted = commit.ok ? void 0 : commit.why;
|
|
16409
16417
|
const reviewed = !uncommitted && before.commitMode === "manual" && before.cardId !== null && (built || passed) ? snapshotReviewed(before) : void 0;
|
|
16410
|
-
const
|
|
16418
|
+
const files2 = (built || passed) && before.commitMode === "files" && !before.scheduled ? filesStop(before) : void 0;
|
|
16411
16419
|
const settled = withStore((store) => {
|
|
16412
|
-
const delivery = store.deliveries.find((d) => d.deliveryId ===
|
|
16420
|
+
const delivery = store.deliveries.find((d) => d.deliveryId === run2.deliveryId);
|
|
16413
16421
|
if (!delivery) return null;
|
|
16414
16422
|
if (uncommitted && delivery.status === "active") {
|
|
16415
16423
|
const review = reviewOf(delivery);
|
|
@@ -16417,7 +16425,7 @@ async function settleDelivery(run) {
|
|
|
16417
16425
|
releaseLanding(delivery);
|
|
16418
16426
|
return null;
|
|
16419
16427
|
}
|
|
16420
|
-
const next = nextAfterSession(delivery,
|
|
16428
|
+
const next = nextAfterSession(delivery, run2);
|
|
16421
16429
|
if ("hold" in next) return null;
|
|
16422
16430
|
if ("finish" in next) {
|
|
16423
16431
|
if (reviewed) {
|
|
@@ -16426,12 +16434,12 @@ async function settleDelivery(run) {
|
|
|
16426
16434
|
}
|
|
16427
16435
|
if (delivery.commitMode === "manual" && !delivery.base) return { end: "finished", complete: delivery.cardId !== null };
|
|
16428
16436
|
if (delivery.commitMode === "files") {
|
|
16429
|
-
if (!
|
|
16430
|
-
reviewOf(delivery).stopped = { reason:
|
|
16437
|
+
if (!files2) return { end: "finished", complete: delivery.cardId !== null };
|
|
16438
|
+
reviewOf(delivery).stopped = { reason: files2.reason, why: files2.why, at: Date.now(), paths: files2.paths };
|
|
16431
16439
|
return null;
|
|
16432
16440
|
}
|
|
16433
16441
|
if (wantsLanding(delivery)) {
|
|
16434
|
-
queueLanding(delivery,
|
|
16442
|
+
queueLanding(delivery, run2);
|
|
16435
16443
|
return null;
|
|
16436
16444
|
}
|
|
16437
16445
|
return { end: "finished" };
|
|
@@ -16441,15 +16449,15 @@ async function settleDelivery(run) {
|
|
|
16441
16449
|
return null;
|
|
16442
16450
|
});
|
|
16443
16451
|
if (settled && "end" in settled) {
|
|
16444
|
-
endDelivery(
|
|
16445
|
-
if (settled.complete) await completeCard(before.cardId,
|
|
16452
|
+
endDelivery(run2.deliveryId, settled.end);
|
|
16453
|
+
if (settled.complete) await completeCard(before.cardId, run2.deliveryId);
|
|
16446
16454
|
}
|
|
16447
|
-
syncAudit(
|
|
16455
|
+
syncAudit(run2.deliveryId, run2);
|
|
16448
16456
|
}
|
|
16449
16457
|
function deliveryAcceptsAnswers(cardId2) {
|
|
16450
16458
|
return !!deliveryStateOf(cardId2)?.paused;
|
|
16451
16459
|
}
|
|
16452
|
-
function queueLanding(delivery,
|
|
16460
|
+
function queueLanding(delivery, run2) {
|
|
16453
16461
|
const landing = delivery.landing ?? { status: "waiting", attempts: 0, at: Date.now() };
|
|
16454
16462
|
delivery.landing = {
|
|
16455
16463
|
...landing,
|
|
@@ -16458,7 +16466,7 @@ function queueLanding(delivery, run) {
|
|
|
16458
16466
|
reason: void 0,
|
|
16459
16467
|
checks: [
|
|
16460
16468
|
...landing.checks ?? [],
|
|
16461
|
-
{ name: `${
|
|
16469
|
+
{ name: `${run2.action} ${run2.sessionId.slice(0, 8)}`, ok: true, at: Date.now() }
|
|
16462
16470
|
],
|
|
16463
16471
|
at: Date.now()
|
|
16464
16472
|
};
|
|
@@ -16917,20 +16925,20 @@ function waitForTarget(delivery) {
|
|
|
16917
16925
|
});
|
|
16918
16926
|
return { done: true };
|
|
16919
16927
|
}
|
|
16920
|
-
function startConflict(delivery, target,
|
|
16928
|
+
function startConflict(delivery, target, files2) {
|
|
16921
16929
|
patchLanding(delivery.deliveryId, (landing) => {
|
|
16922
16930
|
landing.onto = target;
|
|
16923
|
-
landing.conflictFiles =
|
|
16924
|
-
landing.why = `resolving a conflict with ${delivery.targetBranch} in ${names2(
|
|
16931
|
+
landing.conflictFiles = files2;
|
|
16932
|
+
landing.why = `resolving a conflict with ${delivery.targetBranch} in ${names2(files2)}`;
|
|
16925
16933
|
landing.reason = void 0;
|
|
16926
16934
|
});
|
|
16927
16935
|
return { start: conflictRun(delivery) };
|
|
16928
16936
|
}
|
|
16929
16937
|
function reopenConflict(delivery, dir) {
|
|
16930
|
-
const
|
|
16938
|
+
const files2 = conflictedPaths(dir);
|
|
16931
16939
|
patchLanding(delivery.deliveryId, (landing) => {
|
|
16932
16940
|
landing.conflictAt = void 0;
|
|
16933
|
-
if (
|
|
16941
|
+
if (files2.length) landing.conflictFiles = files2;
|
|
16934
16942
|
if (landing.fitAt) landing.fitAt = Date.now();
|
|
16935
16943
|
});
|
|
16936
16944
|
return { start: conflictRun(delivery) };
|
|
@@ -16939,7 +16947,8 @@ async function finishConflict(delivery, dir) {
|
|
|
16939
16947
|
const left = conflictedPaths(dir);
|
|
16940
16948
|
const done2 = left.length ? { ok: false, why: `${names2(left)} ${are(left.length)} still conflicted` } : continueRebase(dir);
|
|
16941
16949
|
if (done2.ok && !rebaseInProgress(dir)) {
|
|
16942
|
-
|
|
16950
|
+
const after2 = await afterRebase(delivery, delivery.landing?.onto ?? delivery.base, "conflict");
|
|
16951
|
+
return after2.done ? after2 : await landNow(delivery);
|
|
16943
16952
|
}
|
|
16944
16953
|
return failAttempt(
|
|
16945
16954
|
delivery,
|
|
@@ -16962,7 +16971,12 @@ async function finishFit(delivery, dir) {
|
|
|
16962
16971
|
landing.conflictFails = void 0;
|
|
16963
16972
|
landing.why = void 0;
|
|
16964
16973
|
});
|
|
16965
|
-
return
|
|
16974
|
+
return await landNow(delivery);
|
|
16975
|
+
}
|
|
16976
|
+
async function landNow(delivery) {
|
|
16977
|
+
const live = readStore().deliveries.find((d) => d.deliveryId === delivery.deliveryId);
|
|
16978
|
+
if (!live || live.status !== "active") return { done: true };
|
|
16979
|
+
return await landStep(live);
|
|
16966
16980
|
}
|
|
16967
16981
|
function failAttempt(delivery, why, left = []) {
|
|
16968
16982
|
const fails = (delivery.landing?.conflictFails ?? 0) + 1;
|
|
@@ -17034,9 +17048,9 @@ async function move(delivery, tip, target) {
|
|
|
17034
17048
|
return { done: true };
|
|
17035
17049
|
}
|
|
17036
17050
|
function landWait(wait, where) {
|
|
17037
|
-
const
|
|
17051
|
+
const files2 = wait.files;
|
|
17038
17052
|
const at = sameDir(where, REPO_ROOT) ? "your checkout" : `\`${rel(where)}\``;
|
|
17039
|
-
return wait.kind === "untracked" ? `${some2(
|
|
17053
|
+
return wait.kind === "untracked" ? `${some2(files2)} in ${at} ${are(files2.length)} not in git, and landing would write over ${them(files2.length)} \u2014 move or delete ${them(files2.length)}` : `your changes to ${some2(files2)} in ${at} would be overwritten by landing \u2014 commit or stash ${them(files2.length)}`;
|
|
17040
17054
|
}
|
|
17041
17055
|
async function finish(delivery, landed) {
|
|
17042
17056
|
patchLanding(delivery.deliveryId, (landing) => {
|
|
@@ -17059,11 +17073,11 @@ function alreadyOnTarget(delivery) {
|
|
|
17059
17073
|
const onto = branchTip(delivery.targetBranch);
|
|
17060
17074
|
if (!onto) return null;
|
|
17061
17075
|
const dir = worktreeDir(delivery.worktree);
|
|
17062
|
-
const
|
|
17063
|
-
if (!
|
|
17076
|
+
const files2 = changedPaths(delivery.base, delivery.branch, dir);
|
|
17077
|
+
if (!files2?.length) return null;
|
|
17064
17078
|
const patch2 = branchPatch(delivery.base, delivery.branch, dir);
|
|
17065
17079
|
if (!patch2 || !reverseApplies(patch2, onto, dir)) return null;
|
|
17066
|
-
return { commit: lastCommitTouching(onto,
|
|
17080
|
+
return { commit: lastCommitTouching(onto, files2, dir) ?? void 0, onto };
|
|
17067
17081
|
}
|
|
17068
17082
|
async function settleAlreadyLanded(delivery) {
|
|
17069
17083
|
const found2 = alreadyOnTarget(delivery);
|
|
@@ -17120,8 +17134,8 @@ var init_landing = __esm({
|
|
|
17120
17134
|
init_store();
|
|
17121
17135
|
init_worktree();
|
|
17122
17136
|
MAX_NAMED2 = 5;
|
|
17123
|
-
names2 = (
|
|
17124
|
-
some2 = (
|
|
17137
|
+
names2 = (files2) => `${files2.slice(0, MAX_NAMED2).join(", ")}${files2.length > MAX_NAMED2 ? `, and ${files2.length - MAX_NAMED2} more` : ""}`;
|
|
17138
|
+
some2 = (files2) => files2.length === 1 ? `\`${files2[0]}\`` : `${files2.length} files`;
|
|
17125
17139
|
are = (n) => n === 1 ? "is" : "are";
|
|
17126
17140
|
them = (n) => n === 1 ? "it" : "them";
|
|
17127
17141
|
sayWhy = (deliveryId, why, wait, reason) => patchLanding(deliveryId, (landing) => {
|
|
@@ -17181,14 +17195,14 @@ function returnRunPictures(sessionId, box2) {
|
|
|
17181
17195
|
} catch {
|
|
17182
17196
|
}
|
|
17183
17197
|
}
|
|
17184
|
-
function moveRunPictures(from, to,
|
|
17185
|
-
if (
|
|
17198
|
+
function moveRunPictures(from, to, files2 = []) {
|
|
17199
|
+
if (files2.length === 0) return [];
|
|
17186
17200
|
try {
|
|
17187
17201
|
fs42.renameSync(pictureBox(from), pictureBox(to));
|
|
17188
17202
|
} catch {
|
|
17189
|
-
return
|
|
17203
|
+
return files2;
|
|
17190
17204
|
}
|
|
17191
|
-
return
|
|
17205
|
+
return files2.map((file) => path43.join(pictureBox(to), path43.basename(file)));
|
|
17192
17206
|
}
|
|
17193
17207
|
var pictureName, boxId, freeBox, pictureBox;
|
|
17194
17208
|
var init_pictures = __esm({
|
|
@@ -17248,8 +17262,8 @@ function globalMemories() {
|
|
|
17248
17262
|
return out;
|
|
17249
17263
|
}
|
|
17250
17264
|
function globalMemoryBlock(agent) {
|
|
17251
|
-
const
|
|
17252
|
-
const kept = agent.memory.map((name) =>
|
|
17265
|
+
const all2 = globalMemories();
|
|
17266
|
+
const kept = agent.memory.map((name) => all2.find((m) => m.name === name)).filter((m) => !!m);
|
|
17253
17267
|
if (!kept.length) return "";
|
|
17254
17268
|
return [
|
|
17255
17269
|
"Global memories you share with other agents \u2014 open one only when the work needs it, and",
|
|
@@ -17455,7 +17469,7 @@ var init_write_agent = __esm({
|
|
|
17455
17469
|
var writing_default;
|
|
17456
17470
|
var init_writing = __esm({
|
|
17457
17471
|
"../cli/src/guide/writing.md"() {
|
|
17458
|
-
writing_default = '# Write a card\n\nA card has a **human half** for review and a folded **agent half** for implementation.\nEvery flow writes a card in this order:\n\n```text\n<one short paragraph: the observable result and the current behavior or constraint it changes.>\n\n## Worth noting <- answered material decisions; keep empty when none\n- **<question the decision settles>**: <answer>\n\n## Worth noting after implementation\n- **<question the decision settles>**: <answer>\n <- answered material decisions building turned up; written\n by the build, omit when empty. Never approved delivery scope\n\n## By `<name>` agent <- every preview agent\'s section\n\n<!-- agent --> <- boundary\n\n## Today\n## Scope <- requirements, not rationale\n## Todo <- one build step per checkbox\n## Decided by the agent\n- **<question the decision settles>**: <answer>\n### Overruled by the user <- always last\n```\n\n## `Worth noting`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting`.\n- **Keep it glanceable**: one entry is one reviewer decision, written as one sentence that\n states the chosen behavior and its main cost. Merge entries governed by one policy. One\n to three independent entries is normal.\n- **Cut the story**: omit chronology, evidence trails, exhaustive consequences, and the\n rejected option unless it is necessary to understand the main cost.\n\n## `Worth noting after implementation`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting after implementation`.\n- **Keep it non-blocking**: this section never changes or blocks a delivery already in\n flight.\n- **Use the same compact format**: one reviewer decision and its main cost in one sentence.\n- **Exclude work logs**: never put diagnostics, run history, check results, suggestions,\n or unresolved questions here.\n- **Never approve a deviation here**: work that contradicts the approved requirements must\n be fixed or sent to the user by the build.\n\n## Agent sections\n\n- **Owner\'s rules first**: before rewriting, removing, or overruling content a preview agent\n owns, read that agent\'s rules with `akb raw agent-file <agent> AGENT.md`, and the\n references they point to for the affected part with the same command and their path. When\n they cannot be read, surface the gap before changing that content.\n\n## `Today`\n\n- **Omit by default**: write it, heading included, only when the build depends on findings\n the code does not make obvious; a small change to a few simple files needs none.\n\n## `Todo`\n\n- **Build steps only**: finish planning on the card; never defer it to a todo.\n- **Preserve completed work**: never edit, delete, or untick a checked todo; append a todo to\n reverse it.\n\n## `Decided by the agent`\n\n- **Only what changes the build**: include answered decisions the builder would otherwise\n get wrong and neither `Worth noting` section holds; omit a finding that needs no action.\n- **Preserve superseded calls**: move agent or review decisions the user reverses to\n `Overruled by the user`; delete one the card leaves the builder no room to repeat.\n\n## General writing requirements\n\n- **Keep section titles fixed**:
|
|
17472
|
+
writing_default = '# Write a card\n\nA card has a **human half** for review and a folded **agent half** for implementation.\nEvery flow writes a card in this order:\n\n```text\n<one short paragraph: the observable result and the current behavior or constraint it changes.>\n\n## Worth noting <- answered material decisions; keep empty when none\n- **<question the decision settles>**: <answer>\n\n## Worth noting after implementation\n- **<question the decision settles>**: <answer>\n <- answered material decisions building turned up; written\n by the build, omit when empty. Never approved delivery scope\n\n## By `<name>` agent <- every preview agent\'s section\n\n<!-- agent --> <- boundary\n\n## Today\n## Scope <- requirements, not rationale\n## Todo <- one build step per checkbox\n## Decided by the agent\n- **<question the decision settles>**: <answer>\n### Overruled by the user <- always last\n```\n\n## `Worth noting`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting`.\n- **Keep it glanceable**: one entry is one reviewer decision, written as one sentence that\n states the chosen behavior and its main cost. Merge entries governed by one policy. One\n to three independent entries is normal.\n- **Cut the story**: omit chronology, evidence trails, exhaustive consequences, and the\n rejected option unless it is necessary to understand the main cost.\n\n## `Worth noting after implementation`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting after implementation`.\n- **Keep it non-blocking**: this section never changes or blocks a delivery already in\n flight.\n- **Use the same compact format**: one reviewer decision and its main cost in one sentence.\n- **Exclude work logs**: never put diagnostics, run history, check results, suggestions,\n or unresolved questions here.\n- **Never approve a deviation here**: work that contradicts the approved requirements must\n be fixed or sent to the user by the build.\n\n## Agent sections\n\n- **Owner\'s rules first**: before rewriting, removing, or overruling content a preview agent\n owns, read that agent\'s rules with `akb raw agent-file <agent> AGENT.md`, and the\n references they point to for the affected part with the same command and their path. When\n they cannot be read, surface the gap before changing that content.\n\n## `Today`\n\n- **Omit by default**: write it, heading included, only when the build depends on findings\n the code does not make obvious; a small change to a few simple files needs none.\n\n## `Todo`\n\n- **Build steps only**: finish planning on the card; never defer it to a todo.\n- **Preserve completed work**: never edit, delete, or untick a checked todo; append a todo to\n reverse it.\n\n## `Decided by the agent`\n\n- **Only what changes the build**: include answered decisions the builder would otherwise\n get wrong and neither `Worth noting` section holds; omit a finding that needs no action.\n- **Preserve superseded calls**: move agent or review decisions the user reverses to\n `Overruled by the user`; delete one the card leaves the builder no room to repeat.\n\n## General writing requirements\n\n- **Keep section titles fixed**: write every `##` and `###` section title in English, never\n rename or translate an existing one, and leave empty scaffold sections in place.\n- **Edit only the body**: use board commands for every frontmatter field (`akb guide board`).\n- **Name the source in frontmatter**: pass each plan path, card `#id`, or URL the card came\n from as `--source` to `akb raw create`, or `--add-source` to `akb raw update`; never write\n a `## Source` section.\n- **Write for a fresh reader**: use professional, comprehensible language with no assumed\n conversation context.\n- **Keep human half self-contained**: nothing above `<!-- agent -->` may rely on the folded\n agent half.\n- **Keep bullets atomic**: use `- **<short title>**: <one clear sentence>` for one rule per\n bullet.\n- **Specify behavior**: omit planning notes and unnecessary coding details.\n- **Supported embeds**: only valid `<Asset>`, `<Compare>` and `<Storyboard>` markers alone in a\n paragraph embed content; other HTML or JSX is not allowed. Inline or fenced examples remain text.\n- **Compare**: to show two images or two videos against each other, such as a source and its\n clone, write `<Compare a="<src>" b="<src>" label="..." a-label="..." b-label="..." />`;\n never render a side-by-side file for it. For videos, add `shots="<src>"`: a JSON list of\n `{ "id": "S1", "start": 0, "end": 2.25, "name": "..." }` covering video A in order, or a\n storyboard of A. B may be a storyboard instead of a video; it then shows its shot with the\n id A is on, beside that shot of a storyboard `shots`.\n- **Asset label and alt**: `label` is a tag of a few words; an image\'s caption goes in its\n optional `alt="..."`, its shipped alt text, which cannot contain `<`, `>` or its own quote mark.\n- **Asset variants**: versions of the same content, such as languages or devices, are one tag\n with `variants=\'{"<label>": "<src>", ...}\'`, `src` being the one shown first; alternatives the\n user picks between stay separate tags.\n- **Say each fact once**: a section holds only what the card does not already say; leave it\n empty rather than restate the opening paragraph or another section. Cut only the repeated\n part of a line: a name, value or case stated nowhere else stays.\n\n- **Validate after edits**: run `akb raw validate <id>` for every card you wrote. Fix each\n reported file and line before finishing; validation does not rewrite the card for you.\n';
|
|
17459
17473
|
}
|
|
17460
17474
|
});
|
|
17461
17475
|
|
|
@@ -17633,7 +17647,6 @@ function contractRepairPrompt(req, errors) {
|
|
|
17633
17647
|
"The task ended with invalid card formatting. Fix only the errors below in the listed files. Preserve planned behavior and completed work; do not repeat the task or request other agents.",
|
|
17634
17648
|
errors,
|
|
17635
17649
|
`Run \`${command} raw validate <id>\` for every affected card and fix every error before finishing.`,
|
|
17636
|
-
languageNote(),
|
|
17637
17650
|
ruleBlock(req, frozenRules(req))
|
|
17638
17651
|
].filter(Boolean).join("\n\n"));
|
|
17639
17652
|
}
|
|
@@ -17647,7 +17660,7 @@ function buildAsk(rawReq, notes = []) {
|
|
|
17647
17660
|
const req = withWorkflow(rawReq);
|
|
17648
17661
|
const command = DELIVERY_FLOWS.has(req.action) ? boardCommandFor(req.id) : boardCommand();
|
|
17649
17662
|
const ask2 = [actionPrompt(req, command, notes), pictureNote(req), commandNote(command)].filter(Boolean).join(" ");
|
|
17650
|
-
return realPaths(boardText([ask2,
|
|
17663
|
+
return realPaths(boardText([ask2, roster(req)].filter(Boolean).join("\n\n")), req);
|
|
17651
17664
|
}
|
|
17652
17665
|
function cardFileOf(id) {
|
|
17653
17666
|
const found2 = id === void 0 ? null : locate(id);
|
|
@@ -17733,10 +17746,10 @@ function settingsBlock(req) {
|
|
|
17733
17746
|
return role ? roleSettingsBlock(role) : "";
|
|
17734
17747
|
}
|
|
17735
17748
|
function pictureNote(req) {
|
|
17736
|
-
const
|
|
17737
|
-
if (!
|
|
17738
|
-
const one =
|
|
17739
|
-
return `${one ? "A picture came" : `${
|
|
17749
|
+
const files2 = req.pictures ?? [];
|
|
17750
|
+
if (!files2.length || agentImages(agentForRun(req), req.runtime)?.as !== "message") return "";
|
|
17751
|
+
const one = files2.length === 1;
|
|
17752
|
+
return `${one ? "A picture came" : `${files2.length} pictures came`} with this. Read ${one ? "it" : "them"} first: ${files2.join(", ")}.`;
|
|
17740
17753
|
}
|
|
17741
17754
|
function frozenRules(req) {
|
|
17742
17755
|
return deliveryFor(req)?.rules;
|
|
@@ -17751,8 +17764,8 @@ function shippedLines(cardId2) {
|
|
|
17751
17764
|
const commit = delivery?.landing?.commit;
|
|
17752
17765
|
if (commit) return [`\`git show ${commit.slice(0, 12)}\` \u2014 the change that landed`];
|
|
17753
17766
|
if (delivery?.commitMode === "files") {
|
|
17754
|
-
const
|
|
17755
|
-
if (
|
|
17767
|
+
const files2 = recordedOutputs(delivery);
|
|
17768
|
+
if (files2.length) return ["the files it delivered:", ...files2.map((file) => ` ${file}`)];
|
|
17756
17769
|
}
|
|
17757
17770
|
return [`nothing on record \u2014 \`git log --grep "(#${cardId2})"\`, or check the card against the project as it stands`];
|
|
17758
17771
|
}
|
|
@@ -18077,7 +18090,6 @@ var init_prompts = __esm({
|
|
|
18077
18090
|
init_outputs();
|
|
18078
18091
|
init_archive();
|
|
18079
18092
|
init_flows();
|
|
18080
|
-
init_language();
|
|
18081
18093
|
init_resolve();
|
|
18082
18094
|
init_runner();
|
|
18083
18095
|
init_workflows();
|
|
@@ -18109,7 +18121,7 @@ var init_prompts = __esm({
|
|
|
18109
18121
|
`The session you were continuing is gone, and this one holds nothing of it.`,
|
|
18110
18122
|
`Do the task below from the top, checking each step's precondition before you do it \u2014 work an earlier run already finished is done, so don't repeat it.`
|
|
18111
18123
|
].join(" ");
|
|
18112
|
-
restartable = (
|
|
18124
|
+
restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
|
|
18113
18125
|
NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
|
|
18114
18126
|
BOARD_FOLDER = "<board folder>";
|
|
18115
18127
|
CARD_FILE = "<card file>";
|
|
@@ -18367,9 +18379,9 @@ function reap(runs, reaped = [], restore = []) {
|
|
|
18367
18379
|
}
|
|
18368
18380
|
return changed;
|
|
18369
18381
|
}
|
|
18370
|
-
function stampDuration(
|
|
18382
|
+
function stampDuration(run2, endedAt) {
|
|
18371
18383
|
try {
|
|
18372
|
-
fs46.appendFileSync(
|
|
18384
|
+
fs46.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
|
|
18373
18385
|
} catch {
|
|
18374
18386
|
}
|
|
18375
18387
|
}
|
|
@@ -18390,19 +18402,19 @@ function cardNow(cardId2) {
|
|
|
18390
18402
|
return null;
|
|
18391
18403
|
}
|
|
18392
18404
|
}
|
|
18393
|
-
function claimCard(
|
|
18394
|
-
const wanted =
|
|
18395
|
-
if (
|
|
18396
|
-
const delivery =
|
|
18397
|
-
|
|
18398
|
-
return { cardId:
|
|
18405
|
+
function claimCard(run2) {
|
|
18406
|
+
const wanted = run2.cardId !== null ? RUN_STATUS[run2.action] : void 0;
|
|
18407
|
+
if (run2.cardId === null || !wanted) return void 0;
|
|
18408
|
+
const delivery = run2.deliveryId ? activeDelivery(run2.cardId) : void 0;
|
|
18409
|
+
run2.priorStatus = (delivery && delivery.deliveryId === run2.deliveryId ? delivery.priorStatus : void 0) ?? cardNow(run2.cardId)?.status ?? "todo";
|
|
18410
|
+
return { cardId: run2.cardId, status: wanted };
|
|
18399
18411
|
}
|
|
18400
|
-
async function restoreCardStatus(
|
|
18401
|
-
if (
|
|
18402
|
-
if (
|
|
18403
|
-
const card = cardNow(
|
|
18412
|
+
async function restoreCardStatus(run2) {
|
|
18413
|
+
if (run2.cardId === null || !RUN_STATUS[run2.action]) return;
|
|
18414
|
+
if (run2.deliveryId && activeDelivery(run2.cardId)?.deliveryId === run2.deliveryId) return;
|
|
18415
|
+
const card = cardNow(run2.cardId);
|
|
18404
18416
|
if (!card) return;
|
|
18405
|
-
await setCardStatus(
|
|
18417
|
+
await setCardStatus(run2.cardId, card.questions > 0 ? "todo" : run2.priorStatus ?? "todo");
|
|
18406
18418
|
}
|
|
18407
18419
|
async function releaseCard(delivery) {
|
|
18408
18420
|
if (delivery.cardId === null) return;
|
|
@@ -18410,46 +18422,46 @@ async function releaseCard(delivery) {
|
|
|
18410
18422
|
if (card?.status !== "implementing") return;
|
|
18411
18423
|
await setCardStatus(delivery.cardId, card.questions > 0 ? "todo" : delivery.priorStatus ?? "ready");
|
|
18412
18424
|
}
|
|
18413
|
-
function recordPrune(
|
|
18414
|
-
if (
|
|
18425
|
+
function recordPrune(run2) {
|
|
18426
|
+
if (run2.action !== "prune-memory" || run2.status !== "done") return;
|
|
18415
18427
|
try {
|
|
18416
18428
|
stampMemoryPrune();
|
|
18417
18429
|
} catch {
|
|
18418
18430
|
}
|
|
18419
18431
|
}
|
|
18420
|
-
function recordMemoryReview(
|
|
18421
|
-
if (
|
|
18432
|
+
function recordMemoryReview(run2) {
|
|
18433
|
+
if (run2.action !== "review-memory" || run2.status !== "done") return;
|
|
18422
18434
|
try {
|
|
18423
|
-
stampMemoryReview(new Date(
|
|
18435
|
+
stampMemoryReview(new Date(run2.startedAt));
|
|
18424
18436
|
} catch {
|
|
18425
18437
|
}
|
|
18426
18438
|
}
|
|
18427
|
-
function recordReflection(
|
|
18428
|
-
if (
|
|
18439
|
+
function recordReflection(run2) {
|
|
18440
|
+
if (run2.action !== "reflect" || run2.status !== "done" || !run2.cards?.length) return;
|
|
18429
18441
|
try {
|
|
18430
|
-
dropReflected(
|
|
18442
|
+
dropReflected(run2.cards);
|
|
18431
18443
|
} catch {
|
|
18432
18444
|
}
|
|
18433
18445
|
}
|
|
18434
|
-
function recordDismissalReview(
|
|
18435
|
-
if (
|
|
18446
|
+
function recordDismissalReview(run2) {
|
|
18447
|
+
if (run2.action !== "review-dismissals" || run2.status !== "done") return;
|
|
18436
18448
|
try {
|
|
18437
|
-
stampDismissalReview(new Date(
|
|
18449
|
+
stampDismissalReview(new Date(run2.startedAt));
|
|
18438
18450
|
} catch {
|
|
18439
18451
|
}
|
|
18440
18452
|
}
|
|
18441
|
-
function recordScheduledRun(
|
|
18442
|
-
if (
|
|
18453
|
+
function recordScheduledRun(run2) {
|
|
18454
|
+
if (run2.action !== "scheduled" || run2.status !== "done" || !run2.workflow || !run2.specAgent) return;
|
|
18443
18455
|
try {
|
|
18444
|
-
const began = (
|
|
18445
|
-
stampScheduledRun(
|
|
18456
|
+
const began = (run2.deliveryId ? findDelivery(run2.deliveryId)?.startedAt : void 0) ?? run2.startedAt;
|
|
18457
|
+
stampScheduledRun(run2.workflow, run2.specAgent, new Date(began));
|
|
18446
18458
|
} catch {
|
|
18447
18459
|
}
|
|
18448
18460
|
}
|
|
18449
|
-
function recordProjectDescription(
|
|
18450
|
-
if (
|
|
18461
|
+
function recordProjectDescription(run2) {
|
|
18462
|
+
if (run2.action !== "describe-project" || run2.status !== "done") return;
|
|
18451
18463
|
try {
|
|
18452
|
-
stampProjectDescription(new Date(
|
|
18464
|
+
stampProjectDescription(new Date(run2.startedAt));
|
|
18453
18465
|
} catch {
|
|
18454
18466
|
}
|
|
18455
18467
|
}
|
|
@@ -18457,8 +18469,8 @@ function resumeIdOf(r) {
|
|
|
18457
18469
|
if (adoptsSessionId(r.harness) && !carriesSession(r)) return r.sessionId;
|
|
18458
18470
|
return r.resumeId;
|
|
18459
18471
|
}
|
|
18460
|
-
function resumeSessionId(
|
|
18461
|
-
return resumesUnder(
|
|
18472
|
+
function resumeSessionId(run2) {
|
|
18473
|
+
return resumesUnder(run2.harness) && !neverStarted(run2) ? resumeIdOf(run2) : void 0;
|
|
18462
18474
|
}
|
|
18463
18475
|
function endedDeliveryRefusal(r, refusals) {
|
|
18464
18476
|
if (!r.deliveryId || !canPickUp(r)) return void 0;
|
|
@@ -18532,7 +18544,7 @@ function leftBoardOnLanding(id) {
|
|
|
18532
18544
|
}
|
|
18533
18545
|
function goneCards(runs) {
|
|
18534
18546
|
const asking = /* @__PURE__ */ new Set();
|
|
18535
|
-
for (const
|
|
18547
|
+
for (const run2 of runs) if (run2.cardId !== null && canPickUp(run2)) asking.add(run2.cardId);
|
|
18536
18548
|
if (asking.size === 0) return asking;
|
|
18537
18549
|
const onBoard3 = boardCardIds();
|
|
18538
18550
|
const gone = /* @__PURE__ */ new Set();
|
|
@@ -18542,12 +18554,12 @@ function goneCards(runs) {
|
|
|
18542
18554
|
async function listRuns() {
|
|
18543
18555
|
const reaped = [];
|
|
18544
18556
|
const restore = [];
|
|
18545
|
-
const runs = withRuns((
|
|
18546
|
-
reap(
|
|
18547
|
-
return
|
|
18557
|
+
const runs = withRuns((all2) => {
|
|
18558
|
+
reap(all2, reaped, restore);
|
|
18559
|
+
return all2.map((r) => ({ ...r }));
|
|
18548
18560
|
});
|
|
18549
|
-
for (const
|
|
18550
|
-
for (const
|
|
18561
|
+
for (const run2 of restore) await restoreCardStatus(run2);
|
|
18562
|
+
for (const run2 of reaped) await settleDelivery(run2);
|
|
18551
18563
|
for (const sub of runs) {
|
|
18552
18564
|
if (sub.action !== "sub" || sub.status !== "running" || !sub.parentId) continue;
|
|
18553
18565
|
if (!runs.some((r) => r.sessionId === sub.parentId && r.status === "running")) await stopRun(sub.sessionId);
|
|
@@ -18584,17 +18596,17 @@ function findRun(runs, id) {
|
|
|
18584
18596
|
if (hits.length === 1) return hits[0];
|
|
18585
18597
|
return hits.length > 1 ? null : void 0;
|
|
18586
18598
|
}
|
|
18587
|
-
async function getRun(id,
|
|
18599
|
+
async function getRun(id, bytes3) {
|
|
18588
18600
|
const restore = [];
|
|
18589
|
-
const runs = withRuns((
|
|
18590
|
-
reap(
|
|
18591
|
-
return
|
|
18601
|
+
const runs = withRuns((all2) => {
|
|
18602
|
+
reap(all2, [], restore);
|
|
18603
|
+
return all2.map((r) => ({ ...r }));
|
|
18592
18604
|
});
|
|
18593
|
-
for (const
|
|
18605
|
+
for (const run2 of restore) await restoreCardStatus(run2);
|
|
18594
18606
|
const found2 = findRun(runs, id);
|
|
18595
18607
|
if (!found2) return null;
|
|
18596
18608
|
const view = toView(found2, goneCards([found2]));
|
|
18597
|
-
const raw = readLogTail(found2.logPath,
|
|
18609
|
+
const raw = readLogTail(found2.logPath, bytes3) ?? "";
|
|
18598
18610
|
const { tail: tail2, result, durationMs, costUsd, model, usage: usage3 } = splitLog(raw);
|
|
18599
18611
|
view.tail = tail2;
|
|
18600
18612
|
if (result && found2.status !== "running" && !view.result) view.result = result;
|
|
@@ -18689,8 +18701,8 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
|
|
|
18689
18701
|
const cardless = !!direct;
|
|
18690
18702
|
let start;
|
|
18691
18703
|
if (pass || req.action === "implement" && (cardless || cardId2 !== null && !activeDelivery(cardId2))) {
|
|
18692
|
-
const
|
|
18693
|
-
const prepared = prepareDelivery(cardId2, req.commitMode,
|
|
18704
|
+
const files2 = direct?.scheduled ? !!workflowById(direct.scheduled.workflow)?.needsArtifact : false;
|
|
18705
|
+
const prepared = prepareDelivery(cardId2, req.commitMode, files2);
|
|
18694
18706
|
if ("error" in prepared) return prepared;
|
|
18695
18707
|
start = prepared.start;
|
|
18696
18708
|
}
|
|
@@ -18783,15 +18795,15 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
|
|
|
18783
18795
|
function planningSession(cardId2, cwd = REPO_ROOT) {
|
|
18784
18796
|
const here = (dir) => !dir || path48.resolve(dir) === cwd;
|
|
18785
18797
|
const ended = readRuns().filter((r) => r.status === "done").sort((a, b) => b.startedAt - a.startedAt);
|
|
18786
|
-
const of = (
|
|
18787
|
-
if (!here(
|
|
18788
|
-
const resumeId = resumeSessionId(
|
|
18789
|
-
return resumeId ? { session: { harness:
|
|
18798
|
+
const of = (run3, fork) => {
|
|
18799
|
+
if (!here(run3.cwd)) return { none: "elsewhere" };
|
|
18800
|
+
const resumeId = resumeSessionId(run3);
|
|
18801
|
+
return resumeId ? { session: { harness: run3.harness, resumeId }, fork } : { none: "unrecorded" };
|
|
18790
18802
|
};
|
|
18791
18803
|
const planned = ended.find((r) => r.cardId === cardId2 && PLANNING.has(r.action));
|
|
18792
18804
|
if (planned) return of(planned, false);
|
|
18793
|
-
const
|
|
18794
|
-
const created =
|
|
18805
|
+
const run2 = ended.find((r) => (r.action === "create" || r.action === "setup") && r.createdCardIds?.includes(cardId2));
|
|
18806
|
+
const created = run2 ? of(run2, true) : void 0;
|
|
18795
18807
|
return created ?? { none: "unrecorded" };
|
|
18796
18808
|
}
|
|
18797
18809
|
function carriedSession(req) {
|
|
@@ -18822,8 +18834,8 @@ function carriedSession(req) {
|
|
|
18822
18834
|
};
|
|
18823
18835
|
}
|
|
18824
18836
|
function planningRun(sessionId) {
|
|
18825
|
-
const
|
|
18826
|
-
return
|
|
18837
|
+
const run2 = peekRun(sessionId);
|
|
18838
|
+
return run2?.status === "running" && PLANNING.has(run2.action) ? run2 : void 0;
|
|
18827
18839
|
}
|
|
18828
18840
|
function requestOf(record) {
|
|
18829
18841
|
const id = record.cardId ?? void 0;
|
|
@@ -18849,11 +18861,11 @@ function requestOf(record) {
|
|
|
18849
18861
|
}
|
|
18850
18862
|
async function openResume(id) {
|
|
18851
18863
|
const restore = [];
|
|
18852
|
-
const runs = withRuns((
|
|
18853
|
-
reap(
|
|
18854
|
-
return
|
|
18864
|
+
const runs = withRuns((all2) => {
|
|
18865
|
+
reap(all2, [], restore);
|
|
18866
|
+
return all2.map((r) => ({ ...r }));
|
|
18855
18867
|
});
|
|
18856
|
-
for (const
|
|
18868
|
+
for (const run2 of restore) await restoreCardStatus(run2);
|
|
18857
18869
|
const prev = findRun(runs, id);
|
|
18858
18870
|
if (prev === null) return ambiguousRun(id);
|
|
18859
18871
|
if (!prev) return unknownRun(id);
|
|
@@ -18952,8 +18964,8 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
|
|
|
18952
18964
|
flowId: prev.flowId
|
|
18953
18965
|
};
|
|
18954
18966
|
const out = withCreationLock(() => withStore((store) => {
|
|
18955
|
-
const
|
|
18956
|
-
const latest =
|
|
18967
|
+
const all2 = store.runs;
|
|
18968
|
+
const latest = all2.find((r) => r.sessionId === prev.sessionId);
|
|
18957
18969
|
if (!latest) return refusal("runContinued", "that run has already been continued");
|
|
18958
18970
|
if (latest.discardedCards?.some((c) => c.pending || locate(c.id))) {
|
|
18959
18971
|
return refusal("discardUnfinished", "A card discard did not finish. Retry discarding it before resuming creation.");
|
|
@@ -18968,11 +18980,11 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
|
|
|
18968
18980
|
repair.errors = repair.errors.split("\n").filter((line2) => !record.discardedCards.some((c) => line2.includes(c.path) || new RegExp(`Task #${c.id}(?![0-9])`).test(line2))).join("\n");
|
|
18969
18981
|
if (!repair.cardIds.length) record.formatRepair = void 0;
|
|
18970
18982
|
}
|
|
18971
|
-
const heldCreation =
|
|
18983
|
+
const heldCreation = all2.find((r) => runIsLive(r) && (r.cardId !== null && (latest.createdCardIds ?? []).includes(r.cardId) || (r.createdCardIds ?? []).some((id) => (latest.createdCardIds ?? []).includes(id))));
|
|
18972
18984
|
if (heldCreation) return refusal("creationHeld", "A card from this creation is already being worked on. Wait for that run to finish.");
|
|
18973
|
-
const locked = lockedBy(
|
|
18985
|
+
const locked = lockedBy(all2, prev.action, prev.cardId, prev.discard);
|
|
18974
18986
|
if (locked) return locked;
|
|
18975
|
-
|
|
18987
|
+
all2.push(record);
|
|
18976
18988
|
const delivery = store.deliveries.find((d) => d.deliveryId === prev.deliveryId && d.status === "active");
|
|
18977
18989
|
if (delivery) {
|
|
18978
18990
|
delivery.sessions.push(record.sessionId);
|
|
@@ -18980,8 +18992,8 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
|
|
|
18980
18992
|
record.deliveryId = delivery.deliveryId;
|
|
18981
18993
|
record.flowId = delivery.deliveryId;
|
|
18982
18994
|
}
|
|
18983
|
-
const at =
|
|
18984
|
-
if (at >= 0)
|
|
18995
|
+
const at = all2.findIndex((r) => r.sessionId === prev.sessionId);
|
|
18996
|
+
if (at >= 0) all2.splice(at, 1);
|
|
18985
18997
|
return { run: record };
|
|
18986
18998
|
}));
|
|
18987
18999
|
if ("error" in out) return out;
|
|
@@ -19020,8 +19032,8 @@ function forgetRun(sessionId) {
|
|
|
19020
19032
|
}
|
|
19021
19033
|
}
|
|
19022
19034
|
function askForSpec(sessionId, ask2) {
|
|
19023
|
-
const
|
|
19024
|
-
if (!
|
|
19035
|
+
const run2 = peekRun(sessionId);
|
|
19036
|
+
if (!run2) return "no-run";
|
|
19025
19037
|
const file = readAsks(sessionId);
|
|
19026
19038
|
if (file.asks.some((a) => a.specAgent === ask2.specAgent && a.cardId === ask2.cardId)) return "already";
|
|
19027
19039
|
file.asks.push(ask2);
|
|
@@ -19090,9 +19102,9 @@ function markSpawned(sessionId, pid) {
|
|
|
19090
19102
|
}
|
|
19091
19103
|
function patch(sessionId, change2) {
|
|
19092
19104
|
return withRuns((runs) => {
|
|
19093
|
-
const
|
|
19094
|
-
if (
|
|
19095
|
-
return
|
|
19105
|
+
const run2 = runs.find((r) => r.sessionId === sessionId);
|
|
19106
|
+
if (run2) change2(run2);
|
|
19107
|
+
return run2 ? { ...run2 } : void 0;
|
|
19096
19108
|
});
|
|
19097
19109
|
}
|
|
19098
19110
|
function peekRun(sessionId) {
|
|
@@ -19100,19 +19112,19 @@ function peekRun(sessionId) {
|
|
|
19100
19112
|
}
|
|
19101
19113
|
async function closeRun(sessionId, res) {
|
|
19102
19114
|
const closed = withRuns((runs) => {
|
|
19103
|
-
const
|
|
19104
|
-
if (!
|
|
19105
|
-
|
|
19106
|
-
if (res.ok !== void 0)
|
|
19107
|
-
|
|
19108
|
-
if (res.error)
|
|
19109
|
-
if (res.note)
|
|
19110
|
-
if (res.errorWhy?.length)
|
|
19111
|
-
if (res.noteWhy?.length)
|
|
19112
|
-
if (res.tickedNothing)
|
|
19113
|
-
|
|
19114
|
-
|
|
19115
|
-
return { ...
|
|
19115
|
+
const run2 = runs.find((r) => r.sessionId === sessionId);
|
|
19116
|
+
if (!run2 || run2.status !== "running") return void 0;
|
|
19117
|
+
run2.status = res.status;
|
|
19118
|
+
if (res.ok !== void 0) run2.ok = res.ok;
|
|
19119
|
+
run2.code = res.code ?? null;
|
|
19120
|
+
if (res.error) run2.error = res.error;
|
|
19121
|
+
if (res.note) run2.note = res.note;
|
|
19122
|
+
if (res.errorWhy?.length) run2.errorWhy = res.errorWhy;
|
|
19123
|
+
if (res.noteWhy?.length) run2.noteWhy = res.noteWhy;
|
|
19124
|
+
if (res.tickedNothing) run2.tickedNothing = true;
|
|
19125
|
+
run2.endedAt = res.endedAt ?? Date.now();
|
|
19126
|
+
run2.pid = void 0;
|
|
19127
|
+
return { ...run2 };
|
|
19116
19128
|
});
|
|
19117
19129
|
if (!closed) return;
|
|
19118
19130
|
if (closed.status === "done" || closed.status === "error") {
|
|
@@ -19138,14 +19150,14 @@ async function stopRun(id) {
|
|
|
19138
19150
|
const restore = [];
|
|
19139
19151
|
const out = withRuns((runs) => {
|
|
19140
19152
|
reap(runs, [], restore);
|
|
19141
|
-
const
|
|
19142
|
-
if (
|
|
19143
|
-
if (!
|
|
19144
|
-
if (
|
|
19145
|
-
|
|
19146
|
-
return { ok: true, sessionId:
|
|
19153
|
+
const run2 = findRun(runs, id);
|
|
19154
|
+
if (run2 === null) return { ok: false, ...ambiguousRun(id) };
|
|
19155
|
+
if (!run2) return { ok: false, ...unknownRun(id) };
|
|
19156
|
+
if (run2.status !== "running") return { ok: true, sessionId: run2.sessionId };
|
|
19157
|
+
run2.stopping = true;
|
|
19158
|
+
return { ok: true, sessionId: run2.sessionId, pid: run2.pid, live: true };
|
|
19147
19159
|
});
|
|
19148
|
-
for (const
|
|
19160
|
+
for (const run2 of restore) await restoreCardStatus(run2);
|
|
19149
19161
|
const live = out;
|
|
19150
19162
|
if (!live.ok || !live.live) {
|
|
19151
19163
|
return { ok: live.ok, sessionId: live.sessionId, error: live.error, reason: live.reason, args: live.args };
|
|
@@ -19179,13 +19191,13 @@ async function cancelDelivery(id) {
|
|
|
19179
19191
|
}
|
|
19180
19192
|
async function stopSubRuns(parentId) {
|
|
19181
19193
|
const live = liveSubRuns(parentId);
|
|
19182
|
-
for (const
|
|
19183
|
-
await Promise.all(live.map((
|
|
19194
|
+
for (const run2 of live) await stopRun(run2.sessionId);
|
|
19195
|
+
await Promise.all(live.map((run2) => runIsDown(run2.sessionId)));
|
|
19184
19196
|
}
|
|
19185
19197
|
async function runIsDown(sessionId) {
|
|
19186
19198
|
for (const until = Date.now() + STOP_WAIT_MS; Date.now() < until; ) {
|
|
19187
|
-
const
|
|
19188
|
-
if (!
|
|
19199
|
+
const run2 = readRuns().find((r) => r.sessionId === sessionId);
|
|
19200
|
+
if (!run2 || !runIsLive(run2)) return;
|
|
19189
19201
|
await sleep2(STOP_POLL_MS);
|
|
19190
19202
|
}
|
|
19191
19203
|
}
|
|
@@ -19529,13 +19541,13 @@ import path49 from "node:path";
|
|
|
19529
19541
|
function cardsOnWorkflow(id) {
|
|
19530
19542
|
const wanted = id || DEFAULT_WORKFLOW;
|
|
19531
19543
|
const ids = [];
|
|
19532
|
-
let
|
|
19544
|
+
let files2;
|
|
19533
19545
|
try {
|
|
19534
|
-
|
|
19546
|
+
files2 = walkMd(TODO);
|
|
19535
19547
|
} catch {
|
|
19536
19548
|
return [];
|
|
19537
19549
|
}
|
|
19538
|
-
for (const file of
|
|
19550
|
+
for (const file of files2) {
|
|
19539
19551
|
const base = path49.basename(file);
|
|
19540
19552
|
const card = base === "root.md" ? idPrefix(path49.basename(path49.dirname(file))) : idPrefix(base);
|
|
19541
19553
|
if (card === null) continue;
|
|
@@ -20116,8 +20128,8 @@ function settleDelivery2(id, skipped, unskipped) {
|
|
|
20116
20128
|
function specRunAgent() {
|
|
20117
20129
|
const id = insideRun();
|
|
20118
20130
|
if (!id) return void 0;
|
|
20119
|
-
const
|
|
20120
|
-
return
|
|
20131
|
+
const run2 = readStore().runs.find((r) => r.sessionId === id);
|
|
20132
|
+
return run2?.action === "spec" ? run2.specAgent : void 0;
|
|
20121
20133
|
}
|
|
20122
20134
|
function cmdTag(id, positions, tagRaw) {
|
|
20123
20135
|
const ns = positions.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map(Number);
|
|
@@ -20442,16 +20454,16 @@ function addToInbox(drop) {
|
|
|
20442
20454
|
if (file && file.data.length > MAX_BYTES) {
|
|
20443
20455
|
return { ok: false, error: `${path55.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
|
|
20444
20456
|
}
|
|
20445
|
-
const
|
|
20446
|
-
const keep =
|
|
20457
|
+
const read6 = describe(typed, file);
|
|
20458
|
+
const keep = read6.keep;
|
|
20447
20459
|
const said3 = drop.source?.trim() ?? "";
|
|
20448
20460
|
const hit = said3 ? matchSourceType(said3) : "";
|
|
20449
20461
|
const kept = said3 && !hit ? metaPair("source", said3) : null;
|
|
20450
20462
|
const incoming = {
|
|
20451
|
-
...
|
|
20463
|
+
...read6.incoming,
|
|
20452
20464
|
...drop.title?.trim() ? { title: oneLine(drop.title) } : {},
|
|
20453
20465
|
...hit ? { sourceType: hit } : {},
|
|
20454
|
-
...kept ? { meta: [kept, ...
|
|
20466
|
+
...kept ? { meta: [kept, ...read6.incoming.meta] } : {},
|
|
20455
20467
|
...drop.agent ? { agent: drop.agent } : {},
|
|
20456
20468
|
...drop.workflow ? { workflow: drop.workflow } : {}
|
|
20457
20469
|
};
|
|
@@ -21080,21 +21092,21 @@ function scheduledNext(workflow, one) {
|
|
|
21080
21092
|
}
|
|
21081
21093
|
function insideScheduledPass() {
|
|
21082
21094
|
const sessionId = insideRun();
|
|
21083
|
-
const
|
|
21084
|
-
return
|
|
21095
|
+
const run2 = sessionId ? readStore().runs.find((r) => r.sessionId === sessionId) : void 0;
|
|
21096
|
+
return run2?.action === "scheduled" && run2.workflow && run2.specAgent ? { workflow: run2.workflow, agent: run2.specAgent } : null;
|
|
21085
21097
|
}
|
|
21086
21098
|
function runAgent() {
|
|
21087
21099
|
const sessionId = insideRun();
|
|
21088
21100
|
const runs = sessionId ? readStore().runs : [];
|
|
21089
|
-
const
|
|
21090
|
-
if (!
|
|
21091
|
-
if (
|
|
21092
|
-
const parent = runs.find((r) => r.sessionId ===
|
|
21093
|
-
return parent ? agentOf(parent) :
|
|
21101
|
+
const run2 = runs.find((r) => r.sessionId === sessionId);
|
|
21102
|
+
if (!run2) return null;
|
|
21103
|
+
if (run2.action !== "sub") return agentOf(run2);
|
|
21104
|
+
const parent = runs.find((r) => r.sessionId === run2.parentId);
|
|
21105
|
+
return parent ? agentOf(parent) : run2.specAgent ?? null;
|
|
21094
21106
|
}
|
|
21095
|
-
function agentOf(
|
|
21096
|
-
if (
|
|
21097
|
-
return flowNodes().find((n) => n.flow ===
|
|
21107
|
+
function agentOf(run2) {
|
|
21108
|
+
if (run2.action === "scheduled") return run2.specAgent ?? null;
|
|
21109
|
+
return flowNodes().find((n) => n.flow === run2.action)?.agent ?? null;
|
|
21098
21110
|
}
|
|
21099
21111
|
var scheduledRequest, isPass, openPasses, stalePasses;
|
|
21100
21112
|
var init_scheduled = __esm({
|
|
@@ -21112,7 +21124,7 @@ var init_scheduled = __esm({
|
|
|
21112
21124
|
workflow: pass.workflow,
|
|
21113
21125
|
specAgent: pass.agent
|
|
21114
21126
|
});
|
|
21115
|
-
isPass = (
|
|
21127
|
+
isPass = (run2, pass) => run2.action === "scheduled" && run2.workflow === pass.workflow && run2.specAgent === pass.agent;
|
|
21116
21128
|
openPasses = (deliveries, pass) => deliveries.filter(
|
|
21117
21129
|
(d) => d.status === "active" && d.scheduled?.workflow === pass.workflow && d.scheduled.agent === pass.agent
|
|
21118
21130
|
);
|
|
@@ -21263,14 +21275,14 @@ function openRows() {
|
|
|
21263
21275
|
}
|
|
21264
21276
|
return rows.sort((a, b) => a.id - b.id);
|
|
21265
21277
|
}
|
|
21266
|
-
function cmdStale(rows,
|
|
21278
|
+
function cmdStale(rows, all2, scope, mod) {
|
|
21267
21279
|
const days = staleAfter();
|
|
21268
21280
|
const ages = cardAges();
|
|
21269
21281
|
if (ages === null) {
|
|
21270
21282
|
say(`not a git repository \u2014 a card's age is the date git last saw its file, so there is nothing to list.`);
|
|
21271
21283
|
return { cards: [], module: mod, staleAfter: days };
|
|
21272
21284
|
}
|
|
21273
|
-
const open3 = new Set(
|
|
21285
|
+
const open3 = new Set(all2.map((r) => r.id));
|
|
21274
21286
|
const stale3 = rows.filter(canGoStale).map((r) => ({ row: r, age: ages.get(r.file) })).filter((r) => r.age !== void 0 && r.age.days >= days).sort((a, b) => b.age.days - a.age.days || a.row.id - b.row.id);
|
|
21275
21287
|
const cards = stale3.map(({ row, age }) => ({ ...row, file: rel(row.file), ...age }));
|
|
21276
21288
|
if (!stale3.length) {
|
|
@@ -21288,18 +21300,18 @@ function cmdStale(rows, all, scope, mod) {
|
|
|
21288
21300
|
return { cards, module: mod, staleAfter: days };
|
|
21289
21301
|
}
|
|
21290
21302
|
function archivedFiles() {
|
|
21291
|
-
const
|
|
21292
|
-
if (!fs59.existsSync(ARCHIVE)) return
|
|
21303
|
+
const files2 = /* @__PURE__ */ new Map();
|
|
21304
|
+
if (!fs59.existsSync(ARCHIVE)) return files2;
|
|
21293
21305
|
for (const file of walkMd(ARCHIVE)) {
|
|
21294
21306
|
const id = idPrefix(path58.basename(file));
|
|
21295
|
-
if (id !== null)
|
|
21307
|
+
if (id !== null) files2.set(id, file);
|
|
21296
21308
|
}
|
|
21297
21309
|
for (const dir of walkDirs(ARCHIVE)) {
|
|
21298
21310
|
const id = idPrefix(path58.basename(dir));
|
|
21299
21311
|
const root = path58.join(dir, "root.md");
|
|
21300
|
-
if (id !== null && fs59.existsSync(root))
|
|
21312
|
+
if (id !== null && fs59.existsSync(root)) files2.set(id, root);
|
|
21301
21313
|
}
|
|
21302
|
-
return
|
|
21314
|
+
return files2;
|
|
21303
21315
|
}
|
|
21304
21316
|
function cmdArchived(opts) {
|
|
21305
21317
|
let since = null;
|
|
@@ -21319,14 +21331,14 @@ function cmdArchived(opts) {
|
|
|
21319
21331
|
}
|
|
21320
21332
|
const workflow = opts.workflow === void 0 ? null : knownWorkflow(opts.workflow);
|
|
21321
21333
|
const open3 = boardCardIds();
|
|
21322
|
-
const
|
|
21334
|
+
const files2 = archivedFiles();
|
|
21323
21335
|
const cards = landedDeliveries({ since: since?.getTime(), workflow: workflow ?? void 0 }).filter((d) => !open3.has(d.cardId)).map((d) => ({
|
|
21324
21336
|
id: d.cardId,
|
|
21325
21337
|
title: d.title,
|
|
21326
21338
|
workflow: d.workflow,
|
|
21327
21339
|
landedAt: formatStamp(new Date(d.at)),
|
|
21328
21340
|
commit: d.commit,
|
|
21329
|
-
file:
|
|
21341
|
+
file: files2.has(d.cardId) ? rel(files2.get(d.cardId)) : ""
|
|
21330
21342
|
}));
|
|
21331
21343
|
const result = { cards, since: since ? formatStamp(since) : null, workflow };
|
|
21332
21344
|
const scope = [workflow ? `on workflow \`${workflow}\`` : "", since ? `since ${formatStamp(since)}` : ""].filter(Boolean).join(" ");
|
|
@@ -21350,8 +21362,8 @@ function cmdList(opts) {
|
|
|
21350
21362
|
}
|
|
21351
21363
|
if (opts.since !== void 0) die("--since only applies to --archived.");
|
|
21352
21364
|
if (opts.workflow !== void 0) die("--workflow only applies to --archived.");
|
|
21353
|
-
const
|
|
21354
|
-
let rows =
|
|
21365
|
+
const all2 = openRows();
|
|
21366
|
+
let rows = all2;
|
|
21355
21367
|
let scope = "on the board";
|
|
21356
21368
|
const mod = opts.module;
|
|
21357
21369
|
if (mod !== void 0) {
|
|
@@ -21363,7 +21375,7 @@ function cmdList(opts) {
|
|
|
21363
21375
|
rows = rows.filter((r) => r.modules.includes(mod));
|
|
21364
21376
|
scope = `tagged \`${mod}\``;
|
|
21365
21377
|
}
|
|
21366
|
-
if (opts.stale) return cmdStale(rows,
|
|
21378
|
+
if (opts.stale) return cmdStale(rows, all2, scope, mod === void 0 ? null : mod);
|
|
21367
21379
|
const cards = rows.map((r) => ({ ...r, file: rel(r.file) }));
|
|
21368
21380
|
if (!rows.length) {
|
|
21369
21381
|
say(`no open cards ${scope}.`);
|
|
@@ -21443,10 +21455,10 @@ function stripOldHeader(text5) {
|
|
|
21443
21455
|
}
|
|
21444
21456
|
function cmdMigrate(opts) {
|
|
21445
21457
|
const dry = opts.dryRun === true;
|
|
21446
|
-
const
|
|
21458
|
+
const files2 = walkMd(TODO).filter((f) => path59.basename(f) !== "README.md");
|
|
21447
21459
|
let changed = 0;
|
|
21448
21460
|
let skipped = 0;
|
|
21449
|
-
for (const file of
|
|
21461
|
+
for (const file of files2) {
|
|
21450
21462
|
const text5 = fs60.readFileSync(file, "utf8");
|
|
21451
21463
|
if (text5.trimStart().startsWith("---")) {
|
|
21452
21464
|
skipped++;
|
|
@@ -21646,16 +21658,16 @@ function removeCard2(id, metric, options) {
|
|
|
21646
21658
|
}
|
|
21647
21659
|
}
|
|
21648
21660
|
withStore((store) => {
|
|
21649
|
-
for (const
|
|
21650
|
-
if (!leaving.some((c) =>
|
|
21651
|
-
const marks = new Map((
|
|
21661
|
+
for (const run2 of store.runs) {
|
|
21662
|
+
if (!leaving.some((c) => run2.createdCardIds?.includes(c.id))) continue;
|
|
21663
|
+
const marks = new Map((run2.discardedCards ?? []).map((c) => [c.id, c]));
|
|
21652
21664
|
for (const c of leaving) marks.set(c.id, { id: c.id, path: rel(c.file), pending: true });
|
|
21653
|
-
|
|
21665
|
+
run2.discardedCards = [...marks.values()];
|
|
21654
21666
|
}
|
|
21655
21667
|
});
|
|
21656
21668
|
}
|
|
21657
21669
|
if (options.discard) withStore((store) => {
|
|
21658
|
-
for (const
|
|
21670
|
+
for (const run2 of store.runs) for (const card of run2.discardedCards ?? []) {
|
|
21659
21671
|
if (leftIds.includes(card.id)) delete card.pending;
|
|
21660
21672
|
}
|
|
21661
21673
|
});
|
|
@@ -21856,10 +21868,10 @@ var init_agent_file = __esm({
|
|
|
21856
21868
|
// ../cli/src/commands/memory-file.ts
|
|
21857
21869
|
function cmdMemoryFile(askedName) {
|
|
21858
21870
|
const name = askedName.trim();
|
|
21859
|
-
const
|
|
21860
|
-
const memory =
|
|
21871
|
+
const all2 = globalMemories();
|
|
21872
|
+
const memory = all2.find((m) => m.name === name);
|
|
21861
21873
|
if (!memory) {
|
|
21862
|
-
const has =
|
|
21874
|
+
const has = all2.length ? `It has: ${all2.map((m) => m.name).join(", ")}.` : "It has none.";
|
|
21863
21875
|
die(`"${name}" is not a global memory on this board. ${has}`, { kind: "no-such-memory", memory: name });
|
|
21864
21876
|
}
|
|
21865
21877
|
say(memory.text);
|
|
@@ -22295,12 +22307,12 @@ function variantsProblem(raw, src, id) {
|
|
|
22295
22307
|
return `variants is not valid JSON. ${shape}`;
|
|
22296
22308
|
}
|
|
22297
22309
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return `variants is not a JSON object. ${shape}`;
|
|
22298
|
-
const
|
|
22299
|
-
if (
|
|
22300
|
-
if (!
|
|
22310
|
+
const files2 = Object.values(parsed);
|
|
22311
|
+
if (files2.length < 2 || !files2.every((f) => typeof f === "string" && f)) return `variants needs at least two labels, each naming a file. ${shape}`;
|
|
22312
|
+
if (!files2.includes(src)) return `src="${src}" is not one of the variants. Set src to the version shown first.`;
|
|
22301
22313
|
if (id === void 0) return null;
|
|
22302
22314
|
const dir = path62.join(ASSETS, String(id));
|
|
22303
|
-
for (const f of
|
|
22315
|
+
for (const f of files2) {
|
|
22304
22316
|
const parts = f.split("/");
|
|
22305
22317
|
if (parts.length !== 3 || parts[0] !== ".assets" || parts[1] !== String(id) || !inside(dir, parts[2])) {
|
|
22306
22318
|
return `Variant ${f} names no file in this card's asset folder .assets/${id}/. Write the file there or fix the path.`;
|
|
@@ -22323,7 +22335,7 @@ function compareProblem(attrs, id) {
|
|
|
22323
22335
|
if (!script && kind !== compareKind(attrs.b)) return src("Compare a and b must both be images or both be videos.");
|
|
22324
22336
|
if (attrs.shots === void 0) return script ? shots('shots is required when b is a storyboard. Add shots="..." naming a storyboard or shots list of video a.') : null;
|
|
22325
22337
|
if (kind !== "video") return shots("shots is for videos only. Remove it from this Compare tag.");
|
|
22326
|
-
const
|
|
22338
|
+
const read6 = (at) => {
|
|
22327
22339
|
const named2 = assetName(at, id, ["json"]);
|
|
22328
22340
|
if (!("name" in named2)) return { message: `Compare shots is ${named2.actual}; expected ${named2.expected}.` };
|
|
22329
22341
|
const file = inside(dir, named2.name);
|
|
@@ -22334,12 +22346,12 @@ function compareProblem(attrs, id) {
|
|
|
22334
22346
|
return { message: `${at} is not valid JSON.` };
|
|
22335
22347
|
}
|
|
22336
22348
|
};
|
|
22337
|
-
const list =
|
|
22349
|
+
const list = read6(attrs.shots);
|
|
22338
22350
|
if ("message" in list) return shots(list.message);
|
|
22339
22351
|
const why = Array.isArray(list.data) ? shotsProblem(list.data) : storyboardProblem(list.data);
|
|
22340
22352
|
if (why) return shots(`${attrs.shots}: ${why}`);
|
|
22341
22353
|
if (!script) return null;
|
|
22342
|
-
const b =
|
|
22354
|
+
const b = read6(attrs.b);
|
|
22343
22355
|
if ("message" in b) return src(b.message);
|
|
22344
22356
|
const bad = storyboardProblem(b.data);
|
|
22345
22357
|
if (bad) return src(`${attrs.b}: ${bad}`);
|
|
@@ -22586,11 +22598,11 @@ function newOpId() {
|
|
|
22586
22598
|
opCounter += 1;
|
|
22587
22599
|
return `${process.pid.toString(36)}-${Date.now().toString(36)}-${opCounter.toString(36)}`;
|
|
22588
22600
|
}
|
|
22589
|
-
async function leaseAnd(provider, target,
|
|
22601
|
+
async function leaseAnd(provider, target, run2) {
|
|
22590
22602
|
const got = await provider.lease(target);
|
|
22591
22603
|
if (!got.ok) return { ok: false, kind: "refused", error: got.error };
|
|
22592
22604
|
try {
|
|
22593
|
-
return await
|
|
22605
|
+
return await run2(envelope(got.lease.revision, got.lease.id));
|
|
22594
22606
|
} finally {
|
|
22595
22607
|
await provider.releaseLease(got.lease.id);
|
|
22596
22608
|
}
|
|
@@ -22732,14 +22744,14 @@ function localBoard() {
|
|
|
22732
22744
|
},
|
|
22733
22745
|
// ---- the named `akb raw` moves ----------------------------------------
|
|
22734
22746
|
async runMove(move2, input, env) {
|
|
22735
|
-
const
|
|
22736
|
-
if (!
|
|
22747
|
+
const run2 = MOVES[move2];
|
|
22748
|
+
if (!run2) return Promise.resolve(opRefused(new Error(`unknown command "${move2}".`)));
|
|
22737
22749
|
const target = moveTarget(move2, input.args);
|
|
22738
22750
|
try {
|
|
22739
22751
|
return await withBoardLock(() => {
|
|
22740
22752
|
const no = checkWrite(target, env, revisionAt(target));
|
|
22741
22753
|
if (no) return no;
|
|
22742
|
-
const data =
|
|
22754
|
+
const data = run2(input);
|
|
22743
22755
|
if (data instanceof Promise) return data.then((data2) => opOk(revisionAt(target), { data: data2 }));
|
|
22744
22756
|
return opOk(revisionAt(target), { data: data || {} });
|
|
22745
22757
|
});
|
|
@@ -22748,10 +22760,10 @@ function localBoard() {
|
|
|
22748
22760
|
}
|
|
22749
22761
|
},
|
|
22750
22762
|
readMove(move2, input) {
|
|
22751
|
-
const
|
|
22752
|
-
if (!
|
|
22763
|
+
const run2 = MOVES[move2];
|
|
22764
|
+
if (!run2) return Promise.reject(new Error(`unknown command "${move2}".`));
|
|
22753
22765
|
try {
|
|
22754
|
-
return Promise.resolve(
|
|
22766
|
+
return Promise.resolve(run2(input) || {});
|
|
22755
22767
|
} catch (e) {
|
|
22756
22768
|
return Promise.reject(e);
|
|
22757
22769
|
}
|
|
@@ -22813,8 +22825,8 @@ function board() {
|
|
|
22813
22825
|
if (!active) active = localBoard();
|
|
22814
22826
|
return active;
|
|
22815
22827
|
}
|
|
22816
|
-
function withLease(target,
|
|
22817
|
-
return leaseAnd(board(), target,
|
|
22828
|
+
function withLease(target, run2) {
|
|
22829
|
+
return leaseAnd(board(), target, run2);
|
|
22818
22830
|
}
|
|
22819
22831
|
var active, setCardStatusOn, archiveCard, finishSetupStep, cancelBoardDelivery, resumeBoardDelivery, discardBoardDelivery;
|
|
22820
22832
|
var init_board2 = __esm({
|
|
@@ -23093,18 +23105,18 @@ async function refresh() {
|
|
|
23093
23105
|
}
|
|
23094
23106
|
}
|
|
23095
23107
|
function sessionFrom(body, previous) {
|
|
23096
|
-
const
|
|
23097
|
-
const subject2 =
|
|
23098
|
-
if (!
|
|
23099
|
-
const expiresAt =
|
|
23108
|
+
const token3 = body;
|
|
23109
|
+
const subject2 = token3.user?.id ?? previous?.subject;
|
|
23110
|
+
if (!token3.access_token || !token3.refresh_token || !subject2) return null;
|
|
23111
|
+
const expiresAt = token3.expires_at ? token3.expires_at * 1e3 : Date.now() + (token3.expires_in ?? 3600) * 1e3;
|
|
23100
23112
|
return {
|
|
23101
23113
|
version: 1,
|
|
23102
23114
|
supabaseUrl: cloudEndpoints().supabaseUrl,
|
|
23103
|
-
accessToken:
|
|
23104
|
-
refreshToken:
|
|
23115
|
+
accessToken: token3.access_token,
|
|
23116
|
+
refreshToken: token3.refresh_token,
|
|
23105
23117
|
expiresAt,
|
|
23106
23118
|
subject: subject2,
|
|
23107
|
-
email:
|
|
23119
|
+
email: token3.user?.email ?? previous?.email,
|
|
23108
23120
|
handle: previous?.handle,
|
|
23109
23121
|
name: previous?.name,
|
|
23110
23122
|
avatarUrl: previous?.avatarUrl,
|
|
@@ -23193,16 +23205,16 @@ function cloudWorkflow(name) {
|
|
|
23193
23205
|
}
|
|
23194
23206
|
async function call(method, route2, body, key) {
|
|
23195
23207
|
if (!cloudConfigured()) return { ok: false, error: NOT_CONFIGURED, code: "bad_request" };
|
|
23196
|
-
const
|
|
23197
|
-
if (!
|
|
23198
|
-
return
|
|
23208
|
+
const token3 = await accessToken();
|
|
23209
|
+
if (!token3.ok) {
|
|
23210
|
+
return token3.reason === "unreachable" ? { ok: false, error: `Cloud could not be reached: ${token3.error}` } : { ok: false, error: "This machine is not signed in to Cloud.", code: "unauthenticated" };
|
|
23199
23211
|
}
|
|
23200
23212
|
let response;
|
|
23201
23213
|
try {
|
|
23202
23214
|
response = await fetch(`${cloudEndpoints().api}${route2}`, {
|
|
23203
23215
|
method,
|
|
23204
23216
|
headers: {
|
|
23205
|
-
authorization: `Bearer ${
|
|
23217
|
+
authorization: `Bearer ${token3.token}`,
|
|
23206
23218
|
...body ? { "content-type": "application/json" } : {},
|
|
23207
23219
|
...key ? { "idempotency-key": key } : {}
|
|
23208
23220
|
},
|
|
@@ -23228,7 +23240,8 @@ async function createTask(from, said3) {
|
|
|
23228
23240
|
source: "api",
|
|
23229
23241
|
source_context: { task: from },
|
|
23230
23242
|
...said3.run ? { run: true } : {},
|
|
23231
|
-
...said3.metadata && Object.keys(said3.metadata).length ? { metadata: said3.metadata } : {}
|
|
23243
|
+
...said3.metadata && Object.keys(said3.metadata).length ? { metadata: said3.metadata } : {},
|
|
23244
|
+
...said3.triage ? { triage: `tri_${source.value.project.replace(/^prj_/, "")}_${said3.triage}` } : {}
|
|
23232
23245
|
};
|
|
23233
23246
|
const key = `akb-${crypto2.createHash("sha256").update(`${from}
|
|
23234
23247
|
${said3.slug?.trim() || said3.title}`).digest("hex").slice(0, 40)}`;
|
|
@@ -23358,6 +23371,7 @@ __export(board_exports, {
|
|
|
23358
23371
|
buildBoardProgram: () => buildBoardProgram
|
|
23359
23372
|
});
|
|
23360
23373
|
import fs68 from "node:fs";
|
|
23374
|
+
import path65 from "node:path";
|
|
23361
23375
|
async function dispatch(move2, cmd, args, opts, cli) {
|
|
23362
23376
|
const ctx = ctxOf(cmd, cli.program);
|
|
23363
23377
|
const data = await runAction(ctx, {}, async () => {
|
|
@@ -23391,24 +23405,30 @@ async function cloudCard(task, opts) {
|
|
|
23391
23405
|
} catch {
|
|
23392
23406
|
}
|
|
23393
23407
|
}
|
|
23394
|
-
const
|
|
23408
|
+
const run2 = opts.schedule !== void 0;
|
|
23395
23409
|
const made = await createTask(task, {
|
|
23396
23410
|
title: String(opts.title ?? "").trim(),
|
|
23397
23411
|
request,
|
|
23398
23412
|
workflow: typeof opts.workflow === "string" && opts.workflow.trim() ? opts.workflow.trim() : void 0,
|
|
23399
|
-
run,
|
|
23413
|
+
run: run2,
|
|
23400
23414
|
slug: typeof opts.slug === "string" ? opts.slug : void 0,
|
|
23401
|
-
metadata:
|
|
23415
|
+
metadata: run2 ? cardFields(opts) : void 0,
|
|
23416
|
+
triage: typeof opts.triage === "string" ? triageFile(opts.triage) : void 0
|
|
23402
23417
|
});
|
|
23403
23418
|
if (!made.ok) throw new BoardError(`Cloud did not make the task: ${made.error}`, { kind: "refused" });
|
|
23404
23419
|
const { task: created, number, from } = made.value;
|
|
23405
|
-
if (
|
|
23420
|
+
if (run2) {
|
|
23406
23421
|
say(number);
|
|
23407
23422
|
say(` made ${created.key} in Cloud${made.value.replayed ? " (already made)" : ""} \u2014 it is planned there, from ${from.key}`);
|
|
23408
23423
|
return { done: { board: KANBAN, id: number, ids: [], key: created.key, task: created.id, replayed: made.value.replayed } };
|
|
23409
23424
|
}
|
|
23410
23425
|
return { opts: { ...opts, id: number, fromTask: from.key, schedule: void 0 } };
|
|
23411
23426
|
}
|
|
23427
|
+
function triageFile(sourceId) {
|
|
23428
|
+
const found2 = [...readInbox(), ...readAllDismissed(), ...readArchived()].find((s) => s.sourceId === sourceId.trim());
|
|
23429
|
+
if (!found2) throw new BoardError(`nothing in triage is ${sourceId}`, { kind: "refused" });
|
|
23430
|
+
return path65.basename(found2.relPath, ".md");
|
|
23431
|
+
}
|
|
23412
23432
|
function cardFields(opts) {
|
|
23413
23433
|
const list = (v) => Array.isArray(v) && v.length ? v.map((x) => String(x).replace(/^#/, "")) : void 0;
|
|
23414
23434
|
return Object.fromEntries(Object.entries({
|
|
@@ -23568,6 +23588,7 @@ var init_board3 = __esm({
|
|
|
23568
23588
|
init_rules();
|
|
23569
23589
|
init_shared();
|
|
23570
23590
|
init_esm();
|
|
23591
|
+
init_inbox();
|
|
23571
23592
|
init_tasks();
|
|
23572
23593
|
init_idea();
|
|
23573
23594
|
CREATE_SCHEDULES = SCHEDULED_ACTIONS.filter((a) => a !== "revise");
|
|
@@ -23577,15 +23598,15 @@ var init_board3 = __esm({
|
|
|
23577
23598
|
|
|
23578
23599
|
// ../cli/src/lib/board-cli.ts
|
|
23579
23600
|
import fs69 from "node:fs";
|
|
23580
|
-
import
|
|
23601
|
+
import path66 from "node:path";
|
|
23581
23602
|
function useBoard(found2, dirNamed) {
|
|
23582
|
-
if (!found2.named && found2.board ===
|
|
23603
|
+
if (!found2.named && found2.board === path66.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
|
|
23583
23604
|
else setBoardDir(found2.board, found2.root);
|
|
23584
23605
|
useProjectState();
|
|
23585
23606
|
}
|
|
23586
23607
|
function whole(found2, move2) {
|
|
23587
23608
|
if (move2 === MAKES_A_BOARD) return found2;
|
|
23588
|
-
if (!fs69.existsSync(
|
|
23609
|
+
if (!fs69.existsSync(path66.join(found2.board, "todo"))) {
|
|
23589
23610
|
die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
|
|
23590
23611
|
kind: "board-incomplete",
|
|
23591
23612
|
dir: found2.root
|
|
@@ -23594,11 +23615,11 @@ function whole(found2, move2) {
|
|
|
23594
23615
|
return found2;
|
|
23595
23616
|
}
|
|
23596
23617
|
function findBoardUpward(from) {
|
|
23597
|
-
let dir =
|
|
23618
|
+
let dir = path66.resolve(from);
|
|
23598
23619
|
for (; ; ) {
|
|
23599
|
-
if (hasBoard(dir)) return { root: dir, board:
|
|
23620
|
+
if (hasBoard(dir)) return { root: dir, board: path66.join(dir, "docs", "kanban") };
|
|
23600
23621
|
if (isBoardDir(dir)) return { root: projectRootOf(dir), board: dir };
|
|
23601
|
-
const up =
|
|
23622
|
+
const up = path66.dirname(dir);
|
|
23602
23623
|
if (up === dir) return null;
|
|
23603
23624
|
dir = up;
|
|
23604
23625
|
}
|
|
@@ -23606,7 +23627,7 @@ function findBoardUpward(from) {
|
|
|
23606
23627
|
function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
|
|
23607
23628
|
const named2 = board2 ?? boardFromEnv();
|
|
23608
23629
|
if (named2) {
|
|
23609
|
-
const folder =
|
|
23630
|
+
const folder = path66.resolve(cwd, named2);
|
|
23610
23631
|
if (!fs69.existsSync(folder) && move2 !== MAKES_A_BOARD) {
|
|
23611
23632
|
die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
|
|
23612
23633
|
}
|
|
@@ -23619,7 +23640,7 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
|
|
|
23619
23640
|
return { root: projectRootOf(folder), board: folder, named: true };
|
|
23620
23641
|
}
|
|
23621
23642
|
if (dir !== null) {
|
|
23622
|
-
const root =
|
|
23643
|
+
const root = path66.resolve(dir);
|
|
23623
23644
|
if (!fs69.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
|
|
23624
23645
|
if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
|
|
23625
23646
|
die(`no board in ${root} \u2014 it has no docs/kanban/. Run ${installHint} there to make one.`, {
|
|
@@ -23627,14 +23648,14 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
|
|
|
23627
23648
|
dir: root
|
|
23628
23649
|
});
|
|
23629
23650
|
}
|
|
23630
|
-
return whole({ root, board:
|
|
23651
|
+
return whole({ root, board: path66.join(root, "docs", "kanban") }, move2);
|
|
23631
23652
|
}
|
|
23632
23653
|
const found2 = findBoardUpward(cwd);
|
|
23633
23654
|
if (found2) return whole(found2, move2);
|
|
23634
|
-
if (move2 === MAKES_A_BOARD) return { root:
|
|
23655
|
+
if (move2 === MAKES_A_BOARD) return { root: path66.resolve(cwd), board: path66.join(path66.resolve(cwd), "docs", "kanban") };
|
|
23635
23656
|
die(
|
|
23636
|
-
`no board here. Looked in ${
|
|
23637
|
-
{ kind: "no-board", dir:
|
|
23657
|
+
`no board here. Looked in ${path66.resolve(cwd)} and every folder above it for docs/kanban/. Run ${installHint} to make one, or name the board with --board.`,
|
|
23658
|
+
{ kind: "no-board", dir: path66.resolve(cwd) }
|
|
23638
23659
|
);
|
|
23639
23660
|
}
|
|
23640
23661
|
function editDistance(a, b) {
|
|
@@ -23716,8 +23737,8 @@ var init_board_cli = __esm({
|
|
|
23716
23737
|
init_io();
|
|
23717
23738
|
init_pointer();
|
|
23718
23739
|
MAKES_A_BOARD = "init";
|
|
23719
|
-
hasBoard = (dir) => fs69.existsSync(
|
|
23720
|
-
isBoardDir = (dir) => fs69.existsSync(
|
|
23740
|
+
hasBoard = (dir) => fs69.existsSync(path66.join(dir, "docs", "kanban"));
|
|
23741
|
+
isBoardDir = (dir) => fs69.existsSync(path66.join(dir, "todo")) && fs69.existsSync(path66.join(dir, "config.md"));
|
|
23721
23742
|
boardFromEnv = () => {
|
|
23722
23743
|
const named2 = process.env.AI4KANBAN_BOARD?.trim();
|
|
23723
23744
|
return named2 ? named2 : null;
|
|
@@ -23726,8 +23747,8 @@ var init_board_cli = __esm({
|
|
|
23726
23747
|
});
|
|
23727
23748
|
|
|
23728
23749
|
// ../cli/src/lib/agent/approve.ts
|
|
23729
|
-
import
|
|
23730
|
-
import
|
|
23750
|
+
import fs75 from "node:fs";
|
|
23751
|
+
import path72 from "node:path";
|
|
23731
23752
|
function approvalRequest(req) {
|
|
23732
23753
|
const id = req.id;
|
|
23733
23754
|
const card = id === void 0 ? null : findCard(id);
|
|
@@ -23745,12 +23766,12 @@ function advancePhase(id) {
|
|
|
23745
23766
|
const card = findCard(id);
|
|
23746
23767
|
const found2 = locate(id);
|
|
23747
23768
|
if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
|
|
23748
|
-
const file = found2.kind === "group" ?
|
|
23749
|
-
const { meta, body } = parseFrontmatter(
|
|
23769
|
+
const file = found2.kind === "group" ? path72.join(found2.target, "root.md") : found2.target;
|
|
23770
|
+
const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
|
|
23750
23771
|
if (!meta) return { error: `#${id} has no frontmatter` };
|
|
23751
23772
|
meta.phase = card.stage + 1;
|
|
23752
23773
|
meta.status = "todo";
|
|
23753
|
-
|
|
23774
|
+
fs75.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
|
|
23754
23775
|
return null;
|
|
23755
23776
|
}
|
|
23756
23777
|
var init_approve = __esm({
|
|
@@ -23840,10 +23861,10 @@ function open2(req, sessionId) {
|
|
|
23840
23861
|
returnRunPictures(sessionId, req.box);
|
|
23841
23862
|
return opened;
|
|
23842
23863
|
}
|
|
23843
|
-
const { run } = opened;
|
|
23844
|
-
const pid = spawnWatcher(
|
|
23845
|
-
markSpawned(
|
|
23846
|
-
return { run, spawned: pid !== void 0 };
|
|
23864
|
+
const { run: run2 } = opened;
|
|
23865
|
+
const pid = spawnWatcher(run2.sessionId);
|
|
23866
|
+
markSpawned(run2.sessionId, pid);
|
|
23867
|
+
return { run: run2, spawned: pid !== void 0 };
|
|
23847
23868
|
}
|
|
23848
23869
|
async function startResume(id) {
|
|
23849
23870
|
const opened = await openResume(id);
|
|
@@ -23870,16 +23891,16 @@ var init_start = __esm({
|
|
|
23870
23891
|
});
|
|
23871
23892
|
|
|
23872
23893
|
// ../cli/src/lib/agent/task-chats.ts
|
|
23873
|
-
import
|
|
23874
|
-
import
|
|
23894
|
+
import fs76 from "node:fs";
|
|
23895
|
+
import path73 from "node:path";
|
|
23875
23896
|
function taskChatFile(task, home3) {
|
|
23876
23897
|
if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
|
|
23877
|
-
return
|
|
23898
|
+
return path73.join(taskChatsDir(home3), `${task}.jsonl`);
|
|
23878
23899
|
}
|
|
23879
23900
|
function readTaskChat(task, home3) {
|
|
23880
23901
|
let text5;
|
|
23881
23902
|
try {
|
|
23882
|
-
text5 =
|
|
23903
|
+
text5 = fs76.readFileSync(taskChatFile(task, home3), "utf8");
|
|
23883
23904
|
} catch {
|
|
23884
23905
|
return [];
|
|
23885
23906
|
}
|
|
@@ -23900,20 +23921,20 @@ var init_task_chats = __esm({
|
|
|
23900
23921
|
"use strict";
|
|
23901
23922
|
init_home();
|
|
23902
23923
|
TASK = /^[\w-]{1,100}$/;
|
|
23903
|
-
taskChatsDir = (home3 = machineHome()) =>
|
|
23924
|
+
taskChatsDir = (home3 = machineHome()) => path73.join(home3, "chats");
|
|
23904
23925
|
}
|
|
23905
23926
|
});
|
|
23906
23927
|
|
|
23907
23928
|
// ../cli/src/lib/agent/memory-review.ts
|
|
23908
|
-
import
|
|
23909
|
-
import
|
|
23929
|
+
import fs77 from "node:fs";
|
|
23930
|
+
import path74 from "node:path";
|
|
23910
23931
|
function taskChatsToReview(tasks) {
|
|
23911
|
-
const cards =
|
|
23932
|
+
const cards = fs77.existsSync(ARCHIVE) ? walkMd(ARCHIVE) : [];
|
|
23912
23933
|
const out = [];
|
|
23913
23934
|
for (const task of [...new Set(tasks)]) {
|
|
23914
23935
|
const slug2 = task.toLowerCase().replace(/_/g, "-");
|
|
23915
|
-
const file = cards.find((f) => /^\d+-/.test(
|
|
23916
|
-
const id = file ? idPrefix(
|
|
23936
|
+
const file = cards.find((f) => /^\d+-/.test(path74.basename(f)) && path74.basename(f).endsWith(`-${slug2}.md`));
|
|
23937
|
+
const id = file ? idPrefix(path74.basename(file)) : null;
|
|
23917
23938
|
const card = file && id !== null ? readArchived2(id, file) : null;
|
|
23918
23939
|
let lines = [];
|
|
23919
23940
|
try {
|
|
@@ -23929,7 +23950,7 @@ function taskChatsToReview(tasks) {
|
|
|
23929
23950
|
}
|
|
23930
23951
|
function readArchived2(id, file) {
|
|
23931
23952
|
try {
|
|
23932
|
-
const { meta, body } = parseFrontmatter(
|
|
23953
|
+
const { meta, body } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
|
|
23933
23954
|
if (!meta) return null;
|
|
23934
23955
|
return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
|
|
23935
23956
|
} catch {
|
|
@@ -23975,8 +23996,8 @@ var init_memory_review = __esm({
|
|
|
23975
23996
|
});
|
|
23976
23997
|
|
|
23977
23998
|
// ../cli/src/lib/agent/flow.ts
|
|
23978
|
-
import
|
|
23979
|
-
import
|
|
23999
|
+
import fs78 from "node:fs";
|
|
24000
|
+
import path75 from "node:path";
|
|
23980
24001
|
function readCard2(id, home3 = "board") {
|
|
23981
24002
|
const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
|
|
23982
24003
|
if (!found2) {
|
|
@@ -23985,10 +24006,10 @@ function readCard2(id, home3 = "board") {
|
|
|
23985
24006
|
{ kind: "card-not-found", id }
|
|
23986
24007
|
);
|
|
23987
24008
|
}
|
|
23988
|
-
const file = found2.kind === "group" ?
|
|
24009
|
+
const file = found2.kind === "group" ? path75.join(found2.target, "root.md") : found2.target;
|
|
23989
24010
|
let text5;
|
|
23990
24011
|
try {
|
|
23991
|
-
text5 =
|
|
24012
|
+
text5 = fs78.readFileSync(file, "utf8");
|
|
23992
24013
|
} catch {
|
|
23993
24014
|
die(`#${id} is on the board but ${rel(file)} can't be read.`, { kind: "card-unreadable", id });
|
|
23994
24015
|
}
|
|
@@ -24046,7 +24067,7 @@ function memoryLines(modules, name) {
|
|
|
24046
24067
|
}
|
|
24047
24068
|
function configText() {
|
|
24048
24069
|
try {
|
|
24049
|
-
return
|
|
24070
|
+
return fs78.readFileSync(CONFIG, "utf8").trim() || null;
|
|
24050
24071
|
} catch {
|
|
24051
24072
|
return null;
|
|
24052
24073
|
}
|
|
@@ -24092,10 +24113,10 @@ function candidateField(delivery) {
|
|
|
24092
24113
|
}
|
|
24093
24114
|
const candidate = candidateOf(delivery);
|
|
24094
24115
|
const stat = candidateStat(candidate);
|
|
24095
|
-
const
|
|
24116
|
+
const files2 = candidateFileStats(candidate);
|
|
24096
24117
|
const patch2 = candidatePatch(candidate);
|
|
24097
24118
|
const command = delivery.worktree && delivery.branch ? `git diff ${delivery.base.slice(0, 12)} ${delivery.branch}` : `git diff ${delivery.base.slice(0, 12)}`;
|
|
24098
|
-
const shown2 =
|
|
24119
|
+
const shown2 = files2?.length ? ["changed files:", ...files2.map((file) => ` ${file}`)] : ["no file changed"];
|
|
24099
24120
|
let diff = [];
|
|
24100
24121
|
if (patch2 === null) diff = ["", "the diff could not be read"];
|
|
24101
24122
|
else if (patch2.length > MAX_DIFF) {
|
|
@@ -24119,7 +24140,7 @@ function workspaceField(delivery) {
|
|
|
24119
24140
|
if (!delivery?.worktree) return [];
|
|
24120
24141
|
return field("workspace", [
|
|
24121
24142
|
`write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
|
|
24122
|
-
|
|
24143
|
+
path75.resolve(process.cwd()).startsWith(worktreeDir(delivery.worktree)) ? `it is your working folder already; the board's own files are NOT in it and never go on that branch.` : `cd into it first \u2014 you are in the project checkout, and code written here is not part of this build. The board's own files are NOT in it and never go on that branch.`,
|
|
24123
24144
|
`every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
|
|
24124
24145
|
`${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
|
|
24125
24146
|
]);
|
|
@@ -24127,7 +24148,7 @@ function workspaceField(delivery) {
|
|
|
24127
24148
|
function conflictField(delivery) {
|
|
24128
24149
|
if (!delivery?.worktree) return field("conflict", "no delivery with a worktree is landing right now");
|
|
24129
24150
|
const dir = worktreeDir(delivery.worktree);
|
|
24130
|
-
const
|
|
24151
|
+
const files2 = conflictedPaths(dir);
|
|
24131
24152
|
const target = delivery.targetBranch ?? "the target branch";
|
|
24132
24153
|
const since = delivery.landing?.originalBase ?? delivery.base;
|
|
24133
24154
|
const onto = delivery.landing?.onto ?? delivery.targetBranch;
|
|
@@ -24138,7 +24159,7 @@ function conflictField(delivery) {
|
|
|
24138
24159
|
return field("conflict", [
|
|
24139
24160
|
...delivery.landing?.why ? [`landing: ${delivery.landing.why}`] : [],
|
|
24140
24161
|
`worktree: ${delivery.worktree}`,
|
|
24141
|
-
...
|
|
24162
|
+
...files2.length ? [`conflicted files (${files2.length}):`, ...listed(files2)] : ["conflicted files: none"],
|
|
24142
24163
|
...since && onto ? [
|
|
24143
24164
|
`original base: ${short2(since)} \u2014 \`git diff ${short2(since)} ${short2(onto)}\` is what ${target} brought in since this delivery started`,
|
|
24144
24165
|
`${target} changed since then (${theirs.length}):`,
|
|
@@ -24500,7 +24521,7 @@ function printFlow(rawReq, program2 = "akb") {
|
|
|
24500
24521
|
say("");
|
|
24501
24522
|
say(`there is no \`akb\` on this machine \u2014 every \`akb\` below, in the flows too, is \`${program2}\` here.`);
|
|
24502
24523
|
}
|
|
24503
|
-
if (req.action !== "setup" &&
|
|
24524
|
+
if (req.action !== "setup" && fs78.existsSync(SETUP_CHECKLIST)) {
|
|
24504
24525
|
say("");
|
|
24505
24526
|
say(`this board is not set up yet \u2014 ${rel(SETUP_CHECKLIST)} is still there.`);
|
|
24506
24527
|
say(`finish it first: ${setupInstruction()}`);
|
|
@@ -24540,6 +24561,11 @@ function printFlow(rawReq, program2 = "akb") {
|
|
|
24540
24561
|
say("");
|
|
24541
24562
|
say(rule);
|
|
24542
24563
|
}
|
|
24564
|
+
const language = languageNote();
|
|
24565
|
+
if (language) {
|
|
24566
|
+
say("");
|
|
24567
|
+
say(language);
|
|
24568
|
+
}
|
|
24543
24569
|
return {
|
|
24544
24570
|
mode: "print",
|
|
24545
24571
|
action: req.action,
|
|
@@ -24589,9 +24615,9 @@ var init_flow = __esm({
|
|
|
24589
24615
|
EDITABLE_FIELDS = "--title|--priority|--roi|--modules|--blocked-by|--related";
|
|
24590
24616
|
bodyScaffoldClose = (lead = "fill the existing") => [`${lead} body scaffold; do not rename or translate its section titles, and leave empty scaffold sections in place`];
|
|
24591
24617
|
MAX_LISTED = 50;
|
|
24592
|
-
listed = (
|
|
24593
|
-
...
|
|
24594
|
-
...
|
|
24618
|
+
listed = (files2) => [
|
|
24619
|
+
...files2.slice(0, MAX_LISTED).map((f) => ` ${f}`),
|
|
24620
|
+
...files2.length > MAX_LISTED ? [` \u2026and ${files2.length - MAX_LISTED} more`] : []
|
|
24595
24621
|
];
|
|
24596
24622
|
answeredClose = (delivery, self) => `${self} delivery answered ${delivery.deliveryId} --changed|--unchanged "<why>" \u2014 before you drop the questions; dropping the last one with nothing said carries the build on as --unchanged`;
|
|
24597
24623
|
stageClose = (meta) => {
|
|
@@ -24715,30 +24741,30 @@ function stalledLine(cardId2, next) {
|
|
|
24715
24741
|
if (!card || card.schedule || refinementStep(card) === "done") return null;
|
|
24716
24742
|
return next === "incomplete" ? `QA left untagged questions on #${card.id}, so it did not finish. #${card.id} is still at todo and nothing else will pick it up \u2014 refine it again, or mark it ready yourself.` : `#${card.id} is still in To do after planning, and the board won't plan it again on its own \u2014 refine it again, or mark it ready yourself.`;
|
|
24717
24743
|
}
|
|
24718
|
-
function planStageEnd(
|
|
24719
|
-
if (
|
|
24720
|
-
const card = currentCard(
|
|
24744
|
+
function planStageEnd(run2) {
|
|
24745
|
+
if (run2.cardId === null || stageOfAction(run2.action) !== "plan") return null;
|
|
24746
|
+
const card = currentCard(run2.cardId);
|
|
24721
24747
|
if (!card || card.openBlockers.length > 0 || openOf(card.questions).length > 0 || card.schedule) return null;
|
|
24722
24748
|
if (refinementStep(card) !== "done") return null;
|
|
24723
24749
|
return endOfStage(stageContract("plan"), card);
|
|
24724
24750
|
}
|
|
24725
|
-
function refinementRunsAfter(
|
|
24726
|
-
for (const id of /* @__PURE__ */ new Set([...changed, ...
|
|
24751
|
+
function refinementRunsAfter(run2, changed, before, waitingForSpec = false, refused = []) {
|
|
24752
|
+
for (const id of /* @__PURE__ */ new Set([...changed, ...run2.cardId === null ? [] : [run2.cardId]])) {
|
|
24727
24753
|
scheduleRefineOnBlock(id);
|
|
24728
24754
|
}
|
|
24729
|
-
const next = waitingForSpec ||
|
|
24730
|
-
const starts2 = refinesAfter(
|
|
24731
|
-
(req) => req.id !==
|
|
24755
|
+
const next = waitingForSpec || run2.cardId === null || !["clarify", "resolve", "edit", "approve"].includes(run2.action) ? null : refinementAfter(run2.action, run2.cardId, run2.flowId);
|
|
24756
|
+
const starts2 = refinesAfter(run2.action, changed, before).filter(
|
|
24757
|
+
(req) => req.id !== run2.cardId || run2.refineRound === void 0 && run2.action !== "spec"
|
|
24732
24758
|
);
|
|
24733
24759
|
const refusedNote = waitingForSpec || !refused.length ? "" : `Spec agents not started: ${refused.map((r) => r.error).join(" ")}`;
|
|
24734
24760
|
const plain2 = typeof next === "object" && next ? next : null;
|
|
24735
24761
|
const carryOn = plain2 && refusedNote ? { ...plain2, notes: [plain2.notes, refusedNote].filter(Boolean).join("\n\n") } : plain2;
|
|
24736
|
-
const end2 = waitingForSpec || carryOn || starts2.length ? null : planStageEnd(
|
|
24762
|
+
const end2 = waitingForSpec || carryOn || starts2.length ? null : planStageEnd(run2);
|
|
24737
24763
|
const short2 = end2 && "missing" in end2 ? end2 : null;
|
|
24738
|
-
const stalled = (short2 ? shortLine(short2,
|
|
24764
|
+
const stalled = (short2 ? shortLine(short2, run2.cardId) : void 0) ?? (waitingForSpec ? void 0 : (run2.refineRound !== void 0 || next === "incomplete") && stalledLine(run2.cardId, next) || void 0);
|
|
24739
24765
|
const stalledWhy = [
|
|
24740
24766
|
...refusedNote ? [{ kind: "specRefused", refusals: refused }] : [],
|
|
24741
|
-
...!stalled ? [] : short2 ? [{ kind: "stageShort", args: { stage: short2.stage, card: String(
|
|
24767
|
+
...!stalled ? [] : short2 ? [{ kind: "stageShort", args: { stage: short2.stage, card: String(run2.cardId), agents: short2.missing.join(", ") } }] : next === "incomplete" ? [{ kind: "qaUnfinished", args: { card: String(run2.cardId) } }] : [{ kind: "refineStalled", args: { card: String(run2.cardId) } }]
|
|
24742
24768
|
];
|
|
24743
24769
|
return {
|
|
24744
24770
|
runs: [...starts2, ...carryOn ? [carryOn] : [], ...end2 && "ask" in end2 ? [end2.ask] : []],
|
|
@@ -24921,21 +24947,21 @@ __export(watch_exports, {
|
|
|
24921
24947
|
watchRun: () => watchRun
|
|
24922
24948
|
});
|
|
24923
24949
|
import { spawn as spawn3 } from "node:child_process";
|
|
24924
|
-
import
|
|
24950
|
+
import fs79 from "node:fs";
|
|
24925
24951
|
async function watchRun(sessionId, resume = startResume) {
|
|
24926
24952
|
const spec = readSpec(sessionId);
|
|
24927
|
-
const
|
|
24928
|
-
if (!
|
|
24929
|
-
if (
|
|
24953
|
+
const run2 = peekRun(sessionId);
|
|
24954
|
+
if (!run2 || !spec) {
|
|
24955
|
+
if (run2?.status === "running") await closeRun(sessionId, { status: "interrupted", code: null });
|
|
24930
24956
|
return 1;
|
|
24931
24957
|
}
|
|
24932
|
-
if (
|
|
24958
|
+
if (run2.stopping) {
|
|
24933
24959
|
await closeRun(sessionId, { status: "stopped", code: null });
|
|
24934
24960
|
return 1;
|
|
24935
24961
|
}
|
|
24936
24962
|
const stopping = () => peekRun(sessionId)?.status !== "running";
|
|
24937
24963
|
let releaseIndex = null;
|
|
24938
|
-
if (needsIndexLock(
|
|
24964
|
+
if (needsIndexLock(run2.action)) {
|
|
24939
24965
|
releaseIndex = await acquireIndexLock(stopping);
|
|
24940
24966
|
if (!releaseIndex) {
|
|
24941
24967
|
if (!stopping()) await closeRun(sessionId, { status: "interrupted", code: null });
|
|
@@ -24954,14 +24980,14 @@ async function watchRun(sessionId, resume = startResume) {
|
|
|
24954
24980
|
const claimed = patch(sessionId, (r) => {
|
|
24955
24981
|
claim = claimCard(r);
|
|
24956
24982
|
});
|
|
24957
|
-
const record = claimed ??
|
|
24983
|
+
const record = claimed ?? run2;
|
|
24958
24984
|
const sub = record.action === "sub";
|
|
24959
24985
|
if (claim) await setCardStatus(claim.cardId, claim.status);
|
|
24960
|
-
|
|
24961
|
-
const logFile =
|
|
24986
|
+
fs79.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
24987
|
+
const logFile = fs79.createWriteStream(record.logPath, { flags: "a" });
|
|
24962
24988
|
logFile.on("error", () => {
|
|
24963
24989
|
});
|
|
24964
|
-
const eventsFile =
|
|
24990
|
+
const eventsFile = fs79.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
|
|
24965
24991
|
eventsFile.on("error", () => {
|
|
24966
24992
|
});
|
|
24967
24993
|
const event2 = (e) => eventsFile.write(`${JSON.stringify(e)}
|
|
@@ -24982,7 +25008,7 @@ async function watchRun(sessionId, resume = startResume) {
|
|
|
24982
25008
|
if (active2.startNote) log.write(`[board] ${active2.startNote}
|
|
24983
25009
|
`);
|
|
24984
25010
|
const basePrompt = record.formatRepair ? contractRepairPrompt(requestOf(record), record.formatRepair.errors) : record.resumedFrom ? spec.prompt || resumePrompt(record.deliveryId, record.cardId, record.action) : spec.prompt;
|
|
24985
|
-
const prompt = [basePrompt, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n");
|
|
25011
|
+
const prompt = inLanguage([basePrompt, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n"));
|
|
24986
25012
|
const before = markBoard();
|
|
24987
25013
|
const sources = snapshotSpecs();
|
|
24988
25014
|
const wasBroken = new Set(boardComplaints());
|
|
@@ -24995,7 +25021,7 @@ async function watchRun(sessionId, resume = startResume) {
|
|
|
24995
25021
|
void refreshCatalog();
|
|
24996
25022
|
const client = active2.client;
|
|
24997
25023
|
const restartBase = !client || record.formatRepair ? void 0 : record.resumedFrom ? restartPrompt(requestOf(record), record.deliveryId) : record.continues && !record.continues.fork ? spec.prompt : void 0;
|
|
24998
|
-
const restart = restartBase ? [restartBase, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n") : void 0;
|
|
25024
|
+
const restart = restartBase ? inLanguage([restartBase, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n")) : void 0;
|
|
24999
25025
|
const stdinPrompt = client ? void 0 : active2.stdinPrompt;
|
|
25000
25026
|
const stdio = [client || stdinPrompt ? "pipe" : "ignore", "pipe", "pipe"];
|
|
25001
25027
|
let child;
|
|
@@ -25444,11 +25470,11 @@ function tickedNoSetupStep(record) {
|
|
|
25444
25470
|
const now = tickedSetupSteps();
|
|
25445
25471
|
return now !== void 0 && now <= record.setupTicked;
|
|
25446
25472
|
}
|
|
25447
|
-
function settleBoard(
|
|
25473
|
+
function settleBoard(run2, changed, before) {
|
|
25448
25474
|
try {
|
|
25449
|
-
const helpers = specRunsAfter(readSpecAsks(
|
|
25450
|
-
const waitingForSpec = helpers.some((req) => req.id ===
|
|
25451
|
-
return refinementRunsAfter(
|
|
25475
|
+
const helpers = specRunsAfter(readSpecAsks(run2.sessionId));
|
|
25476
|
+
const waitingForSpec = helpers.some((req) => req.id === run2.cardId);
|
|
25477
|
+
return refinementRunsAfter(run2, changed, before, waitingForSpec, readSpecRefusals(run2.sessionId));
|
|
25452
25478
|
} catch {
|
|
25453
25479
|
return null;
|
|
25454
25480
|
}
|
|
@@ -25530,6 +25556,7 @@ var init_watch = __esm({
|
|
|
25530
25556
|
init_log();
|
|
25531
25557
|
init_wire();
|
|
25532
25558
|
init_prompts();
|
|
25559
|
+
init_language();
|
|
25533
25560
|
init_resolve();
|
|
25534
25561
|
init_runner();
|
|
25535
25562
|
init_retry();
|
|
@@ -25549,7 +25576,7 @@ var init_watch = __esm({
|
|
|
25549
25576
|
});
|
|
25550
25577
|
|
|
25551
25578
|
// ../cli/src/kanban.ts
|
|
25552
|
-
import
|
|
25579
|
+
import fs87 from "node:fs";
|
|
25553
25580
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25554
25581
|
|
|
25555
25582
|
// ../cli/src/lib/cli/agent.ts
|
|
@@ -25646,11 +25673,11 @@ ${final}` : lines;
|
|
|
25646
25673
|
}
|
|
25647
25674
|
function testConnection(pin) {
|
|
25648
25675
|
const ask2 = !pin ? {} : runtimeById(pin) ? { pin } : { pin, harness: pin };
|
|
25649
|
-
const
|
|
25676
|
+
const run2 = openPlan(planRun(randomUUID7(), REPO_ROOT, void 0, ask2));
|
|
25650
25677
|
const startedAt = Date.now();
|
|
25651
|
-
const [cmd, ...args] =
|
|
25652
|
-
const client =
|
|
25653
|
-
const stdinPrompt = client ? void 0 :
|
|
25678
|
+
const [cmd, ...args] = run2.argv;
|
|
25679
|
+
const client = run2.client;
|
|
25680
|
+
const stdinPrompt = client ? void 0 : run2.stdinPrompt;
|
|
25654
25681
|
const stdio = [client || stdinPrompt ? "pipe" : "ignore", "pipe", "pipe"];
|
|
25655
25682
|
return new Promise((resolve2) => {
|
|
25656
25683
|
let events = "";
|
|
@@ -25662,16 +25689,16 @@ function testConnection(pin) {
|
|
|
25662
25689
|
if (settled) return;
|
|
25663
25690
|
settled = true;
|
|
25664
25691
|
stopTimer();
|
|
25665
|
-
const output =
|
|
25692
|
+
const output = run2.startNote && res.output ? `${run2.startNote}
|
|
25666
25693
|
${res.output}` : res.output;
|
|
25667
|
-
resolve2({ ...res, output, harness:
|
|
25694
|
+
resolve2({ ...res, output, harness: run2.harness, ms: Date.now() - startedAt });
|
|
25668
25695
|
};
|
|
25669
25696
|
const spawnFailed = (e) => {
|
|
25670
25697
|
if (e?.code === "ENOENT") {
|
|
25671
|
-
done2({ ok: false, missing: cmd, install:
|
|
25698
|
+
done2({ ok: false, missing: cmd, install: run2.install });
|
|
25672
25699
|
return;
|
|
25673
25700
|
}
|
|
25674
|
-
done2({ ok: false, output: said2(events,
|
|
25701
|
+
done2({ ok: false, output: said2(events, run2.renderer?.result(), `${stderr}
|
|
25675
25702
|
${String(e)}`) });
|
|
25676
25703
|
};
|
|
25677
25704
|
const mark = stopMark();
|
|
@@ -25683,7 +25710,7 @@ ${String(e)}`) });
|
|
|
25683
25710
|
// folder — an agent started there is outside the repo, and codex refuses to run at
|
|
25684
25711
|
// all ("Not inside a trusted directory").
|
|
25685
25712
|
cwd: REPO_ROOT,
|
|
25686
|
-
env: markEnv(
|
|
25713
|
+
env: markEnv(run2.env, mark),
|
|
25687
25714
|
shell: false,
|
|
25688
25715
|
// Same as a run: a piped stdin makes `claude -p` wait and then warn, which would
|
|
25689
25716
|
// land in the output as if it were the failure — and it is a pipe, and stays open,
|
|
@@ -25705,18 +25732,18 @@ ${String(e)}`) });
|
|
|
25705
25732
|
done2({
|
|
25706
25733
|
ok: false,
|
|
25707
25734
|
timedOut: true,
|
|
25708
|
-
output: said2(events + (
|
|
25735
|
+
output: said2(events + (run2.renderer?.flush() ?? ""), run2.renderer?.result(), stderr + errs.flush())
|
|
25709
25736
|
});
|
|
25710
25737
|
}, TIMEOUT_MS);
|
|
25711
25738
|
if (typeof timer.unref === "function") timer.unref();
|
|
25712
25739
|
stopTimer = () => clearTimeout(timer);
|
|
25713
|
-
const renderer =
|
|
25740
|
+
const renderer = run2.renderer;
|
|
25714
25741
|
if (renderer) {
|
|
25715
25742
|
child.stdout.on("data", (d) => {
|
|
25716
25743
|
events += renderer.push(d.toString());
|
|
25717
25744
|
});
|
|
25718
25745
|
}
|
|
25719
|
-
const errs = createStderrFilter(
|
|
25746
|
+
const errs = createStderrFilter(run2.quietStderr);
|
|
25720
25747
|
child.stderr.on("data", (d) => {
|
|
25721
25748
|
stderr += errs.push(d.toString());
|
|
25722
25749
|
});
|
|
@@ -26053,7 +26080,7 @@ async function testAgent(args) {
|
|
|
26053
26080
|
|
|
26054
26081
|
// ../cli/src/commands/cloud.ts
|
|
26055
26082
|
import fs71 from "node:fs";
|
|
26056
|
-
import
|
|
26083
|
+
import path68 from "node:path";
|
|
26057
26084
|
|
|
26058
26085
|
// ../cli/src/lib/cloud/avatar.ts
|
|
26059
26086
|
init_session();
|
|
@@ -26077,9 +26104,9 @@ async function download(url) {
|
|
|
26077
26104
|
if (!response.ok) return null;
|
|
26078
26105
|
const type = response.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
|
|
26079
26106
|
if (!type.startsWith("image/")) return null;
|
|
26080
|
-
const
|
|
26081
|
-
if (
|
|
26082
|
-
return `data:${type};base64,${
|
|
26107
|
+
const bytes3 = Buffer.from(await response.arrayBuffer());
|
|
26108
|
+
if (bytes3.byteLength === 0 || bytes3.byteLength > MAX_BYTES2) return null;
|
|
26109
|
+
return `data:${type};base64,${bytes3.toString("base64")}`;
|
|
26083
26110
|
} catch {
|
|
26084
26111
|
return null;
|
|
26085
26112
|
}
|
|
@@ -26102,10 +26129,10 @@ init_home();
|
|
|
26102
26129
|
init_config2();
|
|
26103
26130
|
init_session();
|
|
26104
26131
|
import fs70 from "node:fs";
|
|
26105
|
-
import
|
|
26132
|
+
import path67 from "node:path";
|
|
26106
26133
|
var FRESH_MS = 10 * 6e4;
|
|
26107
26134
|
var GRACE_MS = 7 * 24 * 60 * 6e4;
|
|
26108
|
-
var heldFile = () =>
|
|
26135
|
+
var heldFile = () => path67.join(machineHome(), "pro.json");
|
|
26109
26136
|
function forgetPro() {
|
|
26110
26137
|
fs70.rmSync(heldFile(), { force: true });
|
|
26111
26138
|
}
|
|
@@ -26126,18 +26153,18 @@ async function readCloudAccount() {
|
|
|
26126
26153
|
configured: cloudConfigured()
|
|
26127
26154
|
};
|
|
26128
26155
|
if (!blank.configured) return { ...blank, message: NOT_CONFIGURED };
|
|
26129
|
-
const
|
|
26130
|
-
if (!
|
|
26131
|
-
if (
|
|
26132
|
-
if (
|
|
26133
|
-
return { ...blank, ...held3(), state: "signed-in", error:
|
|
26156
|
+
const token3 = await accessToken();
|
|
26157
|
+
if (!token3.ok) {
|
|
26158
|
+
if (token3.reason === "signed-out") return blank;
|
|
26159
|
+
if (token3.reason === "unreachable") {
|
|
26160
|
+
return { ...blank, ...held3(), state: "signed-in", error: token3.error };
|
|
26134
26161
|
}
|
|
26135
26162
|
return { ...blank, ...held3(), state: "expired" };
|
|
26136
26163
|
}
|
|
26137
26164
|
let response;
|
|
26138
26165
|
try {
|
|
26139
26166
|
response = await fetch(`${cloudEndpoints().api}/v1/session`, {
|
|
26140
|
-
headers: { authorization: `Bearer ${
|
|
26167
|
+
headers: { authorization: `Bearer ${token3.token}` }
|
|
26141
26168
|
});
|
|
26142
26169
|
} catch (e) {
|
|
26143
26170
|
return {
|
|
@@ -26280,7 +26307,7 @@ async function cloudImage(opts, program2) {
|
|
|
26280
26307
|
const prompt = opts.prompt.trim();
|
|
26281
26308
|
if (!prompt) usage("Missing the prompt.", line2);
|
|
26282
26309
|
const references = opts.refs.map((file) => {
|
|
26283
|
-
const type = REF_TYPES[
|
|
26310
|
+
const type = REF_TYPES[path68.extname(file).toLowerCase()];
|
|
26284
26311
|
if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line2);
|
|
26285
26312
|
try {
|
|
26286
26313
|
return `data:${type};base64,${fs71.readFileSync(file).toString("base64")}`;
|
|
@@ -26361,13 +26388,13 @@ function refuse(m, fallback) {
|
|
|
26361
26388
|
die([m.zh, m.en, fallback.zh, fallback.en].join("\n"), { kind: "cloud-refused", bare: true });
|
|
26362
26389
|
}
|
|
26363
26390
|
async function generate(g) {
|
|
26364
|
-
const
|
|
26365
|
-
if (!
|
|
26391
|
+
const token3 = await accessToken();
|
|
26392
|
+
if (!token3.ok) refuse(SAID[token3.reason], g.fallback);
|
|
26366
26393
|
let res;
|
|
26367
26394
|
try {
|
|
26368
26395
|
res = await fetch(`${cloudEndpoints().api}${g.endpoint}`, {
|
|
26369
26396
|
method: "POST",
|
|
26370
|
-
headers: { authorization: `Bearer ${
|
|
26397
|
+
headers: { authorization: `Bearer ${token3.token}`, "content-type": "application/json" },
|
|
26371
26398
|
body: JSON.stringify(g.body)
|
|
26372
26399
|
});
|
|
26373
26400
|
} catch {
|
|
@@ -26383,20 +26410,602 @@ async function generate(g) {
|
|
|
26383
26410
|
if (res.status === 400) usage(error?.message ?? "The request was refused.", g.usage);
|
|
26384
26411
|
refuse(g.failed, g.fallback);
|
|
26385
26412
|
}
|
|
26386
|
-
fs71.mkdirSync(
|
|
26413
|
+
fs71.mkdirSync(path68.dirname(path68.resolve(g.out)), { recursive: true });
|
|
26387
26414
|
fs71.writeFileSync(g.out, Buffer.from(await res.arrayBuffer()));
|
|
26388
26415
|
say(`${g.out} (${g.label(res)})`);
|
|
26389
26416
|
return { out: g.out };
|
|
26390
26417
|
}
|
|
26391
26418
|
|
|
26419
|
+
// ../cli/src/commands/cloud-import.ts
|
|
26420
|
+
import crypto3 from "node:crypto";
|
|
26421
|
+
import fs73 from "node:fs";
|
|
26422
|
+
import path70 from "node:path";
|
|
26423
|
+
|
|
26424
|
+
// ../cloud/src/board-files.ts
|
|
26425
|
+
var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
|
|
26426
|
+
var SETTINGS = ["modules.md", "config.md"];
|
|
26427
|
+
var isSettingsPath = (path83) => SETTINGS.includes(path83) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path83);
|
|
26428
|
+
var BOARD_FILE_LIMIT = 256 * 1024;
|
|
26429
|
+
function isBoardPath(path83) {
|
|
26430
|
+
if (!path83 || path83.length > 500 || path83.startsWith("/") || path83.includes("\\") || path83.includes("\0")) return false;
|
|
26431
|
+
if (path83.split("/").some((part) => !part || part === "." || part === "..")) return false;
|
|
26432
|
+
if (path83 === "todo/README.md" || path83.split("/").includes(".sync")) return false;
|
|
26433
|
+
if (path83.startsWith("workflows/")) return true;
|
|
26434
|
+
if (SETTINGS.includes(path83)) return true;
|
|
26435
|
+
if (path83.startsWith("rules/")) return isSettingsPath(path83);
|
|
26436
|
+
if (path83.startsWith("assets/")) return path83.endsWith(".json");
|
|
26437
|
+
return ROOTS.some((root) => path83.startsWith(root)) && path83.endsWith(".md");
|
|
26438
|
+
}
|
|
26439
|
+
var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
26440
|
+
|
|
26441
|
+
// ../cloud/src/v1/workflow-files.ts
|
|
26442
|
+
init_workflow_def();
|
|
26443
|
+
init_parse();
|
|
26444
|
+
var WORKFLOW_FILE2 = "WORKFLOW.md";
|
|
26445
|
+
var MCP_FILE = "mcp.json";
|
|
26446
|
+
var KB = 1024;
|
|
26447
|
+
var FILE_LIMITS = { file: 256 * KB, total: 2 * KB * KB, count: 500, path: 512 };
|
|
26448
|
+
var LAND_ROLES2 = ["builder"];
|
|
26449
|
+
var SEGMENT2 = /^[\w.-]+$/;
|
|
26450
|
+
var bytes = (text5) => new TextEncoder().encode(text5).length;
|
|
26451
|
+
function folderPath(file, top) {
|
|
26452
|
+
const parts = file.split("/");
|
|
26453
|
+
if (parts.length < 3 || parts[0] !== top || !AGENT_NAME.test(parts[1])) return null;
|
|
26454
|
+
if (parts.slice(2).some((p) => !SEGMENT2.test(p) || p === "." || p === "..")) return null;
|
|
26455
|
+
return [parts[1], parts.slice(2).join("/")];
|
|
26456
|
+
}
|
|
26457
|
+
function mcpProblem(text5) {
|
|
26458
|
+
let read6;
|
|
26459
|
+
try {
|
|
26460
|
+
read6 = JSON.parse(text5);
|
|
26461
|
+
} catch {
|
|
26462
|
+
return "is not JSON";
|
|
26463
|
+
}
|
|
26464
|
+
const servers = read6?.mcpServers;
|
|
26465
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) return "must hold `mcpServers`, an object of name \u2192 server";
|
|
26466
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
26467
|
+
const { command, args, ...rest } = server ?? {};
|
|
26468
|
+
if (!AGENT_NAME.test(name)) return `names a server \`${name}\`; use lowercase letters, digits and dashes`;
|
|
26469
|
+
if (typeof command !== "string" || !command) return `server \`${name}\` needs a \`command\``;
|
|
26470
|
+
if (args !== void 0 && !(Array.isArray(args) && args.every((a) => typeof a === "string"))) return `server \`${name}\`: \`args\` must be a list of strings`;
|
|
26471
|
+
if (Object.keys(rest).length) return `server \`${name}\` takes only \`command\` and \`args\``;
|
|
26472
|
+
}
|
|
26473
|
+
return null;
|
|
26474
|
+
}
|
|
26475
|
+
function checkWorkflowFiles(value, official = false) {
|
|
26476
|
+
const fail = (file, reason, tooLarge = false) => ({ ok: false, file, reason, ...tooLarge ? { tooLarge } : {} });
|
|
26477
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return fail("files", "must be an object of path \u2192 text");
|
|
26478
|
+
const entries = Object.entries(value);
|
|
26479
|
+
if (entries.length > FILE_LIMITS.count) return fail("files", `holds at most ${FILE_LIMITS.count} files`, true);
|
|
26480
|
+
let total = 0;
|
|
26481
|
+
const folders2 = /* @__PURE__ */ new Map();
|
|
26482
|
+
const skills = /* @__PURE__ */ new Set();
|
|
26483
|
+
for (const [file, text5] of entries) {
|
|
26484
|
+
if (typeof text5 !== "string" || text5.includes("\0")) return fail(file, "is not a text file");
|
|
26485
|
+
if (file.length > FILE_LIMITS.path) return fail(file, `the path is longer than ${FILE_LIMITS.path} characters`);
|
|
26486
|
+
const size = bytes(text5);
|
|
26487
|
+
if (size > FILE_LIMITS.file) return fail(file, `is larger than ${FILE_LIMITS.file / KB} KB`, true);
|
|
26488
|
+
total += size;
|
|
26489
|
+
if (total > FILE_LIMITS.total) return fail("files", `are larger than ${FILE_LIMITS.total / KB / KB} MB together`, true);
|
|
26490
|
+
if (file === WORKFLOW_FILE2) continue;
|
|
26491
|
+
if (official && file === MCP_FILE) {
|
|
26492
|
+
const problem = mcpProblem(text5);
|
|
26493
|
+
if (problem) return fail(file, problem);
|
|
26494
|
+
continue;
|
|
26495
|
+
}
|
|
26496
|
+
if (official && file.startsWith("skills/")) {
|
|
26497
|
+
if (!folderPath(file, "skills")) return fail(file, "is not inside skills/<name>/");
|
|
26498
|
+
skills.add(file.split("/")[1]);
|
|
26499
|
+
continue;
|
|
26500
|
+
}
|
|
26501
|
+
const at = folderPath(file, "agents");
|
|
26502
|
+
if (!at) return fail(file, `is neither ${WORKFLOW_FILE2} nor inside agents/<name>/`);
|
|
26503
|
+
if (!folders2.has(at[0])) folders2.set(at[0], /* @__PURE__ */ new Map());
|
|
26504
|
+
folders2.get(at[0]).set(at[1], text5);
|
|
26505
|
+
}
|
|
26506
|
+
for (const name of skills) {
|
|
26507
|
+
if (value[`skills/${name}/SKILL.md`] === void 0) return fail(`skills/${name}/SKILL.md`, "is missing");
|
|
26508
|
+
}
|
|
26509
|
+
const source = value[WORKFLOW_FILE2];
|
|
26510
|
+
if (source === void 0) return fail(WORKFLOW_FILE2, "is missing");
|
|
26511
|
+
const read6 = parseWorkflowFile("workflow", source, WORKFLOW_FILE2);
|
|
26512
|
+
if ("problem" in read6) return fail(WORKFLOW_FILE2, read6.problem.replace(`${WORKFLOW_FILE2}: `, ""));
|
|
26513
|
+
const def = read6.def;
|
|
26514
|
+
const agents = /* @__PURE__ */ new Map();
|
|
26515
|
+
for (const [name, own] of folders2) {
|
|
26516
|
+
const from = `agents/${name}/AGENT.md`;
|
|
26517
|
+
const text5 = own.get("AGENT.md");
|
|
26518
|
+
if (text5 === void 0) return fail(from, "is missing");
|
|
26519
|
+
const parsed = parseSpecAgent(text5, from, (p) => own.get(p) ?? null, false, () => [...own.keys()].filter((p) => p !== "AGENT.md"));
|
|
26520
|
+
if ("problem" in parsed) return fail(from, parsed.problem.replace(`${from}: `, ""));
|
|
26521
|
+
if (parsed.agent.name !== name) return fail(from, `calls itself \`${parsed.agent.name}\`; it must match its folder`);
|
|
26522
|
+
agents.set(name, parsed.agent);
|
|
26523
|
+
}
|
|
26524
|
+
const named2 = [
|
|
26525
|
+
...def.previews.map((h) => [h.agent, "it previews with"]),
|
|
26526
|
+
...def.land.filter((h) => !LAND_ROLES2.includes(h.agent)).map((h) => [h.agent, "it lands with"]),
|
|
26527
|
+
...def.scheduled.map((s) => [s.agent, "it schedules"])
|
|
26528
|
+
];
|
|
26529
|
+
for (const [name, why] of named2) {
|
|
26530
|
+
if (!agents.has(name)) return fail(WORKFLOW_FILE2, `${why} \`${name}\`, which has no agents/${name}/AGENT.md`);
|
|
26531
|
+
}
|
|
26532
|
+
return { ok: true, def, files: value };
|
|
26533
|
+
}
|
|
26534
|
+
|
|
26535
|
+
// ../cli/src/commands/cloud-import.ts
|
|
26536
|
+
init_bundled();
|
|
26537
|
+
|
|
26538
|
+
// ../cli/src/lib/agents/shared-memory.ts
|
|
26539
|
+
init_bundled();
|
|
26540
|
+
init_parse();
|
|
26541
|
+
import fs72 from "node:fs";
|
|
26542
|
+
import path69 from "node:path";
|
|
26543
|
+
function sharedMemory(board2) {
|
|
26544
|
+
const declared = /* @__PURE__ */ new Map();
|
|
26545
|
+
const take = (folder, text5, builtIn) => {
|
|
26546
|
+
if (text5 === null || declared.has(folder)) return;
|
|
26547
|
+
const read6 = parseSpecAgent(text5, folder, () => null, builtIn);
|
|
26548
|
+
if ("agent" in read6 && read6.agent.name === folder) declared.set(folder, read6.agent.memoryFiles.workspace);
|
|
26549
|
+
};
|
|
26550
|
+
for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
|
|
26551
|
+
const at = /^([^/]+)\/AGENT\.md$/.exec(key);
|
|
26552
|
+
if (at) take(at[1], text5, true);
|
|
26553
|
+
}
|
|
26554
|
+
const root = path69.join(board2, "agents");
|
|
26555
|
+
let folders2 = [];
|
|
26556
|
+
try {
|
|
26557
|
+
folders2 = fs72.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
26558
|
+
} catch {
|
|
26559
|
+
}
|
|
26560
|
+
for (const folder of folders2.sort()) {
|
|
26561
|
+
let text5 = null;
|
|
26562
|
+
try {
|
|
26563
|
+
text5 = fs72.readFileSync(path69.join(root, folder, "AGENT.md"), "utf8");
|
|
26564
|
+
} catch {
|
|
26565
|
+
}
|
|
26566
|
+
take(folder, text5, false);
|
|
26567
|
+
}
|
|
26568
|
+
const matches = [...declared].flatMap(([agent, paths]) => paths.map((p) => matcher(`agents/${agent}/${p}`)));
|
|
26569
|
+
return (name) => matches.some((m) => m(name));
|
|
26570
|
+
}
|
|
26571
|
+
function matcher(entry) {
|
|
26572
|
+
if (entry.endsWith("/")) return (name) => name.startsWith(entry);
|
|
26573
|
+
const re = new RegExp(`^${entry.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
|
|
26574
|
+
return (name) => re.test(name);
|
|
26575
|
+
}
|
|
26576
|
+
|
|
26577
|
+
// ../cli/src/commands/cloud-import.ts
|
|
26578
|
+
init_workflows();
|
|
26579
|
+
init_cards();
|
|
26580
|
+
init_config2();
|
|
26581
|
+
init_tasks();
|
|
26582
|
+
init_session();
|
|
26583
|
+
init_frontmatter();
|
|
26584
|
+
init_io();
|
|
26585
|
+
init_paths();
|
|
26586
|
+
init_questions();
|
|
26587
|
+
var BATCH = 50;
|
|
26588
|
+
var TITLE_LIMIT = 200;
|
|
26589
|
+
var APP_URL = "https://app.wildcrane.ai";
|
|
26590
|
+
var BOUNDARY = "<!-- agent -->";
|
|
26591
|
+
var Refused = class extends Error {
|
|
26592
|
+
constructor(message, code) {
|
|
26593
|
+
super(message);
|
|
26594
|
+
this.code = code;
|
|
26595
|
+
}
|
|
26596
|
+
code;
|
|
26597
|
+
};
|
|
26598
|
+
var token2 = "";
|
|
26599
|
+
async function api(method, route2, body, raw) {
|
|
26600
|
+
let res;
|
|
26601
|
+
try {
|
|
26602
|
+
res = await fetch(`${cloudEndpoints().api}${route2}`, {
|
|
26603
|
+
method,
|
|
26604
|
+
headers: {
|
|
26605
|
+
authorization: `Bearer ${token2}`,
|
|
26606
|
+
...raw ? { "content-type": raw.type } : body !== void 0 ? { "content-type": "application/json" } : {}
|
|
26607
|
+
},
|
|
26608
|
+
...raw ? { body: raw.bytes } : body !== void 0 ? { body: JSON.stringify(body) } : {},
|
|
26609
|
+
signal: AbortSignal.timeout(12e4)
|
|
26610
|
+
});
|
|
26611
|
+
} catch (e) {
|
|
26612
|
+
throw new Refused(`Cloud could not be reached: ${e instanceof Error ? e.message : String(e)}`);
|
|
26613
|
+
}
|
|
26614
|
+
const parsed = await res.json().catch(() => ({}));
|
|
26615
|
+
if (!res.ok) throw new Refused(parsed.error?.message ?? `Cloud answered ${res.status}.`, parsed.error?.code);
|
|
26616
|
+
return parsed;
|
|
26617
|
+
}
|
|
26618
|
+
async function all(route2) {
|
|
26619
|
+
const out = [];
|
|
26620
|
+
let after2 = null;
|
|
26621
|
+
do {
|
|
26622
|
+
const sep = route2.includes("?") ? "&" : "?";
|
|
26623
|
+
const page = await api("GET", `${route2}${sep}limit=100${after2 ? `&after=${encodeURIComponent(after2)}` : ""}`);
|
|
26624
|
+
out.push(...page.data);
|
|
26625
|
+
after2 = page.next;
|
|
26626
|
+
} while (after2);
|
|
26627
|
+
return out;
|
|
26628
|
+
}
|
|
26629
|
+
async function findProject(asked) {
|
|
26630
|
+
if (/^prj_/.test(asked)) return api("GET", `/v1/projects/${encodeURIComponent(asked)}`);
|
|
26631
|
+
const workspaces = await all("/v1/workspaces");
|
|
26632
|
+
for (const ws of workspaces) {
|
|
26633
|
+
const found2 = (await all(`/v1/projects?workspace=${encodeURIComponent(ws.id)}`)).find((p) => p.prefix.toLowerCase() === asked.toLowerCase());
|
|
26634
|
+
if (found2) return found2;
|
|
26635
|
+
}
|
|
26636
|
+
throw new Refused(`No project of yours has the id or prefix "${asked}".`, "not_found");
|
|
26637
|
+
}
|
|
26638
|
+
var read = (file) => fs73.readFileSync(file, "utf8");
|
|
26639
|
+
var bytes2 = (text5) => Buffer.byteLength(text5, "utf8");
|
|
26640
|
+
function files(dir) {
|
|
26641
|
+
if (!fs73.existsSync(dir)) return [];
|
|
26642
|
+
const out = [];
|
|
26643
|
+
for (const entry of fs73.readdirSync(dir, { withFileTypes: true })) {
|
|
26644
|
+
if (entry.name.startsWith(".")) continue;
|
|
26645
|
+
const full = path70.join(dir, entry.name);
|
|
26646
|
+
if (entry.isDirectory()) out.push(...files(full));
|
|
26647
|
+
else if (entry.isFile()) out.push(full);
|
|
26648
|
+
}
|
|
26649
|
+
return out;
|
|
26650
|
+
}
|
|
26651
|
+
var posix2 = (p) => p.split(path70.sep).join("/");
|
|
26652
|
+
function openCards() {
|
|
26653
|
+
const out = [];
|
|
26654
|
+
for (const file of walkMd(TODO)) {
|
|
26655
|
+
const rel2 = posix2(path70.relative(TODO, file));
|
|
26656
|
+
if (rel2 === "README.md") continue;
|
|
26657
|
+
const parts = rel2.split("/");
|
|
26658
|
+
const text5 = read(file);
|
|
26659
|
+
const meta = parseFrontmatter(text5).meta;
|
|
26660
|
+
if (!meta) continue;
|
|
26661
|
+
const name = parts[parts.length - 1];
|
|
26662
|
+
const root = name === "root.md" && parts.length === 2;
|
|
26663
|
+
const id = root ? idPrefix(parts[0]) : idPrefix(name);
|
|
26664
|
+
if (id === null) continue;
|
|
26665
|
+
const group = !root && parts.length === 2 ? idPrefix(parts[0]) : null;
|
|
26666
|
+
const slug2 = (root ? parts[0] : name.replace(/\.md$/, "")).replace(/^\d+-?/, "");
|
|
26667
|
+
out.push({ id, rel: rel2, text: text5, meta, group, root, slug: slug2 });
|
|
26668
|
+
}
|
|
26669
|
+
return out.sort((a, b) => a.id - b.id);
|
|
26670
|
+
}
|
|
26671
|
+
function bodyProblems(text5) {
|
|
26672
|
+
const out = [];
|
|
26673
|
+
if (bytes2(text5) > BOARD_FILE_LIMIT) out.push({ zh: "\u8D85\u8FC7 256 KB", en: "over 256 KB" });
|
|
26674
|
+
let fence = null;
|
|
26675
|
+
let boundaries = 0;
|
|
26676
|
+
for (const line2 of text5.split("\n")) {
|
|
26677
|
+
const mark = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
|
|
26678
|
+
const run2 = mark?.[1] ?? "";
|
|
26679
|
+
if (fence) {
|
|
26680
|
+
if (run2[0] === fence.char && run2.length >= fence.size && !mark?.[2]?.trim()) fence = null;
|
|
26681
|
+
} else if (run2) fence = { char: run2[0], size: run2.length };
|
|
26682
|
+
else if (line2.trim() === BOUNDARY) boundaries++;
|
|
26683
|
+
}
|
|
26684
|
+
if (fence) out.push({ zh: "\u4EE3\u7801\u5757\u672A\u95ED\u5408", en: "a code fence is never closed" });
|
|
26685
|
+
if (boundaries > 1) out.push({ zh: `\`${BOUNDARY}\` \u51FA\u73B0\u591A\u4E8E\u4E00\u6B21`, en: `holds \`${BOUNDARY}\` more than once` });
|
|
26686
|
+
return out;
|
|
26687
|
+
}
|
|
26688
|
+
function packWorkflow(flow) {
|
|
26689
|
+
const own = path70.join(WORKFLOWS, flow.id);
|
|
26690
|
+
const out = {};
|
|
26691
|
+
for (const file of files(own)) out[posix2(path70.relative(own, file))] = read(file);
|
|
26692
|
+
const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
|
|
26693
|
+
for (const agent of new Set(named2)) {
|
|
26694
|
+
const dir = path70.join(AGENTS, agent);
|
|
26695
|
+
if (fs73.existsSync(path70.join(dir, "AGENT.md"))) {
|
|
26696
|
+
for (const file of files(dir)) out[`agents/${agent}/${posix2(path70.relative(dir, file))}`] = read(file);
|
|
26697
|
+
continue;
|
|
26698
|
+
}
|
|
26699
|
+
for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
|
|
26700
|
+
if (key.startsWith(`${agent}/`)) out[`agents/${key}`] = text5;
|
|
26701
|
+
}
|
|
26702
|
+
}
|
|
26703
|
+
return out;
|
|
26704
|
+
}
|
|
26705
|
+
var builtinId = (id) => `wf_builtin_${id.replace(/-/g, "_")}`;
|
|
26706
|
+
function rewriteRefs(text5, numbers) {
|
|
26707
|
+
const swap = (s) => s.replace(/(^|[^\w#&/])#(\d+)\b/g, (_, lead, n) => {
|
|
26708
|
+
const to = numbers.get(Number(n));
|
|
26709
|
+
return to === void 0 ? `${lead}old#${n}` : `${lead}#${to}`;
|
|
26710
|
+
});
|
|
26711
|
+
let fence = null;
|
|
26712
|
+
return text5.split("\n").map((line2) => {
|
|
26713
|
+
const mark = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
|
|
26714
|
+
const run2 = mark?.[1] ?? "";
|
|
26715
|
+
if (fence) {
|
|
26716
|
+
if (run2[0] === fence.char && run2.length >= fence.size && !mark?.[2]?.trim()) fence = null;
|
|
26717
|
+
return line2;
|
|
26718
|
+
}
|
|
26719
|
+
if (run2) {
|
|
26720
|
+
fence = { char: run2[0], size: run2.length };
|
|
26721
|
+
return line2;
|
|
26722
|
+
}
|
|
26723
|
+
return line2.split(/(`+[^`]*`+)/).map((part, i) => i % 2 ? part : swap(part)).join("");
|
|
26724
|
+
}).join("\n");
|
|
26725
|
+
}
|
|
26726
|
+
function cardForCloud(text5, numbers, workflow) {
|
|
26727
|
+
const end2 = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/.exec(text5)?.[0].length ?? 0;
|
|
26728
|
+
const front = end2 ? text5.slice(0, end2) : "";
|
|
26729
|
+
const lines = rewriteRefs(front, numbers).split("\n").map((line2) => {
|
|
26730
|
+
const list = /^(blocked_by|related):\s*\[(.*)\]\s*$/.exec(line2);
|
|
26731
|
+
if (list) {
|
|
26732
|
+
const kept = list[2].split(",").map((s) => numbers.get(Number(s.trim()))).filter((n) => n !== void 0);
|
|
26733
|
+
return `${list[1]}: [${kept.join(", ")}]`;
|
|
26734
|
+
}
|
|
26735
|
+
if (/^status:\s*implementing\s*$/.test(line2)) return "status: ready";
|
|
26736
|
+
if (workflow && /^workflow:/.test(line2)) return `workflow: ${workflow}`;
|
|
26737
|
+
return line2;
|
|
26738
|
+
});
|
|
26739
|
+
return lines.join("\n") + rewriteRefs(text5.slice(end2), numbers);
|
|
26740
|
+
}
|
|
26741
|
+
function reviewOf2(meta) {
|
|
26742
|
+
const questions = meta.questions ?? [];
|
|
26743
|
+
let open3 = false;
|
|
26744
|
+
for (const [i, q] of questions.entries()) {
|
|
26745
|
+
const { tag, text: text5 } = parseQuestion(q.text);
|
|
26746
|
+
if (tag !== "user" || !openOf([q]).length) continue;
|
|
26747
|
+
open3 = true;
|
|
26748
|
+
if (q.options?.length) return { question: text5, options: q.options.slice(0, 8), position: i + 1 };
|
|
26749
|
+
}
|
|
26750
|
+
return open3 || meta.status === "ready" || meta.status === "implementing" ? true : null;
|
|
26751
|
+
}
|
|
26752
|
+
function draftsOf(text5) {
|
|
26753
|
+
const out = /* @__PURE__ */ new Map();
|
|
26754
|
+
for (const m of text5.matchAll(/\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)|\bsrc=["']([^"']+)["']/g)) {
|
|
26755
|
+
const name = m[1] ?? m[2];
|
|
26756
|
+
const at = /^\.(assets|mockups)\/(.+)$/.exec(name);
|
|
26757
|
+
if (!at) continue;
|
|
26758
|
+
const base = at[1] === "assets" ? ASSETS : MOCKUPS;
|
|
26759
|
+
const file = path70.resolve(base, at[2]);
|
|
26760
|
+
if (file.startsWith(base + path70.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
|
|
26761
|
+
}
|
|
26762
|
+
return [...out].map(([name, file]) => ({ name, file }));
|
|
26763
|
+
}
|
|
26764
|
+
var TYPES = {
|
|
26765
|
+
".png": "image/png",
|
|
26766
|
+
".jpg": "image/jpeg",
|
|
26767
|
+
".jpeg": "image/jpeg",
|
|
26768
|
+
".gif": "image/gif",
|
|
26769
|
+
".webp": "image/webp",
|
|
26770
|
+
".svg": "image/svg+xml",
|
|
26771
|
+
".mp4": "video/mp4",
|
|
26772
|
+
".webm": "video/webm",
|
|
26773
|
+
".mp3": "audio/mpeg",
|
|
26774
|
+
".pdf": "application/pdf",
|
|
26775
|
+
".html": "text/html",
|
|
26776
|
+
".md": "text/markdown",
|
|
26777
|
+
".mov": "video/quicktime",
|
|
26778
|
+
".wav": "audio/wav",
|
|
26779
|
+
".json": "application/json"
|
|
26780
|
+
};
|
|
26781
|
+
function kindOf(ext) {
|
|
26782
|
+
if (ext === ".html") return "html";
|
|
26783
|
+
if (ext === ".md" || ext === ".mdx") return "markdown";
|
|
26784
|
+
const type = TYPES[ext] ?? "";
|
|
26785
|
+
if (type.startsWith("image/")) return "image";
|
|
26786
|
+
return type.startsWith("video/") || type.startsWith("audio/") ? "video" : "asset";
|
|
26787
|
+
}
|
|
26788
|
+
async function uploadDrafts(task, drafts) {
|
|
26789
|
+
const card = await api("GET", `/v1/tasks/${task}/card`);
|
|
26790
|
+
if (Object.keys(card.assets ?? {}).length) return 0;
|
|
26791
|
+
const assets = {};
|
|
26792
|
+
for (const { name, file } of drafts) {
|
|
26793
|
+
if (!card.body.includes(name)) continue;
|
|
26794
|
+
const ext = path70.extname(file).toLowerCase();
|
|
26795
|
+
const type = TYPES[ext] ?? "application/octet-stream";
|
|
26796
|
+
const data = fs73.readFileSync(file);
|
|
26797
|
+
const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
|
|
26798
|
+
const upload = await api("POST", `/v1/artifacts/${artifact.id}/uploads`, { content_type: type, size: Math.max(data.length, 1) });
|
|
26799
|
+
await api("PUT", `/v1/uploads/${upload.id}`, void 0, { bytes: data, type });
|
|
26800
|
+
const version = await api("POST", `/v1/artifacts/${artifact.id}/versions`, { upload: upload.id });
|
|
26801
|
+
assets[name] = { artifact: artifact.id, version: version.version };
|
|
26802
|
+
}
|
|
26803
|
+
if (!Object.keys(assets).length) return 0;
|
|
26804
|
+
await api("PUT", `/v1/tasks/${task}/card`, { body: card.body, base_version: card.version, assets });
|
|
26805
|
+
return Object.keys(assets).length;
|
|
26806
|
+
}
|
|
26807
|
+
function stop(lines) {
|
|
26808
|
+
die(lines.join("\n"), { kind: "cloud-refused", bare: true });
|
|
26809
|
+
}
|
|
26810
|
+
async function cloudImport(opts, program2) {
|
|
26811
|
+
if (!opts.project) {
|
|
26812
|
+
die(`Missing --project.
|
|
26813
|
+
usage: ${program2} cloud import --project <id or prefix> [--dry-run]`, { kind: "bad-args", bare: true, exitCode: 2 });
|
|
26814
|
+
}
|
|
26815
|
+
const held4 = await accessToken();
|
|
26816
|
+
if (!held4.ok) {
|
|
26817
|
+
stop(held4.reason === "unreachable" ? ["\u65E0\u6CD5\u8FDE\u63A5 WildCrane Cloud\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002", "Could not reach WildCrane Cloud. Check your connection and try again."] : [
|
|
26818
|
+
"\u672A\u767B\u5F55 WildCrane Cloud\u3002\u8BF7\u5728 WildCrane \u5E94\u7528\u7684\u201C\u914D\u7F6E \u2192 Cloud\u201D\u4E2D\u767B\u5F55\u540E\u91CD\u8BD5\u3002",
|
|
26819
|
+
"Not signed in to WildCrane Cloud. Sign in from Configuration \u2192 Cloud in the WildCrane app, then try again."
|
|
26820
|
+
]);
|
|
26821
|
+
}
|
|
26822
|
+
token2 = held4.token;
|
|
26823
|
+
try {
|
|
26824
|
+
return await run(opts.project, !!opts.dryRun);
|
|
26825
|
+
} catch (e) {
|
|
26826
|
+
if (!(e instanceof Refused)) throw e;
|
|
26827
|
+
stop([
|
|
26828
|
+
"\u5BFC\u5165\u5DF2\u505C\u6B62\uFF0CCloud \u62D2\u7EDD\u4E86\u8BF7\u6C42\uFF1A",
|
|
26829
|
+
"The import stopped; Cloud refused it:",
|
|
26830
|
+
` ${e.message}`,
|
|
26831
|
+
"\u4FEE\u6B63\u540E\u91CD\u65B0\u8FD0\u884C\uFF0C\u5DF2\u5BFC\u5165\u7684\u5185\u5BB9\u4E0D\u4F1A\u91CD\u590D\u3002",
|
|
26832
|
+
"Fix it and run again; what was imported is not imported twice."
|
|
26833
|
+
]);
|
|
26834
|
+
}
|
|
26835
|
+
}
|
|
26836
|
+
async function run(asked, dryRun) {
|
|
26837
|
+
const project = await findProject(asked);
|
|
26838
|
+
const cloudFlows = await all(`/v1/workflows?workspace=${encodeURIComponent(project.workspace)}`);
|
|
26839
|
+
const board2 = crypto3.createHash("sha256").update(fs73.realpathSync(KANBAN)).digest("hex").slice(0, 16);
|
|
26840
|
+
const cards = openCards();
|
|
26841
|
+
const problems = [];
|
|
26842
|
+
const where = (file) => posix2(path70.relative(KANBAN, file));
|
|
26843
|
+
const own = workflows().filter((w) => !w.builtIn);
|
|
26844
|
+
const packed = /* @__PURE__ */ new Map();
|
|
26845
|
+
for (const flow of own) {
|
|
26846
|
+
const files2 = packWorkflow(flow);
|
|
26847
|
+
const checked = checkWorkflowFiles(files2);
|
|
26848
|
+
if (!checked.ok) problems.push({ file: `workflows/${flow.id}`, reason: { zh: `${checked.file} ${checked.reason}`, en: `${checked.file} ${checked.reason}` } });
|
|
26849
|
+
else packed.set(flow.id, files2);
|
|
26850
|
+
}
|
|
26851
|
+
for (const card of cards) {
|
|
26852
|
+
const file = `todo/${card.rel}`;
|
|
26853
|
+
for (const reason of bodyProblems(card.text)) problems.push({ file, reason });
|
|
26854
|
+
if (!card.meta.title.trim()) problems.push({ file, reason: { zh: "\u6CA1\u6709\u6807\u9898", en: "has no title" } });
|
|
26855
|
+
if (bytes2(card.meta.title.trim()) > TITLE_LIMIT) problems.push({ file, reason: { zh: `\u6807\u9898\u8D85\u8FC7 ${TITLE_LIMIT} \u5B57\u8282`, en: `the title is over ${TITLE_LIMIT} bytes` } });
|
|
26856
|
+
const wf = card.meta.workflow || "coding";
|
|
26857
|
+
if (isBuiltinWorkflow(wf)) {
|
|
26858
|
+
if (!cloudFlows.some((w) => w.id === builtinId(wf))) {
|
|
26859
|
+
problems.push({ file, reason: { zh: `workflow \`${wf}\` \u672A\u5728\u8BE5 workspace \u5B89\u88C5\uFF0C\u8BF7\u5148\u5728 Cloud \u7684 Workflows \u9875\u5B89\u88C5`, en: `workflow \`${wf}\` is not installed in this workspace; install it from Workflows in Cloud first` } });
|
|
26860
|
+
}
|
|
26861
|
+
} else if (!workflowById(wf)) {
|
|
26862
|
+
problems.push({ file, reason: { zh: `\u672A\u77E5\u7684 workflow \`${wf}\``, en: `unknown workflow \`${wf}\`` } });
|
|
26863
|
+
}
|
|
26864
|
+
}
|
|
26865
|
+
const shared = sharedMemory(KANBAN);
|
|
26866
|
+
const root = `projects/${project.id}/`;
|
|
26867
|
+
const boardFiles = [];
|
|
26868
|
+
for (const file of [...files(MEMORY), ...files(RULES), CONFIG, MODULES_MD]) {
|
|
26869
|
+
if (!fs73.existsSync(file)) continue;
|
|
26870
|
+
const rel2 = where(file);
|
|
26871
|
+
if (!isBoardPath(rel2)) continue;
|
|
26872
|
+
const content = read(file);
|
|
26873
|
+
if (bytes2(content) > BOARD_FILE_LIMIT) {
|
|
26874
|
+
problems.push({ file: rel2, reason: { zh: "\u8D85\u8FC7 256 KB", en: "over 256 KB" } });
|
|
26875
|
+
continue;
|
|
26876
|
+
}
|
|
26877
|
+
const isShared = rel2.startsWith("memory/") && shared(rel2.slice("memory/".length));
|
|
26878
|
+
boardFiles.push({ local: rel2, path: isShared ? rel2 : root + rel2, content });
|
|
26879
|
+
}
|
|
26880
|
+
const listed2 = await all(
|
|
26881
|
+
`/v1/tasks?project=${project.id}&workspace=${encodeURIComponent(project.workspace)}`
|
|
26882
|
+
);
|
|
26883
|
+
if (listed2.some((t) => t.source_context?.import?.board !== board2)) {
|
|
26884
|
+
problems.push({ file: `${project.name} (${project.prefix})`, reason: { zh: "\u8BE5\u9879\u76EE\u5DF2\u6709\u81EA\u5DF1\u7684\u4EFB\u52A1\uFF0C\u8BF7\u5BFC\u5165\u4E00\u4E2A\u7A7A\u9879\u76EE", en: "this project already has tasks of its own; import into an empty project" } });
|
|
26885
|
+
}
|
|
26886
|
+
const left = leftLocal(own);
|
|
26887
|
+
if (problems.length) {
|
|
26888
|
+
stop([
|
|
26889
|
+
"\u5BFC\u5165\u524D\u68C0\u67E5\u53D1\u73B0\u95EE\u9898\uFF0C\u4EC0\u4E48\u90FD\u6CA1\u6709\u5199\u5165\uFF1A",
|
|
26890
|
+
"The check before importing found problems; nothing was written:",
|
|
26891
|
+
...problems.map((p) => ` ${p.file}: ${p.reason.zh} / ${p.reason.en}`)
|
|
26892
|
+
]);
|
|
26893
|
+
}
|
|
26894
|
+
const reviews = cards.filter((c) => reviewOf2(c.meta));
|
|
26895
|
+
if (dryRun) {
|
|
26896
|
+
const wouldShare = boardFiles.filter((f) => !f.path.startsWith(root)).length;
|
|
26897
|
+
say(`\u9884\u68C0\u901A\u8FC7 \xB7 Check passed \u2014 ${project.name} (${project.prefix})`);
|
|
26898
|
+
say(` \u5361\u7247 Cards: ${cards.length}\uFF08\u5F85\u5BA1 on review: ${reviews.length}\uFF09`);
|
|
26899
|
+
say(` \u81EA\u5EFA workflow Own workflows: ${own.length}`);
|
|
26900
|
+
say(` \u9879\u76EE\u6587\u4EF6 Project files: ${boardFiles.length - wouldShare} \xB7 workspace \u8BB0\u5FC6 Workspace memory: ${wouldShare}`);
|
|
26901
|
+
cards.forEach((c, i) => say(` #${c.id} \u2192 ${project.prefix}-${i + 1} ${c.meta.title}${reviewOf2(c.meta) ? " [\u5F85\u5BA1 review]" : ""}`));
|
|
26902
|
+
sayLeft(left);
|
|
26903
|
+
return { dryRun: true, cards: cards.length, reviews: reviews.length, workflows: own.length, files: boardFiles.length, left };
|
|
26904
|
+
}
|
|
26905
|
+
const cloudOf = /* @__PURE__ */ new Map();
|
|
26906
|
+
for (const flow of own) {
|
|
26907
|
+
const found2 = cloudFlows.find((w) => !w.builtin && w.name === flow.name);
|
|
26908
|
+
const id = found2?.id ?? (await api("POST", "/v1/workflows", { files: packed.get(flow.id), workspace: project.workspace })).id;
|
|
26909
|
+
cloudOf.set(flow.id, id);
|
|
26910
|
+
}
|
|
26911
|
+
const { next } = await api("POST", `/v1/projects/${project.id}/import`, { board: board2, cards: [] });
|
|
26912
|
+
const numbers = /* @__PURE__ */ new Map();
|
|
26913
|
+
for (const t of listed2) {
|
|
26914
|
+
const at = t.source_context?.import;
|
|
26915
|
+
if (at?.board === board2 && typeof at.number === "number") numbers.set(at.number, numberOf(t.key));
|
|
26916
|
+
}
|
|
26917
|
+
let n = next;
|
|
26918
|
+
for (const c of cards) if (!numbers.has(c.id)) numbers.set(c.id, n++);
|
|
26919
|
+
const ruleFile2 = (rel2) => {
|
|
26920
|
+
const at = /^rules\/workflows\/([^/]+)\.md$/.exec(rel2);
|
|
26921
|
+
const id = at && cloudOf.get(at[1]);
|
|
26922
|
+
return id ? `rules/workflows/${workflowFolder(id)}.md` : rel2;
|
|
26923
|
+
};
|
|
26924
|
+
const sent = boardFiles.map((f) => ({ path: f.path.startsWith(root) ? root + ruleFile2(f.local) : f.path, content: f.content }));
|
|
26925
|
+
const pathOf = (c) => {
|
|
26926
|
+
const at = numbers.get(c.id);
|
|
26927
|
+
if (c.root) return `todo/${at}-${c.slug || "group"}/root.md`;
|
|
26928
|
+
const group = c.group === null ? null : cards.find((x) => x.root && x.id === c.group);
|
|
26929
|
+
const name = `${at}-${c.slug || "task"}.md`;
|
|
26930
|
+
return group ? `todo/${numbers.get(group.id)}-${group.slug || "group"}/${name}` : `todo/${name}`;
|
|
26931
|
+
};
|
|
26932
|
+
const made = [];
|
|
26933
|
+
const kept = [];
|
|
26934
|
+
for (let i = 0; i < Math.max(cards.length, 1); i += BATCH) {
|
|
26935
|
+
const batch = cards.slice(i, i + BATCH).map((c) => {
|
|
26936
|
+
const wf = c.meta.workflow || "coding";
|
|
26937
|
+
const cloud = cloudOf.get(wf);
|
|
26938
|
+
const review = reviewOf2(c.meta);
|
|
26939
|
+
return {
|
|
26940
|
+
ref: c.id,
|
|
26941
|
+
number: numbers.get(c.id),
|
|
26942
|
+
title: c.meta.title.trim(),
|
|
26943
|
+
workflow: cloud ?? builtinId(wf),
|
|
26944
|
+
path: pathOf(c),
|
|
26945
|
+
body: cardForCloud(c.text, numbers, cloud ? workflowFolder(cloud) : null),
|
|
26946
|
+
...review ? { review } : {}
|
|
26947
|
+
};
|
|
26948
|
+
});
|
|
26949
|
+
const done2 = await api(
|
|
26950
|
+
"POST",
|
|
26951
|
+
`/v1/projects/${project.id}/import`,
|
|
26952
|
+
{ board: board2, cards: batch, ...i === 0 ? { files: sent } : {} }
|
|
26953
|
+
);
|
|
26954
|
+
made.push(...done2.cards);
|
|
26955
|
+
kept.push(...done2.kept);
|
|
26956
|
+
}
|
|
26957
|
+
let drafts = 0;
|
|
26958
|
+
for (const c of reviews) {
|
|
26959
|
+
const task = made.find((m) => m.ref === c.id)?.task;
|
|
26960
|
+
const named2 = draftsOf(c.text);
|
|
26961
|
+
if (task && named2.length) drafts += await uploadDrafts(task, named2);
|
|
26962
|
+
}
|
|
26963
|
+
const memory = sent.filter((f) => /(^|\/)memory\//.test(f.path)).length - kept.length;
|
|
26964
|
+
say(`\u5DF2\u5BFC\u5165\u5230 ${project.name} \xB7 Imported into ${project.name} (${project.prefix})`);
|
|
26965
|
+
say(` \u5361\u7247 Cards: ${made.length}\uFF08\u5F85\u5BA1 on review: ${reviews.length}\uFF09`);
|
|
26966
|
+
say(` \u81EA\u5EFA workflow Own workflows: ${cloudOf.size}`);
|
|
26967
|
+
say(` \u8BB0\u5FC6 Memory files: ${memory}${drafts ? ` \xB7 \u8349\u7A3F Drafts: ${drafts}` : ""}`);
|
|
26968
|
+
if (kept.length) {
|
|
26969
|
+
say(" \u8FD9\u4E9B workspace \u8BB0\u5FC6 Cloud \u5DF2\u6709\uFF0C\u4FDD\u7559\u4E86 Cloud \u7684\u7248\u672C \xB7 Kept Cloud\u2019s copy of these workspace memory files:");
|
|
26970
|
+
for (const k of kept) say(` ${k}`);
|
|
26971
|
+
}
|
|
26972
|
+
say(` ${APP_URL}/projects/${project.id}`);
|
|
26973
|
+
sayLeft(left);
|
|
26974
|
+
say("\u672C\u5730\u770B\u677F\u5DF2\u4E0D\u518D\u751F\u6548\uFF0C\u6838\u5BF9\u65E0\u8BEF\u540E\u8BF7\u81EA\u884C\u5220\u9664 `docs/kanban/`\u3002");
|
|
26975
|
+
say("This local board is no longer used. Once you have checked Cloud, delete `docs/kanban/` yourself.");
|
|
26976
|
+
return { project: project.id, cards: made, workflows: [...cloudOf.values()], memory, drafts, kept, left };
|
|
26977
|
+
}
|
|
26978
|
+
function leftLocal(own) {
|
|
26979
|
+
const out = [];
|
|
26980
|
+
const count4 = (dir) => fs73.existsSync(dir) ? walkMd(dir).length : 0;
|
|
26981
|
+
const archived = count4(ARCHIVE);
|
|
26982
|
+
if (archived) out.push(`.archive/ (${archived})`);
|
|
26983
|
+
const triage = count4(TRIAGE);
|
|
26984
|
+
if (triage) out.push(`triage/ (${triage})`);
|
|
26985
|
+
if (fs73.existsSync(DELIVERIES)) out.push("deliveries/");
|
|
26986
|
+
if (fs73.existsSync(path70.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
|
|
26987
|
+
const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
|
|
26988
|
+
if (fs73.existsSync(AGENTS)) {
|
|
26989
|
+
for (const e of fs73.readdirSync(AGENTS, { withFileTypes: true })) {
|
|
26990
|
+
if (e.isDirectory() && !e.name.startsWith(".") && !named2.has(e.name)) out.push(`agents/${e.name}/`);
|
|
26991
|
+
}
|
|
26992
|
+
}
|
|
26993
|
+
return out;
|
|
26994
|
+
}
|
|
26995
|
+
function sayLeft(left) {
|
|
26996
|
+
if (!left.length) return;
|
|
26997
|
+
say(" \u7559\u5728\u672C\u5730\u3001\u672A\u5BFC\u5165 \xB7 Left on this computer, not imported:");
|
|
26998
|
+
for (const l of left) say(` ${l}`);
|
|
26999
|
+
}
|
|
27000
|
+
|
|
26392
27001
|
// ../cli/src/commands/cloud-template.ts
|
|
26393
27002
|
init_config2();
|
|
26394
27003
|
init_session();
|
|
26395
27004
|
init_io();
|
|
26396
27005
|
init_paths();
|
|
26397
27006
|
import { createHash as createHash4 } from "node:crypto";
|
|
26398
|
-
import
|
|
26399
|
-
import
|
|
27007
|
+
import fs74 from "node:fs";
|
|
27008
|
+
import path71 from "node:path";
|
|
26400
27009
|
var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
26401
27010
|
var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
26402
27011
|
var MAX_BYTES3 = 50 * 1024 * 1024;
|
|
@@ -26429,13 +27038,13 @@ function invalid(zh, en) {
|
|
|
26429
27038
|
refuse2({ zh: `\u6A21\u677F\u672A\u901A\u8FC7\u6821\u9A8C\uFF1A${zh}`, en: `The template is not valid: ${en}` });
|
|
26430
27039
|
}
|
|
26431
27040
|
async function ask(route2, init = {}, fallback) {
|
|
26432
|
-
const
|
|
26433
|
-
if (!
|
|
27041
|
+
const token3 = await accessToken();
|
|
27042
|
+
if (!token3.ok) refuse2(SAID[token3.reason], fallback);
|
|
26434
27043
|
let res;
|
|
26435
27044
|
try {
|
|
26436
27045
|
res = await fetch(`${cloudEndpoints().api}/v1/templates${route2}`, {
|
|
26437
27046
|
...init,
|
|
26438
|
-
headers: { authorization: `Bearer ${
|
|
27047
|
+
headers: { authorization: `Bearer ${token3.token}` }
|
|
26439
27048
|
});
|
|
26440
27049
|
} catch {
|
|
26441
27050
|
refuse2(SAID.unreachable, fallback);
|
|
@@ -26494,11 +27103,11 @@ async function templatePreview(name, opts, program2) {
|
|
|
26494
27103
|
This template has no preview.`);
|
|
26495
27104
|
const written = [];
|
|
26496
27105
|
for (const p of previews) {
|
|
26497
|
-
const
|
|
26498
|
-
const file =
|
|
26499
|
-
const out =
|
|
26500
|
-
|
|
26501
|
-
|
|
27106
|
+
const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
|
|
27107
|
+
const file = path71.basename(p.path);
|
|
27108
|
+
const out = path71.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
|
|
27109
|
+
fs74.mkdirSync(opts.to, { recursive: true });
|
|
27110
|
+
fs74.writeFileSync(out, bytes3);
|
|
26502
27111
|
written.push(out);
|
|
26503
27112
|
say(out);
|
|
26504
27113
|
}
|
|
@@ -26511,15 +27120,15 @@ async function templateGet(name, opts, program2) {
|
|
|
26511
27120
|
const { template } = await asJson(await ask(`/${at}`, {}, WITHOUT));
|
|
26512
27121
|
const fetched = [];
|
|
26513
27122
|
for (const f of template.files) {
|
|
26514
|
-
const out =
|
|
26515
|
-
if (!out.startsWith(
|
|
26516
|
-
const
|
|
26517
|
-
if (
|
|
27123
|
+
const out = path71.resolve(to, f.path);
|
|
27124
|
+
if (!out.startsWith(path71.resolve(to) + path71.sep)) refuse2(T.corrupt, WITHOUT);
|
|
27125
|
+
const bytes3 = Buffer.from(await (await ask(`/${at}/files/${route(f.path)}`, {}, WITHOUT)).arrayBuffer());
|
|
27126
|
+
if (bytes3.byteLength !== f.size || createHash4("sha256").update(bytes3).digest("hex") !== f.sha256) {
|
|
26518
27127
|
refuse2(T.corrupt, WITHOUT);
|
|
26519
27128
|
}
|
|
26520
|
-
fetched.push({ out, bytes });
|
|
27129
|
+
fetched.push({ out, bytes: bytes3 });
|
|
26521
27130
|
}
|
|
26522
|
-
const taken = fetched.filter((f) =>
|
|
27131
|
+
const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path71.relative(to, f.out));
|
|
26523
27132
|
if (taken.length > 0) {
|
|
26524
27133
|
refuse2({
|
|
26525
27134
|
zh: `\u76EE\u6807\u6587\u4EF6\u5939\u5DF2\u6709\u540C\u540D\u6587\u4EF6\uFF1A${taken.join("\u3001")}\u3002\u8BF7\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\uFF0C\u6216\u5148\u79FB\u8D70\u8FD9\u4E9B\u6587\u4EF6\u3002`,
|
|
@@ -26527,8 +27136,8 @@ async function templateGet(name, opts, program2) {
|
|
|
26527
27136
|
});
|
|
26528
27137
|
}
|
|
26529
27138
|
for (const f of fetched) {
|
|
26530
|
-
|
|
26531
|
-
|
|
27139
|
+
fs74.mkdirSync(path71.dirname(f.out), { recursive: true });
|
|
27140
|
+
fs74.writeFileSync(f.out, f.bytes);
|
|
26532
27141
|
}
|
|
26533
27142
|
say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
|
|
26534
27143
|
Fetched ${name}: ${fetched.length} files into ${to}`);
|
|
@@ -26546,25 +27155,25 @@ async function templateSource(name, opts) {
|
|
|
26546
27155
|
say(`evidence: ${e.path}`);
|
|
26547
27156
|
continue;
|
|
26548
27157
|
}
|
|
26549
|
-
const out =
|
|
26550
|
-
|
|
26551
|
-
|
|
27158
|
+
const out = path71.join(opts.to, `${name}-${path71.basename(e.path)}`);
|
|
27159
|
+
fs74.mkdirSync(opts.to, { recursive: true });
|
|
27160
|
+
fs74.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
|
|
26552
27161
|
written.push(out);
|
|
26553
27162
|
say(out);
|
|
26554
27163
|
}
|
|
26555
27164
|
return { name, source, evidence: written.length > 0 ? written : evidence.map((e) => e.path) };
|
|
26556
27165
|
}
|
|
26557
27166
|
async function templatePublish(dir, opts) {
|
|
26558
|
-
const root =
|
|
26559
|
-
const manifest =
|
|
26560
|
-
if (!
|
|
26561
|
-
const meta = readTemplateMeta(
|
|
26562
|
-
const
|
|
26563
|
-
const total =
|
|
27167
|
+
const root = path71.resolve(dir);
|
|
27168
|
+
const manifest = path71.join(root, "template.md");
|
|
27169
|
+
if (!fs74.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
|
|
27170
|
+
const meta = readTemplateMeta(fs74.readFileSync(manifest, "utf8"));
|
|
27171
|
+
const files2 = walk2(root);
|
|
27172
|
+
const total = files2.reduce((sum, f) => sum + fs74.statSync(path71.join(root, f)).size, 0);
|
|
26564
27173
|
if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
|
|
26565
27174
|
for (const extra of [...opts.preview, ...opts.evidence]) {
|
|
26566
|
-
if (!
|
|
26567
|
-
if (
|
|
27175
|
+
if (!fs74.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
|
|
27176
|
+
if (path71.resolve(extra).startsWith(root + path71.sep)) {
|
|
26568
27177
|
invalid(
|
|
26569
27178
|
`${extra} \u5728\u6A21\u677F\u6587\u4EF6\u5939\u5185\uFF1B\u9884\u89C8\u56FE\u4E0E\u6765\u6E90\u51ED\u8BC1\u9700\u653E\u5728\u6A21\u677F\u6587\u4EF6\u5939\u4E4B\u5916\u3002`,
|
|
26570
27179
|
`${extra} is inside the template folder; keep previews and evidence outside it.`
|
|
@@ -26576,17 +27185,17 @@ async function templatePublish(dir, opts) {
|
|
|
26576
27185
|
}
|
|
26577
27186
|
const form = new FormData();
|
|
26578
27187
|
form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
|
|
26579
|
-
const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([
|
|
26580
|
-
for (const f of
|
|
26581
|
-
for (const f of opts.preview) add("previews",
|
|
26582
|
-
for (const f of opts.evidence) add("evidence",
|
|
27188
|
+
const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path71.basename(name));
|
|
27189
|
+
for (const f of files2) add("files", f, path71.join(root, f));
|
|
27190
|
+
for (const f of opts.preview) add("previews", path71.basename(f), f);
|
|
27191
|
+
for (const f of opts.evidence) add("evidence", path71.basename(f), f);
|
|
26583
27192
|
const done2 = await asJson(await ask("", { method: "POST", body: form }));
|
|
26584
27193
|
say(
|
|
26585
|
-
done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${
|
|
26586
|
-
Replaced ${done2.name}: ${
|
|
26587
|
-
Published ${done2.name}: ${
|
|
27194
|
+
done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
|
|
27195
|
+
Replaced ${done2.name}: ${files2.length} files.` : `\u5DF2\u53D1\u5E03 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
|
|
27196
|
+
Published ${done2.name}: ${files2.length} files.`
|
|
26588
27197
|
);
|
|
26589
|
-
return { name: done2.name, replaced: done2.replaced, files };
|
|
27198
|
+
return { name: done2.name, replaced: done2.replaced, files: files2 };
|
|
26590
27199
|
}
|
|
26591
27200
|
async function templateDelete(name) {
|
|
26592
27201
|
await ask(`/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
@@ -26595,11 +27204,11 @@ Deleted ${name} from the catalog.`);
|
|
|
26595
27204
|
return { name, deleted: true };
|
|
26596
27205
|
}
|
|
26597
27206
|
async function templateShowcase(name, dir) {
|
|
26598
|
-
const file =
|
|
26599
|
-
if (!
|
|
27207
|
+
const file = path71.join(dir, "showcase.json");
|
|
27208
|
+
if (!fs74.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
|
|
26600
27209
|
let meta;
|
|
26601
27210
|
try {
|
|
26602
|
-
meta = JSON.parse(
|
|
27211
|
+
meta = JSON.parse(fs74.readFileSync(file, "utf8"));
|
|
26603
27212
|
} catch {
|
|
26604
27213
|
return invalid("showcase.json \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002", "showcase.json is not valid JSON.");
|
|
26605
27214
|
}
|
|
@@ -26611,9 +27220,9 @@ async function templateShowcase(name, dir) {
|
|
|
26611
27220
|
const form = new FormData();
|
|
26612
27221
|
form.set("meta", JSON.stringify(meta));
|
|
26613
27222
|
for (const p of new Set(named2)) {
|
|
26614
|
-
const at =
|
|
26615
|
-
if (!
|
|
26616
|
-
form.append(`media:${p}`, new Blob([
|
|
27223
|
+
const at = path71.join(dir, p);
|
|
27224
|
+
if (!fs74.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
|
|
27225
|
+
form.append(`media:${p}`, new Blob([fs74.readFileSync(at)]), p);
|
|
26617
27226
|
}
|
|
26618
27227
|
const done2 = await asJson(await ask(`/${encodeURIComponent(name)}/showcase`, { method: "PUT", body: form }));
|
|
26619
27228
|
say(`\u5DF2\u66F4\u65B0 ${name} \u7684\u5C55\u793A\u8D44\u6599\uFF1A${done2.files} \u4E2A\u6587\u4EF6\u3002
|
|
@@ -26622,7 +27231,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
|
|
|
26622
27231
|
}
|
|
26623
27232
|
function walk2(root, sub = "") {
|
|
26624
27233
|
const out = [];
|
|
26625
|
-
for (const entry of
|
|
27234
|
+
for (const entry of fs74.readdirSync(path71.join(root, sub), { withFileTypes: true })) {
|
|
26626
27235
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
26627
27236
|
const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
|
|
26628
27237
|
if (entry.isDirectory()) out.push(...walk2(root, rel2));
|
|
@@ -26956,7 +27565,7 @@ init_sessions();
|
|
|
26956
27565
|
init_env();
|
|
26957
27566
|
init_store();
|
|
26958
27567
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
26959
|
-
import
|
|
27568
|
+
import path76 from "node:path";
|
|
26960
27569
|
var AGENTS2 = {
|
|
26961
27570
|
claude: "claude-code",
|
|
26962
27571
|
codex: "codex",
|
|
@@ -26971,10 +27580,10 @@ var AGENTS2 = {
|
|
|
26971
27580
|
var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
|
|
26972
27581
|
function agentOf2(args) {
|
|
26973
27582
|
const words = args.trim().split(/\s+/);
|
|
26974
|
-
const first =
|
|
27583
|
+
const first = path76.basename(words[0] ?? "");
|
|
26975
27584
|
const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
|
|
26976
27585
|
if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
|
|
26977
|
-
return AGENTS2[
|
|
27586
|
+
return AGENTS2[path76.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
|
|
26978
27587
|
}
|
|
26979
27588
|
function processChain() {
|
|
26980
27589
|
if (process.platform === "win32") return [];
|
|
@@ -27018,8 +27627,8 @@ function terminalSession(chain, env, opencode, cwd = process.cwd()) {
|
|
|
27018
27627
|
return resumeId ? { harness, resumeId, cwd } : void 0;
|
|
27019
27628
|
}
|
|
27020
27629
|
function currentSession() {
|
|
27021
|
-
const
|
|
27022
|
-
if (
|
|
27630
|
+
const run2 = insideRun();
|
|
27631
|
+
if (run2) return readStore().runs.find((r) => r.sessionId === run2)?.origin;
|
|
27023
27632
|
return terminalSession(probes.chain(), process.env, probes.opencode);
|
|
27024
27633
|
}
|
|
27025
27634
|
|
|
@@ -27035,11 +27644,11 @@ init_tasks();
|
|
|
27035
27644
|
init_read();
|
|
27036
27645
|
init_rules();
|
|
27037
27646
|
init_board2();
|
|
27038
|
-
import
|
|
27039
|
-
import
|
|
27647
|
+
import fs80 from "node:fs";
|
|
27648
|
+
import path77 from "node:path";
|
|
27040
27649
|
var FOLLOW_MS = 400;
|
|
27041
27650
|
async function cmdStartRun(action, args, opts, program2 = "akb") {
|
|
27042
|
-
const { req, follow, print } = readRequest(action, args, opts);
|
|
27651
|
+
const { req, follow: follow2, print } = readRequest(action, args, opts);
|
|
27043
27652
|
const task = action === "create" && req.fromCard !== void 0 ? workspaceTask() : null;
|
|
27044
27653
|
if (task) return await createInCloud(task, req.description, req.workflow);
|
|
27045
27654
|
if (Number.isInteger(req.id)) {
|
|
@@ -27076,13 +27685,13 @@ async function cmdStartRun(action, args, opts, program2 = "akb") {
|
|
|
27076
27685
|
sayIfHeld(req, program2);
|
|
27077
27686
|
const started = action === "refine" ? await startRefinement(req) : await startRun(runnable);
|
|
27078
27687
|
if ("error" in started) die(started.error, { kind: "run-refused", action });
|
|
27079
|
-
const { run, spawned } = started;
|
|
27080
|
-
if (!spawned) die(`couldn't start a process for run ${
|
|
27081
|
-
say(`${action} \u2014 run ${
|
|
27082
|
-
say(` follow it: ${program2} run log ${short(
|
|
27083
|
-
say(` stop it: ${program2} run stop ${short(
|
|
27084
|
-
if (
|
|
27085
|
-
return { sessionId:
|
|
27688
|
+
const { run: run2, spawned } = started;
|
|
27689
|
+
if (!spawned) die(`couldn't start a process for run ${run2.sessionId}`, { kind: "spawn-failed" });
|
|
27690
|
+
say(`${action} \u2014 run ${run2.sessionId}${run2.deliveryId ? ` in delivery ${run2.deliveryId}` : ""}`);
|
|
27691
|
+
say(` follow it: ${program2} run log ${short(run2.sessionId)} --follow${BOARD_FLAG}`);
|
|
27692
|
+
say(` stop it: ${program2} run stop ${short(run2.sessionId)}${BOARD_FLAG}`);
|
|
27693
|
+
if (follow2) return await followStarted(run2.sessionId, program2, opts.events === true);
|
|
27694
|
+
return { sessionId: run2.sessionId, action, cardId: run2.cardId };
|
|
27086
27695
|
}
|
|
27087
27696
|
async function cmdRunScheduled(workflow, agent, opts, program2 = "akb") {
|
|
27088
27697
|
const id = knownWorkflow(workflow);
|
|
@@ -27092,12 +27701,12 @@ async function cmdRunScheduled(workflow, agent, opts, program2 = "akb") {
|
|
|
27092
27701
|
if (insideRun() || opts.print) return printFlow(req, program2);
|
|
27093
27702
|
const started = await startRun(req);
|
|
27094
27703
|
if ("error" in started) die(started.error, { kind: "run-refused", action: "scheduled" });
|
|
27095
|
-
const { run, spawned } = started;
|
|
27096
|
-
if (!spawned) die(`couldn't start a process for run ${
|
|
27097
|
-
say(`${one.agent} \u2014 run ${
|
|
27098
|
-
say(` follow it: ${program2} run log ${short(
|
|
27099
|
-
if (opts.follow) return await followStarted(
|
|
27100
|
-
return { sessionId:
|
|
27704
|
+
const { run: run2, spawned } = started;
|
|
27705
|
+
if (!spawned) die(`couldn't start a process for run ${run2.sessionId}`, { kind: "spawn-failed" });
|
|
27706
|
+
say(`${one.agent} \u2014 run ${run2.sessionId}${run2.deliveryId ? ` in delivery ${run2.deliveryId}` : ""}`);
|
|
27707
|
+
say(` follow it: ${program2} run log ${short(run2.sessionId)} --follow${BOARD_FLAG}`);
|
|
27708
|
+
if (opts.follow) return await followStarted(run2.sessionId, program2, opts.events === true);
|
|
27709
|
+
return { sessionId: run2.sessionId, action: "scheduled" };
|
|
27101
27710
|
}
|
|
27102
27711
|
async function createInCloud(task, description, workflow) {
|
|
27103
27712
|
const title = description.split("\n")[0].trim().slice(0, 120);
|
|
@@ -27163,7 +27772,7 @@ function sayBeforeStart(req, program2) {
|
|
|
27163
27772
|
}
|
|
27164
27773
|
}
|
|
27165
27774
|
function readRequest(action, args, opts) {
|
|
27166
|
-
const
|
|
27775
|
+
const follow2 = opts.follow === true;
|
|
27167
27776
|
const print = opts.print === true;
|
|
27168
27777
|
const words = (at) => {
|
|
27169
27778
|
const value = args[at];
|
|
@@ -27178,28 +27787,28 @@ function readRequest(action, args, opts) {
|
|
|
27178
27787
|
}
|
|
27179
27788
|
return {
|
|
27180
27789
|
req: { action, description: words(0), workflow, runtime: opts.runtime, origin: currentSession(), fromCard },
|
|
27181
|
-
follow,
|
|
27790
|
+
follow: follow2,
|
|
27182
27791
|
print
|
|
27183
27792
|
};
|
|
27184
27793
|
}
|
|
27185
|
-
if (action === "setup") return { req: { action }, follow, print };
|
|
27794
|
+
if (action === "setup") return { req: { action }, follow: follow2, print };
|
|
27186
27795
|
if (action === "review-memory") {
|
|
27187
27796
|
if (!opts.task?.length) die("name the Cloud tasks whose chats to review: --task <id...>", { kind: "run-refused", action });
|
|
27188
|
-
return { req: { action, tasks: opts.task }, follow, print };
|
|
27797
|
+
return { req: { action, tasks: opts.task }, follow: follow2, print };
|
|
27189
27798
|
}
|
|
27190
|
-
if (action === "review-dismissals") return { req: { action, ...opts.all ? { allReasons: true } : {} }, follow, print };
|
|
27799
|
+
if (action === "review-dismissals") return { req: { action, ...opts.all ? { allReasons: true } : {} }, follow: follow2, print };
|
|
27191
27800
|
if (action === "reflect") {
|
|
27192
27801
|
const cards = (opts.cards ?? []).map(Number);
|
|
27193
27802
|
if (!cards.length || !cards.every((n) => Number.isInteger(n) && n > 0)) {
|
|
27194
27803
|
die("name the archived cards to reflect on: --cards <id...>", { kind: "run-refused", action });
|
|
27195
27804
|
}
|
|
27196
|
-
return { req: { action, cards }, follow, print };
|
|
27805
|
+
return { req: { action, cards }, follow: follow2, print };
|
|
27197
27806
|
}
|
|
27198
27807
|
if (action === "conflict") {
|
|
27199
27808
|
const delivery = args[0];
|
|
27200
27809
|
return {
|
|
27201
27810
|
req: { action, id: delivery.cardId ?? void 0, deliveryId: delivery.deliveryId, title: delivery.title },
|
|
27202
|
-
follow,
|
|
27811
|
+
follow: follow2,
|
|
27203
27812
|
print
|
|
27204
27813
|
};
|
|
27205
27814
|
}
|
|
@@ -27210,17 +27819,17 @@ function readRequest(action, args, opts) {
|
|
|
27210
27819
|
if (opts.session || opts.transcript) {
|
|
27211
27820
|
req.carry = { resumeId: opts.session, fork: opts.session ? opts.fork === true : void 0, transcript: opts.transcript };
|
|
27212
27821
|
}
|
|
27213
|
-
return { req, follow, print };
|
|
27822
|
+
return { req, follow: follow2, print };
|
|
27214
27823
|
}
|
|
27215
27824
|
async function cmdResume(id, opts) {
|
|
27216
27825
|
const opened = await startResume(id ?? "last");
|
|
27217
27826
|
if ("error" in opened) die(opened.error, { kind: "run-refused" });
|
|
27218
|
-
const { run } = opened;
|
|
27219
|
-
if (!opened.spawned) die(`couldn't start a process for run ${
|
|
27220
|
-
const within =
|
|
27221
|
-
say(
|
|
27222
|
-
if (opts.follow === true) return await followStarted(
|
|
27223
|
-
return { sessionId:
|
|
27827
|
+
const { run: run2 } = opened;
|
|
27828
|
+
if (!opened.spawned) die(`couldn't start a process for run ${run2.sessionId}`, { kind: "spawn-failed" });
|
|
27829
|
+
const within = run2.deliveryId ? ` in delivery ${run2.deliveryId}` : "";
|
|
27830
|
+
say(run2.resumedFrom ? `continuing ${short(run2.resumedFrom)} \u2014 run ${run2.sessionId}${within}` : `trying again \u2014 run ${run2.sessionId}${within}`);
|
|
27831
|
+
if (opts.follow === true) return await followStarted(run2.sessionId, "akb");
|
|
27832
|
+
return { sessionId: run2.sessionId, resumedFrom: run2.resumedFrom };
|
|
27224
27833
|
}
|
|
27225
27834
|
async function cmdCancel(named2) {
|
|
27226
27835
|
const res = await cancelBoardDelivery(named2);
|
|
@@ -27310,7 +27919,7 @@ async function cmdSubStart(words, opts) {
|
|
|
27310
27919
|
let prompt = words.join(" ");
|
|
27311
27920
|
if (opts.file) {
|
|
27312
27921
|
try {
|
|
27313
|
-
prompt =
|
|
27922
|
+
prompt = fs80.readFileSync(path77.resolve(opts.file), "utf8");
|
|
27314
27923
|
} catch {
|
|
27315
27924
|
die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
|
|
27316
27925
|
}
|
|
@@ -27412,15 +28021,16 @@ async function followRun(sessionId, already = "", program2 = "akb", events = fal
|
|
|
27412
28021
|
let current = first;
|
|
27413
28022
|
let last = first;
|
|
27414
28023
|
let printed = already.length;
|
|
28024
|
+
const idle = { mark: "", since: Date.now() };
|
|
27415
28025
|
for (; ; ) {
|
|
27416
28026
|
last = await followOne(current, printed, events);
|
|
27417
28027
|
seen.add(current.sessionId);
|
|
27418
28028
|
printed = 0;
|
|
27419
28029
|
const deliveryId2 = last.deliveryId ?? first.deliveryId;
|
|
27420
|
-
const next = nextInFlow(first, current.sessionId, seen) ?? (deliveryId2 ? await moveDelivery(deliveryId2) : void 0);
|
|
28030
|
+
const next = nextInFlow(first, current.sessionId, seen) ?? (deliveryId2 ? await moveDelivery(deliveryId2, idle) : void 0);
|
|
27421
28031
|
if (next === void 0) break;
|
|
27422
28032
|
if (next === null) {
|
|
27423
|
-
await pause(
|
|
28033
|
+
await pause(follow.pollMs);
|
|
27424
28034
|
continue;
|
|
27425
28035
|
}
|
|
27426
28036
|
current = next;
|
|
@@ -27440,7 +28050,7 @@ async function followRun(sessionId, already = "", program2 = "akb", events = fal
|
|
|
27440
28050
|
const deliveryId = last.deliveryId ?? first.deliveryId;
|
|
27441
28051
|
const delivery = deliveryId ? deliveryOutcome(deliveryId) : void 0;
|
|
27442
28052
|
if (delivery) say(`delivery ${delivery.id}: ${delivery.line}`);
|
|
27443
|
-
const failed = last.status !== "done" ? `run ${short(last.sessionId)} ended ${last.status}${last.error ? `: ${last.error}` : ""}` : delivery && delivery.status !== "finished" ? `delivery ${delivery.id} did not land: ${delivery.line}` : void 0;
|
|
28053
|
+
const failed = last.status !== "done" ? `run ${short(last.sessionId)} ended ${last.status}${last.error ? `: ${last.error}` : ""}` : delivery && delivery.status !== "finished" ? `delivery ${delivery.id} did not land${idle.timedOut ? ` \u2014 nothing moved it for ${ago(follow.idleMs)}` : ""}: ${delivery.line}` : void 0;
|
|
27444
28054
|
const held4 = peekRun(last.sessionId);
|
|
27445
28055
|
const agentSession = held4 ? resumeSessionId(held4) : void 0;
|
|
27446
28056
|
return {
|
|
@@ -27458,7 +28068,7 @@ async function followStarted(sessionId, program2, events = false) {
|
|
|
27458
28068
|
if (typeof res.failed === "string") die(res.failed, { kind: "run-failed", sessionId, ...res });
|
|
27459
28069
|
return { sessionId, ...res };
|
|
27460
28070
|
}
|
|
27461
|
-
var
|
|
28071
|
+
var follow = { pollMs: 2e3, idleMs: 10 * 6e4 };
|
|
27462
28072
|
var pause = (ms) => new Promise((done2) => setTimeout(done2, ms));
|
|
27463
28073
|
async function followOne(view, already, events) {
|
|
27464
28074
|
let seen = already;
|
|
@@ -27499,11 +28109,20 @@ function nextInFlow(first, after2, seen) {
|
|
|
27499
28109
|
const next = runs.filter((r) => !seen.has(r.sessionId) && r.action !== "sub" && r.startedAt >= first.startedAt).filter((r) => r.resumedFrom === after2 || !!first.flowId && r.flowId === first.flowId).sort((a, b) => a.startedAt - b.startedAt)[0];
|
|
27500
28110
|
return next ? { ...next } : void 0;
|
|
27501
28111
|
}
|
|
27502
|
-
async function moveDelivery(deliveryId) {
|
|
28112
|
+
async function moveDelivery(deliveryId, idle) {
|
|
27503
28113
|
const delivery = findDelivery(deliveryId);
|
|
27504
28114
|
if (!delivery || delivery.status !== "active") return void 0;
|
|
27505
28115
|
const state = deliveryState(delivery, delivery.cardId === null ? 0 : openQuestions(delivery.cardId));
|
|
27506
28116
|
if (state.paused || state.stage === "working") return void 0;
|
|
28117
|
+
const mark = JSON.stringify(delivery);
|
|
28118
|
+
const busy = !!state.retryIn || readRuns().some((r) => !!r.deliveryId && r.status === "running");
|
|
28119
|
+
if (busy || mark !== idle.mark) {
|
|
28120
|
+
idle.mark = mark;
|
|
28121
|
+
idle.since = Date.now();
|
|
28122
|
+
} else if (Date.now() - idle.since >= follow.idleMs) {
|
|
28123
|
+
idle.timedOut = true;
|
|
28124
|
+
return void 0;
|
|
28125
|
+
}
|
|
27507
28126
|
const ask2 = await advanceLanding();
|
|
27508
28127
|
if (!ask2) return null;
|
|
27509
28128
|
const started = await startRun(ask2);
|
|
@@ -27576,7 +28195,7 @@ async function cmdWatch(sessionId) {
|
|
|
27576
28195
|
// ../cli/src/commands/triage.ts
|
|
27577
28196
|
init_scheduled();
|
|
27578
28197
|
init_workflows();
|
|
27579
|
-
import
|
|
28198
|
+
import fs82 from "node:fs";
|
|
27580
28199
|
|
|
27581
28200
|
// ../cli/src/lib/signals/index.ts
|
|
27582
28201
|
init_cards();
|
|
@@ -27586,16 +28205,16 @@ init_yaml();
|
|
|
27586
28205
|
// ../cli/src/lib/signals/config.ts
|
|
27587
28206
|
init_settings2();
|
|
27588
28207
|
init_paths();
|
|
27589
|
-
import
|
|
27590
|
-
import
|
|
28208
|
+
import fs81 from "node:fs";
|
|
28209
|
+
import path78 from "node:path";
|
|
27591
28210
|
var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
|
|
27592
28211
|
var ENDPOINT_SETTING = "Triage endpoint";
|
|
27593
28212
|
var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
|
|
27594
|
-
var boardRel3 = (file) => rel(file).split(
|
|
28213
|
+
var boardRel3 = (file) => rel(file).split(path78.sep).join("/");
|
|
27595
28214
|
function signalEndpoint() {
|
|
27596
28215
|
let text5;
|
|
27597
28216
|
try {
|
|
27598
|
-
text5 =
|
|
28217
|
+
text5 = fs81.readFileSync(CONFIG, "utf8");
|
|
27599
28218
|
} catch {
|
|
27600
28219
|
return "";
|
|
27601
28220
|
}
|
|
@@ -27639,7 +28258,7 @@ function readMeta(sent) {
|
|
|
27639
28258
|
}
|
|
27640
28259
|
return pairs;
|
|
27641
28260
|
}
|
|
27642
|
-
function
|
|
28261
|
+
function read2(raw) {
|
|
27643
28262
|
const missing = REQUIRED.filter((field2) => !text4(raw[field2]));
|
|
27644
28263
|
if (missing.length > 0) return { ok: false, why: `missing ${missing.join(", ")}` };
|
|
27645
28264
|
const sent = text4(raw.collected_at);
|
|
@@ -27715,7 +28334,7 @@ async function fetchSignals() {
|
|
|
27715
28334
|
report3.failed.push({ which: `item ${at + 1}`, why: "not an object" });
|
|
27716
28335
|
return;
|
|
27717
28336
|
}
|
|
27718
|
-
const found2 =
|
|
28337
|
+
const found2 = read2(raw);
|
|
27719
28338
|
if (!found2.ok) {
|
|
27720
28339
|
report3.failed.push({ which: text4(raw.source_id) || `item ${at + 1}`, why: found2.why });
|
|
27721
28340
|
return;
|
|
@@ -27771,7 +28390,7 @@ async function cmdTriageAdd(opts) {
|
|
|
27771
28390
|
let body = opts.text;
|
|
27772
28391
|
if (opts.file !== void 0) {
|
|
27773
28392
|
try {
|
|
27774
|
-
body =
|
|
28393
|
+
body = fs82.readFileSync(opts.file, "utf8");
|
|
27775
28394
|
} catch {
|
|
27776
28395
|
die(`can't read ${opts.file} \u2014 write the item to a file, then pass its path`, { kind: "needs-input" });
|
|
27777
28396
|
}
|
|
@@ -27895,16 +28514,16 @@ async function cmdSpec(opts, program2 = "akb") {
|
|
|
27895
28514
|
}
|
|
27896
28515
|
const started = await startRun(req);
|
|
27897
28516
|
if ("error" in started) die(started.error, { kind: "run-refused", action: "spec" });
|
|
27898
|
-
const { run, spawned } = started;
|
|
27899
|
-
if (!spawned) die(`couldn't start a process to run ${
|
|
28517
|
+
const { run: run2, spawned } = started;
|
|
28518
|
+
if (!spawned) die(`couldn't start a process to run ${run2.sessionId}`, { kind: "spawn-failed" });
|
|
27900
28519
|
if (ownRuntime || ownSession) {
|
|
27901
28520
|
say(`${name} runs in ${ownRuntime ? "its own runtime" : "a session of its own"}, so it started as a separate run \u2014 don't write its section yourself.`);
|
|
27902
28521
|
}
|
|
27903
|
-
say(`spec ${name} #${id} \u2014 run ${
|
|
27904
|
-
say(` follow it: ${program2} run log ${short(
|
|
27905
|
-
say(` stop it: ${program2} run stop ${short(
|
|
27906
|
-
if (opts.follow === true) return await followStarted(
|
|
27907
|
-
return { sessionId:
|
|
28522
|
+
say(`spec ${name} #${id} \u2014 run ${run2.sessionId}`);
|
|
28523
|
+
say(` follow it: ${program2} run log ${short(run2.sessionId)} --follow${BOARD_FLAG}`);
|
|
28524
|
+
say(` stop it: ${program2} run stop ${short(run2.sessionId)}${BOARD_FLAG}`);
|
|
28525
|
+
if (opts.follow === true) return await followStarted(run2.sessionId, program2);
|
|
28526
|
+
return { sessionId: run2.sessionId, action: "spec", specAgent: name, cardId: id };
|
|
27908
28527
|
}
|
|
27909
28528
|
function noteOf(words, flag) {
|
|
27910
28529
|
const typed = words.join(" ").trim();
|
|
@@ -28055,7 +28674,13 @@ function declareRuns(program2, cli) {
|
|
|
28055
28674
|
withShared(cloud.command("sign-out")).summary("forget it: this machine stops reaching Cloud, and nothing on any board changes").action(async function() {
|
|
28056
28675
|
await boardless(this, cli, (p) => cmdCloud(["sign-out"], p));
|
|
28057
28676
|
});
|
|
28058
|
-
withShared(cloud.command("
|
|
28677
|
+
withShared(cloud.command("import")).summary("import this board into an empty Cloud project").description(
|
|
28678
|
+
"Imports the open cards, memory, rules and own workflows of this board into an empty Cloud project, numbered from 1 in the order of their ids. Checks everything first and writes nothing on a problem; changes nothing on this computer. Run again to finish an import that stopped."
|
|
28679
|
+
).option("--project <id-or-prefix>", "the Cloud project, by id or by its prefix").option("--dry-run", "only check, and list what would be imported and what stays here").action(async function() {
|
|
28680
|
+
const o = this.opts();
|
|
28681
|
+
await onBoard2(this, cli, (p) => cloudImport({ project: o.project, dryRun: o.dryRun }, p));
|
|
28682
|
+
});
|
|
28683
|
+
withShared(cloud.command("image")).argument("[prompt...]", "what to draw").summary("generate an image through Cloud, spending Pro credits").option("--aspect <ratio>", "the aspect ratio, e.g. 16:9").option("--ref <file>", "a PNG, JPEG or WebP to draw from; repeat for more", (v, all2) => [...all2, v], []).option("--out <file>", "where to write the image").action(async function(prompt) {
|
|
28059
28684
|
const o = this.opts();
|
|
28060
28685
|
await boardless(
|
|
28061
28686
|
this,
|
|
@@ -28094,7 +28719,7 @@ function declareRuns(program2, cli) {
|
|
|
28094
28719
|
withShared(template.command("source")).argument("<name>", "the template").summary("where a template came from, and the evidence captured at the time").option("--to <dir>", "download the evidence into this folder").action(async function(name) {
|
|
28095
28720
|
await boardless(this, cli, () => templateSource(name, this.opts()));
|
|
28096
28721
|
});
|
|
28097
|
-
withShared(template.command("publish")).argument("<dir>", "the template's folder, holding template.md").summary("publish a template (maintainers only)").option("--preview <file>", "a catalog preview image, kept outside the folder; repeat for more", (v,
|
|
28722
|
+
withShared(template.command("publish")).argument("<dir>", "the template's folder, holding template.md").summary("publish a template (maintainers only)").option("--preview <file>", "a catalog preview image, kept outside the folder; repeat for more", (v, all2) => [...all2, v], []).option("--evidence <file>", "a capture of the reference, kept outside the folder; repeat for more", (v, all2) => [...all2, v], []).option("--replace", "replace the template already published under this name").action(async function(dir) {
|
|
28098
28723
|
const o = this.opts();
|
|
28099
28724
|
await boardless(this, cli, () => templatePublish(dir, o));
|
|
28100
28725
|
});
|
|
@@ -28177,8 +28802,8 @@ function declareRuns(program2, cli) {
|
|
|
28177
28802
|
}
|
|
28178
28803
|
var RUN_ID = "a run's id, any prefix of one that names only one run, or `last`. Left out, it means the newest run";
|
|
28179
28804
|
var DELIVERY = "the delivery, or the card it is on";
|
|
28180
|
-
function declareRunning(
|
|
28181
|
-
const verb = (name) => withShared(
|
|
28805
|
+
function declareRunning(run2, cli) {
|
|
28806
|
+
const verb = (name) => withShared(run2.command(name));
|
|
28182
28807
|
verb("list").summary("what is running, and what ran lately").option("-c, --card <id>", "only the runs on that card", cardId).option("-a, --all", "every run this board has kept, not just the last ten").action(async function() {
|
|
28183
28808
|
await onBoard2(this, cli, (p) => cmdRuns(this.opts(), p));
|
|
28184
28809
|
});
|
|
@@ -28334,8 +28959,8 @@ function declareAgent(program2, cli) {
|
|
|
28334
28959
|
}
|
|
28335
28960
|
|
|
28336
28961
|
// ../cli/src/lib/cli/setup.ts
|
|
28337
|
-
import
|
|
28338
|
-
import
|
|
28962
|
+
import fs86 from "node:fs";
|
|
28963
|
+
import path82 from "node:path";
|
|
28339
28964
|
|
|
28340
28965
|
// ../cli/src/commands/install.ts
|
|
28341
28966
|
init_local();
|
|
@@ -28347,14 +28972,14 @@ init_io();
|
|
|
28347
28972
|
init_paths();
|
|
28348
28973
|
init_memory();
|
|
28349
28974
|
init_yaml2();
|
|
28350
|
-
import
|
|
28351
|
-
import
|
|
28975
|
+
import fs85 from "node:fs";
|
|
28976
|
+
import path81 from "node:path";
|
|
28352
28977
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
28353
28978
|
|
|
28354
28979
|
// ../cli/src/lib/skill/install.ts
|
|
28355
28980
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
28356
|
-
import
|
|
28357
|
-
import
|
|
28981
|
+
import fs84 from "node:fs";
|
|
28982
|
+
import path80 from "node:path";
|
|
28358
28983
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
28359
28984
|
|
|
28360
28985
|
// ../skill/SKILL.md
|
|
@@ -28369,8 +28994,8 @@ init_version();
|
|
|
28369
28994
|
init_version();
|
|
28370
28995
|
init_project();
|
|
28371
28996
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
28372
|
-
import
|
|
28373
|
-
import
|
|
28997
|
+
import fs83 from "node:fs";
|
|
28998
|
+
import path79 from "node:path";
|
|
28374
28999
|
var MARKER3 = "# ai4kanban commit guard";
|
|
28375
29000
|
var MARKER_LINE = new RegExp(`^${MARKER3}\\b`, "m");
|
|
28376
29001
|
var CHECK = [
|
|
@@ -28402,9 +29027,9 @@ function hookScript(sessions) {
|
|
|
28402
29027
|
""
|
|
28403
29028
|
].join("\n");
|
|
28404
29029
|
}
|
|
28405
|
-
var
|
|
29030
|
+
var read3 = (file) => {
|
|
28406
29031
|
try {
|
|
28407
|
-
return
|
|
29032
|
+
return fs83.readFileSync(file, "utf8");
|
|
28408
29033
|
} catch {
|
|
28409
29034
|
return null;
|
|
28410
29035
|
}
|
|
@@ -28416,7 +29041,7 @@ function hooksDir(root) {
|
|
|
28416
29041
|
windowsHide: true
|
|
28417
29042
|
});
|
|
28418
29043
|
if (out.error || out.status !== 0 || !out.stdout.trim()) return null;
|
|
28419
|
-
return
|
|
29044
|
+
return path79.resolve(root, out.stdout.trim());
|
|
28420
29045
|
}
|
|
28421
29046
|
function hasOwnHooksPath(root) {
|
|
28422
29047
|
const out = spawnSync6("git", ["config", "--get", "core.hooksPath"], {
|
|
@@ -28426,18 +29051,18 @@ function hasOwnHooksPath(root) {
|
|
|
28426
29051
|
});
|
|
28427
29052
|
return !out.error && out.status === 0 && !!out.stdout.trim();
|
|
28428
29053
|
}
|
|
28429
|
-
var sessionsPath = (root) =>
|
|
29054
|
+
var sessionsPath = (root) => path79.join(projectStateDir(path79.join(root, "docs", "kanban"), root), SESSIONS_FILE);
|
|
28430
29055
|
function readCommitHook(root) {
|
|
28431
29056
|
const dir = hooksDir(root);
|
|
28432
29057
|
if (!dir) return { state: "no-git" };
|
|
28433
|
-
const where = shown(root,
|
|
29058
|
+
const where = shown(root, path79.join(dir, "pre-commit"));
|
|
28434
29059
|
const line2 = guardLine(sessionsPath(root));
|
|
28435
29060
|
if (hasOwnHooksPath(root)) return { state: "hooks-path", path: where, line: line2 };
|
|
28436
|
-
const text5 =
|
|
29061
|
+
const text5 = read3(path79.join(dir, "pre-commit"));
|
|
28437
29062
|
if (text5 === null) return { state: "absent", path: where };
|
|
28438
29063
|
return MARKER_LINE.test(text5) ? { state: "ours", path: where } : { state: "foreign", path: where, line: line2 };
|
|
28439
29064
|
}
|
|
28440
|
-
var shown = (root, p) => (
|
|
29065
|
+
var shown = (root, p) => (path79.relative(root, p) || p).split(path79.sep).join("/");
|
|
28441
29066
|
function installCommitHook(root) {
|
|
28442
29067
|
const hook = readCommitHook(root);
|
|
28443
29068
|
if (hook.state === "no-git") return {};
|
|
@@ -28448,11 +29073,11 @@ function installCommitHook(root) {
|
|
|
28448
29073
|
${hook.line}`
|
|
28449
29074
|
};
|
|
28450
29075
|
}
|
|
28451
|
-
const file =
|
|
29076
|
+
const file = path79.join(root, hook.path);
|
|
28452
29077
|
try {
|
|
28453
|
-
|
|
28454
|
-
|
|
28455
|
-
|
|
29078
|
+
fs83.mkdirSync(path79.dirname(file), { recursive: true });
|
|
29079
|
+
fs83.writeFileSync(file, hookScript(sessionsPath(root)), { mode: 493 });
|
|
29080
|
+
fs83.chmodSync(file, 493);
|
|
28456
29081
|
} catch (e) {
|
|
28457
29082
|
return { note: `${hook.path} could not be written \u2014 ${e instanceof Error ? e.message : String(e)}` };
|
|
28458
29083
|
}
|
|
@@ -28468,9 +29093,9 @@ function sayCommitHook(state) {
|
|
|
28468
29093
|
|
|
28469
29094
|
// ../cli/src/lib/skill/install.ts
|
|
28470
29095
|
var TARGETS = [
|
|
28471
|
-
{ rel:
|
|
29096
|
+
{ rel: path80.join(".claude", "skills", "kanban"), agent: "Claude Code" },
|
|
28472
29097
|
{
|
|
28473
|
-
rel:
|
|
29098
|
+
rel: path80.join(".agents", "skills", "kanban"),
|
|
28474
29099
|
agent: "Codex, Cursor, OpenCode, Kimi Code, DeepSeek Harness, ZCode, Grok Build"
|
|
28475
29100
|
}
|
|
28476
29101
|
];
|
|
@@ -28482,25 +29107,25 @@ var SELF2 = fileURLToPath2(import.meta.url);
|
|
|
28482
29107
|
function rulesPath() {
|
|
28483
29108
|
return SELF2;
|
|
28484
29109
|
}
|
|
28485
|
-
function
|
|
29110
|
+
function read4(file) {
|
|
28486
29111
|
try {
|
|
28487
|
-
return
|
|
29112
|
+
return fs84.readFileSync(file, "utf8");
|
|
28488
29113
|
} catch {
|
|
28489
29114
|
return null;
|
|
28490
29115
|
}
|
|
28491
29116
|
}
|
|
28492
29117
|
function statOf(p) {
|
|
28493
29118
|
try {
|
|
28494
|
-
return
|
|
29119
|
+
return fs84.lstatSync(p);
|
|
28495
29120
|
} catch {
|
|
28496
29121
|
return null;
|
|
28497
29122
|
}
|
|
28498
29123
|
}
|
|
28499
29124
|
function versionIn(dir) {
|
|
28500
|
-
const note =
|
|
29125
|
+
const note = read4(path80.join(dir, "SKILL.md"));
|
|
28501
29126
|
const stampedNote = note?.match(NOTE_STAMP);
|
|
28502
29127
|
if (stampedNote) return stampedNote[1];
|
|
28503
|
-
const src =
|
|
29128
|
+
const src = read4(path80.join(dir, "kanban.mjs"));
|
|
28504
29129
|
if (!src) return null;
|
|
28505
29130
|
const stamped = src.slice(0, 400).match(BUILT_STAMP);
|
|
28506
29131
|
if (stamped) return stamped[1];
|
|
@@ -28524,7 +29149,7 @@ function folderState(dir) {
|
|
|
28524
29149
|
const st = statOf(dir);
|
|
28525
29150
|
if (!st) return { state: "absent", version: null };
|
|
28526
29151
|
if (st.isSymbolicLink()) return { state: "linked", version: versionIn(dir) };
|
|
28527
|
-
if (!
|
|
29152
|
+
if (!fs84.existsSync(path80.join(dir, "SKILL.md"))) return { state: "absent", version: null };
|
|
28528
29153
|
const version = versionIn(dir);
|
|
28529
29154
|
if (!version) return { state: "unknown", version: null };
|
|
28530
29155
|
return { state: isOlder(version, SKILL_VERSION) ? "stale" : "current", version };
|
|
@@ -28532,9 +29157,9 @@ function folderState(dir) {
|
|
|
28532
29157
|
function readSkillState(root) {
|
|
28533
29158
|
const dir = rootOr(root);
|
|
28534
29159
|
const folders2 = TARGETS.map((target) => ({
|
|
28535
|
-
path: target.rel.split(
|
|
29160
|
+
path: target.rel.split(path80.sep).join("/"),
|
|
28536
29161
|
agent: target.agent,
|
|
28537
|
-
...folderState(
|
|
29162
|
+
...folderState(path80.join(dir, target.rel))
|
|
28538
29163
|
}));
|
|
28539
29164
|
return {
|
|
28540
29165
|
version: SKILL_VERSION,
|
|
@@ -28571,10 +29196,10 @@ function commandParagraph(command) {
|
|
|
28571
29196
|
].join("\n");
|
|
28572
29197
|
}
|
|
28573
29198
|
function writeFolder(dir, command) {
|
|
28574
|
-
|
|
28575
|
-
|
|
29199
|
+
fs84.rmSync(dir, { recursive: true, force: true });
|
|
29200
|
+
fs84.mkdirSync(dir, { recursive: true });
|
|
28576
29201
|
const filled = SKILL_default.replace(COMMAND_BLOCK, commandParagraph(command));
|
|
28577
|
-
|
|
29202
|
+
fs84.writeFileSync(path80.join(dir, "SKILL.md"), `${filled.trimEnd()}
|
|
28578
29203
|
|
|
28579
29204
|
<!-- ai4kanban ${SKILL_VERSION} -->
|
|
28580
29205
|
`);
|
|
@@ -28590,8 +29215,8 @@ function installSkill(root, only, invoked) {
|
|
|
28590
29215
|
const wrote = [];
|
|
28591
29216
|
const skipped = [];
|
|
28592
29217
|
for (const target of TARGETS) {
|
|
28593
|
-
const dest =
|
|
28594
|
-
const shown2 = target.rel.split(
|
|
29218
|
+
const dest = path80.join(dir, target.rel);
|
|
29219
|
+
const shown2 = target.rel.split(path80.sep).join("/");
|
|
28595
29220
|
const { state: state2 } = folderState(dest);
|
|
28596
29221
|
if (state2 === "linked") {
|
|
28597
29222
|
skipped.push({ path: shown2, agent: target.agent, why: "a symlink into a source checkout \u2014 left untouched" });
|
|
@@ -28661,10 +29286,10 @@ var Report = class {
|
|
|
28661
29286
|
for (const line2 of this.notes) say(` ! ${line2}`);
|
|
28662
29287
|
}
|
|
28663
29288
|
};
|
|
28664
|
-
var
|
|
29289
|
+
var read5 = (file) => fs85.existsSync(file) ? fs85.readFileSync(file, "utf8") : null;
|
|
28665
29290
|
function statOf2(p) {
|
|
28666
29291
|
try {
|
|
28667
|
-
return
|
|
29292
|
+
return fs85.lstatSync(p);
|
|
28668
29293
|
} catch {
|
|
28669
29294
|
return null;
|
|
28670
29295
|
}
|
|
@@ -28675,10 +29300,10 @@ async function boardMove(root, args, board2) {
|
|
|
28675
29300
|
if (code !== 0) throw new BoardError(`\`board ${args.join(" ")}\` failed \u2014 nothing else was changed`, { kind: "board-move-failed" });
|
|
28676
29301
|
}
|
|
28677
29302
|
function boardAbove(root) {
|
|
28678
|
-
let at =
|
|
29303
|
+
let at = path81.dirname(path81.resolve(root));
|
|
28679
29304
|
for (; ; ) {
|
|
28680
|
-
if (
|
|
28681
|
-
const up =
|
|
29305
|
+
if (fs85.existsSync(path81.join(at, "docs", "kanban"))) return at;
|
|
29306
|
+
const up = path81.dirname(at);
|
|
28682
29307
|
if (up === at) return null;
|
|
28683
29308
|
at = up;
|
|
28684
29309
|
}
|
|
@@ -28686,10 +29311,10 @@ function boardAbove(root) {
|
|
|
28686
29311
|
async function cmdInstall(ctx) {
|
|
28687
29312
|
const report3 = new Report();
|
|
28688
29313
|
const board2 = ctx.board ?? null;
|
|
28689
|
-
const where = board2 ?
|
|
29314
|
+
const where = board2 ? path81.relative(ctx.dir, board2) || board2 : "docs/kanban";
|
|
28690
29315
|
say(`wildcrane ${SKILL_VERSION} \u2014 installing into ${ctx.dir}`);
|
|
28691
29316
|
say("");
|
|
28692
|
-
const above = board2 ||
|
|
29317
|
+
const above = board2 || fs85.existsSync(path81.join(ctx.dir, "docs", "kanban")) ? null : boardAbove(ctx.dir);
|
|
28693
29318
|
if (above) {
|
|
28694
29319
|
report3.notes.push(`there is already a board at ${above} \u2014 this makes a second one, and commands run here will find this one`);
|
|
28695
29320
|
}
|
|
@@ -28711,7 +29336,7 @@ async function cmdInstall(ctx) {
|
|
|
28711
29336
|
say("board UI (Configuration \u2192 Agent setup), or here:");
|
|
28712
29337
|
say("");
|
|
28713
29338
|
say(` ${ctx.program} skill`);
|
|
28714
|
-
return { installed: ctx.dir, board: board2 ??
|
|
29339
|
+
return { installed: ctx.dir, board: board2 ?? path81.join(ctx.dir, "docs", "kanban") };
|
|
28715
29340
|
}
|
|
28716
29341
|
async function cmdSkill(ctx, mode) {
|
|
28717
29342
|
const report3 = new Report();
|
|
@@ -28756,7 +29381,7 @@ function placeSkill(ctx, mode, report3) {
|
|
|
28756
29381
|
const before = readSkillState(ctx.dir).folders;
|
|
28757
29382
|
for (const folder of before) {
|
|
28758
29383
|
if (folder.state !== "absent" && folder.state !== "linked") {
|
|
28759
|
-
rescueSkillConfig(ctx.dir,
|
|
29384
|
+
rescueSkillConfig(ctx.dir, path81.join(ctx.dir, folder.path), report3);
|
|
28760
29385
|
}
|
|
28761
29386
|
}
|
|
28762
29387
|
const result = installSkill(ctx.dir, mode === "update" ? "present" : void 0, ctx.program);
|
|
@@ -28779,20 +29404,20 @@ function placeSkill(ctx, mode, report3) {
|
|
|
28779
29404
|
return { result, from: before.map((f) => f.version).find(Boolean) || null };
|
|
28780
29405
|
}
|
|
28781
29406
|
function rescueSkillConfig(root, skillDir, report3) {
|
|
28782
|
-
const old =
|
|
29407
|
+
const old = read5(path81.join(skillDir, "config.md"));
|
|
28783
29408
|
if (!old || old.includes("{{")) return;
|
|
28784
|
-
const boardConfig =
|
|
28785
|
-
const current =
|
|
29409
|
+
const boardConfig = path81.join(root, "docs", "kanban", "config.md");
|
|
29410
|
+
const current = read5(boardConfig);
|
|
28786
29411
|
if (current === null) {
|
|
28787
|
-
|
|
28788
|
-
|
|
29412
|
+
fs85.mkdirSync(path81.dirname(boardConfig), { recursive: true });
|
|
29413
|
+
fs85.writeFileSync(boardConfig, old);
|
|
28789
29414
|
report3.did.push("moved your filled-in config.md out of the skill folder to docs/kanban/config.md");
|
|
28790
29415
|
return;
|
|
28791
29416
|
}
|
|
28792
29417
|
if (current === old) return;
|
|
28793
|
-
const aside =
|
|
28794
|
-
if (
|
|
28795
|
-
|
|
29418
|
+
const aside = path81.join(root, "docs", "kanban", "config.from-skill.md");
|
|
29419
|
+
if (fs85.existsSync(aside)) return;
|
|
29420
|
+
fs85.writeFileSync(aside, old);
|
|
28796
29421
|
report3.notes.push(
|
|
28797
29422
|
"the old skill folder held a different filled-in config.md \u2014 saved as docs/kanban/config.from-skill.md; fold anything worth keeping into docs/kanban/config.md and delete it"
|
|
28798
29423
|
);
|
|
@@ -28846,8 +29471,8 @@ async function cmdUpdate2(ctx) {
|
|
|
28846
29471
|
return { updated: ctx.dir, from, to: SKILL_VERSION };
|
|
28847
29472
|
}
|
|
28848
29473
|
async function repairBoard(root, report3) {
|
|
28849
|
-
const board2 =
|
|
28850
|
-
if (!
|
|
29474
|
+
const board2 = path81.join(root, "docs", "kanban");
|
|
29475
|
+
if (!fs85.existsSync(board2)) {
|
|
28851
29476
|
report3.notes.push("no docs/kanban/ here \u2014 run `akb install` to scaffold the board");
|
|
28852
29477
|
return;
|
|
28853
29478
|
}
|
|
@@ -28877,20 +29502,20 @@ function moveRuntimes(root, board2, report3) {
|
|
|
28877
29502
|
}
|
|
28878
29503
|
}
|
|
28879
29504
|
function moveLegacyMemory(board2, report3) {
|
|
28880
|
-
const memory =
|
|
29505
|
+
const memory = path81.join(board2, "memory");
|
|
28881
29506
|
const moved = [];
|
|
28882
29507
|
for (const name of MEMORY_FILES) {
|
|
28883
|
-
const from =
|
|
29508
|
+
const from = path81.join(board2, name);
|
|
28884
29509
|
const st = statOf2(from);
|
|
28885
29510
|
if (!st || !st.isFile()) continue;
|
|
28886
|
-
|
|
28887
|
-
const to =
|
|
28888
|
-
const there =
|
|
29511
|
+
fs85.mkdirSync(memory, { recursive: true });
|
|
29512
|
+
const to = path81.join(memory, name);
|
|
29513
|
+
const there = read5(to);
|
|
28889
29514
|
if (there === null) {
|
|
28890
|
-
|
|
29515
|
+
fs85.renameSync(from, to);
|
|
28891
29516
|
moved.push(name);
|
|
28892
|
-
} else if (there ===
|
|
28893
|
-
|
|
29517
|
+
} else if (there === read5(from)) {
|
|
29518
|
+
fs85.rmSync(from);
|
|
28894
29519
|
moved.push(name);
|
|
28895
29520
|
} else {
|
|
28896
29521
|
report3.notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ \u2014 merge them by hand`);
|
|
@@ -28899,23 +29524,23 @@ function moveLegacyMemory(board2, report3) {
|
|
|
28899
29524
|
if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
|
|
28900
29525
|
}
|
|
28901
29526
|
function dropRecordFile(board2) {
|
|
28902
|
-
const file =
|
|
28903
|
-
if (!
|
|
28904
|
-
|
|
29527
|
+
const file = path81.join(board2, "record.csv");
|
|
29528
|
+
if (!fs85.existsSync(file)) return null;
|
|
29529
|
+
fs85.rmSync(file);
|
|
28905
29530
|
return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
|
|
28906
29531
|
}
|
|
28907
29532
|
function dropModuleGoals(board2, report3) {
|
|
28908
|
-
const memory =
|
|
28909
|
-
if (!
|
|
28910
|
-
const root =
|
|
29533
|
+
const memory = path81.join(board2, "memory");
|
|
29534
|
+
if (!fs85.existsSync(memory)) return;
|
|
29535
|
+
const root = read5(path81.join(memory, "goal.md"));
|
|
28911
29536
|
let dropped = 0;
|
|
28912
|
-
for (const entry of
|
|
29537
|
+
for (const entry of fs85.readdirSync(memory, { withFileTypes: true })) {
|
|
28913
29538
|
if (!entry.isDirectory()) continue;
|
|
28914
|
-
const file =
|
|
28915
|
-
const text5 =
|
|
29539
|
+
const file = path81.join(memory, entry.name, "goal.md");
|
|
29540
|
+
const text5 = read5(file);
|
|
28916
29541
|
if (text5 === null) continue;
|
|
28917
29542
|
if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
|
|
28918
|
-
|
|
29543
|
+
fs85.rmSync(file);
|
|
28919
29544
|
dropped++;
|
|
28920
29545
|
} else {
|
|
28921
29546
|
report3.notes.push(
|
|
@@ -28937,10 +29562,10 @@ var PLAN_LEADS = {
|
|
|
28937
29562
|
var GONE_LEADS = ["software-planner", "planner"];
|
|
28938
29563
|
function planLeads(board2) {
|
|
28939
29564
|
const leads = { ...PLAN_LEADS };
|
|
28940
|
-
const root =
|
|
28941
|
-
for (const entry of
|
|
29565
|
+
const root = path81.join(board2, "workflows");
|
|
29566
|
+
for (const entry of fs85.existsSync(root) ? fs85.readdirSync(root, { withFileTypes: true }) : []) {
|
|
28942
29567
|
if (!entry.isDirectory()) continue;
|
|
28943
|
-
const meta = splitFrontmatter(
|
|
29568
|
+
const meta = splitFrontmatter(read5(path81.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
|
|
28944
29569
|
const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
|
|
28945
29570
|
const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
|
|
28946
29571
|
const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
|
|
@@ -28949,40 +29574,40 @@ function planLeads(board2) {
|
|
|
28949
29574
|
return leads;
|
|
28950
29575
|
}
|
|
28951
29576
|
function movePlanRules(board2, report3) {
|
|
28952
|
-
const rules =
|
|
29577
|
+
const rules = path81.join(board2, "rules");
|
|
28953
29578
|
const moved = [];
|
|
28954
29579
|
for (const [id, leads] of Object.entries(planLeads(board2))) {
|
|
28955
29580
|
for (const lead of leads) {
|
|
28956
|
-
const from =
|
|
28957
|
-
const rule =
|
|
29581
|
+
const from = path81.join(rules, `${lead}.md`);
|
|
29582
|
+
const rule = read5(from)?.trim();
|
|
28958
29583
|
if (!rule) continue;
|
|
28959
|
-
const to =
|
|
28960
|
-
const there =
|
|
29584
|
+
const to = path81.join(rules, "workflows", `${id}.md`);
|
|
29585
|
+
const there = read5(to)?.trim() ?? "";
|
|
28961
29586
|
if (!there.includes(rule)) {
|
|
28962
|
-
|
|
28963
|
-
|
|
29587
|
+
fs85.mkdirSync(path81.dirname(to), { recursive: true });
|
|
29588
|
+
fs85.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
|
|
28964
29589
|
`);
|
|
28965
29590
|
moved.push(`${lead} \u2192 ${id}`);
|
|
28966
29591
|
}
|
|
28967
29592
|
}
|
|
28968
29593
|
}
|
|
28969
|
-
for (const lead of GONE_LEADS)
|
|
29594
|
+
for (const lead of GONE_LEADS) fs85.rmSync(path81.join(rules, `${lead}.md`), { force: true });
|
|
28970
29595
|
if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
|
|
28971
29596
|
}
|
|
28972
29597
|
function noteOldWorkflows(board2, report3) {
|
|
28973
29598
|
const old = (dir, file, keys) => {
|
|
28974
|
-
const root =
|
|
28975
|
-
return (
|
|
29599
|
+
const root = path81.join(board2, dir);
|
|
29600
|
+
return (fs85.existsSync(root) ? fs85.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read5(path81.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
|
|
28976
29601
|
};
|
|
28977
|
-
const
|
|
28978
|
-
if (
|
|
29602
|
+
const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
|
|
29603
|
+
if (files2.length) {
|
|
28979
29604
|
report3.notes.push(
|
|
28980
|
-
`written in the old form, so not read until rewritten \u2014 list a workflow's agents under \`previews\`, \`land\` and \`scheduled\`, and drop \`hook\`/\`lead\` from each agent (or ask the Workflow builder): ${
|
|
29605
|
+
`written in the old form, so not read until rewritten \u2014 list a workflow's agents under \`previews\`, \`land\` and \`scheduled\`, and drop \`hook\`/\`lead\` from each agent (or ask the Workflow builder): ${files2.join(", ")}`
|
|
28981
29606
|
);
|
|
28982
29607
|
}
|
|
28983
29608
|
}
|
|
28984
29609
|
function checkConfig(board2, report3) {
|
|
28985
|
-
const current =
|
|
29610
|
+
const current = read5(path81.join(board2, "config.md"));
|
|
28986
29611
|
if (!current) return;
|
|
28987
29612
|
const added = missingConfigKeys(current);
|
|
28988
29613
|
if (added.length) {
|
|
@@ -28992,7 +29617,7 @@ function checkConfig(board2, report3) {
|
|
|
28992
29617
|
}
|
|
28993
29618
|
}
|
|
28994
29619
|
function checkModules(board2, report3) {
|
|
28995
|
-
const map2 =
|
|
29620
|
+
const map2 = read5(path81.join(board2, "modules.md"));
|
|
28996
29621
|
if (map2 && map2.includes(UNFILLED)) {
|
|
28997
29622
|
report3.notes.push(
|
|
28998
29623
|
"docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
|
|
@@ -29046,13 +29671,13 @@ function declareSetup(program2, cli) {
|
|
|
29046
29671
|
program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
|
|
29047
29672
|
const dirOption = (cmd) => cmd.option("--dir <path>", "the project to work on (default: the current folder)").option("--json", "answer as one JSON object instead of prose");
|
|
29048
29673
|
const where = (cmd) => {
|
|
29049
|
-
const dir =
|
|
29050
|
-
if (!
|
|
29674
|
+
const dir = path82.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
|
|
29675
|
+
if (!fs86.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
|
|
29051
29676
|
return dir;
|
|
29052
29677
|
};
|
|
29053
29678
|
const boardOf = (cmd) => {
|
|
29054
29679
|
const named2 = cmd.optsWithGlobals().board;
|
|
29055
|
-
return named2 ?
|
|
29680
|
+
return named2 ? path82.resolve(cli.cwd, named2) : null;
|
|
29056
29681
|
};
|
|
29057
29682
|
dirOption(program2.command("install")).option("--board <dir>", "put the board here instead of docs/kanban/ (relative to this folder)").summary("scaffold docs/kanban/ \u2014 the board, and nothing else").description(
|
|
29058
29683
|
"Installing writes the board and nothing outside docs/kanban/. Driving that board from a coding agent is a later extra \u2014 `akb skill install`, or the button in the board UI under Configuration \u2192 Agent setup. Safe to run twice."
|
|
@@ -29125,7 +29750,7 @@ function invokedDirectly() {
|
|
|
29125
29750
|
const entry = process.argv[1];
|
|
29126
29751
|
if (!entry) return false;
|
|
29127
29752
|
try {
|
|
29128
|
-
return
|
|
29753
|
+
return fs87.realpathSync(entry) === fs87.realpathSync(SELF3);
|
|
29129
29754
|
} catch {
|
|
29130
29755
|
return false;
|
|
29131
29756
|
}
|