@stackmemoryai/stackmemory 1.10.4 → 1.12.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.
Files changed (95) hide show
  1. package/README.md +104 -23
  2. package/dist/src/cli/claude-sm.js +266 -84
  3. package/dist/src/cli/codex-sm.js +185 -33
  4. package/dist/src/cli/commands/bench.js +209 -2
  5. package/dist/src/cli/commands/cache.js +126 -0
  6. package/dist/src/cli/commands/daemon.js +41 -0
  7. package/dist/src/cli/commands/handoff.js +40 -9
  8. package/dist/src/cli/commands/onboard.js +70 -3
  9. package/dist/src/cli/commands/optimize.js +117 -0
  10. package/dist/src/cli/commands/orchestrate.js +230 -5
  11. package/dist/src/cli/commands/orchestrator.js +312 -24
  12. package/dist/src/cli/commands/pack.js +322 -0
  13. package/dist/src/cli/commands/search.js +40 -1
  14. package/dist/src/cli/commands/setup.js +177 -7
  15. package/dist/src/cli/commands/skills.js +10 -1
  16. package/dist/src/cli/commands/state.js +265 -0
  17. package/dist/src/cli/commands/wiki.js +33 -0
  18. package/dist/src/cli/gemini-sm.js +19 -29
  19. package/dist/src/cli/index.js +90 -29
  20. package/dist/src/cli/opencode-sm.js +38 -21
  21. package/dist/src/cli/utils/determinism-watcher.js +66 -0
  22. package/dist/src/cli/utils/real-cli-bin.js +44 -0
  23. package/dist/src/core/cache/content-cache.js +238 -0
  24. package/dist/src/core/cache/index.js +11 -0
  25. package/dist/src/core/cache/token-estimator.js +16 -0
  26. package/dist/src/core/context/frame-database.js +38 -30
  27. package/dist/src/core/cross-search/cross-project-search.js +269 -0
  28. package/dist/src/core/{merge → cross-search}/index.js +6 -4
  29. package/dist/src/core/database/sqlite-adapter.js +0 -83
  30. package/dist/src/core/extensions/provider-adapter.js +5 -0
  31. package/dist/src/core/models/model-router.js +22 -2
  32. package/dist/src/core/monitoring/logger.js +2 -1
  33. package/dist/src/core/optimization/trace-optimizer.js +413 -0
  34. package/dist/src/core/provenance/confidence-scorer.js +128 -0
  35. package/dist/src/core/provenance/index.js +40 -0
  36. package/dist/src/core/provenance/provenance-store.js +194 -0
  37. package/dist/src/core/provenance/types.js +82 -0
  38. package/dist/src/core/session/project-handoff.js +64 -0
  39. package/dist/src/core/session/session-manager.js +28 -0
  40. package/dist/src/core/shared-state/canonical-store.js +564 -0
  41. package/dist/src/core/skill-packs/index.js +18 -0
  42. package/dist/src/core/skill-packs/parser.js +42 -0
  43. package/dist/src/core/skill-packs/registry.js +224 -0
  44. package/dist/src/core/skill-packs/types.js +66 -0
  45. package/dist/src/core/trace/trace-event-store.js +282 -0
  46. package/dist/src/core/trace/trace-event.js +4 -0
  47. package/dist/src/core/wiki/wiki-compiler.js +219 -0
  48. package/dist/src/daemon/daemon-config.js +7 -0
  49. package/dist/src/daemon/services/github-service.js +126 -0
  50. package/dist/src/daemon/unified-daemon.js +30 -0
  51. package/dist/src/features/sweep/pty-wrapper.js +13 -5
  52. package/dist/src/hooks/schemas.js +2 -0
  53. package/dist/src/integrations/claude-code/subagent-client.js +89 -0
  54. package/dist/src/integrations/github/pr-state.js +158 -0
  55. package/dist/src/integrations/linear/client.js +4 -1
  56. package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
  57. package/dist/src/integrations/mcp/handlers/index.js +40 -59
  58. package/dist/src/integrations/mcp/server.js +425 -311
  59. package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
  60. package/dist/src/integrations/mcp/tool-definitions.js +98 -229
  61. package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
  62. package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
  63. package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
  64. package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
  65. package/dist/src/orchestrators/multimodal/determinism.js +243 -0
  66. package/dist/src/orchestrators/multimodal/harness.js +147 -77
  67. package/dist/src/orchestrators/multimodal/providers.js +44 -3
  68. package/dist/src/utils/hook-installer.js +8 -8
  69. package/package.json +10 -1
  70. package/packs/coding/python-fastapi/instructions.md +60 -0
  71. package/packs/coding/python-fastapi/pack.yaml +28 -0
  72. package/packs/coding/typescript-react/instructions.md +47 -0
  73. package/packs/coding/typescript-react/pack.yaml +28 -0
  74. package/packs/core/commands/capture.md +32 -0
  75. package/packs/core/commands/learn.md +73 -0
  76. package/packs/core/commands/next.md +36 -0
  77. package/packs/core/commands/restart.md +58 -0
  78. package/packs/core/commands/restore.md +29 -0
  79. package/packs/core/commands/start.md +57 -0
  80. package/packs/core/commands/stop.md +65 -0
  81. package/packs/core/commands/summary.md +40 -0
  82. package/packs/core/manifest.json +24 -0
  83. package/packs/ops/decision-recovery/instructions.md +65 -0
  84. package/packs/ops/decision-recovery/pack.yaml +89 -0
  85. package/templates/claude-hooks/doc-ingest.js +76 -0
  86. package/dist/src/cli/commands/team.js +0 -168
  87. package/dist/src/core/context/shared-context-layer.js +0 -620
  88. package/dist/src/core/context/stack-merge-resolver.js +0 -748
  89. package/dist/src/core/merge/conflict-detector.js +0 -430
  90. package/dist/src/core/merge/resolution-engine.js +0 -557
  91. package/dist/src/core/merge/stack-diff.js +0 -531
  92. package/dist/src/core/merge/unified-merge-resolver.js +0 -302
  93. package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
  94. package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
  95. /package/dist/src/core/{merge → cache}/types.js +0 -0
@@ -0,0 +1,564 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import * as fs from "fs/promises";
6
+ import * as path from "path";
7
+ import * as os from "os";
8
+ import { createHash, randomUUID } from "crypto";
9
+ function getBaseStateDir() {
10
+ const xdgState = process.env["XDG_STATE_HOME"]?.trim();
11
+ if (xdgState) {
12
+ return path.join(xdgState, "stackmemory");
13
+ }
14
+ const homeDir = process.env["HOME"] || process.env["USERPROFILE"] || os.homedir();
15
+ return path.join(homeDir, ".stackmemory");
16
+ }
17
+ function projectIdFromIdentifier(identifier) {
18
+ return identifier.replace(/\.git$/, "").replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase().slice(-50);
19
+ }
20
+ function normalizeProjectId(projectId, projectPath) {
21
+ if (projectId && projectId.trim()) {
22
+ return projectIdFromIdentifier(projectId.trim());
23
+ }
24
+ if (!projectPath || !projectPath.trim()) {
25
+ return void 0;
26
+ }
27
+ return createHash("sha1").update(projectPath.trim().toLowerCase()).digest("hex").slice(0, 16);
28
+ }
29
+ async function pathExists(targetPath) {
30
+ try {
31
+ await fs.access(targetPath);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ class CanonicalStateStore {
38
+ rootDir;
39
+ constructor(rootDir = path.join(getBaseStateDir(), "shared-state")) {
40
+ this.rootDir = rootDir;
41
+ }
42
+ getRootDir() {
43
+ return this.rootDir;
44
+ }
45
+ async initialize() {
46
+ await fs.mkdir(this.getInstancesDir(), { recursive: true });
47
+ await fs.mkdir(this.getSessionsDir(), { recursive: true });
48
+ await fs.mkdir(this.getEventsDir(), { recursive: true });
49
+ await fs.mkdir(this.getGithubDir(), { recursive: true });
50
+ await fs.mkdir(this.getClaimsDir(), { recursive: true });
51
+ }
52
+ async upsertInstance(input) {
53
+ await this.initialize();
54
+ const filePath = this.getInstanceFile(input.instanceId);
55
+ const existing = await this.readJsonFile(filePath);
56
+ const now = Date.now();
57
+ const record = {
58
+ instanceId: input.instanceId,
59
+ tool: input.tool,
60
+ sessionId: input.sessionId ?? existing?.sessionId,
61
+ projectId: normalizeProjectId(input.projectId, input.projectPath) ?? existing?.projectId,
62
+ projectPath: input.projectPath ?? existing?.projectPath,
63
+ branch: input.branch ?? existing?.branch,
64
+ worktreePath: input.worktreePath ?? existing?.worktreePath,
65
+ pid: input.pid ?? existing?.pid,
66
+ startedAt: existing?.startedAt ?? input.startedAt ?? now,
67
+ lastSeenAt: input.lastSeenAt ?? now,
68
+ status: input.status ?? existing?.status ?? "active",
69
+ metadata: {
70
+ ...existing?.metadata || {},
71
+ ...input.metadata || {}
72
+ }
73
+ };
74
+ await this.writeJsonFile(filePath, record);
75
+ if (record.sessionId) {
76
+ await this.upsertSession({
77
+ sessionId: record.sessionId,
78
+ tool: record.tool,
79
+ projectId: record.projectId,
80
+ projectPath: record.projectPath,
81
+ branch: record.branch,
82
+ instanceId: record.instanceId,
83
+ metadata: record.metadata
84
+ });
85
+ }
86
+ return record;
87
+ }
88
+ async endInstance(instanceId) {
89
+ await this.initialize();
90
+ const filePath = this.getInstanceFile(instanceId);
91
+ const existing = await this.readJsonFile(filePath);
92
+ if (!existing) {
93
+ return;
94
+ }
95
+ const updated = {
96
+ ...existing,
97
+ status: "ended",
98
+ lastSeenAt: Date.now()
99
+ };
100
+ await this.writeJsonFile(filePath, updated);
101
+ }
102
+ async upsertSession(input) {
103
+ await this.initialize();
104
+ const filePath = this.getSessionFile(input.sessionId);
105
+ const existing = await this.readJsonFile(filePath);
106
+ const now = Date.now();
107
+ const nextInstanceIds = new Set(existing?.instanceIds || []);
108
+ if (input.instanceId) {
109
+ nextInstanceIds.add(input.instanceId);
110
+ }
111
+ for (const instanceId of input.instanceIds || []) {
112
+ nextInstanceIds.add(instanceId);
113
+ }
114
+ const record = {
115
+ sessionId: input.sessionId,
116
+ tool: input.tool,
117
+ projectId: normalizeProjectId(input.projectId, input.projectPath) ?? existing?.projectId,
118
+ projectPath: input.projectPath ?? existing?.projectPath,
119
+ branch: input.branch ?? existing?.branch,
120
+ startedAt: existing?.startedAt ?? input.startedAt ?? now,
121
+ lastSeenAt: input.lastSeenAt ?? now,
122
+ status: input.status ?? existing?.status ?? "active",
123
+ instanceIds: Array.from(nextInstanceIds),
124
+ metadata: {
125
+ ...existing?.metadata || {},
126
+ ...input.metadata || {}
127
+ }
128
+ };
129
+ await this.writeJsonFile(filePath, record);
130
+ return record;
131
+ }
132
+ async endSession(sessionId, status = "closed") {
133
+ await this.initialize();
134
+ const filePath = this.getSessionFile(sessionId);
135
+ const existing = await this.readJsonFile(filePath);
136
+ if (!existing) {
137
+ return;
138
+ }
139
+ const updated = {
140
+ ...existing,
141
+ status,
142
+ lastSeenAt: Date.now()
143
+ };
144
+ await this.writeJsonFile(filePath, updated);
145
+ }
146
+ async saveGitHubPullRequest(projection) {
147
+ await this.initialize();
148
+ const normalizedProjectId = normalizeProjectId(projection.projectId, projection.projectPath) ?? projection.projectId;
149
+ const filePath = this.getGitHubPullRequestFile(
150
+ projection.repo,
151
+ projection.branch
152
+ );
153
+ const record = {
154
+ ...projection,
155
+ projectId: normalizedProjectId,
156
+ lastSyncedAt: projection.lastSyncedAt || Date.now()
157
+ };
158
+ await this.writeJsonFile(filePath, record);
159
+ return record;
160
+ }
161
+ async claimPaths(input) {
162
+ await this.initialize();
163
+ await this.cleanupExpiredClaims();
164
+ const now = input.lastSeenAt ?? Date.now();
165
+ const existing = input.claimId ? await this.readJsonFile(
166
+ this.getClaimFile(input.claimId)
167
+ ) : null;
168
+ const record = {
169
+ claimId: input.claimId || randomUUID(),
170
+ tool: input.tool,
171
+ sessionId: input.sessionId ?? existing?.sessionId,
172
+ instanceId: input.instanceId ?? existing?.instanceId,
173
+ projectId: normalizeProjectId(input.projectId, input.projectPath) ?? existing?.projectId,
174
+ projectPath: input.projectPath ?? existing?.projectPath,
175
+ branch: input.branch ?? existing?.branch,
176
+ paths: Array.from(
177
+ new Set(
178
+ (input.paths ?? existing?.paths ?? []).map((item) => item.trim()).filter(Boolean)
179
+ )
180
+ ),
181
+ status: input.status ?? "active",
182
+ claimedAt: existing?.claimedAt ?? now,
183
+ lastSeenAt: now,
184
+ expiresAt: input.expiresAt ?? now + Math.max(1, input.ttlMs ?? 24 * 60 * 60 * 1e3),
185
+ metadata: {
186
+ ...existing?.metadata || {},
187
+ ...input.metadata || {}
188
+ }
189
+ };
190
+ const conflicts = (await this.listPathClaims({
191
+ projectId: record.projectId,
192
+ projectPath: record.projectPath,
193
+ activeOnly: true
194
+ })).filter((claim) => claim.claimId !== record.claimId).filter((claim) => this.claimsOverlap(record, claim)).map((claim) => ({
195
+ claimId: claim.claimId,
196
+ branch: claim.branch,
197
+ paths: claim.paths,
198
+ sessionId: claim.sessionId,
199
+ instanceId: claim.instanceId
200
+ }));
201
+ await this.writeJsonFile(this.getClaimFile(record.claimId), record);
202
+ return { record, conflicts };
203
+ }
204
+ async releaseClaims(options) {
205
+ await this.initialize();
206
+ const now = Date.now();
207
+ let released = 0;
208
+ const claims = await this.listPathClaims({
209
+ projectId: options.projectId,
210
+ projectPath: options.projectPath,
211
+ activeOnly: false
212
+ });
213
+ for (const claim of claims) {
214
+ if (claim.status !== "active") {
215
+ continue;
216
+ }
217
+ if (options.claimId && claim.claimId !== options.claimId) {
218
+ continue;
219
+ }
220
+ if (options.instanceId && claim.instanceId !== options.instanceId) {
221
+ continue;
222
+ }
223
+ if (options.sessionId && claim.sessionId !== options.sessionId) {
224
+ continue;
225
+ }
226
+ if (options.branch && claim.branch !== options.branch) {
227
+ continue;
228
+ }
229
+ await this.writeJsonFile(this.getClaimFile(claim.claimId), {
230
+ ...claim,
231
+ status: "released",
232
+ lastSeenAt: now,
233
+ releasedAt: now,
234
+ releaseReason: options.reason || claim.releaseReason
235
+ });
236
+ released++;
237
+ }
238
+ return released;
239
+ }
240
+ async listPathClaims(options) {
241
+ await this.initialize();
242
+ await this.cleanupExpiredClaims();
243
+ const projectId = normalizeProjectId(
244
+ options?.projectId,
245
+ options?.projectPath
246
+ );
247
+ const dir = this.getClaimsDir();
248
+ const entries = await fs.readdir(dir);
249
+ const claims = await Promise.all(
250
+ entries.filter((entry) => entry.endsWith(".json")).map(
251
+ (entry) => this.readJsonFile(path.join(dir, entry))
252
+ )
253
+ );
254
+ return claims.filter(Boolean).filter((claim) => !options?.activeOnly || claim.status === "active").filter(
255
+ (claim) => !projectId || this.matchesProject(
256
+ claim.projectId,
257
+ claim.projectPath,
258
+ projectId,
259
+ options?.projectPath
260
+ )
261
+ ).sort((a, b) => b.lastSeenAt - a.lastSeenAt);
262
+ }
263
+ async listActiveProjectPaths() {
264
+ await this.initialize();
265
+ const projectPaths = /* @__PURE__ */ new Set();
266
+ const sessions = await this.listSessions();
267
+ for (const session of sessions) {
268
+ if (session.status === "active" && session.projectPath) {
269
+ projectPaths.add(session.projectPath);
270
+ }
271
+ }
272
+ const instances = await this.listInstances();
273
+ for (const instance of instances) {
274
+ if (instance.status === "active" && instance.projectPath) {
275
+ projectPaths.add(instance.projectPath);
276
+ }
277
+ }
278
+ const pullRequests = await this.listGitHubPullRequests();
279
+ for (const projection of pullRequests) {
280
+ if (projection.projectPath) {
281
+ projectPaths.add(projection.projectPath);
282
+ }
283
+ }
284
+ return Array.from(projectPaths).sort();
285
+ }
286
+ async getGitHubPullRequest(options) {
287
+ await this.initialize();
288
+ return this.readJsonFile(
289
+ this.getGitHubPullRequestFile(options.repo, options.branch)
290
+ );
291
+ }
292
+ async listGitHubPullRequests(options) {
293
+ await this.initialize();
294
+ const projectId = normalizeProjectId(
295
+ options?.projectId,
296
+ options?.projectPath
297
+ );
298
+ const dir = this.getGithubDir();
299
+ const entries = await fs.readdir(dir);
300
+ const records = await Promise.all(
301
+ entries.filter((entry) => entry.endsWith(".json")).map(
302
+ (entry) => this.readJsonFile(path.join(dir, entry))
303
+ )
304
+ );
305
+ return records.filter(Boolean).filter(
306
+ (record) => !projectId || this.matchesProject(
307
+ record.projectId,
308
+ record.projectPath,
309
+ projectId,
310
+ options?.projectPath
311
+ )
312
+ );
313
+ }
314
+ async appendEvent(input) {
315
+ await this.initialize();
316
+ const event = {
317
+ id: input.id || randomUUID(),
318
+ timestamp: input.timestamp || Date.now(),
319
+ type: input.type,
320
+ tool: input.tool,
321
+ instanceId: input.instanceId,
322
+ sessionId: input.sessionId,
323
+ projectId: normalizeProjectId(input.projectId, input.projectPath),
324
+ projectPath: input.projectPath,
325
+ branch: input.branch,
326
+ payload: input.payload || {}
327
+ };
328
+ const date = new Date(event.timestamp).toISOString().slice(0, 10);
329
+ const eventFile = path.join(this.getEventsDir(), `${date}.jsonl`);
330
+ await fs.appendFile(eventFile, `${JSON.stringify(event)}
331
+ `, "utf8");
332
+ if (event.instanceId) {
333
+ const instance = await this.readJsonFile(
334
+ this.getInstanceFile(event.instanceId)
335
+ );
336
+ if (instance) {
337
+ await this.upsertInstance({
338
+ ...instance,
339
+ lastSeenAt: event.timestamp
340
+ });
341
+ }
342
+ }
343
+ if (event.sessionId) {
344
+ const session = await this.readJsonFile(
345
+ this.getSessionFile(event.sessionId)
346
+ );
347
+ if (session) {
348
+ await this.upsertSession({
349
+ ...session,
350
+ lastSeenAt: event.timestamp,
351
+ instanceIds: session.instanceIds
352
+ });
353
+ }
354
+ }
355
+ return event;
356
+ }
357
+ async listSessions() {
358
+ await this.initialize();
359
+ const dir = this.getSessionsDir();
360
+ const entries = await fs.readdir(dir);
361
+ const sessions = await Promise.all(
362
+ entries.filter((entry) => entry.endsWith(".json")).map(
363
+ (entry) => this.readJsonFile(path.join(dir, entry))
364
+ )
365
+ );
366
+ return sessions.filter(Boolean);
367
+ }
368
+ async listInstances() {
369
+ await this.initialize();
370
+ const dir = this.getInstancesDir();
371
+ const entries = await fs.readdir(dir);
372
+ const instances = await Promise.all(
373
+ entries.filter((entry) => entry.endsWith(".json")).map(
374
+ (entry) => this.readJsonFile(path.join(dir, entry))
375
+ )
376
+ );
377
+ return instances.filter(Boolean);
378
+ }
379
+ async getProjectSummary(options) {
380
+ await this.initialize();
381
+ const projectId = normalizeProjectId(
382
+ options.projectId,
383
+ options.projectPath
384
+ );
385
+ const sessions = (await this.listSessions()).filter(
386
+ (session) => session.status === "active" && this.matchesProject(
387
+ session.projectId,
388
+ session.projectPath,
389
+ projectId,
390
+ options.projectPath
391
+ )
392
+ );
393
+ const instances = (await this.listInstances()).filter(
394
+ (instance) => instance.status === "active" && this.matchesProject(
395
+ instance.projectId,
396
+ instance.projectPath,
397
+ projectId,
398
+ options.projectPath
399
+ )
400
+ );
401
+ const activeClaims = await this.listPathClaims({
402
+ projectId,
403
+ projectPath: options.projectPath,
404
+ activeOnly: true
405
+ });
406
+ const recentEvents = await this.listRecentEvents({
407
+ projectId,
408
+ projectPath: options.projectPath,
409
+ limit: options.eventLimit || 10
410
+ });
411
+ return {
412
+ projectId,
413
+ projectPath: options.projectPath,
414
+ activeSessions: sessions.sort((a, b) => b.lastSeenAt - a.lastSeenAt),
415
+ activeInstances: instances.sort((a, b) => b.lastSeenAt - a.lastSeenAt),
416
+ activeClaims,
417
+ recentEvents
418
+ };
419
+ }
420
+ async cleanupExpiredClaims() {
421
+ const dir = this.getClaimsDir();
422
+ const entries = await fs.readdir(dir).catch(() => []);
423
+ const now = Date.now();
424
+ for (const entry of entries) {
425
+ if (!entry.endsWith(".json")) {
426
+ continue;
427
+ }
428
+ const filePath = path.join(dir, entry);
429
+ const claim = await this.readJsonFile(filePath);
430
+ if (!claim || claim.status !== "active" || claim.expiresAt > now) {
431
+ continue;
432
+ }
433
+ await this.writeJsonFile(filePath, {
434
+ ...claim,
435
+ status: "expired",
436
+ releasedAt: now,
437
+ releaseReason: claim.releaseReason || "expired",
438
+ lastSeenAt: now
439
+ });
440
+ }
441
+ }
442
+ claimsOverlap(left, right) {
443
+ if (left.branch && right.branch && left.branch.trim() && right.branch.trim() && left.branch === right.branch) {
444
+ return true;
445
+ }
446
+ for (const leftPath of left.paths) {
447
+ for (const rightPath of right.paths) {
448
+ if (this.pathsOverlap(leftPath, rightPath)) {
449
+ return true;
450
+ }
451
+ }
452
+ }
453
+ return false;
454
+ }
455
+ pathsOverlap(left, right) {
456
+ const normalizedLeft = this.normalizeClaimPath(left);
457
+ const normalizedRight = this.normalizeClaimPath(right);
458
+ if (!normalizedLeft || !normalizedRight) {
459
+ return false;
460
+ }
461
+ if (normalizedLeft === "*" || normalizedRight === "*" || normalizedLeft === "." || normalizedRight === ".") {
462
+ return true;
463
+ }
464
+ if (normalizedLeft === normalizedRight) {
465
+ return true;
466
+ }
467
+ return normalizedLeft.startsWith(`${normalizedRight}/`) || normalizedRight.startsWith(`${normalizedLeft}/`);
468
+ }
469
+ normalizeClaimPath(value) {
470
+ return value.trim().replace(/\\/g, "/").replace(/\/\*\*$/, "").replace(/\/$/, "");
471
+ }
472
+ async listRecentEvents(options) {
473
+ await this.initialize();
474
+ const projectId = normalizeProjectId(
475
+ options.projectId,
476
+ options.projectPath
477
+ );
478
+ const eventDir = this.getEventsDir();
479
+ const eventFiles = (await fs.readdir(eventDir)).filter((entry) => entry.endsWith(".jsonl")).sort().reverse().slice(0, 7);
480
+ const events = [];
481
+ for (const entry of eventFiles) {
482
+ const filePath = path.join(eventDir, entry);
483
+ const content = await fs.readFile(filePath, "utf8");
484
+ const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
485
+ for (let i = lines.length - 1; i >= 0; i--) {
486
+ try {
487
+ const parsed = JSON.parse(lines[i]);
488
+ if (this.matchesProject(
489
+ parsed.projectId,
490
+ parsed.projectPath,
491
+ projectId,
492
+ options.projectPath
493
+ )) {
494
+ events.push(parsed);
495
+ }
496
+ } catch {
497
+ }
498
+ if (events.length >= (options.limit || 20)) {
499
+ return events;
500
+ }
501
+ }
502
+ }
503
+ return events;
504
+ }
505
+ matchesProject(candidateProjectId, candidateProjectPath, projectId, projectPath) {
506
+ if (projectId && candidateProjectId) {
507
+ return candidateProjectId === projectId;
508
+ }
509
+ if (projectPath && candidateProjectPath) {
510
+ return candidateProjectPath === projectPath;
511
+ }
512
+ return !projectId && !projectPath;
513
+ }
514
+ getInstancesDir() {
515
+ return path.join(this.rootDir, "instances");
516
+ }
517
+ getSessionsDir() {
518
+ return path.join(this.rootDir, "sessions");
519
+ }
520
+ getEventsDir() {
521
+ return path.join(this.rootDir, "events");
522
+ }
523
+ getGithubDir() {
524
+ return path.join(this.rootDir, "github", "pull-requests");
525
+ }
526
+ getClaimsDir() {
527
+ return path.join(this.rootDir, "claims");
528
+ }
529
+ getInstanceFile(instanceId) {
530
+ return path.join(this.getInstancesDir(), `${instanceId}.json`);
531
+ }
532
+ getSessionFile(sessionId) {
533
+ return path.join(this.getSessionsDir(), `${sessionId}.json`);
534
+ }
535
+ getGitHubPullRequestFile(repo, branch) {
536
+ const slug = `${repo}__${branch}`.replace(/[\\/]/g, "__").replace(/[^a-zA-Z0-9_.-]/g, "-");
537
+ return path.join(this.getGithubDir(), `${slug}.json`);
538
+ }
539
+ getClaimFile(claimId) {
540
+ return path.join(this.getClaimsDir(), `${claimId}.json`);
541
+ }
542
+ async readJsonFile(filePath) {
543
+ if (!await pathExists(filePath)) {
544
+ return null;
545
+ }
546
+ const content = await fs.readFile(filePath, "utf8");
547
+ return JSON.parse(content);
548
+ }
549
+ async writeJsonFile(filePath, value) {
550
+ const dir = path.dirname(filePath);
551
+ await fs.mkdir(dir, { recursive: true });
552
+ const tempPath = `${filePath}.${process.pid}.tmp`;
553
+ await fs.writeFile(tempPath, JSON.stringify(value, null, 2));
554
+ await fs.rename(tempPath, filePath);
555
+ }
556
+ }
557
+ const canonicalStateStore = new CanonicalStateStore();
558
+ export {
559
+ CanonicalStateStore,
560
+ canonicalStateStore,
561
+ getBaseStateDir as getCanonicalStateBaseDir,
562
+ normalizeProjectId,
563
+ projectIdFromIdentifier
564
+ };
@@ -0,0 +1,18 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ export * from "./types.js";
6
+ import { parsePackYaml, loadPackFromDir } from "./parser.js";
7
+ import {
8
+ SkillPackRegistry,
9
+ getSkillPackRegistry,
10
+ resetSkillPackRegistry
11
+ } from "./registry.js";
12
+ export {
13
+ SkillPackRegistry,
14
+ getSkillPackRegistry,
15
+ loadPackFromDir,
16
+ parsePackYaml,
17
+ resetSkillPackRegistry
18
+ };
@@ -0,0 +1,42 @@
1
+ import { fileURLToPath as __fileURLToPath } from 'url';
2
+ import { dirname as __pathDirname } from 'path';
3
+ const __filename = __fileURLToPath(import.meta.url);
4
+ const __dirname = __pathDirname(__filename);
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import * as yaml from "js-yaml";
8
+ import { SkillPackManifestSchema } from "./types.js";
9
+ import { logger } from "../monitoring/logger.js";
10
+ function parsePackYaml(content) {
11
+ const raw = yaml.load(content);
12
+ const manifest = SkillPackManifestSchema.parse(raw);
13
+ const instructions = manifest.instructions;
14
+ return { manifest, instructions };
15
+ }
16
+ async function loadPackFromDir(dir) {
17
+ const yamlPath = path.join(dir, "pack.yaml");
18
+ if (!fs.existsSync(yamlPath)) {
19
+ throw new Error(`pack.yaml not found in ${dir}`);
20
+ }
21
+ const content = fs.readFileSync(yamlPath, "utf-8");
22
+ const pack = parsePackYaml(content);
23
+ if (pack.manifest.instructions?.endsWith(".md")) {
24
+ const instrPath = path.join(dir, pack.manifest.instructions);
25
+ if (fs.existsSync(instrPath)) {
26
+ pack.instructions = fs.readFileSync(instrPath, "utf-8");
27
+ logger.debug(
28
+ `Loaded instructions from ${instrPath} for pack ${pack.manifest.name}`
29
+ );
30
+ } else {
31
+ logger.warn(
32
+ `Instructions file ${instrPath} referenced but not found for pack ${pack.manifest.name}`
33
+ );
34
+ pack.instructions = void 0;
35
+ }
36
+ }
37
+ return pack;
38
+ }
39
+ export {
40
+ loadPackFromDir,
41
+ parsePackYaml
42
+ };