@massu/core 0.1.1 → 0.4.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 (151) hide show
  1. package/commands/_shared-preamble.md +76 -0
  2. package/commands/massu-audit-deps.md +211 -0
  3. package/commands/massu-changelog.md +174 -0
  4. package/commands/massu-cleanup.md +315 -0
  5. package/commands/massu-commit.md +481 -0
  6. package/commands/massu-create-plan.md +752 -0
  7. package/commands/massu-dead-code.md +131 -0
  8. package/commands/massu-debug.md +484 -0
  9. package/commands/massu-deploy.md +91 -0
  10. package/commands/massu-deps.md +374 -0
  11. package/commands/massu-doc-gen.md +279 -0
  12. package/commands/massu-docs.md +364 -0
  13. package/commands/massu-estimate.md +313 -0
  14. package/commands/massu-golden-path.md +973 -0
  15. package/commands/massu-guide.md +167 -0
  16. package/commands/massu-hotfix.md +480 -0
  17. package/commands/massu-loop-playwright.md +837 -0
  18. package/commands/massu-loop.md +775 -0
  19. package/commands/massu-new-feature.md +511 -0
  20. package/commands/massu-parity.md +214 -0
  21. package/commands/massu-plan.md +456 -0
  22. package/commands/massu-push-light.md +207 -0
  23. package/commands/massu-push.md +434 -0
  24. package/commands/massu-refactor.md +410 -0
  25. package/commands/massu-release.md +363 -0
  26. package/commands/massu-review.md +238 -0
  27. package/commands/massu-simplify.md +281 -0
  28. package/commands/massu-status.md +278 -0
  29. package/commands/massu-tdd.md +201 -0
  30. package/commands/massu-test.md +516 -0
  31. package/commands/massu-verify-playwright.md +281 -0
  32. package/commands/massu-verify.md +667 -0
  33. package/dist/cli.js +7772 -3140
  34. package/dist/hooks/cost-tracker.js +103 -40
  35. package/dist/hooks/post-edit-context.js +74 -8
  36. package/dist/hooks/post-tool-use.js +268 -106
  37. package/dist/hooks/pre-compact.js +167 -43
  38. package/dist/hooks/pre-delete-check.js +159 -42
  39. package/dist/hooks/quality-event.js +103 -40
  40. package/dist/hooks/security-gate.js +29 -0
  41. package/dist/hooks/session-end.js +143 -84
  42. package/dist/hooks/session-start.js +186 -49
  43. package/dist/hooks/user-prompt.js +189 -43
  44. package/package.json +10 -15
  45. package/src/adr-generator.ts +9 -2
  46. package/src/analytics.ts +9 -3
  47. package/src/audit-trail.ts +10 -3
  48. package/src/backfill-sessions.ts +5 -4
  49. package/src/cli.ts +6 -0
  50. package/src/cloud-sync.ts +14 -18
  51. package/src/commands/doctor.ts +193 -6
  52. package/src/commands/init.ts +230 -5
  53. package/src/commands/install-commands.ts +137 -0
  54. package/src/config.ts +68 -2
  55. package/src/cost-tracker.ts +11 -6
  56. package/src/db.ts +115 -2
  57. package/src/dependency-scorer.ts +9 -2
  58. package/src/docs-tools.ts +21 -16
  59. package/src/hooks/post-edit-context.ts +4 -4
  60. package/src/hooks/post-tool-use.ts +130 -0
  61. package/src/hooks/pre-compact.ts +23 -1
  62. package/src/hooks/pre-delete-check.ts +92 -4
  63. package/src/hooks/security-gate.ts +32 -0
  64. package/src/hooks/session-end.ts +3 -3
  65. package/src/hooks/session-start.ts +99 -6
  66. package/src/hooks/user-prompt.ts +46 -1
  67. package/src/import-resolver.ts +2 -1
  68. package/src/knowledge-db.ts +169 -0
  69. package/src/knowledge-indexer.ts +704 -0
  70. package/src/knowledge-tools.ts +1413 -0
  71. package/src/license.ts +482 -0
  72. package/src/memory-db.ts +1364 -23
  73. package/src/memory-tools.ts +14 -15
  74. package/src/observability-tools.ts +13 -2
  75. package/src/observation-extractor.ts +11 -4
  76. package/src/page-deps.ts +3 -2
  77. package/src/prompt-analyzer.ts +9 -2
  78. package/src/python/coupling-detector.ts +124 -0
  79. package/src/python/domain-enforcer.ts +83 -0
  80. package/src/python/impact-analyzer.ts +95 -0
  81. package/src/python/import-parser.ts +244 -0
  82. package/src/python/import-resolver.ts +135 -0
  83. package/src/python/migration-indexer.ts +115 -0
  84. package/src/python/migration-parser.ts +332 -0
  85. package/src/python/model-indexer.ts +70 -0
  86. package/src/python/model-parser.ts +279 -0
  87. package/src/python/route-indexer.ts +58 -0
  88. package/src/python/route-parser.ts +317 -0
  89. package/src/python-tools.ts +629 -0
  90. package/src/regression-detector.ts +9 -3
  91. package/src/security-scorer.ts +9 -2
  92. package/src/sentinel-db.ts +45 -89
  93. package/src/sentinel-tools.ts +8 -11
  94. package/src/server.ts +29 -7
  95. package/src/session-archiver.ts +4 -5
  96. package/src/team-knowledge.ts +9 -2
  97. package/src/tools.ts +1032 -44
  98. package/src/validate-features-runner.ts +0 -1
  99. package/src/validation-engine.ts +9 -2
  100. package/README.md +0 -40
  101. package/dist/server.js +0 -7008
  102. package/src/__tests__/adr-generator.test.ts +0 -260
  103. package/src/__tests__/analytics.test.ts +0 -282
  104. package/src/__tests__/audit-trail.test.ts +0 -382
  105. package/src/__tests__/backfill-sessions.test.ts +0 -690
  106. package/src/__tests__/cli.test.ts +0 -290
  107. package/src/__tests__/cloud-sync.test.ts +0 -261
  108. package/src/__tests__/config-sections.test.ts +0 -359
  109. package/src/__tests__/config.test.ts +0 -732
  110. package/src/__tests__/cost-tracker.test.ts +0 -348
  111. package/src/__tests__/db.test.ts +0 -177
  112. package/src/__tests__/dependency-scorer.test.ts +0 -325
  113. package/src/__tests__/docs-integration.test.ts +0 -178
  114. package/src/__tests__/docs-tools.test.ts +0 -199
  115. package/src/__tests__/domains.test.ts +0 -236
  116. package/src/__tests__/hooks.test.ts +0 -221
  117. package/src/__tests__/import-resolver.test.ts +0 -95
  118. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  119. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  120. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  121. package/src/__tests__/memory-db.test.ts +0 -404
  122. package/src/__tests__/memory-enhancements.test.ts +0 -316
  123. package/src/__tests__/memory-tools.test.ts +0 -199
  124. package/src/__tests__/middleware-tree.test.ts +0 -177
  125. package/src/__tests__/observability-tools.test.ts +0 -595
  126. package/src/__tests__/observability.test.ts +0 -437
  127. package/src/__tests__/observation-extractor.test.ts +0 -167
  128. package/src/__tests__/page-deps.test.ts +0 -60
  129. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  130. package/src/__tests__/regression-detector.test.ts +0 -295
  131. package/src/__tests__/rules.test.ts +0 -87
  132. package/src/__tests__/schema-mapper.test.ts +0 -29
  133. package/src/__tests__/security-scorer.test.ts +0 -238
  134. package/src/__tests__/security-utils.test.ts +0 -175
  135. package/src/__tests__/sentinel-db.test.ts +0 -491
  136. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  137. package/src/__tests__/sentinel-tools.test.ts +0 -324
  138. package/src/__tests__/sentinel-types.test.ts +0 -750
  139. package/src/__tests__/server.test.ts +0 -452
  140. package/src/__tests__/session-archiver.test.ts +0 -524
  141. package/src/__tests__/session-state-generator.test.ts +0 -900
  142. package/src/__tests__/team-knowledge.test.ts +0 -327
  143. package/src/__tests__/tools.test.ts +0 -340
  144. package/src/__tests__/transcript-parser.test.ts +0 -195
  145. package/src/__tests__/trpc-index.test.ts +0 -25
  146. package/src/__tests__/validate-features-runner.test.ts +0 -517
  147. package/src/__tests__/validation-engine.test.ts +0 -300
  148. package/src/core-tools.ts +0 -685
  149. package/src/memory-queries.ts +0 -804
  150. package/src/memory-schema.ts +0 -546
  151. package/src/tool-helpers.ts +0 -41
@@ -3,12 +3,13 @@ import{createRequire as __cr}from"module";const require=__cr(import.meta.url);
3
3
 
4
4
  // src/memory-db.ts
5
5
  import Database from "better-sqlite3";
6
- import { dirname as dirname2 } from "path";
6
+ import { dirname as dirname2, basename } from "path";
7
7
  import { existsSync as existsSync2, mkdirSync } from "fs";
8
8
 
9
9
  // src/config.ts
10
10
  import { resolve, dirname } from "path";
11
11
  import { existsSync, readFileSync } from "fs";
12
+ import { homedir } from "os";
12
13
  import { parse as parseYaml } from "yaml";
13
14
  import { z } from "zod";
14
15
  var DomainConfigSchema = z.object({
@@ -140,6 +141,49 @@ var CloudConfigSchema = z.object({
140
141
  audit: z.boolean().default(true)
141
142
  }).default({ memory: true, analytics: true, audit: true })
142
143
  }).optional();
144
+ var ConventionsConfigSchema = z.object({
145
+ claudeDirName: z.string().default(".claude").refine(
146
+ (s) => !s.includes("..") && !s.startsWith("/"),
147
+ { message: 'claudeDirName must not contain ".." or start with "/"' }
148
+ ),
149
+ sessionStatePath: z.string().default(".claude/session-state/CURRENT.md").refine(
150
+ (s) => !s.includes("..") && !s.startsWith("/"),
151
+ { message: 'sessionStatePath must not contain ".." or start with "/"' }
152
+ ),
153
+ sessionArchivePath: z.string().default(".claude/session-state/archive").refine(
154
+ (s) => !s.includes("..") && !s.startsWith("/"),
155
+ { message: 'sessionArchivePath must not contain ".." or start with "/"' }
156
+ ),
157
+ knowledgeCategories: z.array(z.string()).default([
158
+ "patterns",
159
+ "commands",
160
+ "incidents",
161
+ "reference",
162
+ "protocols",
163
+ "checklists",
164
+ "playbooks",
165
+ "critical",
166
+ "scripts",
167
+ "status",
168
+ "templates",
169
+ "loop-state",
170
+ "session-state",
171
+ "agents"
172
+ ]),
173
+ knowledgeSourceFiles: z.array(z.string()).default(["CLAUDE.md", "MEMORY.md", "corrections.md"]),
174
+ excludePatterns: z.array(z.string()).default(["/ARCHIVE/", "/SESSION-HISTORY/"])
175
+ }).optional();
176
+ var PythonDomainConfigSchema = z.object({
177
+ name: z.string(),
178
+ packages: z.array(z.string()),
179
+ allowed_imports_from: z.array(z.string()).default([])
180
+ });
181
+ var PythonConfigSchema = z.object({
182
+ root: z.string(),
183
+ alembic_dir: z.string().optional(),
184
+ domains: z.array(PythonDomainConfigSchema).default([]),
185
+ exclude_dirs: z.array(z.string()).default(["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache"])
186
+ }).optional();
143
187
  var PathsConfigSchema = z.object({
144
188
  source: z.string().default("src"),
145
189
  aliases: z.record(z.string(), z.string()).default({ "@": "src" }),
@@ -174,7 +218,9 @@ var RawConfigSchema = z.object({
174
218
  security: SecurityConfigSchema,
175
219
  team: TeamConfigSchema,
176
220
  regression: RegressionConfigSchema,
177
- cloud: CloudConfigSchema
221
+ cloud: CloudConfigSchema,
222
+ conventions: ConventionsConfigSchema,
223
+ python: PythonConfigSchema
178
224
  }).passthrough();
179
225
  var _config = null;
180
226
  var _projectRoot = null;
@@ -238,13 +284,24 @@ function getConfig() {
238
284
  security: parsed.security,
239
285
  team: parsed.team,
240
286
  regression: parsed.regression,
241
- cloud: parsed.cloud
287
+ cloud: parsed.cloud,
288
+ conventions: parsed.conventions,
289
+ python: parsed.python
242
290
  };
291
+ if (!_config.cloud?.apiKey && process.env.MASSU_API_KEY) {
292
+ _config.cloud = {
293
+ enabled: true,
294
+ sync: { memory: true, analytics: true, audit: true },
295
+ ..._config.cloud,
296
+ apiKey: process.env.MASSU_API_KEY
297
+ };
298
+ }
243
299
  return _config;
244
300
  }
245
301
  function getResolvedPaths() {
246
302
  const config = getConfig();
247
303
  const root = getProjectRoot();
304
+ const claudeDirName = config.conventions?.claudeDirName ?? ".claude";
248
305
  return {
249
306
  codegraphDbPath: resolve(root, ".codegraph/codegraph.db"),
250
307
  dataDbPath: resolve(root, ".massu/data.db"),
@@ -260,22 +317,43 @@ function getResolvedPaths() {
260
317
  ),
261
318
  extensions: [".ts", ".tsx", ".js", ".jsx"],
262
319
  indexFiles: ["index.ts", "index.tsx", "index.js", "index.jsx"],
263
- patternsDir: resolve(root, ".claude/patterns"),
264
- claudeMdPath: resolve(root, ".claude/CLAUDE.md"),
320
+ patternsDir: resolve(root, claudeDirName, "patterns"),
321
+ claudeMdPath: resolve(root, claudeDirName, "CLAUDE.md"),
265
322
  docsMapPath: resolve(root, ".massu/docs-map.json"),
266
323
  helpSitePath: resolve(root, "../" + config.project.name + "-help"),
267
- memoryDbPath: resolve(root, ".massu/memory.db")
324
+ memoryDbPath: resolve(root, ".massu/memory.db"),
325
+ knowledgeDbPath: resolve(root, ".massu/knowledge.db"),
326
+ plansDir: resolve(root, "docs/plans"),
327
+ docsDir: resolve(root, "docs"),
328
+ claudeDir: resolve(root, claudeDirName),
329
+ memoryDir: resolve(homedir(), claudeDirName, "projects", root.replace(/\//g, "-"), "memory"),
330
+ sessionStatePath: resolve(root, config.conventions?.sessionStatePath ?? `${claudeDirName}/session-state/CURRENT.md`),
331
+ sessionArchivePath: resolve(root, config.conventions?.sessionArchivePath ?? `${claudeDirName}/session-state/archive`),
332
+ mcpJsonPath: resolve(root, ".mcp.json"),
333
+ settingsLocalPath: resolve(root, claudeDirName, "settings.local.json")
268
334
  };
269
335
  }
270
336
 
271
- // src/memory-schema.ts
337
+ // src/memory-db.ts
338
+ function getMemoryDb() {
339
+ const dbPath = getResolvedPaths().memoryDbPath;
340
+ const dir = dirname2(dbPath);
341
+ if (!existsSync2(dir)) {
342
+ mkdirSync(dir, { recursive: true });
343
+ }
344
+ const db = new Database(dbPath);
345
+ db.pragma("journal_mode = WAL");
346
+ db.pragma("foreign_keys = ON");
347
+ initMemorySchema(db);
348
+ return db;
349
+ }
272
350
  function initMemorySchema(db) {
273
351
  db.exec(`
274
352
  -- Sessions table (linked to Claude Code session IDs)
275
353
  CREATE TABLE IF NOT EXISTS sessions (
276
354
  id INTEGER PRIMARY KEY AUTOINCREMENT,
277
355
  session_id TEXT UNIQUE NOT NULL,
278
- project TEXT NOT NULL DEFAULT 'unknown',
356
+ project TEXT NOT NULL DEFAULT 'my-project',
279
357
  git_branch TEXT,
280
358
  started_at TEXT NOT NULL,
281
359
  started_at_epoch INTEGER NOT NULL,
@@ -330,9 +408,7 @@ function initMemorySchema(db) {
330
408
  content_rowid='id'
331
409
  );
332
410
  `);
333
- } catch (e) {
334
- process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
335
- `);
411
+ } catch (_e) {
336
412
  }
337
413
  db.exec(`
338
414
  CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
@@ -392,9 +468,7 @@ function initMemorySchema(db) {
392
468
  content_rowid='id'
393
469
  );
394
470
  `);
395
- } catch (e) {
396
- process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
397
- `);
471
+ } catch (_e) {
398
472
  }
399
473
  db.exec(`
400
474
  CREATE TRIGGER IF NOT EXISTS prompts_ai AFTER INSERT ON user_prompts BEGIN
@@ -464,9 +538,7 @@ function initMemorySchema(db) {
464
538
  content_rowid=id
465
539
  );
466
540
  `);
467
- } catch (e) {
468
- process.stderr.write(`FTS5 setup warning: ${e instanceof Error ? e.message : String(e)}
469
- `);
541
+ } catch (_e) {
470
542
  }
471
543
  db.exec(`
472
544
  CREATE TRIGGER IF NOT EXISTS ct_fts_insert AFTER INSERT ON conversation_turns BEGIN
@@ -490,7 +562,7 @@ function initMemorySchema(db) {
490
562
  CREATE TABLE IF NOT EXISTS session_quality_scores (
491
563
  id INTEGER PRIMARY KEY AUTOINCREMENT,
492
564
  session_id TEXT NOT NULL UNIQUE,
493
- project TEXT NOT NULL DEFAULT 'unknown',
565
+ project TEXT NOT NULL DEFAULT 'my-project',
494
566
  score INTEGER NOT NULL DEFAULT 100,
495
567
  security_score INTEGER NOT NULL DEFAULT 100,
496
568
  architecture_score INTEGER NOT NULL DEFAULT 100,
@@ -513,7 +585,7 @@ function initMemorySchema(db) {
513
585
  CREATE TABLE IF NOT EXISTS session_costs (
514
586
  id INTEGER PRIMARY KEY AUTOINCREMENT,
515
587
  session_id TEXT NOT NULL UNIQUE,
516
- project TEXT NOT NULL DEFAULT 'unknown',
588
+ project TEXT NOT NULL DEFAULT 'my-project',
517
589
  input_tokens INTEGER NOT NULL DEFAULT 0,
518
590
  output_tokens INTEGER NOT NULL DEFAULT 0,
519
591
  cache_read_tokens INTEGER NOT NULL DEFAULT 0,
@@ -740,15 +812,27 @@ function initMemorySchema(db) {
740
812
  );
741
813
  CREATE INDEX IF NOT EXISTS idx_pending_sync_created ON pending_sync(created_at ASC);
742
814
  `);
815
+ db.exec(`
816
+ CREATE TABLE IF NOT EXISTS license_cache (
817
+ api_key_hash TEXT PRIMARY KEY,
818
+ tier TEXT NOT NULL,
819
+ valid_until TEXT NOT NULL,
820
+ last_validated TEXT NOT NULL,
821
+ features TEXT DEFAULT '[]'
822
+ );
823
+ `);
743
824
  }
744
-
745
- // src/memory-queries.ts
746
- import { basename } from "path";
747
825
  function enqueueSyncPayload(db, payload) {
748
826
  db.prepare("INSERT INTO pending_sync (payload) VALUES (?)").run(payload);
749
827
  }
750
828
  function dequeuePendingSync(db, limit = 10) {
751
- db.prepare("DELETE FROM pending_sync WHERE retry_count >= 10").run();
829
+ const stale = db.prepare(
830
+ "SELECT id FROM pending_sync WHERE retry_count >= 10"
831
+ ).all();
832
+ if (stale.length > 0) {
833
+ const ids = stale.map((s) => s.id);
834
+ db.prepare(`DELETE FROM pending_sync WHERE id IN (${ids.map(() => "?").join(",")})`).run(...ids);
835
+ }
752
836
  return db.prepare(
753
837
  "SELECT id, payload, retry_count FROM pending_sync ORDER BY created_at ASC LIMIT ?"
754
838
  ).all(limit);
@@ -840,27 +924,9 @@ function setLastProcessedLine(db, sessionId, lineNumber) {
840
924
  db.prepare("INSERT OR REPLACE INTO memory_meta (key, value) VALUES (?, ?)").run(`last_processed_line:${sessionId}`, String(lineNumber));
841
925
  }
842
926
 
843
- // src/memory-db.ts
844
- var initializedPaths = /* @__PURE__ */ new Set();
845
- function getMemoryDb() {
846
- const dbPath = getResolvedPaths().memoryDbPath;
847
- const dir = dirname2(dbPath);
848
- if (!existsSync2(dir)) {
849
- mkdirSync(dir, { recursive: true });
850
- }
851
- const db = new Database(dbPath);
852
- db.pragma("journal_mode = WAL");
853
- db.pragma("foreign_keys = ON");
854
- if (!initializedPaths.has(dbPath)) {
855
- initMemorySchema(db);
856
- initializedPaths.add(dbPath);
857
- }
858
- return db;
859
- }
860
-
861
927
  // src/session-archiver.ts
862
928
  import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdirSync as mkdirSync2, renameSync } from "fs";
863
- import { resolve as resolve2, dirname as dirname3 } from "path";
929
+ import { resolve as resolve3, dirname as dirname3 } from "path";
864
930
 
865
931
  // src/session-state-generator.ts
866
932
  function generateCurrentMd(db, sessionId) {
@@ -1005,17 +1071,17 @@ function safeParseJson(json, fallback) {
1005
1071
  }
1006
1072
 
1007
1073
  // src/session-archiver.ts
1008
- var PROJECT_ROOT = getProjectRoot();
1009
1074
  function archiveAndRegenerate(db, sessionId) {
1010
- const currentMdPath = resolve2(PROJECT_ROOT, ".claude/session-state/CURRENT.md");
1011
- const archiveDir = resolve2(PROJECT_ROOT, ".claude/session-state/archive");
1075
+ const resolved = getResolvedPaths();
1076
+ const currentMdPath = resolved.sessionStatePath;
1077
+ const archiveDir = resolved.sessionArchivePath;
1012
1078
  let archived = false;
1013
1079
  let archivePath;
1014
1080
  if (existsSync3(currentMdPath)) {
1015
1081
  const existingContent = readFileSync2(currentMdPath, "utf-8");
1016
1082
  if (existingContent.trim().length > 10) {
1017
1083
  const { date, slug } = extractArchiveInfo(existingContent);
1018
- archivePath = resolve2(archiveDir, `${date}-${slug}.md`);
1084
+ archivePath = resolve3(archiveDir, `${date}-${slug}.md`);
1019
1085
  if (!existsSync3(archiveDir)) {
1020
1086
  mkdirSync2(archiveDir, { recursive: true });
1021
1087
  }
@@ -1129,8 +1195,8 @@ async function parseTranscriptFrom(filePath, startLine) {
1129
1195
  }
1130
1196
  return { entries, totalLines: lineNumber };
1131
1197
  }
1132
- function estimateTokens(text2) {
1133
- return Math.ceil(text2.length / 4);
1198
+ function estimateTokens(text) {
1199
+ return Math.ceil(text.length / 4);
1134
1200
  }
1135
1201
 
1136
1202
  // src/cloud-sync.ts
@@ -1211,30 +1277,25 @@ async function syncToCloud(db, payload) {
1211
1277
  };
1212
1278
  }
1213
1279
  async function drainSyncQueue(db) {
1214
- try {
1215
- const config = getConfig();
1216
- if (!config.cloud?.enabled || !config.cloud?.apiKey) return;
1217
- const pending = dequeuePendingSync(db, 10);
1218
- for (const item of pending) {
1219
- try {
1220
- const payload = JSON.parse(item.payload);
1221
- const result = await syncToCloud(db, payload);
1222
- if (result.success) {
1223
- removePendingSync(db, item.id);
1224
- } else {
1225
- incrementRetryCount(db, item.id, result.error ?? "Unknown error");
1226
- }
1227
- } catch (err) {
1228
- incrementRetryCount(db, item.id, err instanceof Error ? err.message : String(err));
1280
+ const config = getConfig();
1281
+ if (!config.cloud?.enabled || !config.cloud?.apiKey) return;
1282
+ const pending = dequeuePendingSync(db, 10);
1283
+ for (const item of pending) {
1284
+ try {
1285
+ const payload = JSON.parse(item.payload);
1286
+ const result = await syncToCloud(db, payload);
1287
+ if (result.success) {
1288
+ removePendingSync(db, item.id);
1289
+ } else {
1290
+ incrementRetryCount(db, item.id, result.error ?? "Unknown error");
1229
1291
  }
1292
+ } catch (err) {
1293
+ incrementRetryCount(db, item.id, err instanceof Error ? err.message : String(err));
1230
1294
  }
1231
- } catch (err) {
1232
- process.stderr.write(`massu: drainSyncQueue failed: ${err instanceof Error ? err.message : String(err)}
1233
- `);
1234
1295
  }
1235
1296
  }
1236
1297
  function sleep(ms) {
1237
- return new Promise((resolve3) => setTimeout(resolve3, ms));
1298
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1238
1299
  }
1239
1300
 
1240
1301
  // src/analytics.ts
@@ -1300,7 +1361,6 @@ function backfillQualityScores(db) {
1300
1361
  FROM sessions s
1301
1362
  LEFT JOIN session_quality_scores q ON s.session_id = q.session_id
1302
1363
  WHERE q.session_id IS NULL
1303
- LIMIT 1000
1304
1364
  `).all();
1305
1365
  let backfilled = 0;
1306
1366
  for (const session of sessions) {
@@ -1313,11 +1373,10 @@ function backfillQualityScores(db) {
1313
1373
 
1314
1374
  // src/cost-tracker.ts
1315
1375
  var DEFAULT_MODEL_PRICING = {
1316
- "claude-opus-4-6": { input_per_million: 5, output_per_million: 25, cache_read_per_million: 0.5, cache_write_per_million: 6.25 },
1376
+ "claude-opus-4-6": { input_per_million: 15, output_per_million: 75, cache_read_per_million: 1.5, cache_write_per_million: 18.75 },
1317
1377
  "claude-sonnet-4-6": { input_per_million: 3, output_per_million: 15, cache_read_per_million: 0.3, cache_write_per_million: 3.75 },
1318
1378
  "claude-sonnet-4-5": { input_per_million: 3, output_per_million: 15, cache_read_per_million: 0.3, cache_write_per_million: 3.75 },
1319
- "claude-3-5-haiku-20241022": { input_per_million: 0.8, output_per_million: 4, cache_read_per_million: 0.08, cache_write_per_million: 1 },
1320
- "claude-haiku-4-5-20251001": { input_per_million: 1, output_per_million: 5, cache_read_per_million: 0.1, cache_write_per_million: 1.25 },
1379
+ "claude-haiku-4-5-20251001": { input_per_million: 0.8, output_per_million: 4, cache_read_per_million: 0.08, cache_write_per_million: 1 },
1321
1380
  "default": { input_per_million: 3, output_per_million: 15, cache_read_per_million: 0.3, cache_write_per_million: 3.75 }
1322
1381
  };
1323
1382
  function getModelPricing() {
@@ -1389,8 +1448,8 @@ import { createHash } from "crypto";
1389
1448
  function escapeRegex(str) {
1390
1449
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1391
1450
  }
1392
- function redactSensitiveContent(text2) {
1393
- return text2.replace(/\b(sk-|ghp_|gho_|xoxb-|xoxp-|AKIA)[A-Za-z0-9_-]{10,}\b/g, "[REDACTED_KEY]").replace(/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/gi, "Bearer [REDACTED_TOKEN]").replace(/:\/\/[^:]+:[^@\s]+@/g, "://[REDACTED_CREDENTIALS]@").replace(/(https?:\/\/[^\s]+[?&](?:token|key|secret|password|auth)=)[^\s&]*/gi, "$1[REDACTED]").replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[REDACTED_EMAIL]").replace(/(?:\/Users\/|\/home\/|C:\\Users\\)[^\s"'`]+/g, "[REDACTED_PATH]");
1451
+ function redactSensitiveContent(text) {
1452
+ return text.replace(/\b(sk-|ghp_|gho_|xoxb-|xoxp-|AKIA)[A-Za-z0-9_-]{10,}\b/g, "[REDACTED_KEY]").replace(/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/gi, "Bearer [REDACTED_TOKEN]").replace(/:\/\/[^:]+:[^@\s]+@/g, "://[REDACTED_CREDENTIALS]@").replace(/(https?:\/\/[^\s]+[?&](?:token|key|secret|password|auth)=)[^\s&]*/gi, "$1[REDACTED]").replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, "[REDACTED_EMAIL]").replace(/(?:\/Users\/|\/home\/|C:\\Users\\)[^\s"'`]+/g, "[REDACTED_PATH]");
1394
1453
  }
1395
1454
 
1396
1455
  // src/prompt-analyzer.ts
@@ -1458,7 +1517,7 @@ function analyzeSessionPrompts(db, sessionId) {
1458
1517
  let stored = 0;
1459
1518
  for (let i = 0; i < prompts.length; i++) {
1460
1519
  const prompt = prompts[i];
1461
- const followUps = prompts.slice(i + 1, i + 4).map((p2) => p2.prompt_text);
1520
+ const followUps = prompts.slice(i + 1, i + 4).map((p) => p.prompt_text);
1462
1521
  const category = categorizePrompt(prompt.prompt_text);
1463
1522
  const hash = hashPrompt(prompt.prompt_text);
1464
1523
  const { outcome, correctionsNeeded, followUpCount } = detectOutcome(followUps, []);
@@ -1510,7 +1569,7 @@ async function main() {
1510
1569
  }
1511
1570
  try {
1512
1571
  const { score, breakdown } = calculateQualityScore(db, session_id);
1513
- if (score > 0) {
1572
+ if (score !== 50) {
1514
1573
  storeQualityScore(db, session_id, score, breakdown);
1515
1574
  }
1516
1575
  backfillQualityScores(db);
@@ -1683,18 +1742,18 @@ function groupEntriesIntoTurns(entries) {
1683
1742
  if (currentTurn) {
1684
1743
  turns.push(currentTurn);
1685
1744
  }
1686
- const text2 = getTextFromBlocks(entry.message.content);
1687
- if (text2.trim()) {
1745
+ const text = getTextFromBlocks(entry.message.content);
1746
+ if (text.trim()) {
1688
1747
  currentTurn = {
1689
- userPrompt: text2.trim(),
1748
+ userPrompt: text.trim(),
1690
1749
  assistantText: null,
1691
1750
  toolCalls: []
1692
1751
  };
1693
1752
  }
1694
1753
  } else if (entry.type === "assistant" && entry.message && currentTurn) {
1695
- const text2 = getTextFromBlocks(entry.message.content);
1696
- if (text2.trim()) {
1697
- currentTurn.assistantText = currentTurn.assistantText ? currentTurn.assistantText + "\n" + text2.trim() : text2.trim();
1754
+ const text = getTextFromBlocks(entry.message.content);
1755
+ if (text.trim()) {
1756
+ currentTurn.assistantText = currentTurn.assistantText ? currentTurn.assistantText + "\n" + text.trim() : text.trim();
1698
1757
  }
1699
1758
  for (const block of entry.message.content) {
1700
1759
  if (block.type === "tool_use") {
@@ -1764,14 +1823,14 @@ function extractFilesFromToolCall(toolName, input) {
1764
1823
  return [];
1765
1824
  }
1766
1825
  function readStdin() {
1767
- return new Promise((resolve3) => {
1826
+ return new Promise((resolve4) => {
1768
1827
  let data = "";
1769
1828
  process.stdin.setEncoding("utf-8");
1770
1829
  process.stdin.on("data", (chunk) => {
1771
1830
  data += chunk;
1772
1831
  });
1773
- process.stdin.on("end", () => resolve3(data));
1774
- setTimeout(() => resolve3(data), 5e3);
1832
+ process.stdin.on("end", () => resolve4(data));
1833
+ setTimeout(() => resolve4(data), 5e3);
1775
1834
  });
1776
1835
  }
1777
1836
  main();