@fitlab-ai/agent-infra 0.9.12 → 0.9.14
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 +11 -11
- package/README.zh-CN.md +11 -11
- package/bin/cli.ts +26 -9
- package/dist/bin/cli.js +173 -157
- package/dist/bin/internal-cli.js +66 -40
- package/dist/lib/agent-client.js +5 -0
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/agent-client.js +5 -2
- package/dist/lib/internal/cli-route-inventory.js +152 -0
- package/dist/lib/internal/codex-lifecycle.js +9 -6
- package/dist/lib/internal/codex-sandbox-controller.js +8 -2
- package/dist/lib/internal/git-workflow.js +10 -6
- package/dist/lib/internal/platform-checks.js +19 -7
- package/dist/lib/internal/platform-comment.js +25 -13
- package/dist/lib/internal/platform-context.js +6 -3
- package/dist/lib/internal/platform-issue.js +20 -13
- package/dist/lib/internal/platform-metadata.js +88 -0
- package/dist/lib/internal/platform-pr-review.js +26 -13
- package/dist/lib/internal/platform-pr.js +84 -31
- package/dist/lib/internal/platform-release-notes.js +9 -6
- package/dist/lib/internal/platform-security.js +93 -0
- package/dist/lib/internal/pr-review-grade.js +25 -12
- package/dist/lib/internal/release-workflow.js +23 -9
- package/dist/lib/internal/sandbox-control.js +50 -7
- package/dist/lib/internal/task-activity.js +16 -7
- package/dist/lib/internal/task-artifact.js +235 -24
- package/dist/lib/internal/task-context.js +4 -1
- package/dist/lib/internal/task-create.js +50 -17
- package/dist/lib/internal/task-delivery.js +4 -1
- package/dist/lib/internal/task-event.js +10 -4
- package/dist/lib/internal/task-finalization.js +6 -3
- package/dist/lib/internal/task-invalidation.js +63 -0
- package/dist/lib/internal/task-ledger.js +20 -6
- package/dist/lib/internal/task-lifecycle.js +6 -3
- package/dist/lib/internal/task-operation-registry.js +421 -0
- package/dist/lib/internal/task-orchestration.js +12 -0
- package/dist/lib/internal/task-override.js +14 -7
- package/dist/lib/internal/task-qualification.js +90 -0
- package/dist/lib/internal/task-review.js +7 -4
- package/dist/lib/internal/task-short-id.js +11 -1
- package/dist/lib/internal/task-snapshot.js +4 -1
- package/dist/lib/internal/task-validate.js +7 -2
- package/dist/lib/internal/task-verify.js +11 -7
- package/dist/lib/internal/task-warning.js +11 -4
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/comment-safety.js +226 -0
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +1130 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +237 -72
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +545 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +541 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +553 -0
- package/dist/lib/platform/provider-contract.js +99 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +577 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +734 -543
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/repository-metadata.js +377 -0
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/security-alerts.js +155 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +129 -248
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/process-data/index.js +7 -0
- package/dist/lib/run/index.js +68 -23
- package/dist/lib/sandbox/commands/control-status.js +22 -0
- package/dist/lib/sandbox/commands/create.js +32 -6
- package/dist/lib/sandbox/commands/ls.js +5 -0
- package/dist/lib/sandbox/commands/rm.js +827 -120
- package/dist/lib/sandbox/commands/show.js +16 -0
- package/dist/lib/sandbox/control/client.js +174 -19
- package/dist/lib/sandbox/control/completed-reentry.js +85 -0
- package/dist/lib/sandbox/control/container-identity.js +28 -31
- package/dist/lib/sandbox/control/executor.js +151 -16
- package/dist/lib/sandbox/control/lifecycle.js +621 -127
- package/dist/lib/sandbox/control/native-file-lock.js +177 -0
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +677 -34
- package/dist/lib/sandbox/control/state.js +209 -4
- package/dist/lib/sandbox/control/task-view.js +183 -0
- package/dist/lib/sandbox/engines/authority.js +289 -0
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
- package/dist/lib/sandbox/index.js +5 -0
- package/dist/lib/sandbox/recovery.js +21 -1
- package/dist/lib/sandbox/workspace-view.js +2 -1
- package/dist/lib/sandbox/worktree-safety.js +4 -1
- package/dist/lib/server/index.js +8 -0
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +75 -18
- package/dist/lib/task/artifact-operations.js +520 -0
- package/dist/lib/task/artifact-repair-intent.js +59 -0
- package/dist/lib/task/artifact-schema.js +135 -0
- package/dist/lib/task/capabilities.js +310 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +12 -5
- package/dist/lib/task/create-service.js +111 -12
- package/dist/lib/task/create.js +25 -0
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +416 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/index.js +11 -3
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +299 -0
- package/dist/lib/task/orchestration.js +240 -144
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/qualification-audit.js +490 -0
- package/dist/lib/task/qualification-confirmation.js +109 -0
- package/dist/lib/task/qualification-intents.js +148 -0
- package/dist/lib/task/review-artifacts.js +22 -23
- package/dist/lib/task/review-finalization.js +121 -4
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/sections.js +6 -4
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-config.js +11 -0
- package/dist/lib/task/verification-engine.js +93 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +9 -0
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/agent-client.ts +4 -2
- package/lib/internal/cli-route-inventory.ts +140 -0
- package/lib/internal/codex-lifecycle.ts +8 -6
- package/lib/internal/codex-sandbox-controller.ts +7 -2
- package/lib/internal/git-workflow.ts +9 -6
- package/lib/internal/platform-checks.ts +14 -6
- package/lib/internal/platform-comment.ts +20 -12
- package/lib/internal/platform-context.ts +5 -3
- package/lib/internal/platform-issue.ts +17 -11
- package/lib/internal/platform-metadata.ts +70 -0
- package/lib/internal/platform-pr-review.ts +23 -12
- package/lib/internal/platform-pr.ts +65 -27
- package/lib/internal/platform-release-notes.ts +8 -6
- package/lib/internal/platform-security.ts +83 -0
- package/lib/internal/pr-review-grade.ts +20 -11
- package/lib/internal/release-workflow.ts +21 -9
- package/lib/internal/sandbox-control.ts +44 -6
- package/lib/internal/task-activity.ts +15 -7
- package/lib/internal/task-artifact.ts +204 -22
- package/lib/internal/task-context.ts +3 -1
- package/lib/internal/task-create.ts +78 -17
- package/lib/internal/task-delivery.ts +3 -1
- package/lib/internal/task-event.ts +9 -4
- package/lib/internal/task-finalization.ts +6 -4
- package/lib/internal/task-invalidation.ts +41 -0
- package/lib/internal/task-ledger.ts +21 -8
- package/lib/internal/task-lifecycle.ts +5 -3
- package/lib/internal/task-operation-registry.ts +482 -0
- package/lib/internal/task-orchestration.ts +12 -0
- package/lib/internal/task-override.ts +15 -9
- package/lib/internal/task-qualification.ts +61 -0
- package/lib/internal/task-review.ts +6 -4
- package/lib/internal/task-short-id.ts +10 -1
- package/lib/internal/task-snapshot.ts +3 -1
- package/lib/internal/task-validate.ts +6 -2
- package/lib/internal/task-verify.ts +10 -7
- package/lib/internal/task-warning.ts +10 -4
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/comment-safety.ts +250 -0
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +1132 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +255 -80
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +639 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +581 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +611 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +611 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +781 -550
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/repository-metadata.ts +453 -0
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/security-alerts.ts +191 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +128 -304
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/process-data/index.ts +7 -0
- package/lib/run/index.ts +62 -22
- package/lib/sandbox/commands/control-status.ts +26 -0
- package/lib/sandbox/commands/create.ts +46 -6
- package/lib/sandbox/commands/ls.ts +7 -0
- package/lib/sandbox/commands/rm.ts +1000 -87
- package/lib/sandbox/commands/show.ts +16 -0
- package/lib/sandbox/control/client.ts +203 -19
- package/lib/sandbox/control/completed-reentry.ts +98 -0
- package/lib/sandbox/control/container-identity.ts +27 -43
- package/lib/sandbox/control/executor.ts +180 -15
- package/lib/sandbox/control/lifecycle.ts +629 -17
- package/lib/sandbox/control/native-file-lock.ts +209 -0
- package/lib/sandbox/control/protocol.ts +118 -2
- package/lib/sandbox/control/server.ts +724 -34
- package/lib/sandbox/control/state.ts +257 -4
- package/lib/sandbox/control/task-view.ts +240 -0
- package/lib/sandbox/engines/authority.ts +365 -0
- package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
- package/lib/sandbox/index.ts +7 -0
- package/lib/sandbox/recovery.ts +21 -1
- package/lib/sandbox/workspace-view.ts +9 -3
- package/lib/sandbox/worktree-safety.ts +4 -1
- package/lib/server/index.ts +10 -0
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +70 -22
- package/lib/task/artifact-operations.ts +626 -0
- package/lib/task/artifact-repair-intent.ts +71 -0
- package/lib/task/artifact-schema.ts +192 -0
- package/lib/task/capabilities.ts +331 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +22 -9
- package/lib/task/create-service.ts +139 -14
- package/lib/task/create.ts +27 -0
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +392 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/index.ts +16 -3
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +432 -0
- package/lib/task/orchestration.ts +223 -133
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/qualification-audit.ts +599 -0
- package/lib/task/qualification-confirmation.ts +117 -0
- package/lib/task/qualification-intents.ts +150 -0
- package/lib/task/review-artifacts.ts +21 -22
- package/lib/task/review-finalization.ts +133 -4
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/sections.ts +6 -4
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-config.ts +12 -0
- package/lib/task/verification-engine.ts +103 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +12 -1
- package/templates/.agents/rules/README.en.md +3 -1
- package/templates/.agents/rules/README.zh-CN.md +3 -1
- package/templates/.agents/rules/decision-qualification.en.md +22 -0
- package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
- package/templates/.agents/rules/evidence-reporting.en.md +28 -0
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +13 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/security-alerts.en.md +8 -8
- package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +41 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
- package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -14
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
- 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 +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
- package/templates/.agents/skills/plan-task/SKILL.en.md +27 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
- package/templates/.agents/skills/review-analysis/SKILL.en.md +17 -4
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-code/SKILL.en.md +18 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
- package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-plan/SKILL.en.md +17 -4
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +9 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
- package/templates/.agents/skills/test/SKILL.en.md +13 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +24 -1
- package/templates/.agents/templates/task.zh-CN.md +24 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
- package/templates/.agents/scripts/security-alerts.github.sh +0 -151
- package/templates/.agents/scripts/security-alerts.sh +0 -31
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
|
|
4
|
+
import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
5
|
+
import { CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, constraintDigest, parseQualificationConfirmations, parseTaskQualification } from './qualification-audit.ts';
|
|
6
|
+
import type { QualificationConfirmation } from './qualification-audit.ts';
|
|
7
|
+
import { findSectionHeading } from './sections.ts';
|
|
8
|
+
import { resolveTaskRef } from './resolve-ref.ts';
|
|
9
|
+
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
10
|
+
import type { TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
11
|
+
|
|
12
|
+
type QualificationConfirmationRequest = {
|
|
13
|
+
taskRef: string;
|
|
14
|
+
constraintId: string;
|
|
15
|
+
expectedDigest: string;
|
|
16
|
+
rationale: string;
|
|
17
|
+
operation?: 'confirm' | 'supersede' | 'revoke';
|
|
18
|
+
dryRun?: boolean;
|
|
19
|
+
};
|
|
20
|
+
type QualificationConfirmationResult = {
|
|
21
|
+
status: 'planned' | 'applied' | 'no-op' | 'failed';
|
|
22
|
+
changed: boolean;
|
|
23
|
+
taskId: string | null;
|
|
24
|
+
qcrId: string | null;
|
|
25
|
+
requestId: string | null;
|
|
26
|
+
operations: readonly TaskOperationSummary[];
|
|
27
|
+
error: { code: string; message: string } | null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function failed(code: string, message: string, taskId: string | null = null): QualificationConfirmationResult {
|
|
31
|
+
return { status: 'failed', changed: false, taskId, qcrId: null, requestId: null, operations: [], error: { code, message } };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function nextQcrId(content: string): string {
|
|
35
|
+
let max = 0;
|
|
36
|
+
for (const match of content.matchAll(/^\|\s*(QCR-(\d+))\s*\|/gm)) max = Math.max(max, Number(match[2]));
|
|
37
|
+
return `QCR-${max + 1}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requestIdFor(taskId: string, constraintId: string, expectedDigest: string, rationale: string, timestamp: string): string {
|
|
41
|
+
const hash = createHash('sha256').update(JSON.stringify([taskId, constraintId, expectedDigest, rationale, timestamp])).digest('hex');
|
|
42
|
+
return `QCR-REQUEST-${hash.slice(0, 20)}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function renderConfirmations(rows: readonly QualificationConfirmation[]): string {
|
|
46
|
+
return [
|
|
47
|
+
`| ${CONFIRMATION_COLUMNS.join(' | ')} |`,
|
|
48
|
+
`| ${CONFIRMATION_COLUMNS.map(() => '---').join(' | ')} |`,
|
|
49
|
+
...rows.map((row) => [row.qcrId, row.constraintId, row.actor, row.entrypoint, row.requestId, row.approvedDigest, row.confirmedAt, row.rationale]
|
|
50
|
+
.map((value) => value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]/g, ' '))
|
|
51
|
+
.join(' | ')).map((line) => `| ${line} |`)
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function applyQualificationConfirmation(request: QualificationConfirmationRequest, options: TaskWriteOptions = {}): QualificationConfirmationResult {
|
|
56
|
+
if (!request || typeof request !== 'object' || !request.taskRef || !/^C-[1-9]\d*$/.test(request.constraintId) || !/^[a-f0-9]{64}$/i.test(request.expectedDigest) || !request.rationale?.trim() || /[\r\n]/.test(request.rationale)) {
|
|
57
|
+
return failed('QUALIFICATION_CONFIRMATION_INVALID', 'taskRef, constraintId, expectedDigest, and a single-line rationale are required');
|
|
58
|
+
}
|
|
59
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
60
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
61
|
+
if (resolved.state !== 'active') return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
62
|
+
let content: string;
|
|
63
|
+
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
64
|
+
catch (error) { return failed('TASK_READ_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
|
|
65
|
+
const parsed = parseTaskQualification(content);
|
|
66
|
+
if (!parsed.ok) return failed(parsed.code, parsed.message, resolved.taskId);
|
|
67
|
+
if (!parsed.qualification.present) return failed('QUALIFICATION_TASK_CONTRACT_MISSING', 'task qualification contract is missing', resolved.taskId);
|
|
68
|
+
const constraint = parsed.qualification.constraints.find((row) => row.constraintId === request.constraintId);
|
|
69
|
+
if (!constraint) return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `unknown constraint '${request.constraintId}'`, resolved.taskId);
|
|
70
|
+
const operation = request.operation ?? 'confirm';
|
|
71
|
+
const existing = parseQualificationConfirmations(content);
|
|
72
|
+
if (!existing.ok) return failed(existing.code, existing.message, resolved.taskId);
|
|
73
|
+
const previous = existing.confirmations.find((row) => row.qcrId === constraint.approvalEvidence);
|
|
74
|
+
if (operation === 'confirm' && constraint.status === 'confirmed') {
|
|
75
|
+
if (previous?.constraintId === constraint.constraintId && previous.approvedDigest === constraint.digest && previous.rationale === request.rationale.trim()) {
|
|
76
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null };
|
|
77
|
+
}
|
|
78
|
+
return failed('QUALIFICATION_CONFIRMATION_CONFLICT', `constraint '${request.constraintId}' is already confirmed with different evidence`, resolved.taskId);
|
|
79
|
+
}
|
|
80
|
+
const transitionStatus = operation === 'supersede' ? 'superseded' : 'open';
|
|
81
|
+
if (operation !== 'confirm' && constraint.status === transitionStatus && !constraint.approvalEvidence) {
|
|
82
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null };
|
|
83
|
+
}
|
|
84
|
+
if (operation !== 'confirm' && constraint.status !== 'confirmed') return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `constraint '${request.constraintId}' is not confirmed`, resolved.taskId);
|
|
85
|
+
if (parsed.qualification.constraintDigest !== request.expectedDigest) return failed('QUALIFICATION_DIGEST_CONFLICT', 'constraint digest changed before human confirmation', resolved.taskId);
|
|
86
|
+
let metadata;
|
|
87
|
+
try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
|
|
88
|
+
catch (error) { return failed('METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
|
|
89
|
+
const qcrId = operation === 'confirm' ? nextQcrId(content) : null;
|
|
90
|
+
const requestId = operation === 'confirm' ? requestIdFor(resolved.taskId, request.constraintId, request.expectedDigest, request.rationale.trim(), metadata.timestamp) : null;
|
|
91
|
+
const confirmedConstraint = qcrId ? {
|
|
92
|
+
constraintId: constraint.constraintId, statement: constraint.statement, status: 'confirmed' as const,
|
|
93
|
+
authority: 'human-declared', source: constraint.source, evidence: constraint.evidence,
|
|
94
|
+
derivedFrom: constraint.derivedFrom, approvalEvidence: qcrId
|
|
95
|
+
} : null;
|
|
96
|
+
const confirmation = confirmedConstraint && qcrId && requestId ? {
|
|
97
|
+
qcrId, constraintId: request.constraintId, actor: 'human-declared' as const, entrypoint: 'task-qualification' as const,
|
|
98
|
+
requestId, approvedDigest: constraintDigest(confirmedConstraint), confirmedAt: metadata.timestamp, rationale: request.rationale.trim()
|
|
99
|
+
} : null;
|
|
100
|
+
const confirmations = confirmation ? [...existing.confirmations, confirmation] : existing.confirmations;
|
|
101
|
+
const activity = locateActivityLog(content);
|
|
102
|
+
if (!activity) return failed('QUALIFICATION_ACTIVITY_MISSING', 'task has no unique Activity Log section', resolved.taskId);
|
|
103
|
+
const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
|
|
104
|
+
{
|
|
105
|
+
kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: ['constraint_id', 'statement', 'status', 'authority', 'source', 'evidence', 'derived_from', 'approval_evidence'], keyColumn: 'constraint_id', key: constraint.constraintId,
|
|
106
|
+
values: { statement: constraint.statement, status: operation === 'confirm' ? 'confirmed' : transitionStatus, authority: operation === 'confirm' ? 'human-declared' : constraint.authority, source: constraint.source, evidence: constraint.evidence, derived_from: constraint.derivedFrom.join(','), approval_evidence: qcrId ?? '' }
|
|
107
|
+
},
|
|
108
|
+
...(operation === 'confirm' ? [{ kind: 'section' as const, aliases: CONFIRMATION_HEADINGS, heading: findSectionHeading(content, [...CONFIRMATION_HEADINGS]), body: renderConfirmations(confirmations) }] : []),
|
|
109
|
+
{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }
|
|
110
|
+
];
|
|
111
|
+
const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
|
|
112
|
+
if (result.status === 'failed') return failed(result.error.code, result.error.message, result.taskId);
|
|
113
|
+
return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export { applyQualificationConfirmation };
|
|
117
|
+
export type { QualificationConfirmationRequest, QualificationConfirmationResult };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
|
|
3
|
+
import { CONSTRAINT_COLUMNS, CANDIDATE_COLUMNS, parseTaskQualification } from './qualification-audit.ts';
|
|
4
|
+
import type { CandidateStatus, ConstraintStatus } from './qualification-audit.ts';
|
|
5
|
+
import { resolveTaskRef } from './resolve-ref.ts';
|
|
6
|
+
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
7
|
+
import type { TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
8
|
+
|
|
9
|
+
type QualificationConstraintProposal = {
|
|
10
|
+
constraintId: string;
|
|
11
|
+
statement: string;
|
|
12
|
+
status: Exclude<ConstraintStatus, 'confirmed'>;
|
|
13
|
+
authority: string;
|
|
14
|
+
source: string;
|
|
15
|
+
evidence: string;
|
|
16
|
+
derivedFrom: readonly string[];
|
|
17
|
+
approvalEvidence: string;
|
|
18
|
+
};
|
|
19
|
+
type QualificationCandidateProposal = {
|
|
20
|
+
candidateId: string;
|
|
21
|
+
statement: string;
|
|
22
|
+
status: Extract<CandidateStatus, 'pending'>;
|
|
23
|
+
constraintIds: readonly string[];
|
|
24
|
+
impact: string;
|
|
25
|
+
evidence: string;
|
|
26
|
+
};
|
|
27
|
+
type QualificationProposalRequest = {
|
|
28
|
+
taskRef: string;
|
|
29
|
+
expectedTaskInputDigest: string;
|
|
30
|
+
constraints?: readonly QualificationConstraintProposal[];
|
|
31
|
+
candidates?: readonly QualificationCandidateProposal[];
|
|
32
|
+
dryRun?: boolean;
|
|
33
|
+
};
|
|
34
|
+
type QualificationProposalResult = {
|
|
35
|
+
status: 'planned' | 'applied' | 'no-op' | 'failed';
|
|
36
|
+
changed: boolean;
|
|
37
|
+
taskId: string | null;
|
|
38
|
+
operations: readonly TaskOperationSummary[];
|
|
39
|
+
error: { code: string; message: string } | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function failed(code: string, message: string, taskId: string | null = null): QualificationProposalResult {
|
|
43
|
+
return { status: 'failed', changed: false, taskId, operations: [], error: { code, message } };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function validSingleLine(value: unknown): value is string {
|
|
47
|
+
return typeof value === 'string' && Boolean(value.trim()) && !/[\r\n]/.test(value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function assertProposalKeys(value: unknown, keys: readonly string[], label: string): Record<string, unknown> {
|
|
51
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`${label} must be an object`);
|
|
52
|
+
const record = value as Record<string, unknown>;
|
|
53
|
+
const unexpected = Object.keys(record).find((key) => !keys.includes(key));
|
|
54
|
+
if (unexpected) throw new Error(`${label} does not accept '${unexpected}'`);
|
|
55
|
+
return record;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseConstraintProposal(value: unknown): QualificationConstraintProposal {
|
|
59
|
+
const row = assertProposalKeys(value, ['constraintId', 'statement', 'status', 'authority', 'source', 'evidence', 'derivedFrom', 'approvalEvidence'], 'constraint proposal');
|
|
60
|
+
for (const key of ['constraintId', 'statement', 'status', 'authority', 'source', 'evidence', 'approvalEvidence']) {
|
|
61
|
+
if (typeof row[key] !== 'string' || /[\r\n]/.test(row[key] as string)) throw new Error(`constraint proposal '${key}' must be a single-line string`);
|
|
62
|
+
}
|
|
63
|
+
if (!/^C-[1-9]\d*$/.test(row.constraintId as string)) throw new Error(`constraint proposal id '${row.constraintId as string}' is invalid`);
|
|
64
|
+
if (row.status === 'confirmed' || !['derived', 'assumption', 'open', 'conflicted', 'superseded'].includes(row.status as string)) throw new Error('proposal cannot write confirmed constraint status');
|
|
65
|
+
if ((row.approvalEvidence as string) !== '') throw new Error('proposal cannot write approval_evidence');
|
|
66
|
+
if (!Array.isArray(row.derivedFrom) || row.derivedFrom.some((item) => typeof item !== 'string' || !/^C-[1-9]\d*$/.test(item))) throw new Error('constraint proposal derivedFrom is invalid');
|
|
67
|
+
return {
|
|
68
|
+
constraintId: row.constraintId as string, statement: row.statement as string, status: row.status as Exclude<ConstraintStatus, 'confirmed'>,
|
|
69
|
+
authority: row.authority as string, source: row.source as string, evidence: row.evidence as string,
|
|
70
|
+
derivedFrom: [...row.derivedFrom as string[]], approvalEvidence: ''
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseCandidateProposal(value: unknown): QualificationCandidateProposal {
|
|
75
|
+
const row = assertProposalKeys(value, ['candidateId', 'statement', 'status', 'constraintIds', 'impact', 'evidence'], 'candidate proposal');
|
|
76
|
+
for (const key of ['candidateId', 'statement', 'status', 'impact', 'evidence']) if (!validSingleLine(row[key])) throw new Error(`candidate proposal '${key}' must be a non-empty single-line string`);
|
|
77
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(row.candidateId as string)) throw new Error(`candidate proposal id '${row.candidateId as string}' is invalid`);
|
|
78
|
+
if (row.status !== 'pending') throw new Error('proposal can only write pending candidate status');
|
|
79
|
+
if (!Array.isArray(row.constraintIds) || row.constraintIds.some((item) => typeof item !== 'string' || !/^C-[1-9]\d*$/.test(item))) throw new Error('candidate proposal constraintIds is invalid');
|
|
80
|
+
return row as unknown as QualificationCandidateProposal;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function proposalValues(request: QualificationProposalRequest): { constraints: QualificationConstraintProposal[]; candidates: QualificationCandidateProposal[] } {
|
|
84
|
+
if (!/^[a-f0-9]{64}$/i.test(request.expectedTaskInputDigest)) throw new Error('expectedTaskInputDigest must be a sha256 digest');
|
|
85
|
+
if (Object.keys(request).some((key) => !['taskRef', 'expectedTaskInputDigest', 'constraints', 'candidates', 'dryRun'].includes(key))) throw new Error('proposal contains an unsupported field');
|
|
86
|
+
if (!Array.isArray(request.constraints) && request.constraints !== undefined) throw new Error('constraints must be an array');
|
|
87
|
+
if (!Array.isArray(request.candidates) && request.candidates !== undefined) throw new Error('candidates must be an array');
|
|
88
|
+
return {
|
|
89
|
+
constraints: (request.constraints ?? []).map(parseConstraintProposal),
|
|
90
|
+
candidates: (request.candidates ?? []).map(parseCandidateProposal)
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function applyQualificationProposal(request: QualificationProposalRequest, options: TaskWriteOptions = {}): QualificationProposalResult {
|
|
95
|
+
if (!request || typeof request !== 'object' || !validSingleLine(request.taskRef)) return failed('QUALIFICATION_PROPOSAL_INVALID', 'taskRef is required');
|
|
96
|
+
let values: ReturnType<typeof proposalValues>;
|
|
97
|
+
try { values = proposalValues(request); }
|
|
98
|
+
catch (error) { return failed('QUALIFICATION_PROPOSAL_INVALID', error instanceof Error ? error.message : String(error)); }
|
|
99
|
+
if (values.constraints.length === 0 && values.candidates.length === 0) return failed('QUALIFICATION_PROPOSAL_INVALID', 'proposal must contain a constraint or candidate');
|
|
100
|
+
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
101
|
+
if (!resolved.ok) return failed(resolved.code, resolved.message, resolved.taskId);
|
|
102
|
+
if (resolved.state !== 'active') return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
103
|
+
let content: string;
|
|
104
|
+
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
105
|
+
catch (error) { return failed('TASK_READ_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
|
|
106
|
+
const parsed = parseTaskQualification(content);
|
|
107
|
+
if (!parsed.ok) return failed(parsed.code, parsed.message, resolved.taskId);
|
|
108
|
+
if (!parsed.qualification.present) return failed('QUALIFICATION_TASK_CONTRACT_MISSING', 'task qualification contract is missing', resolved.taskId);
|
|
109
|
+
const currentConstraintMap = new Map(parsed.qualification.constraints.map((row) => [row.constraintId, row]));
|
|
110
|
+
const currentCandidateMap = new Map(parsed.qualification.candidates.map((row) => [row.candidateId, row]));
|
|
111
|
+
const proposalAlreadyApplied = values.constraints.every((row) => {
|
|
112
|
+
const current = currentConstraintMap.get(row.constraintId);
|
|
113
|
+
return current && current.statement === row.statement && current.status === row.status && current.authority === row.authority
|
|
114
|
+
&& current.source === row.source && current.evidence === row.evidence && current.derivedFrom.join(',') === row.derivedFrom.join(',') && current.approvalEvidence === '';
|
|
115
|
+
}) && values.candidates.every((row) => {
|
|
116
|
+
const current = currentCandidateMap.get(row.candidateId);
|
|
117
|
+
return current && current.statement === row.statement && current.status === row.status && current.constraintIds.join(',') === row.constraintIds.join(',')
|
|
118
|
+
&& current.impact === row.impact && current.evidence === row.evidence;
|
|
119
|
+
});
|
|
120
|
+
if (parsed.qualification.taskInputDigest !== request.expectedTaskInputDigest) {
|
|
121
|
+
return proposalAlreadyApplied
|
|
122
|
+
? { status: 'no-op', changed: false, taskId: resolved.taskId, operations: [], error: null }
|
|
123
|
+
: failed('QUALIFICATION_DIGEST_CONFLICT', 'task qualification input changed before proposal was applied', resolved.taskId);
|
|
124
|
+
}
|
|
125
|
+
for (const row of values.constraints) {
|
|
126
|
+
const current = currentConstraintMap.get(row.constraintId);
|
|
127
|
+
if (current?.status === 'confirmed') return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `confirmed constraint '${row.constraintId}' must be superseded or revoked through task-qualification`, resolved.taskId);
|
|
128
|
+
}
|
|
129
|
+
const knownConstraintIds = new Set([...parsed.qualification.constraints.map((row) => row.constraintId), ...values.constraints.map((row) => row.constraintId)]);
|
|
130
|
+
for (const row of values.constraints) for (const parent of row.derivedFrom) if (!knownConstraintIds.has(parent)) return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `constraint proposal '${row.constraintId}' references unknown '${parent}'`, resolved.taskId);
|
|
131
|
+
for (const row of values.candidates) for (const id of row.constraintIds) if (!knownConstraintIds.has(id)) return failed('QUALIFICATION_CONSTRAINT_UNKNOWN', `candidate proposal '${row.candidateId}' references unknown '${id}'`, resolved.taskId);
|
|
132
|
+
const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [];
|
|
133
|
+
for (const row of values.constraints) {
|
|
134
|
+
mutations.push({ kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: CONSTRAINT_COLUMNS, keyColumn: 'constraint_id', key: row.constraintId, values: { statement: row.statement, status: row.status, authority: row.authority, source: row.source, evidence: row.evidence, derived_from: row.derivedFrom.join(','), approval_evidence: '' } });
|
|
135
|
+
}
|
|
136
|
+
for (const row of values.candidates) {
|
|
137
|
+
mutations.push({ kind: 'table-row', action: 'upsert', sectionAliases: ['候选与否决方案', 'Candidate and Rejected Options', 'Candidates and Rejected Options', 'Candidates and Rejected Alternatives'], columns: CANDIDATE_COLUMNS, keyColumn: 'candidate_id', key: row.candidateId, values: { statement: row.statement, status: row.status, constraint_ids: row.constraintIds.join(','), impact: row.impact, evidence: row.evidence } });
|
|
138
|
+
}
|
|
139
|
+
// Only the submitted rows are mutated; no actor, confirmation, or QCR fields
|
|
140
|
+
// can be smuggled into this internal proposal operation.
|
|
141
|
+
let metadata;
|
|
142
|
+
try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
|
|
143
|
+
catch (error) { return failed('METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId); }
|
|
144
|
+
const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
|
|
145
|
+
if (result.status === 'failed') return failed(result.error.code, result.error.message, result.taskId);
|
|
146
|
+
return { status: result.status, changed: result.changed, taskId: result.taskId, operations: result.operations, error: null };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { applyQualificationProposal };
|
|
150
|
+
export type { QualificationCandidateProposal, QualificationConstraintProposal, QualificationProposalRequest, QualificationProposalResult };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { scanVisibleMarkdown } from './decision-details.ts';
|
|
5
|
+
|
|
4
6
|
type ReviewFindingCounts = {
|
|
5
7
|
blocker: number;
|
|
6
8
|
major: number;
|
|
@@ -77,29 +79,21 @@ function normalizeVerdict(raw: unknown): ReviewVerdict | '' {
|
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
function extractSection(content: string, names: string[]): string {
|
|
80
|
-
const lines = content.split(/\r?\n/);
|
|
81
82
|
const nameSet = new Set(names);
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const sectionLines = [];
|
|
88
|
-
for (let index = start + 1; index < lines.length; index += 1) {
|
|
89
|
-
if (/^##\s+/.test(lines[index]!)) break;
|
|
90
|
-
sectionLines.push(lines[index]);
|
|
91
|
-
}
|
|
92
|
-
return sectionLines.join('\n');
|
|
83
|
+
const scanned = scanVisibleMarkdown(content);
|
|
84
|
+
const heading = scanned.headings.find((item) => item.level === 2 && nameSet.has(item.text));
|
|
85
|
+
if (!heading) return '';
|
|
86
|
+
const next = scanned.headings.find((item) => item.start > heading.start && item.level <= 2);
|
|
87
|
+
return content.slice(heading.end, next?.start ?? content.length);
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
function summaryBounds(content: string): { start: number; end: number } | null {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const summaries = headings.filter((heading) => SUMMARY_HEADINGS.has(heading.name));
|
|
91
|
+
const scanned = scanVisibleMarkdown(content);
|
|
92
|
+
const summaries = scanned.headings.filter((heading) => heading.level === 2 && SUMMARY_HEADINGS.has(heading.text));
|
|
99
93
|
if (summaries.length !== 1) return null;
|
|
100
94
|
const summary = summaries[0]!;
|
|
101
|
-
const next = headings.find((heading) => heading.
|
|
102
|
-
return { start: summary.end, end: next?.
|
|
95
|
+
const next = scanned.headings.find((heading) => heading.start > summary.start && heading.level <= 2);
|
|
96
|
+
return { start: summary.end, end: next?.start ?? content.length };
|
|
103
97
|
}
|
|
104
98
|
|
|
105
99
|
function uniqueLine(
|
|
@@ -107,14 +101,19 @@ function uniqueLine(
|
|
|
107
101
|
bounds: { start: number; end: number },
|
|
108
102
|
pattern: RegExp
|
|
109
103
|
): { value: string; start: number; end: number } | null {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
104
|
+
const matches: Array<{ value: string; index: number; length: number }> = [];
|
|
105
|
+
for (const line of scanVisibleMarkdown(content).lines) {
|
|
106
|
+
if (line.start < bounds.start || line.start >= bounds.end) continue;
|
|
107
|
+
pattern.lastIndex = 0;
|
|
108
|
+
const match = pattern.exec(line.text);
|
|
109
|
+
if (match) matches.push({ value: match[1]!, index: line.start + match.index, length: match[0].length });
|
|
110
|
+
}
|
|
112
111
|
if (matches.length !== 1) return null;
|
|
113
112
|
const match = matches[0]!;
|
|
114
113
|
return {
|
|
115
|
-
value: match
|
|
116
|
-
start:
|
|
117
|
-
end:
|
|
114
|
+
value: match.value,
|
|
115
|
+
start: match.index,
|
|
116
|
+
end: match.index + match.length
|
|
118
117
|
};
|
|
119
118
|
}
|
|
120
119
|
|
|
@@ -14,6 +14,10 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
|
|
|
14
14
|
import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
|
|
15
15
|
import { allowsManualOverride } from './guard-override.ts';
|
|
16
16
|
import type { ManualOverrideCapability } from './guard-override.ts';
|
|
17
|
+
import { getArtifactSchema } from './artifact-schema.ts';
|
|
18
|
+
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.ts';
|
|
19
|
+
import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.ts';
|
|
20
|
+
import type { ArtifactRepairOperation } from './artifact-operations.ts';
|
|
17
21
|
|
|
18
22
|
type ReviewFinalizationErrorCode =
|
|
19
23
|
| ResolveTaskRefErrorCode
|
|
@@ -26,6 +30,7 @@ type ReviewFinalizationErrorCode =
|
|
|
26
30
|
| 'REVIEW_SUMMARY_NOT_FOUND'
|
|
27
31
|
| 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
|
|
28
32
|
| 'REVIEW_SUMMARY_COUNT_MISMATCH'
|
|
33
|
+
| 'REVIEW_ARTIFACT_STRUCTURE_INVALID'
|
|
29
34
|
| 'REVIEW_DECISION_DETAIL_INVALID'
|
|
30
35
|
| 'REVIEW_ARTIFACT_CONFLICT'
|
|
31
36
|
| 'REVIEW_PROVENANCE_INVALID'
|
|
@@ -48,6 +53,10 @@ type ReviewFinalizationResult = {
|
|
|
48
53
|
stage: string;
|
|
49
54
|
artifact: string;
|
|
50
55
|
stageStatus: LedgerStageStatus | null;
|
|
56
|
+
artifactSha256: string | null;
|
|
57
|
+
semanticDigest: string | null;
|
|
58
|
+
repairable: boolean;
|
|
59
|
+
operation: ArtifactRepairOperation | null;
|
|
51
60
|
operations: readonly {
|
|
52
61
|
kind: 'artifact';
|
|
53
62
|
artifact: string;
|
|
@@ -88,7 +97,8 @@ function failed(
|
|
|
88
97
|
code: ReviewFinalizationErrorCode,
|
|
89
98
|
message: string,
|
|
90
99
|
taskId: string | null = null,
|
|
91
|
-
stageStatus: LedgerStageStatus | null = null
|
|
100
|
+
stageStatus: LedgerStageStatus | null = null,
|
|
101
|
+
extra: Partial<ReviewFinalizationResult> = {}
|
|
92
102
|
): ReviewFinalizationResult {
|
|
93
103
|
return {
|
|
94
104
|
status: 'failed',
|
|
@@ -99,8 +109,13 @@ function failed(
|
|
|
99
109
|
stage: request.stage,
|
|
100
110
|
artifact: request.artifact,
|
|
101
111
|
stageStatus,
|
|
112
|
+
artifactSha256: null,
|
|
113
|
+
semanticDigest: null,
|
|
114
|
+
repairable: false,
|
|
115
|
+
operation: null,
|
|
102
116
|
operations: [],
|
|
103
|
-
error: { code, message }
|
|
117
|
+
error: { code, message },
|
|
118
|
+
...extra
|
|
104
119
|
};
|
|
105
120
|
}
|
|
106
121
|
|
|
@@ -167,6 +182,80 @@ function finalizeReviewSummaryUnlocked(
|
|
|
167
182
|
} catch (error) {
|
|
168
183
|
return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
|
|
169
184
|
}
|
|
185
|
+
const schema = getArtifactSchema(spec.family);
|
|
186
|
+
const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
|
|
187
|
+
const artifactSha256 = sha256Content(artifactContent);
|
|
188
|
+
const artifactSemanticDigest = canonicalSemanticDigest(artifactContent);
|
|
189
|
+
let repairIntent;
|
|
190
|
+
try {
|
|
191
|
+
repairIntent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
return failed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, null, {
|
|
194
|
+
artifactSha256,
|
|
195
|
+
semanticDigest: artifactSemanticDigest
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (structure && !structure.ok) {
|
|
199
|
+
const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
|
|
200
|
+
if (repairable) {
|
|
201
|
+
if (repairIntent && (
|
|
202
|
+
repairIntent.state !== 'awaiting-repair' ||
|
|
203
|
+
repairIntent.artifactSha256 !== artifactSha256 ||
|
|
204
|
+
repairIntent.semanticDigest !== structure.semanticDigest
|
|
205
|
+
)) {
|
|
206
|
+
return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
|
|
207
|
+
artifactSha256,
|
|
208
|
+
semanticDigest: structure.semanticDigest
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
if (!repairIntent) {
|
|
213
|
+
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
214
|
+
version: 1,
|
|
215
|
+
taskId: resolved.taskId,
|
|
216
|
+
family: spec.family,
|
|
217
|
+
artifact: request.artifact,
|
|
218
|
+
state: 'awaiting-repair',
|
|
219
|
+
baselineSemanticDigest: structure.semanticDigest,
|
|
220
|
+
artifactSha256,
|
|
221
|
+
semanticDigest: structure.semanticDigest
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
|
|
226
|
+
artifactSha256,
|
|
227
|
+
semanticDigest: structure.semanticDigest,
|
|
228
|
+
operation: structure.repair
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return failed(
|
|
233
|
+
request,
|
|
234
|
+
'REVIEW_ARTIFACT_STRUCTURE_INVALID',
|
|
235
|
+
structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '),
|
|
236
|
+
resolved.taskId,
|
|
237
|
+
null,
|
|
238
|
+
{
|
|
239
|
+
artifactSha256,
|
|
240
|
+
semanticDigest: structure.semanticDigest,
|
|
241
|
+
repairable,
|
|
242
|
+
operation: structure.repair
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== artifactSemanticDigest) {
|
|
247
|
+
return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', resolved.taskId, null, {
|
|
248
|
+
artifactSha256,
|
|
249
|
+
semanticDigest: artifactSemanticDigest
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
|
|
253
|
+
&& (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
|
|
254
|
+
return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
|
|
255
|
+
artifactSha256,
|
|
256
|
+
semanticDigest: artifactSemanticDigest
|
|
257
|
+
});
|
|
258
|
+
}
|
|
170
259
|
if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
|
|
171
260
|
return failed(
|
|
172
261
|
request,
|
|
@@ -232,8 +321,26 @@ function finalizeReviewSummaryUnlocked(
|
|
|
232
321
|
return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
|
|
233
322
|
}
|
|
234
323
|
if (!transformed.changed) {
|
|
324
|
+
if (repairIntent?.state === 'awaiting-repair') {
|
|
325
|
+
try {
|
|
326
|
+
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
327
|
+
...repairIntent,
|
|
328
|
+
state: 'passed',
|
|
329
|
+
artifactSha256,
|
|
330
|
+
semanticDigest: artifactSemanticDigest
|
|
331
|
+
});
|
|
332
|
+
} catch (error) {
|
|
333
|
+
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
|
|
334
|
+
artifactSha256,
|
|
335
|
+
semanticDigest: artifactSemanticDigest
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
235
339
|
return {
|
|
236
|
-
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus
|
|
340
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
|
|
341
|
+
artifactSha256,
|
|
342
|
+
semanticDigest: artifactSemanticDigest
|
|
343
|
+
}),
|
|
237
344
|
status: 'no-op',
|
|
238
345
|
error: null
|
|
239
346
|
};
|
|
@@ -241,7 +348,10 @@ function finalizeReviewSummaryUnlocked(
|
|
|
241
348
|
const operations = [{ kind: 'artifact' as const, artifact: request.artifact, operation: 'update' as const }];
|
|
242
349
|
if (request.dryRun) {
|
|
243
350
|
return {
|
|
244
|
-
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus
|
|
351
|
+
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
|
|
352
|
+
artifactSha256: sha256Content(transformed.content),
|
|
353
|
+
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
354
|
+
}),
|
|
245
355
|
status: 'planned',
|
|
246
356
|
changed: true,
|
|
247
357
|
operations,
|
|
@@ -277,6 +387,21 @@ function finalizeReviewSummaryUnlocked(
|
|
|
277
387
|
cleanupTemp(fileSystem, tempPath);
|
|
278
388
|
return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
|
|
279
389
|
}
|
|
390
|
+
if (repairIntent?.state === 'awaiting-repair') {
|
|
391
|
+
try {
|
|
392
|
+
writeArtifactRepairIntent(resolved.repoRoot, {
|
|
393
|
+
...repairIntent,
|
|
394
|
+
state: 'passed',
|
|
395
|
+
artifactSha256: sha256Content(transformed.content),
|
|
396
|
+
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
397
|
+
});
|
|
398
|
+
} catch (error) {
|
|
399
|
+
return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
|
|
400
|
+
artifactSha256: sha256Content(transformed.content),
|
|
401
|
+
semanticDigest: canonicalSemanticDigest(transformed.content)
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
280
405
|
return {
|
|
281
406
|
status: 'applied',
|
|
282
407
|
changed: true,
|
|
@@ -286,6 +411,10 @@ function finalizeReviewSummaryUnlocked(
|
|
|
286
411
|
stage,
|
|
287
412
|
artifact: request.artifact,
|
|
288
413
|
stageStatus,
|
|
414
|
+
artifactSha256: sha256Content(transformed.content),
|
|
415
|
+
semanticDigest: canonicalSemanticDigest(transformed.content),
|
|
416
|
+
repairable: false,
|
|
417
|
+
operation: null,
|
|
289
418
|
operations,
|
|
290
419
|
error: null
|
|
291
420
|
};
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { artifactName, maxRound } from "./review-artifacts.ts";
|
|
5
|
+
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.ts";
|
|
5
6
|
|
|
6
7
|
type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
|
|
7
8
|
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
@@ -46,15 +47,35 @@ export function findAuthoritativeReviewCodeArtifact(taskDir: string) {
|
|
|
46
47
|
const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
|
|
47
48
|
const round = maxRound(entries, "review-code");
|
|
48
49
|
if (round === 0) {
|
|
49
|
-
return { ok: false, round: 0, fileName: null, path: null };
|
|
50
|
+
return { ok: false, round: 0, fileName: null, path: null, error: null };
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
const fileName = artifactName("review-code", round);
|
|
54
|
+
let taskContent: string;
|
|
55
|
+
try {
|
|
56
|
+
taskContent = fs.readFileSync(path.join(taskDir, "task.md"), "utf8");
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false, round, fileName, path: null,
|
|
60
|
+
error: error instanceof Error ? error.message : String(error)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const invalidation = parseInvalidationDocument(taskContent);
|
|
64
|
+
if (!invalidation.ok) {
|
|
65
|
+
return { ok: false, round, fileName, path: null, error: invalidation.message };
|
|
66
|
+
}
|
|
67
|
+
if (invalidationBlocks(invalidation.document)) {
|
|
68
|
+
return { ok: false, round, fileName, path: null, error: "artifact invalidation is incomplete; reconcile task.md before using review-code" };
|
|
69
|
+
}
|
|
70
|
+
if (isArtifactInvalidated(invalidation.document, "review-code", fileName)) {
|
|
71
|
+
return { ok: false, round, fileName, path: null, error: `${fileName} is invalidated; re-run review-code` };
|
|
72
|
+
}
|
|
53
73
|
return {
|
|
54
74
|
ok: true,
|
|
55
75
|
round,
|
|
56
76
|
fileName,
|
|
57
|
-
path: path.join(taskDir, fileName)
|
|
77
|
+
path: path.join(taskDir, fileName),
|
|
78
|
+
error: null
|
|
58
79
|
};
|
|
59
80
|
}
|
|
60
81
|
|