@fitlab-ai/agent-infra 0.11.6 → 0.11.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/internal-cli.js +8 -2
- package/dist/lib/agent-clients/adapters/codex.js +1 -1
- package/dist/lib/agent-clients/config.js +11 -3
- package/dist/lib/defaults.json +6 -3
- package/dist/lib/git/primary-worktree.js +58 -0
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/platform-pr-review.js +20 -3
- package/dist/lib/internal/task-operation-registry.js +3 -1
- package/dist/lib/internal/task-platform-recovery.js +56 -0
- package/dist/lib/merge.js +25 -5
- package/dist/lib/platform/completion-backfill.js +2 -1
- package/dist/lib/platform/in-label-sync.js +12 -1
- package/dist/lib/platform/issue-comments.js +58 -4
- package/dist/lib/platform/issues.js +105 -5
- package/dist/lib/platform/operation-coordinator.js +74 -0
- package/dist/lib/platform/pr-review.js +107 -1
- package/dist/lib/platform/pr-summary.js +79 -57
- package/dist/lib/platform/pull-requests.js +125 -7
- package/dist/lib/sandbox/commands/create.js +6 -3
- package/dist/lib/sandbox/commands/ls.js +4 -3
- package/dist/lib/sandbox/commands/rm.js +1 -1
- package/dist/lib/sandbox/config.js +22 -4
- package/dist/lib/sandbox/control/server.js +6 -2
- package/dist/lib/sandbox/git-metadata.js +50 -0
- package/dist/lib/sandbox/index.js +1 -1
- package/dist/lib/sandbox/mounts.js +12 -0
- package/dist/lib/sandbox/recovery.js +11 -0
- package/dist/lib/sandbox/removal.js +58 -20
- package/dist/lib/sandbox/tools.js +29 -7
- package/dist/lib/server/config.js +7 -27
- package/dist/lib/server/daemon.js +4 -3
- package/dist/lib/server/process-control.js +4 -2
- package/dist/lib/server/runner.js +4 -3
- package/dist/lib/task/activity-intent.js +4 -3
- package/dist/lib/task/artifact-lifecycle.js +42 -10
- package/dist/lib/task/artifact-receipts.js +62 -22
- package/dist/lib/task/capabilities.js +22 -4
- package/dist/lib/task/completion-facts.js +56 -0
- package/dist/lib/task/create-service.js +11 -5
- package/dist/lib/task/create.js +60 -79
- package/dist/lib/task/events.js +268 -195
- package/dist/lib/task/finalization-artifacts.js +4 -1
- package/dist/lib/task/finalization-handoff.js +2 -1
- package/dist/lib/task/finalization.js +157 -24
- package/dist/lib/task/invalidation-command.js +4 -2
- package/dist/lib/task/invalidation.js +38 -9
- package/dist/lib/task/lifecycle.js +38 -2
- package/dist/lib/task/local-artifact-finalization.js +17 -3
- package/dist/lib/task/platform-operation-journal.js +152 -0
- package/dist/lib/task/platform-operation-recovery.js +459 -0
- package/dist/lib/task/qualification-audit.js +23 -105
- package/dist/lib/task/review-finalization.js +7 -0
- package/dist/lib/task/review-identity.js +69 -0
- package/dist/lib/task/sections.js +6 -5
- package/dist/lib/task/task-execution-lock.js +6 -3
- package/dist/lib/task/verification-engine.js +14 -63
- package/dist/lib/task/verification.js +35 -38
- package/dist/lib/task/workflow-warning-intents.js +7 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex.ts +1 -1
- package/lib/agent-clients/config.ts +9 -3
- package/lib/defaults.json +6 -3
- package/lib/git/primary-worktree.ts +62 -0
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/task-operation-registry.ts +3 -1
- package/lib/internal/task-platform-recovery.ts +53 -0
- package/lib/merge.ts +26 -6
- package/lib/platform/completion-backfill.ts +2 -1
- package/lib/platform/in-label-sync.ts +12 -0
- package/lib/platform/issue-comments.ts +59 -3
- package/lib/platform/issues.ts +103 -5
- package/lib/platform/operation-coordinator.ts +96 -0
- package/lib/platform/pr-review.ts +113 -1
- package/lib/platform/pr-summary.ts +22 -1
- package/lib/platform/pull-requests.ts +132 -8
- package/lib/sandbox/commands/create.ts +7 -8
- package/lib/sandbox/commands/ls.ts +4 -3
- package/lib/sandbox/commands/rm.ts +1 -1
- package/lib/sandbox/config.ts +23 -5
- package/lib/sandbox/control/server.ts +5 -2
- package/lib/sandbox/git-metadata.ts +66 -0
- package/lib/sandbox/index.ts +1 -1
- package/lib/sandbox/mounts.ts +14 -0
- package/lib/sandbox/recovery.ts +13 -0
- package/lib/sandbox/removal.ts +57 -19
- package/lib/sandbox/tool-types.ts +1 -1
- package/lib/sandbox/tools.ts +36 -7
- package/lib/server/config.ts +7 -29
- package/lib/server/daemon.ts +5 -3
- package/lib/server/process-control.ts +4 -2
- package/lib/server/runner.ts +7 -4
- package/lib/server/server.schema.json +0 -4
- package/lib/task/activity-intent.ts +4 -3
- package/lib/task/artifact-lifecycle.ts +35 -11
- package/lib/task/artifact-receipts.ts +55 -15
- package/lib/task/capabilities.ts +17 -3
- package/lib/task/completion-facts.ts +66 -0
- package/lib/task/create-service.ts +12 -6
- package/lib/task/create.ts +54 -69
- package/lib/task/events.ts +248 -182
- package/lib/task/finalization-artifacts.ts +4 -1
- package/lib/task/finalization-handoff.ts +2 -1
- package/lib/task/finalization.ts +154 -21
- package/lib/task/invalidation-command.ts +4 -2
- package/lib/task/invalidation.ts +35 -10
- package/lib/task/lifecycle.ts +35 -3
- package/lib/task/local-artifact-finalization.ts +13 -3
- package/lib/task/platform-operation-journal.ts +230 -0
- package/lib/task/platform-operation-recovery.ts +435 -0
- package/lib/task/qualification-audit.ts +21 -114
- package/lib/task/review-finalization.ts +7 -0
- package/lib/task/review-identity.ts +85 -0
- package/lib/task/sections.ts +6 -6
- package/lib/task/task-execution-lock.ts +7 -3
- package/lib/task/verification-engine.ts +13 -86
- package/lib/task/verification.ts +34 -38
- package/lib/task/workflow-warning-intents.ts +9 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +19 -17
- package/templates/.agents/README.zh-CN.md +18 -16
- package/templates/.agents/rules/README.en.md +2 -1
- package/templates/.agents/rules/README.zh-CN.md +2 -1
- package/templates/.agents/rules/decision-qualification.en.md +4 -3
- package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
- package/templates/.agents/rules/task-management.en.md +12 -0
- package/templates/.agents/rules/task-management.zh-CN.md +12 -0
- package/templates/.agents/rules/validation-output.en.md +19 -0
- package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/code-task/SKILL.en.md +5 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
- package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
- package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
- package/templates/.agents/skills/review-pr/SKILL.en.md +13 -11
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +13 -11
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +19 -6
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -0
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { parseArtifactName } from './artifact-name.js';
|
|
3
|
-
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
4
3
|
import { findSectionRange, parseTable } from './sections.js';
|
|
5
4
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
6
5
|
const TASK_CONSTRAINT_HEADINGS = ['约束', 'Constraints'];
|
|
7
6
|
const TASK_CANDIDATE_HEADINGS = ['候选与否决方案', 'Candidate and Rejected Options', 'Candidates and Rejected Options', 'Candidates and Rejected Alternatives'];
|
|
8
7
|
const AUDIT_HEADINGS = ['资格审计', '资格审计复核', 'Qualification Audit', 'Qualification Audit Review'];
|
|
9
8
|
const CONFIRMATION_HEADINGS = ['资格确认记录', 'Qualification Confirmations'];
|
|
10
|
-
const AUDIT_SUBSECTIONS = ['约束依赖', '候选资格', '分类结果', '
|
|
9
|
+
const AUDIT_SUBSECTIONS = ['约束依赖', '候选资格', '分类结果', '资格快照'];
|
|
11
10
|
const AUDIT_SUBSECTION_HEADINGS = {
|
|
12
11
|
'约束依赖': ['约束依赖', 'Constraint Dependencies'],
|
|
13
12
|
'候选资格': ['候选资格', 'Candidate Qualification'],
|
|
14
13
|
'分类结果': ['分类结果', 'Classification Results'],
|
|
15
|
-
'
|
|
16
|
-
'依赖快照': ['依赖快照', 'Dependency Snapshot']
|
|
14
|
+
'资格快照': ['资格快照', 'Qualification Snapshot']
|
|
17
15
|
};
|
|
18
16
|
const CONSTRAINT_COLUMNS = ['constraint_id', 'statement', 'status', 'authority', 'source', 'evidence', 'derived_from', 'approval_evidence'];
|
|
19
17
|
const CANDIDATE_COLUMNS = ['candidate_id', 'statement', 'status', 'constraint_ids', 'impact', 'evidence'];
|
|
20
18
|
const DEPENDENCY_COLUMNS = ['constraint_id', 'constraint_digest', 'role', 'evidence'];
|
|
21
19
|
const QUALIFICATION_COLUMNS = ['candidate_id', 'status', 'impact', 'constraint_ids', 'evidence'];
|
|
22
20
|
const CLASSIFICATION_COLUMNS = ['decision_id', 'classification', 'evidence'];
|
|
23
|
-
const
|
|
24
|
-
const SNAPSHOT_COLUMNS = ['task_input_digest', 'non_constraint_input_digest', 'upstream_artifact_digest'];
|
|
21
|
+
const SNAPSHOT_COLUMNS = ['task_input_digest', 'non_constraint_input_digest'];
|
|
25
22
|
const CONFIRMATION_COLUMNS = ['qcr_id', 'constraint_id', 'actor', 'entrypoint', 'request_id', 'approved_digest', 'confirmed_at', 'rationale'];
|
|
26
23
|
const ARTIFACT_FAMILIES = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
|
|
27
|
-
const RELATIONS = ['required-input', 'reviewed-input', 'review-context', 'approval-context'];
|
|
28
24
|
const CONSTRAINT_STATUSES = ['confirmed', 'derived', 'assumption', 'open', 'conflicted', 'superseded'];
|
|
29
25
|
const CANDIDATE_STATUSES = ['qualified', 'rejected', 'pending', 'needs-human-decision', 'confirmed', 'excluded'];
|
|
30
26
|
const CLASSIFICATIONS = ['deterministic', 'qualified', 'rejected', 'needs-human-decision', 'not-applicable'];
|
|
@@ -215,86 +211,20 @@ function parseAuditTable(content, heading, columns) {
|
|
|
215
211
|
const table = parseTable(`## Qualification Audit Body\n${subsection}`, { sectionAliases: ['Qualification Audit Body'], columns });
|
|
216
212
|
return table?.rows.map((row) => ({ ...row.values })) ?? null;
|
|
217
213
|
}
|
|
218
|
-
function parseAuditArtifactName(value) {
|
|
219
|
-
const identity = parseArtifactName(value);
|
|
220
|
-
return identity && ARTIFACT_FAMILIES.includes(identity.family)
|
|
221
|
-
? { family: identity.family, round: identity.round } : null;
|
|
222
|
-
}
|
|
223
|
-
function upstreamArtifactDigest(rows) {
|
|
224
|
-
return digest([...rows].map((row) => ({ ...row })).sort((a, b) => `${a.upstreamFamily}/${a.upstreamArtifact}/${a.relation}`.localeCompare(`${b.upstreamFamily}/${b.upstreamArtifact}/${b.relation}`)));
|
|
225
|
-
}
|
|
226
|
-
function validateUpstreamRelation(row) {
|
|
227
|
-
const identity = parseAuditArtifactName(row.upstreamArtifact);
|
|
228
|
-
if (!ARTIFACT_FAMILIES.includes(row.upstreamFamily) || !identity || identity.family !== row.upstreamFamily
|
|
229
|
-
|| identity.round !== row.upstreamRound || !Number.isSafeInteger(row.upstreamRound)
|
|
230
|
-
|| !/^[a-f0-9]{64}$/i.test(row.upstreamSha256) || !RELATIONS.includes(row.relation)) {
|
|
231
|
-
throw new Error(`invalid qualification upstream relation '${row.upstreamArtifact}'`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function expectedQualificationRelations(taskContent, family) {
|
|
235
|
-
let frontmatter;
|
|
236
|
-
try {
|
|
237
|
-
frontmatter = parseTypedTaskFrontmatter(taskContent);
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
241
|
-
}
|
|
242
|
-
const encoded = frontmatter.qualification_input_relations;
|
|
243
|
-
if (typeof encoded === 'string' && encoded) {
|
|
244
|
-
try {
|
|
245
|
-
const parsed = JSON.parse(encoded);
|
|
246
|
-
if (!Array.isArray(parsed))
|
|
247
|
-
throw new Error('qualification_input_relations must be an array');
|
|
248
|
-
const relations = parsed.map((value) => {
|
|
249
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
250
|
-
throw new Error('qualification input relation must be an object');
|
|
251
|
-
const row = value;
|
|
252
|
-
const relation = {
|
|
253
|
-
upstreamFamily: row.upstreamFamily,
|
|
254
|
-
upstreamArtifact: String(row.upstreamArtifact ?? ''),
|
|
255
|
-
upstreamRound: Number(row.upstreamRound),
|
|
256
|
-
upstreamSha256: String(row.upstreamSha256 ?? ''),
|
|
257
|
-
relation: row.relation
|
|
258
|
-
};
|
|
259
|
-
validateUpstreamRelation(relation);
|
|
260
|
-
return relation;
|
|
261
|
-
});
|
|
262
|
-
return { ok: true, relations };
|
|
263
|
-
}
|
|
264
|
-
catch (error) {
|
|
265
|
-
return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const inputName = family === 'code' ? frontmatter.code_input_artifact : family.startsWith('review-') ? frontmatter.review_input_artifact : '';
|
|
269
|
-
const inputSha256 = family === 'code' ? frontmatter.code_input_sha256 : family.startsWith('review-') ? frontmatter.review_input_sha256 : '';
|
|
270
|
-
if (typeof inputName !== 'string' || !inputName || typeof inputSha256 !== 'string' || !inputSha256)
|
|
271
|
-
return { ok: true, relations: undefined };
|
|
272
|
-
const identity = parseAuditArtifactName(inputName);
|
|
273
|
-
if (!identity)
|
|
274
|
-
return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: `started input '${inputName}' is not canonical` };
|
|
275
|
-
const relation = {
|
|
276
|
-
upstreamFamily: identity.family, upstreamArtifact: inputName, upstreamRound: identity.round,
|
|
277
|
-
upstreamSha256: inputSha256, relation: family === 'code' ? 'required-input' : 'reviewed-input'
|
|
278
|
-
};
|
|
279
|
-
try {
|
|
280
|
-
validateUpstreamRelation(relation);
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
return { ok: false, code: 'QUALIFICATION_STARTED_INPUT_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
284
|
-
}
|
|
285
|
-
return { ok: true, relations: [relation] };
|
|
286
|
-
}
|
|
287
214
|
function parseQualificationAudit(content) {
|
|
288
|
-
|
|
289
|
-
|
|
215
|
+
const auditBody = sectionBody(content, AUDIT_HEADINGS);
|
|
216
|
+
if (auditBody === null)
|
|
217
|
+
return { ok: true, audit: { present: false, constraintDependencies: [], candidateQualifications: [], classifications: [], snapshot: null } };
|
|
290
218
|
try {
|
|
219
|
+
if (sectionBody(auditBody, ['上游关系', 'Upstream Relations'], 3) !== null) {
|
|
220
|
+
throw new Error('qualification audit uses the obsolete lifecycle-relation format; regenerate it using the current format');
|
|
221
|
+
}
|
|
291
222
|
const dependencies = parseAuditTable(content, '约束依赖', DEPENDENCY_COLUMNS);
|
|
292
223
|
const qualifications = parseAuditTable(content, '候选资格', QUALIFICATION_COLUMNS);
|
|
293
224
|
const classifications = parseAuditTable(content, '分类结果', CLASSIFICATION_COLUMNS);
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
throw new Error('qualification audit requires all five canonical tables and exactly one dependency snapshot');
|
|
225
|
+
const snapshots = parseAuditTable(content, '资格快照', SNAPSHOT_COLUMNS);
|
|
226
|
+
if (!dependencies || !qualifications || !classifications || !snapshots || snapshots.length !== 1) {
|
|
227
|
+
throw new Error('qualification audit requires three decision tables and exactly one qualification snapshot');
|
|
298
228
|
}
|
|
299
229
|
const constraintDependencies = dependencies.map((row) => {
|
|
300
230
|
if (!/^C-[1-9]\d*$/.test(row.constraint_id ?? '') || !/^[a-f0-9]{64}$/i.test(row.constraint_digest ?? '') || !ROLES.includes(row.role) || !row.evidence?.trim())
|
|
@@ -311,20 +241,15 @@ function parseQualificationAudit(content) {
|
|
|
311
241
|
throw new Error(`invalid qualification classification '${row.decision_id ?? ''}'`);
|
|
312
242
|
return { decisionId: row.decision_id, classification: row.classification, evidence: row.evidence };
|
|
313
243
|
});
|
|
314
|
-
const upstreamRelations = relations.map((row) => {
|
|
315
|
-
const relation = { upstreamFamily: row.upstream_family, upstreamArtifact: row.upstream_artifact ?? '', upstreamRound: Number(row.upstream_round), upstreamSha256: row.upstream_sha256 ?? '', relation: row.relation };
|
|
316
|
-
validateUpstreamRelation(relation);
|
|
317
|
-
return relation;
|
|
318
|
-
});
|
|
319
244
|
const snapshotRow = snapshots[0];
|
|
320
|
-
if (!/^[a-f0-9]{64}$/i.test(snapshotRow.task_input_digest ?? '') || !/^[a-f0-9]{64}$/i.test(snapshotRow.non_constraint_input_digest ?? '')
|
|
321
|
-
throw new Error('qualification
|
|
245
|
+
if (!/^[a-f0-9]{64}$/i.test(snapshotRow.task_input_digest ?? '') || !/^[a-f0-9]{64}$/i.test(snapshotRow.non_constraint_input_digest ?? ''))
|
|
246
|
+
throw new Error('qualification snapshot has invalid digest');
|
|
322
247
|
return {
|
|
323
248
|
ok: true,
|
|
324
249
|
audit: {
|
|
325
250
|
present: true, constraintDependencies, candidateQualifications,
|
|
326
|
-
classifications: classificationRows,
|
|
327
|
-
snapshot: { taskInputDigest: snapshotRow.task_input_digest, nonConstraintInputDigest: snapshotRow.non_constraint_input_digest
|
|
251
|
+
classifications: classificationRows,
|
|
252
|
+
snapshot: { taskInputDigest: snapshotRow.task_input_digest, nonConstraintInputDigest: snapshotRow.non_constraint_input_digest }
|
|
328
253
|
}
|
|
329
254
|
};
|
|
330
255
|
}
|
|
@@ -367,7 +292,7 @@ function validateQualificationAudit(taskContent, artifactContent, options = {})
|
|
|
367
292
|
const audit = parseQualificationAudit(artifactContent);
|
|
368
293
|
if (!audit.ok)
|
|
369
294
|
return audit;
|
|
370
|
-
if (!
|
|
295
|
+
if (!audit.audit.present && !options.require)
|
|
371
296
|
return { ok: true, qualification: task.qualification, audit: audit.audit };
|
|
372
297
|
if (!task.qualification.present)
|
|
373
298
|
return { ok: false, code: 'QUALIFICATION_TASK_CONTRACT_MISSING', message: 'task qualification contract is missing' };
|
|
@@ -424,13 +349,8 @@ function validateQualificationAudit(taskContent, artifactContent, options = {})
|
|
|
424
349
|
return { ok: false, code: 'QUALIFICATION_TASK_DIGEST_MISMATCH', message: 'qualification audit task input digest is stale' };
|
|
425
350
|
if (snapshot.nonConstraintInputDigest !== task.qualification.nonConstraintInputDigest)
|
|
426
351
|
return { ok: false, code: 'QUALIFICATION_NON_CONSTRAINT_DIGEST_MISMATCH', message: 'qualification audit non-constraint input digest is stale' };
|
|
427
|
-
if (snapshot.upstreamArtifactDigest !== upstreamArtifactDigest(audit.audit.upstreamRelations))
|
|
428
|
-
return { ok: false, code: 'QUALIFICATION_UPSTREAM_DIGEST_MISMATCH', message: 'qualification audit upstream digest does not match its relation rows' };
|
|
429
|
-
if (options.expectedUpstreamRelations && (options.expectedUpstreamRelations.length !== audit.audit.upstreamRelations.length
|
|
430
|
-
|| upstreamArtifactDigest(options.expectedUpstreamRelations) !== upstreamArtifactDigest(audit.audit.upstreamRelations)))
|
|
431
|
-
return { ok: false, code: 'QUALIFICATION_UPSTREAM_RELATION_MISMATCH', message: 'qualification audit upstream relations do not match the started artifact inputs' };
|
|
432
352
|
if (options.family && options.artifact) {
|
|
433
|
-
const identity =
|
|
353
|
+
const identity = parseArtifactName(options.artifact);
|
|
434
354
|
if (!identity || identity.family !== options.family)
|
|
435
355
|
return { ok: false, code: 'QUALIFICATION_ARTIFACT_IDENTITY_INVALID', message: `artifact '${options.artifact}' is not canonical for '${options.family}'` };
|
|
436
356
|
}
|
|
@@ -443,13 +363,12 @@ function renderQualificationAudit(audit) {
|
|
|
443
363
|
...rows.map((row) => `| ${row.map((value) => value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' ')).join(' | ')} |`)
|
|
444
364
|
].join('\n');
|
|
445
365
|
const list = (values) => values.join(',');
|
|
446
|
-
const snapshot = audit.snapshot ?? { taskInputDigest: '', nonConstraintInputDigest: ''
|
|
366
|
+
const snapshot = audit.snapshot ?? { taskInputDigest: '', nonConstraintInputDigest: '' };
|
|
447
367
|
return [
|
|
448
368
|
'### 约束依赖', '', table(DEPENDENCY_COLUMNS, audit.constraintDependencies.map((row) => [row.constraintId, row.constraintDigest, row.role, row.evidence])), '',
|
|
449
369
|
'### 候选资格', '', table(QUALIFICATION_COLUMNS, audit.candidateQualifications.map((row) => [row.candidateId, row.status, row.impact, list(row.constraintIds), row.evidence])), '',
|
|
450
370
|
'### 分类结果', '', table(CLASSIFICATION_COLUMNS, audit.classifications.map((row) => [row.decisionId, row.classification, row.evidence])), '',
|
|
451
|
-
'###
|
|
452
|
-
'### 依赖快照', '', table(SNAPSHOT_COLUMNS, [[snapshot.taskInputDigest, snapshot.nonConstraintInputDigest, snapshot.upstreamArtifactDigest]])
|
|
371
|
+
'### 资格快照', '', table(SNAPSHOT_COLUMNS, [[snapshot.taskInputDigest, snapshot.nonConstraintInputDigest]])
|
|
453
372
|
].join('\n');
|
|
454
373
|
}
|
|
455
374
|
function buildQualificationAudit(taskContent, input = {}) {
|
|
@@ -462,15 +381,14 @@ function buildQualificationAudit(taskContent, input = {}) {
|
|
|
462
381
|
const candidates = input.candidates ?? parsed.qualification.candidates.map((row) => row.candidateId);
|
|
463
382
|
const constraintDependencies = parsed.qualification.constraints.filter((row) => ids.has(row.constraintId)).map((row) => ({ constraintId: row.constraintId, constraintDigest: row.digest, role: 'required', evidence: row.evidence }));
|
|
464
383
|
const candidateQualifications = parsed.qualification.candidates.filter((row) => candidates.includes(row.candidateId)).map((row) => ({ candidateId: row.candidateId, status: row.status, impact: row.impact, constraintIds: row.constraintIds, evidence: row.evidence }));
|
|
465
|
-
const upstreamRelations = [...(input.upstreamRelations ?? [])];
|
|
466
384
|
return {
|
|
467
385
|
ok: true,
|
|
468
386
|
audit: {
|
|
469
387
|
present: true, constraintDependencies, candidateQualifications,
|
|
470
|
-
classifications: input.classifications ?? [],
|
|
471
|
-
snapshot: { taskInputDigest: parsed.qualification.taskInputDigest, nonConstraintInputDigest: parsed.qualification.nonConstraintInputDigest
|
|
388
|
+
classifications: input.classifications ?? [],
|
|
389
|
+
snapshot: { taskInputDigest: parsed.qualification.taskInputDigest, nonConstraintInputDigest: parsed.qualification.nonConstraintInputDigest }
|
|
472
390
|
}
|
|
473
391
|
};
|
|
474
392
|
}
|
|
475
|
-
export { ARTIFACT_FAMILIES, AUDIT_HEADINGS, AUDIT_SUBSECTIONS, CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, DEPENDENCY_COLUMNS, QUALIFICATION_COLUMNS, CLASSIFICATION_COLUMNS,
|
|
393
|
+
export { ARTIFACT_FAMILIES, AUDIT_HEADINGS, AUDIT_SUBSECTIONS, CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, DEPENDENCY_COLUMNS, QUALIFICATION_COLUMNS, CLASSIFICATION_COLUMNS, SNAPSHOT_COLUMNS, constraintDigest, buildQualificationAudit, nonConstraintInputDigest, parseQualificationAudit, parseQualificationConfirmations, parseTaskQualification, renderQualificationAudit, taskInputDigest, validateQualificationAudit };
|
|
476
394
|
//# sourceMappingURL=qualification-audit.js.map
|
|
@@ -10,6 +10,7 @@ import { resolveTaskRef } from './resolve-ref.js';
|
|
|
10
10
|
import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
11
11
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
12
12
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
13
|
+
import { validateQualificationAudit } from './qualification-audit.js';
|
|
13
14
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
14
15
|
const STAGES = {
|
|
15
16
|
analysis: { family: 'review-analysis' },
|
|
@@ -186,6 +187,12 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
186
187
|
artifactSha256, semanticDigest: structure.semanticDigest
|
|
187
188
|
});
|
|
188
189
|
}
|
|
190
|
+
const qualification = validateQualificationAudit(taskContent, artifactContent, { family: spec.family, artifact: request.artifact });
|
|
191
|
+
if (!qualification.ok) {
|
|
192
|
+
return reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', `${qualification.code}: ${qualification.message}`, {
|
|
193
|
+
artifactSha256, semanticDigest
|
|
194
|
+
});
|
|
195
|
+
}
|
|
189
196
|
const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
|
|
190
197
|
refreshNumericCounts: true
|
|
191
198
|
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { resolveBranchWorktree } from '../git/branch-worktree.js';
|
|
5
|
+
import { snapshotReview } from '../git/review-snapshot.js';
|
|
6
|
+
import { resolveDeliveryTarget } from './delivery-target.js';
|
|
7
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
8
|
+
import { extractReviewDiffBase, extractReviewDiffFingerprint, extractReviewedHead, extractReviewTargetHead, extractReviewedSnapshotTree, loadPostReviewConfig, resolvePostReviewGlobs } from './review-fingerprint.js';
|
|
9
|
+
function git(cwd, args) {
|
|
10
|
+
return execFileSync('git', ['-C', cwd, ...args], { encoding: 'utf8' }).trim();
|
|
11
|
+
}
|
|
12
|
+
function result(status, message, reviewedHead = null, currentHead = null) {
|
|
13
|
+
return { status, message, reviewedHead, currentHead };
|
|
14
|
+
}
|
|
15
|
+
export function inspectReviewIdentity(taskDir, reportContent, repositoryRoot) {
|
|
16
|
+
const reviewedHead = extractReviewedHead(reportContent);
|
|
17
|
+
const targetHead = extractReviewTargetHead(reportContent);
|
|
18
|
+
const diffBase = extractReviewDiffBase(reportContent);
|
|
19
|
+
const fingerprint = extractReviewDiffFingerprint(reportContent);
|
|
20
|
+
const reviewedTree = extractReviewedSnapshotTree(reportContent);
|
|
21
|
+
if (!reviewedHead || !targetHead || !diffBase || !fingerprint || !reviewedTree) {
|
|
22
|
+
return result('indeterminate', 'Review report is missing commit or snapshot identity', reviewedHead || null);
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const metadata = parseTypedTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
|
|
26
|
+
const taskRepositoryRoot = git(taskDir, ['rev-parse', '--show-toplevel']);
|
|
27
|
+
const branch = String(metadata.branch ?? '').trim();
|
|
28
|
+
const gitRoot = branch ? resolveBranchWorktree(taskRepositoryRoot, branch) : taskRepositoryRoot;
|
|
29
|
+
if (!gitRoot)
|
|
30
|
+
return result('indeterminate', `Task branch '${branch}' is not checked out in a registered worktree`, reviewedHead);
|
|
31
|
+
const currentHead = git(gitRoot, ['rev-parse', 'HEAD']);
|
|
32
|
+
const baseline = git(gitRoot, ['rev-parse', `${reviewedHead}^{commit}`]);
|
|
33
|
+
if (baseline !== currentHead)
|
|
34
|
+
return result('different', 'Reviewed commit differs from current HEAD', baseline, currentHead);
|
|
35
|
+
const remote = String(metadata.delivery_remote ?? '').trim();
|
|
36
|
+
const baseRef = String(metadata.delivery_base_ref ?? '').trim();
|
|
37
|
+
if (!remote || !baseRef)
|
|
38
|
+
return result('indeterminate', 'Task delivery target is not bound', baseline, currentHead);
|
|
39
|
+
const target = resolveDeliveryTarget(gitRoot, { remote, baseRef });
|
|
40
|
+
if (!target.ok)
|
|
41
|
+
return result('indeterminate', target.message, baseline, currentHead);
|
|
42
|
+
const currentTarget = git(gitRoot, ['rev-parse', `${targetHead}^{commit}`]);
|
|
43
|
+
const computedDiffBase = git(gitRoot, ['merge-base', baseline, currentTarget]);
|
|
44
|
+
if (git(gitRoot, ['rev-parse', `${diffBase}^{commit}`]) !== computedDiffBase) {
|
|
45
|
+
return result('different', 'Saved review diff base no longer matches the current target', baseline, currentHead);
|
|
46
|
+
}
|
|
47
|
+
const globs = resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot ?? taskRepositoryRoot));
|
|
48
|
+
const trackedChanges = git(gitRoot, ['diff', '--name-only', '-z', baseline, '--', ...globs]);
|
|
49
|
+
const untrackedChanges = git(gitRoot, ['ls-files', '-o', '--exclude-standard', '-z', '--', ...globs]);
|
|
50
|
+
if (trackedChanges || untrackedChanges)
|
|
51
|
+
return result('different', 'Reviewed worktree contains uncommitted changes', baseline, currentHead);
|
|
52
|
+
const snapshot = snapshotReview({
|
|
53
|
+
cwd: gitRoot,
|
|
54
|
+
mode: 'worktree',
|
|
55
|
+
baseline,
|
|
56
|
+
diffBase: computedDiffBase,
|
|
57
|
+
globs
|
|
58
|
+
});
|
|
59
|
+
if (snapshot.fingerprint !== fingerprint)
|
|
60
|
+
return result('different', 'Reviewed diff fingerprint differs from the current worktree', baseline, currentHead);
|
|
61
|
+
if (snapshot.tree !== reviewedTree)
|
|
62
|
+
return result('different', 'Reviewed snapshot tree differs from the current worktree', baseline, currentHead);
|
|
63
|
+
return result('matched', 'Review report matches the current commit and clean worktree', baseline, currentHead);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return result('indeterminate', error instanceof Error ? error.message : String(error), reviewedHead);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=review-identity.js.map
|
|
@@ -17,8 +17,8 @@ function parseTable(content, input) {
|
|
|
17
17
|
new Set(input.columns).size !== input.columns.length) {
|
|
18
18
|
throw new DocumentMutationError('MUTATION_INVALID', 'table columns are invalid');
|
|
19
19
|
}
|
|
20
|
-
const keyColumn = input.keyColumn
|
|
21
|
-
if (!input.columns.includes(keyColumn)) {
|
|
20
|
+
const keyColumn = input.keyColumn === undefined ? input.columns[0] : input.keyColumn;
|
|
21
|
+
if (keyColumn !== null && !input.columns.includes(keyColumn)) {
|
|
22
22
|
throw new DocumentMutationError('MUTATION_INVALID', 'table key column is invalid');
|
|
23
23
|
}
|
|
24
24
|
const sections = matchingSections(content, input.sectionAliases);
|
|
@@ -68,11 +68,12 @@ function parseTable(content, input) {
|
|
|
68
68
|
column,
|
|
69
69
|
decodeCell(parsed.cells[columnIndex]).trim()
|
|
70
70
|
]));
|
|
71
|
-
const key = values[keyColumn];
|
|
72
|
-
if (seen.has(key)) {
|
|
71
|
+
const key = keyColumn === null ? null : values[keyColumn];
|
|
72
|
+
if (key !== null && seen.has(key)) {
|
|
73
73
|
throw new DocumentMutationError('TABLE_DUPLICATE_KEY', `duplicate table key '${key}'`);
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
if (key !== null)
|
|
76
|
+
seen.add(key);
|
|
76
77
|
rows.push({ values, sourceLine: index - Math.max(firstContent, 0) });
|
|
77
78
|
}
|
|
78
79
|
return { heading: section.heading, rows };
|
|
@@ -29,9 +29,12 @@ function mapLinkError(operation, error, key) {
|
|
|
29
29
|
}
|
|
30
30
|
return new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', `lifecycle lock hard-link operation '${operation}' failed`, detail);
|
|
31
31
|
}
|
|
32
|
-
function lockKey(repoRoot, taskId) {
|
|
32
|
+
function lockKey(repoRoot, taskId, identityScope) {
|
|
33
33
|
const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
|
|
34
|
-
const
|
|
34
|
+
const identity = identityScope === undefined
|
|
35
|
+
? `${canonicalRepoRoot}\0${taskId}`
|
|
36
|
+
: `${identityScope}\0${taskId}`;
|
|
37
|
+
const key = createHash('sha256').update(identity).digest('hex');
|
|
35
38
|
return { canonicalRepoRoot, key };
|
|
36
39
|
}
|
|
37
40
|
function parseOwner(raw, key) {
|
|
@@ -133,7 +136,7 @@ function cleanStaleCandidates(lockRoot, key, identityMatches) {
|
|
|
133
136
|
function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options = {}) {
|
|
134
137
|
let identity;
|
|
135
138
|
try {
|
|
136
|
-
identity = lockKey(repoRoot, taskId);
|
|
139
|
+
identity = lockKey(repoRoot, taskId, options.identityScope);
|
|
137
140
|
}
|
|
138
141
|
catch (error) {
|
|
139
142
|
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'failed to canonicalize the repository for lifecycle locking', { operation: 'canonicalize', errno: errno(error), key: 'unresolved' });
|
|
@@ -4,14 +4,13 @@ import path from "node:path";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import {
|
|
7
|
+
import { extractReviewTargetHead, extractReviewedHead, extractReviewDiffBase, findAuthoritativeReviewCodeArtifact, loadPostReviewConfig, parseReviewVerdict, resolvePostReviewGlobs } from "./verification-review.js";
|
|
8
8
|
import { check as checkPlatformSync } from "../platform/verification-sync.js";
|
|
9
9
|
import { check as checkRequiredChecks } from "../platform/verification-required.js";
|
|
10
10
|
import { inspectPlatformPullRequest } from "../platform/pull-requests.js";
|
|
11
11
|
import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.js";
|
|
12
12
|
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
|
|
13
13
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
|
|
14
|
-
import { resolveBranchWorktree } from "../git/branch-worktree.js";
|
|
15
14
|
import { parseTypedTaskFrontmatter } from "./frontmatter.js";
|
|
16
15
|
import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
|
|
17
16
|
import { isValidAgentInfraVersion } from "../version.js";
|
|
@@ -20,11 +19,9 @@ import { inspectDecisionDetailDuplicates } from "./decision-details.js";
|
|
|
20
19
|
import { parseImplementationInputs } from "./implementation-inputs.js";
|
|
21
20
|
import { scanVisibleMarkdown } from "./markdown.js";
|
|
22
21
|
import { loadVerificationConfig } from "./verification-config.js";
|
|
23
|
-
import { snapshotReview } from "../git/review-snapshot.js";
|
|
24
22
|
import { inspectActivityLog } from "./activity-log.js";
|
|
25
23
|
import { parseWorkflowWarnings } from "./workflow-warnings.js";
|
|
26
24
|
import { OrchestrationStateError, readRun } from "./orchestration.js";
|
|
27
|
-
import { resolveDeliveryTarget } from "./delivery-target.js";
|
|
28
25
|
import { readPrDeliveryFact } from "./pr-delivery-fact.js";
|
|
29
26
|
import { validateLocalArtifact } from "./local-artifact-finalization.js";
|
|
30
27
|
import { getArtifactSchema } from "./artifact-schema.js";
|
|
@@ -35,6 +32,8 @@ import { readManualValidationCompletion } from "./manual-validation-completion.j
|
|
|
35
32
|
import { sha256File } from "./artifact-receipts.js";
|
|
36
33
|
import { summaryCommentState } from "../platform/pr-summary.js";
|
|
37
34
|
import { taskIssueIdentity } from "../platform/task-identities.js";
|
|
35
|
+
import { hasArtifactCompletionFact, hasArtifactCompletionLog } from './completion-facts.js';
|
|
36
|
+
import { inspectReviewIdentity } from './review-identity.js';
|
|
38
37
|
const TASK_ENUMS = {
|
|
39
38
|
type: ["feature", "bugfix", "refactor", "docs", "chore"],
|
|
40
39
|
workflow: ["feature-development", "bug-fix", "refactoring"],
|
|
@@ -1014,12 +1013,16 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
|
|
|
1014
1013
|
return failResult("review-fact", task.message);
|
|
1015
1014
|
}
|
|
1016
1015
|
const content = fs.readFileSync(resolvedArtifact.path, "utf8");
|
|
1016
|
+
if (!hasArtifactCompletionFact(task.content, resolvedArtifact.path, 'review-code.completed')) {
|
|
1017
|
+
return failResult("review-fact", `Review Code completion fact does not match ${path.basename(resolvedArtifact.path)}`);
|
|
1018
|
+
}
|
|
1019
|
+
if (!hasArtifactCompletionLog(task.content, path.basename(resolvedArtifact.path), 'review-code.completed')) {
|
|
1020
|
+
return failResult("review-fact", `Review Code completion entry does not reference ${path.basename(resolvedArtifact.path)}`);
|
|
1021
|
+
}
|
|
1017
1022
|
const verdict = parseReviewVerdict(content);
|
|
1018
1023
|
const reviewTargetHead = extractReviewTargetHead(content);
|
|
1019
1024
|
const reviewReviewedHead = extractReviewedHead(content);
|
|
1020
1025
|
const reviewDiffBase = extractReviewDiffBase(content);
|
|
1021
|
-
const reviewedFingerprint = extractReviewDiffFingerprint(content);
|
|
1022
|
-
const reviewedTree = extractReviewedSnapshotTree(content);
|
|
1023
1026
|
if (!["通过", "需要修改", "拒绝", "Approved", "Changes Requested", "Rejected"].includes(verdict)) {
|
|
1024
1027
|
return failResult("review-fact", `Unsupported review verdict '${verdict}'`);
|
|
1025
1028
|
}
|
|
@@ -1031,71 +1034,19 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
|
|
|
1031
1034
|
if (!reviewTargetHead || !reviewReviewedHead || !reviewDiffBase) {
|
|
1032
1035
|
return failResult("review-fact", "Review fact must record target head, reviewed head, and diff base");
|
|
1033
1036
|
}
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
let targetHead;
|
|
1038
|
-
let diffBase;
|
|
1039
|
-
try {
|
|
1040
|
-
const taskRepositoryRoot = execFileSync("git", ["-C", taskDir, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
1041
|
-
gitRoot = reviewWorktreeForTask(taskRepositoryRoot, String(task.metadata.branch || "").trim()) ?? taskRepositoryRoot;
|
|
1042
|
-
head = execFileSync("git", ["-C", gitRoot, "rev-parse", "HEAD"], { encoding: "utf8" }).trim();
|
|
1043
|
-
const target = resolveDeliveryTarget(gitRoot, { remote: deliveryRemote, baseRef: deliveryBaseRef });
|
|
1044
|
-
if (!target.ok)
|
|
1045
|
-
throw new Error(target.message);
|
|
1046
|
-
const reviewedRef = reviewReviewedHead;
|
|
1047
|
-
baseline = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewedRef}^{commit}`], { encoding: "utf8" }).trim();
|
|
1048
|
-
targetHead = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewTargetHead}^{commit}`], { encoding: "utf8" }).trim();
|
|
1049
|
-
const computed = execFileSync("git", ["-C", gitRoot, "merge-base", baseline, targetHead], { encoding: "utf8" }).trim();
|
|
1050
|
-
diffBase = execFileSync("git", ["-C", gitRoot, "rev-parse", `${reviewDiffBase}^{commit}`], { encoding: "utf8" }).trim();
|
|
1051
|
-
if (diffBase !== computed)
|
|
1052
|
-
throw new Error('saved review diff base does not match merge-base(reviewed head, target head)');
|
|
1053
|
-
}
|
|
1054
|
-
catch {
|
|
1055
|
-
return blockedResult("review-fact", `Unable to resolve saved review commits in the task repository; re-run review-code`);
|
|
1056
|
-
}
|
|
1057
|
-
if (baseline !== head) {
|
|
1058
|
-
return failResult("review-fact", `Reviewed head ${baseline.slice(0, 8)} does not match current HEAD ${head.slice(0, 8)}; re-run review-code`);
|
|
1059
|
-
}
|
|
1060
|
-
let actualSnapshot;
|
|
1061
|
-
try {
|
|
1062
|
-
actualSnapshot = snapshotReview({
|
|
1063
|
-
cwd: gitRoot,
|
|
1064
|
-
mode: "worktree",
|
|
1065
|
-
baseline,
|
|
1066
|
-
diffBase,
|
|
1067
|
-
globs: resolvePostReviewGlobs({}, loadPostReviewConfig(repositoryRoot))
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
catch {
|
|
1071
|
-
return blockedResult("review-fact", `Unable to recompute reviewed diff fingerprint from baseline ${baseline.slice(0, 8)}; re-run review-code`);
|
|
1072
|
-
}
|
|
1073
|
-
if (actualSnapshot.fingerprint !== reviewedFingerprint) {
|
|
1074
|
-
return failResult("review-fact", `Reviewed diff fingerprint does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
|
|
1075
|
-
}
|
|
1076
|
-
if (!reviewedTree || actualSnapshot.tree !== reviewedTree) {
|
|
1077
|
-
return failResult("review-fact", `Reviewed snapshot tree does not match the current worktree for baseline ${baseline.slice(0, 8)}; re-run review-code`);
|
|
1037
|
+
const identity = inspectReviewIdentity(taskDir, content, repositoryRoot);
|
|
1038
|
+
if (identity.status !== 'matched' || !identity.reviewedHead) {
|
|
1039
|
+
return failResult('review-fact', `Review identity is ${identity.status}: ${identity.message}; re-run review-code`);
|
|
1078
1040
|
}
|
|
1041
|
+
const baseline = identity.reviewedHead;
|
|
1079
1042
|
if (["通过", "Approved"].includes(verdict)) {
|
|
1080
1043
|
const lastReviewedCommit = String(task.metadata.last_reviewed_commit || "").trim();
|
|
1081
|
-
|
|
1082
|
-
if (cleanSnapshot && lastReviewedCommit !== baseline) {
|
|
1044
|
+
if (lastReviewedCommit !== baseline) {
|
|
1083
1045
|
return failResult("review-fact", `Approved clean review must set task last_reviewed_commit to reviewed head ${baseline.slice(0, 8)}`);
|
|
1084
1046
|
}
|
|
1085
|
-
if (!cleanSnapshot && lastReviewedCommit) {
|
|
1086
|
-
return failResult("review-fact", "Approved review with uncommitted changes must remain unanchored until commit");
|
|
1087
|
-
}
|
|
1088
1047
|
}
|
|
1089
1048
|
return passResult("review-fact", `Review fact valid for ${path.basename(resolvedArtifact.path)} at ${baseline.slice(0, 8)}`);
|
|
1090
1049
|
}
|
|
1091
|
-
function reviewWorktreeForTask(repositoryRoot, branch) {
|
|
1092
|
-
if (!branch)
|
|
1093
|
-
return null;
|
|
1094
|
-
const worktree = resolveBranchWorktree(repositoryRoot, branch);
|
|
1095
|
-
if (worktree)
|
|
1096
|
-
return worktree;
|
|
1097
|
-
throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
|
|
1098
|
-
}
|
|
1099
1050
|
function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
|
|
1100
1051
|
if (SHA_PATTERN.test(lastReviewedCommit) && gitCommitExists(gitRoot, lastReviewedCommit)) {
|
|
1101
1052
|
return { ok: true, sha: lastReviewedCommit };
|
|
@@ -89,47 +89,44 @@ async function verifyTaskEvent(request, options = {}) {
|
|
|
89
89
|
function statusLabel(status) {
|
|
90
90
|
return status === 'fail' ? 'FAIL' : status === 'blocked' ? 'BLOCKED' : 'pass';
|
|
91
91
|
}
|
|
92
|
-
function renderPayload(payload) {
|
|
93
|
-
const lines = [];
|
|
94
|
-
if (Array.isArray(payload.checks)) {
|
|
95
|
-
const gateMode = typeof payload.gate === 'string';
|
|
96
|
-
lines.push(gateMode
|
|
97
|
-
? `Verification: ${payload.gate} | Skill: ${payload.skill}`
|
|
98
|
-
: `Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
|
|
99
|
-
for (const check of payload.checks) {
|
|
100
|
-
const raw = statusLabel(check.status);
|
|
101
|
-
const effective = statusLabel(check.effectiveStatus ?? check.status);
|
|
102
|
-
const reason = check.reason ?? 'CHECK_FAILED';
|
|
103
|
-
const message = check.message ?? reason;
|
|
104
|
-
lines.push(` [${effective}] ${check.checkId ?? check.type} (${check.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${message}; ${check.action ?? 'Review validation output'}`);
|
|
105
|
-
}
|
|
106
|
-
const summary = payload.summary ?? (() => {
|
|
107
|
-
const checks = payload.checks;
|
|
108
|
-
const statuses = checks.map((check) => check.effectiveStatus ?? check.status);
|
|
109
|
-
return `${statuses.filter((status) => status === 'pass').length} passed, ${statuses.filter((status) => status === 'fail').length} failed${statuses.includes('blocked') ? `, ${statuses.filter((status) => status === 'blocked').length} blocked` : ''}`;
|
|
110
|
-
})();
|
|
111
|
-
lines.push('', `Result: ${summary} - ${payload.action}`);
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
lines.push(`Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
|
|
115
|
-
const raw = statusLabel(payload.status);
|
|
116
|
-
const effective = statusLabel(payload.effectiveStatus ?? payload.status);
|
|
117
|
-
const reason = payload.reason ?? 'CHECK_FAILED';
|
|
118
|
-
lines.push(` [${effective}] ${payload.checkId ?? payload.type} (${payload.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${payload.message ?? reason}; ${payload.action ?? 'Review validation output'}`);
|
|
119
|
-
const status = payload.effectiveStatus ?? payload.status;
|
|
120
|
-
const summary = status === 'pass' ? '1 passed, 0 failed'
|
|
121
|
-
: status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
|
|
122
|
-
const action = status === 'pass' ? 'Requested check passed'
|
|
123
|
-
: status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
|
|
124
|
-
: `Fix ${payload.type} issues and re-run check`;
|
|
125
|
-
lines.push('', `Result: ${summary} - ${action}`);
|
|
126
|
-
}
|
|
127
|
-
return `${lines.join('\n')}\n`;
|
|
128
|
-
}
|
|
129
92
|
function renderTaskVerification(result) {
|
|
130
93
|
if (result.status === 'failed')
|
|
131
94
|
return `Verification failed: ${result.error?.code ?? 'VERIFY_FAILED'} - ${result.error?.message ?? 'unknown error'}\n`;
|
|
132
|
-
|
|
95
|
+
const target = `${result.event}${result.artifact ? ` (${result.artifact})` : ''}`;
|
|
96
|
+
const lines = [`Verification: ${result.status} | Target: ${target} | Skill: ${result.skill}`];
|
|
97
|
+
const checks = result.invocations.flatMap(({ payload }) => {
|
|
98
|
+
if (Array.isArray(payload.checks))
|
|
99
|
+
return payload.checks;
|
|
100
|
+
return [payload];
|
|
101
|
+
});
|
|
102
|
+
const counts = {
|
|
103
|
+
pass: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'pass').length,
|
|
104
|
+
fail: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'fail').length,
|
|
105
|
+
blocked: checks.filter((check) => (check.effectiveStatus ?? check.status) === 'blocked').length
|
|
106
|
+
};
|
|
107
|
+
const summary = `${counts.pass} passed, ${counts.fail} failed${counts.blocked > 0 ? `, ${counts.blocked} blocked` : ''}`;
|
|
108
|
+
lines.push('', `Result: ${summary}`);
|
|
109
|
+
for (const check of checks) {
|
|
110
|
+
const rawStatus = check.status;
|
|
111
|
+
const effectiveStatus = check.effectiveStatus ?? rawStatus;
|
|
112
|
+
const isSoftWarning = (rawStatus === 'fail' || rawStatus === 'blocked') && effectiveStatus === 'pass';
|
|
113
|
+
const reason = check.reason ?? 'CHECK_FAILED';
|
|
114
|
+
const message = check.message ?? reason;
|
|
115
|
+
const action = check.action ?? 'Review validation output';
|
|
116
|
+
const id = check.checkId ?? check.type;
|
|
117
|
+
const isHumanExemptionNotice = id === 'post-review-commit'
|
|
118
|
+
&& typeof message === 'string'
|
|
119
|
+
&& /\bhuman-decided (?:post-review )?exemption\b/i.test(message)
|
|
120
|
+
&& /\bPRC-\d+\s*:/.test(message);
|
|
121
|
+
if (effectiveStatus === 'pass' && !isSoftWarning && !isHumanExemptionNotice)
|
|
122
|
+
continue;
|
|
123
|
+
lines.push(isSoftWarning
|
|
124
|
+
? `Warning: ${id} (${rawStatus === 'blocked' ? `raw BLOCKED; ` : ''}${reason}) - ${message}; ${action}`
|
|
125
|
+
: isHumanExemptionNotice
|
|
126
|
+
? `Notice: ${id} - ${message}; ${action}`
|
|
127
|
+
: ` [${statusLabel(effectiveStatus)}] ${id} (${reason}) - ${message}; ${action}`);
|
|
128
|
+
}
|
|
129
|
+
return `${lines.join('\n')}\n`;
|
|
133
130
|
}
|
|
134
131
|
export { VERIFICATION_CATALOG, renderTaskVerification, verifyTaskEvent };
|
|
135
132
|
//# sourceMappingURL=verification.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
3
4
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
4
5
|
import { WORKFLOW_WARNING_COLUMNS, WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_SEVERITIES, WORKFLOW_WARNING_STATUSES, parseWorkflowWarnings } from './workflow-warnings.js';
|
|
@@ -120,7 +121,12 @@ function projectFinalizationWarning(taskRef, warning, options = {}) {
|
|
|
120
121
|
let prepared = resolved.state === 'completed';
|
|
121
122
|
if (!prepared && resolved.state === 'active') {
|
|
122
123
|
try {
|
|
123
|
-
|
|
124
|
+
const taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
125
|
+
prepared = parseTaskFrontmatter(taskContent).status === 'completed';
|
|
126
|
+
if (!prepared) {
|
|
127
|
+
const lifecycle = JSON.parse(fs.readFileSync(path.join(resolved.taskDir, '.task-lifecycle.json'), 'utf8'));
|
|
128
|
+
prepared = lifecycle.taskId === resolved.taskId && lifecycle.intent === 'complete';
|
|
129
|
+
}
|
|
124
130
|
}
|
|
125
131
|
catch {
|
|
126
132
|
prepared = false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.8",
|
|
4
|
+
"sourceInputHash": "6ddb72f8b50137d5148a47172bd7eb2628b17a937ae647ac38350e582ba08124",
|
|
5
|
+
"internalExecutableBuildHash": "c886e015506879620bb490f1ea5977d9952f2bbb4515d3a0b49e0ec184b48028"
|
|
6
6
|
}
|
|
@@ -54,7 +54,7 @@ const codexAdapter = defineAgentClientAdapter({
|
|
|
54
54
|
containerMount: '/home/devuser/.codex',
|
|
55
55
|
versionCmd: 'codex --version',
|
|
56
56
|
setupHint: 'Run codex once inside the container and choose Device Code login if needed.',
|
|
57
|
-
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'] },
|
|
57
|
+
tmpfs: { size: '512m', seed: ['config.toml', 'model-catalogs'], exec: true },
|
|
58
58
|
hostLiveMounts: [
|
|
59
59
|
{
|
|
60
60
|
hostPath: hostJoin(home, '.codex', 'auth.json'),
|