kenaz 0.3.10 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/.claude/skills/code_reviewer/SKILL.md +0 -1
- package/core/.claude/skills/code_reviewer/patterns.yaml +0 -8
- package/core/.claude/skills/dev_agent/SKILL.md +3 -6
- package/core/.claude/skills/heimdall/SKILL.md +5 -6
- package/core/.claude/skills/huginn/SKILL.md +11 -38
- package/core/.collaboration/core/task-api.js +2 -218
- package/core/.collaboration/core/task-api.test.js +1 -138
- package/core/.formation/bifrost/launch.js +2 -2
- package/core/.formation/bifrost/launch.test.js +2 -2
- package/core/.formation/bifrost/shared/ansuz-boot-policy.js +3 -3
- package/core/.formation/bifrost/shared/codex-injection.js +3 -17
- package/core/.formation/bifrost/shared/consolidate.js +1 -51
- package/core/.formation/bifrost/sync.js +4 -5
- package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +1 -1
- package/core/.formation/tools/dispatch.js +2 -2
- package/core/.formation/tools/kenaz-stats.js +1 -1
- package/core/.formation/tools/mcp-allowlist.test.js +5 -3
- package/core/.formation/tools/post-complete/bounded-http.js +1 -40
- package/core/.formation/tools/post-complete/bounded-http.test.js +2 -89
- package/core/.formation/tools/post-complete/cognitive.js +2 -119
- package/core/.formation/tools/post-complete/testing.js +3 -21
- package/core/.formation/tools/post-complete.js +2 -428
- package/core/.formation/tools/post-complete.test.js +0 -135
- package/core/.formation/tools/route-audit.test.js +8 -20
- package/core/.rule/INDEX.yaml +3 -3
- package/core/.rule/general/ddx-synapse-stack.yaml +11 -11
- package/core/.rule/general/specialist-identity-contract.yaml +5 -5
- package/core/.rule/general/synapse-code-intelligence.yaml +25 -111
- package/core/.rule/general/worktree-isolation.yaml +2 -2
- package/core/.rule/pipeline/task-execution.yaml +6 -9
- package/core/.specialist/Amelia/README.md +5 -5
- package/core/.specialist/Amelia/capability.yaml +10 -18
- package/core/.specialist/Amelia/knowledge.yaml +15 -66
- package/core/.specialist/Amelia/workflow.yaml +12 -21
- package/core/.specialist/Ansuz/capability.yaml +12 -87
- package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +1 -1
- package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +2 -3
- package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +1 -3
- package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +8 -21
- package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +0 -4
- package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +1 -1
- package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +0 -1
- package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +0 -3
- package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +3 -3
- package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +1 -7
- package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +3 -4
- package/core/.specialist/Ansuz/concepts/identity.yaml +1 -2
- package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +0 -1
- package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +3 -4
- package/core/.specialist/Ansuz/core.yaml +3 -6
- package/core/.specialist/Ansuz/elixir.yaml +6 -6
- package/core/.specialist/Ansuz/exo_cortex_tools.yaml +5 -9
- package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +2 -2
- package/core/.specialist/Ansuz/guards/lesson_guards.yaml +6 -19
- package/core/.specialist/Ansuz/identity_contract.yaml +4 -4
- package/core/.specialist/Ansuz/knowledge.yaml +13 -40
- package/core/.specialist/Ansuz/modules/ddx.yaml +2 -2
- package/core/.specialist/Ansuz/modules/prophecy.yaml +19 -19
- package/core/.specialist/Ansuz/modules/pulse_empty.yaml +6 -17
- package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +3 -102
- package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +2 -44
- package/core/.specialist/Ansuz/workflow.yaml +32 -76
- package/core/.specialist/Forseti/core.yaml +2 -7
- package/core/.specialist/Forseti/rule.yaml +1 -1
- package/core/.specialist/Heimdall/SKILL.md +2 -3
- package/core/.specialist/Heimdall/capability.yaml +22 -45
- package/core/.specialist/Heimdall/core.yaml +24 -126
- package/core/.specialist/Heimdall/principles.yaml +8 -11
- package/core/.specialist/Heimdall/rule.yaml +2 -2
- package/core/.specialist/Huginn/capability.yaml +31 -52
- package/core/.specialist/Huginn/core.yaml +43 -123
- package/core/.specialist/Huginn/principles.yaml +5 -5
- package/core/.specialist/Huginn/rule.yaml +3 -9
- package/core/.specialist/REFLECTION_TO_CONTROL.md +3 -4
- package/core/.specialist/shared/guard_schema.json +1 -1
- package/core/.specialist/shared/principles.yaml +6 -6
- package/core/.specialist/tools/phase-gate.js +10 -230
- package/core/.specialist/tools/phase-gate.test.js +2 -2
- package/core/.specialist/tools/prompt-files.test.js +1 -1
- package/core/.specialist/tools/session.js +2 -89
- package/core/.system/memory/README.md +3 -0
- package/core/CLAUDE.md +6 -7
- package/core/KENAZ_CORE_VERSION +1 -1
- package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +26 -0
- package/core/plugins/kenaz/INVENTORY.md +1 -1
- package/core/plugins/kenaz/scripts/kenaz.test.js +2 -1
- package/core/plugins/kenaz/scripts/ygg.js +2 -0
- package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +0 -1
- package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +0 -8
- package/core/plugins/kenaz/skills/dev_agent/SKILL.md +3 -6
- package/lib/cli.js +0 -2
- package/package.json +6 -6
- package/core/.claude/hooks/handoff-snapshot-on-decision.js +0 -188
- package/core/.claude/skills/synapse_maintain/SKILL.md +0 -84
- package/core/.formation/tools/alaya-gate.js +0 -198
- package/core/.formation/tools/post-complete/health.js +0 -464
- package/core/.formation/tools/post-complete/task2038.test.js +0 -246
- package/core/.formation/tools/post-complete/task2341.test.js +0 -74
- package/core/.rule/general/cortex-tools.yaml +0 -181
- package/core/.rule/general/memory-language.yaml +0 -67
- package/core/.rule/general/memory-source-priority.yaml +0 -207
- package/core/.rule/general/synapse-cognitive-fusion.yaml +0 -301
- package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +0 -14
- package/core/.specialist/Ansuz/concepts/write_verification.yaml +0 -26
- package/core/.specialist/Ansuz/modules/recall.yaml +0 -45
- package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +0 -156
- package/core/.specialist/tools/context-injection.js +0 -395
- package/core/dev/scripts/probe/hippocampus-concurrency.js +0 -247
|
@@ -17,14 +17,6 @@ mutation_counterpart:
|
|
|
17
17
|
# orphan = logical state / lifecycle not terminated
|
|
18
18
|
|
|
19
19
|
pairs:
|
|
20
|
-
- pair: hippocampus_episode
|
|
21
|
-
mutation: 'hippocampus_episode_start'
|
|
22
|
-
counterpart: 'hippocampus_episode_close'
|
|
23
|
-
severity: leak
|
|
24
|
-
rationale: 'episode never closed -> consolidation never fires, unarchived memory accumulates'
|
|
25
|
-
grep_mutation: 'hippocampus_episode_start'
|
|
26
|
-
grep_counterpart: 'hippocampus_episode_close'
|
|
27
|
-
|
|
28
20
|
- pair: task_lifecycle
|
|
29
21
|
mutation: 'task-api.js claim'
|
|
30
22
|
counterpart: 'task-api.js complete | task-api.js move'
|
|
@@ -256,8 +256,6 @@ description: 開發協調、Pipeline管理、任務執行、進度追蹤、Agent
|
|
|
256
256
|
| `kenaz_lsp_references` | 所有引用 / 修改影響範圍 | 修改前評估 blast radius |
|
|
257
257
|
| `kenaz_lsp_definition` | 定義位置 | 跳到宣告處 |
|
|
258
258
|
| `kenaz_lsp_symbols` | 檔案內 symbol 列表 | 了解檔案結構 |
|
|
259
|
-
| `synapse_recall` | 歷史 rune / 已知風險 | 修改前召回 |
|
|
260
|
-
| `synapse_status` | 記憶系統狀態 | 任務開始前檢查 |
|
|
261
259
|
|
|
262
260
|
文字與概念搜尋用 Grep / Glob。
|
|
263
261
|
|
|
@@ -281,8 +279,7 @@ description: 開發協調、Pipeline管理、任務執行、進度追蹤、Agent
|
|
|
281
279
|
**在開始任何開發任務前,建議執行:**
|
|
282
280
|
|
|
283
281
|
1. **獲取專案 Context**:
|
|
284
|
-
-
|
|
285
|
-
- 使用 `synapse_recall` 召回相關已知風險與決策
|
|
282
|
+
- 讀取任務卡與 `kenaz learn list` 的相關教訓
|
|
286
283
|
|
|
287
284
|
2. **搜索相關代碼**:
|
|
288
285
|
- 使用 Grep / Glob 找到相關實現
|
|
@@ -335,10 +332,10 @@ description: 開發協調、Pipeline管理、任務執行、進度追蹤、Agent
|
|
|
335
332
|
2. 重新讀取 Skill 文件
|
|
336
333
|
3. 如果問題持續,直接應用該 Agent 的核心職責邏輯
|
|
337
334
|
|
|
338
|
-
### LSP
|
|
335
|
+
### LSP 查詢失敗時
|
|
339
336
|
1. 繼續執行任務(降級為 Grep/Glob)
|
|
340
337
|
2. 確認 Dashboard 已啟動(依賴 Rust HTTP server on port 18080)
|
|
341
|
-
3. 使用 `
|
|
338
|
+
3. 使用 `kenaz_ping` 檢查服務狀態
|
|
342
339
|
|
|
343
340
|
---
|
|
344
341
|
|
package/lib/cli.js
CHANGED
|
@@ -234,8 +234,6 @@ async function doctor(p, flags, out, deps = {}) {
|
|
|
234
234
|
const r = await probe(process.env.KENAZ_HTTP_PORT || '18080');
|
|
235
235
|
let ver = ''; try { ver = JSON.parse(r.body).version; } catch (_) { /* health body is optional */ }
|
|
236
236
|
add('server reachable', r.ok ? 'ok' : 'FAIL', r.detail + (ver ? `, version ${ver}` : ''));
|
|
237
|
-
// TASK_2390: memory is frozen in the CLI build; 'disabled' is the expected state, never a failure.
|
|
238
|
-
if (r.ok) { let emb = ''; try { emb = JSON.parse(r.body).embedding || ''; } catch (_) { /* optional */ } add('memory', 'ok', emb === 'disabled' || !emb ? 'disabled (set KENAZ_MEMORY=on to enable)' : `on, embedding ${emb}`); }
|
|
239
237
|
|
|
240
238
|
const d = dash.detect(p.home, deps.dashboard);
|
|
241
239
|
if (!d.installed) add('dashboard', 'skip', 'no Kenaz Dashboard on this machine');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kenaz",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.11",
|
|
4
4
|
"description": "Install Kenaz (plugin, hooks, kenaz-server) for Claude Code and Codex: npx kenaz install",
|
|
5
5
|
"bin": {
|
|
6
6
|
"kenaz": "bin/kenaz.js"
|
|
@@ -20,11 +20,11 @@
|
|
|
20
20
|
"test": "node --test lib/cli.test.js"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@kenazai/server-darwin-arm64": "0.3.
|
|
24
|
-
"@kenazai/server-darwin-x64": "0.3.
|
|
25
|
-
"@kenazai/server-linux-arm64": "0.3.
|
|
26
|
-
"@kenazai/server-linux-x64": "0.3.
|
|
27
|
-
"@kenazai/server-win32-x64": "0.3.
|
|
23
|
+
"@kenazai/server-darwin-arm64": "0.3.11",
|
|
24
|
+
"@kenazai/server-darwin-x64": "0.3.11",
|
|
25
|
+
"@kenazai/server-linux-arm64": "0.3.11",
|
|
26
|
+
"@kenazai/server-linux-x64": "0.3.11",
|
|
27
|
+
"@kenazai/server-win32-x64": "0.3.11"
|
|
28
28
|
},
|
|
29
29
|
"license": "UNLICENSED",
|
|
30
30
|
"repository": {
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* handoff-snapshot-on-decision.js — PostToolUse hook for synapse_inscribe (TASK_1400)
|
|
4
|
-
*
|
|
5
|
-
* Fires after mcp__kenaz-dashboard__synapse_inscribe when the inscribed rune type
|
|
6
|
-
* is one of: decision, risk, ash. Insight/pattern/fix/summary inscribes do NOT
|
|
7
|
-
* fire (too noisy). Includes rune_id in the nudge.
|
|
8
|
-
* 5-minute dedup keyed on rune_id (or normalized content hash if rune_id absent).
|
|
9
|
-
*
|
|
10
|
-
* Matcher: mcp__kenaz-dashboard__synapse_inscribe
|
|
11
|
-
*/
|
|
12
|
-
'use strict';
|
|
13
|
-
try { require('./_dedupe-guard').guard(typeof __filename === 'string' ? __filename : null); } catch (_) { /* never block a hook on guard load failure (e.g. vm sandbox without __filename) */ }
|
|
14
|
-
const kenazProjectRoot = require('./_project-guard').guard();
|
|
15
|
-
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const { fenceSignal } = require('./_signal-fence');
|
|
19
|
-
|
|
20
|
-
const PROJECT_DIR = kenazProjectRoot;
|
|
21
|
-
const TELEMETRY_PATH = path.join(
|
|
22
|
-
PROJECT_DIR,
|
|
23
|
-
'.kenaz', 'specialist', 'handoff_nudges.jsonl'
|
|
24
|
-
);
|
|
25
|
-
const DEDUP_WINDOW_MS = 5 * 60 * 1000;
|
|
26
|
-
const DEDUP_KEY_PREFIX = 'inscribe:';
|
|
27
|
-
|
|
28
|
-
// Only these rune types trigger the nudge
|
|
29
|
-
const TRIGGER_TYPES = new Set(['decision', 'risk', 'ash']);
|
|
30
|
-
|
|
31
|
-
function readStdin() {
|
|
32
|
-
try {
|
|
33
|
-
return JSON.parse(fs.readFileSync(0, 'utf8').trim() || '{}');
|
|
34
|
-
} catch {
|
|
35
|
-
return {};
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function extractInscribeType(payload) {
|
|
40
|
-
// Type can be in tool_input or inferred from response
|
|
41
|
-
return (
|
|
42
|
-
payload?.tool_input?.type ||
|
|
43
|
-
payload?.toolInput?.type ||
|
|
44
|
-
null
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function extractRuneId(payload) {
|
|
49
|
-
// rune_id appears in the tool_response from synapse_inscribe
|
|
50
|
-
try {
|
|
51
|
-
const resp =
|
|
52
|
-
payload?.tool_response ||
|
|
53
|
-
payload?.tool_result;
|
|
54
|
-
if (!resp) return null;
|
|
55
|
-
|
|
56
|
-
// Attempt to parse text content as JSON
|
|
57
|
-
let text = '';
|
|
58
|
-
if (typeof resp === 'string') {
|
|
59
|
-
text = resp;
|
|
60
|
-
} else if (resp?.content && Array.isArray(resp.content)) {
|
|
61
|
-
text = resp.content.map(c => c?.text || '').join('');
|
|
62
|
-
} else if (typeof resp === 'object') {
|
|
63
|
-
text = JSON.stringify(resp);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Try JSON parse first
|
|
67
|
-
try {
|
|
68
|
-
const parsed = JSON.parse(text);
|
|
69
|
-
if (parsed?.rune_id) return parsed.rune_id;
|
|
70
|
-
if (parsed?.id) return parsed.id;
|
|
71
|
-
} catch { /* not JSON */ }
|
|
72
|
-
|
|
73
|
-
// Fallback: regex extract
|
|
74
|
-
const m = text.match(/"rune_id"\s*:\s*"([^"]+)"/);
|
|
75
|
-
if (m) return m[1];
|
|
76
|
-
const m2 = text.match(/"id"\s*:\s*"([^"]+)"/);
|
|
77
|
-
if (m2) return m2[1];
|
|
78
|
-
return null;
|
|
79
|
-
} catch {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function normalizeKey(runeId, type, content) {
|
|
85
|
-
if (runeId) return runeId;
|
|
86
|
-
// Fallback: hash of type+content snippet
|
|
87
|
-
const snippet = `${type}:${(content || '').substring(0, 40)}`;
|
|
88
|
-
return snippet.replace(/\s+/g, '_');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function isDeduped(key) {
|
|
92
|
-
try {
|
|
93
|
-
if (!fs.existsSync(TELEMETRY_PATH)) return false;
|
|
94
|
-
const lines = fs.readFileSync(TELEMETRY_PATH, 'utf8').split(/\r?\n/).filter(Boolean);
|
|
95
|
-
const cutoff = Date.now() - DEDUP_WINDOW_MS;
|
|
96
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
97
|
-
let entry;
|
|
98
|
-
try { entry = JSON.parse(lines[i]); } catch { continue; }
|
|
99
|
-
const ts = Number(entry.timestamp_ms || 0);
|
|
100
|
-
if (ts && ts < cutoff) break;
|
|
101
|
-
if (entry.dedup_key === key && !entry.suppressed) return true;
|
|
102
|
-
}
|
|
103
|
-
return false;
|
|
104
|
-
} catch {
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function appendTelemetry(record) {
|
|
110
|
-
try {
|
|
111
|
-
fs.mkdirSync(path.dirname(TELEMETRY_PATH), { recursive: true });
|
|
112
|
-
fs.appendFileSync(TELEMETRY_PATH, JSON.stringify(record) + '\n', 'utf8');
|
|
113
|
-
} catch { /* best-effort */ }
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Auto-invoke the snapshot CLI as a detached process so the hook returns fast
|
|
117
|
-
// and the snapshot write does not depend on the agent remembering. Errors are
|
|
118
|
-
// swallowed — never block the user turn.
|
|
119
|
-
function invokeHandoffSnapshot(trigger, milestone, sessionId, transcriptPath) {
|
|
120
|
-
try {
|
|
121
|
-
const { spawn } = require('child_process');
|
|
122
|
-
const cli = path.join(PROJECT_DIR, '.specialist', 'Ansuz', 'tools', 'ansuz-handoff-snapshot.js');
|
|
123
|
-
if (!fs.existsSync(cli)) return;
|
|
124
|
-
const args = [cli, `--trigger=${trigger}`];
|
|
125
|
-
if (milestone) args.push(`--last-milestone=${milestone}`);
|
|
126
|
-
if (sessionId) args.push(`--session-id=${sessionId}`);
|
|
127
|
-
// Scopes pending/unfinished to tasks this conversation touched.
|
|
128
|
-
if (transcriptPath) args.push(`--transcript-path=${transcriptPath}`);
|
|
129
|
-
const child = spawn('node', args, {
|
|
130
|
-
detached: true,
|
|
131
|
-
stdio: 'ignore',
|
|
132
|
-
cwd: PROJECT_DIR,
|
|
133
|
-
windowsHide: true,
|
|
134
|
-
});
|
|
135
|
-
child.unref();
|
|
136
|
-
} catch { /* best-effort */ }
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function main() {
|
|
140
|
-
const payload = readStdin();
|
|
141
|
-
const inscribeType = extractInscribeType(payload);
|
|
142
|
-
|
|
143
|
-
// Only fire for decision/risk/ash types
|
|
144
|
-
if (!inscribeType || !TRIGGER_TYPES.has(inscribeType)) {
|
|
145
|
-
process.exit(0);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Ansuz-only: see handoff-snapshot-on-commit.js.
|
|
149
|
-
if (!require('./_ansuz-conversation').isAnsuzConversation(payload.transcript_path, PROJECT_DIR)) {
|
|
150
|
-
process.exit(0);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const runeId = extractRuneId(payload);
|
|
154
|
-
const content = payload?.tool_input?.content || payload?.toolInput?.content || '';
|
|
155
|
-
const dedupKey = DEDUP_KEY_PREFIX + normalizeKey(runeId, inscribeType, content);
|
|
156
|
-
const suppressed = isDeduped(dedupKey);
|
|
157
|
-
|
|
158
|
-
appendTelemetry({
|
|
159
|
-
timestamp: new Date().toISOString(),
|
|
160
|
-
timestamp_ms: Date.now(),
|
|
161
|
-
hook: 'handoff-snapshot-on-decision',
|
|
162
|
-
dedup_key: dedupKey,
|
|
163
|
-
inscribe_type: inscribeType,
|
|
164
|
-
rune_id: runeId,
|
|
165
|
-
suppressed,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
if (suppressed) {
|
|
169
|
-
process.exit(0);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const runeRef = runeId ? ` (rune_id: ${runeId})` : '';
|
|
173
|
-
|
|
174
|
-
// Auto-write the handoff (system-level enforcement, instance-independent).
|
|
175
|
-
// TASK_1837: pass NO --last-milestone. The old value ('<type> rune inscribed')
|
|
176
|
-
// carried zero information and became the slot's current_topic, clobbering the
|
|
177
|
-
// real work topic (measured 2026-07-31: 5 of 8 orphan slots had it as topic —
|
|
178
|
-
// garbage inherited by the next session). With no milestone the snapshot CLI
|
|
179
|
-
// derives the topic from the latest commit subject: actual work content. The
|
|
180
|
-
// rune reference stays in the stdout nudge below, where it is useful.
|
|
181
|
-
invokeHandoffSnapshot('live_decision_point', null, payload.session_id, payload.transcript_path);
|
|
182
|
-
|
|
183
|
-
process.stdout.write(
|
|
184
|
-
'\n' + fenceSignal('handoff', `🔁 ${inscribeType} rune inscribed${runeRef}. Handoff auto-snapshot dispatched (live_decision_point).`) + '\n'
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
main();
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# synapse_maintain
|
|
2
|
-
|
|
3
|
-
Unified Synapse maintenance checkpoint for agent-facing health reviews.
|
|
4
|
-
|
|
5
|
-
## when_to_call
|
|
6
|
-
|
|
7
|
-
- Phase 2 research: run before planning when the task depends on current codebase health, stale cognition, or cleanup priority.
|
|
8
|
-
- Cognitive checkpoint: run after a risky implementation burst to see whether new gaps, conflicts, or stale runes appeared.
|
|
9
|
-
- Session end: run before handoff so the next agent receives a single dashboard instead of seven separate tool calls.
|
|
10
|
-
|
|
11
|
-
## tool
|
|
12
|
-
|
|
13
|
-
Call `synapse_maintain` with:
|
|
14
|
-
|
|
15
|
-
```json
|
|
16
|
-
{
|
|
17
|
-
"projectPath": "<worktree-or-project-path>",
|
|
18
|
-
"top_n": 5
|
|
19
|
-
}
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
## sections
|
|
23
|
-
|
|
24
|
-
### orphans
|
|
25
|
-
|
|
26
|
-
- Meaning: code symbols that look unreachable and may be removable.
|
|
27
|
-
- Read it as: high count + high removal impact means cleanup can simplify the graph quickly.
|
|
28
|
-
- Follow-up: inspect candidates before deletion; route handlers and command entry points can still be legitimate.
|
|
29
|
-
- Rune guidance: inscribe `fix` after a confirmed deletion or `risk` when a false-positive pattern is discovered.
|
|
30
|
-
|
|
31
|
-
### stale_runes
|
|
32
|
-
|
|
33
|
-
- Meaning: runes whose attached symbols drifted heavily or disappeared from the graph.
|
|
34
|
-
- Read it as: `auto_rejected` is urgent because memory was already invalidated; `needs_review` means knowledge may be stale.
|
|
35
|
-
- Follow-up: refresh or supersede outdated runes before using them in planning.
|
|
36
|
-
- Rune guidance: inscribe `decision` when you intentionally keep a flagged rune valid, or `fix` when you replace stale guidance.
|
|
37
|
-
|
|
38
|
-
### conflicts
|
|
39
|
-
|
|
40
|
-
- Meaning: contradictory runes on the same symbol or within the same sigil.
|
|
41
|
-
- Read it as: any `high` conflict blocks blind trust in recall results.
|
|
42
|
-
- Follow-up: verify, reject, or supersede conflicting runes before further automation.
|
|
43
|
-
- Rune guidance: inscribe `decision` with the resolved interpretation after adjudicating the conflict.
|
|
44
|
-
|
|
45
|
-
### gaps
|
|
46
|
-
|
|
47
|
-
- Meaning: heavily used symbols with no cognitive memory.
|
|
48
|
-
- Read it as: these are high-leverage documentation targets, especially when danger score is high.
|
|
49
|
-
- Follow-up: inspect the top hotspots and inscribe only non-obvious behavior, risks, or design choices.
|
|
50
|
-
- Rune guidance: inscribe `insight`, `risk`, or `summary` depending on the symbol and what was learned.
|
|
51
|
-
|
|
52
|
-
### pain_paths
|
|
53
|
-
|
|
54
|
-
- Meaning: long call chains crossing too many community boundaries.
|
|
55
|
-
- Read it as: repeated community crossings usually signal architecture drag rather than a single bug.
|
|
56
|
-
- Follow-up: use the chain preview to decide where a facade, shortcut, or boundary cleanup would reduce coupling.
|
|
57
|
-
- Rune guidance: inscribe `risk` when a path is fragile, or `decision` when you adopt a refactoring boundary.
|
|
58
|
-
|
|
59
|
-
### overdue_episodes
|
|
60
|
-
|
|
61
|
-
- Meaning: closed episodes with high consolidation scores that still need memory compression or review.
|
|
62
|
-
- Read it as: these sessions contain learnings that are likely to be lost unless consolidated.
|
|
63
|
-
- Follow-up: consolidate, summarize key facts, or extract repeatable lessons.
|
|
64
|
-
- Rune guidance: inscribe `ash` only after a stable cross-session lesson is clear.
|
|
65
|
-
|
|
66
|
-
### audit_summary
|
|
67
|
-
|
|
68
|
-
- Meaning: anomaly rule findings from the structural audit engine.
|
|
69
|
-
- Read it as: `high` findings are active breakage or architectural risk; `medium` usually deserves planned follow-up.
|
|
70
|
-
- Follow-up: treat the file paths as the first investigation slice, not as automatic proof.
|
|
71
|
-
- Rune guidance: inscribe `fix` after resolving a confirmed audit issue, or `risk` if a known anomaly must remain temporarily.
|
|
72
|
-
|
|
73
|
-
## interpretation
|
|
74
|
-
|
|
75
|
-
- `severity=high` means act before more feature work if the section affects active scope.
|
|
76
|
-
- `severity=medium` means plan remediation or inscribe context before handing off.
|
|
77
|
-
- `severity=low` means no urgent action, but keep the diagnosis in the task summary if it influenced decisions.
|
|
78
|
-
- If a section is `degraded`, treat the diagnosis as tool coverage failure, not proof that the codebase is healthy.
|
|
79
|
-
|
|
80
|
-
## output_discipline
|
|
81
|
-
|
|
82
|
-
- Do not summarize the dashboard as "all green" unless every section is both `status=ok` and `severity=low`.
|
|
83
|
-
- If `items` is empty but `status=degraded`, report the error explicitly; degraded sections are not passes.
|
|
84
|
-
- Prefer one follow-up rune with high confidence over bulk inscription from raw dashboard findings.
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
// TASK_2104: shared, dependency-free Alaya relevance gate — extracted out
|
|
2
|
-
// of prompt.js so task-api.js's echoAlayaLessons (TASK_1992) applies the
|
|
3
|
-
// same TASK_2101 cosine-floor + stack-match gate instead of a hand-copied
|
|
4
|
-
// near-duplicate that had neither. Zero deps (no memory-loader/core-resolve)
|
|
5
|
-
// so it still require()s from a sub-project with no .formation/ checkout —
|
|
6
|
-
// callers must treat a require() failure as "gate unavailable" and degrade
|
|
7
|
-
// gracefully (see task-api.js echoAlayaLessons).
|
|
8
|
-
// Shared by: prompt.js appendAlayaLessons(), task-api.js echoAlayaLessons().
|
|
9
|
-
|
|
10
|
-
'use strict';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Build the cross-project context query shared by Cortex and Alaya lookups:
|
|
14
|
-
* scope_paths basenames (extension stripped) + up to 4 non-stop-word
|
|
15
|
-
* keywords from the task title.
|
|
16
|
-
*
|
|
17
|
-
* @param {string|object} taskData - task JSON string or parsed object
|
|
18
|
-
* @returns {string|null} the query string, or null when nothing to query
|
|
19
|
-
*/
|
|
20
|
-
function buildTaskContextQuery(taskData) {
|
|
21
|
-
let scopePaths = [];
|
|
22
|
-
let taskTitle = '';
|
|
23
|
-
try {
|
|
24
|
-
const parsed = typeof taskData === 'string' ? JSON.parse(taskData) : taskData;
|
|
25
|
-
if (parsed) {
|
|
26
|
-
scopePaths = parsed.scope_paths || [];
|
|
27
|
-
taskTitle = parsed.title || '';
|
|
28
|
-
}
|
|
29
|
-
} catch { /* taskData not JSON, skip */ }
|
|
30
|
-
|
|
31
|
-
if (scopePaths.length === 0 && !taskTitle) return null;
|
|
32
|
-
|
|
33
|
-
// Build query from scope_paths file names + task title keywords
|
|
34
|
-
const queryParts = [];
|
|
35
|
-
for (const sp of scopePaths) {
|
|
36
|
-
const parts = String(sp).replace(/\\/g, '/').split('/');
|
|
37
|
-
const last = parts[parts.length - 1].replace(/\.[^.]+$/, '');
|
|
38
|
-
if (last && last.length > 2) queryParts.push(last);
|
|
39
|
-
}
|
|
40
|
-
if (taskTitle) {
|
|
41
|
-
const stopWords = new Set(['the','a','an','is','are','to','of','and','in','for','on','at','by','with','fix','task','add','update']);
|
|
42
|
-
const words = taskTitle.replace(/[^a-zA-Z0-9_\s]/g, ' ').split(/\s+/).filter(w => w.length > 2 && !stopWords.has(w.toLowerCase()));
|
|
43
|
-
queryParts.push(...words.slice(0, 4));
|
|
44
|
-
}
|
|
45
|
-
if (queryParts.length === 0) return null;
|
|
46
|
-
|
|
47
|
-
return [...new Set(queryParts)].join(' ');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// TASK_2101: absolute relevance gate for Alaya lesson injection.
|
|
51
|
-
//
|
|
52
|
-
// Why: alaya_recall's only pre-2101 score is RRF rank fusion (~0.016-0.032),
|
|
53
|
-
// which carries no absolute relevance — lexical overlap on a single common
|
|
54
|
-
// word ("checkpoint", "gate") was enough to rank a bija first regardless of
|
|
55
|
-
// domain. Every bija also had source_count=1 (hub distiller not implemented),
|
|
56
|
-
// so nothing self-corrects via aggregation either. The fix is two gates that
|
|
57
|
-
// must BOTH pass before a bija is even a candidate for injection:
|
|
58
|
-
// 1. cosine floor — the RAW semantic cosine (never the RRF score) must
|
|
59
|
-
// clear ALAYA_MIN_COSINE_DEFAULT. A bija the semantic arm never scored
|
|
60
|
-
// (cosine=null: fts-only hit, or bija has no embedding yet) fails the
|
|
61
|
-
// floor by construction — an unverifiable relevance is not a floor pass.
|
|
62
|
-
// 2. stack match — the bija's coarse tech stack (source_symbol's file
|
|
63
|
-
// extension/dir, plus tags) must overlap the task's (scope_paths'
|
|
64
|
-
// extensions/dirs), UNLESS the cosine is very high
|
|
65
|
-
// (>= ALAYA_STACK_OVERRIDE_COSINE) — a strong enough semantic match can
|
|
66
|
-
// override a stack that a coarse heuristic failed to resolve.
|
|
67
|
-
//
|
|
68
|
-
// Calibration (R4, measured 2026-09-28 against the live backend, 5 real
|
|
69
|
-
// dispatch queries x top-10 semantic hits = 50 bijas, hand-labelled):
|
|
70
|
-
// - ArcticEmbedXs cosines are compressed high: clearly irrelevant noise
|
|
71
|
-
// (payment/CRM/UI-colour/backtest seeds on Rust and JS infra cards)
|
|
72
|
-
// scored 0.656-0.697; on-topic bijas scored 0.709-0.810. 0.45 (the
|
|
73
|
-
// earlier placeholder) passed all 50, i.e. it was not a filter at all.
|
|
74
|
-
// - Confusion at 0.45: 20 on-topic kept, 30 noise kept. At 0.70: 20 kept,
|
|
75
|
-
// 0 noise. The margin is only ~0.012, so treat 0.70 as fitted to a small
|
|
76
|
-
// sample and re-measure when the bija population changes.
|
|
77
|
-
// - The stack half of the gate currently has no real signal: every live
|
|
78
|
-
// bija has source_symbol='' (hub predates the pass-through) and `tags`
|
|
79
|
-
// are content word-bags, not stack labels. In practice a bija on a task
|
|
80
|
-
// with scope_paths passes only at >= ALAYA_STACK_OVERRIDE_COSINE (0.8),
|
|
81
|
-
// so today's effective floor for scoped tasks is 0.8 and 0.70 applies to
|
|
82
|
-
// unscoped tasks. A3 check (dispatch --dry-run on TASK_2096/2097/2100):
|
|
83
|
-
// 10/10 candidates dropped on each; no noise injected.
|
|
84
|
-
// - Also seen: the on-topic hits were near-duplicates (8-9 copies of one
|
|
85
|
-
// "community detection void" / "KuzuDB graph broken" observation) and
|
|
86
|
-
// months stale (the conditions they describe are fixed). Relevance is
|
|
87
|
-
// not the only gap: dedup (distiller) and retraction are next.
|
|
88
|
-
const ALAYA_MIN_COSINE_DEFAULT = 0.70;
|
|
89
|
-
const ALAYA_STACK_OVERRIDE_COSINE = 0.8;
|
|
90
|
-
// Candidate pool requested from alaya_recall — wider than the render cap (3)
|
|
91
|
-
// so post-filtering by the two gates still has real candidates to keep.
|
|
92
|
-
const ALAYA_CANDIDATE_POOL = 10;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Coarse stack tokens (lowercase file extensions + 1-2 top path segments)
|
|
96
|
-
* from a list of paths. Shared by the task side (scope_paths) and the bija
|
|
97
|
-
* side (source_symbol's `{file_path}:{symbol}:{kind}` shape, split on ':').
|
|
98
|
-
*
|
|
99
|
-
* @param {string} normPath - a single '/'-normalised path
|
|
100
|
-
* @param {Set<string>} tokens - mutated in place
|
|
101
|
-
*/
|
|
102
|
-
function addStackTokens(normPath, tokens) {
|
|
103
|
-
if (!normPath) return;
|
|
104
|
-
const extMatch = normPath.match(/\.([a-zA-Z0-9]+)$/);
|
|
105
|
-
if (extMatch) tokens.add(extMatch[1].toLowerCase());
|
|
106
|
-
const parts = normPath.split('/').filter(Boolean);
|
|
107
|
-
if (parts.length > 0) tokens.add(parts[0].toLowerCase());
|
|
108
|
-
if (parts.length > 1) tokens.add(`${parts[0]}/${parts[1]}`.toLowerCase());
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Derive the task's coarse stack from its scope_paths (file extensions +
|
|
113
|
-
* top-level dirs, e.g. {'rs', 'js', 'dashboard', '.formation/tools'}).
|
|
114
|
-
* @param {string[]} scopePaths
|
|
115
|
-
* @returns {Set<string>}
|
|
116
|
-
*/
|
|
117
|
-
function deriveTaskStack(scopePaths) {
|
|
118
|
-
const tokens = new Set();
|
|
119
|
-
for (const sp of scopePaths || []) {
|
|
120
|
-
addStackTokens(String(sp).replace(/\\/g, '/'), tokens);
|
|
121
|
-
}
|
|
122
|
-
return tokens;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Derive a bija's coarse stack from its tags (comma-separated string, as
|
|
127
|
-
* returned by alaya_recall) and its source_symbol (TASK_2101 — hub
|
|
128
|
-
* pass-through of the originating rune's symbol_stable_id; empty for hubs
|
|
129
|
-
* that predate the field, or project-level seeds with no single symbol).
|
|
130
|
-
* @param {object} bija
|
|
131
|
-
* @returns {Set<string>}
|
|
132
|
-
*/
|
|
133
|
-
function deriveBijaStack(bija) {
|
|
134
|
-
const tokens = new Set();
|
|
135
|
-
for (const t of String(bija.tags || '').split(',')) {
|
|
136
|
-
const tag = t.trim().toLowerCase();
|
|
137
|
-
if (tag) tokens.add(tag);
|
|
138
|
-
}
|
|
139
|
-
const sourceSymbol = String(bija.source_symbol || '');
|
|
140
|
-
if (sourceSymbol) {
|
|
141
|
-
const filePath = sourceSymbol.split(':')[0].replace(/\\/g, '/');
|
|
142
|
-
addStackTokens(filePath, tokens);
|
|
143
|
-
}
|
|
144
|
-
return tokens;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Whether a bija passes the TASK_2101 relevance gate: cosine floor AND
|
|
149
|
-
* (stack overlap OR a cosine high enough to override a stack miss).
|
|
150
|
-
* A task with no derivable stack (empty scope_paths) cannot be judged on
|
|
151
|
-
* stack at all, so the stack half of the gate is skipped — the floor alone
|
|
152
|
-
* still applies.
|
|
153
|
-
*
|
|
154
|
-
* @param {object} bija - one alaya_recall result (cosine, tags, source_symbol, ...)
|
|
155
|
-
* @param {Set<string>} taskStack - from deriveTaskStack
|
|
156
|
-
* @param {number} floor - ALAYA_MIN_COSINE_DEFAULT (or an override)
|
|
157
|
-
* @returns {boolean}
|
|
158
|
-
*/
|
|
159
|
-
function passesAlayaRelevanceGate(bija, taskStack, floor) {
|
|
160
|
-
const cosine = typeof bija.cosine === 'number' ? bija.cosine : null;
|
|
161
|
-
if (cosine === null || cosine < floor) return false;
|
|
162
|
-
if (taskStack.size === 0) return true; // no task-side stack signal to judge against
|
|
163
|
-
if (cosine >= ALAYA_STACK_OVERRIDE_COSINE) return true;
|
|
164
|
-
const bijaStack = deriveBijaStack(bija);
|
|
165
|
-
for (const tok of bijaStack) {
|
|
166
|
-
if (taskStack.has(tok)) return true;
|
|
167
|
-
}
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Dedup a list of alaya_recall bijas: key = id, else first 100 chars of
|
|
173
|
-
* content. Shared by prompt.js's appendAlayaLessons and task-api.js's
|
|
174
|
-
* echoAlayaLessons — both queried the same hub, so the same duplicate hubs
|
|
175
|
-
* (near-identical seeds re-inscribed by different sources) can show up.
|
|
176
|
-
*
|
|
177
|
-
* @param {object[]} bijas
|
|
178
|
-
* @returns {object[]}
|
|
179
|
-
*/
|
|
180
|
-
function dedupBijas(bijas) {
|
|
181
|
-
const seen = new Set();
|
|
182
|
-
return (bijas || []).filter(b => {
|
|
183
|
-
const key = b.id || (b.content || '').slice(0, 100);
|
|
184
|
-
if (!key || seen.has(key)) return false;
|
|
185
|
-
seen.add(key); return true;
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
module.exports = {
|
|
190
|
-
buildTaskContextQuery,
|
|
191
|
-
deriveTaskStack,
|
|
192
|
-
deriveBijaStack,
|
|
193
|
-
passesAlayaRelevanceGate,
|
|
194
|
-
dedupBijas,
|
|
195
|
-
ALAYA_MIN_COSINE_DEFAULT,
|
|
196
|
-
ALAYA_STACK_OVERRIDE_COSINE,
|
|
197
|
-
ALAYA_CANDIDATE_POOL,
|
|
198
|
-
};
|