@fitlab-ai/agent-infra 0.9.2 → 0.9.3
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 +10 -9
- package/README.zh-CN.md +10 -9
- package/dist/bin/internal-cli.js +15 -0
- package/dist/lib/agent-clients/adapter.js +45 -2
- package/dist/lib/agent-clients/adapters/antigravity-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +10 -1
- package/dist/lib/agent-clients/adapters/codex.js +9 -0
- package/dist/lib/agent-clients/adapters/index.js +2 -2
- package/dist/lib/agent-clients/adapters/opencode.js +9 -0
- package/dist/lib/agent-clients/config.js +42 -4
- package/dist/lib/agent-clients/next-steps.js +11 -2
- package/dist/lib/agent-clients/reconcile.js +2 -1
- package/dist/lib/agent-clients/registry.js +7 -1
- package/dist/lib/agent-clients/schema.js +18 -0
- package/dist/lib/agent-clients/tokens.js +49 -0
- package/dist/lib/agent-clients/types.js +1 -1
- package/dist/lib/defaults.json +1 -1
- package/dist/lib/git/workflow.js +54 -1
- package/dist/lib/internal/agent-client.js +61 -4
- package/dist/lib/internal/git-workflow.js +6 -1
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +29 -2
- package/dist/lib/internal/platform-issue.js +7 -0
- package/dist/lib/internal/platform-pr-review.js +132 -0
- package/dist/lib/internal/platform-pr.js +20 -2
- package/dist/lib/internal/platform-release-notes.js +52 -16
- package/dist/lib/internal/pr-review-grade.js +140 -0
- package/dist/lib/internal/release-workflow.js +13 -6
- package/dist/lib/internal/task-activity.js +73 -0
- package/dist/lib/internal/task-event.js +17 -1
- package/dist/lib/internal/task-lifecycle.js +7 -0
- package/dist/lib/internal/task-orchestration.js +51 -6
- package/dist/lib/internal/task-review.js +8 -3
- package/dist/lib/platform/adapters.js +8 -1
- package/dist/lib/platform/completion-backfill.js +122 -0
- package/dist/lib/platform/issue-comments.js +3 -2
- package/dist/lib/platform/merged-pr-equivalence.js +115 -5
- package/dist/lib/platform/pr-checks.js +64 -41
- package/dist/lib/platform/pr-review.js +166 -0
- package/dist/lib/platform/pull-requests.js +324 -3
- package/dist/lib/platform/release-notes.js +85 -3
- package/dist/lib/pr-review/evidence-grading.js +229 -0
- package/dist/lib/run/tui.js +3 -5
- package/dist/lib/sandbox/commands/create.js +0 -20
- package/dist/lib/sandbox/commands/show.js +1 -1
- package/dist/lib/task/activity-intent.js +101 -0
- package/dist/lib/task/artifact-lifecycle.js +2 -1
- package/dist/lib/task/commands/log.js +15 -14
- package/dist/lib/task/delegation-receipts.js +23 -2
- package/dist/lib/task/events.js +67 -28
- package/dist/lib/task/finalization-artifacts.js +35 -0
- package/dist/lib/task/lifecycle-execution.js +46 -0
- package/dist/lib/task/orchestration.js +408 -44
- package/dist/lib/task/review-finalization.js +29 -9
- package/dist/lib/task/task-execution-lock.js +231 -0
- package/dist/lib/task/verification-checks.js +1 -1
- package/dist/lib/task/verification-engine.js +218 -2
- package/dist/lib/task/verification.js +4 -3
- package/dist/lib/task/workspace-snapshot.js +17 -5
- package/lib/agent-clients/adapter.ts +75 -2
- package/lib/agent-clients/adapters/antigravity-cli.ts +65 -0
- package/lib/agent-clients/adapters/claude-code.ts +10 -1
- package/lib/agent-clients/adapters/codex.ts +9 -0
- package/lib/agent-clients/adapters/index.ts +2 -2
- package/lib/agent-clients/adapters/opencode.ts +9 -0
- package/lib/agent-clients/config.ts +49 -4
- package/lib/agent-clients/next-steps.ts +15 -2
- package/lib/agent-clients/reconcile.ts +2 -1
- package/lib/agent-clients/registry.ts +10 -0
- package/lib/agent-clients/schema.ts +18 -0
- package/lib/agent-clients/tokens.ts +53 -0
- package/lib/agent-clients/types.ts +15 -1
- package/lib/defaults.json +1 -1
- package/lib/git/workflow.ts +66 -2
- package/lib/internal/agent-client.ts +59 -4
- package/lib/internal/git-workflow.ts +5 -1
- package/lib/internal/platform-checks.ts +4 -4
- package/lib/internal/platform-comment.ts +17 -2
- package/lib/internal/platform-issue.ts +4 -0
- package/lib/internal/platform-pr-review.ts +104 -0
- package/lib/internal/platform-pr.ts +14 -1
- package/lib/internal/platform-release-notes.ts +59 -16
- package/lib/internal/pr-review-grade.ts +130 -0
- package/lib/internal/release-workflow.ts +13 -6
- package/lib/internal/task-activity.ts +55 -0
- package/lib/internal/task-event.ts +9 -1
- package/lib/internal/task-lifecycle.ts +4 -0
- package/lib/internal/task-orchestration.ts +45 -6
- package/lib/internal/task-review.ts +8 -3
- package/lib/platform/adapters.ts +29 -2
- package/lib/platform/completion-backfill.ts +134 -0
- package/lib/platform/issue-comments.ts +3 -2
- package/lib/platform/merged-pr-equivalence.ts +127 -5
- package/lib/platform/pr-checks.ts +72 -45
- package/lib/platform/pr-review.ts +201 -0
- package/lib/platform/pull-requests.ts +395 -2
- package/lib/platform/release-notes.ts +100 -4
- package/lib/pr-review/evidence-grading.ts +318 -0
- package/lib/run/tui.ts +3 -4
- package/lib/sandbox/commands/create.ts +0 -20
- package/lib/sandbox/commands/show.ts +1 -1
- package/lib/server/server.schema.json +2 -2
- package/lib/task/activity-intent.ts +128 -0
- package/lib/task/artifact-lifecycle.ts +2 -1
- package/lib/task/commands/log.ts +15 -14
- package/lib/task/delegation-receipts.ts +32 -3
- package/lib/task/events.ts +75 -30
- package/lib/task/finalization-artifacts.ts +58 -0
- package/lib/task/lifecycle-execution.ts +106 -0
- package/lib/task/orchestration.ts +543 -64
- package/lib/task/review-finalization.ts +40 -9
- package/lib/task/task-execution-lock.ts +296 -0
- package/lib/task/verification-checks.ts +1 -1
- package/lib/task/verification-engine.ts +249 -2
- package/lib/task/verification.ts +5 -3
- package/lib/task/workspace-snapshot.ts +26 -5
- package/package.json +3 -3
- package/templates/.agents/QUICKSTART.en.md +25 -8
- package/templates/.agents/QUICKSTART.zh-CN.md +25 -8
- package/templates/.agents/README.en.md +10 -7
- package/templates/.agents/README.zh-CN.md +10 -7
- package/templates/.agents/hooks/lifecycle-delegation.js +15 -4
- package/templates/.agents/rules/create-issue.github.en.md +4 -2
- package/templates/.agents/rules/create-issue.github.zh-CN.md +4 -2
- package/templates/.agents/rules/issue-fields.github.en.md +3 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +3 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.github.en.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +5 -3
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +5 -3
- package/templates/.agents/rules/issue-sync.github.en.md +7 -3
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +8 -4
- package/templates/.agents/rules/lifecycle-orchestration.en.md +5 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +5 -2
- package/templates/.agents/rules/milestone-inference.github.en.md +4 -2
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +4 -2
- package/templates/.agents/rules/next-step-output.en.md +3 -1
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +17 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +17 -0
- package/templates/.agents/rules/pr-checks-commands.en.md +3 -3
- package/templates/.agents/rules/pr-checks-commands.github.en.md +2 -2
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
- package/templates/.agents/rules/pr-checks-commands.zh-CN.md +3 -3
- package/templates/.agents/rules/pr-sync.en.md +3 -1
- package/templates/.agents/rules/pr-sync.github.en.md +11 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +11 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +3 -1
- package/templates/.agents/rules/release-commands.en.md +1 -1
- package/templates/.agents/rules/release-commands.github.en.md +12 -2
- package/templates/.agents/rules/release-commands.github.zh-CN.md +12 -2
- package/templates/.agents/rules/release-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +11 -0
- package/templates/.agents/rules/task-management.zh-CN.md +11 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +10 -7
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -7
- package/templates/.agents/skills/block-task/SKILL.en.md +7 -5
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +7 -5
- package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -4
- package/templates/.agents/skills/close-codescan/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +3 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +9 -6
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +11 -8
- package/templates/.agents/skills/commit/SKILL.en.md +3 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/commit/reference/commit-message.en.md +1 -1
- package/templates/.agents/skills/commit/reference/commit-message.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- 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/complete-manual-validation/SKILL.en.md +6 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +6 -4
- 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 +21 -6
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +21 -6
- package/templates/.agents/skills/complete-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +17 -0
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +17 -0
- package/templates/.agents/skills/create-pr/SKILL.en.md +3 -1
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -16
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -16
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -3
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -3
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/plan-task/SKILL.en.md +8 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +8 -5
- package/templates/.agents/skills/post-release/SKILL.en.md +2 -0
- package/templates/.agents/skills/post-release/SKILL.zh-CN.md +2 -0
- package/templates/.agents/skills/release/SKILL.en.md +16 -10
- package/templates/.agents/skills/release/SKILL.zh-CN.md +16 -10
- package/templates/.agents/skills/restore-task/SKILL.en.md +4 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +7 -4
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-code/SKILL.en.md +9 -6
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/review-plan/SKILL.en.md +7 -4
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +7 -4
- package/templates/.agents/skills/review-pr/SKILL.en.md +158 -0
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +158 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +37 -0
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +37 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +65 -0
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +65 -0
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +18 -0
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +18 -0
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +85 -0
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +85 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +8 -8
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/run-task/config/verify.en.json +2 -1
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +2 -1
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +20 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +20 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +35 -61
- package/templates/.agents/skills/watch-pr/SKILL.en.md +13 -13
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +13 -13
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +27 -6
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +26 -5
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +3 -3
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +3 -3
- package/templates/.agents/workflows/code-review.en.yaml +1 -1
- package/templates/.agents/workflows/code-review.zh-CN.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +4 -4
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +4 -4
- package/templates/.agents/workflows/refactoring.en.yaml +3 -3
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +3 -3
- package/templates/.claude/commands/post-release.en.md +1 -0
- package/templates/.claude/commands/post-release.zh-CN.md +1 -0
- package/templates/.claude/commands/review-pr.en.md +8 -0
- package/templates/.claude/commands/review-pr.zh-CN.md +8 -0
- package/templates/.gemini/commands/_project_/review-pr.en.toml +10 -0
- package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +10 -0
- package/templates/.opencode/commands/post-release.en.md +2 -0
- package/templates/.opencode/commands/post-release.zh-CN.md +2 -0
- package/templates/.opencode/commands/review-pr.en.md +11 -0
- package/templates/.opencode/commands/review-pr.zh-CN.md +11 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +0 -64
- package/lib/agent-clients/adapters/gemini-cli.ts +0 -65
- package/templates/.gemini/commands/_project_/analyze-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/analyze-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/archive-tasks.en.toml +0 -10
- package/templates/.gemini/commands/_project_/archive-tasks.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/block-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/block-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/cancel-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/cancel-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/check-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/check-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/close-codescan.en.toml +0 -8
- package/templates/.gemini/commands/_project_/close-codescan.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/close-dependabot.en.toml +0 -8
- package/templates/.gemini/commands/_project_/close-dependabot.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/code-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/code-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/commit.en.toml +0 -10
- package/templates/.gemini/commands/_project_/commit.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-manual-validation.en.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-manual-validation.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/complete-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/create-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/create-pr.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/create-release-note.en.toml +0 -8
- package/templates/.gemini/commands/_project_/create-release-note.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/create-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/create-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-codescan.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-codescan.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-dependabot.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-dependabot.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/import-issue.en.toml +0 -8
- package/templates/.gemini/commands/_project_/import-issue.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/init-labels.en.toml +0 -8
- package/templates/.gemini/commands/_project_/init-labels.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/init-milestones.en.toml +0 -10
- package/templates/.gemini/commands/_project_/init-milestones.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/plan-task.en.toml +0 -10
- package/templates/.gemini/commands/_project_/plan-task.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/post-release.en.toml +0 -6
- package/templates/.gemini/commands/_project_/post-release.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/refine-title.en.toml +0 -8
- package/templates/.gemini/commands/_project_/refine-title.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/release.en.toml +0 -8
- package/templates/.gemini/commands/_project_/release.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/restore-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/restore-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/review-analysis.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-analysis.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/review-code.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-code.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/review-plan.en.toml +0 -10
- package/templates/.gemini/commands/_project_/review-plan.zh-CN.toml +0 -10
- package/templates/.gemini/commands/_project_/run-task.en.toml +0 -8
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/test-integration.en.toml +0 -6
- package/templates/.gemini/commands/_project_/test-integration.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/test.en.toml +0 -6
- package/templates/.gemini/commands/_project_/test.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/update-agent-infra.en.toml +0 -6
- package/templates/.gemini/commands/_project_/update-agent-infra.zh-CN.toml +0 -6
- package/templates/.gemini/commands/_project_/upgrade-dependency.en.toml +0 -8
- package/templates/.gemini/commands/_project_/upgrade-dependency.zh-CN.toml +0 -8
- package/templates/.gemini/commands/_project_/watch-pr.en.toml +0 -10
- package/templates/.gemini/commands/_project_/watch-pr.zh-CN.toml +0 -10
- package/templates/.gemini/settings.json +0 -3
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
|
+
import { getProcessStartTime, processIdentityMatches } from '../server/process-state.js';
|
|
6
|
+
class TaskExecutionLockError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
detail;
|
|
9
|
+
constructor(code, message, detail) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'TaskExecutionLockError';
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.detail = detail;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function errno(error) {
|
|
17
|
+
const value = error?.code;
|
|
18
|
+
return typeof value === 'string' ? value : null;
|
|
19
|
+
}
|
|
20
|
+
function mapLinkError(operation, error, key) {
|
|
21
|
+
const code = errno(error);
|
|
22
|
+
if (code === 'EEXIST')
|
|
23
|
+
return 'exists';
|
|
24
|
+
if (operation === 'quarantine' && code === 'ENOENT')
|
|
25
|
+
return 'missing';
|
|
26
|
+
const detail = { operation, errno: code, key };
|
|
27
|
+
if (['EPERM', 'EACCES', 'ENOSYS', 'ENOTSUP', 'EOPNOTSUPP', 'EXDEV'].includes(code ?? '')) {
|
|
28
|
+
return new TaskExecutionLockError('ORCHESTRATION_LOCK_UNSUPPORTED', `lifecycle lock hard-link operation '${operation}' is unsupported`, detail);
|
|
29
|
+
}
|
|
30
|
+
return new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', `lifecycle lock hard-link operation '${operation}' failed`, detail);
|
|
31
|
+
}
|
|
32
|
+
function lockKey(repoRoot, taskId) {
|
|
33
|
+
const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
|
|
34
|
+
const key = createHash('sha256').update(`${canonicalRepoRoot}\0${taskId}`).digest('hex');
|
|
35
|
+
return { canonicalRepoRoot, key };
|
|
36
|
+
}
|
|
37
|
+
function parseOwner(raw, key) {
|
|
38
|
+
try {
|
|
39
|
+
const value = JSON.parse(raw);
|
|
40
|
+
if (value !== null
|
|
41
|
+
&& value.version === 1
|
|
42
|
+
&& Number.isSafeInteger(value.pid)
|
|
43
|
+
&& (value.pid ?? 0) > 0
|
|
44
|
+
&& typeof value.startTime === 'string'
|
|
45
|
+
&& value.startTime.length > 0
|
|
46
|
+
&& typeof value.token === 'string'
|
|
47
|
+
&& value.token.length > 0
|
|
48
|
+
&& typeof value.owner === 'string'
|
|
49
|
+
&& typeof value.canonicalRepoRoot === 'string'
|
|
50
|
+
&& typeof value.taskId === 'string'
|
|
51
|
+
&& typeof value.acquiredAt === 'string') {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Fall through to the stable lock failure below.
|
|
57
|
+
}
|
|
58
|
+
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'lifecycle lock owner record is invalid', { operation: 'read', errno: null, key });
|
|
59
|
+
}
|
|
60
|
+
function unlinkIfPresent(file) {
|
|
61
|
+
try {
|
|
62
|
+
fs.unlinkSync(file);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
if (errno(error) !== 'ENOENT')
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function sameInode(left, right) {
|
|
70
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
71
|
+
}
|
|
72
|
+
function reclaimStaleLock(fixed, quarantine, snapshot, key, link) {
|
|
73
|
+
let linked = false;
|
|
74
|
+
try {
|
|
75
|
+
link(fixed, quarantine);
|
|
76
|
+
linked = true;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
const disposition = mapLinkError('quarantine', error, key);
|
|
80
|
+
if (disposition === 'missing')
|
|
81
|
+
return;
|
|
82
|
+
if (disposition === 'exists') {
|
|
83
|
+
try {
|
|
84
|
+
const quarantineSnapshot = fs.readFileSync(quarantine, 'utf8');
|
|
85
|
+
const fixedStat = fs.statSync(fixed);
|
|
86
|
+
const quarantineStat = fs.statSync(quarantine);
|
|
87
|
+
if (quarantineSnapshot === snapshot && sameInode(fixedStat, quarantineStat)) {
|
|
88
|
+
unlinkIfPresent(quarantine);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (readError) {
|
|
92
|
+
if (errno(readError) !== 'ENOENT')
|
|
93
|
+
throw readError;
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (disposition instanceof TaskExecutionLockError)
|
|
98
|
+
throw disposition;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const fixedSnapshot = fs.readFileSync(fixed, 'utf8');
|
|
102
|
+
const fixedStat = fs.statSync(fixed);
|
|
103
|
+
const quarantineStat = fs.statSync(quarantine);
|
|
104
|
+
if (fixedSnapshot === snapshot && sameInode(fixedStat, quarantineStat))
|
|
105
|
+
unlinkIfPresent(fixed);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (errno(error) !== 'ENOENT')
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
if (linked)
|
|
113
|
+
unlinkIfPresent(quarantine);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function cleanStaleCandidates(lockRoot, key, identityMatches) {
|
|
117
|
+
const prefix = `${key}.candidate.`;
|
|
118
|
+
for (const name of fs.readdirSync(lockRoot)) {
|
|
119
|
+
if (!name.startsWith(prefix))
|
|
120
|
+
continue;
|
|
121
|
+
const candidate = path.join(lockRoot, name);
|
|
122
|
+
try {
|
|
123
|
+
const owner = parseOwner(fs.readFileSync(candidate, 'utf8'), key);
|
|
124
|
+
if (!identityMatches(owner))
|
|
125
|
+
unlinkIfPresent(candidate);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// A malformed candidate never owns the fixed lock and is left for manual diagnosis.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function withTaskExecutionLock(repoRoot, taskId, ownerName, callback, options = {}) {
|
|
133
|
+
let identity;
|
|
134
|
+
try {
|
|
135
|
+
identity = lockKey(repoRoot, taskId);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'failed to canonicalize the repository for lifecycle locking', { operation: 'canonicalize', errno: errno(error), key: 'unresolved' });
|
|
139
|
+
}
|
|
140
|
+
const { canonicalRepoRoot, key } = identity;
|
|
141
|
+
const lockRoot = options.lockRoot ?? path.join(os.tmpdir(), 'agent-infra-lifecycle-locks');
|
|
142
|
+
const token = (options.token ?? randomUUID)();
|
|
143
|
+
const candidate = path.join(lockRoot, `${key}.candidate.${process.pid}.${token}`);
|
|
144
|
+
const fixed = path.join(lockRoot, `${key}.lock`);
|
|
145
|
+
const link = options.linkSync ?? fs.linkSync;
|
|
146
|
+
const startTime = (options.getStartTime ?? getProcessStartTime)(process.pid);
|
|
147
|
+
if (startTime === null) {
|
|
148
|
+
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'current process start time is unavailable', { operation: 'identity', errno: null, key });
|
|
149
|
+
}
|
|
150
|
+
const owner = {
|
|
151
|
+
version: 1,
|
|
152
|
+
pid: process.pid,
|
|
153
|
+
startTime,
|
|
154
|
+
token,
|
|
155
|
+
owner: ownerName,
|
|
156
|
+
canonicalRepoRoot,
|
|
157
|
+
taskId,
|
|
158
|
+
acquiredAt: (options.now ?? (() => new Date().toISOString()))()
|
|
159
|
+
};
|
|
160
|
+
const serialized = `${JSON.stringify(owner)}\n`;
|
|
161
|
+
let ownsFixed = false;
|
|
162
|
+
try {
|
|
163
|
+
fs.mkdirSync(lockRoot, { recursive: true });
|
|
164
|
+
cleanStaleCandidates(lockRoot, key, options.identityMatches ?? processIdentityMatches);
|
|
165
|
+
const descriptor = fs.openSync(candidate, 'wx', 0o600);
|
|
166
|
+
try {
|
|
167
|
+
fs.writeFileSync(descriptor, serialized);
|
|
168
|
+
fs.fsyncSync(descriptor);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
fs.closeSync(descriptor);
|
|
172
|
+
}
|
|
173
|
+
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
174
|
+
try {
|
|
175
|
+
link(candidate, fixed);
|
|
176
|
+
ownsFixed = true;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const disposition = mapLinkError('acquire', error, key);
|
|
181
|
+
if (disposition instanceof TaskExecutionLockError)
|
|
182
|
+
throw disposition;
|
|
183
|
+
let snapshot;
|
|
184
|
+
let current;
|
|
185
|
+
try {
|
|
186
|
+
snapshot = fs.readFileSync(fixed, 'utf8');
|
|
187
|
+
current = parseOwner(snapshot, key);
|
|
188
|
+
}
|
|
189
|
+
catch (readError) {
|
|
190
|
+
if (errno(readError) === 'ENOENT')
|
|
191
|
+
continue;
|
|
192
|
+
throw readError;
|
|
193
|
+
}
|
|
194
|
+
if ((options.identityMatches ?? processIdentityMatches)(current)) {
|
|
195
|
+
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_BUSY', 'another lifecycle operation holds the task lock', { operation: 'acquire', errno: 'EEXIST', key });
|
|
196
|
+
}
|
|
197
|
+
reclaimStaleLock(fixed, `${fixed}.quarantine.${current.token}`, snapshot, key, link);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!ownsFixed) {
|
|
201
|
+
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_BUSY', 'lifecycle task lock could not be acquired after stale recovery', { operation: 'acquire', errno: 'EEXIST', key });
|
|
202
|
+
}
|
|
203
|
+
unlinkIfPresent(candidate);
|
|
204
|
+
return callback();
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (error instanceof TaskExecutionLockError)
|
|
208
|
+
throw error;
|
|
209
|
+
throw new TaskExecutionLockError('ORCHESTRATION_LOCK_FAILED', 'lifecycle task lock operation failed', { operation: ownsFixed ? 'callback' : 'acquire', errno: errno(error), key });
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
try {
|
|
213
|
+
unlinkIfPresent(candidate);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Candidate cleanup is best-effort and must not replace the primary result.
|
|
217
|
+
}
|
|
218
|
+
if (ownsFixed) {
|
|
219
|
+
try {
|
|
220
|
+
const current = parseOwner(fs.readFileSync(fixed, 'utf8'), key);
|
|
221
|
+
if (current.token === token)
|
|
222
|
+
unlinkIfPresent(fixed);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// A missing or replaced fixed lock is not owned by this invocation.
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
export { TaskExecutionLockError, lockKey, mapLinkError, withTaskExecutionLock };
|
|
231
|
+
//# sourceMappingURL=task-execution-lock.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const LOCAL_VERIFICATION_CHECKS = [
|
|
2
2
|
'task-meta', 'artifact', 'implementation-input', 'activity-log', 'completion-checklist',
|
|
3
|
-
'orchestration-state'
|
|
3
|
+
'orchestration-state', 'orchestration-evidence'
|
|
4
4
|
];
|
|
5
5
|
function isLocalVerificationCheck(value) {
|
|
6
6
|
return LOCAL_VERIFICATION_CHECKS.includes(value);
|
|
@@ -96,6 +96,8 @@ function runCheck(type, context, shared) {
|
|
|
96
96
|
return checkCompletionChecklist(context);
|
|
97
97
|
case "review-ledger":
|
|
98
98
|
return checkReviewLedger(context);
|
|
99
|
+
case "manual-validation":
|
|
100
|
+
return checkManualValidation(context);
|
|
99
101
|
case "review-summary":
|
|
100
102
|
return checkReviewSummary(context);
|
|
101
103
|
case "review-fact":
|
|
@@ -104,6 +106,8 @@ function runCheck(type, context, shared) {
|
|
|
104
106
|
return checkPostReviewCommit(context);
|
|
105
107
|
case "orchestration-state":
|
|
106
108
|
return checkOrchestrationState(context);
|
|
109
|
+
case "orchestration-evidence":
|
|
110
|
+
return checkOrchestrationEvidence(context);
|
|
107
111
|
default: {
|
|
108
112
|
const adapter = PLATFORM_ADAPTERS[type];
|
|
109
113
|
if (!adapter) {
|
|
@@ -116,6 +120,22 @@ function runCheck(type, context, shared) {
|
|
|
116
120
|
}
|
|
117
121
|
}
|
|
118
122
|
}
|
|
123
|
+
function isFailClosedLegacyPause(run) {
|
|
124
|
+
if (run.schemaVersion !== 1
|
|
125
|
+
|| run.status !== 'paused'
|
|
126
|
+
|| run.pause?.recoverable !== false
|
|
127
|
+
|| !exactText(run.pause?.message)
|
|
128
|
+
|| !Array.isArray(run.receipts)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (run.pause.code === 'ORCHESTRATION_HISTORICAL_EFFORT_UNVERIFIED') {
|
|
132
|
+
return run.receipts.length > 0 && run.pendingDelegation === null;
|
|
133
|
+
}
|
|
134
|
+
if (run.pause.code === 'ORCHESTRATION_DELEGATION_BUSY') {
|
|
135
|
+
return run.pendingDelegation != null && run.pendingDelegation.status !== 'sealed';
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
119
139
|
function checkOrchestrationState({ taskDir }) {
|
|
120
140
|
const file = path.join(taskDir, 'orchestration.json');
|
|
121
141
|
const stat = safeStat(file);
|
|
@@ -128,22 +148,163 @@ function checkOrchestrationState({ taskDir }) {
|
|
|
128
148
|
catch (error) {
|
|
129
149
|
return failResult('orchestration-state', `Invalid orchestration.json: ${String(error)}`);
|
|
130
150
|
}
|
|
131
|
-
if (
|
|
151
|
+
if (isFailClosedLegacyPause(run)) {
|
|
152
|
+
return passResult('orchestration-state', 'Legacy orchestration run is safely paused');
|
|
153
|
+
}
|
|
154
|
+
if (run.schemaVersion !== 2 || !['paused', 'completed'].includes(run.status)) {
|
|
132
155
|
return failResult('orchestration-state', `Expected paused or completed run, received '${run.status}'`);
|
|
133
156
|
}
|
|
134
157
|
if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
|
|
135
158
|
return failResult('orchestration-state', 'Paused run requires a stable pause code and message');
|
|
136
159
|
}
|
|
137
160
|
if (run.status === 'completed') {
|
|
138
|
-
if (run.
|
|
161
|
+
if (run.completionEvidence != null) {
|
|
162
|
+
const evidenceError = validateCleanCompletionEvidence(run);
|
|
163
|
+
if (evidenceError)
|
|
164
|
+
return failResult('orchestration-state', evidenceError);
|
|
165
|
+
}
|
|
166
|
+
else if (run.pendingDelegation !== null || !run.commitAuthorization?.consumedAt) {
|
|
139
167
|
return failResult('orchestration-state', 'Completed run must consume commit authorization and clear pending delegation');
|
|
140
168
|
}
|
|
141
169
|
}
|
|
170
|
+
else if (run.completionEvidence != null) {
|
|
171
|
+
return failResult('orchestration-state', 'Clean completion evidence requires a completed run');
|
|
172
|
+
}
|
|
142
173
|
else if (run.pendingDelegation?.status === 'sealed') {
|
|
143
174
|
return failResult('orchestration-state', 'Paused run has a sealed delegation that could still advance');
|
|
144
175
|
}
|
|
145
176
|
return passResult('orchestration-state', `Orchestration run is safely ${run.status}`);
|
|
146
177
|
}
|
|
178
|
+
function exactText(value) {
|
|
179
|
+
return typeof value === 'string' && value.length > 0 && value.trim() === value;
|
|
180
|
+
}
|
|
181
|
+
function validateCleanCompletionEvidence(run) {
|
|
182
|
+
const evidence = run.completionEvidence;
|
|
183
|
+
const shaPattern = /^[0-9a-f]{40}$/i;
|
|
184
|
+
if (evidence?.kind !== 'reviewed-head-clean'
|
|
185
|
+
|| !exactText(evidence.observedAt)
|
|
186
|
+
|| Number.isNaN(Date.parse(evidence.observedAt))
|
|
187
|
+
|| !shaPattern.test(evidence.head)
|
|
188
|
+
|| !shaPattern.test(evidence.headTree)
|
|
189
|
+
|| !shaPattern.test(evidence.worktreeTree)
|
|
190
|
+
|| !shaPattern.test(evidence.lastReviewedCommit)
|
|
191
|
+
|| !Number.isInteger(evidence.prNumber)
|
|
192
|
+
|| evidence.prNumber <= 0
|
|
193
|
+
|| !shaPattern.test(evidence.prHead)) {
|
|
194
|
+
return 'Clean completion evidence has an invalid structure';
|
|
195
|
+
}
|
|
196
|
+
if (evidence.head !== evidence.lastReviewedCommit
|
|
197
|
+
|| evidence.head !== evidence.prHead
|
|
198
|
+
|| evidence.headTree !== evidence.worktreeTree) {
|
|
199
|
+
return 'Clean completion evidence does not bind matching commit and tree identities';
|
|
200
|
+
}
|
|
201
|
+
if (run.status !== 'completed'
|
|
202
|
+
|| run.pendingDelegation !== null
|
|
203
|
+
|| run.commitAuthorization?.issuedAt !== null
|
|
204
|
+
|| run.commitAuthorization?.consumedAt !== null
|
|
205
|
+
|| !Array.isArray(run.receipts)
|
|
206
|
+
|| run.receipts.some((receipt) => receipt.stage === 'commit')) {
|
|
207
|
+
return 'Clean completion evidence conflicts with commit delegation state';
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
function checkOrchestrationEvidence({ taskDir }) {
|
|
212
|
+
const file = path.join(taskDir, 'orchestration.json');
|
|
213
|
+
const stat = safeStat(file);
|
|
214
|
+
if (!stat?.isFile())
|
|
215
|
+
return failResult('orchestration-evidence', 'orchestration.json is missing');
|
|
216
|
+
let run;
|
|
217
|
+
try {
|
|
218
|
+
run = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
return failResult('orchestration-evidence', `Invalid orchestration.json: ${String(error)}`);
|
|
222
|
+
}
|
|
223
|
+
if (isFailClosedLegacyPause(run)) {
|
|
224
|
+
return passResult('orchestration-evidence', 'Legacy orchestration evidence is preserved in a fail-closed pause');
|
|
225
|
+
}
|
|
226
|
+
const policy = run.modelPolicy;
|
|
227
|
+
if (!policy
|
|
228
|
+
|| !exactText(policy.executor?.model)
|
|
229
|
+
|| !exactText(policy.executor?.reasoningEffort)
|
|
230
|
+
|| !exactText(policy.reviewer?.model)
|
|
231
|
+
|| !exactText(policy.reviewer?.reasoningEffort)) {
|
|
232
|
+
return failResult('orchestration-evidence', 'Run model policy requires exact executor and reviewer model and effort');
|
|
233
|
+
}
|
|
234
|
+
if (!run.modelPolicySource
|
|
235
|
+
|| !['explicit', 'project-config'].includes(run.modelPolicySource.kind)
|
|
236
|
+
|| !exactText(run.modelPolicySource.client)
|
|
237
|
+
|| !exactText(run.modelPolicySource.resolvedAt)
|
|
238
|
+
|| !Array.isArray(run.recoveryHistory)) {
|
|
239
|
+
return failResult('orchestration-evidence', 'Run model policy source or recovery history is invalid');
|
|
240
|
+
}
|
|
241
|
+
for (const recovery of run.recoveryHistory) {
|
|
242
|
+
if (recovery.code !== 'MODEL_POLICY_SUPPLEMENTED'
|
|
243
|
+
|| recovery.previousSchemaVersion !== 1
|
|
244
|
+
|| recovery.receiptCount !== 0
|
|
245
|
+
|| recovery.pendingDelegation !== false
|
|
246
|
+
|| !exactText(recovery.recoveredAt)
|
|
247
|
+
|| !recovery.policySource) {
|
|
248
|
+
return failResult('orchestration-evidence', 'Run recovery history contains invalid provenance');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (!Array.isArray(run.receipts)) {
|
|
252
|
+
return failResult('orchestration-evidence', 'Run receipts must be an array');
|
|
253
|
+
}
|
|
254
|
+
if (run.completionEvidence != null) {
|
|
255
|
+
const evidenceError = validateCleanCompletionEvidence(run);
|
|
256
|
+
if (evidenceError)
|
|
257
|
+
return failResult('orchestration-evidence', evidenceError);
|
|
258
|
+
}
|
|
259
|
+
else if (run.status === 'completed' && (run.receipts.length === 0
|
|
260
|
+
|| run.receipts.at(-1)?.stage !== 'commit'
|
|
261
|
+
|| run.receipts.at(-1)?.status !== 'consumed')) {
|
|
262
|
+
return failResult('orchestration-evidence', 'Completed run requires a final consumed commit receipt');
|
|
263
|
+
}
|
|
264
|
+
if (run.pendingDelegation && !['prepared', 'activated', 'stage-completed'].includes(run.pendingDelegation.status)) {
|
|
265
|
+
return failResult('orchestration-evidence', `Pending receipt has unsafe status '${run.pendingDelegation.status}'`);
|
|
266
|
+
}
|
|
267
|
+
const receipts = [...run.receipts, ...(run.pendingDelegation ? [run.pendingDelegation] : [])];
|
|
268
|
+
for (const receipt of receipts) {
|
|
269
|
+
const expectedPolicy = policy[receipt.role];
|
|
270
|
+
if (!expectedPolicy || receipt.requestedModel !== expectedPolicy.model) {
|
|
271
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' requested model does not match its role policy`);
|
|
272
|
+
}
|
|
273
|
+
if (receipt.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
274
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' requested reasoning effort does not match its role policy`);
|
|
275
|
+
}
|
|
276
|
+
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
|
|
277
|
+
if (activated) {
|
|
278
|
+
if (!exactText(receipt.actualModel)) {
|
|
279
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual model`);
|
|
280
|
+
}
|
|
281
|
+
if (!exactText(receipt.actualReasoningEffort)) {
|
|
282
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
|
|
283
|
+
}
|
|
284
|
+
if (!exactText(receipt.parentId) || !exactText(receipt.childId) || receipt.parentId === receipt.childId || receipt.spawnMode !== 'fresh') {
|
|
285
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
|
|
286
|
+
}
|
|
287
|
+
if (receipt.actualModel !== receipt.requestedModel && !exactText(receipt.modelFallbackReason)) {
|
|
288
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' model fallback is not justified`);
|
|
289
|
+
}
|
|
290
|
+
if (receipt.actualModel === receipt.requestedModel && receipt.modelFallbackReason !== null) {
|
|
291
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated model fallback reason`);
|
|
292
|
+
}
|
|
293
|
+
if (receipt.actualReasoningEffort !== receipt.requestedReasoningEffort
|
|
294
|
+
&& !exactText(receipt.reasoningEffortFallbackReason)) {
|
|
295
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' reasoning-effort fallback is not justified`);
|
|
296
|
+
}
|
|
297
|
+
if (receipt.actualReasoningEffort === receipt.requestedReasoningEffort
|
|
298
|
+
&& receipt.reasoningEffortFallbackReason !== null) {
|
|
299
|
+
return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated reasoning-effort fallback reason`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (run.receipts.some((receipt) => receipt.status !== 'consumed')) {
|
|
304
|
+
return failResult('orchestration-evidence', 'Historical receipts must be consumed');
|
|
305
|
+
}
|
|
306
|
+
return passResult('orchestration-evidence', 'Persisted orchestration model and delegation evidence is internally consistent');
|
|
307
|
+
}
|
|
147
308
|
// === Check Functions ===
|
|
148
309
|
function checkTaskMeta({ taskDir, config }) {
|
|
149
310
|
const task = loadTask(taskDir);
|
|
@@ -624,6 +785,61 @@ function checkReviewLedger({ taskDir, config }) {
|
|
|
624
785
|
const scopeLabel = stageScope ? ` for stages [${stageScope.join(", ")}]` : "";
|
|
625
786
|
return passResult("review-ledger", `Disagreement ledger clean (${inScopeCount} in-scope entries terminal${scopeLabel})`);
|
|
626
787
|
}
|
|
788
|
+
function checkManualValidation({ taskDir }) {
|
|
789
|
+
// 1. Latest review-code artifact; none -> pass (no review, check not applicable).
|
|
790
|
+
const review = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
791
|
+
if (!review.ok) {
|
|
792
|
+
return passResult("manual-validation", "No review-code artifact; manual validation not applicable");
|
|
793
|
+
}
|
|
794
|
+
// 2. Parse the numeric manual-validation count; unparseable/null -> fail closed
|
|
795
|
+
// (review-code.completed already enforces a numeric count on the normal path).
|
|
796
|
+
const parsed = parseReviewSummary(fs.readFileSync(review.path, "utf8"));
|
|
797
|
+
if (!parsed.ok || parsed.summary.manualValidation === null) {
|
|
798
|
+
return failResult("manual-validation", "Latest review-code has no numeric manual-validation count");
|
|
799
|
+
}
|
|
800
|
+
// 3. Count 0 -> pass (no pending items to cover).
|
|
801
|
+
if (parsed.summary.manualValidation === 0) {
|
|
802
|
+
return passResult("manual-validation", "No pending manual validation items");
|
|
803
|
+
}
|
|
804
|
+
// 4. Highest-round manual-validation artifact; none -> fail (items pending,
|
|
805
|
+
// run complete-manual-validation first). A falsy artifactFile goes through
|
|
806
|
+
// pattern parsing; resolveArtifactPath only returns { ok, path }.
|
|
807
|
+
const resolved = resolveArtifactPath(taskDir, "manual-validation.md|manual-validation-r{N}.md", "");
|
|
808
|
+
if (!resolved.ok) {
|
|
809
|
+
return failResult("manual-validation", `${parsed.summary.manualValidation} manual validation item(s) pending: run /complete-manual-validation`);
|
|
810
|
+
}
|
|
811
|
+
// 5. The Activity Log must record the matching completion entry; the file name
|
|
812
|
+
// is derived with path.basename (resolveArtifactPath does not return fileName).
|
|
813
|
+
const artifactName = path.basename(resolved.path);
|
|
814
|
+
const task = loadTask(taskDir);
|
|
815
|
+
if (!task.ok)
|
|
816
|
+
return failResult("manual-validation", task.message);
|
|
817
|
+
const log = getSectionContent(task.content, ["活动日志", "Activity Log"]);
|
|
818
|
+
const completed = new RegExp(`\\*\\*Complete Manual Validation\\*\\*[\\s\\S]*?Manual validation passed → ${escapeRegExp(artifactName)};`);
|
|
819
|
+
if (!completed.test(log)) {
|
|
820
|
+
return failResult("manual-validation", `Manual validation artifact exists but completion is not recorded for ${artifactName}: re-run /complete-manual-validation`);
|
|
821
|
+
}
|
|
822
|
+
// 6. Timing correlation (PL-3 fix, PL-4 corrected): the completion entry must
|
|
823
|
+
// sit AFTER the latest review-code round's completion entry in the
|
|
824
|
+
// append-only Activity Log. indexOf is a literal search, so the artifactName
|
|
825
|
+
// must NOT be passed through escapeRegExp here (that escapes '.' to '\.',
|
|
826
|
+
// which indexOf never matches). Not reusing completed.exec().index: exec
|
|
827
|
+
// anchors to the first '**Complete Manual Validation**' heading, which can
|
|
828
|
+
// belong to an earlier round's entry and misorder the standard path.
|
|
829
|
+
const reviewDoneIndex = log.indexOf(`**Review Code (Round ${review.round})** by `);
|
|
830
|
+
const completedIndex = log.indexOf(`Manual validation passed → ${artifactName};`);
|
|
831
|
+
if (reviewDoneIndex === -1) {
|
|
832
|
+
// The latest review-code round's completion entry is missing entirely (abnormal
|
|
833
|
+
// state, e.g. a restored/historical task without a review-code.completed record).
|
|
834
|
+
// Re-running complete-manual-validation cannot restore it, so the remediation
|
|
835
|
+
// targets the review record itself. Fail closed either way.
|
|
836
|
+
return failResult("manual-validation", `Latest review-code (round ${review.round}) completion entry is missing from the Activity Log (abnormal state): re-run review-code or restore the completion record`);
|
|
837
|
+
}
|
|
838
|
+
if (completedIndex < reviewDoneIndex) {
|
|
839
|
+
return failResult("manual-validation", `Latest review-code (round ${review.round}) came after the manual validation completion recorded for ${artifactName}: re-run /complete-manual-validation to cover new pending items`);
|
|
840
|
+
}
|
|
841
|
+
return passResult("manual-validation", `Manual validation completed → ${artifactName}`);
|
|
842
|
+
}
|
|
627
843
|
function checkPostReviewCommit({ taskDir, config }) {
|
|
628
844
|
const reviewArtifact = findAuthoritativeReviewCodeArtifact(taskDir);
|
|
629
845
|
if (!reviewArtifact.ok) {
|
|
@@ -19,7 +19,7 @@ const VERIFICATION_CATALOG = {
|
|
|
19
19
|
'block-task.completed': gate('block-task', 'blocked'),
|
|
20
20
|
'cancel-task.completed': gate('cancel-task', 'completed'),
|
|
21
21
|
'commit.completed': gate('commit', 'active'),
|
|
22
|
-
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['review-ledger', 'post-review-commit', 'platform-sync-preflight'] },
|
|
22
|
+
'complete-task.preflight': { skill: 'complete-task', expectedState: 'active', mode: 'checks', checks: ['review-ledger', 'manual-validation', 'post-review-commit', 'platform-sync-preflight'] },
|
|
23
23
|
'complete-task.completed': gate('complete-task', 'completed'),
|
|
24
24
|
'create-pr.completed': gate('create-pr', 'active'),
|
|
25
25
|
'create-task.completed': gate('create-task', 'active'),
|
|
@@ -27,8 +27,9 @@ const VERIFICATION_CATALOG = {
|
|
|
27
27
|
'import-dependabot.completed': gate('import-dependabot', 'active'),
|
|
28
28
|
'import-issue.completed': gate('import-issue', 'active'),
|
|
29
29
|
'watch-pr.completed': gate('watch-pr', 'active'),
|
|
30
|
-
'
|
|
31
|
-
'run-task.
|
|
30
|
+
'review-pr.completed': gate('review-pr', 'active', 'pr-review'),
|
|
31
|
+
'run-task.paused': { skill: 'run-task', expectedState: 'active', mode: 'checks', checks: ['orchestration-state', 'orchestration-evidence'] },
|
|
32
|
+
'run-task.completed': { skill: 'run-task', expectedState: 'active', mode: 'checks', checks: ['orchestration-state', 'orchestration-evidence'] }
|
|
32
33
|
};
|
|
33
34
|
function failure(request, code, message, extra = {}) {
|
|
34
35
|
return {
|
|
@@ -10,15 +10,14 @@ function git(repoRoot, indexFile, args) {
|
|
|
10
10
|
}
|
|
11
11
|
return result.stdout;
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function captureWorktreeTree(repoRoot, forcedPath) {
|
|
14
14
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-infra-orchestration-index-'));
|
|
15
15
|
const indexFile = path.join(tempDir, 'index');
|
|
16
16
|
try {
|
|
17
17
|
git(repoRoot, indexFile, ['read-tree', 'HEAD']);
|
|
18
18
|
git(repoRoot, indexFile, ['add', '-A', '--', ':/']);
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
git(repoRoot, indexFile, ['add', '-f', '--', '.agents/workspace/active']);
|
|
19
|
+
if (forcedPath && fs.existsSync(path.join(repoRoot, forcedPath))) {
|
|
20
|
+
git(repoRoot, indexFile, ['add', '-f', '--', forcedPath]);
|
|
22
21
|
}
|
|
23
22
|
return git(repoRoot, indexFile, ['write-tree']).trim();
|
|
24
23
|
}
|
|
@@ -26,9 +25,22 @@ function captureWorkspaceSnapshot(repoRoot) {
|
|
|
26
25
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
27
26
|
}
|
|
28
27
|
}
|
|
28
|
+
function captureWorkspaceSnapshot(repoRoot, taskId) {
|
|
29
|
+
const workspaceRelative = taskId
|
|
30
|
+
? `.agents/workspace/active/${taskId}`
|
|
31
|
+
: '.agents/workspace/active';
|
|
32
|
+
return captureWorktreeTree(repoRoot, workspaceRelative);
|
|
33
|
+
}
|
|
34
|
+
function captureRepositorySnapshot(repoRoot) {
|
|
35
|
+
return {
|
|
36
|
+
head: git(repoRoot, null, ['rev-parse', 'HEAD']).trim(),
|
|
37
|
+
headTree: git(repoRoot, null, ['rev-parse', 'HEAD^{tree}']).trim(),
|
|
38
|
+
worktreeTree: captureWorktreeTree(repoRoot, null)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
29
41
|
function diffWorkspaceSnapshots(repoRoot, before, after) {
|
|
30
42
|
const output = git(repoRoot, null, ['diff', '--name-only', '-z', before, after]);
|
|
31
43
|
return output.split('\0').filter(Boolean);
|
|
32
44
|
}
|
|
33
|
-
export { captureWorkspaceSnapshot, diffWorkspaceSnapshots };
|
|
45
|
+
export { captureRepositorySnapshot, captureWorkspaceSnapshot, diffWorkspaceSnapshots };
|
|
34
46
|
//# sourceMappingURL=workspace-snapshot.js.map
|