kenaz 0.3.9 → 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/commands/Ansuz.md +12 -16
- package/core/.claude/hooks/memory-guard.js +13 -0
- 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 +259 -0
- package/core/.formation/tools/kenaz-stats.test.js +180 -0
- 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/prompt.js +14 -0
- 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/RULES.md +6 -0
- 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/__tests__/task-2409-lessons.test.js +95 -0
- package/core/.specialist/tools/ansuz-boot-fast.js +1 -0
- package/core/.specialist/tools/lessons.js +234 -0
- 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/commands/Ansuz.md +12 -16
- 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 +13 -3
- 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
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
/**
|
|
3
|
-
* TASK_2038 — post-complete MCP/HTTP payloads always carry an explicit
|
|
4
|
-
* projectPath, instead of omitting it for a core KenazAI run.
|
|
5
|
-
*
|
|
6
|
-
* Prior bug: every MCP/HTTP payload in the post-complete module family spread
|
|
7
|
-
* `...(projectPath ? { projectPath } : {})`. For a core run, projectPath is
|
|
8
|
-
* undefined (post-complete.js only sets it from --project-path, ~L4016), so
|
|
9
|
-
* the key was OMITTED entirely — the backend then fell back to whatever
|
|
10
|
-
* project the Dashboard currently has selected, not this run's actual
|
|
11
|
-
* project. Local, file-based reads in the same modules already resolved
|
|
12
|
-
* `projectPath || ROOT` correctly; only the MCP/HTTP payloads diverged.
|
|
13
|
-
* Measured live (see TASK_2038 dispatch background / rune_ec9ec6c1): 4 of 5
|
|
14
|
-
* recent KEEP/REVERT verdicts were scored against a different repo.
|
|
15
|
-
*
|
|
16
|
-
* Fix: resolveMcpProjectPath(projectPath) in health.js, reused by
|
|
17
|
-
* outcome.js/cognitive.js/post-complete.js, always resolves to
|
|
18
|
-
* `projectPath || ROOT` and is sent explicitly instead of spread.
|
|
19
|
-
*
|
|
20
|
-
* Negative control: with health.js reverted to its pre-fix (HEAD) content,
|
|
21
|
-
* the "measureHealthScoreSync(undefined) sends an explicit projectPath"
|
|
22
|
-
* assertion below fails — the stub server receives an arguments object with
|
|
23
|
-
* no projectPath key at all. See this task's report for the captured output.
|
|
24
|
-
*
|
|
25
|
-
* Run: node --test .formation/tools/post-complete/task2038.test.js
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
const test = require('node:test');
|
|
29
|
-
const assert = require('node:assert/strict');
|
|
30
|
-
const fs = require('fs');
|
|
31
|
-
const path = require('path');
|
|
32
|
-
const http = require('node:http');
|
|
33
|
-
const vm = require('node:vm');
|
|
34
|
-
const Module = require('node:module');
|
|
35
|
-
|
|
36
|
-
const { ROOT } = require('../utils');
|
|
37
|
-
const {
|
|
38
|
-
resolveMcpProjectPath,
|
|
39
|
-
resolveMcpDashboardPort,
|
|
40
|
-
triggerGuardianHealthCheck,
|
|
41
|
-
} = require('./health');
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* measureHealthScoreSync spawns a FRESH node process (spawnSync) to make its
|
|
45
|
-
* HTTP call, which cannot reach a stub server created in this same test
|
|
46
|
-
* process on this sandboxed host (verified: the child process's connection
|
|
47
|
-
* attempt to 127.0.0.1 hangs and times out even with sandboxing disabled —
|
|
48
|
-
* an environment limitation, not a defect in the fix). So instead of a real
|
|
49
|
-
* network round-trip, this loads health.js in an isolated vm context with
|
|
50
|
-
* child_process.spawnSync stubbed, and inspects the EXACT inline script text
|
|
51
|
-
* measureHealthScoreSync builds — the same level of proof (does the outgoing
|
|
52
|
-
* MCP arguments object carry an explicit, correctly-resolved projectPath?)
|
|
53
|
-
* without depending on loopback networking working inside a spawned process.
|
|
54
|
-
*/
|
|
55
|
-
function loadHealthWithSpawnSyncStub(spawnSyncStub) {
|
|
56
|
-
const sourcePath = require.resolve('./health');
|
|
57
|
-
const src = fs.readFileSync(sourcePath, 'utf8');
|
|
58
|
-
const localRequire = Module.createRequire(sourcePath);
|
|
59
|
-
const mod = { exports: {} };
|
|
60
|
-
const ctx = {
|
|
61
|
-
module: mod,
|
|
62
|
-
exports: mod.exports,
|
|
63
|
-
require: (name) => (name === 'child_process' ? { spawnSync: spawnSyncStub } : localRequire(name)),
|
|
64
|
-
console,
|
|
65
|
-
process,
|
|
66
|
-
Buffer,
|
|
67
|
-
__dirname: path.dirname(sourcePath),
|
|
68
|
-
__filename: sourcePath,
|
|
69
|
-
};
|
|
70
|
-
vm.createContext(ctx);
|
|
71
|
-
vm.runInContext(src, ctx, { filename: sourcePath });
|
|
72
|
-
return mod.exports;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function stubServer(t, handler) {
|
|
76
|
-
const s = http.createServer(handler);
|
|
77
|
-
await new Promise((resolve) => s.listen(0, '127.0.0.1', resolve));
|
|
78
|
-
t.after(() => new Promise((resolve) => { s.closeAllConnections(); s.close(resolve); }));
|
|
79
|
-
return s.address().port;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function withEnv(key, value, fn) {
|
|
83
|
-
const had = Object.prototype.hasOwnProperty.call(process.env, key);
|
|
84
|
-
const prev = process.env[key];
|
|
85
|
-
process.env[key] = value;
|
|
86
|
-
try {
|
|
87
|
-
return await fn();
|
|
88
|
-
} finally {
|
|
89
|
-
if (had) process.env[key] = prev; else delete process.env[key];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function writeSynapseHealthResponse(res) {
|
|
94
|
-
const body = JSON.stringify({
|
|
95
|
-
jsonrpc: '2.0',
|
|
96
|
-
id: 'hc-sync',
|
|
97
|
-
result: { content: [{ type: 'text', text: JSON.stringify({ overall_score: 90, grade: 'A', metrics: {} }) }] },
|
|
98
|
-
});
|
|
99
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
100
|
-
res.end(body);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function readJsonBody(req) {
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
let raw = '';
|
|
106
|
-
req.on('data', (c) => { raw += c; });
|
|
107
|
-
req.on('end', () => {
|
|
108
|
-
try { resolve(JSON.parse(raw)); } catch (e) { reject(e); }
|
|
109
|
-
});
|
|
110
|
-
req.on('error', reject);
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ── resolveMcpProjectPath ───────────────────────────────────────────────
|
|
115
|
-
|
|
116
|
-
test('resolveMcpProjectPath falls back to ROOT for every falsy input, passes through an explicit path', () => {
|
|
117
|
-
assert.equal(resolveMcpProjectPath(undefined), ROOT);
|
|
118
|
-
assert.equal(resolveMcpProjectPath(null), ROOT);
|
|
119
|
-
assert.equal(resolveMcpProjectPath(''), ROOT);
|
|
120
|
-
assert.equal(resolveMcpProjectPath('/fixture/project'), '/fixture/project');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test('resolveMcpDashboardPort defaults to 18080, honors KENAZ_DASHBOARD_PORT override', async () => {
|
|
124
|
-
assert.equal(resolveMcpDashboardPort(), 18080);
|
|
125
|
-
await withEnv('KENAZ_DASHBOARD_PORT', '54321', () => {
|
|
126
|
-
assert.equal(resolveMcpDashboardPort(), 54321);
|
|
127
|
-
});
|
|
128
|
-
await withEnv('KENAZ_DASHBOARD_PORT', 'not-a-number', () => {
|
|
129
|
-
assert.equal(resolveMcpDashboardPort(), 18080);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// ── measureHealthScoreSync (TASK_1861 KENAZ_DASHBOARD_PORT seam) ────────
|
|
134
|
-
|
|
135
|
-
function extractSpawnedScript(capturedArgs) {
|
|
136
|
-
assert.ok(capturedArgs, 'spawnSync must have been called');
|
|
137
|
-
assert.equal(capturedArgs[1][0], '-e');
|
|
138
|
-
return capturedArgs[1][1];
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
test('measureHealthScoreSync(undefined) builds an MCP script that sends an explicit projectPath === ROOT (negative control: fails on pre-fix code — see report)', () => {
|
|
142
|
-
let capturedArgs = null;
|
|
143
|
-
const health = loadHealthWithSpawnSyncStub((...args) => {
|
|
144
|
-
capturedArgs = args;
|
|
145
|
-
return { stdout: JSON.stringify({ score: 90, grade: 'A' }), stderr: '' };
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
const result = health.measureHealthScoreSync(undefined);
|
|
149
|
-
// Cross-realm result (JSON.parse ran inside the vm context) -- compare
|
|
150
|
-
// fields directly rather than deepEqual, which fails on prototype identity
|
|
151
|
-
// across realms even when the data matches.
|
|
152
|
-
assert.equal(result.score, 90);
|
|
153
|
-
assert.equal(result.grade, 'A');
|
|
154
|
-
|
|
155
|
-
const script = extractSpawnedScript(capturedArgs);
|
|
156
|
-
const ppMatch = script.match(/const pp = (.+);/);
|
|
157
|
-
assert.ok(ppMatch, 'script must define `pp`');
|
|
158
|
-
const ppValue = JSON.parse(ppMatch[1]);
|
|
159
|
-
assert.equal(ppValue, ROOT,
|
|
160
|
-
'measureHealthScoreSync(undefined) must resolve pp to ROOT, not omit it (pre-fix code sets pp = null here)');
|
|
161
|
-
assert.match(script, /arguments: \{ mode: 'score', save_snapshot: true, projectPath: pp \}/,
|
|
162
|
-
'the MCP arguments object must send projectPath unconditionally, not spread it behind a truthiness check');
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test('measureHealthScoreSync(explicitPath) forwards that path unchanged into the MCP script', () => {
|
|
166
|
-
let capturedArgs = null;
|
|
167
|
-
const health = loadHealthWithSpawnSyncStub((...args) => {
|
|
168
|
-
capturedArgs = args;
|
|
169
|
-
return { stdout: JSON.stringify({ score: 1, grade: 'F' }), stderr: '' };
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
health.measureHealthScoreSync('/fixture/sub-project');
|
|
173
|
-
|
|
174
|
-
const script = extractSpawnedScript(capturedArgs);
|
|
175
|
-
const ppMatch = script.match(/const pp = (.+);/);
|
|
176
|
-
assert.equal(JSON.parse(ppMatch[1]), '/fixture/sub-project');
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
test('measureHealthScoreSync honors KENAZ_DASHBOARD_PORT in the spawned script (TASK_1861 seam, unaffected by this fix)', async () => {
|
|
180
|
-
let capturedArgs = null;
|
|
181
|
-
const health = loadHealthWithSpawnSyncStub((...args) => {
|
|
182
|
-
capturedArgs = args;
|
|
183
|
-
return { stdout: '', stderr: '' };
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
await withEnv('KENAZ_DASHBOARD_PORT', '54329', () => {
|
|
187
|
-
health.measureHealthScoreSync(undefined);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const script = extractSpawnedScript(capturedArgs);
|
|
191
|
-
assert.match(script, /port: 54329,/);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ── triggerGuardianHealthCheck (TASK_2038: must honor the same port seam) ──
|
|
195
|
-
|
|
196
|
-
test('triggerGuardianHealthCheck(undefined) sends explicit projectPath === ROOT and honors KENAZ_DASHBOARD_PORT', async (t) => {
|
|
197
|
-
let resolveReceived;
|
|
198
|
-
const receivedPromise = new Promise((resolve) => { resolveReceived = resolve; });
|
|
199
|
-
const port = await stubServer(t, async (req, res) => {
|
|
200
|
-
const parsed = await readJsonBody(req);
|
|
201
|
-
resolveReceived(parsed.params.arguments);
|
|
202
|
-
writeSynapseHealthResponse(res);
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
await withEnv('KENAZ_DASHBOARD_PORT', String(port), async () => {
|
|
206
|
-
triggerGuardianHealthCheck(undefined);
|
|
207
|
-
const args = await Promise.race([
|
|
208
|
-
receivedPromise,
|
|
209
|
-
new Promise((_, reject) => setTimeout(
|
|
210
|
-
() => reject(new Error('stub server on KENAZ_DASHBOARD_PORT never received a request — triggerGuardianHealthCheck may still hardcode port 18080')),
|
|
211
|
-
5000,
|
|
212
|
-
)),
|
|
213
|
-
]);
|
|
214
|
-
assert.ok(Object.prototype.hasOwnProperty.call(args, 'projectPath'),
|
|
215
|
-
'synapse_health arguments must carry a projectPath key, not omit it');
|
|
216
|
-
assert.equal(args.projectPath, ROOT);
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// ── source assertion: the omitting spread pattern is gone everywhere ────
|
|
221
|
-
|
|
222
|
-
test('the projectPath-omitting spread pattern no longer appears in post-complete/*.js or post-complete.js', () => {
|
|
223
|
-
const files = [
|
|
224
|
-
path.join(__dirname, 'health.js'),
|
|
225
|
-
path.join(__dirname, 'outcome.js'),
|
|
226
|
-
path.join(__dirname, 'cognitive.js'),
|
|
227
|
-
path.join(__dirname, '..', 'post-complete.js'),
|
|
228
|
-
];
|
|
229
|
-
// Matches the exact prior bug shape: `...(projectPath ? { projectPath } : {})`
|
|
230
|
-
// (with or without spaces inside the braces).
|
|
231
|
-
const pattern = /\.\.\.\(\s*projectPath\s*\?\s*\{\s*projectPath\s*\}\s*:\s*\{\s*\}\s*\)/;
|
|
232
|
-
|
|
233
|
-
for (const file of files) {
|
|
234
|
-
assert.ok(fs.existsSync(file), `expected file to exist: ${file}`);
|
|
235
|
-
const src = fs.readFileSync(file, 'utf8');
|
|
236
|
-
// Drop lines that are JSDoc/comment prose (this test file's own fix
|
|
237
|
-
// description quotes the old pattern verbatim inside a `/** ... */`
|
|
238
|
-
// block in health.js) so only executable code is checked.
|
|
239
|
-
const codeOnly = src
|
|
240
|
-
.split('\n')
|
|
241
|
-
.filter((line) => !/^\s*(\*|\/\/|\/\*)/.test(line))
|
|
242
|
-
.join('\n');
|
|
243
|
-
assert.doesNotMatch(codeOnly, pattern,
|
|
244
|
-
`${file} must not conditionally omit projectPath from an MCP/HTTP payload`);
|
|
245
|
-
}
|
|
246
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
// TASK_2341: the memory-only health score must never revert/block a task, and graph-era snapshots must
|
|
3
|
-
// never be compared with memory-era ones.
|
|
4
|
-
const { test } = require('node:test');
|
|
5
|
-
const assert = require('node:assert/strict');
|
|
6
|
-
const fs = require('fs');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const health = require('./health');
|
|
10
|
-
|
|
11
|
-
const GRAPH_ERA_BASELINE = { score: 85, grade: 'B', timestamp: '2026-01-01T00:00:00.000Z', sub_metrics: { degree_entropy: 90, orphan_ratio: 80 } };
|
|
12
|
-
const MEMORY_POST = { score: 40, grade: 'F', score_version: 'memory-v1', sub_metrics: { rune_vitality: 40 }, timestamp: '2026-10-07T00:00:00.000Z' };
|
|
13
|
-
|
|
14
|
-
test('AC: graph-era baseline 85, new memory score 40 -> verdict is not REVERT, no delta is attributed', () => {
|
|
15
|
-
const v = health.computeHealthVerdict(GRAPH_ERA_BASELINE, MEMORY_POST, -2, { projectPath: os.tmpdir() });
|
|
16
|
-
assert.notEqual(v.verdict, 'REVERT');
|
|
17
|
-
assert.equal(v.verdict, 'RECORDED');
|
|
18
|
-
assert.equal(v.delta, null, 'graph-era baseline is incomparable with memory-v1');
|
|
19
|
-
assert.deepEqual(v.subMetricRegressions, []);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('even a same-version drop (85 -> 40) is only recorded, never REVERT / SUBMETRIC_REGRESSION', () => {
|
|
23
|
-
const pre = { score: 85, score_version: 'memory-v1', timestamp: '2026-10-06T00:00:00.000Z', sub_metrics: { degree_entropy: 90 } };
|
|
24
|
-
const v = health.computeHealthVerdict(pre, MEMORY_POST, -2);
|
|
25
|
-
assert.equal(v.verdict, 'RECORDED');
|
|
26
|
-
assert.equal(v.delta, -45);
|
|
27
|
-
assert.deepEqual(v.subMetricRegressions, []);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test('numeric baseline (legacy call shape) is treated as graph-era: no revert, no delta', () => {
|
|
31
|
-
const v = health.computeHealthVerdict(85, MEMORY_POST, -2);
|
|
32
|
-
assert.equal(v.verdict, 'RECORDED');
|
|
33
|
-
assert.equal(v.delta, null);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('no automatic revert / merge-block path remains for health', () => {
|
|
37
|
-
const root = path.resolve(__dirname, '..', '..', '..');
|
|
38
|
-
// comment lines (history notes) are not code paths
|
|
39
|
-
const isComment = l => /^\s*(\/\/|\*|\/\*)/.test(l);
|
|
40
|
-
const read = rel => fs.readFileSync(path.join(root, rel), 'utf8').split('\n').filter(l => !isComment(l)).join('\n');
|
|
41
|
-
const pc = read('.formation/tools/post-complete.js');
|
|
42
|
-
assert.doesNotMatch(pc, /HEIMDALL_POSTMERGE_REVERT|revert -m|verdict === 'REVERT'/);
|
|
43
|
-
const gm = read('.formation/tools/post-complete/git-merge.js');
|
|
44
|
-
assert.doesNotMatch(gm, /healthGateBlock|blockedReason = 'health_gate'|measureHealthScoreSync/);
|
|
45
|
-
const h = read('.formation/tools/post-complete/health.js');
|
|
46
|
-
assert.doesNotMatch(h, /verdict = 'REVERT'|SUBMETRIC_REGRESSION'|verdict: 'REVERT'/);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('snapshots are tagged memory-v1 and graph-era snapshots are excluded from the trend', () => {
|
|
50
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'task2341-'));
|
|
51
|
-
try {
|
|
52
|
-
const dir = path.join(tmp, '.kenaz', 'health_snapshots');
|
|
53
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
54
|
-
fs.writeFileSync(path.join(dir, 'TASK_1_after.json'), JSON.stringify({ score: 90, timestamp: new Date(Date.now() - 5000).toISOString(), sub_metrics: {} }));
|
|
55
|
-
const payload = health.saveTaskHealthSnapshot('TASK_2', 'after', { score: 40, grade: 'F', sub_metrics: {} }, tmp);
|
|
56
|
-
assert.equal(payload.score_version, 'memory-v1');
|
|
57
|
-
assert.equal(payload.delta_from_previous, null, 'graph-era prior (score 90) must not produce a delta');
|
|
58
|
-
const payload2 = health.saveTaskHealthSnapshot('TASK_3', 'after', { score: 43, grade: 'F', sub_metrics: {} }, tmp);
|
|
59
|
-
assert.equal(payload2.delta_from_previous, 3);
|
|
60
|
-
assert.deepEqual(health.loadRecentAfterSnapshots(tmp).map(s => s.taskId).sort(), ['TASK_2', 'TASK_3']);
|
|
61
|
-
} finally { fs.rmSync(tmp, { recursive: true, force: true }); }
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('writeVerdictToTask note carries delta n/a for an incomparable baseline (source check)', () => {
|
|
65
|
-
const src = fs.readFileSync(path.join(__dirname, 'health.js'), 'utf8');
|
|
66
|
-
assert.match(src, /deltaText = typeof delta === 'number' \? delta\.toFixed\(2\) : 'n\/a'/);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('cognitive.js no longer exports callers of the removed link-cognitive route and still loads', () => {
|
|
70
|
-
const cog = require('./cognitive');
|
|
71
|
-
assert.equal(cog.linkCognitiveEdgeHttp, undefined);
|
|
72
|
-
assert.equal(cog.performCognitiveAttribution, undefined);
|
|
73
|
-
for (const k of ['callHippocampusMcpSync', 'triggerPassiveRuneExtraction', 'collectThinkingTags']) assert.equal(typeof cog[k], 'function', k);
|
|
74
|
-
});
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
# .rule/general/cortex-tools.yaml
|
|
2
|
-
# Cortex 跨專案智能規則 — Agent 何時、如何使用 Cortex MCP 工具
|
|
3
|
-
# 優先級:此規則凌駕於 CLAUDE.md 和 .claude/skills/ 中的定義
|
|
4
|
-
# 與 synapse-code-intelligence.yaml 互補:Synapse = 當前專案,Cortex = 跨專案
|
|
5
|
-
|
|
6
|
-
_meta:
|
|
7
|
-
category: "general"
|
|
8
|
-
priority: 1
|
|
9
|
-
applies_to:
|
|
10
|
-
- "ai_agent"
|
|
11
|
-
last_updated: "2026-03-15"
|
|
12
|
-
|
|
13
|
-
rules:
|
|
14
|
-
# =============================================================================
|
|
15
|
-
# COR-001: Cortex 可用性偵測
|
|
16
|
-
# =============================================================================
|
|
17
|
-
- id: "COR-001"
|
|
18
|
-
title: "Cortex 可用性偵測"
|
|
19
|
-
description: |
|
|
20
|
-
Cortex 與 Synapse 共用 Dashboard MCP server。
|
|
21
|
-
SYN-001 偵測到 Synapse 可用時,Cortex 同時可用。
|
|
22
|
-
severity: "should"
|
|
23
|
-
|
|
24
|
-
detection_method: |
|
|
25
|
-
ToolSearch 搜尋 "cortex":
|
|
26
|
-
- 找到 cortex_status / cortex_search / cortex_recall → Cortex 可用
|
|
27
|
-
- 沒找到 → Cortex 不可用,跨專案查詢降級為手動(僅限當前專案 Synapse)
|
|
28
|
-
|
|
29
|
-
on_available: "Synapse + Cortex: ON"
|
|
30
|
-
on_unavailable: "Cortex: OFF — 跨專案功能不可用"
|
|
31
|
-
|
|
32
|
-
# =============================================================================
|
|
33
|
-
# COR-002: Cortex 工具選擇決策樹
|
|
34
|
-
# =============================================================================
|
|
35
|
-
- id: "COR-002"
|
|
36
|
-
title: "Cortex vs Synapse 選擇決策樹"
|
|
37
|
-
description: |
|
|
38
|
-
Synapse 搜尋當前專案,Cortex 搜尋所有已註冊專案。
|
|
39
|
-
預設用 Synapse,只有明確跨專案需求時才用 Cortex。
|
|
40
|
-
severity: "should"
|
|
41
|
-
|
|
42
|
-
decision_tree: |
|
|
43
|
-
搜尋需求
|
|
44
|
-
│
|
|
45
|
-
├── 當前專案內的代碼/symbol? → kenaz_lsp_*(符號關係)/ Grep(文字)
|
|
46
|
-
│
|
|
47
|
-
├── [以下用 Cortex]
|
|
48
|
-
│
|
|
49
|
-
├── 「其他專案怎麼處理這個問題?」→ cortex_search(query=..., exclude_current=true)
|
|
50
|
-
├── 「有沒有跨專案的共通模式?」→ cortex_search(query=...)
|
|
51
|
-
├── 「這個概念在哪些專案出現?」→ cortex_relate(concept_name=...)
|
|
52
|
-
├── 「跨專案的歷史知識(Rune/Sigil)?」→ cortex_recall(query=...)
|
|
53
|
-
├── 「全局概念圖概覽?」→ cortex_overview
|
|
54
|
-
├── 「跨專案的模組/概念關係?」→ cortex_cypher(query=...)
|
|
55
|
-
│
|
|
56
|
-
└── 不確定 → 先用 Synapse,結果不足再用 Cortex 補充
|
|
57
|
-
|
|
58
|
-
# =============================================================================
|
|
59
|
-
# COR-003: Cortex 使用場景
|
|
60
|
-
# =============================================================================
|
|
61
|
-
- id: "COR-003"
|
|
62
|
-
title: "Cortex 使用場景"
|
|
63
|
-
description: |
|
|
64
|
-
定義何時使用 Cortex 跨專案工具。
|
|
65
|
-
severity: "should"
|
|
66
|
-
|
|
67
|
-
use_cortex:
|
|
68
|
-
- trigger: "跨專案語義搜尋"
|
|
69
|
-
tool: "cortex_search"
|
|
70
|
-
description: "在所有已註冊專案中搜尋相似代碼、模式或實現"
|
|
71
|
-
example: "cortex_search(query='authentication middleware', exclude_current=true)"
|
|
72
|
-
best_for: "找其他專案的參考實現、避免重複造輪子"
|
|
73
|
-
|
|
74
|
-
- trigger: "跨專案認知回憶"
|
|
75
|
-
tool: "cortex_recall"
|
|
76
|
-
description: "搜尋所有專案的 Rune 和 Sigil 歷史知識"
|
|
77
|
-
example: "cortex_recall(query='database connection pool')"
|
|
78
|
-
best_for: "查歷史決策、已知風險、跨專案的經驗教訓"
|
|
79
|
-
|
|
80
|
-
- trigger: "概念關聯查詢"
|
|
81
|
-
tool: "cortex_relate"
|
|
82
|
-
description: "查詢一個概念在哪些專案/模組中出現"
|
|
83
|
-
example: "cortex_relate(concept_name='Authentication')"
|
|
84
|
-
best_for: "架構決策時了解概念的跨專案分布"
|
|
85
|
-
|
|
86
|
-
- trigger: "全局概覽"
|
|
87
|
-
tool: "cortex_overview"
|
|
88
|
-
description: "Meta Graph 的 Project/Module/Concept 統計和 top concepts"
|
|
89
|
-
best_for: "快速了解跨專案知識圖譜"
|
|
90
|
-
|
|
91
|
-
- trigger: "複雜跨專案圖查詢"
|
|
92
|
-
tool: "cortex_cypher"
|
|
93
|
-
description: "直接用 Cypher 查詢 Meta Graph(Project, Module, Concept 及其關係)"
|
|
94
|
-
example: "cortex_cypher(query=\"MATCH (c:Concept) RETURN c.name, c.role ORDER BY c.name\")"
|
|
95
|
-
best_for: "模組相似度分析、跨專案概念網路、共享概念發現"
|
|
96
|
-
|
|
97
|
-
do_not_use_cortex:
|
|
98
|
-
- "當前專案內的代碼搜尋 → 用 kenaz_lsp_* / Grep"
|
|
99
|
-
- "精確字串/正則搜尋 → 用 Grep"
|
|
100
|
-
- "單一專案的 symbol 關係 → 用 kenaz_lsp_callers / kenaz_lsp_references"
|
|
101
|
-
|
|
102
|
-
# =============================================================================
|
|
103
|
-
# COR-004: Cortex 工具鏈使用流程
|
|
104
|
-
# =============================================================================
|
|
105
|
-
- id: "COR-004"
|
|
106
|
-
title: "Cortex 工具鏈使用流程"
|
|
107
|
-
severity: "should"
|
|
108
|
-
|
|
109
|
-
recommended_flows:
|
|
110
|
-
architecture_decision: |
|
|
111
|
-
架構決策前的跨專案調研:
|
|
112
|
-
cortex_overview → cortex_relate → cortex_search
|
|
113
|
-
1. cortex_overview 了解全局概念圖
|
|
114
|
-
2. cortex_relate 查詢目標概念在哪些專案/模組出現
|
|
115
|
-
3. cortex_search 找具體實現作為參考
|
|
116
|
-
|
|
117
|
-
reuse_discovery: |
|
|
118
|
-
尋找可複用實現:
|
|
119
|
-
cortex_search → cortex_relate → Grep
|
|
120
|
-
1. cortex_search(exclude_current=true) 找其他專案的相似實現
|
|
121
|
-
2. cortex_relate 確認概念層面的關聯
|
|
122
|
-
3. Grep 在當前專案確認是否已有類似實現
|
|
123
|
-
|
|
124
|
-
historical_knowledge: |
|
|
125
|
-
查詢歷史經驗:
|
|
126
|
-
cortex_recall → synapse_recall
|
|
127
|
-
1. cortex_recall 查跨專案歷史(所有專案的 Rune/Sigil)
|
|
128
|
-
2. synapse_recall 查當前專案的具體 symbol 知識
|
|
129
|
-
|
|
130
|
-
tool_summary:
|
|
131
|
-
cortex_status: "跨專案數據概覽(每個專案的 Synapse/memory 可用性、Rune/Sigil 計數)"
|
|
132
|
-
cortex_recall_contract: |
|
|
133
|
-
Propagation contract (TASK_1034):
|
|
134
|
-
1. synapse_inscribe(projectPath=X) writes to project X's memory.db.
|
|
135
|
-
2. The inscribe path triggers trigger_auto_sync (cortex/sync.rs) which
|
|
136
|
-
upserts the rune into ~/.kenaz/cortex/global_memory.db.
|
|
137
|
-
3. cortex_recall runs sync_all() on every invocation as a safety net,
|
|
138
|
-
so a freshly inscribed rune should be visible within the same call.
|
|
139
|
-
4. Timing budget: inscribe → cortex_recall visibility must be < 10s.
|
|
140
|
-
If longer, investigate sync.rs trigger_auto_sync path.
|
|
141
|
-
Query semantics:
|
|
142
|
-
- Queries are sanitized and tokens are joined with " OR " by default
|
|
143
|
-
(MatchMode::Any). Natural-language queries with extra keywords still
|
|
144
|
-
match runes whose content covers a subset of the tokens.
|
|
145
|
-
- Pass match_mode="all" (or "and") to force strict AND intersection
|
|
146
|
-
when you need every token to appear. Pass match_mode="any" (or "or")
|
|
147
|
-
to be explicit about the default.
|
|
148
|
-
- BM25 rank is folded into scoring so better lexical matches rank
|
|
149
|
-
higher (mirrors synapse recall TASK_1029).
|
|
150
|
-
- SQLite FTS5 uses 'porter unicode61' tokenization: case-insensitive,
|
|
151
|
-
underscore-separated tokens split (e.g. "generate_ddx" →
|
|
152
|
-
"generate" + "ddx"). CJK characters are not tokenized — use
|
|
153
|
-
English content (see MEM-LANG-001).
|
|
154
|
-
cortex_sync: "手動觸發全局記憶同步(從各專案的 memory.db 匯入 global_memory.db)"
|
|
155
|
-
cortex_search: "跨專案語義搜尋(embedding similarity,結果含 project_name)"
|
|
156
|
-
cortex_recall: "跨專案認知回憶(FTS5 搜尋所有專案的 Rune + Sigil)"
|
|
157
|
-
cortex_relate: "概念→專案/模組映射(Concept → BELONGS_TO → Module → HAS_MODULE → Project)"
|
|
158
|
-
cortex_overview: "Meta Graph 全局概覽(節點/關係計數 + top concepts)"
|
|
159
|
-
cortex_cypher: "Meta Graph Cypher 查詢(Project, Module, Concept, HAS_MODULE, SIMILAR_TO, RELATES_TO, BELONGS_TO)"
|
|
160
|
-
|
|
161
|
-
# =============================================================================
|
|
162
|
-
# 快速參考
|
|
163
|
-
# =============================================================================
|
|
164
|
-
quick_reference:
|
|
165
|
-
cortex_search:
|
|
166
|
-
strength: "跨專案語義搜尋、找其他專案的參考實現"
|
|
167
|
-
weakness: "需要各專案已 Synapse 索引"
|
|
168
|
-
best_for: "避免重複造輪子、架構決策參考"
|
|
169
|
-
|
|
170
|
-
cortex_recall:
|
|
171
|
-
strength: "跨專案歷史知識回憶、Rune/Sigil 搜尋"
|
|
172
|
-
best_for: "查歷史決策、已知風險、跨專案經驗"
|
|
173
|
-
|
|
174
|
-
cortex_relate:
|
|
175
|
-
strength: "概念→專案/模組的完整映射"
|
|
176
|
-
best_for: "了解概念跨專案分布、架構視角分析"
|
|
177
|
-
|
|
178
|
-
cortex_cypher:
|
|
179
|
-
strength: "任意 Meta Graph 查詢"
|
|
180
|
-
weakness: "需了解 Meta Graph schema(Project, Module, Concept)"
|
|
181
|
-
best_for: "模組相似度、共享概念、跨專案耦合分析"
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# .rule/general/memory-language.yaml
|
|
2
|
-
# Memory Language Constraint — All agent memory write operations must use English
|
|
3
|
-
# Priority: This rule applies to all agents using Synapse and Hippocampus memory tools.
|
|
4
|
-
# Root cause: SQLite unicode61 FTS5 tokenizer does not properly tokenize CJK characters
|
|
5
|
-
# (e.g., 限制 → 限 + 制), making multi-char CJK term queries unreliable. Agent-level
|
|
6
|
-
# constraint is more robust and consistent than patching the tokenizer.
|
|
7
|
-
|
|
8
|
-
_meta:
|
|
9
|
-
category: "general"
|
|
10
|
-
priority: 1
|
|
11
|
-
applies_to:
|
|
12
|
-
- "ai_agent"
|
|
13
|
-
last_updated: "2026-03-21"
|
|
14
|
-
|
|
15
|
-
rules:
|
|
16
|
-
# =============================================================================
|
|
17
|
-
# MEM-LANG-001: English-Only Memory Write Operations
|
|
18
|
-
# =============================================================================
|
|
19
|
-
- id: "MEM-LANG-001"
|
|
20
|
-
title: "English-only for all agent memory write operations"
|
|
21
|
-
description: |
|
|
22
|
-
All agent memory write operations must use English for searchable content fields.
|
|
23
|
-
Root cause: SQLite unicode61 FTS5 tokenizer does not properly tokenize CJK
|
|
24
|
-
characters, making Chinese queries return 0 results from episode_recall and
|
|
25
|
-
synapse_recall. Agent-level enforcement is more robust than patching FTS5.
|
|
26
|
-
severity: "must"
|
|
27
|
-
|
|
28
|
-
scope:
|
|
29
|
-
enforce_english:
|
|
30
|
-
synapse_inscribe:
|
|
31
|
-
- "content field"
|
|
32
|
-
- "context field"
|
|
33
|
-
hippocampus_episode_start:
|
|
34
|
-
- "scope field"
|
|
35
|
-
- "lesson field"
|
|
36
|
-
hippocampus_episode_append:
|
|
37
|
-
- "content field"
|
|
38
|
-
hippocampus_episode_close:
|
|
39
|
-
- "summary field"
|
|
40
|
-
- "lesson field"
|
|
41
|
-
|
|
42
|
-
allowed_chinese:
|
|
43
|
-
- "task titles (TASK_XXX.yaml title field)"
|
|
44
|
-
- "user-facing output (responses visible to the user)"
|
|
45
|
-
- "code content (preserve source code as-is, including comments)"
|
|
46
|
-
- "task descriptions and acceptance criteria"
|
|
47
|
-
|
|
48
|
-
rationale: |
|
|
49
|
-
SQLite unicode61 tokenizer splits CJK characters individually:
|
|
50
|
-
"KuzuDB UNION ALL 限制" → tokens: [kuzu, db, union, all, 限, 制]
|
|
51
|
-
A query for "限制" (two chars) fails to match because no single token equals "限制".
|
|
52
|
-
Writing memory content in English avoids this tokenization problem entirely.
|
|
53
|
-
|
|
54
|
-
enforcement: |
|
|
55
|
-
Before calling any memory write tool, translate content/summary/lesson/scope fields
|
|
56
|
-
to English. Code snippets, file paths, and symbol names may remain in their original
|
|
57
|
-
form. Task titles and user-facing output are NOT affected by this rule.
|
|
58
|
-
|
|
59
|
-
examples:
|
|
60
|
-
- wrong: |
|
|
61
|
-
synapse_inscribe(content="KuzuDB UNION ALL 有已知限制:子查詢不支援")
|
|
62
|
-
correct: |
|
|
63
|
-
synapse_inscribe(content="KuzuDB UNION ALL has known limitation: subqueries not supported")
|
|
64
|
-
- wrong: |
|
|
65
|
-
hippocampus_episode_close(summary="修復了 kuzu destructor crash 問題")
|
|
66
|
-
correct: |
|
|
67
|
-
hippocampus_episode_close(summary="Fixed kuzu destructor crash in cleanup path")
|