kenaz 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-2288-followups.test.js +2 -37
- package/core/.collaboration/core/task-2421-replica.test.js +229 -0
- package/core/.collaboration/core/task-api.js +223 -230
- package/core/.collaboration/core/task-api.test.js +1 -138
- package/core/.collaboration/core/task-replica.js +189 -0
- package/core/.formation/.merge-lock +1 -0
- package/core/.formation/bifrost/codex-doctor.js +12 -2
- package/core/.formation/bifrost/codex-doctor.test.js +15 -0
- package/core/.formation/bifrost/launch.js +2 -2
- package/core/.formation/bifrost/launch.test.js +2 -2
- package/core/.formation/bifrost/setup-codex.js +84 -16
- 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/task2420.test.js +129 -0
- package/core/.formation/tools/post-complete/testing.js +3 -21
- package/core/.formation/tools/post-complete.js +131 -436
- package/core/.formation/tools/post-complete.test.js +0 -135
- package/core/.formation/tools/route-audit.test.js +8 -20
- package/core/.kenaz/specialist/memory_guard_fired.json +1 -1
- package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -1
- 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/__tests__/rea-detect.test.js +10 -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/rea-detect.js +6 -2
- package/core/.specialist/tools/session.js +2 -89
- package/core/.synapse/memory-shared.json.sync +1 -0
- 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 +75 -83
- package/core/dev/specs/peer/CODEX_MANAGED_DAEMON.md +54 -0
- package/core/dev/specs/peer/setup-codex-hooks.test.js +38 -0
- package/core/dev/spikes/task2145-evidence-20261004/backend-log-excerpt.txt +56 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph.shadow +0 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph.wal +0 -0
- package/core/dev/spikes/task2145-evidence-20261004/graph_corrupt_suspect.marker +1 -0
- package/core/plugins/kenaz/INVENTORY.md +1 -1
- package/core/plugins/kenaz/scripts/kenaz.test.js +70 -1
- package/core/plugins/kenaz/scripts/ygg.js +45 -4
- 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 +17 -9
- package/lib/cli.test.js +10 -0
- package/lib/dashboard.test.js +3 -3
- package/package.json +6 -6
- package/vendor/install-kenaz-cli.js +167 -16
- package/core/.claude/hooks/handoff-snapshot-on-decision.js +0 -188
- package/core/.claude/skills/synapse_maintain/SKILL.md +0 -84
- package/core/.collaboration/core/task-2264-single-after-capture.test.js +0 -61
- 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,156 +0,0 @@
|
|
|
1
|
-
# Memory Recall Trigger Map (TASK_1077)
|
|
2
|
-
|
|
3
|
-
Canonical mapping of specialist decision points to required recall operations,
|
|
4
|
-
source types, and conflict resolution. This document is the authoritative trigger
|
|
5
|
-
contract. Telemetry instrumentation (`memory-action-log.js`, `acted_on` events)
|
|
6
|
-
is built on top of this contract by TASK_1072 — do not mix the layers.
|
|
7
|
-
|
|
8
|
-
**Machine-consumable counterpart:**
|
|
9
|
-
`.kenaz/specialist/memory_source_priority.yaml`
|
|
10
|
-
|
|
11
|
-
**Rule enforcement:**
|
|
12
|
-
`.rule/general/memory-source-priority.yaml` (MSP-001 through MSP-005)
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Core Principle
|
|
17
|
-
|
|
18
|
-
> Memory is only valuable when it changes a decision.
|
|
19
|
-
|
|
20
|
-
Every recall call has a **decision point** on the other side. If you cannot
|
|
21
|
-
name the decision the recall will inform, you are loading context for show.
|
|
22
|
-
The trigger map below makes this concrete: for each phase or moment, it
|
|
23
|
-
specifies _what_ to recall, _from which source_, and _what happens next_.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Trigger Map
|
|
28
|
-
|
|
29
|
-
| Phase / Moment | Tool | Sources Surfaced | Required vs Optional | Conflict Mode |
|
|
30
|
-
|---|---|---|---|---|
|
|
31
|
-
| **Phase 0 — Boot** | `cortex_recall(type=ash, limit=5)` | `summary_or_ash_rune` | Required | advisory |
|
|
32
|
-
| **Phase 0 — Boot** | `synapse_sigils(projectPath)` | `sigil` | Required | advisory |
|
|
33
|
-
| **Phase 0 — Boot** | `hippocampus_episode_recall(limit=3)` | `episode`, `session_handoff` | Conditional (prior session exists) | advisory |
|
|
34
|
-
| **Phase 0 — Boot** | Read `ansuz_self_model.json` | `self_model_lesson` | Required | advisory |
|
|
35
|
-
| **Phase 0 — Boot** | Auto-load handoff slot | `session_handoff` | Conditional (fatigue_warning in prior session) | advisory |
|
|
36
|
-
| **Phase 1 — Requirements** | `synapse_recall(query=task_title+scope)` | `insight_or_pattern_rune`, `risk_or_fix_rune`, `episode` | Required | advisory |
|
|
37
|
-
| **Before editing a symbol** | `synapse_recall(symbol_name)` + `kenaz_lsp_callers` | `risk_or_fix_rune`, `verified_decision_rune` | Required (non-trivial changes) | strict (risk/fix) |
|
|
38
|
-
| **Phase 3 — Decomposition** | `kenaz_lsp_callers(scope symbols)` | `risk_or_fix_rune`, `sigil` | Required | strict (risk/fix) |
|
|
39
|
-
| **Phase 8 — Dispatch** | `synapse_recall(dispatch patterns)` | `sigil`, `self_model_lesson`, `session_handoff` | Optional | advisory |
|
|
40
|
-
| **Phase 9 — Verification** | `git diff --stat` + `synapse_recall(symbols)` | `risk_or_fix_rune`, `sigil` | Required | strict (risk) |
|
|
41
|
-
| **Retry / Bail decision** | `synapse_recall(error signature)` | `risk_or_fix_rune`, `episode` | Required | strict (fix) |
|
|
42
|
-
| **Phase-gate blocked exit** | `lesson_guards` auto-surfaced | `lesson_guard`, `session_handoff` | Auto (phase-gate handles) | advisory |
|
|
43
|
-
|
|
44
|
-
### Conditional triggers (detail)
|
|
45
|
-
|
|
46
|
-
- **Handoff load (Phase 0):** Only if a per-session slot exists under
|
|
47
|
-
`.kenaz/specialist/handoffs/` (newest `generated_at` wins — resolved via
|
|
48
|
-
`handoff-resolver.js readLatestHandoff`; the legacy singular
|
|
49
|
-
`ansuz_session_handoff.json` is retired read-only fallback, M3/TASK_1760)
|
|
50
|
-
AND its `generated_at` timestamp is within the `ttl_hours: 72` window. Stale handoffs
|
|
51
|
-
are ignored (they contain context from a cycle too far in the past).
|
|
52
|
-
|
|
53
|
-
- **Episode recall (Phase 0):** Only when `hippocampus_episode_recall` returns ≥ 1 result.
|
|
54
|
-
If the project has no episodes yet (cold start), skip silently — do not error.
|
|
55
|
-
|
|
56
|
-
- **Phase 1 deep episode drill:** If Phase 0 episode summary mentions a `task_id` relevant
|
|
57
|
-
to current requirements, call `hippocampus_episode_recall(task_id=that_id)` to get full entries.
|
|
58
|
-
|
|
59
|
-
- **synapse_recall before edit:** May skip for trivial changes (config value, typo, < 5 lines).
|
|
60
|
-
Judgment required. If skipping, note it explicitly (FAIL-009 records past recall-skips that
|
|
61
|
-
caused regressions; pulse itself is retired, recall is the replacement).
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## Decision-Point Checklist
|
|
66
|
-
|
|
67
|
-
Before a recalled item can be treated as **acted-on** (influencing a decision),
|
|
68
|
-
confirm all three:
|
|
69
|
-
|
|
70
|
-
1. **Name the specific recalled item.**
|
|
71
|
-
`source_id` = rune_id, episode_id, sigil_id, guard id (L-XXX), blind-spot id (B-XXX),
|
|
72
|
-
handoff `session_id`, or self_model `growth` entry hash.
|
|
73
|
-
|
|
74
|
-
2. **The recall changed what you were about to do.**
|
|
75
|
-
State the "before" plan and the "after" plan in one sentence each. If the plan
|
|
76
|
-
did not change, the recall is informational only — still worth noting, but not acted-on.
|
|
77
|
-
|
|
78
|
-
3. **The reason explains the delta, not the lookup.**
|
|
79
|
-
"Recalled L-003" is not evidence.
|
|
80
|
-
"L-003 flagged scope expansion requires a sub-task — created TASK_1077.1" is evidence.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Conflict Resolution Contract
|
|
85
|
-
|
|
86
|
-
When two sources disagree (e.g. an episode says "retry", a rune says "bail"):
|
|
87
|
-
|
|
88
|
-
1. Apply `priority_rank` from `memory_source_priority.yaml` — higher rank wins.
|
|
89
|
-
2. If same rank: apply `tie_breakers` in order (confidence DESC → freshness ASC → source_id ASC).
|
|
90
|
-
3. Apply the winner's `conflict_mode`:
|
|
91
|
-
- **strict** → hide the loser from the agent; log with `conflict_with: <winner_source_id>`
|
|
92
|
-
- **advisory** → show both; label the loser as "competing view (rank: N)"
|
|
93
|
-
4. If the agent disagrees with the winner's guidance, it must document the override reason
|
|
94
|
-
before proceeding (no silent overrides).
|
|
95
|
-
|
|
96
|
-
### Conflict examples
|
|
97
|
-
|
|
98
|
-
| Winner | Loser | Mode | What happens |
|
|
99
|
-
|---|---|---|---|
|
|
100
|
-
| `risk_or_fix_rune` (rank 60) | `episode` (rank 30) says "proceed" | strict | Agent follows rune; episode suppressed; override requires user statement |
|
|
101
|
-
| `session_handoff` (rank 80) | `insight_or_pattern_rune` (rank 40) | advisory | Both surfaced; agent chooses with documented reason |
|
|
102
|
-
| `verified_decision_rune` (rank 90) | `self_model_lesson` (rank 70) | strict | Agent follows decision rune; self-model view logged only |
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## Anti-Patterns
|
|
107
|
-
|
|
108
|
-
| Pattern | Why it fails |
|
|
109
|
-
|---|---|
|
|
110
|
-
| Recalling every source at every phase | Phase 0 boot cost grows unbounded; irrelevant context degrades signal |
|
|
111
|
-
| Skipping recall at phase boundaries | Misses the decision point; FAIL-009 shows this causes regressions |
|
|
112
|
-
| Treating lesson_guards as primary evidence | They are the *lowest* rank (10); trace back to the originating rune/episode |
|
|
113
|
-
| Loading Phase 0 handoff even when > 72h old | Stale handoffs inject obsolete context; TTL exists for this reason |
|
|
114
|
-
| "Recalling" from a source not actually queried | Incoherent audit trail; metrics show recall=0 / acted_on=1 |
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Minimal Integration Contract for New Specialists
|
|
119
|
-
|
|
120
|
-
A specialist flow integrating with this system needs exactly two behaviors:
|
|
121
|
-
|
|
122
|
-
1. **At each trigger boundary** (see Trigger Map above), call the listed tool.
|
|
123
|
-
Pass `projectPath` to every Synapse/Hippocampus call — omitting it causes
|
|
124
|
-
multi-session collision on the global default project.
|
|
125
|
-
|
|
126
|
-
2. **When a recalled item changes a decision**, mark it as acted-on via the
|
|
127
|
-
`memory_acted_on` field on phase-gate exit OR via `memory-action-log.js`
|
|
128
|
-
`recordActedOn()` directly (TASK_1072 adds this layer).
|
|
129
|
-
|
|
130
|
-
Both behaviors are independently useful: #1 is the contract defined here;
|
|
131
|
-
#2 is observability added by TASK_1072. Do not depend on #2 being present.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Rule References
|
|
136
|
-
|
|
137
|
-
| Rule | File | Scope |
|
|
138
|
-
|---|---|---|
|
|
139
|
-
| MSP-001 through MSP-005 | `.rule/general/memory-source-priority.yaml` | Priority ordering, conflict modes, required triggers |
|
|
140
|
-
| SYN-001 through SYN-007 | `.rule/general/synapse-code-intelligence.yaml` | Which Synapse tool at which moment |
|
|
141
|
-
| SYN-008 through SYN-011 | `.rule/general/synapse-cognitive-fusion.yaml` | When to promote recalled patterns to runes/sigils |
|
|
142
|
-
| MEM-LANG-001 | `.rule/general/memory-language.yaml` | All memory writes must be in English |
|
|
143
|
-
| DDx stack | `.rule/general/ddx-synapse-stack.yaml` | Recall before writing code (pulse-or-recall gate) |
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
## File Locations
|
|
148
|
-
|
|
149
|
-
| File | Purpose |
|
|
150
|
-
|---|---|
|
|
151
|
-
| `.kenaz/specialist/memory_source_priority.yaml` | Machine-consumable priority + conflict contract |
|
|
152
|
-
| `.specialist/tools/MEMORY_RECALL_TRIGGERS.md` | This file — human-readable trigger map |
|
|
153
|
-
| `.specialist/tools/memory-action-log.js` | Telemetry layer (TASK_1072) — `recordRecall` / `recordActedOn` |
|
|
154
|
-
| `.specialist/Ansuz/modules/recall.yaml` | Ansuz-specific recall protocol (Tier 1/2/3 boot sequence) |
|
|
155
|
-
| `.specialist/Ansuz/guards/lesson_guards.yaml` | L-XXX lesson guards surfaced by phase-gate |
|
|
156
|
-
| `.kenaz/specialist/memory_action_log.jsonl` | Append-only event log (TASK_1072) |
|
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* context-injection.js
|
|
4
|
-
* Phase-Aware Synapse 注入工具
|
|
5
|
-
*
|
|
6
|
-
* 根據當前 phase 輸出具體的 Synapse 查詢建議,把「執行 synapse_recall」
|
|
7
|
-
* 從模糊提醒變成具體查詢目標。
|
|
8
|
-
*
|
|
9
|
-
* CLI:
|
|
10
|
-
* node .specialist/tools/context-injection.js --phase=N --task=TASK_XXX [--project-path=PATH]
|
|
11
|
-
*
|
|
12
|
-
* Output: JSON to stdout
|
|
13
|
-
* { phase, task_id, synapse_suggestions: { ... } }
|
|
14
|
-
*
|
|
15
|
-
* Exit codes:
|
|
16
|
-
* 0 = success (including graceful degradation)
|
|
17
|
-
* 1 = task not found or fatal error
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
'use strict';
|
|
21
|
-
|
|
22
|
-
const path = require('path');
|
|
23
|
-
const fs = require('fs');
|
|
24
|
-
const { execSync } = require('child_process');
|
|
25
|
-
|
|
26
|
-
// ═══════════════════════════════════════
|
|
27
|
-
// Path resolution
|
|
28
|
-
// ═══════════════════════════════════════
|
|
29
|
-
|
|
30
|
-
function findProjectRoot() {
|
|
31
|
-
let dir = __dirname;
|
|
32
|
-
while (dir !== path.dirname(dir)) {
|
|
33
|
-
const parent = path.dirname(dir);
|
|
34
|
-
if (fs.existsSync(path.join(parent, '.specialist'))) return parent;
|
|
35
|
-
dir = parent;
|
|
36
|
-
}
|
|
37
|
-
return process.cwd();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const BASE_DIR = findProjectRoot();
|
|
41
|
-
const TASK_API = path.join(BASE_DIR, '.collaboration', 'core', 'task-api.js');
|
|
42
|
-
// TASK_2163: chart files are keyed by project identity + task id (sub-projects
|
|
43
|
-
// reuse bare TASK_ ids and previously collided in the flat core sessions dir).
|
|
44
|
-
const chartPathLib = require('./lib/chart-path');
|
|
45
|
-
|
|
46
|
-
// ═══════════════════════════════════════
|
|
47
|
-
// CLI arg parsing
|
|
48
|
-
// ═══════════════════════════════════════
|
|
49
|
-
|
|
50
|
-
function parseArgs(argv) {
|
|
51
|
-
const args = {};
|
|
52
|
-
for (const arg of argv.slice(2)) {
|
|
53
|
-
const m = arg.match(/^--([^=]+)(?:=(.*))?$/);
|
|
54
|
-
if (m) args[m[1]] = m[2] !== undefined ? m[2] : true;
|
|
55
|
-
}
|
|
56
|
-
return args;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ═══════════════════════════════════════
|
|
60
|
-
// Task loading via task-api.js
|
|
61
|
-
// ═══════════════════════════════════════
|
|
62
|
-
|
|
63
|
-
function loadTask(taskId, projectPath) {
|
|
64
|
-
const ppFlag = projectPath ? ` --project-path="${projectPath}"` : '';
|
|
65
|
-
const cmd = `node "${TASK_API}" get ${taskId}${ppFlag}`;
|
|
66
|
-
try {
|
|
67
|
-
const raw = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
68
|
-
return JSON.parse(raw.trim());
|
|
69
|
-
} catch (err) {
|
|
70
|
-
// task-api exits non-zero when not found, or JSON parse fails
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ═══════════════════════════════════════
|
|
76
|
-
// Chart loading
|
|
77
|
-
// ═══════════════════════════════════════
|
|
78
|
-
|
|
79
|
-
function loadChart(taskId, projectPath) {
|
|
80
|
-
const chartPath = chartPathLib.getChartReadPath(taskId, projectPath ? { projectPath } : {});
|
|
81
|
-
try {
|
|
82
|
-
const raw = fs.readFileSync(chartPath, 'utf8');
|
|
83
|
-
return JSON.parse(raw);
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// ═══════════════════════════════════════
|
|
90
|
-
// Symbol name inference
|
|
91
|
-
// ═══════════════════════════════════════
|
|
92
|
-
|
|
93
|
-
const GENERIC_FILENAMES = new Set(['mod', 'index', 'main', 'lib', 'utils', 'helpers', 'types', 'constants']);
|
|
94
|
-
|
|
95
|
-
function inferSymbolName(scopePath) {
|
|
96
|
-
// Normalize slashes
|
|
97
|
-
const normalized = scopePath.replace(/\\/g, '/');
|
|
98
|
-
const parts = normalized.split('/').filter(Boolean);
|
|
99
|
-
if (parts.length === 0) return null;
|
|
100
|
-
|
|
101
|
-
const lastPart = parts[parts.length - 1];
|
|
102
|
-
const stem = lastPart.replace(/\.[^.]+$/, ''); // remove extension
|
|
103
|
-
|
|
104
|
-
const nameSource = GENERIC_FILENAMES.has(stem) && parts.length >= 2
|
|
105
|
-
? parts[parts.length - 2].replace(/\.[^.]+$/, '')
|
|
106
|
-
: stem;
|
|
107
|
-
|
|
108
|
-
// underscore_case or kebab-case → PascalCase
|
|
109
|
-
return nameSource
|
|
110
|
-
.split(/[_\-]/)
|
|
111
|
-
.filter(Boolean)
|
|
112
|
-
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
|
113
|
-
.join('');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function inferSymbolsFromPaths(scopePaths) {
|
|
117
|
-
if (!scopePaths || scopePaths.length === 0) return [];
|
|
118
|
-
return scopePaths
|
|
119
|
-
.map(inferSymbolName)
|
|
120
|
-
.filter(Boolean)
|
|
121
|
-
.filter((v, i, a) => a.indexOf(v) === i); // deduplicate
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ═══════════════════════════════════════
|
|
125
|
-
// Research notes token extraction (Phase 3)
|
|
126
|
-
// ═══════════════════════════════════════
|
|
127
|
-
|
|
128
|
-
function extractFilePathTokens(text) {
|
|
129
|
-
if (!text) return [];
|
|
130
|
-
// Matches words containing / or . (file-path-like tokens)
|
|
131
|
-
const matches = text.match(/\b[\w\-]+[./][\w\-./]+\b/g) || [];
|
|
132
|
-
return matches
|
|
133
|
-
.filter(t => !t.match(/^\d+\.\d+/)) // skip version numbers like 1.2.3
|
|
134
|
-
.filter((v, i, a) => a.indexOf(v) === i);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function extractResearchNoteTokens(chart) {
|
|
138
|
-
if (!chart) return [];
|
|
139
|
-
const notes = chart?.history?.research_notes || '';
|
|
140
|
-
const text = Array.isArray(notes) ? notes.join(' ') : String(notes);
|
|
141
|
-
return extractFilePathTokens(text);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ═══════════════════════════════════════
|
|
145
|
-
// Phase handlers
|
|
146
|
-
// ═══════════════════════════════════════
|
|
147
|
-
|
|
148
|
-
function buildPhase1Suggestions(task) {
|
|
149
|
-
const scopePaths = task.scope_paths || [];
|
|
150
|
-
const tags = task.tags || [];
|
|
151
|
-
const description = task.description || task.title || '';
|
|
152
|
-
|
|
153
|
-
// Extract keywords: tags + meaningful words from description
|
|
154
|
-
const descWords = description
|
|
155
|
-
.toLowerCase()
|
|
156
|
-
.replace(/[^\w\s\-_]/g, ' ')
|
|
157
|
-
.split(/\s+/)
|
|
158
|
-
.filter(w => w.length >= 4 && !STOPWORDS.has(w));
|
|
159
|
-
|
|
160
|
-
const keywords = [...new Set([...tags, ...descWords])].slice(0, 10);
|
|
161
|
-
|
|
162
|
-
const query = keywords.join(' ') || task.title || task.id;
|
|
163
|
-
|
|
164
|
-
return {
|
|
165
|
-
recall_query: query,
|
|
166
|
-
recall_keywords: keywords,
|
|
167
|
-
hint: scopePaths.length === 0
|
|
168
|
-
? 'No scope_paths defined — using description/tags as recall seeds'
|
|
169
|
-
: `Recall before touching: ${scopePaths.join(', ')}`,
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function buildPhase2Suggestions(task) {
|
|
174
|
-
const scopePaths = task.scope_paths || [];
|
|
175
|
-
if (scopePaths.length === 0) {
|
|
176
|
-
return {
|
|
177
|
-
pulse_targets: [],
|
|
178
|
-
hint: 'scope_paths is empty - Grep/Glob for the task title keywords, then synapse_recall on what you find',
|
|
179
|
-
fallback_query: task.title || task.id,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
const pulseTargets = inferSymbolsFromPaths(scopePaths);
|
|
183
|
-
return {
|
|
184
|
-
pulse_targets: pulseTargets,
|
|
185
|
-
scope_paths: scopePaths,
|
|
186
|
-
hint: 'synapse_recall on scope_paths-inferred targets: '
|
|
187
|
-
+ (pulseTargets.length > 0 ? pulseTargets.join(', ') : 'Grep/Glob the scope_paths filenames first'),
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function buildPhase3Suggestions(task, chart) {
|
|
192
|
-
const scopePaths = task.scope_paths || [];
|
|
193
|
-
const fromPaths = inferSymbolsFromPaths(scopePaths);
|
|
194
|
-
const fromNotes = extractResearchNoteTokens(chart);
|
|
195
|
-
|
|
196
|
-
// Combine: path-derived symbols + file-path-like tokens from research notes
|
|
197
|
-
const impactTargets = [...fromPaths, ...fromNotes]
|
|
198
|
-
.filter((v, i, a) => a.indexOf(v) === i);
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
impact_targets: impactTargets,
|
|
202
|
-
scope_paths: scopePaths,
|
|
203
|
-
research_note_tokens: fromNotes,
|
|
204
|
-
hint: impactTargets.length > 0
|
|
205
|
-
? `Run kenaz_lsp_callers for each: ${impactTargets.join(', ')}`
|
|
206
|
-
: 'No impact targets inferred - Grep the scope_paths filenames, then kenaz_lsp_callers on the symbols found',
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function buildPhase9Suggestions(task) {
|
|
211
|
-
const scopePaths = task.scope_paths || [];
|
|
212
|
-
return {
|
|
213
|
-
detect_changes_scope: scopePaths,
|
|
214
|
-
hint: scopePaths.length === 0
|
|
215
|
-
? 'scope_paths is empty - review the full diff with git diff --stat'
|
|
216
|
-
: `Review the diff for: ${scopePaths.join(', ')} (git diff --stat -- <paths>), then synapse_recall on the touched symbols`,
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ═══════════════════════════════════════
|
|
221
|
-
// Recall telemetry (TASK_1078)
|
|
222
|
-
// ═══════════════════════════════════════
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Per-phase source types to emit recall telemetry for.
|
|
226
|
-
* Derived from .kenaz/specialist/memory_source_priority.yaml phase_bindings.
|
|
227
|
-
* Each entry: { source, rune_type?, label }
|
|
228
|
-
*/
|
|
229
|
-
const PHASE_RECALL_SOURCES = {
|
|
230
|
-
1: [
|
|
231
|
-
{ source: 'rune', rune_type: 'insight', label: 'insight_or_pattern_rune' },
|
|
232
|
-
{ source: 'rune', rune_type: 'pattern', label: 'insight_or_pattern_rune' },
|
|
233
|
-
{ source: 'sigil', label: 'sigil' },
|
|
234
|
-
],
|
|
235
|
-
2: [
|
|
236
|
-
{ source: 'rune', rune_type: 'risk', label: 'risk_or_fix_rune' },
|
|
237
|
-
{ source: 'rune', rune_type: 'fix', label: 'risk_or_fix_rune' },
|
|
238
|
-
{ source: 'sigil', label: 'sigil' },
|
|
239
|
-
],
|
|
240
|
-
3: [
|
|
241
|
-
{ source: 'rune', rune_type: 'risk', label: 'risk_or_fix_rune' },
|
|
242
|
-
{ source: 'rune', rune_type: 'fix', label: 'risk_or_fix_rune' },
|
|
243
|
-
{ source: 'sigil', label: 'sigil' },
|
|
244
|
-
{ source: 'rune', rune_type: 'decision', label: 'verified_decision_rune' },
|
|
245
|
-
],
|
|
246
|
-
9: [
|
|
247
|
-
{ source: 'rune', rune_type: 'risk', label: 'risk_or_fix_rune' },
|
|
248
|
-
{ source: 'rune', rune_type: 'fix', label: 'risk_or_fix_rune' },
|
|
249
|
-
{ source: 'episode', label: 'episode' },
|
|
250
|
-
],
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Emit recall telemetry events for the memory sources required by this phase.
|
|
255
|
-
* Uses a deterministic synthetic source_id so the same phase+task combination
|
|
256
|
-
* always generates the same recall records (idempotent within a session slot).
|
|
257
|
-
*
|
|
258
|
-
* Returns:
|
|
259
|
-
* { recalled: N, recall_ids: string[], sources: [{source, source_id, recall_id}] }
|
|
260
|
-
* or { recalled: 0, recall_ids: [], sources: [], error?: string } on failure.
|
|
261
|
-
*/
|
|
262
|
-
function emitRecallTelemetry(phase, taskId) {
|
|
263
|
-
const telemetry = { recalled: 0, recall_ids: [], sources: [] };
|
|
264
|
-
if (!taskId) return telemetry;
|
|
265
|
-
|
|
266
|
-
const phaseSources = PHASE_RECALL_SOURCES[phase];
|
|
267
|
-
if (!phaseSources || phaseSources.length === 0) return telemetry;
|
|
268
|
-
|
|
269
|
-
let memLog;
|
|
270
|
-
try {
|
|
271
|
-
const memLogPath = path.join(BASE_DIR, '.specialist', 'tools', 'memory-action-log.js');
|
|
272
|
-
if (!fs.existsSync(memLogPath)) return telemetry;
|
|
273
|
-
memLog = require(memLogPath);
|
|
274
|
-
} catch {
|
|
275
|
-
return telemetry;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
for (const src of phaseSources) {
|
|
279
|
-
try {
|
|
280
|
-
// Synthetic source_id: deterministic per phase+task+source type so
|
|
281
|
-
// list-recalls can group these as "context-injection-suggested sources"
|
|
282
|
-
const sourceId = `ctx-inject:phase${phase}:${src.label}`;
|
|
283
|
-
const entry = {
|
|
284
|
-
source: src.source,
|
|
285
|
-
source_id: sourceId,
|
|
286
|
-
task_id: taskId,
|
|
287
|
-
phase,
|
|
288
|
-
reason: `context-injection phase ${phase} loaded ${src.label} sources for task ${taskId}`,
|
|
289
|
-
rune_type: src.rune_type || null,
|
|
290
|
-
};
|
|
291
|
-
const r = memLog.recordRecall(entry);
|
|
292
|
-
if (r.ok) {
|
|
293
|
-
telemetry.recalled += 1;
|
|
294
|
-
telemetry.recall_ids.push(r.recall_id);
|
|
295
|
-
telemetry.sources.push({
|
|
296
|
-
source: src.source,
|
|
297
|
-
source_id: sourceId,
|
|
298
|
-
label: src.label,
|
|
299
|
-
recall_id: r.recall_id,
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
} catch { /* graceful degradation per source */ }
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return telemetry;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// ═══════════════════════════════════════
|
|
309
|
-
// Stopwords (for phase 1 keyword extraction)
|
|
310
|
-
// ═══════════════════════════════════════
|
|
311
|
-
|
|
312
|
-
const STOPWORDS = new Set([
|
|
313
|
-
'this', 'that', 'with', 'from', 'will', 'have', 'been', 'they',
|
|
314
|
-
'when', 'what', 'each', 'into', 'then', 'than', 'also', 'more',
|
|
315
|
-
'some', 'such', 'like', 'task', 'file', 'code', 'tool', 'node',
|
|
316
|
-
'spec', 'test', 'make', 'does', 'just', 'them', 'your', 'over',
|
|
317
|
-
'output', 'input', 'return', 'value', 'params', 'param', 'args',
|
|
318
|
-
]);
|
|
319
|
-
|
|
320
|
-
// ═══════════════════════════════════════
|
|
321
|
-
// Main
|
|
322
|
-
// ═══════════════════════════════════════
|
|
323
|
-
|
|
324
|
-
function main() {
|
|
325
|
-
const args = parseArgs(process.argv);
|
|
326
|
-
|
|
327
|
-
const phaseRaw = args['phase'];
|
|
328
|
-
const taskId = args['task'];
|
|
329
|
-
const projectPath = args['project-path'] || null;
|
|
330
|
-
|
|
331
|
-
const phase = phaseRaw !== undefined ? parseInt(phaseRaw, 10) : NaN;
|
|
332
|
-
|
|
333
|
-
if (!taskId) {
|
|
334
|
-
console.error(JSON.stringify({ error: 'missing --task argument', phase: phase || null }));
|
|
335
|
-
process.exit(1);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (isNaN(phase)) {
|
|
339
|
-
console.error(JSON.stringify({ error: 'missing or invalid --phase argument', task_id: taskId }));
|
|
340
|
-
process.exit(1);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Load task
|
|
344
|
-
const task = loadTask(taskId, projectPath);
|
|
345
|
-
|
|
346
|
-
if (!task) {
|
|
347
|
-
process.stdout.write(JSON.stringify({ error: 'task not found', phase, task_id: taskId }) + '\n');
|
|
348
|
-
process.exit(1);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
// Load chart (optional)
|
|
352
|
-
const chart = loadChart(taskId, projectPath);
|
|
353
|
-
|
|
354
|
-
// Build suggestions by phase
|
|
355
|
-
let synapse_suggestions = {};
|
|
356
|
-
|
|
357
|
-
switch (phase) {
|
|
358
|
-
case 1:
|
|
359
|
-
synapse_suggestions = { phase_1: buildPhase1Suggestions(task) };
|
|
360
|
-
break;
|
|
361
|
-
case 2:
|
|
362
|
-
synapse_suggestions = { phase_2: buildPhase2Suggestions(task) };
|
|
363
|
-
break;
|
|
364
|
-
case 3:
|
|
365
|
-
synapse_suggestions = { phase_3: buildPhase3Suggestions(task, chart) };
|
|
366
|
-
break;
|
|
367
|
-
case 9:
|
|
368
|
-
synapse_suggestions = { phase_9: buildPhase9Suggestions(task) };
|
|
369
|
-
break;
|
|
370
|
-
default:
|
|
371
|
-
// Other phases: empty suggestions, exit 0
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Emit recall telemetry for memory sources required by this phase (TASK_1078).
|
|
376
|
-
// Graceful — failure never blocks context-injection output.
|
|
377
|
-
const recallTelemetry = emitRecallTelemetry(phase, task.id || taskId);
|
|
378
|
-
|
|
379
|
-
const result = {
|
|
380
|
-
phase,
|
|
381
|
-
task_id: task.id || taskId,
|
|
382
|
-
task_title: task.title,
|
|
383
|
-
chart_loaded: chart !== null,
|
|
384
|
-
synapse_suggestions,
|
|
385
|
-
// recall_telemetry: stable recall_ids for this phase's required memory sources.
|
|
386
|
-
// Pass any of these recall_ids to memory-action-log.js record-acted --recalled-from=...
|
|
387
|
-
// to link a later decision back to the memory sources that shaped it.
|
|
388
|
-
recall_telemetry: recallTelemetry,
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
392
|
-
process.exit(0);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
main();
|