@drewpayment/mink 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +347 -0
  2. package/package.json +32 -0
  3. package/src/cli.ts +176 -0
  4. package/src/commands/bug-search.ts +32 -0
  5. package/src/commands/config.ts +109 -0
  6. package/src/commands/cron.ts +295 -0
  7. package/src/commands/daemon.ts +46 -0
  8. package/src/commands/dashboard.ts +21 -0
  9. package/src/commands/designqc.ts +160 -0
  10. package/src/commands/detect-waste.ts +81 -0
  11. package/src/commands/framework-advisor.ts +52 -0
  12. package/src/commands/init.ts +159 -0
  13. package/src/commands/post-read.ts +123 -0
  14. package/src/commands/post-write.ts +157 -0
  15. package/src/commands/pre-read.ts +109 -0
  16. package/src/commands/pre-write.ts +136 -0
  17. package/src/commands/reflect.ts +39 -0
  18. package/src/commands/restore.ts +31 -0
  19. package/src/commands/scan.ts +101 -0
  20. package/src/commands/session-start.ts +21 -0
  21. package/src/commands/session-stop.ts +115 -0
  22. package/src/commands/status.ts +152 -0
  23. package/src/commands/update.ts +121 -0
  24. package/src/core/action-log.ts +341 -0
  25. package/src/core/backup.ts +122 -0
  26. package/src/core/bug-memory.ts +223 -0
  27. package/src/core/cron-parser.ts +94 -0
  28. package/src/core/daemon.ts +152 -0
  29. package/src/core/dashboard-api.ts +280 -0
  30. package/src/core/dashboard-server.ts +580 -0
  31. package/src/core/description.ts +232 -0
  32. package/src/core/design-eval/capture.ts +269 -0
  33. package/src/core/design-eval/route-detect.ts +165 -0
  34. package/src/core/design-eval/server-detect.ts +91 -0
  35. package/src/core/framework-advisor/catalog.ts +360 -0
  36. package/src/core/framework-advisor/decision-tree.ts +287 -0
  37. package/src/core/framework-advisor/generate.ts +132 -0
  38. package/src/core/framework-advisor/migration-prompts.ts +502 -0
  39. package/src/core/framework-advisor/validate.ts +137 -0
  40. package/src/core/fs-utils.ts +30 -0
  41. package/src/core/global-config.ts +74 -0
  42. package/src/core/index-store.ts +72 -0
  43. package/src/core/learning-memory.ts +120 -0
  44. package/src/core/paths.ts +86 -0
  45. package/src/core/pattern-engine.ts +108 -0
  46. package/src/core/project-id.ts +19 -0
  47. package/src/core/project-registry.ts +64 -0
  48. package/src/core/reflection.ts +256 -0
  49. package/src/core/scanner.ts +99 -0
  50. package/src/core/scheduler.ts +352 -0
  51. package/src/core/seed.ts +239 -0
  52. package/src/core/session.ts +128 -0
  53. package/src/core/stdin.ts +13 -0
  54. package/src/core/task-registry.ts +202 -0
  55. package/src/core/token-estimate.ts +36 -0
  56. package/src/core/token-ledger.ts +185 -0
  57. package/src/core/waste-detection.ts +214 -0
  58. package/src/core/write-exclusions.ts +24 -0
  59. package/src/types/action-log.ts +20 -0
  60. package/src/types/backup.ts +6 -0
  61. package/src/types/bug-memory.ts +24 -0
  62. package/src/types/config.ts +59 -0
  63. package/src/types/dashboard.ts +104 -0
  64. package/src/types/design-eval.ts +64 -0
  65. package/src/types/file-index.ts +38 -0
  66. package/src/types/framework-advisor.ts +97 -0
  67. package/src/types/hook-input.ts +27 -0
  68. package/src/types/learning-memory.ts +36 -0
  69. package/src/types/scheduler.ts +82 -0
  70. package/src/types/session.ts +50 -0
  71. package/src/types/token-ledger.ts +43 -0
  72. package/src/types/waste-detection.ts +21 -0
@@ -0,0 +1,185 @@
1
+ import { join } from "path";
2
+ import type { TokenLedger, LedgerSession, LifetimeCounters } from "../types/token-ledger";
3
+ import type { SessionFinalizer, SessionSummary } from "../types/session";
4
+ import { atomicWriteJson, safeReadJson } from "./fs-utils";
5
+
6
+ // ── Helpers ──────────────────────────────────────────────────────────────────
7
+
8
+ function addToLifetime(lifetime: LifetimeCounters, session: LedgerSession): void {
9
+ lifetime.totalTokens += session.totals.estimatedTokens;
10
+ lifetime.totalReads += session.totals.readCount;
11
+ lifetime.totalWrites += session.totals.writeCount;
12
+ lifetime.totalSessions += 1;
13
+ lifetime.totalFileIndexHits += session.totals.fileIndexHits;
14
+ lifetime.totalFileIndexMisses += session.totals.fileIndexMisses;
15
+ lifetime.totalRepeatedReads += session.totals.repeatedReads;
16
+ lifetime.totalEstimatedSavings += session.estimatedSavings;
17
+ }
18
+
19
+ function subtractFromLifetime(lifetime: LifetimeCounters, session: LedgerSession): void {
20
+ lifetime.totalTokens -= session.totals.estimatedTokens;
21
+ lifetime.totalReads -= session.totals.readCount;
22
+ lifetime.totalWrites -= session.totals.writeCount;
23
+ lifetime.totalSessions -= 1;
24
+ lifetime.totalFileIndexHits -= session.totals.fileIndexHits;
25
+ lifetime.totalFileIndexMisses -= session.totals.fileIndexMisses;
26
+ lifetime.totalRepeatedReads -= session.totals.repeatedReads;
27
+ lifetime.totalEstimatedSavings -= session.estimatedSavings;
28
+ }
29
+
30
+ // ── Core functions ────────────────────────────────────────────────────────────
31
+
32
+ export function createEmptyLedger(): TokenLedger {
33
+ return {
34
+ lifetime: {
35
+ totalTokens: 0,
36
+ totalReads: 0,
37
+ totalWrites: 0,
38
+ totalSessions: 0,
39
+ totalFileIndexHits: 0,
40
+ totalFileIndexMisses: 0,
41
+ totalRepeatedReads: 0,
42
+ totalEstimatedSavings: 0,
43
+ },
44
+ sessions: [],
45
+ };
46
+ }
47
+
48
+ export function isTokenLedger(value: unknown): value is TokenLedger {
49
+ if (value === null || typeof value !== "object") return false;
50
+ const obj = value as Record<string, unknown>;
51
+ return (
52
+ typeof obj.lifetime === "object" &&
53
+ obj.lifetime !== null &&
54
+ Array.isArray(obj.sessions)
55
+ );
56
+ }
57
+
58
+ export function loadLedger(ledgerPath: string): TokenLedger {
59
+ const raw = safeReadJson(ledgerPath);
60
+ if (raw === null) {
61
+ return createEmptyLedger();
62
+ }
63
+ if (!isTokenLedger(raw)) {
64
+ console.warn(`[mink] Warning: corrupt token ledger at ${ledgerPath}, starting fresh`);
65
+ return createEmptyLedger();
66
+ }
67
+ return raw;
68
+ }
69
+
70
+ export function saveLedger(ledgerPath: string, ledger: TokenLedger): void {
71
+ atomicWriteJson(ledgerPath, ledger);
72
+ }
73
+
74
+ // ── Task 3: Append Session ────────────────────────────────────────────────────
75
+
76
+ export function summaryToLedgerSession(summary: SessionSummary): LedgerSession {
77
+ return {
78
+ sessionId: summary.sessionId,
79
+ startTimestamp: summary.startTimestamp,
80
+ endTimestamp: summary.endTimestamp,
81
+ reads: summary.reads.map((r) => ({
82
+ filePath: r.filePath,
83
+ estimatedTokens: r.estimatedTokens,
84
+ readCount: r.readCount,
85
+ })),
86
+ writes: summary.writes.map((w) => ({
87
+ filePath: w.filePath,
88
+ estimatedTokens: w.estimatedTokens,
89
+ action: w.action,
90
+ })),
91
+ totals: {
92
+ readCount: summary.totals.readCount,
93
+ writeCount: summary.totals.writeCount,
94
+ estimatedTokens: summary.totals.estimatedTokens,
95
+ repeatedReads: summary.totals.repeatedReads,
96
+ fileIndexHits: summary.totals.fileIndexHits,
97
+ fileIndexMisses: summary.totals.fileIndexMisses,
98
+ },
99
+ estimatedSavings: summary.estimatedSavings,
100
+ };
101
+ }
102
+
103
+ export function appendSession(ledger: TokenLedger, summary: SessionSummary): void {
104
+ const session = summaryToLedgerSession(summary);
105
+ ledger.sessions.push(session);
106
+ addToLifetime(ledger.lifetime, session);
107
+ }
108
+
109
+ // ── Task 4: Update Session ────────────────────────────────────────────────────
110
+
111
+ export function updateSession(ledger: TokenLedger, summary: SessionSummary): void {
112
+ const idx = ledger.sessions.findIndex((s) => s.sessionId === summary.sessionId);
113
+ if (idx === -1) {
114
+ appendSession(ledger, summary);
115
+ return;
116
+ }
117
+ const oldSession = ledger.sessions[idx];
118
+ subtractFromLifetime(ledger.lifetime, oldSession);
119
+ const newSession = summaryToLedgerSession(summary);
120
+ addToLifetime(ledger.lifetime, newSession);
121
+ ledger.sessions[idx] = newSession;
122
+ }
123
+
124
+ // ── Task 5: Archive ───────────────────────────────────────────────────────────
125
+
126
+ export function archiveIfNeeded(
127
+ ledger: TokenLedger,
128
+ threshold: number = 1000
129
+ ): { archived: LedgerSession[] } {
130
+ if (threshold <= 0) {
131
+ return { archived: [] };
132
+ }
133
+ if (ledger.sessions.length <= threshold) {
134
+ return { archived: [] };
135
+ }
136
+ const excess = ledger.sessions.length - threshold;
137
+ const archived = ledger.sessions.splice(0, excess);
138
+ return { archived };
139
+ }
140
+
141
+ export function loadArchive(archivePath: string): LedgerSession[] {
142
+ const raw = safeReadJson(archivePath);
143
+ if (raw === null) {
144
+ return [];
145
+ }
146
+ if (!Array.isArray(raw)) {
147
+ console.warn(`[mink] Warning: corrupt token ledger archive at ${archivePath}, ignoring`);
148
+ return [];
149
+ }
150
+ return raw as LedgerSession[];
151
+ }
152
+
153
+ export function saveArchive(archivePath: string, newlyArchived: LedgerSession[]): void {
154
+ const existing = loadArchive(archivePath);
155
+ const combined = [...newlyArchived, ...existing];
156
+ atomicWriteJson(archivePath, combined);
157
+ }
158
+
159
+ // ── Task 6: Ledger Finalizer Factory ─────────────────────────────────────────
160
+
161
+ export function createLedgerFinalizer(
162
+ projectDir: string,
163
+ archiveThreshold: number = 1000
164
+ ): SessionFinalizer {
165
+ const ledgerPath = join(projectDir, "token-ledger.json");
166
+ const archivePath = join(projectDir, "token-ledger-archive.json");
167
+
168
+ return {
169
+ appendSession(summary: SessionSummary): void {
170
+ const ledger = loadLedger(ledgerPath);
171
+ appendSession(ledger, summary);
172
+ const { archived } = archiveIfNeeded(ledger, archiveThreshold);
173
+ if (archived.length > 0) {
174
+ saveArchive(archivePath, archived);
175
+ }
176
+ saveLedger(ledgerPath, ledger);
177
+ },
178
+
179
+ updateSession(summary: SessionSummary): void {
180
+ const ledger = loadLedger(ledgerPath);
181
+ updateSession(ledger, summary);
182
+ saveLedger(ledgerPath, ledger);
183
+ },
184
+ };
185
+ }
@@ -0,0 +1,214 @@
1
+ import type { WasteFlag, DetectionConfig } from "../types/waste-detection";
2
+ import type { LedgerSession, TokenLedger } from "../types/token-ledger";
3
+ import type { FileIndexEntry, FileIndexHeader } from "../types/file-index";
4
+ import { estimateTokens } from "./token-estimate";
5
+
6
+ // ── Default Config ──────────────────────────────────────────────────────────
7
+
8
+ export function defaultDetectionConfig(): DetectionConfig {
9
+ return {
10
+ actionLogBloatThreshold: 5000,
11
+ learningMemoryStaleDays: 14,
12
+ indexMissRateThreshold: 0.20,
13
+ missedIndexMinTokens: 500,
14
+ };
15
+ }
16
+
17
+ // ── Detector 1: Repeated Reads ──────────────────────────────────────────────
18
+
19
+ export function detectRepeatedReads(
20
+ sessions: LedgerSession[],
21
+ now: string
22
+ ): WasteFlag[] {
23
+ const flags: WasteFlag[] = [];
24
+
25
+ for (const session of sessions) {
26
+ for (const read of session.reads) {
27
+ if (read.readCount > 1) {
28
+ const wasted = (read.readCount - 1) * read.estimatedTokens;
29
+ flags.push({
30
+ pattern: "repeated-reads",
31
+ description: `File "${read.filePath}" was read ${read.readCount} times in session ${session.sessionId}`,
32
+ estimatedTokensWasted: wasted,
33
+ suggestion:
34
+ "Use the file index description instead of re-reading, or cache the content within the session.",
35
+ detectedAt: now,
36
+ });
37
+ }
38
+ }
39
+ }
40
+
41
+ return flags;
42
+ }
43
+
44
+ // ── Detector 2: Missed Index Opportunities ──────────────────────────────────
45
+
46
+ export function detectMissedIndexOpportunities(
47
+ sessions: LedgerSession[],
48
+ indexEntries: Record<string, FileIndexEntry>,
49
+ config: DetectionConfig,
50
+ now: string
51
+ ): WasteFlag[] {
52
+ const aggregated = new Map<string, number>();
53
+
54
+ for (const session of sessions) {
55
+ for (const read of session.reads) {
56
+ if (read.estimatedTokens > config.missedIndexMinTokens) {
57
+ const entry = indexEntries[read.filePath];
58
+ if (entry && entry.description) {
59
+ const prev = aggregated.get(read.filePath) ?? 0;
60
+ aggregated.set(read.filePath, prev + read.estimatedTokens);
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ const flags: WasteFlag[] = [];
67
+ for (const [filePath, totalTokens] of aggregated) {
68
+ const entry = indexEntries[filePath];
69
+ flags.push({
70
+ pattern: "missed-index-opportunity",
71
+ description: `Read of "${filePath}" (~${totalTokens} tokens) could have used index description instead`,
72
+ estimatedTokensWasted: totalTokens,
73
+ suggestion: `Index description available: "${entry.description}". Consider using the index instead of full file reads.`,
74
+ detectedAt: now,
75
+ });
76
+ }
77
+
78
+ return flags;
79
+ }
80
+
81
+ // ── Detector 3: Action Log Bloat ────────────────────────────────────────────
82
+
83
+ export function detectActionLogBloat(
84
+ actionLogContent: string,
85
+ config: DetectionConfig,
86
+ now: string
87
+ ): WasteFlag | null {
88
+ if (!actionLogContent) return null;
89
+
90
+ const tokenCount = estimateTokens(actionLogContent, "action-log.md");
91
+ if (tokenCount > config.actionLogBloatThreshold) {
92
+ return {
93
+ pattern: "action-log-bloat",
94
+ description: `Action log is ~${tokenCount} tokens, exceeding the ${config.actionLogBloatThreshold} token threshold`,
95
+ estimatedTokensWasted: tokenCount - config.actionLogBloatThreshold,
96
+ suggestion: "Run action log consolidation to reduce size.",
97
+ detectedAt: now,
98
+ };
99
+ }
100
+
101
+ return null;
102
+ }
103
+
104
+ // ── Detector 4: Learning Memory Staleness ───────────────────────────────────
105
+
106
+ export function detectLearningMemoryStaleness(
107
+ lastModifiedMs: number | null,
108
+ config: DetectionConfig,
109
+ now: string
110
+ ): WasteFlag | null {
111
+ if (lastModifiedMs === null) {
112
+ return {
113
+ pattern: "learning-memory-staleness",
114
+ description: "Learning memory file is missing",
115
+ estimatedTokensWasted: 0,
116
+ suggestion:
117
+ "Create a learning memory file by running mink init, or manually create learning-memory.md.",
118
+ detectedAt: now,
119
+ };
120
+ }
121
+
122
+ const nowMs = Date.parse(now);
123
+ const ageMs = nowMs - lastModifiedMs;
124
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
125
+
126
+ if (ageDays > config.learningMemoryStaleDays) {
127
+ const lastUpdate = new Date(lastModifiedMs).toISOString().slice(0, 10);
128
+ return {
129
+ pattern: "learning-memory-staleness",
130
+ description: `Learning memory hasn't been updated in ${Math.floor(ageDays)} days (threshold: ${config.learningMemoryStaleDays} days)`,
131
+ estimatedTokensWasted: 0,
132
+ suggestion: "Review and update the learning memory to keep it current.",
133
+ detectedAt: now,
134
+ };
135
+ }
136
+
137
+ return null;
138
+ }
139
+
140
+ // ── Detector 5: Index Miss Rate ─────────────────────────────────────────────
141
+
142
+ export function detectIndexMissRate(
143
+ lifetimeHits: number,
144
+ lifetimeMisses: number,
145
+ config: DetectionConfig,
146
+ now: string
147
+ ): WasteFlag | null {
148
+ const totalLookups = lifetimeHits + lifetimeMisses;
149
+ if (totalLookups === 0) return null;
150
+
151
+ const missRate = lifetimeMisses / totalLookups;
152
+ if (missRate > config.indexMissRateThreshold) {
153
+ const pct = Math.round(missRate * 100);
154
+ return {
155
+ pattern: "index-miss-rate",
156
+ description: `File index miss rate is ${pct}% (${lifetimeMisses} misses out of ${totalLookups} lookups)`,
157
+ estimatedTokensWasted: 0,
158
+ suggestion: "Run a full rescan with mink scan to update the file index.",
159
+ detectedAt: now,
160
+ };
161
+ }
162
+
163
+ return null;
164
+ }
165
+
166
+ // ── Orchestrator ────────────────────────────────────────────────────────────
167
+
168
+ export function runDetection(
169
+ ledger: TokenLedger,
170
+ indexEntries: Record<string, FileIndexEntry>,
171
+ indexHeader: FileIndexHeader,
172
+ actionLogContent: string,
173
+ learningMemoryMtimeMs: number | null,
174
+ config?: Partial<DetectionConfig>,
175
+ now?: Date
176
+ ): WasteFlag[] {
177
+ const fullConfig: DetectionConfig = {
178
+ ...defaultDetectionConfig(),
179
+ ...config,
180
+ };
181
+ const nowStr = (now ?? new Date()).toISOString();
182
+
183
+ const flags: WasteFlag[] = [];
184
+
185
+ flags.push(...detectRepeatedReads(ledger.sessions, nowStr));
186
+ flags.push(
187
+ ...detectMissedIndexOpportunities(
188
+ ledger.sessions,
189
+ indexEntries,
190
+ fullConfig,
191
+ nowStr
192
+ )
193
+ );
194
+
195
+ const bloatFlag = detectActionLogBloat(actionLogContent, fullConfig, nowStr);
196
+ if (bloatFlag) flags.push(bloatFlag);
197
+
198
+ const stalenessFlag = detectLearningMemoryStaleness(
199
+ learningMemoryMtimeMs,
200
+ fullConfig,
201
+ nowStr
202
+ );
203
+ if (stalenessFlag) flags.push(stalenessFlag);
204
+
205
+ const missRateFlag = detectIndexMissRate(
206
+ indexHeader.lifetimeHits,
207
+ indexHeader.lifetimeMisses,
208
+ fullConfig,
209
+ nowStr
210
+ );
211
+ if (missRateFlag) flags.push(missRateFlag);
212
+
213
+ return flags;
214
+ }
@@ -0,0 +1,24 @@
1
+ import { basename } from "path";
2
+
3
+ /**
4
+ * Returns true if the file should be excluded from write tracking.
5
+ * Excluded: .env* files, files inside the .mink state directory.
6
+ */
7
+ export function isWriteExcluded(relativePath: string): boolean {
8
+ // Skip .mink state directory files
9
+ if (
10
+ relativePath === ".mink" ||
11
+ relativePath.startsWith(".mink/") ||
12
+ relativePath.startsWith(".mink\\")
13
+ ) {
14
+ return true;
15
+ }
16
+
17
+ // Skip .env files
18
+ const name = basename(relativePath);
19
+ if (name === ".env" || name.startsWith(".env.")) {
20
+ return true;
21
+ }
22
+
23
+ return false;
24
+ }
@@ -0,0 +1,20 @@
1
+ export interface ActionLogEntry {
2
+ time: string; // HH:MM (UTC)
3
+ action: "Session start" | "Read" | "Create" | "Edit" | "Session end";
4
+ files: string; // Truncated file path or "—"
5
+ outcome: string; // index hit/miss, description, or summary
6
+ tokens: string; // "~NNN" or "—"
7
+ }
8
+
9
+ export interface ConsolidationConfig {
10
+ maxEntries: number; // default 200
11
+ retentionDays: number; // default 7
12
+ }
13
+
14
+ export interface ParsedSession {
15
+ startIndex: number; // char offset of session header
16
+ endIndex: number; // char offset of end of session (before next header or EOF)
17
+ date: string; // parsed date from session header (YYYY-MM-DD)
18
+ entryCount: number; // number of table data rows
19
+ content: string; // full text of this session block
20
+ }
@@ -0,0 +1,6 @@
1
+ export interface BackupInfo {
2
+ name: string;
3
+ timestamp: Date;
4
+ path: string;
5
+ fileCount: number;
6
+ }
@@ -0,0 +1,24 @@
1
+ export interface BugEntry {
2
+ id: string;
3
+ createdAt: string;
4
+ lastSeenAt: string;
5
+ errorMessage: string;
6
+ filePath: string;
7
+ lineNumber?: number;
8
+ rootCause: string;
9
+ fixDescription: string;
10
+ tags: string[];
11
+ occurrenceCount: number;
12
+ relatedBugIds: string[];
13
+ }
14
+
15
+ export interface BugMemory {
16
+ entries: BugEntry[];
17
+ nextId: number;
18
+ }
19
+
20
+ export interface SimilarityMatch {
21
+ entry: BugEntry;
22
+ score: number;
23
+ matchReasons: string[];
24
+ }
@@ -0,0 +1,59 @@
1
+ export interface GlobalConfig {
2
+ "wiki.path"?: string;
3
+ "wiki.enabled"?: string;
4
+ "wiki.sync-mode"?: string;
5
+ "wiki.git-backup"?: string;
6
+ "wiki.git-remote"?: string;
7
+ }
8
+
9
+ export type ConfigKey = keyof GlobalConfig & string;
10
+
11
+ export interface ConfigKeyMeta {
12
+ key: ConfigKey;
13
+ default: string;
14
+ envVar: string;
15
+ description: string;
16
+ }
17
+
18
+ export const CONFIG_KEYS: ConfigKeyMeta[] = [
19
+ {
20
+ key: "wiki.path",
21
+ default: "~/.mink/wiki/",
22
+ envVar: "MINK_WIKI_PATH",
23
+ description: "Wiki vault location",
24
+ },
25
+ {
26
+ key: "wiki.enabled",
27
+ default: "true",
28
+ envVar: "MINK_WIKI_ENABLED",
29
+ description: "Enable/disable the wiki feature",
30
+ },
31
+ {
32
+ key: "wiki.sync-mode",
33
+ default: "immediate",
34
+ envVar: "MINK_WIKI_SYNC_MODE",
35
+ description: "Sync mode: immediate or batched",
36
+ },
37
+ {
38
+ key: "wiki.git-backup",
39
+ default: "false",
40
+ envVar: "MINK_WIKI_GIT_BACKUP",
41
+ description: "Enable/disable auto-commit and push",
42
+ },
43
+ {
44
+ key: "wiki.git-remote",
45
+ default: "origin",
46
+ envVar: "MINK_WIKI_GIT_REMOTE",
47
+ description: "Git remote name for push",
48
+ },
49
+ ];
50
+
51
+ const VALID_KEYS = new Set<string>(CONFIG_KEYS.map((k) => k.key));
52
+
53
+ export function isValidConfigKey(key: string): key is ConfigKey {
54
+ return VALID_KEYS.has(key);
55
+ }
56
+
57
+ export function getConfigKeyMeta(key: ConfigKey): ConfigKeyMeta {
58
+ return CONFIG_KEYS.find((k) => k.key === key)!;
59
+ }
@@ -0,0 +1,104 @@
1
+ import type { LifetimeCounters, LedgerSession } from "./token-ledger";
2
+ import type { WasteFlag } from "./waste-detection";
3
+ import type { FileIndexHeader, FileIndexEntry } from "./file-index";
4
+ import type { BugEntry } from "./bug-memory";
5
+ import type { LearningMemory } from "./learning-memory";
6
+ import type { ParsedSession } from "./action-log";
7
+ import type { TaskDefinition, TaskRunRecord, DeadLetterEntry } from "./scheduler";
8
+
9
+ // ── State File Identifiers ─────────────────────────────────────────────────
10
+
11
+ export type StateFileId =
12
+ | "token-ledger"
13
+ | "file-index"
14
+ | "learning-memory"
15
+ | "bug-memory"
16
+ | "action-log"
17
+ | "scheduler-manifest"
18
+ | "session"
19
+ | "project-meta"
20
+ | "design-report"
21
+ | "project-switched";
22
+
23
+ // ── SSE Event ──────────────────────────────────────────────────────────────
24
+
25
+ export interface StateChangeEvent {
26
+ fileId: StateFileId;
27
+ projectId?: string;
28
+ timestamp: string;
29
+ }
30
+
31
+ // ── File Status ────────────────────────────────────────────────────────────
32
+
33
+ export interface FileStatus {
34
+ name: string;
35
+ status: "ok" | "missing" | "corrupt";
36
+ }
37
+
38
+ // ── API Payloads ───────────────────────────────────────────────────────────
39
+
40
+ export interface OverviewPayload {
41
+ project: { name: string; description: string; cwd: string } | null;
42
+ daemon: {
43
+ running: boolean;
44
+ pid?: number;
45
+ startedAt?: string;
46
+ uptimeMs?: number;
47
+ };
48
+ summary: {
49
+ totalSessions: number;
50
+ totalTokens: number;
51
+ totalReads: number;
52
+ totalWrites: number;
53
+ estimatedSavings: number;
54
+ };
55
+ stateFiles: FileStatus[];
56
+ }
57
+
58
+ export interface TokenLedgerPayload {
59
+ lifetime: LifetimeCounters;
60
+ sessions: LedgerSession[];
61
+ wasteFlags: WasteFlag[];
62
+ }
63
+
64
+ export interface FileIndexPayload {
65
+ header: FileIndexHeader;
66
+ entries: FileIndexEntry[];
67
+ }
68
+
69
+ export interface SchedulerTaskPayload {
70
+ definition: TaskDefinition;
71
+ state: TaskRunRecord | null;
72
+ }
73
+
74
+ export interface SchedulerPayload {
75
+ tasks: SchedulerTaskPayload[];
76
+ deadLetterQueue: DeadLetterEntry[];
77
+ lastHeartbeat: string | null;
78
+ }
79
+
80
+ export interface BugLogPayload {
81
+ entries: BugEntry[];
82
+ nextId: number;
83
+ }
84
+
85
+ export interface ActionLogPayload {
86
+ sessions: ParsedSession[];
87
+ }
88
+
89
+ export interface ActionResult {
90
+ success: boolean;
91
+ error?: string;
92
+ }
93
+
94
+ export interface DesignImagePayload {
95
+ url: string;
96
+ route: string;
97
+ viewport: string;
98
+ section: number;
99
+ timestamp: string;
100
+ }
101
+
102
+ export interface DesignPayload {
103
+ images: DesignImagePayload[];
104
+ }