@fitlab-ai/agent-infra 0.9.12 → 0.9.13
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/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- 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 +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- 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 +551 -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 +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -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 +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -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 +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- 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 +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -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-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- 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 +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- 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 +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -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 +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- 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 +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -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 +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- 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/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -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/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 +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- 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/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- 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 +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- 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 +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- 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/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- 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 +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
import type { InvalidationDocument } from './invalidation.ts';
|
|
5
|
+
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.ts';
|
|
6
|
+
import { receiptForOutput } from './artifact-receipts.ts';
|
|
7
|
+
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
8
|
+
import { parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
9
|
+
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.ts';
|
|
10
|
+
import { parseReworkIntentDocument } from './rework-intent.ts';
|
|
11
|
+
import type { ReworkIntent } from './rework-intent.ts';
|
|
12
|
+
import { sha256File } from './artifact-receipts.ts';
|
|
13
|
+
import { hasOpenLifecycleExecution } from './activity-log.ts';
|
|
14
|
+
|
|
15
|
+
type LifecycleAction =
|
|
16
|
+
| 'analysis' | 'review-analysis' | 'plan' | 'review-plan'
|
|
17
|
+
| 'code' | 'review-code' | 'manual-validation' | 'validation-run';
|
|
18
|
+
type TriggerInitiator = 'human' | 'model' | 'orchestrator';
|
|
19
|
+
type TriggerReason = 'user-request' | 'new-requirement' | 'upstream-fact-doubt' | 'review-finding' | 'retry' | 'validation-rerun';
|
|
20
|
+
type ExplicitTrigger = {
|
|
21
|
+
initiator: TriggerInitiator;
|
|
22
|
+
requestId: string;
|
|
23
|
+
requestedAction: LifecycleAction;
|
|
24
|
+
reasonCode: TriggerReason;
|
|
25
|
+
sourceFinding?: string;
|
|
26
|
+
sourceArtifact?: string;
|
|
27
|
+
sourceSha256?: string;
|
|
28
|
+
implementationInput?: string;
|
|
29
|
+
/** False marks the ordinary recommended workflow event; it is not an authorization fact. */
|
|
30
|
+
explicitRequest?: boolean;
|
|
31
|
+
};
|
|
32
|
+
type LifecycleFacts = {
|
|
33
|
+
taskState: string;
|
|
34
|
+
currentStep: string;
|
|
35
|
+
artifacts: Partial<Record<LifecycleAction, readonly string[]>>;
|
|
36
|
+
staleArtifacts?: Partial<Record<LifecycleAction, readonly string[]>>;
|
|
37
|
+
reviewedInputs?: Partial<Record<'review-analysis' | 'review-plan' | 'review-code', string>>;
|
|
38
|
+
artifactHashes: Readonly<Record<string, string>>;
|
|
39
|
+
reviews: Partial<Record<'review-analysis' | 'review-plan' | 'review-code', 'approved' | 'changes-requested' | 'rejected'>>;
|
|
40
|
+
invalidation: InvalidationDocument;
|
|
41
|
+
reworkIntents?: readonly ReworkIntent[];
|
|
42
|
+
unresolvedLedger: Record<'analysis' | 'plan' | 'code', number>;
|
|
43
|
+
executionBusy: boolean;
|
|
44
|
+
recommendedAction?: LifecycleAction | null;
|
|
45
|
+
};
|
|
46
|
+
type CapabilityResult = {
|
|
47
|
+
allowed: boolean;
|
|
48
|
+
reasonCode: string;
|
|
49
|
+
evidence: readonly string[];
|
|
50
|
+
};
|
|
51
|
+
type LifecycleRecommendation = {
|
|
52
|
+
action: LifecycleAction | null;
|
|
53
|
+
reasonCode: string;
|
|
54
|
+
evidence: readonly string[];
|
|
55
|
+
};
|
|
56
|
+
type LifecycleFactsResult =
|
|
57
|
+
| { ok: true; facts: LifecycleFacts }
|
|
58
|
+
| { ok: false; code: 'TASK_CAPABILITY_FACTS_INVALID'; message: string };
|
|
59
|
+
|
|
60
|
+
const REASONS = new Set<TriggerReason>([
|
|
61
|
+
'user-request', 'new-requirement', 'upstream-fact-doubt', 'review-finding', 'retry', 'validation-rerun'
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
function deny(reasonCode: string, ...evidence: string[]): CapabilityResult {
|
|
65
|
+
return { allowed: false, reasonCode, evidence };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function allow(...evidence: string[]): CapabilityResult {
|
|
69
|
+
return { allowed: true, reasonCode: 'CAPABILITY_ALLOWED', evidence };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hasArtifact(facts: LifecycleFacts, action: LifecycleAction): boolean {
|
|
73
|
+
return (facts.artifacts[action]?.length ?? 0) > 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function canStart(action: LifecycleAction, facts: LifecycleFacts, trigger: ExplicitTrigger): CapabilityResult {
|
|
77
|
+
if (!trigger || trigger.requestedAction !== action) return deny('TRIGGER_ACTION_MISMATCH', `requested=${trigger?.requestedAction ?? 'missing'}`);
|
|
78
|
+
if (!trigger.requestId || !trigger.requestId.trim()) return deny('TRIGGER_REQUEST_ID_REQUIRED');
|
|
79
|
+
if (!REASONS.has(trigger.reasonCode)) return deny('TRIGGER_REASON_INVALID');
|
|
80
|
+
if (trigger.sourceArtifact && !trigger.sourceSha256) return deny('SOURCE_ARTIFACT_HASH_REQUIRED');
|
|
81
|
+
if (trigger.sourceSha256 && !trigger.sourceArtifact) return deny('SOURCE_ARTIFACT_REQUIRED');
|
|
82
|
+
if (trigger.sourceArtifact && trigger.sourceSha256) {
|
|
83
|
+
const actual = facts.artifactHashes[trigger.sourceArtifact];
|
|
84
|
+
if (!actual) return deny('SOURCE_ARTIFACT_MISSING', trigger.sourceArtifact);
|
|
85
|
+
if (actual !== trigger.sourceSha256) return deny('SOURCE_ARTIFACT_HASH_MISMATCH', trigger.sourceArtifact);
|
|
86
|
+
}
|
|
87
|
+
if (facts.taskState !== 'active') return deny('TASK_NOT_ACTIVE', `state=${facts.taskState}`);
|
|
88
|
+
if (invalidationBlocks(facts.invalidation)) return deny('INVALIDATION_INCOMPLETE');
|
|
89
|
+
if (facts.executionBusy) return deny('EXECUTION_BUSY');
|
|
90
|
+
|
|
91
|
+
const implicit = trigger.explicitRequest === false;
|
|
92
|
+
if (action === 'analysis') {
|
|
93
|
+
if (!implicit || hasArtifact(facts, 'analysis') || facts.recommendedAction === 'analysis') return allow('analysis-capability');
|
|
94
|
+
return deny('ANALYSIS_REQUEST_REQUIRES_EXPLICIT_TRIGGER');
|
|
95
|
+
}
|
|
96
|
+
if (action === 'review-analysis') {
|
|
97
|
+
return hasArtifact(facts, 'analysis') ? allow('analysis-artifact') : deny('ANALYSIS_ARTIFACT_REQUIRED');
|
|
98
|
+
}
|
|
99
|
+
if (action === 'plan') {
|
|
100
|
+
if (!hasArtifact(facts, 'review-analysis')) return deny('ANALYSIS_REVIEW_REQUIRED');
|
|
101
|
+
if (!reviewMatchesLatest(facts, 'analysis', 'review-analysis')) return deny('ANALYSIS_REVIEW_NOT_LATEST');
|
|
102
|
+
if (facts.reviews['review-analysis'] !== 'approved') return deny('ANALYSIS_REVIEW_NOT_APPROVED');
|
|
103
|
+
if (facts.unresolvedLedger.analysis > 0) return deny('ANALYSIS_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.analysis}`);
|
|
104
|
+
return allow('analysis-review-approved');
|
|
105
|
+
}
|
|
106
|
+
if (action === 'review-plan') {
|
|
107
|
+
return hasArtifact(facts, 'plan') ? allow('plan-artifact') : deny('PLAN_ARTIFACT_REQUIRED');
|
|
108
|
+
}
|
|
109
|
+
if (action === 'code') {
|
|
110
|
+
if (trigger.implementationInput) {
|
|
111
|
+
if (!hasArtifact(facts, 'review-code')) return deny('CODE_REVIEW_REQUIRED');
|
|
112
|
+
if (!reviewMatchesLatest(facts, 'code', 'review-code')) return deny('CODE_REVIEW_NOT_LATEST');
|
|
113
|
+
if (facts.reviews['review-code'] !== 'approved') return deny('CODE_REVIEW_NOT_APPROVED');
|
|
114
|
+
return allow('implementation-input');
|
|
115
|
+
}
|
|
116
|
+
if (!hasArtifact(facts, 'review-plan')) return deny('PLAN_REVIEW_REQUIRED');
|
|
117
|
+
if (!reviewMatchesLatest(facts, 'plan', 'review-plan')) return deny('PLAN_REVIEW_NOT_LATEST');
|
|
118
|
+
if (facts.reviews['review-plan'] !== 'approved') return deny('PLAN_REVIEW_NOT_APPROVED');
|
|
119
|
+
if (facts.unresolvedLedger.plan > 0) return deny('PLAN_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.plan}`);
|
|
120
|
+
return allow('plan-review-approved');
|
|
121
|
+
}
|
|
122
|
+
if (action === 'review-code') {
|
|
123
|
+
return hasArtifact(facts, 'code') ? allow('code-artifact') : deny('CODE_ARTIFACT_REQUIRED');
|
|
124
|
+
}
|
|
125
|
+
if (action === 'manual-validation' || action === 'validation-run') {
|
|
126
|
+
if (!hasArtifact(facts, 'review-code')) return deny('CODE_REVIEW_REQUIRED');
|
|
127
|
+
if (!reviewMatchesLatest(facts, 'code', 'review-code')) return deny('CODE_REVIEW_NOT_LATEST');
|
|
128
|
+
if (facts.reviews['review-code'] !== 'approved') return deny('CODE_REVIEW_NOT_APPROVED');
|
|
129
|
+
if (facts.unresolvedLedger.code > 0) return deny('CODE_LEDGER_BLOCKED', `unresolved=${facts.unresolvedLedger.code}`);
|
|
130
|
+
return allow('code-review-approved');
|
|
131
|
+
}
|
|
132
|
+
return deny('CAPABILITY_ACTION_UNKNOWN');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function artifactRound(name: string): number {
|
|
136
|
+
const match = /-r(\d+)\.md$/.exec(name);
|
|
137
|
+
return match ? Number(match[1]) : 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function latestArtifact(names: readonly string[]): string | null {
|
|
141
|
+
return [...names].sort((left, right) => artifactRound(right) - artifactRound(left) || left.localeCompare(right))[0] ?? null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function reviewedInputName(content: string, expectedFamily: 'analysis' | 'plan' | 'code'): string | null {
|
|
145
|
+
const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
|
|
146
|
+
if (header < 0) return null;
|
|
147
|
+
const referenceBlock = content.split(/\r?\n/).slice(header, header + 12).join('\n');
|
|
148
|
+
const expected = new RegExp('`' + expectedFamily + '(?:-r[2-9]|-r[1-9]\\d+)?\\.md`');
|
|
149
|
+
return expected.exec(referenceBlock)?.[0].slice(1, -1) ?? null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function reviewMatchesLatest(facts: LifecycleFacts, source: 'analysis' | 'plan' | 'code', review: 'review-analysis' | 'review-plan' | 'review-code'): boolean {
|
|
153
|
+
const latestSource = latestArtifact(facts.artifacts[source] ?? []);
|
|
154
|
+
return Boolean(latestSource && facts.reviewedInputs?.[review] === latestSource);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function recommendNext(facts: LifecycleFacts): LifecycleRecommendation {
|
|
158
|
+
if (invalidationBlocks(facts.invalidation)) return { action: null, reasonCode: 'INVALIDATION_INCOMPLETE', evidence: ['reconcile task invalidation before routing'] };
|
|
159
|
+
const pendingIntent = (facts.reworkIntents ?? []).find((intent) => intent.status === 'pending');
|
|
160
|
+
if (pendingIntent) return { action: pendingIntent.target, reasonCode: 'REWORK_INTENT_PENDING', evidence: [pendingIntent.intentId, pendingIntent.findingId] };
|
|
161
|
+
if (!hasArtifact(facts, 'analysis')) return { action: 'analysis', reasonCode: 'ANALYSIS_ARTIFACT_MISSING', evidence: ['analysis artifact is absent'] };
|
|
162
|
+
if (!hasArtifact(facts, 'review-analysis') || !reviewMatchesLatest(facts, 'analysis', 'review-analysis')) {
|
|
163
|
+
return { action: 'review-analysis', reasonCode: 'ANALYSIS_REVIEW_MISSING', evidence: ['analysis review does not bind the latest analysis artifact'] };
|
|
164
|
+
}
|
|
165
|
+
if (facts.reviews['review-analysis'] !== 'approved' || facts.unresolvedLedger.analysis > 0) {
|
|
166
|
+
return { action: 'analysis', reasonCode: 'ANALYSIS_REWORK_REQUIRED', evidence: ['analysis review or ledger is not clear'] };
|
|
167
|
+
}
|
|
168
|
+
if (!hasArtifact(facts, 'plan')) return { action: 'plan', reasonCode: 'PLAN_ARTIFACT_MISSING', evidence: ['plan artifact is absent'] };
|
|
169
|
+
if (!hasArtifact(facts, 'review-plan') || !reviewMatchesLatest(facts, 'plan', 'review-plan')) {
|
|
170
|
+
return { action: 'review-plan', reasonCode: 'PLAN_REVIEW_MISSING', evidence: ['plan review does not bind the latest plan artifact'] };
|
|
171
|
+
}
|
|
172
|
+
if (facts.reviews['review-plan'] !== 'approved' || facts.unresolvedLedger.plan > 0) {
|
|
173
|
+
return { action: 'plan', reasonCode: 'PLAN_REWORK_REQUIRED', evidence: ['plan review or ledger is not clear'] };
|
|
174
|
+
}
|
|
175
|
+
if (!hasArtifact(facts, 'code')) return { action: 'code', reasonCode: 'CODE_ARTIFACT_MISSING', evidence: ['code artifact is absent'] };
|
|
176
|
+
if (!hasArtifact(facts, 'review-code') || !reviewMatchesLatest(facts, 'code', 'review-code')) {
|
|
177
|
+
return { action: 'review-code', reasonCode: 'CODE_REVIEW_MISSING', evidence: ['code review does not bind the latest code artifact'] };
|
|
178
|
+
}
|
|
179
|
+
if (facts.reviews['review-code'] !== 'approved') {
|
|
180
|
+
return { action: 'code', reasonCode: 'CODE_REWORK_REQUIRED', evidence: ['code review or ledger is not clear'] };
|
|
181
|
+
}
|
|
182
|
+
return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function buildLifecycleFacts(taskDir: string, content: string, taskState = 'active', executionBusy = false): LifecycleFactsResult {
|
|
186
|
+
try {
|
|
187
|
+
const metadata = parseTypedTaskFrontmatter(content);
|
|
188
|
+
const invalidation = parseInvalidationDocument(content);
|
|
189
|
+
if (!invalidation.ok) return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: invalidation.message };
|
|
190
|
+
const rework = parseReworkIntentDocument(content);
|
|
191
|
+
if (!rework.ok) return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: rework.message };
|
|
192
|
+
const ledger = parseLedgerDocument(content);
|
|
193
|
+
if (ledger.present) {
|
|
194
|
+
const invalid = validateLedgerRows(ledger.rows);
|
|
195
|
+
if (invalid) return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: invalid.message };
|
|
196
|
+
}
|
|
197
|
+
const files = fs.readdirSync(taskDir).filter((name) => {
|
|
198
|
+
if (!name.endsWith('.md')) return false;
|
|
199
|
+
const stat = fs.lstatSync(path.join(taskDir, name));
|
|
200
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
201
|
+
});
|
|
202
|
+
const artifactFamilies = {
|
|
203
|
+
analysis: /^analysis(?:-r\d+)?\.md$/,
|
|
204
|
+
'review-analysis': /^review-analysis(?:-r\d+)?\.md$/,
|
|
205
|
+
plan: /^plan(?:-r\d+)?\.md$/,
|
|
206
|
+
'review-plan': /^review-plan(?:-r\d+)?\.md$/,
|
|
207
|
+
code: /^code(?:-r\d+)?\.md$/,
|
|
208
|
+
'review-code': /^review-code(?:-r\d+)?\.md$/
|
|
209
|
+
} as const;
|
|
210
|
+
const familyFor = (name: string): keyof typeof artifactFamilies | null =>
|
|
211
|
+
(Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>)
|
|
212
|
+
.find((family) => artifactFamilies[family].test(name)) ?? null;
|
|
213
|
+
const activeFiles = files.filter((name) => {
|
|
214
|
+
const family = familyFor(name);
|
|
215
|
+
return !family || !isArtifactInvalidated(invalidation.document, family, name);
|
|
216
|
+
});
|
|
217
|
+
const artifactHashes: Record<string, string> = {};
|
|
218
|
+
for (const name of activeFiles) artifactHashes[name] = sha256File(path.join(taskDir, name));
|
|
219
|
+
const artifacts: Partial<Record<LifecycleAction, readonly string[]>> = Object.fromEntries(
|
|
220
|
+
(Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>).map((family) => [
|
|
221
|
+
family, activeFiles.filter((name) => artifactFamilies[family].test(name))
|
|
222
|
+
])
|
|
223
|
+
) as Partial<Record<LifecycleAction, readonly string[]>>;
|
|
224
|
+
const staleArtifacts: Partial<Record<LifecycleAction, readonly string[]>> = Object.fromEntries(
|
|
225
|
+
(Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>).map((family) => [
|
|
226
|
+
family, files.filter((name) => artifactFamilies[family].test(name) && isArtifactInvalidated(invalidation.document, family, name))
|
|
227
|
+
])
|
|
228
|
+
) as Partial<Record<LifecycleAction, readonly string[]>>;
|
|
229
|
+
const reviews: LifecycleFacts['reviews'] = {};
|
|
230
|
+
const reviewedInputs: NonNullable<LifecycleFacts['reviewedInputs']> = {};
|
|
231
|
+
for (const family of ['review-analysis', 'review-plan', 'review-code'] as const) {
|
|
232
|
+
const latest = latestArtifact(artifacts[family] ?? []);
|
|
233
|
+
if (!latest) continue;
|
|
234
|
+
const reviewContent = fs.readFileSync(path.join(taskDir, latest), 'utf8');
|
|
235
|
+
const expectedFamily = family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
236
|
+
const input = reviewedInputName(reviewContent, expectedFamily);
|
|
237
|
+
const receipt = input ? receiptForOutput(content, latest) : null;
|
|
238
|
+
if (input && receipt?.input === input && artifactHashes[input] === receipt.inputSha256) reviewedInputs[family] = input;
|
|
239
|
+
const parsed = parseReviewSummary(reviewContent);
|
|
240
|
+
if (!parsed.ok) continue;
|
|
241
|
+
const verdict = resolveCanonicalVerdict(parsed.summary);
|
|
242
|
+
if (verdict.ok) reviews[family] = verdict.verdict === 'Approved' ? 'approved' : verdict.verdict === 'Changes Requested' ? 'changes-requested' : 'rejected';
|
|
243
|
+
}
|
|
244
|
+
const unresolvedLedger = { analysis: 0, plan: 0, code: 0 };
|
|
245
|
+
if (ledger.present) {
|
|
246
|
+
for (const stage of ['analysis', 'plan', 'code'] as const) unresolvedLedger[stage] = summarizeLedgerStage(ledger.rows, stage).unresolved.length;
|
|
247
|
+
}
|
|
248
|
+
const facts: LifecycleFacts = {
|
|
249
|
+
taskState, currentStep: String(metadata.current_step ?? ''), artifacts, reviews,
|
|
250
|
+
staleArtifacts, reviewedInputs, artifactHashes,
|
|
251
|
+
invalidation: invalidation.document, reworkIntents: rework.intents,
|
|
252
|
+
unresolvedLedger, executionBusy: executionBusy || hasOpenLifecycleExecution(content), recommendedAction: null
|
|
253
|
+
};
|
|
254
|
+
facts.recommendedAction = recommendNext(facts).action;
|
|
255
|
+
return { ok: true, facts };
|
|
256
|
+
} catch (error) {
|
|
257
|
+
return { ok: false, code: 'TASK_CAPABILITY_FACTS_INVALID', message: error instanceof Error ? error.message : String(error) };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export { buildLifecycleFacts, canStart, recommendNext };
|
|
262
|
+
export type { CapabilityResult, ExplicitTrigger, LifecycleAction, LifecycleFacts, LifecycleFactsResult, LifecycleRecommendation, TriggerInitiator, TriggerReason };
|
|
@@ -12,6 +12,9 @@ import { parseActivityLog, pairEntries, startedBackedRows } from '../activity-lo
|
|
|
12
12
|
import { readRun, type OrchestrationRun } from '../orchestration.ts';
|
|
13
13
|
import type { DelegationReceipt } from '../delegation-receipts.ts';
|
|
14
14
|
import { statusCard, type DisplayMessage } from '../../server/display.ts';
|
|
15
|
+
import { invalidationBlocks, parseInvalidationDocument } from '../invalidation.ts';
|
|
16
|
+
import { parseReworkIntentDocument } from '../rework-intent.ts';
|
|
17
|
+
import { buildLifecycleFacts, recommendNext } from '../capabilities.ts';
|
|
15
18
|
|
|
16
19
|
const USAGE = `Usage: ai task status [--task <ref> | -t <ref>]
|
|
17
20
|
|
|
@@ -381,6 +384,8 @@ type StatusModel = {
|
|
|
381
384
|
workflow: WorkflowInfo;
|
|
382
385
|
runtime: RuntimeInfo;
|
|
383
386
|
orchestration: OrchestrationInfo;
|
|
387
|
+
invalidation?: { status: string; processed: number; total: number; pendingIntents: number };
|
|
388
|
+
recommendation?: string;
|
|
384
389
|
git: GitInfo;
|
|
385
390
|
};
|
|
386
391
|
|
|
@@ -429,6 +434,18 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
429
434
|
lines.push(...renderPairs(model.artifacts.groups.map((group) => [group.stage, group.files.join(', ')])));
|
|
430
435
|
}
|
|
431
436
|
|
|
437
|
+
lines.push(
|
|
438
|
+
'',
|
|
439
|
+
'Invalidation',
|
|
440
|
+
...renderPairs([
|
|
441
|
+
['status', model.invalidation?.status ?? 'unknown'],
|
|
442
|
+
['processed', `${model.invalidation?.processed ?? 0}/${model.invalidation?.total ?? 0}`],
|
|
443
|
+
['pending_intents', String(model.invalidation?.pendingIntents ?? 0)]
|
|
444
|
+
])
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
lines.push('', 'Recommendation', ...renderPairs([['action', model.recommendation ?? DASH]]));
|
|
448
|
+
|
|
432
449
|
lines.push(
|
|
433
450
|
'',
|
|
434
451
|
'Orchestration',
|
|
@@ -494,6 +511,8 @@ function renderStatus(model: StatusModel): string[] {
|
|
|
494
511
|
}
|
|
495
512
|
|
|
496
513
|
function modelTone(model: StatusModel): 'info' | 'success' | 'warning' | 'danger' | 'running' {
|
|
514
|
+
if (model.invalidation?.status === 'invalid') return 'danger';
|
|
515
|
+
if (model.invalidation?.status === 'pending' || model.invalidation?.status === 'in-progress') return 'warning';
|
|
497
516
|
if (model.orchestration.status === 'paused') return 'warning';
|
|
498
517
|
if (model.orchestration.status === 'running') return 'running';
|
|
499
518
|
if (model.orchestration.status === 'completed') return 'success';
|
|
@@ -511,6 +530,23 @@ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
|
|
|
511
530
|
const run = input.run ?? makeRunner(input.repoRoot);
|
|
512
531
|
const artifacts = enumerateArtifacts(input.taskDir);
|
|
513
532
|
const workflow = collectWorkflow(content, input.now);
|
|
533
|
+
const invalidation = parseInvalidationDocument(content);
|
|
534
|
+
const rework = parseReworkIntentDocument(content);
|
|
535
|
+
const pendingIntents = rework.ok ? rework.intents.filter((intent) => intent.status === 'pending').length : 0;
|
|
536
|
+
const invalidationInfo = !invalidation.ok
|
|
537
|
+
? { status: 'invalid', processed: 0, total: 0, pendingIntents }
|
|
538
|
+
: {
|
|
539
|
+
status: invalidation.present
|
|
540
|
+
? invalidationBlocks(invalidation.document)
|
|
541
|
+
? invalidation.document.operations.some((operation) => operation.status === 'in-progress') ? 'in-progress' : 'pending'
|
|
542
|
+
: 'completed'
|
|
543
|
+
: 'none',
|
|
544
|
+
processed: invalidation.document.operations.reduce((sum, operation) => sum + operation.processed, 0),
|
|
545
|
+
total: invalidation.document.operations.reduce((sum, operation) => sum + operation.total, 0),
|
|
546
|
+
pendingIntents
|
|
547
|
+
};
|
|
548
|
+
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
|
|
549
|
+
const recommendation = facts.ok ? (recommendNext(facts.facts).action ?? DASH) : DASH;
|
|
514
550
|
|
|
515
551
|
return {
|
|
516
552
|
taskId: input.taskId,
|
|
@@ -522,6 +558,8 @@ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
|
|
|
522
558
|
workflow,
|
|
523
559
|
runtime: collectRuntime(input.taskDir, workflow, run),
|
|
524
560
|
orchestration: collectOrchestration(input.taskDir),
|
|
561
|
+
invalidation: invalidationInfo,
|
|
562
|
+
recommendation,
|
|
525
563
|
git: collectGit(fm.branch ?? '', run)
|
|
526
564
|
};
|
|
527
565
|
}
|
|
@@ -210,7 +210,18 @@ function syncTaskCommit(
|
|
|
210
210
|
);
|
|
211
211
|
const metadata = captureTaskWriteMetadata();
|
|
212
212
|
const commitNote = gitText(task.repoRoot, ['show', '-s', '--format=%h %s', committedHead]);
|
|
213
|
-
|
|
213
|
+
let matchingCommitIndex = -1;
|
|
214
|
+
for (let index = activity.entries.length - 1; index >= 0; index -= 1) {
|
|
215
|
+
const entry = activity.entries[index]!;
|
|
216
|
+
if (entry.step === 'Commit' && entry.note === commitNote) {
|
|
217
|
+
matchingCommitIndex = index;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const reviewedAfterCommit = matchingCommitIndex >= 0 && activity.entries
|
|
222
|
+
.slice(matchingCommitIndex + 1)
|
|
223
|
+
.some((entry) => /^Review Code \(Round \d+\)$/.test(entry.step));
|
|
224
|
+
const alreadyLogged = matchingCommitIndex >= 0 && !reviewedAfterCommit;
|
|
214
225
|
const mutations: TaskMutation[] = [];
|
|
215
226
|
if (!alreadyLogged) mutations.push({
|
|
216
227
|
kind: 'section',
|
|
@@ -206,15 +206,15 @@ function lifecycleTaskId(request: TaskLifecycleControlRequest, repoRoot: string)
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
function applyLifecycleWithAuthority(
|
|
209
|
+
async function applyLifecycleWithAuthority(
|
|
210
210
|
context: TaskControlExecutionContext,
|
|
211
211
|
request: TaskLifecycleControlRequest
|
|
212
|
-
): TaskLifecycleResult & { humanOverride?: unknown } {
|
|
212
|
+
): Promise<TaskLifecycleResult & { humanOverride?: unknown }> {
|
|
213
213
|
const conflict = overrideDryRunConflict(request as unknown as Record<string, unknown>);
|
|
214
214
|
if (conflict) {
|
|
215
215
|
return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
|
|
216
216
|
}
|
|
217
|
-
const execute = (): TaskLifecycleResult & { humanOverride?: unknown } => {
|
|
217
|
+
const execute = async (): Promise<TaskLifecycleResult & { humanOverride?: unknown }> => {
|
|
218
218
|
const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
219
219
|
if (lifecycleResult.status !== 'failed' || !request.overrideTicket) return lifecycleResult;
|
|
220
220
|
if (!request.overrideTarget || !request.overrideScope) {
|
|
@@ -223,7 +223,7 @@ function applyLifecycleWithAuthority(
|
|
|
223
223
|
error: { code: 'OVERRIDE_PAYLOAD_INVALID', message: 'override ticket requires --override-target and --override-scope' }
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
|
-
const override = consumeHumanOverride({
|
|
226
|
+
const override = await consumeHumanOverride({
|
|
227
227
|
taskRef: request.taskRef,
|
|
228
228
|
ticketId: request.overrideTicket,
|
|
229
229
|
failureId: failureId('lifecycle.apply', lifecycleResult.error?.code ?? 'LIFECYCLE_FAILED'),
|
|
@@ -238,9 +238,9 @@ function applyLifecycleWithAuthority(
|
|
|
238
238
|
return { ...lifecycleResult, ...override, humanOverride: override, error: null };
|
|
239
239
|
};
|
|
240
240
|
const taskId = lifecycleTaskId(request, context.repoRoot);
|
|
241
|
-
if (!taskId) return execute();
|
|
241
|
+
if (!taskId) return await execute();
|
|
242
242
|
try {
|
|
243
|
-
return withTaskExecutionLock(
|
|
243
|
+
return await withTaskExecutionLock(
|
|
244
244
|
context.repoRoot,
|
|
245
245
|
taskId,
|
|
246
246
|
`task-lifecycle.${request.intent}`,
|
|
@@ -322,11 +322,11 @@ function orchestrationFailure(error: unknown): OrchestrationResult {
|
|
|
322
322
|
export function dispatchTaskControlOperation(
|
|
323
323
|
context: TaskControlExecutionContext,
|
|
324
324
|
operation: Extract<TaskControlOperation, { family: 'task-lifecycle' }>
|
|
325
|
-
): TaskLifecycleResult
|
|
325
|
+
): Promise<TaskLifecycleResult & { humanOverride?: unknown }>;
|
|
326
326
|
export function dispatchTaskControlOperation(
|
|
327
327
|
context: TaskControlExecutionContext,
|
|
328
328
|
operation: Extract<TaskControlOperation, { family: 'task-finalization' }>
|
|
329
|
-
): TaskFinalizationResult
|
|
329
|
+
): Promise<TaskFinalizationResult>;
|
|
330
330
|
export function dispatchTaskControlOperation(
|
|
331
331
|
context: TaskControlExecutionContext,
|
|
332
332
|
operation: Extract<TaskControlOperation, { family: 'task-orchestration' }>
|
|
@@ -345,6 +345,9 @@ export function dispatchTaskControlOperation(
|
|
|
345
345
|
operationTaskId(context, operation.request.taskRef);
|
|
346
346
|
return applyTaskFinalization(operation.request, {
|
|
347
347
|
repoRoot: context.repoRoot,
|
|
348
|
+
...(context.source === 'sandbox-executor' ? {
|
|
349
|
+
controlBinding: { generation: context.generation, requestId: context.requestId }
|
|
350
|
+
} : {}),
|
|
348
351
|
preflight: (request, options) => verifyTaskEvent(
|
|
349
352
|
{ ...request, event: 'complete-task.hard-preflight' }, options
|
|
350
353
|
)
|
|
@@ -42,8 +42,8 @@ const DEFAULT_DEPENDENCIES: TaskCreateDependencies = {
|
|
|
42
42
|
addWarning: applyWorkflowWarningIntent
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
function verifyCreatedTask(repoRoot: string, taskId: string): { operation: TaskCreateOperation; error: TaskCreateResult['error'] } {
|
|
46
|
-
const verification = verifyTaskEvent(
|
|
45
|
+
async function verifyCreatedTask(repoRoot: string, taskId: string): Promise<{ operation: TaskCreateOperation; error: TaskCreateResult['error'] }> {
|
|
46
|
+
const verification = await verifyTaskEvent(
|
|
47
47
|
{ taskRef: taskId, event: 'create-task.completed' },
|
|
48
48
|
{ repoRoot }
|
|
49
49
|
);
|
|
@@ -78,7 +78,7 @@ function issueFromTask(repoRoot: string, taskId: string): { number: number; url:
|
|
|
78
78
|
return match ? { number: Number(match[1]), url: '' } : null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function createTask(value: unknown, options: CreateTaskOptions): TaskCreateResult {
|
|
81
|
+
async function createTask(value: unknown, options: CreateTaskOptions): Promise<TaskCreateResult> {
|
|
82
82
|
const dependencies = { ...DEFAULT_DEPENDENCIES, ...options.dependencies };
|
|
83
83
|
let candidate: TaskCreateCandidateV1;
|
|
84
84
|
let local;
|
|
@@ -101,13 +101,13 @@ function createTask(value: unknown, options: CreateTaskOptions): TaskCreateResul
|
|
|
101
101
|
name: 'task:local', status: local.status, reasonCode: null
|
|
102
102
|
}];
|
|
103
103
|
let issue = issueFromTask(options.repoRoot, local.task.id);
|
|
104
|
-
const created = dependencies.createIssue(local.task.id, { cwd: options.repoRoot, agent: candidate.agent });
|
|
104
|
+
const created = await dependencies.createIssue(local.task.id, { cwd: options.repoRoot, agent: candidate.agent });
|
|
105
105
|
operations.push(...platformOperations('platform-create', created.operations));
|
|
106
106
|
if (created.issue) issue = { number: created.issue.number, url: created.issue.url };
|
|
107
107
|
|
|
108
108
|
let platformFailure: PlatformResult | null = created.status === 'failed' || created.status === 'blocked' ? created : null;
|
|
109
109
|
if (!platformFailure && (created.task.issueNumber || issue)) {
|
|
110
|
-
const synced = dependencies.syncIssue(local.task.id, {
|
|
110
|
+
const synced = await dependencies.syncIssue(local.task.id, {
|
|
111
111
|
cwd: options.repoRoot,
|
|
112
112
|
agent: candidate.agent,
|
|
113
113
|
status: 'waiting-for-triage',
|
|
@@ -120,7 +120,7 @@ function createTask(value: unknown, options: CreateTaskOptions): TaskCreateResul
|
|
|
120
120
|
if (synced.issue) issue = { number: synced.issue.number, url: synced.issue.url };
|
|
121
121
|
if (synced.status === 'failed' || synced.status === 'blocked') platformFailure = synced;
|
|
122
122
|
if (!platformFailure) {
|
|
123
|
-
const commented = dependencies.syncComment(local.task.id, { cwd: options.repoRoot, kind: 'task', agent: candidate.agent });
|
|
123
|
+
const commented = await dependencies.syncComment(local.task.id, { cwd: options.repoRoot, kind: 'task', agent: candidate.agent });
|
|
124
124
|
operations.push(...platformOperations('platform-comment', commented.operations));
|
|
125
125
|
if (commented.status === 'failed' || commented.status === 'blocked') platformFailure = commented;
|
|
126
126
|
}
|
|
@@ -133,7 +133,7 @@ function createTask(value: unknown, options: CreateTaskOptions): TaskCreateResul
|
|
|
133
133
|
code: 'ISSUE_CREATE_FAILED', target: 'issue', message: `${error.code}: ${error.message}`,
|
|
134
134
|
action: 'Fix platform authentication or connectivity, then retry Issue creation for this task.'
|
|
135
135
|
}, { repoRoot: options.repoRoot });
|
|
136
|
-
const verified = verifyCreatedTask(options.repoRoot, local.task.id);
|
|
136
|
+
const verified = await verifyCreatedTask(options.repoRoot, local.task.id);
|
|
137
137
|
operations.push(verified.operation);
|
|
138
138
|
if (verified.error) return {
|
|
139
139
|
status: verified.error.retryable ? 'blocked' : 'failed', changed: local.changed,
|
|
@@ -150,7 +150,7 @@ function createTask(value: unknown, options: CreateTaskOptions): TaskCreateResul
|
|
|
150
150
|
};
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
const verified = verifyCreatedTask(options.repoRoot, local.task.id);
|
|
153
|
+
const verified = await verifyCreatedTask(options.repoRoot, local.task.id);
|
|
154
154
|
operations.push(verified.operation);
|
|
155
155
|
if (verified.error) return {
|
|
156
156
|
status: verified.error.retryable ? 'blocked' : 'failed', changed: local.changed,
|
|
@@ -2,6 +2,8 @@ import { isValidAgentInfraVersion } from '../version.ts';
|
|
|
2
2
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
3
3
|
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, validateLedgerRows } from './ledger.ts';
|
|
4
4
|
import type { LedgerDocument } from './ledger.ts';
|
|
5
|
+
import { parseInvalidationDocument } from './invalidation.ts';
|
|
6
|
+
import type { InvalidationDocument } from './invalidation.ts';
|
|
5
7
|
|
|
6
8
|
type CurrentTaskContractErrorCode = 'TASK_DOCUMENT_INVALID' | 'TASK_CURRENT_CONTRACT_INVALID';
|
|
7
9
|
type CurrentTaskContractResult =
|
|
@@ -9,6 +11,7 @@ type CurrentTaskContractResult =
|
|
|
9
11
|
ok: true;
|
|
10
12
|
metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
11
13
|
ledger: LedgerDocument;
|
|
14
|
+
invalidation: InvalidationDocument;
|
|
12
15
|
}
|
|
13
16
|
| {
|
|
14
17
|
ok: false;
|
|
@@ -42,7 +45,9 @@ function validateCurrentTaskContract(content: string): CurrentTaskContractResult
|
|
|
42
45
|
}
|
|
43
46
|
const invalid = validateLedgerRows(ledger.rows);
|
|
44
47
|
if (invalid) return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalid.code}: ${invalid.message}` };
|
|
45
|
-
|
|
48
|
+
const invalidation = parseInvalidationDocument(content);
|
|
49
|
+
if (!invalidation.ok) return { ok: false, code: 'TASK_CURRENT_CONTRACT_INVALID', message: `${invalidation.code}: ${invalidation.message}` };
|
|
50
|
+
return { ok: true, metadata, ledger, invalidation: invalidation.document };
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
export { validateCurrentTaskContract };
|
|
@@ -270,11 +270,15 @@ export {
|
|
|
270
270
|
normalizeDecisionDetailForDisplay,
|
|
271
271
|
parseDecisionDetailBlocks,
|
|
272
272
|
parseDecisionEvidence,
|
|
273
|
-
resolveDecisionDetail
|
|
273
|
+
resolveDecisionDetail,
|
|
274
|
+
scanVisibleMarkdown
|
|
274
275
|
};
|
|
275
276
|
export type {
|
|
276
277
|
DecisionDetailBlock,
|
|
277
278
|
DecisionDetailDuplicate,
|
|
278
279
|
DecisionDetailInspection,
|
|
279
|
-
DecisionDetailResolution
|
|
280
|
+
DecisionDetailResolution,
|
|
281
|
+
SourceLine,
|
|
282
|
+
VisibleHeading,
|
|
283
|
+
VisibleMarkdown
|
|
280
284
|
};
|