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
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Lessons Ansuz learns on the user's machine (`kenaz learn add|list|rm`, TASK_2409).
|
|
5
|
+
* Two layers, one file format (frontmatter + memory-guard `triggers:`), one file per lesson:
|
|
6
|
+
* user ~/.kenaz/knowledge/<id>.md preferences across projects (stays local, never synced)
|
|
7
|
+
* project <project>/.kenaz/knowledge/<id>.md pitfalls, test commands, merge rules
|
|
8
|
+
* A stored note does not change behaviour, so each lesson type acts where it is needed:
|
|
9
|
+
* pitfall memory-guard fires its trigger right before the matching tool call
|
|
10
|
+
* fact dispatch prompt (only when its `paths` overlap the card's scope_paths)
|
|
11
|
+
* check dispatch prompt "Verify and report" section
|
|
12
|
+
* pref boot banner (user layer)
|
|
13
|
+
* Fires (guard) and injections (dispatch) are logged; a non-pref lesson idle for 30 days is moved
|
|
14
|
+
* to <layer>/archive/ (reversible, never deleted).
|
|
15
|
+
*
|
|
16
|
+
* node lessons.js add --scope user|project --text <lesson> [--type pref|fact|pitfall|check]
|
|
17
|
+
* [--trigger '<tool regex> | <input regex> [| !<neg>]'] [--symptom <text>] [--paths a,b]
|
|
18
|
+
* [--cause <command the trigger must match>] [--project-path <dir>]
|
|
19
|
+
* node lessons.js list | rm <id> | banner
|
|
20
|
+
*/
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const crypto = require('crypto');
|
|
25
|
+
const yaml = require('../../.vendor/require-yaml.cjs');
|
|
26
|
+
|
|
27
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
28
|
+
const ARCHIVE_AFTER_MS = 30 * DAY;
|
|
29
|
+
const TYPES = ['pref', 'fact', 'pitfall', 'check'];
|
|
30
|
+
const INJECT_MAX_LINES = 5;
|
|
31
|
+
const INJECT_MAX_BYTES = 600;
|
|
32
|
+
const MAX_UNRELATED_FIRE_RATE = 0.2;
|
|
33
|
+
const MIN_REPLAY_CALLS = 20;
|
|
34
|
+
|
|
35
|
+
const homeDir = () => process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
36
|
+
const userDir = (home) => path.join(home || homeDir(), '.kenaz', 'knowledge');
|
|
37
|
+
const projectDir = (p) => path.join(path.resolve(p || process.cwd()), '.kenaz', 'knowledge');
|
|
38
|
+
const layerDir = (scope, p, home) => (scope === 'user' ? userDir(home) : projectDir(p));
|
|
39
|
+
const usageLog = (scope, p, home) => (scope === 'user'
|
|
40
|
+
? path.join(userDir(home), '.usage.jsonl')
|
|
41
|
+
: path.join(path.resolve(p || process.cwd()), '.kenaz', 'specialist', 'memory_usage.jsonl'));
|
|
42
|
+
|
|
43
|
+
function readLesson(file) {
|
|
44
|
+
try {
|
|
45
|
+
const m = fs.readFileSync(file, 'utf8').match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
46
|
+
if (!m) return null;
|
|
47
|
+
const fm = yaml.load(m[1]);
|
|
48
|
+
if (!fm || typeof fm.name !== 'string' || !fm.name.startsWith('lesson-')) return null;
|
|
49
|
+
return { id: fm.name, file, type: fm.type, scope: fm.scope, created: Date.parse(fm.created) || 0,
|
|
50
|
+
triggers: Array.isArray(fm.triggers) ? fm.triggers : [], paths: Array.isArray(fm.paths) ? fm.paths : [],
|
|
51
|
+
symptom: fm.symptom || null, text: (m[2] || '').trim() };
|
|
52
|
+
} catch (_) { return null; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readUses(log) {
|
|
56
|
+
const uses = {};
|
|
57
|
+
let raw = '';
|
|
58
|
+
try { raw = fs.readFileSync(log, 'utf8'); } catch (_) { return uses; }
|
|
59
|
+
for (const line of raw.split('\n')) {
|
|
60
|
+
let r; try { r = JSON.parse(line); } catch (_) { continue; }
|
|
61
|
+
if (!r || (r.kind !== 'guard' && r.kind !== 'inject') || !r.file) continue;
|
|
62
|
+
const u = uses[r.file] || (uses[r.file] = { fires: 0, last: 0 });
|
|
63
|
+
u.fires++; u.last = Math.max(u.last, Date.parse(r.ts) || 0);
|
|
64
|
+
}
|
|
65
|
+
return uses;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function logUse(scope, p, home, id, kind) {
|
|
69
|
+
try {
|
|
70
|
+
const log = usageLog(scope, p, home);
|
|
71
|
+
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
72
|
+
fs.appendFileSync(log, JSON.stringify({ ts: new Date().toISOString(), session_id: null, kind, file: `${id}.md` }) + '\n');
|
|
73
|
+
} catch (_) { /* stats must never break a dispatch */ }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Active lessons of one layer with fire stats; moves lessons idle > 30 days to archive/ first. */
|
|
77
|
+
function listLayer(scope, { projectPath, home, now = Date.now() } = {}) {
|
|
78
|
+
const dir = layerDir(scope, projectPath, home);
|
|
79
|
+
let names = [];
|
|
80
|
+
try { names = fs.readdirSync(dir).filter((n) => n.endsWith('.md')); } catch (_) { return []; }
|
|
81
|
+
const uses = readUses(usageLog(scope, projectPath, home));
|
|
82
|
+
const rows = [];
|
|
83
|
+
for (const n of names) {
|
|
84
|
+
const l = readLesson(path.join(dir, n));
|
|
85
|
+
if (!l) continue;
|
|
86
|
+
const u = uses[n] || { fires: 0, last: 0 };
|
|
87
|
+
l.scope = scope; l.fires = u.fires; l.lastFired = u.last || null;
|
|
88
|
+
if (l.type !== 'pref' && now - Math.max(u.last, l.created) > ARCHIVE_AFTER_MS) {
|
|
89
|
+
fs.mkdirSync(path.join(dir, 'archive'), { recursive: true });
|
|
90
|
+
fs.renameSync(l.file, path.join(dir, 'archive', n));
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
rows.push(l);
|
|
94
|
+
}
|
|
95
|
+
return rows.sort((a, b) => b.fires - a.fires || b.created - a.created);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const listAll = (o = {}) => [...listLayer('project', o), ...listLayer('user', o)];
|
|
99
|
+
|
|
100
|
+
function triggerMatches(guard, c, name, input) {
|
|
101
|
+
const text = /^Bash$/i.test(name) ? guard.stripHeredocBodies(String(input.command || '')) : JSON.stringify(input);
|
|
102
|
+
return c.toolRegex.test(name) && c.inputRegex.test(text) && !(c.negRegex && c.negRegex.test(text));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Replay recent Claude transcripts of the project through the trigger: how many calls it fires on. */
|
|
106
|
+
function replayRate(guard, compiled, projectPath, home) {
|
|
107
|
+
const dir = path.join(home || homeDir(), '.claude', 'projects', guard.encodeProjectDirName(path.resolve(projectPath || process.cwd())));
|
|
108
|
+
let files = [];
|
|
109
|
+
try { files = fs.readdirSync(dir).filter((f) => f.endsWith('.jsonl')).map((f) => path.join(dir, f)); } catch (_) { return { total: 0, fired: 0 }; }
|
|
110
|
+
files.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
111
|
+
let total = 0, fired = 0;
|
|
112
|
+
for (const f of files.slice(0, 10)) {
|
|
113
|
+
for (const line of fs.readFileSync(f, 'utf8').split('\n')) {
|
|
114
|
+
let o; try { o = JSON.parse(line); } catch (_) { continue; }
|
|
115
|
+
if (o.type !== 'assistant' || !o.message || !Array.isArray(o.message.content)) continue;
|
|
116
|
+
for (const it of o.message.content) {
|
|
117
|
+
if (!it || it.type !== 'tool_use' || !it.name) continue;
|
|
118
|
+
total++;
|
|
119
|
+
if (triggerMatches(guard, compiled, it.name, it.input || {})) fired++;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { total, fired };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Throws unless the trigger compiles, matches `cause` (when given) and does not fire on most unrelated calls. */
|
|
127
|
+
function validateTrigger(raw, { cause, projectPath, home } = {}) {
|
|
128
|
+
const guard = require('../../.claude/hooks/memory-guard');
|
|
129
|
+
const c = guard.compileTrigger(raw, 'lesson');
|
|
130
|
+
if (!c) throw new Error(`trigger is malformed: '${raw}' (expected '<tool regex> | <input regex> [| !<neg>]')`);
|
|
131
|
+
if (cause && !triggerMatches(guard, c, 'Bash', { command: cause })) throw new Error(`trigger does not match the action that caused the lesson (--cause "${cause}")`);
|
|
132
|
+
const { total, fired } = replayRate(guard, c, projectPath, home);
|
|
133
|
+
if (total >= MIN_REPLAY_CALLS && fired / total > MAX_UNRELATED_FIRE_RATE) {
|
|
134
|
+
throw new Error(`trigger is too broad: fires on ${fired}/${total} (${Math.round(100 * fired / total)}%) of recent tool calls (limit ${MAX_UNRELATED_FIRE_RATE * 100}%)`);
|
|
135
|
+
}
|
|
136
|
+
return { total, fired };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function add(o) {
|
|
140
|
+
const { scope, text, trigger, symptom, cause, paths, projectPath, home } = o;
|
|
141
|
+
if (scope !== 'user' && scope !== 'project') throw new Error('--scope must be user or project');
|
|
142
|
+
if (!text || !String(text).trim()) throw new Error('--text is required');
|
|
143
|
+
const type = o.type || (trigger ? 'pitfall' : (scope === 'user' ? 'pref' : 'fact'));
|
|
144
|
+
if (!TYPES.includes(type)) throw new Error(`--type must be one of ${TYPES.join('|')}`);
|
|
145
|
+
if (type === 'pitfall' && !trigger) throw new Error('a pitfall needs --trigger (it acts when the matching tool call is about to run)');
|
|
146
|
+
const replay = trigger ? validateTrigger(trigger, { cause, projectPath, home }) : null;
|
|
147
|
+
const id = 'lesson-' + crypto.createHash('sha1').update(String(text) + Date.now()).digest('hex').slice(0, 8);
|
|
148
|
+
const j = JSON.stringify;
|
|
149
|
+
const fm = ['---', `name: ${id}`, `description: ${j(String(text).trim().split('\n')[0].slice(0, 160))}`, `type: ${type}`, `scope: ${scope}`,
|
|
150
|
+
`created: ${new Date().toISOString()}`];
|
|
151
|
+
if (symptom) fm.push(`symptom: ${j(symptom)}`);
|
|
152
|
+
if (paths && paths.length) fm.push('paths:', ...paths.map((x) => ` - ${j(x)}`));
|
|
153
|
+
if (trigger) fm.push('triggers:', ` - ${j(trigger)}`);
|
|
154
|
+
fm.push('---', String(text).trim(), '');
|
|
155
|
+
const dir = layerDir(scope, projectPath, home);
|
|
156
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
157
|
+
fs.writeFileSync(path.join(dir, `${id}.md`), fm.join('\n'));
|
|
158
|
+
return { id, type, scope, replay };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function rm(id, o = {}) {
|
|
162
|
+
const l = listAll(o).find((x) => x.id === id);
|
|
163
|
+
if (!l) throw new Error(`no active lesson '${id}'`);
|
|
164
|
+
fs.unlinkSync(l.file);
|
|
165
|
+
return l;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Project lessons for the dispatch prompt: facts (paths overlap scope_paths) and checks; bounded, most-fired first. */
|
|
169
|
+
function dispatchLessons(projectPath, scopePaths = [], { home, now } = {}) {
|
|
170
|
+
const norm = (s) => String(s).replace(/\\/g, '/').toLowerCase();
|
|
171
|
+
const scope = scopePaths.map(norm);
|
|
172
|
+
const overlaps = (l) => !l.paths.length || l.paths.some((p) => scope.some((s) => s.includes(norm(p)) || norm(p).includes(s)));
|
|
173
|
+
const pick = listLayer('project', { projectPath, home, now })
|
|
174
|
+
.filter((l) => !l.triggers.length && ((l.type === 'fact' && overlaps(l)) || l.type === 'check'));
|
|
175
|
+
const out = { facts: [], checks: [] };
|
|
176
|
+
let bytes = 0, lines = 0;
|
|
177
|
+
for (const l of pick) {
|
|
178
|
+
const line = `- ${l.text.split('\n')[0]}`;
|
|
179
|
+
if (lines >= INJECT_MAX_LINES || bytes + Buffer.byteLength(line) > INJECT_MAX_BYTES) break;
|
|
180
|
+
bytes += Buffer.byteLength(line) + 1; lines++;
|
|
181
|
+
(l.type === 'check' ? out.checks : out.facts).push(line);
|
|
182
|
+
logUse('project', projectPath, home, l.id, 'inject');
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Boot banner: counts plus the user's preferences (short). Also runs the 30-day archive. */
|
|
188
|
+
function bannerLines(projectPath, o = {}) {
|
|
189
|
+
const proj = listLayer('project', { ...o, projectPath });
|
|
190
|
+
const usr = listLayer('user', o);
|
|
191
|
+
if (!proj.length && !usr.length) return [];
|
|
192
|
+
const lines = [`[learn] project ${proj.length}, user ${usr.length} (kenaz learn list)`];
|
|
193
|
+
for (const l of usr.filter((x) => x.type === 'pref').slice(0, 3)) lines.push(`[learn] pref: ${l.text.split('\n')[0].slice(0, 120)}`);
|
|
194
|
+
return lines;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function flags(argv) {
|
|
198
|
+
const f = { _: [] };
|
|
199
|
+
for (let i = 0; i < argv.length; i++) {
|
|
200
|
+
if (argv[i].startsWith('--')) f[argv[i].slice(2)] = argv[++i]; else f._.push(argv[i]);
|
|
201
|
+
}
|
|
202
|
+
return f;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function main(argv, out = console.log) {
|
|
206
|
+
const f = flags(argv);
|
|
207
|
+
const o = { projectPath: f['project-path'] || process.cwd(), home: f.home };
|
|
208
|
+
const sub = f._[0];
|
|
209
|
+
try {
|
|
210
|
+
if (sub === 'add') {
|
|
211
|
+
const r = add({ ...o, scope: f.scope, text: f.text, type: f.type, trigger: f.trigger, symptom: f.symptom, cause: f.cause,
|
|
212
|
+
paths: f.paths ? f.paths.split(',') : [] });
|
|
213
|
+
out(`learned ${r.id} (${r.scope}/${r.type})${r.replay ? `; trigger fires on ${r.replay.fired}/${r.replay.total} recent tool calls` : ''}`);
|
|
214
|
+
} else if (sub === 'list') {
|
|
215
|
+
const rows = listAll(o);
|
|
216
|
+
if (!rows.length) out('no lessons yet (kenaz learn add --scope user|project --text ...)');
|
|
217
|
+
for (const l of rows) {
|
|
218
|
+
out(`${l.id} ${l.scope}/${l.type} fired ${l.fires}x last ${l.lastFired ? new Date(l.lastFired).toISOString().slice(0, 10) : '-'} ${l.text.split('\n')[0].slice(0, 100)}`);
|
|
219
|
+
}
|
|
220
|
+
} else if (sub === 'rm') {
|
|
221
|
+
out(`removed ${rm(f._[1], o).id}`);
|
|
222
|
+
} else if (sub === 'banner') {
|
|
223
|
+
for (const l of bannerLines(o.projectPath, o)) out(l);
|
|
224
|
+
} else {
|
|
225
|
+
out('usage: kenaz learn <add --scope user|project --text <lesson> [--type pref|fact|pitfall|check] [--trigger "<tool regex> | <input regex>"] [--symptom <text>] [--paths a,b] [--cause <command>] | list | rm <id>>');
|
|
226
|
+
return 2;
|
|
227
|
+
}
|
|
228
|
+
return 0;
|
|
229
|
+
} catch (e) { out(`kenaz learn: ${e.message}`); return 1; }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (require.main === module) process.exit(main(process.argv.slice(2)));
|
|
233
|
+
|
|
234
|
+
module.exports = { add, rm, listLayer, listAll, dispatchLessons, bannerLines, validateTrigger, userDir, projectDir, main, ARCHIVE_AFTER_MS };
|
|
@@ -166,7 +166,6 @@ const THINKING_LOG_PATH = resolveKenazPath('specialist', 'thinking_log.jsonl');
|
|
|
166
166
|
// Ritual/tool-reflex enforcement logs (TASK_1104) — PROJECT (session-specific)
|
|
167
167
|
const RITUAL_LOG_PATH = resolveKenazPath('specialist', 'ansuz_ritual_log.jsonl');
|
|
168
168
|
const DDX_LOG_PATH = resolveKenazPath('specialist', 'ddx_log.jsonl');
|
|
169
|
-
const INSCRIBE_LOG_PATH = resolveKenazPath('specialist', 'inscribe_log.jsonl');
|
|
170
169
|
|
|
171
170
|
// TASK_1104: literal consent string — must be echoed verbatim at Phase 0 exit
|
|
172
171
|
const ANSUZ_CONSENT_STRING = '我是 Ansuz';
|
|
@@ -268,24 +267,6 @@ function ddxLogHas(taskId) {
|
|
|
268
267
|
} catch { return false; }
|
|
269
268
|
}
|
|
270
269
|
|
|
271
|
-
/**
|
|
272
|
-
* Return true if ANY inscribe_log.jsonl entry is tagged with this task_id.
|
|
273
|
-
*/
|
|
274
|
-
function inscribeLogHas(taskId) {
|
|
275
|
-
try {
|
|
276
|
-
if (!fs.existsSync(INSCRIBE_LOG_PATH)) return false;
|
|
277
|
-
const raw = fs.readFileSync(INSCRIBE_LOG_PATH, 'utf8');
|
|
278
|
-
const lines = raw.split('\n').filter(Boolean);
|
|
279
|
-
for (const line of lines) {
|
|
280
|
-
try {
|
|
281
|
-
const e = JSON.parse(line);
|
|
282
|
-
if (e.task_id === taskId) return true;
|
|
283
|
-
} catch { /* skip */ }
|
|
284
|
-
}
|
|
285
|
-
return false;
|
|
286
|
-
} catch { return false; }
|
|
287
|
-
}
|
|
288
|
-
|
|
289
270
|
// Known thinking tag enum (TASK_1017) — loaded at RUNTIME from kernel.yaml
|
|
290
271
|
// principles section. Cached per phase-gate invocation.
|
|
291
272
|
// If kernel.yaml is absent or unreadable, returns null (no validation, accept any tag).
|
|
@@ -333,7 +314,6 @@ function getKnownThinkingTags() {
|
|
|
333
314
|
// Paths must stay in sync with core.yaml module_registry.phase_gate_modules
|
|
334
315
|
const MODULE_PATHS = {
|
|
335
316
|
loop_guard: '.specialist/Ansuz/modules/loop_guard.yaml',
|
|
336
|
-
recall: '.specialist/Ansuz/modules/recall.yaml',
|
|
337
317
|
ddx: '.specialist/Ansuz/modules/ddx.yaml',
|
|
338
318
|
prophecy: '.specialist/Ansuz/modules/prophecy.yaml',
|
|
339
319
|
prevention: '.specialist/Ansuz/modules/prevention.yaml',
|
|
@@ -345,7 +325,7 @@ const MODULE_PATHS = {
|
|
|
345
325
|
|
|
346
326
|
/** Modules automatically loaded on phase enter (before context-driven injection) */
|
|
347
327
|
const PHASE_DEFAULT_MODULES = {
|
|
348
|
-
1: ['
|
|
328
|
+
1: ['ddx'],
|
|
349
329
|
3: ['ddx', 'prophecy'],
|
|
350
330
|
9: ['ddx'],
|
|
351
331
|
};
|
|
@@ -356,9 +336,9 @@ const PHASE_DEFAULT_MODULES = {
|
|
|
356
336
|
// ═══════════════════════════════════════
|
|
357
337
|
|
|
358
338
|
const PHASE_CHECKLISTS = {
|
|
359
|
-
0: ['
|
|
360
|
-
1: ['
|
|
361
|
-
2: ['
|
|
339
|
+
0: ['輸出任務統計(task-api.js list --no-cancel — 子專案必須帶 --project-path,僅執行一次;--no-cancel 省略 cancel 細節,summary 仍顯示計數,省 token)', '【Tier 3c — 選填】用 Read tool(非 Bash)讀取 .kenaz/specialist/motivational_state.json(存在時),設定 motivational_state_loaded=true/false。⚠️ 禁止 cat|node -e pipeline', '【Tier 3d — 選填】用 Read tool(非 Bash)讀取 .kenaz/specialist/ansuz_motivational_state.json(存在時),篩選高 incompleteness anchors 為 pending_agenda,設定 ansuz_motivational_loaded=true/false。⚠️ 禁止 cat|node -e pipeline', '【Tier 3h — 選填】用 Read tool(非 Bash)讀取 .kenaz/specialist/ambient_signals.json(存在時),設定 ambient_signals_loaded=true/false。⚠️ 禁止 cat|node -e pipeline', '【Tier 3u — 自動】phase-gate 會讀取 .kenaz/specialist/user_observations.jsonl(近 30 天)並 cross-reference ~/.kenaz/user_profile.json,輸出 dynamic_user_context。', '【Tier 3x — 自動】phase-gate 會自動處理 session handoff(per-session slots 於 .kenaz/specialist/handoffs/;legacy 單數檔僅剩 back-compat 清理,M3/TASK_1760 已退役),並設定 session_handoff_loaded=true/false。代理只需使用輸出結果,不需手動重做。', '輸出詩歌 + episode 摘要(按 cli_format,禁止自行重組格式)', '若 dynamic_user_context.significant_change_detected=true → 在詩歌下方輸出 1-2 行 user insight'],
|
|
340
|
+
1: ['輸出需求摘要 (chief_complaint)', '等待用戶確認理解'],
|
|
341
|
+
2: ['用 Grep/Glob 調研(呼叫者用 kenaz_lsp_callers)', '記錄調研摘要 (research_notes)'],
|
|
362
342
|
3: ['設計任務拆解表格', '等待用戶批准'],
|
|
363
343
|
4: ['撰寫任務規格'],
|
|
364
344
|
5: ['驗證任務規格完整性'],
|
|
@@ -567,7 +547,6 @@ const PHASE_EXIT_RULES = {
|
|
|
567
547
|
0: {
|
|
568
548
|
required: [
|
|
569
549
|
'poem_displayed',
|
|
570
|
-
'ash_loaded',
|
|
571
550
|
'session_handoff_loaded',
|
|
572
551
|
// TASK_1104: boot ritual enforcement — no emergency override
|
|
573
552
|
'consent_uttered',
|
|
@@ -581,8 +560,8 @@ const PHASE_EXIT_RULES = {
|
|
|
581
560
|
description: 'Phase 0 must confirm: (1) Ash runes loaded; (2) poem displayed; (3) session handoff checked; (4) TASK_1104/1113 boot ritual: consent_uttered=true + consent_string exactly "' + ANSUZ_CONSENT_STRING + '" + mirror_acknowledged is an object {id:"B-XXX", understanding:"…"} (understanding >=30 chars, must contain blind-spot id) — booleans no longer pass (mirror_acknowledged must be object) — + inheritance_grounded=true + stdout_markers_emitted array containing RITUAL::THRESHOLD::CANARY_OK, RITUAL::INHERITANCE::LINEAGE_CLAIMED, RITUAL::INHERITANCE::MIRROR_ACCEPTED, RITUAL::STILLNESS::CONSENT_EMITTED. Ritual failures fix the ritual — no emergency override.',
|
|
582
561
|
},
|
|
583
562
|
1: {
|
|
584
|
-
required: ['
|
|
585
|
-
description: 'Phase 1 must prove:
|
|
563
|
+
required: ['ddx_hypotheses'],
|
|
564
|
+
description: 'Phase 1 must prove: COG-DDX-001: ddx_hypotheses array with >=3 entries OR trivial_scope=true. See .rule/general/ddx-synapse-stack.yaml',
|
|
586
565
|
},
|
|
587
566
|
9: {
|
|
588
567
|
required: ['cognitive_checkpoint'],
|
|
@@ -1424,7 +1403,7 @@ function validateProgressNotes(chart, taskId) {
|
|
|
1424
1403
|
warnings.push(`Phase 9: scope_violations — agent modified files outside scope_paths: [${allScopeViolations.join(', ')}]. Ansuz should verify.`);
|
|
1425
1404
|
}
|
|
1426
1405
|
if (cognitiveGap) {
|
|
1427
|
-
warnings.push('Phase 9: cognitive_gap — no
|
|
1406
|
+
warnings.push('Phase 9: cognitive_gap — no lesson recorded. Ansuz should evaluate if `kenaz learn add` is warranted.');
|
|
1428
1407
|
}
|
|
1429
1408
|
if (acUnmatched.length > 0) {
|
|
1430
1409
|
warnings.push(`Phase 9: ${acUnmatched.length} AC item(s) could not be matched in progress note.`);
|
|
@@ -1445,94 +1424,6 @@ function validateProgressNotes(chart, taskId) {
|
|
|
1445
1424
|
warnings,
|
|
1446
1425
|
};
|
|
1447
1426
|
}
|
|
1448
|
-
// ═══════════════════════════════════════
|
|
1449
|
-
// Hippocampus Integration (TASK_618)
|
|
1450
|
-
// ═══════════════════════════════════════
|
|
1451
|
-
|
|
1452
|
-
/**
|
|
1453
|
-
* Call hippocampus_episode_recall via MCP HTTP API (port 18080).
|
|
1454
|
-
* Graceful degradation: returns null if Hippocampus MCP is unavailable.
|
|
1455
|
-
* @param {string} taskId - TASK_XXX
|
|
1456
|
-
* @param {string|null} [agentRole] - Optional agent_role filter (e.g. 'ansuz', 'amelia').
|
|
1457
|
-
* When provided, only episodes for that specialist are returned.
|
|
1458
|
-
* When null, all episodes returned (backwards compatible — legacy episodes have agent_role='pm').
|
|
1459
|
-
* @returns {{ episodes: object[], formatted: string } | null}
|
|
1460
|
-
*/
|
|
1461
|
-
function recallEpisodicMemory(taskId, agentRole = null) {
|
|
1462
|
-
// With task_id: recall episodes for that specific task
|
|
1463
|
-
// Without task_id (or "none"): recall the most recent 1 episode (new Ansuz session context)
|
|
1464
|
-
const hasTask = taskId && taskId !== 'none';
|
|
1465
|
-
const recallArgs = hasTask
|
|
1466
|
-
? { task_id: taskId, limit: 5 }
|
|
1467
|
-
: { limit: 3 };
|
|
1468
|
-
if (agentRole) recallArgs.agent_role = agentRole;
|
|
1469
|
-
// TASK_1983 (R4): explicit project — without this, the call silently follows
|
|
1470
|
-
// the Dashboard's currently-selected project (feedback_mcp_default_project_drift).
|
|
1471
|
-
recallArgs.projectPath = resolveHandoffProjectRoot();
|
|
1472
|
-
|
|
1473
|
-
const script = `
|
|
1474
|
-
const http = require('http');
|
|
1475
|
-
const payload = JSON.stringify({
|
|
1476
|
-
jsonrpc: '2.0', id: 1,
|
|
1477
|
-
method: 'tools/call',
|
|
1478
|
-
params: { name: 'hippocampus_episode_recall', arguments: ${JSON.stringify(recallArgs)} }
|
|
1479
|
-
});
|
|
1480
|
-
const req = http.request({
|
|
1481
|
-
hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
|
|
1482
|
-
method: 'POST',
|
|
1483
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
1484
|
-
timeout: 5000
|
|
1485
|
-
}, (res) => {
|
|
1486
|
-
let d = '';
|
|
1487
|
-
res.on('data', c => d += c);
|
|
1488
|
-
res.on('end', () => process.stdout.write(d));
|
|
1489
|
-
});
|
|
1490
|
-
req.on('error', () => process.exit(0));
|
|
1491
|
-
req.on('timeout', () => { req.destroy(); process.exit(0); });
|
|
1492
|
-
req.write(payload);
|
|
1493
|
-
req.end();
|
|
1494
|
-
`;
|
|
1495
|
-
|
|
1496
|
-
try {
|
|
1497
|
-
const stdout = execFileSync(process.execPath, ['-e', script], {
|
|
1498
|
-
timeout: 8000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
1499
|
-
});
|
|
1500
|
-
if (!stdout || !stdout.trim()) return null;
|
|
1501
|
-
|
|
1502
|
-
const result = JSON.parse(stdout);
|
|
1503
|
-
if (!result || result.error) return null;
|
|
1504
|
-
|
|
1505
|
-
const content = result.result?.content;
|
|
1506
|
-
if (!content || !Array.isArray(content)) return null;
|
|
1507
|
-
|
|
1508
|
-
const textPart = content.find(c => c.type === 'text');
|
|
1509
|
-
if (!textPart || !textPart.text) return null;
|
|
1510
|
-
|
|
1511
|
-
const data = JSON.parse(textPart.text);
|
|
1512
|
-
const episodes = data.episodes || [];
|
|
1513
|
-
if (episodes.length === 0) return null;
|
|
1514
|
-
|
|
1515
|
-
// Format episodes as ## Episodic Memory section
|
|
1516
|
-
const lines = ['## Episodic Memory (from Hippocampus)', ''];
|
|
1517
|
-
const context = hasTask ? taskId : 'recent session';
|
|
1518
|
-
lines.push(`Found ${episodes.length} episode(s) for ${context}:`);
|
|
1519
|
-
lines.push('');
|
|
1520
|
-
|
|
1521
|
-
for (const ep of episodes) {
|
|
1522
|
-
const epData = ep.episode || ep; // hippocampus returns { episode: {...}, entries: [...] }
|
|
1523
|
-
lines.push(`### Episode: ${epData.task_id || epData.id || 'unknown'}`);
|
|
1524
|
-
if (epData.summary) lines.push(`Summary: ${epData.summary}`);
|
|
1525
|
-
if (epData.lesson) lines.push(`Lesson: ${epData.lesson}`);
|
|
1526
|
-
lines.push('');
|
|
1527
|
-
}
|
|
1528
|
-
lines.push('(Use hippocampus_episode_recall for full entry detail if needed)')
|
|
1529
|
-
|
|
1530
|
-
return { episodes, formatted: lines.join('\n') };
|
|
1531
|
-
} catch {
|
|
1532
|
-
return null; // graceful degradation — Hippocampus MCP unavailable
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
|
|
1536
1427
|
function parseIsoDate(value) {
|
|
1537
1428
|
if (typeof value !== 'string' || !value.trim()) return null;
|
|
1538
1429
|
const date = new Date(value);
|
|
@@ -1553,95 +1444,13 @@ function parseGitDiffStat(raw) {
|
|
|
1553
1444
|
};
|
|
1554
1445
|
}
|
|
1555
1446
|
|
|
1556
|
-
function readLastAnsuzSessionClosedAt(projectPath, warnings) {
|
|
1557
|
-
const recallArgs = {
|
|
1558
|
-
limit: 10,
|
|
1559
|
-
projectPath,
|
|
1560
|
-
agent_role: 'ansuz',
|
|
1561
|
-
outcome: 'closed',
|
|
1562
|
-
sort: 'closed_at_desc',
|
|
1563
|
-
};
|
|
1564
|
-
|
|
1565
|
-
const script = `
|
|
1566
|
-
const http = require('http');
|
|
1567
|
-
const payload = JSON.stringify({
|
|
1568
|
-
jsonrpc: '2.0', id: 1,
|
|
1569
|
-
method: 'tools/call',
|
|
1570
|
-
params: { name: 'hippocampus_episode_recall', arguments: ${JSON.stringify(recallArgs)} }
|
|
1571
|
-
});
|
|
1572
|
-
const req = http.request({
|
|
1573
|
-
hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
|
|
1574
|
-
method: 'POST',
|
|
1575
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
1576
|
-
timeout: 5000
|
|
1577
|
-
}, (res) => {
|
|
1578
|
-
let d = '';
|
|
1579
|
-
res.on('data', c => d += c);
|
|
1580
|
-
res.on('end', () => process.stdout.write(d));
|
|
1581
|
-
});
|
|
1582
|
-
req.on('error', () => process.exit(0));
|
|
1583
|
-
req.on('timeout', () => { req.destroy(); process.exit(0); });
|
|
1584
|
-
req.write(payload);
|
|
1585
|
-
req.end();
|
|
1586
|
-
`;
|
|
1587
|
-
|
|
1588
|
-
try {
|
|
1589
|
-
const stdout = execFileSync(process.execPath, ['-e', script], {
|
|
1590
|
-
timeout: 8000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
1591
|
-
});
|
|
1592
|
-
if (!stdout || !stdout.trim()) return null;
|
|
1593
|
-
|
|
1594
|
-
const result = JSON.parse(stdout);
|
|
1595
|
-
if (!result || result.error) return null;
|
|
1596
|
-
|
|
1597
|
-
const content = result.result?.content;
|
|
1598
|
-
if (!content || !Array.isArray(content)) return null;
|
|
1599
|
-
|
|
1600
|
-
const textPart = content.find(c => c.type === 'text');
|
|
1601
|
-
if (!textPart || !textPart.text) return null;
|
|
1602
|
-
|
|
1603
|
-
const data = JSON.parse(textPart.text);
|
|
1604
|
-
const episodes = Array.isArray(data.episodes) ? data.episodes : [];
|
|
1605
|
-
if (episodes.length === 0) return null;
|
|
1606
|
-
|
|
1607
|
-
const sorted = episodes
|
|
1608
|
-
.map((episode) => episode && typeof episode === 'object' ? (episode.episode || episode) : null)
|
|
1609
|
-
.filter(Boolean)
|
|
1610
|
-
.filter((episode) => {
|
|
1611
|
-
const role = String(episode.agent_role || '').toLowerCase();
|
|
1612
|
-
const outcome = String(episode.outcome || '').toLowerCase();
|
|
1613
|
-
return role === 'ansuz' && outcome !== '' && outcome !== 'open';
|
|
1614
|
-
})
|
|
1615
|
-
.map((episode) => ({ episode, closedAt: parseIsoDate(episode.closed_at) }))
|
|
1616
|
-
.filter((item) => item.closedAt !== null)
|
|
1617
|
-
.sort((a, b) => b.closedAt.getTime() - a.closedAt.getTime());
|
|
1618
|
-
|
|
1619
|
-
if (sorted.length === 0) return null;
|
|
1620
|
-
return sorted[0].closedAt.toISOString();
|
|
1621
|
-
} catch (error) {
|
|
1622
|
-
if (Array.isArray(warnings)) {
|
|
1623
|
-
warnings.push(`git_context: hippocampus_episode_recall unavailable, using fallback_24h (${error.message})`);
|
|
1624
|
-
}
|
|
1625
|
-
return null;
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
|
|
1629
1447
|
function resolveGitContextSince(projectPath, warnings) {
|
|
1630
1448
|
const now = new Date();
|
|
1631
1449
|
const fallback = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
|
1632
|
-
|
|
1633
|
-
const lastClosedAt = parseIsoDate(lastClosedAtIso);
|
|
1634
|
-
|
|
1635
|
-
if (!lastClosedAt) {
|
|
1636
|
-
return {
|
|
1637
|
-
since_iso: fallback.toISOString(),
|
|
1638
|
-
since_source: 'fallback_24h',
|
|
1639
|
-
};
|
|
1640
|
-
}
|
|
1641
|
-
|
|
1450
|
+
// The hippocampus-based "last closed Ansuz session" lookup was removed in TASK_2411.
|
|
1642
1451
|
return {
|
|
1643
|
-
since_iso:
|
|
1644
|
-
since_source: '
|
|
1452
|
+
since_iso: fallback.toISOString(),
|
|
1453
|
+
since_source: 'fallback_24h',
|
|
1645
1454
|
};
|
|
1646
1455
|
}
|
|
1647
1456
|
|
|
@@ -2541,28 +2350,6 @@ function enterPhase(phase, taskId, specialistName = null) {
|
|
|
2541
2350
|
}
|
|
2542
2351
|
}
|
|
2543
2352
|
|
|
2544
|
-
// Phase 11: inscribe check. If chart / progress notes contain learning markers,
|
|
2545
|
-
// inscribe_log.jsonl MUST contain a synapse_inscribe record for the task.
|
|
2546
|
-
if (phase === 11) {
|
|
2547
|
-
let chartText = '';
|
|
2548
|
-
try { chartText = JSON.stringify(readChart(taskId)); } catch { /* ignore */ }
|
|
2549
|
-
if (chartText && /\b(learned|insight|surprise|non-obvious)\b/i.test(chartText)) {
|
|
2550
|
-
if (!inscribeLogHas(taskId)) {
|
|
2551
|
-
appendRitualLog('inscribe', 'skipped', { task_id: taskId, phase });
|
|
2552
|
-
return outputResult({
|
|
2553
|
-
phase,
|
|
2554
|
-
action: 'enter',
|
|
2555
|
-
modules_loaded: [],
|
|
2556
|
-
module_content: {},
|
|
2557
|
-
checklist: [],
|
|
2558
|
-
warnings: [],
|
|
2559
|
-
blocked: true,
|
|
2560
|
-
block_reason: `Phase 11 BLOCKED: session contains learning markers (learned|insight|surprise|non-obvious) but no synapse_inscribe record for ${taskId}. Call synapse_inscribe and append an entry to ${INSCRIBE_LOG_PATH} (fields: {task_id, rune_id, timestamp}) before entering Phase 11.`,
|
|
2561
|
-
});
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
|
|
2566
2353
|
// Load phase instructions and context hints (TASK_583)
|
|
2567
2354
|
const phaseInstructions = loadPhaseInstructions(phase);
|
|
2568
2355
|
if (phaseInstructions.warning) {
|
|
@@ -2622,11 +2409,6 @@ function enterPhase(phase, taskId, specialistName = null) {
|
|
|
2622
2409
|
};
|
|
2623
2410
|
}
|
|
2624
2411
|
|
|
2625
|
-
// Phase 0: hippocampus recall removed from phase-gate.
|
|
2626
|
-
// Ansuz calls hippocampus_episode_recall directly when episodic context is needed.
|
|
2627
|
-
// This keeps phase-gate Phase 0 output small (injected_rules like agent-workflow.yaml
|
|
2628
|
-
// are 20KB+ and are already part of the assembled Ansuz persona).
|
|
2629
|
-
|
|
2630
2412
|
// Phase 0: seed integrity check + merge (TASK_869)
|
|
2631
2413
|
let selfModelResult = null;
|
|
2632
2414
|
if (phase === 0) {
|
|
@@ -2838,7 +2620,6 @@ function enterPhase(phase, taskId, specialistName = null) {
|
|
|
2838
2620
|
exit_command: 'node .specialist/tools/phase-gate.js exit --phase=0 --data=<json>',
|
|
2839
2621
|
exit_required_fields: {
|
|
2840
2622
|
poem_displayed: true,
|
|
2841
|
-
ash_loaded: true,
|
|
2842
2623
|
session_handoff_loaded: true,
|
|
2843
2624
|
consent_uttered: true,
|
|
2844
2625
|
// Dynamic read — must equal ANSUZ_CONSENT_STRING exactly; never hard-code a copy
|
|
@@ -3740,7 +3521,6 @@ module.exports = {
|
|
|
3740
3521
|
dumpPhases,
|
|
3741
3522
|
loadPhaseInstructions,
|
|
3742
3523
|
buildContextHints,
|
|
3743
|
-
recallEpisodicMemory,
|
|
3744
3524
|
// TASK_869 seed protection
|
|
3745
3525
|
verifySeedIntegrity,
|
|
3746
3526
|
restoreSeedFromBackup,
|
|
@@ -54,14 +54,14 @@ test('checklists no longer ask for removed tools (preamble, thalamus, pulse, sea
|
|
|
54
54
|
for (const gone of ['synapse_preamble', 'preamble_loaded', 'Tier 2b', 'thalamus', 'synapse_pulse', 'synapse_search', 'synapse_impact']) {
|
|
55
55
|
assert.equal(all.includes(gone), false, gone);
|
|
56
56
|
}
|
|
57
|
-
assert.ok(pg.PHASE_CHECKLISTS[0].some(i => i.includes('cortex_recall')));
|
|
57
|
+
assert.ok(!pg.PHASE_CHECKLISTS[0].some(i => i.includes('cortex_recall') || i.includes('synapse_')));
|
|
58
58
|
assert.ok(pg.PHASE_CHECKLISTS[2].some(i => i.includes('kenaz_lsp_callers')));
|
|
59
59
|
assert.equal(JSON.stringify(pg.PHASE_CHECKLISTS).toLowerCase().includes('muninn'), false);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test('Phase 8 exit rule no longer requires predicted_symbols', () => {
|
|
63
63
|
assert.equal(pg.PHASE_EXIT_RULES[8], undefined);
|
|
64
|
-
assert.deepEqual(pg.PHASE_EXIT_RULES[1].required, ['
|
|
64
|
+
assert.deepEqual(pg.PHASE_EXIT_RULES[1].required, ['ddx_hypotheses']);
|
|
65
65
|
assert.deepEqual(pg.PHASE_EXIT_RULES[9].required, ['cognitive_checkpoint']);
|
|
66
66
|
});
|
|
67
67
|
|
|
@@ -68,7 +68,7 @@ function parseDocs(rel) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
test('corpus is non-trivial (guards against an empty git ls-files)', () => {
|
|
71
|
-
assert.ok(yamlFiles.length >
|
|
71
|
+
assert.ok(yamlFiles.length > 80, `only ${yamlFiles.length} YAML files found`);
|
|
72
72
|
assert.ok(liveFiles.length > 100, `only ${liveFiles.length} live instruction files found`);
|
|
73
73
|
});
|
|
74
74
|
|