wyrm-mcp 3.7.2 → 4.0.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 (61) hide show
  1. package/dist/audit.d.ts +82 -0
  2. package/dist/audit.d.ts.map +1 -0
  3. package/dist/audit.js +151 -0
  4. package/dist/audit.js.map +1 -0
  5. package/dist/causality.d.ts +76 -0
  6. package/dist/causality.d.ts.map +1 -0
  7. package/dist/causality.js +115 -0
  8. package/dist/causality.js.map +1 -0
  9. package/dist/database.d.ts.map +1 -1
  10. package/dist/database.js +5 -0
  11. package/dist/database.js.map +1 -1
  12. package/dist/failure-patterns.d.ts +60 -0
  13. package/dist/failure-patterns.d.ts.map +1 -0
  14. package/dist/failure-patterns.js +117 -0
  15. package/dist/failure-patterns.js.map +1 -0
  16. package/dist/federation.d.ts +82 -0
  17. package/dist/federation.d.ts.map +1 -0
  18. package/dist/federation.js +220 -0
  19. package/dist/federation.js.map +1 -0
  20. package/dist/hours.d.ts +82 -0
  21. package/dist/hours.d.ts.map +1 -0
  22. package/dist/hours.js +162 -0
  23. package/dist/hours.js.map +1 -0
  24. package/dist/http-fast.d.ts.map +1 -1
  25. package/dist/http-fast.js +183 -1
  26. package/dist/http-fast.js.map +1 -1
  27. package/dist/http-server.js +1 -1
  28. package/dist/http-server.js.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1000 -99
  31. package/dist/index.js.map +1 -1
  32. package/dist/indexer.d.ts +2 -0
  33. package/dist/indexer.d.ts.map +1 -1
  34. package/dist/indexer.js +4 -0
  35. package/dist/indexer.js.map +1 -1
  36. package/dist/migrations.d.ts.map +1 -1
  37. package/dist/migrations.js +265 -1
  38. package/dist/migrations.js.map +1 -1
  39. package/dist/presence.d.ts +71 -0
  40. package/dist/presence.d.ts.map +1 -0
  41. package/dist/presence.js +133 -0
  42. package/dist/presence.js.map +1 -0
  43. package/dist/rehydration.d.ts +39 -0
  44. package/dist/rehydration.d.ts.map +1 -0
  45. package/dist/rehydration.js +178 -0
  46. package/dist/rehydration.js.map +1 -0
  47. package/dist/setup.js +0 -0
  48. package/dist/sub-agent.d.ts +58 -0
  49. package/dist/sub-agent.d.ts.map +1 -0
  50. package/dist/sub-agent.js +292 -0
  51. package/dist/sub-agent.js.map +1 -0
  52. package/dist/symbols.d.ts +65 -0
  53. package/dist/symbols.d.ts.map +1 -0
  54. package/dist/symbols.js +291 -0
  55. package/dist/symbols.js.map +1 -0
  56. package/dist/tool-analytics.d.ts +85 -0
  57. package/dist/tool-analytics.d.ts.map +1 -0
  58. package/dist/tool-analytics.js +161 -0
  59. package/dist/tool-analytics.js.map +1 -0
  60. package/dist/wyrm-cli.js +0 -0
  61. package/package.json +6 -6
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Failure Patterns — Counter-pattern detection.
3
+ *
4
+ * Records edits / commands / prompts that failed so the predictive push
5
+ * can BLOCK the same suggestion mid-stream the next time it surfaces.
6
+ * Most "memory" tools only learn from success; Wyrm learns from failure too.
7
+ *
8
+ * Keyed on `(signature, scope)` so semantically-identical failures
9
+ * coalesce — re-recording the same failure increments `occurrences`
10
+ * rather than creating a new row.
11
+ *
12
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
13
+ * @license Proprietary
14
+ */
15
+ import type Database from 'better-sqlite3';
16
+ export type FailureScope = 'file' | 'symbol' | 'command' | 'prompt' | 'edit';
17
+ export type FailureSeverity = 'low' | 'medium' | 'high' | 'critical';
18
+ export interface FailurePattern {
19
+ id: number;
20
+ project_id: number | null;
21
+ signature: string;
22
+ scope: FailureScope;
23
+ target: string;
24
+ description: string;
25
+ why_failed: string | null;
26
+ occurrences: number;
27
+ first_seen: string;
28
+ last_seen: string;
29
+ severity: FailureSeverity;
30
+ resolved: 0 | 1;
31
+ resolution_note: string | null;
32
+ }
33
+ export interface RecordInput {
34
+ project_id?: number | null;
35
+ scope: FailureScope;
36
+ target: string;
37
+ description: string;
38
+ why_failed?: string;
39
+ severity?: FailureSeverity;
40
+ }
41
+ export declare class FailurePatterns {
42
+ private db;
43
+ constructor(db: Database.Database);
44
+ /** Build a stable signature from scope+target+description, so repeated
45
+ * identical failures coalesce instead of creating new rows. */
46
+ static signature(scope: string, target: string, description: string): string;
47
+ /** Record (or increment) a failure. Returns the (new or updated) row. */
48
+ record(input: RecordInput): FailurePattern;
49
+ get(id: number): FailurePattern | null;
50
+ /** Check whether a proposed action matches any unresolved failure.
51
+ * Returns the matching patterns, ranked by recency × occurrences. */
52
+ check(scope: FailureScope, target: string, description: string, projectId?: number | null): FailurePattern[];
53
+ /** List unresolved failures in a project, most-recent-first. */
54
+ list(projectId?: number | null, limit?: number): FailurePattern[];
55
+ /** Mark a failure as resolved (the underlying issue was fixed). */
56
+ resolve(id: number, note?: string): FailurePattern | null;
57
+ /** Permanently delete a failure (use sparingly — resolve() is preferred). */
58
+ delete(id: number): boolean;
59
+ }
60
+ //# sourceMappingURL=failure-patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failure-patterns.d.ts","sourceRoot":"","sources":["../src/failure-patterns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAG3C,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,eAAe,CAAC;IAC1B,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC;IAChB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,eAAe,CAAC;CAC5B;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC;mEAC+D;IAC/D,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM;IAO5E,yEAAyE;IACzE,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc;IA4C1C,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAOtC;yEACqE;IACrE,KAAK,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,cAAc,EAAE;IA+B5G,gEAAgE;IAChE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,SAAK,GAAG,cAAc,EAAE;IAc7D,mEAAmE;IACnE,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IASzD,6EAA6E;IAC7E,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;CAM5B"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Failure Patterns — Counter-pattern detection.
3
+ *
4
+ * Records edits / commands / prompts that failed so the predictive push
5
+ * can BLOCK the same suggestion mid-stream the next time it surfaces.
6
+ * Most "memory" tools only learn from success; Wyrm learns from failure too.
7
+ *
8
+ * Keyed on `(signature, scope)` so semantically-identical failures
9
+ * coalesce — re-recording the same failure increments `occurrences`
10
+ * rather than creating a new row.
11
+ *
12
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
13
+ * @license Proprietary
14
+ */
15
+ import { sanitizeFtsQuery } from './security.js';
16
+ export class FailurePatterns {
17
+ db;
18
+ constructor(db) {
19
+ this.db = db;
20
+ }
21
+ /** Build a stable signature from scope+target+description, so repeated
22
+ * identical failures coalesce instead of creating new rows. */
23
+ static signature(scope, target, description) {
24
+ // Normalize: lowercase, strip whitespace runs, trim
25
+ const norm = (s) => s.toLowerCase().replace(/\s+/g, ' ').trim().slice(0, 200);
26
+ return `${scope}:${norm(target)}:${norm(description).slice(0, 80)}`;
27
+ }
28
+ /** Record (or increment) a failure. Returns the (new or updated) row. */
29
+ record(input) {
30
+ const signature = FailurePatterns.signature(input.scope, input.target, input.description);
31
+ const projectId = input.project_id ?? null;
32
+ const existing = this.db.prepare(`
33
+ SELECT * FROM failure_patterns
34
+ WHERE signature = ? AND (project_id IS ? OR project_id = ?)
35
+ `).get(signature, projectId, projectId);
36
+ if (existing) {
37
+ this.db.prepare(`
38
+ UPDATE failure_patterns
39
+ SET occurrences = occurrences + 1,
40
+ last_seen = datetime('now'),
41
+ why_failed = COALESCE(?, why_failed),
42
+ severity = ?,
43
+ resolved = 0
44
+ WHERE id = ?
45
+ `).run(input.why_failed ?? null, input.severity ?? existing.severity, existing.id);
46
+ return this.get(existing.id);
47
+ }
48
+ const info = this.db.prepare(`
49
+ INSERT INTO failure_patterns
50
+ (project_id, signature, scope, target, description, why_failed, severity)
51
+ VALUES (?, ?, ?, ?, ?, ?, ?)
52
+ `).run(projectId, signature, input.scope, input.target, input.description, input.why_failed ?? null, input.severity ?? 'medium');
53
+ return this.get(info.lastInsertRowid);
54
+ }
55
+ get(id) {
56
+ const row = this.db.prepare('SELECT * FROM failure_patterns WHERE id = ?').get(id);
57
+ return row ?? null;
58
+ }
59
+ /** Check whether a proposed action matches any unresolved failure.
60
+ * Returns the matching patterns, ranked by recency × occurrences. */
61
+ check(scope, target, description, projectId) {
62
+ const signature = FailurePatterns.signature(scope, target, description);
63
+ const exact = this.db.prepare(`
64
+ SELECT * FROM failure_patterns
65
+ WHERE resolved = 0 AND signature = ?
66
+ AND (project_id IS ? OR project_id = ? OR project_id IS NULL)
67
+ ORDER BY occurrences DESC, last_seen DESC
68
+ LIMIT 5
69
+ `).all(signature, projectId ?? null, projectId ?? null);
70
+ if (exact.length > 0)
71
+ return exact;
72
+ // Fuzzy fallback: FTS over description + target
73
+ const q = sanitizeFtsQuery(`${target} ${description}`.slice(0, 100));
74
+ if (!q)
75
+ return [];
76
+ try {
77
+ return this.db.prepare(`
78
+ SELECT fp.* FROM failure_patterns fp
79
+ JOIN failure_patterns_fts fts ON fts.rowid = fp.id
80
+ WHERE failure_patterns_fts MATCH ?
81
+ AND fp.resolved = 0
82
+ AND (fp.project_id IS ? OR fp.project_id = ? OR fp.project_id IS NULL)
83
+ AND fp.scope = ?
84
+ ORDER BY fp.occurrences DESC, fp.last_seen DESC
85
+ LIMIT 5
86
+ `).all(q, projectId ?? null, projectId ?? null, scope);
87
+ }
88
+ catch {
89
+ return [];
90
+ }
91
+ }
92
+ /** List unresolved failures in a project, most-recent-first. */
93
+ list(projectId, limit = 20) {
94
+ if (projectId == null) {
95
+ return this.db.prepare(`SELECT * FROM failure_patterns WHERE resolved = 0
96
+ ORDER BY last_seen DESC LIMIT ?`).all(limit);
97
+ }
98
+ return this.db.prepare(`SELECT * FROM failure_patterns
99
+ WHERE resolved = 0 AND project_id = ?
100
+ ORDER BY last_seen DESC LIMIT ?`).all(projectId, limit);
101
+ }
102
+ /** Mark a failure as resolved (the underlying issue was fixed). */
103
+ resolve(id, note) {
104
+ this.db.prepare(`
105
+ UPDATE failure_patterns
106
+ SET resolved = 1, resolution_note = ?
107
+ WHERE id = ?
108
+ `).run(note ?? null, id);
109
+ return this.get(id);
110
+ }
111
+ /** Permanently delete a failure (use sparingly — resolve() is preferred). */
112
+ delete(id) {
113
+ const info = this.db.prepare('DELETE FROM failure_patterns WHERE id = ?').run(id);
114
+ return info.changes > 0;
115
+ }
116
+ }
117
+ //# sourceMappingURL=failure-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failure-patterns.js","sourceRoot":"","sources":["../src/failure-patterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Federated team Wyrm (Tier 2.8).
3
+ *
4
+ * Each operator runs a local Wyrm; opt-in tagged data syncs to a shared
5
+ * "team Wyrm" instance. PII never leaves the local box unless explicitly
6
+ * shared (per-row `is_shared=1` flag — set when the operator marks a
7
+ * truth/quest/artifact as team-visible).
8
+ *
9
+ * Sync surface:
10
+ * POST /sync/push pushes is_shared=1 local rows changed since cursor
11
+ * GET /sync/pull pulls remote rows changed since cursor
12
+ *
13
+ * Conflict policy: if a remote push lands on top of a local edit whose
14
+ * `updated_at` is newer, we DO NOT silently overwrite. Instead we record
15
+ * the conflict in `sync_conflicts` and mint a quest for the operator to
16
+ * resolve. Last-write-wins is the failure mode of every team sync tool;
17
+ * Wyrm refuses to silently lose work.
18
+ *
19
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
20
+ * @license Proprietary
21
+ */
22
+ import type Database from 'better-sqlite3';
23
+ export type SyncableKind = 'session' | 'quest' | 'truth' | 'artifact' | 'edge';
24
+ export interface SyncableRow {
25
+ kind: SyncableKind;
26
+ id: number;
27
+ project_id?: number | null;
28
+ payload: Record<string, unknown>;
29
+ updated_at: string;
30
+ }
31
+ export interface SyncPushResult {
32
+ pushed: number;
33
+ conflicts: number;
34
+ new_cursor: string;
35
+ conflict_ids: number[];
36
+ }
37
+ export interface SyncPullResult {
38
+ pulled: number;
39
+ new_cursor: string;
40
+ by_kind: Record<string, number>;
41
+ }
42
+ export declare class Federation {
43
+ private db;
44
+ constructor(db: Database.Database);
45
+ /** Mark a row as shared (will be picked up on next push). */
46
+ share(kind: SyncableKind, id: number): boolean;
47
+ /** Unshare (revoke from team visibility). Local row stays. */
48
+ unshare(kind: SyncableKind, id: number): boolean;
49
+ /** Collect rows to push: is_shared=1 AND ts > since_cursor. */
50
+ collectForPush(since_cursor?: string, limit?: number): SyncableRow[];
51
+ /** Apply a remote row locally. Returns:
52
+ * { kind: 'inserted' } — fresh row, no conflict
53
+ * { kind: 'overwritten' } — local row was older, remote wins
54
+ * { kind: 'conflict', conflict_id } — local row newer, conflict recorded
55
+ * Idempotent — applying the same payload twice is a no-op.
56
+ *
57
+ * Conflict policy: if local updated_at > remote updated_at, the remote
58
+ * is REJECTED into sync_conflicts; a quest is minted. The local row stays.
59
+ */
60
+ applyRemote(row: SyncableRow, remoteOrigin: string): {
61
+ kind: 'inserted' | 'overwritten' | 'conflict' | 'skipped';
62
+ conflict_id?: number;
63
+ };
64
+ /** Bulk push — record one sync_log row, return summary. Caller does HTTP. */
65
+ recordPush(rows: SyncableRow[], remoteOrigin: string, duration_ms: number, since_cursor?: string): SyncPushResult;
66
+ /** Bulk pull — apply each remote row, record sync_log, return summary. */
67
+ applyPull(rows: SyncableRow[], remoteOrigin: string, duration_ms: number, since_cursor?: string): SyncPullResult;
68
+ /** Get the latest cursor from a previous sync. */
69
+ latestCursor(direction: 'push' | 'pull', remoteOrigin: string): string | null;
70
+ /** List unresolved sync conflicts. */
71
+ unresolvedConflicts(limit?: number): Array<{
72
+ id: number;
73
+ local_kind: string;
74
+ local_id: number;
75
+ remote_origin: string | null;
76
+ detected_at: string;
77
+ quest_id: number | null;
78
+ }>;
79
+ /** Resolve a conflict. mode: 'kept_local' | 'kept_remote' | 'merged'. */
80
+ resolveConflict(conflict_id: number, mode: 'kept_local' | 'kept_remote' | 'merged', merged_payload?: Record<string, unknown>): boolean;
81
+ }
82
+ //# sourceMappingURL=federation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAE/E,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAUD,qBAAa,UAAU;IACT,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC,6DAA6D;IAC7D,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO;IAS9C,8DAA8D;IAC9D,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO;IAShD,+DAA+D;IAC/D,cAAc,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,KAAK,SAAM,GAAG,WAAW,EAAE;IA2BjE;;;;;;;;OAQG;IACH,WAAW,CAAC,GAAG,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,UAAU,GAAG,aAAa,GAAG,UAAU,GAAG,SAAS,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;IAsExI,6EAA6E;IAC7E,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,cAAc;IAgBjH,0EAA0E;IAC1E,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,cAAc;IAoBhH,kDAAkD;IAClD,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAS7E,sCAAsC;IACtC,mBAAmB,CAAC,KAAK,SAAK,GAAG,KAAK,CAAC;QACrC,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QACjD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5E,CAAC;IAYF,yEAAyE;IACzE,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,QAAQ,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;CAmCvI"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Federated team Wyrm (Tier 2.8).
3
+ *
4
+ * Each operator runs a local Wyrm; opt-in tagged data syncs to a shared
5
+ * "team Wyrm" instance. PII never leaves the local box unless explicitly
6
+ * shared (per-row `is_shared=1` flag — set when the operator marks a
7
+ * truth/quest/artifact as team-visible).
8
+ *
9
+ * Sync surface:
10
+ * POST /sync/push pushes is_shared=1 local rows changed since cursor
11
+ * GET /sync/pull pulls remote rows changed since cursor
12
+ *
13
+ * Conflict policy: if a remote push lands on top of a local edit whose
14
+ * `updated_at` is newer, we DO NOT silently overwrite. Instead we record
15
+ * the conflict in `sync_conflicts` and mint a quest for the operator to
16
+ * resolve. Last-write-wins is the failure mode of every team sync tool;
17
+ * Wyrm refuses to silently lose work.
18
+ *
19
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
20
+ * @license Proprietary
21
+ */
22
+ const KIND_TO_TABLE = {
23
+ session: { table: 'sessions', ts: 'created_at' },
24
+ quest: { table: 'quests', ts: 'created_at' },
25
+ truth: { table: 'ground_truths', ts: 'updated_at' },
26
+ artifact: { table: 'memory_artifacts', ts: 'last_accessed_at' },
27
+ edge: { table: 'decision_edges', ts: 'created_at' },
28
+ };
29
+ export class Federation {
30
+ db;
31
+ constructor(db) {
32
+ this.db = db;
33
+ }
34
+ /** Mark a row as shared (will be picked up on next push). */
35
+ share(kind, id) {
36
+ const t = KIND_TO_TABLE[kind];
37
+ if (!t)
38
+ return false;
39
+ const info = this.db.prepare(`UPDATE ${t.table} SET is_shared = 1 WHERE id = ?`).run(id);
40
+ return info.changes > 0;
41
+ }
42
+ /** Unshare (revoke from team visibility). Local row stays. */
43
+ unshare(kind, id) {
44
+ const t = KIND_TO_TABLE[kind];
45
+ if (!t)
46
+ return false;
47
+ const info = this.db.prepare(`UPDATE ${t.table} SET is_shared = 0 WHERE id = ?`).run(id);
48
+ return info.changes > 0;
49
+ }
50
+ /** Collect rows to push: is_shared=1 AND ts > since_cursor. */
51
+ collectForPush(since_cursor, limit = 500) {
52
+ const cursor = since_cursor ?? '1970-01-01';
53
+ const out = [];
54
+ for (const [kindStr, t] of Object.entries(KIND_TO_TABLE)) {
55
+ if (out.length >= limit)
56
+ break;
57
+ const kind = kindStr;
58
+ const rows = this.db.prepare(`SELECT * FROM ${t.table}
59
+ WHERE is_shared = 1 AND ${t.ts} > ?
60
+ ORDER BY ${t.ts} ASC
61
+ LIMIT ?`).all(cursor, Math.max(1, limit - out.length));
62
+ for (const r of rows) {
63
+ out.push({
64
+ kind,
65
+ id: r.id,
66
+ project_id: (r.project_id ?? null),
67
+ payload: r,
68
+ updated_at: r[t.ts] || new Date().toISOString(),
69
+ });
70
+ }
71
+ }
72
+ return out;
73
+ }
74
+ /** Apply a remote row locally. Returns:
75
+ * { kind: 'inserted' } — fresh row, no conflict
76
+ * { kind: 'overwritten' } — local row was older, remote wins
77
+ * { kind: 'conflict', conflict_id } — local row newer, conflict recorded
78
+ * Idempotent — applying the same payload twice is a no-op.
79
+ *
80
+ * Conflict policy: if local updated_at > remote updated_at, the remote
81
+ * is REJECTED into sync_conflicts; a quest is minted. The local row stays.
82
+ */
83
+ applyRemote(row, remoteOrigin) {
84
+ const t = KIND_TO_TABLE[row.kind];
85
+ if (!t)
86
+ return { kind: 'skipped' };
87
+ const localTs = this.db.prepare(`SELECT ${t.ts} as ts FROM ${t.table} WHERE id = ?`).get(row.id);
88
+ if (!localTs) {
89
+ // Fresh insert. Build columns from payload, skipping id (auto-incremented)
90
+ // — but we want to preserve the same id so future syncs are stable.
91
+ // SQLite allows explicit id insert.
92
+ const cols = Object.keys(row.payload).filter(c => c !== 'rowid');
93
+ const placeholders = cols.map(() => '?').join(',');
94
+ const values = cols.map(c => row.payload[c]);
95
+ try {
96
+ this.db.prepare(`INSERT OR IGNORE INTO ${t.table} (${cols.join(',')}) VALUES (${placeholders})`).run(...values);
97
+ return { kind: 'inserted' };
98
+ }
99
+ catch {
100
+ return { kind: 'skipped' };
101
+ }
102
+ }
103
+ // Existing row — compare timestamps.
104
+ const localNewer = localTs.ts > row.updated_at;
105
+ if (localNewer) {
106
+ // Conflict — record it and mint a quest if this row has a project_id.
107
+ const conflictInfo = this.db.prepare(`
108
+ INSERT INTO sync_conflicts
109
+ (local_kind, local_id, remote_payload, remote_origin)
110
+ VALUES (?, ?, ?, ?)
111
+ `).run(row.kind, row.id, JSON.stringify(row.payload), remoteOrigin);
112
+ const conflict_id = conflictInfo.lastInsertRowid;
113
+ // Best-effort: mint a quest so the operator sees it on next session
114
+ if (row.project_id != null) {
115
+ try {
116
+ this.db.prepare(`
117
+ INSERT INTO quests (project_id, title, description, priority, status, tags)
118
+ VALUES (?, ?, ?, 'high', 'pending', 'sync-conflict')
119
+ `).run(row.project_id, `[sync] Resolve conflict on ${row.kind} #${row.id}`, `A remote ${remoteOrigin} pushed a ${row.kind} that conflicts with a newer local edit. ` +
120
+ `See sync_conflicts row #${conflict_id} for the remote payload. ` +
121
+ `Resolve with wyrm_sync_resolve (kept_local | kept_remote | merged).`);
122
+ }
123
+ catch { /* best-effort */ }
124
+ }
125
+ return { kind: 'conflict', conflict_id };
126
+ }
127
+ // Local is older — accept remote overwrite.
128
+ const cols = Object.keys(row.payload).filter(c => c !== 'id');
129
+ const setClause = cols.map(c => `${c} = ?`).join(', ');
130
+ const values = cols.map(c => row.payload[c]);
131
+ try {
132
+ this.db.prepare(`UPDATE ${t.table} SET ${setClause} WHERE id = ?`).run(...values, row.id);
133
+ return { kind: 'overwritten' };
134
+ }
135
+ catch {
136
+ return { kind: 'skipped' };
137
+ }
138
+ }
139
+ /** Bulk push — record one sync_log row, return summary. Caller does HTTP. */
140
+ recordPush(rows, remoteOrigin, duration_ms, since_cursor) {
141
+ const newCursor = new Date().toISOString();
142
+ this.db.prepare(`
143
+ INSERT INTO sync_log
144
+ (direction, remote_origin, kind, count, since_cursor, new_cursor, duration_ms)
145
+ VALUES ('push', ?, 'mixed', ?, ?, ?, ?)
146
+ `).run(remoteOrigin, rows.length, since_cursor ?? null, newCursor, duration_ms);
147
+ return {
148
+ pushed: rows.length,
149
+ conflicts: 0,
150
+ new_cursor: newCursor,
151
+ conflict_ids: [],
152
+ };
153
+ }
154
+ /** Bulk pull — apply each remote row, record sync_log, return summary. */
155
+ applyPull(rows, remoteOrigin, duration_ms, since_cursor) {
156
+ const byKind = {};
157
+ let pulled = 0;
158
+ for (const row of rows) {
159
+ const result = this.applyRemote(row, remoteOrigin);
160
+ if (result.kind === 'inserted' || result.kind === 'overwritten') {
161
+ pulled++;
162
+ byKind[row.kind] = (byKind[row.kind] ?? 0) + 1;
163
+ }
164
+ }
165
+ const newCursor = new Date().toISOString();
166
+ this.db.prepare(`
167
+ INSERT INTO sync_log
168
+ (direction, remote_origin, kind, count, since_cursor, new_cursor, duration_ms)
169
+ VALUES ('pull', ?, 'mixed', ?, ?, ?, ?)
170
+ `).run(remoteOrigin, pulled, since_cursor ?? null, newCursor, duration_ms);
171
+ return { pulled, new_cursor: newCursor, by_kind: byKind };
172
+ }
173
+ /** Get the latest cursor from a previous sync. */
174
+ latestCursor(direction, remoteOrigin) {
175
+ const row = this.db.prepare(`
176
+ SELECT new_cursor FROM sync_log
177
+ WHERE direction = ? AND remote_origin = ?
178
+ ORDER BY id DESC LIMIT 1
179
+ `).get(direction, remoteOrigin);
180
+ return row?.new_cursor ?? null;
181
+ }
182
+ /** List unresolved sync conflicts. */
183
+ unresolvedConflicts(limit = 50) {
184
+ return this.db.prepare(`
185
+ SELECT id, local_kind, local_id, remote_origin, detected_at, quest_id
186
+ FROM sync_conflicts
187
+ WHERE resolved_at IS NULL
188
+ ORDER BY detected_at DESC LIMIT ?
189
+ `).all(limit);
190
+ }
191
+ /** Resolve a conflict. mode: 'kept_local' | 'kept_remote' | 'merged'. */
192
+ resolveConflict(conflict_id, mode, merged_payload) {
193
+ const conflict = this.db.prepare('SELECT * FROM sync_conflicts WHERE id = ? AND resolved_at IS NULL').get(conflict_id);
194
+ if (!conflict)
195
+ return false;
196
+ if (mode === 'kept_remote' || mode === 'merged') {
197
+ const t = KIND_TO_TABLE[conflict.local_kind];
198
+ if (t) {
199
+ const payload = mode === 'merged' && merged_payload
200
+ ? merged_payload
201
+ : JSON.parse(conflict.remote_payload);
202
+ const cols = Object.keys(payload).filter(c => c !== 'id');
203
+ const setClause = cols.map(c => `${c} = ?`).join(', ');
204
+ const values = cols.map(c => payload[c]);
205
+ try {
206
+ this.db.prepare(`UPDATE ${t.table} SET ${setClause} WHERE id = ?`).run(...values, conflict.local_id);
207
+ }
208
+ catch { /* best-effort */ }
209
+ }
210
+ }
211
+ // 'kept_local' = no row mutation needed.
212
+ this.db.prepare(`
213
+ UPDATE sync_conflicts
214
+ SET resolved_at = datetime('now'), resolution = ?
215
+ WHERE id = ?
216
+ `).run(mode, conflict_id);
217
+ return true;
218
+ }
219
+ }
220
+ //# sourceMappingURL=federation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"federation.js","sourceRoot":"","sources":["../src/federation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Hour Ledger + Invoice Generator.
3
+ *
4
+ * Sessions already record start/end timestamps. This module derives:
5
+ * - hours per project / per client / per date range
6
+ * - markdown invoices with per-session line items
7
+ *
8
+ * Solo founders / freelancers use Wyrm for memory; this turns it into
9
+ * their time tracker too — no separate Toggl/Harvest needed.
10
+ *
11
+ * Session duration falls back to a configurable default (e.g. 60 min)
12
+ * when the session has no explicit end_time — common for "I started
13
+ * a session and forgot to close it". The fallback is exposed in the
14
+ * report so the operator can review and correct.
15
+ *
16
+ * @copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
17
+ * @license Proprietary
18
+ */
19
+ import type Database from 'better-sqlite3';
20
+ export interface SessionRow {
21
+ id: number;
22
+ project_id: number;
23
+ date: string;
24
+ objectives: string | null;
25
+ completed: string | null;
26
+ notes: string | null;
27
+ summary: string | null;
28
+ created_at: string;
29
+ }
30
+ export interface HourReportEntry {
31
+ session_id: number;
32
+ project_id: number;
33
+ project_name: string;
34
+ date: string;
35
+ hours: number;
36
+ is_estimated: boolean;
37
+ summary: string | null;
38
+ }
39
+ export interface HourReport {
40
+ range: {
41
+ start: string;
42
+ end: string;
43
+ };
44
+ total_hours: number;
45
+ estimated_sessions: number;
46
+ by_project: Array<{
47
+ project_id: number;
48
+ project_name: string;
49
+ hours: number;
50
+ session_count: number;
51
+ }>;
52
+ entries: HourReportEntry[];
53
+ }
54
+ export interface InvoiceInput {
55
+ client_name: string;
56
+ client_address?: string;
57
+ business_name?: string;
58
+ business_address?: string;
59
+ business_contact?: string;
60
+ invoice_number?: string;
61
+ hourly_rate_usd: number;
62
+ range_start: string;
63
+ range_end: string;
64
+ project_id?: number;
65
+ default_session_hours?: number;
66
+ currency?: string;
67
+ notes?: string;
68
+ }
69
+ export declare class HourLedger {
70
+ private db;
71
+ constructor(db: Database.Database);
72
+ /** Hours report for a date range, optionally scoped to a project. */
73
+ report(opts: {
74
+ range_start: string;
75
+ range_end: string;
76
+ project_id?: number;
77
+ default_session_hours?: number;
78
+ }): HourReport;
79
+ /** Generate a markdown invoice from an hour report. */
80
+ invoice(input: InvoiceInput): string;
81
+ }
82
+ //# sourceMappingURL=hours.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hours.d.ts","sourceRoot":"","sources":["../src/hours.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,KAAK,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtG,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,UAAU;IACT,OAAO,CAAC,EAAE;gBAAF,EAAE,EAAE,QAAQ,CAAC,QAAQ;IAEzC,qEAAqE;IACrE,MAAM,CAAC,IAAI,EAAE;QACX,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,qBAAqB,CAAC,EAAE,MAAM,CAAC;KAChC,GAAG,UAAU;IA8Ed,uDAAuD;IACvD,OAAO,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM;CAsErC"}