wildcrane 0.11.3 → 0.11.5
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 -1
- package/bin/ai4kanban.mjs +2 -21
- package/dist/kanban.mjs +380 -635
- package/package.json +2 -2
package/dist/kanban.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// ai4kanban 0.11.
|
|
2
|
+
// ai4kanban 0.11.5 — built from the CLI's TypeScript sources by cli/scripts/build.mjs.
|
|
3
3
|
// Do not edit: every change belongs in cli/src/, and a build overwrites this file.
|
|
4
4
|
import { createRequire as __ai4kanbanRequire } from 'node:module'
|
|
5
5
|
const require = __ai4kanbanRequire(import.meta.url)
|
|
@@ -1168,8 +1168,8 @@ var require_command = __commonJS({
|
|
|
1168
1168
|
"node_modules/commander/lib/command.js"(exports) {
|
|
1169
1169
|
var EventEmitter = __require("node:events").EventEmitter;
|
|
1170
1170
|
var childProcess = __require("node:child_process");
|
|
1171
|
-
var
|
|
1172
|
-
var
|
|
1171
|
+
var path86 = __require("node:path");
|
|
1172
|
+
var fs87 = __require("node:fs");
|
|
1173
1173
|
var process2 = __require("node:process");
|
|
1174
1174
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
1175
1175
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -2150,7 +2150,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2150
2150
|
* @param {string} subcommandName
|
|
2151
2151
|
*/
|
|
2152
2152
|
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
2153
|
-
if (
|
|
2153
|
+
if (fs87.existsSync(executableFile)) return;
|
|
2154
2154
|
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";
|
|
2155
2155
|
const executableMissing = `'${executableFile}' does not exist
|
|
2156
2156
|
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
@@ -2168,11 +2168,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2168
2168
|
let launchWithNode = false;
|
|
2169
2169
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2170
2170
|
function findFile(baseDir, baseName) {
|
|
2171
|
-
const localBin =
|
|
2172
|
-
if (
|
|
2173
|
-
if (sourceExt.includes(
|
|
2171
|
+
const localBin = path86.resolve(baseDir, baseName);
|
|
2172
|
+
if (fs87.existsSync(localBin)) return localBin;
|
|
2173
|
+
if (sourceExt.includes(path86.extname(baseName))) return void 0;
|
|
2174
2174
|
const foundExt = sourceExt.find(
|
|
2175
|
-
(ext) =>
|
|
2175
|
+
(ext) => fs87.existsSync(`${localBin}${ext}`)
|
|
2176
2176
|
);
|
|
2177
2177
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
2178
2178
|
return void 0;
|
|
@@ -2184,21 +2184,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2184
2184
|
if (this._scriptPath) {
|
|
2185
2185
|
let resolvedScriptPath;
|
|
2186
2186
|
try {
|
|
2187
|
-
resolvedScriptPath =
|
|
2187
|
+
resolvedScriptPath = fs87.realpathSync(this._scriptPath);
|
|
2188
2188
|
} catch {
|
|
2189
2189
|
resolvedScriptPath = this._scriptPath;
|
|
2190
2190
|
}
|
|
2191
|
-
executableDir =
|
|
2192
|
-
|
|
2191
|
+
executableDir = path86.resolve(
|
|
2192
|
+
path86.dirname(resolvedScriptPath),
|
|
2193
2193
|
executableDir
|
|
2194
2194
|
);
|
|
2195
2195
|
}
|
|
2196
2196
|
if (executableDir) {
|
|
2197
2197
|
let localFile = findFile(executableDir, executableFile);
|
|
2198
2198
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2199
|
-
const legacyName =
|
|
2199
|
+
const legacyName = path86.basename(
|
|
2200
2200
|
this._scriptPath,
|
|
2201
|
-
|
|
2201
|
+
path86.extname(this._scriptPath)
|
|
2202
2202
|
);
|
|
2203
2203
|
if (legacyName !== this._name) {
|
|
2204
2204
|
localFile = findFile(
|
|
@@ -2209,7 +2209,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2209
2209
|
}
|
|
2210
2210
|
executableFile = localFile || executableFile;
|
|
2211
2211
|
}
|
|
2212
|
-
launchWithNode = sourceExt.includes(
|
|
2212
|
+
launchWithNode = sourceExt.includes(path86.extname(executableFile));
|
|
2213
2213
|
let proc;
|
|
2214
2214
|
if (process2.platform !== "win32") {
|
|
2215
2215
|
if (launchWithNode) {
|
|
@@ -3056,7 +3056,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3056
3056
|
* @return {Command}
|
|
3057
3057
|
*/
|
|
3058
3058
|
nameFromFilename(filename) {
|
|
3059
|
-
this._name =
|
|
3059
|
+
this._name = path86.basename(filename, path86.extname(filename));
|
|
3060
3060
|
return this;
|
|
3061
3061
|
}
|
|
3062
3062
|
/**
|
|
@@ -3070,9 +3070,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3070
3070
|
* @param {string} [path]
|
|
3071
3071
|
* @return {(string|null|Command)}
|
|
3072
3072
|
*/
|
|
3073
|
-
executableDir(
|
|
3074
|
-
if (
|
|
3075
|
-
this._executableDir =
|
|
3073
|
+
executableDir(path87) {
|
|
3074
|
+
if (path87 === void 0) return this._executableDir;
|
|
3075
|
+
this._executableDir = path87;
|
|
3076
3076
|
return this;
|
|
3077
3077
|
}
|
|
3078
3078
|
/**
|
|
@@ -3651,7 +3651,7 @@ function useProjectState() {
|
|
|
3651
3651
|
ensureAkbDir();
|
|
3652
3652
|
return ensureProjectState(KANBAN, REPO_ROOT);
|
|
3653
3653
|
}
|
|
3654
|
-
var REPO_ROOT, KANBAN, TODO, ARCHIVE, ARCHIVE_MD, README, MODULES_MD, CONFIG, KANBAN_GITIGNORE, RELEASES, RELEASE_SUMMARIES, AGENTS, WORKFLOWS, LEGACY_AGENTS, ASSETS, BOARD_STATE, MOCKUPS, MEMORY, AGENT_MEMORY, PROJECT_MD, LOCK, UI_CONFIG, LEGACY_UI_CONFIG, LOCAL_CONFIG, ENV_FILE, SESSIONS, SESSIONS_DIR, SESSIONS_LOCK2, INDEX_LOCK2, DELIVERIES, PLANS, PLANS_ARCHIVE, RULES, TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED, SIGNALS_FILES, SIGNALS_OLD_INBOX, SIGNALS_OLD_HANDLED, AKB_DIR, ROOT_GITIGNORE, AKB_IGNORE_LINE, BOARD_FLAG, rel, BOARD_PATH_IN_TEXT, ASSETS_PATH_IN_TEXT, MOCKUPS_PATH_IN_TEXT, BOARD_STATE_IN_TEXT;
|
|
3654
|
+
var REPO_ROOT, KANBAN, TODO, ARCHIVE, REJECTIONS_TO_REVIEW, ARCHIVE_MD, README, MODULES_MD, CONFIG, KANBAN_GITIGNORE, RELEASES, RELEASE_SUMMARIES, AGENTS, WORKFLOWS, LEGACY_AGENTS, ASSETS, BOARD_STATE, MOCKUPS, MEMORY, AGENT_MEMORY, PROJECT_MD, LOCK, UI_CONFIG, LEGACY_UI_CONFIG, LOCAL_CONFIG, ENV_FILE, SESSIONS, SESSIONS_DIR, SESSIONS_LOCK2, INDEX_LOCK2, DELIVERIES, PLANS, PLANS_ARCHIVE, RULES, TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED, SIGNALS_FILES, SIGNALS_OLD_INBOX, SIGNALS_OLD_HANDLED, AKB_DIR, ROOT_GITIGNORE, AKB_IGNORE_LINE, BOARD_FLAG, rel, BOARD_PATH_IN_TEXT, ASSETS_PATH_IN_TEXT, MOCKUPS_PATH_IN_TEXT, BOARD_STATE_IN_TEXT;
|
|
3655
3655
|
var init_paths = __esm({
|
|
3656
3656
|
"src/lib/paths.ts"() {
|
|
3657
3657
|
"use strict";
|
|
@@ -3661,6 +3661,7 @@ var init_paths = __esm({
|
|
|
3661
3661
|
KANBAN = "";
|
|
3662
3662
|
TODO = "";
|
|
3663
3663
|
ARCHIVE = "";
|
|
3664
|
+
REJECTIONS_TO_REVIEW = ".sync";
|
|
3664
3665
|
ARCHIVE_MD = "";
|
|
3665
3666
|
README = "";
|
|
3666
3667
|
MODULES_MD = "";
|
|
@@ -4019,11 +4020,12 @@ function insideRun() {
|
|
|
4019
4020
|
function runEnv(env2, value) {
|
|
4020
4021
|
return { ...env2, [RUN_ENV]: value };
|
|
4021
4022
|
}
|
|
4022
|
-
var RUN_ENV, STOP_ENV, SESSION_VARS;
|
|
4023
|
+
var RUN_ENV, SCHEDULED_AGENTS_ENV, STOP_ENV, SESSION_VARS;
|
|
4023
4024
|
var init_env = __esm({
|
|
4024
4025
|
"src/lib/agent/env.ts"() {
|
|
4025
4026
|
"use strict";
|
|
4026
4027
|
RUN_ENV = "KANBAN_RUN";
|
|
4028
|
+
SCHEDULED_AGENTS_ENV = "AI4KANBAN_SCHEDULED_AGENTS";
|
|
4027
4029
|
STOP_ENV = "KANBAN_STOP_ID";
|
|
4028
4030
|
SESSION_VARS = {
|
|
4029
4031
|
"claude-code": "CLAUDE_CODE_SESSION_ID",
|
|
@@ -4975,8 +4977,8 @@ var init_package = __esm({
|
|
|
4975
4977
|
"package.json"() {
|
|
4976
4978
|
package_default = {
|
|
4977
4979
|
name: "wildcrane",
|
|
4978
|
-
version: "0.11.
|
|
4979
|
-
akbContract:
|
|
4980
|
+
version: "0.11.5",
|
|
4981
|
+
akbContract: 2,
|
|
4980
4982
|
description: "WildCrane's `akb` command \u2014 scaffold the Markdown board under docs/kanban/, put an agent on a card, and carry the flows the agent works by.",
|
|
4981
4983
|
keywords: [
|
|
4982
4984
|
"kanban",
|
|
@@ -7868,10 +7870,10 @@ function findRollout(threadId) {
|
|
|
7868
7870
|
}
|
|
7869
7871
|
for (const name of names4) {
|
|
7870
7872
|
if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
|
|
7871
|
-
const
|
|
7873
|
+
const path86 = join(dir, name);
|
|
7872
7874
|
try {
|
|
7873
|
-
const at = statSync(
|
|
7874
|
-
if (!newest2 || at > newest2.at) newest2 = { path:
|
|
7875
|
+
const at = statSync(path86).mtimeMs;
|
|
7876
|
+
if (!newest2 || at > newest2.at) newest2 = { path: path86, at };
|
|
7875
7877
|
} catch {
|
|
7876
7878
|
}
|
|
7877
7879
|
}
|
|
@@ -7879,11 +7881,11 @@ function findRollout(threadId) {
|
|
|
7879
7881
|
return newest2?.path;
|
|
7880
7882
|
}
|
|
7881
7883
|
function readCodexRunFacts(threadId) {
|
|
7882
|
-
const
|
|
7883
|
-
if (!
|
|
7884
|
+
const path86 = findRollout(threadId);
|
|
7885
|
+
if (!path86) return void 0;
|
|
7884
7886
|
let raw;
|
|
7885
7887
|
try {
|
|
7886
|
-
raw = readFileSync(
|
|
7888
|
+
raw = readFileSync(path86, "utf8");
|
|
7887
7889
|
} catch {
|
|
7888
7890
|
return void 0;
|
|
7889
7891
|
}
|
|
@@ -8011,8 +8013,8 @@ function renderCompleted(item) {
|
|
|
8011
8013
|
const changes = Array.isArray(item.changes) ? item.changes : [];
|
|
8012
8014
|
return changes.map((raw) => {
|
|
8013
8015
|
const change2 = obj(raw);
|
|
8014
|
-
const
|
|
8015
|
-
return
|
|
8016
|
+
const path86 = str2(change2.path);
|
|
8017
|
+
return path86 ? `\u23FA ${str2(change2.kind) || "change"}(${path86})
|
|
8016
8018
|
` : "";
|
|
8017
8019
|
}).join("");
|
|
8018
8020
|
}
|
|
@@ -8271,10 +8273,10 @@ function readKimiRunFacts(sessionId) {
|
|
|
8271
8273
|
const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
|
|
8272
8274
|
let model;
|
|
8273
8275
|
let holding = 0;
|
|
8274
|
-
for (const { path:
|
|
8276
|
+
for (const { path: path86, main } of wireFiles(entry.sessionDir)) {
|
|
8275
8277
|
let raw;
|
|
8276
8278
|
try {
|
|
8277
|
-
raw = readFileSync2(
|
|
8279
|
+
raw = readFileSync2(path86, "utf8");
|
|
8278
8280
|
} catch {
|
|
8279
8281
|
continue;
|
|
8280
8282
|
}
|
|
@@ -8555,8 +8557,8 @@ var init_tail = __esm({
|
|
|
8555
8557
|
import fs22 from "node:fs";
|
|
8556
8558
|
function whereHint(update2, title) {
|
|
8557
8559
|
const locations = Array.isArray(update2.locations) ? update2.locations : [];
|
|
8558
|
-
const
|
|
8559
|
-
return
|
|
8560
|
+
const path86 = str2(obj(locations[0]).path);
|
|
8561
|
+
return path86 && !title.includes(path86) ? hint(path86) : "";
|
|
8560
8562
|
}
|
|
8561
8563
|
function saidBy(update2) {
|
|
8562
8564
|
const content = Array.isArray(update2.content) ? update2.content : [];
|
|
@@ -11782,7 +11784,7 @@ var init_flows = __esm({
|
|
|
11782
11784
|
argument: "<id> [note...]",
|
|
11783
11785
|
argumentNote: NOTE,
|
|
11784
11786
|
gloss: "build the card",
|
|
11785
|
-
options: [RUNTIME_OPTION]
|
|
11787
|
+
options: [RUNTIME_OPTION, ...CARRY_OPTIONS]
|
|
11786
11788
|
},
|
|
11787
11789
|
{
|
|
11788
11790
|
command: "conflict",
|
|
@@ -11861,9 +11863,8 @@ var init_flows = __esm({
|
|
|
11861
11863
|
action: "review-dismissals",
|
|
11862
11864
|
argument: "",
|
|
11863
11865
|
gloss: "learn what the user does not want from their rejection and dismissal reasons",
|
|
11864
|
-
options: [{ flags: "--all", description: "every reason on the board, not only those since the last review \u2014 a Runner's board" }],
|
|
11865
11866
|
more: [
|
|
11866
|
-
"Reads the reasons
|
|
11867
|
+
"Reads the reasons Cloud has not had learned, and withdraws what rested on an item since restored."
|
|
11867
11868
|
]
|
|
11868
11869
|
},
|
|
11869
11870
|
// The project writer's one flow (#1268). Typed bare: it acts on the project.
|
|
@@ -11958,21 +11959,10 @@ function parseStamp(raw) {
|
|
|
11958
11959
|
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]), 0, 0);
|
|
11959
11960
|
return Number.isNaN(d.getTime()) ? null : d;
|
|
11960
11961
|
}
|
|
11961
|
-
|
|
11962
|
-
const c = typeof cadence === "string" ? parseCadence(cadence) : cadence;
|
|
11963
|
-
if (!c) return null;
|
|
11964
|
-
const last = parseStamp(lastRun);
|
|
11965
|
-
if (!last) return /* @__PURE__ */ new Date(0);
|
|
11966
|
-
if (c.unit === "m") return new Date(last.getTime() + c.n * 6e4);
|
|
11967
|
-
if (c.unit === "h") return new Date(last.getTime() + c.n * 36e5);
|
|
11968
|
-
const [hour, minute] = c.at ? c.at.split(":").map(Number) : [last.getHours(), last.getMinutes()];
|
|
11969
|
-
return new Date(last.getFullYear(), last.getMonth(), last.getDate() + c.n, hour, minute, 0, 0);
|
|
11970
|
-
}
|
|
11971
|
-
var CADENCE_FORMS, AUTO_CADENCE, isAuto, CADENCE_RE, pad;
|
|
11962
|
+
var AUTO_CADENCE, isAuto, CADENCE_RE, pad;
|
|
11972
11963
|
var init_cadence = __esm({
|
|
11973
11964
|
"src/lib/cadence.ts"() {
|
|
11974
11965
|
"use strict";
|
|
11975
|
-
CADENCE_FORMS = "<N>m (minutes), <N>h (hours), <N>d (days), or <N>d at HH:MM (whole days, at that time of day) \u2014 e.g. 30m, 6h, 1d at 09:30";
|
|
11976
11966
|
AUTO_CADENCE = "auto";
|
|
11977
11967
|
isAuto = (cadence) => cadence.trim().toLowerCase() === AUTO_CADENCE;
|
|
11978
11968
|
CADENCE_RE = /^(\d+)\s*([mhd])(?:\s+at\s+(\d{1,2}):(\d{2}))?$/i;
|
|
@@ -11981,7 +11971,7 @@ var init_cadence = __esm({
|
|
|
11981
11971
|
});
|
|
11982
11972
|
|
|
11983
11973
|
// src/lib/agent/types.ts
|
|
11984
|
-
var SPECIALIST_ACTIONS, CARD_FREE_ACTIONS, holdsCard, REFINE_ACTIONS, RETIRED, isRetired, refusal, RETIRED_WORKFLOWS, workflowDeleted
|
|
11974
|
+
var SPECIALIST_ACTIONS, CARD_FREE_ACTIONS, holdsCard, REFINE_ACTIONS, RETIRED, isRetired, refusal, RETIRED_WORKFLOWS, workflowDeleted;
|
|
11985
11975
|
var init_types3 = __esm({
|
|
11986
11976
|
"src/lib/agent/types.ts"() {
|
|
11987
11977
|
"use strict";
|
|
@@ -11997,7 +11987,6 @@ var init_types3 = __esm({
|
|
|
11997
11987
|
refusal = (reason, error, args) => args ? { error, reason, args } : { error, reason };
|
|
11998
11988
|
RETIRED_WORKFLOWS = ["content"];
|
|
11999
11989
|
workflowDeleted = (id, known2) => !!id && !known2.includes(id) && !RETIRED_WORKFLOWS.includes(id);
|
|
12000
|
-
SCHEDULED_CADENCE = "1d";
|
|
12001
11990
|
}
|
|
12002
11991
|
});
|
|
12003
11992
|
|
|
@@ -12218,16 +12207,6 @@ function setSecret(name, value) {
|
|
|
12218
12207
|
return { ok: false, ...refusal("fileWrite", `couldn't write ${ENV_FILE}: ${why}`, { path: ENV_FILE, details: why }) };
|
|
12219
12208
|
}
|
|
12220
12209
|
}
|
|
12221
|
-
function readSchedule(key) {
|
|
12222
|
-
const block = configBlock(safeConfig()[key]);
|
|
12223
|
-
const saved = stringIn(block, "cadence");
|
|
12224
|
-
return {
|
|
12225
|
-
enabled: block.off !== true,
|
|
12226
|
-
cadence: saved && parseCadence(saved) ? saved : AUTO_CADENCE,
|
|
12227
|
-
lastRun: stringIn(block, "lastRun"),
|
|
12228
|
-
...stringIn(block, "since") ? { since: stringIn(block, "since") } : {}
|
|
12229
|
-
};
|
|
12230
|
-
}
|
|
12231
12210
|
function stampSchedule(key, when) {
|
|
12232
12211
|
return writeConfig((cfg) => {
|
|
12233
12212
|
cfg[key] = { ...configBlock(cfg[key]), lastRun: formatStamp(when) };
|
|
@@ -12260,7 +12239,7 @@ function dropReflected(ids) {
|
|
|
12260
12239
|
cfg.reflectQueue = queueIn(cfg).filter((id) => !ids.includes(id));
|
|
12261
12240
|
});
|
|
12262
12241
|
}
|
|
12263
|
-
var SILENCE_MINUTES, BACKGROUND_WAIT, BOARD_SPEC_KEYS, IGNORE_BLOCK, stringIn, stampMemoryPrune, queueIn, reflectQueue,
|
|
12242
|
+
var SILENCE_MINUTES, BACKGROUND_WAIT, BOARD_SPEC_KEYS, IGNORE_BLOCK, stringIn, stampMemoryPrune, queueIn, reflectQueue, stampProjectDescription;
|
|
12264
12243
|
var init_settings2 = __esm({
|
|
12265
12244
|
"src/lib/agent/settings.ts"() {
|
|
12266
12245
|
"use strict";
|
|
@@ -12277,8 +12256,6 @@ var init_settings2 = __esm({
|
|
|
12277
12256
|
stampMemoryPrune = (when = /* @__PURE__ */ new Date()) => void stampSchedule("memoryPrune", when);
|
|
12278
12257
|
queueIn = (cfg) => Array.isArray(cfg.reflectQueue) ? cfg.reflectQueue.filter((id) => Number.isInteger(id) && id > 0) : [];
|
|
12279
12258
|
reflectQueue = () => queueIn(safeConfig());
|
|
12280
|
-
dismissalReview = () => readSchedule("dismissalReview");
|
|
12281
|
-
stampDismissalReview = (when) => void stampSchedule("dismissalReview", when);
|
|
12282
12259
|
stampProjectDescription = (when) => void stampSchedule("projectDescription", when);
|
|
12283
12260
|
}
|
|
12284
12261
|
});
|
|
@@ -13353,7 +13330,7 @@ ${rest}`);
|
|
|
13353
13330
|
fs31.rmSync(goal, { force: true });
|
|
13354
13331
|
return words ? `moved the goal to the top of ${rel(agentMemoryFile(PLANNER, "decisions.md"))}` : "removed the empty goal";
|
|
13355
13332
|
}
|
|
13356
|
-
var isGlobalMemory, PLANNER, PLANNER_MEMORY_FILES,
|
|
13333
|
+
var isGlobalMemory, PLANNER, PLANNER_MEMORY_FILES, BOARD_MEMORY_FILES, STARTERS, onlyStarter, setOf, RESERVED_MEMORY_DIR, KEPT_MEMORY_FOLDERS, agentMemoryDir, agentMemoryFile, writtenMemoryNames, memoryNamesOf, agentMemoryFiles, legacyAgentMemoryFile, HEADINGS, HEADING_RE, stripHeading, FOLDED_UNDER, moveMemory, RESERVED_MODULE_NAMES, isModule, moduleMemoryFile, plannerCopies, memoryFile, ENTRY_RE, entriesOf2, demote, promote, PRODUCT_STARTER, SEEDED_GOAL_PARAGRAPHS;
|
|
13357
13334
|
var init_memory = __esm({
|
|
13358
13335
|
"src/lib/memory.ts"() {
|
|
13359
13336
|
"use strict";
|
|
@@ -13364,9 +13341,6 @@ var init_memory = __esm({
|
|
|
13364
13341
|
isGlobalMemory = (name) => name in BUNDLED_MEMORY_FILES || fs31.existsSync(path32.join(MEMORY, name, "MEMORY.md"));
|
|
13365
13342
|
PLANNER = "planner";
|
|
13366
13343
|
PLANNER_MEMORY_FILES = ["decisions.md", "rejected.md", "redesign.md"];
|
|
13367
|
-
PROPOSER = "proposer";
|
|
13368
|
-
PROPOSER_MISSED = "missed.md";
|
|
13369
|
-
proposerMissedFile = () => rel(agentMemoryFile(PROPOSER, PROPOSER_MISSED));
|
|
13370
13344
|
BOARD_MEMORY_FILES = ["readme.md", "project.md"];
|
|
13371
13345
|
STARTERS = {
|
|
13372
13346
|
"readme.md": `# Shipped
|
|
@@ -14023,7 +13997,7 @@ function switchWorkflowAgent(id, agent, on) {
|
|
|
14023
13997
|
if (!listedHere(flow, wanted)) {
|
|
14024
13998
|
if (!onBoard().has(wanted)) return { ok: false, ...refusal("agentNotFound", `this board has no \`${wanted}\` agent`, { agent: wanted }) };
|
|
14025
13999
|
if (agentWorkflow(wanted) === id && flow.scheduled.some((h) => h.agent === wanted)) {
|
|
14026
|
-
return { ok: false, ...refusal("agentNotListed", `\`${wanted}\` is a scheduled agent of "${flow.name}" \u2014 switch it
|
|
14000
|
+
return { ok: false, ...refusal("agentNotListed", `\`${wanted}\` is a scheduled agent of "${flow.name}" \u2014 switch it on the project's Agents page`, { agent: wanted, name: flow.name, workflow: flow.id }) };
|
|
14027
14001
|
}
|
|
14028
14002
|
const refused = elsewhere(wanted, id);
|
|
14029
14003
|
if (refused) return refused;
|
|
@@ -14080,6 +14054,11 @@ function scheduledMembers(flow) {
|
|
|
14080
14054
|
const known2 = onBoard();
|
|
14081
14055
|
return flow.scheduled.filter((h) => known2.has(h.agent));
|
|
14082
14056
|
}
|
|
14057
|
+
function listedScheduled(flow) {
|
|
14058
|
+
const def = (flow.builtIn ? BUILTINS : boardWorkflowDefs().defs).find((d) => d.id === flow.id);
|
|
14059
|
+
const known2 = onBoard();
|
|
14060
|
+
return (def?.scheduled ?? []).map((s) => s.agent).filter((agent) => known2.has(agent));
|
|
14061
|
+
}
|
|
14083
14062
|
function setScheduled(id, change2, claim = "") {
|
|
14084
14063
|
const flow = workflowById(id);
|
|
14085
14064
|
if (!flow) return notFound(id);
|
|
@@ -14092,48 +14071,6 @@ function setScheduled(id, change2, claim = "") {
|
|
|
14092
14071
|
block.stages = stages;
|
|
14093
14072
|
});
|
|
14094
14073
|
}
|
|
14095
|
-
function switchWorkflowScheduled(id, agent, on, now = /* @__PURE__ */ new Date()) {
|
|
14096
|
-
const wanted = canonicalSpecAgent(agent.trim());
|
|
14097
|
-
return setScheduled(id, (rows) => {
|
|
14098
|
-
let one = agentWorkflow(wanted) === id ? rows.find((h) => h.agent === wanted) : void 0;
|
|
14099
|
-
if (!one) {
|
|
14100
|
-
if (!onBoard().has(wanted)) return { ok: false, ...refusal("agentNotFound", `this board has no \`${wanted}\` agent`, { agent: wanted }) };
|
|
14101
|
-
const flow = workflowById(id);
|
|
14102
|
-
if (listedHere(flow, wanted)) return notScheduled(wanted, flow);
|
|
14103
|
-
const refused = elsewhere(wanted, id);
|
|
14104
|
-
if (refused) return refused;
|
|
14105
|
-
one = { agent: wanted, extra: "", off: true, cadence: AUTO_CADENCE, lastRun: "" };
|
|
14106
|
-
rows.push(one);
|
|
14107
|
-
}
|
|
14108
|
-
if (!on) one.off = true;
|
|
14109
|
-
else if (one.off) {
|
|
14110
|
-
delete one.off;
|
|
14111
|
-
one.since = formatStamp(now);
|
|
14112
|
-
}
|
|
14113
|
-
});
|
|
14114
|
-
}
|
|
14115
|
-
function setWorkflowScheduledCadence(id, agent, cadence) {
|
|
14116
|
-
const wanted = canonicalSpecAgent(agent.trim());
|
|
14117
|
-
const said3 = cadence.trim();
|
|
14118
|
-
const next = !said3 || isAuto(said3) ? AUTO_CADENCE : said3;
|
|
14119
|
-
if (!isAuto(next) && !parseCadence(next)) {
|
|
14120
|
-
const formats = `${AUTO_CADENCE}, or ${CADENCE_FORMS}`;
|
|
14121
|
-
return { ok: false, ...refusal("cadence", `"${next}" isn't a cadence \u2014 use ${formats}`, { cadence: next, formats }) };
|
|
14122
|
-
}
|
|
14123
|
-
return setScheduled(id, (rows) => {
|
|
14124
|
-
const one = rows.find((h) => h.agent === wanted);
|
|
14125
|
-
if (!one) return notScheduled(wanted, workflowById(id));
|
|
14126
|
-
one.cadence = next;
|
|
14127
|
-
});
|
|
14128
|
-
}
|
|
14129
|
-
function setWorkflowScheduledExtra(id, agent, extra) {
|
|
14130
|
-
const wanted = canonicalSpecAgent(agent.trim());
|
|
14131
|
-
return setScheduled(id, (rows) => {
|
|
14132
|
-
const one = rows.find((h) => h.agent === wanted);
|
|
14133
|
-
if (!one) return notScheduled(wanted, workflowById(id));
|
|
14134
|
-
one.extra = extra;
|
|
14135
|
-
});
|
|
14136
|
-
}
|
|
14137
14074
|
function stampScheduledRun(id, agent, startedAt) {
|
|
14138
14075
|
return setScheduled(
|
|
14139
14076
|
id,
|
|
@@ -14182,7 +14119,7 @@ function frozenWorkflow(id) {
|
|
|
14182
14119
|
land: landAgents(flow).map(row)
|
|
14183
14120
|
};
|
|
14184
14121
|
}
|
|
14185
|
-
var LAND_ROLES, DEFAULT_WORKFLOW, BUILTINS, BUILTIN_WORKFLOW_IDS, isBuiltinWorkflow, builtinDescription, workflowsBlock, storedStages, PREVIEWS, LAND, SCHEDULE, toRow, scheduledRow, OLD_STAGE_KEYS, savedOldStages, workflowFileProblems, allListed, resolved, agentAfter, agentKeyProblems, agentWorkflow, workflows, workflowById, onBoard, previewMembers, previewAgents, landAgents, archivesOnApproval, workflowRuleFile, save2, ownDef, notFound, trimmedName, numbered, freeId2, nameTaken, listedHere, scheduledAgent,
|
|
14122
|
+
var LAND_ROLES, DEFAULT_WORKFLOW, BUILTINS, BUILTIN_WORKFLOW_IDS, isBuiltinWorkflow, builtinDescription, workflowsBlock, storedStages, PREVIEWS, LAND, SCHEDULE, toRow, scheduledRow, OLD_STAGE_KEYS, savedOldStages, workflowFileProblems, allListed, resolved, agentAfter, agentKeyProblems, agentWorkflow, workflows, workflowById, onBoard, previewMembers, previewAgents, landAgents, archivesOnApproval, workflowRuleFile, save2, ownDef, notFound, trimmedName, numbered, freeId2, nameTaken, listedHere, scheduledAgent, cardWorkflowId, cardWorkflow, cardStages, cardStage;
|
|
14186
14123
|
var init_workflows = __esm({
|
|
14187
14124
|
"src/lib/agent/workflows.ts"() {
|
|
14188
14125
|
"use strict";
|
|
@@ -14286,11 +14223,6 @@ var init_workflows = __esm({
|
|
|
14286
14223
|
const flow = workflowById(id);
|
|
14287
14224
|
return flow ? scheduledMembers(flow).find((h) => h.agent === canonicalSpecAgent(agent.trim())) : void 0;
|
|
14288
14225
|
};
|
|
14289
|
-
scheduledClock = (one) => [one.lastRun, one.since ?? ""].filter((stamp4) => parseStamp(stamp4)).sort().pop() ?? "";
|
|
14290
|
-
notScheduled = (agent, flow) => ({
|
|
14291
|
-
ok: false,
|
|
14292
|
-
...refusal("agentNotScheduled", `\`${agent}\` is not a scheduled agent of "${flow.name}"`, { agent, name: flow.name, workflow: flow.id })
|
|
14293
|
-
});
|
|
14294
14226
|
cardWorkflowId = (id) => cardMeta(id)?.workflow ?? "";
|
|
14295
14227
|
cardWorkflow = (id) => workflowFor(typeof id === "number" ? cardWorkflowId(id) : "");
|
|
14296
14228
|
cardStages = (meta, flow) => previewStages(flow, meta?.previews ?? []);
|
|
@@ -14615,7 +14547,7 @@ function agentRoster() {
|
|
|
14615
14547
|
...specialists.filter((a) => !a.builtIn)
|
|
14616
14548
|
];
|
|
14617
14549
|
}
|
|
14618
|
-
var PLANNER_DECISIONS, PLANNER_REJECTED, PLANNER_REDESIGN, DISCUSSION_HELPER, MEMORY_PRUNER, MEMORY_REVIEWER, DISMISSAL_REVIEWER, PROJECT_WRITER, SMALL_FIX,
|
|
14550
|
+
var PLANNER_DECISIONS, PLANNER_REJECTED, PLANNER_REDESIGN, DISCUSSION_HELPER, MEMORY_PRUNER, MEMORY_REVIEWER, DISMISSAL_REVIEWER, PROJECT_WRITER, SMALL_FIX, PROPOSER, PLANNER_ROLE, BOARD_ROLES, DISCUSSION_ROLE, ROLE_NAMES, PLANNING_ROLE, roles, roleForFlow, roleNamed, hiddenRole, stageContractProblems, memoryOf, ownMemoryOf, agentNames;
|
|
14619
14551
|
var init_roles = __esm({
|
|
14620
14552
|
"src/lib/agent/roles.ts"() {
|
|
14621
14553
|
"use strict";
|
|
@@ -14654,10 +14586,10 @@ var init_roles = __esm({
|
|
|
14654
14586
|
memory: ["memory/project.md"]
|
|
14655
14587
|
};
|
|
14656
14588
|
SMALL_FIX = "A small fix is a small change whose workflow needs none of its previews.";
|
|
14657
|
-
|
|
14589
|
+
PROPOSER = {
|
|
14658
14590
|
name: "proposer",
|
|
14659
14591
|
gloss: "proposes the work a finished card leaves behind",
|
|
14660
|
-
memory: [
|
|
14592
|
+
memory: [],
|
|
14661
14593
|
settings: [
|
|
14662
14594
|
{
|
|
14663
14595
|
key: "small-fixes",
|
|
@@ -14704,7 +14636,7 @@ var init_roles = __esm({
|
|
|
14704
14636
|
MEMORY_REVIEWER,
|
|
14705
14637
|
DISMISSAL_REVIEWER,
|
|
14706
14638
|
PROJECT_WRITER,
|
|
14707
|
-
|
|
14639
|
+
PROPOSER
|
|
14708
14640
|
];
|
|
14709
14641
|
DISCUSSION_ROLE = DISCUSSION_HELPER.name;
|
|
14710
14642
|
ROLE_NAMES = BOARD_ROLES.map((r) => r.name);
|
|
@@ -15598,11 +15530,6 @@ function parse(file, lenient = false) {
|
|
|
15598
15530
|
cardId: /^\d+$/.test(held4.card_id ?? "") ? Number(held4.card_id) : null,
|
|
15599
15531
|
archivedAt: held4.archived_at || "",
|
|
15600
15532
|
contentKept: held4.content_kept !== "false",
|
|
15601
|
-
verdict: VERDICTS.includes(held4.verdict) ? held4.verdict : "",
|
|
15602
|
-
verdictReason: REASONS.includes(held4.verdict_reason) ? held4.verdict_reason : "",
|
|
15603
|
-
verdictCard: /^\d+$/.test(held4.verdict_card ?? "") ? Number(held4.verdict_card) : null,
|
|
15604
|
-
dropConfidence: confidenceOf(held4.drop_confidence),
|
|
15605
|
-
doConfidence: confidenceOf(held4.do_confidence),
|
|
15606
15533
|
relPath: boardRel(file)
|
|
15607
15534
|
};
|
|
15608
15535
|
}
|
|
@@ -15638,11 +15565,6 @@ function writeSignal(incoming, importedAt, slug2 = "") {
|
|
|
15638
15565
|
cardId: null,
|
|
15639
15566
|
archivedAt: "",
|
|
15640
15567
|
contentKept: true,
|
|
15641
|
-
verdict: "",
|
|
15642
|
-
verdictReason: "",
|
|
15643
|
-
verdictCard: null,
|
|
15644
|
-
dropConfidence: null,
|
|
15645
|
-
doConfidence: null,
|
|
15646
15568
|
relPath: ""
|
|
15647
15569
|
};
|
|
15648
15570
|
const file = path41.join(TRIAGE, freeName2(TRIAGE, fileName(signal2.title, slug2)));
|
|
@@ -15727,7 +15649,7 @@ function restoreInboxItems(sourceIds) {
|
|
|
15727
15649
|
function restoreInboxItem(sourceId) {
|
|
15728
15650
|
return restoreInboxItems([sourceId])[0];
|
|
15729
15651
|
}
|
|
15730
|
-
var boardRel,
|
|
15652
|
+
var boardRel, FIELDS, META_LINE, slugOf, fileName, readArchived, readAllDismissed, bySource;
|
|
15731
15653
|
var init_inbox = __esm({
|
|
15732
15654
|
"src/lib/signals/inbox.ts"() {
|
|
15733
15655
|
"use strict";
|
|
@@ -15736,11 +15658,8 @@ var init_inbox = __esm({
|
|
|
15736
15658
|
init_paths();
|
|
15737
15659
|
init_yaml();
|
|
15738
15660
|
boardRel = (file) => rel(file).split(path41.sep).join("/");
|
|
15739
|
-
VERDICTS = ["plan", "plan-without-refine", "skip", "human-review"];
|
|
15740
|
-
REASONS = ["supported", "rejected", "duplicate", "low-value", "needs-user", "unsure", "small", "plan", "no-workflow"];
|
|
15741
15661
|
FIELDS = ["source_id", "title", "collected_at", "imported_at"];
|
|
15742
15662
|
META_LINE = /^\s+("(?:[^"\\]|\\.)*"|[^:]+):\s*(.*)$/;
|
|
15743
|
-
confidenceOf = (said3) => /^(0(\.\d+)?|1(\.0+)?)$/.test(said3 ?? "") ? Number(said3) : null;
|
|
15744
15663
|
slugOf = (said3) => said3.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 56).replace(/-+$/, "");
|
|
15745
15664
|
fileName = (title, slug2 = "") => `${slugOf(slug2) || slugOf(title) || "item"}.md`;
|
|
15746
15665
|
readArchived = () => readFolder(SIGNALS_ARCHIVED);
|
|
@@ -16405,11 +16324,11 @@ function resumeRefusal(delivery, store) {
|
|
|
16405
16324
|
);
|
|
16406
16325
|
}
|
|
16407
16326
|
if (!worktreeExists(delivery.worktree)) {
|
|
16408
|
-
const
|
|
16327
|
+
const path86 = delivery.worktree ?? "";
|
|
16409
16328
|
return refusal(
|
|
16410
16329
|
"deliveryWorktreeGone",
|
|
16411
|
-
`delivery ${id}'s worktree ${
|
|
16412
|
-
{ id, task, path:
|
|
16330
|
+
`delivery ${id}'s worktree ${path86} is gone, so there is nothing to carry on. ${rebuild}`,
|
|
16331
|
+
{ id, task, path: path86, command: discard }
|
|
16413
16332
|
);
|
|
16414
16333
|
}
|
|
16415
16334
|
if (!branchExists(delivery.branch)) {
|
|
@@ -17813,7 +17732,8 @@ function reflectBlocks(ids) {
|
|
|
17813
17732
|
]);
|
|
17814
17733
|
}
|
|
17815
17734
|
function scheduledBlock() {
|
|
17816
|
-
const
|
|
17735
|
+
const given = process.env[SCHEDULED_AGENTS_ENV];
|
|
17736
|
+
const on = new Set(given === void 0 ? workflows().flatMap((flow) => listedScheduled(flow)) : given.split(","));
|
|
17817
17737
|
const lines = specAgentCatalog().agents.filter((a) => on.has(a.name)).map((a) => `${a.name} \u2014 ${a.description}`);
|
|
17818
17738
|
return lines.length ? ["<scheduled-agents>", ...lines, "</scheduled-agents>"] : [];
|
|
17819
17739
|
}
|
|
@@ -17841,10 +17761,10 @@ function answeredNote(cardId2, command) {
|
|
|
17841
17761
|
}
|
|
17842
17762
|
function carriedNote(req) {
|
|
17843
17763
|
if (req.session !== "resume" && req.session !== "fork") {
|
|
17844
|
-
return req.carry?.transcript ? `This card came out of a discussion. Read it for what the card leaves out: ${req.carry.transcript}` : "";
|
|
17764
|
+
return req.carry?.transcript ? `This card came out of a discussion. The card is the final word: the discussion only fills in the background, and what it dropped stays dropped. Read it for what the card leaves out: ${req.carry.transcript}` : "";
|
|
17845
17765
|
}
|
|
17846
|
-
const held4 = req.session === "resume" ? "This session planned this card before." : "This session is a copy of the one this card was created in.";
|
|
17847
|
-
return `${held4} The card as it reads now outranks what you remember of it: read it again before you act.`;
|
|
17766
|
+
const held4 = req.session === "resume" ? "This session planned this card before." : req.carry?.resumeId ? "This session is a copy of the discussion this card came from, and that discussion's rules ended with it: follow this ask." : "This session is a copy of the one this card was created in.";
|
|
17767
|
+
return `${held4} The card as it reads now outranks what you remember of it, and what was dropped along the way stays dropped: read it again before you act.`;
|
|
17848
17768
|
}
|
|
17849
17769
|
function actionPrompt(req, command, notes) {
|
|
17850
17770
|
const kb = skillCall(agentForRun(req), req.runtime);
|
|
@@ -17867,6 +17787,7 @@ function actionPrompt(req, command, notes) {
|
|
|
17867
17787
|
}
|
|
17868
17788
|
return [
|
|
17869
17789
|
`${kb}. Implement task ${req.id} ${named2} following \`akb guide implement\`.`,
|
|
17790
|
+
carriedNote(req),
|
|
17870
17791
|
req.notes ? `Extra notes: ${req.notes}` : ""
|
|
17871
17792
|
].filter(Boolean).join(" ");
|
|
17872
17793
|
case "archive":
|
|
@@ -17934,10 +17855,7 @@ function actionPrompt(req, command, notes) {
|
|
|
17934
17855
|
// The dismissal review (#929). The flow lists the dismissals and restored ids; the guide
|
|
17935
17856
|
// holds every rule.
|
|
17936
17857
|
case "review-dismissals":
|
|
17937
|
-
return
|
|
17938
|
-
`${kb}. Learn what the user does not want from their rejections and dismissals, following \`akb guide review-rejections\`.`,
|
|
17939
|
-
req.allReasons ? `The reasons are the ones \`${command} review-dismissals --all --print\` prints.` : ""
|
|
17940
|
-
].filter(Boolean).join(" ");
|
|
17858
|
+
return `${kb}. Learn what the user does not want from their rejections and dismissals, following \`akb guide review-rejections\`.`;
|
|
17941
17859
|
case "describe-project":
|
|
17942
17860
|
return `${kb}. Describe this project following \`akb guide describe-project\`.`;
|
|
17943
17861
|
// Reflecting on the cards the board has just completed (#534, #1467). They are off the
|
|
@@ -17952,7 +17870,7 @@ function actionPrompt(req, command, notes) {
|
|
|
17952
17870
|
`${kb}. These tasks have just been completed: ${titles.join(", ")}. Propose the work that should follow them, following \`akb guide reflect\`.`,
|
|
17953
17871
|
`They have left the board \u2014 read each in its own <card> block below, with the discussion it came from when the block lists one.`,
|
|
17954
17872
|
`Skip anything already on the board, already in the inbox, or turned down before.`,
|
|
17955
|
-
`Write each survivor with \`${command} triage add\`, and revise an open card whose plan what shipped made untrue with \`${command} card revise\`: that is all you may do unless your settings below say otherwise \u2014 you edit and archive no card yourself, and finding nothing worth proposing is a complete result.`,
|
|
17873
|
+
`Write each survivor with \`${command} triage add\`, and revise an open card whose plan what shipped made untrue with \`${command} card revise\`: that is all you may do${settingsBlock(req) ? " unless your settings below say otherwise" : ""} \u2014 you edit and archive no card yourself, and finding nothing worth proposing is a complete result.`,
|
|
17956
17874
|
`Don't ask me questions with human-in-the-loop.`
|
|
17957
17875
|
].join(" "),
|
|
17958
17876
|
...reflectBlocks(ids),
|
|
@@ -18100,6 +18018,7 @@ var init_prompts = __esm({
|
|
|
18100
18018
|
init_agents();
|
|
18101
18019
|
init_command();
|
|
18102
18020
|
init_deliveries();
|
|
18021
|
+
init_env();
|
|
18103
18022
|
init_facts();
|
|
18104
18023
|
init_outputs();
|
|
18105
18024
|
init_archive();
|
|
@@ -18457,13 +18376,6 @@ function recordReflection(run2) {
|
|
|
18457
18376
|
} catch {
|
|
18458
18377
|
}
|
|
18459
18378
|
}
|
|
18460
|
-
function recordDismissalReview(run2) {
|
|
18461
|
-
if (run2.action !== "review-dismissals" || run2.status !== "done") return;
|
|
18462
|
-
try {
|
|
18463
|
-
stampDismissalReview(new Date(run2.startedAt));
|
|
18464
|
-
} catch {
|
|
18465
|
-
}
|
|
18466
|
-
}
|
|
18467
18379
|
function recordScheduledRun(run2) {
|
|
18468
18380
|
if (run2.action !== "scheduled" || run2.status !== "done" || !run2.workflow || !run2.specAgent) return;
|
|
18469
18381
|
try {
|
|
@@ -18823,7 +18735,8 @@ function planningSession(cardId2, cwd = REPO_ROOT) {
|
|
|
18823
18735
|
return created ?? { none: "unrecorded" };
|
|
18824
18736
|
}
|
|
18825
18737
|
function carriedSession(req) {
|
|
18826
|
-
|
|
18738
|
+
const carries = PLANNING.has(req.action) || req.action === "implement" && !!req.carry?.resumeId;
|
|
18739
|
+
if (!Number.isInteger(req.id) || !carries) return void 0;
|
|
18827
18740
|
const fresh = (why) => ({ note: `new session \u2014 ${why}` });
|
|
18828
18741
|
if (req.session === "new") return fresh("the earlier one is gone, so this step was started again");
|
|
18829
18742
|
const agent = agentForRun(req);
|
|
@@ -19154,7 +19067,6 @@ async function closeRun(sessionId, res) {
|
|
|
19154
19067
|
recordPrune(closed2);
|
|
19155
19068
|
recordMemoryReview(closed2);
|
|
19156
19069
|
recordReflection(closed2);
|
|
19157
|
-
recordDismissalReview(closed2);
|
|
19158
19070
|
recordProjectDescription(closed2);
|
|
19159
19071
|
recordScheduledRun(closed2);
|
|
19160
19072
|
dropSpec(sessionId);
|
|
@@ -19806,7 +19718,7 @@ import fs51 from "node:fs";
|
|
|
19806
19718
|
import path55 from "node:path";
|
|
19807
19719
|
function defaultBody() {
|
|
19808
19720
|
return [
|
|
19809
|
-
"
|
|
19721
|
+
"<\u7528\u4E00\u5C0F\u6BB5\u8BDD\u5148\u8BB2\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u7528\u6237\u9047\u5230\u4EC0\u4E48\uFF0C\u518D\u8BB2\u8FD9\u5F20\u5361\u8981\u8FBE\u5230\u7684\u7ED3\u679C\u3002>",
|
|
19810
19722
|
"",
|
|
19811
19723
|
"## Worth noting",
|
|
19812
19724
|
"",
|
|
@@ -20864,145 +20776,6 @@ _(not filled in yet \u2014 build the map from the repo before tagging a card.)_
|
|
|
20864
20776
|
}
|
|
20865
20777
|
});
|
|
20866
20778
|
|
|
20867
|
-
// src/lib/agent/dismissal-review.ts
|
|
20868
|
-
import fs57 from "node:fs";
|
|
20869
|
-
function rejectionsToReview(since) {
|
|
20870
|
-
if (!fs57.existsSync(ARCHIVE)) return [];
|
|
20871
|
-
const found2 = [];
|
|
20872
|
-
for (const file of walkMd(ARCHIVE)) {
|
|
20873
|
-
const id = archivedId(file);
|
|
20874
|
-
if (id === null) continue;
|
|
20875
|
-
try {
|
|
20876
|
-
const { meta } = parseFrontmatter(fs57.readFileSync(file, "utf8"));
|
|
20877
|
-
if (!meta?.rejected || meta.rejected_by !== "user" || !meta.rejected_reason.trim()) continue;
|
|
20878
|
-
if ((parseStamp(meta.rejected_at)?.getTime() ?? 0) < since) continue;
|
|
20879
|
-
found2.push({ id, title: meta.title, file: rel(file), reason: meta.rejected_reason.trim(), rejectedAt: meta.rejected_at });
|
|
20880
|
-
} catch {
|
|
20881
|
-
continue;
|
|
20882
|
-
}
|
|
20883
|
-
}
|
|
20884
|
-
return found2.sort((a, b) => a.rejectedAt.localeCompare(b.rejectedAt) || a.id - b.id);
|
|
20885
|
-
}
|
|
20886
|
-
function dismissalsToReview(since) {
|
|
20887
|
-
return readAllDismissed().filter((s) => s.dismissedBy === "user" && s.dismissedReason.trim()).filter((s) => (parseStamp(s.dismissedAt)?.getTime() ?? 0) >= since).sort((a, b) => a.dismissedAt.localeCompare(b.dismissedAt)).map((s) => ({
|
|
20888
|
-
sourceId: s.sourceId,
|
|
20889
|
-
title: s.title,
|
|
20890
|
-
file: s.relPath,
|
|
20891
|
-
reason: s.dismissedReason.trim(),
|
|
20892
|
-
dismissedAt: s.dismissedAt
|
|
20893
|
-
}));
|
|
20894
|
-
}
|
|
20895
|
-
function citedSources(text5) {
|
|
20896
|
-
const ids = /* @__PURE__ */ new Set();
|
|
20897
|
-
for (const line of text5.split("\n")) {
|
|
20898
|
-
if (!/^\s*[-*]\s/.test(line)) continue;
|
|
20899
|
-
const m = line.match(SOURCES);
|
|
20900
|
-
for (const id of m?.[1].split(",") ?? []) if (id.trim()) ids.add(id.trim());
|
|
20901
|
-
}
|
|
20902
|
-
return [...ids];
|
|
20903
|
-
}
|
|
20904
|
-
function withdrawnSources() {
|
|
20905
|
-
const cited = [...new Set(plannerCopies("rejected.md").flatMap((file) => {
|
|
20906
|
-
try {
|
|
20907
|
-
return citedSources(fs57.readFileSync(file, "utf8"));
|
|
20908
|
-
} catch {
|
|
20909
|
-
return [];
|
|
20910
|
-
}
|
|
20911
|
-
}))];
|
|
20912
|
-
if (cited.length === 0) return [];
|
|
20913
|
-
const dismissed = new Set(readAllDismissed().map((s) => s.sourceId));
|
|
20914
|
-
const restored = new Set([...readInbox(), ...readArchived()].map((s) => s.sourceId));
|
|
20915
|
-
return cited.filter((id) => restored.has(id) && !dismissed.has(id));
|
|
20916
|
-
}
|
|
20917
|
-
function rejectionWorkWaiting(since) {
|
|
20918
|
-
return rejectionsToReview(since).length > 0 || dismissalsToReview(since).length > 0 || withdrawnSources().length > 0;
|
|
20919
|
-
}
|
|
20920
|
-
var SOURCES;
|
|
20921
|
-
var init_dismissal_review = __esm({
|
|
20922
|
-
"src/lib/agent/dismissal-review.ts"() {
|
|
20923
|
-
"use strict";
|
|
20924
|
-
init_cadence();
|
|
20925
|
-
init_cards();
|
|
20926
|
-
init_frontmatter();
|
|
20927
|
-
init_memory();
|
|
20928
|
-
init_paths();
|
|
20929
|
-
init_inbox();
|
|
20930
|
-
init_archive();
|
|
20931
|
-
SOURCES = /\(([^()]+)\)\s*$/;
|
|
20932
|
-
}
|
|
20933
|
-
});
|
|
20934
|
-
|
|
20935
|
-
// src/lib/agent/due.ts
|
|
20936
|
-
import fs58 from "node:fs";
|
|
20937
|
-
function scheduleDue(ask2, now = Date.now()) {
|
|
20938
|
-
const gap = !isAuto(ask2.cadence) ? ask2.cadence : autoSchedule(ask2.reads, ask2.fallback).every;
|
|
20939
|
-
const from = Math.max(ask2.from, ...ask2.attempts.map((a) => a.startedAt));
|
|
20940
|
-
let next = ask2.goesOn ? /* @__PURE__ */ new Date(0) : from ? nextDue(formatStamp(new Date(from)), gap) ?? /* @__PURE__ */ new Date(0) : /* @__PURE__ */ new Date(0);
|
|
20941
|
-
const failed = failures(ask2.attempts);
|
|
20942
|
-
if (!ask2.goesOn && from && failed > 1) {
|
|
20943
|
-
const base = next.getTime() - from;
|
|
20944
|
-
next = new Date(from + Math.min(base * 2 ** (failed - 1), Math.max(base, DAY)));
|
|
20945
|
-
}
|
|
20946
|
-
const newest2 = ask2.attempts.reduce((a, r) => a && a.startedAt >= r.startedAt ? a : r, null);
|
|
20947
|
-
const retrying = isAuto(ask2.cadence) && newest2?.status === "error";
|
|
20948
|
-
const wait = next.getTime() > now ? retrying ? "retrying" : "tooSoon" : !ask2.newWork() ? "nothingNew" : ask2.building?.() ? "building" : null;
|
|
20949
|
-
const reason = !wait || wait === "tooSoon" ? void 0 : wait === "nothingNew" ? ask2.reads : wait;
|
|
20950
|
-
return { next, wait, ...reason ? { reason } : {} };
|
|
20951
|
-
}
|
|
20952
|
-
function readsNew(reads, since) {
|
|
20953
|
-
switch (reads) {
|
|
20954
|
-
case "archived-cards":
|
|
20955
|
-
return archivedSince(since);
|
|
20956
|
-
case "chats":
|
|
20957
|
-
return false;
|
|
20958
|
-
case "dismissals":
|
|
20959
|
-
return rejectionWorkWaiting(since);
|
|
20960
|
-
}
|
|
20961
|
-
}
|
|
20962
|
-
function archivedSince(since) {
|
|
20963
|
-
if (!fs58.existsSync(ARCHIVE)) return false;
|
|
20964
|
-
const day = formatDay(new Date(since));
|
|
20965
|
-
for (const file of walkMd(ARCHIVE)) {
|
|
20966
|
-
try {
|
|
20967
|
-
if (fs58.statSync(file).mtimeMs <= since) continue;
|
|
20968
|
-
const { meta } = parseFrontmatter(fs58.readFileSync(file, "utf8"));
|
|
20969
|
-
if (meta && !meta.rejected && (meta.archived ?? "") >= day) return true;
|
|
20970
|
-
} catch {
|
|
20971
|
-
continue;
|
|
20972
|
-
}
|
|
20973
|
-
}
|
|
20974
|
-
return false;
|
|
20975
|
-
}
|
|
20976
|
-
function building(store) {
|
|
20977
|
-
const builds = new Set(store.deliveries.filter((d) => d.status === "active" && !d.scheduled).map((d) => d.deliveryId));
|
|
20978
|
-
if (store.deliveries.some((d) => builds.has(d.deliveryId) && d.landing)) return true;
|
|
20979
|
-
return store.runs.some((r) => r.status === "running" && r.deliveryId !== void 0 && builds.has(r.deliveryId));
|
|
20980
|
-
}
|
|
20981
|
-
var AUTO_GAP, DAY, autoSchedule, failures, stampMs;
|
|
20982
|
-
var init_due = __esm({
|
|
20983
|
-
"src/lib/agent/due.ts"() {
|
|
20984
|
-
"use strict";
|
|
20985
|
-
init_cadence();
|
|
20986
|
-
init_cards();
|
|
20987
|
-
init_frontmatter();
|
|
20988
|
-
init_paths();
|
|
20989
|
-
init_dismissal_review();
|
|
20990
|
-
AUTO_GAP = "6h";
|
|
20991
|
-
DAY = 864e5;
|
|
20992
|
-
autoSchedule = (reads, fallback) => reads ? { every: AUTO_GAP, reads } : { every: fallback };
|
|
20993
|
-
failures = (attempts) => {
|
|
20994
|
-
let n = 0;
|
|
20995
|
-
for (const a of [...attempts].sort((x, y) => y.startedAt - x.startedAt)) {
|
|
20996
|
-
if (a.status === "running") continue;
|
|
20997
|
-
if (a.status === "done") break;
|
|
20998
|
-
n++;
|
|
20999
|
-
}
|
|
21000
|
-
return n;
|
|
21001
|
-
};
|
|
21002
|
-
stampMs = (stamp4) => parseStamp(stamp4)?.getTime() ?? 0;
|
|
21003
|
-
}
|
|
21004
|
-
});
|
|
21005
|
-
|
|
21006
20779
|
// src/lib/agent/scheduled.ts
|
|
21007
20780
|
function scheduledBusy(pass, store = readStore()) {
|
|
21008
20781
|
const args = { agent: pass.agent, workflow: pass.workflow };
|
|
@@ -21014,25 +20787,6 @@ function scheduledBusy(pass, store = readStore()) {
|
|
|
21014
20787
|
}
|
|
21015
20788
|
return null;
|
|
21016
20789
|
}
|
|
21017
|
-
function scheduledWait(pass, one, store = readStore(), now = /* @__PURE__ */ new Date()) {
|
|
21018
|
-
const reads = specAgentCatalog().agents.find((a) => a.name === one.agent)?.reads;
|
|
21019
|
-
const lastRun = stampMs(one.lastRun);
|
|
21020
|
-
return scheduleDue(
|
|
21021
|
-
{
|
|
21022
|
-
cadence: one.cadence,
|
|
21023
|
-
fallback: SCHEDULED_CADENCE,
|
|
21024
|
-
reads,
|
|
21025
|
-
from: stampMs(scheduledClock(one)),
|
|
21026
|
-
attempts: store.runs.filter((r) => isPass(r, pass)),
|
|
21027
|
-
newWork: () => !reads || readsNew(reads, lastRun),
|
|
21028
|
-
building: () => building(store)
|
|
21029
|
-
},
|
|
21030
|
-
now.getTime()
|
|
21031
|
-
);
|
|
21032
|
-
}
|
|
21033
|
-
function scheduledNext(workflow, one) {
|
|
21034
|
-
return one.off || !scheduledClock(one) ? null : scheduledWait({ workflow, agent: one.agent }, one);
|
|
21035
|
-
}
|
|
21036
20790
|
function insideScheduledPass() {
|
|
21037
20791
|
const sessionId = insideRun();
|
|
21038
20792
|
const run2 = sessionId ? readStore().runs.find((r) => r.sessionId === sessionId) : void 0;
|
|
@@ -21055,13 +20809,10 @@ var scheduledRequest, isPass, openPasses, stalePasses;
|
|
|
21055
20809
|
var init_scheduled = __esm({
|
|
21056
20810
|
"src/lib/agent/scheduled.ts"() {
|
|
21057
20811
|
"use strict";
|
|
21058
|
-
init_catalog2();
|
|
21059
|
-
init_due();
|
|
21060
20812
|
init_env();
|
|
21061
20813
|
init_stages();
|
|
21062
20814
|
init_store();
|
|
21063
20815
|
init_types3();
|
|
21064
|
-
init_workflows();
|
|
21065
20816
|
scheduledRequest = (pass) => ({
|
|
21066
20817
|
action: "scheduled",
|
|
21067
20818
|
workflow: pass.workflow,
|
|
@@ -21076,15 +20827,15 @@ var init_scheduled = __esm({
|
|
|
21076
20827
|
});
|
|
21077
20828
|
|
|
21078
20829
|
// src/lib/card-age.ts
|
|
21079
|
-
import
|
|
20830
|
+
import fs57 from "node:fs";
|
|
21080
20831
|
import path60 from "node:path";
|
|
21081
20832
|
function staleAfterIn(config) {
|
|
21082
20833
|
const days = Number(LINE.exec(config)?.[1]);
|
|
21083
20834
|
return Number.isInteger(days) && days > 0 ? days : STALE_AFTER_DEFAULT;
|
|
21084
20835
|
}
|
|
21085
20836
|
function staleAfter() {
|
|
21086
|
-
if (!
|
|
21087
|
-
return staleAfterIn(
|
|
20837
|
+
if (!fs57.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
|
|
20838
|
+
return staleAfterIn(fs57.readFileSync(CONFIG, "utf8"));
|
|
21088
20839
|
}
|
|
21089
20840
|
function todoPathspec(top) {
|
|
21090
20841
|
const from = path60.relative(real(top), real(TODO));
|
|
@@ -21126,11 +20877,11 @@ function cardAges(now = Date.now()) {
|
|
|
21126
20877
|
if (!file || ages.has(file) || dirty.has(file)) continue;
|
|
21127
20878
|
const at = Date.parse(when);
|
|
21128
20879
|
if (Number.isNaN(at)) continue;
|
|
21129
|
-
ages.set(file, { lastTouched: when.slice(0, 10), days: Math.max(0, Math.floor((now - at) /
|
|
20880
|
+
ages.set(file, { lastTouched: when.slice(0, 10), days: Math.max(0, Math.floor((now - at) / DAY)) });
|
|
21130
20881
|
}
|
|
21131
20882
|
return ages;
|
|
21132
20883
|
}
|
|
21133
|
-
var STALE_AFTER_DEFAULT, LINE, gitTop, real, unquotePath,
|
|
20884
|
+
var STALE_AFTER_DEFAULT, LINE, gitTop, real, unquotePath, DAY;
|
|
21134
20885
|
var init_card_age = __esm({
|
|
21135
20886
|
"src/lib/card-age.ts"() {
|
|
21136
20887
|
"use strict";
|
|
@@ -21141,7 +20892,7 @@ var init_card_age = __esm({
|
|
|
21141
20892
|
gitTop = () => git(["rev-parse", "--show-toplevel"])?.trim() || null;
|
|
21142
20893
|
real = (p) => {
|
|
21143
20894
|
try {
|
|
21144
|
-
return
|
|
20895
|
+
return fs57.realpathSync(p);
|
|
21145
20896
|
} catch {
|
|
21146
20897
|
return path60.resolve(p);
|
|
21147
20898
|
}
|
|
@@ -21154,7 +20905,7 @@ var init_card_age = __esm({
|
|
|
21154
20905
|
return line.slice(1, -1);
|
|
21155
20906
|
}
|
|
21156
20907
|
};
|
|
21157
|
-
|
|
20908
|
+
DAY = 24 * 60 * 60 * 1e3;
|
|
21158
20909
|
}
|
|
21159
20910
|
});
|
|
21160
20911
|
|
|
@@ -21178,7 +20929,7 @@ var init_card_holds = __esm({
|
|
|
21178
20929
|
});
|
|
21179
20930
|
|
|
21180
20931
|
// src/commands/list.ts
|
|
21181
|
-
import
|
|
20932
|
+
import fs58 from "node:fs";
|
|
21182
20933
|
import path61 from "node:path";
|
|
21183
20934
|
function summaryLine(body) {
|
|
21184
20935
|
const lines = body.split("\n");
|
|
@@ -21201,7 +20952,7 @@ function openRows() {
|
|
|
21201
20952
|
const id = isRoot ? idPrefix(path61.basename(path61.dirname(file))) : idPrefix(base);
|
|
21202
20953
|
if (id == null) continue;
|
|
21203
20954
|
if (isLegacyRecurring(path61.relative(TODO, file))) continue;
|
|
21204
|
-
const { meta, body } = parseFrontmatter(
|
|
20955
|
+
const { meta, body } = parseFrontmatter(fs58.readFileSync(file, "utf8"));
|
|
21205
20956
|
rows.push({
|
|
21206
20957
|
id,
|
|
21207
20958
|
file,
|
|
@@ -21243,7 +20994,7 @@ function cmdStale(rows, all2, scope, mod) {
|
|
|
21243
20994
|
}
|
|
21244
20995
|
function archivedFiles() {
|
|
21245
20996
|
const files2 = /* @__PURE__ */ new Map();
|
|
21246
|
-
if (!
|
|
20997
|
+
if (!fs58.existsSync(ARCHIVE)) return files2;
|
|
21247
20998
|
for (const file of walkMd(ARCHIVE)) {
|
|
21248
20999
|
const id = idPrefix(path61.basename(file));
|
|
21249
21000
|
if (id !== null) files2.set(id, file);
|
|
@@ -21251,7 +21002,7 @@ function archivedFiles() {
|
|
|
21251
21002
|
for (const dir of walkDirs(ARCHIVE)) {
|
|
21252
21003
|
const id = idPrefix(path61.basename(dir));
|
|
21253
21004
|
const root = path61.join(dir, "root.md");
|
|
21254
|
-
if (id !== null &&
|
|
21005
|
+
if (id !== null && fs58.existsSync(root)) files2.set(id, root);
|
|
21255
21006
|
}
|
|
21256
21007
|
return files2;
|
|
21257
21008
|
}
|
|
@@ -21362,7 +21113,7 @@ var init_list = __esm({
|
|
|
21362
21113
|
});
|
|
21363
21114
|
|
|
21364
21115
|
// src/commands/misc.ts
|
|
21365
|
-
import
|
|
21116
|
+
import fs59 from "node:fs";
|
|
21366
21117
|
import path62 from "node:path";
|
|
21367
21118
|
function extractOldMeta(text5, file) {
|
|
21368
21119
|
const grab = (re) => {
|
|
@@ -21400,7 +21151,7 @@ function cmdMigrate(opts) {
|
|
|
21400
21151
|
let changed = 0;
|
|
21401
21152
|
let skipped = 0;
|
|
21402
21153
|
for (const file of files2) {
|
|
21403
|
-
const text5 =
|
|
21154
|
+
const text5 = fs59.readFileSync(file, "utf8");
|
|
21404
21155
|
if (text5.trimStart().startsWith("---")) {
|
|
21405
21156
|
skipped++;
|
|
21406
21157
|
continue;
|
|
@@ -21409,7 +21160,7 @@ function cmdMigrate(opts) {
|
|
|
21409
21160
|
const out = serializeFrontmatter(meta) + "\n\n" + stripOldHeader(text5).replace(/^\n+/, "");
|
|
21410
21161
|
if (dry) say(`would migrate ${rel(file)} (priority=${meta.priority})`);
|
|
21411
21162
|
else {
|
|
21412
|
-
|
|
21163
|
+
fs59.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
|
|
21413
21164
|
say(`migrated ${rel(file)}`);
|
|
21414
21165
|
}
|
|
21415
21166
|
changed++;
|
|
@@ -21430,10 +21181,10 @@ var init_misc = __esm({
|
|
|
21430
21181
|
});
|
|
21431
21182
|
|
|
21432
21183
|
// src/lib/group-close.ts
|
|
21433
|
-
import
|
|
21184
|
+
import fs60 from "node:fs";
|
|
21434
21185
|
function groupCloses(rootFile) {
|
|
21435
21186
|
try {
|
|
21436
|
-
return groupCloseOf(
|
|
21187
|
+
return groupCloseOf(fs60.readFileSync(rootFile, "utf8"));
|
|
21437
21188
|
} catch {
|
|
21438
21189
|
return false;
|
|
21439
21190
|
}
|
|
@@ -21446,7 +21197,7 @@ var init_group_close = __esm({
|
|
|
21446
21197
|
});
|
|
21447
21198
|
|
|
21448
21199
|
// src/commands/remove.ts
|
|
21449
|
-
import
|
|
21200
|
+
import fs61 from "node:fs";
|
|
21450
21201
|
import path63 from "node:path";
|
|
21451
21202
|
function leavingIds(id, found2) {
|
|
21452
21203
|
const ids = /* @__PURE__ */ new Set([id]);
|
|
@@ -21463,10 +21214,10 @@ function findMentions(ids) {
|
|
|
21463
21214
|
const hits = [];
|
|
21464
21215
|
const re = new RegExp(ids.map((id) => `#${id}(?!\\d)`).join("|"));
|
|
21465
21216
|
for (const dir of [TODO, MEMORY]) {
|
|
21466
|
-
if (!
|
|
21217
|
+
if (!fs61.existsSync(dir)) continue;
|
|
21467
21218
|
for (const file of walkMd(dir)) {
|
|
21468
21219
|
if (path63.basename(file) === "README.md") continue;
|
|
21469
|
-
const lines =
|
|
21220
|
+
const lines = fs61.readFileSync(file, "utf8").split("\n");
|
|
21470
21221
|
const fmEnd = frontmatterEnd(lines);
|
|
21471
21222
|
lines.forEach((line, i) => {
|
|
21472
21223
|
if (!re.test(line)) return;
|
|
@@ -21496,10 +21247,10 @@ function stampLeaving(cards, rejection) {
|
|
|
21496
21247
|
const day = formatDay();
|
|
21497
21248
|
const stamp4 = rejection && !rejection.discard ? { reason: rejection.reason, at: (/* @__PURE__ */ new Date()).toISOString(), by: insideRun() ? "agent" : "user" } : rejection && { reason: rejection.reason };
|
|
21498
21249
|
for (const card of cards) {
|
|
21499
|
-
if (!
|
|
21500
|
-
const text5 =
|
|
21250
|
+
if (!fs61.existsSync(card.file)) continue;
|
|
21251
|
+
const text5 = fs61.readFileSync(card.file, "utf8");
|
|
21501
21252
|
const stamped = stampLeavingIn(text5, day, stamp4);
|
|
21502
|
-
if (stamped !== text5)
|
|
21253
|
+
if (stamped !== text5) fs61.writeFileSync(card.file, stamped);
|
|
21503
21254
|
}
|
|
21504
21255
|
}
|
|
21505
21256
|
function unfinishedDelivery(id) {
|
|
@@ -21515,7 +21266,7 @@ function unfinishedDelivery(id) {
|
|
|
21515
21266
|
}[gap];
|
|
21516
21267
|
return `#${id} is not finished: ${what}.`;
|
|
21517
21268
|
}
|
|
21518
|
-
const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !
|
|
21269
|
+
const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs61.existsSync(path63.join(ASSETS, src.slice(".assets/".length))));
|
|
21519
21270
|
return missing ? `#${id} is not finished: ${missing} is not on this machine.` : null;
|
|
21520
21271
|
}
|
|
21521
21272
|
function agentDecisions(text5) {
|
|
@@ -21543,7 +21294,7 @@ function removeCard2(id, metric, options) {
|
|
|
21543
21294
|
if (unfinished) die(unfinished, { kind: "plan-delivery-unfinished" });
|
|
21544
21295
|
const dest = options.cleanupDiscarded && locateArchived(id) ? null : archiveDest(found2);
|
|
21545
21296
|
const cardFile3 = found2.kind === "group" ? path63.join(found2.target, "root.md") : found2.target;
|
|
21546
|
-
const cardText =
|
|
21297
|
+
const cardText = fs61.existsSync(cardFile3) ? fs61.readFileSync(cardFile3, "utf8") : "";
|
|
21547
21298
|
const { meta: cardMeta3 } = parseFrontmatter(cardText);
|
|
21548
21299
|
const alsoRemoved = found2.kind === "group" ? walkMd(found2.target).filter((f) => f !== cardFile3).map((f) => rel(f)).sort() : [];
|
|
21549
21300
|
const leftIds = leavingIds(id, found2);
|
|
@@ -21574,10 +21325,10 @@ function removeCard2(id, metric, options) {
|
|
|
21574
21325
|
const groupRoot = found2.kind === "file" && !options.closing ? enclosingGroupRoot(found2.target) : null;
|
|
21575
21326
|
if (dest) {
|
|
21576
21327
|
stampLeaving(leaving, metric === "rejected" ? { reason: options.reason?.trim() ?? "", discard: options.discard === true } : null);
|
|
21577
|
-
|
|
21578
|
-
|
|
21328
|
+
fs61.mkdirSync(ARCHIVE, { recursive: true });
|
|
21329
|
+
fs61.renameSync(found2.target, dest);
|
|
21579
21330
|
} else {
|
|
21580
|
-
|
|
21331
|
+
fs61.rmSync(found2.target, { recursive: true, force: true });
|
|
21581
21332
|
}
|
|
21582
21333
|
const removedRefs = stripReadmeRefs(found2);
|
|
21583
21334
|
let marked2 = null;
|
|
@@ -21628,11 +21379,12 @@ function removeCard2(id, metric, options) {
|
|
|
21628
21379
|
function closeGroup(rootFile) {
|
|
21629
21380
|
const rootId = idPrefix(path63.basename(path63.dirname(rootFile)));
|
|
21630
21381
|
if (rootId === null) return null;
|
|
21382
|
+
if (runnerProject(KANBAN)) return null;
|
|
21631
21383
|
if (!groupCloses(rootFile)) return null;
|
|
21632
21384
|
say(`
|
|
21633
21385
|
every subtask line on #${rootId} is resolved \u2014 closing the group:`);
|
|
21634
21386
|
try {
|
|
21635
|
-
const { meta, body } = parseFrontmatter(
|
|
21387
|
+
const { meta, body } = parseFrontmatter(fs61.readFileSync(rootFile, "utf8"));
|
|
21636
21388
|
const res = cmdRemove(rootId, "completed", { closing: true });
|
|
21637
21389
|
return {
|
|
21638
21390
|
id: rootId,
|
|
@@ -21699,6 +21451,7 @@ var init_remove = __esm({
|
|
|
21699
21451
|
"use strict";
|
|
21700
21452
|
init_deliveries();
|
|
21701
21453
|
init_store();
|
|
21454
|
+
init_tasks();
|
|
21702
21455
|
init_env();
|
|
21703
21456
|
init_creation_lock();
|
|
21704
21457
|
init_lock();
|
|
@@ -21774,7 +21527,7 @@ var init_memory_file = __esm({
|
|
|
21774
21527
|
});
|
|
21775
21528
|
|
|
21776
21529
|
// src/commands/rule.ts
|
|
21777
|
-
import
|
|
21530
|
+
import fs62 from "node:fs";
|
|
21778
21531
|
import path64 from "node:path";
|
|
21779
21532
|
function cmdRule(askedName, flags) {
|
|
21780
21533
|
const agent = askedName.trim();
|
|
@@ -21792,7 +21545,7 @@ function readRuleText(file, text5) {
|
|
|
21792
21545
|
if (file !== void 0 && text5 !== void 0) die("pass --file or --text, not both");
|
|
21793
21546
|
if (file !== void 0) {
|
|
21794
21547
|
try {
|
|
21795
|
-
return
|
|
21548
|
+
return fs62.readFileSync(file, "utf8").trim();
|
|
21796
21549
|
} catch {
|
|
21797
21550
|
die(`can't read ${file} \u2014 write the rule to a file, then pass its path`);
|
|
21798
21551
|
}
|
|
@@ -21919,15 +21672,15 @@ var init_storyboard = __esm({
|
|
|
21919
21672
|
});
|
|
21920
21673
|
|
|
21921
21674
|
// src/lib/spec-contract.ts
|
|
21922
|
-
import
|
|
21675
|
+
import fs63 from "node:fs";
|
|
21923
21676
|
import path65 from "node:path";
|
|
21924
21677
|
function snapshotSpecs() {
|
|
21925
21678
|
const out = /* @__PURE__ */ new Map();
|
|
21926
|
-
if (!
|
|
21679
|
+
if (!fs63.existsSync(TODO)) return out;
|
|
21927
21680
|
for (const file of walkMd(TODO)) {
|
|
21928
21681
|
if (isLegacyRecurring(path65.relative(TODO, file))) continue;
|
|
21929
21682
|
const id = idPrefix(path65.basename(file) === "root.md" ? path65.basename(path65.dirname(file)) : path65.basename(file));
|
|
21930
|
-
if (id !== null) out.set(file, { id, text:
|
|
21683
|
+
if (id !== null) out.set(file, { id, text: fs63.readFileSync(file, "utf8") });
|
|
21931
21684
|
}
|
|
21932
21685
|
return out;
|
|
21933
21686
|
}
|
|
@@ -22103,9 +21856,9 @@ function validateSpec(file, text5, id) {
|
|
|
22103
21856
|
}
|
|
22104
21857
|
function inside(dir, name) {
|
|
22105
21858
|
try {
|
|
22106
|
-
const root =
|
|
22107
|
-
const real2 =
|
|
22108
|
-
return real2.startsWith(root + path65.sep) &&
|
|
21859
|
+
const root = fs63.realpathSync(dir);
|
|
21860
|
+
const real2 = fs63.realpathSync(path65.join(dir, name));
|
|
21861
|
+
return real2.startsWith(root + path65.sep) && fs63.statSync(real2).isFile() ? real2 : null;
|
|
22109
21862
|
} catch {
|
|
22110
21863
|
return null;
|
|
22111
21864
|
}
|
|
@@ -22153,7 +21906,7 @@ function compareProblem(attrs, id) {
|
|
|
22153
21906
|
const file = inside(dir, named2.name);
|
|
22154
21907
|
if (!file) return { message: `Compare shots ${at} names no file in this card's asset folder. Write the JSON there or fix the path.` };
|
|
22155
21908
|
try {
|
|
22156
|
-
return { data: JSON.parse(
|
|
21909
|
+
return { data: JSON.parse(fs63.readFileSync(file, "utf8")) };
|
|
22157
21910
|
} catch {
|
|
22158
21911
|
return { message: `${at} is not valid JSON.` };
|
|
22159
21912
|
}
|
|
@@ -22322,7 +22075,7 @@ var init_mockups = __esm({
|
|
|
22322
22075
|
});
|
|
22323
22076
|
|
|
22324
22077
|
// src/commands/spec-write.ts
|
|
22325
|
-
import
|
|
22078
|
+
import fs64 from "node:fs";
|
|
22326
22079
|
import path66 from "node:path";
|
|
22327
22080
|
function cmdSpecWrite(id, askedName, flags) {
|
|
22328
22081
|
const agent = findSpecAgent(askedName);
|
|
@@ -22332,10 +22085,10 @@ function cmdSpecWrite(id, askedName, flags) {
|
|
|
22332
22085
|
const found2 = locate(id);
|
|
22333
22086
|
if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
|
|
22334
22087
|
const file = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
|
|
22335
|
-
const { meta, body } = parseFrontmatter(
|
|
22088
|
+
const { meta, body } = parseFrontmatter(fs64.readFileSync(file, "utf8"));
|
|
22336
22089
|
if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
|
|
22337
22090
|
const { body: next, replaced } = splice(body, name, section);
|
|
22338
|
-
|
|
22091
|
+
fs64.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
|
|
22339
22092
|
say(`${replaced ? "rewrote" : "wrote"} the \`${name}\` section on #${id} (${rel(file)})`);
|
|
22340
22093
|
return { id, specAgent: name, replaced, file: rel(file) };
|
|
22341
22094
|
}
|
|
@@ -22344,7 +22097,7 @@ function readSection(file, text5) {
|
|
|
22344
22097
|
let raw;
|
|
22345
22098
|
if (file !== void 0) {
|
|
22346
22099
|
try {
|
|
22347
|
-
raw =
|
|
22100
|
+
raw = fs64.readFileSync(file, "utf8");
|
|
22348
22101
|
} catch {
|
|
22349
22102
|
die(`can't read ${file} \u2014 write your section to a file, then pass its path`);
|
|
22350
22103
|
}
|
|
@@ -22810,11 +22563,11 @@ var init_shared = __esm({
|
|
|
22810
22563
|
});
|
|
22811
22564
|
|
|
22812
22565
|
// src/commands/idea.ts
|
|
22813
|
-
import
|
|
22566
|
+
import fs65 from "node:fs";
|
|
22814
22567
|
function load() {
|
|
22815
22568
|
const file = ideasFile();
|
|
22816
22569
|
if (!file) die("Ideas exist only inside a WildCrane idea conversation.");
|
|
22817
|
-
const held4 = JSON.parse(
|
|
22570
|
+
const held4 = JSON.parse(fs65.readFileSync(file, "utf8"));
|
|
22818
22571
|
return { file, held: { workflows: held4.workflows ?? [], ideas: held4.ideas ?? [] } };
|
|
22819
22572
|
}
|
|
22820
22573
|
function workflowOf(held4, asked) {
|
|
@@ -22836,7 +22589,7 @@ function splitBody2(text5) {
|
|
|
22836
22589
|
}
|
|
22837
22590
|
function readBody(bodyFile) {
|
|
22838
22591
|
if (!bodyFile) die("--body-file is required; write the idea in a writable temporary file first.");
|
|
22839
|
-
const text5 =
|
|
22592
|
+
const text5 = fs65.readFileSync(bodyFile, "utf8");
|
|
22840
22593
|
if (!text5.trim()) die("The idea must not be empty.");
|
|
22841
22594
|
return splitBody2(text5);
|
|
22842
22595
|
}
|
|
@@ -22848,7 +22601,7 @@ function named(held4, id) {
|
|
|
22848
22601
|
function cmdIdea(args, opts) {
|
|
22849
22602
|
const sub = args[0] ?? "";
|
|
22850
22603
|
const { file, held: held4 } = load();
|
|
22851
|
-
const write2 = () =>
|
|
22604
|
+
const write2 = () => fs65.writeFileSync(file, `${JSON.stringify(held4, null, 2)}
|
|
22852
22605
|
`);
|
|
22853
22606
|
if (sub === "new") {
|
|
22854
22607
|
const title = opts.title?.trim();
|
|
@@ -22917,7 +22670,7 @@ var board_exports = {};
|
|
|
22917
22670
|
__export(board_exports, {
|
|
22918
22671
|
buildBoardProgram: () => buildBoardProgram
|
|
22919
22672
|
});
|
|
22920
|
-
import
|
|
22673
|
+
import fs66 from "node:fs";
|
|
22921
22674
|
import path67 from "node:path";
|
|
22922
22675
|
async function dispatch(move2, cmd, args, opts, cli) {
|
|
22923
22676
|
const ctx = ctxOf(cmd, cli.program);
|
|
@@ -22944,7 +22697,7 @@ async function cloudCard(task, opts) {
|
|
|
22944
22697
|
let request;
|
|
22945
22698
|
if (typeof opts.bodyFile === "string") {
|
|
22946
22699
|
try {
|
|
22947
|
-
request =
|
|
22700
|
+
request = fs66.readFileSync(opts.bodyFile, "utf8");
|
|
22948
22701
|
} catch {
|
|
22949
22702
|
}
|
|
22950
22703
|
}
|
|
@@ -23098,7 +22851,7 @@ var init_board2 = __esm({
|
|
|
23098
22851
|
});
|
|
23099
22852
|
|
|
23100
22853
|
// src/lib/board-cli.ts
|
|
23101
|
-
import
|
|
22854
|
+
import fs67 from "node:fs";
|
|
23102
22855
|
import path68 from "node:path";
|
|
23103
22856
|
function useBoard(found2, dirNamed) {
|
|
23104
22857
|
if (!found2.named && found2.board === path68.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
|
|
@@ -23107,7 +22860,7 @@ function useBoard(found2, dirNamed) {
|
|
|
23107
22860
|
}
|
|
23108
22861
|
function whole(found2, move2) {
|
|
23109
22862
|
if (move2 === MAKES_A_BOARD) return found2;
|
|
23110
|
-
if (!
|
|
22863
|
+
if (!fs67.existsSync(path68.join(found2.board, "todo"))) {
|
|
23111
22864
|
die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
|
|
23112
22865
|
kind: "board-incomplete",
|
|
23113
22866
|
dir: found2.root
|
|
@@ -23131,10 +22884,10 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
|
|
|
23131
22884
|
const named2 = board2 ?? boardFromEnv();
|
|
23132
22885
|
if (named2) {
|
|
23133
22886
|
const folder = path68.resolve(cwd, named2);
|
|
23134
|
-
if (!
|
|
22887
|
+
if (!fs67.existsSync(folder) && move2 !== MAKES_A_BOARD) {
|
|
23135
22888
|
die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
|
|
23136
22889
|
}
|
|
23137
|
-
if (
|
|
22890
|
+
if (fs67.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
|
|
23138
22891
|
die(`${folder} is not a board \u2014 it has no todo/ and config.md. Run ${installHint} --board ${named2} to make one.`, {
|
|
23139
22892
|
kind: "no-board",
|
|
23140
22893
|
dir: folder
|
|
@@ -23144,7 +22897,7 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
|
|
|
23144
22897
|
}
|
|
23145
22898
|
if (dir !== null) {
|
|
23146
22899
|
const root = path68.resolve(dir);
|
|
23147
|
-
if (!
|
|
22900
|
+
if (!fs67.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
|
|
23148
22901
|
if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
|
|
23149
22902
|
die(`no board in ${root} \u2014 it has no docs/kanban/. Run ${installHint} there to make one.`, {
|
|
23150
22903
|
kind: "no-board",
|
|
@@ -23241,8 +22994,8 @@ var init_board_cli = __esm({
|
|
|
23241
22994
|
init_pointer();
|
|
23242
22995
|
init_tasks();
|
|
23243
22996
|
MAKES_A_BOARD = "init";
|
|
23244
|
-
hasBoard = (dir) =>
|
|
23245
|
-
isBoardDir = (dir) =>
|
|
22997
|
+
hasBoard = (dir) => fs67.existsSync(path68.join(dir, "docs", "kanban"));
|
|
22998
|
+
isBoardDir = (dir) => fs67.existsSync(path68.join(dir, "todo")) && fs67.existsSync(path68.join(dir, "config.md"));
|
|
23246
22999
|
boardFromEnv = () => {
|
|
23247
23000
|
const named2 = process.env.AI4KANBAN_BOARD?.trim();
|
|
23248
23001
|
return named2 ? named2 : null;
|
|
@@ -23270,7 +23023,7 @@ var init_archive2 = __esm({
|
|
|
23270
23023
|
var board_default;
|
|
23271
23024
|
var init_board3 = __esm({
|
|
23272
23025
|
"src/guide/board.md"() {
|
|
23273
|
-
board_default = "# How this board works\n\nThese rules apply to every card workflow. Read them once; each workflow's guide covers\nthe remaining details.\n\n## Layout\n\n```\ndocs/kanban/\n\u251C\u2500\u2500 todo/ open tasks\n\u2502 \u251C\u2500\u2500 README.md the index \u2014 read it first\n\u2502 \u2514\u2500\u2500 <id>-<slug>.md\n\u2502 one card per file; a group keeps its cards in one folder\n\u251C\u2500\u2500 memory/ all memory \u2014 see \"Who owns a memory file\"\n\u2502 \u251C\u2500\u2500 readme.md what shipped \u2014 the board's own record\n\u2502 \u251C\u2500\u2500 project.md what the project is today \u2014 written only by `akb describe-project`\n\u2502 \u251C\u2500\u2500 <name>/ a global memory: `MEMORY.md` (its rules, unless built in), `README.md`\n\u2502 \u2502 (its index) and its entries\n\u2502 \u2514\u2500\u2500 agents/ one folder per agent that keeps memory, named after it \u2014\n\u2502 `planner/` holds `decisions.md`, `rejected.md`, `redesign.md`\n\u251C\u2500\u2500 rules/ `<agent>.md` \u2014 the user's rule for one agent, appended to every run it\n\u2502 does; written from the board UI or `akb raw rule`\n\u251C\u2500\u2500 triage/ items waiting to be sorted, one file each \u2014 not cards. Only\n\u2502 \u2502 Cloud's sort or Refine idea on the web's Triage page turns one\n\u2502 \u2502 into a card; `akb triage check <source-id>` finds an existing one\n\u2502 \u251C\u2500\u2500 archived/ items a card was made of\n\u2502 \u251C\u2500\u2500 dismissed/ items ignored, with the reason \u2014 `akb triage restore` puts one back\n\u2502 \u2514\u2500\u2500 files/ dropped-in files\n\u2514\u2500\u2500 modules.md one line per module \u2014 `akb guide module-map` writes it\n```\n\n## Task ID\n\nEvery task's id is the number at the front of its filename (`04-plan-cap-enforcement.md` \u2192\nid 4): its Cloud task's number (`WC-4`). Only Cloud assigns one; `akb raw create` takes it.\n\n## Never hand-write a card's frontmatter\n\n`akb raw create`, `update`, and `update-questions` manage all of a card's frontmatter,\nincluding its `previews`. Edit only the card's **body** by hand. `akb raw help`\nlists all operations; `akb raw help <move>` explains one operation.\n\n## Inside a board run\n\nA board run carries `KANBAN_RUN` in its environment. Never unset or bypass it (for example\n`env -u KANBAN_RUN`): when a command only prints a flow, follow the printed flow.\n\n## Group task\n\nA group lives in `todo/<id>-<slug>/`: `root.md` tracks the whole job and each subtask is its\nown card beside it; `akb guide add-task` says how to build one. The root's `## Todo` lists the\nsubtasks, one line each ending in `#<subid>`: archiving a subtask ticks its line, rejecting\none strikes it through, and resolving the last line archives the root in the same run, with\nany open question or unticked todo of its own. A root listing no subtasks is
|
|
23026
|
+
board_default = "# How this board works\n\nThese rules apply to every card workflow. Read them once; each workflow's guide covers\nthe remaining details.\n\n## Layout\n\n```\ndocs/kanban/\n\u251C\u2500\u2500 todo/ open tasks\n\u2502 \u251C\u2500\u2500 README.md the index \u2014 read it first\n\u2502 \u2514\u2500\u2500 <id>-<slug>.md\n\u2502 one card per file; a group keeps its cards in one folder\n\u251C\u2500\u2500 memory/ all memory \u2014 see \"Who owns a memory file\"\n\u2502 \u251C\u2500\u2500 readme.md what shipped \u2014 the board's own record\n\u2502 \u251C\u2500\u2500 project.md what the project is today \u2014 written only by `akb describe-project`\n\u2502 \u251C\u2500\u2500 <name>/ a global memory: `MEMORY.md` (its rules, unless built in), `README.md`\n\u2502 \u2502 (its index) and its entries\n\u2502 \u2514\u2500\u2500 agents/ one folder per agent that keeps memory, named after it \u2014\n\u2502 `planner/` holds `decisions.md`, `rejected.md`, `redesign.md`\n\u251C\u2500\u2500 rules/ `<agent>.md` \u2014 the user's rule for one agent, appended to every run it\n\u2502 does; written from the board UI or `akb raw rule`\n\u251C\u2500\u2500 triage/ items waiting to be sorted, one file each \u2014 not cards. Only\n\u2502 \u2502 Cloud's sort or Refine idea on the web's Triage page turns one\n\u2502 \u2502 into a card; `akb triage check <source-id>` finds an existing one\n\u2502 \u251C\u2500\u2500 archived/ items a card was made of\n\u2502 \u251C\u2500\u2500 dismissed/ items ignored, with the reason \u2014 `akb triage restore` puts one back\n\u2502 \u2514\u2500\u2500 files/ dropped-in files\n\u2514\u2500\u2500 modules.md one line per module \u2014 `akb guide module-map` writes it\n```\n\n## Task ID\n\nEvery task's id is the number at the front of its filename (`04-plan-cap-enforcement.md` \u2192\nid 4): its Cloud task's number (`WC-4`). Only Cloud assigns one; `akb raw create` takes it.\n\n## Never hand-write a card's frontmatter\n\n`akb raw create`, `update`, and `update-questions` manage all of a card's frontmatter,\nincluding its `previews`. Edit only the card's **body** by hand. `akb raw help`\nlists all operations; `akb raw help <move>` explains one operation.\n\n## Inside a board run\n\nA board run carries `KANBAN_RUN` in its environment. Never unset or bypass it (for example\n`env -u KANBAN_RUN`): when a command only prints a flow, follow the printed flow.\n\n## Group task\n\nA group lives in `todo/<id>-<slug>/`: `root.md` tracks the whole job and each subtask is its\nown card beside it; `akb guide add-task` says how to build one. The root's `## Todo` lists the\nsubtasks, one line each ending in `#<subid>`: archiving a subtask ticks its line, rejecting\none strikes it through, and resolving the last line archives the root in the same run, with\nany open question or unticked todo of its own. A board a Runner keeps leaves that close to\nCloud, which waits for the root to be queued with no step on it. A root listing no subtasks is\nonly closed by hand.\n\n## Who owns a memory file\n\n**A memory file belongs to whoever reads and writes it.**\n\n- **`memory/readme.md`**: the board's record of shipped user-facing work.\n- **`memory/project.md`**: what the project is today, from its users' side, and the\n direction every judgement serves. Only `akb describe-project` writes it.\n- **`memory/agents/planner/`**: `decisions.md` holds user-facing answers that guide future\n planning and `redesign.md` design mistakes to avoid, both written by the memory review (`akb\n guide review-memory`); `rejected.md` holds what the user turned down and why, written only\n by the rejection and dismissal review (`akb guide review-rejections`). Other flows only read\n them, `akb prune-memory` aside.\n- **`memory/<name>/`**: a global memory, kept by every agent listing it under `akb.memory`\n by the rules each run is given. Only the user changes its `MEMORY.md`, from the board UI.\n- **`memory/agents/<agent>/`**: an agent keeps only the files and split paths its own AGENT.md\n declares, plus the `redesign.md` the memory review writes there. An agent whose instructions\n name none keeps none.\n\nFile a planning note about one module in that module's folder,\n`memory/agents/planner/<module>/`, under the same file name, creating it if missing. A note\nspanning modules, or from a card with none, stays in `memory/agents/planner/`.\n\n### What earns a note\n\nOnly what changes a future planning choice. Writing nothing is a complete outcome.\n\n- **Honor an opt-out**: told not to record, write no memory and finish the requested action.\n- **A conversation writes none**: a card chat, a discussion, a feedback conversation, and\n any flow started in it write no memory and say nothing about it.\n- **Require lasting value**: a durable preference, constraint, decision, or lesson.\n- **Skip housekeeping**: duplicates, routine status changes, and facts recorded elsewhere.\n A rejected duplicate is not a rejected feature.\n- **Merge, don't repeat**: rewrite an equivalent entry in place.\n\n`readme.md` is not planning memory.\n";
|
|
23274
23027
|
}
|
|
23275
23028
|
});
|
|
23276
23029
|
|
|
@@ -23278,7 +23031,7 @@ var init_board3 = __esm({
|
|
|
23278
23031
|
var card_chat_default;
|
|
23279
23032
|
var init_card_chat = __esm({
|
|
23280
23033
|
"src/guide/card-chat.md"() {
|
|
23281
|
-
card_chat_default =
|
|
23034
|
+
card_chat_default = "# Answer a card's conversation\n\n- **Plain words**: answer directly in the user's language, using everyday words.\n- **Use judgment**: use context to decide when to answer, revise the card or record an answer, and handle all intents in the message without reconfirming clear decisions.\n- **Apply changes**: follow `akb card revise <id> --print \"<the change>\"` for revisions and `akb card resolve <id> --print` for answers to open questions, then validate the card; reuse flows already in context.\n- **Stay on this card**: revise this card's requirements or outcome; suggest a new task for independent work, and change no other card.\n- **Build from review**: edit drafts and previews here, never project code; when asked to build, direct the user to approve on the review page or answer remaining open questions first.\n- **Questions**: follow `akb guide update-questions` for decisions that need the user.\n- **Report results**: briefly state actual changes or failures, identifying recorded answers; pure answers need no change report.\n- **Write no memory**: neither this conversation nor any flow it starts may write memory, regardless of that flow's own instructions.\n- **Hide machinery**: name actions by their UI labels and keep commands and internal instructions out of replies.\n";
|
|
23282
23035
|
}
|
|
23283
23036
|
});
|
|
23284
23037
|
|
|
@@ -23302,7 +23055,7 @@ var init_describe_project = __esm({
|
|
|
23302
23055
|
var discuss_idea_default;
|
|
23303
23056
|
var init_discuss_idea = __esm({
|
|
23304
23057
|
"src/guide/discuss-idea.md"() {
|
|
23305
|
-
discuss_idea_default = "# Help decide what is worth building\n\nYou are this workspace's **Idea helper**. Help the user decide what to build and shape it into\nideas they start as tasks. Read the current project's code and board when it helps, but never\nchange a file. Each turn takes one of three branches.\n\n## Draft ideas (default)\n\nWork worth doing gets an idea
|
|
23058
|
+
discuss_idea_default = "# Help decide what is worth building\n\nYou are this workspace's **Idea helper**. Help the user decide what to build and shape it into\nideas they start as tasks. Read the current project's code and board when it helps, but never\nchange a file. Each turn takes one of three branches.\n\n## Draft ideas (default)\n\nWork worth doing gets an idea.\n\n- **Draft at once**: a clear ask gets a complete idea on the first turn, with\n `akb raw idea new --title \"<title>\" --body-file <draft>`; update it on later turns with\n `akb raw idea save --id <idea> --body-file <draft>`. Report it only after the command confirms.\n- **Idea shape**: in the user's language, the outcome in at most 150 words, then a\n `## Done when` list of checks a reviewer can verify. No technical design.\n- **Pick the workflow**: add `--workflow <id>` naming the workflow in `akb workflow list` that\n does the work, judged by the work and not the topic, and `coding` when none fits; use the\n one the user names or picks. Call it by its listed name in the user's language.\n- **One idea per deliverable**: give separate deliverables separate ideas, and drop a replaced\n one with `akb raw idea drop --id <idea>`.\n- **Settle what you can**: decide the outcome questions you can judge and say which calls you\n made; ask only what the user alone can answer and would change the outcome, with the option\n you recommend.\n- **Finish the turn**: save the ideas and stop; never end by asking them to confirm or press\n **Start**.\n\n## Start on a go\n\nWhen the user tells you to go ahead, start the ideas they mean with\n`akb raw idea start --id <idea>`, drafting them first when needed, and say which tasks started.\n\n## Just answer\n\nA question, an idea not yet clear enough to draft, or a doubt about whether it is worth doing\ngets an answer, with no idea.\n\n- **Answer first**: say whether it is worth doing and why; disagreeing, advising a pause, or\n asking for evidence is a complete answer. Read a reliable source before stating an external\n fact.\n\n## Always\n\n- **Plain words**: answer directly in the user's language, using everyday words.\n\n- **Hide machinery**: never mention plans, files, paths, commands, or the tools and connectors\n you run with, and refer to actions by their button labels.\n";
|
|
23306
23059
|
}
|
|
23307
23060
|
});
|
|
23308
23061
|
|
|
@@ -23318,7 +23071,7 @@ var init_evaluate_task = __esm({
|
|
|
23318
23071
|
var follow_up_default;
|
|
23319
23072
|
var init_follow_up = __esm({
|
|
23320
23073
|
"src/guide/follow-up.md"() {
|
|
23321
|
-
follow_up_default = '# Handle follow-up work\n\nUse when planning or implementation discovers additional work.\n\n- **
|
|
23074
|
+
follow_up_default = '# Handle follow-up work\n\nUse when planning or implementation discovers additional work.\n\n- **Current task**: finish required work on this card; queue independent follow-ups in triage, then resume the original task.\n- **Drop what was turned down**: queue nothing that\n `docs/kanban/memory/agents/planner/rejected.md` or\n `docs/kanban/memory/agents/planner/*/rejected.md` turns down.\n- **Existing card**: when only another open card needs changing, run `akb card revise <id> "<the change and why>"`.\n- **Queue it**: use the command below; skip an item the command reports as already there.\n- **Workflow**: use the source card\'s `workflow:`, or the board default shown by `akb workflow` when it has none.\n\n```text\nakb triage add --title "<one line>" --slug <short-english-slug> --source "#<id>" --workflow <workflow> --text "<body>"\n```\n\n## Write the item\n\n- **Reader**: someone who has read neither the original card nor the code can decide in ten seconds whether the work is worth doing.\n- **Order**: first explain what happened and what the user runs into, then say what the work would do.\n- **Plain words**: keep file, function and field names, card ids, agent names and evidence trails out of the title and body.\n';
|
|
23322
23075
|
}
|
|
23323
23076
|
});
|
|
23324
23077
|
|
|
@@ -23326,7 +23079,7 @@ var init_follow_up = __esm({
|
|
|
23326
23079
|
var implement_default;
|
|
23327
23080
|
var init_implement = __esm({
|
|
23328
23081
|
"src/guide/implement.md"() {
|
|
23329
|
-
implement_default = "# Implement a settled card\n\nBuild the approved card. Preserve settled decisions and unrelated questions.\n\n- **Unchecked card**: a card with `triage:` set and an empty `## Scope` skipped planning; do\n what \"Unchecked card\" in `akb guide refine` says before building.\n- **User-action blockers**: follow `akb guide update-questions` and append a\n question describing the obstacle and action needed. Stop only dependent work; resume it\n after the user resolves the question. Settle routine technical choices yourself.\n- Do not replace or contradict what the card says.\n- **Approved mockups**: build each screen a card's mockup shows to match it, then compare\n your result with the mockup at desktop and phone widths and fix any difference.\n- **Approved API previews**: build each surface a card's API preview shows to match it,\n and write its documentation from it.\n- **Approved schema preview**: write the migration so the resulting schema matches the\n card's approved schema preview.\n- **Building is not shipping**: unless the card's outcome is itself a deploy, release, or\n publish, finish once the change lands and the checks pass; do not push, deploy, release,\n or publish to complete it.\n- **
|
|
23082
|
+
implement_default = "# Implement a settled card\n\nBuild the approved card. Preserve settled decisions and unrelated questions.\n\n- **Unchecked card**: a card with `triage:` set and an empty `## Scope` skipped planning; do\n what \"Unchecked card\" in `akb guide refine` says before building.\n- **User-action blockers**: follow `akb guide update-questions` and append a\n question describing the obstacle and action needed. Stop only dependent work; resume it\n after the user resolves the question. Settle routine technical choices yourself.\n- Do not replace or contradict what the card says.\n- **Approved mockups**: build each screen a card's mockup shows to match it, then compare\n your result with the mockup at desktop and phone widths and fix any difference.\n- **Approved API previews**: build each surface a card's API preview shows to match it,\n and write its documentation from it.\n- **Approved schema preview**: write the migration so the resulting schema matches the\n card's approved schema preview.\n- **Building is not shipping**: unless the card's outcome is itself a deploy, release, or\n publish, finish once the change lands and the checks pass; do not push, deploy, release,\n or publish to complete it.\n- **What needs it live**: leave any verification or step that needs the change live to the\n user. Name it in your final message; queue no follow-up and ask no question about it.\n- **Defer what can wait**: queue as a follow-up any other work this card does not require;\n never hold the card with a question about it.\n- **Follow-ups**: follow `akb guide follow-up` for additional work.\n- **Decisions made while building**: record an answered material decision the user could\n reasonably reverse under `## Worth noting after implementation`.\n- A printed, interactive implementation may stay uncommitted in the target checkout only\n when the card already supplies a concrete outcome, boundaries, and build steps.\n Otherwise use the tracked implementation path. Background runs always keep their delivery\n path.\n- **Checks**: nothing reviews the build after you; run focused checks for the affected path\n plus every repository-required check.\n\n## A build that writes its own card\n\n**Start now** sends a requirement with no card behind it \u2014 a typed sentence, the plan a\ndiscussion settled, or a triage item. Write the card first, in the same run, then build it. The board points\nthe run and its delivery at the card as the create lands, so from there it is an ordinary\nbuild.\n\n- **Read the plan first**: given a plan's path or a triage item's file, that file is the\n requirement \u2014 read it, and everything below reads \"the plan\" for \"the sentence\".\n- **Create it first**: `akb raw create --title \"..\"`. The title is one short line read off\n the sentence, or the plan's own title, in its own language \u2014 add\n `--slug <short-english-slug>` when that is not English, because filenames are ASCII. No `--modules`, no `--question` \u2014 nothing has evaluated this idea.\n- **The requirement is the summary**: replace the scaffold's opening paragraph with the\n sentence, or the plan's whole text, verbatim, in a fenced code block. Open the fence with\n more backticks than the longest run inside it, or the card fails validation on an unclosed\n fence.\n- **From a plan, name it**: pass `--source <plan path>` to the create.\n- **From a triage item, file it**: pass `--triage <source-id>` to the create, and run\n `akb triage archive <source-id> --card <id>` straight after, before any build work.\n- **Leave the rest of the scaffold**: `## Worth noting`, `## Scope`, `## Todo` and\n `## Decided by the agent` stay exactly as `raw create` wrote them.\n- **Then build that card**: the code block is the whole requirement \u2014 build exactly it and\n widen it no further. `akb card implement <id> --print` is the flow from here.\n";
|
|
23330
23083
|
}
|
|
23331
23084
|
});
|
|
23332
23085
|
|
|
@@ -23342,7 +23095,7 @@ var init_module_map = __esm({
|
|
|
23342
23095
|
var prune_memory_default;
|
|
23343
23096
|
var init_prune_memory = __esm({
|
|
23344
23097
|
"src/guide/prune-memory.md"() {
|
|
23345
|
-
prune_memory_default = "# Prune the memory\n\nPrune the board's own record in `docs/kanban/memory/` and every agent's memory beside it in\n`docs/kanban/memory/agents/<agent>/` \u2014 the planner's files and module folders among them (see\n\"Who owns a memory file\" in `akb guide board`). `project.md` is rewritten on its own schedule:\nleave it alone.\n\nOne principle for all files: they exist to stop us re-proposing work, re-making a\ndesign mistake, or re-asking a settled question. Rewrite each as **topics** (h2 title) with\nplain-language takeaways under each section. Keep only what helps\nfuture planning; drop code detail, dates, task ids, step-by-step stories.\n\nDrop every entry that fails \"What earns a note\" in `akb guide board` \u2014 the bar that governs a\nwrite governs what stays. Merge lines that say the same thing. Rewrite, don't just cut.\n\nOn top of that, per file:\n\n- `agents/planner/rejected.md` \u2014 one line per thing the user does not want, and why. Keep\n every source id in parentheses: they are how a restored item withdraws its line. \"Already\n done\" is a shipped fact, not a rejection \u2014 it belongs in the published doc (with a\n `readme.md` line pointing at it), not here; drop it from `rejected.md`.\n- `agents/planner/redesign.md` \u2014 one line per entry: the mistake, then the design to use.\n Drop an entry once that design is the obvious default.\n- `readme.md` \u2014 the board's own record, one line per shipped user-facing behavior. Where a\n published doc covers it, the line is just a link to that doc's path. **Replace a prose\n entry with a link only after you have confirmed the doc covers that behavior** \u2014 search the\n docs first; no doc yet means the prose line stays: `readme.md` is the only record until we\n document it. Anything else \u2014 watermarks, a last focus, open gaps, internal detail \u2014\n doesn't belong here; drop it.\n- `agents/planner/decisions.md` \u2014 one line per live decision, in plain user-facing words.\n Drop anything that fails its bar in `akb guide board` (\"Who owns a memory file\"): a call\n about code detail, or one the published docs now cover. Drop a decision once the question\n no longer arises or `redesign.md` states it as a rule.\n- `agents/<agent>/` \u2014 the files that agent's own AGENT.md defines, each pruned to what it says\n the file holds. Drop an entry once that
|
|
23098
|
+
prune_memory_default = "# Prune the memory\n\nPrune the board's own record in `docs/kanban/memory/` and every agent's memory beside it in\n`docs/kanban/memory/agents/<agent>/` \u2014 the planner's files and module folders among them (see\n\"Who owns a memory file\" in `akb guide board`). `project.md` is rewritten on its own schedule:\nleave it alone.\n\nOne principle for all files: they exist to stop us re-proposing work, re-making a\ndesign mistake, or re-asking a settled question. Rewrite each as **topics** (h2 title) with\nplain-language takeaways under each section. Keep only what helps\nfuture planning; drop code detail, dates, task ids, step-by-step stories.\n\nDrop every entry that fails \"What earns a note\" in `akb guide board` \u2014 the bar that governs a\nwrite governs what stays. Merge lines that say the same thing. Rewrite, don't just cut.\n\nOn top of that, per file:\n\n- `agents/planner/rejected.md` \u2014 one line per thing the user does not want, and why. Keep\n every source id in parentheses: they are how a restored item withdraws its line. \"Already\n done\" is a shipped fact, not a rejection \u2014 it belongs in the published doc (with a\n `readme.md` line pointing at it), not here; drop it from `rejected.md`.\n- `agents/planner/redesign.md` \u2014 one line per entry: the mistake, then the design to use.\n Drop an entry once that design is the obvious default.\n- `readme.md` \u2014 the board's own record, one line per shipped user-facing behavior. Where a\n published doc covers it, the line is just a link to that doc's path. **Replace a prose\n entry with a link only after you have confirmed the doc covers that behavior** \u2014 search the\n docs first; no doc yet means the prose line stays: `readme.md` is the only record until we\n document it. Anything else \u2014 watermarks, a last focus, open gaps, internal detail \u2014\n doesn't belong here; drop it.\n- `agents/planner/decisions.md` \u2014 one line per live decision, in plain user-facing words.\n Drop anything that fails its bar in `akb guide board` (\"Who owns a memory file\"): a call\n about code detail, or one the published docs now cover. Drop a decision once the question\n no longer arises or `redesign.md` states it as a rule.\n- `agents/<agent>/` \u2014 the files that agent's own AGENT.md defines, each pruned to what it says\n the file holds, and its `redesign.md`, pruned as the planner's is. Drop an entry once that\n agent's instructions or the design docs say it, and drop any other file its instructions no\n longer name.\n\nPrune a preview agent's files together, and drop the whole folder when its agent is gone from `akb\nspec`. Drop anything a file says about how the product looks \u2014 colours, dimensions,\ncomponent detail belong in the app's `design.md`, not here \u2014 and fold a product fact worth\nkeeping into the lesson or the decision it supports.\n";
|
|
23346
23099
|
}
|
|
23347
23100
|
});
|
|
23348
23101
|
|
|
@@ -23350,7 +23103,7 @@ var init_prune_memory = __esm({
|
|
|
23350
23103
|
var review_memory_default;
|
|
23351
23104
|
var init_review_memory = __esm({
|
|
23352
23105
|
"src/guide/review-memory.md"() {
|
|
23353
|
-
review_memory_default = "# Review what the conversations settled\n\nRead each conversation given to you right through, then write what it settled into memory.\n\nChats and planning flows write no memory themselves. One turn cannot see where an exchange\nis going, so a \"what if\" thrown out and taken back three turns later used to land as a\ndecision. You read the whole exchange, so you can tell a decision from a passing thought \u2014\nand that judgement is the whole job.\n\n- **Read what you are given**: each card comes in its own `<conversation>` block: its summary,\n its `Worth noting` and `Overruled by the user` lines, then its chat and the comments the user\n left reviewing it, when it had any, the user's words as written and the agent's replies,\n oldest first. The card is archived, so it is complete and reviewed this once; open no card or\n transcript file.\n- **Keep them apart**: judge each block by itself; what one conversation settled never\n becomes a note for another's card or agents.\n- **Hold to the same bar**: \"What earns a note\" in `akb guide board` governs what you write,\n exactly as it governs a flow that writes one. A conversation that settled nothing earns no\n note, and most do not \u2014 writing nothing is a complete result.\n- **Only what the user settled**: a durable preference, constraint, decision or correction\n they stated. Your own proposal is not their decision, and neither is a question, a \"what\n if\", or a direction the conversation then moved off.\n- **Take the last word**: where they said one thing and then another, the later one stands.\n Where a note already in the file says what they have since overturned, rewrite that note in\n place \u2014 never leave both and never add a second.\n- **Honour \"don't record this\"**: told in the conversation not to record something, write no\n note for it. If one is already there from an earlier review, delete it now.\n- **Where it goes**: a durable choice goes in the planner's `decisions.md` as one\n `**<key>**: <call>` line under the topic that fits, without its rationale; a design lesson\n goes in its `redesign.md` under the relevant topic as\n `- \u274C **<mistake>** \u2192 \u2705 <what the design should be instead, one line>.`, written as guidance\n for future tasks, not a history of the fix. Both go in the folder of the module each\n conversation is listed with (`akb guide board`). Each conversation also lists the agents\n whose sections its card holds and the memory files each keeps: where what was settled lands\n on one agent's own work, it goes in that agent's memory instead. Read that agent's AGENT.md\n (`akb raw agent-file <agent> AGENT.md`) only then, and keep its memory the way it says.\n Distill settled feedback into reusable preferences (dos) and corrections (don'ts), retaining\n their scope; never copy raw reviews. An agent whose instructions name no memory gets no new file.\n- **What the card settled**: the summary says what the card was about and earns no note. A\n `Worth noting` line is a decision the user approved, by answering it or letting it stand, so\n it can be a choice but never a correction, and it earns a planner note only where the user's\n own words in the conversation back it; a line under\n `Overruled by the user` is a call the user reversed
|
|
23106
|
+
review_memory_default = "# Review what the conversations settled\n\nRead each conversation given to you right through, then write what it settled into memory.\n\nChats and planning flows write no memory themselves. One turn cannot see where an exchange\nis going, so a \"what if\" thrown out and taken back three turns later used to land as a\ndecision. You read the whole exchange, so you can tell a decision from a passing thought \u2014\nand that judgement is the whole job.\n\n- **Read what you are given**: each card comes in its own `<conversation>` block: its summary,\n its `Worth noting` and `Overruled by the user` lines, then its chat and the comments the user\n left reviewing it, when it had any, the user's words as written and the agent's replies,\n oldest first. The card is archived, so it is complete and reviewed this once; open no card or\n transcript file.\n- **Keep them apart**: judge each block by itself; what one conversation settled never\n becomes a note for another's card or agents.\n- **Hold to the same bar**: \"What earns a note\" in `akb guide board` governs what you write,\n exactly as it governs a flow that writes one. A conversation that settled nothing earns no\n note, and most do not \u2014 writing nothing is a complete result.\n- **Only what the user settled**: a durable preference, constraint, decision or correction\n they stated. Your own proposal is not their decision, and neither is a question, a \"what\n if\", or a direction the conversation then moved off.\n- **Take the last word**: where they said one thing and then another, the later one stands.\n Where a note already in the file says what they have since overturned, rewrite that note in\n place \u2014 never leave both and never add a second.\n- **Honour \"don't record this\"**: told in the conversation not to record something, write no\n note for it. If one is already there from an earlier review, delete it now.\n- **Where it goes**: a durable choice goes in the planner's `decisions.md` as one\n `**<key>**: <call>` line under the topic that fits, without its rationale; a design lesson\n goes in its `redesign.md` under the relevant topic as\n `- \u274C **<mistake>** \u2192 \u2705 <what the design should be instead, one line>.`, written as guidance\n for future tasks, not a history of the fix. Both go in the folder of the module each\n conversation is listed with (`akb guide board`). Each conversation also lists the agents\n whose sections its card holds and the memory files each keeps: where what was settled lands\n on one agent's own work, it goes in that agent's memory instead. Read that agent's AGENT.md\n (`akb raw agent-file <agent> AGENT.md`) only then, and keep its memory the way it says.\n Distill settled feedback into reusable preferences (dos) and corrections (don'ts), retaining\n their scope; never copy raw reviews. An agent whose instructions name no memory gets no new file.\n- **What the card settled**: the summary says what the card was about and earns no note. A\n `Worth noting` line is a decision the user approved, by answering it or letting it stand, so\n it can be a choice but never a correction, and it earns a planner note only where the user's\n own words in the conversation back it; a line under\n `Overruled by the user` is a call the user reversed, so it goes in `redesign.md`: that of\n the agent whose section it came from, created there even if its instructions name other\n files, or the planner's when none did or its instructions name no memory.\n- **Change nothing else**: no card, no open question, no code. The memory\n files are the whole of what this run writes.\n- **Say what you wrote**: finish by reporting each conversation you read, and for each the\n notes you added, rewrote, deleted, or that you wrote none.\n";
|
|
23354
23107
|
}
|
|
23355
23108
|
});
|
|
23356
23109
|
|
|
@@ -23374,7 +23127,7 @@ var init_refine = __esm({
|
|
|
23374
23127
|
var reflect_default;
|
|
23375
23128
|
var init_reflect = __esm({
|
|
23376
23129
|
"src/guide/reflect.md"() {
|
|
23377
|
-
reflect_default =
|
|
23130
|
+
reflect_default = '# Reflect on completed cards\n\n## Read\n\nRead each given `<card>` in full and check its own `shipped` content; keep cards separate.\nFind needs the work left behind or revealed, and verify them in the project. Proposing nothing is fine.\n\n## Filter\n\n- **Duplicates**: check `akb raw list` and `akb triage check <source-id>`; propose shared work only once.\n- **Exclude**: skip unsupported guesses, work covered by `<scheduled-agents>`, work rejected in `docs/kanban/memory/agents/planner/rejected.md` or its module folders, and calls the user reversed, in any `redesign.md` under `docs/kanban/memory/agents/` or under a card\'s `Overruled by the user`.\n- **Existing cards**: when shipped work invalidates an open card\'s plan, run `akb card revise <id> "<the change and why>"` instead of proposing an item.\n\n## Write\n\n- **Reader**: someone who has read neither the original card nor the code can decide in ten seconds whether the work is worth doing.\n- **Order**: first explain what happened and what the user runs into, then say what the work would do.\n- **Plain words**: keep file, function and field names, card ids, agent names and evidence trails out of the title and body.\n\nWrite the body to a temporary Markdown file and end with the source card\'s archive path.\nWrite one triage item per proposal. Exploratory ideas are welcome; identify small chores as such.\nUse the source card\'s `workflow:`, or the board default shown by `akb workflow` when it has none.\n\n```text\nakb triage add --title "<one line>" --slug <short-english-slug> --source "#<id>" --workflow <workflow> --file <body.md>\n```\n\nBriefly report what you proposed and what you skipped, with reasons.\n';
|
|
23378
23131
|
}
|
|
23379
23132
|
});
|
|
23380
23133
|
|
|
@@ -23402,14 +23155,6 @@ var init_preview_agent = __esm({
|
|
|
23402
23155
|
}
|
|
23403
23156
|
});
|
|
23404
23157
|
|
|
23405
|
-
// src/guide/update.md
|
|
23406
|
-
var update_default;
|
|
23407
|
-
var init_update = __esm({
|
|
23408
|
-
"src/guide/update.md"() {
|
|
23409
|
-
update_default = "# Updating the board\n\nUpdating is two things, and nothing else: **a newer `akb`, then a repaired board.** There\nis no third step where the skill is re-installed to pick up a fixed flow \u2014 the flows ship\ninside the command, so a newer command is a newer set of flows everywhere at once.\n\n## 1. A newer command\n\n```\nnpm install -g wildcrane@latest\n```\n\n`akb update` can't do this one to itself \u2014 replacing the file that is running is how you\nget half a command \u2014 so it checks npm and names this line when it is behind.\n\nStill on the old `ai4kanban` package? Run `npm uninstall -g ai4kanban` first; both install `akb`.\n\n## 2. A repaired board\n\n```\nakb update\n```\n\nRun it from the project root. It does the whole mechanical part:\n\n- rewrites the installed `SKILL.md` (the note) in whichever of `.claude/skills/kanban/`\n (Claude Code) and `.agents/skills/kanban/` (Codex) is already there, wholesale, which also\n clears out what older versions left beside it: the `references/` folder of copied flows,\n a skill-folder `config.md`, and the command's own `kanban.mjs`. The note is the whole\n skill now \u2014 the command comes from npm, so the update is two deletions to commit. It never\n adds a folder that isn't there: the skill is an extra a project opts into, so a project\n without one is not broken, and `akb skill install` is how it is added,\n- adds what an older version never wrote: `config.md`, `modules.md`, the planner's memory folder,\n an empty `docs/kanban/memory/project.md`,\n- deletes `releases.md` and every card's `release` field, keeping\n `.release-summaries/` as a record,\n- renames `docs/kanban/memory/product.md` to `project.md`,\n- moves a goal the user wrote in `docs/kanban/memory/goal.md` to the top of the planner's\n `decisions.md` and deletes the file,\n- moves a memory set still sitting at the board root into `docs/kanban/memory/`,\n- moves the memory onto whoever writes it: `decisions.md`, `rejected.md` and `redesign.md`\n merge into `docs/kanban/memory/agents/planner/<module>/` (the board's own copy into\n `planner/`), each module's `readme.md` merges into `docs/kanban/memory/readme.md`, and a\n `## <module>` topic in a planner file moves into that module's folder \u2014 merged, never\n overwritten, and only once,\n- rescues a filled-in `config.md` left inside an old skill folder to\n `docs/kanban/config.md`,\n- prints which version you moved from and to, with a link to everything that changed\n between those two releases.\n\n**The one rule: `docs/kanban/` is yours; the two installed files are upstream's and get\noverwritten wholesale. Never merge the two.** It is safe to run twice, and it never edits\nyour cards, your config, or your memory.\n\n## Then read what it printed\n\nAnything the command can't decide comes back under **Needs your attention**. Each line is\nyours to finish:\n\n- **A new config setting.** Add that one line to `docs/kanban/config.md` and ask the user\n only for the new value. Leave the rest of the config alone.\n- **A blank `docs/kanban/modules.md`.** Write it per `akb guide module-map`, then run\n `akb update` again so every module gets a memory path.\n- **A per-module `goal.md`.** Fold what it says into that module's `decisions.md` in\n `docs/kanban/memory/agents/planner/<module>/`, then delete it.\n- **A `docs/kanban/config.from-skill.md`.** An old skill folder held a config that differs\n from the board's. Fold anything worth keeping into `docs/kanban/config.md`, then delete it.\n- **A missing agent folder.** Only one of the two is here, or neither. `akb skill install`\n writes them if you want that agent to see the board too \u2014 it is never added behind the\n user's back.\n\nLeave the project-wide notes and the cards alone \u2014 don't move notes into the module paths,\nand don't hand-add `modules:` lines to cards.\n\n## Plugin install (the note lives in the plugin cache)\n\nThe plugin cache is read-only, so the note comes from the marketplace instead:\n\n```\n/plugin marketplace update kanban\n/plugin install kanban@kanban\n```\n\nThen run `akb update` anyway \u2014 it repairs the board, which the plugin never touches.\n\n## Verify\n\n```\nakb raw version\n```\n\nTell the user to review `git diff` before committing. The board app updates separately \u2014\nit says when a newer one is out and installs it from that notice.\n";
|
|
23410
|
-
}
|
|
23411
|
-
});
|
|
23412
|
-
|
|
23413
23158
|
// src/guide/update-questions.md
|
|
23414
23159
|
var update_questions_default;
|
|
23415
23160
|
var init_update_questions = __esm({
|
|
@@ -23430,7 +23175,7 @@ var init_write_agent = __esm({
|
|
|
23430
23175
|
var writing_default;
|
|
23431
23176
|
var init_writing = __esm({
|
|
23432
23177
|
"src/guide/writing.md"() {
|
|
23433
|
-
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
|
|
23178
|
+
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<\u7528\u4E00\u5C0F\u6BB5\u8BDD\u5148\u8BB2\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u7528\u6237\u9047\u5230\u4EC0\u4E48\uFF0C\u518D\u8BB2\u8FD9\u5F20\u5361\u8981\u8FBE\u5230\u7684\u7ED3\u679C\u3002>\n\n## Worth noting <- what whoever builds it must not miss; 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- **What goes here**: what whoever builds the card must not miss \u2014 the user\'s answers first,\n then a decision that passes the material-decision test in `akb guide update-questions`; a lasting\n answer that fails it goes below the human half.\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- **What goes here**: the same, for what the build surfaced and settled.\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- **File name**: when the card\'s file name does not say in English what the card is, rename\n it with `akb raw update <id> --slug <short-english-slug>`; leave a group\'s root as it is.\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';
|
|
23434
23179
|
}
|
|
23435
23180
|
});
|
|
23436
23181
|
|
|
@@ -23479,7 +23224,6 @@ var init_guide = __esm({
|
|
|
23479
23224
|
init_resolve2();
|
|
23480
23225
|
init_revise();
|
|
23481
23226
|
init_preview_agent();
|
|
23482
|
-
init_update();
|
|
23483
23227
|
init_update_questions();
|
|
23484
23228
|
init_write_agent();
|
|
23485
23229
|
init_writing();
|
|
@@ -23506,8 +23250,7 @@ var init_guide = __esm({
|
|
|
23506
23250
|
{ name: "reflect", when: "propose the work just-completed cards leave behind", text: reflect_default },
|
|
23507
23251
|
{ name: "preview-agent", when: "be a preview agent: write your one section of a card", text: preview_agent_default },
|
|
23508
23252
|
{ name: "write-agent", when: "create or change an agent, including one built from a skill \u2014 its AGENT.md, files, memory, and how to check it", text: write_agent_default },
|
|
23509
|
-
{ name: "module-map", when: "write or rebuild docs/kanban/modules.md", text: module_map_default }
|
|
23510
|
-
{ name: "update", when: "pull a newer version into a project that already has one", text: update_default }
|
|
23253
|
+
{ name: "module-map", when: "write or rebuild docs/kanban/modules.md", text: module_map_default }
|
|
23511
23254
|
];
|
|
23512
23255
|
guideNames = () => GUIDES.map((g) => g.name);
|
|
23513
23256
|
RENAMED = {
|
|
@@ -23518,7 +23261,7 @@ var init_guide = __esm({
|
|
|
23518
23261
|
});
|
|
23519
23262
|
|
|
23520
23263
|
// src/lib/agent/approve.ts
|
|
23521
|
-
import
|
|
23264
|
+
import fs73 from "node:fs";
|
|
23522
23265
|
import path74 from "node:path";
|
|
23523
23266
|
function approvalRequest(req) {
|
|
23524
23267
|
const id = req.id;
|
|
@@ -23538,11 +23281,11 @@ function advancePhase(id) {
|
|
|
23538
23281
|
const found2 = locate(id);
|
|
23539
23282
|
if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
|
|
23540
23283
|
const file = found2.kind === "group" ? path74.join(found2.target, "root.md") : found2.target;
|
|
23541
|
-
const { meta, body } = parseFrontmatter(
|
|
23284
|
+
const { meta, body } = parseFrontmatter(fs73.readFileSync(file, "utf8"));
|
|
23542
23285
|
if (!meta) return { error: `#${id} has no frontmatter` };
|
|
23543
23286
|
meta.phase = card.stage + 1;
|
|
23544
23287
|
meta.status = "todo";
|
|
23545
|
-
|
|
23288
|
+
fs73.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
|
|
23546
23289
|
return null;
|
|
23547
23290
|
}
|
|
23548
23291
|
var init_approve = __esm({
|
|
@@ -23662,7 +23405,7 @@ var init_start = __esm({
|
|
|
23662
23405
|
});
|
|
23663
23406
|
|
|
23664
23407
|
// src/lib/agent/task-chats.ts
|
|
23665
|
-
import
|
|
23408
|
+
import fs74 from "node:fs";
|
|
23666
23409
|
import path75 from "node:path";
|
|
23667
23410
|
function taskChatFile(task, home3) {
|
|
23668
23411
|
if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
|
|
@@ -23671,7 +23414,7 @@ function taskChatFile(task, home3) {
|
|
|
23671
23414
|
function readTaskChat(task, home3) {
|
|
23672
23415
|
let text5;
|
|
23673
23416
|
try {
|
|
23674
|
-
text5 =
|
|
23417
|
+
text5 = fs74.readFileSync(taskChatFile(task, home3), "utf8");
|
|
23675
23418
|
} catch {
|
|
23676
23419
|
return [];
|
|
23677
23420
|
}
|
|
@@ -23697,7 +23440,7 @@ var init_task_chats = __esm({
|
|
|
23697
23440
|
});
|
|
23698
23441
|
|
|
23699
23442
|
// src/lib/agent/memory-review.ts
|
|
23700
|
-
import
|
|
23443
|
+
import fs75 from "node:fs";
|
|
23701
23444
|
import path76 from "node:path";
|
|
23702
23445
|
function taskChatsToReview(tasks) {
|
|
23703
23446
|
const out = [];
|
|
@@ -23712,7 +23455,7 @@ function taskChatsToReview(tasks) {
|
|
|
23712
23455
|
}
|
|
23713
23456
|
const messages = lines.map((l) => ({ role: l.role === "user" ? "you" : "agent", text: l.text }));
|
|
23714
23457
|
const review = handed(task, messages, card);
|
|
23715
|
-
if (review.settled
|
|
23458
|
+
if (review.settled) out.push(review);
|
|
23716
23459
|
}
|
|
23717
23460
|
return out;
|
|
23718
23461
|
}
|
|
@@ -23721,7 +23464,7 @@ function readArchived2(id) {
|
|
|
23721
23464
|
if (!found2) return null;
|
|
23722
23465
|
try {
|
|
23723
23466
|
const file = found2.kind === "group" ? path76.join(found2.target, "root.md") : found2.target;
|
|
23724
|
-
const { meta, body } = parseFrontmatter(
|
|
23467
|
+
const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
|
|
23725
23468
|
if (!meta) return null;
|
|
23726
23469
|
return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
|
|
23727
23470
|
} catch {
|
|
@@ -23789,9 +23532,74 @@ var init_memory_review = __esm({
|
|
|
23789
23532
|
}
|
|
23790
23533
|
});
|
|
23791
23534
|
|
|
23792
|
-
// src/lib/agent/
|
|
23793
|
-
import
|
|
23535
|
+
// src/lib/agent/dismissal-review.ts
|
|
23536
|
+
import fs76 from "node:fs";
|
|
23794
23537
|
import path77 from "node:path";
|
|
23538
|
+
function rejectionsToReview() {
|
|
23539
|
+
const dir = path77.join(ARCHIVE, REJECTIONS_TO_REVIEW);
|
|
23540
|
+
if (!fs76.existsSync(dir)) return [];
|
|
23541
|
+
const found2 = [];
|
|
23542
|
+
for (const file of walkMd(dir)) {
|
|
23543
|
+
const id = archivedId(file);
|
|
23544
|
+
if (id === null) continue;
|
|
23545
|
+
try {
|
|
23546
|
+
const { meta } = parseFrontmatter(fs76.readFileSync(file, "utf8"));
|
|
23547
|
+
if (!meta?.rejected || meta.rejected_by !== "user" || !meta.rejected_reason.trim()) continue;
|
|
23548
|
+
found2.push({ id, title: meta.title, file: rel(file), reason: meta.rejected_reason.trim(), rejectedAt: meta.rejected_at });
|
|
23549
|
+
} catch {
|
|
23550
|
+
continue;
|
|
23551
|
+
}
|
|
23552
|
+
}
|
|
23553
|
+
return found2.sort((a, b) => a.rejectedAt.localeCompare(b.rejectedAt) || a.id - b.id);
|
|
23554
|
+
}
|
|
23555
|
+
function dismissalsToReview() {
|
|
23556
|
+
return readAllDismissed().filter((s) => s.dismissedBy === "user" && s.dismissedReason.trim()).sort((a, b) => a.dismissedAt.localeCompare(b.dismissedAt)).map((s) => ({
|
|
23557
|
+
sourceId: s.sourceId,
|
|
23558
|
+
title: s.title,
|
|
23559
|
+
file: s.relPath,
|
|
23560
|
+
reason: s.dismissedReason.trim(),
|
|
23561
|
+
dismissedAt: s.dismissedAt
|
|
23562
|
+
}));
|
|
23563
|
+
}
|
|
23564
|
+
function citedSources(text5) {
|
|
23565
|
+
const ids = /* @__PURE__ */ new Set();
|
|
23566
|
+
for (const line of text5.split("\n")) {
|
|
23567
|
+
if (!/^\s*[-*]\s/.test(line)) continue;
|
|
23568
|
+
const m = line.match(SOURCES);
|
|
23569
|
+
for (const id of m?.[1].split(",") ?? []) if (id.trim()) ids.add(id.trim());
|
|
23570
|
+
}
|
|
23571
|
+
return [...ids];
|
|
23572
|
+
}
|
|
23573
|
+
function withdrawnSources() {
|
|
23574
|
+
const cited = [...new Set(plannerCopies("rejected.md").flatMap((file) => {
|
|
23575
|
+
try {
|
|
23576
|
+
return citedSources(fs76.readFileSync(file, "utf8"));
|
|
23577
|
+
} catch {
|
|
23578
|
+
return [];
|
|
23579
|
+
}
|
|
23580
|
+
}))];
|
|
23581
|
+
if (cited.length === 0) return [];
|
|
23582
|
+
const dismissed = new Set(readAllDismissed().map((s) => s.sourceId));
|
|
23583
|
+
const restored = new Set([...readInbox(), ...readArchived()].map((s) => s.sourceId));
|
|
23584
|
+
return cited.filter((id) => restored.has(id) && !dismissed.has(id));
|
|
23585
|
+
}
|
|
23586
|
+
var SOURCES;
|
|
23587
|
+
var init_dismissal_review = __esm({
|
|
23588
|
+
"src/lib/agent/dismissal-review.ts"() {
|
|
23589
|
+
"use strict";
|
|
23590
|
+
init_cards();
|
|
23591
|
+
init_frontmatter();
|
|
23592
|
+
init_memory();
|
|
23593
|
+
init_paths();
|
|
23594
|
+
init_inbox();
|
|
23595
|
+
init_archive();
|
|
23596
|
+
SOURCES = /\(([^()]+)\)\s*$/;
|
|
23597
|
+
}
|
|
23598
|
+
});
|
|
23599
|
+
|
|
23600
|
+
// src/lib/agent/flow.ts
|
|
23601
|
+
import fs77 from "node:fs";
|
|
23602
|
+
import path78 from "node:path";
|
|
23795
23603
|
function readCard2(id, home3 = "board") {
|
|
23796
23604
|
const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
|
|
23797
23605
|
if (!found2) {
|
|
@@ -23800,10 +23608,10 @@ function readCard2(id, home3 = "board") {
|
|
|
23800
23608
|
{ kind: "card-not-found", id }
|
|
23801
23609
|
);
|
|
23802
23610
|
}
|
|
23803
|
-
const file = found2.kind === "group" ?
|
|
23611
|
+
const file = found2.kind === "group" ? path78.join(found2.target, "root.md") : found2.target;
|
|
23804
23612
|
let text5;
|
|
23805
23613
|
try {
|
|
23806
|
-
text5 =
|
|
23614
|
+
text5 = fs77.readFileSync(file, "utf8");
|
|
23807
23615
|
} catch {
|
|
23808
23616
|
die(`#${id} is on the board but ${rel(file)} can't be read.`, { kind: "card-unreadable", id });
|
|
23809
23617
|
}
|
|
@@ -23927,7 +23735,7 @@ function workspaceField(delivery) {
|
|
|
23927
23735
|
if (!delivery?.worktree) return [];
|
|
23928
23736
|
return field("workspace", [
|
|
23929
23737
|
`write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
|
|
23930
|
-
|
|
23738
|
+
path78.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.`,
|
|
23931
23739
|
`every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
|
|
23932
23740
|
`${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
|
|
23933
23741
|
]);
|
|
@@ -24108,12 +23916,12 @@ function buildFlow(req, program2) {
|
|
|
24108
23916
|
}
|
|
24109
23917
|
case "resolve": {
|
|
24110
23918
|
facts.push(...questionsField(card.meta), ...stageField(card.meta));
|
|
24111
|
-
const
|
|
24112
|
-
if (
|
|
24113
|
-
facts.push(...answeringField(
|
|
23919
|
+
const building = activeDelivery(req.id);
|
|
23920
|
+
if (building) {
|
|
23921
|
+
facts.push(...answeringField(building, self));
|
|
24114
23922
|
facts.push(...notesField(card));
|
|
24115
23923
|
}
|
|
24116
|
-
if (
|
|
23924
|
+
if (building) close.push(answeredClose(building, self));
|
|
24117
23925
|
close.push(...stageClose(card.meta));
|
|
24118
23926
|
break;
|
|
24119
23927
|
}
|
|
@@ -24190,8 +23998,7 @@ function buildFlow(req, program2) {
|
|
|
24190
23998
|
facts.push(
|
|
24191
23999
|
...field("memory", [
|
|
24192
24000
|
`${rel(agentMemoryDir(PLANNER))}/<module>/ \u2014 decisions.md, rejected.md, redesign.md: where a planning note goes, the planner's own files for one spanning modules`,
|
|
24193
|
-
`${
|
|
24194
|
-
`${rel(AGENT_MEMORY)}/<agent>/ \u2014 an agent's own files, which its AGENT.md names`
|
|
24001
|
+
`${rel(AGENT_MEMORY)}/<agent>/ \u2014 an agent's own files, which its AGENT.md names, and its redesign.md`
|
|
24195
24002
|
])
|
|
24196
24003
|
);
|
|
24197
24004
|
for (const chat of chats) facts.push("", ...conversationBlock(chat));
|
|
@@ -24205,12 +24012,9 @@ function buildFlow(req, program2) {
|
|
|
24205
24012
|
}
|
|
24206
24013
|
// The rejection review (#929, #1497): the lists are the job, decided before any run starts.
|
|
24207
24014
|
case "review-dismissals": {
|
|
24208
|
-
const
|
|
24209
|
-
const
|
|
24210
|
-
const rejections = rejectionsToReview(since);
|
|
24211
|
-
const dismissals = dismissalsToReview(since);
|
|
24015
|
+
const rejections = rejectionsToReview();
|
|
24016
|
+
const dismissals = dismissalsToReview();
|
|
24212
24017
|
const withdrawn = withdrawnSources();
|
|
24213
|
-
facts.push(...field("window", lastRun ? `since the last review that passed, ${lastRun}` : req.allReasons ? "every reason on the board" : "every reason \u2014 none has been reviewed yet"));
|
|
24214
24018
|
facts.push(
|
|
24215
24019
|
...field(
|
|
24216
24020
|
"rejections",
|
|
@@ -24328,7 +24132,6 @@ var init_flow = __esm({
|
|
|
24328
24132
|
init_frontmatter();
|
|
24329
24133
|
init_io();
|
|
24330
24134
|
init_guide();
|
|
24331
|
-
init_cadence();
|
|
24332
24135
|
init_memory();
|
|
24333
24136
|
init_paths();
|
|
24334
24137
|
init_start();
|
|
@@ -24339,7 +24142,6 @@ var init_flow = __esm({
|
|
|
24339
24142
|
init_deliveries();
|
|
24340
24143
|
init_facts();
|
|
24341
24144
|
init_memory_review();
|
|
24342
|
-
init_settings2();
|
|
24343
24145
|
init_dismissal_review();
|
|
24344
24146
|
init_language();
|
|
24345
24147
|
init_prompts();
|
|
@@ -24677,7 +24479,7 @@ __export(watch_exports, {
|
|
|
24677
24479
|
watchRun: () => watchRun
|
|
24678
24480
|
});
|
|
24679
24481
|
import { spawn as spawn4 } from "node:child_process";
|
|
24680
|
-
import
|
|
24482
|
+
import fs78 from "node:fs";
|
|
24681
24483
|
async function watchRun(sessionId, resume = startResume) {
|
|
24682
24484
|
const spec = readSpec(sessionId);
|
|
24683
24485
|
const run2 = peekRun(sessionId);
|
|
@@ -24713,11 +24515,11 @@ async function watchRun(sessionId, resume = startResume) {
|
|
|
24713
24515
|
const record = claimed ?? run2;
|
|
24714
24516
|
const sub = record.action === "sub";
|
|
24715
24517
|
if (claim) await setCardStatus(claim.cardId, claim.status);
|
|
24716
|
-
|
|
24717
|
-
const logFile =
|
|
24518
|
+
fs78.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
24519
|
+
const logFile = fs78.createWriteStream(record.logPath, { flags: "a" });
|
|
24718
24520
|
logFile.on("error", () => {
|
|
24719
24521
|
});
|
|
24720
|
-
const eventsFile =
|
|
24522
|
+
const eventsFile = fs78.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
|
|
24721
24523
|
eventsFile.on("error", () => {
|
|
24722
24524
|
});
|
|
24723
24525
|
const event2 = (e) => eventsFile.write(`${JSON.stringify(e)}
|
|
@@ -24926,7 +24728,7 @@ async function watchRun(sessionId, resume = startResume) {
|
|
|
24926
24728
|
}
|
|
24927
24729
|
const retrying = !!again && !asked;
|
|
24928
24730
|
const spent = spoken ? spoken.usage : renderer?.usage?.();
|
|
24929
|
-
const lost = !!spec.again && !held4.deliveryId && !ours && !retrying && !spawnError && code !== 0 && lastUsed === 0 && !(spent && spent.input + spent.output + spent.cacheRead + spent.cacheCreation > 0);
|
|
24731
|
+
const lost = !!spec.again && (!held4.deliveryId || held4.action === "implement") && !ours && !retrying && !spawnError && code !== 0 && lastUsed === 0 && !(spent && spent.input + spent.output + spent.cacheRead + spent.cacheCreation > 0);
|
|
24930
24732
|
let status = asked ? "stopped" : silent || unfinished ? "error" : code === 0 ? "done" : "error";
|
|
24931
24733
|
const reported = spoken ? spoken.costUsd : renderer?.costUsd?.();
|
|
24932
24734
|
const totals = reportsSessionTotal(record.harness);
|
|
@@ -25298,7 +25100,7 @@ var init_watch = __esm({
|
|
|
25298
25100
|
});
|
|
25299
25101
|
|
|
25300
25102
|
// src/kanban.ts
|
|
25301
|
-
import
|
|
25103
|
+
import fs86 from "node:fs";
|
|
25302
25104
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25303
25105
|
|
|
25304
25106
|
// src/lib/cli/agent.ts
|
|
@@ -25787,7 +25589,7 @@ async function testAgent(args) {
|
|
|
25787
25589
|
}
|
|
25788
25590
|
|
|
25789
25591
|
// src/commands/cloud.ts
|
|
25790
|
-
import
|
|
25592
|
+
import fs69 from "node:fs";
|
|
25791
25593
|
import path70 from "node:path";
|
|
25792
25594
|
|
|
25793
25595
|
// src/lib/cloud/avatar.ts
|
|
@@ -25836,13 +25638,13 @@ init_config();
|
|
|
25836
25638
|
init_home();
|
|
25837
25639
|
init_config();
|
|
25838
25640
|
init_session();
|
|
25839
|
-
import
|
|
25641
|
+
import fs68 from "node:fs";
|
|
25840
25642
|
import path69 from "node:path";
|
|
25841
25643
|
var FRESH_MS = 10 * 6e4;
|
|
25842
25644
|
var GRACE_MS = 7 * 24 * 60 * 6e4;
|
|
25843
25645
|
var heldFile = () => path69.join(machineHome(), "pro.json");
|
|
25844
25646
|
function forgetPro() {
|
|
25845
|
-
|
|
25647
|
+
fs68.rmSync(heldFile(), { force: true });
|
|
25846
25648
|
}
|
|
25847
25649
|
|
|
25848
25650
|
// src/lib/cloud/account.ts
|
|
@@ -25990,12 +25792,12 @@ var SAID = {
|
|
|
25990
25792
|
en: "Could not reach WildCrane Cloud. Check your connection and try again."
|
|
25991
25793
|
},
|
|
25992
25794
|
credits_used_up: {
|
|
25993
|
-
zh: "\u672C\u6708\u79EF\u5206\u5DF2\u7528\u5B8C\uFF0C\u4E0B\u6708 1 \u65E5\uFF08UTC\uFF09\u91CD\u7F6E\uFF1B\u53EF\u5728\
|
|
25994
|
-
en: "This month\u2019s AI credits are used up. They reset on the 1st of next month (UTC); check your balance
|
|
25795
|
+
zh: "\u672C\u6708\u79EF\u5206\u5DF2\u7528\u5B8C\uFF0C\u4E0B\u6708 1 \u65E5\uFF08UTC\uFF09\u91CD\u7F6E\uFF1B\u53EF\u5728 WildCrane \u7F51\u9875\u8BBE\u7F6E\u4E2D\u67E5\u770B\u4F59\u989D\u3002",
|
|
25796
|
+
en: "This month\u2019s AI credits are used up. They reset on the 1st of next month (UTC); check your balance in WildCrane web settings."
|
|
25995
25797
|
},
|
|
25996
25798
|
trial_credits_used_up: {
|
|
25997
|
-
zh: "\u8BD5\u7528\u671F\u7684 AI \u79EF\u5206\u5DF2\u7528\u5B8C\u3002\u8BD5\u7528\u7ED3\u675F\u540E\u53EF\u5728\
|
|
25998
|
-
en: "Your trial\u2019s AI credits are used up. Get Pro from
|
|
25799
|
+
zh: "\u8BD5\u7528\u671F\u7684 AI \u79EF\u5206\u5DF2\u7528\u5B8C\u3002\u8BD5\u7528\u7ED3\u675F\u540E\u53EF\u5728 WildCrane \u7F51\u9875\u8BBE\u7F6E\u4E2D\u8D2D\u4E70 Pro\u3002",
|
|
25800
|
+
en: "Your trial\u2019s AI credits are used up. Get Pro from WildCrane web settings once the trial ends."
|
|
25999
25801
|
}
|
|
26000
25802
|
};
|
|
26001
25803
|
var REF_TYPES = {
|
|
@@ -26022,7 +25824,7 @@ async function cloudImage(opts, program2) {
|
|
|
26022
25824
|
const type = REF_TYPES[path70.extname(file).toLowerCase()];
|
|
26023
25825
|
if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line);
|
|
26024
25826
|
try {
|
|
26025
|
-
return `data:${type};base64,${
|
|
25827
|
+
return `data:${type};base64,${fs69.readFileSync(file).toString("base64")}`;
|
|
26026
25828
|
} catch (e) {
|
|
26027
25829
|
usage(`--ref ${file}: ${e instanceof Error ? e.message : String(e)}`, line);
|
|
26028
25830
|
}
|
|
@@ -26128,31 +25930,31 @@ async function generate(g) {
|
|
|
26128
25930
|
refuse(g.failed, g.fallback);
|
|
26129
25931
|
}
|
|
26130
25932
|
const out = g.outFor?.(res, g.out) ?? g.out;
|
|
26131
|
-
|
|
26132
|
-
|
|
25933
|
+
fs69.mkdirSync(path70.dirname(path70.resolve(out)), { recursive: true });
|
|
25934
|
+
fs69.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
|
|
26133
25935
|
say(`${out} (${g.label(res)})`);
|
|
26134
25936
|
return { out };
|
|
26135
25937
|
}
|
|
26136
25938
|
|
|
26137
25939
|
// src/commands/cloud-import.ts
|
|
26138
25940
|
import crypto3 from "node:crypto";
|
|
26139
|
-
import
|
|
25941
|
+
import fs71 from "node:fs";
|
|
26140
25942
|
import path72 from "node:path";
|
|
26141
25943
|
|
|
26142
25944
|
// ../cloud/src/board-files.ts
|
|
26143
25945
|
var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
|
|
26144
25946
|
var SETTINGS = ["modules.md", "config.md"];
|
|
26145
|
-
var isSettingsPath = (
|
|
25947
|
+
var isSettingsPath = (path86) => SETTINGS.includes(path86) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path86);
|
|
26146
25948
|
var BOARD_FILE_LIMIT = 256 * 1024;
|
|
26147
|
-
function isBoardPath(
|
|
26148
|
-
if (!
|
|
26149
|
-
if (
|
|
26150
|
-
if (
|
|
26151
|
-
if (
|
|
26152
|
-
if (SETTINGS.includes(
|
|
26153
|
-
if (
|
|
26154
|
-
if (
|
|
26155
|
-
return ROOTS.some((root) =>
|
|
25949
|
+
function isBoardPath(path86) {
|
|
25950
|
+
if (!path86 || path86.length > 500 || path86.startsWith("/") || path86.includes("\\") || path86.includes("\0")) return false;
|
|
25951
|
+
if (path86.split("/").some((part) => !part || part === "." || part === "..")) return false;
|
|
25952
|
+
if (path86 === "todo/README.md" || path86.split("/").includes(".sync")) return false;
|
|
25953
|
+
if (path86.startsWith("workflows/")) return true;
|
|
25954
|
+
if (SETTINGS.includes(path86)) return true;
|
|
25955
|
+
if (path86.startsWith("rules/")) return isSettingsPath(path86);
|
|
25956
|
+
if (path86.startsWith("assets/")) return path86.endsWith(".json");
|
|
25957
|
+
return ROOTS.some((root) => path86.startsWith(root)) && path86.endsWith(".md");
|
|
26156
25958
|
}
|
|
26157
25959
|
var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
26158
25960
|
|
|
@@ -26256,7 +26058,7 @@ init_bundled();
|
|
|
26256
26058
|
// src/lib/agents/shared-memory.ts
|
|
26257
26059
|
init_bundled();
|
|
26258
26060
|
init_parse();
|
|
26259
|
-
import
|
|
26061
|
+
import fs70 from "node:fs";
|
|
26260
26062
|
import path71 from "node:path";
|
|
26261
26063
|
function sharedMemory(board2) {
|
|
26262
26064
|
const declared = /* @__PURE__ */ new Map();
|
|
@@ -26272,13 +26074,13 @@ function sharedMemory(board2) {
|
|
|
26272
26074
|
const root = path71.join(board2, "agents");
|
|
26273
26075
|
let folders2 = [];
|
|
26274
26076
|
try {
|
|
26275
|
-
folders2 =
|
|
26077
|
+
folders2 = fs70.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
26276
26078
|
} catch {
|
|
26277
26079
|
}
|
|
26278
26080
|
for (const folder of folders2.sort()) {
|
|
26279
26081
|
let text5 = null;
|
|
26280
26082
|
try {
|
|
26281
|
-
text5 =
|
|
26083
|
+
text5 = fs70.readFileSync(path71.join(root, folder, "AGENT.md"), "utf8");
|
|
26282
26084
|
} catch {
|
|
26283
26085
|
}
|
|
26284
26086
|
take(folder, text5, false);
|
|
@@ -26367,12 +26169,12 @@ async function findProject(asked) {
|
|
|
26367
26169
|
}
|
|
26368
26170
|
throw new Refused(`No project of yours has the id or prefix "${asked}".`, "not_found");
|
|
26369
26171
|
}
|
|
26370
|
-
var read = (file) =>
|
|
26172
|
+
var read = (file) => fs71.readFileSync(file, "utf8");
|
|
26371
26173
|
var bytes2 = (text5) => Buffer.byteLength(text5, "utf8");
|
|
26372
26174
|
function files(dir) {
|
|
26373
|
-
if (!
|
|
26175
|
+
if (!fs71.existsSync(dir)) return [];
|
|
26374
26176
|
const out = [];
|
|
26375
|
-
for (const entry of
|
|
26177
|
+
for (const entry of fs71.readdirSync(dir, { withFileTypes: true })) {
|
|
26376
26178
|
if (entry.name.startsWith(".")) continue;
|
|
26377
26179
|
const full = path72.join(dir, entry.name);
|
|
26378
26180
|
if (entry.isDirectory()) out.push(...files(full));
|
|
@@ -26424,7 +26226,7 @@ function packWorkflow(flow) {
|
|
|
26424
26226
|
const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
|
|
26425
26227
|
for (const agent of new Set(named2)) {
|
|
26426
26228
|
const dir = path72.join(AGENTS, agent);
|
|
26427
|
-
if (
|
|
26229
|
+
if (fs71.existsSync(path72.join(dir, "AGENT.md"))) {
|
|
26428
26230
|
for (const file of files(dir)) out[`agents/${agent}/${posix2(path72.relative(dir, file))}`] = read(file);
|
|
26429
26231
|
continue;
|
|
26430
26232
|
}
|
|
@@ -26478,7 +26280,7 @@ function draftsOf(text5) {
|
|
|
26478
26280
|
if (!at) continue;
|
|
26479
26281
|
const base = at[1] === "assets" ? ASSETS : MOCKUPS;
|
|
26480
26282
|
const file = path72.resolve(base, at[2]);
|
|
26481
|
-
if (file.startsWith(base + path72.sep) &&
|
|
26283
|
+
if (file.startsWith(base + path72.sep) && fs71.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
|
|
26482
26284
|
}
|
|
26483
26285
|
return [...out].map(([name, file]) => ({ name, file }));
|
|
26484
26286
|
}
|
|
@@ -26514,7 +26316,7 @@ async function uploadDrafts(task, drafts) {
|
|
|
26514
26316
|
if (!card.body.includes(name)) continue;
|
|
26515
26317
|
const ext = path72.extname(file).toLowerCase();
|
|
26516
26318
|
const type = TYPES[ext] ?? "application/octet-stream";
|
|
26517
|
-
const data =
|
|
26319
|
+
const data = fs71.readFileSync(file);
|
|
26518
26320
|
const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
|
|
26519
26321
|
const upload = await api("POST", `/v1/artifacts/${artifact.id}/uploads`, { content_type: type, size: Math.max(data.length, 1) });
|
|
26520
26322
|
await api("PUT", `/v1/uploads/${upload.id}`, void 0, { bytes: data, type });
|
|
@@ -26557,7 +26359,7 @@ usage: ${program2} cloud import --project <id or prefix> [--dry-run]`, { kind: "
|
|
|
26557
26359
|
async function run(asked, dryRun) {
|
|
26558
26360
|
const project = await findProject(asked);
|
|
26559
26361
|
const cloudFlows = await all(`/v1/workflows?workspace=${encodeURIComponent(project.workspace)}`);
|
|
26560
|
-
const board2 = crypto3.createHash("sha256").update(
|
|
26362
|
+
const board2 = crypto3.createHash("sha256").update(fs71.realpathSync(KANBAN)).digest("hex").slice(0, 16);
|
|
26561
26363
|
const cards = openCards();
|
|
26562
26364
|
const problems = [];
|
|
26563
26365
|
const where = (file) => posix2(path72.relative(KANBAN, file));
|
|
@@ -26587,7 +26389,7 @@ async function run(asked, dryRun) {
|
|
|
26587
26389
|
const root = `projects/${project.id}/`;
|
|
26588
26390
|
const boardFiles = [];
|
|
26589
26391
|
for (const file of [...files(MEMORY), ...files(RULES), CONFIG, MODULES_MD]) {
|
|
26590
|
-
if (!
|
|
26392
|
+
if (!fs71.existsSync(file)) continue;
|
|
26591
26393
|
const rel2 = where(file);
|
|
26592
26394
|
if (!isBoardPath(rel2)) continue;
|
|
26593
26395
|
const content = read(file);
|
|
@@ -26698,16 +26500,16 @@ async function run(asked, dryRun) {
|
|
|
26698
26500
|
}
|
|
26699
26501
|
function leftLocal(own) {
|
|
26700
26502
|
const out = [];
|
|
26701
|
-
const count4 = (dir) =>
|
|
26503
|
+
const count4 = (dir) => fs71.existsSync(dir) ? walkMd(dir).length : 0;
|
|
26702
26504
|
const archived = count4(ARCHIVE);
|
|
26703
26505
|
if (archived) out.push(`.archive/ (${archived})`);
|
|
26704
26506
|
const triage = count4(TRIAGE);
|
|
26705
26507
|
if (triage) out.push(`triage/ (${triage})`);
|
|
26706
|
-
if (
|
|
26707
|
-
if (
|
|
26508
|
+
if (fs71.existsSync(DELIVERIES)) out.push("deliveries/");
|
|
26509
|
+
if (fs71.existsSync(path72.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
|
|
26708
26510
|
const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
|
|
26709
|
-
if (
|
|
26710
|
-
for (const e of
|
|
26511
|
+
if (fs71.existsSync(AGENTS)) {
|
|
26512
|
+
for (const e of fs71.readdirSync(AGENTS, { withFileTypes: true })) {
|
|
26711
26513
|
if (e.isDirectory() && !e.name.startsWith(".") && !named2.has(e.name)) out.push(`agents/${e.name}/`);
|
|
26712
26514
|
}
|
|
26713
26515
|
}
|
|
@@ -26725,7 +26527,7 @@ init_session();
|
|
|
26725
26527
|
init_io();
|
|
26726
26528
|
init_paths();
|
|
26727
26529
|
import { createHash as createHash4 } from "node:crypto";
|
|
26728
|
-
import
|
|
26530
|
+
import fs72 from "node:fs";
|
|
26729
26531
|
import path73 from "node:path";
|
|
26730
26532
|
var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
26731
26533
|
var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
@@ -26827,8 +26629,8 @@ This template has no preview.`);
|
|
|
26827
26629
|
const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
|
|
26828
26630
|
const file = path73.basename(p.path);
|
|
26829
26631
|
const out = path73.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
|
|
26830
|
-
|
|
26831
|
-
|
|
26632
|
+
fs72.mkdirSync(opts.to, { recursive: true });
|
|
26633
|
+
fs72.writeFileSync(out, bytes3);
|
|
26832
26634
|
written.push(out);
|
|
26833
26635
|
say(out);
|
|
26834
26636
|
}
|
|
@@ -26849,7 +26651,7 @@ async function templateGet(name, opts, program2) {
|
|
|
26849
26651
|
}
|
|
26850
26652
|
fetched.push({ out, bytes: bytes3 });
|
|
26851
26653
|
}
|
|
26852
|
-
const taken = fetched.filter((f) =>
|
|
26654
|
+
const taken = fetched.filter((f) => fs72.existsSync(f.out)).map((f) => path73.relative(to, f.out));
|
|
26853
26655
|
if (taken.length > 0) {
|
|
26854
26656
|
refuse2({
|
|
26855
26657
|
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`,
|
|
@@ -26857,8 +26659,8 @@ async function templateGet(name, opts, program2) {
|
|
|
26857
26659
|
});
|
|
26858
26660
|
}
|
|
26859
26661
|
for (const f of fetched) {
|
|
26860
|
-
|
|
26861
|
-
|
|
26662
|
+
fs72.mkdirSync(path73.dirname(f.out), { recursive: true });
|
|
26663
|
+
fs72.writeFileSync(f.out, f.bytes);
|
|
26862
26664
|
}
|
|
26863
26665
|
say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
|
|
26864
26666
|
Fetched ${name}: ${fetched.length} files into ${to}`);
|
|
@@ -26877,8 +26679,8 @@ async function templateSource(name, opts) {
|
|
|
26877
26679
|
continue;
|
|
26878
26680
|
}
|
|
26879
26681
|
const out = path73.join(opts.to, `${name}-${path73.basename(e.path)}`);
|
|
26880
|
-
|
|
26881
|
-
|
|
26682
|
+
fs72.mkdirSync(opts.to, { recursive: true });
|
|
26683
|
+
fs72.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
|
|
26882
26684
|
written.push(out);
|
|
26883
26685
|
say(out);
|
|
26884
26686
|
}
|
|
@@ -26887,13 +26689,13 @@ async function templateSource(name, opts) {
|
|
|
26887
26689
|
async function templatePublish(dir, opts) {
|
|
26888
26690
|
const root = path73.resolve(dir);
|
|
26889
26691
|
const manifest = path73.join(root, "template.md");
|
|
26890
|
-
if (!
|
|
26891
|
-
const meta = readTemplateMeta(
|
|
26692
|
+
if (!fs72.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
|
|
26693
|
+
const meta = readTemplateMeta(fs72.readFileSync(manifest, "utf8"));
|
|
26892
26694
|
const files2 = walk2(root);
|
|
26893
|
-
const total = files2.reduce((sum, f) => sum +
|
|
26695
|
+
const total = files2.reduce((sum, f) => sum + fs72.statSync(path73.join(root, f)).size, 0);
|
|
26894
26696
|
if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
|
|
26895
26697
|
for (const extra of [...opts.preview, ...opts.evidence]) {
|
|
26896
|
-
if (!
|
|
26698
|
+
if (!fs72.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
|
|
26897
26699
|
if (path73.resolve(extra).startsWith(root + path73.sep)) {
|
|
26898
26700
|
invalid(
|
|
26899
26701
|
`${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`,
|
|
@@ -26906,7 +26708,7 @@ async function templatePublish(dir, opts) {
|
|
|
26906
26708
|
}
|
|
26907
26709
|
const form = new FormData();
|
|
26908
26710
|
form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
|
|
26909
|
-
const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([
|
|
26711
|
+
const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs72.readFileSync(file)]), path73.basename(name));
|
|
26910
26712
|
for (const f of files2) add("files", f, path73.join(root, f));
|
|
26911
26713
|
for (const f of opts.preview) add("previews", path73.basename(f), f);
|
|
26912
26714
|
for (const f of opts.evidence) add("evidence", path73.basename(f), f);
|
|
@@ -26926,10 +26728,10 @@ Deleted ${name} from the catalog.`);
|
|
|
26926
26728
|
}
|
|
26927
26729
|
async function templateShowcase(name, dir) {
|
|
26928
26730
|
const file = path73.join(dir, "showcase.json");
|
|
26929
|
-
if (!
|
|
26731
|
+
if (!fs72.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
|
|
26930
26732
|
let meta;
|
|
26931
26733
|
try {
|
|
26932
|
-
meta = JSON.parse(
|
|
26734
|
+
meta = JSON.parse(fs72.readFileSync(file, "utf8"));
|
|
26933
26735
|
} catch {
|
|
26934
26736
|
return invalid("showcase.json \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002", "showcase.json is not valid JSON.");
|
|
26935
26737
|
}
|
|
@@ -26942,8 +26744,8 @@ async function templateShowcase(name, dir) {
|
|
|
26942
26744
|
form.set("meta", JSON.stringify(meta));
|
|
26943
26745
|
for (const p of new Set(named2)) {
|
|
26944
26746
|
const at = path73.join(dir, p);
|
|
26945
|
-
if (!
|
|
26946
|
-
form.append(`media:${p}`, new Blob([
|
|
26747
|
+
if (!fs72.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
|
|
26748
|
+
form.append(`media:${p}`, new Blob([fs72.readFileSync(at)]), p);
|
|
26947
26749
|
}
|
|
26948
26750
|
const done2 = await asJson(await ask(`/${encodeURIComponent(name)}/showcase`, { method: "PUT", body: form }));
|
|
26949
26751
|
say(`\u5DF2\u66F4\u65B0 ${name} \u7684\u5C55\u793A\u8D44\u6599\uFF1A${done2.files} \u4E2A\u6587\u4EF6\u3002
|
|
@@ -26952,7 +26754,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
|
|
|
26952
26754
|
}
|
|
26953
26755
|
function walk2(root, sub = "") {
|
|
26954
26756
|
const out = [];
|
|
26955
|
-
for (const entry of
|
|
26757
|
+
for (const entry of fs72.readdirSync(path73.join(root, sub), { withFileTypes: true })) {
|
|
26956
26758
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
26957
26759
|
const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
|
|
26958
26760
|
if (entry.isDirectory()) out.push(...walk2(root, rel2));
|
|
@@ -27093,12 +26895,6 @@ init_io();
|
|
|
27093
26895
|
init_paths();
|
|
27094
26896
|
init_workflows();
|
|
27095
26897
|
init_workflow_cards();
|
|
27096
|
-
init_cadence();
|
|
27097
|
-
init_scheduled();
|
|
27098
|
-
init_start();
|
|
27099
|
-
var cadenceLine = (one) => one.off ? "off" : isAuto(one.cadence) ? "auto" : `every ${one.cadence}`;
|
|
27100
|
-
var WAITS = { tooSoon: "", retrying: "retrying after a failed run", nothingNew: "nothing new yet", building: "waits for the build to finish" };
|
|
27101
|
-
var nextLine = (due) => !due ? "" : ` \xB7 next after ${formatStamp(due.next)}${due.wait && WAITS[due.wait] ? ` (${WAITS[due.wait]})` : ""}`;
|
|
27102
26898
|
var found = (id) => {
|
|
27103
26899
|
const flow = workflowById(id);
|
|
27104
26900
|
if (!flow) {
|
|
@@ -27122,11 +26918,7 @@ function cmdWorkflowList() {
|
|
|
27122
26918
|
if (description !== flow.name) say(` ${description}`);
|
|
27123
26919
|
say(` previews ${previewNames(previewMembers(flow)) || "(none)"}`);
|
|
27124
26920
|
say(` land ${names3(flow.land) || "(none) \u2014 the last approval archives the card"}`);
|
|
27125
|
-
for (const
|
|
27126
|
-
const due = scheduledNext(flow.id, one);
|
|
27127
|
-
const ran = one.lastRun ? `last run ${one.lastRun}` : "never run";
|
|
27128
|
-
say(` scheduled ${one.agent} ${cadenceLine(one)} \xB7 ${ran}${nextLine(due)}`);
|
|
27129
|
-
}
|
|
26921
|
+
for (const agent of listedScheduled(flow)) say(` scheduled ${agent}`);
|
|
27130
26922
|
for (const problem of workflowProblems(flow.id)) say(` ! ${problem}`);
|
|
27131
26923
|
}
|
|
27132
26924
|
const fileProblems = workflowFileProblems();
|
|
@@ -27135,7 +26927,7 @@ function cmdWorkflowList() {
|
|
|
27135
26927
|
workflows: rows.map((flow) => ({
|
|
27136
26928
|
...flow,
|
|
27137
26929
|
previews: previewMembers(flow).map((h) => ({ ...h, after: agentAfter(h.agent) })),
|
|
27138
|
-
scheduled:
|
|
26930
|
+
scheduled: listedScheduled(flow),
|
|
27139
26931
|
description: workflowPurpose(flow),
|
|
27140
26932
|
problems: workflowProblems(flow.id)
|
|
27141
26933
|
})),
|
|
@@ -27209,48 +27001,6 @@ function cmdWorkflowAgent(id, flags) {
|
|
|
27209
27001
|
say(`${flow.name}: ${changes.join(", ")}`);
|
|
27210
27002
|
return { id: flow.id, changes };
|
|
27211
27003
|
}
|
|
27212
|
-
async function cmdWorkflowSchedule(id, flags) {
|
|
27213
|
-
const flow = found(id);
|
|
27214
|
-
const changes = [];
|
|
27215
|
-
if (flags.on !== void 0) {
|
|
27216
|
-
done(switchWorkflowScheduled(flow.id, flags.on, true));
|
|
27217
|
-
changes.push(`${flags.on.trim()} on`);
|
|
27218
|
-
}
|
|
27219
|
-
if (flags.off !== void 0) {
|
|
27220
|
-
done(switchWorkflowScheduled(flow.id, flags.off, false));
|
|
27221
|
-
changes.push(`${flags.off.trim()} off`);
|
|
27222
|
-
}
|
|
27223
|
-
const who = (flags.on ?? flags.off ?? flags.run ?? "").trim();
|
|
27224
|
-
if (flags.cadence !== void 0) {
|
|
27225
|
-
if (!who) die("--cadence says how often one agent runs, so name it: --on <agent> --cadence 1d");
|
|
27226
|
-
done(setWorkflowScheduledCadence(flow.id, who, flags.cadence));
|
|
27227
|
-
changes.push(`${who}: every ${flags.cadence.trim()}`);
|
|
27228
|
-
}
|
|
27229
|
-
if (flags.extra !== void 0) {
|
|
27230
|
-
if (!who) die('--extra says what one agent is asked for here, so name it: --on <agent> --extra "\u2026"');
|
|
27231
|
-
done(setWorkflowScheduledExtra(flow.id, who, flags.extra));
|
|
27232
|
-
changes.push(`${who}: extra requirements`);
|
|
27233
|
-
}
|
|
27234
|
-
if (flags.run !== void 0) {
|
|
27235
|
-
const started = await startRun(scheduledRequest({ workflow: flow.id, agent: flags.run.trim() }));
|
|
27236
|
-
if ("error" in started) die(started.error);
|
|
27237
|
-
say(`${flow.name}: started ${flags.run.trim()} \u2014 run ${started.run.sessionId.slice(0, 8)}`);
|
|
27238
|
-
return { id: flow.id, changes, sessionId: started.run.sessionId };
|
|
27239
|
-
}
|
|
27240
|
-
if (!changes.length) {
|
|
27241
|
-
const rows = scheduledMembers(workflowById(flow.id) ?? flow);
|
|
27242
|
-
say(`${flow.name} \xB7 scheduled`);
|
|
27243
|
-
if (!rows.length) say(" (none) \u2014 an agent no workflow lists can be switched on here");
|
|
27244
|
-
for (const one of rows) {
|
|
27245
|
-
const due = scheduledNext(flow.id, one);
|
|
27246
|
-
const ran = one.lastRun ? `last run ${one.lastRun}` : "never run";
|
|
27247
|
-
say(` ${one.agent} ${cadenceLine(one)} \xB7 ${ran}${nextLine(due)}`);
|
|
27248
|
-
}
|
|
27249
|
-
return { id: flow.id, scheduled: rows };
|
|
27250
|
-
}
|
|
27251
|
-
say(`${flow.name} \xB7 scheduled: ${changes.join(", ")}`);
|
|
27252
|
-
return { id: flow.id, changes };
|
|
27253
|
-
}
|
|
27254
27004
|
|
|
27255
27005
|
// src/commands/run.ts
|
|
27256
27006
|
init_answers();
|
|
@@ -27289,7 +27039,7 @@ init_opencode();
|
|
|
27289
27039
|
init_locate();
|
|
27290
27040
|
init_store();
|
|
27291
27041
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
27292
|
-
import
|
|
27042
|
+
import path79 from "node:path";
|
|
27293
27043
|
var AGENTS2 = {
|
|
27294
27044
|
claude: "claude-code",
|
|
27295
27045
|
codex: "codex",
|
|
@@ -27304,10 +27054,10 @@ var AGENTS2 = {
|
|
|
27304
27054
|
var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
|
|
27305
27055
|
function agentOf2(args) {
|
|
27306
27056
|
const words = args.trim().split(/\s+/);
|
|
27307
|
-
const first =
|
|
27057
|
+
const first = path79.basename(words[0] ?? "");
|
|
27308
27058
|
const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
|
|
27309
27059
|
if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
|
|
27310
|
-
return AGENTS2[
|
|
27060
|
+
return AGENTS2[path79.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
|
|
27311
27061
|
}
|
|
27312
27062
|
function processChain() {
|
|
27313
27063
|
if (process.platform === "win32") return [];
|
|
@@ -27370,8 +27120,8 @@ init_tasks();
|
|
|
27370
27120
|
init_read();
|
|
27371
27121
|
init_rules();
|
|
27372
27122
|
init_board();
|
|
27373
|
-
import
|
|
27374
|
-
import
|
|
27123
|
+
import fs79 from "node:fs";
|
|
27124
|
+
import path80 from "node:path";
|
|
27375
27125
|
var FOLLOW_MS = 400;
|
|
27376
27126
|
async function cmdStartRun(action, args, opts, program2 = "akb") {
|
|
27377
27127
|
const { req, follow: follow2, print } = readRequest(action, args, opts);
|
|
@@ -27533,7 +27283,7 @@ function readRequest(action, args, opts) {
|
|
|
27533
27283
|
if (!opts.task?.length) die("name the archived Cloud tasks to review: --task <task:card...>", { kind: "run-refused", action });
|
|
27534
27284
|
return { req: { action, tasks: opts.task }, follow: follow2, print };
|
|
27535
27285
|
}
|
|
27536
|
-
if (action === "review-dismissals") return { req: { action
|
|
27286
|
+
if (action === "review-dismissals") return { req: { action }, follow: follow2, print };
|
|
27537
27287
|
if (action === "reflect") {
|
|
27538
27288
|
const cards = (opts.cards ?? []).map(Number);
|
|
27539
27289
|
if (!cards.length || !cards.every((n) => Number.isInteger(n) && n > 0)) {
|
|
@@ -27656,7 +27406,7 @@ async function cmdSubStart(words, opts) {
|
|
|
27656
27406
|
let prompt = words.join(" ");
|
|
27657
27407
|
if (opts.file) {
|
|
27658
27408
|
try {
|
|
27659
|
-
prompt =
|
|
27409
|
+
prompt = fs79.readFileSync(path80.resolve(opts.file), "utf8");
|
|
27660
27410
|
} catch {
|
|
27661
27411
|
die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
|
|
27662
27412
|
}
|
|
@@ -27938,7 +27688,7 @@ async function cmdWatch(sessionId) {
|
|
|
27938
27688
|
// src/commands/triage.ts
|
|
27939
27689
|
init_scheduled();
|
|
27940
27690
|
init_workflows();
|
|
27941
|
-
import
|
|
27691
|
+
import fs81 from "node:fs";
|
|
27942
27692
|
|
|
27943
27693
|
// src/lib/signals/index.ts
|
|
27944
27694
|
init_cards();
|
|
@@ -27948,16 +27698,16 @@ init_yaml();
|
|
|
27948
27698
|
// src/lib/signals/config.ts
|
|
27949
27699
|
init_settings2();
|
|
27950
27700
|
init_paths();
|
|
27951
|
-
import
|
|
27952
|
-
import
|
|
27701
|
+
import fs80 from "node:fs";
|
|
27702
|
+
import path81 from "node:path";
|
|
27953
27703
|
var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
|
|
27954
27704
|
var ENDPOINT_SETTING = "Triage endpoint";
|
|
27955
27705
|
var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
|
|
27956
|
-
var boardRel3 = (file) => rel(file).split(
|
|
27706
|
+
var boardRel3 = (file) => rel(file).split(path81.sep).join("/");
|
|
27957
27707
|
function signalEndpoint() {
|
|
27958
27708
|
let text5;
|
|
27959
27709
|
try {
|
|
27960
|
-
text5 =
|
|
27710
|
+
text5 = fs80.readFileSync(CONFIG, "utf8");
|
|
27961
27711
|
} catch {
|
|
27962
27712
|
return "";
|
|
27963
27713
|
}
|
|
@@ -28133,7 +27883,7 @@ async function cmdTriageAdd(opts) {
|
|
|
28133
27883
|
let body = opts.text;
|
|
28134
27884
|
if (opts.file !== void 0) {
|
|
28135
27885
|
try {
|
|
28136
|
-
body =
|
|
27886
|
+
body = fs81.readFileSync(opts.file, "utf8");
|
|
28137
27887
|
} catch {
|
|
28138
27888
|
die(`can't read ${opts.file} \u2014 write the item to a file, then pass its path`, { kind: "needs-input" });
|
|
28139
27889
|
}
|
|
@@ -28486,7 +28236,8 @@ function declareRuns(program2, cli) {
|
|
|
28486
28236
|
}
|
|
28487
28237
|
const workflow = withShared(program2.command("workflow")).summary("the workflows this board runs, and the agents each one lists").description(
|
|
28488
28238
|
"Every card runs through one workflow: previews approved by you, each waiting on the approvals its `after` names, then the agents that land the work, after which the card is archived; with none, the last approval archives it. A card chooses its previews when first planned. The built-ins can have their agents switched, and can be copied but not renamed or deleted. A card names its workflow in its own frontmatter (`akb raw create --workflow`), and a delivery freezes the one it started with."
|
|
28489
|
-
).action(async function() {
|
|
28239
|
+
).allowExcessArguments().allowUnknownOption().action(async function() {
|
|
28240
|
+
if (this.args.length) this.error(`unknown command '${this.args[0]}'`);
|
|
28490
28241
|
await workflowList(this, cli);
|
|
28491
28242
|
});
|
|
28492
28243
|
const flowWord = (name) => withShared(workflow.command(name));
|
|
@@ -28518,12 +28269,6 @@ function declareRuns(program2, cli) {
|
|
|
28518
28269
|
const flags = this.opts();
|
|
28519
28270
|
await onBoard2(this, cli, () => cmdWorkflowAgent(id, flags));
|
|
28520
28271
|
});
|
|
28521
|
-
flowWord("schedule").argument("<id>", "the workflow, from `workflow list`").option("--on <agent>", "switch one of this workflow's scheduled agents on").option("--off <agent>", "switch one off; it stays in the workflow").option("--cadence <cadence>", "how often the --on agent runs, e.g. 6h, 1d, 1d at 09:30, or auto (the default)").option("--extra <text>", "what the --on or --off agent is asked for here, on top of its own instructions").option("--run <agent>", "start one run of it now, due or not").summary("switch a workflow's scheduled agents, set how often each runs, or run one now").description(
|
|
28522
|
-
"With no change asked for, it lists them with their cadence and last run. A scheduled agent is listed under its workflow's `scheduled` and runs by itself on no card \u2014 in `auto`, when what its `akb.reads` names has something new; what it changes is committed and landed like a build."
|
|
28523
|
-
).action(async function(id) {
|
|
28524
|
-
const flags = this.opts();
|
|
28525
|
-
await onBoard2(this, cli, () => cmdWorkflowSchedule(id, flags));
|
|
28526
|
-
});
|
|
28527
28272
|
withShared(program2.command("guide")).argument("[topic]", "one flow in full; left off, every flow is listed one line each").summary("the flows the board works by, shipped with this command").description(
|
|
28528
28273
|
"A printed flow already carries the flows its action is done by, so this is for the rest: how the board works at all, the module map, updating, the local UI. `guide board` is the card format, the layout and the memory set."
|
|
28529
28274
|
).action(async function(...vals) {
|
|
@@ -28702,8 +28447,8 @@ function declareAgent(program2, cli) {
|
|
|
28702
28447
|
}
|
|
28703
28448
|
|
|
28704
28449
|
// src/lib/cli/setup.ts
|
|
28705
|
-
import
|
|
28706
|
-
import
|
|
28450
|
+
import fs85 from "node:fs";
|
|
28451
|
+
import path85 from "node:path";
|
|
28707
28452
|
|
|
28708
28453
|
// src/commands/install.ts
|
|
28709
28454
|
init_local();
|
|
@@ -28715,14 +28460,14 @@ init_io();
|
|
|
28715
28460
|
init_paths();
|
|
28716
28461
|
init_memory();
|
|
28717
28462
|
init_yaml2();
|
|
28718
|
-
import
|
|
28719
|
-
import
|
|
28463
|
+
import fs84 from "node:fs";
|
|
28464
|
+
import path84 from "node:path";
|
|
28720
28465
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
28721
28466
|
|
|
28722
28467
|
// src/lib/skill/install.ts
|
|
28723
28468
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
28724
|
-
import
|
|
28725
|
-
import
|
|
28469
|
+
import fs83 from "node:fs";
|
|
28470
|
+
import path83 from "node:path";
|
|
28726
28471
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
28727
28472
|
|
|
28728
28473
|
// ../skill/SKILL.md
|
|
@@ -28736,8 +28481,8 @@ init_version();
|
|
|
28736
28481
|
init_version();
|
|
28737
28482
|
init_project();
|
|
28738
28483
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
28739
|
-
import
|
|
28740
|
-
import
|
|
28484
|
+
import fs82 from "node:fs";
|
|
28485
|
+
import path82 from "node:path";
|
|
28741
28486
|
var MARKER3 = "# ai4kanban commit guard";
|
|
28742
28487
|
var MARKER_LINE = new RegExp(`^${MARKER3}\\b`, "m");
|
|
28743
28488
|
var CHECK = [
|
|
@@ -28771,7 +28516,7 @@ function hookScript(sessions) {
|
|
|
28771
28516
|
}
|
|
28772
28517
|
var read3 = (file) => {
|
|
28773
28518
|
try {
|
|
28774
|
-
return
|
|
28519
|
+
return fs82.readFileSync(file, "utf8");
|
|
28775
28520
|
} catch {
|
|
28776
28521
|
return null;
|
|
28777
28522
|
}
|
|
@@ -28783,7 +28528,7 @@ function hooksDir(root) {
|
|
|
28783
28528
|
windowsHide: true
|
|
28784
28529
|
});
|
|
28785
28530
|
if (out.error || out.status !== 0 || !out.stdout.trim()) return null;
|
|
28786
|
-
return
|
|
28531
|
+
return path82.resolve(root, out.stdout.trim());
|
|
28787
28532
|
}
|
|
28788
28533
|
function hasOwnHooksPath(root) {
|
|
28789
28534
|
const out = spawnSync6("git", ["config", "--get", "core.hooksPath"], {
|
|
@@ -28793,18 +28538,18 @@ function hasOwnHooksPath(root) {
|
|
|
28793
28538
|
});
|
|
28794
28539
|
return !out.error && out.status === 0 && !!out.stdout.trim();
|
|
28795
28540
|
}
|
|
28796
|
-
var sessionsPath = (root) =>
|
|
28541
|
+
var sessionsPath = (root) => path82.join(projectStateDir(path82.join(root, "docs", "kanban"), root), SESSIONS_FILE);
|
|
28797
28542
|
function readCommitHook(root) {
|
|
28798
28543
|
const dir = hooksDir(root);
|
|
28799
28544
|
if (!dir) return { state: "no-git" };
|
|
28800
|
-
const where = shown(root,
|
|
28545
|
+
const where = shown(root, path82.join(dir, "pre-commit"));
|
|
28801
28546
|
const line = guardLine(sessionsPath(root));
|
|
28802
28547
|
if (hasOwnHooksPath(root)) return { state: "hooks-path", path: where, line };
|
|
28803
|
-
const text5 = read3(
|
|
28548
|
+
const text5 = read3(path82.join(dir, "pre-commit"));
|
|
28804
28549
|
if (text5 === null) return { state: "absent", path: where };
|
|
28805
28550
|
return MARKER_LINE.test(text5) ? { state: "ours", path: where } : { state: "foreign", path: where, line };
|
|
28806
28551
|
}
|
|
28807
|
-
var shown = (root, p) => (
|
|
28552
|
+
var shown = (root, p) => (path82.relative(root, p) || p).split(path82.sep).join("/");
|
|
28808
28553
|
function installCommitHook(root) {
|
|
28809
28554
|
const hook = readCommitHook(root);
|
|
28810
28555
|
if (hook.state === "no-git") return {};
|
|
@@ -28815,11 +28560,11 @@ function installCommitHook(root) {
|
|
|
28815
28560
|
${hook.line}`
|
|
28816
28561
|
};
|
|
28817
28562
|
}
|
|
28818
|
-
const file =
|
|
28563
|
+
const file = path82.join(root, hook.path);
|
|
28819
28564
|
try {
|
|
28820
|
-
|
|
28821
|
-
|
|
28822
|
-
|
|
28565
|
+
fs82.mkdirSync(path82.dirname(file), { recursive: true });
|
|
28566
|
+
fs82.writeFileSync(file, hookScript(sessionsPath(root)), { mode: 493 });
|
|
28567
|
+
fs82.chmodSync(file, 493);
|
|
28823
28568
|
} catch (e) {
|
|
28824
28569
|
return { note: `${hook.path} could not be written \u2014 ${e instanceof Error ? e.message : String(e)}` };
|
|
28825
28570
|
}
|
|
@@ -28835,9 +28580,9 @@ function sayCommitHook(state) {
|
|
|
28835
28580
|
|
|
28836
28581
|
// src/lib/skill/install.ts
|
|
28837
28582
|
var TARGETS = [
|
|
28838
|
-
{ rel:
|
|
28583
|
+
{ rel: path83.join(".claude", "skills", "kanban"), agent: "Claude Code" },
|
|
28839
28584
|
{
|
|
28840
|
-
rel:
|
|
28585
|
+
rel: path83.join(".agents", "skills", "kanban"),
|
|
28841
28586
|
agent: "Codex, Cursor, OpenCode, Kimi Code, DeepSeek Harness, ZCode, Grok Build"
|
|
28842
28587
|
}
|
|
28843
28588
|
];
|
|
@@ -28851,23 +28596,23 @@ function rulesPath() {
|
|
|
28851
28596
|
}
|
|
28852
28597
|
function read4(file) {
|
|
28853
28598
|
try {
|
|
28854
|
-
return
|
|
28599
|
+
return fs83.readFileSync(file, "utf8");
|
|
28855
28600
|
} catch {
|
|
28856
28601
|
return null;
|
|
28857
28602
|
}
|
|
28858
28603
|
}
|
|
28859
28604
|
function statOf(p) {
|
|
28860
28605
|
try {
|
|
28861
|
-
return
|
|
28606
|
+
return fs83.lstatSync(p);
|
|
28862
28607
|
} catch {
|
|
28863
28608
|
return null;
|
|
28864
28609
|
}
|
|
28865
28610
|
}
|
|
28866
28611
|
function versionIn2(dir) {
|
|
28867
|
-
const note = read4(
|
|
28612
|
+
const note = read4(path83.join(dir, "SKILL.md"));
|
|
28868
28613
|
const stampedNote = note?.match(NOTE_STAMP);
|
|
28869
28614
|
if (stampedNote) return stampedNote[1];
|
|
28870
|
-
const src = read4(
|
|
28615
|
+
const src = read4(path83.join(dir, "kanban.mjs"));
|
|
28871
28616
|
if (!src) return null;
|
|
28872
28617
|
const stamped = src.slice(0, 400).match(BUILT_STAMP);
|
|
28873
28618
|
if (stamped) return stamped[1];
|
|
@@ -28891,7 +28636,7 @@ function folderState(dir) {
|
|
|
28891
28636
|
const st = statOf(dir);
|
|
28892
28637
|
if (!st) return { state: "absent", version: null };
|
|
28893
28638
|
if (st.isSymbolicLink()) return { state: "linked", version: versionIn2(dir) };
|
|
28894
|
-
if (!
|
|
28639
|
+
if (!fs83.existsSync(path83.join(dir, "SKILL.md"))) return { state: "absent", version: null };
|
|
28895
28640
|
const version = versionIn2(dir);
|
|
28896
28641
|
if (!version) return { state: "unknown", version: null };
|
|
28897
28642
|
return { state: isOlder(version, SKILL_VERSION) ? "stale" : "current", version };
|
|
@@ -28899,9 +28644,9 @@ function folderState(dir) {
|
|
|
28899
28644
|
function readSkillState(root) {
|
|
28900
28645
|
const dir = rootOr(root);
|
|
28901
28646
|
const folders2 = TARGETS.map((target) => ({
|
|
28902
|
-
path: target.rel.split(
|
|
28647
|
+
path: target.rel.split(path83.sep).join("/"),
|
|
28903
28648
|
agent: target.agent,
|
|
28904
|
-
...folderState(
|
|
28649
|
+
...folderState(path83.join(dir, target.rel))
|
|
28905
28650
|
}));
|
|
28906
28651
|
return {
|
|
28907
28652
|
version: SKILL_VERSION,
|
|
@@ -28911,9 +28656,9 @@ function readSkillState(root) {
|
|
|
28911
28656
|
};
|
|
28912
28657
|
}
|
|
28913
28658
|
function writeFolder(dir) {
|
|
28914
|
-
|
|
28915
|
-
|
|
28916
|
-
|
|
28659
|
+
fs83.rmSync(dir, { recursive: true, force: true });
|
|
28660
|
+
fs83.mkdirSync(dir, { recursive: true });
|
|
28661
|
+
fs83.writeFileSync(path83.join(dir, "SKILL.md"), `${SKILL_default.trimEnd()}
|
|
28917
28662
|
|
|
28918
28663
|
<!-- ai4kanban ${SKILL_VERSION} -->
|
|
28919
28664
|
`);
|
|
@@ -28928,8 +28673,8 @@ function installSkill(root, only) {
|
|
|
28928
28673
|
const wrote = [];
|
|
28929
28674
|
const skipped = [];
|
|
28930
28675
|
for (const target of TARGETS) {
|
|
28931
|
-
const dest =
|
|
28932
|
-
const shown2 = target.rel.split(
|
|
28676
|
+
const dest = path83.join(dir, target.rel);
|
|
28677
|
+
const shown2 = target.rel.split(path83.sep).join("/");
|
|
28933
28678
|
const { state: state2 } = folderState(dest);
|
|
28934
28679
|
if (state2 === "linked") {
|
|
28935
28680
|
skipped.push({ path: shown2, agent: target.agent, why: "a symlink into a source checkout \u2014 left untouched" });
|
|
@@ -28998,10 +28743,10 @@ var Report = class {
|
|
|
28998
28743
|
for (const line of this.notes) say(` ! ${line}`);
|
|
28999
28744
|
}
|
|
29000
28745
|
};
|
|
29001
|
-
var read5 = (file) =>
|
|
28746
|
+
var read5 = (file) => fs84.existsSync(file) ? fs84.readFileSync(file, "utf8") : null;
|
|
29002
28747
|
function statOf2(p) {
|
|
29003
28748
|
try {
|
|
29004
|
-
return
|
|
28749
|
+
return fs84.lstatSync(p);
|
|
29005
28750
|
} catch {
|
|
29006
28751
|
return null;
|
|
29007
28752
|
}
|
|
@@ -29012,10 +28757,10 @@ async function boardMove(root, args, board2) {
|
|
|
29012
28757
|
if (code !== 0) throw new BoardError(`\`board ${args.join(" ")}\` failed \u2014 nothing else was changed`, { kind: "board-move-failed" });
|
|
29013
28758
|
}
|
|
29014
28759
|
function boardAbove(root) {
|
|
29015
|
-
let at =
|
|
28760
|
+
let at = path84.dirname(path84.resolve(root));
|
|
29016
28761
|
for (; ; ) {
|
|
29017
|
-
if (
|
|
29018
|
-
const up =
|
|
28762
|
+
if (fs84.existsSync(path84.join(at, "docs", "kanban"))) return at;
|
|
28763
|
+
const up = path84.dirname(at);
|
|
29019
28764
|
if (up === at) return null;
|
|
29020
28765
|
at = up;
|
|
29021
28766
|
}
|
|
@@ -29023,10 +28768,10 @@ function boardAbove(root) {
|
|
|
29023
28768
|
async function cmdInstall(ctx) {
|
|
29024
28769
|
const report3 = new Report();
|
|
29025
28770
|
const board2 = ctx.board ?? null;
|
|
29026
|
-
const where = board2 ?
|
|
28771
|
+
const where = board2 ? path84.relative(ctx.dir, board2) || board2 : "docs/kanban";
|
|
29027
28772
|
say(`wildcrane ${SKILL_VERSION} \u2014 installing into ${ctx.dir}`);
|
|
29028
28773
|
say("");
|
|
29029
|
-
const above = board2 ||
|
|
28774
|
+
const above = board2 || fs84.existsSync(path84.join(ctx.dir, "docs", "kanban")) ? null : boardAbove(ctx.dir);
|
|
29030
28775
|
if (above) {
|
|
29031
28776
|
report3.notes.push(`there is already a board at ${above} \u2014 this makes a second one, and commands run here will find this one`);
|
|
29032
28777
|
}
|
|
@@ -29048,7 +28793,7 @@ async function cmdInstall(ctx) {
|
|
|
29048
28793
|
say("board UI (Configuration \u2192 Agent setup), or here:");
|
|
29049
28794
|
say("");
|
|
29050
28795
|
say(` ${ctx.program} skill`);
|
|
29051
|
-
return { installed: ctx.dir, board: board2 ??
|
|
28796
|
+
return { installed: ctx.dir, board: board2 ?? path84.join(ctx.dir, "docs", "kanban") };
|
|
29052
28797
|
}
|
|
29053
28798
|
async function cmdSkill(ctx, mode) {
|
|
29054
28799
|
const report3 = new Report();
|
|
@@ -29089,7 +28834,7 @@ function placeSkill(ctx, mode, report3) {
|
|
|
29089
28834
|
const before = readSkillState(ctx.dir).folders;
|
|
29090
28835
|
for (const folder of before) {
|
|
29091
28836
|
if (folder.state !== "absent" && folder.state !== "linked") {
|
|
29092
|
-
rescueSkillConfig(ctx.dir,
|
|
28837
|
+
rescueSkillConfig(ctx.dir, path84.join(ctx.dir, folder.path), report3);
|
|
29093
28838
|
}
|
|
29094
28839
|
}
|
|
29095
28840
|
const result = installSkill(ctx.dir, mode === "update" ? "present" : void 0);
|
|
@@ -29112,20 +28857,20 @@ function placeSkill(ctx, mode, report3) {
|
|
|
29112
28857
|
return { result, from: before.map((f) => f.version).find(Boolean) || null };
|
|
29113
28858
|
}
|
|
29114
28859
|
function rescueSkillConfig(root, skillDir, report3) {
|
|
29115
|
-
const old = read5(
|
|
28860
|
+
const old = read5(path84.join(skillDir, "config.md"));
|
|
29116
28861
|
if (!old || old.includes("{{")) return;
|
|
29117
|
-
const boardConfig =
|
|
28862
|
+
const boardConfig = path84.join(root, "docs", "kanban", "config.md");
|
|
29118
28863
|
const current = read5(boardConfig);
|
|
29119
28864
|
if (current === null) {
|
|
29120
|
-
|
|
29121
|
-
|
|
28865
|
+
fs84.mkdirSync(path84.dirname(boardConfig), { recursive: true });
|
|
28866
|
+
fs84.writeFileSync(boardConfig, old);
|
|
29122
28867
|
report3.did.push("moved your filled-in config.md out of the skill folder to docs/kanban/config.md");
|
|
29123
28868
|
return;
|
|
29124
28869
|
}
|
|
29125
28870
|
if (current === old) return;
|
|
29126
|
-
const aside =
|
|
29127
|
-
if (
|
|
29128
|
-
|
|
28871
|
+
const aside = path84.join(root, "docs", "kanban", "config.from-skill.md");
|
|
28872
|
+
if (fs84.existsSync(aside)) return;
|
|
28873
|
+
fs84.writeFileSync(aside, old);
|
|
29129
28874
|
report3.notes.push(
|
|
29130
28875
|
"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"
|
|
29131
28876
|
);
|
|
@@ -29178,8 +28923,8 @@ async function cmdUpdate2(ctx) {
|
|
|
29178
28923
|
return { updated: ctx.dir, from, to: SKILL_VERSION };
|
|
29179
28924
|
}
|
|
29180
28925
|
async function repairBoard(root, report3) {
|
|
29181
|
-
const board2 =
|
|
29182
|
-
if (!
|
|
28926
|
+
const board2 = path84.join(root, "docs", "kanban");
|
|
28927
|
+
if (!fs84.existsSync(board2)) {
|
|
29183
28928
|
report3.notes.push("no docs/kanban/ here \u2014 run `akb install` to scaffold the board");
|
|
29184
28929
|
return;
|
|
29185
28930
|
}
|
|
@@ -29209,20 +28954,20 @@ function moveRuntimes(root, board2, report3) {
|
|
|
29209
28954
|
}
|
|
29210
28955
|
}
|
|
29211
28956
|
function moveLegacyMemory(board2, report3) {
|
|
29212
|
-
const memory =
|
|
28957
|
+
const memory = path84.join(board2, "memory");
|
|
29213
28958
|
const moved = [];
|
|
29214
28959
|
for (const name of MEMORY_FILES) {
|
|
29215
|
-
const from =
|
|
28960
|
+
const from = path84.join(board2, name);
|
|
29216
28961
|
const st = statOf2(from);
|
|
29217
28962
|
if (!st || !st.isFile()) continue;
|
|
29218
|
-
|
|
29219
|
-
const to =
|
|
28963
|
+
fs84.mkdirSync(memory, { recursive: true });
|
|
28964
|
+
const to = path84.join(memory, name);
|
|
29220
28965
|
const there = read5(to);
|
|
29221
28966
|
if (there === null) {
|
|
29222
|
-
|
|
28967
|
+
fs84.renameSync(from, to);
|
|
29223
28968
|
moved.push(name);
|
|
29224
28969
|
} else if (there === read5(from)) {
|
|
29225
|
-
|
|
28970
|
+
fs84.rmSync(from);
|
|
29226
28971
|
moved.push(name);
|
|
29227
28972
|
} else {
|
|
29228
28973
|
report3.notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ \u2014 merge them by hand`);
|
|
@@ -29231,23 +28976,23 @@ function moveLegacyMemory(board2, report3) {
|
|
|
29231
28976
|
if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
|
|
29232
28977
|
}
|
|
29233
28978
|
function dropRecordFile(board2) {
|
|
29234
|
-
const file =
|
|
29235
|
-
if (!
|
|
29236
|
-
|
|
28979
|
+
const file = path84.join(board2, "record.csv");
|
|
28980
|
+
if (!fs84.existsSync(file)) return null;
|
|
28981
|
+
fs84.rmSync(file);
|
|
29237
28982
|
return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
|
|
29238
28983
|
}
|
|
29239
28984
|
function dropModuleGoals(board2, report3) {
|
|
29240
|
-
const memory =
|
|
29241
|
-
if (!
|
|
29242
|
-
const root = read5(
|
|
28985
|
+
const memory = path84.join(board2, "memory");
|
|
28986
|
+
if (!fs84.existsSync(memory)) return;
|
|
28987
|
+
const root = read5(path84.join(memory, "goal.md"));
|
|
29243
28988
|
let dropped = 0;
|
|
29244
|
-
for (const entry of
|
|
28989
|
+
for (const entry of fs84.readdirSync(memory, { withFileTypes: true })) {
|
|
29245
28990
|
if (!entry.isDirectory()) continue;
|
|
29246
|
-
const file =
|
|
28991
|
+
const file = path84.join(memory, entry.name, "goal.md");
|
|
29247
28992
|
const text5 = read5(file);
|
|
29248
28993
|
if (text5 === null) continue;
|
|
29249
28994
|
if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
|
|
29250
|
-
|
|
28995
|
+
fs84.rmSync(file);
|
|
29251
28996
|
dropped++;
|
|
29252
28997
|
} else {
|
|
29253
28998
|
report3.notes.push(
|
|
@@ -29269,10 +29014,10 @@ var PLAN_LEADS = {
|
|
|
29269
29014
|
var GONE_LEADS = ["software-planner", "planner"];
|
|
29270
29015
|
function planLeads(board2) {
|
|
29271
29016
|
const leads = { ...PLAN_LEADS };
|
|
29272
|
-
const root =
|
|
29273
|
-
for (const entry of
|
|
29017
|
+
const root = path84.join(board2, "workflows");
|
|
29018
|
+
for (const entry of fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []) {
|
|
29274
29019
|
if (!entry.isDirectory()) continue;
|
|
29275
|
-
const meta = splitFrontmatter(read5(
|
|
29020
|
+
const meta = splitFrontmatter(read5(path84.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
|
|
29276
29021
|
const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
|
|
29277
29022
|
const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
|
|
29278
29023
|
const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
|
|
@@ -29281,30 +29026,30 @@ function planLeads(board2) {
|
|
|
29281
29026
|
return leads;
|
|
29282
29027
|
}
|
|
29283
29028
|
function movePlanRules(board2, report3) {
|
|
29284
|
-
const rules =
|
|
29029
|
+
const rules = path84.join(board2, "rules");
|
|
29285
29030
|
const moved = [];
|
|
29286
29031
|
for (const [id, leads] of Object.entries(planLeads(board2))) {
|
|
29287
29032
|
for (const lead of leads) {
|
|
29288
|
-
const from =
|
|
29033
|
+
const from = path84.join(rules, `${lead}.md`);
|
|
29289
29034
|
const rule = read5(from)?.trim();
|
|
29290
29035
|
if (!rule) continue;
|
|
29291
|
-
const to =
|
|
29036
|
+
const to = path84.join(rules, "workflows", `${id}.md`);
|
|
29292
29037
|
const there = read5(to)?.trim() ?? "";
|
|
29293
29038
|
if (!there.includes(rule)) {
|
|
29294
|
-
|
|
29295
|
-
|
|
29039
|
+
fs84.mkdirSync(path84.dirname(to), { recursive: true });
|
|
29040
|
+
fs84.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
|
|
29296
29041
|
`);
|
|
29297
29042
|
moved.push(`${lead} \u2192 ${id}`);
|
|
29298
29043
|
}
|
|
29299
29044
|
}
|
|
29300
29045
|
}
|
|
29301
|
-
for (const lead of GONE_LEADS)
|
|
29046
|
+
for (const lead of GONE_LEADS) fs84.rmSync(path84.join(rules, `${lead}.md`), { force: true });
|
|
29302
29047
|
if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
|
|
29303
29048
|
}
|
|
29304
29049
|
function noteOldWorkflows(board2, report3) {
|
|
29305
29050
|
const old = (dir, file, keys) => {
|
|
29306
|
-
const root =
|
|
29307
|
-
return (
|
|
29051
|
+
const root = path84.join(board2, dir);
|
|
29052
|
+
return (fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read5(path84.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
|
|
29308
29053
|
};
|
|
29309
29054
|
const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
|
|
29310
29055
|
if (files2.length) {
|
|
@@ -29314,7 +29059,7 @@ function noteOldWorkflows(board2, report3) {
|
|
|
29314
29059
|
}
|
|
29315
29060
|
}
|
|
29316
29061
|
function checkConfig(board2, report3) {
|
|
29317
|
-
const current = read5(
|
|
29062
|
+
const current = read5(path84.join(board2, "config.md"));
|
|
29318
29063
|
if (!current) return;
|
|
29319
29064
|
const added = missingConfigKeys(current);
|
|
29320
29065
|
if (added.length) {
|
|
@@ -29324,7 +29069,7 @@ function checkConfig(board2, report3) {
|
|
|
29324
29069
|
}
|
|
29325
29070
|
}
|
|
29326
29071
|
function checkModules(board2, report3) {
|
|
29327
|
-
const map2 = read5(
|
|
29072
|
+
const map2 = read5(path84.join(board2, "modules.md"));
|
|
29328
29073
|
if (map2 && map2.includes(UNFILLED)) {
|
|
29329
29074
|
report3.notes.push(
|
|
29330
29075
|
"docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
|
|
@@ -29378,13 +29123,13 @@ function declareSetup(program2, cli) {
|
|
|
29378
29123
|
program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
|
|
29379
29124
|
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");
|
|
29380
29125
|
const where = (cmd) => {
|
|
29381
|
-
const dir =
|
|
29382
|
-
if (!
|
|
29126
|
+
const dir = path85.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
|
|
29127
|
+
if (!fs85.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
|
|
29383
29128
|
return dir;
|
|
29384
29129
|
};
|
|
29385
29130
|
const boardOf = (cmd) => {
|
|
29386
29131
|
const named2 = cmd.optsWithGlobals().board;
|
|
29387
|
-
return named2 ?
|
|
29132
|
+
return named2 ? path85.resolve(cli.cwd, named2) : null;
|
|
29388
29133
|
};
|
|
29389
29134
|
dirOption(program2.command("install")).option("--board <dir>", "put the board here instead of docs/kanban/ (relative to this folder)").summary("create the board in this project, and nothing else").description(
|
|
29390
29135
|
"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."
|
|
@@ -29457,7 +29202,7 @@ function invokedDirectly() {
|
|
|
29457
29202
|
const entry = process.argv[1];
|
|
29458
29203
|
if (!entry) return false;
|
|
29459
29204
|
try {
|
|
29460
|
-
return
|
|
29205
|
+
return fs86.realpathSync(entry) === fs86.realpathSync(SELF3);
|
|
29461
29206
|
} catch {
|
|
29462
29207
|
return false;
|
|
29463
29208
|
}
|