@fitlab-ai/agent-infra 0.9.14 → 0.10.0
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 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +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/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- 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 +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { inspectOwnedPath } from '../owned-path.js';
|
|
4
|
+
import { parseTaskFrontmatter } from '../task/frontmatter.js';
|
|
5
|
+
import { readTaskFinalizationReceipt } from '../task/finalization.js';
|
|
6
|
+
import { taskFinalizationReceiptState } from '../task/finalization-state.js';
|
|
7
|
+
import { enumerateAllTaskDirs } from '../task/resolve-ref.js';
|
|
8
|
+
import { resolveSandboxCleanupTarget } from './workspace-identity.js';
|
|
9
|
+
import { readSandboxControlManifest } from './control/lifecycle.js';
|
|
10
|
+
import { readJsonFile, readSandboxControlStatus } from './control/state.js';
|
|
11
|
+
import { finalizationTerminalResponse } from './control/finalization-response.js';
|
|
12
|
+
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
13
|
+
function matchingControlManifest(repoRoot, controlRoot, taskId, binding) {
|
|
14
|
+
if (!controlRootIdentity(controlRoot))
|
|
15
|
+
return null;
|
|
16
|
+
const manifestPath = path.join(controlRoot, 'manifest.json');
|
|
17
|
+
let manifest;
|
|
18
|
+
try {
|
|
19
|
+
manifest = readSandboxControlManifest(manifestPath);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (manifest.mode !== 'task-bound' || manifest.taskId !== taskId
|
|
25
|
+
|| canonicalPath(manifest.repoRoot) !== repoRoot || manifest.generation !== binding.generation
|
|
26
|
+
|| path.resolve(manifest.channelDir) !== path.join(controlRoot, 'channel')
|
|
27
|
+
|| path.resolve(manifest.publicStatusDir) !== path.join(controlRoot, 'public')
|
|
28
|
+
|| path.resolve(manifest.processingDir) !== path.join(controlRoot, 'processing')
|
|
29
|
+
|| path.resolve(manifest.runtimeDir) !== path.join(controlRoot, 'runtime'))
|
|
30
|
+
return null;
|
|
31
|
+
return manifest;
|
|
32
|
+
}
|
|
33
|
+
function errorCode(error) {
|
|
34
|
+
const code = error?.code;
|
|
35
|
+
return typeof code === 'string' ? code : null;
|
|
36
|
+
}
|
|
37
|
+
function controlBindingKey(taskId, binding) {
|
|
38
|
+
return `${taskId}\0${binding.generation}\0${binding.requestId}`;
|
|
39
|
+
}
|
|
40
|
+
function controlRootIdentity(root) {
|
|
41
|
+
try {
|
|
42
|
+
const stat = fs.lstatSync(root);
|
|
43
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
44
|
+
return null;
|
|
45
|
+
return { dev: String(stat.dev), ino: String(stat.ino) };
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function controlRootState(root, expected) {
|
|
52
|
+
let stat;
|
|
53
|
+
try {
|
|
54
|
+
stat = fs.lstatSync(root);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return errorCode(error) === 'ENOENT' ? 'missing' : 'replaced';
|
|
58
|
+
}
|
|
59
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
60
|
+
return 'replaced';
|
|
61
|
+
return String(stat.dev) === expected.dev && String(stat.ino) === expected.ino ? 'same' : 'replaced';
|
|
62
|
+
}
|
|
63
|
+
function controlRootIsAbsent(root) {
|
|
64
|
+
try {
|
|
65
|
+
fs.lstatSync(root);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
return errorCode(error) === 'ENOENT';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function canonicalPath(input) {
|
|
73
|
+
try {
|
|
74
|
+
return fs.realpathSync.native(input);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return path.resolve(input);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function taskFinalizationReceiptComplete(receipt, taskId, binding) {
|
|
81
|
+
return receipt.taskId === taskId
|
|
82
|
+
&& taskFinalizationReceiptState(receipt) === 'complete'
|
|
83
|
+
&& receipt.controlBinding?.generation === binding.generation
|
|
84
|
+
&& receipt.controlBinding.requestId === binding.requestId;
|
|
85
|
+
}
|
|
86
|
+
function terminalControlBindingEvidence(repoRootInput, controlRootInput, taskId, binding) {
|
|
87
|
+
const repoRoot = canonicalPath(repoRootInput);
|
|
88
|
+
const controlRoot = path.resolve(controlRootInput);
|
|
89
|
+
if (!TASK_ID_RE.test(taskId) || !controlRootIdentity(controlRoot))
|
|
90
|
+
return false;
|
|
91
|
+
const manifestPath = path.join(controlRoot, 'manifest.json');
|
|
92
|
+
if (!inspectOwnedPath(controlRoot, manifestPath, 'file'))
|
|
93
|
+
return false;
|
|
94
|
+
const manifest = matchingControlManifest(repoRoot, controlRoot, taskId, binding);
|
|
95
|
+
if (!manifest)
|
|
96
|
+
return false;
|
|
97
|
+
try {
|
|
98
|
+
const leasePath = path.join(controlRoot, 'lease.json');
|
|
99
|
+
try {
|
|
100
|
+
fs.lstatSync(leasePath);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (errorCode(error) !== 'ENOENT')
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const receipt = readTaskFinalizationReceipt(repoRoot, taskId);
|
|
108
|
+
if (!receipt || !taskFinalizationReceiptComplete(receipt, taskId, binding)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
const status = readSandboxControlStatus(manifest.publicStatusDir);
|
|
112
|
+
const viewReceipt = status.taskView.receipt;
|
|
113
|
+
const terminalTaskView = (status.taskView.state === 'current' && status.taskView.observedSource === 'completed')
|
|
114
|
+
|| (status.taskView.state === 'finalized-stale' && status.taskView.observedSource === 'active');
|
|
115
|
+
if (status.generation !== binding.generation || status.state !== 'healthy'
|
|
116
|
+
|| status.activeRequestId !== null || !terminalTaskView || status.taskView.taskId !== taskId
|
|
117
|
+
|| !viewReceipt || viewReceipt.generation !== binding.generation
|
|
118
|
+
|| viewReceipt.requestId !== binding.requestId
|
|
119
|
+
|| viewReceipt.receiptId !== receipt.receiptId
|
|
120
|
+
|| viewReceipt.revision !== receipt.revision)
|
|
121
|
+
return false;
|
|
122
|
+
const responsePath = path.join(manifest.channelDir, 'responses', `${binding.requestId}.json`);
|
|
123
|
+
if (!inspectOwnedPath(path.join(controlRoot, 'channel'), responsePath, 'file'))
|
|
124
|
+
return false;
|
|
125
|
+
const actual = readJsonFile(responsePath);
|
|
126
|
+
const expected = finalizationTerminalResponse(taskId, binding.requestId, receipt);
|
|
127
|
+
return JSON.stringify(actual) === JSON.stringify(expected);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function removedControlBindingEvidence(repoRootInput, journal, taskId, binding) {
|
|
134
|
+
const repoRoot = canonicalPath(repoRootInput);
|
|
135
|
+
if (journal.phase !== 'completed'
|
|
136
|
+
|| journal.generation !== binding.generation
|
|
137
|
+
|| !path.isAbsolute(journal.target.controlRoot))
|
|
138
|
+
return false;
|
|
139
|
+
const controlRoot = path.resolve(journal.target.controlRoot);
|
|
140
|
+
if (!controlRootIsAbsent(controlRoot))
|
|
141
|
+
return false;
|
|
142
|
+
const task = enumerateAllTaskDirs(repoRoot).find((entry) => entry.taskId === taskId);
|
|
143
|
+
if (!task || (task.state !== 'completed' && task.state !== 'archive'))
|
|
144
|
+
return false;
|
|
145
|
+
let frontmatter;
|
|
146
|
+
try {
|
|
147
|
+
frontmatter = parseTaskFrontmatter(fs.readFileSync(path.join(task.taskDir, 'task.md'), 'utf8'));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
if (frontmatter.id !== taskId || frontmatter.branch !== journal.target.branch)
|
|
153
|
+
return false;
|
|
154
|
+
try {
|
|
155
|
+
const receipt = readTaskFinalizationReceipt(repoRoot, taskId);
|
|
156
|
+
return receipt !== null && taskFinalizationReceiptComplete(receipt, taskId, binding);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function createSandboxControlBindingEvidence(repoRoot, controlRoots, removalJournals = []) {
|
|
163
|
+
const canonicalRepoRoot = canonicalPath(repoRoot);
|
|
164
|
+
const roots = [...new Set(controlRoots.map((candidate) => path.resolve(candidate)))];
|
|
165
|
+
const captured = roots.flatMap((controlRoot) => {
|
|
166
|
+
let manifest;
|
|
167
|
+
try {
|
|
168
|
+
manifest = readSandboxControlManifest(path.join(controlRoot, 'manifest.json'));
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
const rootIdentity = controlRootIdentity(controlRoot);
|
|
174
|
+
if (!rootIdentity)
|
|
175
|
+
return [];
|
|
176
|
+
const receipt = (() => {
|
|
177
|
+
try {
|
|
178
|
+
return readSandboxControlStatus(manifest.publicStatusDir).taskView.receipt;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
})();
|
|
184
|
+
if (!receipt || !terminalControlBindingEvidence(canonicalRepoRoot, controlRoot, manifest.taskId ?? '', receipt))
|
|
185
|
+
return [];
|
|
186
|
+
return [{
|
|
187
|
+
controlRoot,
|
|
188
|
+
taskId: manifest.taskId,
|
|
189
|
+
key: controlBindingKey(manifest.taskId, receipt),
|
|
190
|
+
rootIdentity
|
|
191
|
+
}];
|
|
192
|
+
});
|
|
193
|
+
return (taskId, binding) => {
|
|
194
|
+
const terminal = captured.some((candidate) => {
|
|
195
|
+
if (candidate.taskId !== taskId || candidate.key !== controlBindingKey(taskId, binding))
|
|
196
|
+
return false;
|
|
197
|
+
const state = controlRootState(candidate.controlRoot, candidate.rootIdentity);
|
|
198
|
+
return state === 'missing'
|
|
199
|
+
|| state === 'same' && terminalControlBindingEvidence(canonicalRepoRoot, candidate.controlRoot, taskId, binding);
|
|
200
|
+
}) || removalJournals.some((journal) => removedControlBindingEvidence(canonicalRepoRoot, journal, taskId, binding));
|
|
201
|
+
if (terminal)
|
|
202
|
+
return 'terminal';
|
|
203
|
+
if (roots.some((root) => matchingControlManifest(canonicalRepoRoot, root, taskId, binding)))
|
|
204
|
+
return 'pending';
|
|
205
|
+
try {
|
|
206
|
+
const branch = resolveSandboxCleanupTarget(taskId, canonicalRepoRoot, { allowProtected: true }).branch;
|
|
207
|
+
if (removalJournals.some((journal) => journal.phase !== 'completed'
|
|
208
|
+
&& journal.generation === binding.generation && journal.target.branch === branch))
|
|
209
|
+
return 'pending';
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Missing or conflicting task identity cannot authorize removal.
|
|
213
|
+
}
|
|
214
|
+
return 'mismatch';
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=task-cleanup.js.map
|
|
@@ -98,7 +98,23 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
|
|
|
98
98
|
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
99
99
|
}
|
|
100
100
|
if (TASK_ID_RE.test(requestedRef)) {
|
|
101
|
-
|
|
101
|
+
let task;
|
|
102
|
+
try {
|
|
103
|
+
task = resolveTaskWorkspace(requestedRef, repoRoot);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (!(error instanceof Error && error.message === `Task not found: ${requestedRef}`))
|
|
107
|
+
throw error;
|
|
108
|
+
const branch = options.resolveMissingTask?.(requestedRef);
|
|
109
|
+
if (!branch)
|
|
110
|
+
throw error;
|
|
111
|
+
return {
|
|
112
|
+
requestedRef,
|
|
113
|
+
branch,
|
|
114
|
+
workspace: { mode: 'task-bound', taskId: requestedRef },
|
|
115
|
+
taskState: 'unknown'
|
|
116
|
+
};
|
|
117
|
+
}
|
|
102
118
|
if (!options.allowProtected && (task.state === 'blocked' || task.state === 'archive')) {
|
|
103
119
|
throw new Error(`SANDBOX_CLEANUP_STATE_UNSUPPORTED: task ${requestedRef} is ${task.state}`);
|
|
104
120
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
|
|
4
5
|
export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
|
|
5
6
|
'active',
|
|
6
7
|
'completed',
|
|
@@ -20,12 +21,7 @@ export function sandboxWorkspaceViewPaths(params) {
|
|
|
20
21
|
const digest = createHash('sha256').update(identityKey).digest('hex').slice(0, 16);
|
|
21
22
|
const projectRoot = path.resolve(params.base, params.project);
|
|
22
23
|
const root = path.resolve(projectRoot, params.container, digest);
|
|
23
|
-
return {
|
|
24
|
-
root,
|
|
25
|
-
taskMountPath: params.identity.mode === 'task-bound'
|
|
26
|
-
? path.join(root, 'active', params.identity.taskId)
|
|
27
|
-
: null
|
|
28
|
-
};
|
|
24
|
+
return { root };
|
|
29
25
|
}
|
|
30
26
|
export function sandboxControlPaths(params) {
|
|
31
27
|
const identityKey = params.identity.mode === 'task-bound'
|
|
@@ -108,7 +104,7 @@ export function prepareSandboxWorkspaceMountTargets(worktreeRoot) {
|
|
|
108
104
|
}
|
|
109
105
|
export function materializeSandboxWorkspaceView(params) {
|
|
110
106
|
const projectRoot = path.resolve(params.base, params.project);
|
|
111
|
-
const { root
|
|
107
|
+
const { root } = sandboxWorkspaceViewPaths(params);
|
|
112
108
|
assertSafeDirectory(projectRoot, path.resolve(params.base));
|
|
113
109
|
assertSafeDirectory(root, projectRoot);
|
|
114
110
|
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
@@ -128,11 +124,7 @@ export function materializeSandboxWorkspaceView(params) {
|
|
|
128
124
|
encoding: 'utf8',
|
|
129
125
|
mode: 0o600
|
|
130
126
|
});
|
|
131
|
-
|
|
132
|
-
assertSafeDirectory(taskMountPath, active);
|
|
133
|
-
fs.mkdirSync(taskMountPath, { recursive: true, mode: 0o700 });
|
|
134
|
-
}
|
|
135
|
-
return { root, taskMountPath };
|
|
127
|
+
return { root };
|
|
136
128
|
}
|
|
137
129
|
export function materializeSandboxControl(params) {
|
|
138
130
|
const { root, channelDir, statusDir, processingDir, runtimeDir, manifestPath } = sandboxControlPaths(params);
|
|
@@ -170,6 +162,12 @@ export function materializeSandboxControl(params) {
|
|
|
170
162
|
}
|
|
171
163
|
const token = randomBytes(32).toString('hex');
|
|
172
164
|
const generation = randomBytes(16).toString('hex');
|
|
165
|
+
const identitySentinel = createSandboxControlIdentitySentinel({
|
|
166
|
+
mode: params.identity.mode,
|
|
167
|
+
taskId: params.identity.mode === 'task-bound' ? params.identity.taskId : null,
|
|
168
|
+
generation
|
|
169
|
+
});
|
|
170
|
+
writeSandboxControlIdentitySentinel(statusDir, identitySentinel);
|
|
173
171
|
const repoRoot = fs.realpathSync.native(params.repoRoot);
|
|
174
172
|
const manifestDraft = {
|
|
175
173
|
engine: params.engine ?? 'docker',
|
|
@@ -182,12 +180,13 @@ export function materializeSandboxControl(params) {
|
|
|
182
180
|
taskId: params.identity.mode === 'task-bound' ? params.identity.taskId : null,
|
|
183
181
|
token,
|
|
184
182
|
generation,
|
|
183
|
+
controlRootId: identitySentinel.controlRootId,
|
|
185
184
|
channelDir,
|
|
186
185
|
publicStatusDir: statusDir,
|
|
187
186
|
processingDir,
|
|
188
|
-
runtimeDir
|
|
187
|
+
runtimeDir,
|
|
189
188
|
};
|
|
190
|
-
return { root, channelDir, statusDir, runtimeDir, manifestPath, manifestDraft, token, generation };
|
|
189
|
+
return { root, channelDir, statusDir, runtimeDir, manifestPath, manifestDraft, token, generation, controlRootId: identitySentinel.controlRootId };
|
|
191
190
|
}
|
|
192
191
|
export function finalizeSandboxControlManifest(setup, identity) {
|
|
193
192
|
if (!identity.engine || !identity.id)
|
|
@@ -2,7 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
3
3
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
4
4
|
import { locateActivityLog, appendActivityEntry } from './activity-log.js';
|
|
5
|
-
import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts
|
|
5
|
+
import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts } from './artifact-lifecycle.js';
|
|
6
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
6
7
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
7
8
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
8
9
|
import { allowsManualOverride } from './guard-override.js';
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const NEXT_H2_RE = /^##\s/;
|
|
1
|
+
import { scanVisibleMarkdown } from './markdown.js';
|
|
3
2
|
const ENTRY_RE = /^- (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}) — \*\*(.+?)\*\* by (.+?) — (.*)$/;
|
|
4
3
|
const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
|
|
5
4
|
const ABORTED_SUFFIX_RE = /\s*\[aborted\]\s*$/;
|
|
@@ -10,29 +9,36 @@ function parseActivityLog(content) {
|
|
|
10
9
|
const section = locateActivityLog(content);
|
|
11
10
|
return section ? { sectionFound: true, entries: section.entries } : { sectionFound: false, entries: [] };
|
|
12
11
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const bodyLines = lines.slice(i + 1, end);
|
|
27
|
-
const parsed = [];
|
|
28
|
-
for (const line of bodyLines) {
|
|
29
|
-
const m = ENTRY_RE.exec(line);
|
|
30
|
-
if (!m)
|
|
12
|
+
/** Returns source-order entries and malformed bullets without applying display or gate policy. */
|
|
13
|
+
function inspectActivityLog(content) {
|
|
14
|
+
const scanned = scanVisibleMarkdown(content);
|
|
15
|
+
const headings = scanned.headings.filter((heading) => heading.level === 2);
|
|
16
|
+
const matches = headings.filter((heading) => heading.text === '活动日志' || heading.text === 'Activity Log');
|
|
17
|
+
if (matches.length !== 1)
|
|
18
|
+
return { section: null, invalidEntries: [] };
|
|
19
|
+
const heading = matches[0];
|
|
20
|
+
const end = headings.find((candidate) => candidate.start > heading.start)?.start ?? content.length;
|
|
21
|
+
const entries = [];
|
|
22
|
+
const invalidEntries = [];
|
|
23
|
+
for (const line of scanned.lines) {
|
|
24
|
+
if (line.start <= heading.end || line.start >= end)
|
|
31
25
|
continue;
|
|
32
|
-
|
|
26
|
+
const text = line.text.trimStart();
|
|
27
|
+
const match = ENTRY_RE.exec(text);
|
|
28
|
+
if (match)
|
|
29
|
+
entries.push({ time: match[1], step: match[2], agent: match[3], note: match[4] });
|
|
30
|
+
else if (text.startsWith('- '))
|
|
31
|
+
invalidEntries.push(text);
|
|
33
32
|
}
|
|
34
|
-
|
|
35
|
-
return {
|
|
33
|
+
const body = content.slice(heading.end + 1, end).replace(/\r\n/g, '\n').replace(/^\n+|\n+$/g, '');
|
|
34
|
+
return { section: { heading: heading.text, body, entries }, invalidEntries };
|
|
35
|
+
}
|
|
36
|
+
function locateActivityLog(content) {
|
|
37
|
+
const { section } = inspectActivityLog(content);
|
|
38
|
+
if (!section)
|
|
39
|
+
return null;
|
|
40
|
+
section.entries.sort((left, right) => Date.parse(left.time.replace(' ', 'T')) - Date.parse(right.time.replace(' ', 'T')));
|
|
41
|
+
return section;
|
|
36
42
|
}
|
|
37
43
|
function appendActivityEntry(section, entry) {
|
|
38
44
|
const line = `- ${entry.time} — **${entry.step}** by ${entry.agent} — ${entry.note}`;
|
|
@@ -94,5 +100,5 @@ function hasOpenLifecycleExecution(content) {
|
|
|
94
100
|
return false;
|
|
95
101
|
return pairEntries(section.entries).some((row) => row.done === '' && LIFECYCLE_STEP_RE.test(row.step));
|
|
96
102
|
}
|
|
97
|
-
export { activityAttempt, appendActivityEntry, hasOpenLifecycleExecution, commitAttemptStartedNote, locateActivityLog, pairEntries, parseActivityLog, parseCommitAttemptStarted, startedBackedRows };
|
|
103
|
+
export { activityAttempt, appendActivityEntry, hasOpenLifecycleExecution, commitAttemptStartedNote, inspectActivityLog, locateActivityLog, pairEntries, parseActivityLog, parseCommitAttemptStarted, startedBackedRows };
|
|
98
104
|
//# sourceMappingURL=activity-log.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { resolveArtifactContext, hasOpenArtifactRound } from './artifact-lifecycle.js';
|
|
3
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
4
|
+
import { resolveTaskRef } from './resolve-ref.js';
|
|
5
|
+
import { getArtifactSchema } from './artifact-schema.js';
|
|
6
|
+
import { finalizeLocalArtifact } from './local-artifact-finalization.js';
|
|
7
|
+
import { initializeArtifactSkeleton } from './artifact-operations.js';
|
|
8
|
+
/** One option contract for the CLI and trusted projection executor. */
|
|
9
|
+
export function parseArtifactCommand(args) {
|
|
10
|
+
const [taskRef, operation] = args;
|
|
11
|
+
if (!taskRef || !['inspect', 'init', 'finalize-local'].includes(operation ?? '')) {
|
|
12
|
+
throw new Error('task ref and a supported artifact operation are required');
|
|
13
|
+
}
|
|
14
|
+
const fields = {};
|
|
15
|
+
const allowed = {
|
|
16
|
+
inspect: ['--family'],
|
|
17
|
+
init: ['--family', '--artifact', '--locale'],
|
|
18
|
+
'finalize-local': ['--family', '--artifact', '--recovery-id']
|
|
19
|
+
}[operation];
|
|
20
|
+
for (let index = 2; index < args.length; index += 1) {
|
|
21
|
+
const flag = args[index];
|
|
22
|
+
if (!allowed.includes(flag))
|
|
23
|
+
throw new Error(`unknown option '${flag}'`);
|
|
24
|
+
if (Object.hasOwn(fields, flag))
|
|
25
|
+
throw new Error(`duplicate option '${flag}'`);
|
|
26
|
+
const value = args[++index];
|
|
27
|
+
if (!value || value.startsWith('--'))
|
|
28
|
+
throw new Error(`option '${flag}' requires a value`);
|
|
29
|
+
fields[flag] = value;
|
|
30
|
+
}
|
|
31
|
+
if (!fields['--family'])
|
|
32
|
+
throw new Error("option '--family' is required");
|
|
33
|
+
if (operation !== 'inspect' && !fields['--artifact'])
|
|
34
|
+
throw new Error("option '--artifact' is required");
|
|
35
|
+
const locale = fields['--locale'];
|
|
36
|
+
if (locale !== undefined && locale !== 'zh-CN' && locale !== 'en')
|
|
37
|
+
throw new Error("option '--locale' must be 'zh-CN' or 'en'");
|
|
38
|
+
return {
|
|
39
|
+
taskRef, operation: operation, family: fields['--family'],
|
|
40
|
+
artifact: fields['--artifact'] ?? '', locale, recoveryId: fields['--recovery-id']
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Resolve authoritative task metadata separately from a trusted candidate directory. */
|
|
44
|
+
export function executeArtifactCommand(command, options = {}) {
|
|
45
|
+
const { taskRef, operation, family, artifact, locale, recoveryId } = command;
|
|
46
|
+
const fail = (code, message) => ({ status: 'failed', changed: false, error: { code, message } });
|
|
47
|
+
if (operation === 'inspect') {
|
|
48
|
+
const result = resolveArtifactContext(taskRef, family, { repoRoot: options.repoRoot });
|
|
49
|
+
return family === 'code' && result.codeMode ? {
|
|
50
|
+
...result, mode: result.codeMode.mode, code_max: result.codeMode.codeMax, rev_max: result.codeMode.reviewMax,
|
|
51
|
+
verdict: result.codeMode.verdict, next_round: result.next?.round ?? null, next_artifact: result.next?.name ?? null,
|
|
52
|
+
review_artifact: result.codeMode.reviewArtifact, implementation_input: result.codeMode.implementationInput,
|
|
53
|
+
decision_id: result.codeMode.decisionId, decision_evidence: result.codeMode.decisionEvidence, message: result.codeMode.message
|
|
54
|
+
} : { ...result };
|
|
55
|
+
}
|
|
56
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
57
|
+
if (!resolved.ok)
|
|
58
|
+
return fail(resolved.code, resolved.message);
|
|
59
|
+
const schema = getArtifactSchema(family);
|
|
60
|
+
const parsed = parseArtifactName(artifact);
|
|
61
|
+
if (!schema || !parsed || parsed.family !== family) {
|
|
62
|
+
return fail('ARTIFACT_IDENTITY_INVALID', `artifact '${artifact}' does not match ${family}`);
|
|
63
|
+
}
|
|
64
|
+
const taskDir = options.artifactDir ?? resolved.taskDir;
|
|
65
|
+
const identity = { taskId: resolved.taskId, taskDir, family, artifact };
|
|
66
|
+
const input = { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskDir, family: schema.family, artifact };
|
|
67
|
+
if (operation === 'init') {
|
|
68
|
+
const context = resolveArtifactContext(taskRef, family, { repoRoot: resolved.repoRoot });
|
|
69
|
+
if (context.status !== 'ready' || (context.next?.name !== artifact && !hasOpenArtifactRound(fs.readFileSync(resolved.taskMdPath, 'utf8'), family, parsed.round))) {
|
|
70
|
+
return fail('ARTIFACT_INIT_CONTEXT_INVALID', context.error?.message ?? `artifact '${artifact}' is not the next ${family} artifact`);
|
|
71
|
+
}
|
|
72
|
+
return { ...initializeArtifactSkeleton({ ...input, ...(locale ? { locale } : {}) }), ...identity };
|
|
73
|
+
}
|
|
74
|
+
if (family !== 'analysis' && family !== 'plan' && family !== 'code') {
|
|
75
|
+
return fail('ARTIFACT_PAYLOAD_INVALID', "finalize-local only supports 'analysis', 'plan', and 'code'");
|
|
76
|
+
}
|
|
77
|
+
return { ...finalizeLocalArtifact({ taskRef, family, artifact, repoRoot: resolved.repoRoot, recoveryId }) };
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=artifact-command.js.map
|
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { ARTIFACT_FAMILY_CATALOG, artifactName, parseArtifactName } from './artifact-name.js';
|
|
3
4
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
4
|
-
import { locateActivityLog } from './activity-log.js';
|
|
5
|
+
import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
|
|
5
6
|
import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.js';
|
|
6
7
|
import { parseVerdict } from './review-artifacts.js';
|
|
7
8
|
import { extractSection, findSectionHeading } from './sections.js';
|
|
8
9
|
import { receiptForOutput, sha256File } from './artifact-receipts.js';
|
|
9
10
|
import { isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
|
|
10
11
|
import { validateQualificationAudit } from './qualification-audit.js';
|
|
11
|
-
import { ARTIFACT_FAMILY_CATALOG } from './artifact-schema.js';
|
|
12
12
|
const artifactFamilyCatalog = ARTIFACT_FAMILY_CATALOG;
|
|
13
|
-
const
|
|
13
|
+
const ARTIFACT_STEPS = {
|
|
14
|
+
analysis: 'Analyze Task', 'review-analysis': 'Review Analysis',
|
|
15
|
+
plan: 'Plan Task', 'review-plan': 'Review Plan',
|
|
16
|
+
code: 'Code Task', 'review-code': 'Review Code'
|
|
17
|
+
};
|
|
18
|
+
/** Init, repair and finalization share the same authoritative round identity. */
|
|
19
|
+
export function hasOpenArtifactRound(content, family, round) {
|
|
20
|
+
const activity = locateActivityLog(content);
|
|
21
|
+
const step = ARTIFACT_STEPS[family];
|
|
22
|
+
if (!activity || !step)
|
|
23
|
+
return false;
|
|
24
|
+
const qualifier = family === 'code'
|
|
25
|
+
? '(?:, (?:fix for review-code(?:-r(?:[2-9]|[1-9]\\d+))?\\.md|decision II-[1-9]\\d*))?'
|
|
26
|
+
: '';
|
|
27
|
+
const expected = new RegExp(`^${step} \\(Round ${round}${qualifier}\\)$`);
|
|
28
|
+
return startedBackedRows(pairEntries(activity.entries)).filter((row) => expected.test(row.step) && !row.done).length === 1;
|
|
29
|
+
}
|
|
14
30
|
const BLOCKING_DIAGNOSTICS = new Set([
|
|
15
31
|
'NONCANONICAL_NAME', 'ROUND_OUT_OF_RANGE', 'MISSING_BASE', 'ROUND_GAP',
|
|
16
32
|
'DUPLICATE_LOGICAL_ROUND', 'NON_REGULAR_FILE', 'SYMBOLIC_LINK',
|
|
@@ -19,30 +35,6 @@ const BLOCKING_DIAGNOSTICS = new Set([
|
|
|
19
35
|
function familySpec(family) {
|
|
20
36
|
return artifactFamilyCatalog.find((item) => item.family === family) ?? null;
|
|
21
37
|
}
|
|
22
|
-
function artifactName(family, round) {
|
|
23
|
-
if (!FAMILY_SET.has(family))
|
|
24
|
-
throw new Error(`unknown artifact family '${family}'`);
|
|
25
|
-
if (!Number.isSafeInteger(round) || round < 1)
|
|
26
|
-
throw new Error('artifact round must be a safe positive integer');
|
|
27
|
-
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
28
|
-
}
|
|
29
|
-
function parseArtifactName(name) {
|
|
30
|
-
if (path.basename(name) !== name)
|
|
31
|
-
return null;
|
|
32
|
-
const matches = [];
|
|
33
|
-
for (const spec of artifactFamilyCatalog) {
|
|
34
|
-
if (name === `${spec.family}.md`)
|
|
35
|
-
matches.push({ family: spec.family, round: 1, name });
|
|
36
|
-
const match = new RegExp(`^${escapeRegExp(spec.family)}-r([1-9]\\d*)\\.md$`).exec(name);
|
|
37
|
-
if (!match)
|
|
38
|
-
continue;
|
|
39
|
-
const round = Number(match[1]);
|
|
40
|
-
if (Number.isSafeInteger(round) && round >= 2 && String(round) === match[1]) {
|
|
41
|
-
matches.push({ family: spec.family, round, name });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return matches.length === 1 ? matches[0] : null;
|
|
45
|
-
}
|
|
46
38
|
function escapeRegExp(value) {
|
|
47
39
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
48
40
|
}
|
|
@@ -478,6 +470,16 @@ function validateCompletedArtifact(taskDir, family, name, round) {
|
|
|
478
470
|
const artifact = inventory.artifacts.find((item) => item.name === name);
|
|
479
471
|
return artifact ? { ok: true, artifact } : { ok: false, error: { code: 'ARTIFACT_NOT_FOUND', message: `artifact '${name}' is not in inventory` } };
|
|
480
472
|
}
|
|
473
|
+
/** A candidate may replace its current slot or fill the next slot, never skip rounds. */
|
|
474
|
+
export function validateArtifactPublication(taskDir, family, name) {
|
|
475
|
+
const inventory = inspectArtifactDirectory(taskDir, family);
|
|
476
|
+
const error = assertWritableInventory(inventory);
|
|
477
|
+
if (error)
|
|
478
|
+
return error;
|
|
479
|
+
if (inventory.next?.name === name || inventory.artifacts.some((artifact) => artifact.name === name))
|
|
480
|
+
return null;
|
|
481
|
+
return { code: 'ARTIFACT_IDENTITY_INVALID', message: `artifact '${name}' is not a current or next ${family} slot` };
|
|
482
|
+
}
|
|
481
483
|
function buildArtifactLinkSection(content, artifact) {
|
|
482
484
|
const spec = familySpec(artifact.family);
|
|
483
485
|
const aliases = [...spec.sectionAliases];
|
|
@@ -507,5 +509,5 @@ function buildArtifactLinkSection(content, artifact) {
|
|
|
507
509
|
body = lines.join('\n').replace(/^\n+|\n+$/g, '');
|
|
508
510
|
return { aliases, heading, body };
|
|
509
511
|
}
|
|
510
|
-
export { artifactFamilyCatalog,
|
|
512
|
+
export { artifactFamilyCatalog, familySpec, inspectTaskArtifacts, inspectArtifactDirectory, resolveArtifactContext, parseReviewedInputReference, parseCodePlanInputReference, resolveCodePlanInput, reviewEventName, assertWritableInventory, validateCompletedArtifact, buildArtifactLinkSection };
|
|
511
513
|
//# sourceMappingURL=artifact-lifecycle.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const ARTIFACT_FAMILY_CATALOG = [
|
|
2
|
+
{ family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
|
|
3
|
+
{ family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
|
|
4
|
+
{ family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
|
|
5
|
+
{ family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
|
|
6
|
+
{ family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
|
|
7
|
+
{ family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
|
|
8
|
+
{ family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
|
|
9
|
+
{ family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
|
|
10
|
+
{ family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
|
|
11
|
+
];
|
|
12
|
+
const FAMILIES = new Set(ARTIFACT_FAMILY_CATALOG.map(({ family }) => family));
|
|
13
|
+
export function artifactName(family, round) {
|
|
14
|
+
if (!FAMILIES.has(family))
|
|
15
|
+
throw new Error(`unknown artifact family '${family}'`);
|
|
16
|
+
if (!Number.isSafeInteger(round) || round < 1)
|
|
17
|
+
throw new Error('artifact round must be a safe positive integer');
|
|
18
|
+
return round === 1 ? `${family}.md` : `${family}-r${round}.md`;
|
|
19
|
+
}
|
|
20
|
+
export function parseArtifactName(name) {
|
|
21
|
+
const match = /^(.+?)(?:-r([1-9]\d*))?\.md$/.exec(name);
|
|
22
|
+
if (!match || match[0] !== name || !FAMILIES.has(match[1]))
|
|
23
|
+
return null;
|
|
24
|
+
const round = match[2] ? Number(match[2]) : 1;
|
|
25
|
+
if (!Number.isSafeInteger(round) || (match[2] && round < 2))
|
|
26
|
+
return null;
|
|
27
|
+
return { family: match[1], round, name };
|
|
28
|
+
}
|
|
29
|
+
export function maxArtifactRound(names, family) {
|
|
30
|
+
return names.reduce((max, name) => {
|
|
31
|
+
const identity = parseArtifactName(name);
|
|
32
|
+
return identity?.family === family ? Math.max(max, identity.round) : max;
|
|
33
|
+
}, 0);
|
|
34
|
+
}
|
|
35
|
+
export { ARTIFACT_FAMILY_CATALOG };
|
|
36
|
+
//# sourceMappingURL=artifact-name.js.map
|