memorix 1.2.1 → 1.2.2
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/CHANGELOG.md +16 -0
- package/README.md +14 -2
- package/README.zh-CN.md +14 -2
- package/TEAM.md +86 -86
- package/dist/cli/index.js +15407 -13779
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1321 -529
- package/dist/index.js.map +1 -1
- package/dist/maintenance-runner.d.ts +1 -1
- package/dist/maintenance-runner.js +8458 -8087
- package/dist/maintenance-runner.js.map +1 -1
- package/dist/memcode-runtime/CHANGELOG.md +16 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +1349 -535
- package/dist/sdk.js.map +1 -1
- package/dist/types.d.ts +49 -1
- package/dist/types.js.map +1 -1
- package/docs/1.2.2-MEMORY-CONTROL-PLANE.md +434 -0
- package/docs/AGENT_OPERATOR_PLAYBOOK.md +4 -0
- package/docs/API_REFERENCE.md +24 -4
- package/docs/DESIGN_DECISIONS.md +357 -357
- package/docs/README.md +1 -1
- package/docs/dev-log/progress.txt +91 -11
- package/package.json +1 -1
- package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
- package/src/audit/index.ts +156 -156
- package/src/cli/command-guide.ts +192 -0
- package/src/cli/commands/audit-list.ts +89 -89
- package/src/cli/commands/audit.ts +9 -4
- package/src/cli/commands/background.ts +659 -659
- package/src/cli/commands/cleanup.ts +5 -1
- package/src/cli/commands/codegraph.ts +15 -5
- package/src/cli/commands/context.ts +3 -2
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/explain.ts +9 -3
- package/src/cli/commands/formation.ts +48 -48
- package/src/cli/commands/git-hook-install.ts +111 -111
- package/src/cli/commands/handoff.ts +75 -61
- package/src/cli/commands/hooks-status.ts +63 -63
- package/src/cli/commands/identity.ts +116 -0
- package/src/cli/commands/ingest-commit.ts +153 -153
- package/src/cli/commands/ingest-image.ts +71 -69
- package/src/cli/commands/ingest-log.ts +180 -180
- package/src/cli/commands/ingest.ts +44 -44
- package/src/cli/commands/integrate-shared.ts +15 -15
- package/src/cli/commands/lock.ts +93 -92
- package/src/cli/commands/memory.ts +58 -21
- package/src/cli/commands/message.ts +123 -118
- package/src/cli/commands/operator-shared.ts +98 -3
- package/src/cli/commands/poll.ts +74 -64
- package/src/cli/commands/purge-all-memory.ts +85 -85
- package/src/cli/commands/purge-project-memory.ts +83 -83
- package/src/cli/commands/reasoning.ts +135 -121
- package/src/cli/commands/retention.ts +9 -4
- package/src/cli/commands/serve-http.ts +8 -2
- package/src/cli/commands/serve-shared.ts +118 -118
- package/src/cli/commands/session.ts +29 -3
- package/src/cli/commands/skills.ts +124 -119
- package/src/cli/commands/status.ts +4 -3
- package/src/cli/commands/task.ts +193 -184
- package/src/cli/commands/team.ts +14 -10
- package/src/cli/commands/transfer.ts +108 -55
- package/src/cli/commands/uninstall-project-artifacts.ts +85 -85
- package/src/cli/identity.ts +89 -0
- package/src/cli/index.ts +96 -19
- package/src/cli/invocation.ts +115 -0
- package/src/cli/tui/ChatView.tsx +234 -234
- package/src/cli/tui/CommandBar.tsx +312 -312
- package/src/cli/tui/ContextRail.tsx +118 -118
- package/src/cli/tui/HeaderBar.tsx +72 -72
- package/src/cli/tui/LogoBanner.tsx +51 -51
- package/src/cli/tui/Sidebar.tsx +179 -179
- package/src/cli/tui/chat-service.ts +41 -18
- package/src/cli/tui/data.ts +23 -44
- package/src/cli/tui/index.ts +41 -41
- package/src/cli/tui/markdown-render.tsx +371 -371
- package/src/cli/tui/operator-context.ts +60 -0
- package/src/cli/tui/use-mouse.ts +157 -157
- package/src/cli/tui/useNavigation.ts +56 -56
- package/src/cli/tui/views/MemoryView.tsx +10 -8
- package/src/cli/update-checker.ts +211 -211
- package/src/cli/version.ts +7 -7
- package/src/cli/workbench.ts +1 -1
- package/src/codegraph/auto-context.ts +31 -2
- package/src/codegraph/context-pack.ts +1 -0
- package/src/codegraph/project-context.ts +2 -0
- package/src/compact/engine.ts +26 -10
- package/src/compact/index-format.ts +25 -2
- package/src/compact/token-budget.ts +74 -74
- package/src/dashboard/project-classification.ts +64 -64
- package/src/dashboard/server.ts +46 -9
- package/src/embedding/fastembed-provider.ts +142 -142
- package/src/embedding/transformers-provider.ts +111 -111
- package/src/git/extractor.ts +209 -209
- package/src/git/hooks-path.ts +85 -85
- package/src/hooks/admission.ts +117 -0
- package/src/hooks/handler.ts +98 -91
- package/src/hooks/pattern-detector.ts +173 -173
- package/src/hooks/significance-filter.ts +250 -250
- package/src/knowledge/context-assembly.ts +97 -0
- package/src/knowledge/workset.ts +179 -10
- package/src/llm/memory-manager.ts +328 -328
- package/src/llm/provider.ts +885 -885
- package/src/llm/quality.ts +248 -248
- package/src/memory/admission.ts +57 -0
- package/src/memory/attribution-guard.ts +249 -249
- package/src/memory/consolidation.ts +13 -2
- package/src/memory/disclosure-policy.ts +140 -135
- package/src/memory/entity-extractor.ts +197 -197
- package/src/memory/export-import.ts +11 -3
- package/src/memory/formation/evaluate.ts +217 -217
- package/src/memory/formation/extract.ts +361 -361
- package/src/memory/formation/index.ts +417 -417
- package/src/memory/formation/resolve.ts +344 -344
- package/src/memory/formation/types.ts +315 -315
- package/src/memory/freshness.ts +122 -122
- package/src/memory/graph-context.ts +8 -2
- package/src/memory/graph.ts +197 -197
- package/src/memory/observations.ts +162 -4
- package/src/memory/quality-audit.ts +2 -0
- package/src/memory/refs.ts +94 -94
- package/src/memory/retention.ts +22 -2
- package/src/memory/secret-filter.ts +79 -79
- package/src/memory/session.ts +5 -2
- package/src/memory/visibility.ts +80 -0
- package/src/multimodal/image-loader.ts +143 -143
- package/src/orchestrate/adapters/claude-stream.ts +192 -192
- package/src/orchestrate/adapters/claude.ts +111 -111
- package/src/orchestrate/adapters/codex-stream.ts +134 -134
- package/src/orchestrate/adapters/codex.ts +41 -41
- package/src/orchestrate/adapters/gemini-stream.ts +166 -166
- package/src/orchestrate/adapters/gemini.ts +42 -42
- package/src/orchestrate/adapters/index.ts +73 -73
- package/src/orchestrate/adapters/opencode-stream.ts +143 -143
- package/src/orchestrate/adapters/opencode.ts +47 -47
- package/src/orchestrate/adapters/spawn-helper.ts +286 -286
- package/src/orchestrate/adapters/types.ts +77 -77
- package/src/orchestrate/capability-router.ts +284 -284
- package/src/orchestrate/context-compact.ts +188 -188
- package/src/orchestrate/cost-tracker.ts +219 -219
- package/src/orchestrate/error-recovery.ts +191 -191
- package/src/orchestrate/evidence.ts +140 -140
- package/src/orchestrate/ledger.ts +110 -110
- package/src/orchestrate/memorix-bridge.ts +378 -340
- package/src/orchestrate/output-budget.ts +80 -80
- package/src/orchestrate/permission.ts +152 -152
- package/src/orchestrate/pipeline-trace.ts +131 -131
- package/src/orchestrate/prompt-builder.ts +155 -155
- package/src/orchestrate/ring-buffer.ts +37 -37
- package/src/orchestrate/task-graph.ts +389 -389
- package/src/orchestrate/worktree.ts +232 -232
- package/src/project/aliases.ts +374 -374
- package/src/project/detector.ts +268 -268
- package/src/rules/adapters/claude-code.ts +99 -99
- package/src/rules/adapters/codex.ts +97 -97
- package/src/rules/adapters/copilot.ts +124 -124
- package/src/rules/adapters/cursor.ts +114 -114
- package/src/rules/adapters/kiro.ts +126 -126
- package/src/rules/adapters/trae.ts +56 -56
- package/src/rules/adapters/windsurf.ts +83 -83
- package/src/rules/syncer.ts +235 -235
- package/src/runtime/control-plane-maintenance.ts +1 -0
- package/src/runtime/isolated-maintenance.ts +1 -0
- package/src/runtime/lifecycle.ts +18 -0
- package/src/runtime/maintenance-jobs.ts +1 -0
- package/src/runtime/maintenance-runner.ts +2 -0
- package/src/runtime/project-maintenance.ts +89 -0
- package/src/sdk.ts +334 -304
- package/src/search/intent-detector.ts +289 -289
- package/src/search/query-expansion.ts +52 -52
- package/src/server/formation-timeout.ts +27 -27
- package/src/server.ts +260 -81
- package/src/skills/mini-skills.ts +386 -386
- package/src/store/chat-store.ts +119 -119
- package/src/store/graph-store.ts +249 -249
- package/src/store/mini-skill-store.ts +349 -349
- package/src/store/orama-store.ts +61 -6
- package/src/store/persistence-json.ts +212 -212
- package/src/store/persistence.ts +291 -291
- package/src/store/project-affinity.ts +195 -195
- package/src/store/sqlite-db.ts +23 -1
- package/src/store/sqlite-store.ts +12 -2
- package/src/team/event-bus.ts +76 -76
- package/src/team/file-locks.ts +173 -173
- package/src/team/handoff.ts +168 -161
- package/src/team/messages.ts +203 -203
- package/src/team/poll.ts +132 -132
- package/src/team/tasks.ts +211 -211
- package/src/types.ts +51 -0
- package/src/wiki/generator.ts +2 -0
- package/src/workspace/mcp-adapters/codex.ts +191 -191
- package/src/workspace/mcp-adapters/copilot.ts +105 -105
- package/src/workspace/mcp-adapters/cursor.ts +53 -53
- package/src/workspace/mcp-adapters/kiro.ts +64 -64
- package/src/workspace/mcp-adapters/opencode.ts +123 -123
- package/src/workspace/mcp-adapters/trae.ts +134 -134
- package/src/workspace/mcp-adapters/windsurf.ts +91 -91
- package/src/workspace/sanitizer.ts +60 -60
- package/src/workspace/workflow-sync.ts +131 -131
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { ObservationAdmissionState, ObservationType } from '../types.js';
|
|
2
|
+
import { isSignificantKnowledge } from './significance-filter.js';
|
|
3
|
+
import type { NormalizedHookInput } from './types.js';
|
|
4
|
+
|
|
5
|
+
export type HookCaptureCategory =
|
|
6
|
+
| 'file_modify'
|
|
7
|
+
| 'file_read'
|
|
8
|
+
| 'command'
|
|
9
|
+
| 'search'
|
|
10
|
+
| 'memorix_internal'
|
|
11
|
+
| 'unknown';
|
|
12
|
+
|
|
13
|
+
export type HookAdmissionDecision =
|
|
14
|
+
| { action: 'drop'; reason: string }
|
|
15
|
+
| {
|
|
16
|
+
action: 'store';
|
|
17
|
+
admissionState: ObservationAdmissionState;
|
|
18
|
+
admissionReason: string;
|
|
19
|
+
valueCategory: 'core' | 'contextual' | 'ephemeral';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const FAILURE_PATTERN = /\b(fail(?:ed|ure)?|error|exception|assert(?:ion)?|exit code [1-9]|TypeError|ReferenceError|SyntaxError|ENOENT|ECONNREFUSED|panic)\b/i;
|
|
23
|
+
const DECISION_PATTERN = /\b(decision|decided|choose|chosen|because|trade-?off|invariant|root cause|workaround|must|should not)\b/i;
|
|
24
|
+
const TASK_PATTERN = /\b(add|implement|fix|refactor|migrate|upgrade|debug|investigate|test|release|deploy|remove)\b/i;
|
|
25
|
+
const VALIDATION_PATTERN = /\b(test|tests|vitest|jest|pytest|playwright|cypress|typecheck|lint|build|cargo test|go test)\b/i;
|
|
26
|
+
const FOCUSED_VALIDATION_PATTERN = /(?:--run|--filter|--grep|\s-t\s|\.test\.|\.spec\.|::[\w-]+)/i;
|
|
27
|
+
const ROUTINE_SUCCESS_PATTERN = /\b(added \d+ packages|up to date|passed|all tests pass|tests? passed|0 failures|success(?:fully)?)\b/i;
|
|
28
|
+
|
|
29
|
+
function isCandidateType(type: ObservationType): boolean {
|
|
30
|
+
return type === 'decision' ||
|
|
31
|
+
type === 'gotcha' ||
|
|
32
|
+
type === 'problem-solution' ||
|
|
33
|
+
type === 'trade-off' ||
|
|
34
|
+
type === 'why-it-exists';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function candidate(
|
|
38
|
+
admissionReason: string,
|
|
39
|
+
valueCategory: 'core' | 'contextual' = 'contextual',
|
|
40
|
+
): HookAdmissionDecision {
|
|
41
|
+
return { action: 'store', admissionState: 'candidate', admissionReason, valueCategory };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ephemeral(admissionReason: string): HookAdmissionDecision {
|
|
45
|
+
return { action: 'store', admissionState: 'ephemeral', admissionReason, valueCategory: 'ephemeral' };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Cheap, deterministic first-pass admission for cross-agent hooks. It never
|
|
50
|
+
* promotes an automatic record directly to durable context: background
|
|
51
|
+
* qualification must still prove a current Code Memory reference.
|
|
52
|
+
*/
|
|
53
|
+
export function assessHookAdmission(input: {
|
|
54
|
+
hook: NormalizedHookInput;
|
|
55
|
+
category: HookCaptureCategory;
|
|
56
|
+
content: string;
|
|
57
|
+
observationType: ObservationType;
|
|
58
|
+
}): HookAdmissionDecision {
|
|
59
|
+
const { hook, category, content, observationType } = input;
|
|
60
|
+
const command = hook.command ?? '';
|
|
61
|
+
const hasFailure = FAILURE_PATTERN.test(content);
|
|
62
|
+
const hasDecision = DECISION_PATTERN.test(content) || isCandidateType(observationType);
|
|
63
|
+
const isValidation = VALIDATION_PATTERN.test(command) || VALIDATION_PATTERN.test(content);
|
|
64
|
+
const isFocusedValidation = FOCUSED_VALIDATION_PATTERN.test(command);
|
|
65
|
+
const isRoutineSuccess = ROUTINE_SUCCESS_PATTERN.test(content);
|
|
66
|
+
|
|
67
|
+
if (hasFailure) {
|
|
68
|
+
return candidate('automatic capture contains a concrete failure or verification signal');
|
|
69
|
+
}
|
|
70
|
+
if (hasDecision) {
|
|
71
|
+
return candidate('automatic capture contains a decision or durable rationale', 'core');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (hook.event === 'user_prompt') {
|
|
75
|
+
return TASK_PATTERN.test(content)
|
|
76
|
+
? candidate('user task contains a concrete technical action')
|
|
77
|
+
: { action: 'drop', reason: 'prompt has no durable technical task signal' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (hook.event === 'post_response') {
|
|
81
|
+
// Agent integrations such as OpenCode emit concise end-of-turn summaries.
|
|
82
|
+
// Keep technically meaningful ones as candidates; qualification still
|
|
83
|
+
// prevents them from becoming automatic context without code evidence.
|
|
84
|
+
return isSignificantKnowledge(content).isSignificant
|
|
85
|
+
? candidate('assistant response records a concrete technical outcome')
|
|
86
|
+
: { action: 'drop', reason: 'response has no durable technical outcome' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (hook.event === 'session_end') {
|
|
90
|
+
// The handler already applies a minimum-content gate for session end.
|
|
91
|
+
// Preserve a compact handoff trace, but keep it out of automatic context
|
|
92
|
+
// until later qualification.
|
|
93
|
+
return candidate('session-end summary awaits Code Memory qualification');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (category === 'file_modify') {
|
|
97
|
+
return candidate('file mutation awaits Code Memory qualification');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (category === 'command') {
|
|
101
|
+
if (isValidation && isFocusedValidation && !isRoutineSuccess) {
|
|
102
|
+
return candidate('focused validation result awaits Code Memory qualification');
|
|
103
|
+
}
|
|
104
|
+
if (isValidation || isRoutineSuccess) {
|
|
105
|
+
return ephemeral('routine command result retained only as a short-lived trace');
|
|
106
|
+
}
|
|
107
|
+
return ephemeral('command activity retained only as a short-lived trace');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (category === 'search' || category === 'unknown') {
|
|
111
|
+
return content.length >= 300
|
|
112
|
+
? ephemeral('unverified automatic activity retained only as a short-lived trace')
|
|
113
|
+
: { action: 'drop', reason: 'automatic activity has insufficient evidence' };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return { action: 'drop', reason: 'capture category is not eligible for automatic storage' };
|
|
117
|
+
}
|
package/src/hooks/handler.ts
CHANGED
|
@@ -2,15 +2,21 @@
|
|
|
2
2
|
* Hook Handler
|
|
3
3
|
*
|
|
4
4
|
* Unified entry point for all agent hooks.
|
|
5
|
-
* Architecture: Normalize → Classify →
|
|
5
|
+
* Architecture: Normalize → Classify → Admit → Store → Respond
|
|
6
6
|
*
|
|
7
7
|
* Design principles (inspired by claude-mem + mcp-memory-service):
|
|
8
|
-
* -
|
|
8
|
+
* - Candidate-first: automatic capture is never durable context by default
|
|
9
9
|
* - Tool Taxonomy: declarative policies per tool category
|
|
10
10
|
* - Pattern = classification only: determines observation type, not storage
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
13
14
|
import type { ObservationType } from '../types.js';
|
|
15
|
+
import {
|
|
16
|
+
assessHookAdmission,
|
|
17
|
+
type HookAdmissionDecision,
|
|
18
|
+
type HookCaptureCategory,
|
|
19
|
+
} from './admission.js';
|
|
14
20
|
import { normalizeHookInput } from './normalizer.js';
|
|
15
21
|
import { detectBestPattern, patternToObservationType } from './pattern-detector.js';
|
|
16
22
|
import { isSignificantKnowledge, isRetrievedResult, isTrivialCommand } from './significance-filter.js';
|
|
@@ -35,6 +41,11 @@ const MIN_PROMPT_LENGTH = 20;
|
|
|
35
41
|
/** Max content length (truncate beyond this) */
|
|
36
42
|
const MAX_CONTENT_LENGTH = 4000;
|
|
37
43
|
|
|
44
|
+
function deriveHookActorId(input: NormalizedHookInput): string {
|
|
45
|
+
const material = `${input.agent ?? 'unknown'}\u0000${input.sessionId ?? 'unknown'}`;
|
|
46
|
+
return `hook:${createHash('sha256').update(material).digest('hex').slice(0, 24)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
/** Truly trivial commands — standalone navigation/inspection only */
|
|
39
50
|
const NOISE_COMMANDS = [
|
|
40
51
|
/^(ls|dir|cd|pwd|echo|cat|type|head|tail|wc|which|where|whoami)(\s|$)/i,
|
|
@@ -49,7 +60,7 @@ const NOISE_COMMANDS = [
|
|
|
49
60
|
// ─── Tool Taxonomy ───
|
|
50
61
|
|
|
51
62
|
/** Tool categories for storage policy */
|
|
52
|
-
type ToolCategory =
|
|
63
|
+
type ToolCategory = HookCaptureCategory;
|
|
53
64
|
|
|
54
65
|
/** Storage policy per tool category */
|
|
55
66
|
interface StoragePolicy {
|
|
@@ -233,7 +244,12 @@ function generateTitle(input: NormalizedHookInput, patternType: string): string
|
|
|
233
244
|
return `Activity (${patternType})`;
|
|
234
245
|
}
|
|
235
246
|
|
|
236
|
-
function buildObservation(
|
|
247
|
+
function buildObservation(
|
|
248
|
+
input: NormalizedHookInput,
|
|
249
|
+
content: string,
|
|
250
|
+
category: ToolCategory,
|
|
251
|
+
admission?: Extract<HookAdmissionDecision, { action: 'store' }>,
|
|
252
|
+
) {
|
|
237
253
|
const pattern = detectBestPattern(content);
|
|
238
254
|
const policy = STORAGE_POLICY[category] ?? STORAGE_POLICY.unknown;
|
|
239
255
|
const fallbackType = input.filePath ? 'what-changed' : policy.defaultType;
|
|
@@ -252,6 +268,15 @@ function buildObservation(input: NormalizedHookInput, content: string, category:
|
|
|
252
268
|
],
|
|
253
269
|
concepts: pattern?.matchedKeywords ?? [],
|
|
254
270
|
filesModified: input.filePath ? [input.filePath] : [],
|
|
271
|
+
...(admission ? {
|
|
272
|
+
valueCategory: admission.valueCategory,
|
|
273
|
+
admissionState: admission.admissionState,
|
|
274
|
+
admissionReason: admission.admissionReason,
|
|
275
|
+
// Automatic capture is private until current code can qualify it for
|
|
276
|
+
// shared project delivery. The opaque actor is stable within a hook session.
|
|
277
|
+
visibility: 'personal' as const,
|
|
278
|
+
createdByAgentId: deriveHookActorId(input),
|
|
279
|
+
} : {}),
|
|
255
280
|
};
|
|
256
281
|
}
|
|
257
282
|
|
|
@@ -281,7 +306,9 @@ async function handleSessionStart(input: NormalizedHookInput): Promise<{
|
|
|
281
306
|
const { initMiniSkillStore } = await import('../store/mini-skill-store.js');
|
|
282
307
|
const { initSessionStore } = await import('../store/session-store.js');
|
|
283
308
|
const { initAliasRegistry, registerAlias } = await import('../project/aliases.js');
|
|
309
|
+
const { MaintenanceTargetStore } = await import('../runtime/maintenance-targets.js');
|
|
284
310
|
const { buildAutoProjectContext, formatAutoProjectContextPrompt } = await import('../codegraph/auto-context.js');
|
|
311
|
+
const { filterReadableObservations } = await import('../memory/visibility.js');
|
|
285
312
|
|
|
286
313
|
const rawProject = detectProject(input.cwd || process.cwd());
|
|
287
314
|
if (!rawProject) throw new Error('No .git found');
|
|
@@ -289,10 +316,18 @@ async function handleSessionStart(input: NormalizedHookInput): Promise<{
|
|
|
289
316
|
|
|
290
317
|
initAliasRegistry(dataDir);
|
|
291
318
|
const canonicalId = await registerAlias(rawProject);
|
|
319
|
+
new MaintenanceTargetStore(dataDir).register({
|
|
320
|
+
projectId: canonicalId,
|
|
321
|
+
projectRoot: rawProject.rootPath,
|
|
322
|
+
dataDir,
|
|
323
|
+
});
|
|
292
324
|
await initObservationStore(dataDir);
|
|
293
325
|
await initMiniSkillStore(dataDir);
|
|
294
326
|
await initSessionStore(dataDir);
|
|
295
|
-
const activeObservations =
|
|
327
|
+
const activeObservations = filterReadableObservations(
|
|
328
|
+
await getStore().loadByProject(canonicalId, { status: 'active' }),
|
|
329
|
+
{ projectId: canonicalId },
|
|
330
|
+
);
|
|
296
331
|
const context = await buildAutoProjectContext({
|
|
297
332
|
project: { ...rawProject, id: canonicalId },
|
|
298
333
|
dataDir,
|
|
@@ -306,6 +341,7 @@ async function handleSessionStart(input: NormalizedHookInput): Promise<{
|
|
|
306
341
|
maxFiles: 5_000,
|
|
307
342
|
});
|
|
308
343
|
}),
|
|
344
|
+
deliveryTarget: 'hook-session-start',
|
|
309
345
|
});
|
|
310
346
|
contextSummary = `\n\n${formatAutoProjectContextPrompt(context)}`;
|
|
311
347
|
} catch (sessErr) {
|
|
@@ -352,8 +388,17 @@ async function handleHookEventCore(input: NormalizedHookInput): Promise<{
|
|
|
352
388
|
if (endContent.length < 50) {
|
|
353
389
|
return { observation: null, output: defaultOutput };
|
|
354
390
|
}
|
|
391
|
+
const draft = buildObservation(input, endContent, 'unknown');
|
|
392
|
+
const admission = assessHookAdmission({
|
|
393
|
+
hook: input,
|
|
394
|
+
category: 'unknown',
|
|
395
|
+
content: endContent,
|
|
396
|
+
observationType: draft.type,
|
|
397
|
+
});
|
|
355
398
|
return {
|
|
356
|
-
observation:
|
|
399
|
+
observation: admission.action === 'store'
|
|
400
|
+
? buildObservation(input, endContent, 'unknown', admission)
|
|
401
|
+
: null,
|
|
357
402
|
output: defaultOutput,
|
|
358
403
|
};
|
|
359
404
|
}
|
|
@@ -425,8 +470,17 @@ async function handleHookEventCore(input: NormalizedHookInput): Promise<{
|
|
|
425
470
|
}
|
|
426
471
|
markTriggered(cooldownKey);
|
|
427
472
|
|
|
473
|
+
const draft = buildObservation(input, content, category);
|
|
474
|
+
const admission = assessHookAdmission({
|
|
475
|
+
hook: input,
|
|
476
|
+
category,
|
|
477
|
+
content,
|
|
478
|
+
observationType: draft.type,
|
|
479
|
+
});
|
|
428
480
|
return {
|
|
429
|
-
observation:
|
|
481
|
+
observation: admission.action === 'store'
|
|
482
|
+
? buildObservation(input, content, category, admission)
|
|
483
|
+
: null,
|
|
430
484
|
output: defaultOutput,
|
|
431
485
|
};
|
|
432
486
|
}
|
|
@@ -440,17 +494,24 @@ async function queueCodegraphRefreshForMutation(input: NormalizedHookInput): Pro
|
|
|
440
494
|
{ getProjectDataDir },
|
|
441
495
|
{ initAliasRegistry, registerAlias },
|
|
442
496
|
{ enqueueCodegraphRefresh },
|
|
497
|
+
{ MaintenanceTargetStore },
|
|
443
498
|
] = await Promise.all([
|
|
444
499
|
import('../project/detector.js'),
|
|
445
500
|
import('../store/persistence.js'),
|
|
446
501
|
import('../project/aliases.js'),
|
|
447
502
|
import('../runtime/lifecycle.js'),
|
|
503
|
+
import('../runtime/maintenance-targets.js'),
|
|
448
504
|
]);
|
|
449
505
|
const project = detectProject(input.cwd || process.cwd());
|
|
450
506
|
if (!project) return;
|
|
451
507
|
const dataDir = await getProjectDataDir(project.id);
|
|
452
508
|
initAliasRegistry(dataDir);
|
|
453
509
|
const projectId = await registerAlias(project);
|
|
510
|
+
new MaintenanceTargetStore(dataDir).register({
|
|
511
|
+
projectId,
|
|
512
|
+
projectRoot: project.rootPath,
|
|
513
|
+
dataDir,
|
|
514
|
+
});
|
|
454
515
|
enqueueCodegraphRefresh({
|
|
455
516
|
dataDir,
|
|
456
517
|
projectId,
|
|
@@ -465,11 +526,25 @@ async function queueCodegraphRefreshForMutation(input: NormalizedHookInput): Pro
|
|
|
465
526
|
export async function handleHookEvent(input: NormalizedHookInput): Promise<{
|
|
466
527
|
observation: ReturnType<typeof buildObservation> | null;
|
|
467
528
|
output: HookOutput;
|
|
529
|
+
}>;
|
|
530
|
+
export async function handleHookEvent(input: NormalizedHookInput, options: {
|
|
531
|
+
deferMaintenance?: boolean;
|
|
532
|
+
}): Promise<{
|
|
533
|
+
observation: ReturnType<typeof buildObservation> | null;
|
|
534
|
+
output: HookOutput;
|
|
535
|
+
}>;
|
|
536
|
+
export async function handleHookEvent(input: NormalizedHookInput, options: {
|
|
537
|
+
deferMaintenance?: boolean;
|
|
538
|
+
} = {}): Promise<{
|
|
539
|
+
observation: ReturnType<typeof buildObservation> | null;
|
|
540
|
+
output: HookOutput;
|
|
468
541
|
}> {
|
|
469
542
|
try {
|
|
470
543
|
return await handleHookEventCore(input);
|
|
471
544
|
} finally {
|
|
472
|
-
|
|
545
|
+
// The CLI persists an automatic observation after this function returns.
|
|
546
|
+
// It defers scheduling so a fast worker never scans before that write.
|
|
547
|
+
if (!options.deferMaintenance) await queueCodegraphRefreshForMutation(input);
|
|
473
548
|
}
|
|
474
549
|
}
|
|
475
550
|
|
|
@@ -570,7 +645,7 @@ export async function runHook(agentOverride?: string, eventOverride?: string): P
|
|
|
570
645
|
}
|
|
571
646
|
|
|
572
647
|
const input = normalizeHookInput(payload);
|
|
573
|
-
const { observation, output } = await handleHookEvent(input);
|
|
648
|
+
const { observation, output } = await handleHookEvent(input, { deferMaintenance: true });
|
|
574
649
|
|
|
575
650
|
if (observation) {
|
|
576
651
|
try {
|
|
@@ -581,6 +656,7 @@ export async function runHook(agentOverride?: string, eventOverride?: string): P
|
|
|
581
656
|
const { detectProject } = await import('../project/detector.js');
|
|
582
657
|
const { getProjectDataDir } = await import('../store/persistence.js');
|
|
583
658
|
const { initAliasRegistry, registerAlias } = await import('../project/aliases.js');
|
|
659
|
+
const { MaintenanceTargetStore } = await import('../runtime/maintenance-targets.js');
|
|
584
660
|
|
|
585
661
|
const rawProject = detectProject(input.cwd || process.cwd());
|
|
586
662
|
if (!rawProject) throw new Error('No .git found');
|
|
@@ -590,94 +666,20 @@ export async function runHook(agentOverride?: string, eventOverride?: string): P
|
|
|
590
666
|
initAliasRegistry(dataDir);
|
|
591
667
|
const canonicalId = await registerAlias(rawProject);
|
|
592
668
|
const projectId = canonicalId;
|
|
669
|
+
new MaintenanceTargetStore(dataDir).register({
|
|
670
|
+
projectId,
|
|
671
|
+
projectRoot: rawProject.rootPath,
|
|
672
|
+
dataDir,
|
|
673
|
+
});
|
|
593
674
|
|
|
594
675
|
await initObservationStore(dataDir);
|
|
595
676
|
await initMSStore(dataDir);
|
|
596
677
|
await initSessStore(dataDir);
|
|
597
678
|
await initObservations(dataDir);
|
|
598
679
|
await storeObservation({ ...observation, projectId, sourceDetail: 'hook' });
|
|
599
|
-
|
|
600
|
-
//
|
|
601
|
-
|
|
602
|
-
const { runFormation } = await import('../memory/formation/index.js');
|
|
603
|
-
const formationMode = (process.env.MEMORIX_FORMATION_MODE as 'shadow' | 'active' | 'fallback') || 'shadow';
|
|
604
|
-
const samplingRate = parseFloat(process.env.MEMORIX_FORMATION_HOOKS_SAMPLING_RATE || '0.1');
|
|
605
|
-
const shouldSample = Math.random() < samplingRate;
|
|
606
|
-
|
|
607
|
-
if (shouldSample) {
|
|
608
|
-
const { withFreshIndex } = await import('../memory/freshness.js');
|
|
609
|
-
const { getAllObservations } = await import('../memory/observations.js');
|
|
610
|
-
await withFreshIndex(() => getAllObservations());
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// In hooks, shadow mode by default for performance
|
|
614
|
-
// Sampling rate controls how often we run full resolve (expensive)
|
|
615
|
-
const searchFn = shouldSample
|
|
616
|
-
? async (q: string, limit: number, pid: string) => {
|
|
617
|
-
const { compactSearch, compactDetail } = await import('../compact/engine.js');
|
|
618
|
-
const result = await compactSearch({ query: q, limit, projectId: pid, status: 'active' });
|
|
619
|
-
if (result.entries.length === 0) return [];
|
|
620
|
-
const details = await compactDetail(result.entries.map(e => e.id));
|
|
621
|
-
return details.documents.map((d, i) => ({
|
|
622
|
-
id: Number(d.id.replace('obs-', '')),
|
|
623
|
-
observationId: d.observationId,
|
|
624
|
-
title: d.title,
|
|
625
|
-
narrative: d.narrative,
|
|
626
|
-
facts: d.facts,
|
|
627
|
-
entityName: d.entityName,
|
|
628
|
-
type: d.type,
|
|
629
|
-
score: result.entries[i]?.score ?? 0,
|
|
630
|
-
}));
|
|
631
|
-
}
|
|
632
|
-
: async () => []; // Skip search for speed (shadow mode)
|
|
633
|
-
|
|
634
|
-
const getObsFn = shouldSample
|
|
635
|
-
? (id: number) => {
|
|
636
|
-
const { getObservation } = require('../memory/observations.js');
|
|
637
|
-
const o = getObservation(id);
|
|
638
|
-
if (!o) return null;
|
|
639
|
-
return {
|
|
640
|
-
id: o.id,
|
|
641
|
-
entityName: o.entityName,
|
|
642
|
-
type: o.type,
|
|
643
|
-
title: o.title,
|
|
644
|
-
narrative: o.narrative,
|
|
645
|
-
facts: o.facts,
|
|
646
|
-
topicKey: o.topicKey,
|
|
647
|
-
};
|
|
648
|
-
}
|
|
649
|
-
: () => null;
|
|
650
|
-
|
|
651
|
-
const getEntityNamesFn = shouldSample
|
|
652
|
-
? () => {
|
|
653
|
-
const { graphManager } = require('../memory/graph.js');
|
|
654
|
-
return graphManager.getEntityNames();
|
|
655
|
-
}
|
|
656
|
-
: () => [];
|
|
657
|
-
|
|
658
|
-
runFormation({
|
|
659
|
-
entityName: observation.entityName,
|
|
660
|
-
type: observation.type,
|
|
661
|
-
title: observation.title,
|
|
662
|
-
narrative: observation.narrative,
|
|
663
|
-
facts: observation.facts,
|
|
664
|
-
projectId,
|
|
665
|
-
source: 'hook' as const,
|
|
666
|
-
}, {
|
|
667
|
-
mode: formationMode,
|
|
668
|
-
useLLM: false,
|
|
669
|
-
minValueScore: 0.3,
|
|
670
|
-
hooksSamplingRate: samplingRate,
|
|
671
|
-
searchMemories: searchFn,
|
|
672
|
-
getObservation: getObsFn,
|
|
673
|
-
getEntityNames: getEntityNamesFn,
|
|
674
|
-
}).catch(() => {});
|
|
675
|
-
} catch { /* Formation is optional — never break hooks */ }
|
|
676
|
-
|
|
677
|
-
// Feedback: tell the agent what was saved
|
|
678
|
-
const emoji = TYPE_EMOJI[observation.type] ?? '[PLAN]';
|
|
679
|
-
output.systemMessage = (output.systemMessage ?? '') +
|
|
680
|
-
`\n${emoji} Memorix saved: ${observation.title} [${observation.type}]`;
|
|
680
|
+
// Automatic capture is deliberately quiet. Candidate state and later
|
|
681
|
+
// qualification are visible through Memorix inspection, not injected as
|
|
682
|
+
// a stream of status messages into the host agent's context.
|
|
681
683
|
} catch (storeErr) {
|
|
682
684
|
// Diagnostic log — hooks must never break the agent, but silent
|
|
683
685
|
// swallow makes end-to-end debugging impossible.
|
|
@@ -685,6 +687,11 @@ export async function runHook(agentOverride?: string, eventOverride?: string): P
|
|
|
685
687
|
}
|
|
686
688
|
}
|
|
687
689
|
|
|
690
|
+
// A candidate must be durable before a Code Memory refresh can qualify it.
|
|
691
|
+
// Keep direct handleHookEvent() backward-compatible, but make the real CLI
|
|
692
|
+
// hook path explicitly capture first and schedule second.
|
|
693
|
+
await queueCodegraphRefreshForMutation(input);
|
|
694
|
+
|
|
688
695
|
// Build hookSpecificOutput — Claude Code only supports it for 3 event types:
|
|
689
696
|
// PreToolUse, UserPromptSubmit, PostToolUse
|
|
690
697
|
// Other events (SessionStart, Stop, PreCompact) must NOT include hookSpecificOutput.
|