@snapback/cli 1.0.2 → 1.0.4

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 (42) hide show
  1. package/dist/SkippedTestDetector-B3JZUE5G.js +5 -0
  2. package/dist/{SkippedTestDetector-JY4EF5BN.js.map → SkippedTestDetector-B3JZUE5G.js.map} +1 -1
  3. package/dist/{analysis-B4NVULM4.js → analysis-Z53F5FT2.js} +6 -5
  4. package/dist/{analysis-B4NVULM4.js.map → analysis-Z53F5FT2.js.map} +1 -1
  5. package/dist/{chunk-BCIXMIPW.js → chunk-6MR2TINI.js} +4 -3
  6. package/dist/chunk-6MR2TINI.js.map +1 -0
  7. package/dist/{chunk-WCQVDF3K.js → chunk-BW7RALUZ.js} +3 -2
  8. package/dist/{chunk-WCQVDF3K.js.map → chunk-BW7RALUZ.js.map} +1 -1
  9. package/dist/{chunk-VSJ33PLA.js → chunk-G7QXHNGB.js} +5 -4
  10. package/dist/chunk-G7QXHNGB.js.map +1 -0
  11. package/dist/{chunk-MTQ6ESQR.js → chunk-ISVRGBWT.js} +6 -5
  12. package/dist/chunk-ISVRGBWT.js.map +1 -0
  13. package/dist/{chunk-KSPLKCVF.js → chunk-NKBZIXCN.js} +7 -6
  14. package/dist/chunk-NKBZIXCN.js.map +1 -0
  15. package/dist/{chunk-RU7BOXR3.js → chunk-P2F6HU3P.js} +4 -3
  16. package/dist/chunk-P2F6HU3P.js.map +1 -0
  17. package/dist/{chunk-BJS6XH2V.js → chunk-QAKFE3NE.js} +4 -3
  18. package/dist/chunk-QAKFE3NE.js.map +1 -0
  19. package/dist/{chunk-WALLF2AH.js → chunk-YOVA65PS.js} +4 -3
  20. package/dist/chunk-YOVA65PS.js.map +1 -0
  21. package/dist/{dist-FBRR6YHP.js → dist-7UKXVKH3.js} +5 -4
  22. package/dist/{dist-7GPVXUEA.js.map → dist-7UKXVKH3.js.map} +1 -1
  23. package/dist/{dist-7GPVXUEA.js → dist-JX77JABV.js} +5 -4
  24. package/dist/{dist-DVM64QIS.js.map → dist-JX77JABV.js.map} +1 -1
  25. package/dist/{dist-DVM64QIS.js → dist-WKLJSPJT.js} +8 -7
  26. package/dist/{dist-FBRR6YHP.js.map → dist-WKLJSPJT.js.map} +1 -1
  27. package/dist/index.js +19 -18
  28. package/dist/index.js.map +1 -1
  29. package/dist/{secure-credentials-YKZHAZNB.js → secure-credentials-6UMEU22H.js} +4 -3
  30. package/dist/secure-credentials-6UMEU22H.js.map +1 -0
  31. package/dist/{snapback-dir-4QRR2IPV.js → snapback-dir-T3CRQRY6.js} +6 -5
  32. package/dist/{snapback-dir-4QRR2IPV.js.map → snapback-dir-T3CRQRY6.js.map} +1 -1
  33. package/package.json +5 -4
  34. package/dist/SkippedTestDetector-JY4EF5BN.js +0 -4
  35. package/dist/chunk-BCIXMIPW.js.map +0 -1
  36. package/dist/chunk-BJS6XH2V.js.map +0 -1
  37. package/dist/chunk-KSPLKCVF.js.map +0 -1
  38. package/dist/chunk-MTQ6ESQR.js.map +0 -1
  39. package/dist/chunk-RU7BOXR3.js.map +0 -1
  40. package/dist/chunk-VSJ33PLA.js.map +0 -1
  41. package/dist/chunk-WALLF2AH.js.map +0 -1
  42. package/dist/secure-credentials-YKZHAZNB.js.map +0 -1
@@ -1,4 +1,5 @@
1
- import { __name } from './chunk-WCQVDF3K.js';
1
+ #!/usr/bin/env node
2
+ import { __name } from './chunk-BW7RALUZ.js';
2
3
  import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from 'crypto';
3
4
  import { unlink, readFile, mkdir, writeFile } from 'fs/promises';
4
5
  import { homedir, hostname, platform, userInfo } from 'os';
@@ -253,5 +254,5 @@ async function isLoggedInSecure() {
253
254
  __name(isLoggedInSecure, "isLoggedInSecure");
254
255
 
255
256
  export { SecureCredentialsManager, clearCredentialsSecure, getCredentialsSecure, getSecureCredentials, isLoggedInSecure, saveCredentialsSecure };
256
- //# sourceMappingURL=secure-credentials-YKZHAZNB.js.map
257
- //# sourceMappingURL=secure-credentials-YKZHAZNB.js.map
257
+ //# sourceMappingURL=secure-credentials-6UMEU22H.js.map
258
+ //# sourceMappingURL=secure-credentials-6UMEU22H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/secure-credentials.ts"],"names":["SERVICE_NAME","ACCOUNT_NAME","ENCRYPTION_ALGORITHM","KEY_LENGTH","IV_LENGTH","AUTH_TAG_LENGTH","SALT_LENGTH","GLOBAL_DIR","join","homedir","CREDENTIALS_FILE","ENCRYPTED_CREDENTIALS_FILE","createKeytarProvider","keytar","name","isAvailable","getPassword","service","account","setPassword","password","deletePassword","deriveMachineKey","salt","machineData","hostname","platform","userInfo","username","process","arch","scryptSync","encryptCredentials","credentials","key","iv","randomBytes","cipher","createCipheriv","plaintext","JSON","stringify","encrypted","Buffer","concat","update","final","authTag","getAuthTag","decryptCredentials","data","subarray","decipher","createDecipheriv","setAuthTag","decrypted","parse","toString","createEncryptedFileProvider","_service","_account","readFile","mkdir","dirname","recursive","writeFile","unlink","SecureCredentialsManager","provider","initialized","initialize","keytarProvider","getProviderName","getCredentials","stored","legacy","getLegacyCredentials","setCredentials","content","Error","clearCredentials","isLoggedIn","accessToken","expiresAt","Date","secureCredentialsManager","getSecureCredentials","getCredentialsSecure","saveCredentialsSecure","clearCredentialsSecure","isLoggedInSecure"],"mappings":"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","file":"secure-credentials-6UMEU22H.js","sourcesContent":["/**\n * Secure Credentials Storage for SnapBack CLI\n *\n * FIX 4: Implements OS keychain storage with file fallback\n *\n * Security Hierarchy:\n * 1. OS Keychain (macOS Keychain, Windows Credential Manager, Linux Secret Service)\n * 2. Encrypted file fallback (AES-256-GCM with machine-derived key)\n * 3. Plain text fallback (development only, with warning)\n *\n * @module services/secure-credentials\n */\n\nimport { createCipheriv, createDecipheriv, randomBytes, scryptSync } from \"node:crypto\";\nimport { mkdir, readFile, unlink, writeFile } from \"node:fs/promises\";\nimport { homedir, hostname, platform, userInfo } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { GlobalCredentials } from \"./snapback-dir\";\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst SERVICE_NAME = \"snapback-cli\";\nconst ACCOUNT_NAME = \"default\";\nconst ENCRYPTION_ALGORITHM = \"aes-256-gcm\";\nconst KEY_LENGTH = 32;\nconst IV_LENGTH = 12;\nconst AUTH_TAG_LENGTH = 16;\nconst SALT_LENGTH = 32;\n\n// File paths\nconst GLOBAL_DIR = join(homedir(), \".snapback\");\nconst CREDENTIALS_FILE = join(GLOBAL_DIR, \"credentials.json\");\nconst ENCRYPTED_CREDENTIALS_FILE = join(GLOBAL_DIR, \"credentials.enc\");\n\n// =============================================================================\n// KEYCHAIN INTERFACE\n// =============================================================================\n\n/**\n * Keychain abstraction interface\n * Allows for different implementations based on availability\n */\ninterface KeychainProvider {\n\tname: string;\n\tisAvailable(): Promise<boolean>;\n\tgetPassword(service: string, account: string): Promise<string | null>;\n\tsetPassword(service: string, account: string, password: string): Promise<void>;\n\tdeletePassword(service: string, account: string): Promise<boolean>;\n}\n\n// =============================================================================\n// KEYTAR PROVIDER (OS KEYCHAIN)\n// =============================================================================\n\n/**\n * Keytar-based keychain provider\n * Uses OS-level secure credential storage\n */\nasync function createKeytarProvider(): Promise<KeychainProvider | null> {\n\ttry {\n\t\t// Dynamic import to handle missing keytar gracefully\n\t\t// @ts-expect-error - keytar is optional dependency\n\t\tconst keytar = await import(\"keytar\");\n\n\t\treturn {\n\t\t\tname: \"keytar\",\n\t\t\tasync isAvailable(): Promise<boolean> {\n\t\t\t\ttry {\n\t\t\t\t\t// Test availability by trying a no-op operation\n\t\t\t\t\tawait keytar.getPassword(\"__snapback_test__\", \"__test__\");\n\t\t\t\t\treturn true;\n\t\t\t\t} catch {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tasync getPassword(service: string, account: string): Promise<string | null> {\n\t\t\t\treturn keytar.getPassword(service, account);\n\t\t\t},\n\t\t\tasync setPassword(service: string, account: string, password: string): Promise<void> {\n\t\t\t\tawait keytar.setPassword(service, account, password);\n\t\t\t},\n\t\t\tasync deletePassword(service: string, account: string): Promise<boolean> {\n\t\t\t\treturn keytar.deletePassword(service, account);\n\t\t\t},\n\t\t};\n\t} catch {\n\t\t// keytar not available (not installed or native module issues)\n\t\treturn null;\n\t}\n}\n\n// =============================================================================\n// ENCRYPTED FILE PROVIDER\n// =============================================================================\n\n/**\n * Derive an encryption key from machine-specific data\n * This provides defense-in-depth even if the file is copied to another machine\n */\nfunction deriveMachineKey(salt: Buffer): Buffer {\n\t// Combine machine-specific values for key derivation\n\tconst machineData = [\n\t\thostname(),\n\t\tplatform(),\n\t\tuserInfo().username,\n\t\thomedir(),\n\t\t// Add some entropy from process info\n\t\tprocess.arch,\n\t\tprocess.platform,\n\t].join(\"|\");\n\n\treturn scryptSync(machineData, salt, KEY_LENGTH);\n}\n\n/**\n * Encrypt credentials with machine-derived key\n */\nfunction encryptCredentials(credentials: GlobalCredentials, salt: Buffer): Buffer {\n\tconst key = deriveMachineKey(salt);\n\tconst iv = randomBytes(IV_LENGTH);\n\tconst cipher = createCipheriv(ENCRYPTION_ALGORITHM, key, iv);\n\n\tconst plaintext = JSON.stringify(credentials);\n\tconst encrypted = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n\tconst authTag = cipher.getAuthTag();\n\n\t// Format: salt (32) + iv (12) + authTag (16) + encrypted data\n\treturn Buffer.concat([salt, iv, authTag, encrypted]);\n}\n\n/**\n * Decrypt credentials with machine-derived key\n */\nfunction decryptCredentials(data: Buffer): GlobalCredentials {\n\t// Extract components\n\tconst salt = data.subarray(0, SALT_LENGTH);\n\tconst iv = data.subarray(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);\n\tconst authTag = data.subarray(SALT_LENGTH + IV_LENGTH, SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\tconst encrypted = data.subarray(SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH);\n\n\tconst key = deriveMachineKey(salt);\n\tconst decipher = createDecipheriv(ENCRYPTION_ALGORITHM, key, iv);\n\tdecipher.setAuthTag(authTag);\n\n\tconst decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\n\treturn JSON.parse(decrypted.toString(\"utf8\")) as GlobalCredentials;\n}\n\n/**\n * Encrypted file provider\n * Uses AES-256-GCM with machine-derived key\n */\nfunction createEncryptedFileProvider(): KeychainProvider {\n\treturn {\n\t\tname: \"encrypted-file\",\n\t\tasync isAvailable(): Promise<boolean> {\n\t\t\treturn true; // Always available as fallback\n\t\t},\n\t\tasync getPassword(_service: string, _account: string): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst data = await readFile(ENCRYPTED_CREDENTIALS_FILE);\n\t\t\t\tconst credentials = decryptCredentials(data);\n\t\t\t\treturn JSON.stringify(credentials);\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync setPassword(_service: string, _account: string, password: string): Promise<void> {\n\t\t\tconst credentials = JSON.parse(password) as GlobalCredentials;\n\t\t\tconst salt = randomBytes(SALT_LENGTH);\n\t\t\tconst encrypted = encryptCredentials(credentials, salt);\n\n\t\t\tawait mkdir(dirname(ENCRYPTED_CREDENTIALS_FILE), { recursive: true });\n\t\t\tawait writeFile(ENCRYPTED_CREDENTIALS_FILE, encrypted);\n\t\t},\n\t\tasync deletePassword(_service: string, _account: string): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait unlink(ENCRYPTED_CREDENTIALS_FILE);\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t};\n}\n\n// =============================================================================\n// PLAIN FILE PROVIDER (DEVELOPMENT FALLBACK)\n// =============================================================================\n\n/**\n * Plain file provider (legacy, development only)\n * Shows warning when used in production\n * @deprecated Use encrypted file provider instead\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction _createPlainFileProvider(): KeychainProvider {\n\tlet warningShown = false;\n\n\treturn {\n\t\tname: \"plain-file\",\n\t\tasync isAvailable(): Promise<boolean> {\n\t\t\treturn true;\n\t\t},\n\t\tasync getPassword(_service: string, _account: string): Promise<string | null> {\n\t\t\ttry {\n\t\t\t\tconst content = await readFile(CREDENTIALS_FILE, \"utf8\");\n\t\t\t\treturn content;\n\t\t\t} catch {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t},\n\t\tasync setPassword(_service: string, _account: string, password: string): Promise<void> {\n\t\t\tif (process.env.NODE_ENV === \"production\" && !warningShown) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t\"\\n⚠️ Warning: Storing credentials in plain text. \" +\n\t\t\t\t\t\t\"Install 'keytar' for OS keychain support: pnpm add keytar\\n\",\n\t\t\t\t);\n\t\t\t\twarningShown = true;\n\t\t\t}\n\n\t\t\tawait mkdir(dirname(CREDENTIALS_FILE), { recursive: true });\n\t\t\tawait writeFile(CREDENTIALS_FILE, password, { mode: 0o600 }); // Restrict file permissions\n\t\t},\n\t\tasync deletePassword(_service: string, _account: string): Promise<boolean> {\n\t\t\ttry {\n\t\t\t\tawait unlink(CREDENTIALS_FILE);\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t};\n}\n\n// =============================================================================\n// SECURE CREDENTIALS MANAGER\n// =============================================================================\n\n/**\n * Secure Credentials Manager\n *\n * Automatically selects the most secure available storage:\n * 1. OS Keychain (via keytar)\n * 2. Encrypted file\n * 3. Plain file (with warning)\n */\nclass SecureCredentialsManager {\n\tprivate provider: KeychainProvider | null = null;\n\tprivate initialized = false;\n\n\t/**\n\t * Initialize the credentials manager\n\t * Selects the best available provider\n\t */\n\tasync initialize(): Promise<void> {\n\t\tif (this.initialized) return;\n\n\t\t// Try keytar first (OS keychain)\n\t\tconst keytarProvider = await createKeytarProvider();\n\t\tif (keytarProvider && (await keytarProvider.isAvailable())) {\n\t\t\tthis.provider = keytarProvider;\n\t\t\tthis.initialized = true;\n\t\t\treturn;\n\t\t}\n\n\t\t// Fall back to encrypted file\n\t\tthis.provider = createEncryptedFileProvider();\n\t\tthis.initialized = true;\n\t}\n\n\t/**\n\t * Get the name of the active provider\n\t */\n\tgetProviderName(): string {\n\t\treturn this.provider?.name ?? \"none\";\n\t}\n\n\t/**\n\t * Get stored credentials\n\t */\n\tasync getCredentials(): Promise<GlobalCredentials | null> {\n\t\tawait this.initialize();\n\t\tif (!this.provider) return null;\n\n\t\tconst stored = await this.provider.getPassword(SERVICE_NAME, ACCOUNT_NAME);\n\t\tif (!stored) {\n\t\t\t// Check legacy plain file as migration fallback\n\t\t\tconst legacy = await this.getLegacyCredentials();\n\t\t\tif (legacy) {\n\t\t\t\t// Migrate to secure storage\n\t\t\t\tawait this.setCredentials(legacy);\n\t\t\t\t// Delete legacy file after successful migration\n\t\t\t\ttry {\n\t\t\t\t\tawait unlink(CREDENTIALS_FILE);\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore if file doesn't exist\n\t\t\t\t}\n\t\t\t\treturn legacy;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\n\t\ttry {\n\t\t\treturn JSON.parse(stored) as GlobalCredentials;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Get legacy plain-text credentials for migration\n\t */\n\tprivate async getLegacyCredentials(): Promise<GlobalCredentials | null> {\n\t\ttry {\n\t\t\tconst content = await readFile(CREDENTIALS_FILE, \"utf8\");\n\t\t\treturn JSON.parse(content) as GlobalCredentials;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t/**\n\t * Save credentials securely\n\t */\n\tasync setCredentials(credentials: GlobalCredentials): Promise<void> {\n\t\tawait this.initialize();\n\t\tif (!this.provider) {\n\t\t\tthrow new Error(\"No credentials provider available\");\n\t\t}\n\n\t\tawait this.provider.setPassword(SERVICE_NAME, ACCOUNT_NAME, JSON.stringify(credentials));\n\t}\n\n\t/**\n\t * Clear stored credentials\n\t */\n\tasync clearCredentials(): Promise<void> {\n\t\tawait this.initialize();\n\t\tif (!this.provider) return;\n\n\t\tawait this.provider.deletePassword(SERVICE_NAME, ACCOUNT_NAME);\n\n\t\t// Also clean up any legacy files\n\t\ttry {\n\t\t\tawait unlink(CREDENTIALS_FILE);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t\ttry {\n\t\t\tawait unlink(ENCRYPTED_CREDENTIALS_FILE);\n\t\t} catch {\n\t\t\t// Ignore\n\t\t}\n\t}\n\n\t/**\n\t * Check if user is logged in\n\t */\n\tasync isLoggedIn(): Promise<boolean> {\n\t\tconst credentials = await this.getCredentials();\n\t\tif (!credentials?.accessToken) return false;\n\n\t\t// Check if token is expired\n\t\tif (credentials.expiresAt) {\n\t\t\tconst expiresAt = new Date(credentials.expiresAt);\n\t\t\tif (expiresAt < new Date()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\n// Singleton instance\nlet secureCredentialsManager: SecureCredentialsManager | null = null;\n\n/**\n * Get the secure credentials manager singleton\n */\nexport function getSecureCredentials(): SecureCredentialsManager {\n\tif (!secureCredentialsManager) {\n\t\tsecureCredentialsManager = new SecureCredentialsManager();\n\t}\n\treturn secureCredentialsManager;\n}\n\n/**\n * Secure versions of credential functions (drop-in replacements)\n */\nexport async function getCredentialsSecure(): Promise<GlobalCredentials | null> {\n\treturn getSecureCredentials().getCredentials();\n}\n\nexport async function saveCredentialsSecure(credentials: GlobalCredentials): Promise<void> {\n\treturn getSecureCredentials().setCredentials(credentials);\n}\n\nexport async function clearCredentialsSecure(): Promise<void> {\n\treturn getSecureCredentials().clearCredentials();\n}\n\nexport async function isLoggedInSecure(): Promise<boolean> {\n\treturn getSecureCredentials().isLoggedIn();\n}\n\nexport { SecureCredentialsManager };\n"]}
@@ -1,5 +1,6 @@
1
- export { appendSnapbackJsonl, clearCredentials, createGlobalDirectory, createSnapbackDirectory, deleteGlobalJson, endCurrentSession, findWorkspaceRoot, getCredentials, getCurrentSession, getGlobalConfig, getGlobalDir, getGlobalPath, getLearnings, getProtectedFiles, getStats, getViolations, getWorkspaceConfig, getWorkspaceDir, getWorkspacePath, getWorkspaceVitals, isLoggedIn, isSnapbackInitialized, loadSnapbackJsonl, pathExists, readGlobalJson, readSnapbackJson, recordLearning, recordViolation, saveCredentials, saveCurrentSession, saveGlobalConfig, saveProtectedFiles, saveWorkspaceConfig, saveWorkspaceVitals, writeGlobalJson, writeSnapbackJson } from './chunk-KSPLKCVF.js';
2
- export { generateId } from './chunk-BCIXMIPW.js';
3
- import './chunk-WCQVDF3K.js';
4
- //# sourceMappingURL=snapback-dir-4QRR2IPV.js.map
5
- //# sourceMappingURL=snapback-dir-4QRR2IPV.js.map
1
+ #!/usr/bin/env node
2
+ export { appendSnapbackJsonl, clearCredentials, createGlobalDirectory, createSnapbackDirectory, deleteGlobalJson, endCurrentSession, findWorkspaceRoot, getCredentials, getCurrentSession, getGlobalConfig, getGlobalDir, getGlobalPath, getLearnings, getProtectedFiles, getStats, getViolations, getWorkspaceConfig, getWorkspaceDir, getWorkspacePath, getWorkspaceVitals, isLoggedIn, isSnapbackInitialized, loadSnapbackJsonl, pathExists, readGlobalJson, readSnapbackJson, recordLearning, recordViolation, saveCredentials, saveCurrentSession, saveGlobalConfig, saveProtectedFiles, saveWorkspaceConfig, saveWorkspaceVitals, writeGlobalJson, writeSnapbackJson } from './chunk-NKBZIXCN.js';
3
+ export { generateId } from './chunk-6MR2TINI.js';
4
+ import './chunk-BW7RALUZ.js';
5
+ //# sourceMappingURL=snapback-dir-T3CRQRY6.js.map
6
+ //# sourceMappingURL=snapback-dir-T3CRQRY6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"snapback-dir-4QRR2IPV.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"snapback-dir-T3CRQRY6.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapback/cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "CLI tool for managing SnapBack snapshots and file protection",
5
5
  "homepage": "https://snapback.dev",
6
6
  "repository": {
@@ -42,6 +42,7 @@
42
42
  "conf": "13.0.1",
43
43
  "esprima": "4.0.1",
44
44
  "execa": "9.5.2",
45
+ "ky": "1.7.2",
45
46
  "log-update": "6.1.0",
46
47
  "ora": "5.4.1",
47
48
  "zod": "3.25.76"
@@ -53,12 +54,12 @@
53
54
  "typescript": "5.9.2",
54
55
  "vitest": "3.2.4",
55
56
  "@snapback/contracts": "0.2.1",
56
- "@snapback/intelligence": "0.1.0",
57
- "@snapback/engine": "0.1.0",
57
+ "@snapback/core": "0.2.0",
58
58
  "@snapback/core-runtime": "0.1.0",
59
+ "@snapback/intelligence": "0.1.0",
59
60
  "@snapback/mcp": "0.1.0",
61
+ "@snapback/engine": "0.1.0",
60
62
  "@snapback/sdk": "0.2.0",
61
- "@snapback/core": "0.2.0",
62
63
  "@snapback/mcp-config": "1.0.0"
63
64
  },
64
65
  "publishConfig": {
@@ -1,4 +0,0 @@
1
- export { analyzeSkippedTests, detectSkippedTests, getSkippedTestSummary } from './chunk-BJS6XH2V.js';
2
- import './chunk-WCQVDF3K.js';
3
- //# sourceMappingURL=SkippedTestDetector-JY4EF5BN.js.map
4
- //# sourceMappingURL=SkippedTestDetector-JY4EF5BN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/contracts/dist/id-generator.js"],"names":["generateId","prefix","id","nanoid","slugify","description","maxLength","toLowerCase","trim","replace","slice","generateSnapshotId","length","slug","Date","now"],"mappings":";;;AAMO,SAASA,WAAWC,MAAAA,EAAM;AAC7B,EAAA,MAAMC,KAAKC,MAAAA,EAAAA;AACX,EAAA,OAAOF,MAAAA,GAAS,CAAA,EAAGA,MAAAA,CAAAA,CAAAA,EAAUC,EAAAA,CAAAA,CAAAA,GAAOA,EAAAA;AACxC;AAHgBF,MAAAA,CAAAA,UAAAA,EAAAA,YAAAA,CAAAA;AAQhB,SAASI,OAAAA,CAAQC,WAAAA,EAAaC,SAAAA,GAAY,EAAA,EAAE;AACxC,EAAA,OAAOD,WAAAA,CACFE,aAAW,CACXC,IAAAA,GACAC,OAAAA,CAAQ,eAAA,EAAiB,EAAA,CAAA,CACzBA,OAAAA,CAAQ,MAAA,EAAQ,GAAA,CAAA,CAChBA,OAAAA,CAAQ,KAAA,EAAO,GAAA,CAAA,CACfA,OAAAA,CAAQ,UAAU,EAAA,CAAA,CAClBC,KAAAA,CAAM,CAAA,EAAGJ,SAAAA,CAAAA;AAClB;AATSF,MAAAA,CAAAA,OAAAA,EAAAA,SAAAA,CAAAA;AAiBF,SAASO,mBAAmBN,WAAAA,EAAW;AAC1C,EAAA,IAAIA,WAAAA,IAAeA,WAAAA,CAAYO,MAAAA,GAAS,CAAA,EAAG;AACvC,IAAA,MAAMC,IAAAA,GAAOT,QAAQC,WAAAA,CAAAA;AACrB,IAAA,IAAIQ,IAAAA,CAAKD,SAAS,CAAA,EAAG;AACjB,MAAA,OAAO,CAAA,SAAA,EAAYC,IAAAA,CAAAA,CAAAA,EAAQC,IAAAA,CAAKC,KAAG,CAAA,CAAA,EAAMZ,MAAAA,CAAO,CAAA,CAAA,CAAA,CAAA;AACpD,IAAA;AACJ,EAAA;AACA,EAAA,OAAO,YAAYW,IAAAA,CAAKC,GAAAA,EAAG,CAAA,CAAA,EAAMZ,MAAAA,CAAO,CAAA,CAAA,CAAA,CAAA;AAC5C;AARgBQ,MAAAA,CAAAA,kBAAAA,EAAAA,oBAAAA,CAAAA","file":"chunk-BCIXMIPW.js","sourcesContent":["import { nanoid } from \"nanoid\";\n/**\n * Generate a unique ID with optional prefix\n * @param prefix Optional prefix for the ID (e.g., 'user', 'session')\n * @returns Unique ID string\n */\nexport function generateId(prefix) {\n const id = nanoid();\n return prefix ? `${prefix}-${id}` : id;\n}\n/**\n * Slugify a description for use in snapshot IDs\n * Converts \"Before fixing auth flow\" to \"before-fixing-auth-flow\"\n */\nfunction slugify(description, maxLength = 30) {\n return description\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, \"\") // Remove non-alphanumeric\n .replace(/\\s+/g, \"-\") // Spaces to hyphens\n .replace(/-+/g, \"-\") // Collapse hyphens\n .replace(/^-|-$/g, \"\") // Trim hyphens\n .slice(0, maxLength);\n}\n/**\n * Generate a snapshot ID in the standard format\n * Format with description: snapshot-<slug>-<timestamp>-<random>\n * Format without: snapshot-<timestamp>-<random>\n * @param description Optional human-readable description\n * @returns Snapshot ID string\n */\nexport function generateSnapshotId(description) {\n if (description && description.length > 0) {\n const slug = slugify(description);\n if (slug.length > 0) {\n return `snapshot-${slug}-${Date.now()}-${nanoid(9)}`;\n }\n }\n return `snapshot-${Date.now()}-${nanoid(9)}`;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../packages/core/dist/analysis/static/SkippedTestDetector.js"],"names":["detectSkippedTests","code","filePath","skipped","visit","node","type","callee","property","name","object","testType","arguments","length","firstArg","value","quasis","raw","push","line","loc","start","column","file","key","Object","keys","Array","isArray","item","ast","parse","sourceType","plugins","errorRecovery","program","parsed","error","Error","message","String","analyzeSkippedTests","files","results","content","includes","getSkippedTestSummary","summary","totalSkipped","byType","describe","it","test","filesWithSkipped","result"],"mappings":";;;AA4BO,SAASA,kBAAAA,CAAmBC,MAAMC,QAAAA,EAAQ;AAC7C,EAAA,MAAMC,UAAU,EAAA;AAChB,EAAA,IAAI;AAOA,IAAA,IAASC,MAAAA,GAAT,SAAeC,IAAAA,EAAI;AACf,MAAA,IAAIA,IAAAA,CAAKC,SAAS,gBAAA,EAAkB;AAChC,QAAA,MAAMC,SAASF,IAAAA,CAAKE,MAAAA;AAEpB,QAAA,IAAIA,MAAAA,CAAOD,IAAAA,KAAS,kBAAA,IAChBC,MAAAA,CAAOC,SAASF,IAAAA,KAAS,YAAA,IACzBC,MAAAA,CAAOC,QAAAA,CAASC,IAAAA,KAAS,MAAA,IACzBF,MAAAA,CAAOG,MAAAA,CAAOJ,SAAS,YAAA,EAAc;AACrC,UAAA,MAAMK,QAAAA,GAAWJ,OAAOG,MAAAA,CAAOD,IAAAA;AAC/B,UAAA,IAAIE,QAAAA,KAAa,UAAA,IAAcA,QAAAA,KAAa,IAAA,IAAQA,aAAa,MAAA,EAAQ;AAErE,YAAA,IAAIF,IAAAA;AACJ,YAAA,IAAIJ,IAAAA,CAAKO,SAAAA,CAAUC,MAAAA,GAAS,CAAA,EAAG;AAC3B,cAAA,MAAMC,QAAAA,GAAWT,IAAAA,CAAKO,SAAAA,CAAU,CAAA,CAAA;AAChC,cAAA,IAAIE,QAAAA,CAASR,SAAS,eAAA,EAAiB;AACnCG,gBAAAA,IAAAA,GAAOK,QAAAA,CAASC,KAAAA;AACpB,cAAA,CAAA,MAAA,IACSD,SAASR,IAAAA,KAAS,iBAAA,IAAqBQ,QAAAA,CAASE,MAAAA,CAAOH,WAAW,CAAA,EAAG;AAC1EJ,gBAAAA,IAAAA,GAAOK,QAAAA,CAASE,MAAAA,CAAO,CAAA,CAAA,CAAGD,KAAAA,CAAME,GAAAA;AACpC,cAAA;AACJ,YAAA;AACAd,YAAAA,OAAAA,CAAQe,IAAAA,CAAK;cACTZ,IAAAA,EAAMK,QAAAA;AACNF,cAAAA,IAAAA;cACAU,IAAAA,EAAMd,IAAAA,CAAKe,GAAAA,EAAKC,KAAAA,CAAMF,IAAAA,IAAQ,CAAA;cAC9BG,MAAAA,EAAQjB,IAAAA,CAAKe,GAAAA,EAAKC,KAAAA,CAAMC,MAAAA,IAAU,CAAA;cAClCC,IAAAA,EAAMrB;aACV,CAAA;AACJ,UAAA;AACJ,QAAA;AAGJ,MAAA;AAEA,MAAA,KAAA,MAAWsB,GAAAA,IAAOC,MAAAA,CAAOC,IAAAA,CAAKrB,IAAAA,CAAAA,EAAO;AACjC,QAAA,MAAMU,KAAAA,GAAQV,KAAKmB,GAAAA,CAAAA;AACnB,QAAA,IAAIT,KAAAA,IAAS,OAAOA,KAAAA,KAAU,QAAA,EAAU;AACpC,UAAA,IAAIY,KAAAA,CAAMC,OAAAA,CAAQb,KAAAA,CAAAA,EAAQ;AACtB,YAAA,KAAA,MAAWc,QAAQd,KAAAA,EAAO;AACtB,cAAA,IAAIc,IAAAA,IAAQ,OAAOA,IAAAA,KAAS,QAAA,IAAY,UAAUA,IAAAA,EAAM;AACpDzB,gBAAAA,OAAMyB,IAAAA,CAAAA;AACV,cAAA;AACJ,YAAA;AACJ,UAAA,CAAA,MAAA,IACS,UAAUd,KAAAA,EAAO;AACtBX,YAAAA,OAAMW,KAAAA,CAAAA;AACV,UAAA;AACJ,QAAA;AACJ,MAAA;AACJ,IAAA,CAAA;AAjDSX,IAAAA,IAAAA,KAAAA,GAAAA,MAAAA;AAAAA,IAAAA,MAAAA,CAAAA,MAAAA,EAAAA,OAAAA,CAAAA;AANT,IAAA,MAAM0B,GAAAA,GAAMC,MAAM9B,IAAAA,EAAM;MACpB+B,UAAAA,EAAY,QAAA;MACZC,OAAAA,EAAS;AAAC,QAAA,YAAA;AAAc,QAAA;;MACxBC,aAAAA,EAAe;KACnB,CAAA;AAoDA9B,IAAAA,MAAAA,CAAM0B,IAAIK,OAAO,CAAA;AACjB,IAAA,OAAO;MAAEZ,IAAAA,EAAMrB,QAAAA;AAAUC,MAAAA,OAAAA;MAASiC,MAAAA,EAAQ;AAAK,KAAA;AACnD,EAAA,CAAA,CAAA,OACOC,KAAAA,EAAO;AACV,IAAA,OAAO;MACHd,IAAAA,EAAMrB,QAAAA;AACNC,MAAAA,OAAAA,EAAS,EAAA;MACTiC,MAAAA,EAAQ,KAAA;AACRC,MAAAA,KAAAA,EAAOA,KAAAA,YAAiBC,KAAAA,GAAQD,KAAAA,CAAME,OAAAA,GAAUC,OAAOH,KAAAA;AAC3D,KAAA;AACJ,EAAA;AACJ;AAtEgBrC,MAAAA,CAAAA,kBAAAA,EAAAA,oBAAAA,CAAAA;AA6ET,SAASyC,oBAAoBC,KAAAA,EAAK;AACrC,EAAA,MAAMC,UAAU,EAAA;AAChB,EAAA,KAAA,MAAW,CAACzC,QAAAA,EAAU0C,OAAAA,CAAAA,IAAYF,KAAAA,EAAO;AAErC,IAAA,IAAIxC,QAAAA,CAAS2C,QAAAA,CAAS,QAAA,CAAA,IAAa3C,QAAAA,CAAS2C,QAAAA,CAAS,QAAA,CAAA,IAAa3C,QAAAA,CAAS2C,QAAAA,CAAS,WAAA,CAAA,EAAc;AAC9FF,MAAAA,OAAAA,CAAQzB,IAAAA,CAAKlB,kBAAAA,CAAmB4C,OAAAA,EAAS1C,QAAAA,CAAAA,CAAAA;AAC7C,IAAA;AACJ,EAAA;AACA,EAAA,OAAOyC,OAAAA;AACX;AATgBF,MAAAA,CAAAA,mBAAAA,EAAAA,qBAAAA,CAAAA;AAaT,SAASK,sBAAsBH,OAAAA,EAAO;AACzC,EAAA,MAAMI,OAAAA,GAAU;IACZC,YAAAA,EAAc,CAAA;IACdC,MAAAA,EAAQ;MAAEC,QAAAA,EAAU,CAAA;MAAGC,EAAAA,EAAI,CAAA;MAAGC,IAAAA,EAAM;AAAE,KAAA;AACtCC,IAAAA,gBAAAA,EAAkB;AACtB,GAAA;AACA,EAAA,KAAA,MAAWC,UAAUX,OAAAA,EAAS;AAC1B,IAAA,IAAIW,MAAAA,CAAOnD,OAAAA,CAAQU,MAAAA,GAAS,CAAA,EAAG;AAC3BkC,MAAAA,OAAAA,CAAQM,gBAAAA,CAAiBnC,IAAAA,CAAKoC,MAAAA,CAAO/B,IAAI,CAAA;AACzCwB,MAAAA,OAAAA,CAAQC,YAAAA,IAAgBM,OAAOnD,OAAAA,CAAQU,MAAAA;AACvC,MAAA,KAAA,MAAWuC,IAAAA,IAAQE,OAAOnD,OAAAA,EAAS;AAC/B4C,QAAAA,OAAAA,CAAQE,MAAAA,CAAOG,KAAK9C,IAAI,CAAA,EAAA;AAC5B,MAAA;AACJ,IAAA;AACJ,EAAA;AACA,EAAA,OAAOyC,OAAAA;AACX;AAhBgBD,MAAAA,CAAAA,qBAAAA,EAAAA,uBAAAA,CAAAA","file":"chunk-BJS6XH2V.js","sourcesContent":["/**\n * Skipped Test Detector\n *\n * Uses @babel/parser to detect skipped tests (describe.skip, it.skip, test.skip)\n * in test files. This helps AI agents identify RED PHASE TDD tests that may\n * be ready to enable.\n *\n * @module analysis/static/SkippedTestDetector\n */\nimport { parse } from \"@babel/parser\";\n/**\n * Detect skipped tests in a file's source code\n *\n * @param code - Source code to analyze\n * @param filePath - Path to file (for error reporting)\n * @returns Detection result with skipped tests\n *\n * @example\n * ```typescript\n * const result = detectSkippedTests(`\n * describe.skip(\"Feature\", () => {\n * it(\"should work\", () => {});\n * });\n * `, \"feature.test.ts\");\n *\n * // result.skipped = [{ type: \"describe\", name: \"Feature\", line: 2, ... }]\n * ```\n */\nexport function detectSkippedTests(code, filePath) {\n const skipped = [];\n try {\n const ast = parse(code, {\n sourceType: \"module\",\n plugins: [\"typescript\", \"jsx\"],\n errorRecovery: true,\n });\n // Simple AST traversal without @babel/traverse (to avoid extra dependency in MCP)\n function visit(node) {\n if (node.type === \"CallExpression\") {\n const callee = node.callee;\n // Check for .skip pattern: describe.skip, it.skip, test.skip\n if (callee.type === \"MemberExpression\" &&\n callee.property.type === \"Identifier\" &&\n callee.property.name === \"skip\" &&\n callee.object.type === \"Identifier\") {\n const testType = callee.object.name;\n if (testType === \"describe\" || testType === \"it\" || testType === \"test\") {\n // Try to extract test name from first argument\n let name;\n if (node.arguments.length > 0) {\n const firstArg = node.arguments[0];\n if (firstArg.type === \"StringLiteral\") {\n name = firstArg.value;\n }\n else if (firstArg.type === \"TemplateLiteral\" && firstArg.quasis.length === 1) {\n name = firstArg.quasis[0].value.raw;\n }\n }\n skipped.push({\n type: testType,\n name,\n line: node.loc?.start.line ?? 0,\n column: node.loc?.start.column ?? 0,\n file: filePath,\n });\n }\n }\n // Also check for skip() as method call: describe(\"name\", () => {}).skip\n // This is less common but supported by some frameworks\n }\n // Recursively visit all properties that could contain nodes\n for (const key of Object.keys(node)) {\n const value = node[key];\n if (value && typeof value === \"object\") {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === \"object\" && \"type\" in item) {\n visit(item);\n }\n }\n }\n else if (\"type\" in value) {\n visit(value);\n }\n }\n }\n }\n visit(ast.program);\n return { file: filePath, skipped, parsed: true };\n }\n catch (error) {\n return {\n file: filePath,\n skipped: [],\n parsed: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n/**\n * Analyze multiple files for skipped tests\n *\n * @param files - Map of file path to content\n * @returns Array of results for each file\n */\nexport function analyzeSkippedTests(files) {\n const results = [];\n for (const [filePath, content] of files) {\n // Only analyze test files\n if (filePath.includes(\".test.\") || filePath.includes(\".spec.\") || filePath.includes(\"__tests__\")) {\n results.push(detectSkippedTests(content, filePath));\n }\n }\n return results;\n}\n/**\n * Get summary of skipped tests across all files\n */\nexport function getSkippedTestSummary(results) {\n const summary = {\n totalSkipped: 0,\n byType: { describe: 0, it: 0, test: 0 },\n filesWithSkipped: [],\n };\n for (const result of results) {\n if (result.skipped.length > 0) {\n summary.filesWithSkipped.push(result.file);\n summary.totalSkipped += result.skipped.length;\n for (const test of result.skipped) {\n summary.byType[test.type]++;\n }\n }\n }\n return summary;\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/services/snapback-dir.ts"],"names":["SNAPBACK_DIR","GLOBAL_SNAPBACK_DIR","getGlobalDir","join","homedir","getWorkspaceDir","workspaceRoot","process","cwd","getGlobalPath","relativePath","getWorkspacePath","createSnapbackDirectory","baseDir","dirs","dir","mkdir","recursive","gitignore","trim","writeFile","createGlobalDirectory","isSnapbackInitialized","configPath","access","constants","F_OK","isLoggedIn","credentials","readGlobalJson","accessToken","expiresAt","Date","readSnapbackJson","content","readFile","JSON","parse","writeSnapbackJson","data","fullPath","dirname","stringify","appendSnapbackJsonl","appendFile","loadSnapbackJsonl","split","filter","line","map","writeGlobalJson","deleteGlobalJson","unlink","getWorkspaceConfig","saveWorkspaceConfig","config","getWorkspaceVitals","saveWorkspaceVitals","vitals","getProtectedFiles","saveProtectedFiles","files","getCurrentSession","saveCurrentSession","session","endCurrentSession","recordLearning","learning","getLearnings","recordViolation","violation","getViolations","getCredentials","getCredentialsSecure","saveCredentials","saveCredentialsSecure","clearCredentials","clearCredentialsSecure","getGlobalConfig","saveGlobalConfig","findWorkspaceRoot","startDir","currentDir","maxDepth","depth","parentDir","pathExists","path","getStats","stats","stat","size","modifiedAt","mtime"],"mappings":"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","file":"chunk-KSPLKCVF.js","sourcesContent":["/**\n * SnapBack Directory Service\n *\n * Manages .snapback/ workspace directory and ~/.snapback/ global directory.\n * This is the foundation for CLI commands that need persistent storage.\n *\n * Storage Architecture:\n * - ~/.snapback/ (GLOBAL) - credentials, user config, MCP configs\n * - .snapback/ (WORKSPACE) - patterns, learnings, session, snapshots\n *\n * @see implementation_plan.md Section 1.3\n */\n\nimport { access, appendFile, constants, mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst SNAPBACK_DIR = \".snapback\";\nconst GLOBAL_SNAPBACK_DIR = \".snapback\";\n\n// =============================================================================\n// TYPE DEFINITIONS\n// =============================================================================\n\nexport interface WorkspaceConfig {\n\tworkspaceId?: string;\n\ttier?: \"free\" | \"pro\";\n\t/**\n\t * CLI protection preset - user-friendly abstraction layer.\n\t *\n\t * Maps to canonical ProtectionLevel values (@snapback/contracts):\n\t * - \"standard\" → \"watch\" (auto-snapshot, warn on risky changes)\n\t * - \"strict\" → \"block\" (confirmation required, block high-risk)\n\t *\n\t * The CLI uses presets for better UX, while internal operations\n\t * use the canonical \"watch\" | \"warn\" | \"block\" values.\n\t */\n\tprotectionLevel?: \"standard\" | \"strict\";\n\tsyncEnabled?: boolean;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface WorkspaceVitals {\n\tframework?: string;\n\tframeworkConfidence?: number;\n\tpackageManager?: \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\ttypescript?: {\n\t\tenabled: boolean;\n\t\tstrict?: boolean;\n\t\tversion?: string;\n\t};\n\tcriticalFiles?: string[];\n\tdetectedAt: string;\n}\n\nexport interface ProtectedFile {\n\tpattern: string;\n\taddedAt: string;\n\treason?: string;\n}\n\nexport interface SessionState {\n\tid: string;\n\ttask?: string;\n\tstartedAt: string;\n\tsnapshotCount: number;\n\tfilesModified?: number;\n}\n\nexport interface LearningEntry {\n\tid: string;\n\ttype: \"pattern\" | \"pitfall\" | \"efficiency\" | \"discovery\" | \"workflow\";\n\ttrigger: string;\n\taction: string;\n\tsource: string;\n\tcreatedAt: string;\n}\n\nexport interface ViolationEntry {\n\ttype: string;\n\tfile: string;\n\tmessage: string;\n\tcount?: number;\n\tdate: string;\n\tprevention?: string;\n}\n\nexport interface GlobalCredentials {\n\taccessToken: string;\n\trefreshToken?: string;\n\temail: string;\n\ttier: \"free\" | \"pro\";\n\texpiresAt?: string;\n}\n\nexport interface GlobalConfig {\n\tapiUrl?: string;\n\tdefaultWorkspace?: string;\n\tanalytics?: boolean;\n}\n\n// =============================================================================\n// PATH HELPERS\n// =============================================================================\n\n/**\n * Get global snapback directory path (~/.snapback/)\n */\nexport function getGlobalDir(): string {\n\treturn join(homedir(), GLOBAL_SNAPBACK_DIR);\n}\n\n/**\n * Get workspace snapback directory path\n */\nexport function getWorkspaceDir(workspaceRoot?: string): string {\n\treturn join(workspaceRoot || process.cwd(), SNAPBACK_DIR);\n}\n\n/**\n * Get path to a file in the global directory\n */\nexport function getGlobalPath(relativePath: string): string {\n\treturn join(getGlobalDir(), relativePath);\n}\n\n/**\n * Get path to a file in the workspace directory\n */\nexport function getWorkspacePath(relativePath: string, workspaceRoot?: string): string {\n\treturn join(getWorkspaceDir(workspaceRoot), relativePath);\n}\n\n// =============================================================================\n// DIRECTORY MANAGEMENT\n// =============================================================================\n\n/**\n * Create the .snapback/ directory structure in a workspace\n * Mirrors the structure expected by MCP server (context-tools.ts)\n */\nexport async function createSnapbackDirectory(workspaceRoot?: string): Promise<void> {\n\tconst baseDir = getWorkspaceDir(workspaceRoot);\n\n\tconst dirs = [\"\", \"patterns\", \"learnings\", \"session\", \"snapshots\"];\n\n\tfor (const dir of dirs) {\n\t\tawait mkdir(join(baseDir, dir), { recursive: true });\n\t}\n\n\t// Create .gitignore to exclude snapshots but keep patterns\n\tconst gitignore = `# SnapBack Directory\n# Ignore snapshot content (large binary data)\nsnapshots/\nembeddings.db\n\n# Keep these for team sharing\n!patterns/\n!learnings/\n!vitals.json\n!config.json\n!protected.json\n`.trim();\n\n\tawait writeFile(join(baseDir, \".gitignore\"), gitignore);\n}\n\n/**\n * Create the global ~/.snapback/ directory structure\n */\nexport async function createGlobalDirectory(): Promise<void> {\n\tconst baseDir = getGlobalDir();\n\n\tconst dirs = [\"\", \"cache\", \"mcp-configs\"];\n\n\tfor (const dir of dirs) {\n\t\tawait mkdir(join(baseDir, dir), { recursive: true });\n\t}\n}\n\n/**\n * Check if .snapback/ directory exists in workspace\n */\nexport async function isSnapbackInitialized(workspaceRoot?: string): Promise<boolean> {\n\ttry {\n\t\tconst configPath = getWorkspacePath(\"config.json\", workspaceRoot);\n\t\tawait access(configPath, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Check if user is logged in (has credentials)\n */\nexport async function isLoggedIn(): Promise<boolean> {\n\ttry {\n\t\tconst credentials = await readGlobalJson<GlobalCredentials>(\"credentials.json\");\n\t\tif (!credentials?.accessToken) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Check if token is expired\n\t\tif (credentials.expiresAt) {\n\t\t\tconst expiresAt = new Date(credentials.expiresAt);\n\t\t\tif (expiresAt < new Date()) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n// =============================================================================\n// JSON FILE OPERATIONS - WORKSPACE\n// =============================================================================\n\n/**\n * Read JSON file from .snapback/\n */\nexport async function readSnapbackJson<T>(relativePath: string, workspaceRoot?: string): Promise<T | null> {\n\ttry {\n\t\tconst content = await readFile(getWorkspacePath(relativePath, workspaceRoot), \"utf-8\");\n\t\treturn JSON.parse(content) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Write JSON file to .snapback/\n */\nexport async function writeSnapbackJson<T>(relativePath: string, data: T, workspaceRoot?: string): Promise<void> {\n\tconst fullPath = getWorkspacePath(relativePath, workspaceRoot);\n\tawait mkdir(dirname(fullPath), { recursive: true });\n\tawait writeFile(fullPath, JSON.stringify(data, null, 2));\n}\n\n/**\n * Append to JSONL file in .snapback/\n */\nexport async function appendSnapbackJsonl<T extends object>(\n\trelativePath: string,\n\tdata: T,\n\tworkspaceRoot?: string,\n): Promise<void> {\n\tconst fullPath = getWorkspacePath(relativePath, workspaceRoot);\n\tawait mkdir(dirname(fullPath), { recursive: true });\n\tawait appendFile(fullPath, `${JSON.stringify(data)}\\n`);\n}\n\n/**\n * Load JSONL file from .snapback/\n */\nexport async function loadSnapbackJsonl<T>(relativePath: string, workspaceRoot?: string): Promise<T[]> {\n\ttry {\n\t\tconst content = await readFile(getWorkspacePath(relativePath, workspaceRoot), \"utf-8\");\n\t\treturn content\n\t\t\t.split(\"\\n\")\n\t\t\t.filter((line) => line.trim())\n\t\t\t.map((line) => JSON.parse(line) as T);\n\t} catch {\n\t\treturn [];\n\t}\n}\n\n// =============================================================================\n// JSON FILE OPERATIONS - GLOBAL\n// =============================================================================\n\n/**\n * Read JSON file from ~/.snapback/\n */\nexport async function readGlobalJson<T>(relativePath: string): Promise<T | null> {\n\ttry {\n\t\tconst content = await readFile(getGlobalPath(relativePath), \"utf-8\");\n\t\treturn JSON.parse(content) as T;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Write JSON file to ~/.snapback/\n */\nexport async function writeGlobalJson<T>(relativePath: string, data: T): Promise<void> {\n\tconst fullPath = getGlobalPath(relativePath);\n\tawait mkdir(dirname(fullPath), { recursive: true });\n\tawait writeFile(fullPath, JSON.stringify(data, null, 2));\n}\n\n/**\n * Delete JSON file from ~/.snapback/\n */\nexport async function deleteGlobalJson(relativePath: string): Promise<void> {\n\tconst fullPath = getGlobalPath(relativePath);\n\ttry {\n\t\tconst { unlink } = await import(\"node:fs/promises\");\n\t\tawait unlink(fullPath);\n\t} catch {\n\t\t// File doesn't exist, that's fine\n\t}\n}\n\n// =============================================================================\n// TYPED ACCESSORS\n// =============================================================================\n\n/**\n * Get workspace configuration\n */\nexport async function getWorkspaceConfig(workspaceRoot?: string): Promise<WorkspaceConfig | null> {\n\treturn readSnapbackJson<WorkspaceConfig>(\"config.json\", workspaceRoot);\n}\n\n/**\n * Save workspace configuration\n */\nexport async function saveWorkspaceConfig(config: WorkspaceConfig, workspaceRoot?: string): Promise<void> {\n\tawait writeSnapbackJson(\"config.json\", config, workspaceRoot);\n}\n\n/**\n * Get workspace vitals\n */\nexport async function getWorkspaceVitals(workspaceRoot?: string): Promise<WorkspaceVitals | null> {\n\treturn readSnapbackJson<WorkspaceVitals>(\"vitals.json\", workspaceRoot);\n}\n\n/**\n * Save workspace vitals\n */\nexport async function saveWorkspaceVitals(vitals: WorkspaceVitals, workspaceRoot?: string): Promise<void> {\n\tawait writeSnapbackJson(\"vitals.json\", vitals, workspaceRoot);\n}\n\n/**\n * Get protected files list\n */\nexport async function getProtectedFiles(workspaceRoot?: string): Promise<ProtectedFile[]> {\n\treturn (await readSnapbackJson<ProtectedFile[]>(\"protected.json\", workspaceRoot)) ?? [];\n}\n\n/**\n * Save protected files list\n */\nexport async function saveProtectedFiles(files: ProtectedFile[], workspaceRoot?: string): Promise<void> {\n\tawait writeSnapbackJson(\"protected.json\", files, workspaceRoot);\n}\n\n/**\n * Get current session state\n */\nexport async function getCurrentSession(workspaceRoot?: string): Promise<SessionState | null> {\n\treturn readSnapbackJson<SessionState>(\"session/current.json\", workspaceRoot);\n}\n\n/**\n * Save current session state\n */\nexport async function saveCurrentSession(session: SessionState, workspaceRoot?: string): Promise<void> {\n\tawait writeSnapbackJson(\"session/current.json\", session, workspaceRoot);\n}\n\n/**\n * End current session (delete current.json)\n */\nexport async function endCurrentSession(workspaceRoot?: string): Promise<void> {\n\tconst fullPath = getWorkspacePath(\"session/current.json\", workspaceRoot);\n\ttry {\n\t\tconst { unlink } = await import(\"node:fs/promises\");\n\t\tawait unlink(fullPath);\n\t} catch {\n\t\t// File doesn't exist, that's fine\n\t}\n}\n\n/**\n * Record a learning\n */\nexport async function recordLearning(learning: LearningEntry, workspaceRoot?: string): Promise<void> {\n\tawait appendSnapbackJsonl(\"learnings/user-learnings.jsonl\", learning, workspaceRoot);\n}\n\n/**\n * Get all learnings\n */\nexport async function getLearnings(workspaceRoot?: string): Promise<LearningEntry[]> {\n\treturn loadSnapbackJsonl<LearningEntry>(\"learnings/user-learnings.jsonl\", workspaceRoot);\n}\n\n/**\n * Record a violation\n */\nexport async function recordViolation(violation: ViolationEntry, workspaceRoot?: string): Promise<void> {\n\tawait appendSnapbackJsonl(\"patterns/violations.jsonl\", violation, workspaceRoot);\n}\n\n/**\n * Get all violations\n */\nexport async function getViolations(workspaceRoot?: string): Promise<ViolationEntry[]> {\n\treturn loadSnapbackJsonl<ViolationEntry>(\"patterns/violations.jsonl\", workspaceRoot);\n}\n\n/**\n * Get credentials\n * @deprecated Use getCredentialsSecure from secure-credentials.ts for production\n */\nexport async function getCredentials(): Promise<GlobalCredentials | null> {\n\t// Try secure credentials first, fall back to legacy\n\ttry {\n\t\tconst { getCredentialsSecure } = await import(\"./secure-credentials\");\n\t\treturn await getCredentialsSecure();\n\t} catch {\n\t\t// Fallback to legacy plain text (development mode)\n\t\treturn readGlobalJson<GlobalCredentials>(\"credentials.json\");\n\t}\n}\n\n/**\n * Save credentials\n * @deprecated Use saveCredentialsSecure from secure-credentials.ts for production\n */\nexport async function saveCredentials(credentials: GlobalCredentials): Promise<void> {\n\t// Try secure credentials first, fall back to legacy\n\ttry {\n\t\tconst { saveCredentialsSecure } = await import(\"./secure-credentials\");\n\t\treturn await saveCredentialsSecure(credentials);\n\t} catch {\n\t\t// Fallback to legacy plain text (development mode)\n\t\tawait createGlobalDirectory();\n\t\tawait writeGlobalJson(\"credentials.json\", credentials);\n\t}\n}\n\n/**\n * Clear credentials (logout)\n * @deprecated Use clearCredentialsSecure from secure-credentials.ts for production\n */\nexport async function clearCredentials(): Promise<void> {\n\t// Try secure credentials first, fall back to legacy\n\ttry {\n\t\tconst { clearCredentialsSecure } = await import(\"./secure-credentials\");\n\t\treturn await clearCredentialsSecure();\n\t} catch {\n\t\t// Fallback to legacy plain text (development mode)\n\t\tawait deleteGlobalJson(\"credentials.json\");\n\t}\n}\n\n/**\n * Get global config\n */\nexport async function getGlobalConfig(): Promise<GlobalConfig | null> {\n\treturn readGlobalJson<GlobalConfig>(\"config.json\");\n}\n\n/**\n * Save global config\n */\nexport async function saveGlobalConfig(config: GlobalConfig): Promise<void> {\n\tawait createGlobalDirectory();\n\tawait writeGlobalJson(\"config.json\", config);\n}\n\n// =============================================================================\n// UTILITY FUNCTIONS\n// =============================================================================\n\n// Re-export generateId from @snapback/contracts for backwards compatibility\nexport { generateId } from \"@snapback/contracts/id-generator\";\n\n/**\n * Get workspace root by searching for .snapback/ or package.json\n */\nexport async function findWorkspaceRoot(startDir?: string): Promise<string | null> {\n\tlet currentDir = startDir || process.cwd();\n\n\t// Limit search depth to prevent infinite loops\n\tconst maxDepth = 10;\n\tlet depth = 0;\n\n\twhile (depth < maxDepth) {\n\t\t// Check for .snapback directory\n\t\ttry {\n\t\t\tawait access(join(currentDir, SNAPBACK_DIR), constants.F_OK);\n\t\t\treturn currentDir;\n\t\t} catch {\n\t\t\t// Not found, continue\n\t\t}\n\n\t\t// Check for package.json (workspace root indicator)\n\t\ttry {\n\t\t\tawait access(join(currentDir, \"package.json\"), constants.F_OK);\n\t\t\treturn currentDir;\n\t\t} catch {\n\t\t\t// Not found, continue\n\t\t}\n\n\t\t// Move up one directory\n\t\tconst parentDir = dirname(currentDir);\n\t\tif (parentDir === currentDir) {\n\t\t\t// Reached root\n\t\t\tbreak;\n\t\t}\n\t\tcurrentDir = parentDir;\n\t\tdepth++;\n\t}\n\n\treturn null;\n}\n\n/**\n * Check if a path exists\n */\nexport async function pathExists(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path, constants.F_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Get file stats\n */\nexport async function getStats(path: string): Promise<{ size: number; modifiedAt: Date } | null> {\n\ttry {\n\t\tconst stats = await stat(path);\n\t\treturn {\n\t\t\tsize: stats.size,\n\t\t\tmodifiedAt: stats.mtime,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n"]}