hivemind-context-governance 1.3.0
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 +95 -0
- package/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/cli/init.d.ts +23 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +175 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +162 -0
- package/dist/cli.js.map +1 -0
- package/dist/dashboard/i18n/en.d.ts +43 -0
- package/dist/dashboard/i18n/en.d.ts.map +1 -0
- package/dist/dashboard/i18n/en.js +43 -0
- package/dist/dashboard/i18n/en.js.map +1 -0
- package/dist/dashboard/i18n/index.d.ts +10 -0
- package/dist/dashboard/i18n/index.d.ts.map +1 -0
- package/dist/dashboard/i18n/index.js +14 -0
- package/dist/dashboard/i18n/index.js.map +1 -0
- package/dist/dashboard/i18n/vi.d.ts +43 -0
- package/dist/dashboard/i18n/vi.d.ts.map +1 -0
- package/dist/dashboard/i18n/vi.js +43 -0
- package/dist/dashboard/i18n/vi.js.map +1 -0
- package/dist/dashboard/server.d.ts +23 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +282 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/hooks/compaction.d.ts +21 -0
- package/dist/hooks/compaction.d.ts.map +1 -0
- package/dist/hooks/compaction.js +79 -0
- package/dist/hooks/compaction.js.map +1 -0
- package/dist/hooks/index.d.ts +8 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/session-lifecycle.d.ts +28 -0
- package/dist/hooks/session-lifecycle.d.ts.map +1 -0
- package/dist/hooks/session-lifecycle.js +94 -0
- package/dist/hooks/session-lifecycle.js.map +1 -0
- package/dist/hooks/tool-gate.d.ts +28 -0
- package/dist/hooks/tool-gate.d.ts.map +1 -0
- package/dist/hooks/tool-gate.js +152 -0
- package/dist/hooks/tool-gate.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/complexity.d.ts +41 -0
- package/dist/lib/complexity.d.ts.map +1 -0
- package/dist/lib/complexity.js +67 -0
- package/dist/lib/complexity.js.map +1 -0
- package/dist/lib/context-refresh.d.ts +46 -0
- package/dist/lib/context-refresh.d.ts.map +1 -0
- package/dist/lib/context-refresh.js +77 -0
- package/dist/lib/context-refresh.js.map +1 -0
- package/dist/lib/index.d.ts +10 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +10 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logging.d.ts +13 -0
- package/dist/lib/logging.d.ts.map +1 -0
- package/dist/lib/logging.js +40 -0
- package/dist/lib/logging.js.map +1 -0
- package/dist/lib/persistence.d.ts +15 -0
- package/dist/lib/persistence.d.ts.map +1 -0
- package/dist/lib/persistence.js +62 -0
- package/dist/lib/persistence.js.map +1 -0
- package/dist/lib/planning-fs.d.ts +34 -0
- package/dist/lib/planning-fs.d.ts.map +1 -0
- package/dist/lib/planning-fs.js +134 -0
- package/dist/lib/planning-fs.js.map +1 -0
- package/dist/lib/sentiment.d.ts +31 -0
- package/dist/lib/sentiment.d.ts.map +1 -0
- package/dist/lib/sentiment.js +158 -0
- package/dist/lib/sentiment.js.map +1 -0
- package/dist/schemas/brain-state.d.ts +60 -0
- package/dist/schemas/brain-state.d.ts.map +1 -0
- package/dist/schemas/brain-state.js +176 -0
- package/dist/schemas/brain-state.js.map +1 -0
- package/dist/schemas/config.d.ts +51 -0
- package/dist/schemas/config.d.ts.map +1 -0
- package/dist/schemas/config.js +128 -0
- package/dist/schemas/config.js.map +1 -0
- package/dist/schemas/hierarchy.d.ts +22 -0
- package/dist/schemas/hierarchy.d.ts.map +1 -0
- package/dist/schemas/hierarchy.js +27 -0
- package/dist/schemas/hierarchy.js.map +1 -0
- package/dist/schemas/index.d.ts +7 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +7 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/tools/compact-session.d.ts +15 -0
- package/dist/tools/compact-session.d.ts.map +1 -0
- package/dist/tools/compact-session.js +76 -0
- package/dist/tools/compact-session.js.map +1 -0
- package/dist/tools/declare-intent.d.ts +15 -0
- package/dist/tools/declare-intent.d.ts.map +1 -0
- package/dist/tools/declare-intent.js +83 -0
- package/dist/tools/declare-intent.js.map +1 -0
- package/dist/tools/index.d.ts +14 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +14 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/map-context.d.ts +15 -0
- package/dist/tools/map-context.d.ts.map +1 -0
- package/dist/tools/map-context.js +85 -0
- package/dist/tools/map-context.js.map +1 -0
- package/dist/tools/self-rate.d.ts +15 -0
- package/dist/tools/self-rate.d.ts.map +1 -0
- package/dist/tools/self-rate.js +76 -0
- package/dist/tools/self-rate.js.map +1 -0
- package/package.json +71 -0
- package/src/cli/init.ts +214 -0
- package/src/cli.ts +178 -0
- package/src/dashboard/i18n/en.ts +43 -0
- package/src/dashboard/i18n/index.ts +20 -0
- package/src/dashboard/i18n/vi.ts +43 -0
- package/src/dashboard/server.ts +372 -0
- package/src/hooks/compaction.ts +104 -0
- package/src/hooks/index.ts +8 -0
- package/src/hooks/session-lifecycle.ts +133 -0
- package/src/hooks/tool-gate.ts +205 -0
- package/src/index.ts +206 -0
- package/src/lib/complexity.ts +96 -0
- package/src/lib/context-refresh.ts +107 -0
- package/src/lib/index.ts +10 -0
- package/src/lib/logging.ts +53 -0
- package/src/lib/persistence.ts +83 -0
- package/src/lib/planning-fs.ts +187 -0
- package/src/lib/sentiment.ts +183 -0
- package/src/schemas/brain-state.ts +257 -0
- package/src/schemas/config.ts +184 -0
- package/src/schemas/hierarchy.ts +58 -0
- package/src/schemas/index.ts +7 -0
- package/src/tools/compact-session.ts +97 -0
- package/src/tools/declare-intent.ts +104 -0
- package/src/tools/index.ts +14 -0
- package/src/tools/map-context.ts +105 -0
- package/src/tools/self-rate.ts +86 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Gate Hook — Configurable governance enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Intercepts tool calls and applies governance based on mode:
|
|
5
|
+
* - strict: blocks writes until declare_intent called
|
|
6
|
+
* - assisted: warns but allows
|
|
7
|
+
* - permissive: silently allows, tracks for metrics
|
|
8
|
+
*
|
|
9
|
+
* P3: try/catch — never break tool execution
|
|
10
|
+
* P5: Config cached in closure — single disk read on first call
|
|
11
|
+
*/
|
|
12
|
+
import { isSessionLocked, incrementTurnCount, shouldTriggerDriftWarning, addFileTouched, calculateDriftScore, setComplexityNudgeShown, } from "../schemas/brain-state.js";
|
|
13
|
+
import { createStateManager } from "../lib/persistence.js";
|
|
14
|
+
import { checkComplexity } from "../lib/complexity.js";
|
|
15
|
+
/** Tools that are always allowed regardless of governance state */
|
|
16
|
+
const EXEMPT_TOOLS = new Set([
|
|
17
|
+
"declare_intent",
|
|
18
|
+
"map_context",
|
|
19
|
+
"compact_session",
|
|
20
|
+
"read",
|
|
21
|
+
"grep",
|
|
22
|
+
"glob",
|
|
23
|
+
"find",
|
|
24
|
+
"list",
|
|
25
|
+
"search",
|
|
26
|
+
]);
|
|
27
|
+
/** Write/edit tools that governance should gate */
|
|
28
|
+
const WRITE_TOOLS = new Set([
|
|
29
|
+
"write",
|
|
30
|
+
"edit",
|
|
31
|
+
"create",
|
|
32
|
+
"delete",
|
|
33
|
+
"rename",
|
|
34
|
+
"move",
|
|
35
|
+
"patch",
|
|
36
|
+
]);
|
|
37
|
+
function isExemptTool(toolName) {
|
|
38
|
+
// Check full name match
|
|
39
|
+
if (EXEMPT_TOOLS.has(toolName))
|
|
40
|
+
return true;
|
|
41
|
+
// Check if tool name starts with an exempt prefix
|
|
42
|
+
for (const exempt of EXEMPT_TOOLS) {
|
|
43
|
+
if (toolName.startsWith(exempt))
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
function isWriteTool(toolName) {
|
|
49
|
+
for (const w of WRITE_TOOLS) {
|
|
50
|
+
if (toolName.includes(w))
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Creates the tool gate hook.
|
|
57
|
+
*
|
|
58
|
+
* Hook factory pattern — captures config + logger + directory.
|
|
59
|
+
*/
|
|
60
|
+
export function createToolGateHook(log, directory, config) {
|
|
61
|
+
const stateManager = createStateManager(directory);
|
|
62
|
+
return async (input) => {
|
|
63
|
+
try {
|
|
64
|
+
const { tool: toolName } = input;
|
|
65
|
+
// Always allow exempt tools (governance tools + read-only)
|
|
66
|
+
if (isExemptTool(toolName)) {
|
|
67
|
+
return { allowed: true };
|
|
68
|
+
}
|
|
69
|
+
// Load brain state
|
|
70
|
+
let state = await stateManager.load();
|
|
71
|
+
// No state = no session initialized yet
|
|
72
|
+
if (!state) {
|
|
73
|
+
switch (config.governance_mode) {
|
|
74
|
+
case "strict":
|
|
75
|
+
return {
|
|
76
|
+
allowed: false,
|
|
77
|
+
error: "SESSION NOT INITIALIZED. Use 'declare_intent' to start a session.",
|
|
78
|
+
};
|
|
79
|
+
case "assisted":
|
|
80
|
+
await log.warn(`Tool "${toolName}" called without session. Consider using declare_intent.`);
|
|
81
|
+
return {
|
|
82
|
+
allowed: true,
|
|
83
|
+
warning: "No session initialized. Consider calling declare_intent.",
|
|
84
|
+
};
|
|
85
|
+
case "permissive":
|
|
86
|
+
return { allowed: true };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Session is locked — governance gate
|
|
90
|
+
if (state && isSessionLocked(state)) {
|
|
91
|
+
switch (config.governance_mode) {
|
|
92
|
+
case "strict":
|
|
93
|
+
if (isWriteTool(toolName)) {
|
|
94
|
+
return {
|
|
95
|
+
allowed: false,
|
|
96
|
+
error: "SESSION LOCKED. Use 'declare_intent' to unlock before writing.",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { allowed: true };
|
|
100
|
+
case "assisted":
|
|
101
|
+
if (isWriteTool(toolName)) {
|
|
102
|
+
await log.warn(`Write tool "${toolName}" used while session locked.`);
|
|
103
|
+
return {
|
|
104
|
+
allowed: true,
|
|
105
|
+
warning: "Session locked. Declare your intent for better tracking.",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { allowed: true };
|
|
109
|
+
case "permissive":
|
|
110
|
+
return { allowed: true };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Session is open — track activity
|
|
114
|
+
if (state) {
|
|
115
|
+
state = incrementTurnCount(state);
|
|
116
|
+
// Track file touches for write tools (tool name used as proxy)
|
|
117
|
+
if (isWriteTool(toolName)) {
|
|
118
|
+
state = addFileTouched(state, `[via ${toolName}]`);
|
|
119
|
+
}
|
|
120
|
+
// Update drift score
|
|
121
|
+
state.metrics.drift_score = calculateDriftScore(state);
|
|
122
|
+
// Save updated state
|
|
123
|
+
await stateManager.save(state);
|
|
124
|
+
// Check drift warning
|
|
125
|
+
if (shouldTriggerDriftWarning(state, config.max_turns_before_warning)) {
|
|
126
|
+
await log.warn(`Drift warning: ${state.metrics.turn_count} turns, score: ${state.metrics.drift_score}/100. Consider using map_context to re-focus.`);
|
|
127
|
+
if (config.governance_mode !== "permissive") {
|
|
128
|
+
return {
|
|
129
|
+
allowed: true,
|
|
130
|
+
warning: `Drift detected (${state.metrics.drift_score}/100). Use map_context to re-focus.`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Check complexity and show nudge (once per session)
|
|
135
|
+
const complexityCheck = checkComplexity(state);
|
|
136
|
+
if (complexityCheck.isComplex && !state.complexity_nudge_shown) {
|
|
137
|
+
await log.warn(`[Nudge] ${complexityCheck.message}`);
|
|
138
|
+
// Mark nudge as shown
|
|
139
|
+
state = setComplexityNudgeShown(state);
|
|
140
|
+
await stateManager.save(state);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { allowed: true };
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
// P3: Never break tool execution
|
|
147
|
+
await log.error(`Tool gate error: ${error}`);
|
|
148
|
+
return { allowed: true };
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=tool-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-gate.js","sourceRoot":"","sources":["../../src/hooks/tool-gate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HiveMind Context Governance — OpenCode Plugin Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Context-aware governance layer that prevents drift and manages
|
|
5
|
+
* session state across lifecycles. 3 tools, 3 hooks, configurable modes.
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL: NO console.log anywhere — breaks TUI rendering.
|
|
8
|
+
*/
|
|
9
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
10
|
+
/**
|
|
11
|
+
* Plugin factory — hook factory pattern with captured state.
|
|
12
|
+
*
|
|
13
|
+
* GUARD: If .opencode/planning/ doesn't exist, the plugin was not initialized.
|
|
14
|
+
* Return empty hooks to avoid zombie directory creation.
|
|
15
|
+
* The user must run `hivemind init` first.
|
|
16
|
+
*/
|
|
17
|
+
declare const hivemind: Plugin;
|
|
18
|
+
export default hivemind;
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAqBjD;;;;;;GAMG;AACH,QAAA,MAAM,QAAQ,EAAE,MAsKf,CAAA;AAED,eAAe,QAAQ,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HiveMind Context Governance — OpenCode Plugin Entry Point
|
|
3
|
+
*
|
|
4
|
+
* Context-aware governance layer that prevents drift and manages
|
|
5
|
+
* session state across lifecycles. 3 tools, 3 hooks, configurable modes.
|
|
6
|
+
*
|
|
7
|
+
* CRITICAL: NO console.log anywhere — breaks TUI rendering.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { createLogger } from "./lib/logging.js";
|
|
12
|
+
import { loadConfig } from "./lib/persistence.js";
|
|
13
|
+
import { createToolGateHook, createSessionLifecycleHook, createCompactionHook, } from "./hooks/index.js";
|
|
14
|
+
import { createDeclareIntentTool, createMapContextTool, createCompactSessionTool, createSelfRateTool, } from "./tools/index.js";
|
|
15
|
+
import { createStateManager } from "./lib/persistence.js";
|
|
16
|
+
import { trackToolCall, addSentimentSignals } from "./schemas/brain-state.js";
|
|
17
|
+
import { detectSentiment } from "./lib/sentiment.js";
|
|
18
|
+
import { checkRefreshTrigger } from "./lib/context-refresh.js";
|
|
19
|
+
/**
|
|
20
|
+
* Plugin factory — hook factory pattern with captured state.
|
|
21
|
+
*
|
|
22
|
+
* GUARD: If .opencode/planning/ doesn't exist, the plugin was not initialized.
|
|
23
|
+
* Return empty hooks to avoid zombie directory creation.
|
|
24
|
+
* The user must run `hivemind init` first.
|
|
25
|
+
*/
|
|
26
|
+
const hivemind = async ({ directory }) => {
|
|
27
|
+
// Init guard: skip if not initialized
|
|
28
|
+
const planningDir = join(directory, ".opencode", "planning");
|
|
29
|
+
if (!existsSync(planningDir)) {
|
|
30
|
+
return {};
|
|
31
|
+
}
|
|
32
|
+
const log = await createLogger(join(directory, ".opencode", "planning", "logs"), "hivemind-core");
|
|
33
|
+
await log.info(`HiveMind loaded for ${directory}`);
|
|
34
|
+
// Load config
|
|
35
|
+
const config = await loadConfig(directory);
|
|
36
|
+
// Create hook instances
|
|
37
|
+
const toolGateHook = createToolGateHook(log, directory, config);
|
|
38
|
+
const sessionLifecycleHook = createSessionLifecycleHook(log, directory, config);
|
|
39
|
+
const compactionHook = createCompactionHook(log, directory);
|
|
40
|
+
// Create tool instances with directory bound via closure
|
|
41
|
+
const declare_intent = createDeclareIntentTool(directory);
|
|
42
|
+
const map_context = createMapContextTool(directory);
|
|
43
|
+
const compact_session = createCompactSessionTool(directory);
|
|
44
|
+
const self_rate = createSelfRateTool(directory);
|
|
45
|
+
return {
|
|
46
|
+
/**
|
|
47
|
+
* Session lifecycle events.
|
|
48
|
+
*/
|
|
49
|
+
event: async ({ event }) => {
|
|
50
|
+
try {
|
|
51
|
+
await log.info(`event: ${event.type}`);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// P3: Never crash on event handling
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
/**
|
|
58
|
+
* Tool gate — configurable governance enforcement.
|
|
59
|
+
* Logs warnings/errors based on governance mode.
|
|
60
|
+
* Uses soft enforcement — log, don't hard-block.
|
|
61
|
+
*/
|
|
62
|
+
"tool.execute.before": async (input) => {
|
|
63
|
+
const result = await toolGateHook({
|
|
64
|
+
sessionID: input.sessionID,
|
|
65
|
+
tool: input.tool,
|
|
66
|
+
});
|
|
67
|
+
if (!result.allowed) {
|
|
68
|
+
await log.warn(`GOVERNANCE BLOCK: ${result.error ?? "Operation blocked."} (tool: ${input.tool})`);
|
|
69
|
+
}
|
|
70
|
+
if (result.warning) {
|
|
71
|
+
await log.warn(result.warning);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
/**
|
|
75
|
+
* Tool execution tracking — auto-rating on completion.
|
|
76
|
+
* Tracks successful tool calls for auto-health calculation.
|
|
77
|
+
*/
|
|
78
|
+
"tool.execute.after": async (_input) => {
|
|
79
|
+
try {
|
|
80
|
+
const stateManager = createStateManager(directory);
|
|
81
|
+
let state = await stateManager.load();
|
|
82
|
+
if (state) {
|
|
83
|
+
// Track successful tool call
|
|
84
|
+
state = trackToolCall(state, true);
|
|
85
|
+
await stateManager.save(state);
|
|
86
|
+
// Log health score periodically (every 10 calls)
|
|
87
|
+
if (state.metrics.total_tool_calls % 10 === 0) {
|
|
88
|
+
await log.info(`[AutoHealth] Score: ${state.metrics.auto_health_score}/100 (${state.metrics.successful_tool_calls}/${state.metrics.total_tool_calls} successful)`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// P3: Never crash on tracking
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
/**
|
|
97
|
+
* System prompt — injects hierarchy + governance status.
|
|
98
|
+
* Budget: ≤250 tokens. ADD, not REPLACE.
|
|
99
|
+
*/
|
|
100
|
+
"experimental.chat.system.transform": async (input, output) => {
|
|
101
|
+
await sessionLifecycleHook(input, output);
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Chat message hook — sentiment detection on user messages.
|
|
105
|
+
* Detects negative signals (frustration, confusion) and updates drift score.
|
|
106
|
+
*/
|
|
107
|
+
"chat.message": async (_input, output) => {
|
|
108
|
+
try {
|
|
109
|
+
// Extract text from user message parts
|
|
110
|
+
const messageText = output.parts
|
|
111
|
+
.filter((p) => p.type === "text")
|
|
112
|
+
.map((p) => p.text ?? p.content ?? "")
|
|
113
|
+
.join(" ");
|
|
114
|
+
if (!messageText)
|
|
115
|
+
return;
|
|
116
|
+
const stateManager = createStateManager(directory);
|
|
117
|
+
let state = await stateManager.load();
|
|
118
|
+
if (state) {
|
|
119
|
+
const signals = detectSentiment(messageText, state.metrics.turn_count, "user");
|
|
120
|
+
if (signals.length > 0) {
|
|
121
|
+
state = addSentimentSignals(state, signals);
|
|
122
|
+
await stateManager.save(state);
|
|
123
|
+
await log.info(`[Sentiment] Detected ${signals.length} signals: ${signals.map((s) => s.type).join(", ")}`);
|
|
124
|
+
// Check if refresh trigger threshold reached
|
|
125
|
+
const refreshCheck = checkRefreshTrigger(state);
|
|
126
|
+
if (refreshCheck.shouldRefresh) {
|
|
127
|
+
await log.warn(refreshCheck.message);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// P3: Never crash on sentiment detection
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
/**
|
|
137
|
+
* Compaction — preserves hierarchy context across LLM compaction.
|
|
138
|
+
* Budget: ≤500 tokens.
|
|
139
|
+
*/
|
|
140
|
+
"experimental.session.compacting": async (input, output) => {
|
|
141
|
+
await compactionHook(input, output);
|
|
142
|
+
},
|
|
143
|
+
/**
|
|
144
|
+
* 4 lifecycle tools:
|
|
145
|
+
* declare_intent — unlock session, set mode/focus
|
|
146
|
+
* map_context — update hierarchy level
|
|
147
|
+
* compact_session — archive + reset
|
|
148
|
+
* self_rate — agent self-assessment
|
|
149
|
+
*/
|
|
150
|
+
tool: {
|
|
151
|
+
declare_intent,
|
|
152
|
+
map_context,
|
|
153
|
+
compact_session,
|
|
154
|
+
self_rate,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
export default hivemind;
|
|
159
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Complexity Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Detects when sessions get complex based on:
|
|
5
|
+
* - Files touched count (unique file paths)
|
|
6
|
+
* - Turn count (total tool calls since last declare_intent)
|
|
7
|
+
*
|
|
8
|
+
* Configurable thresholds (default: 3 files OR 5 turns)
|
|
9
|
+
*/
|
|
10
|
+
import type { BrainState } from "../schemas/brain-state.js";
|
|
11
|
+
export interface ComplexityThreshold {
|
|
12
|
+
maxFiles: number;
|
|
13
|
+
maxTurns: number;
|
|
14
|
+
}
|
|
15
|
+
export interface ComplexityCheckResult {
|
|
16
|
+
isComplex: boolean;
|
|
17
|
+
filesCount: number;
|
|
18
|
+
turnsCount: number;
|
|
19
|
+
threshold: ComplexityThreshold;
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
/** Default complexity threshold */
|
|
23
|
+
export declare const DEFAULT_COMPLEXITY_THRESHOLD: ComplexityThreshold;
|
|
24
|
+
/**
|
|
25
|
+
* Check if current session complexity exceeds thresholds
|
|
26
|
+
*/
|
|
27
|
+
export declare function checkComplexity(state: BrainState, threshold?: ComplexityThreshold): ComplexityCheckResult;
|
|
28
|
+
/**
|
|
29
|
+
* Check if nudge should be shown (only once per session)
|
|
30
|
+
*/
|
|
31
|
+
export declare function shouldShowComplexityNudge(state: BrainState, nudgeShown: boolean): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Format complexity for display
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatComplexity(state: BrainState): string;
|
|
36
|
+
/**
|
|
37
|
+
* Get complexity percentage (0-100)
|
|
38
|
+
* Based on how close to thresholds
|
|
39
|
+
*/
|
|
40
|
+
export declare function getComplexityPercentage(state: BrainState, threshold?: ComplexityThreshold): number;
|
|
41
|
+
//# sourceMappingURL=complexity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complexity.d.ts","sourceRoot":"","sources":["../../src/lib/complexity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,mBAAmB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mCAAmC;AACnC,eAAO,MAAM,4BAA4B,EAAE,mBAG1C,CAAC;AAEF;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,SAAS,GAAE,mBAAkD,GAC5D,qBAAqB,CAuBvB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,OAAO,GAClB,OAAO,CAKT;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAG1D;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,UAAU,EACjB,SAAS,GAAE,mBAAkD,GAC5D,MAAM,CAKR"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Complexity Detection Module
|
|
3
|
+
*
|
|
4
|
+
* Detects when sessions get complex based on:
|
|
5
|
+
* - Files touched count (unique file paths)
|
|
6
|
+
* - Turn count (total tool calls since last declare_intent)
|
|
7
|
+
*
|
|
8
|
+
* Configurable thresholds (default: 3 files OR 5 turns)
|
|
9
|
+
*/
|
|
10
|
+
/** Default complexity threshold */
|
|
11
|
+
export const DEFAULT_COMPLEXITY_THRESHOLD = {
|
|
12
|
+
maxFiles: 3,
|
|
13
|
+
maxTurns: 5,
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Check if current session complexity exceeds thresholds
|
|
17
|
+
*/
|
|
18
|
+
export function checkComplexity(state, threshold = DEFAULT_COMPLEXITY_THRESHOLD) {
|
|
19
|
+
const filesCount = state.metrics.files_touched.length;
|
|
20
|
+
const turnsCount = state.metrics.turn_count;
|
|
21
|
+
const filesExceeded = filesCount >= threshold.maxFiles;
|
|
22
|
+
const turnsExceeded = turnsCount >= threshold.maxTurns;
|
|
23
|
+
const isComplex = filesExceeded || turnsExceeded;
|
|
24
|
+
let message = "Complexity normal";
|
|
25
|
+
if (isComplex) {
|
|
26
|
+
const reasons = [];
|
|
27
|
+
if (filesExceeded)
|
|
28
|
+
reasons.push(`${filesCount} files touched`);
|
|
29
|
+
if (turnsExceeded)
|
|
30
|
+
reasons.push(`${turnsCount} turns`);
|
|
31
|
+
message = `Complexity rising (${reasons.join(", ")}). Consider declare_intent.`;
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
isComplex,
|
|
35
|
+
filesCount,
|
|
36
|
+
turnsCount,
|
|
37
|
+
threshold,
|
|
38
|
+
message,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if nudge should be shown (only once per session)
|
|
43
|
+
*/
|
|
44
|
+
export function shouldShowComplexityNudge(state, nudgeShown) {
|
|
45
|
+
if (nudgeShown)
|
|
46
|
+
return false;
|
|
47
|
+
const check = checkComplexity(state);
|
|
48
|
+
return check.isComplex;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Format complexity for display
|
|
52
|
+
*/
|
|
53
|
+
export function formatComplexity(state) {
|
|
54
|
+
const check = checkComplexity(state);
|
|
55
|
+
return `${check.filesCount} files, ${check.turnsCount} turns`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get complexity percentage (0-100)
|
|
59
|
+
* Based on how close to thresholds
|
|
60
|
+
*/
|
|
61
|
+
export function getComplexityPercentage(state, threshold = DEFAULT_COMPLEXITY_THRESHOLD) {
|
|
62
|
+
const filesRatio = state.metrics.files_touched.length / threshold.maxFiles;
|
|
63
|
+
const turnsRatio = state.metrics.turn_count / threshold.maxTurns;
|
|
64
|
+
const maxRatio = Math.max(filesRatio, turnsRatio);
|
|
65
|
+
return Math.min(100, Math.round(maxRatio * 100));
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=complexity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complexity.js","sourceRoot":"","sources":["../../src/lib/complexity.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAiBH,mCAAmC;AACnC,MAAM,CAAC,MAAM,4BAA4B,GAAwB;IAC/D,QAAQ,EAAE,CAAC;IACX,QAAQ,EAAE,CAAC;CACZ,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAiB,EACjB,YAAiC,4BAA4B;IAE7D,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC;IACtD,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;IAE5C,MAAM,aAAa,GAAG,UAAU,IAAI,SAAS,CAAC,QAAQ,CAAC;IACvD,MAAM,aAAa,GAAG,UAAU,IAAI,SAAS,CAAC,QAAQ,CAAC;IACvD,MAAM,SAAS,GAAG,aAAa,IAAI,aAAa,CAAC;IAEjD,IAAI,OAAO,GAAG,mBAAmB,CAAC;IAClC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,IAAI,aAAa;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,gBAAgB,CAAC,CAAC;QAC/D,IAAI,aAAa;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,QAAQ,CAAC,CAAC;QACvD,OAAO,GAAG,sBAAsB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC;IAClF,CAAC;IAED,OAAO;QACL,SAAS;QACT,UAAU;QACV,UAAU;QACV,SAAS;QACT,OAAO;KACR,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAiB,EACjB,UAAmB;IAEnB,IAAI,UAAU;QAAE,OAAO,KAAK,CAAC;IAE7B,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC,SAAS,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAiB;IAChD,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;IACrC,OAAO,GAAG,KAAK,CAAC,UAAU,WAAW,KAAK,CAAC,UAAU,QAAQ,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,KAAiB,EACjB,YAAiC,4BAA4B;IAE7D,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC;IAC3E,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,QAAQ,CAAC;IACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Refresh Trigger Module
|
|
3
|
+
*
|
|
4
|
+
* Triggers context refresh recommendations when negative sentiment
|
|
5
|
+
* signals exceed thresholds.
|
|
6
|
+
*
|
|
7
|
+
* Threshold: 2 negative signals within 5 turns triggers refresh warning
|
|
8
|
+
*/
|
|
9
|
+
import type { BrainState } from "../schemas/brain-state.js";
|
|
10
|
+
import type { SentimentSignal } from "./sentiment.js";
|
|
11
|
+
export interface RefreshTriggerResult {
|
|
12
|
+
shouldRefresh: boolean;
|
|
13
|
+
signalCount: number;
|
|
14
|
+
windowSize: number;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
/** Default threshold configuration */
|
|
18
|
+
export declare const DEFAULT_REFRESH_THRESHOLD: {
|
|
19
|
+
signalCount: number;
|
|
20
|
+
windowSize: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Check if context refresh should be triggered
|
|
24
|
+
* Based on signal count within recent turn window
|
|
25
|
+
*/
|
|
26
|
+
export declare function checkRefreshTrigger(state: BrainState, threshold?: {
|
|
27
|
+
signalCount: number;
|
|
28
|
+
windowSize: number;
|
|
29
|
+
}): RefreshTriggerResult;
|
|
30
|
+
/**
|
|
31
|
+
* Get recent signals for display
|
|
32
|
+
*/
|
|
33
|
+
export declare function getRecentSignals(state: BrainState, windowSize?: number): SentimentSignal[];
|
|
34
|
+
/**
|
|
35
|
+
* Format signals for logging
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatSignalsForLog(signals: SentimentSignal[]): string;
|
|
38
|
+
/**
|
|
39
|
+
* Check if drift warning should be added to active.md
|
|
40
|
+
*/
|
|
41
|
+
export declare function shouldAddDriftWarning(state: BrainState): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Generate drift warning content for active.md
|
|
44
|
+
*/
|
|
45
|
+
export declare function generateDriftWarning(state: BrainState): string;
|
|
46
|
+
//# sourceMappingURL=context-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-refresh.d.ts","sourceRoot":"","sources":["../../src/lib/context-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sCAAsC;AACtC,eAAO,MAAM,yBAAyB;;;CAGrC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,EACjB,SAAS;;;CAA4B,GACpC,oBAAoB,CAmBtB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,UAAU,EACjB,UAAU,GAAE,MAAU,GACrB,eAAe,EAAE,CAOnB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,MAAM,CAMtE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAGhE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAgB9D"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Refresh Trigger Module
|
|
3
|
+
*
|
|
4
|
+
* Triggers context refresh recommendations when negative sentiment
|
|
5
|
+
* signals exceed thresholds.
|
|
6
|
+
*
|
|
7
|
+
* Threshold: 2 negative signals within 5 turns triggers refresh warning
|
|
8
|
+
*/
|
|
9
|
+
/** Default threshold configuration */
|
|
10
|
+
export const DEFAULT_REFRESH_THRESHOLD = {
|
|
11
|
+
signalCount: 2,
|
|
12
|
+
windowSize: 5, // turns
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Check if context refresh should be triggered
|
|
16
|
+
* Based on signal count within recent turn window
|
|
17
|
+
*/
|
|
18
|
+
export function checkRefreshTrigger(state, threshold = DEFAULT_REFRESH_THRESHOLD) {
|
|
19
|
+
const currentTurn = state.metrics.turn_count;
|
|
20
|
+
const windowStart = Math.max(0, currentTurn - threshold.windowSize);
|
|
21
|
+
// Count signals within the window
|
|
22
|
+
const recentSignals = state.sentiment_signals.filter((signal) => signal.turn_number >= windowStart && signal.turn_number <= currentTurn);
|
|
23
|
+
const shouldRefresh = recentSignals.length >= threshold.signalCount;
|
|
24
|
+
return {
|
|
25
|
+
shouldRefresh,
|
|
26
|
+
signalCount: recentSignals.length,
|
|
27
|
+
windowSize: threshold.windowSize,
|
|
28
|
+
message: shouldRefresh
|
|
29
|
+
? `[ContextRefresh] Drift detected (${recentSignals.length} negative signals in last ${threshold.windowSize} turns). Consider compact_session.`
|
|
30
|
+
: `Context stable (${recentSignals.length} signals in last ${threshold.windowSize} turns)`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get recent signals for display
|
|
35
|
+
*/
|
|
36
|
+
export function getRecentSignals(state, windowSize = 5) {
|
|
37
|
+
const currentTurn = state.metrics.turn_count;
|
|
38
|
+
const windowStart = Math.max(0, currentTurn - windowSize);
|
|
39
|
+
return state.sentiment_signals.filter((signal) => signal.turn_number >= windowStart && signal.turn_number <= currentTurn);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format signals for logging
|
|
43
|
+
*/
|
|
44
|
+
export function formatSignalsForLog(signals) {
|
|
45
|
+
if (signals.length === 0)
|
|
46
|
+
return "No recent signals";
|
|
47
|
+
return signals
|
|
48
|
+
.map((s) => `[Turn ${s.turn_number}] ${s.type}: "${s.matched_text}"`)
|
|
49
|
+
.join("\n ");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if drift warning should be added to active.md
|
|
53
|
+
*/
|
|
54
|
+
export function shouldAddDriftWarning(state) {
|
|
55
|
+
const result = checkRefreshTrigger(state);
|
|
56
|
+
return result.shouldRefresh;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Generate drift warning content for active.md
|
|
60
|
+
*/
|
|
61
|
+
export function generateDriftWarning(state) {
|
|
62
|
+
const recentSignals = getRecentSignals(state);
|
|
63
|
+
return [
|
|
64
|
+
"",
|
|
65
|
+
"## ⚠️ Drift Warning",
|
|
66
|
+
"",
|
|
67
|
+
`**Detected**: ${recentSignals.length} negative signals in recent turns`,
|
|
68
|
+
"",
|
|
69
|
+
"**Recommendation**: Consider using `compact_session` to reset context,",
|
|
70
|
+
"or `map_context` to refocus on current objectives.",
|
|
71
|
+
"",
|
|
72
|
+
"**Recent Signals**:",
|
|
73
|
+
...recentSignals.map((s) => `- Turn ${s.turn_number}: ${s.type} (${s.matched_text})`),
|
|
74
|
+
"",
|
|
75
|
+
].join("\n");
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=context-refresh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-refresh.js","sourceRoot":"","sources":["../../src/lib/context-refresh.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,sCAAsC;AACtC,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,WAAW,EAAE,CAAC;IACd,UAAU,EAAE,CAAC,EAAE,QAAQ;CACxB,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAiB,EACjB,SAAS,GAAG,yBAAyB;IAErC,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;IAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IAEpE,kCAAkC;IAClC,MAAM,aAAa,GAAG,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAClD,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,IAAI,WAAW,IAAI,MAAM,CAAC,WAAW,IAAI,WAAW,CACnF,CAAC;IAEF,MAAM,aAAa,GAAG,aAAa,CAAC,MAAM,IAAI,SAAS,CAAC,WAAW,CAAC;IAEpE,OAAO;QACL,aAAa;QACb,WAAW,EAAE,aAAa,CAAC,MAAM;QACjC,UAAU,EAAE,SAAS,CAAC,UAAU;QAChC,OAAO,EAAE,aAAa;YACpB,CAAC,CAAC,oCAAoC,aAAa,CAAC,MAAM,6BAA6B,SAAS,CAAC,UAAU,oCAAoC;YAC/I,CAAC,CAAC,mBAAmB,aAAa,CAAC,MAAM,oBAAoB,SAAS,CAAC,UAAU,SAAS;KAC7F,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAiB,EACjB,aAAqB,CAAC;IAEtB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC;IAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC,CAAC;IAE1D,OAAO,KAAK,CAAC,iBAAiB,CAAC,MAAM,CACnC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,IAAI,WAAW,IAAI,MAAM,CAAC,WAAW,IAAI,WAAW,CACnF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAA0B;IAC5D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,mBAAmB,CAAC;IAErD,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,YAAY,GAAG,CAAC;SACpE,IAAI,CAAC,MAAM,CAAC,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAiB;IACrD,MAAM,MAAM,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,aAAa,CAAC;AAC9B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAAiB;IACpD,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAE9C,OAAO;QACL,EAAE;QACF,qBAAqB;QACrB,EAAE;QACF,iBAAiB,aAAa,CAAC,MAAM,mCAAmC;QACxE,EAAE;QACF,wEAAwE;QACxE,oDAAoD;QACpD,EAAE;QACF,qBAAqB;QACrB,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,WAAW,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,YAAY,GAAG,CAAC;QACrF,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lib barrel exports
|
|
3
|
+
*/
|
|
4
|
+
export * from "./logging.js";
|
|
5
|
+
export * from "./persistence.js";
|
|
6
|
+
export * from "./planning-fs.js";
|
|
7
|
+
export * from "./sentiment.js";
|
|
8
|
+
export * from "./context-refresh.js";
|
|
9
|
+
export * from "./complexity.js";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lib barrel exports
|
|
3
|
+
*/
|
|
4
|
+
export * from "./logging.js";
|
|
5
|
+
export * from "./persistence.js";
|
|
6
|
+
export * from "./planning-fs.js";
|
|
7
|
+
export * from "./sentiment.js";
|
|
8
|
+
export * from "./context-refresh.js";
|
|
9
|
+
export * from "./complexity.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI-safe logging
|
|
3
|
+
* File-based logging only - never use console.log
|
|
4
|
+
*/
|
|
5
|
+
export interface Logger {
|
|
6
|
+
debug: (message: string) => Promise<void>;
|
|
7
|
+
info: (message: string) => Promise<void>;
|
|
8
|
+
warn: (message: string) => Promise<void>;
|
|
9
|
+
error: (message: string) => Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createLogger(logDir: string, service: string): Promise<Logger>;
|
|
12
|
+
export declare const noopLogger: Logger;
|
|
13
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../../src/lib/logging.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,CA0BjB;AAGD,eAAO,MAAM,UAAU,EAAE,MAKxB,CAAC"}
|