@fitlab-ai/agent-infra 0.11.0 → 0.11.1

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.
Files changed (80) hide show
  1. package/dist/lib/internal/manual-validation.js +6 -2
  2. package/dist/lib/platform/issues.js +3 -4
  3. package/dist/lib/platform/verification-sync.js +142 -154
  4. package/dist/lib/sandbox/control/workflow-executor.js +7 -1
  5. package/dist/lib/task/artifact-lifecycle.js +15 -52
  6. package/dist/lib/task/artifact-operations.js +27 -1
  7. package/dist/lib/task/capabilities.js +0 -2
  8. package/dist/lib/task/events.js +60 -43
  9. package/dist/lib/task/gate-policy.js +94 -0
  10. package/dist/lib/task/local-artifact-finalization.js +7 -4
  11. package/dist/lib/task/orchestration.js +8 -6
  12. package/dist/lib/task/review-artifacts.js +12 -6
  13. package/dist/lib/task/review-finalization.js +3 -9
  14. package/dist/lib/task/verification-engine.js +28 -17
  15. package/dist/lib/task/verification.js +25 -9
  16. package/dist/lifecycle-build-manifest.json +3 -3
  17. package/lib/internal/manual-validation.ts +6 -2
  18. package/lib/platform/issues.ts +3 -4
  19. package/lib/platform/verification-sync.ts +159 -177
  20. package/lib/sandbox/control/workflow-executor.ts +11 -1
  21. package/lib/task/artifact-lifecycle.ts +12 -49
  22. package/lib/task/artifact-operations.ts +27 -1
  23. package/lib/task/capabilities.ts +0 -1
  24. package/lib/task/events.ts +49 -32
  25. package/lib/task/gate-policy.ts +133 -0
  26. package/lib/task/local-artifact-finalization.ts +6 -4
  27. package/lib/task/orchestration.ts +12 -7
  28. package/lib/task/review-artifacts.ts +8 -2
  29. package/lib/task/review-finalization.ts +3 -9
  30. package/lib/task/verification-engine.ts +24 -14
  31. package/lib/task/verification-types.ts +7 -0
  32. package/lib/task/verification.ts +25 -9
  33. package/package.json +1 -1
  34. package/templates/.agents/rules/issue-sync.en.md +2 -0
  35. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
  36. package/templates/.agents/rules/local-artifact-repair.en.md +11 -3
  37. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +11 -3
  38. package/templates/.agents/skills/analyze-task/SKILL.en.md +2 -2
  39. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +2 -2
  40. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
  41. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
  42. package/templates/.agents/skills/block-task/config/verify.json +1 -3
  43. package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
  44. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  45. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  46. package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
  47. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
  48. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  49. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  50. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
  51. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
  52. package/templates/.agents/skills/commit/config/verify.json +1 -5
  53. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
  54. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
  55. package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
  56. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
  57. package/templates/.agents/skills/create-pr/config/verify.json +1 -10
  58. package/templates/.agents/skills/create-task/config/verify.json +1 -4
  59. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  60. package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
  61. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
  62. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
  63. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
  64. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
  65. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
  66. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
  67. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
  68. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
  69. package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
  70. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
  71. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
  72. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
  73. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
  74. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
  75. package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
  76. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
  77. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
  78. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
  79. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
  80. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
@@ -69,7 +69,6 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
69
69
  let taskContent;
70
70
  let content;
71
71
  let recovery;
72
- let existingIntent;
73
72
  try {
74
73
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
75
74
  if (request.recoveryId) {
@@ -89,7 +88,6 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
89
88
  return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
90
89
  content = fs.readFileSync(validated.artifact.path, 'utf8');
91
90
  const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
92
- existingIntent = intent;
93
91
  if (intent?.state === 'preflight-ready')
94
92
  recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
95
93
  if (intent?.state === 'consumed')
@@ -143,12 +141,6 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
143
141
  ...(recovery ? { recovery: recoveryInfo(recovery) } : {})
144
142
  });
145
143
  }
146
- if (!recovery && existingIntent?.state === 'passed') {
147
- return {
148
- ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
149
- status: 'passed', changed: false, error: null
150
- };
151
- }
152
144
  if (request.dryRun || recovery && readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact)?.state === 'preflight-ready') {
153
145
  return {
154
146
  ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, ...(recovery ? { recovery: recoveryInfo(recovery) } : {}) }),
@@ -316,7 +308,9 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
316
308
  ...(recovery ? { recovery } : {})
317
309
  };
318
310
  }
319
- const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
311
+ const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
312
+ refreshNumericCounts: Boolean(request.recoveryId && recovery)
313
+ });
320
314
  if (!transformed.ok)
321
315
  return reject(transformed.code, transformed.message);
322
316
  const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
@@ -29,6 +29,7 @@ import { validateLocalArtifact } from "./local-artifact-finalization.js";
29
29
  import { validateQualificationAudit } from "./qualification-audit.js";
30
30
  import { getArtifactSchema } from "./artifact-schema.js";
31
31
  import { inspectArtifactContract } from "./artifact-operations.js";
32
+ import { normalizeVerificationRecord } from './gate-policy.js';
32
33
  import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.js";
33
34
  import { readManualValidationCompletion } from "./manual-validation-completion.js";
34
35
  import { sha256File } from "./artifact-receipts.js";
@@ -1198,19 +1199,19 @@ function interpolate(template, taskDir, artifactFile) {
1198
1199
  .replace(/\{artifact-stem\}/g, artifactStem);
1199
1200
  }
1200
1201
  function summarizeGate(checks) {
1201
- if (checks.some((check) => check.status === "blocked")) {
1202
+ if (checks.some((check) => check.effectiveStatus === "blocked")) {
1202
1203
  return "blocked";
1203
1204
  }
1204
- if (checks.some((check) => check.status === "fail")) {
1205
+ if (checks.some((check) => check.effectiveStatus === "fail")) {
1205
1206
  return "fail";
1206
1207
  }
1207
1208
  return "pass";
1208
1209
  }
1209
1210
  function summarizeChecks(checks) {
1210
1211
  const counts = {
1211
- pass: checks.filter((check) => check.status === "pass").length,
1212
- fail: checks.filter((check) => check.status === "fail").length,
1213
- blocked: checks.filter((check) => check.status === "blocked").length
1212
+ pass: checks.filter((check) => check.effectiveStatus === "pass").length,
1213
+ fail: checks.filter((check) => check.effectiveStatus === "fail").length,
1214
+ blocked: checks.filter((check) => check.effectiveStatus === "blocked").length
1214
1215
  };
1215
1216
  if (counts.blocked > 0) {
1216
1217
  return `${counts.pass} passed, ${counts.fail} failed, ${counts.blocked} blocked`;
@@ -1221,7 +1222,7 @@ function buildAction(gate, checks) {
1221
1222
  if (gate === "pass") {
1222
1223
  return "All declared checks passed";
1223
1224
  }
1224
- const firstFailure = checks.find((check) => check.status !== "pass");
1225
+ const firstFailure = checks.find((check) => check.effectiveStatus !== "pass");
1225
1226
  if (!firstFailure) {
1226
1227
  return "Review validation output";
1227
1228
  }
@@ -1277,16 +1278,16 @@ async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks:
1277
1278
  for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
1278
1279
  if (checkConfig === null)
1279
1280
  continue;
1280
- const result = await runCheck(type, {
1281
+ const result = normalizeVerificationRecord(await runCheck(type, {
1281
1282
  mode,
1282
1283
  skillName,
1283
1284
  taskDir: path.resolve(taskDir),
1284
1285
  artifactFile,
1285
1286
  repositoryRoot: repoRoot,
1286
1287
  config: checkConfig
1287
- }, shared);
1288
- checks.push(result);
1289
- if (result.status === "blocked")
1288
+ }, shared));
1289
+ checks.push(...(result.subchecks?.map(normalizeVerificationRecord) ?? [result]));
1290
+ if (checks.at(-1)?.effectiveStatus === "blocked")
1290
1291
  break;
1291
1292
  }
1292
1293
  const gate = summarizeGate(checks);
@@ -1298,15 +1299,25 @@ async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks:
1298
1299
  return { skill: skillName, ...failResult(type, `Unknown check type '${type}' for skill '${skillName}'.`) };
1299
1300
  if (config === null)
1300
1301
  return { skill: skillName, ...passResult(type, `Check '${type}' is disabled for skill '${skillName}'.`) };
1302
+ const result = normalizeVerificationRecord(await runCheck(type, {
1303
+ skillName,
1304
+ taskDir: path.resolve(taskDir),
1305
+ artifactFile,
1306
+ repositoryRoot: repoRoot,
1307
+ config
1308
+ }, shared));
1309
+ if (!result.subchecks?.length)
1310
+ return { skill: skillName, ...result };
1311
+ const checks = result.subchecks.map(normalizeVerificationRecord);
1312
+ const gate = summarizeGate(checks);
1301
1313
  return {
1302
1314
  skill: skillName,
1303
- ...await runCheck(type, {
1304
- skillName,
1305
- taskDir: path.resolve(taskDir),
1306
- artifactFile,
1307
- repositoryRoot: repoRoot,
1308
- config
1309
- }, shared)
1315
+ ...result,
1316
+ status: gate,
1317
+ effectiveStatus: gate,
1318
+ reason: gate === 'pass' ? 'OK' : checks.find((check) => check.effectiveStatus !== 'pass')?.reason ?? 'CHECK_FAILED',
1319
+ action: buildAction(gate, checks),
1320
+ checks
1310
1321
  };
1311
1322
  }
1312
1323
  export { verifyInProcess };
@@ -80,7 +80,7 @@ async function verifyTaskEvent(request, options = {}) {
80
80
  else {
81
81
  for (const check of spec.checks ?? []) {
82
82
  const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
83
- const status = payload.status;
83
+ const status = (payload.effectiveStatus ?? payload.status);
84
84
  if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
85
85
  invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
86
86
  break;
@@ -103,19 +103,35 @@ function statusLabel(status) {
103
103
  function renderPayload(payload) {
104
104
  const lines = [];
105
105
  if (Array.isArray(payload.checks)) {
106
- lines.push(`Verification: ${payload.gate} | Skill: ${payload.skill}`, '');
106
+ const gateMode = typeof payload.gate === 'string';
107
+ lines.push(gateMode
108
+ ? `Verification: ${payload.gate} | Skill: ${payload.skill}`
109
+ : `Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
107
110
  for (const check of payload.checks) {
108
- lines.push(` [${statusLabel(check.status)}] ${check.type} - ${check.message}`);
111
+ const raw = statusLabel(check.status);
112
+ const effective = statusLabel(check.effectiveStatus ?? check.status);
113
+ const reason = check.reason ?? 'CHECK_FAILED';
114
+ const message = check.message ?? reason;
115
+ lines.push(` [${effective}] ${check.checkId ?? check.type} (${check.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${message}; ${check.action ?? 'Review validation output'}`);
109
116
  }
110
- lines.push('', `Result: ${payload.summary} - ${payload.action}`);
117
+ const summary = payload.summary ?? (() => {
118
+ const checks = payload.checks;
119
+ const statuses = checks.map((check) => check.effectiveStatus ?? check.status);
120
+ 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` : ''}`;
121
+ })();
122
+ lines.push('', `Result: ${summary} - ${payload.action}`);
111
123
  }
112
124
  else {
113
125
  lines.push(`Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
114
- lines.push(` [${statusLabel(payload.status)}] ${payload.type} - ${payload.message}`);
115
- const summary = payload.status === 'pass' ? '1 passed, 0 failed'
116
- : payload.status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
117
- const action = payload.status === 'pass' ? 'Requested check passed'
118
- : payload.status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
126
+ const raw = statusLabel(payload.status);
127
+ const effective = statusLabel(payload.effectiveStatus ?? payload.status);
128
+ const reason = payload.reason ?? 'CHECK_FAILED';
129
+ lines.push(` [${effective}] ${payload.checkId ?? payload.type} (${payload.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${payload.message ?? reason}; ${payload.action ?? 'Review validation output'}`);
130
+ const status = payload.effectiveStatus ?? payload.status;
131
+ const summary = status === 'pass' ? '1 passed, 0 failed'
132
+ : status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
133
+ const action = status === 'pass' ? 'Requested check passed'
134
+ : status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
119
135
  : `Fix ${payload.type} issues and re-run check`;
120
136
  lines.push('', `Result: ${summary} - ${action}`);
121
137
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.0",
4
- "sourceInputHash": "c67e66a46040d5246d67c751af605b796cd61913b98add01ff327829c01ce3db",
5
- "internalExecutableBuildHash": "f6b4be06df12111b3a7614d335c9e802049c3cea194c6e7c040f3251188c694f"
3
+ "packageVersion": "0.11.1",
4
+ "sourceInputHash": "0d66fb32bbdc0982d38a04b86b0278768bb6091eb945ebd90a432f9bae363712",
5
+ "internalExecutableBuildHash": "4b1edd20ed87d3d742e5e7975fd49b1ac1fb0ff14f837d52ee9ba3ab11318365"
6
6
  }
@@ -44,6 +44,7 @@ function printFailure(format: OutputFormat, error: { code: string; message: stri
44
44
  }
45
45
 
46
46
  const MANUAL_VALIDATION_STATUS_SECTION = /^###\s+(?:⚠️\s+(?:需人工校验|Manual Validation Required)|✅\s+(?:人工验证已通过|无需人工校验|Manual Validation Passed|No Manual Validation Required)|⏳\s+(?:人工验证待收尾|Manual Validation Pending))\s*$[\s\S]*?(?=^#{1,3}\s|(?![\s\S]))/gmu;
47
+ const TRAILING_CANONICAL_REPORT_PLACEHOLDER = /\n*<!--\s*canonical-pr-change-report\s*-->\s*$/u;
47
48
 
48
49
  function manualSummaryBody(body: string, phase: 'pending' | 'final', transactionId: string, receiptDigest = '', evidenceDigest = '', prHeadSha = ''): string {
49
50
  const chinese = /###\s+(?:⚠️\s+需人工校验|✅\s+(?:人工验证已通过|无需人工校验)|⏳\s+人工验证待收尾)\s*$/mu.test(body);
@@ -55,10 +56,13 @@ function manualSummaryBody(body: string, phase: 'pending' | 'final', transaction
55
56
  ? `### ✅ 人工验证已通过\n\n人工验证已通过;transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`
56
57
  : `### ✅ Manual Validation Passed\n\nManual validation passed; transaction=${transactionId}; receipt=${receiptDigest}; evidence=${evidenceDigest}; head=${prHeadSha}.`;
57
58
  let inserted = false;
58
- const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, () => {
59
+ const updated = body.replace(MANUAL_VALIDATION_STATUS_SECTION, (matched) => {
59
60
  if (inserted) return '';
60
61
  inserted = true;
61
- return `${section}\n\n`;
62
+ const trailingPlaceholder = TRAILING_CANONICAL_REPORT_PLACEHOLDER.exec(matched)?.[0];
63
+ return trailingPlaceholder
64
+ ? `${trailingPlaceholder.trim()}\n\n${section}\n\n`
65
+ : `${section}\n\n`;
62
66
  });
63
67
  return inserted ? `${updated.replace(/\s+$/u, '')}\n` : `${body.replace(/\s+$/u, '')}\n\n${section}\n`;
64
68
  }
@@ -574,9 +574,8 @@ async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise
574
574
  const failure = plan.operations.find((operation) => operation.status === 'failed');
575
575
  if (failure) return result('failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: { code: failure.reasonCode || 'ISSUE_SYNC_FAILED', message: `Operation ${failure.name} failed`, retryable: false } });
576
576
  const planned = plan.operations.filter((operation) => operation.status === 'planned');
577
- const skipped = plan.operations.some((operation) => operation.status === 'skipped');
578
- if (options.dryRun) return result(planned.length ? 'planned' : skipped ? 'degraded' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
579
- if (planned.length === 0) return result(skipped ? 'degraded' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
577
+ if (options.dryRun) return result(planned.length ? 'planned' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
578
+ if (planned.length === 0) return result('no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
580
579
  const payload = applyRestOperations(snapshot, planned) as Partial<{
581
580
  title: string; body: string; labels: string[]; assignees: string[];
582
581
  milestone: string | null; state: 'open' | 'closed'; fields: Record<string, string | number | null>;
@@ -611,7 +610,7 @@ async function syncPlatformIssue(taskRef: string, options: SyncOptions): Promise
611
610
  error: { code: 'IN_LABEL_SYNC_PARTIAL', message: 'Issue labels did not converge after update', retryable: true }
612
611
  });
613
612
  }
614
- return result(skipped ? 'degraded' : 'applied', base.resolved.taskId, base.issueNumber, {
613
+ return result('applied', base.resolved.taskId, base.issueNumber, {
615
614
  changed: updated.value.changed, platform: base.context.platform, capabilities: base.context.capabilities,
616
615
  resource: { kind: 'issue', number: base.issueNumber, identity: base.issueIdentity }, issue: finalIssue,
617
616
  operations: plan.operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null