@vodailoc/kilo-kit-mcp 1.1.1 → 1.2.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/README.md +33 -3
- package/mcp/README.md +38 -10
- package/mcp/dist/formatters.js +142 -1
- package/mcp/dist/orchestration-audit.js +20 -0
- package/mcp/dist/orchestration-memory.js +258 -0
- package/mcp/dist/orchestration-types.js +1 -0
- package/mcp/dist/orchestrator.js +222 -0
- package/mcp/dist/question-templates.js +249 -0
- package/mcp/dist/route-analytics.js +149 -0
- package/mcp/dist/router.js +75 -82
- package/mcp/dist/routing-policy-data.js +241 -0
- package/mcp/dist/routing-policy.js +145 -0
- package/mcp/dist/server.js +93 -4
- package/mcp/dist/smoke-env.js +18 -0
- package/mcp/dist/smoke.js +68 -1
- package/mcp/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/social-preview.png" alt="Kilo-Kit social preview" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://github.com/VoDaiLocz/KILO-KIT/stargazers"><img src="https://img.shields.io/github/stars/VoDaiLocz/KILO-KIT?style=for-the-badge&logo=github&label=Stars&color=18181b&labelColor=0f172a" alt="GitHub stars"></a>
|
|
7
|
+
<a href="https://github.com/VoDaiLocz/KILO-KIT/commits/main"><img src="https://img.shields.io/github/last-commit/VoDaiLocz/KILO-KIT?style=for-the-badge&logo=git&label=Last%20commit&color=22c55e&labelColor=0f172a" alt="Last commit"></a>
|
|
8
|
+
<a href="https://github.com/VoDaiLocz/KILO-KIT/graphs/contributors"><img src="https://img.shields.io/github/contributors/VoDaiLocz/KILO-KIT?style=for-the-badge&logo=github&label=Contributors&color=f97316&labelColor=0f172a" alt="Contributors"></a>
|
|
9
|
+
<a href="https://github.com/VoDaiLocz/KILO-KIT/actions/workflows/publish.yml"><img src="https://img.shields.io/github/actions/workflow/status/VoDaiLocz/KILO-KIT/publish.yml?style=for-the-badge&logo=githubactions&label=Publish&color=22c55e&labelColor=0f172a" alt="Publish workflow"></a>
|
|
10
|
+
<a href="https://www.npmjs.com/package/@vodailoc/kilo-kit-mcp"><img src="https://img.shields.io/npm/v/@vodailoc/kilo-kit-mcp?style=for-the-badge&logo=npm&label=npm&color=ef4444&labelColor=0f172a" alt="npm version"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@vodailoc/kilo-kit-mcp"><img src="https://img.shields.io/npm/dm/@vodailoc/kilo-kit-mcp?style=for-the-badge&logo=npm&label=downloads&color=0284c7&labelColor=0f172a" alt="npm downloads"></a>
|
|
12
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/VoDaiLocz/KILO-KIT?style=for-the-badge&label=License&color=64748b&labelColor=0f172a" alt="License"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
<p align="center">
|
|
16
|
+
<img src="https://img.shields.io/badge/skills-134-06b6d4?style=for-the-badge&labelColor=0f172a" alt="134 skills">
|
|
17
|
+
<img src="https://img.shields.io/badge/MCP-ready-14b8a6?style=for-the-badge&logo=modelcontextprotocol&labelColor=0f172a" alt="MCP ready">
|
|
18
|
+
<img src="https://img.shields.io/badge/Codex-ready-111827?style=for-the-badge&logo=openai&labelColor=0f172a" alt="Codex ready">
|
|
19
|
+
<img src="https://img.shields.io/badge/Trusted%20Publishing-OIDC-8b5cf6?style=for-the-badge&logo=githubactions&labelColor=0f172a" alt="Trusted publishing">
|
|
20
|
+
<img src="https://img.shields.io/badge/Node-%3E%3D20-339933?style=for-the-badge&logo=nodedotjs&labelColor=0f172a" alt="Node >=20">
|
|
21
|
+
<img src="https://img.shields.io/badge/TypeScript-5.9-3178c6?style=for-the-badge&logo=typescript&labelColor=0f172a" alt="TypeScript 5.9">
|
|
22
|
+
</p>
|
|
23
|
+
|
|
1
24
|
# 🚀 Kilo-Kit: Professional AI Agent Development Framework
|
|
2
25
|
|
|
3
26
|
> **Version:** 1.0.0
|
|
@@ -173,13 +196,16 @@ Skills are automatically loaded when your task matches their keywords. See the [
|
|
|
173
196
|
|
|
174
197
|
## 🔌 MCP Integration
|
|
175
198
|
|
|
176
|
-
Kilo-Kit v1.
|
|
199
|
+
Kilo-Kit v1.2.0 includes a read-only MCP server that exposes the skill library as an adaptive routing service for MCP-capable agents.
|
|
177
200
|
|
|
178
201
|
| MCP Surface | Purpose |
|
|
179
202
|
|-------------|---------|
|
|
180
|
-
| `
|
|
203
|
+
| `kilo_orchestrate_task` | C4 central gate: route internally, require brainstorming, ask questions, check memory, and release final workflow |
|
|
204
|
+
| `kilo_route_intent` | Route the current chat request to skills, workflow order, rule hierarchy, and decision trail |
|
|
181
205
|
| `kilo_search_skills` | Search the skill catalog by task or keyword |
|
|
182
206
|
| `kilo_get_skill` | Load one exact `SKILL.md` with context-safe truncation |
|
|
207
|
+
| `kilo_route_report` | Summarize route telemetry, top skills, workflows, and conflict penalties |
|
|
208
|
+
| `kilo_memory_report` | Inspect C4 global memory facts, decisions, and suggestions |
|
|
183
209
|
| `kilo_validate_skills` | Run the skill validation quality gate |
|
|
184
210
|
| `kilo://skills/index` | Resource view of the lightweight skill index |
|
|
185
211
|
| `kilo://skills/{category}/{skill}` | Resource view for one skill |
|
|
@@ -209,6 +235,10 @@ enabled = true
|
|
|
209
235
|
|
|
210
236
|
The `--prefix` keeps npm from resolving the local source checkout when Codex is opened inside the Kilo-Kit repository.
|
|
211
237
|
|
|
238
|
+
Route telemetry is kept in memory by default. Set `KILO_KIT_WRITE_DECISIONS=true` to persist JSONL route decisions under `.kilo/decision-trail.jsonl`, or set `KILO_KIT_DECISION_TRAIL_PATH` to choose a different file.
|
|
239
|
+
|
|
240
|
+
C4 orchestration memory is global by default at `~/.kilo-kit/orchestrator.sqlite` when Node's SQLite runtime is available. Set `KILO_KIT_MEMORY_PATH` to use a different SQLite file, and set `KILO_KIT_ORCHESTRATION_AUDIT_PATH` to write an append-only JSONL audit trail for C4 state transitions.
|
|
241
|
+
|
|
212
242
|
For local development, build and verify:
|
|
213
243
|
|
|
214
244
|
```bash
|
|
@@ -247,7 +277,7 @@ Kilo-Kit publishes `@vodailoc/kilo-kit-mcp` through npm Trusted Publishing. Conf
|
|
|
247
277
|
| Repository | `VoDaiLocz/KILO-KIT` |
|
|
248
278
|
| Workflow filename | `publish.yml` |
|
|
249
279
|
|
|
250
|
-
After that, run the GitHub Actions workflow `Publish npm package`, or push a version tag such as `v1.
|
|
280
|
+
After that, run the GitHub Actions workflow `Publish npm package`, or push a version tag such as `v1.2.0`. The workflow uses OIDC, so it does not need an npm token or interactive OTP.
|
|
251
281
|
|
|
252
282
|
## 📋 Skill Dispatch Table
|
|
253
283
|
|
package/mcp/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🔌 Kilo-Kit MCP Server
|
|
2
2
|
|
|
3
|
-
> **Version:** 1.
|
|
3
|
+
> **Version:** 1.2.0
|
|
4
4
|
> **Mode:** Read-only Skill Registry + Validator
|
|
5
5
|
> **Transport:** stdio
|
|
6
6
|
|
|
@@ -12,9 +12,12 @@ The Kilo-Kit MCP server exposes the `skills/` workflow surface to MCP-capable ag
|
|
|
12
12
|
|
|
13
13
|
| Capability | MCP Tool | Purpose |
|
|
14
14
|
|------------|----------|---------|
|
|
15
|
+
| C4 orchestration | `kilo_orchestrate_task` | Enforce brainstorming-first workflow, required questions, memory suggestions, and final workflow release |
|
|
15
16
|
| Skill discovery | `kilo_search_skills` | Search the skill library by task/query |
|
|
16
|
-
| Intent routing | `kilo_route_intent` | Recommend skills for the current chat context |
|
|
17
|
+
| Intent routing | `kilo_route_intent` | Recommend skills, workflow order, rule hierarchy, and decision trail for the current chat context |
|
|
17
18
|
| Skill loading | `kilo_get_skill` | Load one exact `SKILL.md` with output limits |
|
|
19
|
+
| Route reporting | `kilo_route_report` | Summarize route telemetry, top skills, workflows, scores, and conflict penalties |
|
|
20
|
+
| Memory reporting | `kilo_memory_report` | Inspect C4 global memory facts, decisions, and suggestions |
|
|
18
21
|
| Quality gate | `kilo_validate_skills` | Run the Kilo-Kit skill validator summary |
|
|
19
22
|
|
|
20
23
|
Resources:
|
|
@@ -47,8 +50,8 @@ npm run smoke
|
|
|
47
50
|
Expected verification:
|
|
48
51
|
|
|
49
52
|
```text
|
|
50
|
-
Test Files
|
|
51
|
-
Tests
|
|
53
|
+
Test Files 9 passed
|
|
54
|
+
Tests 22 passed
|
|
52
55
|
MCP smoke check passed.
|
|
53
56
|
```
|
|
54
57
|
|
|
@@ -71,6 +74,25 @@ Use the published npm package in any MCP-capable client:
|
|
|
71
74
|
|
|
72
75
|
The npm package includes the Kilo-Kit skill library, core master file, validator, and built MCP server.
|
|
73
76
|
|
|
77
|
+
Route telemetry is in-memory by default. To persist route decisions between server runs, set:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
KILO_KIT_WRITE_DECISIONS=true
|
|
81
|
+
# optional override:
|
|
82
|
+
KILO_KIT_DECISION_TRAIL_PATH=/absolute/path/decision-trail.jsonl
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
When persistence is enabled and no override is provided, decisions are written to `.kilo/decision-trail.jsonl` under the configured repository root.
|
|
86
|
+
|
|
87
|
+
C4 orchestration memory is global by default at `~/.kilo-kit/orchestrator.sqlite` when Node's SQLite runtime is available. Override it with:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
KILO_KIT_MEMORY_PATH=/absolute/path/orchestrator.sqlite
|
|
91
|
+
KILO_KIT_ORCHESTRATION_AUDIT_PATH=/absolute/path/orchestration-audit.jsonl
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`kilo_orchestrate_task` uses the C4 Brainstorming-First Gate: substantive work starts with `productivity/brainstorming`, then required questions, then memory suggestions that require explicit accept/reject before a final workflow is released.
|
|
95
|
+
|
|
74
96
|
### Codex CLI on Windows
|
|
75
97
|
|
|
76
98
|
When Codex is opened inside the Kilo-Kit source checkout, `npx -y @vodailoc/kilo-kit-mcp` can resolve the local package instead of the published package. Use an npm prefix outside the repository:
|
|
@@ -98,8 +120,8 @@ Configure the npm package once:
|
|
|
98
120
|
Then publish by running the GitHub Actions workflow `Publish npm package`, or by pushing a version tag:
|
|
99
121
|
|
|
100
122
|
```bash
|
|
101
|
-
git tag v1.
|
|
102
|
-
git push origin v1.
|
|
123
|
+
git tag v1.2.0
|
|
124
|
+
git push origin v1.2.0
|
|
103
125
|
```
|
|
104
126
|
|
|
105
127
|
The workflow runs build, typecheck, tests, smoke, skill validation, package dry-run, and then `npm publish --access public --ignore-scripts` through OIDC.
|
|
@@ -136,7 +158,9 @@ Use `.mcp/kilo-kit.example.json` as the portable template.
|
|
|
136
158
|
|
|
137
159
|
## 🛡️ Security Posture
|
|
138
160
|
|
|
139
|
-
-
|
|
161
|
+
- Externally read-only by default.
|
|
162
|
+
- Route telemetry is in-memory by default; JSONL persistence is opt-in with `KILO_KIT_WRITE_DECISIONS=true`.
|
|
163
|
+
- C4 memory stores structured facts and decisions, not raw chat logs.
|
|
140
164
|
- No arbitrary filesystem reads.
|
|
141
165
|
- Category and skill names must be single kebab-case path segments.
|
|
142
166
|
- Repository paths are resolved through an allowlist boundary.
|
|
@@ -149,8 +173,12 @@ Use `.mcp/kilo-kit.example.json` as the portable template.
|
|
|
149
173
|
|
|
150
174
|
```text
|
|
151
175
|
User request
|
|
152
|
-
→
|
|
153
|
-
→
|
|
154
|
-
→
|
|
176
|
+
→ kilo_orchestrate_task(message, context)
|
|
177
|
+
→ answer required C4 questions
|
|
178
|
+
→ accept/reject memory suggestions
|
|
179
|
+
→ kilo_get_skill(category, skill) for the first workflow skill
|
|
180
|
+
→ follow final workflow skills in order
|
|
181
|
+
→ kilo_route_report when you need routing analytics
|
|
182
|
+
→ kilo_memory_report when you need memory analytics
|
|
155
183
|
→ kilo_validate_skills before claiming the skill library is healthy
|
|
156
184
|
```
|
package/mcp/dist/formatters.js
CHANGED
|
@@ -54,7 +54,23 @@ export function formatRoute(result, format) {
|
|
|
54
54
|
return [
|
|
55
55
|
"# Kilo-Kit Skill Route",
|
|
56
56
|
"",
|
|
57
|
-
|
|
57
|
+
`Task mode: \`${result.taskMode}\``,
|
|
58
|
+
"",
|
|
59
|
+
"## Recommended Skills",
|
|
60
|
+
"",
|
|
61
|
+
...result.recommended.map((item, index) => `${index + 1}. **${item.skill.id}** (${Math.round(item.confidence * 100)}%, score ${item.score ?? "n/a"})\n ${item.reason}\n Path: \`${item.skill.skillPath}\``),
|
|
62
|
+
"",
|
|
63
|
+
"## Workflow",
|
|
64
|
+
"",
|
|
65
|
+
...result.workflow.map((step, index) => `${index + 1}. **${step.skill.id}** (${step.role})\n ${step.reason}`),
|
|
66
|
+
"",
|
|
67
|
+
"## Rule Hierarchy",
|
|
68
|
+
"",
|
|
69
|
+
result.ruleHierarchy.map((rule, index) => `${index + 1}. \`${rule}\``).join("\n"),
|
|
70
|
+
"",
|
|
71
|
+
"## Decision Trail",
|
|
72
|
+
"",
|
|
73
|
+
...result.decisionTrail.slice(0, 5).map((entry, index) => `${index + 1}. **${entry.skillId}** score ${entry.score}\n Signals: ${entry.matchedSignals.length > 0 ? entry.matchedSignals.map((signal) => `\`${signal}\``).join(", ") : "none"}\n ${entry.reason}`),
|
|
58
74
|
"",
|
|
59
75
|
`Next action: ${result.nextAction}`,
|
|
60
76
|
].join("\n");
|
|
@@ -75,3 +91,128 @@ export function formatValidation(summary, format) {
|
|
|
75
91
|
"```",
|
|
76
92
|
].join("\n");
|
|
77
93
|
}
|
|
94
|
+
export function formatRouteReport(report, format) {
|
|
95
|
+
if (format === "json") {
|
|
96
|
+
return JSON.stringify(report, null, 2);
|
|
97
|
+
}
|
|
98
|
+
const taskModes = report.taskModes.length > 0
|
|
99
|
+
? report.taskModes.map((item) => `- \`${item.taskMode}\`: ${item.count}`).join("\n")
|
|
100
|
+
: "- No task modes recorded.";
|
|
101
|
+
const workflows = report.workflows.length > 0
|
|
102
|
+
? report.workflows
|
|
103
|
+
.slice(0, 5)
|
|
104
|
+
.map((item) => `- ${item.workflow.map((skill) => `\`${skill}\``).join(" -> ")}: ${item.count}`)
|
|
105
|
+
.join("\n")
|
|
106
|
+
: "- No workflows recorded.";
|
|
107
|
+
const topSkills = report.topSkills.length > 0
|
|
108
|
+
? report.topSkills
|
|
109
|
+
.slice(0, 10)
|
|
110
|
+
.map((item) => `- \`${item.skillId}\`: recommended ${item.timesRecommended}, workflow ${item.timesInWorkflow}, primary ${item.timesPrimary}, avg score ${item.avgScore}`)
|
|
111
|
+
.join("\n")
|
|
112
|
+
: "- No skills recorded.";
|
|
113
|
+
const conflicts = report.conflictPenalties.length > 0
|
|
114
|
+
? report.conflictPenalties
|
|
115
|
+
.map((item) => `- \`${item.skillId}\`: ${item.count} penalties, total ${item.totalPenalty}`)
|
|
116
|
+
.join("\n")
|
|
117
|
+
: "- No conflict penalties recorded.";
|
|
118
|
+
return [
|
|
119
|
+
"# Kilo-Kit Route Report",
|
|
120
|
+
"",
|
|
121
|
+
`Total events: **${report.totalEvents}**`,
|
|
122
|
+
"",
|
|
123
|
+
"## Task Modes",
|
|
124
|
+
taskModes,
|
|
125
|
+
"",
|
|
126
|
+
"## Workflows",
|
|
127
|
+
workflows,
|
|
128
|
+
"",
|
|
129
|
+
"## Top Skills",
|
|
130
|
+
topSkills,
|
|
131
|
+
"",
|
|
132
|
+
"## Conflict Penalties",
|
|
133
|
+
conflicts,
|
|
134
|
+
].join("\n");
|
|
135
|
+
}
|
|
136
|
+
export function formatOrchestration(result, format) {
|
|
137
|
+
if (format === "json") {
|
|
138
|
+
return JSON.stringify(result, null, 2);
|
|
139
|
+
}
|
|
140
|
+
const questions = result.questions.length > 0
|
|
141
|
+
? result.questions
|
|
142
|
+
.map((question, index) => `${index + 1}. **${question.id}**${question.required ? " (required)" : ""}\n ${question.prompt}`)
|
|
143
|
+
.join("\n")
|
|
144
|
+
: "No questions required.";
|
|
145
|
+
const workflow = result.workflow.length > 0
|
|
146
|
+
? result.workflow.map((step, index) => `${index + 1}. **${step.skill.id}** (${step.role})\n ${step.reason}`).join("\n")
|
|
147
|
+
: "No workflow selected.";
|
|
148
|
+
const memorySuggestions = result.memorySuggestions.length > 0
|
|
149
|
+
? result.memorySuggestions
|
|
150
|
+
.map((suggestion) => `- **${suggestion.key}** (${Math.round(suggestion.confidence * 100)}%)\n ${suggestion.reason}\n Requires confirmation: ${suggestion.requiresConfirmation ? "yes" : "no"}`)
|
|
151
|
+
.join("\n")
|
|
152
|
+
: "- No memory suggestions.";
|
|
153
|
+
const verification = result.verificationGate.commands.length > 0
|
|
154
|
+
? result.verificationGate.commands.map((command) => `- \`${command}\``).join("\n")
|
|
155
|
+
: "- No commands selected.";
|
|
156
|
+
return [
|
|
157
|
+
"# Kilo-Kit C4 Orchestration",
|
|
158
|
+
"",
|
|
159
|
+
`Session: \`${result.sessionId}\``,
|
|
160
|
+
`State: \`${result.state}\``,
|
|
161
|
+
`Task mode: \`${result.taskMode}\``,
|
|
162
|
+
"",
|
|
163
|
+
"## Questions",
|
|
164
|
+
questions,
|
|
165
|
+
"",
|
|
166
|
+
"## Workflow",
|
|
167
|
+
workflow,
|
|
168
|
+
"",
|
|
169
|
+
"## Memory Suggestions",
|
|
170
|
+
memorySuggestions,
|
|
171
|
+
"",
|
|
172
|
+
"## Verification Gate",
|
|
173
|
+
verification,
|
|
174
|
+
"",
|
|
175
|
+
`Next action: ${result.nextAction}`,
|
|
176
|
+
].join("\n");
|
|
177
|
+
}
|
|
178
|
+
export function formatMemoryReport(report, format) {
|
|
179
|
+
if (format === "json") {
|
|
180
|
+
return JSON.stringify(report, null, 2);
|
|
181
|
+
}
|
|
182
|
+
const facts = report.facts.length > 0
|
|
183
|
+
? report.facts
|
|
184
|
+
.slice(0, 20)
|
|
185
|
+
.map((fact) => `- \`${fact.key}\` (${fact.kind}, confidence ${fact.confidence}) from ${fact.source}`)
|
|
186
|
+
.join("\n")
|
|
187
|
+
: "- No memory facts recorded.";
|
|
188
|
+
const decisions = report.decisions.length > 0
|
|
189
|
+
? report.decisions.map((decision) => `- \`${decision.suggestionKey}\`: ${decision.decision}`).join("\n")
|
|
190
|
+
: "- No memory decisions recorded.";
|
|
191
|
+
const sessions = report.sessions.length > 0
|
|
192
|
+
? report.sessions
|
|
193
|
+
.slice(0, 10)
|
|
194
|
+
.map((session) => `- \`${session.id}\`: ${session.state} (${session.taskMode})`)
|
|
195
|
+
.join("\n")
|
|
196
|
+
: "- No orchestration sessions recorded.";
|
|
197
|
+
const outcomes = report.outcomes.length > 0
|
|
198
|
+
? report.outcomes
|
|
199
|
+
.slice(0, 10)
|
|
200
|
+
.map((outcome) => `- \`${outcome.id}\`: ${outcome.outcome} (${outcome.taskMode})`)
|
|
201
|
+
.join("\n")
|
|
202
|
+
: "- No workflow outcomes recorded.";
|
|
203
|
+
return [
|
|
204
|
+
"# Kilo-Kit Memory Report",
|
|
205
|
+
"",
|
|
206
|
+
"## Facts",
|
|
207
|
+
facts,
|
|
208
|
+
"",
|
|
209
|
+
"## Decisions",
|
|
210
|
+
decisions,
|
|
211
|
+
"",
|
|
212
|
+
"## Sessions",
|
|
213
|
+
sessions,
|
|
214
|
+
"",
|
|
215
|
+
"## Outcomes",
|
|
216
|
+
outcomes,
|
|
217
|
+
].join("\n");
|
|
218
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
export function createNoopOrchestrationAudit() {
|
|
5
|
+
return {
|
|
6
|
+
record() {
|
|
7
|
+
return undefined;
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function createJsonlOrchestrationAudit(filePath) {
|
|
12
|
+
return {
|
|
13
|
+
record(event) {
|
|
14
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
15
|
+
const auditRef = randomUUID();
|
|
16
|
+
appendFileSync(filePath, `${JSON.stringify({ auditRef, timestamp: new Date().toISOString(), ...event })}\n`, "utf8");
|
|
17
|
+
return auditRef;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
export function createInMemoryOrchestrationMemory(initialFacts = []) {
|
|
5
|
+
const facts = new Map(initialFacts.map((fact) => [fact.key, clone(fact)]));
|
|
6
|
+
const decisions = [];
|
|
7
|
+
const suggestions = [];
|
|
8
|
+
const sessions = new Map();
|
|
9
|
+
const outcomes = [];
|
|
10
|
+
return {
|
|
11
|
+
rememberFact(fact) {
|
|
12
|
+
facts.set(fact.key, normalizeFact(fact, facts.get(fact.key)));
|
|
13
|
+
},
|
|
14
|
+
suggest(input) {
|
|
15
|
+
const produced = buildSuggestions([...facts.values()], input);
|
|
16
|
+
suggestions.splice(0, suggestions.length, ...produced.map(clone));
|
|
17
|
+
return produced;
|
|
18
|
+
},
|
|
19
|
+
recordDecision(decision) {
|
|
20
|
+
decisions.push({ ...decision });
|
|
21
|
+
},
|
|
22
|
+
recordSession(session) {
|
|
23
|
+
sessions.set(session.id, clone(session));
|
|
24
|
+
},
|
|
25
|
+
recordOutcome(outcome) {
|
|
26
|
+
outcomes.push(clone(outcome));
|
|
27
|
+
},
|
|
28
|
+
report() {
|
|
29
|
+
return {
|
|
30
|
+
facts: [...facts.values()].map(clone),
|
|
31
|
+
decisions: decisions.map((decision) => ({ ...decision })),
|
|
32
|
+
suggestions: suggestions.map(clone),
|
|
33
|
+
sessions: [...sessions.values()].map(clone),
|
|
34
|
+
outcomes: outcomes.map(clone),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export async function createSqliteOrchestrationMemory(options) {
|
|
40
|
+
let sqlite;
|
|
41
|
+
try {
|
|
42
|
+
sqlite = await import("node:sqlite");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return createInMemoryOrchestrationMemory();
|
|
46
|
+
}
|
|
47
|
+
mkdirSync(path.dirname(options.filePath), { recursive: true });
|
|
48
|
+
const database = new sqlite.DatabaseSync(options.filePath);
|
|
49
|
+
database.exec(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS memory_facts (
|
|
51
|
+
id TEXT PRIMARY KEY,
|
|
52
|
+
kind TEXT NOT NULL,
|
|
53
|
+
key TEXT NOT NULL UNIQUE,
|
|
54
|
+
value_json TEXT NOT NULL,
|
|
55
|
+
confidence REAL NOT NULL,
|
|
56
|
+
source TEXT NOT NULL,
|
|
57
|
+
created_at TEXT NOT NULL,
|
|
58
|
+
updated_at TEXT NOT NULL
|
|
59
|
+
);
|
|
60
|
+
CREATE TABLE IF NOT EXISTS memory_decisions (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
suggestion_key TEXT NOT NULL,
|
|
63
|
+
decision TEXT NOT NULL,
|
|
64
|
+
reason TEXT,
|
|
65
|
+
created_at TEXT NOT NULL
|
|
66
|
+
);
|
|
67
|
+
CREATE TABLE IF NOT EXISTS orchestration_sessions (
|
|
68
|
+
id TEXT PRIMARY KEY,
|
|
69
|
+
state TEXT NOT NULL,
|
|
70
|
+
message TEXT NOT NULL,
|
|
71
|
+
task_mode TEXT NOT NULL,
|
|
72
|
+
route_json TEXT NOT NULL,
|
|
73
|
+
questions_json TEXT NOT NULL,
|
|
74
|
+
answers_json TEXT NOT NULL,
|
|
75
|
+
memory_suggestions_json TEXT NOT NULL,
|
|
76
|
+
final_workflow_json TEXT NOT NULL,
|
|
77
|
+
created_at TEXT NOT NULL,
|
|
78
|
+
updated_at TEXT NOT NULL
|
|
79
|
+
);
|
|
80
|
+
CREATE TABLE IF NOT EXISTS workflow_outcomes (
|
|
81
|
+
id TEXT PRIMARY KEY,
|
|
82
|
+
session_id TEXT NOT NULL,
|
|
83
|
+
task_mode TEXT NOT NULL,
|
|
84
|
+
workflow_json TEXT NOT NULL,
|
|
85
|
+
verification_json TEXT NOT NULL,
|
|
86
|
+
outcome TEXT NOT NULL,
|
|
87
|
+
created_at TEXT NOT NULL
|
|
88
|
+
);
|
|
89
|
+
`);
|
|
90
|
+
return {
|
|
91
|
+
rememberFact(fact) {
|
|
92
|
+
const existing = factByKey(database, fact.key);
|
|
93
|
+
const normalized = normalizeFact(fact, existing);
|
|
94
|
+
database
|
|
95
|
+
.prepare(`INSERT INTO memory_facts (id, kind, key, value_json, confidence, source, created_at, updated_at)
|
|
96
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
97
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
98
|
+
kind = excluded.kind,
|
|
99
|
+
value_json = excluded.value_json,
|
|
100
|
+
confidence = excluded.confidence,
|
|
101
|
+
source = excluded.source,
|
|
102
|
+
updated_at = excluded.updated_at`)
|
|
103
|
+
.run(normalized.id, normalized.kind, normalized.key, JSON.stringify(normalized.value), normalized.confidence, normalized.source, normalized.createdAt, normalized.updatedAt);
|
|
104
|
+
},
|
|
105
|
+
suggest(input) {
|
|
106
|
+
return buildSuggestions(allFacts(database), input);
|
|
107
|
+
},
|
|
108
|
+
recordDecision(decision) {
|
|
109
|
+
database
|
|
110
|
+
.prepare(`INSERT INTO memory_decisions (id, suggestion_key, decision, reason, created_at)
|
|
111
|
+
VALUES (?, ?, ?, ?, ?)`)
|
|
112
|
+
.run(randomUUID(), decision.suggestionKey, decision.decision, decision.reason ?? null, new Date().toISOString());
|
|
113
|
+
},
|
|
114
|
+
recordSession(session) {
|
|
115
|
+
database
|
|
116
|
+
.prepare(`INSERT INTO orchestration_sessions
|
|
117
|
+
(id, state, message, task_mode, route_json, questions_json, answers_json, memory_suggestions_json, final_workflow_json, created_at, updated_at)
|
|
118
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
119
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
120
|
+
state = excluded.state,
|
|
121
|
+
message = excluded.message,
|
|
122
|
+
task_mode = excluded.task_mode,
|
|
123
|
+
route_json = excluded.route_json,
|
|
124
|
+
questions_json = excluded.questions_json,
|
|
125
|
+
answers_json = excluded.answers_json,
|
|
126
|
+
memory_suggestions_json = excluded.memory_suggestions_json,
|
|
127
|
+
final_workflow_json = excluded.final_workflow_json,
|
|
128
|
+
updated_at = excluded.updated_at`)
|
|
129
|
+
.run(session.id, session.state, session.message, session.taskMode, JSON.stringify(session.route), JSON.stringify(session.questions), JSON.stringify(session.answers), JSON.stringify(session.memorySuggestions), JSON.stringify(session.finalWorkflow), session.createdAt, session.updatedAt);
|
|
130
|
+
},
|
|
131
|
+
recordOutcome(outcome) {
|
|
132
|
+
database
|
|
133
|
+
.prepare(`INSERT INTO workflow_outcomes
|
|
134
|
+
(id, session_id, task_mode, workflow_json, verification_json, outcome, created_at)
|
|
135
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`)
|
|
136
|
+
.run(outcome.id, outcome.sessionId, outcome.taskMode, JSON.stringify(outcome.workflow), JSON.stringify(outcome.verification), outcome.outcome, outcome.createdAt);
|
|
137
|
+
},
|
|
138
|
+
report() {
|
|
139
|
+
return {
|
|
140
|
+
facts: allFacts(database),
|
|
141
|
+
decisions: allDecisions(database),
|
|
142
|
+
suggestions: [],
|
|
143
|
+
sessions: allSessions(database),
|
|
144
|
+
outcomes: allOutcomes(database),
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function buildSuggestions(facts, input) {
|
|
150
|
+
return facts
|
|
151
|
+
.filter((fact) => fact.confidence >= 0.7)
|
|
152
|
+
.filter((fact) => factApplies(fact, input))
|
|
153
|
+
.map((fact) => ({
|
|
154
|
+
key: fact.key,
|
|
155
|
+
title: suggestionTitle(fact),
|
|
156
|
+
reason: suggestionReason(fact, input),
|
|
157
|
+
value: clone(fact.value),
|
|
158
|
+
confidence: fact.confidence,
|
|
159
|
+
requiresConfirmation: true,
|
|
160
|
+
applied: false,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
function factApplies(fact, input) {
|
|
164
|
+
if (fact.kind === "verification-default") {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
if (fact.kind === "workflow-default") {
|
|
168
|
+
const skillId = fact.value.skillId;
|
|
169
|
+
return typeof skillId === "string" && !input.workflowSkillIds.includes(skillId);
|
|
170
|
+
}
|
|
171
|
+
if (fact.kind === "task-mode-default") {
|
|
172
|
+
return fact.value.taskMode === input.taskMode;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
function suggestionTitle(fact) {
|
|
177
|
+
if (fact.kind === "verification-default") {
|
|
178
|
+
return "Apply remembered verification gate";
|
|
179
|
+
}
|
|
180
|
+
if (fact.kind === "workflow-default") {
|
|
181
|
+
return "Apply remembered workflow preference";
|
|
182
|
+
}
|
|
183
|
+
return "Apply remembered operating preference";
|
|
184
|
+
}
|
|
185
|
+
function suggestionReason(fact, input) {
|
|
186
|
+
const project = input.projectFingerprint ? ` for ${input.projectFingerprint}` : "";
|
|
187
|
+
return `Memory fact '${fact.key}' matched task mode '${input.taskMode}'${project}. Confirmation is required before applying it.`;
|
|
188
|
+
}
|
|
189
|
+
function normalizeFact(input, existing) {
|
|
190
|
+
const now = new Date().toISOString();
|
|
191
|
+
return {
|
|
192
|
+
id: existing?.id ?? randomUUID(),
|
|
193
|
+
kind: input.kind,
|
|
194
|
+
key: input.key,
|
|
195
|
+
value: clone(input.value),
|
|
196
|
+
confidence: input.confidence,
|
|
197
|
+
source: input.source,
|
|
198
|
+
createdAt: existing?.createdAt ?? now,
|
|
199
|
+
updatedAt: now,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function factByKey(database, key) {
|
|
203
|
+
const row = database.prepare("SELECT * FROM memory_facts WHERE key = ?").get(key);
|
|
204
|
+
return row ? rowToFact(row) : undefined;
|
|
205
|
+
}
|
|
206
|
+
function allFacts(database) {
|
|
207
|
+
return database.prepare("SELECT * FROM memory_facts ORDER BY updated_at DESC").all().map(rowToFact);
|
|
208
|
+
}
|
|
209
|
+
function allDecisions(database) {
|
|
210
|
+
return database.prepare("SELECT * FROM memory_decisions ORDER BY created_at DESC").all().map((row) => ({
|
|
211
|
+
suggestionKey: row.suggestion_key,
|
|
212
|
+
decision: row.decision === "accepted" ? "accepted" : "rejected",
|
|
213
|
+
...(row.reason ? { reason: row.reason } : {}),
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
function allSessions(database) {
|
|
217
|
+
return database
|
|
218
|
+
.prepare("SELECT * FROM orchestration_sessions ORDER BY updated_at DESC")
|
|
219
|
+
.all().map((row) => ({
|
|
220
|
+
id: row.id,
|
|
221
|
+
state: row.state,
|
|
222
|
+
message: row.message,
|
|
223
|
+
taskMode: row.task_mode,
|
|
224
|
+
route: JSON.parse(row.route_json),
|
|
225
|
+
questions: JSON.parse(row.questions_json),
|
|
226
|
+
answers: JSON.parse(row.answers_json),
|
|
227
|
+
memorySuggestions: JSON.parse(row.memory_suggestions_json),
|
|
228
|
+
finalWorkflow: JSON.parse(row.final_workflow_json),
|
|
229
|
+
createdAt: row.created_at,
|
|
230
|
+
updatedAt: row.updated_at,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
function allOutcomes(database) {
|
|
234
|
+
return database.prepare("SELECT * FROM workflow_outcomes ORDER BY created_at DESC").all().map((row) => ({
|
|
235
|
+
id: row.id,
|
|
236
|
+
sessionId: row.session_id,
|
|
237
|
+
taskMode: row.task_mode,
|
|
238
|
+
workflow: JSON.parse(row.workflow_json),
|
|
239
|
+
verification: JSON.parse(row.verification_json),
|
|
240
|
+
outcome: row.outcome,
|
|
241
|
+
createdAt: row.created_at,
|
|
242
|
+
}));
|
|
243
|
+
}
|
|
244
|
+
function rowToFact(row) {
|
|
245
|
+
return {
|
|
246
|
+
id: row.id,
|
|
247
|
+
kind: row.kind,
|
|
248
|
+
key: row.key,
|
|
249
|
+
value: JSON.parse(row.value_json),
|
|
250
|
+
confidence: row.confidence,
|
|
251
|
+
source: row.source,
|
|
252
|
+
createdAt: row.created_at,
|
|
253
|
+
updatedAt: row.updated_at,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function clone(value) {
|
|
257
|
+
return JSON.parse(JSON.stringify(value));
|
|
258
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|