@fraction12/deepclean 0.1.0-alpha.1 → 0.1.0-alpha.2
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/CHANGELOG.md +7 -0
- package/README.md +15 -10
- package/dist/cli.js +179 -8
- package/dist/cli.js.map +1 -1
- package/dist/defaults.js +1 -1
- package/dist/defaults.js.map +1 -1
- package/dist/plans.js +2 -2
- package/dist/plans.js.map +1 -1
- package/dist/state.d.ts +5 -1
- package/dist/state.js +25 -1
- package/dist/state.js.map +1 -1
- package/dist/synthesis.d.ts +3 -1
- package/dist/synthesis.js +267 -28
- package/dist/synthesis.js.map +1 -1
- package/dist/types.d.ts +85 -0
- package/dist/types.js +55 -0
- package/dist/types.js.map +1 -1
- package/docs/privacy-and-trust.md +7 -2
- package/docs/troubleshooting.md +5 -6
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.0-alpha.2 - 2026-05-27
|
|
6
|
+
|
|
7
|
+
- Changed `scan` and CI-style scans to request Codex synthesis by default after local evidence collection, with `--evidence-only` as the deterministic-only escape hatch.
|
|
8
|
+
- Added synthesis attempt ledgers with validation checks, failure records for malformed provider output, and final candidate ID alignment after ranking.
|
|
9
|
+
- Included `.deepclean/synthesis/` in doctor/status/prune retention so synthesis artifacts are visible, validated, and cleaned up with the rest of a run.
|
|
10
|
+
- Refined the public site hero and motion treatment after UAT.
|
|
11
|
+
|
|
5
12
|
## 0.1.0-alpha.1 - 2026-05-27
|
|
6
13
|
|
|
7
14
|
- Added semantic feature mapping with `.deepclean/features/` artifacts, `deepclean map`, scan feature counts, and first-pass package script, TS/JS, Python, test-suite, route/component/module, and config feature records.
|
package/README.md
CHANGED
|
@@ -30,33 +30,34 @@ deepclean next
|
|
|
30
30
|
deepclean plan candidate-001
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
`deepclean scan` collects local evidence first, then runs Codex synthesis by default. Use evidence-only mode when you only want deterministic local analysis:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
deepclean scan --
|
|
36
|
+
deepclean scan --evidence-only --json
|
|
37
37
|
deepclean report
|
|
38
|
-
deepclean plan theme-001
|
|
39
38
|
```
|
|
40
39
|
|
|
41
40
|
Global flags work before or after the command:
|
|
42
41
|
|
|
43
42
|
```bash
|
|
44
|
-
deepclean --root ./some-repo scan
|
|
45
|
-
deepclean scan --root ./some-repo --
|
|
43
|
+
deepclean --root ./some-repo scan
|
|
44
|
+
deepclean scan --root ./some-repo --evidence-only
|
|
46
45
|
```
|
|
47
46
|
|
|
47
|
+
Older examples may include `deepclean scan --synthesize`; that flag still works, but it is no longer required. Plain `deepclean scan` is the normal synthesized path. Use `--evidence-only`, `--offline`, or `--local-only` when a run must avoid provider execution.
|
|
48
|
+
|
|
48
49
|
## Workflow
|
|
49
50
|
|
|
50
51
|
```bash
|
|
51
52
|
deepclean init
|
|
52
53
|
deepclean map
|
|
53
54
|
deepclean scan
|
|
54
|
-
deepclean scan --synthesize
|
|
55
55
|
deepclean report
|
|
56
56
|
deepclean cluster
|
|
57
57
|
deepclean plan theme-001 --format codex
|
|
58
58
|
deepclean next
|
|
59
59
|
deepclean show <candidate-id>
|
|
60
|
+
deepclean explain <candidate-or-finding-id>
|
|
60
61
|
deepclean triage <candidate-id> --status ignored --note "intentional boundary"
|
|
61
62
|
deepclean handoff <candidate-id> --format codex
|
|
62
63
|
```
|
|
@@ -68,6 +69,7 @@ Deepclean writes durable local artifacts under `.deepclean/`:
|
|
|
68
69
|
- `runs/` - scan metadata
|
|
69
70
|
- `features/` - semantic feature/work-unit maps
|
|
70
71
|
- `evidence/` - raw local evidence records
|
|
72
|
+
- `synthesis/` - provider attempt ledgers, prompt manifests, and candidate validation results
|
|
71
73
|
- `candidates/` - cleanup candidates
|
|
72
74
|
- `clusters/` - related cleanup themes
|
|
73
75
|
- `reports/` - Markdown and JSON reports
|
|
@@ -84,12 +86,13 @@ Core commands support `--json` for automation:
|
|
|
84
86
|
```bash
|
|
85
87
|
deepclean scan --json
|
|
86
88
|
deepclean map --json
|
|
87
|
-
deepclean scan --
|
|
89
|
+
deepclean scan --evidence-only --json
|
|
88
90
|
deepclean report --json
|
|
89
91
|
deepclean cluster --json
|
|
90
92
|
deepclean plan theme-001 --json
|
|
91
93
|
deepclean next --json
|
|
92
94
|
deepclean show candidate-001 --json
|
|
95
|
+
deepclean explain candidate-001 --json
|
|
93
96
|
deepclean handoff candidate-001 --json
|
|
94
97
|
```
|
|
95
98
|
|
|
@@ -104,7 +107,7 @@ Useful global flags:
|
|
|
104
107
|
|
|
105
108
|
## Local Evidence
|
|
106
109
|
|
|
107
|
-
Deepclean runs local evidence first and
|
|
110
|
+
Deepclean runs local evidence first and model synthesis second unless evidence-only or local-only mode is selected. The built-in evidence layer includes:
|
|
108
111
|
|
|
109
112
|
- semantic feature mapping for package scripts, TS/JS modules/routes/components, Python modules, test suites, and config files
|
|
110
113
|
- file metrics
|
|
@@ -122,7 +125,9 @@ For TS/JS projects using NodeNext-style source imports, Deepclean resolves emitt
|
|
|
122
125
|
|
|
123
126
|
## Codex Synthesis
|
|
124
127
|
|
|
125
|
-
`deepclean scan
|
|
128
|
+
`deepclean scan` runs the local `codex` CLI in read-only mode over the collected evidence bundle by default. The model is asked to return strict JSON, and candidates are validated before they are persisted: cited evidence IDs must exist, file paths must be anchored by cited evidence, line ranges must be sane, and optional quotes must match source. Rejected drafts stay in the synthesis attempt ledger as diagnostics rather than becoming open findings.
|
|
129
|
+
|
|
130
|
+
Use `deepclean explain <candidate-or-finding-id>` to inspect why a candidate exists, which evidence supports it, which validation checks passed, and what fix-readiness guidance was attached.
|
|
126
131
|
|
|
127
132
|
Synthesis uses a built-in reviewer pack so runs do not depend on arbitrary local agent skills. The current pack looks for architecture boundaries, conceptual duplication, dependency graph risk, testability gaps, domain language drift, agent-sized cleanup slices, and weak findings that should be rejected.
|
|
128
133
|
|
|
@@ -137,7 +142,7 @@ Reviewer packs can be configured in `.deepclean/config.json`:
|
|
|
137
142
|
}
|
|
138
143
|
```
|
|
139
144
|
|
|
140
|
-
Source samples are redacted from the synthesis prompt by default. Use `--allow-source-in-model` only when the target repository and provider configuration make that acceptable.
|
|
145
|
+
Source samples are redacted from the synthesis prompt by default. Use `--allow-source-in-model` only when the target repository and provider configuration make that acceptable. Use `--evidence-only`, `--offline`, or `--local-only` when no provider should run.
|
|
141
146
|
|
|
142
147
|
See [Privacy And Trust](docs/privacy-and-trust.md), [Reviewer References](docs/reviewer-references.md), and [Troubleshooting](docs/troubleshooting.md) before using synthesis on private repos.
|
|
143
148
|
|
package/dist/cli.js
CHANGED
|
@@ -17,7 +17,7 @@ import { LockContentionError, lockRecoveryCommand, readLockStatuses, recoverStal
|
|
|
17
17
|
import { buildCandidatePlan, buildClusterPlan } from "./plans.js";
|
|
18
18
|
import { classifyRevalidation } from "./revalidation.js";
|
|
19
19
|
import { buildHandoff, buildReportRecord, renderMarkdownReport, renderMarkdownReportWithClusters, } from "./reporting.js";
|
|
20
|
-
import { ensureState, latestRunId, readConfig, readCandidates, readFindings, readLatestCandidates, readLatestClusters, readLatestEvidence, readLatestFeatures, readLifecycleEvents, resolveStatePaths, updateLatestCandidates, writeCandidates, writeCandidateObservations, writeCiRun, writeClusters, writeEvidence, writeFeatures, writeFindings, writeFixAttempt, writeHandoff, writeLifecycleEvents, writePlan, writeReport, writeRetentionManifest, writeRevalidation, writeRun, writeTriage, } from "./state.js";
|
|
20
|
+
import { ensureState, latestRunId, readConfig, readCandidates, readFindings, readLatestCandidates, readLatestClusters, readLatestEvidence, readLatestFeatures, readLatestSynthesisAttempt, readLifecycleEvents, resolveStatePaths, updateLatestCandidates, writeCandidates, writeCandidateObservations, writeCiRun, writeClusters, writeEvidence, writeFeatures, writeFindings, writeFixAttempt, writeHandoff, writeLifecycleEvents, writePlan, writeReport, writeRetentionManifest, writeRevalidation, writeRun, writeSynthesisAttempt, writeTriage, } from "./state.js";
|
|
21
21
|
import { candidateStatuses, schemaVersion, } from "./types.js";
|
|
22
22
|
import { timestampId } from "./ids.js";
|
|
23
23
|
import { synthesizeWithCodex } from "./synthesis.js";
|
|
@@ -35,6 +35,7 @@ const commands = [
|
|
|
35
35
|
"list",
|
|
36
36
|
"findings",
|
|
37
37
|
"show",
|
|
38
|
+
"explain",
|
|
38
39
|
"history",
|
|
39
40
|
"revalidate",
|
|
40
41
|
"unlock",
|
|
@@ -60,7 +61,8 @@ Commands:
|
|
|
60
61
|
ci Run non-interactive scan and policy gates for CI
|
|
61
62
|
map Write semantic feature records without producing candidates
|
|
62
63
|
scan Collect local evidence and generate candidates
|
|
63
|
-
--synthesize Run local Codex synthesis over evidence
|
|
64
|
+
--synthesize Run local Codex synthesis over evidence (default)
|
|
65
|
+
--evidence-only Skip synthesis and produce local evidence candidates only
|
|
64
66
|
--allow-source-in-model Include source samples in Codex prompt
|
|
65
67
|
--offline Skip provider calls and network-style analyzers
|
|
66
68
|
--local-only Alias for --offline
|
|
@@ -87,6 +89,8 @@ Commands:
|
|
|
87
89
|
list List findings with shared filters
|
|
88
90
|
findings Alias for list
|
|
89
91
|
show <candidate-or-theme> Show one candidate or cleanup theme with evidence
|
|
92
|
+
explain <candidate-or-finding>
|
|
93
|
+
Explain evidence, validation, and fix-readiness for a finding
|
|
90
94
|
history <finding-or-candidate-id>
|
|
91
95
|
Show lifecycle history for a finding
|
|
92
96
|
revalidate <finding-id|candidate-id|all>
|
|
@@ -177,6 +181,8 @@ export async function main(argv, cwd = process.cwd()) {
|
|
|
177
181
|
return await listCommand(context);
|
|
178
182
|
case "show":
|
|
179
183
|
return await showCommand(context);
|
|
184
|
+
case "explain":
|
|
185
|
+
return await explainCommand(context);
|
|
180
186
|
case "history":
|
|
181
187
|
return await historyCommand(context);
|
|
182
188
|
case "revalidate":
|
|
@@ -696,16 +702,26 @@ async function executeFeatureMap(context) {
|
|
|
696
702
|
}
|
|
697
703
|
async function ciCommand(context) {
|
|
698
704
|
const requireSynthesis = flagBoolean(context.parsed.flags, "require-synthesis");
|
|
699
|
-
|
|
705
|
+
const config = await ensureState(context.paths);
|
|
706
|
+
if (requireSynthesis && synthesisDisabledByPolicy(context, config)) {
|
|
700
707
|
const diagnostic = {
|
|
701
708
|
level: "error",
|
|
702
709
|
code: "ci_synthesis_required",
|
|
703
|
-
message: "CI policy requires synthesis; rerun
|
|
710
|
+
message: "CI policy requires synthesis; rerun without evidence-only/local-only flags and with a configured provider.",
|
|
704
711
|
};
|
|
705
712
|
emit(context.json, fail("ci", "ci_synthesis_required", diagnostic.message, [diagnostic]));
|
|
706
713
|
return 2;
|
|
707
714
|
}
|
|
708
|
-
const scan = await executeScan(context, {
|
|
715
|
+
const scan = await executeScan(context, {});
|
|
716
|
+
const synthesisFailure = requireSynthesis ? requiredSynthesisFailure(scan) : undefined;
|
|
717
|
+
if (synthesisFailure) {
|
|
718
|
+
const diagnostics = [
|
|
719
|
+
synthesisFailure,
|
|
720
|
+
...scan.diagnostics.filter((diagnostic) => !sameSynthesisFailure(diagnostic, synthesisFailure)),
|
|
721
|
+
];
|
|
722
|
+
emit(context.json, fail("ci", "ci_synthesis_failed", synthesisFailure.message, diagnostics));
|
|
723
|
+
return 2;
|
|
724
|
+
}
|
|
709
725
|
const policy = ciPolicyFromFlags(context);
|
|
710
726
|
const gate = evaluateCiPolicy(scan.data.candidates, policy);
|
|
711
727
|
const createdAt = new Date().toISOString();
|
|
@@ -773,14 +789,13 @@ async function executeScan(context, options) {
|
|
|
773
789
|
const evidence = markDirtyTreeEvidence(adapterResult.evidence, scope);
|
|
774
790
|
const completedAt = new Date().toISOString();
|
|
775
791
|
const localCandidates = candidatesFromEvidence(runId, evidence, completedAt, config.candidateCaps, verificationProfile);
|
|
776
|
-
const synthesisRequested = options.synthesize ??
|
|
777
|
-
|| config.reviewSynthesis.enabled);
|
|
792
|
+
const synthesisRequested = options.synthesize ?? true;
|
|
778
793
|
const runtime = providerRuntimeControls(context, config);
|
|
779
794
|
if (synthesisRequested && runtime.offline) {
|
|
780
795
|
adapterResult.diagnostics.push({
|
|
781
796
|
level: "info",
|
|
782
797
|
code: "synthesis_skipped_by_policy",
|
|
783
|
-
message: "Provider synthesis was skipped because offline/local-only mode is active.",
|
|
798
|
+
message: "Provider synthesis was skipped because evidence-only/offline/local-only mode is active.",
|
|
784
799
|
adapter: "codex-synthesis",
|
|
785
800
|
});
|
|
786
801
|
}
|
|
@@ -814,6 +829,9 @@ async function executeScan(context, options) {
|
|
|
814
829
|
const clusters = buildClusters(runId, candidates, evidence, completedAt, config.clusters);
|
|
815
830
|
await writeFeatures(context.paths, runId, features);
|
|
816
831
|
await writeEvidence(context.paths, runId, evidence);
|
|
832
|
+
if (synthesisResult.attempt) {
|
|
833
|
+
await writeSynthesisAttempt(context.paths, remapSynthesisAttemptCandidateIds(synthesisResult.attempt, candidates));
|
|
834
|
+
}
|
|
817
835
|
await writeCandidates(context.paths, runId, candidates);
|
|
818
836
|
await writeFindings(context.paths, identity.findings);
|
|
819
837
|
await writeCandidateObservations(context.paths, runId, identity.observations);
|
|
@@ -835,6 +853,9 @@ async function executeScan(context, options) {
|
|
|
835
853
|
requested: shouldSynthesize,
|
|
836
854
|
provider: shouldSynthesize ? runtime.provider : undefined,
|
|
837
855
|
candidateCount: synthesisResult.candidates.length,
|
|
856
|
+
attemptId: synthesisResult.attempt?.id,
|
|
857
|
+
acceptedCandidateCount: synthesisResult.attempt?.acceptedCandidateCount,
|
|
858
|
+
rejectedCandidateCount: synthesisResult.attempt?.rejectedCandidateCount,
|
|
838
859
|
runtime: providerRuntimeSummary(runtime),
|
|
839
860
|
},
|
|
840
861
|
scope,
|
|
@@ -851,6 +872,9 @@ async function executeScan(context, options) {
|
|
|
851
872
|
synthesis: {
|
|
852
873
|
requested: shouldSynthesize,
|
|
853
874
|
candidateCount: synthesisResult.candidates.length,
|
|
875
|
+
attemptId: synthesisResult.attempt?.id,
|
|
876
|
+
acceptedCandidateCount: synthesisResult.attempt?.acceptedCandidateCount,
|
|
877
|
+
rejectedCandidateCount: synthesisResult.attempt?.rejectedCandidateCount,
|
|
854
878
|
runtime: providerRuntimeSummary(runtime),
|
|
855
879
|
},
|
|
856
880
|
candidates,
|
|
@@ -860,6 +884,20 @@ async function executeScan(context, options) {
|
|
|
860
884
|
};
|
|
861
885
|
return { runId, diagnostics, data };
|
|
862
886
|
}
|
|
887
|
+
function remapSynthesisAttemptCandidateIds(attempt, candidates) {
|
|
888
|
+
const candidateIdByValidationId = new Map(candidates
|
|
889
|
+
.filter((candidate) => candidate.provenance.source === "model-synthesis")
|
|
890
|
+
.flatMap((candidate) => candidate.provenance.validationId
|
|
891
|
+
? [[candidate.provenance.validationId, candidate.id]]
|
|
892
|
+
: []));
|
|
893
|
+
return {
|
|
894
|
+
...attempt,
|
|
895
|
+
validations: attempt.validations.map((validation) => ({
|
|
896
|
+
...validation,
|
|
897
|
+
candidateId: candidateIdByValidationId.get(validation.id),
|
|
898
|
+
})),
|
|
899
|
+
};
|
|
900
|
+
}
|
|
863
901
|
async function reportCommand(context) {
|
|
864
902
|
const { candidates, evidence, runId } = await latestState(context.paths);
|
|
865
903
|
const config = await ensureState(context.paths);
|
|
@@ -966,6 +1004,79 @@ async function showCommand(context) {
|
|
|
966
1004
|
}
|
|
967
1005
|
return 0;
|
|
968
1006
|
}
|
|
1007
|
+
async function explainCommand(context) {
|
|
1008
|
+
const id = requireCandidateId(context);
|
|
1009
|
+
const { candidates, evidence } = await latestState(context.paths);
|
|
1010
|
+
const attempt = await readLatestSynthesisAttempt(context.paths);
|
|
1011
|
+
const candidate = candidates.find((item) => item.id === id || item.findingId === id);
|
|
1012
|
+
if (!candidate) {
|
|
1013
|
+
emit(context.json, fail("explain", "candidate_not_found", `Candidate or finding not found: ${id}`));
|
|
1014
|
+
return 1;
|
|
1015
|
+
}
|
|
1016
|
+
const supportingEvidence = evidenceForIds(evidence, candidate.evidenceIds);
|
|
1017
|
+
const validation = validationForCandidate(candidate, attempt);
|
|
1018
|
+
const diagnostics = validation?.diagnostics ?? [];
|
|
1019
|
+
const explanation = {
|
|
1020
|
+
candidate,
|
|
1021
|
+
evidence: supportingEvidence,
|
|
1022
|
+
synthesisAttempt: attempt ? {
|
|
1023
|
+
id: attempt.id,
|
|
1024
|
+
runId: attempt.runId,
|
|
1025
|
+
provider: attempt.provider,
|
|
1026
|
+
model: attempt.model,
|
|
1027
|
+
promptVersion: attempt.promptVersion,
|
|
1028
|
+
promptBytes: attempt.promptBytes,
|
|
1029
|
+
rawCandidateCount: attempt.rawCandidateCount,
|
|
1030
|
+
acceptedCandidateCount: attempt.acceptedCandidateCount,
|
|
1031
|
+
rejectedCandidateCount: attempt.rejectedCandidateCount,
|
|
1032
|
+
evidenceManifest: attempt.evidenceManifest,
|
|
1033
|
+
} : undefined,
|
|
1034
|
+
validation,
|
|
1035
|
+
fixReadiness: candidate.fixReadiness,
|
|
1036
|
+
verification: candidate.verification,
|
|
1037
|
+
diagnostics,
|
|
1038
|
+
};
|
|
1039
|
+
emit(context.json, ok("explain", explanation, diagnostics));
|
|
1040
|
+
if (!context.json && !context.quiet) {
|
|
1041
|
+
printCandidate(candidate);
|
|
1042
|
+
console.log("");
|
|
1043
|
+
console.log("Why this exists:");
|
|
1044
|
+
console.log(` ${candidate.whyItMatters}`);
|
|
1045
|
+
console.log("");
|
|
1046
|
+
console.log("Evidence:");
|
|
1047
|
+
for (const record of supportingEvidence) {
|
|
1048
|
+
console.log(` ${record.id} ${record.kind}: ${record.summary}`);
|
|
1049
|
+
for (const file of record.files) {
|
|
1050
|
+
console.log(` ${formatFileRef(file)}`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (validation) {
|
|
1054
|
+
console.log("");
|
|
1055
|
+
console.log(`Validation: ${validation.status} (${validation.id})`);
|
|
1056
|
+
if (validation.diagnostics.length === 0) {
|
|
1057
|
+
console.log(" All cited evidence IDs, file paths, line ranges, and quotes passed validation.");
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
for (const diagnostic of validation.diagnostics) {
|
|
1061
|
+
console.log(` ${diagnostic.code}: ${diagnostic.message}`);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (candidate.fixReadiness) {
|
|
1066
|
+
console.log("");
|
|
1067
|
+
console.log("Fix readiness:");
|
|
1068
|
+
console.log(` scope: ${candidate.fixReadiness.minimumFixScope}`);
|
|
1069
|
+
console.log(` regression: ${candidate.fixReadiness.suggestedRegressionTest}`);
|
|
1070
|
+
console.log(` test gap: ${candidate.fixReadiness.whyCurrentTestsMissIt}`);
|
|
1071
|
+
for (const reason of candidate.fixReadiness.confidenceDowngradeReasons) {
|
|
1072
|
+
console.log(` confidence note: ${reason}`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
console.log("");
|
|
1076
|
+
console.log(`Verification: ${candidate.verification.join("; ") || "n/a"}`);
|
|
1077
|
+
}
|
|
1078
|
+
return 0;
|
|
1079
|
+
}
|
|
969
1080
|
async function historyCommand(context) {
|
|
970
1081
|
const id = requireCandidateId(context);
|
|
971
1082
|
const runId = flagString(context.parsed.flags, "run");
|
|
@@ -1456,6 +1567,22 @@ function evidenceForIds(evidence, ids) {
|
|
|
1456
1567
|
const wanted = new Set(ids);
|
|
1457
1568
|
return evidence.filter((item) => wanted.has(item.id));
|
|
1458
1569
|
}
|
|
1570
|
+
function validationForCandidate(candidate, attempt) {
|
|
1571
|
+
const validationId = candidate.provenance.validationId;
|
|
1572
|
+
if (!attempt || !validationId) {
|
|
1573
|
+
return undefined;
|
|
1574
|
+
}
|
|
1575
|
+
return attempt.validations.find((validation) => validation.id === validationId);
|
|
1576
|
+
}
|
|
1577
|
+
function formatFileRef(file) {
|
|
1578
|
+
if (file.startLine !== undefined && file.endLine !== undefined) {
|
|
1579
|
+
return `${file.path}:${file.startLine}-${file.endLine}`;
|
|
1580
|
+
}
|
|
1581
|
+
if (file.startLine !== undefined) {
|
|
1582
|
+
return `${file.path}:${file.startLine}`;
|
|
1583
|
+
}
|
|
1584
|
+
return file.path;
|
|
1585
|
+
}
|
|
1459
1586
|
function printDiagnostics(diagnostics) {
|
|
1460
1587
|
for (const diagnostic of diagnostics) {
|
|
1461
1588
|
console.log(`${diagnostic.level}: ${diagnostic.code}: ${diagnostic.message}`);
|
|
@@ -1489,6 +1616,7 @@ async function missingStateDirectories(paths) {
|
|
|
1489
1616
|
["locks", paths.locksDir],
|
|
1490
1617
|
["retention", paths.retentionDir],
|
|
1491
1618
|
["fixes", paths.fixesDir],
|
|
1619
|
+
["synthesis", paths.synthesisDir],
|
|
1492
1620
|
];
|
|
1493
1621
|
const missing = [];
|
|
1494
1622
|
for (const [name, dir] of expected) {
|
|
@@ -1805,6 +1933,7 @@ async function buildRetentionManifest(context) {
|
|
|
1805
1933
|
[context.paths.candidatesDir, "json"],
|
|
1806
1934
|
[context.paths.clustersDir, "json"],
|
|
1807
1935
|
[context.paths.observationsDir, "json"],
|
|
1936
|
+
[context.paths.synthesisDir, "json"],
|
|
1808
1937
|
]) {
|
|
1809
1938
|
const files = await filesWithExtension(dir, extension);
|
|
1810
1939
|
for (const file of files) {
|
|
@@ -2096,6 +2225,7 @@ function providerRuntimeControls(context, config) {
|
|
|
2096
2225
|
?? config.reviewSynthesis.privacyMode;
|
|
2097
2226
|
const offline = flagBoolean(context.parsed.flags, "offline")
|
|
2098
2227
|
|| flagBoolean(context.parsed.flags, "local-only")
|
|
2228
|
+
|| flagBoolean(context.parsed.flags, "evidence-only")
|
|
2099
2229
|
|| config.reviewSynthesis.offline
|
|
2100
2230
|
|| privacyMode === "local-only";
|
|
2101
2231
|
const excerptBudget = numberFlag(context, "excerpt-budget") ?? config.reviewSynthesis.excerptBudget;
|
|
@@ -2125,6 +2255,46 @@ function providerRuntimeControls(context, config) {
|
|
|
2125
2255
|
}
|
|
2126
2256
|
return runtime;
|
|
2127
2257
|
}
|
|
2258
|
+
function synthesisDisabledByPolicy(context, config) {
|
|
2259
|
+
const privacyMode = privacyModeFromFlag(flagString(context.parsed.flags, "privacy-mode"))
|
|
2260
|
+
?? config.reviewSynthesis.privacyMode;
|
|
2261
|
+
return flagBoolean(context.parsed.flags, "offline")
|
|
2262
|
+
|| flagBoolean(context.parsed.flags, "local-only")
|
|
2263
|
+
|| flagBoolean(context.parsed.flags, "evidence-only")
|
|
2264
|
+
|| config.reviewSynthesis.offline
|
|
2265
|
+
|| privacyMode === "local-only";
|
|
2266
|
+
}
|
|
2267
|
+
const requiredSynthesisFailureCodes = new Set([
|
|
2268
|
+
"codex_provider_unavailable",
|
|
2269
|
+
"codex_synthesis_timeout",
|
|
2270
|
+
"codex_synthesis_failed",
|
|
2271
|
+
"codex_synthesis_error",
|
|
2272
|
+
]);
|
|
2273
|
+
function requiredSynthesisFailure(scan) {
|
|
2274
|
+
if (!scan.data.synthesis.requested) {
|
|
2275
|
+
return {
|
|
2276
|
+
level: "error",
|
|
2277
|
+
code: "ci_synthesis_required",
|
|
2278
|
+
message: "CI policy requires synthesis, but the scan did not run provider synthesis.",
|
|
2279
|
+
adapter: "codex-synthesis",
|
|
2280
|
+
};
|
|
2281
|
+
}
|
|
2282
|
+
const diagnostic = scan.diagnostics.find((item) => (item.adapter === "codex-synthesis"
|
|
2283
|
+
&& requiredSynthesisFailureCodes.has(item.code)));
|
|
2284
|
+
if (!diagnostic) {
|
|
2285
|
+
return undefined;
|
|
2286
|
+
}
|
|
2287
|
+
return {
|
|
2288
|
+
...diagnostic,
|
|
2289
|
+
level: "error",
|
|
2290
|
+
message: `CI policy requires synthesis, but provider synthesis failed: ${diagnostic.message}`,
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
function sameSynthesisFailure(diagnostic, failure) {
|
|
2294
|
+
return diagnostic.adapter === failure.adapter
|
|
2295
|
+
&& diagnostic.code === failure.code
|
|
2296
|
+
&& requiredSynthesisFailureCodes.has(diagnostic.code);
|
|
2297
|
+
}
|
|
2128
2298
|
function providerRuntimeSummary(runtime) {
|
|
2129
2299
|
return {
|
|
2130
2300
|
provider: runtime.provider,
|
|
@@ -2193,6 +2363,7 @@ async function stateArtifactCounts(paths) {
|
|
|
2193
2363
|
["locks", paths.locksDir],
|
|
2194
2364
|
["retention", paths.retentionDir],
|
|
2195
2365
|
["fixes", paths.fixesDir],
|
|
2366
|
+
["synthesis", paths.synthesisDir],
|
|
2196
2367
|
];
|
|
2197
2368
|
const counts = {};
|
|
2198
2369
|
for (const [name, dir] of dirs) {
|