loki-mode 6.60.0 → 6.62.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +34 -8
- package/autonomy/completion-council.sh +70 -32
- package/autonomy/issue-parser.sh +4 -7
- package/autonomy/loki +238 -119
- package/autonomy/notification-checker.py +49 -23
- package/autonomy/run.sh +162 -79
- package/autonomy/sandbox.sh +91 -24
- package/bin/loki-mode.js +1 -2
- package/bin/postinstall.js +10 -4
- package/dashboard/__init__.py +1 -1
- package/dashboard/control.py +46 -36
- package/dashboard/database.py +21 -4
- package/dashboard/server.py +107 -78
- package/docs/BUG-AUDIT-v6.61.0.md +957 -0
- package/docs/INSTALLATION.md +2 -2
- package/events/bus.py +129 -28
- package/events/bus.ts +41 -27
- package/events/emit.sh +1 -1
- package/integrations/openclaw/README.md +139 -0
- package/integrations/openclaw/SKILL.md +88 -0
- package/integrations/openclaw/bridge/__init__.py +1 -0
- package/integrations/openclaw/bridge/__main__.py +88 -0
- package/integrations/openclaw/bridge/schema_map.py +180 -0
- package/integrations/openclaw/bridge/watcher.py +100 -0
- package/integrations/openclaw/scripts/format-progress.sh +80 -0
- package/integrations/openclaw/scripts/poll-status.sh +74 -0
- package/integrations/vibe-kanban.md +289 -0
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +96 -73
- package/memory/consolidation.py +21 -6
- package/memory/engine.py +53 -26
- package/memory/layers/index_layer.py +16 -3
- package/memory/layers/timeline_layer.py +16 -3
- package/memory/retrieval.py +4 -1
- package/memory/schemas.py +4 -2
- package/memory/storage.py +25 -4
- package/memory/token_economics.py +9 -2
- package/memory/vector_index.py +2 -2
- package/package.json +3 -1
- package/providers/cline.sh +5 -4
- package/providers/codex.sh +27 -5
- package/providers/gemini.sh +59 -23
- package/providers/loader.sh +3 -2
- package/skills/parallel-workflows.md +9 -7
- package/state/__init__.py +10 -0
- package/state/index.ts +18 -0
- package/state/manager.py +1801 -0
- package/state/manager.ts +1774 -0
- package/state/sqlite_backend.py +188 -0
- package/state/test_manager.py +703 -0
- package/state/test_manager.ts +366 -0
- package/templates/README.md +19 -4
- package/templates/dashboard.md +45 -0
- package/templates/data-pipeline.md +45 -0
- package/templates/game.md +48 -0
- package/templates/microservice.md +49 -0
- package/templates/npm-library.md +42 -0
- package/templates/rest-api.md +170 -33
- package/templates/slack-bot.md +48 -0
- package/templates/web-scraper.md +45 -0
- package/web-app/server.py +360 -191
- package/templates/saas-app.md +0 -42
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for TypeScript State Manager
|
|
3
|
+
*
|
|
4
|
+
* Run with: npx ts-node state/test_manager.ts
|
|
5
|
+
* Or with Deno: deno run --allow-read --allow-write state/test_manager.ts
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
import * as os from "os";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
StateManager,
|
|
14
|
+
StateChange,
|
|
15
|
+
ManagedFile,
|
|
16
|
+
getStateDiff,
|
|
17
|
+
getStateManager,
|
|
18
|
+
resetStateManager,
|
|
19
|
+
ConflictStrategy,
|
|
20
|
+
VersionVector,
|
|
21
|
+
PendingUpdate,
|
|
22
|
+
ConflictInfo,
|
|
23
|
+
} from "./manager";
|
|
24
|
+
|
|
25
|
+
// Test helpers
|
|
26
|
+
function assert(condition: boolean, message: string): void {
|
|
27
|
+
if (!condition) {
|
|
28
|
+
throw new Error(`Assertion failed: ${message}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assertEqual<T>(actual: T, expected: T, message: string): void {
|
|
33
|
+
const actualStr = JSON.stringify(actual);
|
|
34
|
+
const expectedStr = JSON.stringify(expected);
|
|
35
|
+
if (actualStr !== expectedStr) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`Assertion failed: ${message}\n Expected: ${expectedStr}\n Actual: ${actualStr}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Test runner
|
|
43
|
+
async function runTests(): Promise<void> {
|
|
44
|
+
// Create temp directory
|
|
45
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "state-manager-test-"));
|
|
46
|
+
const lokiDir = path.join(tempDir, ".loki");
|
|
47
|
+
|
|
48
|
+
console.log("Testing StateManager (TypeScript)...");
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// Test: Manager initialization
|
|
52
|
+
const manager = new StateManager({ lokiDir, enableWatch: false, enableEvents: false });
|
|
53
|
+
assert(fs.existsSync(path.join(lokiDir, "state")), "state directory should exist");
|
|
54
|
+
assert(fs.existsSync(path.join(lokiDir, "queue")), "queue directory should exist");
|
|
55
|
+
console.log(" [PASS] Manager initialized");
|
|
56
|
+
|
|
57
|
+
// Test: set_state
|
|
58
|
+
const change1 = manager.setState("test.json", { key: "value" });
|
|
59
|
+
assertEqual(change1.changeType, "create", "should be create");
|
|
60
|
+
assertEqual(change1.newValue, { key: "value" }, "should have new value");
|
|
61
|
+
console.log(" [PASS] setState works");
|
|
62
|
+
|
|
63
|
+
// Test: get_state
|
|
64
|
+
const result1 = manager.getState("test.json");
|
|
65
|
+
assertEqual(result1, { key: "value" }, "should get state");
|
|
66
|
+
console.log(" [PASS] getState works");
|
|
67
|
+
|
|
68
|
+
// Test: update_state
|
|
69
|
+
const change2 = manager.updateState("test.json", { another: "field" });
|
|
70
|
+
assertEqual(
|
|
71
|
+
change2.newValue,
|
|
72
|
+
{ key: "value", another: "field" },
|
|
73
|
+
"should merge updates"
|
|
74
|
+
);
|
|
75
|
+
console.log(" [PASS] updateState works");
|
|
76
|
+
|
|
77
|
+
// Test: ManagedFile enum
|
|
78
|
+
manager.setState(ManagedFile.ORCHESTRATOR, { phase: "planning" });
|
|
79
|
+
const result2 = manager.getState(ManagedFile.ORCHESTRATOR);
|
|
80
|
+
assertEqual(result2, { phase: "planning" }, "should work with enum");
|
|
81
|
+
console.log(" [PASS] ManagedFile enum works");
|
|
82
|
+
|
|
83
|
+
// Test: subscriptions
|
|
84
|
+
const changes: StateChange[] = [];
|
|
85
|
+
const subscription = manager.subscribe((change) => {
|
|
86
|
+
changes.push(change);
|
|
87
|
+
});
|
|
88
|
+
manager.setState("sub-test.json", { data: 123 });
|
|
89
|
+
assertEqual(changes.length, 1, "should receive change");
|
|
90
|
+
console.log(" [PASS] Subscriptions work");
|
|
91
|
+
|
|
92
|
+
subscription.dispose();
|
|
93
|
+
manager.setState("sub-test.json", { data: 456 });
|
|
94
|
+
assertEqual(changes.length, 1, "should not receive after dispose");
|
|
95
|
+
console.log(" [PASS] Unsubscribe works");
|
|
96
|
+
|
|
97
|
+
// Test: convenience methods
|
|
98
|
+
manager.setOrchestratorState({ phase: "dev" });
|
|
99
|
+
assertEqual(manager.getOrchestratorState(), { phase: "dev" }, "orchestrator state");
|
|
100
|
+
console.log(" [PASS] Convenience methods work");
|
|
101
|
+
|
|
102
|
+
// Test: getStateDiff
|
|
103
|
+
const diff = getStateDiff({ a: 1, b: 2 }, { a: 1, c: 3 });
|
|
104
|
+
assertEqual(diff.added, { c: 3 }, "diff added");
|
|
105
|
+
assertEqual(diff.removed, { b: 2 }, "diff removed");
|
|
106
|
+
assertEqual(diff.changed, {}, "diff changed");
|
|
107
|
+
console.log(" [PASS] getStateDiff works");
|
|
108
|
+
|
|
109
|
+
// Test: delete_state
|
|
110
|
+
manager.setState("to-delete.json", { temp: true });
|
|
111
|
+
const change3 = manager.deleteState("to-delete.json");
|
|
112
|
+
assert(change3 !== null, "should return change");
|
|
113
|
+
assertEqual(change3!.changeType, "delete", "should be delete");
|
|
114
|
+
const result3 = manager.getState("to-delete.json");
|
|
115
|
+
assert(result3 === null, "should be null after delete");
|
|
116
|
+
console.log(" [PASS] deleteState works");
|
|
117
|
+
|
|
118
|
+
// Test: corrupted JSON handling
|
|
119
|
+
const corruptedPath = path.join(lokiDir, "corrupted.json");
|
|
120
|
+
fs.writeFileSync(corruptedPath, "{ this is not valid json }");
|
|
121
|
+
const resultCorrupted = manager.getState("corrupted.json");
|
|
122
|
+
assert(resultCorrupted === null, "corrupted JSON should return null");
|
|
123
|
+
console.log(" [PASS] Corrupted JSON handling works");
|
|
124
|
+
|
|
125
|
+
// Test: empty file handling
|
|
126
|
+
const emptyPath = path.join(lokiDir, "empty.json");
|
|
127
|
+
fs.writeFileSync(emptyPath, "");
|
|
128
|
+
const resultEmpty = manager.getState("empty.json");
|
|
129
|
+
assert(resultEmpty === null, "empty file should return null");
|
|
130
|
+
console.log(" [PASS] Empty file handling works");
|
|
131
|
+
|
|
132
|
+
// Test: partial/truncated JSON handling
|
|
133
|
+
const partialPath = path.join(lokiDir, "partial.json");
|
|
134
|
+
fs.writeFileSync(partialPath, '{"key": "value", "incomplete":');
|
|
135
|
+
const resultPartial = manager.getState("partial.json");
|
|
136
|
+
assert(resultPartial === null, "partial JSON should return null");
|
|
137
|
+
console.log(" [PASS] Partial JSON handling works");
|
|
138
|
+
|
|
139
|
+
// Test: corrupted JSON with default value
|
|
140
|
+
const resultWithDefault = manager.getState("corrupted.json", { fallback: true });
|
|
141
|
+
assertEqual(resultWithDefault, { fallback: true }, "should return default for corrupted");
|
|
142
|
+
console.log(" [PASS] Corrupted JSON with default works");
|
|
143
|
+
|
|
144
|
+
// Cleanup
|
|
145
|
+
manager.stop();
|
|
146
|
+
|
|
147
|
+
console.log("");
|
|
148
|
+
console.log("All basic TypeScript tests passed!");
|
|
149
|
+
|
|
150
|
+
// Run optimistic update tests
|
|
151
|
+
await runOptimisticUpdateTests();
|
|
152
|
+
|
|
153
|
+
} finally {
|
|
154
|
+
// Cleanup temp directory
|
|
155
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Optimistic Update Tests (SYN-014)
|
|
160
|
+
async function runOptimisticUpdateTests(): Promise<void> {
|
|
161
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "optimistic-test-"));
|
|
162
|
+
const lokiDir = path.join(tempDir, ".loki");
|
|
163
|
+
|
|
164
|
+
console.log("");
|
|
165
|
+
console.log("Testing Optimistic Updates (SYN-014)...");
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// Test: VersionVector
|
|
169
|
+
console.log(" Testing VersionVector...");
|
|
170
|
+
const vv1 = new VersionVector();
|
|
171
|
+
vv1.increment("source1");
|
|
172
|
+
assert(vv1.get("source1") === 1, "version should be 1");
|
|
173
|
+
vv1.increment("source1");
|
|
174
|
+
assert(vv1.get("source1") === 2, "version should be 2");
|
|
175
|
+
assert(vv1.get("nonexistent") === 0, "nonexistent should be 0");
|
|
176
|
+
console.log(" [PASS] VersionVector increment works");
|
|
177
|
+
|
|
178
|
+
// Test: VersionVector merge
|
|
179
|
+
const vv2 = new VersionVector();
|
|
180
|
+
vv2.increment("source2");
|
|
181
|
+
vv2.increment("source2");
|
|
182
|
+
const merged = vv1.merge(vv2);
|
|
183
|
+
assert(merged.get("source1") === 2, "merged source1 should be 2");
|
|
184
|
+
assert(merged.get("source2") === 2, "merged source2 should be 2");
|
|
185
|
+
console.log(" [PASS] VersionVector merge works");
|
|
186
|
+
|
|
187
|
+
// Test: VersionVector concurrent
|
|
188
|
+
const vvA = new VersionVector();
|
|
189
|
+
vvA.increment("agentA");
|
|
190
|
+
const vvB = new VersionVector();
|
|
191
|
+
vvB.increment("agentB");
|
|
192
|
+
assert(vvA.concurrentWith(vvB), "should be concurrent");
|
|
193
|
+
console.log(" [PASS] VersionVector concurrent detection works");
|
|
194
|
+
|
|
195
|
+
// Test: VersionVector dominates
|
|
196
|
+
const vvDom1 = new VersionVector();
|
|
197
|
+
vvDom1.increment("source1");
|
|
198
|
+
vvDom1.increment("source1");
|
|
199
|
+
const vvDom2 = new VersionVector();
|
|
200
|
+
vvDom2.increment("source1");
|
|
201
|
+
assert(vvDom1.dominates(vvDom2), "vvDom1 should dominate vvDom2");
|
|
202
|
+
assert(!vvDom2.dominates(vvDom1), "vvDom2 should not dominate vvDom1");
|
|
203
|
+
console.log(" [PASS] VersionVector dominates works");
|
|
204
|
+
|
|
205
|
+
// Test: serialization
|
|
206
|
+
const vvSer = new VersionVector({ source1: 3, source2: 2 });
|
|
207
|
+
const dict = vvSer.toDict();
|
|
208
|
+
const vvDeser = VersionVector.fromDict(dict);
|
|
209
|
+
assert(vvDeser.get("source1") === 3, "deserialized source1 should be 3");
|
|
210
|
+
assert(vvDeser.get("source2") === 2, "deserialized source2 should be 2");
|
|
211
|
+
console.log(" [PASS] VersionVector serialization works");
|
|
212
|
+
|
|
213
|
+
// Test: StateManager optimistic updates
|
|
214
|
+
const manager = new StateManager({ lokiDir, enableWatch: false, enableEvents: false });
|
|
215
|
+
|
|
216
|
+
// Test: basic optimistic update
|
|
217
|
+
const pending = manager.optimisticUpdate("test.json", "key1", "value1", "agent1");
|
|
218
|
+
assert(pending.status === "pending", "should be pending");
|
|
219
|
+
assert(pending.key === "key1", "key should match");
|
|
220
|
+
assert(pending.value === "value1", "value should match");
|
|
221
|
+
|
|
222
|
+
const state = manager.getState("test.json");
|
|
223
|
+
assert(state !== null && state.key1 === "value1", "value should be applied");
|
|
224
|
+
console.log(" [PASS] optimisticUpdate works");
|
|
225
|
+
|
|
226
|
+
// Test: version tracking
|
|
227
|
+
manager.optimisticUpdate("test.json", "key2", "value2", "agent2");
|
|
228
|
+
const vv = manager.getVersionVector("test.json");
|
|
229
|
+
assert(vv.get("agent1") === 1, "agent1 version should be 1");
|
|
230
|
+
assert(vv.get("agent2") === 1, "agent2 version should be 1");
|
|
231
|
+
console.log(" [PASS] Version tracking works");
|
|
232
|
+
|
|
233
|
+
// Test: pending updates tracking
|
|
234
|
+
const pendingList = manager.getPendingUpdates("test.json");
|
|
235
|
+
assert(pendingList.length === 2, "should have 2 pending updates");
|
|
236
|
+
console.log(" [PASS] Pending updates tracking works");
|
|
237
|
+
|
|
238
|
+
// Test: conflict detection
|
|
239
|
+
const manager2 = new StateManager({
|
|
240
|
+
lokiDir: path.join(tempDir, ".loki2"),
|
|
241
|
+
enableWatch: false,
|
|
242
|
+
enableEvents: false,
|
|
243
|
+
});
|
|
244
|
+
manager2.optimisticUpdate("conflict.json", "key1", "local_value", "agent1");
|
|
245
|
+
|
|
246
|
+
const remoteState = {
|
|
247
|
+
key1: "remote_value",
|
|
248
|
+
_version_vector: { agent2: 1 },
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const conflicts = manager2.detectConflicts("conflict.json", remoteState, "agent2");
|
|
252
|
+
assert(conflicts.length === 1, "should have 1 conflict");
|
|
253
|
+
assert(conflicts[0].key === "key1", "conflict key should be key1");
|
|
254
|
+
assert(conflicts[0].localValue === "local_value", "local value should match");
|
|
255
|
+
assert(conflicts[0].remoteValue === "remote_value", "remote value should match");
|
|
256
|
+
console.log(" [PASS] Conflict detection works");
|
|
257
|
+
|
|
258
|
+
// Test: conflict resolution - last write wins
|
|
259
|
+
manager2.setConflictStrategy(ConflictStrategy.LAST_WRITE_WINS);
|
|
260
|
+
const resolved = manager2.resolveConflicts("conflict.json", conflicts);
|
|
261
|
+
assert(resolved.key1 === "remote_value", "remote value should win");
|
|
262
|
+
assert(conflicts[0].resolution === "last_write_wins", "resolution should be last_write_wins");
|
|
263
|
+
console.log(" [PASS] Last-write-wins resolution works");
|
|
264
|
+
|
|
265
|
+
// Test: conflict resolution - merge for dicts
|
|
266
|
+
const manager3 = new StateManager({
|
|
267
|
+
lokiDir: path.join(tempDir, ".loki3"),
|
|
268
|
+
enableWatch: false,
|
|
269
|
+
enableEvents: false,
|
|
270
|
+
});
|
|
271
|
+
manager3.optimisticUpdate("merge.json", "config", { a: 1, b: 2 }, "agent1");
|
|
272
|
+
manager3.setConflictStrategy(ConflictStrategy.MERGE);
|
|
273
|
+
|
|
274
|
+
const remoteDict = {
|
|
275
|
+
config: { b: 3, c: 4 },
|
|
276
|
+
_version_vector: { agent2: 1 },
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const dictConflicts = manager3.detectConflicts("merge.json", remoteDict, "agent2");
|
|
280
|
+
const resolvedDict = manager3.resolveConflicts("merge.json", dictConflicts);
|
|
281
|
+
const mergedConfig = resolvedDict.config as Record<string, number>;
|
|
282
|
+
assert(mergedConfig.a === 1, "merged a should be 1");
|
|
283
|
+
assert(mergedConfig.b === 3, "merged b should be 3 (remote wins)");
|
|
284
|
+
assert(mergedConfig.c === 4, "merged c should be 4");
|
|
285
|
+
console.log(" [PASS] Merge resolution for dicts works");
|
|
286
|
+
|
|
287
|
+
// Test: commit pending updates
|
|
288
|
+
const manager4 = new StateManager({
|
|
289
|
+
lokiDir: path.join(tempDir, ".loki4"),
|
|
290
|
+
enableWatch: false,
|
|
291
|
+
enableEvents: false,
|
|
292
|
+
});
|
|
293
|
+
manager4.optimisticUpdate("commit.json", "key1", "value1", "agent1");
|
|
294
|
+
manager4.optimisticUpdate("commit.json", "key2", "value2", "agent1");
|
|
295
|
+
|
|
296
|
+
const committed = manager4.commitPendingUpdates("commit.json");
|
|
297
|
+
assert(committed === 2, "should commit 2 updates");
|
|
298
|
+
|
|
299
|
+
const remaining = manager4.getPendingUpdates("commit.json");
|
|
300
|
+
assert(remaining.length === 0, "should have no pending updates");
|
|
301
|
+
console.log(" [PASS] Commit pending updates works");
|
|
302
|
+
|
|
303
|
+
// Test: rollback pending updates
|
|
304
|
+
const manager5 = new StateManager({
|
|
305
|
+
lokiDir: path.join(tempDir, ".loki5"),
|
|
306
|
+
enableWatch: false,
|
|
307
|
+
enableEvents: false,
|
|
308
|
+
});
|
|
309
|
+
const originalState = { original: true };
|
|
310
|
+
manager5.setState("rollback.json", originalState);
|
|
311
|
+
|
|
312
|
+
manager5.optimisticUpdate("rollback.json", "key1", "value1", "agent1");
|
|
313
|
+
manager5.optimisticUpdate("rollback.json", "key2", "value2", "agent1");
|
|
314
|
+
|
|
315
|
+
const rolledBack = manager5.rollbackPendingUpdates("rollback.json", originalState);
|
|
316
|
+
assert(rolledBack === 2, "should rollback 2 updates");
|
|
317
|
+
|
|
318
|
+
const restoredState = manager5.getState("rollback.json");
|
|
319
|
+
assertEqual(restoredState, originalState, "state should be restored");
|
|
320
|
+
console.log(" [PASS] Rollback pending updates works");
|
|
321
|
+
|
|
322
|
+
// Test: sync with remote
|
|
323
|
+
const manager6 = new StateManager({
|
|
324
|
+
lokiDir: path.join(tempDir, ".loki6"),
|
|
325
|
+
enableWatch: false,
|
|
326
|
+
enableEvents: false,
|
|
327
|
+
});
|
|
328
|
+
manager6.setState("sync.json", { existing: "value" });
|
|
329
|
+
manager6.optimisticUpdate("sync.json", "local_key", "local_value", "agent1");
|
|
330
|
+
|
|
331
|
+
const syncRemote = {
|
|
332
|
+
existing: "value",
|
|
333
|
+
remote_key: "remote_value",
|
|
334
|
+
_version_vector: { agent2: 1 },
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const { resolvedState, conflicts: syncConflicts, committed: syncCommitted } =
|
|
338
|
+
manager6.syncWithRemote("sync.json", syncRemote, "agent2");
|
|
339
|
+
|
|
340
|
+
assert(syncConflicts.length === 0, "should have no conflicts (different keys)");
|
|
341
|
+
assert(syncCommitted === 1, "should commit 1 update");
|
|
342
|
+
assert(resolvedState.local_key === "local_value", "local key should be preserved");
|
|
343
|
+
console.log(" [PASS] Sync with remote works");
|
|
344
|
+
|
|
345
|
+
// Cleanup managers
|
|
346
|
+
manager.stop();
|
|
347
|
+
manager2.stop();
|
|
348
|
+
manager3.stop();
|
|
349
|
+
manager4.stop();
|
|
350
|
+
manager5.stop();
|
|
351
|
+
manager6.stop();
|
|
352
|
+
|
|
353
|
+
console.log("");
|
|
354
|
+
console.log("All Optimistic Update tests passed!");
|
|
355
|
+
|
|
356
|
+
} finally {
|
|
357
|
+
// Cleanup temp directory
|
|
358
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Run tests
|
|
363
|
+
runTests().catch((err) => {
|
|
364
|
+
console.error("Test failed:", err);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
});
|
package/templates/README.md
CHANGED
|
@@ -11,6 +11,9 @@ claude --dangerously-skip-permissions
|
|
|
11
11
|
|
|
12
12
|
# Or via CLI
|
|
13
13
|
./autonomy/run.sh templates/e-commerce.md
|
|
14
|
+
|
|
15
|
+
# Or scaffold a new project from a template
|
|
16
|
+
loki init my-project --template saas-starter
|
|
14
17
|
```
|
|
15
18
|
|
|
16
19
|
## Templates
|
|
@@ -27,12 +30,20 @@ claude --dangerously-skip-permissions
|
|
|
27
30
|
|
|
28
31
|
| Template | Description | Tech Stack | Est. Time |
|
|
29
32
|
|----------|-------------|------------|-----------|
|
|
33
|
+
| [rest-api.md](rest-api.md) | REST API with CRUD, pagination, filtering, Swagger docs (no auth) | Express, TypeScript, Prisma, SQLite | 25-35 min |
|
|
30
34
|
| [rest-api-auth.md](rest-api-auth.md) | REST API with JWT auth, registration, login, refresh, rate limiting | Express/FastAPI, PostgreSQL, JWT, bcrypt | 30-45 min |
|
|
31
|
-
| [full-stack-demo.md](full-stack-demo.md) | Bookmark manager with tags, search, and filtering | React, Express, SQLite, TailwindCSS | 30-60 min |
|
|
32
35
|
| [cli-tool.md](cli-tool.md) | File organizer CLI with subcommands, config, watch mode, undo | Node.js, Commander.js, chalk, chokidar | 30-45 min |
|
|
33
36
|
| [discord-bot.md](discord-bot.md) | Moderation bot with slash commands, auto-mod, reaction roles | discord.js, SQLite, node-cron | 45-60 min |
|
|
34
37
|
| [chrome-extension.md](chrome-extension.md) | Tab manager extension with groups, sessions, search, memory monitor | Manifest V3, vanilla JS, Chrome APIs | 30-45 min |
|
|
35
38
|
| [blog-platform.md](blog-platform.md) | Blog with markdown CMS, categories, RSS feed, SEO | Next.js, CodeMirror, SQLite, TailwindCSS | 45-60 min |
|
|
39
|
+
| [full-stack-demo.md](full-stack-demo.md) | Bookmark manager with tags, search, and filtering | React, Express, SQLite, TailwindCSS | 30-60 min |
|
|
40
|
+
| [web-scraper.md](web-scraper.md) | Configurable scraper with pagination, robots.txt, multi-format export | Python, httpx, BeautifulSoup4, SQLite | 30-45 min |
|
|
41
|
+
| [data-pipeline.md](data-pipeline.md) | ETL pipeline with multi-source ingestion, transforms, monitoring | Python, Pydantic, SQLAlchemy, Click | 30-45 min |
|
|
42
|
+
| [dashboard.md](dashboard.md) | Real-time analytics dashboard with charts, tables, drag-and-drop layout | React, Recharts, TanStack Table, WebSocket | 45-60 min |
|
|
43
|
+
| [game.md](game.md) | Browser-based 2D game with enemy AI, scoring, levels, high scores | HTML5 Canvas, TypeScript, Web Audio API | 30-45 min |
|
|
44
|
+
| [slack-bot.md](slack-bot.md) | Slack bot with slash commands, events, interactive messages, scheduling | Node.js, Bolt SDK, SQLite | 30-45 min |
|
|
45
|
+
| [npm-library.md](npm-library.md) | npm package with TypeScript, dual ESM/CJS, tree shaking, auto docs | TypeScript, tsup, Vitest, typedoc | 30-45 min |
|
|
46
|
+
| [microservice.md](microservice.md) | Containerized service with health checks, logging, Prometheus metrics | Express, TypeScript, Docker, Prisma, pino | 30-45 min |
|
|
36
47
|
|
|
37
48
|
### Complex
|
|
38
49
|
|
|
@@ -57,6 +68,7 @@ Every template follows a consistent structure:
|
|
|
57
68
|
- **Requirements** - Non-functional requirements
|
|
58
69
|
- **Testing** - Test strategy and coverage expectations
|
|
59
70
|
- **Out of Scope** - Explicit boundaries to prevent scope creep
|
|
71
|
+
- **Acceptance Criteria** - Measurable conditions for each feature
|
|
60
72
|
- **Success Criteria** - How to know when it is done
|
|
61
73
|
- **Purpose Footer** - What aspect of Loki Mode this template exercises
|
|
62
74
|
|
|
@@ -69,12 +81,15 @@ Every template follows a consistent structure:
|
|
|
69
81
|
**Building something real?** Pick the template closest to your goal and customize it. The complex templates (`saas-starter.md`, `e-commerce.md`, `ai-chatbot.md`) are production-grade starting points.
|
|
70
82
|
|
|
71
83
|
**Testing specific agent types:**
|
|
72
|
-
- Frontend agent: `static-landing-page.md`, `chrome-extension.md`
|
|
73
|
-
- Backend agent: `api-only.md`, `rest-api-auth.md`, `
|
|
84
|
+
- Frontend agent: `static-landing-page.md`, `chrome-extension.md`, `dashboard.md`
|
|
85
|
+
- Backend agent: `api-only.md`, `rest-api.md`, `rest-api-auth.md`, `microservice.md`
|
|
74
86
|
- Full-stack agent: `full-stack-demo.md`, `blog-platform.md`
|
|
75
|
-
- DevOps/CLI agent: `cli-tool.md`
|
|
87
|
+
- DevOps/CLI agent: `cli-tool.md`, `data-pipeline.md`
|
|
88
|
+
- Bot agent: `discord-bot.md`, `slack-bot.md`
|
|
76
89
|
- Mobile agent: `mobile-app.md`
|
|
77
90
|
- AI/ML agent: `ai-chatbot.md`
|
|
91
|
+
- Game agent: `game.md`
|
|
92
|
+
- Library agent: `npm-library.md`
|
|
78
93
|
|
|
79
94
|
## Customizing Templates
|
|
80
95
|
|
package/templates/dashboard.md
CHANGED
|
@@ -33,6 +33,51 @@ A real-time analytics dashboard that visualizes key business metrics with intera
|
|
|
33
33
|
- Responsive design tested at 3 breakpoints (mobile, tablet, desktop)
|
|
34
34
|
- Accessibility: all charts have aria labels, tables are keyboard navigable
|
|
35
35
|
|
|
36
|
+
## Project Structure
|
|
37
|
+
```
|
|
38
|
+
/
|
|
39
|
+
├── src/
|
|
40
|
+
│ ├── components/
|
|
41
|
+
│ │ ├── charts/ # Line, bar, pie, area chart components
|
|
42
|
+
│ │ ├── tables/ # Data table with sorting and filtering
|
|
43
|
+
│ │ ├── layout/ # Dashboard grid, drag-and-drop wrapper
|
|
44
|
+
│ │ └── controls/ # Date picker, export buttons, filters
|
|
45
|
+
│ ├── hooks/
|
|
46
|
+
│ │ ├── useWebSocket.ts # Real-time data subscription
|
|
47
|
+
│ │ └── useLayout.ts # Layout persistence (localStorage)
|
|
48
|
+
│ ├── services/
|
|
49
|
+
│ │ └── api.ts # Mock data API client
|
|
50
|
+
│ ├── types/
|
|
51
|
+
│ │ └── index.ts # Shared TypeScript types
|
|
52
|
+
│ ├── App.tsx
|
|
53
|
+
│ └── main.tsx
|
|
54
|
+
├── server/
|
|
55
|
+
│ ├── index.ts # Express + WebSocket server
|
|
56
|
+
│ └── mockData.ts # Sample metrics generator
|
|
57
|
+
├── tests/
|
|
58
|
+
│ ├── charts.test.tsx # Chart rendering tests
|
|
59
|
+
│ └── e2e/ # Playwright tests
|
|
60
|
+
├── package.json
|
|
61
|
+
└── README.md
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Out of Scope
|
|
65
|
+
- Real database or data warehouse connections
|
|
66
|
+
- User authentication or multi-tenant dashboards
|
|
67
|
+
- Server-side rendering
|
|
68
|
+
- PDF report generation
|
|
69
|
+
- Alerting or notification rules
|
|
70
|
+
- Historical data backfill
|
|
71
|
+
- Custom chart builder UI
|
|
72
|
+
|
|
73
|
+
## Acceptance Criteria
|
|
74
|
+
- All four chart types (line, bar, pie, area) render with correct data
|
|
75
|
+
- Date range filter applies to every widget on the dashboard
|
|
76
|
+
- WebSocket pushes update visible charts without page refresh
|
|
77
|
+
- Layout changes via drag-and-drop persist after browser reload
|
|
78
|
+
- CSV export matches displayed table data exactly
|
|
79
|
+
- PNG export captures the selected chart at screen resolution
|
|
80
|
+
|
|
36
81
|
## Success Metrics
|
|
37
82
|
- Dashboard loads with sample data and renders all chart types
|
|
38
83
|
- Date range filter updates all widgets simultaneously
|
|
@@ -33,6 +33,51 @@ An ETL data pipeline that ingests data from multiple sources, transforms it thro
|
|
|
33
33
|
- Incremental processing verified across multiple runs
|
|
34
34
|
- Dead letter queue captures all failure categories
|
|
35
35
|
|
|
36
|
+
## Project Structure
|
|
37
|
+
```
|
|
38
|
+
/
|
|
39
|
+
├── src/
|
|
40
|
+
│ ├── pipeline/
|
|
41
|
+
│ │ ├── runner.py # Pipeline execution engine
|
|
42
|
+
│ │ ├── transforms.py # Built-in transform functions
|
|
43
|
+
│ │ └── validators.py # Schema validation logic
|
|
44
|
+
│ ├── sources/
|
|
45
|
+
│ │ ├── csv_source.py # CSV file reader
|
|
46
|
+
│ │ ├── json_source.py # JSON API reader
|
|
47
|
+
│ │ └── db_source.py # PostgreSQL reader
|
|
48
|
+
│ ├── sinks/
|
|
49
|
+
│ │ └── loader.py # Target data store writer
|
|
50
|
+
│ ├── monitoring/
|
|
51
|
+
│ │ └── metrics.py # Processing metrics collector
|
|
52
|
+
│ ├── cli.py # Click CLI entrypoint
|
|
53
|
+
│ └── config.py # YAML config loader
|
|
54
|
+
├── pipelines/
|
|
55
|
+
│ └── example.yaml # Sample pipeline definition
|
|
56
|
+
├── tests/
|
|
57
|
+
│ ├── test_transforms.py # Transform function tests
|
|
58
|
+
│ ├── test_validators.py # Schema validation tests
|
|
59
|
+
│ └── test_pipeline.py # Integration tests
|
|
60
|
+
├── pyproject.toml
|
|
61
|
+
└── README.md
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Out of Scope
|
|
65
|
+
- Real-time streaming (Kafka, Kinesis)
|
|
66
|
+
- Distributed processing (Spark, Dask)
|
|
67
|
+
- Web-based pipeline builder UI
|
|
68
|
+
- Data lineage or provenance tracking
|
|
69
|
+
- Alerting integrations (PagerDuty, Slack)
|
|
70
|
+
- Cloud-native deployment (Airflow, Dagster)
|
|
71
|
+
- Data catalog or discovery features
|
|
72
|
+
|
|
73
|
+
## Acceptance Criteria
|
|
74
|
+
- Pipeline definition in YAML configures sources, transforms, and sinks
|
|
75
|
+
- CSV, JSON, and database sources each read data correctly
|
|
76
|
+
- Invalid records are routed to the dead letter queue with error details
|
|
77
|
+
- Incremental mode skips previously processed records on re-run
|
|
78
|
+
- Metrics report records processed, errors, and elapsed time
|
|
79
|
+
- Cron scheduling triggers pipeline runs at configured intervals
|
|
80
|
+
|
|
36
81
|
## Success Metrics
|
|
37
82
|
- Pipeline processes sample dataset end-to-end without errors
|
|
38
83
|
- Invalid records quarantined with descriptive error messages
|
package/templates/game.md
CHANGED
|
@@ -33,6 +33,54 @@ A browser-based 2D game with player controls, enemy AI, scoring, levels, and per
|
|
|
33
33
|
- Controls responsive on both keyboard and touch (mobile)
|
|
34
34
|
- No memory leaks during extended play sessions
|
|
35
35
|
|
|
36
|
+
## Project Structure
|
|
37
|
+
```
|
|
38
|
+
/
|
|
39
|
+
├── src/
|
|
40
|
+
│ ├── engine/
|
|
41
|
+
│ │ ├── game.ts # Main game loop and state machine
|
|
42
|
+
│ │ ├── renderer.ts # Canvas rendering layer
|
|
43
|
+
│ │ ├── input.ts # Keyboard and touch input handler
|
|
44
|
+
│ │ └── audio.ts # Web Audio API wrapper
|
|
45
|
+
│ ├── entities/
|
|
46
|
+
│ │ ├── player.ts # Player sprite and controls
|
|
47
|
+
│ │ ├── enemy.ts # Enemy types and AI patterns
|
|
48
|
+
│ │ └── projectile.ts # Bullets and projectiles
|
|
49
|
+
│ ├── systems/
|
|
50
|
+
│ │ ├── collision.ts # AABB collision detection
|
|
51
|
+
│ │ ├── scoring.ts # Score and level progression
|
|
52
|
+
│ │ └── highscores.ts # LocalStorage leaderboard
|
|
53
|
+
│ ├── assets/
|
|
54
|
+
│ │ ├── sprites/ # Sprite images
|
|
55
|
+
│ │ └── sounds/ # Sound effect files
|
|
56
|
+
│ ├── main.ts # Entry point
|
|
57
|
+
│ └── config.ts # Game constants and key bindings
|
|
58
|
+
├── tests/
|
|
59
|
+
│ ├── collision.test.ts # Collision detection tests
|
|
60
|
+
│ └── scoring.test.ts # Score and level logic tests
|
|
61
|
+
├── index.html
|
|
62
|
+
├── package.json
|
|
63
|
+
└── README.md
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Out of Scope
|
|
67
|
+
- Multiplayer or networked gameplay
|
|
68
|
+
- 3D rendering or WebGL
|
|
69
|
+
- Level editor or user-generated content
|
|
70
|
+
- Achievements or progression system beyond levels
|
|
71
|
+
- Mobile app packaging (Capacitor, Electron)
|
|
72
|
+
- Analytics or telemetry
|
|
73
|
+
- In-app purchases or monetization
|
|
74
|
+
|
|
75
|
+
## Acceptance Criteria
|
|
76
|
+
- Game transitions through menu, playing, paused, and game-over states
|
|
77
|
+
- Player movement responds within one frame of input
|
|
78
|
+
- At least three enemy types exhibit distinct movement patterns
|
|
79
|
+
- Collision detection correctly identifies overlapping sprites
|
|
80
|
+
- Score increases on enemy defeat and displays on screen
|
|
81
|
+
- High score table persists across browser sessions
|
|
82
|
+
- Sound effects play on collision, shoot, and level-up events
|
|
83
|
+
|
|
36
84
|
## Success Metrics
|
|
37
85
|
- Game starts, plays, and ends with proper state transitions
|
|
38
86
|
- Player can move, shoot, and interact with enemies
|
|
@@ -34,6 +34,55 @@ A containerized microservice with health checks, structured logging, graceful sh
|
|
|
34
34
|
- Graceful shutdown completes within timeout
|
|
35
35
|
- Metrics endpoint serves valid Prometheus format
|
|
36
36
|
|
|
37
|
+
## Project Structure
|
|
38
|
+
```
|
|
39
|
+
/
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── app.ts # Express app setup, middleware stack
|
|
42
|
+
│ ├── server.ts # Entry point with graceful shutdown
|
|
43
|
+
│ ├── config/
|
|
44
|
+
│ │ └── index.ts # Env-based config with validation
|
|
45
|
+
│ ├── middleware/
|
|
46
|
+
│ │ ├── requestId.ts # Correlation ID injection
|
|
47
|
+
│ │ ├── healthCheck.ts # Liveness and readiness probes
|
|
48
|
+
│ │ └── metrics.ts # Prometheus metrics collection
|
|
49
|
+
│ ├── routes/
|
|
50
|
+
│ │ └── items.ts # Example resource routes
|
|
51
|
+
│ ├── repositories/
|
|
52
|
+
│ │ └── itemRepo.ts # Repository pattern data access
|
|
53
|
+
│ ├── logger.ts # pino structured logger
|
|
54
|
+
│ └── types/
|
|
55
|
+
│ └── index.ts # Shared types
|
|
56
|
+
├── prisma/
|
|
57
|
+
│ ├── schema.prisma # Database schema
|
|
58
|
+
│ └── migrations/ # Database migrations
|
|
59
|
+
├── tests/
|
|
60
|
+
│ ├── items.test.ts # API integration tests
|
|
61
|
+
│ └── healthCheck.test.ts # Health probe tests
|
|
62
|
+
├── Dockerfile # Multi-stage build
|
|
63
|
+
├── docker-compose.yml # Local dev with PostgreSQL
|
|
64
|
+
├── package.json
|
|
65
|
+
└── README.md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Out of Scope
|
|
69
|
+
- Service mesh or sidecar proxy configuration
|
|
70
|
+
- Message queue integration (RabbitMQ, Kafka)
|
|
71
|
+
- API gateway or load balancer setup
|
|
72
|
+
- Distributed tracing (OpenTelemetry spans)
|
|
73
|
+
- Secret management (Vault, AWS Secrets Manager)
|
|
74
|
+
- Kubernetes manifests or Helm charts
|
|
75
|
+
- CI/CD pipeline configuration
|
|
76
|
+
|
|
77
|
+
## Acceptance Criteria
|
|
78
|
+
- Docker image builds with multi-stage Dockerfile under 150MB
|
|
79
|
+
- GET /health/live returns 200 immediately after startup
|
|
80
|
+
- GET /health/ready returns 200 only when database is connected
|
|
81
|
+
- All request logs are valid JSON containing a correlation ID
|
|
82
|
+
- SIGTERM triggers graceful shutdown: stops accepting new connections and drains existing ones
|
|
83
|
+
- GET /metrics returns valid Prometheus exposition format
|
|
84
|
+
- Environment variables validated on startup; missing required vars cause exit 1
|
|
85
|
+
|
|
37
86
|
## Success Metrics
|
|
38
87
|
- Service starts in Docker and responds to API requests
|
|
39
88
|
- Health probes return healthy status after startup
|