kandown 0.22.0 → 0.24.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/CHANGELOG.md +20 -0
- package/README.md +7 -4
- package/bin/kandown.js +239 -60
- package/dist/index.html +199 -133
- package/package.json +1 -1
- package/templates/kandown.json +18 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.24.0 — 2026-07-20 — "Work Output Configurator"
|
|
4
|
+
|
|
5
|
+
- **Added**: **`kandown work` Output Configurator** — added a new Settings panel under Agent configuration that lets each project control the markdown emitted by `kandown work`. Users can enable or disable the base rules, project instructions, and live board digest blocks without editing generated files by hand.
|
|
6
|
+
- **Added**: **Raw Template Mode for Agent Context** — added an advanced raw editor for the complete `kandown work` output with `{{baseRules}}`, `{{projectInstructions}}`, and `{{boardDigest}}` variables. This gives power users full control over the final prompt while keeping the source of truth in `.kandown/kandown.json`.
|
|
7
|
+
- **Added**: **Token-Efficient Concise Rules Mode** — added a compact Kandown rules preset for `kandown work` that keeps the essential task-management contract while dramatically reducing prompt size for cost-sensitive or context-limited agents.
|
|
8
|
+
- **Added**: **Estimated Token Counter and Output Preview** — the Settings configurator now previews the generated `kandown work` markdown and displays an approximate token estimate so users can tune the output before handing it to an agent.
|
|
9
|
+
- **Added**: **Project Instructions Editor** — added authenticated web/server helpers and UI editing for `.kandown/instructions.md`, keeping custom project behavior inside `.kandown/` instead of polluting project-root agent files.
|
|
10
|
+
- **Changed**: **Project-Scoped Work Output Storage** — added `agent.workOutput` to `.kandown/kandown.json` and the default template config, including section toggles, section ordering, digest detail toggles, base-rule verbosity, and raw template content.
|
|
11
|
+
- **Changed**: **Configurable Board Digest Detail** — the generated board digest can now hide column counts, task lists, priorities, assignees, blocked-by annotations, and next-actionable-task output independently to minimize tokens when desired.
|
|
12
|
+
- **Fixed**: **Safe `.kandown/AGENT_KANDOWN.md` Healing** — Kandown now recreates `.kandown/AGENT_KANDOWN.md` when missing and overwrites malformed or stale generated copies from the package template, while keeping all generated reference docs inside `.kandown/` only.
|
|
13
|
+
|
|
14
|
+
## 0.23.0 — 2026-07-20 — "Theme Switcher & Auto Update"
|
|
15
|
+
|
|
16
|
+
- **Added**: **Animated Theme Switcher** — added a shadcn-style three-option theme switcher under `src/components/ui/theme-switcher-1.tsx` with system, light, and dark choices, lucide icons, and Motion layout animation.
|
|
17
|
+
- **Changed**: **Theme Mode UX** — replaced the Appearance settings theme dropdown and the old header two-state toggle with the new animated switcher. The component keeps Kandown's existing `auto`, `light`, and `dark` config values so existing `.kandown/kandown.json` files remain compatible.
|
|
18
|
+
- **Changed**: **Framework Integration** — adapted the provided `next-themes` component to Kandown's Vite/Zustand theme pipeline instead of adding a Next.js-only provider. Theme changes continue to persist through the existing project config and apply via the local CSS token engine.
|
|
19
|
+
- **Fixed**: **Auto-Updater Retry Blocking** — failed auto-update installs are no longer cached as a successful update check. Previously, if npm answered the registry but the install failed, Kandown could suppress retries for 24 hours, making auto-update feel blocked across open projects.
|
|
20
|
+
- **Fixed**: **Multi-Project Auto-Update Refresh** — after a successful in-place global update, Kandown now scans active local daemons and refreshes `kandown.html` for every open project, so two simultaneously open boards do not remain stuck on different web UI bundles.
|
|
21
|
+
- **Fixed**: **Installed Version Verification** — the auto-updater now verifies the installed Kandown CLI version instead of only checking the npm registry version, making successful restart decisions based on the actual local binary.
|
|
22
|
+
|
|
3
23
|
## 0.22.0 — 2026-07-20 — "Sectioned List View"
|
|
4
24
|
|
|
5
25
|
- **Added**: **Sectioned List View** — rebuilt the web List view so board columns such as Backlog, Todo, In Progress, Review, and Done are displayed as horizontal task sections stacked vertically. This keeps the dense list/table workflow while making the List view match the mental model of the Kanban board in the opposite orientation.
|
package/README.md
CHANGED
|
@@ -131,11 +131,14 @@ kandown commit -m "tasks: add auth refactor"
|
|
|
131
131
|
Running `kandown work` prints, as plain markdown on stdout:
|
|
132
132
|
|
|
133
133
|
1. **The agent rules** — always fresh, served straight from the installed CLI version instead of a copy that goes stale the moment the package updates.
|
|
134
|
-
2. **
|
|
135
|
-
3. **
|
|
136
|
-
4. **A live board digest** — column counts, tasks per column with blocked-by annotations, and a computed **"next actionable task"** (closest to done, unblocked, highest priority) — so the agent gets its context in the same call.
|
|
134
|
+
2. **Project instructions** (optional) — `.kandown/instructions.md`, this project only (stack quirks, "always use pnpm", commit message language, token-efficient agent preferences, etc).
|
|
135
|
+
3. **A live board digest** — column counts, tasks per column with blocked-by annotations, and a computed **"next actionable task"** (closest to done, unblocked, highest priority) — so the agent gets its context in the same call.
|
|
137
136
|
|
|
138
|
-
|
|
137
|
+
The Settings page includes an **Agent → kandown work output** configurator. You can toggle each generated block, switch the base rules to a concise token-efficient mode, hide detailed digest fields, see an estimated token count, or use **Raw template** mode to control the complete output with `{{baseRules}}`, `{{projectInstructions}}`, and `{{boardDigest}}` variables.
|
|
138
|
+
|
|
139
|
+
Kandown keeps generated agent reference docs inside `.kandown/` only. If `.kandown/AGENT_KANDOWN.md` is missing, malformed, or an outdated generated copy, the CLI recreates it from the installed package template. Custom behavior belongs in `.kandown/instructions.md` and `.kandown/kandown.json`, not in project-root agent files.
|
|
140
|
+
|
|
141
|
+
This removes the drift problem of a rules block frozen into every project's `AGENTS.md` at init time, keeps the injected footprint to one line, and lets you layer project instructions without touching the agent file at all.
|
|
139
142
|
|
|
140
143
|
> **Upgrading from before v0.18.0?** `kandown shell <cmd>` was removed (no alias) — the commands are now top-level: `kandown list/show/create/move/assign/commit`. Existing projects keep their old `AGENTS.md`/`CLAUDE.md` block until you re-run `kandown init`. The CLI prints a one-time notice about this the next time you run an interactive command after updating — see [Environment variables](#environment-variables) if you want to silence version-check output entirely.
|
|
141
144
|
|
package/bin/kandown.js
CHANGED
|
@@ -145,6 +145,32 @@ function resolveKandownBin() {
|
|
|
145
145
|
return null;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
async function readInstalledKandownVersion(targetVersion) {
|
|
149
|
+
const localVersion = getCurrentVersion();
|
|
150
|
+
if (localVersion && semverGt(localVersion, targetVersion) >= 0) return localVersion;
|
|
151
|
+
|
|
152
|
+
const bin = resolveKandownBin();
|
|
153
|
+
if (!bin) return localVersion;
|
|
154
|
+
|
|
155
|
+
return await new Promise((resolveVersion) => {
|
|
156
|
+
const child = spawn(bin, ['--version'], {
|
|
157
|
+
timeout: 5000,
|
|
158
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
159
|
+
env: { ...process.env, KANDOWN_NO_UPDATE: '1' },
|
|
160
|
+
detached: false,
|
|
161
|
+
});
|
|
162
|
+
let stdout = '';
|
|
163
|
+
child.stdout.on('data', (d) => { stdout += d; });
|
|
164
|
+
child.stderr.on('data', () => {});
|
|
165
|
+
child.on('error', () => resolveVersion(localVersion));
|
|
166
|
+
child.on('close', (code) => {
|
|
167
|
+
if (code !== 0) return resolveVersion(localVersion);
|
|
168
|
+
const match = stdout.trim().match(/v?(\d+\.\d+\.\d+(?:-[\w.-]+)?)/);
|
|
169
|
+
resolveVersion(match ? match[1] : localVersion);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
148
174
|
/**
|
|
149
175
|
* 📖 Compares two semver strings (major.minor.patch, optional -prerelease).
|
|
150
176
|
* Prerelease-safe: "0.18.0-beta.1" no longer parses as NaN — the numeric
|
|
@@ -260,11 +286,15 @@ async function checkForUpdate(argv = process.argv) {
|
|
|
260
286
|
});
|
|
261
287
|
});
|
|
262
288
|
|
|
263
|
-
|
|
264
|
-
// Offline / registry-down does NOT — we retry on the next interactive run.
|
|
265
|
-
if (latest) rememberUpdateCheck();
|
|
289
|
+
if (!latest) return; // offline / registry-down — retry on the next interactive run
|
|
266
290
|
|
|
267
|
-
if (
|
|
291
|
+
if (semverGt(current, latest) >= 0) {
|
|
292
|
+
// 📖 Up-to-date registry answers can be throttled. Failed update attempts
|
|
293
|
+
// below are intentionally NOT cached, otherwise one broken install would
|
|
294
|
+
// silence auto-update retries for 24h across every open project.
|
|
295
|
+
rememberUpdateCheck();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
268
298
|
|
|
269
299
|
tuiDone('⚡', `Update available: ${c.dim}kandown ${current}${c.reset} → ${c.green}${latest}${c.reset}`);
|
|
270
300
|
|
|
@@ -305,22 +335,9 @@ async function checkForUpdate(argv = process.argv) {
|
|
|
305
335
|
return;
|
|
306
336
|
}
|
|
307
337
|
|
|
308
|
-
// 📖 Step 4: Verify the
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
timeout: 5000,
|
|
312
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
313
|
-
detached: false,
|
|
314
|
-
});
|
|
315
|
-
let stdout = '';
|
|
316
|
-
child.stdout.on('data', (d) => { stdout += d; });
|
|
317
|
-
child.stderr.on('data', () => {});
|
|
318
|
-
child.on('error', () => resolve(null));
|
|
319
|
-
child.on('close', (code) => {
|
|
320
|
-
if (code !== 0) return resolve(null);
|
|
321
|
-
resolve(stdout.trim().replace(/^"|"$/g, '') || null);
|
|
322
|
-
});
|
|
323
|
-
});
|
|
338
|
+
// 📖 Step 4: Verify the installed CLI, not just the npm registry, because
|
|
339
|
+
// `npm view kandown version` only proves a version exists remotely.
|
|
340
|
+
const postVersion = await readInstalledKandownVersion(latest);
|
|
324
341
|
|
|
325
342
|
if (!postVersion || semverGt(postVersion, latest) < 0) {
|
|
326
343
|
tuiDone('✗', `${c.yellow}Update did not apply${c.reset} — continuing with current version`);
|
|
@@ -329,6 +346,18 @@ async function checkForUpdate(argv = process.argv) {
|
|
|
329
346
|
return;
|
|
330
347
|
}
|
|
331
348
|
|
|
349
|
+
rememberUpdateCheck();
|
|
350
|
+
|
|
351
|
+
// 📖 If the running package directory has been replaced in-place (normal
|
|
352
|
+
// global install), refresh every currently open project daemon, not only the
|
|
353
|
+
// project that triggered the update. This keeps two simultaneously open
|
|
354
|
+
// Kandown boards from getting stuck on different kandown.html versions.
|
|
355
|
+
const localVersionAfterInstall = getCurrentVersion();
|
|
356
|
+
if (localVersionAfterInstall && semverGt(localVersionAfterInstall, latest) >= 0) {
|
|
357
|
+
const refreshed = await refreshRunningProjectHtml();
|
|
358
|
+
if (refreshed > 0) info(`Refreshed ${refreshed} open project${refreshed === 1 ? '' : 's'}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
332
361
|
tuiDone('✓', `${c.green}Updated to v${postVersion}${c.reset} — restarting…`);
|
|
333
362
|
printVersionChangelog(postVersion);
|
|
334
363
|
printBreakingChangeNotices(current, postVersion);
|
|
@@ -840,6 +869,7 @@ function ensureKandownDir(rawArgs) {
|
|
|
840
869
|
} else if (migration.skipped) {
|
|
841
870
|
info('Both .kandown/tasks/ and ./tasks/ have files — leaving both in place');
|
|
842
871
|
}
|
|
872
|
+
syncKandownAgentDoc(kandownDir);
|
|
843
873
|
return { kandownDir, alreadyExisted: true };
|
|
844
874
|
}
|
|
845
875
|
|
|
@@ -865,13 +895,13 @@ function doInit(args, cwd, kandownPath, kandownDir) {
|
|
|
865
895
|
copyFileSync(htmlSrc, htmlDest);
|
|
866
896
|
success('kandown.html');
|
|
867
897
|
|
|
868
|
-
// 📖
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
898
|
+
// 📖 Keep the generated agent reference inside `.kandown/` only. It is safe
|
|
899
|
+
// to recreate/overwrite because user-specific behavior belongs in
|
|
900
|
+
// `.kandown/instructions.md` or the `agent.workOutput` config, not in this
|
|
901
|
+
// generated package reference.
|
|
902
|
+
syncKandownAgentDoc(kandownDir);
|
|
903
|
+
success('AGENT_KANDOWN.md');
|
|
904
|
+
|
|
875
905
|
const templatesDir = join(PKG_ROOT, 'templates');
|
|
876
906
|
if (!existsSync(join(kandownDir, 'README.md'))) {
|
|
877
907
|
copyFileSync(join(templatesDir, 'README.md'), join(kandownDir, 'README.md'));
|
|
@@ -1528,6 +1558,93 @@ function priorityRank(p) {
|
|
|
1528
1558
|
return Object.prototype.hasOwnProperty.call(PRIORITY_RANK, p) ? PRIORITY_RANK[p] : 4;
|
|
1529
1559
|
}
|
|
1530
1560
|
|
|
1561
|
+
const WORK_OUTPUT_SECTION_IDS = ['baseRules', 'projectInstructions', 'boardDigest'];
|
|
1562
|
+
const CONCISE_AGENT_RULES = `# Kandown agent rules — concise
|
|
1563
|
+
|
|
1564
|
+
- Task state lives in project-root \`tasks/*.md\`; do not maintain a separate board index.
|
|
1565
|
+
- Before work, read the relevant task file and keep it updated while you progress.
|
|
1566
|
+
- Move tasks by editing frontmatter \`status:\`; complete work by setting \`status: Done\` and adding a markdown \`report:\` summary.
|
|
1567
|
+
- Update subtasks in-place: \`- [ ]\` → \`- [x]\`, with a short \`report:\` line for meaningful progress.
|
|
1568
|
+
- Board columns live in \`.kandown/kandown.json\` under \`board.columns\`; project instructions live in \`.kandown/instructions.md\`.
|
|
1569
|
+
- Never put Kandown task data inside \`.kandown/\`; tasks belong in \`./tasks/\`.`;
|
|
1570
|
+
const DEFAULT_WORK_OUTPUT = {
|
|
1571
|
+
mode: 'blocks',
|
|
1572
|
+
includeBaseRules: true,
|
|
1573
|
+
baseRulesMode: 'full',
|
|
1574
|
+
includeProjectInstructions: true,
|
|
1575
|
+
includeBoardDigest: true,
|
|
1576
|
+
sectionOrder: WORK_OUTPUT_SECTION_IDS,
|
|
1577
|
+
rawTemplate: '{{baseRules}}\n\n---\n\n{{projectInstructions}}\n\n---\n\n{{boardDigest}}',
|
|
1578
|
+
boardDigest: {
|
|
1579
|
+
showColumnCounts: true,
|
|
1580
|
+
showTasks: true,
|
|
1581
|
+
showPriority: true,
|
|
1582
|
+
showAssignee: true,
|
|
1583
|
+
showBlockedBy: true,
|
|
1584
|
+
showNextActionable: true,
|
|
1585
|
+
},
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
function safePlainObject(value) {
|
|
1589
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
function normalizeWorkOutputConfig(config) {
|
|
1593
|
+
const raw = safePlainObject(safePlainObject(config?.agent).workOutput);
|
|
1594
|
+
const digest = safePlainObject(raw.boardDigest);
|
|
1595
|
+
const sectionOrder = Array.isArray(raw.sectionOrder)
|
|
1596
|
+
? raw.sectionOrder.filter(id => WORK_OUTPUT_SECTION_IDS.includes(id))
|
|
1597
|
+
: DEFAULT_WORK_OUTPUT.sectionOrder;
|
|
1598
|
+
const order = sectionOrder.length > 0 ? sectionOrder : DEFAULT_WORK_OUTPUT.sectionOrder;
|
|
1599
|
+
return {
|
|
1600
|
+
...DEFAULT_WORK_OUTPUT,
|
|
1601
|
+
...raw,
|
|
1602
|
+
mode: raw.mode === 'raw' ? 'raw' : 'blocks',
|
|
1603
|
+
baseRulesMode: raw.baseRulesMode === 'concise' ? 'concise' : 'full',
|
|
1604
|
+
sectionOrder: order,
|
|
1605
|
+
rawTemplate: typeof raw.rawTemplate === 'string' && raw.rawTemplate.trim()
|
|
1606
|
+
? raw.rawTemplate
|
|
1607
|
+
: DEFAULT_WORK_OUTPUT.rawTemplate,
|
|
1608
|
+
boardDigest: { ...DEFAULT_WORK_OUTPUT.boardDigest, ...digest },
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function readBaseAgentRules(mode = 'full') {
|
|
1613
|
+
if (mode === 'concise') return CONCISE_AGENT_RULES;
|
|
1614
|
+
try {
|
|
1615
|
+
return readFileSync(join(PKG_ROOT, 'templates', 'AGENT_KANDOWN.md'), 'utf8').trim();
|
|
1616
|
+
} catch (e) {
|
|
1617
|
+
warn(`Could not read base rules (${e.message})`);
|
|
1618
|
+
return '';
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function syncKandownAgentDoc(kandownDir) {
|
|
1623
|
+
const source = join(PKG_ROOT, 'templates', 'AGENT_KANDOWN.md');
|
|
1624
|
+
const target = join(kandownDir, 'AGENT_KANDOWN.md');
|
|
1625
|
+
if (!existsSync(source)) return false;
|
|
1626
|
+
const expected = readFileSync(source, 'utf8');
|
|
1627
|
+
const existing = existsSync(target) ? readFileSync(target, 'utf8') : null;
|
|
1628
|
+
const isMissing = existing === null;
|
|
1629
|
+
const isMalformed = existing !== null && !existing.includes('# Kandown') && !existing.includes('## The System');
|
|
1630
|
+
const isStaleGeneratedDoc = existing !== null && existing.includes('# Kandown') && existing !== expected;
|
|
1631
|
+
|
|
1632
|
+
if (isMissing || isMalformed || isStaleGeneratedDoc) {
|
|
1633
|
+
atomicWriteFileSync(target, expected.endsWith('\n') ? expected : `${expected}\n`);
|
|
1634
|
+
return true;
|
|
1635
|
+
}
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function applyWorkRawTemplate(template, blocks) {
|
|
1640
|
+
return template
|
|
1641
|
+
.replaceAll('{{baseRules}}', blocks.baseRules)
|
|
1642
|
+
.replaceAll('{{projectInstructions}}', blocks.projectInstructions)
|
|
1643
|
+
.replaceAll('{{boardDigest}}', blocks.boardDigest)
|
|
1644
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
1645
|
+
.trim();
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1531
1648
|
/**
|
|
1532
1649
|
* 📖 Builds the "Current board" section: column counts, tasks per column
|
|
1533
1650
|
* (with blocked-by annotations), and a "Next actionable task" pick — the
|
|
@@ -1536,7 +1653,8 @@ function priorityRank(p) {
|
|
|
1536
1653
|
* `depends_on`, tie-broken by priority. Mirrors the same gate logic used by
|
|
1537
1654
|
* `move`/the TUI/the web store.
|
|
1538
1655
|
*/
|
|
1539
|
-
function buildBoardDigest(kandownDir) {
|
|
1656
|
+
function buildBoardDigest(kandownDir, options = DEFAULT_WORK_OUTPUT.boardDigest) {
|
|
1657
|
+
const digestOptions = { ...DEFAULT_WORK_OUTPUT.boardDigest, ...safePlainObject(options) };
|
|
1540
1658
|
const config = readKandownConfig(kandownDir);
|
|
1541
1659
|
const columns = (config && config.board && Array.isArray(config.board.columns) && config.board.columns.length > 0)
|
|
1542
1660
|
? config.board.columns
|
|
@@ -1579,17 +1697,21 @@ function buildBoardDigest(kandownDir) {
|
|
|
1579
1697
|
}
|
|
1580
1698
|
|
|
1581
1699
|
const lines = ['## Current board', ''];
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
const
|
|
1592
|
-
|
|
1700
|
+
if (digestOptions.showColumnCounts) {
|
|
1701
|
+
lines.push(`**Columns:** ${columns.map(col => `${col} (${(byColumn.get(col) || []).length})`).join(' · ')}`);
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
if (digestOptions.showTasks) {
|
|
1705
|
+
for (const col of columns) {
|
|
1706
|
+
const tasks = (byColumn.get(col) || []).sort((a, b) => priorityRank(a.fm.priority) - priorityRank(b.fm.priority));
|
|
1707
|
+
if (tasks.length === 0) continue;
|
|
1708
|
+
lines.push('', `### ${col}`);
|
|
1709
|
+
for (const t of tasks) {
|
|
1710
|
+
const pri = digestOptions.showPriority && t.fm.priority ? `[${t.fm.priority}] ` : '';
|
|
1711
|
+
const assignee = digestOptions.showAssignee && t.fm.assignee ? ` (@${t.fm.assignee})` : '';
|
|
1712
|
+
const blockedStr = digestOptions.showBlockedBy && t.blocked.length > 0 ? ` ⛔ blocked by ${t.blocked.join(', ')}` : '';
|
|
1713
|
+
lines.push(`- ${t.id} ${pri}${t.fm.title || '(untitled)'}${assignee}${blockedStr}`);
|
|
1714
|
+
}
|
|
1593
1715
|
}
|
|
1594
1716
|
}
|
|
1595
1717
|
|
|
@@ -1606,30 +1728,23 @@ function buildBoardDigest(kandownDir) {
|
|
|
1606
1728
|
return priorityRank(a.fm.priority) - priorityRank(b.fm.priority);
|
|
1607
1729
|
})[0];
|
|
1608
1730
|
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1731
|
+
if (digestOptions.showNextActionable) {
|
|
1732
|
+
lines.push('', '### Next actionable task');
|
|
1733
|
+
lines.push(next
|
|
1734
|
+
? `→ **${next.id}** — ${next.fm.title || '(untitled)'} (${next.fm.priority || 'no priority'}, ${next.fm.status || columns[0]})`
|
|
1735
|
+
: 'None — every task is done, archived, or blocked.');
|
|
1736
|
+
}
|
|
1613
1737
|
|
|
1614
1738
|
return lines.join('\n');
|
|
1615
1739
|
}
|
|
1616
1740
|
|
|
1617
1741
|
async function cmdWork(rawArgs) {
|
|
1618
1742
|
const { kandownDir } = ensureKandownDir(rawArgs);
|
|
1743
|
+
const config = readKandownConfig(kandownDir);
|
|
1744
|
+
const workOutput = normalizeWorkOutputConfig(config);
|
|
1745
|
+
syncKandownAgentDoc(kandownDir);
|
|
1619
1746
|
|
|
1620
|
-
|
|
1621
|
-
try {
|
|
1622
|
-
baseRules = readFileSync(join(PKG_ROOT, 'templates', 'AGENT_KANDOWN.md'), 'utf8').trim();
|
|
1623
|
-
} catch (e) {
|
|
1624
|
-
warn(`Could not read base rules (${e.message})`);
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
let globalInstructions = '';
|
|
1628
|
-
const globalPath = join(homedir(), '.kandown', 'instructions.md');
|
|
1629
|
-
if (existsSync(globalPath)) {
|
|
1630
|
-
try { globalInstructions = readFileSync(globalPath, 'utf8').trim(); }
|
|
1631
|
-
catch (e) { warn(`Could not read global instructions (${e.message})`); }
|
|
1632
|
-
}
|
|
1747
|
+
const baseRules = readBaseAgentRules(workOutput.baseRulesMode);
|
|
1633
1748
|
|
|
1634
1749
|
let projectInstructions = '';
|
|
1635
1750
|
const projectPath = join(kandownDir, 'instructions.md');
|
|
@@ -1638,10 +1753,23 @@ async function cmdWork(rawArgs) {
|
|
|
1638
1753
|
catch (e) { warn(`Could not read project instructions (${e.message})`); }
|
|
1639
1754
|
}
|
|
1640
1755
|
|
|
1641
|
-
const
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1756
|
+
const blocks = {
|
|
1757
|
+
baseRules,
|
|
1758
|
+
projectInstructions: projectInstructions ? `## Project-specific instructions\n\n${projectInstructions}` : '',
|
|
1759
|
+
boardDigest: buildBoardDigest(kandownDir, workOutput.boardDigest),
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
if (workOutput.mode === 'raw') {
|
|
1763
|
+
out(applyWorkRawTemplate(workOutput.rawTemplate, blocks));
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
const sections = [];
|
|
1768
|
+
for (const sectionId of workOutput.sectionOrder) {
|
|
1769
|
+
if (sectionId === 'baseRules' && workOutput.includeBaseRules) sections.push(blocks.baseRules);
|
|
1770
|
+
if (sectionId === 'projectInstructions' && workOutput.includeProjectInstructions) sections.push(blocks.projectInstructions);
|
|
1771
|
+
if (sectionId === 'boardDigest' && workOutput.includeBoardDigest) sections.push(blocks.boardDigest);
|
|
1772
|
+
}
|
|
1645
1773
|
|
|
1646
1774
|
out(sections.filter(Boolean).join('\n\n---\n\n'));
|
|
1647
1775
|
}
|
|
@@ -1785,6 +1913,28 @@ function refreshKandownHtml(kandownDir) {
|
|
|
1785
1913
|
return false;
|
|
1786
1914
|
}
|
|
1787
1915
|
|
|
1916
|
+
async function refreshRunningProjectHtml() {
|
|
1917
|
+
const ports = [];
|
|
1918
|
+
for (let port = START_PORT_RANGE; port <= END_PORT_RANGE; port++) {
|
|
1919
|
+
if (!isBrowserUnsafePort(port)) ports.push(port);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
const daemons = await Promise.all(ports.map(port => fetchDaemonInfo(port)));
|
|
1923
|
+
const kandownDirs = new Set(
|
|
1924
|
+
daemons
|
|
1925
|
+
.map(daemon => daemon?.kandownDir)
|
|
1926
|
+
.filter(dir => typeof dir === 'string' && dir.length > 0)
|
|
1927
|
+
);
|
|
1928
|
+
|
|
1929
|
+
let refreshed = 0;
|
|
1930
|
+
for (const kandownDir of kandownDirs) {
|
|
1931
|
+
try {
|
|
1932
|
+
if (refreshKandownHtml(kandownDir)) refreshed++;
|
|
1933
|
+
} catch { /* best-effort: one locked project must not block restart */ }
|
|
1934
|
+
}
|
|
1935
|
+
return refreshed;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1788
1938
|
async function waitForDaemon(kandownDir, timeoutMs = 8000) {
|
|
1789
1939
|
// 📖 Detect daemon startup via TCP probe + process liveness rather than HTTP
|
|
1790
1940
|
// fetch. The freshly spawned child is known to be ours, so once its process
|
|
@@ -2181,6 +2331,30 @@ function putConfig(req, res, kandownDir) {
|
|
|
2181
2331
|
});
|
|
2182
2332
|
}
|
|
2183
2333
|
|
|
2334
|
+
function getInstructions(res, kandownDir) {
|
|
2335
|
+
const instructionsPath = join(kandownDir, 'instructions.md');
|
|
2336
|
+
if (!existsSync(instructionsPath)) {
|
|
2337
|
+
writeText(res, 200, '');
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2340
|
+
try {
|
|
2341
|
+
writeText(res, 200, readFileSync(instructionsPath, 'utf8'));
|
|
2342
|
+
} catch (e) {
|
|
2343
|
+
writeText(res, 500, `Failed to read instructions: ${e.message}`);
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
function putInstructions(req, res, kandownDir) {
|
|
2348
|
+
readBody(req).then(body => {
|
|
2349
|
+
const instructionsPath = join(kandownDir, 'instructions.md');
|
|
2350
|
+
const normalized = body.trim() ? body.replace(/\s+$/, '') + '\n' : '';
|
|
2351
|
+
atomicWriteFileSync(instructionsPath, normalized);
|
|
2352
|
+
writeJson(res, 200, { ok: true });
|
|
2353
|
+
}).catch(e => {
|
|
2354
|
+
writeJson(res, e.statusCode || 500, { error: `Failed to write instructions: ${e.message}` });
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2184
2358
|
function getBoard(res, kandownDir) {
|
|
2185
2359
|
const boardPath = join(kandownDir, 'board.md');
|
|
2186
2360
|
if (!existsSync(boardPath)) {
|
|
@@ -2454,6 +2628,11 @@ function handleApi(req, res, url, kandownDir) {
|
|
|
2454
2628
|
if (req.method === 'PUT') return putConfig(req, res, kandownDir);
|
|
2455
2629
|
}
|
|
2456
2630
|
|
|
2631
|
+
if (resource === 'instructions') {
|
|
2632
|
+
if (req.method === 'GET') return getInstructions(res, kandownDir);
|
|
2633
|
+
if (req.method === 'PUT') return putInstructions(req, res, kandownDir);
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2457
2636
|
if (resource === 'board') {
|
|
2458
2637
|
if (req.method === 'GET') return getBoard(res, kandownDir);
|
|
2459
2638
|
if (req.method === 'PUT') return putBoard(req, res, kandownDir);
|