karajan-code 4.49.5 → 4.50.0
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/package.json +1 -2
- package/src/cli/register-meta.js +1 -0
- package/src/commands/hu.js +11 -11
- package/src/commands/steward.js +4 -1
- package/src/environment/playbook.js +6 -6
- package/src/review/card-first.js +10 -4
- package/src/steward/proposed-work.js +6 -10
- package/docs/karajan-code-logo-small.png +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "karajan-code",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.50.0",
|
|
4
4
|
"description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -60,7 +60,6 @@
|
|
|
60
60
|
"packages/ai-trash/src/",
|
|
61
61
|
"packages/ai-trash/bin/",
|
|
62
62
|
"packages/ai-trash/package.json",
|
|
63
|
-
"docs/karajan-code-logo-small.png",
|
|
64
63
|
"docs/README.es.md",
|
|
65
64
|
"LICENSE",
|
|
66
65
|
"README.md"
|
package/src/cli/register-meta.js
CHANGED
|
@@ -315,6 +315,7 @@ export function registerMeta(program, { pkgVersion }) {
|
|
|
315
315
|
.option("--scope <text>", "Scope of the story (files/areas)")
|
|
316
316
|
.option("--task-type <type>", "sw | infra | doc | add-tests | refactor")
|
|
317
317
|
.option("--criteria <text>", "Legacy alias for a single acceptance criterion")
|
|
318
|
+
.option("--ref <id>", "Id of this card's replica in the external tracker (Planning Game, Jira, Linear...)")
|
|
318
319
|
.option("--json", "Machine-readable output")
|
|
319
320
|
.action(async (title, flags) => {
|
|
320
321
|
await withConfig(pkgVersion, "hu", flags, async ({ config }) => {
|
package/src/commands/hu.js
CHANGED
|
@@ -62,15 +62,9 @@ export async function huCommand({ config = null, action, args = [], flags = {} }
|
|
|
62
62
|
const projectDir = config?.projectDir || process.cwd();
|
|
63
63
|
const emit = (obj, human) => { console.log(flags.json ? JSON.stringify(obj) : human); return obj; };
|
|
64
64
|
|
|
65
|
-
// KJC-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
const name = config?.board?.name || "an external board";
|
|
69
|
-
throw new Error(
|
|
70
|
-
`this project's board lives in ${name} (state_backend: external) — create and move cards there, `
|
|
71
|
-
+ `through your agent's MCP/tools. kj does not mirror external boards.`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
65
|
+
// KJC-BUG-0305 (issue #2042): the HU Board is always kj's board. An external
|
|
66
|
+
// tracker coexists with it: each HU points at its replica there (--ref).
|
|
67
|
+
const externalBoard = config?.state_backend === "external" ? (config?.board?.name || "the external board") : null;
|
|
74
68
|
|
|
75
69
|
if (action === "list") {
|
|
76
70
|
const plans = await listPlans(projectDir);
|
|
@@ -84,12 +78,14 @@ export async function huCommand({ config = null, action, args = [], flags = {} }
|
|
|
84
78
|
// KJC-TSK-0661: provenance — where a card came from is part of
|
|
85
79
|
// the card. Older HUs predate the stamp: shown as "?".
|
|
86
80
|
created_by: h.created_by || null, created_at: h.createdAt || null,
|
|
81
|
+
external_ref: h.external_ref || null,
|
|
87
82
|
});
|
|
88
83
|
}
|
|
89
84
|
}
|
|
90
85
|
if (flags.json) { console.log(JSON.stringify(rows)); return rows; }
|
|
91
86
|
for (const r of rows) {
|
|
92
|
-
const
|
|
87
|
+
const suffixes = [r.created_at?.slice(0, 10), r.external_ref].filter(Boolean).map((s) => ` · ${s}`).join("");
|
|
88
|
+
const origin = `${r.plan} · by ${r.created_by || "?"}${suffixes}`;
|
|
93
89
|
console.log(`${(r.short_id || r.id).padEnd(28)} ${r.status.padEnd(8)} ${r.title} [${origin}]`);
|
|
94
90
|
}
|
|
95
91
|
if (rows.length === 0) console.log("no HUs yet — create one with: kj hu add \"<story>\"");
|
|
@@ -164,7 +160,11 @@ export async function huCommand({ config = null, action, args = [], flags = {} }
|
|
|
164
160
|
task_type: flags.taskType || undefined,
|
|
165
161
|
});
|
|
166
162
|
hu.created_by = creatorLabel();
|
|
163
|
+
if (flags.ref) hu.external_ref = String(flags.ref).trim();
|
|
167
164
|
await savePlan(projectDir, plan);
|
|
165
|
+
if (externalBoard && !hu.external_ref) {
|
|
166
|
+
console.log(`→ replicate it in ${externalBoard} through your agent's MCP/tools and link both: kj hu add ... --ref <id in ${externalBoard}>`);
|
|
167
|
+
}
|
|
168
168
|
const missing = [];
|
|
169
169
|
if (hu.acceptance_criteria.length === 0) missing.push("acceptance criteria (--ac \"...\")");
|
|
170
170
|
if (hu.acceptance_tests.length === 0) missing.push("tests (--tests \"...\")");
|
|
@@ -174,7 +174,7 @@ export async function huCommand({ config = null, action, args = [], flags = {} }
|
|
|
174
174
|
return emit({
|
|
175
175
|
id: hu.id, short_id: hu.short_id, status: hu.status, created_by: hu.created_by,
|
|
176
176
|
acceptance_criteria: hu.acceptance_criteria, acceptance_tests: hu.acceptance_tests,
|
|
177
|
-
scope: hu.scope, task_type: hu.task_type,
|
|
177
|
+
scope: hu.scope, task_type: hu.task_type, external_ref: hu.external_ref || null,
|
|
178
178
|
}, `✓ HU created: ${hu.short_id || hu.id} (pending, by ${hu.created_by}) — it shows up in \`kj board\``);
|
|
179
179
|
}
|
|
180
180
|
|
package/src/commands/steward.js
CHANGED
|
@@ -113,7 +113,10 @@ export async function stewardSweepCommand({ flags = {}, config = {}, logger = co
|
|
|
113
113
|
try {
|
|
114
114
|
const sync = await syncProposedWork({ projectDir, config, results, sweptAt: report.sweptAt });
|
|
115
115
|
if (!sync.synced) logger.warn?.(`⚠ steward: broken invariants not carded — ${sync.reason}`);
|
|
116
|
-
else if (sync.created || sync.updated || sync.resolved)
|
|
116
|
+
else if (sync.created || sync.updated || sync.resolved) {
|
|
117
|
+
logger.info?.(`steward board: ${sync.created} card(s) proposed, ${sync.updated} updated, ${sync.resolved} resolved`);
|
|
118
|
+
if (sync.replicateIn) logger.info?.(`→ replicate them in ${sync.replicateIn} through your agent's board tools (kj hu list shows them)`);
|
|
119
|
+
}
|
|
117
120
|
} catch (err) { logger.warn?.(`⚠ steward: carding the broken invariants failed (${err.message}) — the report and the seal stand`); }
|
|
118
121
|
|
|
119
122
|
// A report nobody can see is not shared state — say it, do not fix their tree.
|
|
@@ -30,20 +30,20 @@ const TARGET_FILES = {
|
|
|
30
30
|
|
|
31
31
|
// ENV-D1 (KJC-TSK-0642): the tracking invariant names the CHOSEN state
|
|
32
32
|
// backend — a playbook that says "board or PG" makes the host guess.
|
|
33
|
-
// KJC-
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
33
|
+
// KJC-BUG-0305 (issue #2042, replaces KJC-TSK-0684): the HU Board is always
|
|
34
|
+
// kj's board. An external board (Linear, Trello, Jira…) coexists with it: the
|
|
35
|
+
// host agent replicates each card there through its own MCP/tools, and
|
|
36
|
+
// `kj hu add --ref` links the two.
|
|
37
37
|
const BACKEND_TRACKING = {
|
|
38
38
|
"hu-board": "Every piece of work has a tracked story/bug in the HU Board (`kj hu add` / `kj board`) before it starts — and `kj hu list` BEFORE `kj hu add`: reuse the existing card if one covers the work. Cards are permanent — never delete or recreate one; discard with `kj hu move <id> skipped`. `kj brief board` explains the board.",
|
|
39
|
-
"planning-game": "Every piece of work has a tracked card in the Planning Game MCP before it starts (In Progress while you work it).",
|
|
40
39
|
};
|
|
41
40
|
|
|
42
41
|
const externalTracking = (boardName) =>
|
|
43
|
-
`Every piece of work has a
|
|
42
|
+
`Every piece of work has a card in the HU Board before it starts (\`kj hu list\`, then \`kj hu add --ref <id>\`) and its replica in ${boardName}, created through its MCP/tools; move both as you work it.`;
|
|
44
43
|
|
|
45
44
|
function trackingLine(stateBackend, boardName) {
|
|
46
45
|
if (stateBackend === "external") return externalTracking(boardName || "the project's external board");
|
|
46
|
+
if (stateBackend === "planning-game") return externalTracking("the Planning Game");
|
|
47
47
|
return BACKEND_TRACKING[stateBackend] || BACKEND_TRACKING["hu-board"];
|
|
48
48
|
}
|
|
49
49
|
|
package/src/review/card-first.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Card-first gate (KJC-TSK-0686, MG-A of épica KJC-PCS-0068): the rule
|
|
3
3
|
* that work starts from a card moves from playbook text into the gate.
|
|
4
4
|
* hu-board → the branch must reference a LIVE card (block by default);
|
|
5
|
-
* planning-game/external →
|
|
6
|
-
* block via method_gates.card_first). Exemptions explicit and visible:
|
|
5
|
+
* planning-game/external → a live HU on the HU Board first, else a card-shaped
|
|
6
|
+
* reference (warn default, block via method_gates.card_first). Exemptions explicit and visible:
|
|
7
7
|
* base branch and configured prefixes (releases). No escape (ADR 0015): a
|
|
8
8
|
* gate an agent can switch off is a suggestion.
|
|
9
9
|
*/
|
|
@@ -40,7 +40,8 @@ async function findLiveCardInBranch(projectDir, branch) {
|
|
|
40
40
|
for (const meta of await listPlans(projectDir)) {
|
|
41
41
|
const plan = await loadPlan(projectDir, meta.planId);
|
|
42
42
|
for (const h of plan.hus || []) {
|
|
43
|
-
|
|
43
|
+
// KJC-BUG-0353: an HU is also named by the id of its replica in an external tracker.
|
|
44
|
+
for (const ref of [h.short_id, h.id, h.external_ref]) {
|
|
44
45
|
if (ref && refInBranch(ref, needle)) {
|
|
45
46
|
if (LIVE_STATUSES.has(h.status)) return { ref, live: true, status: h.status };
|
|
46
47
|
closedHit = { ref, live: false, status: h.status };
|
|
@@ -83,6 +84,10 @@ export async function checkCardFirst({ config = {}, projectDir = process.cwd(),
|
|
|
83
84
|
// result names its level so nobody mistakes branch-ref for tracker.
|
|
84
85
|
const boardName = config.board?.name || backend;
|
|
85
86
|
const effective = policy === "auto" ? "warn" : policy;
|
|
87
|
+
// KJC-BUG-0353: the HU Board is always kj's board (KJC-BUG-0305) — a live HU
|
|
88
|
+
// the branch names, or whose replica it names, is checked here, not guessed.
|
|
89
|
+
const huHit = await findLiveCardInBranch(projectDir, branch);
|
|
90
|
+
if (huHit?.live) return { ok: true, mode: "pass", ref: huHit.ref, level: "hu-board" };
|
|
86
91
|
if (CARD_REF_RE.test(branch)) {
|
|
87
92
|
const ref = branch.match(CARD_REF_RE)[0];
|
|
88
93
|
const { verifyFn = verifyCardAlive } = deps;
|
|
@@ -105,7 +110,8 @@ export async function checkCardFirst({ config = {}, projectDir = process.cwd(),
|
|
|
105
110
|
reason: `branch references ${ref} but the card could not be verified against ${boardName} — ${v.note} (board.unverifiable: block refuses to approve a check kj did not make)`,
|
|
106
111
|
};
|
|
107
112
|
}
|
|
108
|
-
|
|
113
|
+
const huNote = `no live HU on the HU Board links ${ref}: kj hu add "…" --ref ${ref}`;
|
|
114
|
+
return { ok: true, mode: "pass", ref, level: v.level, note: v.note ? `${v.note}; ${huNote}` : huNote };
|
|
109
115
|
}
|
|
110
116
|
const reason = `no card reference in branch "${branch}" — card-first on ${boardName}: create the card there and name the branch after it (e.g. feat/ABC-123-summary)`;
|
|
111
117
|
return effective === "block"
|
|
@@ -7,11 +7,9 @@
|
|
|
7
7
|
* a daily sweep must never flood the board with twins — and a green
|
|
8
8
|
* invariant resolves it with the green evidence.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Sentinel's inevitable notice (STW-C) pushes the host agent — who does
|
|
14
|
-
* have the board's MCP — to card them.
|
|
10
|
+
* KJC-BUG-0305: the HU Board is always kj's board, so the cards land there on
|
|
11
|
+
* every backend. With planning-game or an external board the result names it
|
|
12
|
+
* (replicateIn): the host agent, who has that board's MCP, replicates them.
|
|
15
13
|
*/
|
|
16
14
|
import fs from "node:fs";
|
|
17
15
|
import path from "node:path";
|
|
@@ -34,13 +32,11 @@ const cardText = (r, brokenSince) => [
|
|
|
34
32
|
*/
|
|
35
33
|
export async function syncProposedWork({ projectDir, config = {}, results = [], sweptAt }) {
|
|
36
34
|
const backend = config.state_backend || "hu-board";
|
|
37
|
-
|
|
38
|
-
return { synced: false, reason: `the board lives in ${backend} — kj never mirrors a board it does not own; card the broken invariants through your agent's board tools` };
|
|
39
|
-
}
|
|
35
|
+
const replica = backend === "hu-board" ? {} : { replicateIn: backend };
|
|
40
36
|
const map = readMap(projectDir);
|
|
41
37
|
const broken = results.filter((r) => r.verdict === "broken");
|
|
42
38
|
const okAgain = results.filter((r) => r.verdict === "ok" && map[r.id]);
|
|
43
|
-
if (broken.length === 0 && okAgain.length === 0) return { synced: true, created: 0, updated: 0, resolved: 0 };
|
|
39
|
+
if (broken.length === 0 && okAgain.length === 0) return { synced: true, created: 0, updated: 0, resolved: 0, ...replica };
|
|
44
40
|
|
|
45
41
|
const plan = await backlogPlan(projectDir);
|
|
46
42
|
let created = 0, updated = 0, resolved = 0;
|
|
@@ -64,5 +60,5 @@ export async function syncProposedWork({ projectDir, config = {}, results = [],
|
|
|
64
60
|
await savePlan(projectDir, plan);
|
|
65
61
|
fs.mkdirSync(path.dirname(mapPath(projectDir)), { recursive: true });
|
|
66
62
|
fs.writeFileSync(mapPath(projectDir), `${JSON.stringify(map, null, 2)}\n`);
|
|
67
|
-
return { synced: true, created, updated, resolved };
|
|
63
|
+
return { synced: true, created, updated, resolved, ...replica };
|
|
68
64
|
}
|
|
Binary file
|