@sir_yami/reviewd-core 0.1.0 → 0.3.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.
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/browser.js +2 -0
- package/dist/browser.js.map +2 -2
- package/dist/index.js +54 -3
- package/dist/index.js.map +2 -2
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-code-review contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ harness integrations are adapters over it.
|
|
|
11
11
|
npm install @sir_yami/reviewd-core
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
The package is part of the published `reviewd` 0.
|
|
14
|
+
The package is part of the published `reviewd` 0.2.0 release. Use the npm package or a
|
|
15
15
|
local tarball from the same commit.
|
|
16
16
|
|
|
17
17
|
```bash
|
package/dist/browser.js
CHANGED
|
@@ -196,6 +196,7 @@ function fold(state, event) {
|
|
|
196
196
|
case "review/closed": {
|
|
197
197
|
next.status = "closed";
|
|
198
198
|
next.closedAt ??= event.at;
|
|
199
|
+
next.closeVerdict = event.data.verdict;
|
|
199
200
|
break;
|
|
200
201
|
}
|
|
201
202
|
case "review/submitted": {
|
|
@@ -208,6 +209,7 @@ function fold(state, event) {
|
|
|
208
209
|
if (submission.verdict === "approve") {
|
|
209
210
|
next.status = "closed";
|
|
210
211
|
next.closedAt ??= event.at;
|
|
212
|
+
next.closeVerdict ??= "approved";
|
|
211
213
|
}
|
|
212
214
|
break;
|
|
213
215
|
}
|
package/dist/browser.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/review/coverage.ts", "../src/review/fold.ts", "../src/review/final.ts", "../src/review/plan.ts", "../src/review/aggregate.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ManifestFile, ManifestFileReason, ReviewManifest } from '../types.js';\nimport type { CoverageStatus, CoverageTotals, FileCoverage, ReviewCoverage } from './types.js';\n\nexport const COVERAGE_STATUSES: readonly CoverageStatus[] = Object.freeze([\n 'pending',\n 'deep-reviewed',\n 'light-reviewed',\n 'manifest-only',\n 'skipped',\n 'no-hunks',\n 'deferred',\n 'failed',\n]);\n\n/** Statuses that mean work is still owed before a round can be `done`. */\nconst UNRESOLVED_STATUSES = new Set<CoverageStatus>(['pending', 'skipped', 'deferred', 'failed']);\n\n/** Manifest reasons where policy, not the render budget, excluded the content. */\nconst POLICY_REASONS = new Set<ManifestFileReason>(['binary', 'ignored', 'generated']);\n\n/** Manifest reasons that explain why a non-policy file was not reviewed. */\nconst SKIP_REASONS = new Set<ManifestFileReason>([\n 'render-budget',\n 'per-file-cap',\n 'diff-truncated',\n 'unreadable',\n 'legacy-truncated',\n 'symlink',\n]);\n\n/** Warnings that prove the manifest's changed-path listing itself was cut. */\nconst INCOMPLETE_MANIFEST_WARNINGS = new Set([\n 'manifest-listing-truncated',\n 'untracked-list-truncated',\n 'manifest-missing',\n]);\n\n/**\n * Counts file coverage by status without mutating the ledger. `files` counts\n * every ledger entry; unknown persisted statuses are excluded from `byStatus`\n * rather than creating a NaN bucket.\n */\nexport function summarizeCoverage(files: readonly FileCoverage[]): CoverageTotals {\n const byStatus = Object.fromEntries(COVERAGE_STATUSES.map((status) => [status, 0])) as Record<\n CoverageStatus,\n number\n >;\n const known = new Set<string>(COVERAGE_STATUSES);\n for (const file of files) {\n if (!known.has(file.status)) continue;\n byStatus[file.status] += 1;\n }\n return { files: files.length, byStatus };\n}\n\n/**\n * Seeds a coverage ledger from the manifest: every changed file starts as\n * pending so no manifest path can be silently absent from coverage.\n */\nexport function initialCoverage(manifest: ReviewManifest): ReviewCoverage {\n const files: FileCoverage[] = manifest.files.map((file) => ({\n path: file.path,\n status: 'pending',\n }));\n return { files, totals: summarizeCoverage(files) };\n}\n\nfunction policyReason(file: ManifestFile): ManifestFileReason | undefined {\n if (file.reason !== undefined && POLICY_REASONS.has(file.reason)) return file.reason;\n if (file.ignored) return 'ignored';\n if (file.binary) return 'binary';\n return undefined;\n}\n\nfunction skipReason(file: ManifestFile): ManifestFileReason {\n if (file.reason !== undefined && SKIP_REASONS.has(file.reason)) return file.reason;\n return 'render-budget';\n}\n\n/**\n * Builds the R1 coverage bridge from the complete manifest and the paths whose\n * file header made it into the prompt. Policy-excluded content is explicitly\n * `manifest-only`; an included file with a reviewable hunk is `pending` and may\n * at most become `light-reviewed` after the report. A file with no content\n * change (pure rename, mode-only, empty file, or an untracked symlink whose\n * target is deliberately not followed) is terminal `no-hunks`, because there\n * is nothing to inspect and it was not dropped by a budget. Everything else is\n * `skipped` with the manifest reason (or `render-budget` when the prompt\n * dropped it). This never mutates the manifest.\n */\nexport function coverageFromPrompt(\n manifest: ReviewManifest,\n includedPaths: readonly string[],\n): ReviewCoverage {\n const included = new Set(includedPaths);\n const files = manifest.files.map((file): FileCoverage => {\n const policy = policyReason(file);\n if (policy !== undefined) {\n return { path: file.path, status: 'manifest-only', reason: policy };\n }\n if (\n file.reason === 'symlink' &&\n file.hunkCount === 0 &&\n file.added === 0 &&\n file.deleted === 0\n ) {\n return { path: file.path, status: 'no-hunks' };\n }\n if (included.has(file.path)) {\n if (file.hunkCount > 0) return { path: file.path, status: 'pending' };\n if (file.reason === undefined && file.added === 0 && file.deleted === 0) {\n return { path: file.path, status: 'no-hunks' };\n }\n }\n return { path: file.path, status: 'skipped', reason: skipReason(file) };\n });\n const incomplete = manifest.warnings.some((warning) => INCOMPLETE_MANIFEST_WARNINGS.has(warning));\n return {\n files,\n totals: summarizeCoverage(files),\n ...(incomplete ? { incomplete: true } : {}),\n };\n}\n\n/**\n * A round is complete only when every ledger file has a final reviewed or\n * policy-excluded status. Unknown persisted statuses and inconsistent totals\n * are treated as incomplete: they prove the ledger cannot be trusted.\n */\nexport function isCoverageComplete(coverage: ReviewCoverage): boolean {\n if (coverage.incomplete === true) return false;\n const computed = summarizeCoverage(coverage.files);\n const knownCount = COVERAGE_STATUSES.reduce((sum, status) => sum + computed.byStatus[status], 0);\n if (knownCount !== coverage.files.length) return false;\n if (coverage.totals.files !== coverage.files.length) return false;\n for (const status of COVERAGE_STATUSES) {\n if ((coverage.totals.byStatus[status] ?? 0) !== computed.byStatus[status]) return false;\n }\n const persistedCount = COVERAGE_STATUSES.reduce(\n (sum, status) => sum + (coverage.totals.byStatus[status] ?? 0),\n 0,\n );\n if (persistedCount !== coverage.files.length) return false;\n return coverage.files.every((file) => {\n if (UNRESOLVED_STATUSES.has(file.status)) return false;\n if (file.status !== 'manifest-only') return true;\n return file.reason !== undefined && POLICY_REASONS.has(file.reason as ManifestFileReason);\n });\n}\n\n/**\n * Promotes pending files to light-reviewed after an accepted report. The\n * returned ledger and its file entries are new objects; the input is not\n * mutated. Totals are always recomputed from the resulting files.\n */\nexport function promotePendingToLight(coverage: ReviewCoverage): ReviewCoverage {\n const files = coverage.files.map((file): FileCoverage =>\n file.status === 'pending' ? { ...file, status: 'light-reviewed' } : { ...file },\n );\n return {\n files,\n totals: summarizeCoverage(files),\n ...(coverage.incomplete === true ? { incomplete: true } : {}),\n };\n}\n", "import { isCoverageComplete, promotePendingToLight, summarizeCoverage } from './coverage.js';\nimport type { ReviewEvent } from './events.js';\nimport type { AgentStatus, Comment, ReviewState } from './types.js';\n\nexport class ReviewFoldError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ReviewFoldError';\n }\n}\n\nfunction findComment(state: ReviewState, commentId: string): Comment {\n const comment = state.comments.find((candidate) => candidate.id === commentId);\n if (comment === undefined) {\n throw new ReviewFoldError(`unknown comment: ${commentId}`);\n }\n return comment;\n}\n\n/**\n * Pure reduction of one event into review state. The input is never mutated;\n * callers replay events in append order. `review/created` seeds the aggregate\n * (and resets the version to 1); every other event advances it by one.\n */\nexport function fold(state: ReviewState, event: ReviewEvent): ReviewState {\n if (event.reviewId !== state.id) {\n throw new ReviewFoldError(`event ${event.id} belongs to ${event.reviewId}, not ${state.id}`);\n }\n\n if (event.type === 'review/created') {\n if (event.data.review.id !== event.reviewId) {\n throw new ReviewFoldError(\n `review payload ${event.data.review.id} belongs to a different review than ${event.reviewId}`,\n );\n }\n return {\n ...structuredClone(event.data.review),\n version: 1,\n warnings: [],\n };\n }\n\n const next = structuredClone(state);\n next.version += 1;\n next.warnings ??= [];\n\n switch (event.type) {\n case 'round/created': {\n if (next.rounds.some((round) => round.no === event.data.round.no)) {\n throw new ReviewFoldError(`duplicate round: ${event.data.round.no}`);\n }\n const expected = next.rounds.length + 1;\n if (event.data.round.no !== expected) {\n throw new ReviewFoldError(`round order: expected ${expected}, got ${event.data.round.no}`);\n }\n next.rounds.push(structuredClone(event.data.round));\n break;\n }\n case 'comment/added': {\n if (next.comments.some((comment) => comment.id === event.data.comment.id)) {\n throw new ReviewFoldError(`duplicate comment: ${event.data.comment.id}`);\n }\n next.comments.push(structuredClone(event.data.comment));\n break;\n }\n case 'comment/replied': {\n const comment = findComment(next, event.data.commentId);\n comment.replies.push(structuredClone(event.data.reply));\n break;\n }\n case 'comment/state': {\n findComment(next, event.data.commentId).state = event.data.state;\n break;\n }\n case 'comment/edited': {\n const comment = findComment(next, event.data.commentId);\n comment.body = event.data.body;\n comment.editedAt = event.at;\n comment.editedBy = structuredClone(event.actor);\n break;\n }\n case 'comment/deleted': {\n const comment = findComment(next, event.data.commentId);\n comment.deletedAt = event.at;\n comment.deletedBy = structuredClone(event.actor);\n break;\n }\n case 'comment/anchor': {\n const comment = findComment(next, event.data.commentId);\n const anchor = structuredClone(event.data.anchor);\n const index = comment.anchors.findIndex((candidate) => candidate.round === anchor.round);\n if (index < 0) comment.anchors.push(anchor);\n else comment.anchors[index] = anchor;\n if (event.data.outcome !== undefined) {\n comment.outcomes.push(structuredClone(event.data.outcome));\n }\n break;\n }\n case 'suggestion/state': {\n findComment(next, event.data.commentId).suggestionState = event.data.state;\n break;\n }\n case 'fix/claim': {\n next.fixClaims ??= [];\n next.fixClaims.push(...structuredClone(event.data.claims));\n break;\n }\n case 'finding/decision': {\n next.findingDecisions ??= [];\n next.findingDecisions.push(structuredClone(event.data.decision));\n if (event.data.comment !== undefined) {\n const comment = event.data.comment;\n if (next.comments.some((candidate) => candidate.id === comment.id)) {\n throw new ReviewFoldError(`duplicate comment: ${comment.id}`);\n }\n next.comments.push(structuredClone(comment));\n }\n if (event.data.deletedCommentId !== undefined) {\n const comment = findComment(next, event.data.deletedCommentId);\n if (comment.deletedAt === undefined) {\n comment.deletedAt = event.at;\n comment.deletedBy = structuredClone(event.actor);\n }\n }\n break;\n }\n case 'review/updated': {\n if (event.data.title !== undefined) next.title = event.data.title;\n if (event.data.reviewer !== undefined) {\n next.reviewer = structuredClone(event.data.reviewer);\n }\n next.updatedAt = event.at;\n next.updatedBy = structuredClone(event.actor);\n break;\n }\n case 'review/deleted': {\n next.deletedAt = event.at;\n next.deletedBy = structuredClone(event.actor);\n break;\n }\n case 'suggestion/applied': {\n const comment = findComment(next, event.data.commentId);\n const alreadyApplied = comment.outcomes.some(\n (outcome) => outcome.round === event.data.round && outcome.status === 'applied',\n );\n if (!alreadyApplied) {\n comment.outcomes.push({\n round: event.data.round,\n status: 'applied',\n confidence: 'high',\n });\n comment.suggestionState = 'applied';\n }\n break;\n }\n case 'review/closed': {\n next.status = 'closed';\n next.closedAt ??= event.at;\n break;\n }\n case 'review/submitted': {\n const submission = structuredClone(event.data.submission);\n next.submissions ??= [];\n if (next.submissions.some((item) => item.submissionId === submission.submissionId)) {\n throw new ReviewFoldError(`duplicate submission: ${submission.submissionId}`);\n }\n next.submissions.push(submission);\n if (submission.verdict === 'approve') {\n next.status = 'closed';\n next.closedAt ??= event.at;\n }\n break;\n }\n case 'agent/coverage': {\n next.agent = {\n status: 'queued',\n round: event.data.round,\n coverage: structuredClone(event.data.coverage),\n findings: [],\n shards: [],\n };\n break;\n }\n case 'agent/started': {\n next.agent = {\n ...(next.agent?.coverage === undefined ? {} : { coverage: next.agent.coverage }),\n ...(next.agent?.shards === undefined ? {} : { shards: next.agent.shards }),\n status: 'running',\n jobId: event.data.jobId,\n round: event.data.round,\n startedAt: event.at,\n findings: [],\n };\n break;\n }\n case 'agent/shard': {\n next.agent ??= { status: 'running', findings: [] };\n next.agent.shards ??= [];\n const index = next.agent.shards.findIndex((shard) => shard.shardId === event.data.shardId);\n const shard = {\n shardId: event.data.shardId,\n shardIndex: event.data.shardIndex,\n shardCount: event.data.shardCount,\n round: event.data.round,\n report: structuredClone(event.data.report),\n };\n if (index < 0) next.agent.shards.push(shard);\n else next.agent.shards[index] = shard;\n if (next.agent.coverage !== undefined && event.data.paths !== undefined) {\n const covered = new Set(event.data.paths);\n const tier = event.data.tier ?? 'deep';\n const files = next.agent.coverage.files.map((file) => {\n if (!covered.has(file.path) || file.status !== 'pending') return file;\n const nextFile = {\n ...file,\n status: tier === 'deep' ? ('deep-reviewed' as const) : ('light-reviewed' as const),\n };\n delete nextFile.reason;\n return nextFile;\n });\n next.agent.coverage = {\n ...next.agent.coverage,\n files,\n totals: summarizeCoverage(files),\n };\n }\n break;\n }\n case 'agent/finding': {\n next.agent ??= { status: 'running', findings: [] };\n next.agent.findings ??= [];\n next.agent.findings.push(structuredClone(event.data.finding));\n break;\n }\n case 'agent/report': {\n const reportSkipped = event.data.report.skipped === true;\n const coverage =\n event.data.coverage !== undefined\n ? structuredClone(event.data.coverage)\n : reportSkipped || next.agent?.coverage === undefined\n ? next.agent?.coverage\n : promotePendingToLight(next.agent.coverage);\n let status: AgentStatus;\n if (reportSkipped) status = 'skipped';\n else if (coverage === undefined) status = 'done';\n else status = isCoverageComplete(coverage) ? 'done' : 'partial';\n next.report = structuredClone(event.data.report);\n next.agent = {\n ...(next.agent ?? {}),\n status,\n round: event.data.report.round,\n findings: structuredClone(event.data.report.findings),\n ...(coverage === undefined ? {} : { coverage }),\n };\n break;\n }\n case 'agent/failed': {\n next.agent = {\n ...(next.agent ?? {}),\n status: 'failed',\n error: event.data.message,\n };\n break;\n }\n case 'agent/retry': {\n const agent = next.agent ?? { status: 'queued' as const, findings: [] };\n const retryShards = new Set(event.data.shardIds);\n let coverage = agent.coverage;\n if (coverage !== undefined) {\n const files = coverage.files.map((file) => {\n if (\n file.shardId === undefined ||\n !retryShards.has(file.shardId) ||\n (file.status !== 'failed' && file.status !== 'pending')\n ) {\n return file;\n }\n const retried = { ...file, status: 'pending' as const };\n delete retried.reason;\n return retried;\n });\n coverage = { ...coverage, files, totals: summarizeCoverage(files) };\n }\n next.agent = {\n ...agent,\n status: 'queued',\n round: event.data.round,\n ...(coverage === undefined ? {} : { coverage }),\n };\n delete next.agent.jobId;\n delete next.agent.startedAt;\n delete next.agent.error;\n break;\n }\n }\n\n return next;\n}\n", "import { isCoverageComplete } from './coverage.js';\nimport type { ReviewState } from './types.js';\n\n/**\n * True when the stored report is the final report for the latest round.\n *\n * A queued, running, failed, skipped or coverage-partial run does not make its\n * report final, even when a previous run left a report in place. Legacy states\n * without an agent still qualify because their report is all we have.\n */\nexport function isFinalReviewReport(\n review: ReviewState,\n report: ReviewState['report'] = review.report,\n): boolean {\n const latestRound = review.rounds.at(-1);\n if (report === undefined || latestRound === undefined || report.round !== latestRound.no) {\n return false;\n }\n const agent = review.agent;\n if (agent === undefined) return true;\n if (agent.status !== 'done') return false;\n return agent.coverage === undefined || isCoverageComplete(agent.coverage);\n}\n", "import type { ManifestFile, ReviewManifest } from '../types.js';\nimport type { FileCoverage, ReviewCoverage } from './types.js';\nimport { summarizeCoverage } from './coverage.js';\n\nexport type ReviewTier = 'deep' | 'light' | 'manifest-only';\nexport type FileKind =\n | 'source'\n | 'test'\n | 'docs'\n | 'config'\n | 'migration'\n | 'generated'\n | 'binary'\n | 'instructions'\n | 'other';\n\nexport interface ModuleRoot {\n id: string;\n root: string;\n name?: string;\n}\n\nexport interface ModuleInfo extends ModuleRoot {\n source: 'workspace' | 'convention' | 'root';\n}\n\nexport interface ModuleMap {\n modules: ModuleInfo[];\n moduleFor(path: string): string;\n}\n\nconst WORKSPACE_CONTAINERS = new Set([\n 'packages',\n 'apps',\n 'services',\n 'libs',\n 'modules',\n 'plugins',\n]);\n\nconst CONVENTION_ROOTS = new Set([\n 'src',\n 'app',\n 'backend',\n 'frontend',\n 'server',\n 'client',\n 'ui',\n 'web',\n 'api',\n 'docs',\n 'tests',\n 'test',\n 'e2e',\n 'ui-tests',\n 'migrations',\n 'config',\n 'scripts',\n 'tools',\n]);\n\nconst INSTRUCTION_FILES = new Set([\n 'AGENTS.md',\n 'CLAUDE.md',\n 'GEMINI.md',\n '.cursorrules',\n '.github/copilot-instructions.md',\n]);\n\nconst DOC_FILES = new Set([\n 'README.md',\n 'CHANGELOG.md',\n 'ARCHITECTURE.md',\n 'BACKEND_SPEC.md',\n 'CONTRIBUTING.md',\n 'SECURITY.md',\n 'LICENSE.md',\n]);\n\nconst CONFIG_FILES = new Set([\n 'package.json',\n 'pnpm-workspace.yaml',\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'yarn.lock',\n 'uv.lock',\n 'poetry.lock',\n 'Cargo.lock',\n 'go.sum',\n 'tsconfig.json',\n 'pyproject.toml',\n 'requirements.txt',\n 'go.mod',\n 'Cargo.toml',\n 'pom.xml',\n 'Makefile',\n 'Dockerfile',\n 'docker-compose.yml',\n 'docker-compose.yaml',\n]);\n\nconst SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.py',\n '.go',\n '.rs',\n '.java',\n '.kt',\n '.kts',\n '.swift',\n '.c',\n '.cc',\n '.cpp',\n '.h',\n '.hpp',\n '.rb',\n '.php',\n '.cs',\n '.sh',\n '.bash',\n '.zsh',\n '.sql',\n]);\n\nconst DOC_EXTENSIONS = new Set(['.md', '.mdx', '.rst', '.adoc']);\nconst GENERATED_DIRS = new Set([\n 'dist',\n 'build',\n 'coverage',\n 'node_modules',\n '.next',\n 'out',\n 'target',\n '.turbo',\n '.cache',\n]);\n\nfunction basename(path: string): string {\n const parts = path.split('/');\n return parts.at(-1) ?? path;\n}\n\nfunction extension(path: string): string {\n const name = basename(path);\n const dot = name.lastIndexOf('.');\n return dot <= 0 ? '' : name.slice(dot).toLowerCase();\n}\n\nfunction normalizeRoot(root: string): string {\n const normalized = root.replace(/\\\\/g, '/').replace(/^\\/+|\\/+$/g, '');\n return normalized === '' ? '.' : normalized;\n}\n\nfunction isUnder(path: string, root: string): boolean {\n if (root === '.') return true;\n return path === root || path.startsWith(`${root}/`);\n}\n\nfunction conventionRoot(path: string): string {\n const parts = path.split('/');\n if (parts.length === 1) return '.';\n const first = parts[0];\n if (first === undefined) return '.';\n if (WORKSPACE_CONTAINERS.has(first) && parts.length > 2) {\n return `${first}/${parts[1] ?? ''}`;\n }\n if (CONVENTION_ROOTS.has(first)) return first;\n return first;\n}\n\n/**\n * Builds a deterministic path-to-module map. Explicit workspace roots win over\n * convention roots for the same directory; the longest matching root wins for\n * a path. Root-level files belong to the synthetic `.` module.\n */\nexport function buildModuleMap(\n files: readonly ManifestFile[],\n roots: readonly ModuleRoot[] = [],\n): ModuleMap {\n const byRoot = new Map<string, ModuleInfo>();\n const add = (root: string, source: ModuleInfo['source'], info?: ModuleRoot): void => {\n const normalized = normalizeRoot(root);\n if (normalized === '.' && source !== 'root') return;\n const existing = byRoot.get(normalized);\n if (existing !== undefined && !(source === 'workspace' && existing.source === 'convention')) {\n return;\n }\n if (existing !== undefined && source === 'workspace') {\n byRoot.set(normalized, {\n id: info?.id ?? existing.id,\n root: normalized,\n ...(info?.name === undefined && existing.name === undefined\n ? {}\n : { name: info?.name ?? existing.name }),\n source,\n });\n return;\n }\n byRoot.set(normalized, {\n id: info?.id ?? normalized,\n root: normalized,\n ...(info?.name === undefined ? {} : { name: info.name }),\n source,\n });\n };\n\n for (const root of roots) {\n add(root.root, 'workspace', root);\n }\n for (const path of files.map((file) => file.path)) {\n add(conventionRoot(path), 'convention');\n }\n add('.', 'root');\n\n const modules = [...byRoot.values()].sort((left, right) => {\n if (left.id < right.id) return -1;\n if (left.id > right.id) return 1;\n return 0;\n });\n const orderedByRoot = [...modules].sort((left, right) => right.root.length - left.root.length);\n\n return {\n modules,\n moduleFor(path: string): string {\n for (const module of orderedByRoot) {\n if (isUnder(path, module.root)) return module.id;\n }\n return '.';\n },\n };\n}\n\nfunction isGenerated(file: ManifestFile): boolean {\n if (file.reason === 'generated') return true;\n const name = basename(file.path);\n if (\n name === 'pnpm-lock.yaml' ||\n name === 'package-lock.json' ||\n name === 'yarn.lock' ||\n name === 'uv.lock' ||\n name === 'poetry.lock' ||\n name === 'Cargo.lock' ||\n name === 'go.sum'\n ) {\n return true;\n }\n if (/(\\.min\\.|\\.generated\\.)/.test(name)) return true;\n return file.path.split('/').some((part) => GENERATED_DIRS.has(part));\n}\n\nfunction isInstructions(path: string): boolean {\n return INSTRUCTION_FILES.has(path) || path.startsWith('.dsh/');\n}\n\nfunction isMigration(path: string): boolean {\n if (extension(path) === '.sql') return true;\n return /(^|\\/)(migrations?|alembic|migrate)\\//.test(path);\n}\n\nfunction isTest(path: string): boolean {\n if (/(^|\\/)(test|tests|__tests__|e2e|ui-tests|spec|specs)\\//.test(path)) return true;\n return /\\.(test|spec)\\.[^/]+$/.test(path);\n}\n\nfunction isDocs(path: string): boolean {\n if (path.startsWith('docs/')) return true;\n if (DOC_FILES.has(basename(path))) return true;\n return DOC_EXTENSIONS.has(extension(path));\n}\n\nfunction isConfig(path: string): boolean {\n const name = basename(path);\n if (CONFIG_FILES.has(name)) return true;\n if (path.startsWith('.github/')) return true;\n if (\n /^(tsconfig|vite|vitest|eslint|prettier|playwright|jest|rollup|webpack).*\\.(json|js|mjs|cjs|ts)$/.test(\n name,\n )\n ) {\n return true;\n }\n if (/^\\.env(\\..*)?$/.test(name)) return true;\n return extension(path) === '.yml' || extension(path) === '.yaml' || extension(path) === '.toml';\n}\n\nfunction isSource(path: string): boolean {\n return SOURCE_EXTENSIONS.has(extension(path));\n}\n\n/**\n * Classifies a changed path by kind. Manifest policy (`generated`) and binary\n * metadata take priority over the path shape; `ignored` alone does not change\n * the kind because ignore policy is applied by the tier planner.\n */\nexport function classifyFile(file: ManifestFile): FileKind {\n if (isInstructions(file.path)) return 'instructions';\n if (file.binary) return 'binary';\n if (isGenerated(file)) return 'generated';\n if (isMigration(file.path)) return 'migration';\n if (isTest(file.path)) return 'test';\n if (isDocs(file.path)) return 'docs';\n if (isConfig(file.path)) return 'config';\n if (isSource(file.path)) return 'source';\n return 'other';\n}\n\n/**\n * User-facing docs are deep-reviewed because they can change behavior or\n * contracts. Internal notes are still docs, but light.\n */\nexport function isUserFacingDoc(path: string): boolean {\n if (path.startsWith('docs/internal/')) return false;\n if (path.startsWith('docs/')) return true;\n const name = basename(path);\n return DOC_FILES.has(name) || /^README\\./i.test(name);\n}\n\nexport interface ShardLimits {\n maxFiles: number;\n maxPatchBytes: number;\n}\n\nexport const DEFAULT_SHARD_LIMITS: ShardLimits = Object.freeze({\n maxFiles: 12,\n maxPatchBytes: 120_000,\n});\n\nexport interface PlanFile {\n path: string;\n moduleId: string;\n kind: FileKind;\n tier: ReviewTier;\n shardId?: string;\n /** Policy reason for a manifest-only file; absent for reviewed files. */\n reason?: string;\n}\n\nexport interface ReviewShard {\n id: string;\n moduleId: string;\n tier: 'deep' | 'light';\n effort: 'deep' | 'light';\n paths: string[];\n fileCount: number;\n patchBytes: number;\n}\n\nexport interface ReviewPlan {\n schemaVersion: 1;\n files: PlanFile[];\n shards: ReviewShard[];\n coverage: ReviewCoverage;\n coveragePlan: {\n total: number;\n byTier: Record<ReviewTier, number>;\n };\n riskFlags: string[];\n limits: ShardLimits;\n}\n\nconst INCOMPLETE_MANIFEST_WARNINGS = new Set([\n 'manifest-listing-truncated',\n 'untracked-list-truncated',\n 'manifest-missing',\n]);\n\nfunction byteCompare(left: string, right: string): number {\n return Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));\n}\n\nfunction isNoHunks(file: ManifestFile): boolean {\n return (\n file.reason === undefined &&\n !file.binary &&\n !file.ignored &&\n file.hunkCount === 0 &&\n file.added === 0 &&\n file.deleted === 0\n );\n}\n\nfunction policyReason(file: ManifestFile, kind: FileKind): string | undefined {\n if (file.reason !== undefined && ['generated', 'binary', 'ignored'].includes(file.reason)) {\n return file.reason;\n }\n if (file.ignored) return 'ignored';\n if (file.binary) return 'binary';\n if (kind === 'generated') return 'generated';\n return undefined;\n}\n\nfunction tierFor(file: ManifestFile, kind: FileKind, moduleHasSource: boolean): ReviewTier {\n if (kind === 'binary' || kind === 'generated' || file.ignored) return 'manifest-only';\n switch (kind) {\n case 'source':\n case 'config':\n case 'migration':\n case 'instructions':\n return 'deep';\n case 'test':\n return moduleHasSource ? 'deep' : 'light';\n case 'docs':\n return isUserFacingDoc(file.path) ? 'deep' : 'light';\n case 'other':\n return 'light';\n }\n}\n\nfunction riskFlags(input: {\n manifest: ReviewManifest;\n files: readonly PlanFile[];\n modules: number;\n}): string[] {\n const flags: string[] = [];\n const generated = input.files.filter((file) => file.tier === 'manifest-only').length;\n if (input.manifest.totals.files >= 100 || input.manifest.totals.patchBytes >= 1_000_000) {\n flags.push('large-diff');\n }\n if (input.files.length >= 10 && generated * 10 >= input.files.length) {\n flags.push('generated-heavy');\n }\n if (input.manifest.warnings.some((warning) => INCOMPLETE_MANIFEST_WARNINGS.has(warning))) {\n flags.push('manifest-incomplete');\n }\n if (\n input.files.some((file) => /(^|\\/)(auth|security|crypto|migrations?|config)\\//.test(file.path))\n ) {\n flags.push('security-sensitive');\n }\n if (!input.files.some((file) => file.tier === 'deep' && file.kind === 'source')) {\n flags.push('no-source');\n }\n if (input.modules > 8) flags.push('many-modules');\n return flags;\n}\n\n/**\n * Builds a pure, deterministic review plan. The planner never executes a\n * shard, calls a model, or marks work complete; every deep/light file starts\n * pending in the returned coverage ledger.\n */\nexport function buildReviewPlan(input: {\n manifest: ReviewManifest;\n moduleRoots?: readonly ModuleRoot[];\n limits?: Partial<ShardLimits>;\n}): ReviewPlan {\n const limits: ShardLimits = {\n maxFiles: Math.max(1, Math.trunc(input.limits?.maxFiles ?? DEFAULT_SHARD_LIMITS.maxFiles)),\n maxPatchBytes: Math.max(\n 1,\n Math.trunc(input.limits?.maxPatchBytes ?? DEFAULT_SHARD_LIMITS.maxPatchBytes),\n ),\n };\n const modules = buildModuleMap(input.manifest.files, input.moduleRoots);\n const kinds = new Map<string, FileKind>();\n for (const file of input.manifest.files) {\n kinds.set(file.path, classifyFile(file));\n }\n const sourceModules = new Set<string>();\n for (const file of input.manifest.files) {\n if (kinds.get(file.path) === 'source') sourceModules.add(modules.moduleFor(file.path));\n }\n\n const files: PlanFile[] = input.manifest.files.map((file) => {\n const kind = kinds.get(file.path) ?? 'other';\n const moduleId = modules.moduleFor(file.path);\n const tier = tierFor(file, kind, sourceModules.has(moduleId));\n const reason = tier === 'manifest-only' ? policyReason(file, kind) : undefined;\n return {\n path: file.path,\n moduleId,\n kind,\n tier,\n ...(reason === undefined ? {} : { reason }),\n };\n });\n const byPath = new Map(files.map((file) => [file.path, file]));\n\n const groups = new Map<string, PlanFile[]>();\n for (const file of files) {\n if (file.tier === 'manifest-only') continue;\n const manifestFile = input.manifest.files.find((item) => item.path === file.path);\n if (manifestFile === undefined || isNoHunks(manifestFile)) continue;\n const key = `${file.moduleId}\\0${file.tier}`;\n const group = groups.get(key) ?? [];\n group.push(file);\n groups.set(key, group);\n }\n\n const shards: ReviewShard[] = [];\n const keys = [...groups.keys()].sort(byteCompare);\n for (const key of keys) {\n const group = groups.get(key) ?? [];\n group.sort((left, right) => byteCompare(left.path, right.path));\n let current: PlanFile[] = [];\n let currentBytes = 0;\n const flush = (): void => {\n if (current.length === 0) return;\n const id = `shard_${String(shards.length + 1).padStart(3, '0')}`;\n const first = current[0];\n if (first === undefined) return;\n const shard: ReviewShard = {\n id,\n moduleId: first.moduleId,\n tier: first.tier === 'deep' ? 'deep' : 'light',\n effort: first.tier === 'deep' ? 'deep' : 'light',\n paths: current.map((file) => file.path),\n fileCount: current.length,\n patchBytes: currentBytes,\n };\n shards.push(shard);\n for (const file of current) file.shardId = id;\n current = [];\n currentBytes = 0;\n };\n for (const file of group) {\n const manifestFile = input.manifest.files.find((item) => item.path === file.path);\n const bytes = manifestFile?.patchBytes ?? 0;\n if (\n current.length > 0 &&\n (current.length >= limits.maxFiles || currentBytes + bytes > limits.maxPatchBytes)\n ) {\n flush();\n }\n current.push(file);\n currentBytes += bytes;\n }\n flush();\n }\n\n const coverageFiles: FileCoverage[] = input.manifest.files.map((manifestFile) => {\n const planFile = byPath.get(manifestFile.path);\n if (planFile === undefined) {\n return { path: manifestFile.path, status: 'skipped', reason: 'render-budget' };\n }\n if (planFile.tier === 'manifest-only') {\n return {\n path: planFile.path,\n status: 'manifest-only',\n ...(planFile.reason === undefined ? {} : { reason: planFile.reason }),\n };\n }\n if (isNoHunks(manifestFile)) {\n return { path: planFile.path, status: 'no-hunks' };\n }\n if (planFile.shardId !== undefined) {\n return { path: planFile.path, status: 'pending', shardId: planFile.shardId };\n }\n return { path: planFile.path, status: 'skipped', reason: 'render-budget' };\n });\n const coverage: ReviewCoverage = {\n files: coverageFiles,\n totals: summarizeCoverage(coverageFiles),\n ...(input.manifest.warnings.some((warning) => INCOMPLETE_MANIFEST_WARNINGS.has(warning))\n ? { incomplete: true }\n : {}),\n };\n\n const byTier: Record<ReviewTier, number> = {\n deep: files.filter((file) => file.tier === 'deep').length,\n light: files.filter((file) => file.tier === 'light').length,\n 'manifest-only': files.filter((file) => file.tier === 'manifest-only').length,\n };\n return {\n schemaVersion: 1,\n files,\n shards,\n coverage,\n coveragePlan: { total: files.length, byTier },\n riskFlags: riskFlags({\n manifest: input.manifest,\n files,\n modules: modules.modules.length,\n }),\n limits,\n };\n}\n\n/** Returns a detached copy of the plan's coverage ledger for event persistence. */\nexport function coverageFromPlan(plan: ReviewPlan): ReviewCoverage {\n return {\n files: plan.coverage.files.map((file) => ({ ...file })),\n totals: summarizeCoverage(plan.coverage.files),\n ...(plan.coverage.incomplete === true ? { incomplete: true } : {}),\n };\n}\n", "import type {\n AgentReport,\n Finding,\n ReviewQuestion,\n RiskLevel,\n TestCommand,\n Verification,\n} from '../reviewer/types.js';\nimport type { ReviewPlan } from './plan.js';\nimport { coverageFromPlan } from './plan.js';\nimport { summarizeCoverage } from './coverage.js';\nimport type { FileCoverage, ReviewCoverage, ReviewId, RoundNo } from './types.js';\nimport type { ReviewerBackendKind } from '../reviewer/types.js';\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nexport interface ShardReportInput {\n shardId: string;\n report: AgentReport;\n}\n\nexport interface AggregateShardReportsInput {\n reviewId: ReviewId;\n round: RoundNo;\n backend: ReviewerBackendKind;\n plan: ReviewPlan;\n reports: readonly ShardReportInput[];\n /** Shards known to have failed; missing shards are failed automatically. */\n failedShards?: readonly string[];\n}\n\nexport interface AggregateShardReportsResult {\n report: AgentReport;\n coverage: ReviewCoverage;\n}\n\nfunction findingKey(finding: Finding): string {\n return `${finding.file}\\0${finding.lineStart}\\0${finding.lineEnd}\\0${finding.title}`;\n}\n\nfunction questionKey(question: ReviewQuestion): string {\n return `${question.file}\\0${question.line}\\0${question.question}`;\n}\n\nfunction mergeFindings(reports: readonly ShardReportInput[]): Finding[] {\n const findings: Finding[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const finding of report.report.findings) {\n const key = findingKey(finding);\n if (seen.has(key)) continue;\n seen.add(key);\n findings.push({ ...finding });\n }\n }\n return findings;\n}\n\nfunction mergeQuestions(reports: readonly ShardReportInput[]): ReviewQuestion[] {\n const questions: ReviewQuestion[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const question of report.report.questions) {\n const key = questionKey(question);\n if (seen.has(key)) continue;\n seen.add(key);\n questions.push({ ...question });\n }\n }\n return questions;\n}\n\nfunction mergeTests(reports: readonly ShardReportInput[]): AgentReport['tests'] {\n const commands: TestCommand[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const command of report.report.tests.commands) {\n const key = `${command.command}\\0${command.result}\\0${command.summary}`;\n if (seen.has(key)) continue;\n seen.add(key);\n commands.push({ ...command });\n }\n }\n return { ran: reports.some((report) => report.report.tests.ran), commands };\n}\n\nfunction mergeVerification(reports: readonly ShardReportInput[]): Verification[] {\n const entries: Verification[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const item of report.report.verification) {\n const key = `${item.commentId}\\0${item.status}\\0${item.evidence}`;\n if (seen.has(key)) continue;\n seen.add(key);\n entries.push({ ...item });\n }\n }\n return entries;\n}\n\nfunction highestRisk(reports: readonly ShardReportInput[]): RiskLevel {\n let result: RiskLevel = 'low';\n for (const report of reports) {\n if (RISK_ORDER[report.report.riskLevel] > RISK_ORDER[result]) {\n result = report.report.riskLevel;\n }\n }\n return result;\n}\n\nfunction updateCoverage(\n plan: ReviewPlan,\n reports: readonly ShardReportInput[],\n failed: ReadonlySet<string>,\n): ReviewCoverage {\n const base = coverageFromPlan(plan);\n const byShard = new Map(reports.map((report) => [report.shardId, report]));\n const files: FileCoverage[] = base.files.map((file) => {\n const planFile = plan.files.find((item) => item.path === file.path);\n const shardId = planFile?.shardId;\n if (shardId === undefined) return { ...file };\n if (failed.has(shardId)) {\n return { ...file, status: 'failed', reason: 'shard-failed' };\n }\n const shard = plan.shards.find((item) => item.id === shardId);\n if (shard === undefined || !byShard.has(shardId)) {\n return { ...file, status: 'failed', reason: 'shard-failed' };\n }\n const status = shard.tier === 'deep' ? 'deep-reviewed' : 'light-reviewed';\n const next: FileCoverage = { ...file, status };\n delete next.reason;\n return next;\n });\n return {\n files,\n totals: summarizeCoverage(files),\n ...(base.incomplete === true ? { incomplete: true } : {}),\n };\n}\n\nexport interface MergeShardReportsInput {\n reviewId: ReviewId;\n round: RoundNo;\n backend: ReviewerBackendKind;\n /**\n * Reports in display order. Callers that have a plan must sort by plan order;\n * live previews sort by shard index before calling this helper.\n */\n reports: readonly ShardReportInput[];\n}\n\n/**\n * Merges completed shard reports without another model call or coverage\n * assumptions. Missing shards are intentionally ignored: this is the live\n * preview path while a round is still running. Final aggregation, which also\n * computes coverage and fails missing shards, uses this helper after ordering\n * reports by the plan.\n */\nexport function mergeShardReports(input: MergeShardReportsInput): AgentReport {\n const findings = mergeFindings(input.reports);\n const summaryParts = input.reports\n .map((report) => report.report.summary.trim())\n .filter((summary) => summary !== '');\n const summary =\n summaryParts.length === 0\n ? 'The review did not produce a shard report.'\n : summaryParts.join('\\n\\n').slice(0, 20_000);\n return {\n schemaVersion: 1,\n summary,\n riskLevel: highestRisk(input.reports),\n findings,\n questions: mergeQuestions(input.reports),\n tests: mergeTests(input.reports),\n verification: mergeVerification(input.reports),\n reviewId: input.reviewId,\n round: input.round,\n backend: input.backend,\n rejected: input.reports.flatMap((item) => item.report.rejected),\n warnings: [...new Set(input.reports.flatMap((report) => report.report.warnings))],\n ...(input.reports.some((item) => item.report.dirtyWorktree === true)\n ? { dirtyWorktree: true }\n : {}),\n skipped: false,\n };\n}\n\n/**\n * Merges shard reports without another model call. Deterministic: reports are\n * ordered by the review plan (not shard completion order), findings dedupe by\n * file/range/title, coverage is derived from the plan and the completed shards.\n * Missing shards are failed, never silently reviewed.\n */\nexport function aggregateShardReports(\n input: AggregateShardReportsInput,\n): AggregateShardReportsResult {\n const failed = new Set(input.failedShards ?? []);\n for (const shard of input.plan.shards) {\n if (!input.reports.some((report) => report.shardId === shard.id)) failed.add(shard.id);\n }\n const shardOrder = new Map(input.plan.shards.map((shard, index) => [shard.id, index]));\n const rank = (shardId: string): number => shardOrder.get(shardId) ?? input.plan.shards.length;\n const reports = [...input.reports].sort(\n (left, right) => rank(left.shardId) - rank(right.shardId),\n );\n const report = mergeShardReports({\n reviewId: input.reviewId,\n round: input.round,\n backend: input.backend,\n reports,\n });\n for (const shardId of [...failed].sort()) {\n report.warnings.push(`shard-failed:${shardId}`);\n }\n return { report, coverage: updateCoverage(input.plan, reports, failed) };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { ManifestFile, ManifestFileReason, ReviewManifest } from '../types.js';\nimport type { CoverageStatus, CoverageTotals, FileCoverage, ReviewCoverage } from './types.js';\n\nexport const COVERAGE_STATUSES: readonly CoverageStatus[] = Object.freeze([\n 'pending',\n 'deep-reviewed',\n 'light-reviewed',\n 'manifest-only',\n 'skipped',\n 'no-hunks',\n 'deferred',\n 'failed',\n]);\n\n/** Statuses that mean work is still owed before a round can be `done`. */\nconst UNRESOLVED_STATUSES = new Set<CoverageStatus>(['pending', 'skipped', 'deferred', 'failed']);\n\n/** Manifest reasons where policy, not the render budget, excluded the content. */\nconst POLICY_REASONS = new Set<ManifestFileReason>(['binary', 'ignored', 'generated']);\n\n/** Manifest reasons that explain why a non-policy file was not reviewed. */\nconst SKIP_REASONS = new Set<ManifestFileReason>([\n 'render-budget',\n 'per-file-cap',\n 'diff-truncated',\n 'unreadable',\n 'legacy-truncated',\n 'symlink',\n]);\n\n/** Warnings that prove the manifest's changed-path listing itself was cut. */\nconst INCOMPLETE_MANIFEST_WARNINGS = new Set([\n 'manifest-listing-truncated',\n 'untracked-list-truncated',\n 'manifest-missing',\n]);\n\n/**\n * Counts file coverage by status without mutating the ledger. `files` counts\n * every ledger entry; unknown persisted statuses are excluded from `byStatus`\n * rather than creating a NaN bucket.\n */\nexport function summarizeCoverage(files: readonly FileCoverage[]): CoverageTotals {\n const byStatus = Object.fromEntries(COVERAGE_STATUSES.map((status) => [status, 0])) as Record<\n CoverageStatus,\n number\n >;\n const known = new Set<string>(COVERAGE_STATUSES);\n for (const file of files) {\n if (!known.has(file.status)) continue;\n byStatus[file.status] += 1;\n }\n return { files: files.length, byStatus };\n}\n\n/**\n * Seeds a coverage ledger from the manifest: every changed file starts as\n * pending so no manifest path can be silently absent from coverage.\n */\nexport function initialCoverage(manifest: ReviewManifest): ReviewCoverage {\n const files: FileCoverage[] = manifest.files.map((file) => ({\n path: file.path,\n status: 'pending',\n }));\n return { files, totals: summarizeCoverage(files) };\n}\n\nfunction policyReason(file: ManifestFile): ManifestFileReason | undefined {\n if (file.reason !== undefined && POLICY_REASONS.has(file.reason)) return file.reason;\n if (file.ignored) return 'ignored';\n if (file.binary) return 'binary';\n return undefined;\n}\n\nfunction skipReason(file: ManifestFile): ManifestFileReason {\n if (file.reason !== undefined && SKIP_REASONS.has(file.reason)) return file.reason;\n return 'render-budget';\n}\n\n/**\n * Builds the R1 coverage bridge from the complete manifest and the paths whose\n * file header made it into the prompt. Policy-excluded content is explicitly\n * `manifest-only`; an included file with a reviewable hunk is `pending` and may\n * at most become `light-reviewed` after the report. A file with no content\n * change (pure rename, mode-only, empty file, or an untracked symlink whose\n * target is deliberately not followed) is terminal `no-hunks`, because there\n * is nothing to inspect and it was not dropped by a budget. Everything else is\n * `skipped` with the manifest reason (or `render-budget` when the prompt\n * dropped it). This never mutates the manifest.\n */\nexport function coverageFromPrompt(\n manifest: ReviewManifest,\n includedPaths: readonly string[],\n): ReviewCoverage {\n const included = new Set(includedPaths);\n const files = manifest.files.map((file): FileCoverage => {\n const policy = policyReason(file);\n if (policy !== undefined) {\n return { path: file.path, status: 'manifest-only', reason: policy };\n }\n if (\n file.reason === 'symlink' &&\n file.hunkCount === 0 &&\n file.added === 0 &&\n file.deleted === 0\n ) {\n return { path: file.path, status: 'no-hunks' };\n }\n if (included.has(file.path)) {\n if (file.hunkCount > 0) return { path: file.path, status: 'pending' };\n if (file.reason === undefined && file.added === 0 && file.deleted === 0) {\n return { path: file.path, status: 'no-hunks' };\n }\n }\n return { path: file.path, status: 'skipped', reason: skipReason(file) };\n });\n const incomplete = manifest.warnings.some((warning) => INCOMPLETE_MANIFEST_WARNINGS.has(warning));\n return {\n files,\n totals: summarizeCoverage(files),\n ...(incomplete ? { incomplete: true } : {}),\n };\n}\n\n/**\n * A round is complete only when every ledger file has a final reviewed or\n * policy-excluded status. Unknown persisted statuses and inconsistent totals\n * are treated as incomplete: they prove the ledger cannot be trusted.\n */\nexport function isCoverageComplete(coverage: ReviewCoverage): boolean {\n if (coverage.incomplete === true) return false;\n const computed = summarizeCoverage(coverage.files);\n const knownCount = COVERAGE_STATUSES.reduce((sum, status) => sum + computed.byStatus[status], 0);\n if (knownCount !== coverage.files.length) return false;\n if (coverage.totals.files !== coverage.files.length) return false;\n for (const status of COVERAGE_STATUSES) {\n if ((coverage.totals.byStatus[status] ?? 0) !== computed.byStatus[status]) return false;\n }\n const persistedCount = COVERAGE_STATUSES.reduce(\n (sum, status) => sum + (coverage.totals.byStatus[status] ?? 0),\n 0,\n );\n if (persistedCount !== coverage.files.length) return false;\n return coverage.files.every((file) => {\n if (UNRESOLVED_STATUSES.has(file.status)) return false;\n if (file.status !== 'manifest-only') return true;\n return file.reason !== undefined && POLICY_REASONS.has(file.reason as ManifestFileReason);\n });\n}\n\n/**\n * Promotes pending files to light-reviewed after an accepted report. The\n * returned ledger and its file entries are new objects; the input is not\n * mutated. Totals are always recomputed from the resulting files.\n */\nexport function promotePendingToLight(coverage: ReviewCoverage): ReviewCoverage {\n const files = coverage.files.map((file): FileCoverage =>\n file.status === 'pending' ? { ...file, status: 'light-reviewed' } : { ...file },\n );\n return {\n files,\n totals: summarizeCoverage(files),\n ...(coverage.incomplete === true ? { incomplete: true } : {}),\n };\n}\n", "import { isCoverageComplete, promotePendingToLight, summarizeCoverage } from './coverage.js';\nimport type { ReviewEvent } from './events.js';\nimport type { AgentStatus, Comment, ReviewState } from './types.js';\n\nexport class ReviewFoldError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ReviewFoldError';\n }\n}\n\nfunction findComment(state: ReviewState, commentId: string): Comment {\n const comment = state.comments.find((candidate) => candidate.id === commentId);\n if (comment === undefined) {\n throw new ReviewFoldError(`unknown comment: ${commentId}`);\n }\n return comment;\n}\n\n/**\n * Pure reduction of one event into review state. The input is never mutated;\n * callers replay events in append order. `review/created` seeds the aggregate\n * (and resets the version to 1); every other event advances it by one.\n */\nexport function fold(state: ReviewState, event: ReviewEvent): ReviewState {\n if (event.reviewId !== state.id) {\n throw new ReviewFoldError(`event ${event.id} belongs to ${event.reviewId}, not ${state.id}`);\n }\n\n if (event.type === 'review/created') {\n if (event.data.review.id !== event.reviewId) {\n throw new ReviewFoldError(\n `review payload ${event.data.review.id} belongs to a different review than ${event.reviewId}`,\n );\n }\n return {\n ...structuredClone(event.data.review),\n version: 1,\n warnings: [],\n };\n }\n\n const next = structuredClone(state);\n next.version += 1;\n next.warnings ??= [];\n\n switch (event.type) {\n case 'round/created': {\n if (next.rounds.some((round) => round.no === event.data.round.no)) {\n throw new ReviewFoldError(`duplicate round: ${event.data.round.no}`);\n }\n const expected = next.rounds.length + 1;\n if (event.data.round.no !== expected) {\n throw new ReviewFoldError(`round order: expected ${expected}, got ${event.data.round.no}`);\n }\n next.rounds.push(structuredClone(event.data.round));\n break;\n }\n case 'comment/added': {\n if (next.comments.some((comment) => comment.id === event.data.comment.id)) {\n throw new ReviewFoldError(`duplicate comment: ${event.data.comment.id}`);\n }\n next.comments.push(structuredClone(event.data.comment));\n break;\n }\n case 'comment/replied': {\n const comment = findComment(next, event.data.commentId);\n comment.replies.push(structuredClone(event.data.reply));\n break;\n }\n case 'comment/state': {\n findComment(next, event.data.commentId).state = event.data.state;\n break;\n }\n case 'comment/edited': {\n const comment = findComment(next, event.data.commentId);\n comment.body = event.data.body;\n comment.editedAt = event.at;\n comment.editedBy = structuredClone(event.actor);\n break;\n }\n case 'comment/deleted': {\n const comment = findComment(next, event.data.commentId);\n comment.deletedAt = event.at;\n comment.deletedBy = structuredClone(event.actor);\n break;\n }\n case 'comment/anchor': {\n const comment = findComment(next, event.data.commentId);\n const anchor = structuredClone(event.data.anchor);\n const index = comment.anchors.findIndex((candidate) => candidate.round === anchor.round);\n if (index < 0) comment.anchors.push(anchor);\n else comment.anchors[index] = anchor;\n if (event.data.outcome !== undefined) {\n comment.outcomes.push(structuredClone(event.data.outcome));\n }\n break;\n }\n case 'suggestion/state': {\n findComment(next, event.data.commentId).suggestionState = event.data.state;\n break;\n }\n case 'fix/claim': {\n next.fixClaims ??= [];\n next.fixClaims.push(...structuredClone(event.data.claims));\n break;\n }\n case 'finding/decision': {\n next.findingDecisions ??= [];\n next.findingDecisions.push(structuredClone(event.data.decision));\n if (event.data.comment !== undefined) {\n const comment = event.data.comment;\n if (next.comments.some((candidate) => candidate.id === comment.id)) {\n throw new ReviewFoldError(`duplicate comment: ${comment.id}`);\n }\n next.comments.push(structuredClone(comment));\n }\n if (event.data.deletedCommentId !== undefined) {\n const comment = findComment(next, event.data.deletedCommentId);\n if (comment.deletedAt === undefined) {\n comment.deletedAt = event.at;\n comment.deletedBy = structuredClone(event.actor);\n }\n }\n break;\n }\n case 'review/updated': {\n if (event.data.title !== undefined) next.title = event.data.title;\n if (event.data.reviewer !== undefined) {\n next.reviewer = structuredClone(event.data.reviewer);\n }\n next.updatedAt = event.at;\n next.updatedBy = structuredClone(event.actor);\n break;\n }\n case 'review/deleted': {\n next.deletedAt = event.at;\n next.deletedBy = structuredClone(event.actor);\n break;\n }\n case 'suggestion/applied': {\n const comment = findComment(next, event.data.commentId);\n const alreadyApplied = comment.outcomes.some(\n (outcome) => outcome.round === event.data.round && outcome.status === 'applied',\n );\n if (!alreadyApplied) {\n comment.outcomes.push({\n round: event.data.round,\n status: 'applied',\n confidence: 'high',\n });\n comment.suggestionState = 'applied';\n }\n break;\n }\n case 'review/closed': {\n next.status = 'closed';\n next.closedAt ??= event.at;\n next.closeVerdict = event.data.verdict;\n break;\n }\n case 'review/submitted': {\n const submission = structuredClone(event.data.submission);\n next.submissions ??= [];\n if (next.submissions.some((item) => item.submissionId === submission.submissionId)) {\n throw new ReviewFoldError(`duplicate submission: ${submission.submissionId}`);\n }\n next.submissions.push(submission);\n if (submission.verdict === 'approve') {\n next.status = 'closed';\n next.closedAt ??= event.at;\n next.closeVerdict ??= 'approved';\n }\n break;\n }\n case 'agent/coverage': {\n next.agent = {\n status: 'queued',\n round: event.data.round,\n coverage: structuredClone(event.data.coverage),\n findings: [],\n shards: [],\n };\n break;\n }\n case 'agent/started': {\n next.agent = {\n ...(next.agent?.coverage === undefined ? {} : { coverage: next.agent.coverage }),\n ...(next.agent?.shards === undefined ? {} : { shards: next.agent.shards }),\n status: 'running',\n jobId: event.data.jobId,\n round: event.data.round,\n startedAt: event.at,\n findings: [],\n };\n break;\n }\n case 'agent/shard': {\n next.agent ??= { status: 'running', findings: [] };\n next.agent.shards ??= [];\n const index = next.agent.shards.findIndex((shard) => shard.shardId === event.data.shardId);\n const shard = {\n shardId: event.data.shardId,\n shardIndex: event.data.shardIndex,\n shardCount: event.data.shardCount,\n round: event.data.round,\n report: structuredClone(event.data.report),\n };\n if (index < 0) next.agent.shards.push(shard);\n else next.agent.shards[index] = shard;\n if (next.agent.coverage !== undefined && event.data.paths !== undefined) {\n const covered = new Set(event.data.paths);\n const tier = event.data.tier ?? 'deep';\n const files = next.agent.coverage.files.map((file) => {\n if (!covered.has(file.path) || file.status !== 'pending') return file;\n const nextFile = {\n ...file,\n status: tier === 'deep' ? ('deep-reviewed' as const) : ('light-reviewed' as const),\n };\n delete nextFile.reason;\n return nextFile;\n });\n next.agent.coverage = {\n ...next.agent.coverage,\n files,\n totals: summarizeCoverage(files),\n };\n }\n break;\n }\n case 'agent/finding': {\n next.agent ??= { status: 'running', findings: [] };\n next.agent.findings ??= [];\n next.agent.findings.push(structuredClone(event.data.finding));\n break;\n }\n case 'agent/report': {\n const reportSkipped = event.data.report.skipped === true;\n const coverage =\n event.data.coverage !== undefined\n ? structuredClone(event.data.coverage)\n : reportSkipped || next.agent?.coverage === undefined\n ? next.agent?.coverage\n : promotePendingToLight(next.agent.coverage);\n let status: AgentStatus;\n if (reportSkipped) status = 'skipped';\n else if (coverage === undefined) status = 'done';\n else status = isCoverageComplete(coverage) ? 'done' : 'partial';\n next.report = structuredClone(event.data.report);\n next.agent = {\n ...(next.agent ?? {}),\n status,\n round: event.data.report.round,\n findings: structuredClone(event.data.report.findings),\n ...(coverage === undefined ? {} : { coverage }),\n };\n break;\n }\n case 'agent/failed': {\n next.agent = {\n ...(next.agent ?? {}),\n status: 'failed',\n error: event.data.message,\n };\n break;\n }\n case 'agent/retry': {\n const agent = next.agent ?? { status: 'queued' as const, findings: [] };\n const retryShards = new Set(event.data.shardIds);\n let coverage = agent.coverage;\n if (coverage !== undefined) {\n const files = coverage.files.map((file) => {\n if (\n file.shardId === undefined ||\n !retryShards.has(file.shardId) ||\n (file.status !== 'failed' && file.status !== 'pending')\n ) {\n return file;\n }\n const retried = { ...file, status: 'pending' as const };\n delete retried.reason;\n return retried;\n });\n coverage = { ...coverage, files, totals: summarizeCoverage(files) };\n }\n next.agent = {\n ...agent,\n status: 'queued',\n round: event.data.round,\n ...(coverage === undefined ? {} : { coverage }),\n };\n delete next.agent.jobId;\n delete next.agent.startedAt;\n delete next.agent.error;\n break;\n }\n }\n\n return next;\n}\n", "import { isCoverageComplete } from './coverage.js';\nimport type { ReviewState } from './types.js';\n\n/**\n * True when the stored report is the final report for the latest round.\n *\n * A queued, running, failed, skipped or coverage-partial run does not make its\n * report final, even when a previous run left a report in place. Legacy states\n * without an agent still qualify because their report is all we have.\n */\nexport function isFinalReviewReport(\n review: ReviewState,\n report: ReviewState['report'] = review.report,\n): boolean {\n const latestRound = review.rounds.at(-1);\n if (report === undefined || latestRound === undefined || report.round !== latestRound.no) {\n return false;\n }\n const agent = review.agent;\n if (agent === undefined) return true;\n if (agent.status !== 'done') return false;\n return agent.coverage === undefined || isCoverageComplete(agent.coverage);\n}\n", "import type { ManifestFile, ReviewManifest } from '../types.js';\nimport type { FileCoverage, ReviewCoverage } from './types.js';\nimport { summarizeCoverage } from './coverage.js';\n\nexport type ReviewTier = 'deep' | 'light' | 'manifest-only';\nexport type FileKind =\n | 'source'\n | 'test'\n | 'docs'\n | 'config'\n | 'migration'\n | 'generated'\n | 'binary'\n | 'instructions'\n | 'other';\n\nexport interface ModuleRoot {\n id: string;\n root: string;\n name?: string;\n}\n\nexport interface ModuleInfo extends ModuleRoot {\n source: 'workspace' | 'convention' | 'root';\n}\n\nexport interface ModuleMap {\n modules: ModuleInfo[];\n moduleFor(path: string): string;\n}\n\nconst WORKSPACE_CONTAINERS = new Set([\n 'packages',\n 'apps',\n 'services',\n 'libs',\n 'modules',\n 'plugins',\n]);\n\nconst CONVENTION_ROOTS = new Set([\n 'src',\n 'app',\n 'backend',\n 'frontend',\n 'server',\n 'client',\n 'ui',\n 'web',\n 'api',\n 'docs',\n 'tests',\n 'test',\n 'e2e',\n 'ui-tests',\n 'migrations',\n 'config',\n 'scripts',\n 'tools',\n]);\n\nconst INSTRUCTION_FILES = new Set([\n 'AGENTS.md',\n 'CLAUDE.md',\n 'GEMINI.md',\n '.cursorrules',\n '.github/copilot-instructions.md',\n]);\n\nconst DOC_FILES = new Set([\n 'README.md',\n 'CHANGELOG.md',\n 'ARCHITECTURE.md',\n 'BACKEND_SPEC.md',\n 'CONTRIBUTING.md',\n 'SECURITY.md',\n 'LICENSE.md',\n]);\n\nconst CONFIG_FILES = new Set([\n 'package.json',\n 'pnpm-workspace.yaml',\n 'pnpm-lock.yaml',\n 'package-lock.json',\n 'yarn.lock',\n 'uv.lock',\n 'poetry.lock',\n 'Cargo.lock',\n 'go.sum',\n 'tsconfig.json',\n 'pyproject.toml',\n 'requirements.txt',\n 'go.mod',\n 'Cargo.toml',\n 'pom.xml',\n 'Makefile',\n 'Dockerfile',\n 'docker-compose.yml',\n 'docker-compose.yaml',\n]);\n\nconst SOURCE_EXTENSIONS = new Set([\n '.ts',\n '.tsx',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.py',\n '.go',\n '.rs',\n '.java',\n '.kt',\n '.kts',\n '.swift',\n '.c',\n '.cc',\n '.cpp',\n '.h',\n '.hpp',\n '.rb',\n '.php',\n '.cs',\n '.sh',\n '.bash',\n '.zsh',\n '.sql',\n]);\n\nconst DOC_EXTENSIONS = new Set(['.md', '.mdx', '.rst', '.adoc']);\nconst GENERATED_DIRS = new Set([\n 'dist',\n 'build',\n 'coverage',\n 'node_modules',\n '.next',\n 'out',\n 'target',\n '.turbo',\n '.cache',\n]);\n\nfunction basename(path: string): string {\n const parts = path.split('/');\n return parts.at(-1) ?? path;\n}\n\nfunction extension(path: string): string {\n const name = basename(path);\n const dot = name.lastIndexOf('.');\n return dot <= 0 ? '' : name.slice(dot).toLowerCase();\n}\n\nfunction normalizeRoot(root: string): string {\n const normalized = root.replace(/\\\\/g, '/').replace(/^\\/+|\\/+$/g, '');\n return normalized === '' ? '.' : normalized;\n}\n\nfunction isUnder(path: string, root: string): boolean {\n if (root === '.') return true;\n return path === root || path.startsWith(`${root}/`);\n}\n\nfunction conventionRoot(path: string): string {\n const parts = path.split('/');\n if (parts.length === 1) return '.';\n const first = parts[0];\n if (first === undefined) return '.';\n if (WORKSPACE_CONTAINERS.has(first) && parts.length > 2) {\n return `${first}/${parts[1] ?? ''}`;\n }\n if (CONVENTION_ROOTS.has(first)) return first;\n return first;\n}\n\n/**\n * Builds a deterministic path-to-module map. Explicit workspace roots win over\n * convention roots for the same directory; the longest matching root wins for\n * a path. Root-level files belong to the synthetic `.` module.\n */\nexport function buildModuleMap(\n files: readonly ManifestFile[],\n roots: readonly ModuleRoot[] = [],\n): ModuleMap {\n const byRoot = new Map<string, ModuleInfo>();\n const add = (root: string, source: ModuleInfo['source'], info?: ModuleRoot): void => {\n const normalized = normalizeRoot(root);\n if (normalized === '.' && source !== 'root') return;\n const existing = byRoot.get(normalized);\n if (existing !== undefined && !(source === 'workspace' && existing.source === 'convention')) {\n return;\n }\n if (existing !== undefined && source === 'workspace') {\n byRoot.set(normalized, {\n id: info?.id ?? existing.id,\n root: normalized,\n ...(info?.name === undefined && existing.name === undefined\n ? {}\n : { name: info?.name ?? existing.name }),\n source,\n });\n return;\n }\n byRoot.set(normalized, {\n id: info?.id ?? normalized,\n root: normalized,\n ...(info?.name === undefined ? {} : { name: info.name }),\n source,\n });\n };\n\n for (const root of roots) {\n add(root.root, 'workspace', root);\n }\n for (const path of files.map((file) => file.path)) {\n add(conventionRoot(path), 'convention');\n }\n add('.', 'root');\n\n const modules = [...byRoot.values()].sort((left, right) => {\n if (left.id < right.id) return -1;\n if (left.id > right.id) return 1;\n return 0;\n });\n const orderedByRoot = [...modules].sort((left, right) => right.root.length - left.root.length);\n\n return {\n modules,\n moduleFor(path: string): string {\n for (const module of orderedByRoot) {\n if (isUnder(path, module.root)) return module.id;\n }\n return '.';\n },\n };\n}\n\nfunction isGenerated(file: ManifestFile): boolean {\n if (file.reason === 'generated') return true;\n const name = basename(file.path);\n if (\n name === 'pnpm-lock.yaml' ||\n name === 'package-lock.json' ||\n name === 'yarn.lock' ||\n name === 'uv.lock' ||\n name === 'poetry.lock' ||\n name === 'Cargo.lock' ||\n name === 'go.sum'\n ) {\n return true;\n }\n if (/(\\.min\\.|\\.generated\\.)/.test(name)) return true;\n return file.path.split('/').some((part) => GENERATED_DIRS.has(part));\n}\n\nfunction isInstructions(path: string): boolean {\n return INSTRUCTION_FILES.has(path) || path.startsWith('.dsh/');\n}\n\nfunction isMigration(path: string): boolean {\n if (extension(path) === '.sql') return true;\n return /(^|\\/)(migrations?|alembic|migrate)\\//.test(path);\n}\n\nfunction isTest(path: string): boolean {\n if (/(^|\\/)(test|tests|__tests__|e2e|ui-tests|spec|specs)\\//.test(path)) return true;\n return /\\.(test|spec)\\.[^/]+$/.test(path);\n}\n\nfunction isDocs(path: string): boolean {\n if (path.startsWith('docs/')) return true;\n if (DOC_FILES.has(basename(path))) return true;\n return DOC_EXTENSIONS.has(extension(path));\n}\n\nfunction isConfig(path: string): boolean {\n const name = basename(path);\n if (CONFIG_FILES.has(name)) return true;\n if (path.startsWith('.github/')) return true;\n if (\n /^(tsconfig|vite|vitest|eslint|prettier|playwright|jest|rollup|webpack).*\\.(json|js|mjs|cjs|ts)$/.test(\n name,\n )\n ) {\n return true;\n }\n if (/^\\.env(\\..*)?$/.test(name)) return true;\n return extension(path) === '.yml' || extension(path) === '.yaml' || extension(path) === '.toml';\n}\n\nfunction isSource(path: string): boolean {\n return SOURCE_EXTENSIONS.has(extension(path));\n}\n\n/**\n * Classifies a changed path by kind. Manifest policy (`generated`) and binary\n * metadata take priority over the path shape; `ignored` alone does not change\n * the kind because ignore policy is applied by the tier planner.\n */\nexport function classifyFile(file: ManifestFile): FileKind {\n if (isInstructions(file.path)) return 'instructions';\n if (file.binary) return 'binary';\n if (isGenerated(file)) return 'generated';\n if (isMigration(file.path)) return 'migration';\n if (isTest(file.path)) return 'test';\n if (isDocs(file.path)) return 'docs';\n if (isConfig(file.path)) return 'config';\n if (isSource(file.path)) return 'source';\n return 'other';\n}\n\n/**\n * User-facing docs are deep-reviewed because they can change behavior or\n * contracts. Internal notes are still docs, but light.\n */\nexport function isUserFacingDoc(path: string): boolean {\n if (path.startsWith('docs/internal/')) return false;\n if (path.startsWith('docs/')) return true;\n const name = basename(path);\n return DOC_FILES.has(name) || /^README\\./i.test(name);\n}\n\nexport interface ShardLimits {\n maxFiles: number;\n maxPatchBytes: number;\n}\n\nexport const DEFAULT_SHARD_LIMITS: ShardLimits = Object.freeze({\n maxFiles: 12,\n maxPatchBytes: 120_000,\n});\n\nexport interface PlanFile {\n path: string;\n moduleId: string;\n kind: FileKind;\n tier: ReviewTier;\n shardId?: string;\n /** Policy reason for a manifest-only file; absent for reviewed files. */\n reason?: string;\n}\n\nexport interface ReviewShard {\n id: string;\n moduleId: string;\n tier: 'deep' | 'light';\n effort: 'deep' | 'light';\n paths: string[];\n fileCount: number;\n patchBytes: number;\n}\n\nexport interface ReviewPlan {\n schemaVersion: 1;\n files: PlanFile[];\n shards: ReviewShard[];\n coverage: ReviewCoverage;\n coveragePlan: {\n total: number;\n byTier: Record<ReviewTier, number>;\n };\n riskFlags: string[];\n limits: ShardLimits;\n}\n\nconst INCOMPLETE_MANIFEST_WARNINGS = new Set([\n 'manifest-listing-truncated',\n 'untracked-list-truncated',\n 'manifest-missing',\n]);\n\nfunction byteCompare(left: string, right: string): number {\n return Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));\n}\n\nfunction isNoHunks(file: ManifestFile): boolean {\n return (\n file.reason === undefined &&\n !file.binary &&\n !file.ignored &&\n file.hunkCount === 0 &&\n file.added === 0 &&\n file.deleted === 0\n );\n}\n\nfunction policyReason(file: ManifestFile, kind: FileKind): string | undefined {\n if (file.reason !== undefined && ['generated', 'binary', 'ignored'].includes(file.reason)) {\n return file.reason;\n }\n if (file.ignored) return 'ignored';\n if (file.binary) return 'binary';\n if (kind === 'generated') return 'generated';\n return undefined;\n}\n\nfunction tierFor(file: ManifestFile, kind: FileKind, moduleHasSource: boolean): ReviewTier {\n if (kind === 'binary' || kind === 'generated' || file.ignored) return 'manifest-only';\n switch (kind) {\n case 'source':\n case 'config':\n case 'migration':\n case 'instructions':\n return 'deep';\n case 'test':\n return moduleHasSource ? 'deep' : 'light';\n case 'docs':\n return isUserFacingDoc(file.path) ? 'deep' : 'light';\n case 'other':\n return 'light';\n }\n}\n\nfunction riskFlags(input: {\n manifest: ReviewManifest;\n files: readonly PlanFile[];\n modules: number;\n}): string[] {\n const flags: string[] = [];\n const generated = input.files.filter((file) => file.tier === 'manifest-only').length;\n if (input.manifest.totals.files >= 100 || input.manifest.totals.patchBytes >= 1_000_000) {\n flags.push('large-diff');\n }\n if (input.files.length >= 10 && generated * 10 >= input.files.length) {\n flags.push('generated-heavy');\n }\n if (input.manifest.warnings.some((warning) => INCOMPLETE_MANIFEST_WARNINGS.has(warning))) {\n flags.push('manifest-incomplete');\n }\n if (\n input.files.some((file) => /(^|\\/)(auth|security|crypto|migrations?|config)\\//.test(file.path))\n ) {\n flags.push('security-sensitive');\n }\n if (!input.files.some((file) => file.tier === 'deep' && file.kind === 'source')) {\n flags.push('no-source');\n }\n if (input.modules > 8) flags.push('many-modules');\n return flags;\n}\n\n/**\n * Builds a pure, deterministic review plan. The planner never executes a\n * shard, calls a model, or marks work complete; every deep/light file starts\n * pending in the returned coverage ledger.\n */\nexport function buildReviewPlan(input: {\n manifest: ReviewManifest;\n moduleRoots?: readonly ModuleRoot[];\n limits?: Partial<ShardLimits>;\n}): ReviewPlan {\n const limits: ShardLimits = {\n maxFiles: Math.max(1, Math.trunc(input.limits?.maxFiles ?? DEFAULT_SHARD_LIMITS.maxFiles)),\n maxPatchBytes: Math.max(\n 1,\n Math.trunc(input.limits?.maxPatchBytes ?? DEFAULT_SHARD_LIMITS.maxPatchBytes),\n ),\n };\n const modules = buildModuleMap(input.manifest.files, input.moduleRoots);\n const kinds = new Map<string, FileKind>();\n for (const file of input.manifest.files) {\n kinds.set(file.path, classifyFile(file));\n }\n const sourceModules = new Set<string>();\n for (const file of input.manifest.files) {\n if (kinds.get(file.path) === 'source') sourceModules.add(modules.moduleFor(file.path));\n }\n\n const files: PlanFile[] = input.manifest.files.map((file) => {\n const kind = kinds.get(file.path) ?? 'other';\n const moduleId = modules.moduleFor(file.path);\n const tier = tierFor(file, kind, sourceModules.has(moduleId));\n const reason = tier === 'manifest-only' ? policyReason(file, kind) : undefined;\n return {\n path: file.path,\n moduleId,\n kind,\n tier,\n ...(reason === undefined ? {} : { reason }),\n };\n });\n const byPath = new Map(files.map((file) => [file.path, file]));\n\n const groups = new Map<string, PlanFile[]>();\n for (const file of files) {\n if (file.tier === 'manifest-only') continue;\n const manifestFile = input.manifest.files.find((item) => item.path === file.path);\n if (manifestFile === undefined || isNoHunks(manifestFile)) continue;\n const key = `${file.moduleId}\\0${file.tier}`;\n const group = groups.get(key) ?? [];\n group.push(file);\n groups.set(key, group);\n }\n\n const shards: ReviewShard[] = [];\n const keys = [...groups.keys()].sort(byteCompare);\n for (const key of keys) {\n const group = groups.get(key) ?? [];\n group.sort((left, right) => byteCompare(left.path, right.path));\n let current: PlanFile[] = [];\n let currentBytes = 0;\n const flush = (): void => {\n if (current.length === 0) return;\n const id = `shard_${String(shards.length + 1).padStart(3, '0')}`;\n const first = current[0];\n if (first === undefined) return;\n const shard: ReviewShard = {\n id,\n moduleId: first.moduleId,\n tier: first.tier === 'deep' ? 'deep' : 'light',\n effort: first.tier === 'deep' ? 'deep' : 'light',\n paths: current.map((file) => file.path),\n fileCount: current.length,\n patchBytes: currentBytes,\n };\n shards.push(shard);\n for (const file of current) file.shardId = id;\n current = [];\n currentBytes = 0;\n };\n for (const file of group) {\n const manifestFile = input.manifest.files.find((item) => item.path === file.path);\n const bytes = manifestFile?.patchBytes ?? 0;\n if (\n current.length > 0 &&\n (current.length >= limits.maxFiles || currentBytes + bytes > limits.maxPatchBytes)\n ) {\n flush();\n }\n current.push(file);\n currentBytes += bytes;\n }\n flush();\n }\n\n const coverageFiles: FileCoverage[] = input.manifest.files.map((manifestFile) => {\n const planFile = byPath.get(manifestFile.path);\n if (planFile === undefined) {\n return { path: manifestFile.path, status: 'skipped', reason: 'render-budget' };\n }\n if (planFile.tier === 'manifest-only') {\n return {\n path: planFile.path,\n status: 'manifest-only',\n ...(planFile.reason === undefined ? {} : { reason: planFile.reason }),\n };\n }\n if (isNoHunks(manifestFile)) {\n return { path: planFile.path, status: 'no-hunks' };\n }\n if (planFile.shardId !== undefined) {\n return { path: planFile.path, status: 'pending', shardId: planFile.shardId };\n }\n return { path: planFile.path, status: 'skipped', reason: 'render-budget' };\n });\n const coverage: ReviewCoverage = {\n files: coverageFiles,\n totals: summarizeCoverage(coverageFiles),\n ...(input.manifest.warnings.some((warning) => INCOMPLETE_MANIFEST_WARNINGS.has(warning))\n ? { incomplete: true }\n : {}),\n };\n\n const byTier: Record<ReviewTier, number> = {\n deep: files.filter((file) => file.tier === 'deep').length,\n light: files.filter((file) => file.tier === 'light').length,\n 'manifest-only': files.filter((file) => file.tier === 'manifest-only').length,\n };\n return {\n schemaVersion: 1,\n files,\n shards,\n coverage,\n coveragePlan: { total: files.length, byTier },\n riskFlags: riskFlags({\n manifest: input.manifest,\n files,\n modules: modules.modules.length,\n }),\n limits,\n };\n}\n\n/** Returns a detached copy of the plan's coverage ledger for event persistence. */\nexport function coverageFromPlan(plan: ReviewPlan): ReviewCoverage {\n return {\n files: plan.coverage.files.map((file) => ({ ...file })),\n totals: summarizeCoverage(plan.coverage.files),\n ...(plan.coverage.incomplete === true ? { incomplete: true } : {}),\n };\n}\n", "import type {\n AgentReport,\n Finding,\n ReviewQuestion,\n RiskLevel,\n TestCommand,\n Verification,\n} from '../reviewer/types.js';\nimport type { ReviewPlan } from './plan.js';\nimport { coverageFromPlan } from './plan.js';\nimport { summarizeCoverage } from './coverage.js';\nimport type { FileCoverage, ReviewCoverage, ReviewId, RoundNo } from './types.js';\nimport type { ReviewerBackendKind } from '../reviewer/types.js';\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nexport interface ShardReportInput {\n shardId: string;\n report: AgentReport;\n}\n\nexport interface AggregateShardReportsInput {\n reviewId: ReviewId;\n round: RoundNo;\n backend: ReviewerBackendKind;\n plan: ReviewPlan;\n reports: readonly ShardReportInput[];\n /** Shards known to have failed; missing shards are failed automatically. */\n failedShards?: readonly string[];\n}\n\nexport interface AggregateShardReportsResult {\n report: AgentReport;\n coverage: ReviewCoverage;\n}\n\nfunction findingKey(finding: Finding): string {\n return `${finding.file}\\0${finding.lineStart}\\0${finding.lineEnd}\\0${finding.title}`;\n}\n\nfunction questionKey(question: ReviewQuestion): string {\n return `${question.file}\\0${question.line}\\0${question.question}`;\n}\n\nfunction mergeFindings(reports: readonly ShardReportInput[]): Finding[] {\n const findings: Finding[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const finding of report.report.findings) {\n const key = findingKey(finding);\n if (seen.has(key)) continue;\n seen.add(key);\n findings.push({ ...finding });\n }\n }\n return findings;\n}\n\nfunction mergeQuestions(reports: readonly ShardReportInput[]): ReviewQuestion[] {\n const questions: ReviewQuestion[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const question of report.report.questions) {\n const key = questionKey(question);\n if (seen.has(key)) continue;\n seen.add(key);\n questions.push({ ...question });\n }\n }\n return questions;\n}\n\nfunction mergeTests(reports: readonly ShardReportInput[]): AgentReport['tests'] {\n const commands: TestCommand[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const command of report.report.tests.commands) {\n const key = `${command.command}\\0${command.result}\\0${command.summary}`;\n if (seen.has(key)) continue;\n seen.add(key);\n commands.push({ ...command });\n }\n }\n return { ran: reports.some((report) => report.report.tests.ran), commands };\n}\n\nfunction mergeVerification(reports: readonly ShardReportInput[]): Verification[] {\n const entries: Verification[] = [];\n const seen = new Set<string>();\n for (const report of reports) {\n for (const item of report.report.verification) {\n const key = `${item.commentId}\\0${item.status}\\0${item.evidence}`;\n if (seen.has(key)) continue;\n seen.add(key);\n entries.push({ ...item });\n }\n }\n return entries;\n}\n\nfunction highestRisk(reports: readonly ShardReportInput[]): RiskLevel {\n let result: RiskLevel = 'low';\n for (const report of reports) {\n if (RISK_ORDER[report.report.riskLevel] > RISK_ORDER[result]) {\n result = report.report.riskLevel;\n }\n }\n return result;\n}\n\nfunction updateCoverage(\n plan: ReviewPlan,\n reports: readonly ShardReportInput[],\n failed: ReadonlySet<string>,\n): ReviewCoverage {\n const base = coverageFromPlan(plan);\n const byShard = new Map(reports.map((report) => [report.shardId, report]));\n const files: FileCoverage[] = base.files.map((file) => {\n const planFile = plan.files.find((item) => item.path === file.path);\n const shardId = planFile?.shardId;\n if (shardId === undefined) return { ...file };\n if (failed.has(shardId)) {\n return { ...file, status: 'failed', reason: 'shard-failed' };\n }\n const shard = plan.shards.find((item) => item.id === shardId);\n if (shard === undefined || !byShard.has(shardId)) {\n return { ...file, status: 'failed', reason: 'shard-failed' };\n }\n const status = shard.tier === 'deep' ? 'deep-reviewed' : 'light-reviewed';\n const next: FileCoverage = { ...file, status };\n delete next.reason;\n return next;\n });\n return {\n files,\n totals: summarizeCoverage(files),\n ...(base.incomplete === true ? { incomplete: true } : {}),\n };\n}\n\nexport interface MergeShardReportsInput {\n reviewId: ReviewId;\n round: RoundNo;\n backend: ReviewerBackendKind;\n /**\n * Reports in display order. Callers that have a plan must sort by plan order;\n * live previews sort by shard index before calling this helper.\n */\n reports: readonly ShardReportInput[];\n}\n\n/**\n * Merges completed shard reports without another model call or coverage\n * assumptions. Missing shards are intentionally ignored: this is the live\n * preview path while a round is still running. Final aggregation, which also\n * computes coverage and fails missing shards, uses this helper after ordering\n * reports by the plan.\n */\nexport function mergeShardReports(input: MergeShardReportsInput): AgentReport {\n const findings = mergeFindings(input.reports);\n const summaryParts = input.reports\n .map((report) => report.report.summary.trim())\n .filter((summary) => summary !== '');\n const summary =\n summaryParts.length === 0\n ? 'The review did not produce a shard report.'\n : summaryParts.join('\\n\\n').slice(0, 20_000);\n return {\n schemaVersion: 1,\n summary,\n riskLevel: highestRisk(input.reports),\n findings,\n questions: mergeQuestions(input.reports),\n tests: mergeTests(input.reports),\n verification: mergeVerification(input.reports),\n reviewId: input.reviewId,\n round: input.round,\n backend: input.backend,\n rejected: input.reports.flatMap((item) => item.report.rejected),\n warnings: [...new Set(input.reports.flatMap((report) => report.report.warnings))],\n ...(input.reports.some((item) => item.report.dirtyWorktree === true)\n ? { dirtyWorktree: true }\n : {}),\n skipped: false,\n };\n}\n\n/**\n * Merges shard reports without another model call. Deterministic: reports are\n * ordered by the review plan (not shard completion order), findings dedupe by\n * file/range/title, coverage is derived from the plan and the completed shards.\n * Missing shards are failed, never silently reviewed.\n */\nexport function aggregateShardReports(\n input: AggregateShardReportsInput,\n): AggregateShardReportsResult {\n const failed = new Set(input.failedShards ?? []);\n for (const shard of input.plan.shards) {\n if (!input.reports.some((report) => report.shardId === shard.id)) failed.add(shard.id);\n }\n const shardOrder = new Map(input.plan.shards.map((shard, index) => [shard.id, index]));\n const rank = (shardId: string): number => shardOrder.get(shardId) ?? input.plan.shards.length;\n const reports = [...input.reports].sort(\n (left, right) => rank(left.shardId) - rank(right.shardId),\n );\n const report = mergeShardReports({\n reviewId: input.reviewId,\n round: input.round,\n backend: input.backend,\n reports,\n });\n for (const shardId of [...failed].sort()) {\n report.warnings.push(`shard-failed:${shardId}`);\n }\n return { report, coverage: updateCoverage(input.plan, reports, failed) };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["shard", "summary"]
|
|
7
7
|
}
|
package/dist/index.js
CHANGED
|
@@ -656,6 +656,7 @@ function manifestFileFromDiff(file) {
|
|
|
656
656
|
binary: file.binary,
|
|
657
657
|
ignored: file.ignored === true,
|
|
658
658
|
oversized: file.oversized,
|
|
659
|
+
...file.conflicted === true ? { conflicted: true } : {},
|
|
659
660
|
added: file.added,
|
|
660
661
|
deleted: file.deleted,
|
|
661
662
|
hunkCount: file.hunks.length,
|
|
@@ -1132,6 +1133,7 @@ function manifestEntry(input) {
|
|
|
1132
1133
|
const ignored = input.seed.ignored || mapped?.ignored === true;
|
|
1133
1134
|
const oversized = input.seed.oversized || mapped?.oversized === true;
|
|
1134
1135
|
const binary = input.seed.binary || mapped?.binary === true;
|
|
1136
|
+
const conflicted = input.seed.conflicted === true || mapped?.conflicted === true;
|
|
1135
1137
|
let reason = mapped?.reason ?? input.seed.reason;
|
|
1136
1138
|
if (reason === "diff-truncated" && binary && !ignored) reason = "binary";
|
|
1137
1139
|
if (reason === void 0) {
|
|
@@ -1146,6 +1148,7 @@ function manifestEntry(input) {
|
|
|
1146
1148
|
binary,
|
|
1147
1149
|
ignored,
|
|
1148
1150
|
oversized,
|
|
1151
|
+
...conflicted ? { conflicted: true } : {},
|
|
1149
1152
|
added: input.seed.fromNumstat ? input.seed.added : mapped?.added ?? input.seed.added,
|
|
1150
1153
|
deleted: input.seed.fromNumstat ? input.seed.deleted : mapped?.deleted ?? input.seed.deleted,
|
|
1151
1154
|
hunkCount: mapped?.hunkCount ?? 0,
|
|
@@ -1281,6 +1284,19 @@ async function untrackedFile(repoRoot, path, maxFileLines, maxContentBytes, incl
|
|
|
1281
1284
|
if (includeContent && !truncated) file.newLines = read.lines;
|
|
1282
1285
|
return file;
|
|
1283
1286
|
}
|
|
1287
|
+
async function listUnmerged(repoRoot, signal) {
|
|
1288
|
+
const result = await runGit(repoRoot, ["ls-files", "-u", "-z"], {
|
|
1289
|
+
signal,
|
|
1290
|
+
maxBytes: 8 * 1024 * 1024,
|
|
1291
|
+
truncateOnOverflow: true
|
|
1292
|
+
});
|
|
1293
|
+
const paths = /* @__PURE__ */ new Set();
|
|
1294
|
+
for (const record of splitNulPaths(result.stdout)) {
|
|
1295
|
+
const tab = record.indexOf(" ");
|
|
1296
|
+
if (tab >= 0) paths.add(record.slice(tab + 1));
|
|
1297
|
+
}
|
|
1298
|
+
return { paths, truncated: result.truncated === true };
|
|
1299
|
+
}
|
|
1284
1300
|
async function buildSnapshot(options) {
|
|
1285
1301
|
const includeContent = options.includeContent ?? DEFAULTS.includeContent;
|
|
1286
1302
|
const includeUntracked = options.includeUntracked ?? DEFAULTS.includeUntracked;
|
|
@@ -1303,6 +1319,33 @@ async function buildSnapshot(options) {
|
|
|
1303
1319
|
const listingTruncated = rawListing.truncated || numstatListing.truncated;
|
|
1304
1320
|
if (listingTruncated) warnings.push("manifest-listing-truncated");
|
|
1305
1321
|
const seeds = rawSeeds(rawListing.entries, numstatListing.entries, ignore);
|
|
1322
|
+
const unmerged = /* @__PURE__ */ new Set();
|
|
1323
|
+
if (scope.kind === "worktree") {
|
|
1324
|
+
const listing = await listUnmerged(scope.repoRoot, options.signal);
|
|
1325
|
+
if (listing.truncated) warnings.push("merge-conflict-list-truncated");
|
|
1326
|
+
for (const path of listing.paths) {
|
|
1327
|
+
if (paths !== void 0 && !paths.includes(path)) continue;
|
|
1328
|
+
unmerged.add(path);
|
|
1329
|
+
const seed = seeds.get(path);
|
|
1330
|
+
if (seed === void 0) {
|
|
1331
|
+
seeds.set(path, {
|
|
1332
|
+
path,
|
|
1333
|
+
status: "modified",
|
|
1334
|
+
binary: false,
|
|
1335
|
+
added: 0,
|
|
1336
|
+
deleted: 0,
|
|
1337
|
+
ignored: isIgnored(path, ignore),
|
|
1338
|
+
oversized: false,
|
|
1339
|
+
conflicted: true,
|
|
1340
|
+
fromNumstat: false
|
|
1341
|
+
});
|
|
1342
|
+
} else {
|
|
1343
|
+
seed.conflicted = true;
|
|
1344
|
+
}
|
|
1345
|
+
const warning = `merge-conflict:${path}`;
|
|
1346
|
+
if (!warnings.includes(warning)) warnings.push(warning);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1306
1349
|
const diff = await runGit(scope.repoRoot, diffArgs(scope, "patch", paths), {
|
|
1307
1350
|
signal: options.signal,
|
|
1308
1351
|
maxBytes: Math.max(maxDiffBytes * 8, 16 * 1024 * 1024),
|
|
@@ -1485,6 +1528,11 @@ async function buildSnapshot(options) {
|
|
|
1485
1528
|
if (newLines !== void 0) file.newLines = newLines;
|
|
1486
1529
|
files.push(file);
|
|
1487
1530
|
}
|
|
1531
|
+
if (unmerged.size > 0) {
|
|
1532
|
+
for (const file of files) {
|
|
1533
|
+
if (unmerged.has(file.path)) file.conflicted = true;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1488
1536
|
if (scope.kind === "worktree" && includeUntracked) {
|
|
1489
1537
|
const listing = await listUntracked(scope.repoRoot, options.signal);
|
|
1490
1538
|
if (listing.truncated) {
|
|
@@ -2849,6 +2897,7 @@ function fold(state, event) {
|
|
|
2849
2897
|
case "review/closed": {
|
|
2850
2898
|
next.status = "closed";
|
|
2851
2899
|
next.closedAt ??= event.at;
|
|
2900
|
+
next.closeVerdict = event.data.verdict;
|
|
2852
2901
|
break;
|
|
2853
2902
|
}
|
|
2854
2903
|
case "review/submitted": {
|
|
@@ -2861,6 +2910,7 @@ function fold(state, event) {
|
|
|
2861
2910
|
if (submission.verdict === "approve") {
|
|
2862
2911
|
next.status = "closed";
|
|
2863
2912
|
next.closedAt ??= event.at;
|
|
2913
|
+
next.closeVerdict ??= "approved";
|
|
2864
2914
|
}
|
|
2865
2915
|
break;
|
|
2866
2916
|
}
|
|
@@ -5186,9 +5236,10 @@ function buildFeedback(input) {
|
|
|
5186
5236
|
const openComments = activeComments.filter(
|
|
5187
5237
|
(comment) => comment.author.kind === "human" && comment.state === "open"
|
|
5188
5238
|
);
|
|
5189
|
-
const
|
|
5239
|
+
const resolvedComments = activeComments.filter(
|
|
5190
5240
|
(comment) => comment.author.kind === "human" && comment.state === "resolved"
|
|
5191
5241
|
);
|
|
5242
|
+
const resolved = input.includeResolved === false ? [] : resolvedComments;
|
|
5192
5243
|
const reportFindings = report?.findings ?? [];
|
|
5193
5244
|
const decisions = /* @__PURE__ */ new Map();
|
|
5194
5245
|
for (const finding of reportFindings) {
|
|
@@ -5295,7 +5346,7 @@ function buildFeedback(input) {
|
|
|
5295
5346
|
(question) => `- [${question.id}] ${question.question} \u2014 ${question.why}`
|
|
5296
5347
|
)
|
|
5297
5348
|
);
|
|
5298
|
-
if (resolved.length > 0
|
|
5349
|
+
if (resolved.length > 0) {
|
|
5299
5350
|
section(
|
|
5300
5351
|
lines,
|
|
5301
5352
|
"\u0423\u0436\u0435 \u0438\u0441\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043E \u0432 \u044D\u0442\u043E\u043C \u0440\u0430\u0443\u043D\u0434\u0435",
|
|
@@ -6378,7 +6429,7 @@ var ReviewServiceImpl = class {
|
|
|
6378
6429
|
};
|
|
6379
6430
|
|
|
6380
6431
|
// packages/core/src/index.ts
|
|
6381
|
-
var CORE_VERSION = "0.
|
|
6432
|
+
var CORE_VERSION = "0.3.0";
|
|
6382
6433
|
export {
|
|
6383
6434
|
AnchorError,
|
|
6384
6435
|
CORE_VERSION,
|