cc-devflow 4.5.11 → 4.5.12
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/.claude/skills/cc-act/CHANGELOG.md +18 -0
- package/.claude/skills/cc-act/PLAYBOOK.md +17 -269
- package/.claude/skills/cc-act/SKILL.md +38 -425
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_INDEX_TEMPLATE.md +2 -13
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_TEMPLATE.md +1 -9
- package/.claude/skills/cc-act/assets/PR_BRIEF_TEMPLATE.md +21 -177
- package/.claude/skills/cc-act/references/closure-contract.md +12 -63
- package/.claude/skills/cc-act/references/git-commit-guidelines.md +5 -5
- package/.claude/skills/cc-act/scripts/cc-act-common.sh +5 -322
- package/.claude/skills/cc-act/scripts/detect-ship-target.sh +11 -2
- package/.claude/skills/cc-act/scripts/inspect-git-index.sh +58 -0
- package/.claude/skills/cc-act/scripts/render-pr-brief.sh +40 -440
- package/.claude/skills/cc-act/scripts/verify-act-gate.sh +10 -50
- package/.claude/skills/cc-check/CHANGELOG.md +18 -0
- package/.claude/skills/cc-check/PLAYBOOK.md +19 -273
- package/.claude/skills/cc-check/SKILL.md +33 -456
- package/.claude/skills/cc-check/references/review-contract.md +12 -147
- package/.claude/skills/cc-dev/CHANGELOG.md +15 -0
- package/.claude/skills/cc-dev/PLAYBOOK.md +1 -1
- package/.claude/skills/cc-dev/SKILL.md +52 -137
- package/.claude/skills/cc-dev/scripts/resolve-cc-devflow.sh +181 -0
- package/.claude/skills/cc-do/CHANGELOG.md +11 -0
- package/.claude/skills/cc-do/PLAYBOOK.md +19 -113
- package/.claude/skills/cc-do/SKILL.md +39 -245
- package/.claude/skills/cc-do/references/execution-recovery.md +15 -109
- package/.claude/skills/cc-do/scripts/cc-do-common.sh +5 -57
- package/.claude/skills/cc-do/scripts/check-task-status.sh +35 -65
- package/.claude/skills/cc-do/scripts/mark-task-complete.sh +9 -46
- package/.claude/skills/cc-do/scripts/select-ready-tasks.sh +29 -97
- package/.claude/skills/cc-investigate/CHANGELOG.md +16 -0
- package/.claude/skills/cc-investigate/PLAYBOOK.md +20 -180
- package/.claude/skills/cc-investigate/SKILL.md +64 -246
- package/.claude/skills/cc-investigate/assets/TASKS_TEMPLATE.md +48 -98
- package/.claude/skills/cc-investigate/references/investigation-contract.md +14 -218
- package/.claude/skills/cc-next/CHANGELOG.md +6 -0
- package/.claude/skills/cc-next/PLAYBOOK.md +12 -8
- package/.claude/skills/cc-next/SKILL.md +34 -140
- package/.claude/skills/cc-plan/CHANGELOG.md +16 -0
- package/.claude/skills/cc-plan/PLAYBOOK.md +22 -161
- package/.claude/skills/cc-plan/SKILL.md +45 -295
- package/.claude/skills/cc-plan/assets/TASKS_TEMPLATE.md +30 -228
- package/.claude/skills/cc-plan/references/planning-contract.md +24 -161
- package/.claude/skills/cc-plan/scripts/next-change-key.sh +8 -44
- package/.claude/skills/cc-plan/scripts/parse-task-dependencies.js +2 -2
- package/.claude/skills/cc-plan/scripts/validate-scope.sh +1 -1
- package/.claude/skills/cc-pr-land/SKILL.md +14 -114
- package/.claude/skills/cc-pr-review/CHANGELOG.md +4 -0
- package/.claude/skills/cc-pr-review/SKILL.md +20 -103
- package/.claude/skills/cc-review/CHANGELOG.md +17 -0
- package/.claude/skills/cc-review/PLAYBOOK.md +13 -86
- package/.claude/skills/cc-review/SKILL.md +53 -241
- package/.claude/skills/cc-review/references/e2e-and-plugin-verification.md +2 -2
- package/.claude/skills/cc-review/references/implementation-review-branch.md +7 -147
- package/.claude/skills/cc-review/references/plan-review-branch.md +5 -147
- package/.claude/skills/cc-review/references/review-methods.md +10 -218
- package/.claude/skills/cc-review/scripts/collect-review-context.sh +4 -63
- package/.claude/skills/cc-roadmap/PLAYBOOK.md +1 -1
- package/.claude/skills/cc-roadmap/SKILL.md +3 -3
- package/.claude/skills/cc-simplify/CHANGELOG.md +7 -0
- package/.claude/skills/cc-simplify/SKILL.md +26 -21
- package/.claude/skills/cc-spec-init/PLAYBOOK.md +12 -48
- package/.claude/skills/cc-spec-init/SKILL.md +29 -132
- package/.claude/skills/cc-spec-init/references/spec-contract.md +8 -17
- package/CHANGELOG.md +13 -0
- package/bin/cc-devflow-cli.js +20 -260
- package/bin/cc-devflow.js +44 -7
- package/docs/commands/README.md +1 -1
- package/docs/commands/README.zh-CN.md +1 -1
- package/docs/examples/README.md +1 -1
- package/docs/examples/START-HERE.md +14 -15
- package/docs/examples/example-bindings.json +11 -11
- package/docs/examples/full-design-blocked/README.md +4 -6
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/{planning/tasks.md → task.md} +20 -15
- package/docs/examples/local-handoff/README.md +8 -11
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/pr-brief.md +31 -0
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/{planning/tasks.md → task.md} +18 -13
- package/docs/examples/pdca-loop/README.md +6 -9
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/pr-brief.md +9 -11
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/{planning/tasks.md → task.md} +18 -13
- package/docs/examples/scripts/check-example-bindings.sh +11 -62
- package/docs/guides/artifact-contract.md +10 -40
- package/docs/guides/getting-started.md +8 -8
- package/docs/guides/getting-started.zh-CN.md +8 -8
- package/docs/guides/minimize-artifacts.md +16 -130
- package/docs/guides/project-postmortem.md +14 -71
- package/lib/compiler/__tests__/skills-registry.test.js +9 -8
- package/lib/compiler/resource-copier.js +29 -0
- package/lib/skill-runtime/__tests__/archive-change.test.js +2 -2
- package/lib/skill-runtime/__tests__/benchmark-skills.test.js +3 -3
- package/lib/skill-runtime/__tests__/cli-bootstrap.integration.test.js +14 -4
- package/lib/skill-runtime/errors.js +3 -3
- package/lib/skill-runtime/index.js +5 -23
- package/lib/skill-runtime/paths.js +5 -52
- package/lib/skill-runtime/query-registry.js +4 -4
- package/lib/skill-runtime/query.js +89 -201
- package/lib/skill-runtime/store.js +4 -40
- package/lib/skill-runtime/trace.js +2 -2
- package/package.json +2 -5
- package/.claude/skills/cc-act/assets/PROJECT_POSTMORTEM_PRINCIPLES_TEMPLATE.md +0 -29
- package/.claude/skills/cc-act/assets/RELEASE_NOTE_TEMPLATE.md +0 -54
- package/.claude/skills/cc-act/scripts/generate-status-report.sh +0 -92
- package/.claude/skills/cc-act/scripts/sync-act-docs.sh +0 -355
- package/.claude/skills/cc-check/assets/REPORT_CARD_TEMPLATE.json +0 -234
- package/.claude/skills/cc-check/scripts/render-report-card.js +0 -438
- package/.claude/skills/cc-check/scripts/verify-gate.sh +0 -85
- package/.claude/skills/cc-do/scripts/build-task-context.sh +0 -175
- package/.claude/skills/cc-do/scripts/record-review-decision.sh +0 -88
- package/.claude/skills/cc-do/scripts/recover-workflow.sh +0 -82
- package/.claude/skills/cc-do/scripts/run-problem-analysis.sh +0 -70
- package/.claude/skills/cc-do/scripts/verify-task-gates.sh +0 -109
- package/.claude/skills/cc-do/scripts/write-task-checkpoint.sh +0 -92
- package/.claude/skills/cc-investigate/assets/TASK_MANIFEST_TEMPLATE.json +0 -224
- package/.claude/skills/cc-plan/assets/TASK_MANIFEST_TEMPLATE.json +0 -178
- package/.claude/skills/cc-spec-init/assets/CHANGE_META_TEMPLATE.json +0 -28
- package/.claude/skills/cc-spec-init/scripts/validate-spec-links.sh +0 -45
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/design.md +0 -234
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/planning/task-manifest.json +0 -488
- package/docs/examples/full-design-blocked/changes/REQ-002-bulk-invite-import/review/report-card.json +0 -189
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/resume-index.md +0 -39
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/handoff/status.md +0 -29
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/design.md +0 -123
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/planning/task-manifest.json +0 -292
- package/docs/examples/local-handoff/changes/REQ-003-audit-log-export/review/report-card.json +0 -136
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/handoff/status.md +0 -29
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/design.md +0 -124
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/planning/task-manifest.json +0 -292
- package/docs/examples/pdca-loop/changes/REQ-001-copy-invite-link/review/report-card.json +0 -136
- package/docs/get-shit-done-strategy-audit.md +0 -518
- package/docs/skill-runtime-migration.md +0 -46
- package/lib/skill-runtime/__tests__/approve.test.js +0 -92
- package/lib/skill-runtime/__tests__/autopilot.test.js +0 -253
- package/lib/skill-runtime/__tests__/benchmark-artifacts.test.js +0 -165
- package/lib/skill-runtime/__tests__/delegation.test.js +0 -97
- package/lib/skill-runtime/__tests__/dispatch.test.js +0 -237
- package/lib/skill-runtime/__tests__/intent.test.js +0 -203
- package/lib/skill-runtime/__tests__/lifecycle.test.js +0 -169
- package/lib/skill-runtime/__tests__/planner.tdd.test.js +0 -331
- package/lib/skill-runtime/__tests__/prepare-pr.test.js +0 -126
- package/lib/skill-runtime/__tests__/query.test.js +0 -860
- package/lib/skill-runtime/__tests__/readiness.test.js +0 -53
- package/lib/skill-runtime/__tests__/release.test.js +0 -85
- package/lib/skill-runtime/__tests__/review-check-integration.test.js +0 -148
- package/lib/skill-runtime/__tests__/review-records.test.js +0 -619
- package/lib/skill-runtime/__tests__/runtime.integration.test.js +0 -351
- package/lib/skill-runtime/__tests__/schemas.test.js +0 -337
- package/lib/skill-runtime/__tests__/task-contract-migrate.test.js +0 -137
- package/lib/skill-runtime/__tests__/task-contract.test.js +0 -874
- package/lib/skill-runtime/__tests__/team-state.test.js +0 -51
- package/lib/skill-runtime/__tests__/verify-artifacts.test.js +0 -203
- package/lib/skill-runtime/__tests__/worker-run.test.js +0 -275
- package/lib/skill-runtime/__tests__/worker.test.js +0 -56
- package/lib/skill-runtime/__tests__/workflow-context-legacy-fallback.test.js +0 -31
- package/lib/skill-runtime/__tests__/workflow-context.test.js +0 -98
- package/lib/skill-runtime/artifacts.js +0 -88
- package/lib/skill-runtime/context-index.js +0 -545
- package/lib/skill-runtime/delegation.js +0 -533
- package/lib/skill-runtime/intent.js +0 -309
- package/lib/skill-runtime/lifecycle.js +0 -294
- package/lib/skill-runtime/operations/CLAUDE.md +0 -19
- package/lib/skill-runtime/operations/approve.js +0 -81
- package/lib/skill-runtime/operations/autopilot-core.js +0 -337
- package/lib/skill-runtime/operations/autopilot-execution.js +0 -307
- package/lib/skill-runtime/operations/autopilot-shared.js +0 -48
- package/lib/skill-runtime/operations/autopilot.js +0 -163
- package/lib/skill-runtime/operations/dispatch.js +0 -416
- package/lib/skill-runtime/operations/init.js +0 -60
- package/lib/skill-runtime/operations/janitor.js +0 -61
- package/lib/skill-runtime/operations/plan.js +0 -59
- package/lib/skill-runtime/operations/prepare-pr.js +0 -25
- package/lib/skill-runtime/operations/release.js +0 -99
- package/lib/skill-runtime/operations/resume.js +0 -126
- package/lib/skill-runtime/operations/review-records.js +0 -265
- package/lib/skill-runtime/operations/snapshot.js +0 -45
- package/lib/skill-runtime/operations/task-contract.js +0 -593
- package/lib/skill-runtime/operations/verify.js +0 -170
- package/lib/skill-runtime/operations/worker-run.js +0 -531
- package/lib/skill-runtime/operations/worker.js +0 -33
- package/lib/skill-runtime/planner.js +0 -539
- package/lib/skill-runtime/readiness.js +0 -84
- package/lib/skill-runtime/review-records.js +0 -123
- package/lib/skill-runtime/review.js +0 -855
- package/lib/skill-runtime/schemas.js +0 -746
- package/lib/skill-runtime/task-contract.js +0 -188
- package/lib/skill-runtime/team-state.js +0 -122
- package/lib/skill-runtime/workflow-context.js +0 -748
|
@@ -1,855 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* [INPUT]: 依赖 manifest/runtime/codex 输出,接收 repoRoot、changeId、strict、skipReview 等上下文。
|
|
3
|
-
* [OUTPUT]: 生成任务层与需求层的统一审查结果,包含 reviewer、findings、summary 与 blocking 结论。
|
|
4
|
-
* [POS]: skill runtime 原生审查层,被 verify/intent/query 等阶段复用。
|
|
5
|
-
* [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const {
|
|
9
|
-
exists,
|
|
10
|
-
readJson,
|
|
11
|
-
readText,
|
|
12
|
-
runCommand
|
|
13
|
-
} = require('./store');
|
|
14
|
-
const { getChangePaths } = require('./paths');
|
|
15
|
-
const { parseReviewFindingsDoc } = require('./schemas');
|
|
16
|
-
const {
|
|
17
|
-
getReviewFindingsPath,
|
|
18
|
-
getReviewLedgerPath,
|
|
19
|
-
parseReviewLedger
|
|
20
|
-
} = require('./review-records');
|
|
21
|
-
|
|
22
|
-
const CODEX_BOUNDARY = [
|
|
23
|
-
'IMPORTANT: Do NOT read or execute any files under ~/.claude/, ~/.agents/, .claude/skills/, or agents/.',
|
|
24
|
-
'These are skill definitions for other AI systems and are not the repository under review.',
|
|
25
|
-
'Stay focused on the repository code and the current diff only.'
|
|
26
|
-
].join(' ');
|
|
27
|
-
|
|
28
|
-
const REVIEW_ORDER = ['pass', 'skipped', 'blocked', 'fail'];
|
|
29
|
-
const TASK_REVIEW_KINDS = ['spec', 'code'];
|
|
30
|
-
|
|
31
|
-
function shellEscape(value) {
|
|
32
|
-
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function normalizeReviewStatus(value, fallback = 'pending') {
|
|
36
|
-
const normalized = String(value || fallback).trim().toLowerCase();
|
|
37
|
-
if (['pass', 'fail', 'blocked', 'pending', 'skipped'].includes(normalized)) {
|
|
38
|
-
return normalized;
|
|
39
|
-
}
|
|
40
|
-
return fallback;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function statusRank(status) {
|
|
44
|
-
const index = REVIEW_ORDER.indexOf(status);
|
|
45
|
-
return index === -1 ? REVIEW_ORDER.length : index;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function mergeSectionStatus(current, next) {
|
|
49
|
-
return statusRank(next) > statusRank(current) ? next : current;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function severityRank(severity) {
|
|
53
|
-
return {
|
|
54
|
-
critical: 4,
|
|
55
|
-
important: 3,
|
|
56
|
-
minor: 2,
|
|
57
|
-
info: 1
|
|
58
|
-
}[severity] || 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function truncate(value, max = 240) {
|
|
62
|
-
const normalized = String(value || '').replace(/\s+/g, ' ').trim();
|
|
63
|
-
if (normalized.length <= max) {
|
|
64
|
-
return normalized;
|
|
65
|
-
}
|
|
66
|
-
return `${normalized.slice(0, max - 3).trimEnd()}...`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function makeEvidence(kind, label, ref = '', observation = '') {
|
|
70
|
-
return {
|
|
71
|
-
kind,
|
|
72
|
-
label,
|
|
73
|
-
ref,
|
|
74
|
-
observation: truncate(observation, 400)
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function makeFinding({
|
|
79
|
-
id,
|
|
80
|
-
source,
|
|
81
|
-
scope,
|
|
82
|
-
category,
|
|
83
|
-
severity,
|
|
84
|
-
summary,
|
|
85
|
-
details = '',
|
|
86
|
-
file,
|
|
87
|
-
line,
|
|
88
|
-
action = 'none',
|
|
89
|
-
status = 'open',
|
|
90
|
-
fingerprint,
|
|
91
|
-
confidenceScore,
|
|
92
|
-
displayTier
|
|
93
|
-
}) {
|
|
94
|
-
const derivedConfidenceScore = severityRank(severity) >= severityRank('important') ? 8 : 6;
|
|
95
|
-
return {
|
|
96
|
-
id,
|
|
97
|
-
source,
|
|
98
|
-
scope,
|
|
99
|
-
category,
|
|
100
|
-
severity,
|
|
101
|
-
summary,
|
|
102
|
-
details: truncate(details, 600),
|
|
103
|
-
...(file ? { file } : {}),
|
|
104
|
-
...(typeof line === 'number' ? { line } : {}),
|
|
105
|
-
action,
|
|
106
|
-
status,
|
|
107
|
-
confidenceScore: confidenceScore || derivedConfidenceScore,
|
|
108
|
-
fingerprint: fingerprint || `${source}:${category}:${id}`,
|
|
109
|
-
displayTier: displayTier || (status === 'informational'
|
|
110
|
-
? 'info'
|
|
111
|
-
: (severityRank(severity) >= severityRank('important') ? 'blocking' : 'warning')),
|
|
112
|
-
suppressionReason: null
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function makeReviewer({
|
|
117
|
-
key,
|
|
118
|
-
scope,
|
|
119
|
-
mode,
|
|
120
|
-
source,
|
|
121
|
-
status,
|
|
122
|
-
summary,
|
|
123
|
-
evidence = [],
|
|
124
|
-
findings = []
|
|
125
|
-
}) {
|
|
126
|
-
return {
|
|
127
|
-
key,
|
|
128
|
-
scope,
|
|
129
|
-
mode,
|
|
130
|
-
source,
|
|
131
|
-
status,
|
|
132
|
-
summary: truncate(summary, 300),
|
|
133
|
-
evidence,
|
|
134
|
-
findings
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function mapPriorityTag(tag) {
|
|
139
|
-
switch (tag) {
|
|
140
|
-
case 'P1':
|
|
141
|
-
return 'critical';
|
|
142
|
-
case 'P2':
|
|
143
|
-
return 'important';
|
|
144
|
-
case 'P3':
|
|
145
|
-
return 'minor';
|
|
146
|
-
default:
|
|
147
|
-
return 'info';
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function extractLocation(text) {
|
|
152
|
-
const match = String(text || '').match(/([A-Za-z0-9_./-]+\.[A-Za-z0-9]+):(\d+)/);
|
|
153
|
-
if (!match) {
|
|
154
|
-
return {};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
file: match[1],
|
|
159
|
-
line: Number(match[2])
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function parseCodexStructuredOutput(rawOutput = '') {
|
|
164
|
-
const findings = [];
|
|
165
|
-
const lines = String(rawOutput || '').split(/\r?\n/);
|
|
166
|
-
|
|
167
|
-
for (const line of lines) {
|
|
168
|
-
const match = line.match(/\[(P\d)\]\s*(.+)$/);
|
|
169
|
-
if (!match) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const [, tag, summary] = match;
|
|
174
|
-
const severity = mapPriorityTag(tag);
|
|
175
|
-
const location = extractLocation(line);
|
|
176
|
-
|
|
177
|
-
findings.push(makeFinding({
|
|
178
|
-
id: `codex-structured-${findings.length + 1}`,
|
|
179
|
-
source: 'codex:structured',
|
|
180
|
-
scope: 'requirement',
|
|
181
|
-
category: 'diff-review',
|
|
182
|
-
severity,
|
|
183
|
-
summary: truncate(summary, 240),
|
|
184
|
-
details: line,
|
|
185
|
-
action: severityRank(severity) >= severityRank('important') ? 'fix_now' : 'follow_up',
|
|
186
|
-
...location,
|
|
187
|
-
fingerprint: location.file
|
|
188
|
-
? `${location.file}:${location.line || 0}:diff-review:${tag}`
|
|
189
|
-
: `codex-structured:${findings.length + 1}`
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return findings;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function parseJsonLine(line) {
|
|
197
|
-
try {
|
|
198
|
-
return JSON.parse(line);
|
|
199
|
-
} catch {
|
|
200
|
-
return null;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function parseCodexAdversarialOutput(rawOutput = '') {
|
|
205
|
-
const findings = [];
|
|
206
|
-
const lines = String(rawOutput || '').split(/\r?\n/);
|
|
207
|
-
|
|
208
|
-
for (const line of lines) {
|
|
209
|
-
const trimmed = line.trim();
|
|
210
|
-
if (!trimmed || trimmed === 'NO_FINDINGS') {
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
const parsed = parseJsonLine(trimmed);
|
|
215
|
-
if (!parsed || !parsed.summary) {
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const severity = ['critical', 'important', 'minor', 'info'].includes(parsed.severity)
|
|
220
|
-
? parsed.severity
|
|
221
|
-
: 'important';
|
|
222
|
-
|
|
223
|
-
findings.push(makeFinding({
|
|
224
|
-
id: parsed.id || `codex-adversarial-${findings.length + 1}`,
|
|
225
|
-
source: 'codex:adversarial',
|
|
226
|
-
scope: 'requirement',
|
|
227
|
-
category: parsed.category || 'failure-mode',
|
|
228
|
-
severity,
|
|
229
|
-
summary: truncate(parsed.summary, 240),
|
|
230
|
-
details: parsed.details || '',
|
|
231
|
-
file: parsed.file,
|
|
232
|
-
line: typeof parsed.line === 'number' ? parsed.line : undefined,
|
|
233
|
-
action: parsed.action || (severityRank(severity) >= severityRank('important') ? 'cc-investigate' : 'follow_up'),
|
|
234
|
-
fingerprint: parsed.fingerprint
|
|
235
|
-
}));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
return findings;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async function detectBaseRef(repoRoot) {
|
|
242
|
-
for (const candidate of ['main', 'origin/main', 'master', 'origin/master', 'HEAD~1']) {
|
|
243
|
-
const result = await runCommand(`git rev-parse --verify ${candidate}`, { cwd: repoRoot });
|
|
244
|
-
if (result.code === 0) {
|
|
245
|
-
return candidate;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return 'HEAD~1';
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async function detectCurrentCommit(repoRoot) {
|
|
253
|
-
const result = await runCommand('git rev-parse --short HEAD', { cwd: repoRoot });
|
|
254
|
-
if (result.code === 0 && result.stdout.trim()) {
|
|
255
|
-
return result.stdout.trim();
|
|
256
|
-
}
|
|
257
|
-
return 'working-tree';
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async function runCodexStructuredReview({ repoRoot, baseRef }) {
|
|
261
|
-
const command = [
|
|
262
|
-
'codex review',
|
|
263
|
-
shellEscape(`${CODEX_BOUNDARY}\n\nReview the diff against the base branch. Focus on correctness, requirement drift, missing tests, and production risks.`),
|
|
264
|
-
'--base',
|
|
265
|
-
shellEscape(baseRef),
|
|
266
|
-
'-c',
|
|
267
|
-
shellEscape('model_reasoning_effort="high"')
|
|
268
|
-
].join(' ');
|
|
269
|
-
const result = await runCommand(command, {
|
|
270
|
-
cwd: repoRoot,
|
|
271
|
-
timeoutMs: 30 * 60 * 1000
|
|
272
|
-
});
|
|
273
|
-
const findings = result.code === 0 ? parseCodexStructuredOutput(result.stdout) : [];
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
result,
|
|
277
|
-
findings
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
async function runCodexAdversarialReview({ repoRoot, baseRef }) {
|
|
282
|
-
const prompt = [
|
|
283
|
-
CODEX_BOUNDARY,
|
|
284
|
-
'',
|
|
285
|
-
`Review the changes on this branch against ${baseRef}.`,
|
|
286
|
-
'Run git diff against the base branch before answering.',
|
|
287
|
-
'Output ONLY JSON objects, one per line, with keys:',
|
|
288
|
-
'id, severity (critical|important|minor|info), category, summary, details, file, line, action, fingerprint.',
|
|
289
|
-
'If nothing stands out, output NO_FINDINGS.',
|
|
290
|
-
'Be adversarial. Focus on edge cases, failure modes, unsafe assumptions, and silent data corruption.'
|
|
291
|
-
].join('\n');
|
|
292
|
-
const command = [
|
|
293
|
-
'codex exec',
|
|
294
|
-
shellEscape(prompt),
|
|
295
|
-
'-C',
|
|
296
|
-
shellEscape(repoRoot),
|
|
297
|
-
'-s',
|
|
298
|
-
'read-only',
|
|
299
|
-
'-c',
|
|
300
|
-
shellEscape('model_reasoning_effort="high"')
|
|
301
|
-
].join(' ');
|
|
302
|
-
const result = await runCommand(command, {
|
|
303
|
-
cwd: repoRoot,
|
|
304
|
-
timeoutMs: 15 * 60 * 1000
|
|
305
|
-
});
|
|
306
|
-
const findings = result.code === 0 ? parseCodexAdversarialOutput(result.stdout) : [];
|
|
307
|
-
|
|
308
|
-
return {
|
|
309
|
-
result,
|
|
310
|
-
findings
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function collectTaskReviewEvidence({ task, kind }) {
|
|
315
|
-
const manifestVerdict = normalizeReviewStatus(task.reviews?.[kind], 'pending');
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
verdict: manifestVerdict,
|
|
319
|
-
summary: ''
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async function runTaskReviewSection({ repoRoot, changeId, manifest }) {
|
|
324
|
-
const passedTasks = (manifest.tasks || []).filter((task) => task.status === 'passed');
|
|
325
|
-
if (passedTasks.length === 0) {
|
|
326
|
-
return {
|
|
327
|
-
status: 'skipped',
|
|
328
|
-
required: false,
|
|
329
|
-
summary: 'No passed tasks yet, task-level review gate skipped.',
|
|
330
|
-
reviewers: [],
|
|
331
|
-
findings: []
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
let status = 'pass';
|
|
336
|
-
const reviewers = [];
|
|
337
|
-
const findings = [];
|
|
338
|
-
|
|
339
|
-
for (const task of passedTasks) {
|
|
340
|
-
for (const kind of TASK_REVIEW_KINDS) {
|
|
341
|
-
const evidence = collectTaskReviewEvidence({ task, kind });
|
|
342
|
-
const verdict = normalizeReviewStatus(evidence.verdict, 'pending');
|
|
343
|
-
const reviewerStatus = verdict === 'pending' ? 'blocked' : verdict;
|
|
344
|
-
let summary = evidence.summary || `${task.id} ${kind} review is ${reviewerStatus}.`;
|
|
345
|
-
|
|
346
|
-
if (reviewerStatus === 'blocked') {
|
|
347
|
-
summary = `${task.id} is missing ${kind} review proof.`;
|
|
348
|
-
findings.push(makeFinding({
|
|
349
|
-
id: `${task.id}-${kind}-missing`,
|
|
350
|
-
source: 'task-review',
|
|
351
|
-
scope: 'task',
|
|
352
|
-
category: `${kind}-review`,
|
|
353
|
-
severity: 'important',
|
|
354
|
-
summary,
|
|
355
|
-
details: 'Completed tasks must carry spec/code review proof before cc-check can pass.',
|
|
356
|
-
action: 'fix_now',
|
|
357
|
-
fingerprint: `${task.id}:${kind}:missing`
|
|
358
|
-
}));
|
|
359
|
-
} else if (reviewerStatus === 'fail') {
|
|
360
|
-
findings.push(makeFinding({
|
|
361
|
-
id: `${task.id}-${kind}-failed`,
|
|
362
|
-
source: 'task-review',
|
|
363
|
-
scope: 'task',
|
|
364
|
-
category: `${kind}-review`,
|
|
365
|
-
severity: 'critical',
|
|
366
|
-
summary: `${task.id} ${kind} review failed.`,
|
|
367
|
-
details: summary,
|
|
368
|
-
action: 'fix_now',
|
|
369
|
-
fingerprint: `${task.id}:${kind}:failed`
|
|
370
|
-
}));
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
reviewers.push(makeReviewer({
|
|
374
|
-
key: `task-${task.id}-${kind}`,
|
|
375
|
-
scope: 'task',
|
|
376
|
-
mode: kind,
|
|
377
|
-
source: 'manifest',
|
|
378
|
-
status: reviewerStatus,
|
|
379
|
-
summary,
|
|
380
|
-
evidence: [
|
|
381
|
-
makeEvidence('note', `${task.id} ${kind} review state`, '', `Manifest verdict: ${verdict}`)
|
|
382
|
-
],
|
|
383
|
-
findings: findings.filter((item) => item.id.startsWith(`${task.id}-${kind}`))
|
|
384
|
-
}));
|
|
385
|
-
|
|
386
|
-
status = mergeSectionStatus(status, reviewerStatus);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const openIssues = findings.filter((item) => item.status === 'open').length;
|
|
391
|
-
const reviewedPairs = reviewers.filter((item) => item.status === 'pass' || item.status === 'skipped').length;
|
|
392
|
-
|
|
393
|
-
return {
|
|
394
|
-
status,
|
|
395
|
-
required: true,
|
|
396
|
-
summary: `${reviewedPairs}/${passedTasks.length * TASK_REVIEW_KINDS.length} task review gates cleared. Open review findings: ${openIssues}.`,
|
|
397
|
-
reviewers,
|
|
398
|
-
findings
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
function buildCodexErrorFinding(source, summary, details, severity = 'important') {
|
|
403
|
-
return makeFinding({
|
|
404
|
-
id: `${source}-unavailable`,
|
|
405
|
-
source,
|
|
406
|
-
scope: 'requirement',
|
|
407
|
-
category: 'review-runtime',
|
|
408
|
-
severity,
|
|
409
|
-
summary,
|
|
410
|
-
details,
|
|
411
|
-
action: 'cc-investigate',
|
|
412
|
-
fingerprint: `${source}:runtime`
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
async function firstExistingPath(paths) {
|
|
417
|
-
for (const filePath of paths) {
|
|
418
|
-
if (await exists(filePath)) {
|
|
419
|
-
return filePath;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return '';
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function recordStatus(summary = {}, findings = []) {
|
|
426
|
-
if (summary.status === 'blocked') {
|
|
427
|
-
return 'blocked';
|
|
428
|
-
}
|
|
429
|
-
if (
|
|
430
|
-
summary.status === 'findings'
|
|
431
|
-
|| Number(summary.blockingCount || 0) > 0
|
|
432
|
-
|| findings.some((item) => item.displayTier === 'blocking' || severityRank(item.severity) >= severityRank('important'))
|
|
433
|
-
) {
|
|
434
|
-
return 'fail';
|
|
435
|
-
}
|
|
436
|
-
return 'pass';
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
function reviewRecordSeverity(value) {
|
|
440
|
-
return value === 'advisory' ? 'minor' : value;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function reviewRecordAction(route, displayTier) {
|
|
444
|
-
if (route === 'cc-plan') return 'reroute-cc-plan';
|
|
445
|
-
if (route === 'cc-investigate') return 'reroute-cc-investigate';
|
|
446
|
-
if (route === 'cc-do') return displayTier === 'blocking' ? 'fix_now' : 'reroute-cc-do';
|
|
447
|
-
return displayTier === 'blocking' ? 'fix_now' : 'follow_up';
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function makeReviewRecordFinding(item, index) {
|
|
451
|
-
const displayTier = item.displayTier || 'blocking';
|
|
452
|
-
const status = displayTier === 'info'
|
|
453
|
-
? 'informational'
|
|
454
|
-
: (displayTier === 'suppressed' ? 'accepted' : 'open');
|
|
455
|
-
return makeFinding({
|
|
456
|
-
id: item.id || item.findingId || `review-record-${index + 1}`,
|
|
457
|
-
source: 'review-records',
|
|
458
|
-
scope: 'requirement',
|
|
459
|
-
category: 'review-record',
|
|
460
|
-
severity: reviewRecordSeverity(item.severity),
|
|
461
|
-
summary: truncate(item.evidence || item.recommendation || 'Review finding', 240),
|
|
462
|
-
details: item.recommendation || item.evidence || '',
|
|
463
|
-
file: item.path,
|
|
464
|
-
action: reviewRecordAction(item.route, displayTier),
|
|
465
|
-
status,
|
|
466
|
-
fingerprint: item.fingerprint,
|
|
467
|
-
confidenceScore: Number(item.confidence) || undefined,
|
|
468
|
-
displayTier
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function normalizeFreshness(freshness = {}) {
|
|
473
|
-
return {
|
|
474
|
-
status: freshness.status || 'unknown',
|
|
475
|
-
reviewedCommit: freshness.reviewedCommit || '',
|
|
476
|
-
currentCommit: freshness.currentCommit || '',
|
|
477
|
-
commitsSinceReview: freshness.commitsSinceReview ?? null,
|
|
478
|
-
staleReason: freshness.staleReason || ''
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function makeRecordReviewer(recordReview) {
|
|
483
|
-
return makeReviewer({
|
|
484
|
-
key: 'requirement-review-records',
|
|
485
|
-
scope: 'requirement',
|
|
486
|
-
mode: 'structured',
|
|
487
|
-
source: 'runtime',
|
|
488
|
-
status: recordReview.status,
|
|
489
|
-
summary: recordReview.summary,
|
|
490
|
-
evidence: [
|
|
491
|
-
makeEvidence('file', 'review record source', recordReview.source, recordReview.summary)
|
|
492
|
-
],
|
|
493
|
-
findings: recordReview.findings || []
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
async function readReviewFindingsDoc(filePath) {
|
|
498
|
-
try {
|
|
499
|
-
const doc = parseReviewFindingsDoc(await readJson(filePath));
|
|
500
|
-
const findings = doc.findings.map(makeReviewRecordFinding);
|
|
501
|
-
return {
|
|
502
|
-
source: 'review-findings.json',
|
|
503
|
-
status: recordStatus(doc.summary, findings),
|
|
504
|
-
required: true,
|
|
505
|
-
summary: `review-findings.json reports ${doc.summary.status} with ${doc.summary.blockingCount} blocking finding(s).`,
|
|
506
|
-
freshness: normalizeFreshness(doc.freshness),
|
|
507
|
-
reviewers: [],
|
|
508
|
-
findings,
|
|
509
|
-
errors: []
|
|
510
|
-
};
|
|
511
|
-
} catch (error) {
|
|
512
|
-
return {
|
|
513
|
-
source: 'review-findings.json',
|
|
514
|
-
status: 'blocked',
|
|
515
|
-
required: true,
|
|
516
|
-
summary: `review-findings.json is unreadable: ${error.message}`,
|
|
517
|
-
freshness: normalizeFreshness({ status: 'unknown', staleReason: error.message }),
|
|
518
|
-
reviewers: [],
|
|
519
|
-
findings: [makeFinding({
|
|
520
|
-
id: 'review-findings-unreadable',
|
|
521
|
-
source: 'review-records',
|
|
522
|
-
scope: 'requirement',
|
|
523
|
-
category: 'review-record',
|
|
524
|
-
severity: 'important',
|
|
525
|
-
summary: 'review-findings.json is unreadable.',
|
|
526
|
-
details: error.message,
|
|
527
|
-
action: 'fix_now'
|
|
528
|
-
})],
|
|
529
|
-
errors: [error.message]
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
async function readReviewLedger(filePath) {
|
|
535
|
-
const parsed = parseReviewLedger(await readText(filePath, ''));
|
|
536
|
-
const findings = parsed.findings.map(makeReviewRecordFinding);
|
|
537
|
-
return {
|
|
538
|
-
source: 'review-ledger.jsonl',
|
|
539
|
-
status: recordStatus(parsed.summary, findings),
|
|
540
|
-
required: true,
|
|
541
|
-
summary: `review ledger reports ${parsed.summary.status} with ${parsed.summary.blockingCount} blocking finding(s).`,
|
|
542
|
-
freshness: normalizeFreshness(parsed.freshness),
|
|
543
|
-
reviewers: [],
|
|
544
|
-
findings,
|
|
545
|
-
errors: parsed.errors
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
async function runReviewRecordSection({ repoRoot, changeId }) {
|
|
550
|
-
const change = getChangePaths(repoRoot, changeId);
|
|
551
|
-
const findingsPath = await firstExistingPath([
|
|
552
|
-
getReviewFindingsPath(repoRoot, changeId, { changeKey: change.changeKey }),
|
|
553
|
-
`${change.reviewDir}/cc-review-findings.json`
|
|
554
|
-
]);
|
|
555
|
-
if (findingsPath) {
|
|
556
|
-
const recordReview = await readReviewFindingsDoc(findingsPath);
|
|
557
|
-
return {
|
|
558
|
-
...recordReview,
|
|
559
|
-
reviewers: [makeRecordReviewer(recordReview)]
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
const ledgerPath = await firstExistingPath([
|
|
564
|
-
getReviewLedgerPath(repoRoot, changeId, { changeKey: change.changeKey }),
|
|
565
|
-
`${change.reviewDir}/cc-review-ledger.jsonl`
|
|
566
|
-
]);
|
|
567
|
-
if (ledgerPath) {
|
|
568
|
-
const recordReview = await readReviewLedger(ledgerPath);
|
|
569
|
-
return {
|
|
570
|
-
...recordReview,
|
|
571
|
-
reviewers: [makeRecordReviewer(recordReview)]
|
|
572
|
-
};
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const legacyReportPath = await firstExistingPath([
|
|
576
|
-
`${change.reviewDir}/cc-review-report.md`
|
|
577
|
-
]);
|
|
578
|
-
if (legacyReportPath) {
|
|
579
|
-
const recordReview = {
|
|
580
|
-
source: 'cc-review-report.md',
|
|
581
|
-
status: 'pass',
|
|
582
|
-
required: true,
|
|
583
|
-
summary: 'legacy cc-review-report.md found; review freshness is unknown.',
|
|
584
|
-
freshness: normalizeFreshness({
|
|
585
|
-
status: 'unknown',
|
|
586
|
-
staleReason: 'legacy cc-review-report.md has no machine freshness fields'
|
|
587
|
-
}),
|
|
588
|
-
reviewers: [],
|
|
589
|
-
findings: [],
|
|
590
|
-
errors: []
|
|
591
|
-
};
|
|
592
|
-
return {
|
|
593
|
-
...recordReview,
|
|
594
|
-
reviewers: [makeRecordReviewer(recordReview)]
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
const finding = makeFinding({
|
|
599
|
-
id: 'review-missing',
|
|
600
|
-
source: 'review-records',
|
|
601
|
-
scope: 'requirement',
|
|
602
|
-
category: 'review-record',
|
|
603
|
-
severity: 'important',
|
|
604
|
-
summary: 'review-missing: no review records are present.',
|
|
605
|
-
details: 'Expected review-findings.json, review-ledger.jsonl, or legacy cc-review-report.md before cc-check can pass.',
|
|
606
|
-
action: 'fix_now',
|
|
607
|
-
fingerprint: `${change.changeKey}:review-missing`
|
|
608
|
-
});
|
|
609
|
-
const recordReview = {
|
|
610
|
-
source: 'review-records',
|
|
611
|
-
status: 'blocked',
|
|
612
|
-
required: true,
|
|
613
|
-
summary: 'review-missing: no review records found.',
|
|
614
|
-
freshness: normalizeFreshness({
|
|
615
|
-
status: 'unknown',
|
|
616
|
-
staleReason: 'review-missing'
|
|
617
|
-
}),
|
|
618
|
-
reviewers: [],
|
|
619
|
-
findings: [finding],
|
|
620
|
-
errors: ['review-missing']
|
|
621
|
-
};
|
|
622
|
-
return {
|
|
623
|
-
...recordReview,
|
|
624
|
-
reviewers: [makeRecordReviewer(recordReview)]
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
async function runDiffReviewSection({ repoRoot, strict, skipReview }) {
|
|
629
|
-
if (!strict) {
|
|
630
|
-
return {
|
|
631
|
-
status: 'skipped',
|
|
632
|
-
required: false,
|
|
633
|
-
summary: 'Strict mode disabled, diff review skipped.',
|
|
634
|
-
reviewers: [],
|
|
635
|
-
findings: []
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
if (skipReview) {
|
|
640
|
-
return {
|
|
641
|
-
status: 'skipped',
|
|
642
|
-
required: true,
|
|
643
|
-
summary: 'Diff review skipped by --skip-review.',
|
|
644
|
-
reviewers: [],
|
|
645
|
-
findings: []
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
const hasCodex = await runCommand('command -v codex', { cwd: repoRoot });
|
|
650
|
-
if (hasCodex.code !== 0) {
|
|
651
|
-
const finding = buildCodexErrorFinding(
|
|
652
|
-
'codex',
|
|
653
|
-
'Codex CLI is required for strict diff review.',
|
|
654
|
-
'Run strict verify on a machine with codex installed, or use --skip-review intentionally.'
|
|
655
|
-
);
|
|
656
|
-
|
|
657
|
-
return {
|
|
658
|
-
status: 'blocked',
|
|
659
|
-
required: true,
|
|
660
|
-
summary: finding.summary,
|
|
661
|
-
reviewers: [],
|
|
662
|
-
findings: [finding]
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
const baseRef = await detectBaseRef(repoRoot);
|
|
667
|
-
const reviewers = [];
|
|
668
|
-
const findings = [];
|
|
669
|
-
let status = 'pass';
|
|
670
|
-
|
|
671
|
-
const structured = await runCodexStructuredReview({ repoRoot, baseRef });
|
|
672
|
-
const structuredError = structured.result.code !== 0
|
|
673
|
-
? buildCodexErrorFinding(
|
|
674
|
-
'codex:structured',
|
|
675
|
-
'Codex structured diff review failed to run.',
|
|
676
|
-
structured.result.stderr || structured.result.stdout || 'codex review failed'
|
|
677
|
-
)
|
|
678
|
-
: null;
|
|
679
|
-
const structuredFindings = structuredError ? [structuredError] : structured.findings;
|
|
680
|
-
if (structuredError) {
|
|
681
|
-
status = mergeSectionStatus(status, 'blocked');
|
|
682
|
-
} else if (structuredFindings.some((item) => severityRank(item.severity) >= severityRank('important'))) {
|
|
683
|
-
status = mergeSectionStatus(status, 'fail');
|
|
684
|
-
}
|
|
685
|
-
findings.push(...structuredFindings);
|
|
686
|
-
reviewers.push(makeReviewer({
|
|
687
|
-
key: 'requirement-structured',
|
|
688
|
-
scope: 'requirement',
|
|
689
|
-
mode: 'structured',
|
|
690
|
-
source: 'codex',
|
|
691
|
-
status: structuredError ? 'blocked' : (structuredFindings.length > 0 ? 'fail' : 'pass'),
|
|
692
|
-
summary: structuredError
|
|
693
|
-
? structuredError.summary
|
|
694
|
-
: (structuredFindings.length > 0
|
|
695
|
-
? `Structured diff review found ${structuredFindings.length} issue(s).`
|
|
696
|
-
: 'Structured diff review passed with no parsed findings.'),
|
|
697
|
-
evidence: [
|
|
698
|
-
makeEvidence('command', 'codex review', `base=${baseRef}`, structured.result.stderr || structured.result.stdout)
|
|
699
|
-
],
|
|
700
|
-
findings: structuredFindings
|
|
701
|
-
}));
|
|
702
|
-
|
|
703
|
-
const adversarial = await runCodexAdversarialReview({ repoRoot, baseRef });
|
|
704
|
-
const adversarialError = adversarial.result.code !== 0
|
|
705
|
-
? buildCodexErrorFinding(
|
|
706
|
-
'codex:adversarial',
|
|
707
|
-
'Codex adversarial review was unavailable.',
|
|
708
|
-
adversarial.result.stderr || adversarial.result.stdout || 'codex exec failed',
|
|
709
|
-
'minor'
|
|
710
|
-
)
|
|
711
|
-
: null;
|
|
712
|
-
const adversarialFindings = adversarialError ? [adversarialError] : adversarial.findings;
|
|
713
|
-
if (!adversarialError && adversarialFindings.some((item) => severityRank(item.severity) >= severityRank('important'))) {
|
|
714
|
-
status = mergeSectionStatus(status, 'fail');
|
|
715
|
-
}
|
|
716
|
-
findings.push(...adversarialFindings);
|
|
717
|
-
reviewers.push(makeReviewer({
|
|
718
|
-
key: 'requirement-adversarial',
|
|
719
|
-
scope: 'requirement',
|
|
720
|
-
mode: 'adversarial',
|
|
721
|
-
source: 'codex',
|
|
722
|
-
status: adversarialError ? 'blocked' : (adversarialFindings.length > 0 ? 'fail' : 'pass'),
|
|
723
|
-
summary: adversarialError
|
|
724
|
-
? adversarialError.summary
|
|
725
|
-
: (adversarialFindings.length > 0
|
|
726
|
-
? `Adversarial diff review found ${adversarialFindings.length} issue(s).`
|
|
727
|
-
: 'Adversarial diff review found no issues.'),
|
|
728
|
-
evidence: [
|
|
729
|
-
makeEvidence('command', 'codex exec adversarial', `base=${baseRef}`, adversarial.result.stderr || adversarial.result.stdout)
|
|
730
|
-
],
|
|
731
|
-
findings: adversarialFindings
|
|
732
|
-
}));
|
|
733
|
-
|
|
734
|
-
if (structuredError && !adversarialError) {
|
|
735
|
-
status = 'blocked';
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
findings.sort((left, right) => severityRank(right.severity) - severityRank(left.severity));
|
|
739
|
-
|
|
740
|
-
return {
|
|
741
|
-
status,
|
|
742
|
-
required: true,
|
|
743
|
-
summary: `Base ${baseRef}. Structured reviewers: ${structuredFindings.length}. Adversarial findings: ${adversarialFindings.length}.`,
|
|
744
|
-
reviewers,
|
|
745
|
-
findings
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
function flattenFindings(...sections) {
|
|
750
|
-
return sections.flatMap((section) => section.findings || []);
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
function deriveReviewStatus(taskReviews, diffReview, recordReview) {
|
|
754
|
-
let status = 'pass';
|
|
755
|
-
|
|
756
|
-
for (const section of [taskReviews, recordReview, diffReview]) {
|
|
757
|
-
if (!section.required && section.status === 'skipped') {
|
|
758
|
-
continue;
|
|
759
|
-
}
|
|
760
|
-
status = mergeSectionStatus(status, section.status);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
return status;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
async function runReviewSuite({ repoRoot, changeId, manifest, strict, skipReview }) {
|
|
767
|
-
const taskReviews = await runTaskReviewSection({ repoRoot, changeId, manifest });
|
|
768
|
-
const recordReview = await runReviewRecordSection({ repoRoot, changeId });
|
|
769
|
-
const diffReview = await runDiffReviewSection({ repoRoot, strict, skipReview });
|
|
770
|
-
const findings = flattenFindings(taskReviews, recordReview, diffReview);
|
|
771
|
-
const status = deriveReviewStatus(taskReviews, diffReview, recordReview);
|
|
772
|
-
const currentCommit = await detectCurrentCommit(repoRoot);
|
|
773
|
-
const details = [
|
|
774
|
-
taskReviews.summary,
|
|
775
|
-
recordReview.summary,
|
|
776
|
-
diffReview.summary
|
|
777
|
-
].filter(Boolean).join(' ');
|
|
778
|
-
|
|
779
|
-
return {
|
|
780
|
-
status,
|
|
781
|
-
summary: `Task review: ${taskReviews.status}. Record review: ${recordReview.status}. Diff review: ${diffReview.status}.`,
|
|
782
|
-
details,
|
|
783
|
-
freshness: recordReview.freshness || {
|
|
784
|
-
status: 'fresh',
|
|
785
|
-
reviewedCommit: currentCommit,
|
|
786
|
-
currentCommit,
|
|
787
|
-
commitsSinceReview: 0,
|
|
788
|
-
staleReason: ''
|
|
789
|
-
},
|
|
790
|
-
qualityScore: status === 'pass' ? 9 : (status === 'blocked' ? 5 : 3),
|
|
791
|
-
specialistReviews: [
|
|
792
|
-
{
|
|
793
|
-
name: 'testing',
|
|
794
|
-
status: taskReviews.status,
|
|
795
|
-
required: true,
|
|
796
|
-
summary: taskReviews.summary,
|
|
797
|
-
skipReason: '',
|
|
798
|
-
findings: []
|
|
799
|
-
},
|
|
800
|
-
{
|
|
801
|
-
name: 'review-records',
|
|
802
|
-
status: recordReview.status,
|
|
803
|
-
required: true,
|
|
804
|
-
summary: recordReview.summary,
|
|
805
|
-
skipReason: '',
|
|
806
|
-
findings: recordReview.findings || []
|
|
807
|
-
}
|
|
808
|
-
],
|
|
809
|
-
runtime: {
|
|
810
|
-
status: status === 'pass' ? 'pass' : (status === 'fail' ? 'fail' : 'blocked'),
|
|
811
|
-
failureOwnership: []
|
|
812
|
-
},
|
|
813
|
-
qa: {
|
|
814
|
-
status: 'pass',
|
|
815
|
-
regressionProof: [],
|
|
816
|
-
testQuality: [],
|
|
817
|
-
coverageAudit: {
|
|
818
|
-
status: 'pass',
|
|
819
|
-
coveragePct: null,
|
|
820
|
-
pathMap: [],
|
|
821
|
-
gaps: [],
|
|
822
|
-
testsAdded: [],
|
|
823
|
-
e2eRequired: false,
|
|
824
|
-
evalRequired: false,
|
|
825
|
-
qualityStars: ''
|
|
826
|
-
},
|
|
827
|
-
browserEvidence: {
|
|
828
|
-
status: 'skipped',
|
|
829
|
-
mode: 'not-applicable',
|
|
830
|
-
affectedRoutes: [],
|
|
831
|
-
screenshots: [],
|
|
832
|
-
consoleErrors: [],
|
|
833
|
-
healthScore: null,
|
|
834
|
-
issues: [],
|
|
835
|
-
skipReason: 'runtime verify did not identify a UI browser QA target'
|
|
836
|
-
},
|
|
837
|
-
tddException: null
|
|
838
|
-
},
|
|
839
|
-
taskReviews,
|
|
840
|
-
recordReview,
|
|
841
|
-
diffReview,
|
|
842
|
-
findings
|
|
843
|
-
};
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
function summarizeOpenReviewFindings(findings = []) {
|
|
847
|
-
return findings
|
|
848
|
-
.filter((item) => item.status === 'open' && severityRank(item.severity) >= severityRank('important'))
|
|
849
|
-
.map((item) => `${item.source}: ${item.summary}`);
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
module.exports = {
|
|
853
|
-
runReviewSuite,
|
|
854
|
-
summarizeOpenReviewFindings
|
|
855
|
-
};
|