@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
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
+
import { readStableFileSync } from '../host-control/secure-fs.ts';
|
|
5
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
4
6
|
|
|
5
|
-
import { scanVisibleMarkdown } from './
|
|
6
|
-
import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
|
|
7
|
-
import { readArtifactRepairIntent } from './artifact-repair-intent.ts';
|
|
7
|
+
import { scanVisibleMarkdown, type VisibleMarkdown, type VisibleHeading } from './markdown.ts';
|
|
8
8
|
import {
|
|
9
9
|
getArtifactSchema,
|
|
10
10
|
renderArtifactSkeleton
|
|
11
11
|
} from './artifact-schema.ts';
|
|
12
|
+
import { withTaskExecutionLock } from './task-execution-lock.ts';
|
|
12
13
|
import type {
|
|
13
14
|
ArtifactSchema,
|
|
14
15
|
ArtifactSchemaFamily,
|
|
15
16
|
ArtifactSection
|
|
16
17
|
} from './artifact-schema.ts';
|
|
17
|
-
import { withTaskExecutionLock } from './task-execution-lock.ts';
|
|
18
|
-
|
|
19
|
-
type ArtifactRepairOperation = Readonly<{
|
|
20
|
-
kind: 'replace-line' | 'insert-section';
|
|
21
|
-
sectionId: string;
|
|
22
|
-
line: number;
|
|
23
|
-
from: string;
|
|
24
|
-
to: string;
|
|
25
|
-
start: number;
|
|
26
|
-
end: number;
|
|
27
|
-
}>;
|
|
28
18
|
|
|
29
19
|
type ArtifactStructuralDiagnosticCode =
|
|
30
20
|
| 'ARTIFACT_EMPTY'
|
|
@@ -44,8 +34,6 @@ type ArtifactStructuralDiagnostic = Readonly<{
|
|
|
44
34
|
message: string;
|
|
45
35
|
sectionId: string | null;
|
|
46
36
|
line: number | null;
|
|
47
|
-
repairable: boolean;
|
|
48
|
-
operation?: ArtifactRepairOperation;
|
|
49
37
|
}>;
|
|
50
38
|
|
|
51
39
|
type ArtifactStructureResult = Readonly<{
|
|
@@ -53,7 +41,6 @@ type ArtifactStructureResult = Readonly<{
|
|
|
53
41
|
family: ArtifactSchemaFamily;
|
|
54
42
|
semanticDigest: string;
|
|
55
43
|
diagnostics: readonly ArtifactStructuralDiagnostic[];
|
|
56
|
-
repair: ArtifactRepairOperation | null;
|
|
57
44
|
}>;
|
|
58
45
|
|
|
59
46
|
type ArtifactFileResult = Readonly<{
|
|
@@ -61,21 +48,9 @@ type ArtifactFileResult = Readonly<{
|
|
|
61
48
|
changed: boolean;
|
|
62
49
|
artifactSha256: string | null;
|
|
63
50
|
semanticDigest: string | null;
|
|
64
|
-
operation: ArtifactRepairOperation | null;
|
|
65
51
|
error: { code: string; message: string } | null;
|
|
66
52
|
}>;
|
|
67
53
|
|
|
68
|
-
type ArtifactRepairRequest = Readonly<{
|
|
69
|
-
repoRoot: string;
|
|
70
|
-
taskId: string;
|
|
71
|
-
taskDir: string;
|
|
72
|
-
family: ArtifactSchemaFamily;
|
|
73
|
-
artifact: string;
|
|
74
|
-
expectedSha256: string;
|
|
75
|
-
expectedSemanticDigest: string;
|
|
76
|
-
operation: ArtifactRepairOperation;
|
|
77
|
-
}>;
|
|
78
|
-
|
|
79
54
|
type ArtifactInitRequest = Readonly<{
|
|
80
55
|
repoRoot: string;
|
|
81
56
|
taskId: string;
|
|
@@ -89,35 +64,6 @@ function sha256Content(content: string): string {
|
|
|
89
64
|
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
90
65
|
}
|
|
91
66
|
|
|
92
|
-
function sourceLines(content: string): Array<{ start: number; end: number; text: string }> {
|
|
93
|
-
const lines: Array<{ start: number; end: number; text: string }> = [];
|
|
94
|
-
let start = 0;
|
|
95
|
-
while (start < content.length) {
|
|
96
|
-
const newline = content.indexOf('\n', start);
|
|
97
|
-
const end = newline === -1 ? content.length : newline;
|
|
98
|
-
lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
|
|
99
|
-
start = newline === -1 ? content.length : newline + 1;
|
|
100
|
-
}
|
|
101
|
-
if (content.length === 0) lines.push({ start: 0, end: 0, text: '' });
|
|
102
|
-
return lines;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function hasUnclosedFence(content: string): boolean {
|
|
106
|
-
let fence: { character: '`' | '~'; length: number } | null = null;
|
|
107
|
-
for (const line of sourceLines(content)) {
|
|
108
|
-
if (fence) {
|
|
109
|
-
const close = line.text.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
|
|
110
|
-
if (close && close[1]![0] === fence.character && close[1]!.length >= fence.length) fence = null;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
const open = line.text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
|
|
114
|
-
if (open && !(open[1]![0] === '`' && open[2]!.includes('`'))) {
|
|
115
|
-
fence = { character: open[1]![0] as '`' | '~', length: open[1]!.length };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return fence !== null;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
67
|
function lineNumber(content: string, offset: number): number {
|
|
122
68
|
return content.slice(0, offset).split('\n').length;
|
|
123
69
|
}
|
|
@@ -130,9 +76,9 @@ function escapeRegExp(value: string): string {
|
|
|
130
76
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
131
77
|
}
|
|
132
78
|
|
|
133
|
-
function markerEntries(content: string): Array<{ marker: string; start: number; end: number; line: number; standalone: boolean }> {
|
|
79
|
+
function markerEntries(content: string, scanned: VisibleMarkdown): Array<{ marker: string; start: number; end: number; line: number; standalone: boolean }> {
|
|
134
80
|
const entries: Array<{ marker: string; start: number; end: number; line: number; standalone: boolean }> = [];
|
|
135
|
-
for (const line of
|
|
81
|
+
for (const line of scanned.lines) {
|
|
136
82
|
const match = line.text.match(/<!--\s*(artifact-section:[^\s>]+)\s*-->/g);
|
|
137
83
|
if (!match) continue;
|
|
138
84
|
for (const raw of match) {
|
|
@@ -150,12 +96,9 @@ function markerEntries(content: string): Array<{ marker: string; start: number;
|
|
|
150
96
|
return entries;
|
|
151
97
|
}
|
|
152
98
|
|
|
153
|
-
function sectionBodyBounds(content: string, heading:
|
|
154
|
-
const next =
|
|
155
|
-
|
|
156
|
-
));
|
|
157
|
-
const headingEnd = scanVisibleMarkdown(content).headings.find((candidate) => candidate.start === heading.start)?.end ?? heading.start;
|
|
158
|
-
return { start: headingEnd, end: next?.start ?? content.length };
|
|
99
|
+
function sectionBodyBounds(content: string, heading: VisibleHeading, scanned: VisibleMarkdown): { start: number; end: number } {
|
|
100
|
+
const next = scanned.headings.find((candidate) => candidate.start > heading.start && candidate.level <= 2);
|
|
101
|
+
return { start: heading.end, end: next?.start ?? content.length };
|
|
159
102
|
}
|
|
160
103
|
|
|
161
104
|
function stripHtmlComments(content: string): string {
|
|
@@ -176,71 +119,17 @@ function diagnostic(
|
|
|
176
119
|
code: ArtifactStructuralDiagnosticCode,
|
|
177
120
|
message: string,
|
|
178
121
|
sectionId: string | null,
|
|
179
|
-
line: number | null
|
|
180
|
-
repairable = false,
|
|
181
|
-
operation?: ArtifactRepairOperation
|
|
122
|
+
line: number | null
|
|
182
123
|
): ArtifactStructuralDiagnostic {
|
|
183
|
-
return {
|
|
184
|
-
code, message, sectionId, line, repairable,
|
|
185
|
-
...(operation ? { operation } : {})
|
|
186
|
-
};
|
|
124
|
+
return { code, message, sectionId, line };
|
|
187
125
|
}
|
|
188
126
|
|
|
189
127
|
function sectionHeadings(schema: ArtifactSchema, section: ArtifactSection): readonly string[] {
|
|
190
128
|
return [section.headings.zh, section.headings.en];
|
|
191
129
|
}
|
|
192
130
|
|
|
193
|
-
function
|
|
194
|
-
content: string,
|
|
195
|
-
section: ArtifactSection,
|
|
196
|
-
heading: { start: number; end: number; text: string }
|
|
197
|
-
): ArtifactRepairOperation {
|
|
198
|
-
const to = heading.text.slice(0, -1);
|
|
199
|
-
return {
|
|
200
|
-
kind: 'replace-line',
|
|
201
|
-
sectionId: section.id,
|
|
202
|
-
line: lineNumber(content, heading.start),
|
|
203
|
-
from: heading.text,
|
|
204
|
-
to,
|
|
205
|
-
start: heading.start,
|
|
206
|
-
end: heading.end
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function createSectionInsertionRepair(
|
|
211
|
-
content: string,
|
|
212
|
-
section: ArtifactSection,
|
|
213
|
-
marker: { start: number; line: number },
|
|
214
|
-
locale: 'zh-CN' | 'en'
|
|
215
|
-
): ArtifactRepairOperation {
|
|
216
|
-
return {
|
|
217
|
-
kind: 'insert-section',
|
|
218
|
-
sectionId: section.id,
|
|
219
|
-
line: marker.line,
|
|
220
|
-
from: '',
|
|
221
|
-
to: `## ${section.headings[locale === 'en' ? 'en' : 'zh']}\n`,
|
|
222
|
-
start: marker.start,
|
|
223
|
-
end: marker.start
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function applyRepairOperation(content: string, operation: ArtifactRepairOperation): string | null {
|
|
228
|
-
if (operation.start < 0 || operation.end < operation.start || operation.end > content.length) return null;
|
|
229
|
-
if (operation.kind === 'insert-section') {
|
|
230
|
-
if (operation.from !== '' || operation.start !== operation.end || !operation.to) return null;
|
|
231
|
-
return `${content.slice(0, operation.start)}${operation.to}${content.slice(operation.end)}`;
|
|
232
|
-
}
|
|
233
|
-
const raw = content.slice(operation.start, operation.end);
|
|
234
|
-
const offset = raw.indexOf(operation.from);
|
|
235
|
-
if (!operation.from || offset < 0 || raw.indexOf(operation.from, offset + operation.from.length) >= 0) return null;
|
|
236
|
-
return `${content.slice(0, operation.start)}${raw.slice(0, offset)}${operation.to}${raw.slice(offset + operation.from.length)}${content.slice(operation.end)}`;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function canonicalContent(content: string, operation: ArtifactRepairOperation | null = null): string {
|
|
131
|
+
function canonicalContent(content: string): string {
|
|
240
132
|
let normalized = content;
|
|
241
|
-
if (operation) {
|
|
242
|
-
normalized = applyRepairOperation(normalized, operation) ?? normalized;
|
|
243
|
-
}
|
|
244
133
|
normalized = normalized.replace(/\r\n/g, '\n');
|
|
245
134
|
normalized = normalized.replace(/^<!--\s*artifact-context:[^\n]+-->\s*\n?/gm, '');
|
|
246
135
|
normalized = normalized.replace(/^\s*<!--\s*artifact-section:[^\n]+-->\s*\n?/gm, '');
|
|
@@ -248,8 +137,8 @@ function canonicalContent(content: string, operation: ArtifactRepairOperation |
|
|
|
248
137
|
return normalized;
|
|
249
138
|
}
|
|
250
139
|
|
|
251
|
-
function canonicalSemanticDigest(content: string
|
|
252
|
-
return sha256Content(canonicalContent(content
|
|
140
|
+
function canonicalSemanticDigest(content: string): string {
|
|
141
|
+
return sha256Content(canonicalContent(content));
|
|
253
142
|
}
|
|
254
143
|
|
|
255
144
|
function inspectArtifactStructure(
|
|
@@ -258,16 +147,15 @@ function inspectArtifactStructure(
|
|
|
258
147
|
): ArtifactStructureResult {
|
|
259
148
|
const diagnostics: ArtifactStructuralDiagnostic[] = [];
|
|
260
149
|
const scanned = scanVisibleMarkdown(content);
|
|
261
|
-
const repairCandidates: ArtifactRepairOperation[] = [];
|
|
262
150
|
if (!content.trim()) diagnostics.push(diagnostic('ARTIFACT_EMPTY', 'artifact is empty', null, null));
|
|
263
|
-
if (hasUnclosedFence
|
|
151
|
+
if (scanned.hasUnclosedFence) diagnostics.push(diagnostic('ARTIFACT_UNCLOSED_FENCE', 'artifact contains an unclosed Markdown fence', null, null));
|
|
264
152
|
|
|
265
153
|
const headingsBySection = new Map<string, typeof scanned.headings>();
|
|
266
154
|
for (const section of schema.sections) {
|
|
267
155
|
const aliases = sectionHeadings(schema, section);
|
|
268
156
|
const exact = scanned.headings.filter((heading) => heading.level === 2 && aliases.includes(heading.text));
|
|
269
157
|
const punctuated = scanned.headings.filter((heading) => heading.level === 2 && aliases.some((alias) => heading.text === `${alias}:` || heading.text === `${alias}:`));
|
|
270
|
-
headingsBySection.set(section.id, exact.length
|
|
158
|
+
headingsBySection.set(section.id, exact.length > 0 ? exact : punctuated);
|
|
271
159
|
if (exact.length > 1 || punctuated.length > 0 && exact.length > 0 || punctuated.length > 1) {
|
|
272
160
|
const first = exact[1] ?? punctuated[0] ?? exact[0];
|
|
273
161
|
diagnostics.push(diagnostic(
|
|
@@ -277,16 +165,18 @@ function inspectArtifactStructure(
|
|
|
277
165
|
first ? lineNumber(content, first.start) : null
|
|
278
166
|
));
|
|
279
167
|
} else if (exact.length === 0 && punctuated.length === 1) {
|
|
280
|
-
|
|
168
|
+
diagnostics.push(diagnostic(
|
|
169
|
+
'ARTIFACT_HEADING_TRAILING_PUNCTUATION',
|
|
170
|
+
`visible required H2 '${punctuated[0]!.text}' has trailing punctuation`,
|
|
171
|
+
section.id,
|
|
172
|
+
lineNumber(content, punctuated[0]!.start)
|
|
173
|
+
));
|
|
281
174
|
} else if (exact.length === 0) {
|
|
282
175
|
diagnostics.push(diagnostic('ARTIFACT_MISSING_SECTION', `required section '${aliases[0]}' is missing`, section.id, null));
|
|
283
176
|
}
|
|
284
177
|
}
|
|
285
178
|
|
|
286
|
-
const markers = markerEntries(content);
|
|
287
|
-
const englishHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.en === heading.text)).length;
|
|
288
|
-
const chineseHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.zh === heading.text)).length;
|
|
289
|
-
const locale = englishHeadings > chineseHeadings ? 'en' : 'zh-CN';
|
|
179
|
+
const markers = markerEntries(content, scanned);
|
|
290
180
|
for (const section of schema.sections) {
|
|
291
181
|
const matches = markers.filter((entry) => entry.marker === section.marker);
|
|
292
182
|
if (matches.length === 0) {
|
|
@@ -300,7 +190,7 @@ function inspectArtifactStructure(
|
|
|
300
190
|
diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' must occupy its own visible line`, section.id, marker.line));
|
|
301
191
|
}
|
|
302
192
|
if (marker && heading) {
|
|
303
|
-
const bounds = sectionBodyBounds(content, heading);
|
|
193
|
+
const bounds = sectionBodyBounds(content, heading, scanned);
|
|
304
194
|
if (marker.start < bounds.start || marker.start >= bounds.end) {
|
|
305
195
|
diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' is outside its section`, section.id, marker.line));
|
|
306
196
|
}
|
|
@@ -310,9 +200,7 @@ function inspectArtifactStructure(
|
|
|
310
200
|
if (marker && !heading && marker.standalone) {
|
|
311
201
|
const nextHeading = scanned.headings.find((candidate) => candidate.start > marker.start && candidate.level <= 2);
|
|
312
202
|
const body = stripHtmlComments(content.slice(marker.end, nextHeading?.start ?? content.length)).trim();
|
|
313
|
-
if (body
|
|
314
|
-
repairCandidates.push(createSectionInsertionRepair(content, section, marker, locale));
|
|
315
|
-
} else if (!body) {
|
|
203
|
+
if (!body) {
|
|
316
204
|
diagnostics.push(diagnostic('ARTIFACT_EMPTY_SECTION', `section '${section.headings.zh}' has no semantic body`, section.id, marker.line));
|
|
317
205
|
}
|
|
318
206
|
}
|
|
@@ -323,32 +211,6 @@ function inspectArtifactStructure(
|
|
|
323
211
|
}
|
|
324
212
|
}
|
|
325
213
|
|
|
326
|
-
for (const candidate of repairCandidates) {
|
|
327
|
-
if (candidate.kind === 'replace-line') {
|
|
328
|
-
diagnostics.push(diagnostic(
|
|
329
|
-
'ARTIFACT_HEADING_TRAILING_PUNCTUATION',
|
|
330
|
-
`visible required H2 '${candidate.from}' may be normalized to '${candidate.to}'`,
|
|
331
|
-
candidate.sectionId,
|
|
332
|
-
candidate.line,
|
|
333
|
-
true,
|
|
334
|
-
candidate
|
|
335
|
-
));
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
const missing = diagnostics.findIndex((item) => item.code === 'ARTIFACT_MISSING_SECTION' && item.sectionId === candidate.sectionId);
|
|
339
|
-
if (missing >= 0) {
|
|
340
|
-
const section = schema.sections.find((item) => item.id === candidate.sectionId)!;
|
|
341
|
-
diagnostics.splice(missing, 1, diagnostic(
|
|
342
|
-
'ARTIFACT_MISSING_SECTION',
|
|
343
|
-
`required section '${section.headings.zh}' is missing and can be inserted at its unique marker`,
|
|
344
|
-
candidate.sectionId,
|
|
345
|
-
candidate.line,
|
|
346
|
-
true,
|
|
347
|
-
candidate
|
|
348
|
-
));
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
214
|
const orderedHeadings = schema.sections.flatMap((section) => {
|
|
353
215
|
const heading = headingsBySection.get(section.id)?.[0];
|
|
354
216
|
return heading ? [{ section, start: heading.start }] : [];
|
|
@@ -364,16 +226,11 @@ function inspectArtifactStructure(
|
|
|
364
226
|
diagnostics.push(diagnostic('ARTIFACT_SECTION_ORDER_INVALID', 'section markers are not in schema order', null, null));
|
|
365
227
|
}
|
|
366
228
|
|
|
367
|
-
const repair = repairCandidates.length === 1 && (
|
|
368
|
-
diagnostics.length === 0 ||
|
|
369
|
-
(diagnostics.length === 1 && diagnostics[0]?.operation?.kind === repairCandidates[0]?.kind)
|
|
370
|
-
) ? repairCandidates[0]! : null;
|
|
371
229
|
return {
|
|
372
230
|
ok: diagnostics.length === 0,
|
|
373
231
|
family: schema.family,
|
|
374
|
-
semanticDigest: canonicalSemanticDigest(content
|
|
375
|
-
diagnostics
|
|
376
|
-
repair
|
|
232
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
233
|
+
diagnostics
|
|
377
234
|
};
|
|
378
235
|
}
|
|
379
236
|
|
|
@@ -393,8 +250,7 @@ function inspectArtifactPatterns(
|
|
|
393
250
|
ok: diagnostics.length === 0,
|
|
394
251
|
family: schema.family,
|
|
395
252
|
semanticDigest: canonicalSemanticDigest(content),
|
|
396
|
-
diagnostics
|
|
397
|
-
repair: null
|
|
253
|
+
diagnostics
|
|
398
254
|
};
|
|
399
255
|
}
|
|
400
256
|
|
|
@@ -405,18 +261,16 @@ function inspectArtifactContract(
|
|
|
405
261
|
const structure = inspectArtifactStructure(content, schema);
|
|
406
262
|
const patterns = inspectArtifactPatterns(content, schema);
|
|
407
263
|
const diagnostics = [...structure.diagnostics, ...patterns.diagnostics];
|
|
408
|
-
const repair = patterns.diagnostics.length === 0 ? structure.repair : null;
|
|
409
264
|
return {
|
|
410
265
|
ok: diagnostics.length === 0,
|
|
411
266
|
family: schema.family,
|
|
412
|
-
semanticDigest: canonicalSemanticDigest(content
|
|
413
|
-
diagnostics
|
|
414
|
-
repair
|
|
267
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
268
|
+
diagnostics
|
|
415
269
|
};
|
|
416
270
|
}
|
|
417
271
|
|
|
418
272
|
function resultFailure(code: string, message: string): ArtifactFileResult {
|
|
419
|
-
return { status: 'failed', changed: false, artifactSha256: null, semanticDigest: null,
|
|
273
|
+
return { status: 'failed', changed: false, artifactSha256: null, semanticDigest: null, error: { code, message } };
|
|
420
274
|
}
|
|
421
275
|
|
|
422
276
|
function resultNoOp(content: string): ArtifactFileResult {
|
|
@@ -425,139 +279,13 @@ function resultNoOp(content: string): ArtifactFileResult {
|
|
|
425
279
|
changed: false,
|
|
426
280
|
artifactSha256: sha256Content(content),
|
|
427
281
|
semanticDigest: canonicalSemanticDigest(content),
|
|
428
|
-
operation: null,
|
|
429
282
|
error: null
|
|
430
283
|
};
|
|
431
284
|
}
|
|
432
285
|
|
|
433
|
-
function validateRepairContext(request: ArtifactRepairRequest, content: string): { ok: true } | { ok: false; code: string; message: string } {
|
|
434
|
-
const round = artifactRound(request.family, request.artifact);
|
|
435
|
-
if (round === null) return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact round is not canonical' };
|
|
436
|
-
const contextPattern = /^<!--\s*artifact-context:([^:\s]+):([^:\s]+):(\d+)\s*-->\s*$/;
|
|
437
|
-
const contexts = scanVisibleMarkdown(content).lines
|
|
438
|
-
.map((line) => line.text.trim().match(contextPattern))
|
|
439
|
-
.filter((match): match is RegExpMatchArray => match !== null);
|
|
440
|
-
if (contexts.length !== 1
|
|
441
|
-
|| contexts[0]![1] !== request.taskId
|
|
442
|
-
|| contexts[0]![2] !== request.family
|
|
443
|
-
|| Number(contexts[0]![3]) !== round) {
|
|
444
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact context marker does not match the requested task, family, and round' };
|
|
445
|
-
}
|
|
446
|
-
let taskContent: string;
|
|
447
|
-
try { taskContent = fs.readFileSync(path.join(request.taskDir, 'task.md'), 'utf8'); }
|
|
448
|
-
catch (error) { return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `cannot read task lifecycle context: ${String(error)}` }; }
|
|
449
|
-
const activity = locateActivityLog(taskContent);
|
|
450
|
-
if (!activity) return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'task has no unique Activity Log section' };
|
|
451
|
-
const label: Record<ArtifactSchemaFamily, string> = {
|
|
452
|
-
analysis: 'Analyze Task',
|
|
453
|
-
'review-analysis': 'Review Analysis',
|
|
454
|
-
plan: 'Plan Task',
|
|
455
|
-
'review-plan': 'Review Plan',
|
|
456
|
-
code: 'Code Task',
|
|
457
|
-
'review-code': 'Review Code'
|
|
458
|
-
};
|
|
459
|
-
const escaped = escapeRegExp(label[request.family]);
|
|
460
|
-
const qualifier = request.family === 'code'
|
|
461
|
-
? '(?:, (?:fix for review-code(?:-r(?:[2-9]|[1-9]\\d+))?\\.md|decision II-[1-9]\\d+))?'
|
|
462
|
-
: '';
|
|
463
|
-
const expected = new RegExp(`^${escaped} \\(Round ${round}${qualifier}\\)$`);
|
|
464
|
-
const open = startedBackedRows(pairEntries(activity.entries)).filter((row) => expected.test(row.step) && !row.done);
|
|
465
|
-
if (open.length !== 1) {
|
|
466
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `artifact '${request.artifact}' does not have exactly one matching open started lifecycle event` };
|
|
467
|
-
}
|
|
468
|
-
let intent;
|
|
469
|
-
try { intent = readArtifactRepairIntent(request.repoRoot, request.taskId, request.family, request.artifact); }
|
|
470
|
-
catch (error) { return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: String(error) }; }
|
|
471
|
-
if (!intent || intent.state !== 'awaiting-repair') {
|
|
472
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'artifact has no awaiting-repair finalization provenance' };
|
|
473
|
-
}
|
|
474
|
-
if (intent.artifactSha256 !== request.expectedSha256 || intent.semanticDigest !== request.expectedSemanticDigest) {
|
|
475
|
-
return { ok: false, code: 'ARTIFACT_REPAIR_PROVENANCE_INVALID', message: 'repair request does not match the current finalization provenance' };
|
|
476
|
-
}
|
|
477
|
-
return { ok: true };
|
|
478
|
-
}
|
|
479
|
-
|
|
480
286
|
function artifactRound(family: ArtifactSchemaFamily, artifact: string): number | null {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
if (!match) return null;
|
|
484
|
-
const round = Number(match[1]);
|
|
485
|
-
return Number.isSafeInteger(round) ? round : null;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
function validateTarget(taskDir: string, family: ArtifactSchemaFamily, artifact: string): { path: string } | ArtifactFileResult {
|
|
489
|
-
if (path.basename(artifact) !== artifact || artifactRound(family, artifact) === null || !artifact.endsWith('.md')) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be a canonical top-level Markdown file');
|
|
490
|
-
if (!/^TASK-\d{8}-\d{6}$/.test(path.basename(path.resolve(taskDir)))) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'target directory is not a task directory');
|
|
491
|
-
const target = path.join(taskDir, artifact);
|
|
492
|
-
if (path.dirname(path.resolve(target)) !== path.resolve(taskDir)) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact must be inside the current task directory');
|
|
493
|
-
let stat: fs.Stats;
|
|
494
|
-
try { stat = fs.lstatSync(target); }
|
|
495
|
-
catch (error) { return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact cannot be read: ${String(error)}`); }
|
|
496
|
-
if (stat.isSymbolicLink() || !stat.isFile()) return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', 'artifact is not a regular file');
|
|
497
|
-
try { fs.accessSync(target, fs.constants.R_OK | fs.constants.W_OK); }
|
|
498
|
-
catch (error) { return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', `artifact is not readable and writable: ${String(error)}`); }
|
|
499
|
-
return { path: target };
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function applyRepairUnlocked(request: ArtifactRepairRequest): ArtifactFileResult {
|
|
503
|
-
const schema = getArtifactSchema(request.family);
|
|
504
|
-
if (!schema) return resultFailure('ARTIFACT_FAMILY_UNKNOWN', `unknown artifact family '${request.family}'`);
|
|
505
|
-
const target = validateTarget(request.taskDir, request.family, request.artifact);
|
|
506
|
-
if ('status' in target) return target;
|
|
507
|
-
let content: string;
|
|
508
|
-
try { content = fs.readFileSync(target.path, 'utf8'); }
|
|
509
|
-
catch (error) { return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', String(error)); }
|
|
510
|
-
const actualSha256 = sha256Content(content);
|
|
511
|
-
if (actualSha256 !== request.expectedSha256) return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact SHA-256 does not match the expected repair baseline');
|
|
512
|
-
const context = validateRepairContext(request, content);
|
|
513
|
-
if (!context.ok) return resultFailure(context.code, context.message);
|
|
514
|
-
const inspection = inspectArtifactStructure(content, schema);
|
|
515
|
-
if (!inspection.repair || inspection.diagnostics.length !== 1) {
|
|
516
|
-
return resultFailure('ARTIFACT_REPAIR_UNSAFE', inspection.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ') || 'no deterministic structural repair is available');
|
|
517
|
-
}
|
|
518
|
-
if (inspection.repair.kind !== request.operation.kind
|
|
519
|
-
|| inspection.repair.sectionId !== request.operation.sectionId
|
|
520
|
-
|| inspection.repair.from !== request.operation.from
|
|
521
|
-
|| inspection.repair.to !== request.operation.to
|
|
522
|
-
|| inspection.repair.start !== request.operation.start
|
|
523
|
-
|| inspection.repair.end !== request.operation.end) {
|
|
524
|
-
return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact structure');
|
|
525
|
-
}
|
|
526
|
-
if (inspection.semanticDigest !== request.expectedSemanticDigest) return resultFailure('ARTIFACT_REPAIR_BASELINE_MISMATCH', 'artifact semantic digest does not match the expected repair baseline');
|
|
527
|
-
const transformed = applyRepairOperation(content, inspection.repair);
|
|
528
|
-
if (transformed === null) return resultFailure('ARTIFACT_REPAIR_OPERATION_MISMATCH', 'repair operation does not match the current artifact bytes');
|
|
529
|
-
if (transformed === content) return resultFailure('ARTIFACT_REPAIR_NO_PROGRESS', 'repair operation produced no byte change');
|
|
530
|
-
const tempPath = path.join(request.taskDir, `.${request.artifact}.repair-${process.pid}-${Date.now()}.tmp`);
|
|
531
|
-
let mode = 0o600;
|
|
532
|
-
try {
|
|
533
|
-
mode = fs.statSync(target.path).mode & 0o777;
|
|
534
|
-
fs.writeFileSync(tempPath, transformed, { encoding: 'utf8', mode, flag: 'wx' });
|
|
535
|
-
const currentStat = fs.lstatSync(target.path);
|
|
536
|
-
if (currentStat.isSymbolicLink() || !currentStat.isFile() || sha256Content(fs.readFileSync(target.path, 'utf8')) !== actualSha256) {
|
|
537
|
-
fs.unlinkSync(tempPath);
|
|
538
|
-
return resultFailure('ARTIFACT_REPAIR_CONFLICT', 'artifact changed or was replaced during repair');
|
|
539
|
-
}
|
|
540
|
-
fs.renameSync(tempPath, target.path);
|
|
541
|
-
} catch (error) {
|
|
542
|
-
try { fs.unlinkSync(tempPath); } catch { /* preserve primary error */ }
|
|
543
|
-
return resultFailure('ARTIFACT_REPAIR_WRITE_FAILED', String(error));
|
|
544
|
-
}
|
|
545
|
-
return {
|
|
546
|
-
status: 'applied',
|
|
547
|
-
changed: true,
|
|
548
|
-
artifactSha256: sha256Content(transformed),
|
|
549
|
-
semanticDigest: canonicalSemanticDigest(transformed),
|
|
550
|
-
operation: inspection.repair,
|
|
551
|
-
error: null
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function applyArtifactRepair(request: ArtifactRepairRequest): ArtifactFileResult {
|
|
556
|
-
try {
|
|
557
|
-
return withTaskExecutionLock(request.repoRoot, request.taskId, 'task-artifact.repair', () => applyRepairUnlocked(request));
|
|
558
|
-
} catch (error) {
|
|
559
|
-
return resultFailure('ARTIFACT_REPAIR_LOCK_FAILED', String(error));
|
|
560
|
-
}
|
|
287
|
+
const identity = parseArtifactName(artifact);
|
|
288
|
+
return identity?.family === family ? identity.round : null;
|
|
561
289
|
}
|
|
562
290
|
|
|
563
291
|
function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileResult {
|
|
@@ -572,7 +300,7 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
|
|
|
572
300
|
try {
|
|
573
301
|
const existing = fs.lstatSync(target);
|
|
574
302
|
if (existing.isSymbolicLink() || !existing.isFile()) return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
|
|
575
|
-
return resultNoOp(
|
|
303
|
+
return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
|
|
576
304
|
} catch (error) {
|
|
577
305
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
|
|
578
306
|
}
|
|
@@ -597,7 +325,6 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
|
|
|
597
325
|
changed: true,
|
|
598
326
|
artifactSha256: sha256Content(content),
|
|
599
327
|
semanticDigest: canonicalSemanticDigest(content),
|
|
600
|
-
operation: null,
|
|
601
328
|
error: null
|
|
602
329
|
};
|
|
603
330
|
});
|
|
@@ -607,7 +334,6 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
|
|
|
607
334
|
}
|
|
608
335
|
|
|
609
336
|
export {
|
|
610
|
-
applyArtifactRepair,
|
|
611
337
|
canonicalSemanticDigest,
|
|
612
338
|
initializeArtifactSkeleton,
|
|
613
339
|
inspectArtifactContract,
|
|
@@ -618,8 +344,6 @@ export {
|
|
|
618
344
|
export type {
|
|
619
345
|
ArtifactFileResult,
|
|
620
346
|
ArtifactInitRequest,
|
|
621
|
-
ArtifactRepairOperation,
|
|
622
|
-
ArtifactRepairRequest,
|
|
623
347
|
ArtifactStructuralDiagnostic,
|
|
624
348
|
ArtifactStructuralDiagnosticCode,
|
|
625
349
|
ArtifactStructureResult
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
|
|
4
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
4
5
|
import { extractSection, findSectionHeading, parseTable } from './sections.ts';
|
|
5
6
|
|
|
6
7
|
const RECEIPT_SECTION_ALIASES = ['产物生命周期收据', 'Artifact Lifecycle Receipts'] as const;
|
|
@@ -12,7 +13,6 @@ const RECEIPT_EVENTS = new Set([
|
|
|
12
13
|
'code.completed'
|
|
13
14
|
]);
|
|
14
15
|
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
15
|
-
const ARTIFACT_NAME_RE = /^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r([2-9]|[1-9]\d+))?\.md$/;
|
|
16
16
|
const RECEIPT_SHAPES = {
|
|
17
17
|
'review-analysis.completed': { output: 'review-analysis', input: 'analysis' },
|
|
18
18
|
'review-plan.completed': { output: 'review-plan', input: 'plan' },
|
|
@@ -59,20 +59,9 @@ function sha256File(filePath: string): string {
|
|
|
59
59
|
return sha256Bytes(fs.readFileSync(filePath));
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
function parseReceiptArtifactName(name: string): { family: string } | null {
|
|
63
|
-
const match = ARTIFACT_NAME_RE.exec(name);
|
|
64
|
-
if (!match) return null;
|
|
65
|
-
const roundText = match[2];
|
|
66
|
-
if (roundText !== undefined) {
|
|
67
|
-
const round = Number(roundText);
|
|
68
|
-
if (!Number.isSafeInteger(round) || String(round) !== roundText) return null;
|
|
69
|
-
}
|
|
70
|
-
return { family: match[1]! };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
62
|
function validateReceiptShape(event: ArtifactReceiptEvent, output: string, input: string): void {
|
|
74
|
-
const outputIdentity =
|
|
75
|
-
const inputIdentity =
|
|
63
|
+
const outputIdentity = parseArtifactName(output);
|
|
64
|
+
const inputIdentity = parseArtifactName(input);
|
|
76
65
|
if (!outputIdentity || !inputIdentity) throw new ArtifactReceiptError(`receipt artifact identity is invalid: ${output} -> ${input}`);
|
|
77
66
|
const shape = RECEIPT_SHAPES[event];
|
|
78
67
|
if (outputIdentity.family !== shape.output || inputIdentity.family !== shape.input) {
|