@fitlab-ai/agent-infra 0.9.11 → 0.9.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/README.md +6 -6
- package/README.zh-CN.md +6 -6
- package/dist/bin/internal-cli.js +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +71 -10
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/git/workflow.js +43 -1
- package/dist/lib/init.js +1 -0
- package/dist/lib/internal/git-workflow.js +8 -1
- package/dist/lib/internal/platform-pr.js +8 -2
- package/dist/lib/internal/task-delivery.js +59 -0
- package/dist/lib/platform/github-client.js +7 -1
- package/dist/lib/platform/pr-checks.js +8 -5
- package/dist/lib/platform/pr-summary.js +12 -8
- package/dist/lib/platform/pull-requests.js +393 -91
- package/dist/lib/platform/verification-required.js +13 -2
- package/dist/lib/platform/verification-sync.js +9 -4
- package/dist/lib/pr-review/evidence-grading.js +12 -4
- package/dist/lib/sandbox/config.js +5 -0
- package/dist/lib/sandbox/control/lifecycle.js +29 -7
- package/dist/lib/server/process-state.js +2 -2
- package/dist/lib/task/artifact-lifecycle.js +2 -2
- package/dist/lib/task/commit-intent.js +100 -0
- package/dist/lib/task/commit-operation.js +195 -101
- package/dist/lib/task/create.js +33 -7
- package/dist/lib/task/decision-details.js +3 -48
- package/dist/lib/task/delivery-target.js +97 -0
- package/dist/lib/task/delivery.js +153 -0
- package/dist/lib/task/human-override.js +5 -2
- package/dist/lib/task/lifecycle.js +106 -9
- package/dist/lib/task/orchestration.js +55 -62
- package/dist/lib/task/pr-delivery-fact.js +182 -0
- package/dist/lib/task/review-finalization.js +4 -8
- package/dist/lib/task/review-fingerprint.js +8 -0
- package/dist/lib/task/verification-engine.js +48 -24
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/update.js +9 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +105 -10
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +12 -1
- package/lib/defaults.json +4 -0
- package/lib/git/workflow.ts +38 -1
- package/lib/init.ts +3 -0
- package/lib/internal/git-workflow.ts +7 -1
- package/lib/internal/platform-pr.ts +8 -1
- package/lib/internal/task-delivery.ts +53 -0
- package/lib/platform/github-client.ts +8 -1
- package/lib/platform/pr-checks.ts +7 -5
- package/lib/platform/pr-summary.ts +10 -7
- package/lib/platform/pull-requests.ts +425 -87
- package/lib/platform/verification-required.ts +12 -2
- package/lib/platform/verification-sync.ts +9 -4
- package/lib/pr-review/evidence-grading.ts +7 -4
- package/lib/sandbox/config.ts +7 -0
- package/lib/sandbox/control/lifecycle.ts +30 -8
- package/lib/server/process-state.ts +2 -2
- package/lib/task/artifact-lifecycle.ts +2 -2
- package/lib/task/commit-intent.ts +137 -0
- package/lib/task/commit-operation.ts +174 -87
- package/lib/task/create.ts +34 -9
- package/lib/task/decision-details.ts +2 -62
- package/lib/task/delivery-target.ts +130 -0
- package/lib/task/delivery.ts +160 -0
- package/lib/task/human-override.ts +5 -2
- package/lib/task/lifecycle.ts +93 -4
- package/lib/task/orchestration.ts +62 -65
- package/lib/task/pr-delivery-fact.ts +254 -0
- package/lib/task/review-finalization.ts +4 -14
- package/lib/task/review-fingerprint.ts +10 -0
- package/lib/task/verification-engine.ts +45 -28
- package/lib/task/verification-review.ts +2 -0
- package/lib/update.ts +10 -0
- package/package.json +1 -1
- package/templates/.agents/rules/README.en.md +4 -2
- package/templates/.agents/rules/README.zh-CN.md +4 -2
- package/templates/.agents/rules/create-issue.en.md +11 -2
- package/templates/.agents/rules/create-issue.zh-CN.md +24 -2
- package/templates/.agents/rules/issue-pr-commands.en.md +4 -17
- package/templates/.agents/rules/issue-sync.en.md +26 -5
- package/templates/.agents/rules/issue-sync.zh-CN.md +62 -5
- package/templates/.agents/rules/label-milestone-setup.en.md +15 -2
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +15 -2
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -0
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +46 -0
- package/templates/.agents/rules/milestone-inference.en.md +12 -2
- package/templates/.agents/rules/milestone-inference.zh-CN.md +25 -3
- package/templates/.agents/rules/pr-sync.en.md +22 -13
- package/templates/.agents/rules/pr-sync.zh-CN.md +22 -1
- package/templates/.agents/rules/release-commands.en.md +42 -3
- package/templates/.agents/rules/release-commands.zh-CN.md +42 -3
- package/templates/.agents/rules/security-alerts.en.md +17 -2
- package/templates/.agents/rules/security-alerts.zh-CN.md +17 -2
- package/templates/.agents/rules/sync-content-generation.en.md +8 -0
- package/templates/.agents/rules/sync-content-generation.zh-CN.md +8 -0
- package/templates/.agents/rules/task-management.en.md +1 -1
- package/templates/.agents/rules/task-management.zh-CN.md +1 -1
- package/templates/.agents/scripts/security-alerts.github.sh +151 -0
- package/templates/.agents/scripts/security-alerts.sh +31 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +5 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +6 -3
- package/templates/.agents/skills/commit/SKILL.en.md +3 -3
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/config/verify.json +1 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +3 -3
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +2 -2
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +10 -10
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +9 -10
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +9 -10
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +57 -1
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +27 -1
- package/templates/.agents/skills/init-milestones/SKILL.en.md +3 -5
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +3 -5
- package/templates/.agents/skills/plan-task/SKILL.en.md +1 -0
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-analysis/SKILL.en.md +4 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +7 -6
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +7 -6
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +19 -1
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +19 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +4 -2
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +4 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +4 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +4 -3
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +18 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/SKILL.en.md +1 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +1 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +2 -2
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -0
- 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 +4 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/templates/task.en.md +5 -1
- package/templates/.agents/templates/task.zh-CN.md +5 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +9 -8
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/feature-development.en.yaml +9 -8
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +9 -8
- package/templates/.agents/workflows/refactoring.en.yaml +9 -8
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +9 -8
- package/templates/.agents/rules/create-issue.github.en.md +0 -14
- package/templates/.agents/rules/create-issue.github.zh-CN.md +0 -27
- package/templates/.agents/rules/issue-pr-commands.github.en.md +0 -34
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +0 -49
- package/templates/.agents/rules/issue-sync.github.en.md +0 -40
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +0 -76
- package/templates/.agents/rules/label-milestone-setup.github.en.md +0 -60
- package/templates/.agents/rules/label-milestone-setup.github.zh-CN.md +0 -60
- package/templates/.agents/rules/milestone-inference.github.en.md +0 -15
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +0 -27
- package/templates/.agents/rules/pr-checks-commands.github.en.md +0 -18
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +0 -34
- package/templates/.agents/rules/pr-sync.github.en.md +0 -43
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +0 -69
- package/templates/.agents/rules/release-commands.github.en.md +0 -44
- package/templates/.agents/rules/release-commands.github.zh-CN.md +0 -44
- package/templates/.agents/rules/security-alerts.github.en.md +0 -43
- package/templates/.agents/rules/security-alerts.github.zh-CN.md +0 -43
- /package/templates/.agents/rules/{issue-fields.github.en.md → issue-fields.en.md} +0 -0
- /package/templates/.agents/rules/{issue-fields.github.zh-CN.md → issue-fields.zh-CN.md} +0 -0
|
@@ -29,7 +29,6 @@ import type {
|
|
|
29
29
|
} from './delegation-receipts.ts';
|
|
30
30
|
import { normalizeAgentClients } from '../agent-clients/config.ts';
|
|
31
31
|
import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.ts';
|
|
32
|
-
import { inspectPlatformPullRequest } from '../platform/pull-requests.ts';
|
|
33
32
|
import {
|
|
34
33
|
getAgentClientCapability,
|
|
35
34
|
getAgentClientDelegationEvidence,
|
|
@@ -52,6 +51,8 @@ import { assertGitRepositoryBinding } from '../git/worktree-identity.ts';
|
|
|
52
51
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
53
52
|
import { hasActiveCodexLifecycleEvidence } from '../agent-clients/adapters/codex-lifecycle/store.ts';
|
|
54
53
|
import type { CodexCapabilityProvenanceDetail } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
54
|
+
import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead, extractReviewedHead } from './review-fingerprint.ts';
|
|
55
|
+
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
|
|
55
56
|
|
|
56
57
|
type OrchestrationStatus = 'running' | 'paused' | 'completed';
|
|
57
58
|
type ModelPolicySource = Readonly<{
|
|
@@ -84,8 +85,8 @@ type CleanCompletionEvidence = Readonly<{
|
|
|
84
85
|
headTree: string;
|
|
85
86
|
worktreeTree: string;
|
|
86
87
|
lastReviewedCommit: string;
|
|
87
|
-
prNumber: number;
|
|
88
|
-
prHead: string;
|
|
88
|
+
prNumber: number | null;
|
|
89
|
+
prHead: string | null;
|
|
89
90
|
}>;
|
|
90
91
|
type OrchestrationRun = Readonly<{
|
|
91
92
|
taskId: string;
|
|
@@ -273,9 +274,8 @@ function isCompletionEvidence(value: unknown): value is CleanCompletionEvidence
|
|
|
273
274
|
&& exactText(value.headTree)
|
|
274
275
|
&& exactText(value.worktreeTree)
|
|
275
276
|
&& exactText(value.lastReviewedCommit)
|
|
276
|
-
&& Number.isSafeInteger(value.prNumber)
|
|
277
|
-
&& (value.
|
|
278
|
-
&& exactText(value.prHead);
|
|
277
|
+
&& (value.prNumber === null || (Number.isSafeInteger(value.prNumber) && (value.prNumber as number) > 0))
|
|
278
|
+
&& (value.prHead === null || exactText(value.prHead));
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
const ORCHESTRATION_RUN_KEYS = [
|
|
@@ -523,6 +523,44 @@ function artifactName(family: string, round: number): string {
|
|
|
523
523
|
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
+
function validateSavedCurrentDiffBase(
|
|
527
|
+
repoRoot: string,
|
|
528
|
+
metadata: ReturnType<typeof parseTypedTaskFrontmatter>,
|
|
529
|
+
taskDir: string,
|
|
530
|
+
reviewedHead: string
|
|
531
|
+
): Readonly<{ code: string; message: string }> | null {
|
|
532
|
+
const reviewRound = highestRound(taskDir, 'review-code');
|
|
533
|
+
const reviewContent = fs.readFileSync(path.join(taskDir, artifactName('review-code', reviewRound)), 'utf8');
|
|
534
|
+
const savedTargetHead = extractReviewTargetHead(reviewContent);
|
|
535
|
+
const savedDiffBase = extractReviewDiffBase(reviewContent);
|
|
536
|
+
const savedReviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
537
|
+
if (!savedTargetHead && !savedDiffBase && !savedReviewedHead) return null;
|
|
538
|
+
if (savedReviewedHead && savedReviewedHead !== reviewedHead) {
|
|
539
|
+
return { code: 'ORCHESTRATION_REVIEWED_HEAD_MISMATCH', message: 'review-code saved reviewed head does not match last_reviewed_commit' };
|
|
540
|
+
}
|
|
541
|
+
if (!savedTargetHead || !savedDiffBase) {
|
|
542
|
+
return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: 'review-code must save target head and diff base together' };
|
|
543
|
+
}
|
|
544
|
+
const savedBase = resolveDiffBase(repoRoot, reviewedHead, savedTargetHead);
|
|
545
|
+
if (!savedBase.ok) return { code: 'ORCHESTRATION_REVIEW_TARGET_INVALID', message: savedBase.message };
|
|
546
|
+
if (savedBase.diffBase !== savedDiffBase) {
|
|
547
|
+
return { code: 'ORCHESTRATION_REVIEW_DIFF_BASE_MISMATCH', message: 'saved review diff base does not match reviewed head and saved target head' };
|
|
548
|
+
}
|
|
549
|
+
const target = resolveDeliveryTarget(repoRoot, {
|
|
550
|
+
remote: typeof metadata.delivery_remote === 'string' ? metadata.delivery_remote : undefined,
|
|
551
|
+
baseRef: typeof metadata.delivery_base_ref === 'string' ? metadata.delivery_base_ref : undefined
|
|
552
|
+
});
|
|
553
|
+
if (!target.ok) return { code: target.code, message: target.message };
|
|
554
|
+
const currentTarget = resolveTargetHead(repoRoot, target.value);
|
|
555
|
+
if (!currentTarget.ok) return { code: currentTarget.code, message: currentTarget.message };
|
|
556
|
+
const currentBase = resolveDiffBase(repoRoot, reviewedHead, currentTarget.head!);
|
|
557
|
+
if (!currentBase.ok) return { code: currentBase.code, message: currentBase.message };
|
|
558
|
+
if (currentBase.diffBase !== savedDiffBase) {
|
|
559
|
+
return { code: 'ORCHESTRATION_REVIEW_TARGET_CHANGED', message: 'current delivery target changed the reviewed diff base; re-run review-code' };
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
|
|
526
564
|
function hasReviewAfterArtifact(taskDir: string, family: string, reviewFamily: string): boolean {
|
|
527
565
|
const artifact = inspectArtifactDirectory(taskDir, family as 'analysis' | 'plan' | 'code');
|
|
528
566
|
const review = inspectArtifactDirectory(taskDir, reviewFamily as 'review-analysis' | 'review-plan' | 'review-code');
|
|
@@ -656,29 +694,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
656
694
|
if (run?.status === 'completed') {
|
|
657
695
|
return { status: 'completed', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
658
696
|
}
|
|
659
|
-
|
|
660
|
-
let config: unknown;
|
|
661
|
-
try {
|
|
662
|
-
config = JSON.parse(fs.readFileSync(path.join(resolved.repoRoot, '.agents', '.airc.json'), 'utf8'));
|
|
663
|
-
} catch (error) {
|
|
664
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
665
|
-
}
|
|
666
|
-
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
667
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', 'project configuration must be an object', resolved.taskId);
|
|
668
|
-
}
|
|
669
|
-
const projectConfig = config as Record<string, unknown>;
|
|
670
|
-
if (!Object.hasOwn(projectConfig, 'prFlow') || projectConfig.prFlow === 'disabled') {
|
|
671
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
672
|
-
}
|
|
673
|
-
if (projectConfig.prFlow !== 'required') {
|
|
674
|
-
return failed('ORCHESTRATION_CONFIG_INVALID', "project configuration 'prFlow' must be 'required' or 'disabled'", resolved.taskId);
|
|
675
|
-
}
|
|
676
|
-
const prNumber = Number(metadata.pr_number);
|
|
677
|
-
if (!Number.isInteger(prNumber) || prNumber <= 0) {
|
|
678
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
679
|
-
}
|
|
680
697
|
if (!run) {
|
|
681
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
698
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
682
699
|
}
|
|
683
700
|
if (run.status !== 'running' || run.pendingDelegation !== null) {
|
|
684
701
|
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'clean completion requires an idle running orchestration', resolved.taskId);
|
|
@@ -692,35 +709,19 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
692
709
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
693
710
|
}
|
|
694
711
|
if (before.headTree !== before.worktreeTree) {
|
|
695
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
712
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
696
713
|
}
|
|
697
714
|
const lastReviewedCommit = String(metadata.last_reviewed_commit ?? '');
|
|
698
715
|
if (before.head !== lastReviewedCommit) {
|
|
699
716
|
return failed('ORCHESTRATION_REVIEWED_HEAD_MISMATCH', 'local HEAD does not match last_reviewed_commit', resolved.taskId);
|
|
700
717
|
}
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
709
|
-
}
|
|
710
|
-
if (inspection.status === 'blocked') {
|
|
711
|
-
return failed('ORCHESTRATION_PR_INSPECTION_BLOCKED', inspection.error?.message ?? 'pull request inspection is blocked', resolved.taskId);
|
|
712
|
-
}
|
|
713
|
-
if (inspection.status === 'failed') {
|
|
714
|
-
return failed('ORCHESTRATION_PR_INSPECTION_FAILED', inspection.error?.message ?? 'pull request inspection failed', resolved.taskId);
|
|
715
|
-
}
|
|
716
|
-
const inspectedPrNumber = inspection.task?.prNumber ?? inspection.prNumber;
|
|
717
|
-
const prHead = inspection.pullRequest?.head?.sha;
|
|
718
|
-
if (typeof prHead !== 'string') {
|
|
719
|
-
return failed('ORCHESTRATION_PR_INSPECTION_INVALID', 'pull request inspection returned incomplete identity evidence', resolved.taskId);
|
|
720
|
-
}
|
|
721
|
-
if (inspectedPrNumber !== prNumber || prHead !== before.head) {
|
|
722
|
-
return failed('ORCHESTRATION_PR_HEAD_MISMATCH', 'pull request identity does not match the reviewed local HEAD', resolved.taskId);
|
|
723
|
-
}
|
|
718
|
+
const diffBaseError = validateSavedCurrentDiffBase(
|
|
719
|
+
resolved.repoRoot,
|
|
720
|
+
metadata,
|
|
721
|
+
resolved.taskDir,
|
|
722
|
+
lastReviewedCommit
|
|
723
|
+
);
|
|
724
|
+
if (diffBaseError) return failed(diffBaseError.code, diffBaseError.message, resolved.taskId);
|
|
724
725
|
|
|
725
726
|
let after: RepositorySnapshot;
|
|
726
727
|
try {
|
|
@@ -729,7 +730,7 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
729
730
|
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
730
731
|
}
|
|
731
732
|
if (after.headTree !== after.worktreeTree) {
|
|
732
|
-
return { status: 'running', changed: false, taskId: resolved.taskId, run, next, error: null };
|
|
733
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
733
734
|
}
|
|
734
735
|
if (after.head !== before.head || after.headTree !== before.headTree) {
|
|
735
736
|
return failed('ORCHESTRATION_REPOSITORY_CHANGED', 'repository changed while clean completion evidence was collected', resolved.taskId);
|
|
@@ -743,8 +744,8 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
743
744
|
headTree: after.headTree,
|
|
744
745
|
worktreeTree: after.worktreeTree,
|
|
745
746
|
lastReviewedCommit,
|
|
746
|
-
prNumber,
|
|
747
|
-
prHead
|
|
747
|
+
prNumber: null,
|
|
748
|
+
prHead: null
|
|
748
749
|
};
|
|
749
750
|
const completed = withUpdatedRun(run, {
|
|
750
751
|
status: 'completed',
|
|
@@ -852,10 +853,7 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
852
853
|
}
|
|
853
854
|
const updated = withUpdatedRun(run, {
|
|
854
855
|
nextStage: next.stage,
|
|
855
|
-
pendingDelegation: receipt
|
|
856
|
-
commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
|
|
857
|
-
? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
|
|
858
|
-
: run.commitAuthorization
|
|
856
|
+
pendingDelegation: receipt
|
|
859
857
|
});
|
|
860
858
|
saveRun(resolved.taskDir, updated);
|
|
861
859
|
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
|
|
@@ -1460,16 +1458,15 @@ function advanceOrchestration(taskRef: string, options: OrchestrationOptions = {
|
|
|
1460
1458
|
if (!run?.pendingDelegation) return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
1461
1459
|
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
1462
1460
|
if (!result.ok) return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
1463
|
-
|
|
1461
|
+
if (result.receipt.stage === 'commit') {
|
|
1462
|
+
return failed('ORCHESTRATED_COMMIT_REMOVED', 'Orchestrated commit is no longer a lifecycle stage; use local checkpoint delivery from code-task', resolved.taskId);
|
|
1463
|
+
}
|
|
1464
1464
|
const updated = withUpdatedRun(run, {
|
|
1465
|
-
status:
|
|
1465
|
+
status: 'running',
|
|
1466
1466
|
nextStage: null,
|
|
1467
1467
|
stepCount: run.stepCount + 1,
|
|
1468
1468
|
pendingDelegation: null,
|
|
1469
|
-
receipts: Object.freeze([...run.receipts, result.receipt])
|
|
1470
|
-
commitAuthorization: completed
|
|
1471
|
-
? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
|
|
1472
|
-
: run.commitAuthorization
|
|
1469
|
+
receipts: Object.freeze([...run.receipts, result.receipt])
|
|
1473
1470
|
});
|
|
1474
1471
|
saveRun(resolved.taskDir, updated);
|
|
1475
1472
|
return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
|
|
2
|
+
|
|
3
|
+
type PrDeliveryIdentity = {
|
|
4
|
+
repository: string;
|
|
5
|
+
number: number;
|
|
6
|
+
nodeId: string;
|
|
7
|
+
url: string;
|
|
8
|
+
head: { repository: string; ref: string; sha: string };
|
|
9
|
+
base: { repository: string; ref: string; sha: string };
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type PrDeliveryBindingSource =
|
|
13
|
+
| 'created'
|
|
14
|
+
| 'reused'
|
|
15
|
+
| 'explicit-bind'
|
|
16
|
+
| 'external-unique'
|
|
17
|
+
| 'external-explicit';
|
|
18
|
+
|
|
19
|
+
type PrDeliveryProvenance =
|
|
20
|
+
| 'create-post'
|
|
21
|
+
| 'reuse'
|
|
22
|
+
| 'explicit-bind'
|
|
23
|
+
| 'external-unique'
|
|
24
|
+
| 'external-explicit';
|
|
25
|
+
|
|
26
|
+
type PrDeliveryFact =
|
|
27
|
+
| { version: 1; state: 'unbound'; reason: 'initial' }
|
|
28
|
+
| { version: 1; state: 'skipped'; reason: 'explicit'; decidedAt: string }
|
|
29
|
+
| {
|
|
30
|
+
version: 1;
|
|
31
|
+
state: 'bound';
|
|
32
|
+
identity: PrDeliveryIdentity;
|
|
33
|
+
binding: {
|
|
34
|
+
status: 'verified';
|
|
35
|
+
source: PrDeliveryBindingSource;
|
|
36
|
+
verifiedAt: string;
|
|
37
|
+
issueNumber: number | null;
|
|
38
|
+
remoteState: 'open' | 'closed';
|
|
39
|
+
mergedAt: string | null;
|
|
40
|
+
mergeCommitSha: string | null;
|
|
41
|
+
};
|
|
42
|
+
provenance: { establishedBy: PrDeliveryProvenance };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type CreationOutcome =
|
|
46
|
+
| { kind: 'planned'; action: 'create' | 'reuse'; createdByCurrentOperation: false }
|
|
47
|
+
| { kind: 'not-created'; reason: 'precondition-failed' | 'post-rejected'; createdByCurrentOperation: false }
|
|
48
|
+
| { kind: 'created'; createdByCurrentOperation: true }
|
|
49
|
+
| { kind: 'reused'; createdByCurrentOperation: false }
|
|
50
|
+
| { kind: 'no-op'; createdByCurrentOperation: false }
|
|
51
|
+
| { kind: 'unknown'; errorCode: 'PR_CREATE_OUTCOME_UNKNOWN' };
|
|
52
|
+
|
|
53
|
+
type PrDeliveryFactReadResult =
|
|
54
|
+
| { status: 'valid'; fact: PrDeliveryFact }
|
|
55
|
+
| { status: 'missing'; fact: null }
|
|
56
|
+
| { status: 'invalid'; fact: null; error: Error };
|
|
57
|
+
|
|
58
|
+
function factError(message: string): Error & { code: string } {
|
|
59
|
+
return Object.assign(new Error(`PR_DELIVERY_FACT_INVALID: ${message}`), { code: 'PR_DELIVERY_FACT_INVALID' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
63
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function exactKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
|
|
67
|
+
const actual = Object.keys(value).sort();
|
|
68
|
+
const expected = [...keys].sort();
|
|
69
|
+
if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
|
|
70
|
+
throw factError(`${label} has an invalid field set`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function text(value: unknown, label: string): string {
|
|
75
|
+
if (typeof value !== 'string' || !value || value.trim() !== value) throw factError(`${label} must be a non-empty string`);
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function nullableText(value: unknown, label: string): string | null {
|
|
80
|
+
return value === null ? null : text(value, label);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function positiveNumber(value: unknown, label: string): number {
|
|
84
|
+
if (!Number.isSafeInteger(value) || (value as number) <= 0) throw factError(`${label} must be a positive integer`);
|
|
85
|
+
return value as number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function optionalTimestamp(value: unknown, label: string): string | null {
|
|
89
|
+
const result = nullableText(value, label);
|
|
90
|
+
if (result !== null && Number.isNaN(Date.parse(result))) throw factError(`${label} must be a valid timestamp`);
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseIdentity(value: unknown): PrDeliveryIdentity {
|
|
95
|
+
if (!isRecord(value)) throw factError('identity must be an object');
|
|
96
|
+
exactKeys(value, ['repository', 'number', 'nodeId', 'url', 'head', 'base'], 'identity');
|
|
97
|
+
const head = parseRef(value.head, 'head');
|
|
98
|
+
const base = parseRef(value.base, 'base');
|
|
99
|
+
return {
|
|
100
|
+
repository: text(value.repository, 'identity.repository'),
|
|
101
|
+
number: positiveNumber(value.number, 'identity.number'),
|
|
102
|
+
nodeId: text(value.nodeId, 'identity.nodeId'),
|
|
103
|
+
url: text(value.url, 'identity.url'),
|
|
104
|
+
head,
|
|
105
|
+
base
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseRef(value: unknown, label: string): { repository: string; ref: string; sha: string } {
|
|
110
|
+
if (!isRecord(value)) throw factError(`${label} must be an object`);
|
|
111
|
+
exactKeys(value, ['repository', 'ref', 'sha'], label);
|
|
112
|
+
return {
|
|
113
|
+
repository: text(value.repository, `${label}.repository`),
|
|
114
|
+
ref: text(value.ref, `${label}.ref`),
|
|
115
|
+
sha: text(value.sha, `${label}.sha`)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function parseFact(value: unknown): PrDeliveryFact {
|
|
120
|
+
if (!isRecord(value)) throw factError('fact must be an object');
|
|
121
|
+
if (value.version !== 1 || typeof value.state !== 'string') throw factError('version or state is invalid');
|
|
122
|
+
if (value.state === 'unbound') {
|
|
123
|
+
exactKeys(value, ['version', 'state', 'reason'], 'unbound fact');
|
|
124
|
+
if (value.reason !== 'initial') throw factError('unbound reason is invalid');
|
|
125
|
+
return { version: 1, state: 'unbound', reason: value.reason };
|
|
126
|
+
}
|
|
127
|
+
if (value.state === 'skipped') {
|
|
128
|
+
exactKeys(value, ['version', 'state', 'reason', 'decidedAt'], 'skipped fact');
|
|
129
|
+
if (value.reason !== 'explicit') throw factError('skipped reason is invalid');
|
|
130
|
+
const decidedAt = text(value.decidedAt, 'skipped.decidedAt');
|
|
131
|
+
if (Number.isNaN(Date.parse(decidedAt))) throw factError('skipped.decidedAt must be a valid timestamp');
|
|
132
|
+
return { version: 1, state: 'skipped', reason: 'explicit', decidedAt };
|
|
133
|
+
}
|
|
134
|
+
if (value.state !== 'bound') throw factError('state is invalid');
|
|
135
|
+
exactKeys(value, ['version', 'state', 'identity', 'binding', 'provenance'], 'bound fact');
|
|
136
|
+
if (!isRecord(value.binding)) throw factError('binding must be an object');
|
|
137
|
+
exactKeys(value.binding, ['status', 'source', 'verifiedAt', 'issueNumber', 'remoteState', 'mergedAt', 'mergeCommitSha'], 'binding');
|
|
138
|
+
if (value.binding.status !== 'verified') throw factError('binding status is invalid');
|
|
139
|
+
const sources: readonly PrDeliveryBindingSource[] = ['created', 'reused', 'explicit-bind', 'external-unique', 'external-explicit'];
|
|
140
|
+
if (!sources.includes(value.binding.source as PrDeliveryBindingSource)) throw factError('binding source is invalid');
|
|
141
|
+
const verifiedAt = text(value.binding.verifiedAt, 'binding.verifiedAt');
|
|
142
|
+
if (Number.isNaN(Date.parse(verifiedAt))) throw factError('binding.verifiedAt must be a valid timestamp');
|
|
143
|
+
const issueNumber = value.binding.issueNumber === null ? null : positiveNumber(value.binding.issueNumber, 'binding.issueNumber');
|
|
144
|
+
if (value.binding.remoteState !== 'open' && value.binding.remoteState !== 'closed') throw factError('binding.remoteState is invalid');
|
|
145
|
+
const mergedAt = optionalTimestamp(value.binding.mergedAt, 'binding.mergedAt');
|
|
146
|
+
const mergeCommitSha = nullableText(value.binding.mergeCommitSha, 'binding.mergeCommitSha');
|
|
147
|
+
if (Boolean(mergedAt) !== Boolean(mergeCommitSha)) throw factError('mergedAt and mergeCommitSha must be paired');
|
|
148
|
+
if (!isRecord(value.provenance)) throw factError('provenance must be an object');
|
|
149
|
+
exactKeys(value.provenance, ['establishedBy'], 'provenance');
|
|
150
|
+
const provenance: readonly PrDeliveryProvenance[] = ['create-post', 'reuse', 'explicit-bind', 'external-unique', 'external-explicit'];
|
|
151
|
+
if (!provenance.includes(value.provenance.establishedBy as PrDeliveryProvenance)) throw factError('provenance.establishedBy is invalid');
|
|
152
|
+
if (value.provenance.establishedBy !== provenanceForSource(value.binding.source as PrDeliveryBindingSource)) {
|
|
153
|
+
throw factError('binding.source and provenance.establishedBy do not match');
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
version: 1,
|
|
157
|
+
state: 'bound',
|
|
158
|
+
identity: parseIdentity(value.identity),
|
|
159
|
+
binding: {
|
|
160
|
+
status: 'verified',
|
|
161
|
+
source: value.binding.source as PrDeliveryBindingSource,
|
|
162
|
+
verifiedAt,
|
|
163
|
+
issueNumber,
|
|
164
|
+
remoteState: value.binding.remoteState,
|
|
165
|
+
mergedAt,
|
|
166
|
+
mergeCommitSha
|
|
167
|
+
},
|
|
168
|
+
provenance: { establishedBy: value.provenance.establishedBy as PrDeliveryProvenance }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function decodePrDeliveryFact(value: unknown): PrDeliveryFact {
|
|
173
|
+
if (typeof value === 'string') {
|
|
174
|
+
try { return parseFact(JSON.parse(value)); }
|
|
175
|
+
catch (error) { throw error instanceof Error && error.message.startsWith('PR_DELIVERY_FACT_INVALID:') ? error : factError('value is not valid JSON'); }
|
|
176
|
+
}
|
|
177
|
+
return parseFact(value);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function encodePrDeliveryFact(fact: PrDeliveryFact): string {
|
|
181
|
+
return JSON.stringify(parseFact(fact));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function readPrDeliveryFact(metadata: Record<string, unknown>): PrDeliveryFactReadResult {
|
|
185
|
+
if (!Object.hasOwn(metadata, PR_DELIVERY_FACT_KEY) || metadata[PR_DELIVERY_FACT_KEY] === '') return { status: 'missing', fact: null };
|
|
186
|
+
try { return { status: 'valid', fact: decodePrDeliveryFact(metadata[PR_DELIVERY_FACT_KEY]) }; }
|
|
187
|
+
catch (error) { return { status: 'invalid', fact: null, error: error instanceof Error ? error : factError(String(error)) }; }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildUnboundFact(): PrDeliveryFact {
|
|
191
|
+
return { version: 1, state: 'unbound', reason: 'initial' };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function buildSkippedFact(decidedAt: string): PrDeliveryFact {
|
|
195
|
+
return parseFact({ version: 1, state: 'skipped', reason: 'explicit', decidedAt });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function provenanceForSource(source: PrDeliveryBindingSource): PrDeliveryProvenance {
|
|
199
|
+
return source === 'created' ? 'create-post' : source === 'reused' ? 'reuse' : source;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildBoundFact(input: {
|
|
203
|
+
identity: PrDeliveryIdentity;
|
|
204
|
+
source: PrDeliveryBindingSource;
|
|
205
|
+
verifiedAt: string;
|
|
206
|
+
issueNumber?: number | null;
|
|
207
|
+
remoteState: 'open' | 'closed';
|
|
208
|
+
mergedAt?: string | null;
|
|
209
|
+
mergeCommitSha?: string | null;
|
|
210
|
+
}): PrDeliveryFact {
|
|
211
|
+
return parseFact({
|
|
212
|
+
version: 1,
|
|
213
|
+
state: 'bound',
|
|
214
|
+
identity: input.identity,
|
|
215
|
+
binding: {
|
|
216
|
+
status: 'verified',
|
|
217
|
+
source: input.source,
|
|
218
|
+
verifiedAt: input.verifiedAt,
|
|
219
|
+
issueNumber: input.issueNumber ?? null,
|
|
220
|
+
remoteState: input.remoteState,
|
|
221
|
+
mergedAt: input.mergedAt ?? null,
|
|
222
|
+
mergeCommitSha: input.mergeCommitSha ?? null
|
|
223
|
+
},
|
|
224
|
+
provenance: { establishedBy: provenanceForSource(input.source) }
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function factFrontmatterMutation(fact: PrDeliveryFact): { set: { [PR_DELIVERY_FACT_KEY]: string } } {
|
|
229
|
+
return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function identityFromPullRequest(pullRequest: PrDeliveryIdentity): PrDeliveryIdentity {
|
|
233
|
+
return parseIdentity(pullRequest);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export {
|
|
237
|
+
PR_DELIVERY_FACT_KEY,
|
|
238
|
+
buildBoundFact,
|
|
239
|
+
buildSkippedFact,
|
|
240
|
+
buildUnboundFact,
|
|
241
|
+
decodePrDeliveryFact,
|
|
242
|
+
encodePrDeliveryFact,
|
|
243
|
+
factFrontmatterMutation,
|
|
244
|
+
identityFromPullRequest,
|
|
245
|
+
readPrDeliveryFact
|
|
246
|
+
};
|
|
247
|
+
export type {
|
|
248
|
+
CreationOutcome,
|
|
249
|
+
PrDeliveryBindingSource,
|
|
250
|
+
PrDeliveryFact,
|
|
251
|
+
PrDeliveryFactReadResult,
|
|
252
|
+
PrDeliveryIdentity,
|
|
253
|
+
PrDeliveryProvenance
|
|
254
|
+
};
|
|
@@ -7,7 +7,7 @@ import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecyc
|
|
|
7
7
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
8
8
|
import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
|
|
9
9
|
import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
10
|
-
import { inspectDecisionDetailDuplicates
|
|
10
|
+
import { inspectDecisionDetailDuplicates } from './decision-details.ts';
|
|
11
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
12
12
|
import { validateLifecycleExecution } from './lifecycle-execution.ts';
|
|
13
13
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
@@ -214,17 +214,7 @@ function finalizeReviewSummaryUnlocked(
|
|
|
214
214
|
);
|
|
215
215
|
}
|
|
216
216
|
const stageStatus = summarizeLedgerStage(rows, stage);
|
|
217
|
-
const
|
|
218
|
-
if (!repaired.ok) {
|
|
219
|
-
return failed(
|
|
220
|
-
request,
|
|
221
|
-
'REVIEW_DECISION_DETAIL_INVALID',
|
|
222
|
-
`${repaired.code}: ${repaired.message}`,
|
|
223
|
-
resolved.taskId,
|
|
224
|
-
stageStatus
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
const detailInspection = inspectDecisionDetailDuplicates(repaired.content);
|
|
217
|
+
const detailInspection = inspectDecisionDetailDuplicates(artifactContent);
|
|
228
218
|
if (!detailInspection.ok) {
|
|
229
219
|
return failed(
|
|
230
220
|
request,
|
|
@@ -235,13 +225,13 @@ function finalizeReviewSummaryUnlocked(
|
|
|
235
225
|
);
|
|
236
226
|
}
|
|
237
227
|
const transformed = finalizeReviewSummaryContent(
|
|
238
|
-
|
|
228
|
+
artifactContent,
|
|
239
229
|
stageStatus.unresolvedFindingCounts
|
|
240
230
|
);
|
|
241
231
|
if (!transformed.ok) {
|
|
242
232
|
return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
|
|
243
233
|
}
|
|
244
|
-
if (!
|
|
234
|
+
if (!transformed.changed) {
|
|
245
235
|
return {
|
|
246
236
|
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
|
|
247
237
|
status: 'no-op',
|
|
@@ -63,6 +63,16 @@ export function extractReviewBaseline(content: string): string {
|
|
|
63
63
|
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
export function extractReviewTargetHead(content: string): string {
|
|
67
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查目标提交|审查目标 HEAD|Review Target Commit|Review Target Head)\*\*[::]\s*(.*?)\s*$/m);
|
|
68
|
+
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function extractReviewedHead(content: string): string {
|
|
72
|
+
const match = String(content).match(/^[-*]?\s*\*\*(?:审查已检视提交|已检视提交|Reviewed Head Commit|Reviewed Head)\*\*[::]\s*(.*?)\s*$/m);
|
|
73
|
+
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
74
|
+
}
|
|
75
|
+
|
|
66
76
|
export function extractReviewDiffBase(content: string): string {
|
|
67
77
|
const match = String(content).match(/^[-*]?\s*\*\*(?:审查差异基线|Reviewed Diff Base)\*\*[::]\s*(.*?)\s*$/m);
|
|
68
78
|
return match ? match[1]!.trim().replace(/`/g, "") : "";
|