@vainplex/openclaw-leuko 0.1.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 +25 -0
- package/LICENSE +21 -0
- package/README.md +160 -0
- package/dist/src/check-runner.d.ts +65 -0
- package/dist/src/check-runner.d.ts.map +1 -0
- package/dist/src/check-runner.js +40 -0
- package/dist/src/check-runner.js.map +1 -0
- package/dist/src/check-utils.d.ts +21 -0
- package/dist/src/check-utils.d.ts.map +1 -0
- package/dist/src/check-utils.js +39 -0
- package/dist/src/check-utils.js.map +1 -0
- package/dist/src/checks/anomaly-detection.d.ts +3 -0
- package/dist/src/checks/anomaly-detection.d.ts.map +1 -0
- package/dist/src/checks/anomaly-detection.js +127 -0
- package/dist/src/checks/anomaly-detection.js.map +1 -0
- package/dist/src/checks/bootstrap-integrity.d.ts +3 -0
- package/dist/src/checks/bootstrap-integrity.d.ts.map +1 -0
- package/dist/src/checks/bootstrap-integrity.js +107 -0
- package/dist/src/checks/bootstrap-integrity.js.map +1 -0
- package/dist/src/checks/goal-quality.d.ts +3 -0
- package/dist/src/checks/goal-quality.d.ts.map +1 -0
- package/dist/src/checks/goal-quality.js +143 -0
- package/dist/src/checks/goal-quality.js.map +1 -0
- package/dist/src/checks/pipeline-correlation.d.ts +7 -0
- package/dist/src/checks/pipeline-correlation.d.ts.map +1 -0
- package/dist/src/checks/pipeline-correlation.js +118 -0
- package/dist/src/checks/pipeline-correlation.js.map +1 -0
- package/dist/src/checks/recommendations.d.ts +3 -0
- package/dist/src/checks/recommendations.d.ts.map +1 -0
- package/dist/src/checks/recommendations.js +132 -0
- package/dist/src/checks/recommendations.js.map +1 -0
- package/dist/src/checks/thread-health.d.ts +3 -0
- package/dist/src/checks/thread-health.d.ts.map +1 -0
- package/dist/src/checks/thread-health.js +129 -0
- package/dist/src/checks/thread-health.js.map +1 -0
- package/dist/src/config.d.ts +10 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +301 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +27 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +198 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/llm-client.d.ts +7 -0
- package/dist/src/llm-client.d.ts.map +1 -0
- package/dist/src/llm-client.js +111 -0
- package/dist/src/llm-client.js.map +1 -0
- package/dist/src/status-reader.d.ts +6 -0
- package/dist/src/status-reader.d.ts.map +1 -0
- package/dist/src/status-reader.js +156 -0
- package/dist/src/status-reader.js.map +1 -0
- package/dist/src/status-writer.d.ts +14 -0
- package/dist/src/status-writer.d.ts.map +1 -0
- package/dist/src/status-writer.js +52 -0
- package/dist/src/status-writer.js.map +1 -0
- package/dist/src/tool.d.ts +9 -0
- package/dist/src/tool.d.ts.map +1 -0
- package/dist/src/tool.js +129 -0
- package/dist/src/tool.js.map +1 -0
- package/dist/src/types.d.ts +261 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +5 -0
- package/dist/src/types.js.map +1 -0
- package/openclaw.plugin.json +22 -0
- package/package.json +51 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] — 2026-02-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Initial release of `@vainplex/openclaw-leuko` — Cognitive Immune System Plugin
|
|
7
|
+
- 6 Cognitive Checks (CK-01 through CK-06):
|
|
8
|
+
- CK-01: Goal Quality Assessment (LLM) — with pre-filter for expired/stale goals
|
|
9
|
+
- CK-02: Thread Health Assessment (LLM) — with pre-filter for stale threads
|
|
10
|
+
- CK-03: Pipeline Correlation (deterministic) — NATS/thread/cron cross-referencing
|
|
11
|
+
- CK-04: Anomaly Detection (deterministic) — directory size and metric trend analysis
|
|
12
|
+
- CK-05: Bootstrap Integrity (LLM) — BOOTSTRAP.md factual verification
|
|
13
|
+
- CK-06: Recommendations (LLM) — proactive housekeeping suggestions
|
|
14
|
+
- `leuko_status` tool for agent health queries (sections: summary, daemon, cognitive, recommendations, all)
|
|
15
|
+
- `/leuko` command (subcommands: refresh, detail, config)
|
|
16
|
+
- `before_agent_start` hook for health context injection
|
|
17
|
+
- External config pattern (`~/.openclaw/plugins/openclaw-leuko/config.json`)
|
|
18
|
+
- LLM client with primary (Ollama) + fallback (LiteLLM) support
|
|
19
|
+
- Atomic write protocol for `leuko-status.json` (preserves daemon fields)
|
|
20
|
+
- Consecutive-critical tracking with `escalation_needed` flag
|
|
21
|
+
- Fail-open behavior: LLM failures default to `severity: "ok"`
|
|
22
|
+
- Adopted Sitrep `errors` and `custom` collector config (implementation deferred to v0.2.0)
|
|
23
|
+
|
|
24
|
+
### Replaces
|
|
25
|
+
- `@vainplex/openclaw-sitrep` (deprecated) — absorbed goals/threads pre-filtering, dropped redundant collectors
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Albert Hild / Vainplex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# @vainplex/openclaw-leuko
|
|
2
|
+
|
|
3
|
+
**Cognitive immune system for OpenClaw** — L2 semantic health checks with LLM analysis, tool exposure for agent queries, and Sitrep replacement.
|
|
4
|
+
|
|
5
|
+
Part of the [Vainplex OpenClaw Plugin Suite](https://github.com/alberthild/vainplex-openclaw).
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
Leuko operates as a **two-tier hybrid system**:
|
|
10
|
+
|
|
11
|
+
- **Level 1 (Python daemon):** Heuristic checks — file freshness, JSON validity, service connectivity, cron health. Runs every 15min, zero API cost.
|
|
12
|
+
- **Level 2 (This plugin):** Cognitive checks — semantic analysis via LLM + deterministic correlation. Runs every 2h or on-demand.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Plugin (L2) Agents
|
|
16
|
+
│ │
|
|
17
|
+
┌─ Read daemon_checks[] from leuko-status.json │
|
|
18
|
+
├─ Run CK-01..CK-06 (4 LLM + 2 deterministic) │
|
|
19
|
+
├─ Write cognitive_checks[] to leuko-status.json │
|
|
20
|
+
│ │
|
|
21
|
+
├── leuko_status tool ────────────────────────────┤
|
|
22
|
+
├── before_agent_start hook ──────────────────────┤
|
|
23
|
+
└── /leuko command ──────────────────────────────→│
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install @vainplex/openclaw-leuko
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Add to `openclaw.json`:
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"plugins": {
|
|
36
|
+
"entries": {
|
|
37
|
+
"openclaw-leuko": { "enabled": true }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The plugin will auto-create a default config at `~/.openclaw/plugins/openclaw-leuko/config.json` on first run.
|
|
44
|
+
|
|
45
|
+
## Cognitive Checks
|
|
46
|
+
|
|
47
|
+
| Check | ID | Type | Purpose |
|
|
48
|
+
|-------|-----|------|---------|
|
|
49
|
+
| Goal Quality | CK-01 | LLM | Are pending goals specific, actionable, non-redundant? |
|
|
50
|
+
| Thread Health | CK-02 | LLM | Are threads stale, duplicate, or accumulating? |
|
|
51
|
+
| Pipeline Correlation | CK-03 | Deterministic | Are inputs flowing to outputs? (NATS → threads) |
|
|
52
|
+
| Anomaly Detection | CK-04 | Deterministic | Statistical deviations in metrics and directory sizes |
|
|
53
|
+
| Bootstrap Integrity | CK-05 | LLM | Is BOOTSTRAP.md factually current? |
|
|
54
|
+
| Recommendations | CK-06 | LLM | Proactive housekeeping suggestions |
|
|
55
|
+
|
|
56
|
+
### Severity Levels
|
|
57
|
+
|
|
58
|
+
- **`ok`** — All checks pass, no issues detected
|
|
59
|
+
- **`warn`** — Issues found that should be reviewed
|
|
60
|
+
- **`critical`** — Significant problems requiring attention
|
|
61
|
+
|
|
62
|
+
## Tool: `leuko_status`
|
|
63
|
+
|
|
64
|
+
Agents can query system health:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
leuko_status({ section: "summary" })
|
|
68
|
+
leuko_status({ section: "cognitive", severity_filter: "warn" })
|
|
69
|
+
leuko_status({ section: "recommendations" })
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Sections: `summary` | `daemon` | `cognitive` | `recommendations` | `all`
|
|
73
|
+
|
|
74
|
+
## Command: `/leuko`
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
/leuko — Health summary
|
|
78
|
+
/leuko refresh — Trigger immediate L2 check cycle
|
|
79
|
+
/leuko detail — All checks with findings
|
|
80
|
+
/leuko config — Active configuration
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Hook: `before_agent_start`
|
|
84
|
+
|
|
85
|
+
When system health is degraded, injects a one-liner into the agent's context:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
⚕️ Leuko Health: WARN — 2 issues: goal_quality (3/9 vague goals), facts (stale 48h)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Configurable: only injected when issues exist, respects maxLength.
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
Full config lives at `~/.openclaw/plugins/openclaw-leuko/config.json`:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"enabled": true,
|
|
100
|
+
"statusPath": "~/clawd/memory/leuko-status.json",
|
|
101
|
+
"intervalMinutes": 120,
|
|
102
|
+
"llm": {
|
|
103
|
+
"primary": {
|
|
104
|
+
"provider": "ollama",
|
|
105
|
+
"model": "qwen3:14b",
|
|
106
|
+
"baseUrl": "http://localhost:11434",
|
|
107
|
+
"timeoutSec": 30
|
|
108
|
+
},
|
|
109
|
+
"fallback": {
|
|
110
|
+
"provider": "litellm",
|
|
111
|
+
"model": "gemini/gemini-2.0-flash-lite",
|
|
112
|
+
"baseUrl": "http://localhost:4000",
|
|
113
|
+
"timeoutSec": 30
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"checks": {
|
|
117
|
+
"goal_quality": { "enabled": true, "usesLlm": true },
|
|
118
|
+
"thread_health": { "enabled": true, "staleDays": 5 },
|
|
119
|
+
"pipeline_correlation": { "enabled": true },
|
|
120
|
+
"anomaly_detection": { "enabled": true },
|
|
121
|
+
"bootstrap_integrity": { "enabled": true },
|
|
122
|
+
"recommendations": { "enabled": true, "maxRecommendations": 5 }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## LLM Integration
|
|
128
|
+
|
|
129
|
+
- **Primary:** `ollama/qwen3:14b` (local, $0.00/run)
|
|
130
|
+
- **Fallback:** `gemini/gemini-2.0-flash-lite` via LiteLLM (~$0.002/run)
|
|
131
|
+
- **Budget:** ≤ $0.05 per run (30x margin)
|
|
132
|
+
- **Max 4 LLM calls per run** (CK-01, CK-02, CK-05, CK-06)
|
|
133
|
+
- **Fail-open:** If LLM unavailable, severity defaults to `ok` with explanatory detail
|
|
134
|
+
|
|
135
|
+
## Sitrep Deprecation
|
|
136
|
+
|
|
137
|
+
This plugin replaces `@vainplex/openclaw-sitrep`. Sitrep's `errors` and `custom` collectors are adopted; `systemd_timers`, `nats`, `goals`, `threads`, and `calendar` collectors are either absorbed into cognitive checks or dropped (covered by L1).
|
|
138
|
+
|
|
139
|
+
## Development
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm install
|
|
143
|
+
npm run build # TypeScript compilation
|
|
144
|
+
npm run test # Run all tests
|
|
145
|
+
npm run test:coverage # Coverage report (>80% lines)
|
|
146
|
+
npm run typecheck # Type checking only
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Standards
|
|
150
|
+
|
|
151
|
+
- TypeScript strict mode, `noUncheckedIndexedAccess`
|
|
152
|
+
- 0 `any` types
|
|
153
|
+
- ESM (`type: module`)
|
|
154
|
+
- 0 runtime dependencies (only `node:*` builtins)
|
|
155
|
+
- vitest for tests
|
|
156
|
+
- kebab-case filenames
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { CognitiveCheckResult, LlmClient, PluginLogger, Severity } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for the generic LLM check runner.
|
|
4
|
+
*
|
|
5
|
+
* TInput: the shape read from disk (goals, threads, bootstrap text, etc.)
|
|
6
|
+
* TResponse: the expected LLM JSON response shape
|
|
7
|
+
*/
|
|
8
|
+
export interface LlmCheckOpts<TInput, TResponse> {
|
|
9
|
+
/** Check name (e.g. "cognitive:goal_quality") */
|
|
10
|
+
name: string;
|
|
11
|
+
/** LLM system prompt */
|
|
12
|
+
systemPrompt: string;
|
|
13
|
+
/** LLM client instance */
|
|
14
|
+
llm: LlmClient;
|
|
15
|
+
/** Logger */
|
|
16
|
+
logger: PluginLogger;
|
|
17
|
+
/** Read input data; return null to skip with a given result */
|
|
18
|
+
readInput(timestamp: string, startMs: number): ReadInputResult<TInput>;
|
|
19
|
+
/** Run deterministic pre-filter; returns pre-severity and partial data */
|
|
20
|
+
preFilter(input: TInput): PreFilterResult;
|
|
21
|
+
/** Build the LLM user prompt from input */
|
|
22
|
+
buildPrompt(input: TInput): string;
|
|
23
|
+
/** Build fail-open result when LLM fails */
|
|
24
|
+
buildFailOpen(opts: FailOpenOpts): CognitiveCheckResult;
|
|
25
|
+
/** Parse and merge LLM response + pre-filter into final result */
|
|
26
|
+
mergeResults(opts: MergeOpts<TResponse>): CognitiveCheckResult;
|
|
27
|
+
}
|
|
28
|
+
export type ReadInputResult<T> = {
|
|
29
|
+
ok: true;
|
|
30
|
+
input: T;
|
|
31
|
+
} | {
|
|
32
|
+
ok: false;
|
|
33
|
+
skip: CognitiveCheckResult;
|
|
34
|
+
};
|
|
35
|
+
export interface PreFilterResult {
|
|
36
|
+
severity: Severity;
|
|
37
|
+
findingCount: number;
|
|
38
|
+
/** Arbitrary data passed through to mergeResults / buildFailOpen */
|
|
39
|
+
data: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
export interface FailOpenOpts {
|
|
42
|
+
name: string;
|
|
43
|
+
pre: PreFilterResult;
|
|
44
|
+
message: string;
|
|
45
|
+
llmModel: string;
|
|
46
|
+
llmTokens: number;
|
|
47
|
+
timestamp: string;
|
|
48
|
+
startMs: number;
|
|
49
|
+
}
|
|
50
|
+
export interface MergeOpts<TResponse> {
|
|
51
|
+
parsed: TResponse;
|
|
52
|
+
pre: PreFilterResult;
|
|
53
|
+
llmModel: string;
|
|
54
|
+
llmTokens: number;
|
|
55
|
+
timestamp: string;
|
|
56
|
+
startMs: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Generic runner for all LLM-based cognitive checks.
|
|
60
|
+
*
|
|
61
|
+
* Flow: readInput → preFilter → LLM call → parse → mergeResults
|
|
62
|
+
* On LLM failure: fail-open via buildFailOpen.
|
|
63
|
+
*/
|
|
64
|
+
export declare function runLlmCheck<TInput, TResponse>(opts: LlmCheckOpts<TInput, TResponse>): Promise<CognitiveCheckResult>;
|
|
65
|
+
//# sourceMappingURL=check-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-runner.d.ts","sourceRoot":"","sources":["../../src/check-runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,SAAS,EACT,YAAY,EACZ,QAAQ,EACT,MAAM,YAAY,CAAC;AAGpB;;;;;GAKG;AACH,MAAM,WAAW,YAAY,CAAC,MAAM,EAAE,SAAS;IAC7C,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,wBAAwB;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,GAAG,EAAE,SAAS,CAAC;IACf,aAAa;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,+DAA+D;IAC/D,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvE,0EAA0E;IAC1E,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAAC;IAC1C,2CAA2C;IAC3C,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,4CAA4C;IAC5C,aAAa,CAAC,IAAI,EAAE,YAAY,GAAG,oBAAoB,CAAC;IACxD,kEAAkE;IAClE,YAAY,CAAC,IAAI,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,oBAAoB,CAAC;CAChE;AAED,MAAM,MAAM,eAAe,CAAC,CAAC,IACzB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACtB;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,eAAe,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS,CAAC,SAAS;IAClC,MAAM,EAAE,SAAS,CAAC;IAClB,GAAG,EAAE,eAAe,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,SAAS,EACjD,IAAI,EAAE,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,GACpC,OAAO,CAAC,oBAAoB,CAAC,CAmC/B"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parseLlmJson } from "./check-utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generic runner for all LLM-based cognitive checks.
|
|
4
|
+
*
|
|
5
|
+
* Flow: readInput → preFilter → LLM call → parse → mergeResults
|
|
6
|
+
* On LLM failure: fail-open via buildFailOpen.
|
|
7
|
+
*/
|
|
8
|
+
export async function runLlmCheck(opts) {
|
|
9
|
+
const startMs = Date.now();
|
|
10
|
+
const timestamp = new Date().toISOString();
|
|
11
|
+
const readResult = opts.readInput(timestamp, startMs);
|
|
12
|
+
if (!readResult.ok)
|
|
13
|
+
return readResult.skip;
|
|
14
|
+
const pre = opts.preFilter(readResult.input);
|
|
15
|
+
const userPrompt = opts.buildPrompt(readResult.input);
|
|
16
|
+
const llmResult = await opts.llm.generate(opts.systemPrompt, userPrompt, 30000);
|
|
17
|
+
if (llmResult.content === null) {
|
|
18
|
+
const msg = llmResult.error
|
|
19
|
+
? `LLM unavailable (${llmResult.error})`
|
|
20
|
+
: "LLM timeout";
|
|
21
|
+
return opts.buildFailOpen({
|
|
22
|
+
name: opts.name, pre, message: msg,
|
|
23
|
+
llmModel: llmResult.model, llmTokens: 0, timestamp, startMs,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const parsed = parseLlmJson(llmResult.content);
|
|
27
|
+
if (!parsed) {
|
|
28
|
+
return opts.buildFailOpen({
|
|
29
|
+
name: opts.name, pre, message: "LLM response parsing failed",
|
|
30
|
+
llmModel: llmResult.model, llmTokens: llmResult.tokens,
|
|
31
|
+
timestamp, startMs,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return opts.mergeResults({
|
|
35
|
+
parsed, pre,
|
|
36
|
+
llmModel: llmResult.model, llmTokens: llmResult.tokens,
|
|
37
|
+
timestamp, startMs,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=check-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-runner.js","sourceRoot":"","sources":["../../src/check-runner.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA2DhD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,IAAqC;IAErC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAE3C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACtD,IAAI,CAAC,UAAU,CAAC,EAAE;QAAE,OAAO,UAAU,CAAC,IAAI,CAAC;IAE3C,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;IAEhF,IAAI,SAAS,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC/B,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK;YACzB,CAAC,CAAC,oBAAoB,SAAS,CAAC,KAAK,GAAG;YACxC,CAAC,CAAC,aAAa,CAAC;QAClB,OAAO,IAAI,CAAC,aAAa,CAAC;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG;YAClC,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAY,SAAS,CAAC,OAAO,CAAC,CAAC;IAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC,aAAa,CAAC;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,6BAA6B;YAC5D,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,MAAM;YACtD,SAAS,EAAE,OAAO;SACnB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,IAAI,CAAC,YAAY,CAAC;QACvB,MAAM,EAAE,GAAG;QACX,QAAQ,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,CAAC,MAAM;QACtD,SAAS,EAAE,OAAO;KACnB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Severity } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a string (typically from LLM response) into a valid Severity.
|
|
4
|
+
* Returns "ok" for unrecognized values.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseSeverityString(v: unknown): Severity;
|
|
7
|
+
/**
|
|
8
|
+
* Return the most severe of the given severities.
|
|
9
|
+
* Order: ok < warn < critical.
|
|
10
|
+
*/
|
|
11
|
+
export declare function worstSeverity(...severities: Severity[]): Severity;
|
|
12
|
+
/**
|
|
13
|
+
* Type guard: is value a plain object (non-null, non-array)?
|
|
14
|
+
*/
|
|
15
|
+
export declare function isRecord(v: unknown): v is Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Parse a JSON string (typically raw LLM output) into a typed object.
|
|
18
|
+
* Returns null on parse failure.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseLlmJson<T>(raw: string): T | null;
|
|
21
|
+
//# sourceMappingURL=check-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-utils.d.ts","sourceRoot":"","sources":["../../src/check-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,GAAG,QAAQ,CAGxD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,UAAU,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAIjE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEjE;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAMrD"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a string (typically from LLM response) into a valid Severity.
|
|
3
|
+
* Returns "ok" for unrecognized values.
|
|
4
|
+
*/
|
|
5
|
+
export function parseSeverityString(v) {
|
|
6
|
+
if (v === "ok" || v === "warn" || v === "critical")
|
|
7
|
+
return v;
|
|
8
|
+
return "ok";
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Return the most severe of the given severities.
|
|
12
|
+
* Order: ok < warn < critical.
|
|
13
|
+
*/
|
|
14
|
+
export function worstSeverity(...severities) {
|
|
15
|
+
if (severities.includes("critical"))
|
|
16
|
+
return "critical";
|
|
17
|
+
if (severities.includes("warn"))
|
|
18
|
+
return "warn";
|
|
19
|
+
return "ok";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Type guard: is value a plain object (non-null, non-array)?
|
|
23
|
+
*/
|
|
24
|
+
export function isRecord(v) {
|
|
25
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a JSON string (typically raw LLM output) into a typed object.
|
|
29
|
+
* Returns null on parse failure.
|
|
30
|
+
*/
|
|
31
|
+
export function parseLlmJson(raw) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=check-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check-utils.js","sourceRoot":"","sources":["../../src/check-utils.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAU;IAC5C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,UAAU;QAAE,OAAO,CAAC,CAAC;IAC7D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,GAAG,UAAsB;IACrD,IAAI,UAAU,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,UAAU,CAAC;IACvD,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAC/C,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,CAAU;IACjC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAI,GAAW;IACzC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CognitiveCheckResult, AnomalyDetectionCheckConfig, LeukoHistory, PluginLogger } from "../types.js";
|
|
2
|
+
export declare function runAnomalyDetectionCheck(config: AnomalyDetectionCheckConfig, history: LeukoHistory | null, logger: PluginLogger): CognitiveCheckResult;
|
|
3
|
+
//# sourceMappingURL=anomaly-detection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anomaly-detection.d.ts","sourceRoot":"","sources":["../../../src/checks/anomaly-detection.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,oBAAoB,EAEpB,2BAA2B,EAC3B,YAAY,EACZ,YAAY,EAEb,MAAM,aAAa,CAAC;AAqGrB,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,2BAA2B,EACnC,OAAO,EAAE,YAAY,GAAG,IAAI,EAC5B,MAAM,EAAE,YAAY,GACnB,oBAAoB,CAsBtB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { statSync, existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const CHECK_NAME = "cognitive:anomaly_detection";
|
|
4
|
+
function getDirSizeMb(dirPath) {
|
|
5
|
+
try {
|
|
6
|
+
if (!existsSync(dirPath))
|
|
7
|
+
return null;
|
|
8
|
+
let totalBytes = 0;
|
|
9
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
10
|
+
for (const entry of entries) {
|
|
11
|
+
try {
|
|
12
|
+
if (entry.isFile())
|
|
13
|
+
totalBytes += statSync(join(dirPath, entry.name)).size;
|
|
14
|
+
}
|
|
15
|
+
catch { /* skip inaccessible */ }
|
|
16
|
+
}
|
|
17
|
+
return Math.round((totalBytes / (1024 * 1024)) * 100) / 100;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function detectTrends(history, metricName) {
|
|
24
|
+
if (!history || history.snapshots.length < 3)
|
|
25
|
+
return { consecutive: 0, direction: "stable" };
|
|
26
|
+
const values = history.snapshots
|
|
27
|
+
.filter((s) => typeof s.metrics[metricName] === "number")
|
|
28
|
+
.map((s) => s.metrics[metricName]);
|
|
29
|
+
if (values.length < 3)
|
|
30
|
+
return { consecutive: 0, direction: "stable" };
|
|
31
|
+
return computeConsecutiveTrend(values);
|
|
32
|
+
}
|
|
33
|
+
function computeConsecutiveTrend(values) {
|
|
34
|
+
let up = 0;
|
|
35
|
+
let down = 0;
|
|
36
|
+
for (let i = values.length - 1; i > 0; i--) {
|
|
37
|
+
const prev = values[i - 1];
|
|
38
|
+
const curr = values[i];
|
|
39
|
+
if (curr > prev) {
|
|
40
|
+
up++;
|
|
41
|
+
down = 0;
|
|
42
|
+
}
|
|
43
|
+
else if (curr < prev) {
|
|
44
|
+
down++;
|
|
45
|
+
up = 0;
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
if (down >= 3)
|
|
51
|
+
return { consecutive: down, direction: "shrinking" };
|
|
52
|
+
if (up >= 3)
|
|
53
|
+
return { consecutive: up, direction: "growing" };
|
|
54
|
+
return { consecutive: 0, direction: "stable" };
|
|
55
|
+
}
|
|
56
|
+
function checkDirSizes(config, history, baselines, anomalies) {
|
|
57
|
+
for (const dir of config.monitoredDirs) {
|
|
58
|
+
const resolved = dir.path.replace(/^~/, process.env["HOME"] ?? "/tmp");
|
|
59
|
+
const currentMb = getDirSizeMb(resolved);
|
|
60
|
+
if (currentMb === null)
|
|
61
|
+
continue;
|
|
62
|
+
baselines[`${dir.label}_dir_mb`] = currentMb;
|
|
63
|
+
checkGrowthAnomaly(dir.label, currentMb, history, anomalies);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function checkGrowthAnomaly(label, currentMb, history, anomalies) {
|
|
67
|
+
if (!history || history.snapshots.length === 0)
|
|
68
|
+
return;
|
|
69
|
+
const weekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
70
|
+
const snap = history.snapshots.find((s) => {
|
|
71
|
+
const ts = new Date(s.timestamp).getTime();
|
|
72
|
+
return !isNaN(ts) && ts < weekAgo;
|
|
73
|
+
});
|
|
74
|
+
if (!snap)
|
|
75
|
+
return;
|
|
76
|
+
const baselineMb = snap.metrics[`${label}_dir_mb`];
|
|
77
|
+
if (typeof baselineMb !== "number" || baselineMb <= 0)
|
|
78
|
+
return;
|
|
79
|
+
const ratio = currentMb / baselineMb;
|
|
80
|
+
if (ratio > 5) {
|
|
81
|
+
anomalies.push({ metric: `${label}_dir_mb`, current: currentMb, baseline: baselineMb, deviation: `${ratio.toFixed(1)}x growth in 7 days`, severity: "critical" });
|
|
82
|
+
}
|
|
83
|
+
else if (ratio > 2) {
|
|
84
|
+
anomalies.push({ metric: `${label}_dir_mb`, current: currentMb, baseline: baselineMb, deviation: `${ratio.toFixed(1)}x growth in 7 days`, severity: "warn" });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function checkMetricTrends(history, anomalies) {
|
|
88
|
+
const tracked = ["fact_count", "goal_count", "thread_count"];
|
|
89
|
+
for (const metric of tracked) {
|
|
90
|
+
const trend = detectTrends(history, metric);
|
|
91
|
+
if (trend.direction !== "shrinking" || trend.consecutive < 3)
|
|
92
|
+
continue;
|
|
93
|
+
const severity = trend.consecutive >= 5 ? "critical" : "warn";
|
|
94
|
+
const deviation = trend.consecutive >= 5
|
|
95
|
+
? `${trend.consecutive} consecutive decreases — possible data loss`
|
|
96
|
+
: `${trend.consecutive} consecutive decreases`;
|
|
97
|
+
anomalies.push({ metric, current: trend.consecutive, baseline: 0, deviation, severity });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function overallSeverity(anomalies) {
|
|
101
|
+
if (anomalies.some((a) => a.severity === "critical"))
|
|
102
|
+
return "critical";
|
|
103
|
+
if (anomalies.some((a) => a.severity === "warn"))
|
|
104
|
+
return "warn";
|
|
105
|
+
return "ok";
|
|
106
|
+
}
|
|
107
|
+
export function runAnomalyDetectionCheck(config, history, logger) {
|
|
108
|
+
const startMs = Date.now();
|
|
109
|
+
const timestamp = new Date().toISOString();
|
|
110
|
+
const anomalies = [];
|
|
111
|
+
const baselines = {};
|
|
112
|
+
checkDirSizes(config, history, baselines, anomalies);
|
|
113
|
+
checkMetricTrends(history, anomalies);
|
|
114
|
+
const detail = anomalies.length === 0
|
|
115
|
+
? "All metrics within normal range"
|
|
116
|
+
: `${anomalies.length} anomaly(s) detected`;
|
|
117
|
+
return {
|
|
118
|
+
check_name: CHECK_NAME,
|
|
119
|
+
severity: overallSeverity(anomalies),
|
|
120
|
+
detail,
|
|
121
|
+
anomalies,
|
|
122
|
+
baselines,
|
|
123
|
+
timestamp,
|
|
124
|
+
duration_ms: Date.now() - startMs,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=anomaly-detection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anomaly-detection.js","sourceRoot":"","sources":["../../../src/checks/anomaly-detection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CognitiveCheckResult, BootstrapIntegrityCheckConfig, LlmClient, LeukoStatus, PluginLogger } from "../types.js";
|
|
2
|
+
export declare function runBootstrapIntegrityCheck(config: BootstrapIntegrityCheckConfig, llm: LlmClient, status: LeukoStatus | null, logger: PluginLogger): Promise<CognitiveCheckResult>;
|
|
3
|
+
//# sourceMappingURL=bootstrap-integrity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap-integrity.d.ts","sourceRoot":"","sources":["../../../src/checks/bootstrap-integrity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EAEpB,6BAA6B,EAC7B,SAAS,EACT,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAC;AAqErB,wBAAsB,0BAA0B,CAC9C,MAAM,EAAE,6BAA6B,EACrC,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,WAAW,GAAG,IAAI,EAC1B,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,oBAAoB,CAAC,CA2D/B"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { readTextInput } from "../status-reader.js";
|
|
2
|
+
import { parseSeverityString } from "../check-utils.js";
|
|
3
|
+
import { runLlmCheck } from "../check-runner.js";
|
|
4
|
+
const CHECK_NAME = "cognitive:bootstrap_integrity";
|
|
5
|
+
const SYSTEM_PROMPT = `You are a system health evaluator. Verify that the BOOTSTRAP.md file is factually current and complete.
|
|
6
|
+
Respond ONLY with valid JSON matching this schema:
|
|
7
|
+
{
|
|
8
|
+
"severity": "ok" | "warn" | "critical",
|
|
9
|
+
"detail": "single line summary",
|
|
10
|
+
"findings": [
|
|
11
|
+
{
|
|
12
|
+
"issue": "stale_reference" | "missing_subsystem" | "factual_error" | "outdated_state",
|
|
13
|
+
"line": "the problematic text from BOOTSTRAP.md",
|
|
14
|
+
"detail": "explanation of what's wrong",
|
|
15
|
+
"recommendation": "what to fix"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Evaluation rules:
|
|
21
|
+
- Does it reference services/crons that no longer exist?
|
|
22
|
+
- Are file paths correct?
|
|
23
|
+
- Is "current state" aligned with actual system status?
|
|
24
|
+
- Are key subsystems mentioned (NATS, Membrane, Cortex, Leuko, Governance)?
|
|
25
|
+
- Content aligns with system state → "ok"
|
|
26
|
+
- Minor omissions or stale references → "warn"
|
|
27
|
+
- Major factual errors or missing critical subsystems → "critical"`;
|
|
28
|
+
function buildSystemContext(status) {
|
|
29
|
+
if (!status)
|
|
30
|
+
return "System status: unavailable";
|
|
31
|
+
const daemonSummary = status.daemon_checks
|
|
32
|
+
.filter((c) => c.severity !== "ok")
|
|
33
|
+
.map((c) => `${c.check_name}: ${c.severity} — ${c.detail}`)
|
|
34
|
+
.join("\n");
|
|
35
|
+
return [
|
|
36
|
+
`Last check: ${status.last_check}`,
|
|
37
|
+
`Overall severity: ${status.overall_severity}`,
|
|
38
|
+
`Daemon checks: ${status.daemon_checks.length} total`,
|
|
39
|
+
daemonSummary ? `Issues:\n${daemonSummary}` : "All daemon checks OK",
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
function parseFindings(parsed) {
|
|
43
|
+
if (!Array.isArray(parsed.findings))
|
|
44
|
+
return [];
|
|
45
|
+
return parsed.findings.map((f) => ({
|
|
46
|
+
issue: typeof f.issue === "string" ? f.issue : "unknown",
|
|
47
|
+
line: typeof f.line === "string" ? f.line : undefined,
|
|
48
|
+
detail: typeof f.detail === "string" ? f.detail : "",
|
|
49
|
+
recommendation: typeof f.recommendation === "string" ? f.recommendation : undefined,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
export async function runBootstrapIntegrityCheck(config, llm, status, logger) {
|
|
53
|
+
return runLlmCheck({
|
|
54
|
+
name: CHECK_NAME,
|
|
55
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
56
|
+
llm,
|
|
57
|
+
logger,
|
|
58
|
+
readInput(timestamp, startMs) {
|
|
59
|
+
const content = readTextInput(config.inputPath, 4000, logger);
|
|
60
|
+
if (content === null) {
|
|
61
|
+
return { ok: false, skip: { check_name: CHECK_NAME, severity: "warn", detail: "BOOTSTRAP.md not found — cannot verify integrity", timestamp, duration_ms: Date.now() - startMs } };
|
|
62
|
+
}
|
|
63
|
+
return { ok: true, input: { content, systemContext: buildSystemContext(status) } };
|
|
64
|
+
},
|
|
65
|
+
preFilter() {
|
|
66
|
+
return { severity: "ok", findingCount: 0, data: {} };
|
|
67
|
+
},
|
|
68
|
+
buildPrompt(input) {
|
|
69
|
+
return [
|
|
70
|
+
`Current date: ${new Date().toISOString().split("T")[0]}`,
|
|
71
|
+
"",
|
|
72
|
+
"=== System State ===",
|
|
73
|
+
input.systemContext,
|
|
74
|
+
"",
|
|
75
|
+
"=== BOOTSTRAP.md Content ===",
|
|
76
|
+
input.content,
|
|
77
|
+
].join("\n");
|
|
78
|
+
},
|
|
79
|
+
buildFailOpen({ message, llmModel, llmTokens, timestamp, startMs }) {
|
|
80
|
+
return {
|
|
81
|
+
check_name: CHECK_NAME,
|
|
82
|
+
severity: "ok",
|
|
83
|
+
detail: `${message} — check skipped`,
|
|
84
|
+
timestamp,
|
|
85
|
+
model_used: llmModel,
|
|
86
|
+
tokens_used: llmTokens,
|
|
87
|
+
duration_ms: Date.now() - startMs,
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
mergeResults(opts) {
|
|
91
|
+
const findings = parseFindings(opts.parsed);
|
|
92
|
+
return {
|
|
93
|
+
check_name: CHECK_NAME,
|
|
94
|
+
severity: parseSeverityString(opts.parsed.severity),
|
|
95
|
+
detail: typeof opts.parsed.detail === "string"
|
|
96
|
+
? opts.parsed.detail
|
|
97
|
+
: `${findings.length} findings`,
|
|
98
|
+
findings,
|
|
99
|
+
timestamp: opts.timestamp,
|
|
100
|
+
model_used: opts.llmModel,
|
|
101
|
+
tokens_used: opts.llmTokens,
|
|
102
|
+
duration_ms: Date.now() - opts.startMs,
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=bootstrap-integrity.js.map
|