@elizaos/plugin-gitpathologist 2.0.3-beta.2 → 2.0.3-beta.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.
- package/dist/cjs/index.cjs +1079 -0
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.js.map +20 -0
- package/dist/index.d.ts +2 -0
- package/dist/node/actions/git-pathology.d.ts +13 -0
- package/dist/node/actions/git-pathology.d.ts.map +1 -0
- package/dist/node/cache/report-cache.d.ts +26 -0
- package/dist/node/cache/report-cache.d.ts.map +1 -0
- package/dist/node/index.d.ts +14 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +1023 -0
- package/dist/node/index.js.map +20 -0
- package/dist/node/pipeline/classify.d.ts +13 -0
- package/dist/node/pipeline/classify.d.ts.map +1 -0
- package/dist/node/pipeline/inflect.d.ts +19 -0
- package/dist/node/pipeline/inflect.d.ts.map +1 -0
- package/dist/node/pipeline/narrate.d.ts +22 -0
- package/dist/node/pipeline/narrate.d.ts.map +1 -0
- package/dist/node/pipeline/scan.d.ts +19 -0
- package/dist/node/pipeline/scan.d.ts.map +1 -0
- package/dist/node/pipeline/score.d.ts +22 -0
- package/dist/node/pipeline/score.d.ts.map +1 -0
- package/dist/node/render.d.ts +3 -0
- package/dist/node/render.d.ts.map +1 -0
- package/dist/node/secret-scrubber.d.ts +17 -0
- package/dist/node/secret-scrubber.d.ts.map +1 -0
- package/dist/node/services/git-pathology-service.d.ts +19 -0
- package/dist/node/services/git-pathology-service.d.ts.map +1 -0
- package/dist/node/types.d.ts +113 -0
- package/dist/node/types.d.ts.map +1 -0
- package/package.json +4 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts", "../../src/actions/git-pathology.ts", "../../src/render.ts", "../../src/services/git-pathology-service.ts", "../../src/cache/report-cache.ts", "../../src/pipeline/classify.ts", "../../src/pipeline/inflect.ts", "../../src/pipeline/narrate.ts", "../../src/secret-scrubber.ts", "../../src/pipeline/scan.ts", "../../src/pipeline/score.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * @elizaos/plugin-gitpathologist\n *\n * Forensic git-history analysis. See README and the GIT_PATHOLOGY action for\n * usage. The plugin auto-detects whether the workspace is a git repository\n * and only registers its action when the GitPathologyService starts.\n */\n\nimport { type IAgentRuntime, logger, type Plugin } from \"@elizaos/core\";\nimport { gitPathologyAction } from \"./actions/git-pathology.ts\";\nimport {\n GIT_PATHOLOGY_SERVICE_NAME,\n GitPathologyService,\n} from \"./services/git-pathology-service.ts\";\n\nconst gitpathologistPlugin: Plugin = {\n name: \"@elizaos/plugin-gitpathologist\",\n description:\n \"Forensic git-history analysis for elizaOS agents: per-surface health timeline, drift inflection detection, rot post-mortem.\",\n init: async (_config: Record<string, string>, _runtime: IAgentRuntime): Promise<void> => {\n logger.info(\"[GitPathology] plugin initialized\");\n },\n async dispose(runtime: IAgentRuntime) {\n const svc = runtime.getService<GitPathologyService>(GIT_PATHOLOGY_SERVICE_NAME);\n await svc?.stop();\n },\n services: [GitPathologyService],\n actions: [gitPathologyAction],\n providers: [],\n};\n\nexport default gitpathologistPlugin;\nexport { gitPathologyAction } from \"./actions/git-pathology.ts\";\nexport {\n GIT_PATHOLOGY_SERVICE_NAME,\n GitPathologyService,\n} from \"./services/git-pathology-service.ts\";\nexport type {\n AnalysisOptions,\n CommitHealthPoint,\n InflectionPoint,\n PathologyReport,\n RotCause,\n SurfaceSpec,\n} from \"./types.ts\";\n",
|
|
6
|
+
"/**\n * GIT_PATHOLOGY action — single multiplex Action that dispatches to the\n * GitPathologyService. Pattern mirrors plugin-agent-orchestrator's TASKS.\n *\n * Actions:\n * report (default) — full pathology report for a surface\n * list — list cached reports for the repo root\n */\n\nimport path from \"node:path\";\nimport type {\n Action,\n ActionResult,\n HandlerCallback,\n HandlerOptions,\n IAgentRuntime,\n Memory,\n State,\n} from \"@elizaos/core\";\nimport { renderReport } from \"../render.ts\";\nimport {\n GIT_PATHOLOGY_SERVICE_NAME,\n type GitPathologyService,\n} from \"../services/git-pathology-service.ts\";\nimport type { AnalysisOptions, Operation, PathologyReport, SurfaceSpec } from \"../types.ts\";\n\ntype GitPathologyOperation = Operation;\n\nconst VALID_ACTIONS: ReadonlySet<GitPathologyOperation> = new Set([\"report\", \"list\"]);\nconst SURFACE_HINT_RE =\n /\\b(pathology|git\\s+history|code\\s+health|drift|rot|inflection|when\\s+did\\s+(?:this\\s+)?(?:code|file|module|package|plugin|service|component|path|repo|repository|branch|commit))\\b/i;\n\nfunction getService(runtime: IAgentRuntime): GitPathologyService | null {\n return runtime.getService<GitPathologyService>(GIT_PATHOLOGY_SERVICE_NAME) ?? null;\n}\n\nfunction paramsRecord(\n options: HandlerOptions | Record<string, unknown> | undefined\n): Record<string, unknown> {\n if (!options || typeof options !== \"object\") return {};\n const parameters = (options as HandlerOptions).parameters;\n if (parameters && typeof parameters === \"object\") {\n return parameters as Record<string, unknown>;\n }\n const params = (options as Record<string, unknown>).params;\n if (params && typeof params === \"object\") return params as Record<string, unknown>;\n return options as Record<string, unknown>;\n}\n\nfunction readAction(params: Record<string, unknown>): GitPathologyOperation {\n const rawValue = params.action;\n const raw = typeof rawValue === \"string\" ? rawValue.toLowerCase() : \"report\";\n return VALID_ACTIONS.has(raw as GitPathologyOperation)\n ? (raw as GitPathologyOperation)\n : \"report\";\n}\n\nfunction readString(params: Record<string, unknown>, key: string): string | undefined {\n const v = params[key];\n return typeof v === \"string\" && v.trim() ? v.trim() : undefined;\n}\n\nfunction readNumber(params: Record<string, unknown>, key: string): number | undefined {\n const v = params[key];\n if (typeof v === \"number\" && Number.isFinite(v)) return v;\n if (typeof v === \"string\" && v.trim()) {\n const parsed = Number.parseInt(v, 10);\n return Number.isFinite(parsed) ? parsed : undefined;\n }\n return undefined;\n}\n\nfunction buildOptions(params: Record<string, unknown>): Partial<AnalysisOptions> {\n const out: Partial<AnalysisOptions> = {};\n const since = readString(params, \"since\");\n if (since) out.since = since;\n const budget = readNumber(params, \"budget\");\n if (typeof budget === \"number\") out.budget = budget;\n const cache = readString(params, \"cache\");\n if (cache === \"auto\" || cache === \"force\" || cache === \"read-only\") out.cache = cache;\n return out;\n}\n\nfunction resolveRepoRoot(): string {\n const fromEnv = process.env.ELIZA_WORKSPACE_DIR;\n const cwd = fromEnv?.trim() ? fromEnv.trim() : process.cwd();\n return path.resolve(cwd);\n}\n\nfunction listResult(service: GitPathologyService, repoRoot: string): ActionResult {\n const summaries = service.listReports(repoRoot);\n if (summaries.length === 0) {\n return {\n success: true,\n text: \"No cached pathology reports for this repo yet.\",\n data: { reports: [] },\n };\n }\n const lines = summaries.map(\n (s) =>\n `- ${s.surface} (${s.commitCount} commits) — HEAD ${s.headSha.slice(0, 7)}, generated ${s.generatedAt}`\n );\n return {\n success: true,\n text: `Cached pathology reports:\\n${lines.join(\"\\n\")}`,\n data: { reports: summaries },\n };\n}\n\nfunction reportResult(report: PathologyReport): ActionResult {\n return {\n success: true,\n text: renderReport(report),\n data: { report },\n };\n}\n\nexport const gitPathologyAction: Action & { suppressPostActionContinuation: true } = {\n name: \"GIT_PATHOLOGY\",\n similes: [\n \"ANALYZE_GIT_PATHOLOGY\",\n \"GIT_HEALTH\",\n \"GIT_FORENSICS\",\n \"PATHOLOGY_REPORT\",\n \"CODE_HISTORY_HEALTH\",\n \"WHERE_DID_ROT_START\",\n ],\n description:\n \"Forensic git-history analysis for a path/glob surface. Returns peaks (peak quality moments), drift inflections (where rot started), and a post-mortem narrative. Use when the user asks 'when did this code get bad', 'where did rot start in X', or 'analyze git pathology for Y'. Actions: report (default), list (show cached reports).\",\n contexts: [\"code\", \"git\", \"general\"],\n suppressPostActionContinuation: true,\n parameters: [\n {\n name: \"action\",\n description: \"Which gitpathologist action: report or list. Default: report.\",\n required: false,\n schema: { type: \"string\" as const, enum: [\"report\", \"list\"] },\n },\n {\n name: \"surface\",\n description: \"Path or glob to analyze (relative to repo root). Required for action=report.\",\n required: false,\n schema: { type: \"string\" as const },\n },\n {\n name: \"since\",\n description: \"Lookback window. ISO date or relative (e.g. '14d', '4w'). Default '14d'.\",\n required: false,\n schema: { type: \"string\" as const },\n },\n {\n name: \"budget\",\n description: \"Max LLM narration calls per analysis. Default 20.\",\n required: false,\n schema: { type: \"integer\" as const, minimum: 0 },\n },\n {\n name: \"cache\",\n description: \"Cache policy: auto (default), force (recompute), read-only (fail on miss).\",\n required: false,\n schema: { type: \"string\" as const, enum: [\"auto\", \"force\", \"read-only\"] },\n },\n ],\n validate: async (runtime: IAgentRuntime, message: Memory) => {\n if (!getService(runtime)) return false;\n const content = message.content as { text?: unknown; params?: unknown };\n const params =\n content.params && typeof content.params === \"object\"\n ? (content.params as Record<string, unknown>)\n : null;\n if (params && typeof params.action === \"string\") {\n return true;\n }\n if (params && typeof params.surface === \"string\") return true;\n const text = typeof content.text === \"string\" ? content.text : \"\";\n return SURFACE_HINT_RE.test(text);\n },\n handler: async (\n runtime: IAgentRuntime,\n _message: Memory,\n _state?: State,\n options?: HandlerOptions,\n callback?: HandlerCallback\n ): Promise<ActionResult> => {\n const service = getService(runtime);\n if (!service) {\n const text = \"GitPathologyService not registered on runtime.\";\n return { success: false, text, error: \"SERVICE_UNAVAILABLE\" };\n }\n const params = paramsRecord(options);\n const action = readAction(params);\n const repoRoot = resolveRepoRoot();\n\n if (action === \"list\") {\n const result = listResult(service, repoRoot);\n if (callback && typeof result.text === \"string\") {\n await callback({ text: result.text });\n }\n return result;\n }\n\n const surfacePath = readString(params, \"surface\");\n if (!surfacePath) {\n const text = \"action=report requires a `surface` param (path or glob relative to repo root).\";\n if (callback) await callback({ text });\n return { success: false, text, error: \"MISSING_SURFACE\" };\n }\n const surface: SurfaceSpec = { path: surfacePath, repoRoot };\n const overrides = buildOptions(params);\n\n let report: PathologyReport;\n try {\n report = await service.runReport(surface, overrides);\n } catch (err) {\n const text = `Git pathology analysis failed: ${(err as Error).message}`;\n if (callback) await callback({ text });\n return { success: false, text, error: \"ANALYSIS_FAILED\" };\n }\n const result = reportResult(report);\n if (callback && typeof result.text === \"string\") {\n await callback({ text: result.text });\n }\n return result;\n },\n};\n",
|
|
7
|
+
"import type { InflectionPoint, PathologyReport, RotCause } from \"./types.ts\";\n\nfunction short(sha: string): string {\n return sha.slice(0, 7);\n}\n\nfunction inflectionLine(point: InflectionPoint): string {\n const direction = point.delta >= 0 ? \"+\" : \"\";\n return `- \\`${short(point.sha)}\\` (${point.date.slice(0, 10)}, ${point.author}): score ${point.score.toFixed(2)} ${direction}${point.delta.toFixed(2)} — ${point.reasonShort}`;\n}\n\nfunction rotCauseBlock(cause: RotCause): string {\n const [from, to] = cause.shaRange;\n const evidence =\n cause.evidence.length > 0 ? `\\n - Evidence: ${cause.evidence.map(short).join(\", \")}` : \"\";\n return `### ${cause.category} — \\`${short(from)}\\`..\\`${short(to)}\\`\\n\\n${cause.narrative}${evidence}`;\n}\n\nexport function renderReport(report: PathologyReport): string {\n const window = `${report.window.since.slice(0, 10)} → ${report.window.until.slice(0, 10)}`;\n const peaks =\n report.peaks.length === 0\n ? \"_None detected in window._\"\n : report.peaks.map(inflectionLine).join(\"\\n\");\n const drifts =\n report.drifts.length === 0\n ? \"_None detected in window._\"\n : report.drifts.map(inflectionLine).join(\"\\n\");\n const causes =\n report.rotCauses.length === 0\n ? \"_No drift narration generated. Either no drifts detected, or budget = 0._\"\n : report.rotCauses.map(rotCauseBlock).join(\"\\n\\n\");\n const authors = report.authors.length > 0 ? report.authors.join(\", \") : \"_none_\";\n\n return `# Git Pathology — \\`${report.surface}\\`\n\n**Repo:** \\`${report.repoRoot}\\`\n**Window:** ${window}\n**HEAD:** \\`${short(report.headSha)}\\`\n**Commits analyzed:** ${report.commitCount} (${authors})\n**LLM calls:** ${report.llmCalls}\n\n## Peaks (local maxima of health)\n\n${peaks}\n\n## Drift inflections (sustained downturns)\n\n${drifts}\n\n## Rot post-mortem\n\n${causes}\n`;\n}\n",
|
|
8
|
+
"/**\n * GitPathologyService — orchestrates the gitpathologist pipeline.\n *\n * One service per agent runtime. Actions call {@link runReport}; the service\n * handles cache check, scan → classify → score → inflect → narrate, and cache\n * write. No internal background work; pure on-demand.\n */\n\nimport { type IAgentRuntime, logger, Service } from \"@elizaos/core\";\nimport { createReportCache, defaultCacheDir, makeCacheKey } from \"../cache/report-cache.ts\";\nimport { classify } from \"../pipeline/classify.ts\";\nimport { findInflections } from \"../pipeline/inflect.ts\";\nimport { narrate } from \"../pipeline/narrate.ts\";\nimport { headSha as readHeadSha, resolveSurfacePath, scan } from \"../pipeline/scan.ts\";\nimport { score } from \"../pipeline/score.ts\";\nimport { scrubSecretsDeep } from \"../secret-scrubber.ts\";\nimport type {\n AnalysisOptions,\n CachedReportSummary,\n PathologyReport,\n SurfaceSpec,\n} from \"../types.ts\";\n\nexport const GIT_PATHOLOGY_SERVICE_NAME = \"git_pathology\";\nconst LOG_PREFIX = \"[GitPathologyService]\";\n\nconst DEFAULT_OPTIONS: AnalysisOptions = {\n since: \"14d\",\n budget: 20,\n cache: \"auto\",\n};\n\nfunction defaultBudget(): number {\n const raw = process.env.GITPATHOLOGIST_BUDGET?.trim();\n if (!raw) return DEFAULT_OPTIONS.budget;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_OPTIONS.budget;\n}\n\nexport class GitPathologyService extends Service {\n static serviceType: string = GIT_PATHOLOGY_SERVICE_NAME;\n capabilityDescription =\n \"Forensic git-history analysis: per-surface health timeline, drift inflection detection, rot post-mortem.\";\n\n static async start(runtime: IAgentRuntime): Promise<GitPathologyService> {\n logger.info(`${LOG_PREFIX} starting`);\n return new GitPathologyService(runtime);\n }\n\n async stop(): Promise<void> {\n // No background work to stop.\n }\n\n async runReport(\n surface: SurfaceSpec,\n overrides: Partial<AnalysisOptions> = {}\n ): Promise<PathologyReport> {\n const options = { ...DEFAULT_OPTIONS, budget: defaultBudget(), ...overrides };\n const cacheDir = defaultCacheDir(surface.repoRoot);\n const cache = createReportCache(cacheDir);\n const cacheKey = makeCacheKey({ surface: surface.path, since: options.since });\n const currentHead = readHeadSha(surface.repoRoot);\n\n if (options.cache !== \"force\") {\n const cached = cache.read(cacheKey);\n if (cached && cached.headSha === currentHead) {\n logger.info(`${LOG_PREFIX} cache hit ${cacheKey.slice(0, 12)} surface=${surface.path}`);\n return scrubSecretsDeep(cached);\n }\n if (options.cache === \"read-only\") {\n throw new Error(\n `gitpathology cache miss for ${surface.path} (HEAD changed or no prior report)`\n );\n }\n }\n\n const raw = scan(surface, { since: options.since });\n if (raw.length === 0) {\n const empty = scrubSecretsDeep(emptyReport(surface, options, currentHead, cacheKey));\n cache.write(empty);\n return empty;\n }\n\n const chronological = [...raw].reverse();\n const classified = classify(chronological);\n const points = score(classified);\n const { peaks, drifts } = findInflections(points);\n const { rotCauses, llmCalls } = await narrate(this.runtime ?? null, {\n surfacePath: resolveSurfacePath(surface),\n repoRoot: surface.repoRoot,\n timeline: points,\n drifts,\n budget: options.budget,\n });\n\n const oldest = points[0]?.date ?? new Date().toISOString();\n const newest = points[points.length - 1]?.date ?? new Date().toISOString();\n const authors = Array.from(new Set(points.map((p) => p.author))).sort();\n\n const report: PathologyReport = {\n surface: surface.path,\n repoRoot: surface.repoRoot,\n window: { since: oldest, until: newest },\n commitCount: points.length,\n authors,\n timeline: points,\n peaks,\n drifts,\n rotCauses,\n llmCalls,\n headSha: currentHead,\n generatedAt: new Date().toISOString(),\n cacheKey,\n };\n\n const safeReport = scrubSecretsDeep(report);\n cache.write(safeReport);\n logger.info(\n `${LOG_PREFIX} report written ${cacheKey.slice(0, 12)} surface=${surface.path} commits=${points.length} llm=${llmCalls}`\n );\n return safeReport;\n }\n\n listReports(repoRoot: string): CachedReportSummary[] {\n return createReportCache(defaultCacheDir(repoRoot)).list();\n }\n}\n\nfunction emptyReport(\n surface: SurfaceSpec,\n options: AnalysisOptions,\n headSha: string,\n cacheKey: string\n): PathologyReport {\n const now = new Date().toISOString();\n return {\n surface: surface.path,\n repoRoot: surface.repoRoot,\n window: { since: options.since, until: now },\n commitCount: 0,\n authors: [],\n timeline: [],\n peaks: [],\n drifts: [],\n rotCauses: [],\n llmCalls: 0,\n headSha,\n generatedAt: now,\n cacheKey,\n };\n}\n",
|
|
9
|
+
"/**\n * On-disk cache for {@link PathologyReport}s.\n *\n * Layout: `<cacheDir>/<sha256(surface + since)>.json`.\n *\n * The cache stores the HEAD sha at analysis time. A subsequent run with a\n * different HEAD is treated as a miss — incremental splice is a v2 goal, not\n * needed for the first-pass \"fast repeat call\" win.\n */\n\nimport { createHash } from \"node:crypto\";\nimport {\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport path from \"node:path\";\nimport type { CachedReportSummary, PathologyReport } from \"../types.ts\";\n\nexport interface CacheKeyInput {\n surface: string;\n since: string;\n}\n\nexport function makeCacheKey(input: CacheKeyInput): string {\n return createHash(\"sha256\").update(`${input.surface}\\0${input.since}`).digest(\"hex\");\n}\n\nexport function defaultCacheDir(repoRoot: string): string {\n const override = process.env.GITPATHOLOGIST_CACHE_DIR?.trim();\n if (override) {\n return path.isAbsolute(override) ? override : path.join(repoRoot, override);\n }\n return path.join(repoRoot, \".eliza\", \"gitpathology\");\n}\n\nexport function ensureCacheDir(cacheDir: string): void {\n if (!existsSync(cacheDir)) mkdirSync(cacheDir, { recursive: true });\n}\n\nexport interface ReportCache {\n readonly dir: string;\n read(key: string): PathologyReport | null;\n write(report: PathologyReport): void;\n list(): CachedReportSummary[];\n isFreshFor(key: string, currentHeadSha: string): boolean;\n}\n\nexport function createReportCache(cacheDir: string): ReportCache {\n ensureCacheDir(cacheDir);\n const pathFor = (key: string) => path.join(cacheDir, `${key}.json`);\n return {\n dir: cacheDir,\n read(key) {\n const file = pathFor(key);\n if (!existsSync(file)) return null;\n try {\n const raw = readFileSync(file, \"utf8\");\n return JSON.parse(raw) as PathologyReport;\n } catch {\n return null;\n }\n },\n write(report) {\n const file = pathFor(report.cacheKey);\n const tmp = path.join(cacheDir, `.${report.cacheKey}.${process.pid}.${Date.now()}.tmp`);\n writeFileSync(tmp, JSON.stringify(report, null, 2), \"utf8\");\n renameSync(tmp, file);\n },\n list() {\n if (!existsSync(cacheDir)) return [];\n const files = readdirSync(cacheDir).filter((f) => f.endsWith(\".json\"));\n const out: CachedReportSummary[] = [];\n for (const file of files) {\n try {\n const full = path.join(cacheDir, file);\n const stat = statSync(full);\n const raw = readFileSync(full, \"utf8\");\n const report = JSON.parse(raw) as PathologyReport;\n out.push({\n cacheKey: report.cacheKey,\n surface: report.surface,\n generatedAt: report.generatedAt,\n headSha: report.headSha,\n commitCount: report.commitCount,\n sizeBytes: stat.size,\n });\n } catch {}\n }\n return out.sort((a, b) => b.generatedAt.localeCompare(a.generatedAt));\n },\n isFreshFor(key, currentHeadSha) {\n const cached = this.read(key);\n return cached?.headSha === currentHeadSha;\n },\n };\n}\n",
|
|
10
|
+
"/**\n * Step 2 — classify: per-commit triage.\n *\n * Rules-first (cheap, deterministic). Anything matching a conventional commit\n * prefix or obvious revert/merge/WIP signal is classified directly. The\n * remainder are marked \"other\" by rules. Optional LLM batching can refine\n * those if a model is available; it stays out of the default path to avoid LLM coupling here.\n * Score+inflect work fine with \"other\".\n */\n\nimport type { ClassifiedCommit, CommitType, RawCommit } from \"../types.ts\";\n\nconst PREFIX_MAP: Record<string, CommitType> = {\n feat: \"feature\",\n feature: \"feature\",\n fix: \"fix\",\n bug: \"fix\",\n hotfix: \"fix\",\n refactor: \"refactor\",\n perf: \"refactor\",\n revert: \"revert\",\n chore: \"chore\",\n docs: \"chore\",\n doc: \"chore\",\n style: \"chore\",\n test: \"chore\",\n ci: \"chore\",\n build: \"chore\",\n};\n\nconst CONVENTIONAL_RE = /^([a-z]+)(?:\\(([^)]+)\\))?!?:\\s*(.+)$/i;\nconst WIP_RE = /^(?:wip|fixup!|squash!|amend!)\\b/i;\nconst REVERT_SUBJECT_RE = /^Revert\\b/;\nconst MERGE_SUBJECT_RE = /^Merge\\b/;\n\nexport function classifyOne(commit: RawCommit): ClassifiedCommit {\n const subject = commit.subject.trim();\n const riskFlags: string[] = [];\n let type: CommitType = \"other\";\n let scope: string | undefined;\n\n if (commit.parents.length > 1) {\n type = \"merge\";\n } else if (MERGE_SUBJECT_RE.test(subject)) {\n type = \"merge\";\n } else if (REVERT_SUBJECT_RE.test(subject)) {\n type = \"revert\";\n riskFlags.push(\"revert-subject\");\n } else if (WIP_RE.test(subject)) {\n type = \"wip\";\n riskFlags.push(\"wip-message\");\n } else {\n const match = subject.match(CONVENTIONAL_RE);\n if (match) {\n const prefix = (match[1] ?? \"\").toLowerCase();\n const mapped = PREFIX_MAP[prefix];\n if (mapped) {\n type = mapped;\n scope = match[2];\n }\n }\n }\n\n const churn = commit.files.reduce((acc, f) => acc + f.added + f.deleted, 0);\n if (churn >= 500) riskFlags.push(\"large-churn\");\n if (commit.files.length >= 20) riskFlags.push(\"wide-blast\");\n if (subject.length < 12 && type === \"other\") riskFlags.push(\"terse-message\");\n if (subject.includes(\"!:\")) riskFlags.push(\"breaking\");\n\n return {\n ...commit,\n type,\n scope,\n riskFlags,\n classifiedBy: \"rule\",\n };\n}\n\nexport function classify(commits: RawCommit[]): ClassifiedCommit[] {\n return commits.map(classifyOne);\n}\n",
|
|
11
|
+
"/**\n * Step 4 — inflect: find peaks and drift onsets in the health timeline.\n *\n * Peak = local maximum of the EMA score over a small window. We require the\n * point to dominate its neighbours strictly on both sides and to be at least\n * PEAK_MIN above zero — otherwise everything is a \"peak\" in flat early history.\n *\n * Drift onset = a commit i where the average score over the next WINDOW\n * commits is at least DRIFT_DROP below the score at i. The point itself is\n * the inflection; subsequent commits realize the decline.\n *\n * Both lists are sorted by absolute significance and capped.\n */\n\nimport type { CommitHealthPoint, InflectionPoint } from \"../types.ts\";\n\nconst PEAK_WINDOW = 2;\nconst PEAK_MIN_SCORE = 0.05;\nconst PEAK_LIMIT = 5;\n\nconst DRIFT_WINDOW = 5;\nconst DRIFT_DROP = 0.25;\nconst DRIFT_LIMIT = 5;\n\nfunction toInflection(point: CommitHealthPoint, reason: string): InflectionPoint {\n return {\n sha: point.sha,\n date: point.date,\n author: point.author,\n score: point.score,\n delta: point.delta,\n reasonShort: reason,\n };\n}\n\nfunction reasonForPeak(point: CommitHealthPoint): string {\n const parts: string[] = [];\n if (point.type === \"feature\") parts.push(\"feature landed\");\n else if (point.type === \"refactor\") parts.push(\"clean refactor\");\n else if (point.type === \"fix\") parts.push(\"targeted fix\");\n else parts.push(`${point.type} commit`);\n if (point.riskFlags.includes(\"large-churn\") === false && point.churn < 200) {\n parts.push(\"low churn\");\n }\n if (point.delta > 0.4) parts.push(\"strong delta\");\n return parts.join(\", \") || \"local maximum\";\n}\n\nfunction reasonForDrift(point: CommitHealthPoint, avgAfter: number): string {\n const drop = (point.score - avgAfter).toFixed(2);\n const flags = point.riskFlags.length > 0 ? ` flags=${point.riskFlags.join(\"|\")}` : \"\";\n return `score drops ${drop} over next ${DRIFT_WINDOW} commits${flags}`;\n}\n\nfunction avg(points: CommitHealthPoint[], from: number, count: number): number {\n let sum = 0;\n let taken = 0;\n for (let i = from; i < points.length && taken < count; i++) {\n const p = points[i];\n if (!p) continue;\n sum += p.score;\n taken += 1;\n }\n return taken === 0 ? 0 : sum / taken;\n}\n\nexport function findInflections(points: CommitHealthPoint[]): {\n peaks: InflectionPoint[];\n drifts: InflectionPoint[];\n} {\n const peaks: InflectionPoint[] = [];\n const drifts: InflectionPoint[] = [];\n\n for (let i = 0; i < points.length; i++) {\n const point = points[i];\n if (!point) continue;\n if (point.score < PEAK_MIN_SCORE) continue;\n const window = points.slice(Math.max(0, i - PEAK_WINDOW), i + PEAK_WINDOW + 1);\n // Peak = max-in-window with at least one strictly lower neighbour. On\n // plateaus we keep the first occurrence: subsequent identical scores\n // are rejected because they tie with the earlier (already-picked) peak.\n const isMaxInWindow = window.every((p) => p.score <= point.score);\n const hasStrictlyLessNeighbour = window.some((p) => p.score < point.score);\n if (!isMaxInWindow || !hasStrictlyLessNeighbour) continue;\n // Reject a tied score within the same window — we already picked an\n // earlier peak with the same score, so this one would be a duplicate.\n const tiedEarlier = peaks.some((existing) => {\n const prevIdx = points.findIndex((p) => p.sha === existing.sha);\n return prevIdx >= 0 && prevIdx >= i - PEAK_WINDOW && existing.score === point.score;\n });\n if (tiedEarlier) continue;\n peaks.push(toInflection(point, reasonForPeak(point)));\n }\n\n for (let i = 0; i < points.length; i++) {\n const point = points[i];\n if (!point) continue;\n if (i + DRIFT_WINDOW >= points.length) break;\n const after = avg(points, i + 1, DRIFT_WINDOW);\n const drop = point.score - after;\n if (drop >= DRIFT_DROP) {\n drifts.push(toInflection(point, reasonForDrift(point, after)));\n }\n }\n\n peaks.sort((a, b) => b.score - a.score);\n drifts.sort((a, b) => b.score - b.delta - (a.score - a.delta));\n\n return {\n peaks: peaks.slice(0, PEAK_LIMIT),\n drifts: drifts.slice(0, DRIFT_LIMIT),\n };\n}\n",
|
|
12
|
+
"/**\n * Step 5 — narrate: LLM post-mortem for drift inflections.\n *\n * One LLM call per drift, capped by `budget`.\n *\n * All commit text + diff snippets pass through {@link scrubSecrets} before\n * leaving the process.\n */\n\nimport { type IAgentRuntime, logger, ModelType } from \"@elizaos/core\";\nimport { scrubSecrets } from \"../secret-scrubber.ts\";\nimport type { CommitHealthPoint, InflectionPoint, RotCategory, RotCause } from \"../types.ts\";\nimport { fetchDiffSnippet } from \"./scan.ts\";\n\nconst LOG_PREFIX = \"[GitPathology/narrate]\";\n\nconst VALID_CATEGORIES: ReadonlySet<RotCategory> = new Set([\n \"rushed-fix\",\n \"scope-creep\",\n \"bad-merge\",\n \"revert-cycle\",\n \"churn-spiral\",\n \"other\",\n]);\n\nexport interface NarrateContext {\n surfacePath: string;\n repoRoot: string;\n timeline: CommitHealthPoint[];\n drifts: InflectionPoint[];\n budget: number;\n}\n\ninterface UseModelLike {\n useModel?: (modelType: string, options: Record<string, unknown>) => Promise<unknown>;\n}\n\nexport async function narrate(\n runtime: IAgentRuntime | null,\n ctx: NarrateContext\n): Promise<{ rotCauses: RotCause[]; llmCalls: number }> {\n const rotCauses: RotCause[] = [];\n let llmCalls = 0;\n const indexBySha = new Map<string, number>(ctx.timeline.map((point, idx) => [point.sha, idx]));\n const useModelFn = (runtime as UseModelLike | null)?.useModel;\n const budget = Math.max(0, Math.floor(ctx.budget));\n\n for (const drift of ctx.drifts) {\n const idx = indexBySha.get(drift.sha);\n if (idx === undefined) continue;\n const point = ctx.timeline[idx];\n if (!point) continue;\n const before = ctx.timeline.slice(Math.max(0, idx - 3), idx);\n const after = ctx.timeline.slice(idx + 1, idx + 4);\n const fallback = deterministicRotCause(point, before, after, drift);\n\n if (typeof useModelFn === \"function\" && llmCalls < budget) {\n const diff = scrubSecrets(\n fetchDiffSnippet(ctx.repoRoot, point.sha, ctx.surfacePath, 8 * 1024)\n );\n try {\n const result = await callModel(\n useModelFn,\n buildPrompt(ctx.surfacePath, point, before, after, diff)\n );\n llmCalls += 1;\n const parsed = parseRotCause(result);\n if (parsed) {\n rotCauses.push({\n ...fallback,\n category: parsed.category,\n narrative: parsed.narrative,\n });\n continue;\n }\n logger.warn(`${LOG_PREFIX} model returned unparseable rot cause for ${point.sha}`);\n } catch (err) {\n logger.warn(`${LOG_PREFIX} model call failed for ${point.sha}: ${(err as Error).message}`);\n }\n } else if (typeof useModelFn !== \"function\" && llmCalls === 0 && rotCauses.length === 0) {\n logger.warn(`${LOG_PREFIX} runtime has no useModel; using deterministic rot-cause fallback`);\n }\n\n rotCauses.push(fallback);\n }\n\n return { rotCauses, llmCalls };\n}\n\nfunction deterministicRotCause(\n point: CommitHealthPoint,\n before: CommitHealthPoint[],\n after: CommitHealthPoint[],\n drift: InflectionPoint\n): RotCause {\n const category = categorizeDeterministically(point, before, after);\n const flags = point.riskFlags.length > 0 ? point.riskFlags.join(\", \") : \"no explicit flags\";\n const previousScore = before.at(-1)?.score;\n const nextScore = after.at(-1)?.score;\n const narrative = [\n `${point.sha.slice(0, 7)} marks a ${Math.abs(drift.delta).toFixed(2)}-point quality drop on this surface, with ${point.churn} churn across ${point.files.length} file(s) and ${flags}.`,\n `The surrounding window moves from ${typeof previousScore === \"number\" ? previousScore.toFixed(2) : \"no prior score\"} to ${point.score.toFixed(2)}${typeof nextScore === \"number\" ? ` and then ${nextScore.toFixed(2)}` : \"\"}, so this commit is a deterministic inflection even without LLM narration.`,\n ].join(\" \");\n return {\n shaRange: rangeFor(point, after),\n category,\n evidence: evidenceShas(point, before, after),\n narrative,\n };\n}\n\nfunction categorizeDeterministically(\n point: CommitHealthPoint,\n before: CommitHealthPoint[],\n after: CommitHealthPoint[]\n): RotCategory {\n const subject = point.subject.toLowerCase();\n const flags = new Set(point.riskFlags.map((flag) => flag.toLowerCase()));\n if (point.type === \"revert\" || subject.includes(\"revert\")) return \"revert-cycle\";\n if (point.type === \"merge\" || flags.has(\"merge\")) return \"bad-merge\";\n if (flags.has(\"hotfix\") || subject.includes(\"hotfix\") || subject.includes(\"quick fix\")) {\n return \"rushed-fix\";\n }\n const neighboringChurn = [...before, ...after].reduce((sum, commit) => sum + commit.churn, 0);\n if (point.churn > 500 || neighboringChurn > 1000) return \"churn-spiral\";\n if (point.files.length > 8 || flags.has(\"large-change\")) return \"scope-creep\";\n return \"other\";\n}\n\nfunction rangeFor(point: CommitHealthPoint, after: CommitHealthPoint[]): [string, string] {\n const last = after.length > 0 ? after[after.length - 1] : null;\n return [point.sha, last ? last.sha : point.sha];\n}\n\nfunction evidenceShas(\n point: CommitHealthPoint,\n before: CommitHealthPoint[],\n after: CommitHealthPoint[]\n): string[] {\n return [...before.map((p) => p.sha), point.sha, ...after.map((p) => p.sha)];\n}\n\nfunction buildPrompt(\n surface: string,\n point: CommitHealthPoint,\n before: CommitHealthPoint[],\n after: CommitHealthPoint[],\n diff: string\n): string {\n const fmt = (p: CommitHealthPoint) =>\n ` ${p.sha.slice(0, 7)} [${p.type}] (${p.churn} churn, score ${p.score.toFixed(2)}) ${scrubSecrets(p.subject)}`;\n return [\n \"You are diagnosing the start of a code-quality decline in a git repository surface.\",\n \"\",\n `Surface: ${surface}`,\n `Drift commit: ${point.sha.slice(0, 7)} by ${point.author} on ${point.date.slice(0, 10)}`,\n ` Subject: ${scrubSecrets(point.subject)}`,\n ` Type: ${point.type} Risk flags: ${point.riskFlags.join(\", \") || \"(none)\"} Churn: ${point.churn} Files: ${point.files.length}`,\n ` Score: ${point.score.toFixed(2)} Delta: ${point.delta.toFixed(2)}`,\n \"\",\n \"Commits immediately before (oldest first):\",\n before.length === 0 ? \" (none in window)\" : before.map(fmt).join(\"\\n\"),\n \"\",\n \"Commits immediately after (oldest first):\",\n after.length === 0 ? \" (none in window)\" : after.map(fmt).join(\"\\n\"),\n \"\",\n \"Diff snippet of the drift commit (secrets redacted):\",\n diff || \" (no diff available)\",\n \"\",\n \"Classify the most likely cause from this set:\",\n ' \"rushed-fix\", \"scope-creep\", \"bad-merge\", \"revert-cycle\", \"churn-spiral\", \"other\"',\n \"\",\n \"Then write a 2-3 sentence narrative explaining WHY this commit looks like the start of decline. Reference specific evidence from the commits or diff above.\",\n \"\",\n 'Respond with exactly one JSON object: {\"category\": \"<one of the above>\", \"narrative\": \"<2-3 sentences>\"}',\n ].join(\"\\n\");\n}\n\nasync function callModel(\n useModelFn: NonNullable<UseModelLike[\"useModel\"]>,\n prompt: string\n): Promise<string> {\n const result = await useModelFn(ModelType.TEXT_SMALL, {\n prompt,\n temperature: 0.2,\n stream: false,\n });\n if (typeof result !== \"string\") return \"\";\n return result;\n}\n\nfunction parseRotCause(raw: string): { category: RotCategory; narrative: string } | null {\n if (!raw) return null;\n const jsonStart = raw.indexOf(\"{\");\n const jsonEnd = raw.lastIndexOf(\"}\");\n if (jsonStart < 0 || jsonEnd <= jsonStart) return null;\n const slice = raw.slice(jsonStart, jsonEnd + 1);\n try {\n const obj = JSON.parse(slice) as { category?: unknown; narrative?: unknown };\n const category = typeof obj.category === \"string\" ? obj.category : \"other\";\n const narrative = typeof obj.narrative === \"string\" ? obj.narrative.trim() : \"\";\n if (!narrative) return null;\n const safeCategory = VALID_CATEGORIES.has(category as RotCategory)\n ? (category as RotCategory)\n : \"other\";\n return { category: safeCategory, narrative };\n } catch {\n return null;\n }\n}\n",
|
|
13
|
+
"/**\n * Local secret scrubber for raw text (commit messages, diff snippets).\n *\n * Kept independent of plugin-training's privacy-filter — that filter operates\n * on trajectory objects, not raw strings, and pulling it in would create a\n * cross-plugin dependency for one regex pass.\n *\n * Patterns aligned with plugin-training/privacy-filter:\n * - Anthropic / OpenAI style keys (sk-ant-..., sk-...)\n * - GitHub PATs (ghp_, gho_, ghu_, ghs_, ghr_)\n * - AWS access keys (AKIA...)\n * - Bearer tokens in headers\n * - Long opaque hex/base64 chunks that follow known secret-y env names\n */\n\nconst SECRET_PATTERNS: Array<{ label: string; pattern: RegExp }> = [\n { label: \"ANTHROPIC\", pattern: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g },\n { label: \"OPENAI\", pattern: /\\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\\b/g },\n { label: \"GITHUB\", pattern: /\\bgh[pousr]_[A-Za-z0-9]{20,}\\b/g },\n { label: \"AWS\", pattern: /\\bAKIA[0-9A-Z]{16}\\b/g },\n { label: \"SLACK\", pattern: /\\bxox[bpoa]-[A-Za-z0-9-]{10,}\\b/g },\n { label: \"BEARER\", pattern: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{20,}/g },\n];\n\nconst SECRET_ENV_NAME =\n /\\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASS|API|AUTH|CREDENTIAL)[A-Z0-9_]*)\\s*[:=]\\s*['\"]?([A-Za-z0-9_\\-.+/=]{12,})['\"]?/g;\n\nexport function scrubSecrets(input: string): string {\n if (!input) return input;\n let out = input;\n for (const { label, pattern } of SECRET_PATTERNS) {\n out = out.replace(pattern, `<REDACTED:${label}>`);\n }\n out = out.replace(SECRET_ENV_NAME, (_match, name: string) => `${name}=<REDACTED:ENV>`);\n return out;\n}\n\nexport function scrubSecretsDeep<T>(value: T): T {\n if (typeof value === \"string\") return scrubSecrets(value) as T;\n if (Array.isArray(value)) return value.map(scrubSecretsDeep) as T;\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n out[k] = scrubSecretsDeep(v);\n }\n return out as T;\n }\n return value;\n}\n",
|
|
14
|
+
"/**\n * Step 1 — scan: deterministic git log parsing.\n *\n * Calls `git log` once with a NUL-separated custom format and `--name-status\n * --numstat` for file-level change info. No LLM. Diff snippets are NOT\n * captured here — narrate() pulls those per-drift via `git show` to keep\n * scan cheap.\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport path from \"node:path\";\nimport type { FileTouch, RawCommit, SurfaceSpec } from \"../types.ts\";\n\nconst RECORD_SEP = \"\\x1e\";\nconst UNIT_SEP = \"\\x1f\";\n\n// Format: <RS>COMMIT<US>sha<US>parents<US>author<US>email<US>date<US>subject<US>body\n// Git appends `--name-status --numstat` lines AFTER the body, inside the same\n// RS-delimited record. The next commit begins with its own leading RS — so we\n// MUST NOT add a trailing RS here, or the file-stats become orphan records.\nconst LOG_FORMAT = `${[`${RECORD_SEP}COMMIT`, \"%H\", \"%P\", \"%an\", \"%ae\", \"%aI\", \"%s\"].join(\n UNIT_SEP\n)}${UNIT_SEP}%b`;\n\nexport interface ScanOptions {\n since: string;\n}\n\nexport function resolveSurfacePath(surface: SurfaceSpec): string {\n if (path.isAbsolute(surface.path)) {\n return path.relative(surface.repoRoot, surface.path) || \".\";\n }\n return surface.path;\n}\n\nexport function runGit(repoRoot: string, args: string[]): string {\n const result = spawnSync(\"git\", args, {\n cwd: repoRoot,\n encoding: \"utf8\",\n maxBuffer: 64 * 1024 * 1024,\n });\n // spawnSync sets `result.error` on ENOENT (git binary missing). The status\n // is null in that case — checking status alone gives an opaque error.\n if (result.error || result.status !== 0) {\n const detail = result.error?.message ?? result.stderr?.toString() ?? \"\";\n throw new Error(`git ${args.join(\" \")} failed: ${detail}`);\n }\n return result.stdout.toString();\n}\n\nexport function headSha(repoRoot: string): string {\n return runGit(repoRoot, [\"rev-parse\", \"HEAD\"]).trim();\n}\n\nfunction inferStatus(added: number, deleted: number): FileTouch[\"status\"] {\n if (added > 0 && deleted === 0) return \"A\";\n if (added === 0 && deleted > 0) return \"D\";\n return \"M\";\n}\n\nfunction parseFileBlock(lines: string[]): FileTouch[] {\n // git log --numstat emits one line per file: \"<added>\\t<deleted>\\t<path>\".\n // For binary files, added and deleted are \"-\". Status (A/M/D) is inferred\n // from the counts — coarse but sufficient for the scoring formula, which\n // only consumes churn and file count, not the precise A/M/D distinction.\n const touches: FileTouch[] = [];\n for (const line of lines) {\n if (!line) continue;\n const parts = line.split(\"\\t\");\n if (parts.length < 3) continue;\n const [addedRaw, deletedRaw, ...pathParts] = parts;\n const addedStr = addedRaw ?? \"0\";\n const deletedStr = deletedRaw ?? \"0\";\n const added = addedStr === \"-\" ? 0 : Number.parseInt(addedStr, 10);\n const deleted = deletedStr === \"-\" ? 0 : Number.parseInt(deletedStr, 10);\n if (!Number.isFinite(added) || !Number.isFinite(deleted)) continue;\n const filePath = pathParts.join(\"\\t\");\n if (!filePath) continue;\n touches.push({ path: filePath, added, deleted, status: inferStatus(added, deleted) });\n }\n return touches;\n}\n\nexport function scan(surface: SurfaceSpec, options: ScanOptions): RawCommit[] {\n const surfacePath = resolveSurfacePath(surface);\n const args = [\n \"log\",\n \"--no-color\",\n `--since=${normalizeSince(options.since)}`,\n `--pretty=format:${LOG_FORMAT}`,\n \"--numstat\",\n \"--no-renames\",\n \"--\",\n surfacePath,\n ];\n const raw = runGit(surface.repoRoot, args);\n if (!raw.trim()) return [];\n\n const records = raw.split(RECORD_SEP).filter((r) => r.trim());\n const commits: RawCommit[] = [];\n for (const record of records) {\n if (!record.startsWith(\"COMMIT\")) continue;\n const rest = record.slice(\"COMMIT\".length);\n const fields = rest.split(UNIT_SEP);\n if (fields.length < 8) continue;\n const [, sha, parentsStr, author, authorEmail, date, subject, bodyAndFiles] = fields;\n if (!sha || !date) continue;\n const lines = (bodyAndFiles ?? \"\").split(\"\\n\");\n const splitIdx = findFileBlockStart(lines);\n const body = lines.slice(0, splitIdx).join(\"\\n\").trim();\n const fileLines = lines.slice(splitIdx).filter((l) => l.length > 0);\n const files = parseFileBlock(fileLines);\n commits.push({\n sha,\n parents: parentsStr ? parentsStr.split(\" \").filter(Boolean) : [],\n author: author ?? \"\",\n authorEmail: authorEmail ?? \"\",\n date,\n subject: subject ?? \"\",\n body,\n files,\n diffSnippet: \"\",\n });\n }\n return commits;\n}\n\nfunction findFileBlockStart(lines: string[]): number {\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] ?? \"\";\n if (!line) continue;\n const first = line.split(\"\\t\")[0] ?? \"\";\n // --numstat lines start with a number (or \"-\" for binary).\n if (/^\\d+$/.test(first) || first === \"-\") return i;\n }\n return lines.length;\n}\n\nconst RELATIVE_SINCE = /^(\\d+)\\s*(d|w|m|y)$/;\n\nexport function normalizeSince(since: string): string {\n const match = since.trim().match(RELATIVE_SINCE);\n if (!match) return since;\n const n = match[1] ?? \"0\";\n const unit = match[2] ?? \"d\";\n const map: Record<string, string> = {\n d: \"days\",\n w: \"weeks\",\n m: \"months\",\n y: \"years\",\n };\n return `${n} ${map[unit] ?? \"days\"} ago`;\n}\n\nexport function fetchDiffSnippet(\n repoRoot: string,\n sha: string,\n surfacePath: string,\n maxBytes = 16 * 1024\n): string {\n const args = [\"show\", \"--no-color\", \"-U2\", \"--format=\", sha, \"--\", surfacePath];\n const result = spawnSync(\"git\", args, {\n cwd: repoRoot,\n encoding: \"utf8\",\n maxBuffer: 8 * 1024 * 1024,\n });\n if (result.status !== 0) return \"\";\n const out = result.stdout.toString();\n return out.length > maxBytes ? `${out.slice(0, maxBytes)}\\n... [truncated]` : out;\n}\n",
|
|
15
|
+
"/**\n * Step 3 — score: per-commit health delta + running EMA.\n *\n * Deterministic. The goal is a stable, explainable score that a human can\n * sanity-check against the actual history. No LLM.\n *\n * Formula sketch (all components small, bounded):\n * delta = baseScore(type)\n * - churnPenalty (log10 of total lines, capped)\n * - filesPenalty (5% per file, capped at 0.5)\n * - wipPenalty (-0.3 if subject signals WIP/fixup)\n * - revertProximityPenalty (-0.5 on the commit that gets reverted within window)\n * + testBonus (+0.2 if any file touches a test path)\n *\n * EMA: score_i = alpha * delta_i + (1 - alpha) * score_{i-1}, alpha = 0.3.\n *\n * The timeline is processed CHRONOLOGICALLY (oldest first). `git log` returns\n * newest-first; the caller reverses before scoring.\n */\n\nimport type { ClassifiedCommit, CommitHealthPoint, CommitType } from \"../types.ts\";\n\nconst ALPHA = 0.3;\nconst REVERT_LOOKBACK = 7;\n\nconst BASE: Record<CommitType, number> = {\n feature: 0.5,\n refactor: 0.4,\n fix: 0.2,\n chore: 0.1,\n merge: 0.0,\n other: 0.0,\n wip: -0.3,\n revert: -0.5,\n};\n\nconst TEST_PATH_RE = /(?:^|\\/)(__tests__|tests?)\\/|\\.(?:test|spec)\\.[jt]sx?$/i;\nconst REVERT_SHA_RE = /\\b([0-9a-f]{7,40})\\b/i;\n\n/**\n * Free under 100 lines (normal commit size), log-scaled penalty above that.\n * Examples:\n * 100 lines → 0 500 lines → 0.28\n * 1000 lines → 0.40 10000 lines → 0.80\n * Capped at 1.2 so a single catastrophic commit cannot overwhelm the EMA.\n */\nfunction churnPenalty(churn: number): number {\n if (churn <= 100) return 0;\n const value = Math.log10(churn / 100) * 0.4;\n return Math.min(value, 1.2);\n}\n\nfunction filesPenalty(count: number): number {\n return Math.min(count * 0.05, 0.5);\n}\n\nfunction testBonus(commit: ClassifiedCommit): number {\n return commit.files.some((f) => TEST_PATH_RE.test(f.path)) ? 0.2 : 0;\n}\n\nfunction wipPenalty(commit: ClassifiedCommit): number {\n return commit.riskFlags.includes(\"wip-message\") ? 0.3 : 0;\n}\n\nfunction findRevertTarget(\n commit: ClassifiedCommit,\n byShortSha: Map<string, number>\n): number | null {\n if (commit.type !== \"revert\") return null;\n const match = commit.body.match(REVERT_SHA_RE) ?? commit.subject.match(REVERT_SHA_RE);\n if (!match) return null;\n const sha = (match[1] ?? \"\").toLowerCase();\n const short = sha.slice(0, 7);\n const idx = byShortSha.get(short);\n return typeof idx === \"number\" ? idx : null;\n}\n\nexport function score(commits: ClassifiedCommit[]): CommitHealthPoint[] {\n const points: CommitHealthPoint[] = [];\n const byShortSha = new Map<string, number>();\n let running = 0;\n\n for (let i = 0; i < commits.length; i++) {\n const commit = commits[i];\n if (!commit) continue;\n const churn = commit.files.reduce((acc, f) => acc + f.added + f.deleted, 0);\n const base = BASE[commit.type];\n const delta =\n base -\n churnPenalty(churn) -\n filesPenalty(commit.files.length) -\n wipPenalty(commit) +\n testBonus(commit);\n running = ALPHA * delta + (1 - ALPHA) * running;\n points.push({ ...commit, delta, score: running, churn });\n byShortSha.set(commit.sha.slice(0, 7), i);\n }\n\n // Apply revert-proximity penalty by mutating the reverted commit's score and\n // re-rolling the EMA forward from that point.\n let needsRecompute = false;\n for (let i = 0; i < points.length; i++) {\n const point = points[i];\n if (!point) continue;\n const targetIdx = findRevertTarget(point, byShortSha);\n if (targetIdx === null) continue;\n const distance = i - targetIdx;\n if (distance <= 0 || distance > REVERT_LOOKBACK) continue;\n const target = points[targetIdx];\n if (!target) continue;\n target.delta -= 0.5;\n target.riskFlags = [...target.riskFlags, \"later-reverted\"];\n needsRecompute = true;\n }\n if (needsRecompute) {\n running = 0;\n for (const point of points) {\n running = ALPHA * point.delta + (1 - ALPHA) * running;\n point.score = running;\n }\n }\n\n return points;\n}\n"
|
|
16
|
+
],
|
|
17
|
+
"mappings": "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",
|
|
18
|
+
"debugId": "1234025F431646EF64756E2164756E21",
|
|
19
|
+
"names": []
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 2 — classify: per-commit triage.
|
|
3
|
+
*
|
|
4
|
+
* Rules-first (cheap, deterministic). Anything matching a conventional commit
|
|
5
|
+
* prefix or obvious revert/merge/WIP signal is classified directly. The
|
|
6
|
+
* remainder are marked "other" by rules. Optional LLM batching can refine
|
|
7
|
+
* those if a model is available; it stays out of the default path to avoid LLM coupling here.
|
|
8
|
+
* Score+inflect work fine with "other".
|
|
9
|
+
*/
|
|
10
|
+
import type { ClassifiedCommit, RawCommit } from "../types.ts";
|
|
11
|
+
export declare function classifyOne(commit: RawCommit): ClassifiedCommit;
|
|
12
|
+
export declare function classify(commits: RawCommit[]): ClassifiedCommit[];
|
|
13
|
+
//# sourceMappingURL=classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../../src/pipeline/classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAc,SAAS,EAAE,MAAM,aAAa,CAAC;AAyB3E,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,gBAAgB,CAyC/D;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,gBAAgB,EAAE,CAEjE"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 4 — inflect: find peaks and drift onsets in the health timeline.
|
|
3
|
+
*
|
|
4
|
+
* Peak = local maximum of the EMA score over a small window. We require the
|
|
5
|
+
* point to dominate its neighbours strictly on both sides and to be at least
|
|
6
|
+
* PEAK_MIN above zero — otherwise everything is a "peak" in flat early history.
|
|
7
|
+
*
|
|
8
|
+
* Drift onset = a commit i where the average score over the next WINDOW
|
|
9
|
+
* commits is at least DRIFT_DROP below the score at i. The point itself is
|
|
10
|
+
* the inflection; subsequent commits realize the decline.
|
|
11
|
+
*
|
|
12
|
+
* Both lists are sorted by absolute significance and capped.
|
|
13
|
+
*/
|
|
14
|
+
import type { CommitHealthPoint, InflectionPoint } from "../types.ts";
|
|
15
|
+
export declare function findInflections(points: CommitHealthPoint[]): {
|
|
16
|
+
peaks: InflectionPoint[];
|
|
17
|
+
drifts: InflectionPoint[];
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=inflect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inflect.d.ts","sourceRoot":"","sources":["../../../src/pipeline/inflect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAoDtE,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,EAAE,GAAG;IAC5D,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B,CA2CA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 5 — narrate: LLM post-mortem for drift inflections.
|
|
3
|
+
*
|
|
4
|
+
* One LLM call per drift, capped by `budget`.
|
|
5
|
+
*
|
|
6
|
+
* All commit text + diff snippets pass through {@link scrubSecrets} before
|
|
7
|
+
* leaving the process.
|
|
8
|
+
*/
|
|
9
|
+
import { type IAgentRuntime } from "@elizaos/core";
|
|
10
|
+
import type { CommitHealthPoint, InflectionPoint, RotCause } from "../types.ts";
|
|
11
|
+
export interface NarrateContext {
|
|
12
|
+
surfacePath: string;
|
|
13
|
+
repoRoot: string;
|
|
14
|
+
timeline: CommitHealthPoint[];
|
|
15
|
+
drifts: InflectionPoint[];
|
|
16
|
+
budget: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function narrate(runtime: IAgentRuntime | null, ctx: NarrateContext): Promise<{
|
|
19
|
+
rotCauses: RotCause[];
|
|
20
|
+
llmCalls: number;
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=narrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"narrate.d.ts","sourceRoot":"","sources":["../../../src/pipeline/narrate.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,eAAe,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAe,QAAQ,EAAE,MAAM,aAAa,CAAC;AAc7F,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD,wBAAsB,OAAO,CAC3B,OAAO,EAAE,aAAa,GAAG,IAAI,EAC7B,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+CtD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 1 — scan: deterministic git log parsing.
|
|
3
|
+
*
|
|
4
|
+
* Calls `git log` once with a NUL-separated custom format and `--name-status
|
|
5
|
+
* --numstat` for file-level change info. No LLM. Diff snippets are NOT
|
|
6
|
+
* captured here — narrate() pulls those per-drift via `git show` to keep
|
|
7
|
+
* scan cheap.
|
|
8
|
+
*/
|
|
9
|
+
import type { RawCommit, SurfaceSpec } from "../types.ts";
|
|
10
|
+
export interface ScanOptions {
|
|
11
|
+
since: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function resolveSurfacePath(surface: SurfaceSpec): string;
|
|
14
|
+
export declare function runGit(repoRoot: string, args: string[]): string;
|
|
15
|
+
export declare function headSha(repoRoot: string): string;
|
|
16
|
+
export declare function scan(surface: SurfaceSpec, options: ScanOptions): RawCommit[];
|
|
17
|
+
export declare function normalizeSince(since: string): string;
|
|
18
|
+
export declare function fetchDiffSnippet(repoRoot: string, sha: string, surfacePath: string, maxBytes?: number): string;
|
|
19
|
+
//# sourceMappingURL=scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../../src/pipeline/scan.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAa,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAarE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAK/D;AAED,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAa/D;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEhD;AA+BD,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,GAAG,SAAS,EAAE,CA0C5E;AAeD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYpD;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,QAAQ,SAAY,GACnB,MAAM,CAUR"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step 3 — score: per-commit health delta + running EMA.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic. The goal is a stable, explainable score that a human can
|
|
5
|
+
* sanity-check against the actual history. No LLM.
|
|
6
|
+
*
|
|
7
|
+
* Formula sketch (all components small, bounded):
|
|
8
|
+
* delta = baseScore(type)
|
|
9
|
+
* - churnPenalty (log10 of total lines, capped)
|
|
10
|
+
* - filesPenalty (5% per file, capped at 0.5)
|
|
11
|
+
* - wipPenalty (-0.3 if subject signals WIP/fixup)
|
|
12
|
+
* - revertProximityPenalty (-0.5 on the commit that gets reverted within window)
|
|
13
|
+
* + testBonus (+0.2 if any file touches a test path)
|
|
14
|
+
*
|
|
15
|
+
* EMA: score_i = alpha * delta_i + (1 - alpha) * score_{i-1}, alpha = 0.3.
|
|
16
|
+
*
|
|
17
|
+
* The timeline is processed CHRONOLOGICALLY (oldest first). `git log` returns
|
|
18
|
+
* newest-first; the caller reverses before scoring.
|
|
19
|
+
*/
|
|
20
|
+
import type { ClassifiedCommit, CommitHealthPoint } from "../types.ts";
|
|
21
|
+
export declare function score(commits: ClassifiedCommit[]): CommitHealthPoint[];
|
|
22
|
+
//# sourceMappingURL=score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../../src/pipeline/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAc,MAAM,aAAa,CAAC;AAyDnF,wBAAgB,KAAK,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,iBAAiB,EAAE,CA8CtE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,eAAe,EAAY,MAAM,YAAY,CAAC;AAkB7E,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAoC5D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local secret scrubber for raw text (commit messages, diff snippets).
|
|
3
|
+
*
|
|
4
|
+
* Kept independent of plugin-training's privacy-filter — that filter operates
|
|
5
|
+
* on trajectory objects, not raw strings, and pulling it in would create a
|
|
6
|
+
* cross-plugin dependency for one regex pass.
|
|
7
|
+
*
|
|
8
|
+
* Patterns aligned with plugin-training/privacy-filter:
|
|
9
|
+
* - Anthropic / OpenAI style keys (sk-ant-..., sk-...)
|
|
10
|
+
* - GitHub PATs (ghp_, gho_, ghu_, ghs_, ghr_)
|
|
11
|
+
* - AWS access keys (AKIA...)
|
|
12
|
+
* - Bearer tokens in headers
|
|
13
|
+
* - Long opaque hex/base64 chunks that follow known secret-y env names
|
|
14
|
+
*/
|
|
15
|
+
export declare function scrubSecrets(input: string): string;
|
|
16
|
+
export declare function scrubSecretsDeep<T>(value: T): T;
|
|
17
|
+
//# sourceMappingURL=secret-scrubber.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scrubber.d.ts","sourceRoot":"","sources":["../../src/secret-scrubber.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAcH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAW/C"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitPathologyService — orchestrates the gitpathologist pipeline.
|
|
3
|
+
*
|
|
4
|
+
* One service per agent runtime. Actions call {@link runReport}; the service
|
|
5
|
+
* handles cache check, scan → classify → score → inflect → narrate, and cache
|
|
6
|
+
* write. No internal background work; pure on-demand.
|
|
7
|
+
*/
|
|
8
|
+
import { type IAgentRuntime, Service } from "@elizaos/core";
|
|
9
|
+
import type { AnalysisOptions, CachedReportSummary, PathologyReport, SurfaceSpec } from "../types.ts";
|
|
10
|
+
export declare const GIT_PATHOLOGY_SERVICE_NAME = "git_pathology";
|
|
11
|
+
export declare class GitPathologyService extends Service {
|
|
12
|
+
static serviceType: string;
|
|
13
|
+
capabilityDescription: string;
|
|
14
|
+
static start(runtime: IAgentRuntime): Promise<GitPathologyService>;
|
|
15
|
+
stop(): Promise<void>;
|
|
16
|
+
runReport(surface: SurfaceSpec, overrides?: Partial<AnalysisOptions>): Promise<PathologyReport>;
|
|
17
|
+
listReports(repoRoot: string): CachedReportSummary[];
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=git-pathology-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-pathology-service.d.ts","sourceRoot":"","sources":["../../../src/services/git-pathology-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,aAAa,EAAU,OAAO,EAAE,MAAM,eAAe,CAAC;AAQpE,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,WAAW,EACZ,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,0BAA0B,kBAAkB,CAAC;AAgB1D,qBAAa,mBAAoB,SAAQ,OAAO;IAC9C,MAAM,CAAC,WAAW,EAAE,MAAM,CAA8B;IACxD,qBAAqB,SACwF;WAEhG,KAAK,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAKlE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,SAAS,CACb,OAAO,EAAE,WAAW,EACpB,SAAS,GAAE,OAAO,CAAC,eAAe,CAAM,GACvC,OAAO,CAAC,eAAe,CAAC;IAmE3B,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,EAAE;CAGrD"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for @elizaos/plugin-gitpathologist.
|
|
3
|
+
*
|
|
4
|
+
* The pipeline produces a {@link PathologyReport} for a given {@link SurfaceSpec}.
|
|
5
|
+
* Phases:
|
|
6
|
+
* scan → {@link RawCommit}[]
|
|
7
|
+
* classify → {@link ClassifiedCommit}[]
|
|
8
|
+
* score → {@link CommitHealthPoint}[]
|
|
9
|
+
* inflect → peaks + drifts
|
|
10
|
+
* narrate → {@link RotCause}[]
|
|
11
|
+
*/
|
|
12
|
+
export type CommitType = "feature" | "fix" | "refactor" | "revert" | "chore" | "wip" | "merge" | "other";
|
|
13
|
+
export type RotCategory = "rushed-fix" | "scope-creep" | "bad-merge" | "revert-cycle" | "churn-spiral" | "other";
|
|
14
|
+
export interface SurfaceSpec {
|
|
15
|
+
/** Path or glob, relative to the repo root. */
|
|
16
|
+
path: string;
|
|
17
|
+
/** Repository root. Defaults to runtime workspace cwd. */
|
|
18
|
+
repoRoot: string;
|
|
19
|
+
}
|
|
20
|
+
export interface AnalysisOptions {
|
|
21
|
+
/** ISO date or relative window (e.g. "14d", "4w"). Default "14d". */
|
|
22
|
+
since: string;
|
|
23
|
+
/** Maximum LLM narration calls. Default 20. */
|
|
24
|
+
budget: number;
|
|
25
|
+
/** Cache policy. */
|
|
26
|
+
cache: "auto" | "force" | "read-only";
|
|
27
|
+
}
|
|
28
|
+
export interface FileTouch {
|
|
29
|
+
path: string;
|
|
30
|
+
added: number;
|
|
31
|
+
deleted: number;
|
|
32
|
+
status: "A" | "M" | "D" | "R" | "C" | "T" | "U" | "X";
|
|
33
|
+
/** Previous path when status is R (rename) or C (copy). */
|
|
34
|
+
fromPath?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface RawCommit {
|
|
37
|
+
sha: string;
|
|
38
|
+
parents: string[];
|
|
39
|
+
author: string;
|
|
40
|
+
authorEmail: string;
|
|
41
|
+
date: string;
|
|
42
|
+
subject: string;
|
|
43
|
+
body: string;
|
|
44
|
+
files: FileTouch[];
|
|
45
|
+
/** Truncated diff for narration phase. Empty when scan was metadata-only. */
|
|
46
|
+
diffSnippet: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ClassifiedCommit extends RawCommit {
|
|
49
|
+
type: CommitType;
|
|
50
|
+
scope?: string;
|
|
51
|
+
riskFlags: string[];
|
|
52
|
+
/** Whether classification came from rules (cheap) or the LLM (batched). */
|
|
53
|
+
classifiedBy: "rule" | "llm";
|
|
54
|
+
}
|
|
55
|
+
export interface CommitHealthPoint extends ClassifiedCommit {
|
|
56
|
+
/** Per-commit health delta. Negative = rot-leaning. */
|
|
57
|
+
delta: number;
|
|
58
|
+
/** Running exponentially-weighted moving average. */
|
|
59
|
+
score: number;
|
|
60
|
+
/** Total touched lines (added + deleted). */
|
|
61
|
+
churn: number;
|
|
62
|
+
}
|
|
63
|
+
export interface InflectionPoint {
|
|
64
|
+
sha: string;
|
|
65
|
+
date: string;
|
|
66
|
+
author: string;
|
|
67
|
+
score: number;
|
|
68
|
+
delta: number;
|
|
69
|
+
reasonShort: string;
|
|
70
|
+
}
|
|
71
|
+
export interface RotCause {
|
|
72
|
+
shaRange: [string, string];
|
|
73
|
+
category: RotCategory;
|
|
74
|
+
evidence: string[];
|
|
75
|
+
narrative: string;
|
|
76
|
+
}
|
|
77
|
+
export interface PathologyReport {
|
|
78
|
+
surface: string;
|
|
79
|
+
repoRoot: string;
|
|
80
|
+
window: {
|
|
81
|
+
since: string;
|
|
82
|
+
until: string;
|
|
83
|
+
};
|
|
84
|
+
commitCount: number;
|
|
85
|
+
authors: string[];
|
|
86
|
+
timeline: CommitHealthPoint[];
|
|
87
|
+
peaks: InflectionPoint[];
|
|
88
|
+
drifts: InflectionPoint[];
|
|
89
|
+
rotCauses: RotCause[];
|
|
90
|
+
/** Counter of LLM calls made for this analysis. */
|
|
91
|
+
llmCalls: number;
|
|
92
|
+
/** Sha of repo HEAD at time of analysis — used for incremental cache. */
|
|
93
|
+
headSha: string;
|
|
94
|
+
generatedAt: string;
|
|
95
|
+
cacheKey: string;
|
|
96
|
+
}
|
|
97
|
+
export type Operation = "report" | "list";
|
|
98
|
+
export interface OperationParams {
|
|
99
|
+
action?: Operation;
|
|
100
|
+
surface?: string;
|
|
101
|
+
since?: string;
|
|
102
|
+
budget?: number;
|
|
103
|
+
cache?: AnalysisOptions["cache"];
|
|
104
|
+
}
|
|
105
|
+
export interface CachedReportSummary {
|
|
106
|
+
cacheKey: string;
|
|
107
|
+
surface: string;
|
|
108
|
+
generatedAt: string;
|
|
109
|
+
headSha: string;
|
|
110
|
+
commitCount: number;
|
|
111
|
+
sizeBytes: number;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elizaos/plugin-gitpathologist",
|
|
3
|
-
"version": "2.0.3-beta.
|
|
3
|
+
"version": "2.0.3-beta.4",
|
|
4
4
|
"description": "Forensic git-history analysis for elizaOS agents: per-surface health timeline, drift inflection detection, rot post-mortem.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/node/index.js",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"format:check": "bunx @biomejs/biome format ."
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@elizaos/core": "2.0.3-beta.
|
|
65
|
+
"@elizaos/core": "2.0.3-beta.4"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@biomejs/biome": "^2.4.14",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"vitest": "^4.1.8"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
|
-
"@elizaos/core": "2.0.3-beta.
|
|
74
|
+
"@elizaos/core": "2.0.3-beta.4"
|
|
75
75
|
},
|
|
76
76
|
"publishConfig": {
|
|
77
77
|
"access": "public"
|
|
@@ -108,5 +108,5 @@
|
|
|
108
108
|
"node": "Node.js build available via exports.node"
|
|
109
109
|
}
|
|
110
110
|
},
|
|
111
|
-
"gitHead": "
|
|
111
|
+
"gitHead": "f76f55793a0fb8d6b869dd8ddce03970de061e34"
|
|
112
112
|
}
|