cto-ai-cli 1.3.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/govern/audit.ts","../../src/govern/secrets.ts","../../src/engine/graph-utils.ts","../../src/govern/policy.ts","../../src/govern/snapshot.ts","../../src/govern/integrity.ts"],"sourcesContent":["import { randomUUID, createHash } from 'node:crypto';\nimport { readdir, chmod } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { userInfo } from 'node:os';\nimport { homedir } from 'node:os';\nimport type { AuditEntry, AuditAction } from '../types/govern.js';\n\n// ===== AUDIT TRAIL =====\n//\n// Immutable, tamper-evident audit log for all CTO interactions.\n// Each entry is SHA-256 hashed for integrity verification.\n\nconst CTO_DIR = '.cto-ai';\nconst AUDIT_DIR = 'audit';\nconst MAX_ENTRIES_PER_FILE = 500;\n\nfunction getAuditDir(): string {\n return join(homedir(), CTO_DIR, AUDIT_DIR);\n}\n\nfunction getCurrentAuditFile(): string {\n const date = new Date().toISOString().split('T')[0].replace(/-/g, '');\n return join(getAuditDir(), `audit_${date}.json`);\n}\n\nfunction computeIntegrityHash(entry: Omit<AuditEntry, 'integrityHash'>): string {\n const payload = JSON.stringify({\n id: entry.id,\n timestamp: entry.timestamp,\n action: entry.action,\n user: entry.user,\n projectPath: entry.projectPath,\n details: entry.details,\n });\n return createHash('sha256').update(payload).digest('hex');\n}\n\n// ===== HELPERS (inline to avoid cross-layer deps) =====\n\nasync function ensureDir(dirPath: string): Promise<void> {\n const { mkdir } = await import('node:fs/promises');\n await mkdir(dirPath, { recursive: true });\n}\n\nasync function readJSON<T>(filePath: string): Promise<T | null> {\n const { readFile } = await import('node:fs/promises');\n try {\n const content = await readFile(filePath, 'utf-8');\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\nasync function writeJSON(filePath: string, data: unknown): Promise<void> {\n const { writeFile } = await import('node:fs/promises');\n await ensureDir(join(filePath, '..'));\n await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\n// ===== PUBLIC API =====\n\nexport async function logAudit(\n action: AuditAction,\n projectPath: string,\n details: Record<string, unknown> = {},\n): Promise<AuditEntry> {\n const auditDir = getAuditDir();\n await ensureDir(auditDir);\n\n let currentUser: string;\n try {\n currentUser = userInfo().username;\n } catch {\n currentUser = process.env.USER ?? process.env.USERNAME ?? 'unknown';\n }\n\n const partialEntry = {\n id: randomUUID().substring(0, 12),\n timestamp: new Date(),\n action,\n user: currentUser,\n projectPath,\n details,\n };\n\n const entry: AuditEntry = {\n ...partialEntry,\n integrityHash: computeIntegrityHash(partialEntry),\n };\n\n const auditFile = getCurrentAuditFile();\n let entries = await readJSON<AuditEntry[]>(auditFile) ?? [];\n entries.push(entry);\n\n if (entries.length > MAX_ENTRIES_PER_FILE) {\n entries = entries.slice(-MAX_ENTRIES_PER_FILE);\n }\n\n await writeJSON(auditFile, entries);\n try { await chmod(auditFile, 0o600); } catch { /* ignore on Windows */ }\n\n return entry;\n}\n\nexport async function getAuditEntries(\n options: {\n projectPath?: string;\n action?: AuditAction;\n since?: Date;\n limit?: number;\n } = {},\n): Promise<AuditEntry[]> {\n const auditDir = getAuditDir();\n let files: string[];\n try {\n files = await readdir(auditDir);\n } catch {\n return [];\n }\n\n const auditFiles = files\n .filter((f) => f.startsWith('audit_') && f.endsWith('.json'))\n .sort()\n .reverse();\n\n const allEntries: AuditEntry[] = [];\n const limit = options.limit ?? 100;\n\n for (const file of auditFiles) {\n if (allEntries.length >= limit) break;\n\n const entries = await readJSON<AuditEntry[]>(join(auditDir, file));\n if (!entries) continue;\n\n for (const entry of entries.reverse()) {\n if (allEntries.length >= limit) break;\n if (options.projectPath && entry.projectPath !== options.projectPath) continue;\n if (options.action && entry.action !== options.action) continue;\n if (options.since && new Date(entry.timestamp) < options.since) continue;\n allEntries.push(entry);\n }\n }\n\n return allEntries;\n}\n\nexport function verifyAuditEntry(entry: AuditEntry): boolean {\n const { integrityHash, ...rest } = entry;\n const expected = computeIntegrityHash(rest as Omit<AuditEntry, 'integrityHash'>);\n return expected === integrityHash;\n}\n\nexport async function verifyAuditIntegrity(): Promise<{\n totalEntries: number;\n validEntries: number;\n invalidEntries: AuditEntry[];\n}> {\n const entries = await getAuditEntries({ limit: 10000 });\n const invalidEntries: AuditEntry[] = [];\n\n for (const entry of entries) {\n if (!verifyAuditEntry(entry)) {\n invalidEntries.push(entry);\n }\n }\n\n return {\n totalEntries: entries.length,\n validEntries: entries.length - invalidEntries.length,\n invalidEntries,\n };\n}\n\nexport async function purgeOldAuditEntries(retentionDays: number): Promise<number> {\n const auditDir = getAuditDir();\n let files: string[];\n try {\n files = await readdir(auditDir);\n } catch {\n return 0;\n }\n\n const cutoff = new Date();\n cutoff.setDate(cutoff.getDate() - retentionDays);\n const cutoffStr = cutoff.toISOString().split('T')[0].replace(/-/g, '');\n\n let purged = 0;\n const { unlink } = await import('node:fs/promises');\n\n for (const file of files) {\n if (!file.startsWith('audit_') || !file.endsWith('.json')) continue;\n const dateStr = file.replace('audit_', '').replace('.json', '');\n if (dateStr < cutoffStr) {\n try {\n await unlink(join(auditDir, file));\n purged++;\n } catch { /* ignore */ }\n }\n }\n\n return purged;\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve, relative } from 'node:path';\nimport type { SecretFinding, SecretType } from '../types/govern.js';\n\n// ===== SECRET DETECTION ENGINE =====\n\ninterface SecretPattern {\n type: SecretType;\n pattern: RegExp;\n severity: SecretFinding['severity'];\n description: string;\n}\n\nconst BUILTIN_PATTERNS: { type: SecretType; source: string; flags: string; severity: SecretFinding['severity']; description: string }[] = [\n // API Keys\n { type: 'api-key', source: '(?:api[_-]?key|apikey)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9_\\\\-]{20,})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'API Key' },\n { type: 'api-key', source: 'sk-[a-zA-Z0-9]{20,}', flags: 'g', severity: 'critical', description: 'OpenAI/Anthropic API Key' },\n { type: 'api-key', source: 'sk-ant-[a-zA-Z0-9\\\\-]{20,}', flags: 'g', severity: 'critical', description: 'Anthropic API Key' },\n\n // AWS\n { type: 'aws-key', source: 'AKIA[0-9A-Z]{16}', flags: 'g', severity: 'critical', description: 'AWS Access Key ID' },\n { type: 'aws-key', source: '(?:aws_secret_access_key|aws_secret)\\\\s*[:=]\\\\s*[\\'\"]?([a-zA-Z0-9/+=]{40})[\\'\"]?', flags: 'gi', severity: 'critical', description: 'AWS Secret Key' },\n\n // Private Keys\n { type: 'private-key', source: '-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----', flags: 'g', severity: 'critical', description: 'Private Key' },\n { type: 'private-key', source: '-----BEGIN OPENSSH PRIVATE KEY-----', flags: 'g', severity: 'critical', description: 'SSH Private Key' },\n\n // Passwords\n { type: 'password', source: '(?:password|passwd|pwd)\\\\s*[:=]\\\\s*[\\'\"]([^\\'\"]{8,})[\\'\"](?!\\\\s*\\\\{)', flags: 'gi', severity: 'high', description: 'Hardcoded Password' },\n { type: 'password', source: '(?:DB_PASSWORD|DATABASE_PASSWORD|MYSQL_PASSWORD|POSTGRES_PASSWORD)\\\\s*[:=]\\\\s*[\\'\"]?([^\\'\"{}\\\\s]{4,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Database Password' },\n\n // Tokens\n { type: 'token', source: '(?:bearer|token|auth_token|access_token|refresh_token)\\\\s*[:=]\\\\s*[\\'\"]([a-zA-Z0-9_\\\\-.]{20,})[\\'\"](?!\\\\s*\\\\{)', flags: 'gi', severity: 'high', description: 'Auth Token' },\n { type: 'token', source: 'ghp_[a-zA-Z0-9]{36}', flags: 'g', severity: 'critical', description: 'GitHub Personal Access Token' },\n { type: 'token', source: 'gho_[a-zA-Z0-9]{36}', flags: 'g', severity: 'critical', description: 'GitHub OAuth Token' },\n { type: 'token', source: 'glpat-[a-zA-Z0-9\\\\-]{20,}', flags: 'g', severity: 'critical', description: 'GitLab Personal Access Token' },\n { type: 'token', source: 'npm_[a-zA-Z0-9]{36}', flags: 'g', severity: 'high', description: 'npm Token' },\n\n // Connection strings\n { type: 'connection-string', source: '(?:mongodb(?:\\\\+srv)?|postgres(?:ql)?|mysql|redis|amqp):\\\\/\\\\/[^\\\\s\\'\"]+:[^\\\\s\\'\"]+@[^\\\\s\\'\"]+', flags: 'gi', severity: 'critical', description: 'Database Connection String' },\n { type: 'connection-string', source: '(?:DATABASE_URL|REDIS_URL|MONGODB_URI)\\\\s*[:=]\\\\s*[\\'\"]?([^\\\\s\\'\"]{10,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Database URL' },\n\n // Environment variables with secrets\n { type: 'env-variable', source: '(?:SECRET|PRIVATE|ENCRYPTION)[_-]?(?:KEY|TOKEN|PASS)\\\\s*[:=]\\\\s*[\\'\"]?([^\\\\s\\'\"]{8,})[\\'\"]?', flags: 'gi', severity: 'high', description: 'Secret Environment Variable' },\n];\n\nfunction buildPatterns(customPatterns: string[] = []): SecretPattern[] {\n const patterns: SecretPattern[] = BUILTIN_PATTERNS.map((def) => ({\n type: def.type,\n pattern: new RegExp(def.source, def.flags),\n severity: def.severity,\n description: def.description,\n }));\n\n for (const custom of customPatterns) {\n try {\n patterns.push({\n type: 'custom',\n pattern: new RegExp(custom, 'gi'),\n severity: 'medium',\n description: `Custom pattern: ${custom}`,\n });\n } catch { /* skip invalid regex */ }\n }\n\n return patterns;\n}\n\nexport function scanContentForSecrets(\n content: string,\n filePath: string,\n customPatterns: string[] = [],\n): SecretFinding[] {\n const findings: SecretFinding[] = [];\n const lines = content.split('\\n');\n const allPatterns = buildPatterns(customPatterns);\n\n for (const secretPattern of allPatterns) {\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n secretPattern.pattern.lastIndex = 0;\n let match: RegExpExecArray | null;\n\n while ((match = secretPattern.pattern.exec(line)) !== null) {\n const matchText = match[0];\n if (isTemplateOrPlaceholder(matchText)) continue;\n\n findings.push({\n type: secretPattern.type,\n file: filePath,\n line: i + 1,\n match: matchText,\n redacted: redactSecret(matchText),\n severity: secretPattern.severity,\n });\n }\n }\n }\n\n return deduplicateFindings(findings);\n}\n\nexport async function scanFileForSecrets(\n filePath: string,\n projectPath: string,\n customPatterns: string[] = [],\n): Promise<SecretFinding[]> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const relPath = relative(resolve(projectPath), resolve(filePath));\n return scanContentForSecrets(content, relPath, customPatterns);\n } catch {\n return [];\n }\n}\n\nexport async function scanProjectForSecrets(\n projectPath: string,\n filePaths: string[],\n customPatterns: string[] = [],\n): Promise<SecretFinding[]> {\n const allFindings: SecretFinding[] = [];\n\n for (const fp of filePaths) {\n const findings = await scanFileForSecrets(fp, projectPath, customPatterns);\n allFindings.push(...findings);\n }\n\n return allFindings.sort((a, b) => {\n const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };\n return severityOrder[a.severity] - severityOrder[b.severity];\n });\n}\n\nexport function sanitizeContent(content: string, customPatterns: string[] = []): string {\n let sanitized = content;\n const allPatterns = buildPatterns(customPatterns);\n\n for (const secretPattern of allPatterns) {\n sanitized = sanitized.replace(secretPattern.pattern, (match) => {\n if (isTemplateOrPlaceholder(match)) return match;\n return redactSecret(match);\n });\n }\n\n return sanitized;\n}\n\nfunction redactSecret(value: string): string {\n if (value.length <= 8) return '***REDACTED***';\n const prefix = value.substring(0, 4);\n const suffix = value.substring(value.length - 2);\n return `${prefix}${'*'.repeat(Math.min(value.length - 6, 20))}${suffix}`;\n}\n\nfunction isTemplateOrPlaceholder(value: string): boolean {\n const placeholders = [\n /\\$\\{.*\\}/, /\\{\\{.*\\}\\}/, /%[sd]/, /<[A-Z_]+>/, /YOUR_.*_HERE/i,\n /\\bCHANGE_ME\\b/i, /\\bPLACEHOLDER\\b/i, /\\bexample\\b/i, /\\bTODO\\b/i, /xxx+/i,\n /\\breplace.?me\\b/i, /\\bdummy\\b/i, /\\btest_?key\\b/i, /\\bsample\\b/i,\n ];\n return placeholders.some((p) => p.test(value));\n}\n\nfunction deduplicateFindings(findings: SecretFinding[]): SecretFinding[] {\n const seen = new Set<string>();\n return findings.filter((f) => {\n const key = `${f.file}:${f.line}:${f.type}:${f.match}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n","import type { GraphEdge } from '../types/engine.js';\n\n// ===== SHARED GRAPH UTILITIES =====\n\nexport interface AdjacencyList {\n forward: Map<string, string[]>; // file → files it imports\n reverse: Map<string, string[]>; // file → files that import it\n}\n\nexport function buildAdjacencyList(edges: GraphEdge[]): AdjacencyList {\n const forward = new Map<string, string[]>();\n const reverse = new Map<string, string[]>();\n\n for (const edge of edges) {\n if (!forward.has(edge.from)) forward.set(edge.from, []);\n forward.get(edge.from)!.push(edge.to);\n\n if (!reverse.has(edge.to)) reverse.set(edge.to, []);\n reverse.get(edge.to)!.push(edge.from);\n }\n\n return { forward, reverse };\n}\n\nexport function bfsBidirectional(\n seeds: string[],\n adj: AdjacencyList,\n depth: number,\n): Set<string> {\n const result = new Set(seeds);\n let frontier = [...seeds];\n const visited = new Set<string>();\n\n for (let d = 0; d < depth; d++) {\n const nextFrontier: string[] = [];\n\n for (const node of frontier) {\n if (visited.has(node)) continue;\n visited.add(node);\n\n // Forward neighbors (imports)\n const fwd = adj.forward.get(node);\n if (fwd) {\n for (const neighbor of fwd) {\n if (!visited.has(neighbor)) {\n result.add(neighbor);\n nextFrontier.push(neighbor);\n }\n }\n }\n\n // Reverse neighbors (imported by)\n const rev = adj.reverse.get(node);\n if (rev) {\n for (const neighbor of rev) {\n if (!visited.has(neighbor)) {\n result.add(neighbor);\n nextFrontier.push(neighbor);\n }\n }\n }\n }\n\n frontier = nextFrontier;\n }\n\n return result;\n}\n\nexport function matchGlob(path: string, pattern: string): boolean {\n const regexStr = pattern\n .replace(/\\./g, '\\\\.')\n .replace(/\\*\\*/g, '§§')\n .replace(/\\*/g, '[^/]*')\n .replace(/§§/g, '.*')\n .replace(/\\?/g, '.');\n\n try {\n return new RegExp(`^${regexStr}$`).test(path);\n } catch {\n return false;\n }\n}\n","import type {\n PolicySet,\n PolicyRule,\n PolicyRuleType,\n PolicyValidation,\n PolicyViolation,\n PolicyWarning,\n} from '../types/govern.js';\nimport type { ContextSelection, AnalyzedFile } from '../types/engine.js';\nimport { matchGlob } from '../engine/graph-utils.js';\n\n// ===== POLICY ENGINE =====\n//\n// YAML-backed rule engine for controlling what context gets included/excluded.\n// Policies are evaluated after selection to validate compliance.\n\nexport const DEFAULT_POLICY: PolicySet = {\n version: '1.0',\n name: 'default',\n rules: [\n {\n id: 'no-env',\n type: 'exclude-always',\n pattern: '**/*.env*',\n reason: 'Environment files must never be sent to AI',\n enabled: true,\n },\n {\n id: 'no-secrets',\n type: 'secret-block',\n reason: 'Files with detected secrets are blocked',\n enabled: true,\n },\n {\n id: 'min-coverage',\n type: 'coverage-minimum',\n threshold: 70,\n reason: 'Warn if context coverage drops below 70%',\n enabled: true,\n },\n ],\n};\n\n// ===== VALIDATION =====\n\nexport function validateSelection(\n selection: ContextSelection,\n policies: PolicySet,\n allFiles?: AnalyzedFile[],\n): PolicyValidation {\n const violations: PolicyViolation[] = [];\n const warnings: PolicyWarning[] = [];\n\n const includedPaths = new Set(selection.files.map((f) => f.relativePath));\n\n for (const rule of policies.rules) {\n if (!rule.enabled) continue;\n\n switch (rule.type) {\n case 'exclude-always': {\n if (!rule.pattern) break;\n const violatingFiles = selection.files.filter((f) =>\n matchGlob(f.relativePath, rule.pattern!),\n );\n for (const f of violatingFiles) {\n violations.push({\n rule,\n message: `File \"${f.relativePath}\" is included but matches exclude-always pattern \"${rule.pattern}\"`,\n severity: 'error',\n });\n }\n break;\n }\n\n case 'include-always': {\n if (!rule.pattern || !allFiles) break;\n const requiredFiles = allFiles.filter((f) =>\n matchGlob(f.relativePath, rule.pattern!),\n );\n for (const f of requiredFiles) {\n if (!includedPaths.has(f.relativePath)) {\n violations.push({\n rule,\n message: `File \"${f.relativePath}\" matches include-always pattern \"${rule.pattern}\" but is not included`,\n severity: 'warning',\n });\n }\n }\n break;\n }\n\n case 'coverage-minimum': {\n const threshold = rule.threshold ?? 70;\n if (selection.coverage.score < threshold) {\n warnings.push({\n rule,\n message: `Coverage ${selection.coverage.score}% is below minimum ${threshold}%`,\n currentValue: selection.coverage.score,\n threshold,\n });\n }\n break;\n }\n\n case 'risk-maximum': {\n const threshold = rule.threshold ?? 50;\n if (selection.riskScore > threshold) {\n warnings.push({\n rule,\n message: `Exclusion risk ${selection.riskScore}/100 exceeds maximum ${threshold}`,\n currentValue: selection.riskScore,\n threshold,\n });\n }\n break;\n }\n\n case 'budget-limit': {\n if (!rule.category || !rule.threshold) break;\n const categoryFiles = selection.files.filter((f) =>\n fileMatchesCategory(f.relativePath, rule.category!),\n );\n const categoryTokens = categoryFiles.reduce((s, f) => s + f.tokens, 0);\n const categoryPercent = selection.totalTokens > 0\n ? (categoryTokens / selection.totalTokens) * 100\n : 0;\n\n if (categoryPercent > rule.threshold) {\n warnings.push({\n rule,\n message: `Category \"${rule.category}\" uses ${Math.round(categoryPercent)}% of budget (max: ${rule.threshold}%)`,\n currentValue: Math.round(categoryPercent),\n threshold: rule.threshold,\n });\n }\n break;\n }\n }\n }\n\n return {\n passed: violations.filter((v) => v.severity === 'error').length === 0,\n violations,\n warnings,\n };\n}\n\n// ===== POLICY CRUD =====\n\nexport function addRule(policies: PolicySet, rule: PolicyRule): PolicySet {\n return {\n ...policies,\n rules: [...policies.rules, rule],\n };\n}\n\nexport function removeRule(policies: PolicySet, ruleId: string): PolicySet {\n return {\n ...policies,\n rules: policies.rules.filter((r) => r.id !== ruleId),\n };\n}\n\nexport function toggleRule(policies: PolicySet, ruleId: string, enabled: boolean): PolicySet {\n return {\n ...policies,\n rules: policies.rules.map((r) =>\n r.id === ruleId ? { ...r, enabled } : r,\n ),\n };\n}\n\n// ===== HELPERS =====\n\nfunction fileMatchesCategory(path: string, category: string): boolean {\n switch (category) {\n case 'test':\n return /\\.(test|spec)\\.[jt]sx?$/.test(path) || /\\/__tests__\\//.test(path) || /\\/tests?\\//.test(path);\n case 'config':\n return /\\.(config|rc)\\.[jt]s$/.test(path) || /\\.json$/.test(path) || /\\.ya?ml$/.test(path);\n case 'docs':\n return /\\.(md|txt|rst)$/.test(path);\n case 'types':\n return /types?\\//i.test(path) || /\\.d\\.ts$/.test(path);\n default:\n return path.includes(category);\n }\n}\n","import { randomUUID, createHash } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport type {\n ContextSnapshot,\n SnapshotFile,\n SnapshotVerification,\n} from '../types/govern.js';\nimport type { ContextSelection, ProjectAnalysis } from '../types/engine.js';\n\n// ===== CONTEXT SNAPSHOTS =====\n//\n// Reproducible, verifiable snapshots of context selections.\n// Used for: deterministic mode, audit evidence, before/after comparisons.\n\nexport function createSnapshot(\n name: string,\n analysis: ProjectAnalysis,\n selection: ContextSelection,\n metadata: Record<string, unknown> = {},\n): ContextSnapshot {\n const files: SnapshotFile[] = selection.files.map((f) => ({\n relativePath: f.relativePath,\n hash: hashString(`${f.relativePath}:${f.tokens}:${f.pruneLevel}`),\n tokens: f.tokens,\n pruneLevel: f.pruneLevel,\n }));\n\n const snapshotData = files\n .map((f) => `${f.relativePath}:${f.hash}:${f.pruneLevel}`)\n .sort()\n .join('|');\n\n return {\n id: randomUUID().substring(0, 8),\n name,\n createdAt: new Date(),\n hash: hashString(snapshotData),\n projectHash: analysis.hash,\n analysisHash: analysis.hash,\n selectionHash: selection.hash,\n files,\n totalTokens: selection.totalTokens,\n coverageScore: selection.coverage.score,\n riskScore: selection.riskScore,\n metadata,\n };\n}\n\nexport async function verifySnapshot(\n snapshot: ContextSnapshot,\n currentAnalysis: ProjectAnalysis,\n currentSelection: ContextSelection,\n): Promise<SnapshotVerification> {\n const currentFiles = new Map(\n currentSelection.files.map((f) => [f.relativePath, f]),\n );\n\n let filesMatched = 0;\n const filesMissing: string[] = [];\n const filesChanged: string[] = [];\n\n for (const snapFile of snapshot.files) {\n const current = currentFiles.get(snapFile.relativePath);\n\n if (!current) {\n filesMissing.push(snapFile.relativePath);\n continue;\n }\n\n const currentHash = hashString(\n `${current.relativePath}:${current.tokens}:${current.pruneLevel}`,\n );\n\n if (currentHash === snapFile.hash) {\n filesMatched++;\n } else {\n filesChanged.push(snapFile.relativePath);\n }\n }\n\n // Verify overall hash\n const currentSnapshotData = snapshot.files\n .map((f) => {\n const current = currentFiles.get(f.relativePath);\n if (!current) return `${f.relativePath}:MISSING:MISSING`;\n return `${current.relativePath}:${hashString(`${current.relativePath}:${current.tokens}:${current.pruneLevel}`)}:${current.pruneLevel}`;\n })\n .sort()\n .join('|');\n\n const currentHash = hashString(currentSnapshotData);\n const integrityOk = currentHash === snapshot.hash && filesMissing.length === 0 && filesChanged.length === 0;\n\n return {\n valid: integrityOk,\n snapshotId: snapshot.id,\n filesChecked: snapshot.files.length,\n filesMatched,\n filesMissing,\n filesChanged,\n integrityOk,\n };\n}\n\nexport function compareSnapshots(\n older: ContextSnapshot,\n newer: ContextSnapshot,\n): {\n added: string[];\n removed: string[];\n changed: string[];\n tokenDelta: number;\n coverageDelta: number;\n riskDelta: number;\n} {\n const olderFiles = new Map(older.files.map((f) => [f.relativePath, f]));\n const newerFiles = new Map(newer.files.map((f) => [f.relativePath, f]));\n\n const added: string[] = [];\n const removed: string[] = [];\n const changed: string[] = [];\n\n for (const [path, file] of newerFiles) {\n const old = olderFiles.get(path);\n if (!old) {\n added.push(path);\n } else if (old.hash !== file.hash) {\n changed.push(path);\n }\n }\n\n for (const path of olderFiles.keys()) {\n if (!newerFiles.has(path)) {\n removed.push(path);\n }\n }\n\n return {\n added,\n removed,\n changed,\n tokenDelta: newer.totalTokens - older.totalTokens,\n coverageDelta: newer.coverageScore - older.coverageScore,\n riskDelta: newer.riskScore - older.riskScore,\n };\n}\n\n// ===== HELPERS =====\n\nfunction hashString(input: string): string {\n return createHash('sha256').update(input).digest('hex').substring(0, 16);\n}\n","import { createHash } from 'node:crypto';\nimport { readFile, chmod, readdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { IntegrityManifest, IntegrityEntry } from '../types/govern.js';\n\n// ===== INTEGRITY VERIFICATION =====\n//\n// SHA-256 hash-based integrity checking for CTO artifacts.\n// Detects tampering of snapshots, audit logs, configs, and policies.\n\nexport function hashContent(content: Buffer | string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\nexport async function hashFile(filePath: string): Promise<string | null> {\n try {\n const content = await readFile(filePath);\n return hashContent(content);\n } catch {\n return null;\n }\n}\n\nexport async function buildManifest(\n projectDir: string,\n): Promise<IntegrityManifest> {\n const entries: IntegrityEntry[] = [];\n\n async function scanDir(dir: string, type: IntegrityEntry['type']): Promise<void> {\n let files: string[];\n try {\n files = await readdir(dir);\n } catch {\n return;\n }\n\n for (const file of files) {\n const fullPath = join(dir, file);\n try {\n const fileStat = await stat(fullPath);\n if (fileStat.isFile()) {\n const hash = await hashFile(fullPath);\n if (hash) {\n entries.push({\n filePath: fullPath,\n hash,\n size: fileStat.size,\n createdAt: fileStat.mtime,\n type,\n });\n }\n }\n } catch { /* skip */ }\n }\n }\n\n // Scan known CTO directories\n await scanDir(join(projectDir, 'snapshots'), 'snapshot');\n await scanDir(join(projectDir, 'audit'), 'audit');\n await scanDir(projectDir, 'config');\n\n return {\n version: '2.0',\n createdAt: new Date(),\n entries,\n };\n}\n\nexport async function verifyManifest(\n manifest: IntegrityManifest,\n): Promise<{\n totalFiles: number;\n validFiles: number;\n invalidFiles: string[];\n missingFiles: string[];\n}> {\n const invalidFiles: string[] = [];\n const missingFiles: string[] = [];\n\n for (const entry of manifest.entries) {\n const currentHash = await hashFile(entry.filePath);\n\n if (currentHash === null) {\n missingFiles.push(entry.filePath);\n } else if (currentHash !== entry.hash) {\n invalidFiles.push(entry.filePath);\n }\n }\n\n return {\n totalFiles: manifest.entries.length,\n validFiles: manifest.entries.length - invalidFiles.length - missingFiles.length,\n invalidFiles,\n missingFiles,\n };\n}\n\nexport async function securePermissions(dirPath: string): Promise<number> {\n let count = 0;\n\n try {\n await chmod(dirPath, 0o700);\n count++;\n\n const files = await readdir(dirPath);\n for (const file of files) {\n try {\n const fullPath = join(dirPath, file);\n const fileStat = await stat(fullPath);\n if (fileStat.isFile()) {\n await chmod(fullPath, 0o600);\n count++;\n }\n } catch { /* skip */ }\n }\n } catch { /* dir might not exist */ }\n\n return count;\n}\n"],"mappings":"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","names":["readFile","randomUUID","createHash","currentHash","createHash","readFile","chmod","readdir","join"]}
@@ -0,0 +1,234 @@
1
+ interface AnalyzedFile {
2
+ path: string;
3
+ relativePath: string;
4
+ extension: string;
5
+ size: number;
6
+ tokens: number;
7
+ lines: number;
8
+ lastModified: Date;
9
+ kind: FileKind;
10
+ imports: string[];
11
+ importedBy: string[];
12
+ isHub: boolean;
13
+ complexity: number;
14
+ riskScore: number;
15
+ riskFactors: RiskFactor[];
16
+ exclusionImpact: ExclusionImpact;
17
+ }
18
+ type FileKind = 'source' | 'type' | 'test' | 'config' | 'entry' | 'asset';
19
+ type ExclusionImpact = 'critical' | 'high' | 'medium' | 'low' | 'none';
20
+ interface ProjectAnalysis {
21
+ projectPath: string;
22
+ projectName: string;
23
+ analyzedAt: Date;
24
+ hash: string;
25
+ files: AnalyzedFile[];
26
+ totalFiles: number;
27
+ totalTokens: number;
28
+ graph: ProjectGraph;
29
+ riskProfile: RiskProfile;
30
+ stack: string[];
31
+ tokenMethod: 'chars4' | 'tiktoken';
32
+ }
33
+ interface ProjectGraph {
34
+ nodes: string[];
35
+ edges: GraphEdge[];
36
+ hubs: HubNode[];
37
+ leaves: string[];
38
+ orphans: string[];
39
+ clusters: FileCluster[];
40
+ }
41
+ interface GraphEdge {
42
+ from: string;
43
+ to: string;
44
+ type: 'import' | 'export' | 're-export';
45
+ }
46
+ interface HubNode {
47
+ relativePath: string;
48
+ dependents: number;
49
+ dependencies: number;
50
+ score: number;
51
+ }
52
+ interface FileCluster {
53
+ id: string;
54
+ name: string;
55
+ files: string[];
56
+ totalTokens: number;
57
+ internalEdges: number;
58
+ externalEdges: number;
59
+ cohesion: number;
60
+ }
61
+ interface RiskProfile {
62
+ distribution: {
63
+ critical: number;
64
+ high: number;
65
+ medium: number;
66
+ low: number;
67
+ };
68
+ topRiskFiles: AnalyzedFile[];
69
+ overallComplexity: number;
70
+ }
71
+ interface RiskFactor {
72
+ type: RiskFactorType;
73
+ score: number;
74
+ weight: number;
75
+ detail: string;
76
+ }
77
+ type RiskFactorType = 'hub' | 'type-provider' | 'complexity' | 'recency' | 'config' | 'churn';
78
+ interface CoverageResult {
79
+ score: number;
80
+ relevantFiles: string[];
81
+ includedRelevant: string[];
82
+ missingRelevant: string[];
83
+ missingCritical: string[];
84
+ explanation: string;
85
+ }
86
+ interface ContextSelection {
87
+ files: SelectedFile[];
88
+ totalTokens: number;
89
+ budget: number;
90
+ usedPercent: number;
91
+ coverage: CoverageResult;
92
+ riskScore: number;
93
+ deterministic: boolean;
94
+ hash: string;
95
+ decisions: SelectionDecision[];
96
+ }
97
+ interface SelectedFile {
98
+ relativePath: string;
99
+ tokens: number;
100
+ originalTokens: number;
101
+ pruneLevel: PruneLevel;
102
+ riskScore: number;
103
+ reason: string;
104
+ }
105
+ type PruneLevel = 'full' | 'signatures' | 'skeleton' | 'excluded';
106
+ interface SelectionDecision {
107
+ file: string;
108
+ action: 'include-full' | 'include-signatures' | 'include-skeleton' | 'exclude';
109
+ reason: string;
110
+ alternatives?: string;
111
+ }
112
+
113
+ interface InteractionPlan {
114
+ id: string;
115
+ task: string;
116
+ taskType: TaskType;
117
+ timestamp: Date;
118
+ context: ContextSelection;
119
+ model: ModelChoice;
120
+ prompt: StructuredPrompt;
121
+ cost: CostEstimate;
122
+ decisions: PlanDecision[];
123
+ }
124
+ type TaskType = 'debug' | 'review' | 'refactor' | 'test' | 'docs' | 'feature' | 'architecture' | 'simple-edit';
125
+ interface ModelChoice {
126
+ model: ModelId;
127
+ reason: string;
128
+ confidence: number;
129
+ alternatives: ModelAlternative[];
130
+ }
131
+ type ModelId = string;
132
+ interface ModelAlternative {
133
+ model: ModelId;
134
+ costDelta: number;
135
+ tradeoff: string;
136
+ }
137
+ interface ModelSpec {
138
+ id: ModelId;
139
+ name: string;
140
+ tier: 'fast' | 'balanced' | 'reasoning';
141
+ pricing: {
142
+ inputPerMillion: number;
143
+ outputPerMillion: number;
144
+ cacheReadPerMillion: number;
145
+ };
146
+ contextWindow: number;
147
+ strengths: string[];
148
+ }
149
+ interface StructuredPrompt {
150
+ sections: PromptSection[];
151
+ totalTokens: number;
152
+ rendered: string;
153
+ }
154
+ interface PromptSection {
155
+ id: string;
156
+ role: 'system' | 'context' | 'task' | 'constraints' | 'format';
157
+ content: string;
158
+ tokens: number;
159
+ }
160
+ interface CostEstimate {
161
+ model: ModelId;
162
+ inputTokens: number;
163
+ estimatedOutputTokens: number;
164
+ inputCost: number;
165
+ outputCost: number;
166
+ totalCost: number;
167
+ formatted: string;
168
+ withoutOptimization: {
169
+ inputTokens: number;
170
+ totalCost: number;
171
+ formatted: string;
172
+ };
173
+ savings: {
174
+ tokensSaved: number;
175
+ costSaved: number;
176
+ percent: number;
177
+ formatted: string;
178
+ };
179
+ }
180
+ interface PlanDecision {
181
+ step: 'classify' | 'select-context' | 'choose-model' | 'build-prompt' | 'estimate-cost';
182
+ decision: string;
183
+ reason: string;
184
+ data?: Record<string, unknown>;
185
+ }
186
+
187
+ interface PolicySet {
188
+ version: string;
189
+ name: string;
190
+ rules: PolicyRule[];
191
+ }
192
+ interface PolicyRule {
193
+ id: string;
194
+ type: PolicyRuleType;
195
+ pattern?: string;
196
+ threshold?: number;
197
+ category?: string;
198
+ reason: string;
199
+ enabled: boolean;
200
+ }
201
+ type PolicyRuleType = 'include-always' | 'exclude-always' | 'budget-limit' | 'coverage-minimum' | 'risk-maximum' | 'secret-block';
202
+
203
+ interface OrchestratorInput {
204
+ task: string;
205
+ analysis: ProjectAnalysis;
206
+ budget?: number;
207
+ model?: string;
208
+ policies?: PolicySet;
209
+ depth?: number;
210
+ enableCoT?: boolean;
211
+ enableConstraints?: boolean;
212
+ enableAntiHallucination?: boolean;
213
+ }
214
+ declare function planInteraction(input: OrchestratorInput): Promise<InteractionPlan>;
215
+
216
+ declare const MODEL_REGISTRY: ModelSpec[];
217
+ declare function classifyTask(taskDescription: string): TaskType;
218
+ declare function routeModel(taskType: TaskType, analysis: ProjectAnalysis, preferredModel?: ModelId): ModelChoice;
219
+ declare function getModelSpec(modelId: ModelId): ModelSpec | undefined;
220
+
221
+ declare function estimateCost(modelId: ModelId, inputTokens: number, totalProjectTokens: number, estimatedOutputRatio?: number): CostEstimate;
222
+
223
+ interface PromptOptions {
224
+ task: string;
225
+ taskType: TaskType;
226
+ analysis: ProjectAnalysis;
227
+ selection: ContextSelection;
228
+ enableCoT?: boolean;
229
+ enableConstraints?: boolean;
230
+ enableAntiHallucination?: boolean;
231
+ }
232
+ declare function buildPrompt(options: PromptOptions): StructuredPrompt;
233
+
234
+ export { MODEL_REGISTRY, type OrchestratorInput, type PromptOptions, buildPrompt, classifyTask, estimateCost, getModelSpec, planInteraction, routeModel };