@fitlab-ai/agent-infra 0.9.0 → 0.9.2
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 +22 -5
- package/README.zh-CN.md +21 -5
- package/bin/cli.ts +12 -0
- package/dist/bin/cli.js +14 -0
- package/dist/bin/internal-cli.js +20 -0
- package/dist/lib/agent-client.js +163 -0
- package/dist/lib/agent-clients/adapter.js +294 -0
- package/dist/lib/agent-clients/adapters/claude-code.js +62 -0
- package/dist/lib/agent-clients/adapters/codex-sandbox.js +210 -0
- package/dist/lib/agent-clients/adapters/codex.js +54 -0
- package/dist/lib/agent-clients/adapters/gemini-cli.js +64 -0
- package/dist/lib/agent-clients/adapters/index.js +20 -0
- package/dist/lib/agent-clients/adapters/opencode.js +64 -0
- package/dist/lib/agent-clients/config.js +177 -0
- package/dist/lib/agent-clients/custom-tuis.js +99 -0
- package/dist/lib/agent-clients/invocation.js +9 -0
- package/dist/lib/agent-clients/next-steps.js +38 -0
- package/dist/lib/agent-clients/project-assets.js +45 -0
- package/dist/lib/agent-clients/reconcile.js +235 -0
- package/dist/lib/agent-clients/registry.js +72 -0
- package/dist/lib/agent-clients/schema.js +57 -0
- package/dist/lib/agent-clients/types.js +29 -0
- package/dist/lib/builtin-tuis.js +9 -14
- package/dist/lib/defaults.json +85 -73
- package/dist/lib/git/review-snapshot.js +25 -10
- package/dist/lib/git/reviewed-commit-equivalence.js +110 -0
- package/dist/lib/init.js +47 -91
- package/dist/lib/internal/agent-client.js +117 -0
- package/dist/lib/internal/git-workflow.js +1 -1
- package/dist/lib/internal/platform-release-notes.js +53 -0
- package/dist/lib/internal/release-workflow.js +175 -17
- package/dist/lib/internal/task-orchestration.js +151 -0
- package/dist/lib/internal/task-review.js +65 -0
- package/dist/lib/merge.js +2 -1
- package/dist/lib/platform/adapters.js +38 -1
- package/dist/lib/platform/change-request-git-evidence.js +113 -0
- package/dist/lib/platform/github-release-notes.js +96 -0
- package/dist/lib/platform/issues.js +4 -2
- package/dist/lib/platform/merged-pr-equivalence.js +84 -0
- package/dist/lib/platform/pr-checks.js +43 -15
- package/dist/lib/platform/pull-requests.js +113 -7
- package/dist/lib/platform/release-notes.js +143 -0
- package/dist/lib/platform/releases.js +1 -1
- package/dist/lib/platform/verification-required.js +31 -9
- package/dist/lib/prompt.js +15 -3
- package/dist/lib/release/channels.js +4 -1
- package/dist/lib/release/workflow.js +4 -2
- package/dist/lib/remove-dir.js +25 -0
- package/dist/lib/run/tui.js +8 -2
- package/dist/lib/sandbox/agent-client-reconciler.js +234 -0
- package/dist/lib/sandbox/build-proxy.js +63 -0
- package/dist/lib/sandbox/clipboard/linux.js +2 -1
- package/dist/lib/sandbox/commands/create.js +133 -189
- package/dist/lib/sandbox/commands/enter.js +17 -1
- package/dist/lib/sandbox/commands/prune.js +2 -2
- package/dist/lib/sandbox/commands/rebuild.js +24 -7
- package/dist/lib/sandbox/commands/rm.js +3 -2
- package/dist/lib/sandbox/config.js +21 -4
- package/dist/lib/sandbox/constants.js +3 -0
- package/dist/lib/sandbox/dockerfile.js +2 -1
- package/dist/lib/sandbox/image-build.js +2 -1
- package/dist/lib/sandbox/managed-fs.js +10 -5
- package/dist/lib/sandbox/recovery.js +52 -36
- package/dist/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/dist/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/dist/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/dist/lib/sandbox/shell.js +3 -2
- package/dist/lib/sandbox/tool-types.js +8 -0
- package/dist/lib/sandbox/tools.js +2 -126
- package/dist/lib/task/artifact-lifecycle.js +1 -49
- package/dist/lib/task/commands/decisions.js +50 -20
- package/dist/lib/task/commands/issue-body.js +10 -5
- package/dist/lib/task/delegation-receipts.js +107 -0
- package/dist/lib/task/events.js +101 -2
- package/dist/lib/task/issue-form.js +2 -0
- package/dist/lib/task/orchestration.js +420 -0
- package/dist/lib/task/review-artifacts.js +143 -59
- package/dist/lib/task/review-finalization.js +166 -0
- package/dist/lib/task/review-fingerprint.js +13 -0
- package/dist/lib/task/verification-checks.js +2 -1
- package/dist/lib/task/verification-engine.js +168 -23
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/verification.js +4 -2
- package/dist/lib/task/workspace-snapshot.js +34 -0
- package/dist/lib/update.js +42 -161
- package/lib/agent-client.ts +214 -0
- package/lib/agent-clients/adapter.ts +461 -0
- package/lib/agent-clients/adapters/claude-code.ts +70 -0
- package/lib/agent-clients/adapters/codex-sandbox.ts +245 -0
- package/lib/agent-clients/adapters/codex.ts +58 -0
- package/lib/agent-clients/adapters/gemini-cli.ts +65 -0
- package/lib/agent-clients/adapters/index.ts +27 -0
- package/lib/agent-clients/adapters/opencode.ts +66 -0
- package/lib/agent-clients/config.ts +278 -0
- package/lib/agent-clients/custom-tuis.ts +149 -0
- package/lib/agent-clients/invocation.ts +19 -0
- package/lib/agent-clients/next-steps.ts +72 -0
- package/lib/agent-clients/project-assets.ts +96 -0
- package/lib/agent-clients/reconcile.ts +324 -0
- package/lib/agent-clients/registry.ts +144 -0
- package/lib/agent-clients/schema.ts +62 -0
- package/lib/agent-clients/types.ts +78 -0
- package/lib/benchmark/schemas/v1.0.0/case-manifest.schema.json +120 -0
- package/lib/benchmark/schemas/v1.0.0/common.schema.json +109 -0
- package/lib/benchmark/schemas/v1.0.0/grader-result.schema.json +160 -0
- package/lib/benchmark/schemas/v1.0.0/run-manifest.schema.json +122 -0
- package/lib/benchmark/schemas/v1.0.0/subject.schema.json +227 -0
- package/lib/builtin-tuis.ts +16 -15
- package/lib/defaults.json +24 -12
- package/lib/git/review-snapshot.ts +39 -9
- package/lib/git/reviewed-commit-equivalence.ts +135 -0
- package/lib/init.ts +53 -121
- package/lib/internal/agent-client.ts +131 -0
- package/lib/internal/git-workflow.ts +1 -1
- package/lib/internal/platform-release-notes.ts +55 -0
- package/lib/internal/release-workflow.ts +172 -18
- package/lib/internal/task-orchestration.ts +132 -0
- package/lib/internal/task-review.ts +65 -0
- package/lib/merge.ts +2 -1
- package/lib/platform/adapters.ts +142 -3
- package/lib/platform/change-request-git-evidence.ts +159 -0
- package/lib/platform/github-release-notes.ts +132 -0
- package/lib/platform/issues.ts +6 -4
- package/lib/platform/merged-pr-equivalence.ts +107 -0
- package/lib/platform/pr-checks.ts +57 -28
- package/lib/platform/pull-requests.ts +140 -25
- package/lib/platform/release-notes.ts +167 -0
- package/lib/platform/releases.ts +1 -1
- package/lib/platform/verification-required.ts +37 -9
- package/lib/prompt.ts +16 -3
- package/lib/release/channels.ts +4 -1
- package/lib/release/workflow.ts +4 -3
- package/lib/remove-dir.ts +28 -0
- package/lib/run/tui.ts +9 -1
- package/lib/sandbox/agent-client-reconciler.ts +378 -0
- package/lib/sandbox/build-proxy.ts +94 -0
- package/lib/sandbox/clipboard/linux.ts +2 -1
- package/lib/sandbox/commands/create.ts +188 -203
- package/lib/sandbox/commands/enter.ts +23 -1
- package/lib/sandbox/commands/prune.ts +2 -2
- package/lib/sandbox/commands/rebuild.ts +29 -7
- package/lib/sandbox/commands/rm.ts +3 -2
- package/lib/sandbox/config.ts +31 -4
- package/lib/sandbox/constants.ts +6 -0
- package/lib/sandbox/dockerfile.ts +2 -1
- package/lib/sandbox/image-build.ts +4 -1
- package/lib/sandbox/managed-fs.ts +18 -5
- package/lib/sandbox/recovery.ts +98 -67
- package/lib/sandbox/runtimes/ai-tools.dockerfile +24 -3
- package/lib/sandbox/runtimes/base.dockerfile +8 -1
- package/lib/sandbox/runtimes/java17.dockerfile +8 -1
- package/lib/sandbox/runtimes/java21.dockerfile +8 -1
- package/lib/sandbox/runtimes/node20.dockerfile +8 -1
- package/lib/sandbox/runtimes/node22.dockerfile +8 -1
- package/lib/sandbox/runtimes/python3.dockerfile +8 -1
- package/lib/sandbox/shell.ts +4 -2
- package/lib/sandbox/tool-types.ts +143 -0
- package/lib/sandbox/tools.ts +5 -157
- package/lib/task/artifact-lifecycle.ts +1 -38
- package/lib/task/commands/decisions.ts +53 -20
- package/lib/task/commands/issue-body.ts +10 -5
- package/lib/task/delegation-receipts.ts +170 -0
- package/lib/task/events.ts +111 -3
- package/lib/task/issue-form.ts +4 -0
- package/lib/task/orchestration.ts +543 -0
- package/lib/task/review-artifacts.ts +201 -69
- package/lib/task/review-finalization.ts +275 -0
- package/lib/task/review-fingerprint.ts +15 -1
- package/lib/task/verification-checks.ts +2 -1
- package/lib/task/verification-engine.ts +190 -25
- package/lib/task/verification-review.ts +2 -0
- package/lib/task/verification.ts +6 -3
- package/lib/task/workspace-snapshot.ts +36 -0
- package/lib/update.ts +48 -184
- package/package.json +2 -2
- package/templates/.agents/README.en.md +21 -20
- package/templates/.agents/README.zh-CN.md +21 -20
- package/templates/.agents/hooks/lifecycle-delegation.js +64 -0
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/commit-and-pr.en.md +1 -0
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -0
- package/templates/.agents/rules/human-decision-context.en.md +14 -12
- package/templates/.agents/rules/human-decision-context.zh-CN.md +14 -12
- package/templates/.agents/rules/lifecycle-orchestration.en.md +21 -0
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +21 -0
- package/templates/.agents/rules/next-step-output.en.md +16 -0
- package/templates/.agents/rules/next-step-output.zh-CN.md +16 -0
- package/templates/.agents/rules/no-mid-flow-questions.en.md +2 -0
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +2 -0
- package/templates/.agents/rules/release-commands.en.md +2 -2
- package/templates/.agents/rules/release-commands.github.en.md +15 -39
- package/templates/.agents/rules/release-commands.github.zh-CN.md +14 -38
- package/templates/.agents/rules/release-commands.zh-CN.md +2 -2
- package/templates/.agents/rules/review-handshake.en.md +4 -3
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -3
- package/templates/.agents/rules/review-method.en.md +113 -0
- package/templates/.agents/rules/review-method.zh-CN.md +98 -0
- package/templates/.agents/rules/testing-discipline.en.md +20 -2
- package/templates/.agents/rules/testing-discipline.zh-CN.md +20 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +9 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +9 -6
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -16
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -16
- package/templates/.agents/skills/close-codescan/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +4 -4
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +14 -16
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +14 -16
- package/templates/.agents/skills/code-task/reference/output-template.en.md +3 -3
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +11 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +11 -1
- package/templates/.agents/skills/commit/reference/task-status-update.en.md +24 -28
- package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +24 -28
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +10 -10
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +10 -10
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +3 -3
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -38
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/create-task/SKILL.en.md +15 -11
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +15 -11
- package/templates/.agents/skills/create-task/reference/context-capture.en.md +29 -0
- package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +29 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/init-labels/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.en.md +4 -4
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/refine-title/SKILL.en.md +1 -1
- package/templates/.agents/skills/refine-title/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +51 -5
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +52 -6
- package/templates/.agents/skills/review-analysis/reference/review-criteria.en.md +25 -22
- package/templates/.agents/skills/review-analysis/reference/review-criteria.zh-CN.md +25 -22
- package/templates/.agents/skills/review-code/SKILL.en.md +28 -8
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +28 -8
- package/templates/.agents/skills/review-code/config/verify.en.json +7 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +7 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/concurrency-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.en.md +27 -0
- package/templates/.agents/skills/review-code/reference/cross-platform-risks.zh-CN.md +27 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.en.md +13 -0
- package/templates/.agents/skills/review-code/reference/documentation-antipatterns.zh-CN.md +13 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +22 -24
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +22 -24
- package/templates/.agents/skills/review-code/reference/report-template.en.md +54 -7
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +54 -7
- package/templates/.agents/skills/review-code/reference/review-criteria.en.md +29 -24
- package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +29 -24
- package/templates/.agents/skills/review-code/reference/security-risks.en.md +25 -0
- package/templates/.agents/skills/review-code/reference/security-risks.zh-CN.md +25 -0
- package/templates/.agents/skills/review-plan/SKILL.en.md +13 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +6 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +6 -0
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +13 -15
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +13 -15
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +61 -5
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +61 -5
- package/templates/.agents/skills/review-plan/reference/review-criteria.en.md +21 -24
- package/templates/.agents/skills/review-plan/reference/review-criteria.zh-CN.md +21 -24
- package/templates/.agents/skills/run-task/SKILL.en.md +22 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +22 -0
- package/templates/.agents/skills/run-task/config/verify.en.json +9 -0
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +9 -0
- package/templates/.agents/skills/test/SKILL.en.md +4 -4
- package/templates/.agents/skills/test/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.en.md +4 -4
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +5 -5
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +647 -203
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +4 -4
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.en.md +8 -8
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +8 -8
- package/templates/.agents/templates/task.en.md +18 -0
- package/templates/.agents/templates/task.zh-CN.md +18 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +6 -0
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +6 -0
- package/templates/.claude/commands/run-task.en.md +8 -0
- package/templates/.claude/commands/run-task.zh-CN.md +8 -0
- package/templates/.claude/settings.json +24 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +3 -0
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +3 -0
- package/templates/.gemini/commands/_project_/run-task.en.toml +8 -0
- package/templates/.gemini/commands/_project_/run-task.zh-CN.toml +8 -0
- package/templates/.opencode/commands/run-task.en.md +11 -0
- package/templates/.opencode/commands/run-task.zh-CN.md +11 -0
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.js';
|
|
5
|
+
import { parseReviewSummary } from './review-artifacts.js';
|
|
6
|
+
import { inspectArtifactDirectory } from './artifact-lifecycle.js';
|
|
7
|
+
import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
|
|
8
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
9
|
+
import { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
|
|
10
|
+
import { getAgentClientCapability } from '../agent-clients/registry.js';
|
|
11
|
+
import { captureWorkspaceSnapshot, diffWorkspaceSnapshots } from './workspace-snapshot.js';
|
|
12
|
+
function orchestrationPath(taskDir) {
|
|
13
|
+
return path.join(taskDir, 'orchestration.json');
|
|
14
|
+
}
|
|
15
|
+
function readRun(taskDir) {
|
|
16
|
+
const file = orchestrationPath(taskDir);
|
|
17
|
+
if (!fs.existsSync(file))
|
|
18
|
+
return null;
|
|
19
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
function atomicWrite(file, value) {
|
|
22
|
+
const temp = `${file}.${process.pid}.${randomUUID()}.tmp`;
|
|
23
|
+
fs.writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
24
|
+
fs.renameSync(temp, file);
|
|
25
|
+
}
|
|
26
|
+
function saveRun(taskDir, run) {
|
|
27
|
+
atomicWrite(orchestrationPath(taskDir), run);
|
|
28
|
+
}
|
|
29
|
+
function withUpdatedRun(run, updates) {
|
|
30
|
+
return Object.freeze({ ...run, ...updates, updatedAt: new Date().toISOString() });
|
|
31
|
+
}
|
|
32
|
+
function failed(code, message, taskId = null) {
|
|
33
|
+
return { status: 'failed', changed: false, taskId, run: null, next: null, error: { code, message } };
|
|
34
|
+
}
|
|
35
|
+
function beginOrResumeOrchestration(taskRef, options = {}) {
|
|
36
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
37
|
+
if (!resolved.ok)
|
|
38
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
39
|
+
const existing = readRun(resolved.taskDir);
|
|
40
|
+
if (existing) {
|
|
41
|
+
if (existing.status === 'paused' && existing.pause?.recoverable && existing.pendingDelegation === null) {
|
|
42
|
+
const resumed = withUpdatedRun(existing, { status: 'running', pause: null });
|
|
43
|
+
saveRun(resolved.taskDir, resumed);
|
|
44
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: resumed, next: null, error: null };
|
|
45
|
+
}
|
|
46
|
+
return { status: existing.status, changed: false, taskId: resolved.taskId, run: existing, next: null, error: null };
|
|
47
|
+
}
|
|
48
|
+
const now = (options.now ?? (() => new Date().toISOString()))();
|
|
49
|
+
const run = {
|
|
50
|
+
schemaVersion: 1,
|
|
51
|
+
taskId: resolved.taskId,
|
|
52
|
+
runId: (options.id ?? randomUUID)(),
|
|
53
|
+
status: 'running',
|
|
54
|
+
nextStage: null,
|
|
55
|
+
stepCount: 0,
|
|
56
|
+
maxSteps: options.maxSteps ?? 24,
|
|
57
|
+
baseline: '',
|
|
58
|
+
pendingDelegation: null,
|
|
59
|
+
receipts: [],
|
|
60
|
+
pause: null,
|
|
61
|
+
commitAuthorization: { issuedAt: null, consumedAt: null },
|
|
62
|
+
createdAt: now,
|
|
63
|
+
updatedAt: now
|
|
64
|
+
};
|
|
65
|
+
atomicWrite(orchestrationPath(resolved.taskDir), run);
|
|
66
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run, next: null, error: null };
|
|
67
|
+
}
|
|
68
|
+
function highestRound(taskDir, family) {
|
|
69
|
+
const pattern = new RegExp(`^${family}(?:-r(\\d+))?\\.md$`);
|
|
70
|
+
return fs.readdirSync(taskDir).reduce((max, name) => {
|
|
71
|
+
const match = pattern.exec(name);
|
|
72
|
+
return match ? Math.max(max, match[1] ? Number(match[1]) : 1) : max;
|
|
73
|
+
}, 0);
|
|
74
|
+
}
|
|
75
|
+
function artifactName(family, round) {
|
|
76
|
+
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
77
|
+
}
|
|
78
|
+
function hasReviewAfterArtifact(taskDir, family, reviewFamily) {
|
|
79
|
+
const artifact = inspectArtifactDirectory(taskDir, family);
|
|
80
|
+
const review = inspectArtifactDirectory(taskDir, reviewFamily);
|
|
81
|
+
return artifact.status === 'ready'
|
|
82
|
+
&& review.status === 'ready'
|
|
83
|
+
&& artifact.latest !== null
|
|
84
|
+
&& review.reviewedInput?.name === artifact.latest.name;
|
|
85
|
+
}
|
|
86
|
+
function latestReviewApproved(taskDir, family) {
|
|
87
|
+
const round = highestRound(taskDir, family);
|
|
88
|
+
if (round === 0)
|
|
89
|
+
return false;
|
|
90
|
+
const parsed = parseReviewSummary(fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8'));
|
|
91
|
+
return parsed.ok && parsed.summary.verdict === 'Approved' && parsed.summary.counts !== null;
|
|
92
|
+
}
|
|
93
|
+
function routeFromFacts(taskDir) {
|
|
94
|
+
const analysisRound = highestRound(taskDir, 'analysis');
|
|
95
|
+
const analysisReviewRound = highestRound(taskDir, 'review-analysis');
|
|
96
|
+
const planRound = highestRound(taskDir, 'plan');
|
|
97
|
+
const planReviewRound = highestRound(taskDir, 'review-plan');
|
|
98
|
+
const codeRound = highestRound(taskDir, 'code');
|
|
99
|
+
const codeReviewRound = highestRound(taskDir, 'review-code');
|
|
100
|
+
if (analysisRound === 0) {
|
|
101
|
+
const round = analysisRound + 1;
|
|
102
|
+
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
103
|
+
}
|
|
104
|
+
if (!hasReviewAfterArtifact(taskDir, 'analysis', 'review-analysis')) {
|
|
105
|
+
const round = analysisReviewRound + 1;
|
|
106
|
+
return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
|
|
107
|
+
}
|
|
108
|
+
if (!latestReviewApproved(taskDir, 'review-analysis')) {
|
|
109
|
+
const round = analysisRound + 1;
|
|
110
|
+
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
111
|
+
}
|
|
112
|
+
if (planRound === 0) {
|
|
113
|
+
const round = planRound + 1;
|
|
114
|
+
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
115
|
+
}
|
|
116
|
+
if (!hasReviewAfterArtifact(taskDir, 'plan', 'review-plan')) {
|
|
117
|
+
const round = planReviewRound + 1;
|
|
118
|
+
return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
|
|
119
|
+
}
|
|
120
|
+
if (!latestReviewApproved(taskDir, 'review-plan')) {
|
|
121
|
+
const round = planRound + 1;
|
|
122
|
+
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
123
|
+
}
|
|
124
|
+
if (codeRound === 0) {
|
|
125
|
+
const round = codeRound + 1;
|
|
126
|
+
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
127
|
+
}
|
|
128
|
+
if (!hasReviewAfterArtifact(taskDir, 'code', 'review-code')) {
|
|
129
|
+
const round = codeReviewRound + 1;
|
|
130
|
+
return { action: 'review-code', role: 'reviewer', stage: 'review-code', round, artifact: artifactName('review-code', round) };
|
|
131
|
+
}
|
|
132
|
+
if (!latestReviewApproved(taskDir, 'review-code')) {
|
|
133
|
+
const round = codeRound + 1;
|
|
134
|
+
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
135
|
+
}
|
|
136
|
+
return { action: 'commit', role: 'executor', stage: 'commit', round: 1, artifact: 'commit' };
|
|
137
|
+
}
|
|
138
|
+
function routeOrchestration(taskRef, options = {}) {
|
|
139
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
140
|
+
if (!resolved.ok)
|
|
141
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
142
|
+
let content;
|
|
143
|
+
try {
|
|
144
|
+
content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
parseTypedTaskFrontmatter(content);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
154
|
+
}
|
|
155
|
+
const next = routeFromFacts(resolved.taskDir);
|
|
156
|
+
if (!next)
|
|
157
|
+
return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
158
|
+
if (next.stage === 'commit') {
|
|
159
|
+
const reviewRound = highestRound(resolved.taskDir, 'review-code');
|
|
160
|
+
const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
|
|
161
|
+
if (!review.ok || review.summary.manualValidation === null) {
|
|
162
|
+
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
|
|
163
|
+
}
|
|
164
|
+
if (review.summary.manualValidation > 0) {
|
|
165
|
+
return failed('ORCHESTRATION_MANUAL_VALIDATION_PENDING', 'manual validation must be completed before commit', resolved.taskId);
|
|
166
|
+
}
|
|
167
|
+
const rows = parseLedger(content);
|
|
168
|
+
const ledgerError = validateLedgerRows(rows);
|
|
169
|
+
if (ledgerError)
|
|
170
|
+
return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
|
|
171
|
+
if (!summarizeLedgerStage(rows, 'code').canAdvance) {
|
|
172
|
+
return failed('ORCHESTRATION_LEDGER_BLOCKED', 'code review ledger has unresolved findings or human decisions', resolved.taskId);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: readRun(resolved.taskDir), next, error: null };
|
|
176
|
+
}
|
|
177
|
+
function statusOrchestration(taskRef, options = {}) {
|
|
178
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
179
|
+
if (!resolved.ok)
|
|
180
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
181
|
+
const run = readRun(resolved.taskDir);
|
|
182
|
+
if (!run)
|
|
183
|
+
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
184
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
185
|
+
}
|
|
186
|
+
function prepareOrchestrationDelegation(taskRef, input, options = {}) {
|
|
187
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
188
|
+
if (!resolved.ok)
|
|
189
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
190
|
+
if (getAgentClientCapability(input.client, 'subagents').level === 'unsupported'
|
|
191
|
+
|| getAgentClientCapability(input.client, 'orchestration').level === 'unsupported') {
|
|
192
|
+
return failed('ORCHESTRATION_CLIENT_UNSUPPORTED', `client '${input.client}' does not support lifecycle orchestration`, resolved.taskId);
|
|
193
|
+
}
|
|
194
|
+
const run = readRun(resolved.taskDir);
|
|
195
|
+
if (!run || run.status !== 'running')
|
|
196
|
+
return failed('ORCHESTRATION_RUN_NOT_RUNNING', 'a running orchestration is required', resolved.taskId);
|
|
197
|
+
if (run.pendingDelegation)
|
|
198
|
+
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the run already has a pending delegation', resolved.taskId);
|
|
199
|
+
const repositoryPending = ['claude-code', 'codex']
|
|
200
|
+
.flatMap((client) => matchingDelegations(client, () => true, options));
|
|
201
|
+
if (repositoryPending.length > 0) {
|
|
202
|
+
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
|
|
203
|
+
}
|
|
204
|
+
if (run.stepCount >= run.maxSteps)
|
|
205
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
206
|
+
const routed = routeOrchestration(taskRef, options);
|
|
207
|
+
if (!routed.next)
|
|
208
|
+
return routed;
|
|
209
|
+
const next = routed.next;
|
|
210
|
+
let beforeFingerprint;
|
|
211
|
+
try {
|
|
212
|
+
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(resolved.repoRoot);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
return failed('ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
216
|
+
}
|
|
217
|
+
const receipt = prepareDelegation({
|
|
218
|
+
taskId: resolved.taskId,
|
|
219
|
+
runId: run.runId,
|
|
220
|
+
role: next.role,
|
|
221
|
+
stage: next.stage,
|
|
222
|
+
round: next.round,
|
|
223
|
+
artifact: next.artifact,
|
|
224
|
+
client: input.client,
|
|
225
|
+
requestedModel: input.requestedModel ?? null,
|
|
226
|
+
beforeFingerprint
|
|
227
|
+
}, { id: options.id, now: options.now });
|
|
228
|
+
const updated = withUpdatedRun(run, {
|
|
229
|
+
nextStage: next.stage,
|
|
230
|
+
pendingDelegation: receipt,
|
|
231
|
+
commitAuthorization: next.stage === 'commit' && run.commitAuthorization.issuedAt === null
|
|
232
|
+
? { issuedAt: (options.now ?? (() => new Date().toISOString()))(), consumedAt: null }
|
|
233
|
+
: run.commitAuthorization
|
|
234
|
+
});
|
|
235
|
+
saveRun(resolved.taskDir, updated);
|
|
236
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next, error: null };
|
|
237
|
+
}
|
|
238
|
+
function matchingDelegations(client, predicate, options) {
|
|
239
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
240
|
+
const activeRoot = path.join(repoRoot, '.agents', 'workspace', 'active');
|
|
241
|
+
if (!fs.existsSync(activeRoot))
|
|
242
|
+
return [];
|
|
243
|
+
return fs.readdirSync(activeRoot, { withFileTypes: true })
|
|
244
|
+
.filter((entry) => entry.isDirectory())
|
|
245
|
+
.flatMap((entry) => {
|
|
246
|
+
const run = readRun(path.join(activeRoot, entry.name));
|
|
247
|
+
if (!run?.pendingDelegation)
|
|
248
|
+
return [];
|
|
249
|
+
const receipt = run.pendingDelegation;
|
|
250
|
+
return receipt.client === client && predicate(receipt)
|
|
251
|
+
? [{ taskId: entry.name, run }]
|
|
252
|
+
: [];
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
function uniqueMatchingDelegation(client, predicate, options) {
|
|
256
|
+
const matches = matchingDelegations(client, predicate, options);
|
|
257
|
+
if (matches.length === 0)
|
|
258
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no matching lifecycle delegation exists');
|
|
259
|
+
if (matches.length > 1)
|
|
260
|
+
return failed('ORCHESTRATION_DELEGATION_AMBIGUOUS', 'multiple lifecycle delegations match the native hook event');
|
|
261
|
+
return matches[0];
|
|
262
|
+
}
|
|
263
|
+
function activateMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
264
|
+
const role = managedDelegationRole(event.nativeAgent);
|
|
265
|
+
if (!role)
|
|
266
|
+
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
267
|
+
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status === 'prepared', options);
|
|
268
|
+
if ('status' in matched)
|
|
269
|
+
return matched;
|
|
270
|
+
if (matched.run.receipts.some((receipt) => receipt.childId === event.childId)) {
|
|
271
|
+
return pauseOrchestration(matched.taskId, 'DELEGATION_IDENTITY_REUSED', 'native child identity was already used by this run', true, options);
|
|
272
|
+
}
|
|
273
|
+
return activateOrchestrationDelegation(matched.taskId, event, options);
|
|
274
|
+
}
|
|
275
|
+
function sealMatchingOrchestrationDelegation(client, event, options = {}) {
|
|
276
|
+
const role = managedDelegationRole(event.nativeAgent);
|
|
277
|
+
if (!role)
|
|
278
|
+
return failed('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
|
|
279
|
+
const matched = uniqueMatchingDelegation(client, (receipt) => receipt.status !== 'prepared', options);
|
|
280
|
+
if ('status' in matched)
|
|
281
|
+
return matched;
|
|
282
|
+
const receipt = matched.run.pendingDelegation;
|
|
283
|
+
if (receipt.role !== role) {
|
|
284
|
+
return pauseOrchestration(matched.taskId, 'DELEGATION_ROLE_MISMATCH', `managed role ${role} does not match ${receipt.role}`, true, options);
|
|
285
|
+
}
|
|
286
|
+
const repoRoot = options.repoRoot ?? process.cwd();
|
|
287
|
+
try {
|
|
288
|
+
const afterFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)(repoRoot);
|
|
289
|
+
const changedPaths = (options.diffWorkspace ?? diffWorkspaceSnapshots)(repoRoot, receipt.beforeFingerprint, afterFingerprint);
|
|
290
|
+
return sealOrchestrationDelegation(matched.taskId, {
|
|
291
|
+
childId: event.childId,
|
|
292
|
+
exitCode: 0,
|
|
293
|
+
afterFingerprint,
|
|
294
|
+
changedPaths
|
|
295
|
+
}, options);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
return pauseOrchestration(matched.taskId, 'ORCHESTRATION_SNAPSHOT_FAILED', error instanceof Error ? error.message : String(error), true, options);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function activateOrchestrationDelegation(taskRef, event, options = {}) {
|
|
302
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
303
|
+
if (!resolved.ok)
|
|
304
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
305
|
+
const run = readRun(resolved.taskDir);
|
|
306
|
+
if (!run?.pendingDelegation)
|
|
307
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
308
|
+
const result = activateDelegation(run.pendingDelegation, event, { now: options.now });
|
|
309
|
+
if (!result.ok) {
|
|
310
|
+
if (result.code === 'DELEGATION_IGNORED')
|
|
311
|
+
return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
312
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
313
|
+
}
|
|
314
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
315
|
+
saveRun(resolved.taskDir, updated);
|
|
316
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
317
|
+
}
|
|
318
|
+
function completeOrchestrationStage(taskRef, event, options = {}) {
|
|
319
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
320
|
+
if (!resolved.ok)
|
|
321
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
322
|
+
const run = readRun(resolved.taskDir);
|
|
323
|
+
if (!run)
|
|
324
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
325
|
+
if (!run.pendingDelegation)
|
|
326
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending delegation', resolved.taskId);
|
|
327
|
+
const result = completeDelegationStage(run.pendingDelegation, event);
|
|
328
|
+
if (!result.ok)
|
|
329
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
330
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
331
|
+
saveRun(resolved.taskDir, updated);
|
|
332
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
333
|
+
}
|
|
334
|
+
function completeCommitOrchestrationStage(taskRef, agent, options = {}) {
|
|
335
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
336
|
+
if (!resolved.ok)
|
|
337
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
338
|
+
const run = readRun(resolved.taskDir);
|
|
339
|
+
if (!run)
|
|
340
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
341
|
+
const receipt = run.pendingDelegation;
|
|
342
|
+
if (!receipt || receipt.stage !== 'commit') {
|
|
343
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'active run has no pending commit delegation', resolved.taskId);
|
|
344
|
+
}
|
|
345
|
+
return completeOrchestrationStage(taskRef, {
|
|
346
|
+
stage: receipt.stage,
|
|
347
|
+
round: receipt.round,
|
|
348
|
+
artifact: receipt.artifact,
|
|
349
|
+
agent
|
|
350
|
+
}, options);
|
|
351
|
+
}
|
|
352
|
+
function validateOrchestrationStage(taskRef, event, options = {}) {
|
|
353
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
354
|
+
if (!resolved.ok)
|
|
355
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
356
|
+
const run = readRun(resolved.taskDir);
|
|
357
|
+
if (!run)
|
|
358
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run: null, next: null, error: null };
|
|
359
|
+
const receipt = run.pendingDelegation;
|
|
360
|
+
if (!receipt
|
|
361
|
+
|| receipt.status !== 'activated'
|
|
362
|
+
|| receipt.stage !== event.stage
|
|
363
|
+
|| receipt.round !== event.round
|
|
364
|
+
|| receipt.artifact !== event.artifact
|
|
365
|
+
|| receipt.role !== event.role) {
|
|
366
|
+
return pauseOrchestration(taskRef, 'ORCHESTRATION_PROVENANCE_MISMATCH', 'active run does not have one matching activated delegation', true, options);
|
|
367
|
+
}
|
|
368
|
+
return { status: 'running', changed: false, taskId: resolved.taskId, run, next: null, error: null };
|
|
369
|
+
}
|
|
370
|
+
function sealOrchestrationDelegation(taskRef, event, options = {}) {
|
|
371
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
372
|
+
if (!resolved.ok)
|
|
373
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
374
|
+
const run = readRun(resolved.taskDir);
|
|
375
|
+
if (!run?.pendingDelegation)
|
|
376
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
377
|
+
const result = sealDelegation(run.pendingDelegation, event, { now: options.now });
|
|
378
|
+
if (!result.ok)
|
|
379
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
380
|
+
const updated = withUpdatedRun(run, { pendingDelegation: result.receipt });
|
|
381
|
+
saveRun(resolved.taskDir, updated);
|
|
382
|
+
return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
383
|
+
}
|
|
384
|
+
function advanceOrchestration(taskRef, options = {}) {
|
|
385
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
386
|
+
if (!resolved.ok)
|
|
387
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
388
|
+
const run = readRun(resolved.taskDir);
|
|
389
|
+
if (!run?.pendingDelegation)
|
|
390
|
+
return failed('ORCHESTRATION_DELEGATION_MISSING', 'no pending delegation exists', resolved.taskId);
|
|
391
|
+
const result = consumeDelegation(run.pendingDelegation, { now: options.now });
|
|
392
|
+
if (!result.ok)
|
|
393
|
+
return pauseOrchestration(taskRef, result.code, result.message, true, options);
|
|
394
|
+
const completed = result.receipt.stage === 'commit';
|
|
395
|
+
const updated = withUpdatedRun(run, {
|
|
396
|
+
status: completed ? 'completed' : 'running',
|
|
397
|
+
nextStage: null,
|
|
398
|
+
stepCount: run.stepCount + 1,
|
|
399
|
+
pendingDelegation: null,
|
|
400
|
+
receipts: Object.freeze([...run.receipts, result.receipt]),
|
|
401
|
+
commitAuthorization: completed
|
|
402
|
+
? { ...run.commitAuthorization, consumedAt: (options.now ?? (() => new Date().toISOString()))() }
|
|
403
|
+
: run.commitAuthorization
|
|
404
|
+
});
|
|
405
|
+
saveRun(resolved.taskDir, updated);
|
|
406
|
+
return { status: updated.status, changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
407
|
+
}
|
|
408
|
+
function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
|
|
409
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
410
|
+
if (!resolved.ok)
|
|
411
|
+
return failed(resolved.code, resolved.message, resolved.taskId);
|
|
412
|
+
const run = readRun(resolved.taskDir);
|
|
413
|
+
if (!run)
|
|
414
|
+
return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
|
|
415
|
+
const updated = withUpdatedRun(run, { status: 'paused', pause: { code, message, recoverable } });
|
|
416
|
+
saveRun(resolved.taskDir, updated);
|
|
417
|
+
return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
|
|
418
|
+
}
|
|
419
|
+
export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, completeCommitOrchestrationStage, completeOrchestrationStage, orchestrationPath, pauseOrchestration, prepareOrchestrationDelegation, readRun, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration, validateOrchestrationStage };
|
|
420
|
+
//# sourceMappingURL=orchestration.js.map
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const SUMMARY_HEADINGS = new Set(['审查摘要', 'Review Summary']);
|
|
4
|
+
const PLACEHOLDERS = [
|
|
5
|
+
'{unresolved-blockers}',
|
|
6
|
+
'{unresolved-major}',
|
|
7
|
+
'{unresolved-minor}'
|
|
8
|
+
];
|
|
9
|
+
function escapeRegExp(value) {
|
|
10
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
9
11
|
}
|
|
10
|
-
|
|
12
|
+
function maxRound(entries, stem) {
|
|
11
13
|
let max = 0;
|
|
12
14
|
for (const entry of entries) {
|
|
13
15
|
if (entry === `${stem}.md`) {
|
|
@@ -15,87 +17,169 @@ export function maxRound(entries, stem) {
|
|
|
15
17
|
continue;
|
|
16
18
|
}
|
|
17
19
|
const match = entry.match(new RegExp(`^${escapeRegExp(stem)}-r(\\d+)\\.md$`));
|
|
18
|
-
if (match)
|
|
20
|
+
if (match)
|
|
19
21
|
max = Math.max(max, Number(match[1]));
|
|
20
|
-
}
|
|
21
22
|
}
|
|
22
23
|
return max;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
+
function artifactName(stem, round) {
|
|
25
26
|
return round === 1 ? `${stem}.md` : `${stem}-r${round}.md`;
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
+
function normalizeVerdict(raw) {
|
|
28
29
|
const value = String(raw).trim().toLowerCase();
|
|
29
|
-
if (value ===
|
|
30
|
-
return
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return "Rejected";
|
|
37
|
-
}
|
|
38
|
-
return "";
|
|
30
|
+
if (value === '通过' || value === 'approved')
|
|
31
|
+
return 'Approved';
|
|
32
|
+
if (value === '需要修改' || value === 'changes requested')
|
|
33
|
+
return 'Changes Requested';
|
|
34
|
+
if (value === '拒绝' || value === 'rejected')
|
|
35
|
+
return 'Rejected';
|
|
36
|
+
return '';
|
|
39
37
|
}
|
|
40
|
-
|
|
38
|
+
function extractSection(content, names) {
|
|
41
39
|
const lines = content.split(/\r?\n/);
|
|
42
40
|
const nameSet = new Set(names);
|
|
43
41
|
const start = lines.findIndex((line) => {
|
|
44
42
|
const match = line.trim().match(/^##\s+(.+?)\s*$/);
|
|
45
43
|
return match ? nameSet.has(match[1]) : false;
|
|
46
44
|
});
|
|
47
|
-
if (start === -1)
|
|
48
|
-
return
|
|
49
|
-
}
|
|
45
|
+
if (start === -1)
|
|
46
|
+
return '';
|
|
50
47
|
const sectionLines = [];
|
|
51
48
|
for (let index = start + 1; index < lines.length; index += 1) {
|
|
52
|
-
if (/^##\s+/.test(lines[index]))
|
|
49
|
+
if (/^##\s+/.test(lines[index]))
|
|
53
50
|
break;
|
|
54
|
-
}
|
|
55
51
|
sectionLines.push(lines[index]);
|
|
56
52
|
}
|
|
57
|
-
return sectionLines.join(
|
|
53
|
+
return sectionLines.join('\n');
|
|
58
54
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
return
|
|
55
|
+
function summaryBounds(content) {
|
|
56
|
+
const headings = [...content.matchAll(/^##\s+(.+?)\s*$/gm)]
|
|
57
|
+
.map((match) => ({ index: match.index, end: match.index + match[0].length, name: match[1] }));
|
|
58
|
+
const summaries = headings.filter((heading) => SUMMARY_HEADINGS.has(heading.name));
|
|
59
|
+
if (summaries.length !== 1)
|
|
60
|
+
return null;
|
|
61
|
+
const summary = summaries[0];
|
|
62
|
+
const next = headings.find((heading) => heading.index > summary.index);
|
|
63
|
+
return { start: summary.end, end: next?.index ?? content.length };
|
|
64
|
+
}
|
|
65
|
+
function uniqueLine(content, bounds, pattern) {
|
|
66
|
+
const section = content.slice(bounds.start, bounds.end);
|
|
67
|
+
const matches = [...section.matchAll(pattern)];
|
|
68
|
+
if (matches.length !== 1)
|
|
69
|
+
return null;
|
|
70
|
+
const match = matches[0];
|
|
71
|
+
return {
|
|
72
|
+
value: match[1],
|
|
73
|
+
start: bounds.start + match.index,
|
|
74
|
+
end: bounds.start + match.index + match[0].length
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function parseCounts(value) {
|
|
78
|
+
const zh = /^(\d+)\s*阻塞项,\s*(\d+)\s*主要,\s*(\d+)\s*次要(?:\s*\/.*)?$/.exec(value);
|
|
79
|
+
const en = /^(\d+)\s*blockers?,\s*(\d+)\s*majors?,\s*(\d+)\s*minors?(?:\s*\/.*)?$/i.exec(value);
|
|
80
|
+
const match = zh ?? en;
|
|
81
|
+
if (!match)
|
|
82
|
+
return null;
|
|
83
|
+
const [blocker, major, minor] = match.slice(1, 4).map(Number);
|
|
84
|
+
if (![blocker, major, minor].every(Number.isSafeInteger))
|
|
85
|
+
return null;
|
|
86
|
+
return { blocker: blocker, major: major, minor: minor };
|
|
87
|
+
}
|
|
88
|
+
function parseManualValidation(value) {
|
|
89
|
+
const match = /\*\*(?:人工校验|Manual[- ]validation)\*\*[::]\s*(\d+)/i.exec(value);
|
|
90
|
+
if (!match)
|
|
91
|
+
return null;
|
|
92
|
+
const count = Number(match[1]);
|
|
93
|
+
return Number.isSafeInteger(count) ? count : null;
|
|
94
|
+
}
|
|
95
|
+
function hasCanonicalPlaceholders(value) {
|
|
96
|
+
const zh = /^\{unresolved-blockers\}\s*阻塞项,\s*\{unresolved-major\}\s*主要,\s*\{unresolved-minor\}\s*次要(?:\s*\/.*)?$/.test(value);
|
|
97
|
+
const en = /^\{unresolved-blockers\}\s*blockers?,\s*\{unresolved-major\}\s*majors?,\s*\{unresolved-minor\}\s*minors?(?:\s*\/.*)?$/i.test(value);
|
|
98
|
+
return zh || en;
|
|
99
|
+
}
|
|
100
|
+
function parseReviewSummary(content) {
|
|
101
|
+
const bounds = summaryBounds(content);
|
|
102
|
+
if (!bounds) {
|
|
103
|
+
return { ok: false, code: 'REVIEW_SUMMARY_NOT_FOUND', message: 'review artifact must contain one review summary section' };
|
|
65
104
|
}
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
if (!
|
|
70
|
-
return { ok: false,
|
|
105
|
+
const verdictLine = uniqueLine(content, bounds, /^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.+?)\s*$/gim);
|
|
106
|
+
const findingsLine = uniqueLine(content, bounds, /^[-*]?\s*\*\*(?:发现(AI 可处理)|Findings \(AI-actionable\))\*\*[::]\s*(.+?)\s*$/gim);
|
|
107
|
+
const verdict = verdictLine ? normalizeVerdict(verdictLine.value) : '';
|
|
108
|
+
if (!verdict || !findingsLine) {
|
|
109
|
+
return { ok: false, code: 'REVIEW_SUMMARY_NOT_FOUND', message: 'review summary verdict or finding count line is missing or repeated' };
|
|
71
110
|
}
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
return {
|
|
111
|
+
const counts = parseCounts(findingsLine.value);
|
|
112
|
+
if (counts) {
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
summary: { verdict, counts, manualValidation: parseManualValidation(findingsLine.value), countState: 'numeric' },
|
|
116
|
+
findingsStart: findingsLine.start,
|
|
117
|
+
findingsEnd: findingsLine.end
|
|
118
|
+
};
|
|
75
119
|
}
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
120
|
+
const placeholderOccurrences = PLACEHOLDERS.map((token) => ([...findingsLine.value.matchAll(new RegExp(escapeRegExp(token), 'g'))].length));
|
|
121
|
+
if (hasCanonicalPlaceholders(findingsLine.value) && placeholderOccurrences.every((count) => count === 1)) {
|
|
78
122
|
return {
|
|
79
|
-
ok:
|
|
80
|
-
verdict: null,
|
|
81
|
-
|
|
123
|
+
ok: true,
|
|
124
|
+
summary: { verdict, counts: null, manualValidation: parseManualValidation(findingsLine.value), countState: 'placeholders' },
|
|
125
|
+
findingsStart: findingsLine.start,
|
|
126
|
+
findingsEnd: findingsLine.end
|
|
82
127
|
};
|
|
83
128
|
}
|
|
84
|
-
|
|
85
|
-
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
code: 'REVIEW_SUMMARY_PLACEHOLDER_INVALID',
|
|
132
|
+
message: 'review summary finding counts must be three canonical placeholders or non-negative integers'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function equalCounts(left, right) {
|
|
136
|
+
return left.blocker === right.blocker && left.major === right.major && left.minor === right.minor;
|
|
137
|
+
}
|
|
138
|
+
function finalizeReviewSummaryContent(content, counts) {
|
|
139
|
+
const parsed = parseReviewSummary(content);
|
|
140
|
+
if (!parsed.ok)
|
|
141
|
+
return parsed;
|
|
142
|
+
if (parsed.summary.countState === 'numeric') {
|
|
143
|
+
if (equalCounts(parsed.summary.counts, counts))
|
|
144
|
+
return { ok: true, changed: false, content };
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
code: 'REVIEW_SUMMARY_COUNT_MISMATCH',
|
|
148
|
+
message: 'review summary finding counts do not match the ledger snapshot'
|
|
149
|
+
};
|
|
86
150
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
151
|
+
let line = content.slice(parsed.findingsStart, parsed.findingsEnd);
|
|
152
|
+
line = line
|
|
153
|
+
.replace(PLACEHOLDERS[0], String(counts.blocker))
|
|
154
|
+
.replace(PLACEHOLDERS[1], String(counts.major))
|
|
155
|
+
.replace(PLACEHOLDERS[2], String(counts.minor));
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
changed: true,
|
|
159
|
+
content: content.slice(0, parsed.findingsStart) + line + content.slice(parsed.findingsEnd)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function parseVerdict(reviewPath) {
|
|
163
|
+
if (!fs.existsSync(reviewPath)) {
|
|
164
|
+
return { ok: false, verdict: null, message: `Review artifact not found: ${path.basename(reviewPath)}` };
|
|
90
165
|
}
|
|
91
|
-
const
|
|
92
|
-
if (!
|
|
93
|
-
return {
|
|
166
|
+
const parsed = parseReviewSummary(fs.readFileSync(reviewPath, 'utf8'));
|
|
167
|
+
if (!parsed.ok) {
|
|
168
|
+
return {
|
|
169
|
+
ok: false,
|
|
170
|
+
verdict: null,
|
|
171
|
+
message: `cannot parse review summary in ${path.basename(reviewPath)}: ${parsed.message}`
|
|
172
|
+
};
|
|
94
173
|
}
|
|
95
|
-
const
|
|
174
|
+
const { verdict, counts } = parsed.summary;
|
|
175
|
+
if (verdict !== 'Approved')
|
|
176
|
+
return { ok: true, verdict };
|
|
96
177
|
return {
|
|
97
178
|
ok: true,
|
|
98
|
-
verdict:
|
|
179
|
+
verdict: counts && equalCounts(counts, { blocker: 0, major: 0, minor: 0 })
|
|
180
|
+
? 'Approved'
|
|
181
|
+
: 'Approved-with-issues'
|
|
99
182
|
};
|
|
100
183
|
}
|
|
184
|
+
export { artifactName, equalCounts, escapeRegExp, extractSection, finalizeReviewSummaryContent, maxRound, normalizeVerdict, parseReviewSummary, parseVerdict };
|
|
101
185
|
//# sourceMappingURL=review-artifacts.js.map
|