@mirnoorata/codexa 0.2.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 +634 -0
- package/dist/artifacts.d.ts +2 -0
- package/dist/artifacts.js +375 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/autonomy.d.ts +17 -0
- package/dist/autonomy.js +124 -0
- package/dist/autonomy.js.map +1 -0
- package/dist/autoverify/policy.d.ts +5 -0
- package/dist/autoverify/policy.js +18 -0
- package/dist/autoverify/policy.js.map +1 -0
- package/dist/autoverify.d.ts +45 -0
- package/dist/autoverify.js +1041 -0
- package/dist/autoverify.js.map +1 -0
- package/dist/cache-lock.d.ts +16 -0
- package/dist/cache-lock.js +181 -0
- package/dist/cache-lock.js.map +1 -0
- package/dist/cli/hooks.d.ts +5 -0
- package/dist/cli/hooks.js +264 -0
- package/dist/cli/hooks.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1034 -0
- package/dist/cli.js.map +1 -0
- package/dist/codex-contract.d.ts +2 -0
- package/dist/codex-contract.js +78 -0
- package/dist/codex-contract.js.map +1 -0
- package/dist/command.d.ts +34 -0
- package/dist/command.js +162 -0
- package/dist/command.js.map +1 -0
- package/dist/doctor.d.ts +112 -0
- package/dist/doctor.js +518 -0
- package/dist/doctor.js.map +1 -0
- package/dist/eval/baseline.d.ts +7 -0
- package/dist/eval/baseline.js +146 -0
- package/dist/eval/baseline.js.map +1 -0
- package/dist/eval/historical.d.ts +4 -0
- package/dist/eval/historical.js +663 -0
- package/dist/eval/historical.js.map +1 -0
- package/dist/eval/render.d.ts +2 -0
- package/dist/eval/render.js +53 -0
- package/dist/eval/render.js.map +1 -0
- package/dist/eval/scoring.d.ts +21 -0
- package/dist/eval/scoring.js +618 -0
- package/dist/eval/scoring.js.map +1 -0
- package/dist/eval/synthetic.d.ts +36 -0
- package/dist/eval/synthetic.js +107 -0
- package/dist/eval/synthetic.js.map +1 -0
- package/dist/eval/types.d.ts +36 -0
- package/dist/eval/types.js +2 -0
- package/dist/eval/types.js.map +1 -0
- package/dist/eval.d.ts +140 -0
- package/dist/eval.js +551 -0
- package/dist/eval.js.map +1 -0
- package/dist/git.d.ts +17 -0
- package/dist/git.js +189 -0
- package/dist/git.js.map +1 -0
- package/dist/github-release.d.ts +47 -0
- package/dist/github-release.js +610 -0
- package/dist/github-release.js.map +1 -0
- package/dist/github-sync.d.ts +68 -0
- package/dist/github-sync.js +345 -0
- package/dist/github-sync.js.map +1 -0
- package/dist/graph.d.ts +10 -0
- package/dist/graph.js +665 -0
- package/dist/graph.js.map +1 -0
- package/dist/indexer/aliases.d.ts +2 -0
- package/dist/indexer/aliases.js +190 -0
- package/dist/indexer/aliases.js.map +1 -0
- package/dist/indexer/artifact-writing.d.ts +3 -0
- package/dist/indexer/artifact-writing.js +79 -0
- package/dist/indexer/artifact-writing.js.map +1 -0
- package/dist/indexer/discovery.d.ts +2 -0
- package/dist/indexer/discovery.js +5 -0
- package/dist/indexer/discovery.js.map +1 -0
- package/dist/indexer/external-facts.d.ts +6 -0
- package/dist/indexer/external-facts.js +45 -0
- package/dist/indexer/external-facts.js.map +1 -0
- package/dist/indexer/freshness.d.ts +8 -0
- package/dist/indexer/freshness.js +56 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/graph-stage.d.ts +2 -0
- package/dist/indexer/graph-stage.js +21 -0
- package/dist/indexer/graph-stage.js.map +1 -0
- package/dist/indexer/parsing.d.ts +30 -0
- package/dist/indexer/parsing.js +177 -0
- package/dist/indexer/parsing.js.map +1 -0
- package/dist/indexer/pipeline.d.ts +5 -0
- package/dist/indexer/pipeline.js +8 -0
- package/dist/indexer/pipeline.js.map +1 -0
- package/dist/indexer/ranking.d.ts +4 -0
- package/dist/indexer/ranking.js +134 -0
- package/dist/indexer/ranking.js.map +1 -0
- package/dist/indexer.d.ts +13 -0
- package/dist/indexer.js +395 -0
- package/dist/indexer.js.map +1 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +566 -0
- package/dist/init.js.map +1 -0
- package/dist/language.d.ts +8 -0
- package/dist/language.js +123 -0
- package/dist/language.js.map +1 -0
- package/dist/live-index.d.ts +68 -0
- package/dist/live-index.js +215 -0
- package/dist/live-index.js.map +1 -0
- package/dist/lsp/assist.d.ts +44 -0
- package/dist/lsp/assist.js +331 -0
- package/dist/lsp/assist.js.map +1 -0
- package/dist/lsp/client.d.ts +59 -0
- package/dist/lsp/client.js +208 -0
- package/dist/lsp/client.js.map +1 -0
- package/dist/mcp/compaction.d.ts +15 -0
- package/dist/mcp/compaction.js +1249 -0
- package/dist/mcp/compaction.js.map +1 -0
- package/dist/mcp/envelope.d.ts +44 -0
- package/dist/mcp/envelope.js +425 -0
- package/dist/mcp/envelope.js.map +1 -0
- package/dist/mcp/prompts.d.ts +2 -0
- package/dist/mcp/prompts.js +109 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +2 -0
- package/dist/mcp/resources.js +132 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/runtime.d.ts +15 -0
- package/dist/mcp/runtime.js +122 -0
- package/dist/mcp/runtime.js.map +1 -0
- package/dist/mcp/session-memory.d.ts +3 -0
- package/dist/mcp/session-memory.js +61 -0
- package/dist/mcp/session-memory.js.map +1 -0
- package/dist/mcp/tool-registry.d.ts +269 -0
- package/dist/mcp/tool-registry.js +284 -0
- package/dist/mcp/tool-registry.js.map +1 -0
- package/dist/mcp/tools.d.ts +53 -0
- package/dist/mcp/tools.js +372 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp-repo-root.d.ts +16 -0
- package/dist/mcp-repo-root.js +322 -0
- package/dist/mcp-repo-root.js.map +1 -0
- package/dist/mcp-tool-catalog.d.ts +2 -0
- package/dist/mcp-tool-catalog.js +2 -0
- package/dist/mcp-tool-catalog.js.map +1 -0
- package/dist/mcp.d.ts +11 -0
- package/dist/mcp.js +332 -0
- package/dist/mcp.js.map +1 -0
- package/dist/outcome-ranking.d.ts +5 -0
- package/dist/outcome-ranking.js +115 -0
- package/dist/outcome-ranking.js.map +1 -0
- package/dist/parser/context.d.ts +28 -0
- package/dist/parser/context.js +2 -0
- package/dist/parser/context.js.map +1 -0
- package/dist/parser/ecma.d.ts +5 -0
- package/dist/parser/ecma.js +388 -0
- package/dist/parser/ecma.js.map +1 -0
- package/dist/parser/facts.d.ts +12 -0
- package/dist/parser/facts.js +137 -0
- package/dist/parser/facts.js.map +1 -0
- package/dist/parser/json.d.ts +3 -0
- package/dist/parser/json.js +318 -0
- package/dist/parser/json.js.map +1 -0
- package/dist/parser/markdown.d.ts +3 -0
- package/dist/parser/markdown.js +180 -0
- package/dist/parser/markdown.js.map +1 -0
- package/dist/parser/nodes.d.ts +5 -0
- package/dist/parser/nodes.js +75 -0
- package/dist/parser/nodes.js.map +1 -0
- package/dist/parser/python.d.ts +2 -0
- package/dist/parser/python.js +307 -0
- package/dist/parser/python.js.map +1 -0
- package/dist/parser/references.d.ts +3 -0
- package/dist/parser/references.js +204 -0
- package/dist/parser/references.js.map +1 -0
- package/dist/parser/risks.d.ts +4 -0
- package/dist/parser/risks.js +62 -0
- package/dist/parser/risks.js.map +1 -0
- package/dist/parser/routes.d.ts +5 -0
- package/dist/parser/routes.js +97 -0
- package/dist/parser/routes.js.map +1 -0
- package/dist/parser/shallow.d.ts +3 -0
- package/dist/parser/shallow.js +545 -0
- package/dist/parser/shallow.js.map +1 -0
- package/dist/parser/source.d.ts +4 -0
- package/dist/parser/source.js +127 -0
- package/dist/parser/source.js.map +1 -0
- package/dist/parser.d.ts +2 -0
- package/dist/parser.js +2 -0
- package/dist/parser.js.map +1 -0
- package/dist/placeholder-signals.d.ts +15 -0
- package/dist/placeholder-signals.js +511 -0
- package/dist/placeholder-signals.js.map +1 -0
- package/dist/post-edit-outcomes.d.ts +167 -0
- package/dist/post-edit-outcomes.js +484 -0
- package/dist/post-edit-outcomes.js.map +1 -0
- package/dist/queries.d.ts +12 -0
- package/dist/queries.js +13 -0
- package/dist/queries.js.map +1 -0
- package/dist/query/change-plan.d.ts +48 -0
- package/dist/query/change-plan.js +858 -0
- package/dist/query/change-plan.js.map +1 -0
- package/dist/query/compact-data.d.ts +25 -0
- package/dist/query/compact-data.js +74 -0
- package/dist/query/compact-data.js.map +1 -0
- package/dist/query/context.d.ts +5 -0
- package/dist/query/context.js +1162 -0
- package/dist/query/context.js.map +1 -0
- package/dist/query/diff.d.ts +5 -0
- package/dist/query/diff.js +111 -0
- package/dist/query/diff.js.map +1 -0
- package/dist/query/edge-evidence.d.ts +3 -0
- package/dist/query/edge-evidence.js +36 -0
- package/dist/query/edge-evidence.js.map +1 -0
- package/dist/query/formatting.d.ts +14 -0
- package/dist/query/formatting.js +67 -0
- package/dist/query/formatting.js.map +1 -0
- package/dist/query/graph-traversal.d.ts +22 -0
- package/dist/query/graph-traversal.js +218 -0
- package/dist/query/graph-traversal.js.map +1 -0
- package/dist/query/graph.d.ts +14 -0
- package/dist/query/graph.js +102 -0
- package/dist/query/graph.js.map +1 -0
- package/dist/query/impact.d.ts +28 -0
- package/dist/query/impact.js +568 -0
- package/dist/query/impact.js.map +1 -0
- package/dist/query/inspection.d.ts +9 -0
- package/dist/query/inspection.js +290 -0
- package/dist/query/inspection.js.map +1 -0
- package/dist/query/next-tools.d.ts +3 -0
- package/dist/query/next-tools.js +25 -0
- package/dist/query/next-tools.js.map +1 -0
- package/dist/query/placeholders.d.ts +24 -0
- package/dist/query/placeholders.js +121 -0
- package/dist/query/placeholders.js.map +1 -0
- package/dist/query/post-edit/decision.d.ts +49 -0
- package/dist/query/post-edit/decision.js +130 -0
- package/dist/query/post-edit/decision.js.map +1 -0
- package/dist/query/post-edit/dirty-scope.d.ts +16 -0
- package/dist/query/post-edit/dirty-scope.js +21 -0
- package/dist/query/post-edit/dirty-scope.js.map +1 -0
- package/dist/query/post-edit/next-actions.d.ts +22 -0
- package/dist/query/post-edit/next-actions.js +44 -0
- package/dist/query/post-edit/next-actions.js.map +1 -0
- package/dist/query/post-edit/snapshot-contract.d.ts +8 -0
- package/dist/query/post-edit/snapshot-contract.js +111 -0
- package/dist/query/post-edit/snapshot-contract.js.map +1 -0
- package/dist/query/post-edit.d.ts +5 -0
- package/dist/query/post-edit.js +1108 -0
- package/dist/query/post-edit.js.map +1 -0
- package/dist/query/quality.d.ts +43 -0
- package/dist/query/quality.js +134 -0
- package/dist/query/quality.js.map +1 -0
- package/dist/query/raw-search.d.ts +23 -0
- package/dist/query/raw-search.js +147 -0
- package/dist/query/raw-search.js.map +1 -0
- package/dist/query/runtime.d.ts +11 -0
- package/dist/query/runtime.js +79 -0
- package/dist/query/runtime.js.map +1 -0
- package/dist/query/search.d.ts +25 -0
- package/dist/query/search.js +429 -0
- package/dist/query/search.js.map +1 -0
- package/dist/query/session-memory.d.ts +3 -0
- package/dist/query/session-memory.js +108 -0
- package/dist/query/session-memory.js.map +1 -0
- package/dist/query/session.d.ts +41 -0
- package/dist/query/session.js +90 -0
- package/dist/query/session.js.map +1 -0
- package/dist/query/targets.d.ts +25 -0
- package/dist/query/targets.js +97 -0
- package/dist/query/targets.js.map +1 -0
- package/dist/query/test-commands.d.ts +10 -0
- package/dist/query/test-commands.js +110 -0
- package/dist/query/test-commands.js.map +1 -0
- package/dist/query/test-plan.d.ts +6 -0
- package/dist/query/test-plan.js +104 -0
- package/dist/query/test-plan.js.map +1 -0
- package/dist/query/tests.d.ts +48 -0
- package/dist/query/tests.js +444 -0
- package/dist/query/tests.js.map +1 -0
- package/dist/query/verification/shell.d.ts +20 -0
- package/dist/query/verification/shell.js +164 -0
- package/dist/query/verification/shell.js.map +1 -0
- package/dist/query/verification.d.ts +47 -0
- package/dist/query/verification.js +1123 -0
- package/dist/query/verification.js.map +1 -0
- package/dist/query/workflow.d.ts +17 -0
- package/dist/query/workflow.js +252 -0
- package/dist/query/workflow.js.map +1 -0
- package/dist/query/workspace-guidance.d.ts +26 -0
- package/dist/query/workspace-guidance.js +214 -0
- package/dist/query/workspace-guidance.js.map +1 -0
- package/dist/query/worktree-state.d.ts +22 -0
- package/dist/query/worktree-state.js +32 -0
- package/dist/query/worktree-state.js.map +1 -0
- package/dist/query/worktree.d.ts +16 -0
- package/dist/query/worktree.js +194 -0
- package/dist/query/worktree.js.map +1 -0
- package/dist/query-data.d.ts +4 -0
- package/dist/query-data.js +112 -0
- package/dist/query-data.js.map +1 -0
- package/dist/repo-files.d.ts +24 -0
- package/dist/repo-files.js +105 -0
- package/dist/repo-files.js.map +1 -0
- package/dist/resolver.d.ts +9 -0
- package/dist/resolver.js +555 -0
- package/dist/resolver.js.map +1 -0
- package/dist/retrieval.d.ts +46 -0
- package/dist/retrieval.js +783 -0
- package/dist/retrieval.js.map +1 -0
- package/dist/risk-ingest.d.ts +16 -0
- package/dist/risk-ingest.js +458 -0
- package/dist/risk-ingest.js.map +1 -0
- package/dist/rules.d.ts +10 -0
- package/dist/rules.js +107 -0
- package/dist/rules.js.map +1 -0
- package/dist/semantic/python.d.ts +9 -0
- package/dist/semantic/python.js +817 -0
- package/dist/semantic/python.js.map +1 -0
- package/dist/semantic/typescript.d.ts +10 -0
- package/dist/semantic/typescript.js +714 -0
- package/dist/semantic/typescript.js.map +1 -0
- package/dist/semantic-retrieval.d.ts +53 -0
- package/dist/semantic-retrieval.js +673 -0
- package/dist/semantic-retrieval.js.map +1 -0
- package/dist/session-memory/derivation.d.ts +6 -0
- package/dist/session-memory/derivation.js +400 -0
- package/dist/session-memory/derivation.js.map +1 -0
- package/dist/session-memory/event-log.d.ts +23 -0
- package/dist/session-memory/event-log.js +126 -0
- package/dist/session-memory/event-log.js.map +1 -0
- package/dist/session-memory/formatting.d.ts +7 -0
- package/dist/session-memory/formatting.js +86 -0
- package/dist/session-memory/formatting.js.map +1 -0
- package/dist/session-memory/model.d.ts +94 -0
- package/dist/session-memory/model.js +17 -0
- package/dist/session-memory/model.js.map +1 -0
- package/dist/session-memory/runtime.d.ts +24 -0
- package/dist/session-memory/runtime.js +289 -0
- package/dist/session-memory/runtime.js.map +1 -0
- package/dist/session-memory/store.d.ts +27 -0
- package/dist/session-memory/store.js +447 -0
- package/dist/session-memory/store.js.map +1 -0
- package/dist/session-memory.d.ts +1 -0
- package/dist/session-memory.js +2 -0
- package/dist/session-memory.js.map +1 -0
- package/dist/static-analysis.d.ts +36 -0
- package/dist/static-analysis.js +505 -0
- package/dist/static-analysis.js.map +1 -0
- package/dist/symbol-report-ingest.d.ts +8 -0
- package/dist/symbol-report-ingest.js +504 -0
- package/dist/symbol-report-ingest.js.map +1 -0
- package/dist/task-snapshots.d.ts +41 -0
- package/dist/task-snapshots.js +430 -0
- package/dist/task-snapshots.js.map +1 -0
- package/dist/types.d.ts +848 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/dist/util.d.ts +11 -0
- package/dist/util.js +63 -0
- package/dist/util.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/package.json +81 -0
- package/plugins/codexa/.codex-plugin/plugin.json +38 -0
- package/plugins/codexa/.mcp.json +20 -0
- package/plugins/codexa/scripts/codexa-mcp.js +100 -0
- package/plugins/codexa/skills/codexa/SKILL.md +48 -0
|
@@ -0,0 +1,1108 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isTestPath } from "../language.js";
|
|
4
|
+
import { groupDiffImpact, formatDiffGroups, formatGaps, indexGaps } from "./diff.js";
|
|
5
|
+
import { clampInt, fitLinesToTokenBudget } from "./formatting.js";
|
|
6
|
+
import { affectedWorkflowGraphEdges, testsFromGraphEdges } from "./graph.js";
|
|
7
|
+
import { contextPackQuery } from "./context.js";
|
|
8
|
+
import { formatContextQuality } from "./quality.js";
|
|
9
|
+
import { freshnessBanner } from "./runtime.js";
|
|
10
|
+
import { ensureQuerySession } from "./session.js";
|
|
11
|
+
import { normalizeSearchText } from "./search.js";
|
|
12
|
+
import { formatTestRecommendations, narrowTestRecommendationsByChangeType, recommendTests, uniqueTests } from "./tests.js";
|
|
13
|
+
import { findFile, normalizeInputPaths, resolveSymbolTarget } from "./targets.js";
|
|
14
|
+
import { formatVerificationCoverage, formatVerificationLedger, verificationEvidenceForCommandReports, verificationLedgerForPostEdit } from "./verification.js";
|
|
15
|
+
import { isCodexaControlPath, formatChangedEntry } from "./worktree.js";
|
|
16
|
+
import { postEditDecision } from "./post-edit/decision.js";
|
|
17
|
+
import { postEditDirtyScope } from "./post-edit/dirty-scope.js";
|
|
18
|
+
import { postEditNextActions, postEditStructuredNextTools } from "./post-edit/next-actions.js";
|
|
19
|
+
import { reconcileSnapshotTests, snapshotRiskBaseline, snapshotSymbolBaseline } from "./post-edit/snapshot-contract.js";
|
|
20
|
+
import { buildPostEditOutcome, savePostEditOutcome } from "../post-edit-outcomes.js";
|
|
21
|
+
import { readSessionMemory } from "../session-memory.js";
|
|
22
|
+
import { loadTaskSnapshot } from "../task-snapshots.js";
|
|
23
|
+
import { CURRENT_VERIFICATION_PROVENANCE } from "../types.js";
|
|
24
|
+
import { isTrustedAutoVerifyCommandReport } from "../autoverify.js";
|
|
25
|
+
import { AUTO_VERIFY_POLICY_DIGEST, AUTO_VERIFY_POLICY_ID } from "../autoverify/policy.js";
|
|
26
|
+
import { stableId, uniqueSorted } from "../util.js";
|
|
27
|
+
export async function postEditReviewQuery(sessionInput, input = {}, options = {}) {
|
|
28
|
+
return postEditReviewQueryInternal(sessionInput, input, options, {});
|
|
29
|
+
}
|
|
30
|
+
export async function postEditReviewWithTrustedRunnerReports(sessionInput, input = {}, trustedRunnerReports = [], options = {}) {
|
|
31
|
+
return postEditReviewQueryInternal(sessionInput, input, options, { trustedRunnerReports });
|
|
32
|
+
}
|
|
33
|
+
async function postEditReviewQueryInternal(sessionInput, input, options, internal) {
|
|
34
|
+
const session = await ensureQuerySession(sessionInput, options);
|
|
35
|
+
const { index, freshness, refresh, repoRoot } = session;
|
|
36
|
+
const tokenBudget = clampInt(input.tokenBudget ?? 2800, 600, 10000);
|
|
37
|
+
const limit = clampInt(input.limit ?? 10, 3, 30);
|
|
38
|
+
const loadedSnapshot = await loadTaskSnapshot(repoRoot, input.taskId);
|
|
39
|
+
const snapshot = loadedSnapshot.snapshot;
|
|
40
|
+
const snapshotAmbiguity = !input.taskId && snapshot ? await latestSnapshotAmbiguity(repoRoot, snapshot.taskId) : undefined;
|
|
41
|
+
const currentEntries = await session.getChangedFileEntries();
|
|
42
|
+
const dirtyScope = postEditDirtyScope({ snapshot, currentEntries, freshness, index });
|
|
43
|
+
const { currentDirtyPaths, changedSinceSnapshot, resolvedBaselineFiles, editPaths, unindexedEditedFiles } = dirtyScope;
|
|
44
|
+
const changedSymbols = (await session.getChangedSymbols()).filter((entry) => editPaths.includes(entry.symbol.path));
|
|
45
|
+
const requestedSymbolNames = new Set([...(snapshot?.input.symbols ?? []), ...(input.symbols ?? [])].map(normalizeSearchText));
|
|
46
|
+
const plannedSymbolIds = requestedSymbolIds(snapshot, requestedSymbolNames);
|
|
47
|
+
const unplannedChangedSymbols = requestedSymbolNames.size > 0 && plannedSymbolIds.size > 0
|
|
48
|
+
? changedSymbols.filter((entry) => !plannedSymbolIds.has(entry.symbol.id) && plannedScopeContainsSymbolFile(snapshot, entry.symbol.path))
|
|
49
|
+
: [];
|
|
50
|
+
const changedGroups = groupDiffImpact(index, changedSinceSnapshot, changedSymbols, unindexedEditedFiles);
|
|
51
|
+
const explicitFiles = normalizeInputPaths(input.files ?? [], repoRoot);
|
|
52
|
+
const explicitSymbolFiles = (input.symbols ?? [])
|
|
53
|
+
.flatMap((symbol) => {
|
|
54
|
+
const resolved = resolveSymbolTarget(index, symbol);
|
|
55
|
+
return resolved.symbol ? [resolved.symbol.path] : [];
|
|
56
|
+
})
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
const plannedScope = snapshot ? (snapshot.plannedEditTargets.length > 0 ? snapshot.plannedEditTargets : snapshot.plannedFiles) : [];
|
|
59
|
+
const plannedScopeSet = new Set(plannedScope);
|
|
60
|
+
const plannedRenames = snapshot
|
|
61
|
+
? changedSinceSnapshot.filter((entry) => entry.oldPath && plannedScopeSet.has(entry.oldPath) && !plannedScopeSet.has(entry.path))
|
|
62
|
+
: [];
|
|
63
|
+
const unplannedEditedFiles = snapshot && plannedScopeSet.size > 0
|
|
64
|
+
? changedSinceSnapshot
|
|
65
|
+
.filter((entry) => !isCodexaControlPath(entry.path))
|
|
66
|
+
.filter((entry) => !plannedScopeSet.has(entry.path) && !(entry.oldPath && plannedScopeSet.has(entry.oldPath)))
|
|
67
|
+
.map((entry) => entry.path)
|
|
68
|
+
: [];
|
|
69
|
+
const renamedAwayPaths = new Set(changedSinceSnapshot.flatMap((entry) => (entry.oldPath ? [entry.oldPath] : [])));
|
|
70
|
+
const plannedButUntouchedFiles = snapshot
|
|
71
|
+
? plannedScope.filter((filePath) => !editPaths.includes(filePath) && !currentDirtyPaths.includes(filePath) && !renamedAwayPaths.has(filePath))
|
|
72
|
+
: [];
|
|
73
|
+
const headChanged = Boolean(snapshot && snapshot.dirtyBaseline.headCommit !== freshness.headCommit);
|
|
74
|
+
const task = input.task ?? snapshot?.task ?? "Post-edit review";
|
|
75
|
+
const effectiveTaskId = snapshot?.taskId ?? loadedSnapshot.latestTaskId ?? input.taskId;
|
|
76
|
+
const changeType = input.changeType ?? snapshot?.changeType ?? "unknown";
|
|
77
|
+
const reviewTargets = uniqueSorted([
|
|
78
|
+
...(editPaths.length > 0 ? editPaths : []),
|
|
79
|
+
...explicitFiles,
|
|
80
|
+
...explicitSymbolFiles,
|
|
81
|
+
...(editPaths.length === 0 && explicitFiles.length === 0 && explicitSymbolFiles.length === 0 && snapshot ? snapshot.plannedFiles.slice(0, limit) : [])
|
|
82
|
+
]).slice(0, Math.max(limit, 3));
|
|
83
|
+
const context = await contextPackQuery(session, {
|
|
84
|
+
task,
|
|
85
|
+
files: reviewTargets,
|
|
86
|
+
symbols: input.symbols,
|
|
87
|
+
changeType,
|
|
88
|
+
diff: !snapshot,
|
|
89
|
+
tokenBudget: Math.min(tokenBudget, 3600),
|
|
90
|
+
limit,
|
|
91
|
+
includeSnippets: input.includeSnippets ?? false
|
|
92
|
+
}, { ...options, autoRefresh: false });
|
|
93
|
+
const contextData = context.data;
|
|
94
|
+
const semanticReviewContext = contextData.retrieval?.semantic;
|
|
95
|
+
const priorSessionMemory = await readSessionMemory({
|
|
96
|
+
repoRoot,
|
|
97
|
+
taskId: effectiveTaskId,
|
|
98
|
+
files: reviewTargets,
|
|
99
|
+
kinds: ["claim", "ruled_out", "open_question", "decision"],
|
|
100
|
+
freshness,
|
|
101
|
+
limit: 8,
|
|
102
|
+
includeStale: true
|
|
103
|
+
}).catch(() => undefined);
|
|
104
|
+
const selectedFiles = [...new Set([...reviewTargets, ...(contextData.focusFiles ?? []).map((entry) => entry.file.path)])].slice(0, Math.max(limit * 2, 12));
|
|
105
|
+
const symbolDeltas = compareSnapshotSymbols(snapshot, index, uniqueSorted([...reviewTargets, ...editPaths]));
|
|
106
|
+
const modifiedSymbols = changedSymbols
|
|
107
|
+
.map((entry) => `${entry.symbol.qualifiedName} (${entry.symbol.kind}) in ${entry.symbol.path}`)
|
|
108
|
+
.sort((a, b) => a.localeCompare(b));
|
|
109
|
+
const modifiedPublicSymbols = changedSymbols
|
|
110
|
+
.filter((entry) => entry.symbol.exported || ["route", "node"].includes(entry.symbol.kind))
|
|
111
|
+
.map((entry) => `${entry.symbol.qualifiedName} (${entry.symbol.kind}) in ${entry.symbol.path}`)
|
|
112
|
+
.sort((a, b) => a.localeCompare(b));
|
|
113
|
+
const riskDeltas = compareSnapshotRisks(snapshot, index, uniqueSorted([...reviewTargets, ...editPaths]));
|
|
114
|
+
const affectedEdges = affectedWorkflowGraphEdges(index, reviewTargets).slice(0, 20);
|
|
115
|
+
const affectedTests = uniqueSorted([
|
|
116
|
+
...testsFromGraphEdges(affectedEdges),
|
|
117
|
+
...index.testEdges.filter((edge) => edge.targetPath && reviewTargets.includes(edge.targetPath)).map((edge) => edge.path)
|
|
118
|
+
]);
|
|
119
|
+
const reviewScope = reviewTargets.length > 0 ? reviewTargets : currentDirtyPaths;
|
|
120
|
+
const snapshotTestScope = snapshot ? (snapshot.plannedEditTargets.length > 0 ? snapshot.plannedEditTargets : snapshot.plannedFiles) : [];
|
|
121
|
+
const freshReviewTests = recommendTests(index, reviewScope, repoRoot, changeType);
|
|
122
|
+
const freshReviewTestPaths = new Set([...(contextData.tests ?? []), ...freshReviewTests]
|
|
123
|
+
.filter((test) => test.provenance?.degraded !== true)
|
|
124
|
+
.map((test) => test.path));
|
|
125
|
+
const reconciledSnapshotTests = reconcileSnapshotTests(snapshot?.plannedTests ?? [], reviewScope, snapshotTestScope);
|
|
126
|
+
const degradedSnapshotTests = reconciledSnapshotTests.degraded.filter((test) => !freshReviewTestPaths.has(test.path));
|
|
127
|
+
const supersededDegradedSnapshotTests = reconciledSnapshotTests.degraded.filter((test) => freshReviewTestPaths.has(test.path));
|
|
128
|
+
const mergedTests = uniqueTests([
|
|
129
|
+
...reconciledSnapshotTests.trusted,
|
|
130
|
+
...(contextData.tests ?? []),
|
|
131
|
+
...freshReviewTests
|
|
132
|
+
]);
|
|
133
|
+
const tests = narrowTestRecommendationsByChangeType(mergedTests, reviewScope, changeType).slice(0, 12);
|
|
134
|
+
const ranTests = input.ranTests ?? [];
|
|
135
|
+
const ranCommands = input.ranCommands ?? [];
|
|
136
|
+
const manualRanCommandReports = (input.ranCommandReports ?? []).map(stripRunnerMetadata);
|
|
137
|
+
const runnerReview = await reviewTrustedRunnerReports(internal.trustedRunnerReports ?? [], {
|
|
138
|
+
freshness,
|
|
139
|
+
snapshot,
|
|
140
|
+
repoRoot
|
|
141
|
+
});
|
|
142
|
+
const ranCommandReports = [...manualRanCommandReports, ...runnerReview.coveringReports];
|
|
143
|
+
const displayedRanCommandReports = [...manualRanCommandReports, ...runnerReview.displayReports];
|
|
144
|
+
const waivedChecks = input.waivedChecks ?? [];
|
|
145
|
+
const waivers = input.waivers ?? [];
|
|
146
|
+
const preliminaryVerificationCoverage = verificationEvidenceForCommandReports(index, ranCommands, ranCommandReports, repoRoot).coverage;
|
|
147
|
+
const hasActualEditedFiles = editPaths.length > 0;
|
|
148
|
+
const riskEscalations = reviewTargets
|
|
149
|
+
.map((filePath) => findFile(index, filePath))
|
|
150
|
+
.filter((file) => Boolean(file))
|
|
151
|
+
.filter((file) => file.riskScore >= 4 || unplannedEditedFiles.includes(file.path))
|
|
152
|
+
.sort((a, b) => b.riskScore - a.riskScore || b.rank - a.rank || a.path.localeCompare(b.path))
|
|
153
|
+
.slice(0, 10);
|
|
154
|
+
const workflows = index.workflows
|
|
155
|
+
.filter((workflow) => reviewTargets.some((filePath) => workflow.relatedFiles.includes(filePath) || workflow.entryPath === filePath))
|
|
156
|
+
.sort((a, b) => b.rank - a.rank || a.title.localeCompare(b.title))
|
|
157
|
+
.slice(0, 6);
|
|
158
|
+
const workflowChecks = evaluateRequiredChecks(snapshot?.requiredWorkflowChecks ?? [], {
|
|
159
|
+
editPaths,
|
|
160
|
+
reviewTargets,
|
|
161
|
+
selectedFiles,
|
|
162
|
+
workflows,
|
|
163
|
+
affectedEdges,
|
|
164
|
+
affectedTests,
|
|
165
|
+
tests,
|
|
166
|
+
ranTests,
|
|
167
|
+
verificationCoverage: preliminaryVerificationCoverage
|
|
168
|
+
});
|
|
169
|
+
const dependencyChecks = evaluateRequiredChecks(snapshot?.requiredDependencyChecks ?? [], {
|
|
170
|
+
editPaths,
|
|
171
|
+
reviewTargets,
|
|
172
|
+
selectedFiles,
|
|
173
|
+
workflows,
|
|
174
|
+
affectedEdges,
|
|
175
|
+
affectedTests,
|
|
176
|
+
tests,
|
|
177
|
+
ranTests,
|
|
178
|
+
verificationCoverage: preliminaryVerificationCoverage
|
|
179
|
+
});
|
|
180
|
+
const verification = verificationLedgerForPostEdit({
|
|
181
|
+
index,
|
|
182
|
+
tests,
|
|
183
|
+
ranTests,
|
|
184
|
+
ranCommands,
|
|
185
|
+
ranCommandReports,
|
|
186
|
+
waivedChecks,
|
|
187
|
+
waivers,
|
|
188
|
+
repoRoot,
|
|
189
|
+
workflowChecks,
|
|
190
|
+
dependencyChecks
|
|
191
|
+
});
|
|
192
|
+
const verificationCoverage = verification.coverage;
|
|
193
|
+
const commandEnvelopes = verification.commandEnvelopes;
|
|
194
|
+
const verificationLedger = verification.ledger;
|
|
195
|
+
const testsNotRun = verification.testsNotRun;
|
|
196
|
+
const waivedVerification = verificationLedger.filter((entry) => entry.status === "waived");
|
|
197
|
+
const dataRanCommandReports = displayedRanCommandReports.map((report) => sanitizeCommandReportForDisplay(report, repoRoot));
|
|
198
|
+
const dataRanCommands = ranCommands.map((command) => sanitizeCommandText(command, repoRoot));
|
|
199
|
+
const dataCommandEnvelopes = commandEnvelopes.map((envelope) => sanitizeCommandEnvelopeForDisplay(envelope, repoRoot));
|
|
200
|
+
const dataVerificationCoverage = verificationCoverage.map((entry) => sanitizeCoverageForDisplay(entry, repoRoot));
|
|
201
|
+
const dataVerificationLedger = verificationLedger.map((entry) => sanitizeLedgerForDisplay(entry, repoRoot));
|
|
202
|
+
const dataWaivedVerification = dataVerificationLedger.filter((entry) => entry.status === "waived");
|
|
203
|
+
const missedLikelyTests = testsNotRun;
|
|
204
|
+
const autoVerifyCandidates = buildAutoVerifyCandidates({
|
|
205
|
+
snapshot,
|
|
206
|
+
testsNotRun,
|
|
207
|
+
reviewTargets,
|
|
208
|
+
repoRoot
|
|
209
|
+
});
|
|
210
|
+
const hasTestVerificationAccounting = verificationLedger.some((entry) => entry.kind === "test" && (entry.status === "covered" || entry.status === "waived"));
|
|
211
|
+
const hasCredibleVerificationEvidence = hasRelevantVerificationEvidence({
|
|
212
|
+
verificationLedger,
|
|
213
|
+
verificationCoverage,
|
|
214
|
+
ranTests,
|
|
215
|
+
tests,
|
|
216
|
+
workflowChecks,
|
|
217
|
+
dependencyChecks,
|
|
218
|
+
reviewTargets,
|
|
219
|
+
editPaths
|
|
220
|
+
});
|
|
221
|
+
const noVerificationProofForEditedFiles = hasActualEditedFiles && !hasCredibleVerificationEvidence && tests.length === 0 && workflowChecks.length === 0 && dependencyChecks.length === 0;
|
|
222
|
+
const decision = postEditDecision({
|
|
223
|
+
snapshot,
|
|
224
|
+
loadedSnapshot,
|
|
225
|
+
snapshotAmbiguity,
|
|
226
|
+
worktreeDegradationReasons: session.worktreeDegradationReasons,
|
|
227
|
+
headChanged,
|
|
228
|
+
unplannedEditedFiles,
|
|
229
|
+
unplannedChangedSymbols,
|
|
230
|
+
unindexedEditedFiles,
|
|
231
|
+
symbolDeltas,
|
|
232
|
+
riskDeltas,
|
|
233
|
+
workflowChecks,
|
|
234
|
+
dependencyChecks,
|
|
235
|
+
degradedSnapshotTests,
|
|
236
|
+
quality: contextData.quality,
|
|
237
|
+
riskEscalations,
|
|
238
|
+
waivedVerification,
|
|
239
|
+
hasActualEditedFiles,
|
|
240
|
+
testsNotRun,
|
|
241
|
+
hasTestVerificationAccounting,
|
|
242
|
+
noVerificationProofForEditedFiles
|
|
243
|
+
});
|
|
244
|
+
const { driftReasons, verdict, missingWorkflowCheckCount, missingDependencyCheckCount, riskEscalationsCoveredByVerification, riskEscalationsNeedInspection } = decision;
|
|
245
|
+
const { inspectMode, inspectReasons, completionAuthority } = decision;
|
|
246
|
+
const nextActions = postEditNextActions(verdict, {
|
|
247
|
+
snapshot,
|
|
248
|
+
unplannedEditedFiles,
|
|
249
|
+
testsNotRun,
|
|
250
|
+
riskEscalations,
|
|
251
|
+
reviewTargets,
|
|
252
|
+
workflows,
|
|
253
|
+
missingChecks: [...workflowChecks, ...dependencyChecks].filter((check) => check.status === "missing"),
|
|
254
|
+
noVerificationProofForEditedFiles,
|
|
255
|
+
degradedSnapshotTests
|
|
256
|
+
});
|
|
257
|
+
const structuredNextTools = postEditStructuredNextTools(verdict, {
|
|
258
|
+
reviewScope,
|
|
259
|
+
changeType,
|
|
260
|
+
testsNotRun,
|
|
261
|
+
degradedSnapshotTests,
|
|
262
|
+
riskEscalationsNeedInspection,
|
|
263
|
+
riskEscalations
|
|
264
|
+
});
|
|
265
|
+
const quality = contextData.quality;
|
|
266
|
+
const sessionMemoryPointer = priorSessionMemory
|
|
267
|
+
? {
|
|
268
|
+
sessionId: priorSessionMemory.sessionId,
|
|
269
|
+
revision: priorSessionMemory.revision,
|
|
270
|
+
entryIds: priorSessionMemory.memory.entries.map((entry) => entry.id).slice(0, 20),
|
|
271
|
+
summaryHash: stableSessionMemoryHash(priorSessionMemory.memory.entries.map((entry) => entry.summary).join("\n"))
|
|
272
|
+
}
|
|
273
|
+
: undefined;
|
|
274
|
+
const outcomeInput = {
|
|
275
|
+
repoRoot,
|
|
276
|
+
task,
|
|
277
|
+
taskId: effectiveTaskId,
|
|
278
|
+
snapshotPath: loadedSnapshot.path ? path.relative(repoRoot, loadedSnapshot.path).split(path.sep).join("/") : undefined,
|
|
279
|
+
verdict,
|
|
280
|
+
inspectMode,
|
|
281
|
+
inspectReasons,
|
|
282
|
+
completionAuthority,
|
|
283
|
+
freshness,
|
|
284
|
+
changedFiles: editPaths,
|
|
285
|
+
plannedEditTargets: plannedScope,
|
|
286
|
+
reviewTargets,
|
|
287
|
+
unplannedEditedFiles,
|
|
288
|
+
unindexedEditedFiles,
|
|
289
|
+
modifiedSymbols,
|
|
290
|
+
modifiedPublicSymbols,
|
|
291
|
+
affectedWorkflows: workflows.map((workflow) => workflow.title),
|
|
292
|
+
workflowChecks,
|
|
293
|
+
dependencyChecks,
|
|
294
|
+
driftReasons,
|
|
295
|
+
tests,
|
|
296
|
+
degradedSnapshotTests,
|
|
297
|
+
testsNotRun,
|
|
298
|
+
missedLikelyTests,
|
|
299
|
+
ranTests,
|
|
300
|
+
ranCommands,
|
|
301
|
+
ranCommandReports: displayedRanCommandReports,
|
|
302
|
+
commandEnvelopes,
|
|
303
|
+
waivedChecks,
|
|
304
|
+
waivers,
|
|
305
|
+
verificationCoverage,
|
|
306
|
+
verificationLedger,
|
|
307
|
+
verificationProvenance: CURRENT_VERIFICATION_PROVENANCE,
|
|
308
|
+
sessionMemory: sessionMemoryPointer,
|
|
309
|
+
riskDeltas: riskDeltas.map((delta) => ({
|
|
310
|
+
path: delta.path,
|
|
311
|
+
beforeRisk: delta.before.riskScore,
|
|
312
|
+
afterRisk: delta.after.riskScore,
|
|
313
|
+
delta: delta.delta
|
|
314
|
+
})),
|
|
315
|
+
quality,
|
|
316
|
+
confidence: quality?.counts
|
|
317
|
+
};
|
|
318
|
+
const persistOutcome = input.persistOutcome ?? true;
|
|
319
|
+
const savedOutcome = persistOutcome ? await savePostEditOutcome(outcomeInput) : undefined;
|
|
320
|
+
const outcome = savedOutcome?.outcome ?? buildPostEditOutcome(outcomeInput);
|
|
321
|
+
const outcomePath = savedOutcome?.relativePath;
|
|
322
|
+
const text = [
|
|
323
|
+
freshnessBanner(freshness, refresh),
|
|
324
|
+
quality ? formatContextQuality(quality) : undefined,
|
|
325
|
+
"Codexa post-edit review",
|
|
326
|
+
"Review gate: first-class post-edit review; reconcile snapshot, dirty diff, semantic context, and verification before finalizing.",
|
|
327
|
+
`Task: ${task}`,
|
|
328
|
+
snapshot ? `Snapshot: ${snapshot.taskId} (${snapshot.createdAt})` : `Snapshot: unavailable${loadedSnapshot.missingReason ? ` (${loadedSnapshot.missingReason})` : ""}; using current dirty tree only`,
|
|
329
|
+
`Verdict: ${verdict}`,
|
|
330
|
+
`Inspect classification: ${inspectMode}; authority ${completionAuthority}`,
|
|
331
|
+
semanticReviewContext ? formatPostEditSemanticReviewContext(semanticReviewContext) : undefined,
|
|
332
|
+
`Outcome record: ${outcomePath ?? "not persisted"}`,
|
|
333
|
+
"",
|
|
334
|
+
"Changed since snapshot:",
|
|
335
|
+
...(changedSinceSnapshot.length > 0 ? changedSinceSnapshot.slice(0, 30).map(formatChangedEntry) : ["- none detected"]),
|
|
336
|
+
"",
|
|
337
|
+
"Changed files grouped by module:",
|
|
338
|
+
...formatDiffGroups(changedGroups.slice(0, 16)),
|
|
339
|
+
resolvedBaselineFiles.length > 0 ? "" : undefined,
|
|
340
|
+
resolvedBaselineFiles.length > 0 ? "Baseline dirty files now clean or absent:" : undefined,
|
|
341
|
+
...resolvedBaselineFiles.slice(0, 20).map((filePath) => `- ${filePath}`),
|
|
342
|
+
"",
|
|
343
|
+
"Plan drift:",
|
|
344
|
+
snapshot ? `- Planned edit targets: ${plannedScope.slice(0, 20).join(", ") || "none"}` : "- Planned edit targets: unavailable",
|
|
345
|
+
`- Actual edited files since snapshot: ${editPaths.slice(0, 30).join(", ") || "none"}`,
|
|
346
|
+
unplannedEditedFiles.length > 0 ? `- Unplanned edited files: ${unplannedEditedFiles.join(", ")}` : "- No unplanned edits detected against the saved planned scope.",
|
|
347
|
+
plannedRenames.length > 0 ? `- Planned renames: ${plannedRenames.map((entry) => `${entry.oldPath} -> ${entry.path}`).join(", ")}` : undefined,
|
|
348
|
+
unplannedChangedSymbols.length > 0
|
|
349
|
+
? `- Changed symbols outside requested target: ${unplannedChangedSymbols.slice(0, 12).map((entry) => entry.symbol.qualifiedName).join(", ")}`
|
|
350
|
+
: requestedSymbolNames.size > 0
|
|
351
|
+
? "- No changed symbols outside the requested symbol target detected."
|
|
352
|
+
: undefined,
|
|
353
|
+
snapshot && plannedButUntouchedFiles.length > 0 ? `- Planned targets not touched yet: ${plannedButUntouchedFiles.slice(0, 12).join(", ")}` : undefined,
|
|
354
|
+
headChanged ? `- Snapshot commit ${snapshot?.dirtyBaseline.headCommit ?? "none"} differs from current ${freshness.headCommit ?? "none"}` : undefined,
|
|
355
|
+
"",
|
|
356
|
+
"Symbol delta:",
|
|
357
|
+
...formatSymbolDeltas(symbolDeltas),
|
|
358
|
+
"Modified symbols:",
|
|
359
|
+
...formatModifiedSymbols(modifiedSymbols, modifiedPublicSymbols),
|
|
360
|
+
"",
|
|
361
|
+
"Risk deltas:",
|
|
362
|
+
...formatRiskDeltas(riskDeltas),
|
|
363
|
+
"",
|
|
364
|
+
"Risk and workflow signals:",
|
|
365
|
+
...(riskEscalations.length > 0 ? riskEscalations.map((file) => `- ${file.path}: risk ${file.riskScore.toFixed(1)}, rank ${file.rank.toFixed(2)}`) : ["- none above threshold"]),
|
|
366
|
+
...workflows.slice(0, 4).map((workflow) => `- workflow ${workflow.title}: ${workflow.confidence}; ${workflow.relatedFiles.slice(0, 5).join(", ")}`),
|
|
367
|
+
...affectedEdges.slice(0, 10).map((edge) => `- edge ${edge.edgeKind}: ${edge.fromPath ?? edge.fromId} -> ${edge.toPath ?? edge.toId}; ${edge.confidence}; ${edge.reason}`),
|
|
368
|
+
affectedTests.length > 0 ? `- Affected tests/workflows: ${affectedTests.slice(0, 10).join(", ")}` : "- Affected tests/workflows: none proven from typed graph edges",
|
|
369
|
+
priorSessionMemory && priorSessionMemory.memory.entries.length > 0 ? "" : undefined,
|
|
370
|
+
priorSessionMemory && priorSessionMemory.memory.entries.length > 0 ? "Session memory:" : undefined,
|
|
371
|
+
...(priorSessionMemory?.memory.entries.slice(0, 8).map((entry) => `- ${entry.kind}: ${entry.summary} (${entry.evidenceTier}/${entry.confidence}; ${entry.status})`) ?? []),
|
|
372
|
+
"",
|
|
373
|
+
"Required workflow checks:",
|
|
374
|
+
...formatCheckResults(workflowChecks),
|
|
375
|
+
"",
|
|
376
|
+
"Required dependency checks:",
|
|
377
|
+
...formatCheckResults(dependencyChecks),
|
|
378
|
+
"",
|
|
379
|
+
"Recommended tests:",
|
|
380
|
+
...formatTestRecommendations(tests),
|
|
381
|
+
degradedSnapshotTests.length > 0 ? "" : undefined,
|
|
382
|
+
degradedSnapshotTests.length > 0 ? "Degraded planned snapshot tests:" : undefined,
|
|
383
|
+
...degradedSnapshotTests.map((test) => `- ${test.path}: ${test.provenance?.degradedReason ?? "provenance does not match current review scope"}`),
|
|
384
|
+
ranTests.length > 0 ? `Reported ran tests: ${ranTests.join(", ")}` : "Reported ran tests: none",
|
|
385
|
+
dataRanCommands.length > 0 ? `Reported ran commands: ${dataRanCommands.join(" | ")}` : "Reported ran commands: none",
|
|
386
|
+
dataRanCommandReports.length > 0 ? `Reported command reports: ${dataRanCommandReports.map(formatCommandReport).join(" | ")}` : "Reported command reports: none",
|
|
387
|
+
runnerReview.reviewEntries.length > 0 ? `AutoVerify runner evidence: ${runnerReview.reviewEntries.map(formatRunnerReviewEntry).join(" | ")}` : undefined,
|
|
388
|
+
dataCommandEnvelopes.length > 0 ? `Command envelopes: ${dataCommandEnvelopes.map(formatCommandEnvelope).join(" | ")}` : "Command envelopes: none",
|
|
389
|
+
waivedChecks.length > 0 ? `Explicit waivers: ${waivedChecks.join(" | ")}` : "Explicit waivers: none",
|
|
390
|
+
waivers.length > 0 ? `Structured waivers: ${waivers.map((waiver) => `${waiver.kind}:${waiver.target} (${waiver.reason})`).join(" | ")}` : "Structured waivers: none",
|
|
391
|
+
"",
|
|
392
|
+
"Verification coverage inferred from commands:",
|
|
393
|
+
...formatVerificationCoverage(dataVerificationCoverage),
|
|
394
|
+
"",
|
|
395
|
+
"Verification ledger:",
|
|
396
|
+
...formatVerificationLedger(dataVerificationLedger),
|
|
397
|
+
missedLikelyTests.length > 0 ? `Tests still unaccounted for: ${missedLikelyTests.slice(0, 8).map((test) => test.path).join(", ")}` : "Tests still unaccounted for: none",
|
|
398
|
+
"",
|
|
399
|
+
"Drift reasons:",
|
|
400
|
+
...(driftReasons.length > 0 ? driftReasons.map((reason) => `- ${reason}`) : ["- none"]),
|
|
401
|
+
inspectReasons.length > 0 ? "" : undefined,
|
|
402
|
+
inspectReasons.length > 0 ? "Inspect reasons:" : undefined,
|
|
403
|
+
...inspectReasons.map((reason) => `- ${reason}`),
|
|
404
|
+
"",
|
|
405
|
+
"Next actions:",
|
|
406
|
+
...nextActions.map((action) => `- ${action}`),
|
|
407
|
+
"",
|
|
408
|
+
"Known gaps:",
|
|
409
|
+
...formatGaps(uniqueSorted([...(contextData.gaps ?? []), ...indexGaps(index, freshness, unindexedEditedFiles)]))
|
|
410
|
+
]
|
|
411
|
+
.filter((line) => line !== undefined)
|
|
412
|
+
.join("\n");
|
|
413
|
+
return {
|
|
414
|
+
freshness,
|
|
415
|
+
refresh,
|
|
416
|
+
text: fitLinesToTokenBudget(text.split(/\r?\n/), tokenBudget),
|
|
417
|
+
data: {
|
|
418
|
+
mode: "post_edit_review",
|
|
419
|
+
task,
|
|
420
|
+
verdict,
|
|
421
|
+
inspectMode,
|
|
422
|
+
inspectReasons,
|
|
423
|
+
completionAuthority,
|
|
424
|
+
snapshot: compactSnapshotForData(snapshot),
|
|
425
|
+
snapshotLoad: {
|
|
426
|
+
taskId: loadedSnapshot.latestTaskId,
|
|
427
|
+
path: loadedSnapshot.path,
|
|
428
|
+
missingReason: loadedSnapshot.missingReason,
|
|
429
|
+
error: loadedSnapshot.error,
|
|
430
|
+
recoveredLatest: loadedSnapshot.recoveredLatest,
|
|
431
|
+
ambiguousLatest: Boolean(snapshotAmbiguity),
|
|
432
|
+
ambiguityReason: snapshotAmbiguity
|
|
433
|
+
},
|
|
434
|
+
files: selectedFiles,
|
|
435
|
+
reviewTargets,
|
|
436
|
+
changedSinceSnapshot: limitArray(changedSinceSnapshot, 40),
|
|
437
|
+
changedGroups: limitArray(changedGroups, 20),
|
|
438
|
+
resolvedBaselineFiles: limitArray(resolvedBaselineFiles, 30),
|
|
439
|
+
unplannedEditedFiles,
|
|
440
|
+
worktree: {
|
|
441
|
+
knownClean: session.worktreeDegradationReasons.length === 0 && currentDirtyPaths.length === 0,
|
|
442
|
+
degraded: session.worktreeDegradationReasons.length > 0,
|
|
443
|
+
dirtyFileCount: currentDirtyPaths.length,
|
|
444
|
+
symbolCount: changedSymbols.length,
|
|
445
|
+
degradedReasons: session.worktreeDegradationReasons
|
|
446
|
+
},
|
|
447
|
+
worktreeDegradationReasons: session.worktreeDegradationReasons,
|
|
448
|
+
plannedRenames: limitArray(plannedRenames, 20),
|
|
449
|
+
unplannedChangedSymbols: limitArray(unplannedChangedSymbols, 20),
|
|
450
|
+
plannedButUntouchedFiles: limitArray(plannedButUntouchedFiles, 30),
|
|
451
|
+
headChanged,
|
|
452
|
+
symbolDeltas: limitArray(symbolDeltas, 20),
|
|
453
|
+
modifiedSymbols: limitArray(modifiedSymbols, 40),
|
|
454
|
+
modifiedPublicSymbols: limitArray(modifiedPublicSymbols, 40),
|
|
455
|
+
riskDeltas: limitArray(riskDeltas, 20),
|
|
456
|
+
affectedEdges: limitArray(affectedEdges, 30),
|
|
457
|
+
affectedTests: limitArray(affectedTests, 30),
|
|
458
|
+
tests: limitArray(tests, 30),
|
|
459
|
+
degradedSnapshotTests: limitArray(degradedSnapshotTests, 30),
|
|
460
|
+
supersededDegradedSnapshotTests: limitArray(supersededDegradedSnapshotTests, 30),
|
|
461
|
+
testsNotRun: limitArray(testsNotRun, 30),
|
|
462
|
+
missedLikelyTests: limitArray(missedLikelyTests, 30),
|
|
463
|
+
ranTests,
|
|
464
|
+
ranCommands: dataRanCommands,
|
|
465
|
+
ranCommandReports: dataRanCommandReports,
|
|
466
|
+
autoVerifyCandidates: limitArray(autoVerifyCandidates, 30),
|
|
467
|
+
autoVerifyRunnerEvidence: runnerReview.reviewEntries.map((entry) => ({
|
|
468
|
+
command: sanitizeCommandText(entry.command, repoRoot),
|
|
469
|
+
covering: entry.covering,
|
|
470
|
+
reason: sanitizeSummary(entry.reason, repoRoot) ?? entry.reason,
|
|
471
|
+
policyId: entry.policyId,
|
|
472
|
+
sourceMutationDetected: entry.sourceMutationDetected,
|
|
473
|
+
timedOut: entry.timedOut
|
|
474
|
+
})),
|
|
475
|
+
commandEnvelopes: dataCommandEnvelopes,
|
|
476
|
+
waivedChecks,
|
|
477
|
+
waivers,
|
|
478
|
+
verificationCoverage: limitArray(dataVerificationCoverage, 40),
|
|
479
|
+
verificationLedger: limitArray(dataVerificationLedger, 60),
|
|
480
|
+
verificationProvenance: CURRENT_VERIFICATION_PROVENANCE,
|
|
481
|
+
sessionMemory: sessionMemoryPointer,
|
|
482
|
+
priorSessionMemory: priorSessionMemory
|
|
483
|
+
? {
|
|
484
|
+
sessionId: priorSessionMemory.sessionId,
|
|
485
|
+
revision: priorSessionMemory.revision,
|
|
486
|
+
entries: priorSessionMemory.memory.entries.slice(0, 8),
|
|
487
|
+
warnings: priorSessionMemory.warnings
|
|
488
|
+
}
|
|
489
|
+
: undefined,
|
|
490
|
+
waivedVerification: limitArray(dataWaivedVerification, 30),
|
|
491
|
+
unindexedEditedFiles,
|
|
492
|
+
riskEscalations: limitArray(riskEscalations, 20),
|
|
493
|
+
riskEscalationsCoveredByVerification,
|
|
494
|
+
riskEscalationsNeedInspection,
|
|
495
|
+
workflows: limitArray(workflows, 12),
|
|
496
|
+
workflowChecks: limitArray(workflowChecks, 20),
|
|
497
|
+
dependencyChecks: limitArray(dependencyChecks, 30),
|
|
498
|
+
context: compactContextData(context.data),
|
|
499
|
+
quality,
|
|
500
|
+
semanticReviewContext,
|
|
501
|
+
driftReasons,
|
|
502
|
+
nextActions,
|
|
503
|
+
nextTools: structuredNextTools,
|
|
504
|
+
systemMessage: structuredNextTools[0]?.reason,
|
|
505
|
+
outcome: {
|
|
506
|
+
...outcome,
|
|
507
|
+
persisted: Boolean(savedOutcome),
|
|
508
|
+
path: outcomePath
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function formatPostEditSemanticReviewContext(summary) {
|
|
514
|
+
if (summary.status === "ok") {
|
|
515
|
+
return `Semantic review context: ok (${summary.provider ?? "provider"} ${summary.model ?? "model"}; ${summary.chunkCount ?? 0} chunks)`;
|
|
516
|
+
}
|
|
517
|
+
if (summary.status === "unavailable") {
|
|
518
|
+
return `Semantic review context: unavailable${summary.diagnostics.length > 0 ? ` (${summary.diagnostics.join("; ")})` : ""}`;
|
|
519
|
+
}
|
|
520
|
+
return "Semantic review context: disabled";
|
|
521
|
+
}
|
|
522
|
+
function compareSnapshotSymbols(snapshot, index, paths) {
|
|
523
|
+
if (!snapshot?.symbolBaseline) {
|
|
524
|
+
return [];
|
|
525
|
+
}
|
|
526
|
+
return uniqueSorted(paths)
|
|
527
|
+
.map((filePath) => {
|
|
528
|
+
const before = snapshot.symbolBaseline?.[filePath] ?? [];
|
|
529
|
+
const after = snapshotSymbolBaseline(index, [filePath])[filePath] ?? [];
|
|
530
|
+
const beforeKeys = new Set(before.map(symbolDeltaKey));
|
|
531
|
+
const afterKeys = new Set(after.map(symbolDeltaKey));
|
|
532
|
+
return {
|
|
533
|
+
path: filePath,
|
|
534
|
+
newSymbols: after.filter((symbol) => !beforeKeys.has(symbolDeltaKey(symbol))),
|
|
535
|
+
removedSymbols: before.filter((symbol) => !afterKeys.has(symbolDeltaKey(symbol)))
|
|
536
|
+
};
|
|
537
|
+
})
|
|
538
|
+
.filter((delta) => delta.newSymbols.length > 0 || delta.removedSymbols.length > 0);
|
|
539
|
+
}
|
|
540
|
+
function requestedSymbolIds(snapshot, requestedSymbols) {
|
|
541
|
+
const ids = new Set();
|
|
542
|
+
if (!snapshot?.symbolBaseline || requestedSymbols.size === 0) {
|
|
543
|
+
return ids;
|
|
544
|
+
}
|
|
545
|
+
for (const symbols of Object.values(snapshot.symbolBaseline)) {
|
|
546
|
+
for (const symbol of symbols) {
|
|
547
|
+
if (requestedSymbols.has(normalizeSearchText(symbol.id)) ||
|
|
548
|
+
requestedSymbols.has(normalizeSearchText(symbol.name)) ||
|
|
549
|
+
requestedSymbols.has(normalizeSearchText(symbol.qualifiedName))) {
|
|
550
|
+
ids.add(symbol.id);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return ids;
|
|
555
|
+
}
|
|
556
|
+
async function latestSnapshotAmbiguity(repoRoot, latestTaskId) {
|
|
557
|
+
const dir = path.join(repoRoot, ".codex/cache/codexa-tasks");
|
|
558
|
+
try {
|
|
559
|
+
const entries = await fs.readdir(dir);
|
|
560
|
+
const taskSnapshots = entries.filter((entry) => entry.endsWith(".json") && entry !== "latest.json" && !entry.endsWith(".blocked.json"));
|
|
561
|
+
const otherSnapshots = taskSnapshots.filter((entry) => entry !== `${latestTaskId}.json`);
|
|
562
|
+
if (otherSnapshots.length === 0) {
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
return `post_edit_review used latest snapshot ${latestTaskId} without an explicit taskId while ${otherSnapshots.length} other snapshot(s) exist; pass taskId to bind review to the intended plan`;
|
|
566
|
+
}
|
|
567
|
+
catch {
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
function plannedScopeContainsSymbolFile(snapshot, filePath) {
|
|
572
|
+
if (!snapshot) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
const planned = snapshot.plannedEditTargets.length > 0 ? snapshot.plannedEditTargets : snapshot.plannedFiles;
|
|
576
|
+
return planned.includes(filePath);
|
|
577
|
+
}
|
|
578
|
+
function compareSnapshotRisks(snapshot, index, paths) {
|
|
579
|
+
if (!snapshot?.riskBaseline) {
|
|
580
|
+
return [];
|
|
581
|
+
}
|
|
582
|
+
return uniqueSorted(paths)
|
|
583
|
+
.map((filePath) => {
|
|
584
|
+
const before = snapshot.riskBaseline?.[filePath] ?? { riskScore: 0, signals: [] };
|
|
585
|
+
const after = snapshotRiskBaseline(index, [filePath])[filePath] ?? { riskScore: 0, signals: [] };
|
|
586
|
+
return {
|
|
587
|
+
path: filePath,
|
|
588
|
+
before,
|
|
589
|
+
after,
|
|
590
|
+
delta: after.riskScore - before.riskScore,
|
|
591
|
+
newSignals: multisetDifference(after.signals, before.signals),
|
|
592
|
+
removedSignals: multisetDifference(before.signals, after.signals)
|
|
593
|
+
};
|
|
594
|
+
})
|
|
595
|
+
.filter((delta) => Math.abs(delta.delta) > 0.01 || delta.newSignals.length > 0 || delta.removedSignals.length > 0)
|
|
596
|
+
.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta) || a.path.localeCompare(b.path));
|
|
597
|
+
}
|
|
598
|
+
function multisetDifference(values, baseline) {
|
|
599
|
+
const remaining = new Map();
|
|
600
|
+
for (const value of baseline) {
|
|
601
|
+
remaining.set(value, (remaining.get(value) ?? 0) + 1);
|
|
602
|
+
}
|
|
603
|
+
const result = [];
|
|
604
|
+
for (const value of values) {
|
|
605
|
+
const count = remaining.get(value) ?? 0;
|
|
606
|
+
if (count > 0) {
|
|
607
|
+
remaining.set(value, count - 1);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
result.push(value);
|
|
611
|
+
}
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
function symbolDeltaKey(symbol) {
|
|
615
|
+
return `${symbol.kind}\0${symbol.qualifiedName}\0${symbol.name}`;
|
|
616
|
+
}
|
|
617
|
+
function formatSymbolDeltas(deltas) {
|
|
618
|
+
if (deltas.length === 0) {
|
|
619
|
+
return ["- none detected or no symbol baseline available"];
|
|
620
|
+
}
|
|
621
|
+
return deltas.flatMap((delta) => [
|
|
622
|
+
`- ${delta.path}`,
|
|
623
|
+
...(delta.newSymbols.length > 0 ? [` new: ${delta.newSymbols.slice(0, 8).map((symbol) => `${symbol.qualifiedName} (${symbol.kind})`).join(", ")}`] : []),
|
|
624
|
+
...(delta.removedSymbols.length > 0 ? [` removed: ${delta.removedSymbols.slice(0, 8).map((symbol) => `${symbol.qualifiedName} (${symbol.kind})`).join(", ")}`] : [])
|
|
625
|
+
]);
|
|
626
|
+
}
|
|
627
|
+
function formatModifiedSymbols(modifiedSymbols, modifiedPublicSymbols) {
|
|
628
|
+
if (modifiedSymbols.length === 0) {
|
|
629
|
+
return ["- none detected from changed line ranges"];
|
|
630
|
+
}
|
|
631
|
+
const publicSuffix = modifiedPublicSymbols.length > 0 ? `; public/runtime ${modifiedPublicSymbols.slice(0, 8).join(", ")}` : "";
|
|
632
|
+
return [`- ${modifiedSymbols.slice(0, 12).join(", ")}${modifiedSymbols.length > 12 ? ", ..." : ""}${publicSuffix}`];
|
|
633
|
+
}
|
|
634
|
+
function evaluateRequiredChecks(checks, input) {
|
|
635
|
+
if (checks.length === 0) {
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
const editSet = new Set(input.editPaths);
|
|
639
|
+
const reviewSet = new Set(input.reviewTargets);
|
|
640
|
+
const selectedSet = new Set(input.selectedFiles);
|
|
641
|
+
const affectedTestSet = new Set(input.affectedTests);
|
|
642
|
+
const workflowTitles = new Set(input.workflows.map((workflow) => workflow.title));
|
|
643
|
+
const edgePaths = new Set(input.affectedEdges.flatMap((edge) => [edge.fromPath, edge.toPath]).filter((filePath) => Boolean(filePath)));
|
|
644
|
+
return checks.map((check) => {
|
|
645
|
+
const relevant = input.editPaths.length === 0 || check.paths.some((filePath) => editSet.has(filePath) || reviewSet.has(filePath));
|
|
646
|
+
const evidencePaths = check.paths.filter((filePath) => !editSet.has(filePath));
|
|
647
|
+
const hasNonEditedEvidence = evidencePaths.some((filePath) => edgePaths.has(filePath) || selectedSet.has(filePath) || affectedTestSet.has(filePath));
|
|
648
|
+
if (!relevant) {
|
|
649
|
+
return { ...check, status: "not_applicable" };
|
|
650
|
+
}
|
|
651
|
+
const covered = check.kind === "workflow"
|
|
652
|
+
? workflowTitles.has(check.target) && hasNonEditedEvidence
|
|
653
|
+
: hasNonEditedEvidence || dependencyCheckCoveredByVerification(check, input);
|
|
654
|
+
return { ...check, status: covered ? "covered" : "missing" };
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
function dependencyCheckCoveredByVerification(check, input) {
|
|
658
|
+
const checkPaths = check.paths.map(normalizePathLike);
|
|
659
|
+
const testPaths = check.paths.filter(isTestPath);
|
|
660
|
+
if (testPaths.some((testPath) => input.ranTests.some((ranTest) => normalizePathLike(ranTest) === normalizePathLike(testPath)) ||
|
|
661
|
+
input.verificationCoverage.some((coverage) => coverage.kind === (testPath.endsWith(".py") ? "python-tests" : "javascript-tests") && coverageCoversPath(coverage, testPath)))) {
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
const sourcePaths = check.paths.filter((filePath) => !isTestPath(filePath));
|
|
665
|
+
return input.verificationCoverage.some((coverage) => {
|
|
666
|
+
if (coverage.targetPath) {
|
|
667
|
+
return checkPaths.includes(normalizePathLike(coverage.targetPath)) && coverageKindCompatibleWithSourcePath(coverage.kind, coverage.targetPath);
|
|
668
|
+
}
|
|
669
|
+
return sourcePaths.some((filePath) => coverageKindCompatibleWithSourcePath(coverage.kind, filePath) && coverageCoversPath(coverage, filePath));
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
function coverageKindCompatibleWithSourcePath(kind, filePath) {
|
|
673
|
+
const normalized = normalizePathLike(filePath).toLowerCase();
|
|
674
|
+
if (normalized.endsWith(".py")) {
|
|
675
|
+
return kind === "python-tests";
|
|
676
|
+
}
|
|
677
|
+
if (/\.(?:cjs|cts|js|jsx|mjs|mts|ts|tsx)$/u.test(normalized)) {
|
|
678
|
+
return kind === "build" || kind === "typescript-syntax" || kind === "javascript-tests";
|
|
679
|
+
}
|
|
680
|
+
return kind === "build";
|
|
681
|
+
}
|
|
682
|
+
function coverageCoversPath(coverage, filePath) {
|
|
683
|
+
const normalizedPath = normalizePathLike(filePath);
|
|
684
|
+
if (coverage.targetPath) {
|
|
685
|
+
return normalizePathLike(coverage.targetPath) === normalizedPath;
|
|
686
|
+
}
|
|
687
|
+
const normalizedScope = normalizePathLike(coverage.scope ?? ".");
|
|
688
|
+
return normalizedScope === "." || normalizedPath === normalizedScope || normalizedPath.startsWith(`${normalizedScope}/`);
|
|
689
|
+
}
|
|
690
|
+
function normalizePathLike(value) {
|
|
691
|
+
return value.replace(/\\/gu, "/").replace(/^\.\//u, "").replace(/\/+/gu, "/");
|
|
692
|
+
}
|
|
693
|
+
function formatCheckResults(checks) {
|
|
694
|
+
if (checks.length === 0) {
|
|
695
|
+
return ["- none saved in the task snapshot"];
|
|
696
|
+
}
|
|
697
|
+
return checks.slice(0, 12).map((check) => `- ${check.status}: ${check.target}; ${check.confidence}; ${check.reason}`);
|
|
698
|
+
}
|
|
699
|
+
async function reviewTrustedRunnerReports(reports, ctx) {
|
|
700
|
+
const repoRealRoot = await fs.realpath(ctx.repoRoot).catch(() => path.resolve(ctx.repoRoot));
|
|
701
|
+
const currentDirtyHash = dirtyHashFromFreshness(ctx.freshness);
|
|
702
|
+
const snapshotDigest = ctx.snapshot ? autoVerifySnapshotDigest(ctx.snapshot) : undefined;
|
|
703
|
+
const coveringReports = [];
|
|
704
|
+
const displayReports = [];
|
|
705
|
+
const reviewEntries = [];
|
|
706
|
+
for (const report of reports) {
|
|
707
|
+
const reasons = runnerReportRejectionReasons(report, {
|
|
708
|
+
currentDirtyHash,
|
|
709
|
+
snapshotDigest,
|
|
710
|
+
taskId: ctx.snapshot?.taskId,
|
|
711
|
+
repoRealRoot
|
|
712
|
+
});
|
|
713
|
+
displayReports.push(report);
|
|
714
|
+
const covering = reasons.length === 0;
|
|
715
|
+
if (covering) {
|
|
716
|
+
coveringReports.push(report);
|
|
717
|
+
}
|
|
718
|
+
reviewEntries.push({
|
|
719
|
+
command: sanitizeCommandText(report.command, ctx.repoRoot),
|
|
720
|
+
covering,
|
|
721
|
+
reason: sanitizeSummary(covering ? "fresh trusted AutoVerify report" : reasons.join("; "), ctx.repoRoot) ?? "runner evidence unavailable",
|
|
722
|
+
policyId: report.runner?.policyId,
|
|
723
|
+
sourceMutationDetected: report.runner?.sourceMutationDetected,
|
|
724
|
+
timedOut: report.runner?.timedOut
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
return { coveringReports, displayReports, reviewEntries };
|
|
728
|
+
}
|
|
729
|
+
function runnerReportRejectionReasons(report, ctx) {
|
|
730
|
+
const runner = report.runner;
|
|
731
|
+
const reasons = [];
|
|
732
|
+
if (!isTrustedAutoVerifyCommandReport(report)) {
|
|
733
|
+
return ["missing internal AutoVerify trust marker"];
|
|
734
|
+
}
|
|
735
|
+
if (!runner || runner.schemaVersion !== 1 || runner.reportKind !== "codexa-autoverify-report" || runner.runnerName !== "codexa") {
|
|
736
|
+
return ["missing trusted AutoVerify runner metadata"];
|
|
737
|
+
}
|
|
738
|
+
if (runner.policyId !== AUTO_VERIFY_POLICY_ID)
|
|
739
|
+
reasons.push("unexpected runner policy");
|
|
740
|
+
if (runner.policyDigest !== AUTO_VERIFY_POLICY_DIGEST)
|
|
741
|
+
reasons.push("unexpected runner policy digest");
|
|
742
|
+
if (runner.envMode !== "minimal")
|
|
743
|
+
reasons.push("unexpected runner environment");
|
|
744
|
+
if (!runner.outputRedacted)
|
|
745
|
+
reasons.push("runner output was not redacted");
|
|
746
|
+
if (report.exitCode !== 0)
|
|
747
|
+
reasons.push(report.exitCode === undefined ? "missing exit code" : `exit code ${report.exitCode}`);
|
|
748
|
+
if (!report.cwd)
|
|
749
|
+
reasons.push("missing cwd");
|
|
750
|
+
if (runner.timedOut)
|
|
751
|
+
reasons.push("runner timed out");
|
|
752
|
+
if (runner.sourceMutationDetected)
|
|
753
|
+
reasons.push("source mutation detected");
|
|
754
|
+
if (runner.skippedReason)
|
|
755
|
+
reasons.push(runner.skippedReason);
|
|
756
|
+
if (ctx.taskId && runner.taskId !== ctx.taskId)
|
|
757
|
+
reasons.push("task id mismatch");
|
|
758
|
+
if (ctx.snapshotDigest && runner.snapshotDigest !== ctx.snapshotDigest)
|
|
759
|
+
reasons.push("snapshot digest mismatch");
|
|
760
|
+
if (runner.dirtyHashAfter !== ctx.currentDirtyHash)
|
|
761
|
+
reasons.push("stale dirty tree");
|
|
762
|
+
if (!absoluteSubpath(runner.cwdRealpath, ctx.repoRealRoot))
|
|
763
|
+
reasons.push("runner cwd outside repo");
|
|
764
|
+
if (runner.targetRealpaths.length === 0)
|
|
765
|
+
reasons.push("missing runner targets");
|
|
766
|
+
if (runner.targetRealpaths.some((target) => !absoluteSubpath(target, ctx.repoRealRoot)))
|
|
767
|
+
reasons.push("runner target outside repo");
|
|
768
|
+
if (runner.canonicalDigest !== runnerReportDigest(report, runner))
|
|
769
|
+
reasons.push("runner digest mismatch");
|
|
770
|
+
return reasons;
|
|
771
|
+
}
|
|
772
|
+
function runnerReportDigest(report, runner) {
|
|
773
|
+
return stableId("codexa-autoverify-report", report.command, report.exitCode, runner.policyId, runner.policyDigest, runner.taskId, runner.snapshotDigest, runner.commandId, runner.candidateDigest, runner.headCommit ?? "null", runner.dirtyHashBefore, runner.dirtyHashAfter, runner.cwdRealpath, JSON.stringify(runner.targetRealpaths), runner.envMode, JSON.stringify(runner.allowedBy), runner.sourceMutationDetected ? "mutated" : "clean", runner.timedOut ? "timed-out" : "not-timed-out", runner.outputRedacted ? "redacted" : "not-redacted", runner.signal ?? "", runner.skippedReason ?? "");
|
|
774
|
+
}
|
|
775
|
+
function stripRunnerMetadata(report) {
|
|
776
|
+
return {
|
|
777
|
+
command: report.command,
|
|
778
|
+
cwd: report.cwd,
|
|
779
|
+
packageManager: report.packageManager,
|
|
780
|
+
workspace: report.workspace,
|
|
781
|
+
packageRoot: report.packageRoot,
|
|
782
|
+
packageName: report.packageName,
|
|
783
|
+
scriptName: report.scriptName,
|
|
784
|
+
args: report.args,
|
|
785
|
+
exitCode: report.exitCode,
|
|
786
|
+
durationMs: report.durationMs,
|
|
787
|
+
stdoutSummary: report.stdoutSummary,
|
|
788
|
+
stderrSummary: report.stderrSummary,
|
|
789
|
+
outputSummary: report.outputSummary
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
function dirtyHashFromFreshness(freshness) {
|
|
793
|
+
return stableId("autoverify-dirty-tree", freshness.headCommit ?? "null", JSON.stringify({
|
|
794
|
+
dirtyFiles: [...freshness.dirtyFiles].sort(),
|
|
795
|
+
dirtyFileHashes: Object.fromEntries(Object.entries(freshness.dirtyFileHashes).sort(([a], [b]) => a.localeCompare(b)))
|
|
796
|
+
}));
|
|
797
|
+
}
|
|
798
|
+
function autoVerifySnapshotDigest(snapshot) {
|
|
799
|
+
return stableId("autoverify-snapshot", snapshot.taskId, snapshot.createdAt, JSON.stringify(snapshot.plannedEditTargets), JSON.stringify(snapshot.plannedTests.map((test) => test.path)));
|
|
800
|
+
}
|
|
801
|
+
function absoluteSubpath(candidate, parent) {
|
|
802
|
+
const relative = path.relative(path.resolve(parent), path.resolve(candidate));
|
|
803
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
804
|
+
}
|
|
805
|
+
function sanitizeCommandReportForDisplay(report, repoRoot) {
|
|
806
|
+
return {
|
|
807
|
+
...report,
|
|
808
|
+
command: sanitizeCommandText(report.command, repoRoot),
|
|
809
|
+
cwd: sanitizePathField(report.cwd, repoRoot),
|
|
810
|
+
packageManager: sanitizeSummary(report.packageManager, repoRoot),
|
|
811
|
+
workspace: sanitizeSummary(report.workspace, repoRoot),
|
|
812
|
+
packageRoot: sanitizePathField(report.packageRoot, repoRoot),
|
|
813
|
+
packageName: sanitizeSummary(report.packageName, repoRoot),
|
|
814
|
+
scriptName: sanitizeSummary(report.scriptName, repoRoot),
|
|
815
|
+
stdoutSummary: sanitizeSummary(report.stdoutSummary, repoRoot),
|
|
816
|
+
stderrSummary: sanitizeSummary(report.stderrSummary, repoRoot),
|
|
817
|
+
outputSummary: sanitizeSummary(report.outputSummary, repoRoot),
|
|
818
|
+
args: sanitizeCommandArgs(report.args, repoRoot),
|
|
819
|
+
runner: sanitizeRunnerForDisplay(report.runner, repoRoot)
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function sanitizeRunnerForDisplay(runner, repoRoot) {
|
|
823
|
+
if (!runner) {
|
|
824
|
+
return undefined;
|
|
825
|
+
}
|
|
826
|
+
return {
|
|
827
|
+
...runner,
|
|
828
|
+
cwdRealpath: sanitizePathField(runner.cwdRealpath, repoRoot) ?? runner.cwdRealpath,
|
|
829
|
+
targetRealpaths: runner.targetRealpaths.map((target) => sanitizePathField(target, repoRoot) ?? target),
|
|
830
|
+
allowedBy: runner.allowedBy.map((reason) => sanitizeSummary(reason, repoRoot) ?? reason),
|
|
831
|
+
skippedReason: sanitizeSummary(runner.skippedReason, repoRoot)
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
function sanitizeCommandEnvelopeForDisplay(envelope, repoRoot) {
|
|
835
|
+
return {
|
|
836
|
+
...envelope,
|
|
837
|
+
command: sanitizeCommandText(envelope.command, repoRoot),
|
|
838
|
+
cwd: sanitizePathField(envelope.cwd, repoRoot),
|
|
839
|
+
packageManager: sanitizeSummary(envelope.packageManager, repoRoot),
|
|
840
|
+
workspace: sanitizeSummary(envelope.workspace, repoRoot),
|
|
841
|
+
packageRoot: sanitizePathField(envelope.packageRoot, repoRoot),
|
|
842
|
+
packageName: sanitizeSummary(envelope.packageName, repoRoot),
|
|
843
|
+
scriptName: sanitizeSummary(envelope.scriptName, repoRoot),
|
|
844
|
+
stdoutSummary: sanitizeSummary(envelope.stdoutSummary, repoRoot),
|
|
845
|
+
stderrSummary: sanitizeSummary(envelope.stderrSummary, repoRoot),
|
|
846
|
+
outputSummary: sanitizeSummary(envelope.outputSummary, repoRoot),
|
|
847
|
+
args: sanitizeCommandArgs(envelope.args, repoRoot) ?? []
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function sanitizeCoverageForDisplay(entry, repoRoot) {
|
|
851
|
+
return {
|
|
852
|
+
...entry,
|
|
853
|
+
command: sanitizeCommandText(entry.command, repoRoot),
|
|
854
|
+
source: sanitizeSummary(entry.source, repoRoot) ?? entry.source,
|
|
855
|
+
scope: sanitizePathField(entry.scope, repoRoot),
|
|
856
|
+
targetPath: sanitizePathField(entry.targetPath, repoRoot),
|
|
857
|
+
details: entry.details.map((detail) => sanitizeSummary(detail, repoRoot) ?? "").filter(Boolean),
|
|
858
|
+
outputSummary: sanitizeSummary(entry.outputSummary, repoRoot),
|
|
859
|
+
commandEnvelope: entry.commandEnvelope ? sanitizeCommandEnvelopeForDisplay(entry.commandEnvelope, repoRoot) : undefined
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
function sanitizeLedgerForDisplay(entry, repoRoot) {
|
|
863
|
+
return {
|
|
864
|
+
...entry,
|
|
865
|
+
command: entry.command ? sanitizeCommandText(entry.command, repoRoot) : undefined,
|
|
866
|
+
evidence: entry.evidence.map((item) => sanitizeSummary(item, repoRoot) ?? "").filter(Boolean)
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
function sanitizeCommandText(value, repoRoot) {
|
|
870
|
+
return sanitizeSummary(value, repoRoot) ?? "";
|
|
871
|
+
}
|
|
872
|
+
function sanitizeSummary(value, repoRoot) {
|
|
873
|
+
const clean = redactSecretText(value)
|
|
874
|
+
?.replaceAll(repoRoot, "<repo>")
|
|
875
|
+
.replace(/__outside_repo__:[^\s;|)]+/gu, "__outside_repo__:<outside-repo>")
|
|
876
|
+
.replace(/(^|[\s([,{])\/[^\s;|)\]'",]+/gu, "$1<abs-path>")
|
|
877
|
+
.replace(/(^|[\s([,{])(?:\.\.?\/)[^\s;|)\]'",]+/gu, "$1<rel-path>")
|
|
878
|
+
.replace(/\s+/gu, " ")
|
|
879
|
+
.trim();
|
|
880
|
+
if (!clean) {
|
|
881
|
+
return undefined;
|
|
882
|
+
}
|
|
883
|
+
return clean.length > 500 ? `${clean.slice(0, 497)}...` : clean;
|
|
884
|
+
}
|
|
885
|
+
function sanitizeCommandArgs(args, repoRoot) {
|
|
886
|
+
if (!args) {
|
|
887
|
+
return undefined;
|
|
888
|
+
}
|
|
889
|
+
let redactNext = false;
|
|
890
|
+
return args.map((arg) => {
|
|
891
|
+
if (redactNext) {
|
|
892
|
+
redactNext = false;
|
|
893
|
+
return "<redacted>";
|
|
894
|
+
}
|
|
895
|
+
if (isSecretFlag(arg) && !arg.includes("=")) {
|
|
896
|
+
redactNext = true;
|
|
897
|
+
return sanitizeSummary(arg, repoRoot) ?? "";
|
|
898
|
+
}
|
|
899
|
+
return sanitizeSummary(redactSecretArg(arg), repoRoot) ?? "";
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
function redactSecretText(value) {
|
|
903
|
+
return value
|
|
904
|
+
?.replace(/(^|[\s([,{])((?:--?[a-z0-9-]*(?:token|secret|password|passwd|pwd|api[-_]?key|access[-_]?key|auth|credential|cookie)[a-z0-9-]*)(?:=|\s+))([^\s;|)\]'",]+)/giu, "$1$2<redacted>")
|
|
905
|
+
.replace(/(\b[A-Z_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|PWD|API_?KEY|ACCESS_?KEY|AUTH|CREDENTIAL|COOKIE)[A-Z0-9_]*=)([^\s;|)\]'",]+)/gu, "$1<redacted>")
|
|
906
|
+
.replace(/\b(Bearer)\s+[A-Za-z0-9._~+/-]+=*/giu, "$1 <redacted>");
|
|
907
|
+
}
|
|
908
|
+
function redactSecretArg(value) {
|
|
909
|
+
if (/^Bearer\s+/iu.test(value)) {
|
|
910
|
+
return "Bearer <redacted>";
|
|
911
|
+
}
|
|
912
|
+
if (isSecretFlag(value) && value.includes("=")) {
|
|
913
|
+
return value.replace(/=.*/u, "=<redacted>");
|
|
914
|
+
}
|
|
915
|
+
if (/^(?:[A-Z_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|PWD|API_?KEY|ACCESS_?KEY|AUTH|CREDENTIAL|COOKIE)[A-Z0-9_]*)=/iu.test(value)) {
|
|
916
|
+
return value.replace(/=.*/u, "=<redacted>");
|
|
917
|
+
}
|
|
918
|
+
return value;
|
|
919
|
+
}
|
|
920
|
+
function isSecretFlag(value) {
|
|
921
|
+
return /^--?[a-z0-9-]*(?:token|secret|password|passwd|pwd|api-?key|access-?key|auth|credential|cookie)[a-z0-9-]*(?:=.*)?$/iu.test(value);
|
|
922
|
+
}
|
|
923
|
+
function sanitizePathField(value, repoRoot) {
|
|
924
|
+
if (!value) {
|
|
925
|
+
return undefined;
|
|
926
|
+
}
|
|
927
|
+
if (value.startsWith("__outside_repo__:")) {
|
|
928
|
+
return "__outside_repo__:<outside-repo>";
|
|
929
|
+
}
|
|
930
|
+
if (value === "." || value === "./") {
|
|
931
|
+
return ".";
|
|
932
|
+
}
|
|
933
|
+
if (value === ".." || value.startsWith("../")) {
|
|
934
|
+
return "<outside-repo>";
|
|
935
|
+
}
|
|
936
|
+
if (value.startsWith("./")) {
|
|
937
|
+
const relative = normalizePathLike(value);
|
|
938
|
+
return relative === "." ? "." : `<repo>/${relative}`;
|
|
939
|
+
}
|
|
940
|
+
if (path.isAbsolute(value)) {
|
|
941
|
+
const relative = path.relative(repoRoot, value);
|
|
942
|
+
if (relative === "") {
|
|
943
|
+
return "<repo>";
|
|
944
|
+
}
|
|
945
|
+
return !relative.startsWith("..") && !path.isAbsolute(relative) ? `<repo>/${relative.split(path.sep).join("/")}` : "<outside-repo>";
|
|
946
|
+
}
|
|
947
|
+
return sanitizeSummary(value, repoRoot);
|
|
948
|
+
}
|
|
949
|
+
function formatCommandReport(report) {
|
|
950
|
+
const status = report.exitCode === undefined ? "exit unknown" : `exit ${report.exitCode}`;
|
|
951
|
+
const cwd = report.cwd ? `; cwd ${report.cwd}` : "";
|
|
952
|
+
const duration = report.durationMs === undefined ? "" : `; ${report.durationMs}ms`;
|
|
953
|
+
const summary = report.outputSummary ?? report.stderrSummary ?? report.stdoutSummary;
|
|
954
|
+
return `${report.command} (${status}${cwd}${duration}${summary ? `; ${summary}` : ""})`;
|
|
955
|
+
}
|
|
956
|
+
function formatRunnerReviewEntry(entry) {
|
|
957
|
+
return `${entry.covering ? "trusted" : "non-covering"} ${entry.command} (${entry.reason})`;
|
|
958
|
+
}
|
|
959
|
+
function formatCommandEnvelope(envelope) {
|
|
960
|
+
const manager = envelope.packageManager ? `${envelope.packageManager}` : "unknown manager";
|
|
961
|
+
const script = envelope.scriptName ? ` ${envelope.scriptName}` : "";
|
|
962
|
+
const scope = envelope.packageRoot ? `; scope ${envelope.packageRoot}` : envelope.cwd ? `; cwd ${envelope.cwd}` : "";
|
|
963
|
+
const args = envelope.args.length > 0 ? `; args ${envelope.args.slice(0, 5).join(" ")}` : "";
|
|
964
|
+
return `${manager}${script} (${envelope.scopeStatus}; ${envelope.source}${scope}${args})`;
|
|
965
|
+
}
|
|
966
|
+
function formatRiskDeltas(deltas) {
|
|
967
|
+
if (deltas.length === 0) {
|
|
968
|
+
return ["- none detected or no risk baseline available"];
|
|
969
|
+
}
|
|
970
|
+
return deltas.slice(0, 12).map((delta) => {
|
|
971
|
+
const direction = delta.delta > 0 ? "+" : "";
|
|
972
|
+
const newText = delta.newSignals.length > 0 ? `; new ${delta.newSignals.slice(0, 3).join(" | ")}` : "";
|
|
973
|
+
const removedText = delta.removedSignals.length > 0 ? `; removed ${delta.removedSignals.slice(0, 3).join(" | ")}` : "";
|
|
974
|
+
return `- ${delta.path}: ${delta.before.riskScore.toFixed(1)} -> ${delta.after.riskScore.toFixed(1)} (${direction}${delta.delta.toFixed(1)})${newText}${removedText}`;
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
function limitArray(value, limit) {
|
|
978
|
+
return value.slice(0, limit);
|
|
979
|
+
}
|
|
980
|
+
function compactSnapshotForData(snapshot) {
|
|
981
|
+
if (!snapshot) {
|
|
982
|
+
return undefined;
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
taskId: snapshot.taskId,
|
|
986
|
+
createdAt: snapshot.createdAt,
|
|
987
|
+
changeType: snapshot.changeType,
|
|
988
|
+
plannedEditTargets: limitArray(snapshot.plannedEditTargets, 30),
|
|
989
|
+
plannedFiles: limitArray(snapshot.plannedFiles, 40),
|
|
990
|
+
plannedTests: limitArray(snapshot.plannedTests, 20),
|
|
991
|
+
requiredWorkflowCheckCount: snapshot.requiredWorkflowChecks.length,
|
|
992
|
+
requiredDependencyCheckCount: snapshot.requiredDependencyChecks.length
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function buildAutoVerifyCandidates(input) {
|
|
996
|
+
const snapshot = input.snapshot;
|
|
997
|
+
if (!snapshot) {
|
|
998
|
+
return [];
|
|
999
|
+
}
|
|
1000
|
+
const snapshotDigest = autoVerifySnapshotDigest(snapshot);
|
|
1001
|
+
return input.testsNotRun
|
|
1002
|
+
.filter((test) => test.command && test.commandCwd && test.commandExecutable && test.commandArgs)
|
|
1003
|
+
.map((test, index) => {
|
|
1004
|
+
const command = test.command;
|
|
1005
|
+
const commandCwd = test.commandCwd;
|
|
1006
|
+
const commandExecutable = test.commandExecutable;
|
|
1007
|
+
const commandArgs = test.commandArgs;
|
|
1008
|
+
return {
|
|
1009
|
+
schemaVersion: 1,
|
|
1010
|
+
taskId: snapshot.taskId,
|
|
1011
|
+
snapshotDigest,
|
|
1012
|
+
commandId: stableId("autoverify-command", snapshot.taskId, command, commandCwd, JSON.stringify(commandArgs)),
|
|
1013
|
+
command,
|
|
1014
|
+
commandExecutable,
|
|
1015
|
+
commandArgs,
|
|
1016
|
+
commandCwd,
|
|
1017
|
+
targetPaths: uniqueSorted([test.path, ...(test.provenance?.targetPaths ?? input.reviewTargets)]),
|
|
1018
|
+
source: autoVerifyCandidateSource(test.provenance),
|
|
1019
|
+
rank: test.rank - index / 100
|
|
1020
|
+
};
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
function autoVerifyCandidateSource(provenance) {
|
|
1024
|
+
const sources = provenance?.sources ?? [];
|
|
1025
|
+
if (sources.includes("explicit_target"))
|
|
1026
|
+
return "explicit";
|
|
1027
|
+
if (sources.includes("authoritative_test_edge"))
|
|
1028
|
+
return "authoritative-test-edge";
|
|
1029
|
+
if (sources.includes("derived_import") || sources.includes("derived_impact_expansion") || sources.includes("package_import") || sources.includes("outcome_history"))
|
|
1030
|
+
return "derived-impact";
|
|
1031
|
+
if (sources.length > 0)
|
|
1032
|
+
return "heuristic";
|
|
1033
|
+
return "legacy";
|
|
1034
|
+
}
|
|
1035
|
+
function stableSessionMemoryHash(value) {
|
|
1036
|
+
return stableId("session-memory-summary", value);
|
|
1037
|
+
}
|
|
1038
|
+
function compactContextData(data) {
|
|
1039
|
+
if (!data || typeof data !== "object") {
|
|
1040
|
+
return undefined;
|
|
1041
|
+
}
|
|
1042
|
+
const record = data;
|
|
1043
|
+
return {
|
|
1044
|
+
mode: record.mode,
|
|
1045
|
+
packetVerdict: record.packetVerdict,
|
|
1046
|
+
diagnostics: Array.isArray(record.diagnostics) ? record.diagnostics.slice(0, 12) : undefined,
|
|
1047
|
+
focusFiles: Array.isArray(record.focusFiles) ? record.focusFiles.slice(0, 20) : undefined,
|
|
1048
|
+
tests: Array.isArray(record.tests) ? record.tests.slice(0, 20) : undefined,
|
|
1049
|
+
quality: record.quality,
|
|
1050
|
+
gaps: Array.isArray(record.gaps) ? record.gaps.slice(0, 20) : undefined,
|
|
1051
|
+
warnings: Array.isArray(record.warnings) ? record.warnings.slice(0, 20) : undefined
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function hasRelevantVerificationEvidence(input) {
|
|
1055
|
+
const checkedTargets = new Set([
|
|
1056
|
+
...input.tests.map((test) => normalizeReviewPath(test.path)),
|
|
1057
|
+
...input.workflowChecks.map((check) => normalizeReviewPath(check.target)),
|
|
1058
|
+
...input.dependencyChecks.map((check) => normalizeReviewPath(check.target))
|
|
1059
|
+
]);
|
|
1060
|
+
if (input.verificationLedger.some((entry) => (entry.status === "covered" || entry.status === "waived") && (checkedTargets.size === 0 || checkedTargets.has(normalizeReviewPath(entry.target))))) {
|
|
1061
|
+
return true;
|
|
1062
|
+
}
|
|
1063
|
+
const recommendedTests = new Set(input.tests.map((test) => normalizeReviewPath(test.path)));
|
|
1064
|
+
if (input.ranTests.some((test) => recommendedTests.has(normalizeReviewPath(test)))) {
|
|
1065
|
+
return true;
|
|
1066
|
+
}
|
|
1067
|
+
const changedTargets = uniqueSorted([...input.editPaths, ...input.reviewTargets].map(normalizeReviewPath).filter(Boolean));
|
|
1068
|
+
return input.verificationCoverage.some((coverage) => coverageIsRelevantProof(coverage, changedTargets, recommendedTests));
|
|
1069
|
+
}
|
|
1070
|
+
function coverageIsRelevantProof(coverage, changedTargets, recommendedTests) {
|
|
1071
|
+
if (coverage.kind === "unknown" || coverage.kind === "audit" || coverage.kind === "privacy" || coverage.kind === "lint") {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
const target = coverage.targetPath ? normalizeReviewPath(coverage.targetPath) : undefined;
|
|
1075
|
+
if (target) {
|
|
1076
|
+
return changedTargets.includes(target) || recommendedTests.has(target) || changedTargets.some((changed) => pathIntersects(target, changed));
|
|
1077
|
+
}
|
|
1078
|
+
if (coverage.kind === "javascript-tests" || coverage.kind === "python-tests" || coverage.kind === "targeted-test") {
|
|
1079
|
+
return recommendedTests.size === 0 && changedTargets.some((changed) => scopeCoversReviewPath(coverage.scope ?? ".", changed));
|
|
1080
|
+
}
|
|
1081
|
+
if (coverage.kind === "build" || coverage.kind === "typescript-syntax") {
|
|
1082
|
+
return changedTargets.some((changed) => sourcePathFitsCoverageKind(changed, coverage.kind) && scopeCoversReviewPath(coverage.scope ?? ".", changed));
|
|
1083
|
+
}
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
function sourcePathFitsCoverageKind(filePath, kind) {
|
|
1087
|
+
if (kind === "typescript-syntax") {
|
|
1088
|
+
return /\.(?:[cm]?[jt]sx?)$/iu.test(filePath);
|
|
1089
|
+
}
|
|
1090
|
+
if (kind === "build") {
|
|
1091
|
+
return !isTestPath(filePath);
|
|
1092
|
+
}
|
|
1093
|
+
return false;
|
|
1094
|
+
}
|
|
1095
|
+
function scopeCoversReviewPath(scope, filePath) {
|
|
1096
|
+
const normalizedScope = normalizeReviewPath(scope);
|
|
1097
|
+
const normalizedPath = normalizeReviewPath(filePath);
|
|
1098
|
+
return normalizedScope === "." || normalizedScope === "" || normalizedPath === normalizedScope || normalizedPath.startsWith(`${normalizedScope}/`);
|
|
1099
|
+
}
|
|
1100
|
+
function pathIntersects(left, right) {
|
|
1101
|
+
return left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`);
|
|
1102
|
+
}
|
|
1103
|
+
function normalizeReviewPath(value) {
|
|
1104
|
+
const normalized = value.replace(/\\/gu, "/").replace(/^\.\/+/u, "");
|
|
1105
|
+
const collapsed = path.posix.normalize(normalized);
|
|
1106
|
+
return collapsed === "." ? "." : collapsed.replace(/^\/+/u, "");
|
|
1107
|
+
}
|
|
1108
|
+
//# sourceMappingURL=post-edit.js.map
|