@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,441 @@
|
|
|
1
|
+
import { inspectOwnedPath } from '../owned-path.js';
|
|
2
|
+
import { taskFinalizationReceiptState } from './finalization-state.js';
|
|
3
|
+
import { withTaskExecutionLock } from './task-execution-lock.js';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { checkpointCommitMatches } from './commit-identity.js';
|
|
7
|
+
import { readCheckpointIntent } from './commit-intent.js';
|
|
8
|
+
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
9
|
+
import { readTaskFinalizationReceipt } from './finalization.js';
|
|
10
|
+
import { semanticDigest, sha256Content } from './local-artifact-finalization.js';
|
|
11
|
+
import { artifactRecoveryRoot, readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
12
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
13
|
+
import { enumerateAllTaskDirs } from './resolve-ref.js';
|
|
14
|
+
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
15
|
+
const LOCAL_INTENT_RE = /^(TASK-\d{8}-\d{6})-(analysis|review-analysis|plan|review-plan|code|review-code)-(.+\.md)\.json$/;
|
|
16
|
+
const COMMIT_INTENT_RE = /^(TASK-\d{8}-\d{6})\.json$/;
|
|
17
|
+
const AUXILIARY_ROOTS = [
|
|
18
|
+
'.local-artifact-finalization-intents',
|
|
19
|
+
'.task-commit-intents'
|
|
20
|
+
];
|
|
21
|
+
function auxiliaryRoot(repoRoot, name) {
|
|
22
|
+
return path.join(repoRoot, '.agents', 'workspace', name);
|
|
23
|
+
}
|
|
24
|
+
function taskRoot(repoRoot) {
|
|
25
|
+
return path.join(repoRoot, '.agents', 'workspace');
|
|
26
|
+
}
|
|
27
|
+
function fileIdentity(stat) {
|
|
28
|
+
return {
|
|
29
|
+
dev: String(stat.dev),
|
|
30
|
+
ino: String(stat.ino),
|
|
31
|
+
size: stat.size,
|
|
32
|
+
mtimeMs: stat.mtimeMs
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function sameFileIdentity(left, right) {
|
|
36
|
+
return left.dev === right.dev && left.ino === right.ino
|
|
37
|
+
&& left.size === right.size && left.mtimeMs === right.mtimeMs;
|
|
38
|
+
}
|
|
39
|
+
function errorCode(error) {
|
|
40
|
+
const code = error?.code;
|
|
41
|
+
return typeof code === 'string' ? code : null;
|
|
42
|
+
}
|
|
43
|
+
function safeLstat(root, target, expect) {
|
|
44
|
+
const stat = inspectOwnedPath(root, target, expect);
|
|
45
|
+
return stat ? fileIdentity(stat) : null;
|
|
46
|
+
}
|
|
47
|
+
function inspectTaskRecords(repoRoot, taskIds) {
|
|
48
|
+
const records = new Map();
|
|
49
|
+
for (const entry of enumerateAllTaskDirs(repoRoot)) {
|
|
50
|
+
if (taskIds && !taskIds.has(entry.taskId))
|
|
51
|
+
continue;
|
|
52
|
+
const taskMdPath = path.join(entry.taskDir, 'task.md');
|
|
53
|
+
if (safeLstat(taskRoot(repoRoot), taskMdPath, 'file') === null)
|
|
54
|
+
continue;
|
|
55
|
+
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
56
|
+
records.set(entry.taskId, {
|
|
57
|
+
taskId: entry.taskId,
|
|
58
|
+
taskDir: entry.taskDir,
|
|
59
|
+
state: entry.state,
|
|
60
|
+
frontmatter: parseTaskFrontmatter(content)
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return records;
|
|
64
|
+
}
|
|
65
|
+
function item(kind, taskId, filePath, disposition, reason, identity = null) {
|
|
66
|
+
return { kind, taskId, path: path.resolve(filePath), disposition, reason, identity };
|
|
67
|
+
}
|
|
68
|
+
function taskGate(task) {
|
|
69
|
+
if (!task)
|
|
70
|
+
return 'TASK_NOT_FOUND';
|
|
71
|
+
if (task.state !== 'completed' && task.state !== 'archive')
|
|
72
|
+
return 'TASK_STATE_PROTECTED';
|
|
73
|
+
if (task.frontmatter.id !== task.taskId || task.frontmatter.status !== 'completed')
|
|
74
|
+
return 'TASK_STATE_PROTECTED';
|
|
75
|
+
if (!task.frontmatter.branch)
|
|
76
|
+
return 'TASK_IDENTITY_INVALID';
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function receiptGate(repoRoot, task, options) {
|
|
80
|
+
let receipt;
|
|
81
|
+
try {
|
|
82
|
+
receipt = readTaskFinalizationReceipt(repoRoot, task.taskId);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return 'FINALIZATION_RECEIPT_INVALID';
|
|
86
|
+
}
|
|
87
|
+
if (!receipt)
|
|
88
|
+
return 'FINALIZATION_RECEIPT_MISSING';
|
|
89
|
+
const state = taskFinalizationReceiptState(receipt);
|
|
90
|
+
if (state === 'pending')
|
|
91
|
+
return 'FINALIZATION_RECEIPT_PENDING';
|
|
92
|
+
if (state === 'unresolved')
|
|
93
|
+
return 'LFAI_RETRY_OR_WARNING_OPEN';
|
|
94
|
+
if (receipt.controlBinding) {
|
|
95
|
+
const evidence = options.controlBindingEvidence?.(task.taskId, receipt.controlBinding);
|
|
96
|
+
if (evidence !== 'terminal' && !(options.preflight && evidence === 'pending'))
|
|
97
|
+
return 'CONTROL_BINDING_MISMATCH';
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function canonicalLocalIntentPath(taskId, family, artifact) {
|
|
102
|
+
return `${taskId}-${family}-${artifact}.json`;
|
|
103
|
+
}
|
|
104
|
+
function localIntentCandidate(repoRoot, task, filePath, taskId, family, artifact, identity, options) {
|
|
105
|
+
const protectedReason = taskGate(task);
|
|
106
|
+
if (protectedReason)
|
|
107
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', protectedReason, identity);
|
|
108
|
+
if (path.basename(filePath) !== canonicalLocalIntentPath(taskId, family, artifact)
|
|
109
|
+
|| !parseArtifactName(artifact)?.family || parseArtifactName(artifact).family !== family) {
|
|
110
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity);
|
|
111
|
+
}
|
|
112
|
+
let intent;
|
|
113
|
+
try {
|
|
114
|
+
intent = readArtifactRecoveryIntent(repoRoot, taskId, family, artifact);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'LFAI_SCHEMA_INVALID', identity);
|
|
118
|
+
}
|
|
119
|
+
if (!intent || intent.state !== 'consumed') {
|
|
120
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'LFAI_STATE_PROTECTED', identity);
|
|
121
|
+
}
|
|
122
|
+
const artifactPath = path.join(task.taskDir, artifact);
|
|
123
|
+
if (safeLstat(task.taskDir, artifactPath, 'file') === null) {
|
|
124
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_IDENTITY_MISMATCH', identity);
|
|
125
|
+
}
|
|
126
|
+
let content;
|
|
127
|
+
try {
|
|
128
|
+
content = fs.readFileSync(artifactPath, 'utf8');
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_UNREADABLE', identity);
|
|
132
|
+
}
|
|
133
|
+
if (intent.finalArtifactSha256 !== sha256Content(content) || intent.finalSemanticDigest !== semanticDigest(content)) {
|
|
134
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', 'ARTIFACT_DIGEST_MISMATCH', identity);
|
|
135
|
+
}
|
|
136
|
+
const receiptReason = receiptGate(repoRoot, task, options);
|
|
137
|
+
if (receiptReason)
|
|
138
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'protected', receiptReason, identity);
|
|
139
|
+
return item('LFAI-CONSUMED', taskId, filePath, 'planned', 'LFAI_CONSUMED_AND_VERIFIED', identity);
|
|
140
|
+
}
|
|
141
|
+
function commitIntentCandidate(repoRoot, task, filePath, taskId, identity, options) {
|
|
142
|
+
const protectedReason = taskGate(task);
|
|
143
|
+
if (protectedReason)
|
|
144
|
+
return item('COMMIT-SYNCED', taskId, filePath, 'protected', protectedReason, identity);
|
|
145
|
+
let intent;
|
|
146
|
+
try {
|
|
147
|
+
intent = readCheckpointIntent(repoRoot, taskId);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return item('COMMIT-SYNCED', taskId, filePath, 'protected', 'COMMIT_INTENT_INVALID', identity);
|
|
151
|
+
}
|
|
152
|
+
if (!intent || intent.state !== 'synced' || intent.taskId !== taskId
|
|
153
|
+
|| intent.branch !== task.frontmatter.branch || !intent.committedHead
|
|
154
|
+
|| task.frontmatter.checkpoint_commit !== intent.committedHead
|
|
155
|
+
|| !checkpointCommitMatches(repoRoot, intent, intent.committedHead)) {
|
|
156
|
+
return item('COMMIT-SYNCED', taskId, filePath, 'protected', 'COMMIT_EVIDENCE_MISMATCH', identity);
|
|
157
|
+
}
|
|
158
|
+
const receiptReason = receiptGate(repoRoot, task, options);
|
|
159
|
+
if (receiptReason)
|
|
160
|
+
return item('COMMIT-SYNCED', taskId, filePath, 'protected', receiptReason, identity);
|
|
161
|
+
return item('COMMIT-SYNCED', taskId, filePath, 'planned', 'COMMIT_SYNCED_AND_VERIFIED', identity);
|
|
162
|
+
}
|
|
163
|
+
function readAuxiliaryCandidates(repoRoot, tasks, options) {
|
|
164
|
+
const candidates = [];
|
|
165
|
+
const selected = options.taskIds ? new Set(options.taskIds) : null;
|
|
166
|
+
for (const rootName of AUXILIARY_ROOTS) {
|
|
167
|
+
const root = auxiliaryRoot(repoRoot, rootName);
|
|
168
|
+
let rootIdentity;
|
|
169
|
+
try {
|
|
170
|
+
rootIdentity = safeLstat(taskRoot(repoRoot), root, 'directory');
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
rootIdentity = null;
|
|
174
|
+
}
|
|
175
|
+
if (rootIdentity === null) {
|
|
176
|
+
let stat = null;
|
|
177
|
+
try {
|
|
178
|
+
stat = fs.lstatSync(root);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
stat = null;
|
|
182
|
+
}
|
|
183
|
+
if (stat?.isSymbolicLink())
|
|
184
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, root, 'protected', 'PATH_SYMLINK'));
|
|
185
|
+
else if (stat)
|
|
186
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, root, 'protected', 'PATH_IDENTITY_MISMATCH'));
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
let entries;
|
|
190
|
+
try {
|
|
191
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, root, 'protected', 'AUXILIARY_ROOT_UNREADABLE', rootIdentity));
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
198
|
+
const filePath = path.join(root, entry.name);
|
|
199
|
+
if (entry.isSymbolicLink()) {
|
|
200
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_SYMLINK'));
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (!entry.isFile()) {
|
|
204
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH'));
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const identity = safeLstat(root, filePath, 'file');
|
|
208
|
+
if (!identity) {
|
|
209
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH'));
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (rootName === '.local-artifact-finalization-intents') {
|
|
213
|
+
const match = LOCAL_INTENT_RE.exec(entry.name);
|
|
214
|
+
if (!match || (selected && !selected.has(match[1]))) {
|
|
215
|
+
if (!selected || !match)
|
|
216
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity));
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
candidates.push(localIntentCandidate(repoRoot, tasks.get(match[1]), filePath, match[1], match[2], match[3], identity, options));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const match = COMMIT_INTENT_RE.exec(entry.name);
|
|
223
|
+
if (!match || (selected && !selected.has(match[1]))) {
|
|
224
|
+
if (!selected || !match)
|
|
225
|
+
candidates.push(item('AUXILIARY-UNKNOWN', null, filePath, 'protected', 'PATH_IDENTITY_MISMATCH', identity));
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
candidates.push(commitIntentCandidate(repoRoot, tasks.get(match[1]), filePath, match[1], identity, options));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (entries.length === 0) {
|
|
232
|
+
candidates.push(item('EMPTY-AUX-PARENT', null, root, 'planned', 'EMPTY_CANONICAL_AUXILIARY_ROOT', rootIdentity));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return candidates;
|
|
236
|
+
}
|
|
237
|
+
function buildIntermediateCleanupReport(items, dryRun = false, partial = false) {
|
|
238
|
+
const remaining = items.filter((candidate) => candidate.disposition !== 'deleted' && candidate.disposition !== 'skipped');
|
|
239
|
+
return {
|
|
240
|
+
status: dryRun ? 'dry-run' : partial || remaining.some((candidate) => candidate.disposition === 'failed') ? 'partial' : 'completed',
|
|
241
|
+
items,
|
|
242
|
+
remaining
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function scanInternal(repoRootInput, options) {
|
|
246
|
+
const repoRoot = path.resolve(repoRootInput);
|
|
247
|
+
if (safeLstat(path.dirname(repoRoot), repoRoot, 'directory') === null) {
|
|
248
|
+
throw new Error('INTERMEDIATE_CLEANUP_REPOSITORY_INVALID');
|
|
249
|
+
}
|
|
250
|
+
const selected = options.taskIds ? new Set(options.taskIds.filter((taskId) => TASK_ID_RE.test(taskId))) : undefined;
|
|
251
|
+
const tasks = inspectTaskRecords(repoRoot, selected);
|
|
252
|
+
return readAuxiliaryCandidates(repoRoot, tasks, options);
|
|
253
|
+
}
|
|
254
|
+
function scanIntermediateCleanup(repoRoot, options = {}) {
|
|
255
|
+
const candidates = scanInternal(repoRoot, options);
|
|
256
|
+
return buildIntermediateCleanupReport(candidates, true);
|
|
257
|
+
}
|
|
258
|
+
function cleanupConsumedRecoveryFiles(repoRoot, task, candidate) {
|
|
259
|
+
if (candidate.kind !== 'LFAI-CONSUMED' || !candidate.taskId)
|
|
260
|
+
return true;
|
|
261
|
+
if (!task)
|
|
262
|
+
return false;
|
|
263
|
+
const match = LOCAL_INTENT_RE.exec(path.basename(candidate.path));
|
|
264
|
+
if (!match)
|
|
265
|
+
return false;
|
|
266
|
+
let intent;
|
|
267
|
+
try {
|
|
268
|
+
intent = readArtifactRecoveryIntent(repoRoot, candidate.taskId, match[2], match[3]);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (!intent || intent.state !== 'consumed')
|
|
274
|
+
return false;
|
|
275
|
+
const recovery = artifactRecoveryRoot(task.taskDir, intent.recoveryOperationId);
|
|
276
|
+
let stat = null;
|
|
277
|
+
try {
|
|
278
|
+
stat = fs.lstatSync(recovery);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
if (errorCode(error) === 'ENOENT')
|
|
282
|
+
return true;
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
286
|
+
return false;
|
|
287
|
+
let entries;
|
|
288
|
+
try {
|
|
289
|
+
entries = fs.readdirSync(recovery, { withFileTypes: true });
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
if (entry.isSymbolicLink() || !entry.isFile() || !['baseline.md', 'candidate.md', 'final.md', 'publish.md'].includes(entry.name))
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
for (const entry of entries)
|
|
300
|
+
fs.unlinkSync(path.join(recovery, entry.name));
|
|
301
|
+
fs.rmdirSync(recovery);
|
|
302
|
+
const recoveryRoot = path.dirname(recovery);
|
|
303
|
+
if (fs.readdirSync(recoveryRoot).length === 0)
|
|
304
|
+
fs.rmdirSync(recoveryRoot);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
return errorCode(error) === 'ENOENT';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function removeCandidate(repoRoot, candidate, task) {
|
|
312
|
+
if (candidate.disposition !== 'planned')
|
|
313
|
+
return candidate;
|
|
314
|
+
const root = candidate.kind === 'EMPTY-AUX-PARENT'
|
|
315
|
+
? path.dirname(path.dirname(candidate.path))
|
|
316
|
+
: path.dirname(candidate.path);
|
|
317
|
+
const expected = candidate.identity;
|
|
318
|
+
if (!expected)
|
|
319
|
+
return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_MISSING' };
|
|
320
|
+
let raw;
|
|
321
|
+
try {
|
|
322
|
+
raw = fs.lstatSync(candidate.path);
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (errorCode(error) === 'ENOENT')
|
|
326
|
+
return { ...candidate, disposition: 'skipped', reason: 'ALREADY_GONE' };
|
|
327
|
+
return { ...candidate, disposition: 'failed', reason: 'CLEANUP_RECHECK_FAILED' };
|
|
328
|
+
}
|
|
329
|
+
if (raw.isSymbolicLink())
|
|
330
|
+
return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
|
|
331
|
+
const actual = safeLstat(root, candidate.path, candidate.kind === 'EMPTY-AUX-PARENT' ? 'directory' : 'file');
|
|
332
|
+
if (!actual)
|
|
333
|
+
return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
|
|
334
|
+
if (!sameFileIdentity(expected, actual))
|
|
335
|
+
return { ...candidate, disposition: 'protected', reason: 'PATH_IDENTITY_CHANGED' };
|
|
336
|
+
if (!cleanupConsumedRecoveryFiles(repoRoot, task, candidate)) {
|
|
337
|
+
return { ...candidate, disposition: 'protected', reason: 'RECOVERY_STAGING_NOT_SAFE_TO_REMOVE' };
|
|
338
|
+
}
|
|
339
|
+
try {
|
|
340
|
+
if (candidate.kind === 'EMPTY-AUX-PARENT') {
|
|
341
|
+
if (fs.readdirSync(candidate.path).length !== 0)
|
|
342
|
+
return { ...candidate, disposition: 'protected', reason: 'AUXILIARY_ROOT_NOT_EMPTY' };
|
|
343
|
+
fs.rmdirSync(candidate.path);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
fs.unlinkSync(candidate.path);
|
|
347
|
+
}
|
|
348
|
+
return { ...candidate, disposition: 'deleted', reason: 'DELETED_AFTER_LOCKED_RECHECK' };
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (errorCode(error) === 'ENOENT')
|
|
352
|
+
return { ...candidate, disposition: 'skipped', reason: 'ALREADY_GONE' };
|
|
353
|
+
return { ...candidate, disposition: 'failed', reason: 'CLEANUP_DELETE_FAILED' };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function intermediateCleanupKey(item) {
|
|
357
|
+
return `${item.kind}\0${item.path}`;
|
|
358
|
+
}
|
|
359
|
+
function cleanupIntermediateUnderRemovalCoordinator(repoRoot, options = {}) {
|
|
360
|
+
const scanOptions = {
|
|
361
|
+
...(options.taskIds === undefined ? {} : { taskIds: options.taskIds }),
|
|
362
|
+
...(options.controlBindingEvidence === undefined ? {} : { controlBindingEvidence: options.controlBindingEvidence })
|
|
363
|
+
};
|
|
364
|
+
const initial = scanIntermediateCleanup(repoRoot, scanOptions);
|
|
365
|
+
if (options.dryRun)
|
|
366
|
+
return initial;
|
|
367
|
+
const items = [...initial.items];
|
|
368
|
+
const lockedTaskIds = options.lockedTaskIds ?? new Set();
|
|
369
|
+
const byTask = new Map();
|
|
370
|
+
for (const [index, candidate] of items.entries()) {
|
|
371
|
+
if (!candidate.taskId || candidate.disposition !== 'planned')
|
|
372
|
+
continue;
|
|
373
|
+
const indexes = byTask.get(candidate.taskId) ?? [];
|
|
374
|
+
indexes.push(index);
|
|
375
|
+
byTask.set(candidate.taskId, indexes);
|
|
376
|
+
}
|
|
377
|
+
const tasks = inspectTaskRecords(repoRoot, new Set(byTask.keys()));
|
|
378
|
+
for (const taskId of [...byTask.keys()].sort()) {
|
|
379
|
+
const execute = () => {
|
|
380
|
+
const refreshed = scanIntermediateCleanup(repoRoot, {
|
|
381
|
+
...scanOptions,
|
|
382
|
+
taskIds: [taskId]
|
|
383
|
+
});
|
|
384
|
+
const refreshedByKey = new Map(refreshed.items.map((candidate) => [intermediateCleanupKey(candidate), candidate]));
|
|
385
|
+
for (const index of byTask.get(taskId) ?? []) {
|
|
386
|
+
const initialCandidate = items[index];
|
|
387
|
+
if (initialCandidate.disposition !== 'planned')
|
|
388
|
+
continue;
|
|
389
|
+
const current = refreshedByKey.get(intermediateCleanupKey(initialCandidate));
|
|
390
|
+
items[index] = current
|
|
391
|
+
? removeCandidate(repoRoot, current, tasks.get(taskId))
|
|
392
|
+
: { ...initialCandidate, disposition: 'protected', reason: 'CANDIDATE_NO_LONGER_ELIGIBLE' };
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
try {
|
|
396
|
+
if (lockedTaskIds.has(taskId))
|
|
397
|
+
execute();
|
|
398
|
+
else
|
|
399
|
+
withTaskExecutionLock(repoRoot, taskId, 'sandbox-removal', execute);
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
for (const index of byTask.get(taskId) ?? []) {
|
|
403
|
+
const candidate = items[index];
|
|
404
|
+
if (candidate.disposition === 'planned')
|
|
405
|
+
items[index] = { ...candidate, disposition: 'protected', reason: 'TASK_LOCK_BUSY' };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (const [index, candidate] of items.entries()) {
|
|
410
|
+
if (candidate.taskId || candidate.disposition !== 'planned')
|
|
411
|
+
continue;
|
|
412
|
+
items[index] = removeCandidate(repoRoot, candidate);
|
|
413
|
+
}
|
|
414
|
+
const knownPaths = new Set(items.map((candidate) => intermediateCleanupKey(candidate)));
|
|
415
|
+
for (const candidate of scanIntermediateCleanup(repoRoot, scanOptions).items) {
|
|
416
|
+
if (candidate.kind !== 'EMPTY-AUX-PARENT' || candidate.disposition !== 'planned'
|
|
417
|
+
|| knownPaths.has(intermediateCleanupKey(candidate)))
|
|
418
|
+
continue;
|
|
419
|
+
items.push(removeCandidate(repoRoot, candidate));
|
|
420
|
+
}
|
|
421
|
+
return buildIntermediateCleanupReport(items);
|
|
422
|
+
}
|
|
423
|
+
function protectIntermediateCleanupReport(report, reason) {
|
|
424
|
+
const items = report.items.map((item) => item.disposition === 'planned'
|
|
425
|
+
? { ...item, disposition: 'protected', reason }
|
|
426
|
+
: item);
|
|
427
|
+
return buildIntermediateCleanupReport(items, false, true);
|
|
428
|
+
}
|
|
429
|
+
function mergeIntermediateCleanupReports(reports) {
|
|
430
|
+
const items = reports.flatMap((report) => report.items);
|
|
431
|
+
return buildIntermediateCleanupReport(items, false, reports.some((report) => report.status === 'partial'));
|
|
432
|
+
}
|
|
433
|
+
function formatIntermediateCleanupReport(report) {
|
|
434
|
+
return [
|
|
435
|
+
`remaining ${report.remaining.length} item(s)`,
|
|
436
|
+
...report.items.map((candidate) => (`${candidate.disposition} ${candidate.kind} ${candidate.path}`
|
|
437
|
+
+ ` (${candidate.taskId ?? 'unbound'}; ${candidate.reason})`))
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
export { formatIntermediateCleanupReport, cleanupIntermediateUnderRemovalCoordinator, protectIntermediateCleanupReport, mergeIntermediateCleanupReports, scanIntermediateCleanup };
|
|
441
|
+
//# sourceMappingURL=intermediate-cleanup.js.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
+
import { findSectionRange, parseTable } from './sections.js';
|
|
3
|
+
import { scanVisibleMarkdown } from './markdown.js';
|
|
2
4
|
const INVALIDATION_HEADINGS = ['产物失效记录', 'Artifact Invalidation'];
|
|
3
5
|
const OPERATION_COLUMNS = [
|
|
4
6
|
'operation_id', 'source_family', 'source_artifact', 'source_round', 'source_sha256',
|
|
@@ -29,61 +31,14 @@ function createInvalidationOperation(source, targets = []) {
|
|
|
29
31
|
createdAt: source.createdAt, updatedAt: source.updatedAt, completedAt: '', error: ''
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const cells = [];
|
|
39
|
-
let start = 0;
|
|
40
|
-
let escaped = false;
|
|
41
|
-
for (let index = 0; index < inner.length; index += 1) {
|
|
42
|
-
const char = inner[index];
|
|
43
|
-
if (char === '|' && !escaped) {
|
|
44
|
-
cells.push(inner.slice(start, index));
|
|
45
|
-
start = index + 1;
|
|
46
|
-
}
|
|
47
|
-
escaped = char === '\\' && !escaped;
|
|
48
|
-
if (char !== '\\')
|
|
49
|
-
escaped = false;
|
|
50
|
-
}
|
|
51
|
-
cells.push(inner.slice(start));
|
|
52
|
-
return cells.map((cell) => cell.replace(/\\([\\|])/g, '$1').trim());
|
|
53
|
-
}
|
|
54
|
-
function isSeparator(cells, count) {
|
|
55
|
-
return cells.length === count && cells.every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
56
|
-
}
|
|
57
|
-
function sectionBody(content) {
|
|
58
|
-
const heading = INVALIDATION_HEADINGS.map((value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
59
|
-
const match = new RegExp(`^##\\s+(${heading})\\s*$`, 'm').exec(content);
|
|
60
|
-
if (!match)
|
|
61
|
-
return null;
|
|
62
|
-
const start = (match.index ?? 0) + match[0].length;
|
|
63
|
-
const rest = content.slice(start);
|
|
64
|
-
const end = rest.search(/^##\s+/m);
|
|
65
|
-
return rest.slice(0, end < 0 ? rest.length : end);
|
|
66
|
-
}
|
|
67
|
-
function parseTable(body, heading, columns) {
|
|
68
|
-
const marker = new RegExp(`^###\\s+${heading}\\s*$`, 'm').exec(body);
|
|
69
|
-
if (!marker)
|
|
70
|
-
return null;
|
|
71
|
-
const start = (marker.index ?? 0) + marker[0].length;
|
|
72
|
-
const rest = body.slice(start);
|
|
73
|
-
const end = rest.search(/^###\s+/m);
|
|
74
|
-
const lines = rest.slice(0, end < 0 ? rest.length : end).split(/\r?\n/).filter((line) => line.trim());
|
|
75
|
-
const header = lines.length > 0 ? lineCells(lines[0]) : null;
|
|
76
|
-
const separator = lines.length > 1 ? lineCells(lines[1]) : null;
|
|
77
|
-
if (!header || !separator || header.length !== columns.length || !header.every((value, index) => value === columns[index]) || !isSeparator(separator, columns.length))
|
|
78
|
-
return false;
|
|
79
|
-
const rows = [];
|
|
80
|
-
for (const line of lines.slice(2)) {
|
|
81
|
-
const cells = lineCells(line);
|
|
82
|
-
if (!cells || cells.length !== columns.length)
|
|
83
|
-
return false;
|
|
84
|
-
rows.push(Object.fromEntries(columns.map((column, index) => [column, cells[index]])));
|
|
34
|
+
function parseInvalidationTable(body, heading, columns) {
|
|
35
|
+
const section = findSectionRange(body, [heading], 3);
|
|
36
|
+
const lines = scanVisibleMarkdown(body).lines.filter((line) => line.text.trim());
|
|
37
|
+
const table = parseTable(lines.map((line) => line.text).join('\n'), { sectionAliases: [heading], columns });
|
|
38
|
+
if (!section || !table || lines.filter((line) => line.start >= section.bodyStart && line.start < section.end).length !== table.rows.length + 2) {
|
|
39
|
+
throw new Error(`invalidation ${heading} section must contain only its canonical table`);
|
|
85
40
|
}
|
|
86
|
-
return rows;
|
|
41
|
+
return table.rows;
|
|
87
42
|
}
|
|
88
43
|
function required(value, field) {
|
|
89
44
|
if (!value)
|
|
@@ -102,16 +57,14 @@ function numberValue(value, field, allowZero = true) {
|
|
|
102
57
|
return parsed;
|
|
103
58
|
}
|
|
104
59
|
function parseInvalidationDocument(content) {
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
60
|
+
const section = findSectionRange(content, INVALIDATION_HEADINGS);
|
|
61
|
+
if (section === null)
|
|
107
62
|
return { ok: true, present: false, document: { operations: [], targets: [] } };
|
|
108
63
|
try {
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
const operations = operationRows.map((row) => ({
|
|
64
|
+
const body = content.slice(section.bodyStart, section.end);
|
|
65
|
+
const operationRows = parseInvalidationTable(body, 'Operations', OPERATION_COLUMNS);
|
|
66
|
+
const targetRows = parseInvalidationTable(body, 'Targets', TARGET_COLUMNS);
|
|
67
|
+
const operations = operationRows.map(({ values: row }) => ({
|
|
115
68
|
operationId: required(row.operation_id, 'operation_id'), sourceFamily: required(row.source_family, 'source_family'),
|
|
116
69
|
sourceArtifact: required(row.source_artifact, 'source_artifact'), sourceRound: numberValue(row.source_round, 'source_round', false),
|
|
117
70
|
sourceSha256: required(row.source_sha256, 'source_sha256'), status: status(row.status, 'operation status'),
|
|
@@ -119,7 +72,7 @@ function parseInvalidationDocument(content) {
|
|
|
119
72
|
createdAt: required(row.created_at, 'created_at'), updatedAt: required(row.updated_at, 'updated_at'),
|
|
120
73
|
completedAt: row.completed_at ?? '', error: row.error ?? ''
|
|
121
74
|
}));
|
|
122
|
-
const targets = targetRows.map((row) => ({
|
|
75
|
+
const targets = targetRows.map(({ values: row }) => ({
|
|
123
76
|
targetId: required(row.target_id, 'target_id'), operationId: required(row.operation_id, 'operation_id'),
|
|
124
77
|
targetKind: row.target_kind, targetFamily: required(row.target_family, 'target_family'),
|
|
125
78
|
targetArtifact: required(row.target_artifact, 'target_artifact'), targetRound: numberValue(row.target_round, 'target_round', false),
|
|
@@ -248,11 +201,11 @@ function isArtifactInvalidated(document, family, artifact) {
|
|
|
248
201
|
&& target.targetArtifact === artifact);
|
|
249
202
|
}
|
|
250
203
|
function invalidationMutation(content, document) {
|
|
251
|
-
const existing =
|
|
204
|
+
const existing = findSectionRange(content, INVALIDATION_HEADINGS);
|
|
252
205
|
return {
|
|
253
206
|
kind: 'section',
|
|
254
207
|
aliases: INVALIDATION_HEADINGS,
|
|
255
|
-
heading: existing
|
|
208
|
+
heading: existing?.heading ?? INVALIDATION_HEADINGS[0],
|
|
256
209
|
body: renderInvalidation(document)
|
|
257
210
|
};
|
|
258
211
|
}
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { declareImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
|
|
5
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
5
6
|
import { LEDGER_COLUMNS, LEDGER_HEADINGS, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, nextHdId, parseLedgerDocument, validateLedgerRows } from './ledger.js';
|
|
6
7
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
8
|
import { writeTask } from './write.js';
|
|
@@ -10,16 +11,6 @@ import { parseReworkIntentDocument, reworkIntentMutation, upsertReworkIntent } f
|
|
|
10
11
|
const PREFIX = { analysis: 'AN', plan: 'PL', code: 'CD' };
|
|
11
12
|
const RESPONSE = new Set(['accepted', 'adjusted', 'refuted', 'cannot-judge']);
|
|
12
13
|
const DISPOSITION = new Set(['confirmed', 'closed', 'open', 'needs-human-decision']);
|
|
13
|
-
const REVIEW_ARTIFACT = {
|
|
14
|
-
analysis: /^review-analysis(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
|
|
15
|
-
plan: /^review-plan(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
|
|
16
|
-
code: /^review-code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/
|
|
17
|
-
};
|
|
18
|
-
const ARTIFACT = {
|
|
19
|
-
analysis: /^analysis(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
|
|
20
|
-
plan: /^plan(?:-r(?:[2-9]|[1-9]\d+))?\.md$/,
|
|
21
|
-
code: /^code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/
|
|
22
|
-
};
|
|
23
14
|
function failed(intent, code, message, taskId = null, entityId = null) {
|
|
24
15
|
return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, operations: [], error: { code, message } };
|
|
25
16
|
}
|
|
@@ -52,10 +43,6 @@ function nextFindingId(rows, stage) {
|
|
|
52
43
|
}
|
|
53
44
|
return `${prefix}-${max + 1}`;
|
|
54
45
|
}
|
|
55
|
-
function reviewArtifactRound(reviewArtifact) {
|
|
56
|
-
const match = /-r([1-9]\d*)\.md$/.exec(reviewArtifact);
|
|
57
|
-
return match ? Number(match[1]) : 1;
|
|
58
|
-
}
|
|
59
46
|
function rowMutation(row) {
|
|
60
47
|
return {
|
|
61
48
|
kind: 'table-row', action: 'upsert', sectionAliases: LEDGER_HEADINGS,
|
|
@@ -63,9 +50,6 @@ function rowMutation(row) {
|
|
|
63
50
|
values: { stage: row.stage, round: row.round, severity: row.severity, status: row.status, evidence: row.evidence }
|
|
64
51
|
};
|
|
65
52
|
}
|
|
66
|
-
function ledgerSectionMutation(content) {
|
|
67
|
-
return [];
|
|
68
|
-
}
|
|
69
53
|
function implementationInputMutation(content, rows) {
|
|
70
54
|
const english = /^##\s+Activity Log\s*$/m.test(content);
|
|
71
55
|
return {
|
|
@@ -112,7 +96,7 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
112
96
|
if (!finding || finding.severity === 'decision')
|
|
113
97
|
return failed(intent, 'LEDGER_NOT_FOUND', `finding '${intent.findingId}' was not found`, resolved.taskId, intent.findingId);
|
|
114
98
|
const sourceFromEvidence = finding.evidence.split('#')[0];
|
|
115
|
-
if (sourceFromEvidence !== intent.sourceArtifact ||
|
|
99
|
+
if (sourceFromEvidence !== intent.sourceArtifact || parseArtifactName(intent.sourceArtifact)?.family !== `review-${finding.stage}`) {
|
|
116
100
|
return failed(intent, 'LEDGER_IDENTITY_CONFLICT', 'rework intent source artifact does not match finding evidence', resolved.taskId, intent.intentId);
|
|
117
101
|
}
|
|
118
102
|
try {
|
|
@@ -142,7 +126,8 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
142
126
|
let before = null;
|
|
143
127
|
let after;
|
|
144
128
|
if (intent.kind === 'finding-upsert') {
|
|
145
|
-
|
|
129
|
+
const identity = parseArtifactName(intent.reviewArtifact);
|
|
130
|
+
if (identity?.family !== `review-${intent.stage}` || !Number.isInteger(intent.ordinal) || intent.ordinal < 1 || !['blocker', 'major', 'minor'].includes(intent.severity)) {
|
|
146
131
|
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'finding identity, stage, ordinal, or severity is invalid', resolved.taskId);
|
|
147
132
|
}
|
|
148
133
|
const evidence = normalizedEvidence(intent.evidence);
|
|
@@ -162,13 +147,13 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
162
147
|
else {
|
|
163
148
|
const id = nextFindingId(rows, intent.stage);
|
|
164
149
|
after = {
|
|
165
|
-
id, stage: intent.stage, round: String(
|
|
150
|
+
id, stage: intent.stage, round: String(identity.round),
|
|
166
151
|
severity: intent.severity, status: 'open', evidence, sourceLine: -1
|
|
167
152
|
};
|
|
168
153
|
}
|
|
169
154
|
}
|
|
170
155
|
else if (intent.kind === 'decision-upsert') {
|
|
171
|
-
if (!/^HD-[1-9]\d*$/.test(intent.id) || !
|
|
156
|
+
if (!/^HD-[1-9]\d*$/.test(intent.id) || !['analysis', 'plan', 'code'].includes(intent.stage) || parseArtifactName(intent.artifact)?.family !== intent.stage) {
|
|
172
157
|
return failed(intent, 'LEDGER_PAYLOAD_INVALID', 'decision id, stage, or artifact is invalid', resolved.taskId, intent.id);
|
|
173
158
|
}
|
|
174
159
|
const evidence = `${intent.artifact}#${intent.id}`;
|
|
@@ -249,7 +234,7 @@ function applyLedgerIntent(intent, options = {}) {
|
|
|
249
234
|
}
|
|
250
235
|
const result = writeTask({
|
|
251
236
|
taskRef: intent.taskRef, expectedState: stateOverride ? resolved.state : 'active',
|
|
252
|
-
mutations: [
|
|
237
|
+
mutations: [rowMutation(after), ...(implementationMutation ? [implementationMutation] : [])],
|
|
253
238
|
dryRun: 'dryRun' in intent ? intent.dryRun : false
|
|
254
239
|
}, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state } });
|
|
255
240
|
return mapWrite(intent, after.id, before, after, result);
|
package/dist/lib/task/ledger.js
CHANGED
|
@@ -17,9 +17,7 @@ const LEDGER_SECTION_MISSING_MESSAGE = 'review disagreement ledger section is mi
|
|
|
17
17
|
function isReviewStage(stage) {
|
|
18
18
|
return stage === 'analysis' || stage === 'plan' || stage === 'code';
|
|
19
19
|
}
|
|
20
|
-
// Parse
|
|
21
|
-
// header row (`| id | ... |`) and the `|---|` separator; ignores non-`|` lines.
|
|
22
|
-
// Rows with fewer than 6 columns are skipped (mirrors the JS gate parser).
|
|
20
|
+
// Parse ledger rows using the shared strict table structure contract.
|
|
23
21
|
function parseLedgerDocument(content) {
|
|
24
22
|
const table = parseTable(content, { sectionAliases: LEDGER_HEADINGS, columns: LEDGER_COLUMNS });
|
|
25
23
|
if (!table)
|