kenaz 0.3.10 โ†’ 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 (131) hide show
  1. package/core/.claude/skills/code_reviewer/SKILL.md +0 -1
  2. package/core/.claude/skills/code_reviewer/patterns.yaml +0 -8
  3. package/core/.claude/skills/dev_agent/SKILL.md +3 -6
  4. package/core/.claude/skills/heimdall/SKILL.md +5 -6
  5. package/core/.claude/skills/huginn/SKILL.md +11 -38
  6. package/core/.collaboration/core/task-2288-followups.test.js +2 -37
  7. package/core/.collaboration/core/task-2421-replica.test.js +229 -0
  8. package/core/.collaboration/core/task-api.js +223 -230
  9. package/core/.collaboration/core/task-api.test.js +1 -138
  10. package/core/.collaboration/core/task-replica.js +189 -0
  11. package/core/.formation/.merge-lock +1 -0
  12. package/core/.formation/bifrost/codex-doctor.js +12 -2
  13. package/core/.formation/bifrost/codex-doctor.test.js +15 -0
  14. package/core/.formation/bifrost/launch.js +2 -2
  15. package/core/.formation/bifrost/launch.test.js +2 -2
  16. package/core/.formation/bifrost/setup-codex.js +84 -16
  17. package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
  18. package/core/.formation/bifrost/shared/codex-injection.js +3 -17
  19. package/core/.formation/bifrost/shared/consolidate.js +1 -51
  20. package/core/.formation/bifrost/sync.js +4 -5
  21. package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +1 -1
  22. package/core/.formation/tools/dispatch.js +2 -2
  23. package/core/.formation/tools/kenaz-stats.js +1 -1
  24. package/core/.formation/tools/mcp-allowlist.test.js +5 -3
  25. package/core/.formation/tools/post-complete/bounded-http.js +1 -40
  26. package/core/.formation/tools/post-complete/bounded-http.test.js +2 -89
  27. package/core/.formation/tools/post-complete/cognitive.js +2 -119
  28. package/core/.formation/tools/post-complete/task2420.test.js +129 -0
  29. package/core/.formation/tools/post-complete/testing.js +3 -21
  30. package/core/.formation/tools/post-complete.js +131 -436
  31. package/core/.formation/tools/post-complete.test.js +0 -135
  32. package/core/.formation/tools/route-audit.test.js +8 -20
  33. package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
  34. package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
  35. package/core/.rule/INDEX.yaml +3 -3
  36. package/core/.rule/general/ddx-synapse-stack.yaml +11 -11
  37. package/core/.rule/general/specialist-identity-contract.yaml +5 -5
  38. package/core/.rule/general/synapse-code-intelligence.yaml +25 -111
  39. package/core/.rule/general/worktree-isolation.yaml +2 -2
  40. package/core/.rule/pipeline/task-execution.yaml +6 -9
  41. package/core/.specialist/Amelia/README.md +5 -5
  42. package/core/.specialist/Amelia/capability.yaml +10 -18
  43. package/core/.specialist/Amelia/knowledge.yaml +15 -66
  44. package/core/.specialist/Amelia/workflow.yaml +12 -21
  45. package/core/.specialist/Ansuz/capability.yaml +12 -87
  46. package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +1 -1
  47. package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +2 -3
  48. package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +1 -3
  49. package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +8 -21
  50. package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +0 -4
  51. package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +1 -1
  52. package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +0 -1
  53. package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +0 -3
  54. package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +3 -3
  55. package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +1 -7
  56. package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +3 -4
  57. package/core/.specialist/Ansuz/concepts/identity.yaml +1 -2
  58. package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +0 -1
  59. package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +3 -4
  60. package/core/.specialist/Ansuz/core.yaml +3 -6
  61. package/core/.specialist/Ansuz/elixir.yaml +6 -6
  62. package/core/.specialist/Ansuz/exo_cortex_tools.yaml +5 -9
  63. package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +2 -2
  64. package/core/.specialist/Ansuz/guards/lesson_guards.yaml +6 -19
  65. package/core/.specialist/Ansuz/identity_contract.yaml +4 -4
  66. package/core/.specialist/Ansuz/knowledge.yaml +13 -40
  67. package/core/.specialist/Ansuz/modules/ddx.yaml +2 -2
  68. package/core/.specialist/Ansuz/modules/prophecy.yaml +19 -19
  69. package/core/.specialist/Ansuz/modules/pulse_empty.yaml +6 -17
  70. package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +3 -102
  71. package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +2 -44
  72. package/core/.specialist/Ansuz/workflow.yaml +32 -76
  73. package/core/.specialist/Forseti/core.yaml +2 -7
  74. package/core/.specialist/Forseti/rule.yaml +1 -1
  75. package/core/.specialist/Heimdall/SKILL.md +2 -3
  76. package/core/.specialist/Heimdall/capability.yaml +22 -45
  77. package/core/.specialist/Heimdall/core.yaml +24 -126
  78. package/core/.specialist/Heimdall/principles.yaml +8 -11
  79. package/core/.specialist/Heimdall/rule.yaml +2 -2
  80. package/core/.specialist/Huginn/capability.yaml +31 -52
  81. package/core/.specialist/Huginn/core.yaml +43 -123
  82. package/core/.specialist/Huginn/principles.yaml +5 -5
  83. package/core/.specialist/Huginn/rule.yaml +3 -9
  84. package/core/.specialist/REFLECTION_TO_CONTROL.md +3 -4
  85. package/core/.specialist/shared/guard_schema.json +1 -1
  86. package/core/.specialist/shared/principles.yaml +6 -6
  87. package/core/.specialist/tools/__tests__/rea-detect.test.js +10 -0
  88. package/core/.specialist/tools/phase-gate.js +10 -230
  89. package/core/.specialist/tools/phase-gate.test.js +2 -2
  90. package/core/.specialist/tools/prompt-files.test.js +1 -1
  91. package/core/.specialist/tools/rea-detect.js +6 -2
  92. package/core/.specialist/tools/session.js +2 -89
  93. package/core/.synapse/memory-shared.json.sync +1 -0
  94. package/core/.system/memory/README.md +3 -0
  95. package/core/CLAUDE.md +6 -7
  96. package/core/KENAZ_CORE_VERSION +1 -1
  97. package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +75 -83
  98. package/core/dev/specs/peer/CODEX_MANAGED_DAEMON.md +54 -0
  99. package/core/dev/specs/peer/setup-codex-hooks.test.js +38 -0
  100. package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +56 -0
  101. package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
  102. package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
  103. package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +1 -0
  104. package/core/plugins/kenaz/INVENTORY.md +1 -1
  105. package/core/plugins/kenaz/scripts/kenaz.test.js +70 -1
  106. package/core/plugins/kenaz/scripts/ygg.js +45 -4
  107. package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +0 -1
  108. package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +0 -8
  109. package/core/plugins/kenaz/skills/dev_agent/SKILL.md +3 -6
  110. package/lib/cli.js +17 -9
  111. package/lib/cli.test.js +10 -0
  112. package/lib/dashboard.test.js +3 -3
  113. package/package.json +6 -6
  114. package/vendor/install-kenaz-cli.js +167 -16
  115. package/core/.claude/hooks/handoff-snapshot-on-decision.js +0 -188
  116. package/core/.claude/skills/synapse_maintain/SKILL.md +0 -84
  117. package/core/.collaboration/core/task-2264-single-after-capture.test.js +0 -61
  118. package/core/.formation/tools/alaya-gate.js +0 -198
  119. package/core/.formation/tools/post-complete/health.js +0 -464
  120. package/core/.formation/tools/post-complete/task2038.test.js +0 -246
  121. package/core/.formation/tools/post-complete/task2341.test.js +0 -74
  122. package/core/.rule/general/cortex-tools.yaml +0 -181
  123. package/core/.rule/general/memory-language.yaml +0 -67
  124. package/core/.rule/general/memory-source-priority.yaml +0 -207
  125. package/core/.rule/general/synapse-cognitive-fusion.yaml +0 -301
  126. package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +0 -14
  127. package/core/.specialist/Ansuz/concepts/write_verification.yaml +0 -26
  128. package/core/.specialist/Ansuz/modules/recall.yaml +0 -45
  129. package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +0 -156
  130. package/core/.specialist/tools/context-injection.js +0 -395
  131. package/core/dev/scripts/probe/hippocampus-concurrency.js +0 -247
@@ -43,24 +43,18 @@
43
43
  * |-------|------------------------------------------|-------------------------------------------|----------------------------|-------------------------------------|
44
44
  * | GC | gcStaleWorktrees() | always | try/catch (non-fatal) | orphan worktrees accumulate |
45
45
  * | Guard | Duplicate-suppress guard | recent worktree pass for same taskId (<2m) | try/catch (non-fatal) | double execution |
46
- * | 0 | Hippocampus episode start | taskId && !dryRun | try/catch (non-fatal) | episodes not started |
47
46
  * | 1 | archiveCheck() | !skipArchive | inline logging | none โ€” optional |
48
47
  * | 2 | gitBranchAndMerge() or _gitMergeFromWorktree() | !skipGit && taskId | throws (caught at CLI) | task stuck in active |
49
48
  * | 2.5a | Yggdrasil commit-sync (fire-and-forget) | gitResult.committed && !dryRun | catch (silent) | sync missed silently |
50
- * | 2.6a | Passive rune extraction | gitResult.committed && !dryRun | catch (silent) | runes not extracted |
51
49
  * | 2.7 | Outcome recording + surprise detection | !dryRun | catch (silent) | no outcome metrics recorded |
52
50
  * | 2.8 | Skill matrix update | !dryRun | catch (log warning) | metrics not updated |
53
51
  * | 2.9 | Cognitive attribution | !dryRun && mergeSucceeded | catch (silent) | attribution missed |
54
52
  * | 2.5b | Complete/block task via task-api | merge success or failure | spawnSync; safety net move | task stuck in active |
55
53
  * | 2.5c | Record AC failures (ACCEPTANCE_FAILED) | acFailures.length > 0 | catch (log) | AC failures invisible |
56
54
  * | 2.5d | Safety net: force-move to done | merge succeeded but complete() failed | spawnSync | task stays in active |
57
- * | 2.5e | Passive rune extraction (completed) | result.taskCompleted && !dryRun | catch (silent) | completion runes not tagged |
58
55
  * | 2.6b | Ansuz inbox signal | result.taskCompleted && !dryRun | catch (silent) | Ansuz not notified |
59
56
  * | 2.6c | Relay task_completed event (TASK_1409) | result.taskCompleted && !dryRun && relay | catch (silent) | PM inbox not notified cross-node |
60
57
  * | 3 | Memory agent onTaskCompleted/onError | memoryAgent && taskId | catch (log warning) | memory event not queued |
61
- * | 3b | Provenance runes (synapse_inscribe) | !dryRun && git.merged | catch (silent) | provenance not tagged |
62
- * | 4 | Guardian health check + KEEP/REVERT | taskId (always) | catch (log) | no feedback signal for loop |
63
- * | 6 | Hippocampus episode close | hippocampusEpisodeId set | catch (log warning) | episode not closed properly |
64
58
  *
65
59
  * A.2 gitBranchAndMerge() โ€” serial mode (L2344)
66
60
  *
@@ -333,7 +327,6 @@ try {
333
327
 
334
328
  const CONFIG = {
335
329
  DEFAULT_KEEP: 10,
336
- GUARDIAN_INTERVAL: 10, // Run synapse_health snapshot every N completed tasks
337
330
  };
338
331
 
339
332
  // ?????????????????????????????????????????????????????????????????????????????
@@ -1199,20 +1192,15 @@ function gcStaleWorktrees(currentTaskId, projectPath) {
1199
1192
 
1200
1193
  // ============================================================================
1201
1194
  // Cognitive tool usage tracking (TASK_1105)
1202
- // Records which cognitive tools (synapse_recall, kenaz_lsp_*, ddx, etc.) were
1195
+ // Records which cognitive tools (kenaz_lsp_*, ddx, etc.) were
1203
1196
  // invoked during the task, aggregates into .kenaz/specialist/ansuz_self_model.json
1204
1197
  // under cognitive_competence, and flags negative-trend tools as mirror blind spots.
1205
1198
  // ============================================================================
1206
1199
 
1207
1200
  const COGNITIVE_TOOLS = [
1208
- 'synapse_recall',
1209
1201
  'kenaz_lsp_callers',
1210
1202
  'kenaz_lsp_references',
1211
- 'synapse_inscribe',
1212
- 'synapse_forge',
1213
1203
  'ddx',
1214
- 'hippocampus_episode_start',
1215
- 'cortex_recall',
1216
1204
  ];
1217
1205
 
1218
1206
  function sleepSync(ms) {
@@ -1778,6 +1766,98 @@ function classifyMergeFailureReason(g) {
1778
1766
  return 'git_step_failed';
1779
1767
  }
1780
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
+
1781
1861
  function blockTaskForMergeFailure(taskId, projectPath, gitResult) {
1782
1862
  if (!taskId) return false;
1783
1863
 
@@ -1858,7 +1938,9 @@ function blockTaskForMergeFailure(taskId, projectPath, gitResult) {
1858
1938
 
1859
1939
  if (updateResult.status !== 0) {
1860
1940
  const errMsg = (updateResult.stderr || updateResult.stdout || '').trim();
1861
- 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;
1862
1944
  return false;
1863
1945
  }
1864
1946
 
@@ -2039,23 +2121,6 @@ setStagingConfig(CONFIG);
2039
2121
  // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
2040
2122
 
2041
2123
  const TASK_STATUS_DIRS = ['backlog', 'todo', 'active', 'done', 'blocked'];
2042
- // ?????????????????????????????????????????????????????????????????????????????
2043
- // SECTION: HEALTH_AND_GUARDIAN (extracted to ./post-complete/health.js)
2044
- // ?????????????????????????????????????????????????????????????????????????????
2045
- const {
2046
- resolveMcpProjectPath,
2047
- readLoopConfig,
2048
- measureHealthScoreSync,
2049
- readPreTaskHealthSnapshot,
2050
- saveTaskHealthSnapshot,
2051
- computeHealthVerdict,
2052
- writeVerdictToTask,
2053
- readGuardianCounter,
2054
- writeGuardianCounter,
2055
- triggerGuardianHealthCheck,
2056
- HEALTH_SNAPSHOTS_DIR_NAME,
2057
- } = require('./post-complete/health');
2058
-
2059
2124
  // TASK_1885: deliberately NOT the same knob as loopCfg.revert_threshold (DEFAULT_REVERT_THRESHOLD
2060
2125
  // in health.js). This is a fixed calibration point for the Design Reasoning Attribution valence
2061
2126
  // classifier (feeds skill matrix / EFE learning signals) -- it answers "was this outcome bad
@@ -2076,8 +2141,6 @@ const {
2076
2141
  parseCognitiveCheckpoint,
2077
2142
  detectCognitiveCheckpoint,
2078
2143
  collectThinkingTags,
2079
- triggerPassiveRuneExtraction,
2080
- callHippocampusMcpSync,
2081
2144
  } = require('./post-complete/cognitive');
2082
2145
  initCognitive({ findLogForTask, getPostCompleteChangedFiles, rankTouchedFiles, stripCJK });
2083
2146
 
@@ -2094,7 +2157,7 @@ const {
2094
2157
  updateSkillMatrix,
2095
2158
  accumulateValence,
2096
2159
  } = require('./post-complete/outcome');
2097
- initOutcome({ resolveProjectRoot, resolveTasksBase, loadTaskScopePaths, getPostCompleteChangedFiles, callHippocampusMcpSync });
2160
+ initOutcome({ resolveProjectRoot, resolveTasksBase, loadTaskScopePaths, getPostCompleteChangedFiles });
2098
2161
 
2099
2162
  // ?????????????????????????????????????????????????????????????????????????????
2100
2163
  // SECTION: INTEGRATION_GAPS (extracted to ./post-complete/integration-gaps.js)
@@ -2155,6 +2218,7 @@ function postComplete(opts = {}) {
2155
2218
  formation,
2156
2219
  skill,
2157
2220
  worktreePath = process.env.FORMATION_WORKTREE_PATH || null,
2221
+ noSource = false,
2158
2222
  } = opts;
2159
2223
 
2160
2224
  // B11: opportunistic GC of orphan worktrees from prior failed dispatches
@@ -2182,56 +2246,6 @@ function postComplete(opts = {}) {
2182
2246
 
2183
2247
  const result = { archive: null, git: null };
2184
2248
 
2185
- // Step 0: Hippocampus Episode Start (episodic memory โ€” TASK_716)
2186
- // TASK_963: Resolve touched file_paths so Rust can create TOUCHES edges to Symbols
2187
- let hippocampusEpisodeId = null;
2188
- if (taskId && !dryRun) {
2189
- try {
2190
- // Collect file_paths from worktree git diff or task scope_paths
2191
- let filePaths = [];
2192
- const diffRoot = worktreePath || resolveProjectRoot(projectPath);
2193
- if (diffRoot) {
2194
- try {
2195
- const diffCmd = worktreePath
2196
- ? 'git diff --name-only main...HEAD'
2197
- : 'git diff --name-only HEAD~1 HEAD';
2198
- const diffOut = spawnSync('git', diffCmd.split(' ').slice(1), {
2199
- cwd: diffRoot, encoding: 'utf8', timeout: 8000, windowsHide: true,
2200
- });
2201
- if (diffOut.status === 0 && diffOut.stdout) {
2202
- filePaths = diffOut.stdout.trim().split('\n').filter(Boolean);
2203
- }
2204
- } catch { /* git diff failed โ€” fall through to scope_paths */ }
2205
- }
2206
- if (filePaths.length === 0) {
2207
- filePaths = loadTaskScopePaths(taskId, projectPath);
2208
- }
2209
-
2210
- const epArgs = {
2211
- task_id: taskId,
2212
- scope: `post-complete:${taskId}`,
2213
- agent_role: formation || 'unit',
2214
- agent_skill: skill || 'do',
2215
- ...(filePaths.length > 0 ? { file_paths: filePaths } : {}),
2216
- // TASK_2025: hippocampus_episode_start is now a write tool that requires
2217
- // an explicit projectPath (mcp.rs resolve_project_for_write) -- it no
2218
- // longer silently falls back to the Dashboard's currently-selected
2219
- // project. projectPath is only set here when --project-path was passed
2220
- // (sub-project dispatch); core-project dispatches never pass it, so
2221
- // fall back to ROOT (this script's own core project root) rather than
2222
- // omitting the param and hard-failing every core-project run.
2223
- projectPath: projectPath || ROOT,
2224
- };
2225
- const epResult = callHippocampusMcpSync('hippocampus_episode_start', epArgs);
2226
- if (epResult && epResult.episode_id) {
2227
- hippocampusEpisodeId = epResult.episode_id;
2228
- console.log(` ๐Ÿง  Hippocampus: episode ${hippocampusEpisodeId} started (${filePaths.length} file_paths)`);
2229
- }
2230
- } catch (e) {
2231
- console.log(` โš ๏ธ Hippocampus episode start skipped: ${e.message}`);
2232
- }
2233
- }
2234
-
2235
2249
  // Step 1: Archive
2236
2250
  if (!skipArchive) {
2237
2251
  console.log('โ”€โ”€ Archive Check โ”€โ”€');
@@ -2325,7 +2339,13 @@ function postComplete(opts = {}) {
2325
2339
  // branch commits + the YAML move enter main).
2326
2340
  // Flag absent (default) => legacy gitBranchAndMerge path, byte-identical
2327
2341
  // to previous behavior โ€” rollback is trivial. Dry runs stay on legacy.
2328
- 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) {
2329
2349
  gitResult = require('./integration.js').integrateForPostComplete({ taskId, projectPath, message });
2330
2350
  } else {
2331
2351
  gitResult = gitBranchAndMerge({ taskId, message, dryRun, projectPath, skipTests, testCommands, worktreePath });
@@ -2356,17 +2376,6 @@ function postComplete(opts = {}) {
2356
2376
  } catch { /* commit-sync not available โ€” graceful degradation */ }
2357
2377
  }
2358
2378
 
2359
- // Step 2.6: Passive Rune Extraction๏ผˆfire-and-forget๏ผ‰
2360
- if (gitResult.committed && !dryRun) {
2361
- try {
2362
- const commitMsg = message || `[${taskId}] task completed`;
2363
- const projectRoot = resolveProjectRoot(projectPath);
2364
- if (projectRoot) {
2365
- triggerPassiveRuneExtraction(commitMsg, projectRoot, projectPath);
2366
- }
2367
- } catch { /* rune extraction failed โ€” never block post-complete */ }
2368
- }
2369
-
2370
2379
  // Step 2.7: Outcome Recording + Surprise Detection๏ผˆfire-and-forget๏ผ‰
2371
2380
  if (!dryRun) {
2372
2381
  try {
@@ -2472,10 +2481,12 @@ function postComplete(opts = {}) {
2472
2481
  result.taskCompleted = true;
2473
2482
  console.log(` โœ… Task ${taskId} already in done/`);
2474
2483
  } else {
2475
- console.log(` โš ๏ธ Failed to complete task: ${errMsg.split('\n')[0]}`);
2484
+ result.completeError = spawnFailureCause(completeResult);
2485
+ console.log(` โš ๏ธ Failed to complete task: ${result.completeError}`);
2476
2486
  }
2477
2487
  }
2478
2488
  } catch (e) {
2489
+ result.completeError = e.message;
2479
2490
  console.log(` โš ๏ธ Task complete error: ${e.message}`);
2480
2491
  }
2481
2492
  } else if (taskId && !dryRun && result.git && result.git.noChanges) {
@@ -2503,10 +2514,12 @@ function postComplete(opts = {}) {
2503
2514
  result.taskCompleted = true;
2504
2515
  console.log(` โœ… Task ${taskId} already in done/`);
2505
2516
  } else {
2506
- console.log(` โš ๏ธ Failed to complete task: ${errMsg.split('\n')[0]}`);
2517
+ result.completeError = spawnFailureCause(completeResult);
2518
+ console.log(` โš ๏ธ Failed to complete task: ${result.completeError}`);
2507
2519
  }
2508
2520
  }
2509
2521
  } catch (e) {
2522
+ result.completeError = e.message;
2510
2523
  console.log(` โš ๏ธ Task complete error: ${e.message}`);
2511
2524
  }
2512
2525
  } else if (taskId && result.git && !isVerifiedMergeSuccess(result.git) && !result.git.noGitRepo && !result.git.noChanges) {
@@ -2599,12 +2612,14 @@ function postComplete(opts = {}) {
2599
2612
  console.log(` ๐Ÿšจ [TASK_1536] Reconciliation: task ${taskId} was in done/ but merge did not land โ€” moved to blocked/ (failure_reason=${reconcileVitals.failure_reason})`);
2600
2613
  } else if (reconcileResult.status === 0) {
2601
2614
  // task was not in done/ โ€” already blocked or active, nothing to reconcile
2615
+ result.reconciliationChecked = true;
2602
2616
  console.log(` โœ… [TASK_1536] Reconciliation check: task ${taskId} not in done/ โ€” no reconciliation needed`);
2603
2617
  } else {
2604
- const errMsg = (reconcileResult.stderr || reconcileResult.stdout || '').trim();
2605
- 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}`);
2606
2620
  }
2607
2621
  } catch (reconcileErr) {
2622
+ result.reconcileError = reconcileErr.message || String(reconcileErr);
2608
2623
  console.log(` โš ๏ธ [TASK_1536] Reconciliation error (non-fatal): ${(reconcileErr.message || '').split('\n')[0]}`);
2609
2624
  }
2610
2625
  }
@@ -2636,10 +2651,12 @@ function postComplete(opts = {}) {
2636
2651
  result.taskCompleted = true;
2637
2652
  console.log(` โœ… Task ${taskId} already in done/`);
2638
2653
  } else {
2639
- console.log(` โš ๏ธ Safety net move failed: ${errMsg.split('\n')[0]}`);
2654
+ result.completeError = spawnFailureCause(moveResult);
2655
+ console.log(` โš ๏ธ Safety net move failed: ${result.completeError}`);
2640
2656
  }
2641
2657
  }
2642
2658
  } catch (e) {
2659
+ result.completeError = e.message;
2643
2660
  console.log(` โš ๏ธ Safety net error: ${e.message}`);
2644
2661
  }
2645
2662
  }
@@ -2694,74 +2711,6 @@ function postComplete(opts = {}) {
2694
2711
  }
2695
2712
  }
2696
2713
 
2697
- // Step 2.5: Passive Rune Extraction โ€” task completion signal (TASK_747)
2698
- if (result.taskCompleted && taskId && !dryRun) {
2699
- try {
2700
- const runeScope = loadTaskScopePaths(taskId, projectPath);
2701
- if (runeScope.length > 0) {
2702
- // Read task title and requirements via task-api get
2703
- let runeContent = `Task ${taskId}: completed.`;
2704
- try {
2705
- const getArgs = projectPath ? ['--project-path', projectPath] : [];
2706
- getArgs.push('get', taskId);
2707
- const getResult = spawnSync('node', [COLLABORATION_API, ...getArgs], {
2708
- encoding: 'utf8', timeout: 10000, cwd: ROOT, windowsHide: true,
2709
- });
2710
- if (getResult.status === 0 && getResult.stdout) {
2711
- const taskData = JSON.parse(getResult.stdout.trim());
2712
- const rawTitle = taskData.title || '';
2713
- const reqs = Array.isArray(taskData.requirements) ? taskData.requirements : [];
2714
- // MEM-LANG-001: strip CJK so FTS5 unicode61 tokenizer can index the rune
2715
- const title = stripCJK(rawTitle);
2716
- const reqSummary = stripCJK(reqs.slice(0, 3).join('; '));
2717
- // Fallback: if English portion of title is too short, use task ID as title
2718
- const effectiveTitle = title.length >= 10 ? title : `${taskId} task`;
2719
- runeContent = `Task ${taskId}: ${effectiveTitle}. Requirements: ${reqSummary}`;
2720
- if (runeContent.length > 300) runeContent = runeContent.slice(0, 297) + '...';
2721
- }
2722
- } catch { /* skip โ€” use fallback content */ }
2723
-
2724
- // Fire-and-forget synapse_inscribe for each scope path
2725
- const http = require('http');
2726
- let fired = 0;
2727
- for (const scopePath of runeScope) {
2728
- try {
2729
- const inscribeArgs = {
2730
- symbol_id: scopePath,
2731
- type: 'summary',
2732
- content: runeContent,
2733
- confidence: 0.65,
2734
- linked_task: taskId,
2735
- // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
2736
- projectPath: projectPath || ROOT,
2737
- };
2738
- const payload = JSON.stringify({
2739
- jsonrpc: '2.0',
2740
- id: `task-rune-${taskId}-${fired}`,
2741
- method: 'tools/call',
2742
- params: { name: 'synapse_inscribe', arguments: inscribeArgs },
2743
- });
2744
- const req = http.request({
2745
- hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
2746
- method: 'POST',
2747
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
2748
- timeout: 10000,
2749
- }, (res) => {
2750
- res.on('data', () => {});
2751
- res.on('end', () => {});
2752
- });
2753
- req.on('error', (err) => { console.warn(` โš ๏ธ synapse_inscribe HTTP error: ${err.message}`); });
2754
- req.on('timeout', () => { req.destroy(); });
2755
- req.write(payload);
2756
- req.end();
2757
- fired++;
2758
- } catch { /* skip this scope path */ }
2759
- }
2760
- console.log(` ๐Ÿง  Rune extraction: ${fired} scope symbols for ${taskId}`);
2761
- }
2762
- } catch { /* skip โ€” never blocks post-complete */ }
2763
- }
2764
-
2765
2714
  // Step 2.55 (cognitive tool usage -> cognitive_competence) was removed in TASK_2348: its only
2766
2715
  // data source, turn_feedback.jsonl, was written by the retired reflex hook.
2767
2716
 
@@ -3077,138 +3026,6 @@ function postComplete(opts = {}) {
3077
3026
  }
3078
3027
  }
3079
3028
 
3080
- // Step 3b: Provenance rune โ€” record which task + role modified scope paths (Cognee-inspired)
3081
- if (taskId && !dryRun && result.git && result.git.merged) {
3082
- try {
3083
- const scopePaths = loadTaskScopePaths(taskId, projectPath);
3084
- const role = result.taskRole || 'unknown';
3085
- if (scopePaths.length > 0 && scopePaths.length <= 5) {
3086
- const http = require('http');
3087
- for (const scopePath of scopePaths) {
3088
- try {
3089
- const payload = JSON.stringify({
3090
- jsonrpc: '2.0', id: `provenance-${taskId}`,
3091
- method: 'tools/call',
3092
- params: { name: 'synapse_inscribe', arguments: {
3093
- symbol_id: scopePath,
3094
- type: 'summary',
3095
- content: `Modified by ${taskId} (${role}). Scope: ${scopePaths.join(', ')}`,
3096
- confidence: 0.6,
3097
- author: `agent:${role}`,
3098
- linked_task: taskId,
3099
- // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
3100
- projectPath: projectPath || ROOT,
3101
- }},
3102
- });
3103
- const req = http.request({
3104
- hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
3105
- method: 'POST',
3106
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
3107
- timeout: 5000,
3108
- }, (res) => { res.on('data', () => {}); res.on('end', () => {}); });
3109
- req.on('error', () => {});
3110
- req.on('timeout', () => { req.destroy(); });
3111
- req.write(payload);
3112
- req.end();
3113
- } catch { /* skip */ }
3114
- }
3115
- console.log(` ๐Ÿ“ Provenance runes: ${scopePaths.length} scope paths tagged with ${taskId}/${role}`);
3116
- }
3117
- } catch { /* never blocks */ }
3118
- }
3119
-
3120
- // Step 4: Guardian Health Check + KEEP/REVERT delta (TASK_706)
3121
- if (taskId) {
3122
- const counter = readGuardianCounter();
3123
- counter.completed_since_last_health = (counter.completed_since_last_health || 0) + 1;
3124
-
3125
- const loopCfg = readLoopConfig(projectPath);
3126
- const regressionSkipVerdictReason = result.git?.regression?.skipVerdictReason || '';
3127
- const shouldSkipHealthVerdict = !!result.git?.regression?.skipVerdict;
3128
-
3129
- // TASK_2341: synapse_health now scores MEMORY (rune/episode/conflict backlog), not code,
3130
- // so it can never revert, block or penalise a task. Both modes only RECORD the trend.
3131
- if (loopCfg.mode === 'auto' && shouldSkipHealthVerdict) {
3132
- console.log('โ”€โ”€ Guardian Memory-Health Trend (loop auto mode) โ”€โ”€');
3133
- console.log(` โš ๏ธ Guardian: health snapshot skipped โ€” ${regressionSkipVerdictReason}`);
3134
- result.healthVerdictSkipped = { reason: regressionSkipVerdictReason };
3135
- counter.completed_since_last_health = 0;
3136
- counter.last_health_check = new Date().toISOString();
3137
- writeGuardianCounter(counter);
3138
- } else {
3139
- const preSn = readPreTaskHealthSnapshot(taskId, projectPath);
3140
- if (preSn && typeof preSn.score === 'number') {
3141
- console.log(`โ”€โ”€ Guardian Memory-Health Trend (${loopCfg.mode} mode โ€” record only, never reverts) โ”€โ”€`);
3142
- const postHealth = measureHealthScoreSync(projectPath);
3143
- if (postHealth) {
3144
- const afterPayload = saveTaskHealthSnapshot(taskId, 'after', postHealth, projectPath);
3145
- const vr = computeHealthVerdict(preSn, postHealth, loopCfg.revert_threshold, {
3146
- trendDeltaFromPrevious: afterPayload ? afterPayload.delta_from_previous : null,
3147
- });
3148
- console.log(` ๐Ÿ›ก๏ธ Memory health: ${postHealth.score}/100 (${vr.reason})`);
3149
- if (typeof vr.trendDeltaFromPrevious === 'number') {
3150
- console.log(` ๐Ÿ“ˆ Trend (delta_from_previous): ${vr.trendDeltaFromPrevious >= 0 ? '+' : ''}${vr.trendDeltaFromPrevious}`);
3151
- }
3152
- writeVerdictToTask(taskId, vr.verdict, vr.delta, vr.reason, projectPath);
3153
- result.healthVerdict = { verdict: vr.verdict, delta: vr.delta, reason: vr.reason };
3154
- // Auto-acted_on: mark runes recalled during this task as acted_on (closes the
3155
- // recall->acted_on->confidence_boost loop). Route still exists (synapse.rs).
3156
- if (loopCfg.mode === 'auto') {
3157
- try {
3158
- const actedOnPayload = JSON.stringify({ taskId });
3159
- const actReq = require('http').request({
3160
- hostname: '127.0.0.1',
3161
- port: 18080,
3162
- path: '/api/synapse/runes/mark-acted-on-by-task',
3163
- method: 'POST',
3164
- headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(actedOnPayload) },
3165
- timeout: 3000,
3166
- }, (res) => {
3167
- let body = '';
3168
- res.on('data', c => body += c);
3169
- res.on('end', () => {
3170
- try {
3171
- const r = JSON.parse(body);
3172
- if (r.marked > 0) console.log(` ๐Ÿ”— Acted-on: ${r.marked} runes marked for ${taskId}`);
3173
- } catch { /* parse error โ€” silent */ }
3174
- });
3175
- });
3176
- actReq.on('error', () => { /* Dashboard offline โ€” silent skip */ });
3177
- actReq.write(actedOnPayload);
3178
- actReq.end();
3179
- } catch { /* never blocks post-complete */ }
3180
- }
3181
- counter.completed_since_last_health = 0;
3182
- counter.last_health_check = new Date().toISOString();
3183
- } else {
3184
- console.log(` ๐Ÿ›ก๏ธ Guardian: Dashboard not running โ€” after-snapshot skipped for ${taskId}`);
3185
- }
3186
- } else if (loopCfg.mode === 'auto') {
3187
- const postHealth = measureHealthScoreSync(projectPath);
3188
- if (postHealth) {
3189
- saveTaskHealthSnapshot(taskId, 'before', postHealth, projectPath);
3190
- console.log(` ๐Ÿ›ก๏ธ Guardian: memory health=${postHealth.score}/100 (${postHealth.grade}) โ€” saved as baseline (no pre-task snapshot found)`);
3191
- } else {
3192
- console.log(' ๐Ÿ›ก๏ธ Guardian: Dashboard not running โ€” health snapshot skipped');
3193
- }
3194
- counter.completed_since_last_health = 0;
3195
- counter.last_health_check = new Date().toISOString();
3196
- } else if (counter.completed_since_last_health >= CONFIG.GUARDIAN_INTERVAL) {
3197
- console.log(`โ”€โ”€ Guardian Health Check (every ${CONFIG.GUARDIAN_INTERVAL} tasks) โ”€โ”€`);
3198
- triggerGuardianHealthCheck(projectPath);
3199
- counter.completed_since_last_health = 0;
3200
- counter.last_health_check = new Date().toISOString();
3201
- } else {
3202
- const remaining = CONFIG.GUARDIAN_INTERVAL - counter.completed_since_last_health;
3203
- console.log(` ๐Ÿ›ก๏ธ Guardian: ${remaining} tasks until next health snapshot (no pre-dispatch snapshot for ${taskId})`);
3204
- }
3205
- }
3206
-
3207
- if (!shouldSkipHealthVerdict || loopCfg.mode !== 'auto') {
3208
- writeGuardianCounter(counter);
3209
- }
3210
- }
3211
-
3212
3029
  // Step 4.6: Guard Failure Detector (TASK_1653)
3213
3030
  // Runs shadow-mode failure detectors for active lesson guards that carry failure_signature.
3214
3031
  // Reads guard snapshots directly from lesson_guards.yaml (not the task chart) because
@@ -3376,14 +3193,6 @@ function postComplete(opts = {}) {
3376
3193
  console.log(` ๐Ÿ‘‰ On branch: ${result.git.userBranch} โ€” merge to main when ready`);
3377
3194
  }
3378
3195
  }
3379
- // Health verdict summary (TASK_706)
3380
- if (result.healthVerdict) {
3381
- const hv = result.healthVerdict;
3382
- console.log(` Memory Health: ${hv.verdict} (trend=${typeof hv.delta === 'number' ? hv.delta.toFixed(1) : 'n/a'}) โ€” ${hv.reason}`);
3383
- } else if (result.healthVerdictSkipped) {
3384
- console.log(` Health Verdict: SKIPPED โš ๏ธ โ€” ${result.healthVerdictSkipped.reason}`);
3385
- }
3386
-
3387
3196
  // Cognitive checkpoint summary
3388
3197
  if (result.cognitive) {
3389
3198
  const cog = result.cognitive;
@@ -3398,84 +3207,6 @@ function postComplete(opts = {}) {
3398
3207
  }
3399
3208
  console.log('');
3400
3209
 
3401
- // Step 8a: Hippocampus Episode Append โ€” record task outcome (TASK_969)
3402
- if (hippocampusEpisodeId && !dryRun) {
3403
- try {
3404
- const contentParts = [`Task ${taskId} outcome:`];
3405
-
3406
- // Changed files
3407
- const changedFileList = result.git?.diffStat || [];
3408
- if (changedFileList.length > 0) {
3409
- contentParts.push(`Changed files: ${changedFileList.join(', ')}`);
3410
- } else if (result.git?.committed) {
3411
- contentParts.push('Changes committed (file list unavailable).');
3412
- } else if (result.git?.noChanges) {
3413
- contentParts.push('No file changes detected.');
3414
- }
3415
-
3416
- // Health delta (regression test results)
3417
- if (result.git?.regression?.skipVerdict) {
3418
- contentParts.push(`Health verdict skipped: ${result.git.regression.skipVerdictReason}.`);
3419
- } else if (result.git?.regression && !result.git.regression.skipped) {
3420
- contentParts.push(result.git.regression.passed
3421
- ? 'Health: tests passed.'
3422
- : `Health: tests FAILED.${result.git.regressionFixTask ? ` Fix task: ${result.git.regressionFixTask}` : ''}`);
3423
- }
3424
-
3425
- // Fix loop status
3426
- if (result.git?.acceptanceFixTasks && result.git.acceptanceFixTasks.length > 0) {
3427
- contentParts.push(`Fix loop triggered: ${result.git.acceptanceFixTasks.join(', ')}`);
3428
- }
3429
-
3430
- // Git merge status
3431
- if (isVerifiedMergeSuccess(result.git)) {
3432
- contentParts.push(`Merged to ${result.git.userBranch}.`);
3433
- }
3434
-
3435
- const appendArgs = {
3436
- episode_id: hippocampusEpisodeId,
3437
- phase: 'outcome',
3438
- content: contentParts.join(' '),
3439
- confidence: 0.8,
3440
- outcome: isVerifiedMergeSuccess(result.git) ? 'success' : 'partial',
3441
- // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
3442
- projectPath: projectPath || ROOT,
3443
- };
3444
- callHippocampusMcpSync('hippocampus_episode_append', appendArgs);
3445
- console.log(` ๐Ÿง  Hippocampus: episode ${hippocampusEpisodeId} appended outcome entry`);
3446
- } catch (e) {
3447
- console.log(` โš ๏ธ Hippocampus episode append skipped: ${e.message}`);
3448
- }
3449
- }
3450
-
3451
- // Step 8b: Hippocampus Episode Close (episodic memory โ€” TASK_716)
3452
- if (hippocampusEpisodeId && !dryRun) {
3453
- try {
3454
- const outcome = isVerifiedMergeSuccess(result.git)
3455
- ? 'success'
3456
- : (result.git?.noChanges || result.git?.memoryOnly ? 'success' : 'partial');
3457
- const summaryParts = [`Task ${taskId} post-complete finished.`];
3458
- if (isVerifiedMergeSuccess(result.git)) summaryParts.push(`Git merged to ${result.git.userBranch}.`);
3459
- if (result.taskCompleted) summaryParts.push('Task marked done.');
3460
- if (result.git?.regression?.skipVerdict) {
3461
- summaryParts.push('Health verdict skipped due to cargo lock contention.');
3462
- } else if (result.git?.regression && !result.git.regression.skipped) {
3463
- summaryParts.push(result.git.regression.passed ? 'Tests passed.' : 'Tests failed โ€” fix task created.');
3464
- }
3465
- const closeArgs = {
3466
- episode_id: hippocampusEpisodeId,
3467
- outcome,
3468
- summary: summaryParts.join(' '),
3469
- // TASK_2025: write tool -- must not omit projectPath (see epArgs comment above).
3470
- projectPath: projectPath || ROOT,
3471
- };
3472
- callHippocampusMcpSync('hippocampus_episode_close', closeArgs);
3473
- console.log(` ๐Ÿง  Hippocampus: episode ${hippocampusEpisodeId} closed (${outcome})`);
3474
- } catch (e) {
3475
- console.log(` โš ๏ธ Hippocampus episode close skipped: ${e.message}`);
3476
- }
3477
- }
3478
-
3479
3210
  // TASK_1593: Record outcome in dedup guard so a failed pass does not
3480
3211
  // suppress the next legitimate retry (Guard 1 worktree->null check).
3481
3212
  if (taskId && !dryRun) {
@@ -3484,6 +3215,14 @@ function postComplete(opts = {}) {
3484
3215
  updatePostCompleteGuardOutcome(guardFile, taskId, passSucceeded ? 'success' : 'failure');
3485
3216
  }
3486
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
+
3487
3226
  return result;
3488
3227
  }
3489
3228
 
@@ -3509,6 +3248,7 @@ Options:
3509
3248
  --keep N ไฟ็•™ๆœ€ๆ–ฐ N ๅ€‹ done ไปปๅ‹™๏ผˆ้ ่จญ 10๏ผ‰
3510
3249
  --skip-archive ่ทณ้Ž archive ๆชขๆŸฅ
3511
3250
  --skip-git ่ทณ้Ž git ๅˆ†ๆ”ฏ็ฎก็†
3251
+ --no-source ็„ก source ไปปๅ‹™๏ผˆruntime/config ไฟฎๅพฉ๏ผŒ็„ก branch๏ผ‰๏ผšgit merge/verify ๆจ™่จ˜ skipped
3512
3252
  --skip-tests ่ทณ้Žๅ›žๆญธๆธฌ่ฉฆ๏ผˆPPL-113๏ผ‰
3513
3253
  --test-cmd "CMD" Agent ๆŒ‡ๅฎš็š„ๆธฌ่ฉฆๆŒ‡ไปค๏ผˆๅฏๅคšๆฌกๆŒ‡ๅฎš๏ผŒPPL-113๏ผ‰
3514
3254
  --message "MSG" ่‡ช่จ‚ commit message
@@ -3562,6 +3302,7 @@ Examples:
3562
3302
  const keep = getArg('--keep') ? parseInt(getArg('--keep'), 10) : CONFIG.DEFAULT_KEEP;
3563
3303
  const skipArchive = args.includes('--skip-archive');
3564
3304
  const skipGit = args.includes('--skip-git');
3305
+ const noSource = args.includes('--no-source');
3565
3306
  const skipTests = args.includes('--skip-tests');
3566
3307
  const dryRun = args.includes('--dry-run');
3567
3308
  const message = getArg('--message');
@@ -3584,7 +3325,7 @@ Examples:
3584
3325
  }
3585
3326
 
3586
3327
  try {
3587
- postComplete({ taskId, keep, skipArchive, skipGit, skipTests, dryRun, message, projectPath, testCommands, worktreePath });
3328
+ postComplete({ taskId, keep, skipArchive, skipGit, skipTests, dryRun, message, projectPath, testCommands, worktreePath, noSource });
3588
3329
  } catch (e) {
3589
3330
  console.error(`โŒ Post-complete failed: ${e.message}`);
3590
3331
  process.exit(1);
@@ -3593,58 +3334,13 @@ Examples:
3593
3334
  // ?????????????????????????????????????????????????????????????????????????????
3594
3335
  // SECTION: EXPORTS
3595
3336
  // ?????????????????????????????????????????????????????????????????????????????
3596
- // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3597
- // Sub-agent post-complete (lightweight equivalent for Agent tool dispatch)
3598
- // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
3599
-
3600
- /**
3601
- * Lightweight post-complete for tasks dispatched via Agent tool (isolation=worktree).
3602
- * The Agent tool handles worktree git isolation automatically, so git merge is skipped.
3603
- * Health scoring is deferred to the next Heimdall cycle (dashboard must be running
3604
- * for measureHealthScoreSync to work; safe to call from Ansuz interactive session).
3605
- *
3606
- * Call this after the Agent tool sub-agent returns to record the health delta.
3607
- *
3608
- * @param {string} taskId - Task ID (TASK_XXX)
3609
- * @param {string|null} projectPath - Sub-project path (null = core)
3610
- * @returns {Promise<{ healthDelta: number|null, grade: string|null }>}
3611
- */
3612
- async function subagentPostComplete(taskId, projectPath) {
3613
- const result = { healthDelta: null, grade: null };
3614
-
3615
- try {
3616
- // Record post-task health snapshot (for audit trail + Heimdall comparison)
3617
- const postHealth = measureHealthScoreSync(projectPath);
3618
- if (postHealth && taskId) {
3619
- saveTaskHealthSnapshot(taskId, 'after', postHealth, projectPath);
3620
- const preSn = readPreTaskHealthSnapshot(taskId, projectPath);
3621
- if (preSn?.score != null) {
3622
- // TASK_2341: null when the baseline is graph-era (different score_version)
3623
- result.healthDelta = computeHealthVerdict(preSn, postHealth).delta;
3624
- result.grade = postHealth.grade;
3625
- console.log(
3626
- ` ๐Ÿ›ก๏ธ Sub-agent Guardian: health=${postHealth.score}/100 (${postHealth.grade})` +
3627
- ` trend=${typeof result.healthDelta === 'number' ? (result.healthDelta >= 0 ? '+' : '') + result.healthDelta : 'n/a'}`
3628
- );
3629
- } else {
3630
- // Save as baseline for future comparison
3631
- saveTaskHealthSnapshot(taskId, 'before', postHealth, projectPath);
3632
- result.grade = postHealth.grade;
3633
- console.log(` ๐Ÿ›ก๏ธ Sub-agent Guardian: health=${postHealth.score}/100 (${postHealth.grade}) โ€” saved as baseline`);
3634
- }
3635
- }
3636
- } catch { /* never block Ansuz on health check failure */ }
3637
-
3638
- return result;
3639
- }
3640
-
3641
3337
  module.exports = {
3642
3338
  classifyMergeFailureReason,
3339
+ summarizePostComplete,
3340
+ spawnFailureCause,
3643
3341
  postComplete,
3644
3342
  archiveCheck,
3645
3343
  gitBranchAndMerge,
3646
- measureHealthScoreSync,
3647
- saveTaskHealthSnapshot,
3648
3344
  classifySkillKey,
3649
3345
  isDaemonManagedPath,
3650
3346
  listDaemonManagedPaths,
@@ -3653,7 +3349,6 @@ module.exports = {
3653
3349
  checkAndUpdatePostCompleteGuard,
3654
3350
  updatePostCompleteGuardOutcome,
3655
3351
  writeInboxEntryIdempotent,
3656
- subagentPostComplete,
3657
3352
  readLatestHandoff, // TASK_1828: handoffs/-first resolver for relay summaries
3658
3353
  reclaimWorktreeDir, // TASK_2028: unlock + git-level reclaim for stale/interrupted worktrees
3659
3354
  appendPhase9OutcomeLog, // TASK_2078: exported for agent_result/transcript precedence unit testing