UNPKG

zephyr-agent

Version:
451 lines (450 loc) • 19 kB
"use strict"; var __webpack_require__ = {}; (()=>{ __webpack_require__.d = (exports1, getters, values)=>{ var define = (defs, kind)=>{ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, { enumerable: true, [kind]: defs[key] }); }; define(getters, "get"); define(values, "value"); }; })(); (()=>{ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop); })(); (()=>{ __webpack_require__.r = (exports1)=>{ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, { value: 'Module' }); Object.defineProperty(exports1, '__esModule', { value: true }); }; })(); var __webpack_exports__ = {}; __webpack_require__.r(__webpack_exports__); __webpack_require__.d(__webpack_exports__, { attributionRepository: ()=>attributionRepository, captureSource: ()=>captureSource, comparisonFile: ()=>comparisonFile, finishSourceCapture: ()=>finishSourceCapture, loadSourceRecord: ()=>loadSourceRecord, loadVersionAttribution: ()=>loadVersionAttribution, readAttributionConfig: ()=>external_config_js_namespaceObject.readAttributionConfig, sourceCommitBaseline: ()=>sourceCommitBaseline }); const external_node_child_process_namespaceObject = require("node:child_process"); const external_node_crypto_namespaceObject = require("node:crypto"); const external_node_fs_namespaceObject = require("node:fs"); const external_node_path_namespaceObject = require("node:path"); const external_git_ai_js_namespaceObject = require("./git-ai.js"); const external_sessions_js_namespaceObject = require("./sessions.js"); const external_config_js_namespaceObject = require("./config.js"); const digest = (data)=>(0, external_node_crypto_namespaceObject.createHash)('sha256').update(data).digest('hex'); function git(root, args, encoding) { const options = { cwd: root, timeout: 5000, maxBuffer: 67108864, stdio: [ 'ignore', 'pipe', 'ignore' ] }; return encoding ? (0, external_node_child_process_namespaceObject.execFileSync)('git', args, { ...options, encoding }) : (0, external_node_child_process_namespaceObject.execFileSync)('git', args, options); } function attributionRepository(directory) { const root = git((0, external_node_path_namespaceObject.resolve)(directory), [ 'rev-parse', '--show-toplevel' ], 'utf8').trim(); const gitDir = git(root, [ 'rev-parse', '--absolute-git-dir' ], 'utf8').trim(); return { root, gitDir }; } function excluded(file, config) { return file.split('/').some((part)=>[ 'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', '.output', '.turbo', 'coverage', '.npmrc', '.netrc' ].includes(part) || /^\.env(?:\.|$)/.test(part) || /\.(?:pem|key|p12|pfx)$/i.test(part)) || (config.exclude ?? []).some((prefix)=>file === prefix || file.startsWith(`${prefix.replace(/\/$/, '')}/`)); } function privateDirectory(gitDir) { const directory = (0, external_node_path_namespaceObject.join)(gitDir, 'zephyr-attribution'); (0, external_node_fs_namespaceObject.mkdirSync)(directory, { recursive: true, mode: 448 }); return directory; } function captureSource(directory, phase = 'publication') { let repository; try { repository = attributionRepository(directory); } catch { return; } const { root, gitDir } = repository; try { const config = (0, external_config_js_namespaceObject.readAttributionConfig)(root, gitDir); if (!config?.enabled) return; let baseCommit = ''; try { baseCommit = git(root, [ 'rev-parse', '--verify', 'HEAD' ], 'utf8').trim(); } catch {} const paths = [ ...new Set(git(root, [ 'ls-files', '-z', '--cached', '--others', '--exclude-standard' ], 'utf8').split('\0').filter(Boolean)) ].filter((file)=>!excluded(file, config)).sort(); if (paths.length > 20000) throw new Error('Source capture exceeds 20,000 files; configure exclude prefixes'); const head = new Map(commitEntries(root, baseCommit, config).map((entry)=>[ entry.file, entry.oid ])); const working = (0, external_git_ai_js_namespaceObject.readGitAiWorkingAttribution)(gitDir, baseCommit); const metadata = (0, external_sessions_js_namespaceObject.readSessionMetadata)(gitDir); const sessions = Object.create(null); const files = Object.create(null); let bytes = 0; for (const file of paths){ const absolute = (0, external_node_path_namespaceObject.resolve)(root, file); if ((0, external_node_path_namespaceObject.isAbsolute)((0, external_node_path_namespaceObject.relative)(root, absolute)) || (0, external_node_path_namespaceObject.relative)(root, absolute).startsWith(`..${external_node_path_namespaceObject.sep}`)) throw new Error('Source path escapes repository'); const parents = file.split('/').slice(0, -1); let parent = root; for (const part of parents){ parent = (0, external_node_path_namespaceObject.join)(parent, part); if ((0, external_node_fs_namespaceObject.existsSync)(parent) && (0, external_node_fs_namespaceObject.lstatSync)(parent).isSymbolicLink()) throw new Error('Source parent is a symlink'); } let stat; try { stat = (0, external_node_fs_namespaceObject.lstatSync)(absolute); } catch (error) { if ('ENOENT' === error.code) continue; throw error; } if (stat.isDirectory()) throw new Error(`Submodule or directory entry needs an exclusion: ${file}`); if (!stat.isFile() && !stat.isSymbolicLink()) throw new Error(`Unsupported source entry: ${file}`); bytes += stat.size; if (bytes > 52428800) throw new Error('Source capture exceeds 50 MiB; configure exclude prefixes'); const content = stat.isSymbolicLink() ? Buffer.from((0, external_node_fs_namespaceObject.readlinkSync)(absolute)) : (0, external_node_fs_namespaceObject.readFileSync)(absolute); const hash = digest(content); const mode = stat.isSymbolicLink() ? '120000' : 73 & stat.mode ? '100755' : '100644'; const attribution = (0, external_git_ai_js_namespaceObject.workingRanges)(working.entries.get(file), hash, working.authors); const oid = head.get(file); const committed = void 0 === attribution && !working.entries.has(file) && Boolean(oid && blobId(content, oid.length) === oid) && !stat.isSymbolicLink(); for (const range of attribution ?? []){ const match = (0, external_sessions_js_namespaceObject.sessionForOrigin)(range.origin, metadata); if (!match) continue; sessions[match.key] = match.session; const session = match.session; range.origin = { ...range.origin, agent: session.agent, harness: session.harness, provider: session.provider, reasoningEffort: session.reasoningEffort, turn: session.turn, turnAssociation: session.turnAssociation, promptInitiator: session.promptInitiator }; } files[file] = { hash, mode, content: content.toString('base64'), attribution: attribution ?? [], ...committed ? { committed: true } : {} }; } const fingerprint = digest(JSON.stringify(Object.entries(files).map(([file, value])=>[ file, value.mode, value.hash ]))); let workspaceHuman; try { workspaceHuman = git(root, [ 'var', 'GIT_AUTHOR_IDENT' ], 'utf8').trim().replace(/ \d+ [+-]\d{4}$/, ''); } catch {} const record = { schemaVersion: 1, id: `source-${(0, external_node_crypto_namespaceObject.randomUUID)()}`, fingerprint, baseCommit, dirty: Boolean(git(root, [ 'status', '--porcelain', '--untracked-files=normal' ], 'utf8').trim()), capturedAt: new Date().toISOString(), workspaceHuman, sessions, files }; (0, external_node_fs_namespaceObject.writeFileSync)((0, external_node_path_namespaceObject.join)(privateDirectory(gitDir), `${record.id}.json`), JSON.stringify(record), { flag: 'wx', mode: 384 }); return { ...repository, phase, record, storage: config.storage ?? 'local' }; } catch (error) { return { ...repository, phase, reason: error.message }; } } function finishSourceCapture(directory, start, version) { const end = captureSource(directory); if (!end && !start) return; const record = end?.record; if (!record) return { schemaVersion: 1, status: 'unavailable', reason: end?.reason ?? 'Attribution disabled during build', scope: 'git-working-tree', consistency: 'unavailable', identity: 'self-reported' }; const summary = { schemaVersion: 1, status: 'captured', scope: 'git-working-tree', identity: 'self-reported', storage: end.storage ?? 'local', sourceId: record.id, sourceFingerprint: record.fingerprint, baseCommit: record.baseCommit, dirty: record.dirty, capturedAt: record.capturedAt, workspaceHuman: record.workspaceHuman, sessions: record.sessions, reason: start?.reason, startSourceId: start?.record?.id, startSourceFingerprint: start?.record?.fingerprint, consistency: start?.reason ? 'unavailable' : start?.record && 'build-start' === start.phase ? start.record.fingerprint === record.fingerprint ? 'boundary-match' : 'changed-during-build' : 'publication-only', files: Object.fromEntries(Object.entries(record.files).map(([file, value])=>[ file, { hash: value.hash, mode: value.mode, attribution: value.attribution } ])) }; try { (0, external_node_fs_namespaceObject.writeFileSync)((0, external_node_path_namespaceObject.join)(privateDirectory(end.gitDir), `version-${digest(version)}.json`), JSON.stringify({ version, summary }), { flag: 'wx', mode: 384 }); } catch (error) { return { schemaVersion: 1, status: 'unavailable', scope: 'git-working-tree', consistency: 'unavailable', identity: 'self-reported', reason: 'EEXIST' === error.code ? `A source receipt already exists for version ${version}` : 'Unable to save the private version receipt' }; } return summary; } function loadSourceRecord(directory, id) { const { gitDir } = attributionRepository(directory); const storage = privateDirectory(gitDir); let sourceId = id; if (!/^source-[a-f0-9-]{36}$/.test(id)) { const receipt = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)((0, external_node_path_namespaceObject.join)(storage, `version-${digest(id)}.json`), 'utf8')); sourceId = receipt.summary.sourceId; } if (!/^source-[a-f0-9-]{36}$/.test(sourceId)) throw new Error('Invalid source record id'); const record = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)((0, external_node_path_namespaceObject.join)(storage, `${sourceId}.json`), 'utf8')); if (1 !== record.schemaVersion || record.id !== sourceId) throw new Error('Unsupported source record'); return record; } function loadVersionAttribution(directory, version) { const { gitDir } = attributionRepository(directory); const receipt = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)((0, external_node_path_namespaceObject.join)(privateDirectory(gitDir), `version-${digest(version)}.json`), 'utf8')); if (receipt.version !== version || receipt.summary?.schemaVersion !== 1) throw new Error('Invalid private version receipt'); return receipt.summary; } function blobId(content, oidLength) { return (0, external_node_crypto_namespaceObject.createHash)(64 === oidLength ? 'sha256' : 'sha1').update(`blob ${content.length}\0`).update(content).digest('hex'); } function commitEntries(root, commit, config) { const entries = (commit ? git(root, [ 'ls-tree', '-rz', '--full-tree', commit ], 'utf8') : '').split('\0').filter(Boolean).map((entry)=>{ const tab = entry.indexOf('\t'); const [mode, type, oid] = entry.slice(0, tab).split(' '); return { mode, type, oid, file: entry.slice(tab + 1) }; }).filter(({ file })=>!excluded(file, config)); if (entries.length > 20000) throw new Error('Git baseline exceeds 20,000 entries'); if (entries.some(({ type })=>'blob' !== type)) throw new Error('Git baseline contains a submodule; configure exclusions'); return entries; } function comparisonFile(directory, record, file) { const value = record.files[file]; if (!value?.committed || !record.baseCommit || value.attribution.length) return value; const { root, gitDir } = attributionRepository(directory); const config = (0, external_config_js_namespaceObject.readAttributionConfig)(root, gitDir); return { ...value, attribution: (0, external_git_ai_js_namespaceObject.committedRanges)(root, config?.gitAiPath ?? 'git-ai', file, record.baseCommit) }; } function sourceCommitBaseline(directory, captured) { const { root, gitDir } = attributionRepository(directory); const config = (0, external_config_js_namespaceObject.readAttributionConfig)(root, gitDir); if (!config?.enabled) throw new Error('Attribution is disabled'); const files = Object.create(null); const entries = commitEntries(root, captured.baseCommit, config); const reused = new Map(); const missing = new Set(); for (const { file, oid } of entries){ const current = captured.files[file]; const content = current && Buffer.from(current.content, 'base64'); if (content && blobId(content, oid.length) === oid) reused.set(oid, content); else missing.add(oid); } const blobs = new Map(reused); const requested = [ ...missing ].filter((oid)=>!blobs.has(oid)); if (requested.length) { const output = (0, external_node_child_process_namespaceObject.execFileSync)('git', [ 'cat-file', '--batch' ], { cwd: root, input: requested.join('\n') + '\n', timeout: 5000, maxBuffer: 67108864, stdio: [ 'pipe', 'pipe', 'ignore' ] }); let offset = 0; let fetched = 0; for (const oid of requested){ const end = output.indexOf(10, offset); const header = output.subarray(offset, end).toString('ascii'); const match = /^([a-f0-9]{40,64}) blob (\d+)$/.exec(header); if (end < 0 || !match || match[1] !== oid) throw new Error('Invalid Git blob batch'); const size = Number(match[2]); fetched += size; if (fetched > 52428800) throw new Error('Git baseline exceeds 50 MiB'); offset = end + 1; if (!Number.isSafeInteger(size) || offset + size >= output.length || 10 !== output[offset + size]) throw new Error('Incomplete Git blob batch'); blobs.set(oid, output.subarray(offset, offset + size)); offset += size + 1; } } let bytes = 0; for (const { mode, oid, file } of entries){ const content = blobs.get(oid); bytes += content.length; if (bytes > 52428800) throw new Error('Git baseline exceeds 50 MiB'); const hash = digest(content); const current = captured.files[file]; const unchanged = hash === current?.hash; files[file] = { hash, mode, content: content.toString('base64'), attribution: unchanged ? current.attribution : [], ...unchanged ? current.committed ? { committed: true } : {} : { committed: '120000' !== mode } }; } return { schemaVersion: 1, id: `head-${captured.baseCommit || 'unborn'}`, fingerprint: digest(JSON.stringify(Object.entries(files).map(([file, value])=>[ file, value.mode, value.hash ]))), baseCommit: captured.baseCommit, dirty: false, capturedAt: captured.capturedAt, files }; } exports.attributionRepository = __webpack_exports__.attributionRepository; exports.captureSource = __webpack_exports__.captureSource; exports.comparisonFile = __webpack_exports__.comparisonFile; exports.finishSourceCapture = __webpack_exports__.finishSourceCapture; exports.loadSourceRecord = __webpack_exports__.loadSourceRecord; exports.loadVersionAttribution = __webpack_exports__.loadVersionAttribution; exports.readAttributionConfig = __webpack_exports__.readAttributionConfig; exports.sourceCommitBaseline = __webpack_exports__.sourceCommitBaseline; for(var __rspack_i in __webpack_exports__)if (-1 === [ "attributionRepository", "captureSource", "comparisonFile", "finishSourceCapture", "loadSourceRecord", "loadVersionAttribution", "readAttributionConfig", "sourceCommitBaseline" ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i]; Object.defineProperty(exports, '__esModule', { value: true }); //# sourceMappingURL=source.js.map