@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.
- package/README.md +347 -0
- package/package.json +32 -0
- package/src/cli.ts +176 -0
- package/src/commands/bug-search.ts +32 -0
- package/src/commands/config.ts +109 -0
- package/src/commands/cron.ts +295 -0
- package/src/commands/daemon.ts +46 -0
- package/src/commands/dashboard.ts +21 -0
- package/src/commands/designqc.ts +160 -0
- package/src/commands/detect-waste.ts +81 -0
- package/src/commands/framework-advisor.ts +52 -0
- package/src/commands/init.ts +159 -0
- package/src/commands/post-read.ts +123 -0
- package/src/commands/post-write.ts +157 -0
- package/src/commands/pre-read.ts +109 -0
- package/src/commands/pre-write.ts +136 -0
- package/src/commands/reflect.ts +39 -0
- package/src/commands/restore.ts +31 -0
- package/src/commands/scan.ts +101 -0
- package/src/commands/session-start.ts +21 -0
- package/src/commands/session-stop.ts +115 -0
- package/src/commands/status.ts +152 -0
- package/src/commands/update.ts +121 -0
- package/src/core/action-log.ts +341 -0
- package/src/core/backup.ts +122 -0
- package/src/core/bug-memory.ts +223 -0
- package/src/core/cron-parser.ts +94 -0
- package/src/core/daemon.ts +152 -0
- package/src/core/dashboard-api.ts +280 -0
- package/src/core/dashboard-server.ts +580 -0
- package/src/core/description.ts +232 -0
- package/src/core/design-eval/capture.ts +269 -0
- package/src/core/design-eval/route-detect.ts +165 -0
- package/src/core/design-eval/server-detect.ts +91 -0
- package/src/core/framework-advisor/catalog.ts +360 -0
- package/src/core/framework-advisor/decision-tree.ts +287 -0
- package/src/core/framework-advisor/generate.ts +132 -0
- package/src/core/framework-advisor/migration-prompts.ts +502 -0
- package/src/core/framework-advisor/validate.ts +137 -0
- package/src/core/fs-utils.ts +30 -0
- package/src/core/global-config.ts +74 -0
- package/src/core/index-store.ts +72 -0
- package/src/core/learning-memory.ts +120 -0
- package/src/core/paths.ts +86 -0
- package/src/core/pattern-engine.ts +108 -0
- package/src/core/project-id.ts +19 -0
- package/src/core/project-registry.ts +64 -0
- package/src/core/reflection.ts +256 -0
- package/src/core/scanner.ts +99 -0
- package/src/core/scheduler.ts +352 -0
- package/src/core/seed.ts +239 -0
- package/src/core/session.ts +128 -0
- package/src/core/stdin.ts +13 -0
- package/src/core/task-registry.ts +202 -0
- package/src/core/token-estimate.ts +36 -0
- package/src/core/token-ledger.ts +185 -0
- package/src/core/waste-detection.ts +214 -0
- package/src/core/write-exclusions.ts +24 -0
- package/src/types/action-log.ts +20 -0
- package/src/types/backup.ts +6 -0
- package/src/types/bug-memory.ts +24 -0
- package/src/types/config.ts +59 -0
- package/src/types/dashboard.ts +104 -0
- package/src/types/design-eval.ts +64 -0
- package/src/types/file-index.ts +38 -0
- package/src/types/framework-advisor.ts +97 -0
- package/src/types/hook-input.ts +27 -0
- package/src/types/learning-memory.ts +36 -0
- package/src/types/scheduler.ts +82 -0
- package/src/types/session.ts +50 -0
- package/src/types/token-ledger.ts +43 -0
- package/src/types/waste-detection.ts +21 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { parseLearningMemory, serializeLearningMemory } from "../core/learning-memory";
|
|
3
|
+
import { reflectMemory } from "../core/reflection";
|
|
4
|
+
import { atomicWriteText, safeReadJson } from "../core/fs-utils";
|
|
5
|
+
import type { ReflectionResult } from "../types/learning-memory";
|
|
6
|
+
import type { ProjectConfig } from "../types/file-index";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_TOKEN_BUDGET = 2000;
|
|
9
|
+
|
|
10
|
+
export function reflect(
|
|
11
|
+
projectDir: string,
|
|
12
|
+
memoryPath: string,
|
|
13
|
+
configPath: string
|
|
14
|
+
): ReflectionResult | null {
|
|
15
|
+
if (!existsSync(memoryPath)) {
|
|
16
|
+
console.log("[mink] no learning memory found");
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const markdown = readFileSync(memoryPath, "utf-8");
|
|
21
|
+
const mem = parseLearningMemory(markdown);
|
|
22
|
+
|
|
23
|
+
const config = safeReadJson(configPath) as ProjectConfig | null;
|
|
24
|
+
const tokenBudget = config?.learningMemoryTokenBudget ?? DEFAULT_TOKEN_BUDGET;
|
|
25
|
+
|
|
26
|
+
const { memory: updated, result } = reflectMemory(mem, tokenBudget);
|
|
27
|
+
|
|
28
|
+
if (result.mergedCount > 0 || result.trimmedCount > 0) {
|
|
29
|
+
atomicWriteText(memoryPath, serializeLearningMemory(updated));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
console.log(
|
|
33
|
+
`[mink] reflect: ${result.beforeTokens} → ${result.afterTokens} tokens` +
|
|
34
|
+
` | merged: ${result.mergedCount} | trimmed: ${result.trimmedCount}` +
|
|
35
|
+
` | within budget: ${result.withinBudget}`
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { listBackups, restoreBackup } from "../core/backup";
|
|
2
|
+
|
|
3
|
+
export function restore(cwd: string, args: string[]): void {
|
|
4
|
+
const backupName = args[0];
|
|
5
|
+
|
|
6
|
+
if (!backupName) {
|
|
7
|
+
// List available backups
|
|
8
|
+
const backups = listBackups(cwd);
|
|
9
|
+
if (backups.length === 0) {
|
|
10
|
+
console.log("[mink] no backups available");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log("[mink] available backups:");
|
|
15
|
+
for (const b of backups) {
|
|
16
|
+
console.log(
|
|
17
|
+
` ${b.name} (${b.timestamp.toISOString().replace("T", " ").slice(0, 19)}, ${b.fileCount} files)`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
restoreBackup(cwd, backupName);
|
|
25
|
+
console.log(`[mink] restored from: ${backupName}`);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
28
|
+
console.error(`[mink] restore failed: ${msg}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { fileIndexPath, configPath } from "../core/paths";
|
|
4
|
+
import { atomicWriteJson, safeReadJson } from "../core/fs-utils";
|
|
5
|
+
import { scanProject, loadConfig, getExcludes } from "../core/scanner";
|
|
6
|
+
import { extractDescription } from "../core/description";
|
|
7
|
+
import { estimateTokens } from "../core/token-estimate";
|
|
8
|
+
import {
|
|
9
|
+
createEmptyIndex,
|
|
10
|
+
isFileIndex,
|
|
11
|
+
upsertEntry,
|
|
12
|
+
checkStaleness,
|
|
13
|
+
} from "../core/index-store";
|
|
14
|
+
import type { FileIndex, FileIndexEntry } from "../types/file-index";
|
|
15
|
+
|
|
16
|
+
function loadExistingIndex(indexPath: string): FileIndex {
|
|
17
|
+
const raw = safeReadJson(indexPath);
|
|
18
|
+
if (isFileIndex(raw)) return raw;
|
|
19
|
+
if (raw !== null) {
|
|
20
|
+
console.error("[mink] file-index.json is corrupt — starting fresh");
|
|
21
|
+
}
|
|
22
|
+
return createEmptyIndex();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function scan(cwd: string, options: { check: boolean }): void {
|
|
26
|
+
const idxPath = fileIndexPath(cwd);
|
|
27
|
+
const cfgPath = configPath(cwd);
|
|
28
|
+
const config = loadConfig(cfgPath);
|
|
29
|
+
const excludes = getExcludes(config);
|
|
30
|
+
const maxFiles = config.maxFiles ?? 500;
|
|
31
|
+
|
|
32
|
+
if (options.check) {
|
|
33
|
+
const existing = safeReadJson(idxPath);
|
|
34
|
+
if (!isFileIndex(existing)) {
|
|
35
|
+
console.error("[mink] no index found — run mink scan first");
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const scanned = scanProject(cwd, excludes, maxFiles);
|
|
40
|
+
const scannedPaths = scanned.map((f) => f.relativePath);
|
|
41
|
+
const report = checkStaleness(existing, scannedPaths);
|
|
42
|
+
|
|
43
|
+
if (!report.isStale) {
|
|
44
|
+
console.log("[mink] index is up to date");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (report.missingFromIndex.length > 0) {
|
|
49
|
+
console.log(`Missing from index (${report.missingFromIndex.length}):`);
|
|
50
|
+
for (const f of report.missingFromIndex) {
|
|
51
|
+
console.log(` + ${f}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (report.orphanedEntries.length > 0) {
|
|
55
|
+
console.log(`Orphaned entries (${report.orphanedEntries.length}):`);
|
|
56
|
+
for (const f of report.orphanedEntries) {
|
|
57
|
+
console.log(` - ${f}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Full scan
|
|
64
|
+
const start = Date.now();
|
|
65
|
+
const index = loadExistingIndex(idxPath);
|
|
66
|
+
|
|
67
|
+
const scanned = scanProject(cwd, excludes, maxFiles);
|
|
68
|
+
|
|
69
|
+
// Build new entries, preserving lifetime counters
|
|
70
|
+
const newIndex = createEmptyIndex();
|
|
71
|
+
newIndex.header.lifetimeHits = index.header.lifetimeHits;
|
|
72
|
+
newIndex.header.lifetimeMisses = index.header.lifetimeMisses;
|
|
73
|
+
|
|
74
|
+
for (const file of scanned) {
|
|
75
|
+
const fullPath = join(cwd, file.relativePath);
|
|
76
|
+
let content: string;
|
|
77
|
+
try {
|
|
78
|
+
content = readFileSync(fullPath, "utf-8");
|
|
79
|
+
} catch {
|
|
80
|
+
continue; // Skip unreadable files
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const entry: FileIndexEntry = {
|
|
84
|
+
filePath: file.relativePath,
|
|
85
|
+
description: extractDescription(file.relativePath, content),
|
|
86
|
+
estimatedTokens: estimateTokens(content, file.relativePath),
|
|
87
|
+
lastModified: new Date(file.mtimeMs).toISOString(),
|
|
88
|
+
lastIndexed: new Date().toISOString(),
|
|
89
|
+
};
|
|
90
|
+
upsertEntry(newIndex, entry);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
newIndex.header.lastScanTimestamp = new Date().toISOString();
|
|
94
|
+
|
|
95
|
+
atomicWriteJson(idxPath, newIndex);
|
|
96
|
+
|
|
97
|
+
const elapsed = Date.now() - start;
|
|
98
|
+
console.log(
|
|
99
|
+
`[mink] indexed ${newIndex.header.totalFiles} files in ${elapsed}ms`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mkdirSync } from "fs";
|
|
2
|
+
import { createSessionState } from "../core/session";
|
|
3
|
+
import { projectDir, sessionPath, actionLogPath } from "../core/paths";
|
|
4
|
+
import { atomicWriteJson } from "../core/fs-utils";
|
|
5
|
+
import { createActionLogWriter } from "../core/action-log";
|
|
6
|
+
|
|
7
|
+
export function sessionStart(cwd: string): void {
|
|
8
|
+
const dir = projectDir(cwd);
|
|
9
|
+
mkdirSync(dir, { recursive: true });
|
|
10
|
+
|
|
11
|
+
const state = createSessionState();
|
|
12
|
+
atomicWriteJson(sessionPath(cwd), state);
|
|
13
|
+
|
|
14
|
+
// Append session header to action log
|
|
15
|
+
try {
|
|
16
|
+
const logWriter = createActionLogWriter(actionLogPath(cwd));
|
|
17
|
+
logWriter.appendSessionHeader(state.startTimestamp);
|
|
18
|
+
} catch {
|
|
19
|
+
// Never crash hooks
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { statSync, existsSync } from "fs";
|
|
2
|
+
import { join, dirname } from "path";
|
|
3
|
+
import { safeReadJson, atomicWriteJson } from "../core/fs-utils";
|
|
4
|
+
import { isSessionState, buildSummary } from "../core/session";
|
|
5
|
+
import { reflect } from "./reflect";
|
|
6
|
+
import { createLedgerFinalizer } from "../core/token-ledger";
|
|
7
|
+
import { loadBugMemory, hasBugForFileInSession } from "../core/bug-memory";
|
|
8
|
+
import { createActionLogWriter, consolidateLog } from "../core/action-log";
|
|
9
|
+
import type { SessionState, SessionFinalizer } from "../types/session";
|
|
10
|
+
import type { ProjectConfig } from "../types/file-index";
|
|
11
|
+
|
|
12
|
+
function hasActivity(state: SessionState): boolean {
|
|
13
|
+
return Object.keys(state.reads).length > 0 || state.writes.length > 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getEditCounts(state: SessionState): Record<string, number> {
|
|
17
|
+
const counts: Record<string, number> = {};
|
|
18
|
+
for (const write of state.writes) {
|
|
19
|
+
counts[write.filePath] = (counts[write.filePath] || 0) + 1;
|
|
20
|
+
}
|
|
21
|
+
return counts;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function isLearningMemoryStale(memoryPath: string): boolean {
|
|
25
|
+
try {
|
|
26
|
+
const stat = statSync(memoryPath);
|
|
27
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
28
|
+
const twentyFourHours = 24 * 60 * 60 * 1000;
|
|
29
|
+
return ageMs > twentyFourHours;
|
|
30
|
+
} catch {
|
|
31
|
+
// File doesn't exist yet — not stale, just absent
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function sessionStop(
|
|
37
|
+
sessionFile: string,
|
|
38
|
+
finalizer?: SessionFinalizer,
|
|
39
|
+
onReminder: (msg: string) => void = (msg) => console.error(msg)
|
|
40
|
+
): void {
|
|
41
|
+
const raw = safeReadJson(sessionFile);
|
|
42
|
+
if (!isSessionState(raw)) {
|
|
43
|
+
if (raw !== null) {
|
|
44
|
+
console.error("[mink] session.json is corrupt — skipping finalization");
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const state: SessionState = raw;
|
|
50
|
+
state.stopCount++;
|
|
51
|
+
|
|
52
|
+
const projDir = dirname(sessionFile);
|
|
53
|
+
const effectiveFinalizer = finalizer ?? createLedgerFinalizer(projDir);
|
|
54
|
+
|
|
55
|
+
if (hasActivity(state)) {
|
|
56
|
+
const summary = buildSummary(state);
|
|
57
|
+
|
|
58
|
+
if (state.stopCount === 1) {
|
|
59
|
+
effectiveFinalizer.appendSession(summary);
|
|
60
|
+
} else {
|
|
61
|
+
effectiveFinalizer.updateSession(summary);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Append session end to action log and run consolidation
|
|
65
|
+
try {
|
|
66
|
+
const logPath = join(projDir, "action-log.md");
|
|
67
|
+
const logWriter = createActionLogWriter(logPath);
|
|
68
|
+
logWriter.appendSessionEnd(summary);
|
|
69
|
+
|
|
70
|
+
const cfgRaw = safeReadJson(join(projDir, "config.json")) as ProjectConfig | null;
|
|
71
|
+
consolidateLog(logPath, {
|
|
72
|
+
maxEntries: cfgRaw?.actionLogMaxEntries ?? 200,
|
|
73
|
+
retentionDays: cfgRaw?.actionLogRetentionDays ?? 7,
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
// Never crash
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for files edited 3+ times without a corresponding bug entry
|
|
81
|
+
const editCounts = getEditCounts(state);
|
|
82
|
+
const bugMemoryFile = join(projDir, "bug-memory.json");
|
|
83
|
+
const bugMemory = loadBugMemory(bugMemoryFile);
|
|
84
|
+
|
|
85
|
+
for (const [filePath, count] of Object.entries(editCounts)) {
|
|
86
|
+
if (count >= 3) {
|
|
87
|
+
const hasBug = hasBugForFileInSession(
|
|
88
|
+
bugMemory,
|
|
89
|
+
filePath,
|
|
90
|
+
state.startTimestamp
|
|
91
|
+
);
|
|
92
|
+
if (!hasBug) {
|
|
93
|
+
onReminder(
|
|
94
|
+
`[mink] ${filePath} was edited ${count} times — consider logging a bug`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Run reflection to merge duplicates and prune oversized memory
|
|
101
|
+
const memoryPath = join(projDir, "learning-memory.md");
|
|
102
|
+
const cfgPath = join(projDir, "config.json");
|
|
103
|
+
if (existsSync(memoryPath)) {
|
|
104
|
+
reflect(projDir, memoryPath, cfgPath);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check if learning memory is stale (>24h since last update)
|
|
108
|
+
if (isLearningMemoryStale(memoryPath)) {
|
|
109
|
+
onReminder(
|
|
110
|
+
"[mink] learning memory hasn't been updated in 24+ hours — consider reviewing it"
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
atomicWriteJson(sessionFile, state);
|
|
115
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
2
|
+
import {
|
|
3
|
+
sessionPath,
|
|
4
|
+
fileIndexPath,
|
|
5
|
+
configPath,
|
|
6
|
+
learningMemoryPath,
|
|
7
|
+
tokenLedgerPath,
|
|
8
|
+
bugMemoryPath,
|
|
9
|
+
actionLogPath,
|
|
10
|
+
} from "../core/paths";
|
|
11
|
+
import { safeReadJson } from "../core/fs-utils";
|
|
12
|
+
import { isFileIndex } from "../core/index-store";
|
|
13
|
+
import { loadLedger } from "../core/token-ledger";
|
|
14
|
+
import { parseLearningMemory, totalEntryCount } from "../core/learning-memory";
|
|
15
|
+
import { loadBugMemory } from "../core/bug-memory";
|
|
16
|
+
import { getDaemonStatus } from "../core/daemon";
|
|
17
|
+
|
|
18
|
+
interface FileCheck {
|
|
19
|
+
name: string;
|
|
20
|
+
path: string;
|
|
21
|
+
status: "ok" | "missing" | "corrupt";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function checkJsonFile(name: string, filePath: string, validator?: (v: unknown) => boolean): FileCheck {
|
|
25
|
+
if (!existsSync(filePath)) return { name, path: filePath, status: "missing" };
|
|
26
|
+
const data = safeReadJson(filePath);
|
|
27
|
+
if (data === null) return { name, path: filePath, status: "corrupt" };
|
|
28
|
+
if (validator && !validator(data)) return { name, path: filePath, status: "corrupt" };
|
|
29
|
+
return { name, path: filePath, status: "ok" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function checkTextFile(name: string, filePath: string): FileCheck {
|
|
33
|
+
if (!existsSync(filePath)) return { name, path: filePath, status: "missing" };
|
|
34
|
+
try {
|
|
35
|
+
readFileSync(filePath, "utf-8");
|
|
36
|
+
return { name, path: filePath, status: "ok" };
|
|
37
|
+
} catch {
|
|
38
|
+
return { name, path: filePath, status: "corrupt" };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function status(cwd: string): void {
|
|
43
|
+
console.log("[mink] project status");
|
|
44
|
+
console.log();
|
|
45
|
+
|
|
46
|
+
// Section 1: State directory integrity
|
|
47
|
+
const checks: FileCheck[] = [
|
|
48
|
+
checkJsonFile("session.json", sessionPath(cwd)),
|
|
49
|
+
checkJsonFile("file-index.json", fileIndexPath(cwd), isFileIndex),
|
|
50
|
+
checkJsonFile("config.json", configPath(cwd)),
|
|
51
|
+
checkTextFile("learning-memory.md", learningMemoryPath(cwd)),
|
|
52
|
+
checkJsonFile("token-ledger.json", tokenLedgerPath(cwd)),
|
|
53
|
+
checkJsonFile("bug-memory.json", bugMemoryPath(cwd)),
|
|
54
|
+
checkTextFile("action-log.md", actionLogPath(cwd)),
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
console.log(" State files:");
|
|
58
|
+
for (const check of checks) {
|
|
59
|
+
const icon = check.status === "ok" ? "ok" : check.status === "missing" ? "missing" : "corrupt";
|
|
60
|
+
console.log(` ${check.name}: ${icon}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
64
|
+
if (corrupt.length > 0) {
|
|
65
|
+
console.log();
|
|
66
|
+
console.log(" Warning: corrupted files detected. Consider running: mink restore");
|
|
67
|
+
}
|
|
68
|
+
console.log();
|
|
69
|
+
|
|
70
|
+
// Section 2: File index
|
|
71
|
+
try {
|
|
72
|
+
const raw = safeReadJson(fileIndexPath(cwd));
|
|
73
|
+
if (raw && isFileIndex(raw)) {
|
|
74
|
+
const h = raw.header;
|
|
75
|
+
const total = h.lifetimeHits + h.lifetimeMisses;
|
|
76
|
+
const ratio = total > 0 ? ((h.lifetimeHits / total) * 100).toFixed(1) : "N/A";
|
|
77
|
+
console.log(" File index:");
|
|
78
|
+
console.log(` Files: ${h.totalFiles}`);
|
|
79
|
+
console.log(` Last scan: ${h.lastScanTimestamp || "never"}`);
|
|
80
|
+
console.log(` Hit/miss ratio: ${ratio}${total > 0 ? "%" : ""} (${h.lifetimeHits} hits, ${h.lifetimeMisses} misses)`);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(" File index: not available");
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
console.log(" File index: error reading");
|
|
86
|
+
}
|
|
87
|
+
console.log();
|
|
88
|
+
|
|
89
|
+
// Section 3: Token ledger
|
|
90
|
+
try {
|
|
91
|
+
const ledger = loadLedger(tokenLedgerPath(cwd));
|
|
92
|
+
const lt = ledger.lifetime;
|
|
93
|
+
console.log(" Token ledger:");
|
|
94
|
+
console.log(` Sessions: ${lt.totalSessions}`);
|
|
95
|
+
console.log(` Total tokens: ${lt.totalTokens.toLocaleString()}`);
|
|
96
|
+
console.log(` Reads: ${lt.totalReads} Writes: ${lt.totalWrites}`);
|
|
97
|
+
console.log(` Estimated savings: ${lt.totalEstimatedSavings.toLocaleString()} tokens`);
|
|
98
|
+
} catch {
|
|
99
|
+
console.log(" Token ledger: error reading");
|
|
100
|
+
}
|
|
101
|
+
console.log();
|
|
102
|
+
|
|
103
|
+
// Section 4: Learning memory
|
|
104
|
+
try {
|
|
105
|
+
const memPath = learningMemoryPath(cwd);
|
|
106
|
+
if (existsSync(memPath)) {
|
|
107
|
+
const content = readFileSync(memPath, "utf-8");
|
|
108
|
+
const mem = parseLearningMemory(content);
|
|
109
|
+
const total = totalEntryCount(mem);
|
|
110
|
+
const mtime = statSync(memPath).mtime;
|
|
111
|
+
console.log(" Learning memory:");
|
|
112
|
+
console.log(` User Preferences: ${mem.sections["User Preferences"].length}`);
|
|
113
|
+
console.log(` Key Learnings: ${mem.sections["Key Learnings"].length}`);
|
|
114
|
+
console.log(` Do-Not-Repeat: ${mem.sections["Do-Not-Repeat"].length}`);
|
|
115
|
+
console.log(` Decision Log: ${mem.sections["Decision Log"].length}`);
|
|
116
|
+
console.log(` Total entries: ${total}`);
|
|
117
|
+
console.log(` Last modified: ${mtime.toISOString()}`);
|
|
118
|
+
} else {
|
|
119
|
+
console.log(" Learning memory: not initialized");
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
console.log(" Learning memory: error reading");
|
|
123
|
+
}
|
|
124
|
+
console.log();
|
|
125
|
+
|
|
126
|
+
// Section 5: Bug log
|
|
127
|
+
try {
|
|
128
|
+
const bugs = loadBugMemory(bugMemoryPath(cwd));
|
|
129
|
+
console.log(` Bug log: ${bugs.entries.length} entries`);
|
|
130
|
+
} catch {
|
|
131
|
+
console.log(" Bug log: error reading");
|
|
132
|
+
}
|
|
133
|
+
console.log();
|
|
134
|
+
|
|
135
|
+
// Section 6: Daemon status
|
|
136
|
+
try {
|
|
137
|
+
const daemon = getDaemonStatus(cwd);
|
|
138
|
+
if (daemon.running) {
|
|
139
|
+
const uptimeMs = Date.now() - new Date(daemon.startedAt!).getTime();
|
|
140
|
+
const uptimeMin = Math.floor(uptimeMs / 60_000);
|
|
141
|
+
const uptimeHrs = Math.floor(uptimeMin / 60);
|
|
142
|
+
const uptimeStr = uptimeHrs > 0
|
|
143
|
+
? `${uptimeHrs}h ${uptimeMin % 60}m`
|
|
144
|
+
: `${uptimeMin}m`;
|
|
145
|
+
console.log(` Daemon: running (PID: ${daemon.pid}, uptime: ${uptimeStr})`);
|
|
146
|
+
} else {
|
|
147
|
+
console.log(" Daemon: stopped");
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
console.log(" Daemon: unknown");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { resolve, dirname, basename } from "path";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { listRegisteredProjects } from "../core/project-registry";
|
|
4
|
+
import { createBackup } from "../core/backup";
|
|
5
|
+
import { projectMetaPath } from "../core/paths";
|
|
6
|
+
import { atomicWriteJson, safeReadJson } from "../core/fs-utils";
|
|
7
|
+
import { buildHooksConfig, detectRuntime, mergeHooksIntoSettings } from "./init";
|
|
8
|
+
|
|
9
|
+
function parseArgs(args: string[]): {
|
|
10
|
+
dryRun: boolean;
|
|
11
|
+
project: string | null;
|
|
12
|
+
list: boolean;
|
|
13
|
+
} {
|
|
14
|
+
let dryRun = false;
|
|
15
|
+
let project: string | null = null;
|
|
16
|
+
let list = false;
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < args.length; i++) {
|
|
19
|
+
if (args[i] === "--dry-run") dryRun = true;
|
|
20
|
+
else if (args[i] === "--list") list = true;
|
|
21
|
+
else if (args[i] === "--project" && i + 1 < args.length) {
|
|
22
|
+
project = args[++i];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return { dryRun, project, list };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function update(cwd: string, args: string[]): Promise<void> {
|
|
30
|
+
const { dryRun, project, list } = parseArgs(args);
|
|
31
|
+
|
|
32
|
+
const registered = listRegisteredProjects();
|
|
33
|
+
|
|
34
|
+
if (list) {
|
|
35
|
+
if (registered.length === 0) {
|
|
36
|
+
console.log("[mink] no registered projects found");
|
|
37
|
+
console.log(" Run 'mink init' in a project directory to register it.");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log("[mink] registered projects:");
|
|
41
|
+
console.log(
|
|
42
|
+
" " +
|
|
43
|
+
"ID".padEnd(30) +
|
|
44
|
+
"Name".padEnd(20) +
|
|
45
|
+
"Version".padEnd(12) +
|
|
46
|
+
"Path"
|
|
47
|
+
);
|
|
48
|
+
console.log(" " + "-".repeat(80));
|
|
49
|
+
for (const p of registered) {
|
|
50
|
+
console.log(
|
|
51
|
+
" " +
|
|
52
|
+
p.id.padEnd(30) +
|
|
53
|
+
p.name.padEnd(20) +
|
|
54
|
+
p.version.padEnd(12) +
|
|
55
|
+
p.cwd
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let targets = registered;
|
|
62
|
+
if (project) {
|
|
63
|
+
targets = registered.filter(
|
|
64
|
+
(p) => p.name === project || p.id === project
|
|
65
|
+
);
|
|
66
|
+
if (targets.length === 0) {
|
|
67
|
+
console.error(`[mink] project not found: ${project}`);
|
|
68
|
+
console.error(
|
|
69
|
+
" Available: " + registered.map((p) => p.name).join(", ")
|
|
70
|
+
);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (targets.length === 0) {
|
|
76
|
+
console.log("[mink] no registered projects found");
|
|
77
|
+
console.log(" Run 'mink init' in a project directory to register it.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const runtime = detectRuntime();
|
|
82
|
+
const cliPath = resolve(
|
|
83
|
+
dirname(new URL(import.meta.url).pathname),
|
|
84
|
+
"../cli.ts"
|
|
85
|
+
);
|
|
86
|
+
const newHooks = buildHooksConfig(runtime, cliPath);
|
|
87
|
+
|
|
88
|
+
for (const target of targets) {
|
|
89
|
+
console.log(`[mink] updating: ${target.name} (${target.id})`);
|
|
90
|
+
|
|
91
|
+
if (dryRun) {
|
|
92
|
+
console.log(" [dry-run] would update hooks and project metadata");
|
|
93
|
+
console.log(` [dry-run] would create backup before changes`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Create backup
|
|
98
|
+
const backupName = createBackup(target.cwd);
|
|
99
|
+
console.log(` backup: ${backupName}`);
|
|
100
|
+
|
|
101
|
+
// Update hooks
|
|
102
|
+
const settingsPath = resolve(target.cwd, ".claude", "settings.json");
|
|
103
|
+
mergeHooksIntoSettings(settingsPath, newHooks);
|
|
104
|
+
console.log(" hooks: updated");
|
|
105
|
+
|
|
106
|
+
// Update project meta
|
|
107
|
+
const metaPath = projectMetaPath(target.cwd);
|
|
108
|
+
const existing = safeReadJson(metaPath) as Record<string, unknown> | null;
|
|
109
|
+
atomicWriteJson(metaPath, {
|
|
110
|
+
...(existing ?? {}),
|
|
111
|
+
cwd: target.cwd,
|
|
112
|
+
name: target.name,
|
|
113
|
+
version: "0.1.0",
|
|
114
|
+
});
|
|
115
|
+
console.log(" metadata: updated");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!dryRun) {
|
|
119
|
+
console.log(`[mink] ${targets.length} project(s) updated`);
|
|
120
|
+
}
|
|
121
|
+
}
|