kenaz 0.3.11 → 0.3.12

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.
Files changed (31) hide show
  1. package/core/.collaboration/core/task-2288-followups.test.js +2 -37
  2. package/core/.collaboration/core/task-2421-replica.test.js +229 -0
  3. package/core/.collaboration/core/task-api.js +221 -12
  4. package/core/.collaboration/core/task-replica.js +189 -0
  5. package/core/.formation/.merge-lock +1 -0
  6. package/core/.formation/bifrost/codex-doctor.js +12 -2
  7. package/core/.formation/bifrost/codex-doctor.test.js +15 -0
  8. package/core/.formation/bifrost/setup-codex.js +84 -16
  9. package/core/.formation/tools/post-complete/task2420.test.js +129 -0
  10. package/core/.formation/tools/post-complete.js +129 -8
  11. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  12. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  13. package/core/.specialist/tools/__tests__/rea-detect.test.js +10 -0
  14. package/core/.specialist/tools/rea-detect.js +6 -2
  15. package/core/.synapse/memory-shared.json.sync +1 -0
  16. package/core/KENAZ_CORE_VERSION +1 -1
  17. package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +75 -109
  18. package/core/dev/specs/peer/CODEX_MANAGED_DAEMON.md +54 -0
  19. package/core/dev/specs/peer/setup-codex-hooks.test.js +38 -0
  20. package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +56 -0
  21. package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
  22. package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
  23. package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +1 -0
  24. package/core/plugins/kenaz/scripts/kenaz.test.js +68 -0
  25. package/core/plugins/kenaz/scripts/ygg.js +43 -4
  26. package/lib/cli.js +17 -7
  27. package/lib/cli.test.js +10 -0
  28. package/lib/dashboard.test.js +3 -3
  29. package/package.json +6 -6
  30. package/vendor/install-kenaz-cli.js +167 -16
  31. package/core/.collaboration/core/task-2264-single-after-capture.test.js +0 -61
@@ -225,7 +225,7 @@ function uninstall(options = {}) {
225
225
  for (let n = 0; n < segs.length; n++) {
226
226
  const [h, body] = segs[n], nx = segs[n + 1];
227
227
  if (h && /^\s*\[\[hooks\.\w+\]\]\s*$/.test(h) && nx && nx[0] && /^\s*\[\[hooks\.\w+\.hooks\]\]/.test(nx[0]) &&
228
- nx[1].join('\n').split('\\').join('/').includes(HOOK_SHIM_REL)) { n++; res.hooks = 'removed'; continue; }
228
+ isKenazHandler(nx)) { n++; res.hooks = 'removed'; continue; } // TASK_2418: any checkout's shim, same identity rule as missingHookEvents
229
229
  out.push(...body.filter((l) => !/^# kenaz-dashboard native hooks/.test(l)));
230
230
  }
231
231
  if (res.hooks === 'removed' && !dryRun) fs.writeFileSync(cfg, out.join('\n').replace(/\n{3,}/g, '\n\n'), 'utf8');
@@ -283,23 +283,82 @@ function getHookShimPath(_projectRoot) {
283
283
  return path.resolve(__dirname, '..', '..', HOOK_SHIM_REL);
284
284
  }
285
285
 
286
+ // TASK_2418: what counts as a kenaz hook, and what counts as a CURRENT one.
287
+ // A handler is kenaz's when its command runs codex-hook-shim.js (any checkout). It is current only when it
288
+ // runs exactly this core's shim (absolute path, slashes normalized, Windows case-insensitive) and the
289
+ // UserPromptSubmit one carries --pull (TASK_2379). Stale ones are replaced in place, never duplicated; handlers
290
+ // that do not run our shim are never touched.
291
+ const HOOK_SHIM_NAME = path.posix.basename(HOOK_SHIM_REL);
292
+ const HOOK_MARKER = /^# kenaz-dashboard native hooks/;
293
+ const HANDLER_HEADER = /^\s*\[\[hooks\.(\w+)\.hooks\]\]\s*$/;
294
+ const GROUP_HEADER = /^\s*\[\[hooks\.(\w+)\]\]\s*$/;
295
+ const slashes = (s) => String(s).replace(/\\/g, '/');
296
+ const foldCase = (s) => (process.platform === 'win32' ? s.toLowerCase() : s);
297
+
298
+ function parseSegments(text) { // [header line | null, body lines]
299
+ const segs = [];
300
+ for (const line of text.split(/\r?\n/)) {
301
+ if (/^\s*\[/.test(line) || !segs.length) segs.push([/^\s*\[/.test(line) ? line : null, [line]]);
302
+ else segs[segs.length - 1][1].push(line);
303
+ }
304
+ return segs;
305
+ }
306
+ const isKenazHandler = (seg) => slashes(seg[1].join('\n')).includes(HOOK_SHIM_NAME);
307
+ function isCurrentHandler(ev, seg, projectRoot) {
308
+ const m = slashes(seg[1].join('\n')).match(/^\s*command\s*=\s*(.*)$/m);
309
+ if (!m) return false;
310
+ const cmd = foldCase(m[1]);
311
+ const shim = foldCase(slashes(getHookShimPath(projectRoot)));
312
+ const at = cmd.indexOf('"' + shim + '"');
313
+ if (at < 0) return false;
314
+ return ev !== 'UserPromptSubmit' || /(^|\s)--pull(\s|['"]|$)/.test(cmd.slice(at + shim.length + 2));
315
+ }
316
+ /** Per event: segment indexes of kenaz handlers, split into current / stale. */
317
+ function scanKenazHooks(text, projectRoot) {
318
+ const segs = parseSegments(text), found = {};
319
+ segs.forEach((seg, i) => {
320
+ const m = seg[0] && seg[0].match(HANDLER_HEADER);
321
+ if (!m || !HOOK_EVENTS.includes(m[1]) || !isKenazHandler(seg)) return;
322
+ const f = found[m[1]] || (found[m[1]] = { current: [], stale: [] });
323
+ (isCurrentHandler(m[1], seg, projectRoot) ? f.current : f.stale).push(i);
324
+ });
325
+ return { segs, found };
326
+ }
327
+ /** config text without the kenaz handlers of `events` (and the empty group each one leaves behind). */
328
+ function stripKenazHooks(text, events, projectRoot) {
329
+ const { segs, found } = scanKenazHooks(text, projectRoot);
330
+ const drop = new Set();
331
+ for (const ev of events) {
332
+ for (const i of [...(found[ev] ? found[ev].current : []), ...(found[ev] ? found[ev].stale : [])]) {
333
+ drop.add(i);
334
+ const prev = segs[i - 1], next = segs[i + 1];
335
+ const g = prev && prev[0] && prev[0].match(GROUP_HEADER);
336
+ if (g && g[1] === ev && !(next && next[0] && HANDLER_HEADER.test(next[0]) && next[0].includes('.' + ev + '.'))
337
+ && prev[1].slice(1).every((l) => !l.trim())) drop.add(i - 1);
338
+ }
339
+ }
340
+ const out = [];
341
+ segs.forEach((seg, i) => {
342
+ if (!drop.has(i)) { out.push(...seg[1]); return; }
343
+ out.push(...seg[1].slice(1).filter((l) => /^\s*#/.test(l) && !HOOK_MARKER.test(l))); // keep neighbours' comments
344
+ });
345
+ return out.join('\n');
346
+ }
347
+
286
348
  function areHooksAlreadyRegistered(projectRoot) {
287
349
  const configPath = getCodexConfigPath();
288
350
  if (!fs.existsSync(configPath)) return false;
289
- const content = fs.readFileSync(configPath, 'utf8');
290
- const shimPath = getHookShimPath(projectRoot);
291
- // Registered if config contains our shim path in any hooks entry
292
- return content.includes(HOOK_SHIM_REL) || content.includes(shimPath.split('\\').join('/'));
351
+ // at least one CURRENT kenaz handler: a block left by an old checkout does not run this core's shim
352
+ const { found } = scanKenazHooks(fs.readFileSync(configPath, 'utf8'), projectRoot);
353
+ return Object.values(found).some((f) => f.current.length > 0);
293
354
  }
294
355
 
295
- // TASK_2299: HOOK_EVENTS that have no [[hooks.EV.hooks]] block pointing at our shim in config.toml.
296
- function missingHookEvents() {
356
+ // TASK_2299: HOOK_EVENTS without exactly one current kenaz handler. TASK_2418: stale (old checkout path,
357
+ // UserPromptSubmit without --pull) and duplicated ones count as missing, so registerHooks rewrites them.
358
+ function missingHookEvents(projectRoot) {
297
359
  const configPath = getCodexConfigPath();
298
- const content = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8').replace(/\\/g, '/') : '';
299
- return HOOK_EVENTS.filter((ev) => {
300
- const blocks = content.match(new RegExp('\\[\\[hooks\\.' + ev + '\\.hooks\\]\\][^\\[]*', 'g')) || [];
301
- return !blocks.some((blk) => blk.includes(HOOK_SHIM_REL));
302
- });
360
+ const { found } = scanKenazHooks(fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '', projectRoot);
361
+ return HOOK_EVENTS.filter((ev) => !found[ev] || found[ev].stale.length > 0 || found[ev].current.length !== 1);
303
362
  }
304
363
 
305
364
  // True when the codex CLI both supports hooks AND our shim is registered.
@@ -313,6 +372,7 @@ function nativeHooksActive(projectRoot) {
313
372
  function registerHooks(log, projectRoot, events) {
314
373
  const shimPath = getHookShimPath(projectRoot).split('\\').join('/');
315
374
  const configPath = getCodexConfigPath();
375
+ const evs = events || HOOK_EVENTS;
316
376
 
317
377
  // Ensure ~/.codex dir exists
318
378
  const codexDir = path.dirname(configPath);
@@ -322,8 +382,16 @@ function registerHooks(log, projectRoot, events) {
322
382
 
323
383
  // [[hooks.X]] array-of-tables entries are additive-safe TOML: appending is valid
324
384
  // even if the user already has their own [[hooks.X]] entries from other tools.
325
- const lines = ['', '# kenaz-dashboard native hooks (TASK_1733) -- auto-registered by setup-codex.js'];
326
- for (const ev of events || HOOK_EVENTS) {
385
+ // TASK_2418: kenaz handlers already present for these events (stale or not) are dropped first, so each
386
+ // event ends with exactly one current handler.
387
+ let kept = '';
388
+ try {
389
+ kept = fs.existsSync(configPath) ? stripKenazHooks(fs.readFileSync(configPath, 'utf8'), evs, projectRoot) : '';
390
+ if (!Object.keys(scanKenazHooks(kept, projectRoot).found).length) kept = kept.split('\n').filter((l) => !HOOK_MARKER.test(l)).join('\n');
391
+ } catch (err) { log('hook registration failed: ' + err.message); return { success: false, action: 'hooks_failed', error: err.message }; }
392
+ const lines = [''];
393
+ if (!kept.split('\n').some((l) => HOOK_MARKER.test(l))) lines.push('# kenaz-dashboard native hooks (TASK_1733) -- auto-registered by setup-codex.js');
394
+ for (const ev of evs) {
327
395
  lines.push(
328
396
  '[[hooks.' + ev + ']]',
329
397
  '',
@@ -337,8 +405,8 @@ function registerHooks(log, projectRoot, events) {
337
405
  const hookBlock = lines.join('\n');
338
406
 
339
407
  try {
340
- fs.appendFileSync(configPath, hookBlock, 'utf8');
341
- log('kenaz hooks appended to ' + configPath);
408
+ fs.writeFileSync(configPath, kept + (kept && !kept.endsWith('\n') ? '\n' : '') + hookBlock, 'utf8');
409
+ log('kenaz hooks written to ' + configPath);
342
410
  return { success: true, action: 'hooks_registered' };
343
411
  } catch (err) {
344
412
  log('hook registration failed: ' + err.message);
@@ -0,0 +1,129 @@
1
+ /**
2
+ * TASK_2420 — runtime/config-only completion and truthful outcome reporting.
3
+ *
4
+ * Drives the REAL task-api.js CLI (`complete`) against a temp git repo with a temp
5
+ * .kenaz/tasks store. Never touches ~/.kenaz, ~/.claude or ~/.codex.
6
+ */
7
+ 'use strict';
8
+
9
+ const test = require('node:test');
10
+ const assert = require('node:assert/strict');
11
+ const fs = require('fs');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const { execSync, spawnSync } = require('child_process');
15
+
16
+ const ROOT = path.resolve(__dirname, '..', '..', '..');
17
+ const TASK_API = path.join(ROOT, '.collaboration', 'core', 'task-api.js');
18
+ const { summarizePostComplete, spawnFailureCause } = require('../post-complete');
19
+
20
+ function git(cmd, cwd) {
21
+ return execSync(cmd, { cwd, encoding: 'utf8', timeout: 20000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
22
+ }
23
+
24
+ function setup(taskId, scopePaths = []) {
25
+ const tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'tn2420-')));
26
+ const repo = path.join(tmp, 'repo');
27
+ fs.mkdirSync(repo);
28
+ git('git init -b userwork', repo);
29
+ git('git config user.email t@t.com', repo);
30
+ git('git config user.name Test', repo);
31
+ git('git config commit.gpgsign false', repo);
32
+ fs.writeFileSync(path.join(repo, 'README.md'), 'init\n');
33
+ fs.writeFileSync(path.join(repo, '.gitignore'), '.kenaz/\n');
34
+ git('git add README.md .gitignore', repo);
35
+ git('git commit --no-verify -m init', repo);
36
+ const dir = path.join(repo, '.kenaz', 'tasks', 'active');
37
+ fs.mkdirSync(dir, { recursive: true });
38
+ fs.writeFileSync(path.join(dir, `${taskId}.yaml`), [
39
+ `id: "${taskId}"`, 'title: "runtime repair"', 'status: in_progress', 'priority: P2',
40
+ scopePaths.length ? `scope_paths:\n${scopePaths.map(p => ` - "${p}"`).join('\n')}` : 'scope_paths: []', '',
41
+ ].join('\n'));
42
+ return { tmp, repo };
43
+ }
44
+
45
+ function complete(repo, taskId, extra = []) {
46
+ return spawnSync('node', [TASK_API, '--project-path', repo, 'complete', taskId, '--skip-archive', ...extra], {
47
+ cwd: ROOT, encoding: 'utf8', timeout: 120000, windowsHide: true,
48
+ env: { ...process.env, KENAZ_INTEGRATION_QUEUE: '' },
49
+ });
50
+ }
51
+
52
+ const where = (repo, taskId) => ['done', 'active', 'blocked']
53
+ .filter(d => fs.existsSync(path.join(repo, '.kenaz', 'tasks', d, `${taskId}.yaml`)));
54
+
55
+ test('TASK_2420: no-branch task with --no-source completes; git merge/verify reported as skipped', () => {
56
+ const taskId = 'TASK_24201';
57
+ const { tmp, repo } = setup(taskId);
58
+ try {
59
+ const r = complete(repo, taskId, ['--no-source']);
60
+ const out = `${r.stdout}\n${r.stderr}`;
61
+ assert.equal(r.status, 0, out);
62
+ assert.match(r.stdout, /任務已完成/);
63
+ assert.match(out, /no branch: nothing to integrate/);
64
+ assert.doesNotMatch(out, /Git merge failed/);
65
+ assert.deepEqual(where(repo, taskId), ['done']);
66
+ } finally { fs.rmSync(tmp, { recursive: true, force: true }); }
67
+ });
68
+
69
+ test('TASK_2420: --no-source is refused when the task branch has unmerged commits (work is not silently dropped)', () => {
70
+ const taskId = 'TASK_24202';
71
+ const { tmp, repo } = setup(taskId);
72
+ try {
73
+ git(`git checkout -b task/${taskId.toLowerCase()}`, repo);
74
+ fs.writeFileSync(path.join(repo, 'a.txt'), 'real work\n');
75
+ git('git add a.txt', repo);
76
+ git('git commit --no-verify -m work', repo);
77
+ git('git checkout userwork', repo);
78
+ const r = complete(repo, taskId, ['--no-source']);
79
+ assert.notEqual(r.status, 0, 'must not report success');
80
+ assert.doesNotMatch(r.stdout, /✅ 任務已完成/);
81
+ assert.match(r.stderr, /任務未完成/);
82
+ assert.match(r.stderr, /no_source_but_work_exists/);
83
+ assert.match(r.stderr, /exists with commits not on userwork/);
84
+ assert.ok(!where(repo, taskId).includes('done'), `card must not stay in done/: ${where(repo, taskId)}`);
85
+ } finally { fs.rmSync(tmp, { recursive: true, force: true }); }
86
+ });
87
+
88
+ test('TASK_2420: branch task whose merge fails is NOT reported as plain success and the cause is shown', () => {
89
+ const taskId = 'TASK_24203';
90
+ const { tmp, repo } = setup(taskId);
91
+ try {
92
+ // Conflicting edits: branch and userwork both change a.txt differently.
93
+ fs.writeFileSync(path.join(repo, 'a.txt'), 'base\n');
94
+ git('git add a.txt', repo); git('git commit --no-verify -m base', repo);
95
+ git(`git checkout -b task/${taskId.toLowerCase()}`, repo);
96
+ fs.writeFileSync(path.join(repo, 'a.txt'), 'branch side\n');
97
+ git('git commit --no-verify -am branch', repo);
98
+ git('git checkout userwork', repo);
99
+ fs.writeFileSync(path.join(repo, 'a.txt'), 'user side\n');
100
+ git('git commit --no-verify -am user', repo);
101
+ const r = complete(repo, taskId);
102
+ assert.notEqual(r.status, 0, `${r.stdout}\n${r.stderr}`);
103
+ assert.doesNotMatch(r.stdout, /✅ 任務已完成/);
104
+ assert.match(r.stderr, /任務未完成/);
105
+ assert.match(r.stderr, /git: failed — \S+/);
106
+ assert.ok(!where(repo, taskId).includes('done'), `card must not stay in done/: ${where(repo, taskId)}`);
107
+ } finally { fs.rmSync(tmp, { recursive: true, force: true }); }
108
+ });
109
+
110
+ test('TASK_2420: summarize — move/reconcile failures carry their real cause and card is flagged unreconciled', () => {
111
+ const s = summarizePostComplete({
112
+ git: { merged: false, taskBranch: 'task/x', failure_reason: 'merge_conflict', mergeErrorText: 'CONFLICT in a.txt', blockError: 'EBUSY: resource busy' },
113
+ reconcileError: 'ETIMEDOUT: spawnSync node ETIMEDOUT',
114
+ });
115
+ assert.equal(s.outcome, 'not_completed');
116
+ assert.equal(s.cardState, 'done_unreconciled');
117
+ const txt = JSON.stringify(s.steps);
118
+ assert.match(txt, /EBUSY: resource busy/);
119
+ assert.match(txt, /ETIMEDOUT/);
120
+ assert.match(txt, /CONFLICT in a\.txt/);
121
+ assert.doesNotMatch(txt, /unknown error/i);
122
+ });
123
+
124
+ test('TASK_2420: spawnFailureCause never returns "unknown error"', () => {
125
+ assert.match(spawnFailureCause({ status: null, stderr: '', stdout: '', error: Object.assign(new Error('spawnSync node ETIMEDOUT'), { code: 'ETIMEDOUT' }) }), /ETIMEDOUT/);
126
+ assert.match(spawnFailureCause({ status: null, signal: 'SIGTERM', stderr: '', stdout: '' }), /SIGTERM/);
127
+ assert.match(spawnFailureCause({ status: 3, stderr: '', stdout: '' }), /code 3/);
128
+ assert.equal(spawnFailureCause({ status: 1, stderr: 'boom\nmore' }), 'boom');
129
+ });
@@ -1766,6 +1766,98 @@ function classifyMergeFailureReason(g) {
1766
1766
  return 'git_step_failed';
1767
1767
  }
1768
1768
 
1769
+ /**
1770
+ * TASK_2420: the real cause of a failed spawnSync. stderr/stdout first, then the spawn
1771
+ * error (ENOENT / ETIMEDOUT), then the signal, then the exit code — never a bare "unknown".
1772
+ */
1773
+ function spawnFailureCause(res) {
1774
+ if (!res) return 'no result from child process';
1775
+ const text = (res.stderr || res.stdout || '').toString().trim();
1776
+ if (text) return text.split('\n')[0];
1777
+ if (res.error) return `${res.error.code ? res.error.code + ': ' : ''}${res.error.message}`;
1778
+ if (res.signal) return `child killed by signal ${res.signal} (timeout?)`;
1779
+ return `child exited with code ${res.status} and no output`;
1780
+ }
1781
+
1782
+ /**
1783
+ * TASK_2420: git step that has nothing to integrate (task declared source-less: runtime /
1784
+ * config repair). Never skips when real work exists: a worktree or a task/<id> branch that
1785
+ * is ahead of the user branch means there IS something to merge, so that is a failure.
1786
+ */
1787
+ function buildNoSourceGitResult(taskId, projectPath, worktreePath) {
1788
+ const skipReason = 'no branch: nothing to integrate';
1789
+ const taskBranch = `task/${taskId.toLowerCase()}`;
1790
+ const root = resolveProjectRoot(projectPath);
1791
+ const refuse = (why) => ({
1792
+ taskBranch, userBranch: null, originalBranch: null, committed: false, merged: false,
1793
+ verify_merge_landed: false, failure_reason: 'no_source_but_work_exists', blockedReason: why,
1794
+ });
1795
+ if (worktreePath) return refuse(`--no-source given but worktree ${worktreePath} exists; merge it or drop --no-source`);
1796
+ if (root && branchExists(taskBranch, root)) {
1797
+ const userBranch = resolveUserBranch(root);
1798
+ const ahead = userBranch ? git(`rev-list ${userBranch}..${taskBranch}`, { cwd: root, throws: false }) : null;
1799
+ if (ahead === null || ahead.trim()) {
1800
+ return refuse(`--no-source given but ${taskBranch} exists with commits not on ${userBranch}; merge it or drop --no-source`);
1801
+ }
1802
+ }
1803
+ return { taskBranch: null, userBranch: null, originalBranch: null, committed: false, merged: false,
1804
+ noChanges: true, gitSkipped: true, noSource: true, gitSkipReason: 'no_source', gitSkipDetail: skipReason };
1805
+ }
1806
+
1807
+ /**
1808
+ * TASK_2420: truthful per-step summary of a postComplete() result.
1809
+ * outcome: 'completed' | 'completed_with_warnings' | 'not_completed'.
1810
+ * Semantics: a git step that failed means the card must NOT be reported completed — it is
1811
+ * moved to blocked/ (existing behaviour) and `cardState` says where it really is. Failures
1812
+ * after the merge landed (card bookkeeping) leave code delivered, so they are warnings.
1813
+ */
1814
+ function summarizePostComplete(result) {
1815
+ const steps = [];
1816
+ const warnings = [];
1817
+ const g = result.git;
1818
+ let gitFailed = false;
1819
+ if (!g) {
1820
+ steps.push({ step: 'git', status: 'skipped', reason: 'git step not run' });
1821
+ } else if (isVerifiedMergeSuccess(g)) {
1822
+ steps.push({ step: 'git', status: 'ok' });
1823
+ } else if (g.noSource) {
1824
+ steps.push({ step: 'git', status: 'skipped', reason: g.gitSkipDetail });
1825
+ steps.push({ step: 'verify_merge', status: 'skipped', reason: g.gitSkipDetail });
1826
+ } else if (g.gitSkipped) {
1827
+ steps.push({ step: 'git', status: 'skipped', reason: g.gitSkipReason || 'git step skipped' });
1828
+ } else if (g.noGitRepo) {
1829
+ steps.push({ step: 'git', status: 'skipped', reason: 'not a git repository' });
1830
+ } else if (g.noChanges) {
1831
+ steps.push({ step: 'git', status: 'skipped', reason: g.memoryOnly ? 'memory-only task' : 'no changes within declared scope' });
1832
+ } else {
1833
+ gitFailed = true;
1834
+ const cause = [classifyMergeFailureReason(g), g.blockedReason, g.mergeErrorText, g.gitException, g.verify_merge_landed_reason]
1835
+ .filter(Boolean).map(s => String(s).split('\n')[0]).filter((s, i, a) => a.indexOf(s) === i).join(' | ');
1836
+ steps.push({ step: 'git', status: 'failed', reason: cause });
1837
+ }
1838
+ let cardState = null;
1839
+ if (gitFailed) {
1840
+ if (result.taskBlocked || result.reconciliationApplied) {
1841
+ cardState = 'blocked';
1842
+ steps.push({ step: 'move_blocked', status: 'ok' });
1843
+ } else if (result.reconciliationChecked) {
1844
+ cardState = 'active';
1845
+ steps.push({ step: 'move_blocked', status: 'failed', reason: g.blockError || 'card is not in done/ but could not be moved to blocked/' });
1846
+ } else {
1847
+ cardState = 'done_unreconciled';
1848
+ steps.push({ step: 'move_blocked', status: 'failed', reason: g.blockError || 'unknown' });
1849
+ steps.push({ step: 'reconcile', status: 'failed', reason: result.reconcileError || 'reconciliation did not run' });
1850
+ }
1851
+ } else if (g && !result.taskCompleted && result.completeError) {
1852
+ warnings.push(`card completion step failed: ${result.completeError || 'see log'}`);
1853
+ }
1854
+ if (result.forsetiL0 && result.forsetiL0.status === 'FAIL') {
1855
+ warnings.push(`Forseti L0: ${result.forsetiL0.failures.map(f => f.message).join('; ')}`);
1856
+ }
1857
+ const outcome = gitFailed ? 'not_completed' : (warnings.length ? 'completed_with_warnings' : 'completed');
1858
+ return { outcome, cardState, steps, warnings };
1859
+ }
1860
+
1769
1861
  function blockTaskForMergeFailure(taskId, projectPath, gitResult) {
1770
1862
  if (!taskId) return false;
1771
1863
 
@@ -1846,7 +1938,9 @@ function blockTaskForMergeFailure(taskId, projectPath, gitResult) {
1846
1938
 
1847
1939
  if (updateResult.status !== 0) {
1848
1940
  const errMsg = (updateResult.stderr || updateResult.stdout || '').trim();
1849
- console.log(` ⚠️ Failed to move ${taskId} to blocked/: ${errMsg.split('\n')[0] || 'unknown error'}`);
1941
+ const cause = spawnFailureCause(updateResult);
1942
+ console.log(` ⚠️ Failed to move ${taskId} to blocked/: ${cause}`);
1943
+ if (gitResult) gitResult.blockError = cause;
1850
1944
  return false;
1851
1945
  }
1852
1946
 
@@ -2124,6 +2218,7 @@ function postComplete(opts = {}) {
2124
2218
  formation,
2125
2219
  skill,
2126
2220
  worktreePath = process.env.FORMATION_WORKTREE_PATH || null,
2221
+ noSource = false,
2127
2222
  } = opts;
2128
2223
 
2129
2224
  // B11: opportunistic GC of orphan worktrees from prior failed dispatches
@@ -2244,7 +2339,13 @@ function postComplete(opts = {}) {
2244
2339
  // branch commits + the YAML move enter main).
2245
2340
  // Flag absent (default) => legacy gitBranchAndMerge path, byte-identical
2246
2341
  // to previous behavior — rollback is trivial. Dry runs stay on legacy.
2247
- if (process.env.KENAZ_INTEGRATION_QUEUE === '1' && !dryRun) {
2342
+ if (noSource) {
2343
+ // TASK_2420: explicit source-less completion (runtime/config repair, no branch).
2344
+ gitResult = buildNoSourceGitResult(taskId, projectPath, worktreePath);
2345
+ console.log(gitResult.noSource
2346
+ ? ` ⏭️ git merge/verify skipped (${gitResult.gitSkipDetail})`
2347
+ : ` ❌ --no-source refused: ${gitResult.blockedReason}`);
2348
+ } else if (process.env.KENAZ_INTEGRATION_QUEUE === '1' && !dryRun) {
2248
2349
  gitResult = require('./integration.js').integrateForPostComplete({ taskId, projectPath, message });
2249
2350
  } else {
2250
2351
  gitResult = gitBranchAndMerge({ taskId, message, dryRun, projectPath, skipTests, testCommands, worktreePath });
@@ -2380,10 +2481,12 @@ function postComplete(opts = {}) {
2380
2481
  result.taskCompleted = true;
2381
2482
  console.log(` ✅ Task ${taskId} already in done/`);
2382
2483
  } else {
2383
- console.log(` ⚠️ Failed to complete task: ${errMsg.split('\n')[0]}`);
2484
+ result.completeError = spawnFailureCause(completeResult);
2485
+ console.log(` ⚠️ Failed to complete task: ${result.completeError}`);
2384
2486
  }
2385
2487
  }
2386
2488
  } catch (e) {
2489
+ result.completeError = e.message;
2387
2490
  console.log(` ⚠️ Task complete error: ${e.message}`);
2388
2491
  }
2389
2492
  } else if (taskId && !dryRun && result.git && result.git.noChanges) {
@@ -2411,10 +2514,12 @@ function postComplete(opts = {}) {
2411
2514
  result.taskCompleted = true;
2412
2515
  console.log(` ✅ Task ${taskId} already in done/`);
2413
2516
  } else {
2414
- console.log(` ⚠️ Failed to complete task: ${errMsg.split('\n')[0]}`);
2517
+ result.completeError = spawnFailureCause(completeResult);
2518
+ console.log(` ⚠️ Failed to complete task: ${result.completeError}`);
2415
2519
  }
2416
2520
  }
2417
2521
  } catch (e) {
2522
+ result.completeError = e.message;
2418
2523
  console.log(` ⚠️ Task complete error: ${e.message}`);
2419
2524
  }
2420
2525
  } else if (taskId && result.git && !isVerifiedMergeSuccess(result.git) && !result.git.noGitRepo && !result.git.noChanges) {
@@ -2507,12 +2612,14 @@ function postComplete(opts = {}) {
2507
2612
  console.log(` 🚨 [TASK_1536] Reconciliation: task ${taskId} was in done/ but merge did not land — moved to blocked/ (failure_reason=${reconcileVitals.failure_reason})`);
2508
2613
  } else if (reconcileResult.status === 0) {
2509
2614
  // task was not in done/ — already blocked or active, nothing to reconcile
2615
+ result.reconciliationChecked = true;
2510
2616
  console.log(` ✅ [TASK_1536] Reconciliation check: task ${taskId} not in done/ — no reconciliation needed`);
2511
2617
  } else {
2512
- const errMsg = (reconcileResult.stderr || reconcileResult.stdout || '').trim();
2513
- console.log(` ⚠️ [TASK_1536] Reconciliation failed: ${errMsg.split('\n')[0] || 'unknown error'}`);
2618
+ result.reconcileError = spawnFailureCause(reconcileResult);
2619
+ console.log(` ⚠️ [TASK_1536] Reconciliation failed: ${result.reconcileError}`);
2514
2620
  }
2515
2621
  } catch (reconcileErr) {
2622
+ result.reconcileError = reconcileErr.message || String(reconcileErr);
2516
2623
  console.log(` ⚠️ [TASK_1536] Reconciliation error (non-fatal): ${(reconcileErr.message || '').split('\n')[0]}`);
2517
2624
  }
2518
2625
  }
@@ -2544,10 +2651,12 @@ function postComplete(opts = {}) {
2544
2651
  result.taskCompleted = true;
2545
2652
  console.log(` ✅ Task ${taskId} already in done/`);
2546
2653
  } else {
2547
- console.log(` ⚠️ Safety net move failed: ${errMsg.split('\n')[0]}`);
2654
+ result.completeError = spawnFailureCause(moveResult);
2655
+ console.log(` ⚠️ Safety net move failed: ${result.completeError}`);
2548
2656
  }
2549
2657
  }
2550
2658
  } catch (e) {
2659
+ result.completeError = e.message;
2551
2660
  console.log(` ⚠️ Safety net error: ${e.message}`);
2552
2661
  }
2553
2662
  }
@@ -3106,6 +3215,14 @@ function postComplete(opts = {}) {
3106
3215
  updatePostCompleteGuardOutcome(guardFile, taskId, passSucceeded ? 'success' : 'failure');
3107
3216
  }
3108
3217
 
3218
+ // TASK_2420: truthful outcome — never "completed" when a required step failed.
3219
+ result.summary = summarizePostComplete(result);
3220
+ if (taskId && !dryRun && result.summary.outcome !== 'completed') {
3221
+ console.log(`── Outcome: ${result.summary.outcome.toUpperCase()} ──`);
3222
+ for (const st of result.summary.steps) console.log(` ${st.status === 'failed' ? '❌' : st.status === 'skipped' ? '⏭️ ' : '✅'} ${st.step}: ${st.status}${st.reason ? ' — ' + st.reason : ''}`);
3223
+ for (const w of result.summary.warnings) console.log(` ⚠️ ${w}`);
3224
+ }
3225
+
3109
3226
  return result;
3110
3227
  }
3111
3228
 
@@ -3131,6 +3248,7 @@ Options:
3131
3248
  --keep N 保留最新 N 個 done 任務(預設 10)
3132
3249
  --skip-archive 跳過 archive 檢查
3133
3250
  --skip-git 跳過 git 分支管理
3251
+ --no-source 無 source 任務(runtime/config 修復,無 branch):git merge/verify 標記 skipped
3134
3252
  --skip-tests 跳過回歸測試(PPL-113)
3135
3253
  --test-cmd "CMD" Agent 指定的測試指令(可多次指定,PPL-113)
3136
3254
  --message "MSG" 自訂 commit message
@@ -3184,6 +3302,7 @@ Examples:
3184
3302
  const keep = getArg('--keep') ? parseInt(getArg('--keep'), 10) : CONFIG.DEFAULT_KEEP;
3185
3303
  const skipArchive = args.includes('--skip-archive');
3186
3304
  const skipGit = args.includes('--skip-git');
3305
+ const noSource = args.includes('--no-source');
3187
3306
  const skipTests = args.includes('--skip-tests');
3188
3307
  const dryRun = args.includes('--dry-run');
3189
3308
  const message = getArg('--message');
@@ -3206,7 +3325,7 @@ Examples:
3206
3325
  }
3207
3326
 
3208
3327
  try {
3209
- postComplete({ taskId, keep, skipArchive, skipGit, skipTests, dryRun, message, projectPath, testCommands, worktreePath });
3328
+ postComplete({ taskId, keep, skipArchive, skipGit, skipTests, dryRun, message, projectPath, testCommands, worktreePath, noSource });
3210
3329
  } catch (e) {
3211
3330
  console.error(`❌ Post-complete failed: ${e.message}`);
3212
3331
  process.exit(1);
@@ -3217,6 +3336,8 @@ Examples:
3217
3336
  // ?????????????????????????????????????????????????????????????????????????????
3218
3337
  module.exports = {
3219
3338
  classifyMergeFailureReason,
3339
+ summarizePostComplete,
3340
+ spawnFailureCause,
3220
3341
  postComplete,
3221
3342
  archiveCheck,
3222
3343
  gitBranchAndMerge,
@@ -1 +1 @@
1
- {"28d36a7a-fcf1-488c-a1ed-d794ac52615f":{"files":["reference_worktree_rust_verify_recipe.md","feedback_mcp_default_project_drift.md","feedback_mark_landed_after_scoped_merge.md","feedback_heredoc_template_literal_escapes.md","reference_dashboard_release_procedure.md"],"lastFiredAtMs":1790248806590,"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge"]},"30cd222d-1a83-4617-a267-65ce8ec74a3e":{"files":["feedback_mcp_default_project_drift.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790249042951},"a0067f0e-60c1-4713-bdcb-8a34cd5205fd":{"files":["feedback_heredoc_template_literal_escapes.md","feedback_mcp_default_project_drift.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790259958980},"ee35d32d-321b-47e3-8a30-a8e096995cdd":{"files":["reference_worktree_rust_verify_recipe.md","feedback_never_cd_into_agent_worktrees.md","feedback_agent_tool_worktree_collision.md","feedback_mark_landed_after_scoped_merge.md","feedback_heredoc_template_literal_escapes.md","feedback_mcp_default_project_drift.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness"],"lastFiredAtMs":1790276539381},"1531340e-bf07-4bd6-909c-f22394d60876":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process"],"lastFiredAtMs":1790279018827},"73e6ee82-700b-4cbf-b002-c0b9b79dbab4":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process"],"lastFiredAtMs":1790365961440},"0722c428-a54f-47dc-9c1c-e12c1199dfe3":{"files":["feedback_never_cd_into_agent_worktrees.md","feedback_heredoc_template_literal_escapes.md","feedback_mark_landed_after_scoped_merge.md","feedback_agent_tool_worktree_collision.md","rune_synapse_incremental_index_blind_gap.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process"],"lastFiredAtMs":1790418630509},"f17d4fd3-7d6c-4e5e-9369-2b72a9266f90":{"files":["feedback_never_cd_into_agent_worktrees.md","feedback_heredoc_template_literal_escapes.md","rune_synapse_incremental_index_blind_gap.md","reference_dashboard_release_procedure.md","feedback_scoped_checkout_clobbers_worktree_state.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790457931617},"6794bd6e-562a-4b58-a746-b5bdcaca0e7c":{"files":["feedback_mark_landed_after_scoped_merge.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge"],"lastFiredAtMs":1790455100260},"75239ce3-f0a2-4792-81f5-d842d132528f":{"files":["feedback_never_cd_into_agent_worktrees.md","feedback_mark_landed_after_scoped_merge.md","feedback_agent_tool_worktree_collision.md","feedback_heredoc_template_literal_escapes.md","reference_dashboard_release_procedure.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790507402115},"804ea3d7-3de3-4586-b955-e6c8c684ff15":{"files":["feedback_heredoc_template_literal_escapes.md","feedback_never_cd_into_agent_worktrees.md","feedback_mcp_default_project_drift.md","feedback_mark_landed_after_scoped_merge.md","reference_worktree_rust_verify_recipe.md","reference_dashboard_release_procedure.md","feedback_agent_tool_worktree_collision.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness"],"lastFiredAtMs":1790581324936},"3e9b8a3f-882d-45d4-bbe5-a258b2bb3ce4":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790542391812},"3e15f2bf-f80a-4e4d-8960-bcd1a640fdff":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790543837477},"61843060-69da-46a7-b86f-e5a0b342cea8":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790581238348},"5b87c0bf-873f-4d27-a261-fbde5ea03299":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790619871099},"9c6a266c-9db4-4810-a341-5ffcd1de8726":{"files":["reference_dashboard_release_procedure.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790621819503},"12bd9885-66a8-4c0c-9811-0b327d551fc0":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790629466743},"bb010604-4dca-4993-9de0-aecb4754ad8e":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790666206918},"4b12601f-6069-452b-a58c-bf8263400324":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790666901610},"4df219ec-ba5e-4f76-97c5-a43137f300de":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790713272079},"3eecd2fc-56d7-4ee8-9a4f-2290da3fd123":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790718864114},"a806aaa3-3eaa-46bf-a9f2-f30cfa33acaa":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":[],"lastFiredAtMs":1790786463472},"c699f823-b467-4f50-ab73-e7351250a897":{"files":["reference_dashboard_release_procedure.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790790894539},"2a1ccb2b-e66f-4b3a-a4e6-2ec48622defe":{"files":["rune_synapse_incremental_index_blind_gap.md","feedback_mcp_default_project_drift.md","reference_worktree_rust_verify_recipe.md","feedback_never_cd_into_agent_worktrees.md","feedback_heredoc_template_literal_escapes.md","feedback_agent_tool_worktree_collision.md","feedback_mark_landed_after_scoped_merge.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness"],"lastFiredAtMs":1790868373263},"9782d641-82fa-4c35-acf6-97758550adbb":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge"],"lastFiredAtMs":1790806751702},"cd5fc5ea-1d77-4c15-8b09-3bcb5da02559":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790863308920}}
1
+ {"28d36a7a-fcf1-488c-a1ed-d794ac52615f":{"files":["reference_worktree_rust_verify_recipe.md","feedback_mcp_default_project_drift.md","feedback_mark_landed_after_scoped_merge.md","feedback_heredoc_template_literal_escapes.md","reference_dashboard_release_procedure.md"],"lastFiredAtMs":1790248806590,"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge"]},"30cd222d-1a83-4617-a267-65ce8ec74a3e":{"files":["feedback_mcp_default_project_drift.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790249042951},"a0067f0e-60c1-4713-bdcb-8a34cd5205fd":{"files":["feedback_heredoc_template_literal_escapes.md","feedback_mcp_default_project_drift.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790259958980},"ee35d32d-321b-47e3-8a30-a8e096995cdd":{"files":["reference_worktree_rust_verify_recipe.md","feedback_never_cd_into_agent_worktrees.md","feedback_agent_tool_worktree_collision.md","feedback_mark_landed_after_scoped_merge.md","feedback_heredoc_template_literal_escapes.md","feedback_mcp_default_project_drift.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness"],"lastFiredAtMs":1790276539381},"1531340e-bf07-4bd6-909c-f22394d60876":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process"],"lastFiredAtMs":1790279018827},"73e6ee82-700b-4cbf-b002-c0b9b79dbab4":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process"],"lastFiredAtMs":1790365961440},"0722c428-a54f-47dc-9c1c-e12c1199dfe3":{"files":["feedback_never_cd_into_agent_worktrees.md","feedback_heredoc_template_literal_escapes.md","feedback_mark_landed_after_scoped_merge.md","feedback_agent_tool_worktree_collision.md","rune_synapse_incremental_index_blind_gap.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process"],"lastFiredAtMs":1790418630509},"f17d4fd3-7d6c-4e5e-9369-2b72a9266f90":{"files":["feedback_never_cd_into_agent_worktrees.md","feedback_heredoc_template_literal_escapes.md","rune_synapse_incremental_index_blind_gap.md","reference_dashboard_release_procedure.md","feedback_scoped_checkout_clobbers_worktree_state.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790457931617},"6794bd6e-562a-4b58-a746-b5bdcaca0e7c":{"files":["feedback_mark_landed_after_scoped_merge.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge"],"lastFiredAtMs":1790455100260},"75239ce3-f0a2-4792-81f5-d842d132528f":{"files":["feedback_never_cd_into_agent_worktrees.md","feedback_mark_landed_after_scoped_merge.md","feedback_agent_tool_worktree_collision.md","feedback_heredoc_template_literal_escapes.md","reference_dashboard_release_procedure.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790507402115},"804ea3d7-3de3-4586-b955-e6c8c684ff15":{"files":["feedback_heredoc_template_literal_escapes.md","feedback_never_cd_into_agent_worktrees.md","feedback_mcp_default_project_drift.md","feedback_mark_landed_after_scoped_merge.md","reference_worktree_rust_verify_recipe.md","reference_dashboard_release_procedure.md","feedback_agent_tool_worktree_collision.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness"],"lastFiredAtMs":1790581324936},"3e9b8a3f-882d-45d4-bbe5-a258b2bb3ce4":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790542391812},"3e15f2bf-f80a-4e4d-8960-bcd1a640fdff":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790543837477},"61843060-69da-46a7-b86f-e5a0b342cea8":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790581238348},"5b87c0bf-873f-4d27-a261-fbde5ea03299":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790619871099},"9c6a266c-9db4-4810-a341-5ffcd1de8726":{"files":["reference_dashboard_release_procedure.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790621819503},"12bd9885-66a8-4c0c-9811-0b327d551fc0":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790629466743},"bb010604-4dca-4993-9de0-aecb4754ad8e":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790666206918},"4b12601f-6069-452b-a58c-bf8263400324":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790666901610},"4df219ec-ba5e-4f76-97c5-a43137f300de":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790713272079},"3eecd2fc-56d7-4ee8-9a4f-2290da3fd123":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790718864114},"a806aaa3-3eaa-46bf-a9f2-f30cfa33acaa":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":[],"lastFiredAtMs":1790786463472},"c699f823-b467-4f50-ab73-e7351250a897":{"files":["reference_dashboard_release_procedure.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1790790894539},"2a1ccb2b-e66f-4b3a-a4e6-2ec48622defe":{"files":["rune_synapse_incremental_index_blind_gap.md","feedback_mcp_default_project_drift.md","reference_worktree_rust_verify_recipe.md","feedback_never_cd_into_agent_worktrees.md","feedback_heredoc_template_literal_escapes.md","feedback_agent_tool_worktree_collision.md","feedback_mark_landed_after_scoped_merge.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Yggdrasil, Alaya, relay & products","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Ansuz cognition & process","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Hooks & Claude Code harness"],"lastFiredAtMs":1790868373263},"9782d641-82fa-4c35-acf6-97758550adbb":{"files":["feedback_heredoc_template_literal_escapes.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge"],"lastFiredAtMs":1790806751702},"cd5fc5ea-1d77-4c15-8b09-3bcb5da02559":{"files":[],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Synapse, KuzuDB & recall"],"lastFiredAtMs":1790863308920},"566fcb6b-3fdb-4c39-801c-824eaeeadfbc":{"files":["feedback_agent_tool_worktree_collision.md","feedback_mark_landed_after_scoped_merge.md","feedback_worktree_remove_junction_empties_main.md","feedback_never_cd_into_agent_worktrees.md","feedback_heredoc_template_literal_escapes.md","feedback_rust_merge_restarts_live_dashboard.md","reference_worktree_rust_verify_recipe.md","feedback_never_truncate_tsc_output.md","reference_dashboard_release_procedure.md","feedback_cargo_feature_change_verify_tauri_dev.md","feedback_eprintln_not_in_backend_log.md"],"areas":["C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Worktree, dispatch & merge","C:\\Users\\user\\.claude\\projects\\c--Users-user-Documents-GitHub-KenazAI\\memory\\KNOWLEDGE.md::Tauri, Rust build & release"],"lastFiredAtMs":1791717483417}}