@fitlab-ai/agent-infra 0.9.10 → 0.9.11
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 +13 -3
- package/README.zh-CN.md +13 -3
- package/bin/cli.ts +17 -4
- package/dist/bin/cli.js +19 -4
- package/dist/bin/internal-cli.js +50 -4
- package/dist/lib/agent-client.js +0 -1
- package/dist/lib/agent-clients/adapters/opencode-sandbox.js +0 -4
- package/dist/lib/agent-clients/adapters/traecli.js +7 -8
- package/dist/lib/agent-clients/config.js +14 -99
- package/dist/lib/agent-clients/reconcile.js +1 -11
- package/dist/lib/agent-clients/schema.js +5 -4
- package/dist/lib/agent-clients/tokens.js +5 -15
- package/dist/lib/decide.js +14 -30
- package/dist/lib/defaults.json +4 -0
- package/dist/lib/init.js +2 -2
- package/dist/lib/internal/release-workflow.js +0 -1
- package/dist/lib/internal/task-finalization.js +19 -9
- package/dist/lib/internal/task-ledger.js +8 -2
- package/dist/lib/internal/task-lifecycle.js +17 -93
- package/dist/lib/internal/task-orchestration.js +30 -216
- package/dist/lib/internal/task-validate.js +2 -1
- package/dist/lib/merge.js +205 -103
- package/dist/lib/process-data/normalize.js +2 -9
- package/dist/lib/run/host.js +46 -16
- package/dist/lib/sandbox/agent-client-reconciler.js +4 -12
- package/dist/lib/sandbox/commands/create.js +3 -3
- package/dist/lib/sandbox/commands/enter.js +18 -4
- package/dist/lib/sandbox/config.js +3 -12
- package/dist/lib/sandbox/control/executor.js +131 -39
- package/dist/lib/sandbox/control/lifecycle.js +29 -20
- package/dist/lib/sandbox/control/server.js +18 -16
- package/dist/lib/sandbox/mounts.js +3 -2
- package/dist/lib/sandbox/recovery.js +123 -22
- package/dist/lib/sandbox/workspace-identity.js +14 -0
- package/dist/lib/sandbox/workspace-view.js +8 -9
- package/dist/lib/self-update.js +278 -0
- package/dist/lib/server/protocol.js +5 -5
- package/dist/lib/task/activity-log.js +4 -1
- package/dist/lib/task/artifact-lifecycle.js +4 -2
- package/dist/lib/task/codex-orchestration.js +1 -1
- package/dist/lib/task/command-options.js +1 -3
- package/dist/lib/task/commands/cat.js +0 -5
- package/dist/lib/task/commands/decisions.js +76 -92
- package/dist/lib/task/commands/files.js +6 -6
- package/dist/lib/task/commands/grep.js +25 -61
- package/dist/lib/task/commands/issue-body.js +6 -6
- package/dist/lib/task/commands/log.js +25 -10
- package/dist/lib/task/commands/show.js +5 -6
- package/dist/lib/task/commands/status.js +8 -8
- package/dist/lib/task/commit-operation.js +21 -25
- package/dist/lib/task/commit-policy.js +1 -1
- package/dist/lib/task/control-authority.js +431 -0
- package/dist/lib/task/create.js +6 -1
- package/dist/lib/task/current-contract.js +35 -0
- package/dist/lib/task/decision-details.js +263 -0
- package/dist/lib/task/decision-intents.js +5 -2
- package/dist/lib/task/events.js +11 -5
- package/dist/lib/task/index.js +19 -20
- package/dist/lib/task/ledger-intents.js +8 -9
- package/dist/lib/task/ledger.js +13 -6
- package/dist/lib/task/lifecycle.js +16 -0
- package/dist/lib/task/orchestration.js +50 -13
- package/dist/lib/task/review-artifacts.js +31 -12
- package/dist/lib/task/review-finalization.js +19 -6
- package/dist/lib/task/verification-engine.js +38 -15
- package/dist/lib/task/verification-review.js +1 -1
- package/dist/lib/task/workspace-snapshot.js +5 -11
- package/dist/lib/task/write.js +11 -1
- package/dist/lib/update.js +6 -37
- package/dist/lib/version.js +5 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +0 -1
- package/lib/agent-clients/adapters/opencode-sandbox.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +7 -8
- package/lib/agent-clients/config.ts +14 -144
- package/lib/agent-clients/reconcile.ts +1 -12
- package/lib/agent-clients/schema.ts +5 -4
- package/lib/agent-clients/tokens.ts +5 -18
- package/lib/decide.ts +12 -22
- package/lib/defaults.json +4 -0
- package/lib/init.ts +2 -2
- package/lib/internal/release-workflow.ts +0 -1
- package/lib/internal/task-finalization.ts +27 -13
- package/lib/internal/task-ledger.ts +8 -2
- package/lib/internal/task-lifecycle.ts +33 -76
- package/lib/internal/task-orchestration.ts +33 -190
- package/lib/internal/task-validate.ts +3 -2
- package/lib/merge.ts +236 -120
- package/lib/process-data/normalize.ts +2 -8
- package/lib/run/host.ts +63 -14
- package/lib/sandbox/agent-client-reconciler.ts +5 -15
- package/lib/sandbox/commands/create.ts +4 -4
- package/lib/sandbox/commands/enter.ts +24 -4
- package/lib/sandbox/config.ts +2 -14
- package/lib/sandbox/control/container-identity.ts +2 -2
- package/lib/sandbox/control/executor.ts +148 -51
- package/lib/sandbox/control/lifecycle.ts +34 -28
- package/lib/sandbox/control/protocol.ts +1 -3
- package/lib/sandbox/control/server.ts +15 -12
- package/lib/sandbox/control/state.ts +7 -7
- package/lib/sandbox/mounts.ts +4 -3
- package/lib/sandbox/recovery.ts +162 -34
- package/lib/sandbox/tools.ts +1 -1
- package/lib/sandbox/workspace-identity.ts +28 -0
- package/lib/sandbox/workspace-view.ts +14 -10
- package/lib/self-update.ts +341 -0
- package/lib/server/protocol.ts +5 -5
- package/lib/task/activity-log.ts +6 -1
- package/lib/task/artifact-lifecycle.ts +5 -3
- package/lib/task/codex-orchestration.ts +2 -1
- package/lib/task/command-options.ts +1 -2
- package/lib/task/commands/cat.ts +0 -4
- package/lib/task/commands/decisions.ts +80 -75
- package/lib/task/commands/files.ts +6 -6
- package/lib/task/commands/grep.ts +23 -51
- package/lib/task/commands/issue-body.ts +6 -6
- package/lib/task/commands/log.ts +22 -10
- package/lib/task/commands/show.ts +5 -6
- package/lib/task/commands/status.ts +8 -8
- package/lib/task/commit-operation.ts +25 -30
- package/lib/task/commit-policy.ts +1 -2
- package/lib/task/control-authority.ts +563 -0
- package/lib/task/create.ts +6 -1
- package/lib/task/current-contract.ts +49 -0
- package/lib/task/decision-details.ts +340 -0
- package/lib/task/decision-intents.ts +7 -2
- package/lib/task/events.ts +10 -5
- package/lib/task/index.ts +19 -20
- package/lib/task/ledger-intents.ts +6 -8
- package/lib/task/ledger.ts +17 -7
- package/lib/task/lifecycle.ts +20 -0
- package/lib/task/orchestration.ts +40 -15
- package/lib/task/review-artifacts.ts +53 -15
- package/lib/task/review-finalization.ts +33 -7
- package/lib/task/verification-engine.ts +33 -18
- package/lib/task/verification-review.ts +1 -1
- package/lib/task/workspace-snapshot.ts +4 -13
- package/lib/task/write.ts +20 -1
- package/lib/update.ts +6 -40
- package/lib/version.ts +6 -1
- package/package.json +1 -1
- package/templates/.agents/README.en.md +5 -3
- package/templates/.agents/README.zh-CN.md +5 -3
- package/templates/.agents/rules/compatibility-policy.en.md +5 -4
- package/templates/.agents/rules/compatibility-policy.zh-CN.md +5 -4
- package/templates/.agents/rules/create-issue.github.en.md +1 -1
- package/templates/.agents/rules/create-issue.github.zh-CN.md +1 -1
- package/templates/.agents/rules/human-decision-context.en.md +9 -7
- package/templates/.agents/rules/human-decision-context.zh-CN.md +9 -7
- package/templates/.agents/rules/issue-fields.github.en.md +1 -1
- package/templates/.agents/rules/issue-fields.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.github.en.md +1 -1
- package/templates/.agents/rules/issue-sync.github.zh-CN.md +2 -2
- package/templates/.agents/rules/milestone-inference.github.en.md +1 -1
- package/templates/.agents/rules/milestone-inference.github.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +3 -3
- package/templates/.agents/rules/next-step-output.zh-CN.md +3 -3
- package/templates/.agents/rules/no-mid-flow-questions.en.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.en.md +1 -1
- package/templates/.agents/rules/pr-sync.github.zh-CN.md +1 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/release-commands.github.en.md +6 -0
- package/templates/.agents/rules/release-commands.github.zh-CN.md +6 -0
- package/templates/.agents/rules/review-handshake.en.md +1 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +1 -2
- package/templates/.agents/rules/task-management.zh-CN.md +1 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/commit/SKILL.en.md +27 -7
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +3 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -5
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +5 -7
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +5 -7
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +9 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +9 -4
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +203 -0
- package/templates/.agents/skills/create-release-note/SKILL.en.md +5 -6
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +5 -6
- package/templates/.agents/skills/create-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/review-code/reference/migration-risks.en.md +2 -1
- package/templates/.agents/skills/review-code/reference/migration-risks.zh-CN.md +2 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -0
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/update-agent-infra/SKILL.en.md +2 -2
- package/templates/.agents/skills/update-agent-infra/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +140 -109
- 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/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/analyze-task.en.md +1 -1
- package/templates/.claude/commands/analyze-task.zh-CN.md +1 -1
- package/templates/.claude/commands/block-task.en.md +1 -1
- package/templates/.claude/commands/block-task.zh-CN.md +1 -1
- package/templates/.claude/commands/cancel-task.en.md +1 -1
- package/templates/.claude/commands/cancel-task.zh-CN.md +1 -1
- package/templates/.claude/commands/check-task.en.md +1 -1
- package/templates/.claude/commands/check-task.zh-CN.md +1 -1
- package/templates/.claude/commands/code-task.en.md +1 -1
- package/templates/.claude/commands/code-task.zh-CN.md +1 -1
- package/templates/.claude/commands/commit.en.md +1 -1
- package/templates/.claude/commands/commit.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.en.md +1 -1
- package/templates/.claude/commands/complete-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/complete-task.en.md +1 -1
- package/templates/.claude/commands/complete-task.zh-CN.md +1 -1
- package/templates/.claude/commands/create-pr.en.md +1 -1
- package/templates/.claude/commands/create-pr.zh-CN.md +1 -1
- package/templates/.claude/commands/plan-task.en.md +1 -1
- package/templates/.claude/commands/plan-task.zh-CN.md +1 -1
- package/templates/.claude/commands/review-analysis.en.md +1 -1
- package/templates/.claude/commands/review-analysis.zh-CN.md +1 -1
- package/templates/.claude/commands/review-code.en.md +1 -1
- package/templates/.claude/commands/review-code.zh-CN.md +1 -1
- package/templates/.claude/commands/review-plan.en.md +1 -1
- package/templates/.claude/commands/review-plan.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/templates/.claude/commands/run-task.en.md +1 -1
- package/templates/.claude/commands/run-task.zh-CN.md +1 -1
- package/templates/.claude/commands/watch-pr.en.md +1 -1
- package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.en.md +0 -1
- package/templates/{.trae/skills → .traecli/commands}/update-agent-infra.zh-CN.md +0 -1
- package/dist/lib/builtin-tuis.js +0 -40
- package/lib/builtin-tuis.ts +0 -56
package/lib/task/lifecycle.ts
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
} from './short-id.ts';
|
|
13
13
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
14
14
|
import type { TaskFileSystem, TaskMutation, TaskOperationSummary, TaskWriteMetadata } from './write.ts';
|
|
15
|
+
import { validateCurrentTaskContract } from './current-contract.ts';
|
|
16
|
+
import { isValidAgentInfraVersion } from '../version.ts';
|
|
15
17
|
|
|
16
18
|
const lifecycleIntentCatalog = [
|
|
17
19
|
'block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore'
|
|
@@ -126,6 +128,14 @@ function failed(request: Pick<TaskLifecycleRequest, 'taskRef' | 'intent'>, error
|
|
|
126
128
|
};
|
|
127
129
|
}
|
|
128
130
|
|
|
131
|
+
export function taskLifecycleFailure(
|
|
132
|
+
request: Pick<TaskLifecycleRequest, 'taskRef' | 'intent'>,
|
|
133
|
+
error: LifecycleError,
|
|
134
|
+
taskId: string | null = null
|
|
135
|
+
): TaskLifecycleResult {
|
|
136
|
+
return failed(request, error, { taskId });
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
function normalizedRequest(request: TaskLifecycleRequest): TaskLifecycleRequest | LifecycleError {
|
|
130
140
|
if (!request || typeof request !== 'object' || !lifecycleIntentCatalog.includes(request.intent as TaskLifecycleIntent)) {
|
|
131
141
|
return { code: 'LIFECYCLE_PAYLOAD_INVALID', message: 'intent is not in the lifecycle catalog' };
|
|
@@ -385,6 +395,10 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
385
395
|
catch (error) { return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: error instanceof Error ? error.message : String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
|
|
386
396
|
if (frontmatter.id !== taskId) return failed(request, { code: 'LIFECYCLE_IDENTITY_INVALID', message: 'task.md id does not match its lifecycle task id' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
387
397
|
if (!locateActivityLog(content) && !allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING')) return failed(request, { code: 'LIFECYCLE_LOG_MISSING', message: 'task has no unique Activity Log section' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
398
|
+
if (spec.target === 'active') {
|
|
399
|
+
const contract = validateCurrentTaskContract(content);
|
|
400
|
+
if (!contract.ok) return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: contract.message }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
401
|
+
}
|
|
388
402
|
const registryRequest = executeShortIdCommand({
|
|
389
403
|
operation: 'list', activeDir: path.join(repoRoot, '.agents', 'workspace', 'active'),
|
|
390
404
|
shortIdLength: configuredShortIdLength(repoRoot)
|
|
@@ -408,6 +422,9 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
408
422
|
try { journal = parseJournal(io.readFileSync(initialJournalPath)); }
|
|
409
423
|
catch (error) { return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath }); }
|
|
410
424
|
if (journal.taskId !== taskId || journal.intent !== request.intent) return failed(request, { code: 'LIFECYCLE_INTENT_CONFLICT', message: 'an in-progress lifecycle journal belongs to a different request' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
425
|
+
if (spec.target === 'active' && !isValidAgentInfraVersion(journal.metadata.agentInfraVersion)) {
|
|
426
|
+
return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: `journal metadata agentInfraVersion must be a valid v-prefixed semver (received ${journal.metadata.agentInfraVersion})` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
427
|
+
}
|
|
411
428
|
if (request.dryRun) {
|
|
412
429
|
if (!journal.failure) return failed(request, { code: 'LIFECYCLE_JOURNAL_INVALID', message: 'existing lifecycle journal has no durable failure evidence for a read-only probe' }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath });
|
|
413
430
|
return failed(request, journal.failure, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath, journalPath: initialJournalPath, completedSteps: journal.completedSteps, pendingSteps: STEPS.filter((step) => !journal.completedSteps.includes(step)) });
|
|
@@ -418,6 +435,9 @@ function applyTaskLifecycle(requestInput: TaskLifecycleRequest, options: TaskLif
|
|
|
418
435
|
let metadata: TaskWriteMetadata;
|
|
419
436
|
try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
|
|
420
437
|
catch (error) { return failed(request, { code: 'LIFECYCLE_METADATA_FAILED', message: String(error) }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath }); }
|
|
438
|
+
if (spec.target === 'active' && !isValidAgentInfraVersion(metadata.agentInfraVersion)) {
|
|
439
|
+
return failed(request, { code: 'LIFECYCLE_DOCUMENT_INVALID', message: `metadata agentInfraVersion must be a valid v-prefixed semver (received ${metadata.agentInfraVersion})` }, { taskId, sourceState, targetState: spec.target, sourcePath, targetPath });
|
|
440
|
+
}
|
|
421
441
|
journal = { version: 1, taskId, intent: request.intent, intentDigest: digest, sourceState, targetState: spec.target, metadata, completedSteps: [] };
|
|
422
442
|
if (request.dryRun) {
|
|
423
443
|
const plannedMutations = mutationsFor(request, content, metadata, restoredFiles, allowsManualOverride(options, 'LIFECYCLE_LOG_MISSING'));
|
|
@@ -3,9 +3,11 @@ import path from 'node:path';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
5
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
6
|
-
import {
|
|
6
|
+
import { validateCurrentTaskContract } from './current-contract.ts';
|
|
7
|
+
import { parseReviewSummary, resolveCanonicalVerdict } from './review-artifacts.ts';
|
|
7
8
|
import { inspectArtifactDirectory } from './artifact-lifecycle.ts';
|
|
8
|
-
import {
|
|
9
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
10
|
+
import type { LedgerDocument } from './ledger.ts';
|
|
9
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
10
12
|
import {
|
|
11
13
|
activateDelegation,
|
|
@@ -530,14 +532,20 @@ function hasReviewAfterArtifact(taskDir: string, family: string, reviewFamily: s
|
|
|
530
532
|
&& review.reviewedInput?.name === artifact.latest.name;
|
|
531
533
|
}
|
|
532
534
|
|
|
533
|
-
function latestReviewApproved(taskDir: string, family: 'review-analysis' | 'review-plan' | 'review-code'): boolean {
|
|
535
|
+
function latestReviewApproved(taskDir: string, family: 'review-analysis' | 'review-plan' | 'review-code'): { approved: boolean; error: string | null } {
|
|
534
536
|
const round = highestRound(taskDir, family);
|
|
535
|
-
if (round === 0) return false;
|
|
536
|
-
|
|
537
|
-
|
|
537
|
+
if (round === 0) return { approved: false, error: null };
|
|
538
|
+
let content: string;
|
|
539
|
+
try { content = fs.readFileSync(path.join(taskDir, artifactName(family, round)), 'utf8'); }
|
|
540
|
+
catch (error) { return { approved: false, error: String(error) }; }
|
|
541
|
+
const parsed = parseReviewSummary(content);
|
|
542
|
+
if (!parsed.ok) return { approved: false, error: `${parsed.code}: ${parsed.message}` };
|
|
543
|
+
const verdict = resolveCanonicalVerdict(parsed.summary);
|
|
544
|
+
if (!verdict.ok) return { approved: false, error: `${verdict.code}: ${verdict.message}` };
|
|
545
|
+
return { approved: verdict.verdict === 'Approved', error: null };
|
|
538
546
|
}
|
|
539
547
|
|
|
540
|
-
function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | null {
|
|
548
|
+
function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedModel' | 'requestedReasoningEffort'> | { error: string } | null {
|
|
541
549
|
const analysisRound = highestRound(taskDir, 'analysis');
|
|
542
550
|
const analysisReviewRound = highestRound(taskDir, 'review-analysis');
|
|
543
551
|
const planRound = highestRound(taskDir, 'plan');
|
|
@@ -552,7 +560,9 @@ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedMode
|
|
|
552
560
|
const round = analysisReviewRound + 1;
|
|
553
561
|
return { action: 'review-analysis', role: 'reviewer', stage: 'review-analysis', round, artifact: artifactName('review-analysis', round) };
|
|
554
562
|
}
|
|
555
|
-
|
|
563
|
+
const analysisVerdict = latestReviewApproved(taskDir, 'review-analysis');
|
|
564
|
+
if (analysisVerdict.error) return { error: analysisVerdict.error };
|
|
565
|
+
if (!analysisVerdict.approved) {
|
|
556
566
|
const round = analysisRound + 1;
|
|
557
567
|
return { action: 'analyze-task', role: 'executor', stage: 'analysis', round, artifact: artifactName('analysis', round) };
|
|
558
568
|
}
|
|
@@ -564,7 +574,9 @@ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedMode
|
|
|
564
574
|
const round = planReviewRound + 1;
|
|
565
575
|
return { action: 'review-plan', role: 'reviewer', stage: 'review-plan', round, artifact: artifactName('review-plan', round) };
|
|
566
576
|
}
|
|
567
|
-
|
|
577
|
+
const planVerdict = latestReviewApproved(taskDir, 'review-plan');
|
|
578
|
+
if (planVerdict.error) return { error: planVerdict.error };
|
|
579
|
+
if (!planVerdict.approved) {
|
|
568
580
|
const round = planRound + 1;
|
|
569
581
|
return { action: 'plan-task', role: 'executor', stage: 'plan', round, artifact: artifactName('plan', round) };
|
|
570
582
|
}
|
|
@@ -576,7 +588,9 @@ function routeFromFacts(taskDir: string): Omit<OrchestrationNext, 'requestedMode
|
|
|
576
588
|
const round = codeReviewRound + 1;
|
|
577
589
|
return { action: 'review-code', role: 'reviewer', stage: 'review-code', round, artifact: artifactName('review-code', round) };
|
|
578
590
|
}
|
|
579
|
-
|
|
591
|
+
const codeVerdict = latestReviewApproved(taskDir, 'review-code');
|
|
592
|
+
if (codeVerdict.error) return { error: codeVerdict.error };
|
|
593
|
+
if (!codeVerdict.approved) {
|
|
580
594
|
const round = codeRound + 1;
|
|
581
595
|
return { action: 'code-task', role: 'executor', stage: 'code', round, artifact: artifactName('code', round) };
|
|
582
596
|
}
|
|
@@ -599,12 +613,21 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
599
613
|
return failed('ORCHESTRATION_TASK_READ_FAILED', String(error), resolved.taskId);
|
|
600
614
|
}
|
|
601
615
|
let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
return failed('
|
|
616
|
+
let currentLedger: LedgerDocument | null = null;
|
|
617
|
+
if (resolved.state === 'active') {
|
|
618
|
+
const contract = validateCurrentTaskContract(content);
|
|
619
|
+
if (!contract.ok) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
|
|
620
|
+
metadata = contract.metadata;
|
|
621
|
+
currentLedger = contract.ledger;
|
|
622
|
+
} else {
|
|
623
|
+
try {
|
|
624
|
+
metadata = parseTypedTaskFrontmatter(content);
|
|
625
|
+
} catch (error) {
|
|
626
|
+
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
627
|
+
}
|
|
606
628
|
}
|
|
607
629
|
const routed = routeFromFacts(resolved.taskDir);
|
|
630
|
+
if (routed && 'error' in routed) return failed('ORCHESTRATION_REVIEW_INVALID', routed.error, resolved.taskId);
|
|
608
631
|
if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
609
632
|
const run = readRun(resolved.taskDir);
|
|
610
633
|
const policy = run ? rolePolicy(run, routed.role) : null;
|
|
@@ -622,7 +645,9 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
622
645
|
if (!review.ok || review.summary.manualValidation === null) {
|
|
623
646
|
return failed('ORCHESTRATION_REVIEW_INVALID', 'latest code review has no numeric manual-validation count', resolved.taskId);
|
|
624
647
|
}
|
|
625
|
-
const
|
|
648
|
+
const ledger = currentLedger ?? parseLedgerDocument(content);
|
|
649
|
+
if (!ledger.present) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
|
|
650
|
+
const rows = ledger.rows;
|
|
626
651
|
const ledgerError = validateLedgerRows(rows);
|
|
627
652
|
if (ledgerError) return failed('ORCHESTRATION_LEDGER_INVALID', ledgerError.message, resolved.taskId);
|
|
628
653
|
if (!summarizeLedgerStage(rows, 'code').canAdvance) {
|
|
@@ -18,6 +18,21 @@ type ReviewSummaryErrorCode =
|
|
|
18
18
|
| 'REVIEW_SUMMARY_NOT_FOUND'
|
|
19
19
|
| 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
|
|
20
20
|
| 'REVIEW_SUMMARY_COUNT_MISMATCH';
|
|
21
|
+
type VerdictSemanticErrorCode =
|
|
22
|
+
| 'REVIEW_VERDICT_FINDING_MISMATCH'
|
|
23
|
+
| 'REVIEW_FINDING_COUNTS_NOT_FINALIZED';
|
|
24
|
+
type CanonicalVerdictResult =
|
|
25
|
+
| { ok: true; verdict: ReviewVerdict }
|
|
26
|
+
| { ok: false; verdict: null; code: VerdictSemanticErrorCode; message: string };
|
|
27
|
+
type PathVerdictErrorCode =
|
|
28
|
+
| 'REVIEW_ARTIFACT_NOT_FOUND'
|
|
29
|
+
| 'REVIEW_ARTIFACT_READ_FAILED'
|
|
30
|
+
| 'REVIEW_SUMMARY_NOT_FOUND'
|
|
31
|
+
| 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
|
|
32
|
+
| VerdictSemanticErrorCode;
|
|
33
|
+
type PathVerdictResult =
|
|
34
|
+
| { ok: true; verdict: ReviewVerdict }
|
|
35
|
+
| { ok: false; verdict: null; code: PathVerdictErrorCode; message: string };
|
|
21
36
|
type ReviewSummaryParseResult =
|
|
22
37
|
| { ok: true; summary: ReviewSummary; findingsStart: number; findingsEnd: number }
|
|
23
38
|
| { ok: false; code: ReviewSummaryErrorCode; message: string };
|
|
@@ -203,28 +218,46 @@ function finalizeReviewSummaryContent(
|
|
|
203
218
|
};
|
|
204
219
|
}
|
|
205
220
|
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
221
|
+
function resolveCanonicalVerdict(summary: ReviewSummary): CanonicalVerdictResult {
|
|
222
|
+
if (summary.counts === null) {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
verdict: null,
|
|
226
|
+
code: 'REVIEW_FINDING_COUNTS_NOT_FINALIZED',
|
|
227
|
+
message: 'review summary finding counts are not finalized'
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (summary.verdict === 'Approved' && !equalCounts(summary.counts, { blocker: 0, major: 0, minor: 0 })) {
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
verdict: null,
|
|
234
|
+
code: 'REVIEW_VERDICT_FINDING_MISMATCH',
|
|
235
|
+
message: 'Approved verdict requires zero finalized findings'
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
return { ok: true, verdict: summary.verdict };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function parseVerdict(reviewPath: string): PathVerdictResult {
|
|
209
242
|
if (!fs.existsSync(reviewPath)) {
|
|
210
|
-
return { ok: false, verdict: null, message: `Review artifact not found: ${path.basename(reviewPath)}` };
|
|
243
|
+
return { ok: false, verdict: null, code: 'REVIEW_ARTIFACT_NOT_FOUND', message: `Review artifact not found: ${path.basename(reviewPath)}` };
|
|
244
|
+
}
|
|
245
|
+
let content: string;
|
|
246
|
+
try {
|
|
247
|
+
content = fs.readFileSync(reviewPath, 'utf8');
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return { ok: false, verdict: null, code: 'REVIEW_ARTIFACT_READ_FAILED', message: `Review artifact could not be read: ${String(error)}` };
|
|
211
250
|
}
|
|
212
|
-
const parsed = parseReviewSummary(
|
|
251
|
+
const parsed = parseReviewSummary(content);
|
|
213
252
|
if (!parsed.ok) {
|
|
214
253
|
return {
|
|
215
254
|
ok: false,
|
|
216
255
|
verdict: null,
|
|
256
|
+
code: parsed.code === 'REVIEW_SUMMARY_PLACEHOLDER_INVALID' ? parsed.code : 'REVIEW_SUMMARY_NOT_FOUND',
|
|
217
257
|
message: `cannot parse review summary in ${path.basename(reviewPath)}: ${parsed.message}`
|
|
218
258
|
};
|
|
219
259
|
}
|
|
220
|
-
|
|
221
|
-
if (verdict !== 'Approved') return { ok: true, verdict };
|
|
222
|
-
return {
|
|
223
|
-
ok: true,
|
|
224
|
-
verdict: counts && equalCounts(counts, { blocker: 0, major: 0, minor: 0 })
|
|
225
|
-
? 'Approved'
|
|
226
|
-
: 'Approved-with-issues'
|
|
227
|
-
};
|
|
260
|
+
return resolveCanonicalVerdict(parsed.summary);
|
|
228
261
|
}
|
|
229
262
|
|
|
230
263
|
export {
|
|
@@ -236,13 +269,18 @@ export {
|
|
|
236
269
|
maxRound,
|
|
237
270
|
normalizeVerdict,
|
|
238
271
|
parseReviewSummary,
|
|
239
|
-
parseVerdict
|
|
272
|
+
parseVerdict,
|
|
273
|
+
resolveCanonicalVerdict
|
|
240
274
|
};
|
|
241
275
|
export type {
|
|
276
|
+
CanonicalVerdictResult,
|
|
277
|
+
PathVerdictErrorCode,
|
|
278
|
+
PathVerdictResult,
|
|
242
279
|
ReviewFindingCounts,
|
|
243
280
|
ReviewSummary,
|
|
244
281
|
ReviewSummaryErrorCode,
|
|
245
282
|
ReviewSummaryFinalizeResult,
|
|
246
283
|
ReviewSummaryParseResult,
|
|
247
|
-
ReviewVerdict
|
|
284
|
+
ReviewVerdict,
|
|
285
|
+
VerdictSemanticErrorCode
|
|
248
286
|
};
|
|
@@ -2,11 +2,12 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
|
-
import { locateActivityLog, pairEntries } from './activity-log.ts';
|
|
5
|
+
import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
|
|
6
6
|
import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
8
8
|
import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
|
|
9
9
|
import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
10
|
+
import { inspectDecisionDetailDuplicates, repairDecisionDetailDuplicates } from './decision-details.ts';
|
|
10
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
11
12
|
import { validateLifecycleExecution } from './lifecycle-execution.ts';
|
|
12
13
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
@@ -25,6 +26,7 @@ type ReviewFinalizationErrorCode =
|
|
|
25
26
|
| 'REVIEW_SUMMARY_NOT_FOUND'
|
|
26
27
|
| 'REVIEW_SUMMARY_PLACEHOLDER_INVALID'
|
|
27
28
|
| 'REVIEW_SUMMARY_COUNT_MISMATCH'
|
|
29
|
+
| 'REVIEW_DECISION_DETAIL_INVALID'
|
|
28
30
|
| 'REVIEW_ARTIFACT_CONFLICT'
|
|
29
31
|
| 'REVIEW_PROVENANCE_INVALID'
|
|
30
32
|
| 'REVIEW_TEMP_WRITE_FAILED'
|
|
@@ -106,8 +108,8 @@ function openReviewRound(content: string, action: string, round: number): boolea
|
|
|
106
108
|
const section = locateActivityLog(content);
|
|
107
109
|
if (!section) return false;
|
|
108
110
|
const expected = `${action} (Round ${round})`;
|
|
109
|
-
return pairEntries(section.entries).filter((row) => (
|
|
110
|
-
row.step === expected &&
|
|
111
|
+
return startedBackedRows(pairEntries(section.entries)).filter((row) => (
|
|
112
|
+
row.step === expected && !row.done
|
|
111
113
|
)).length === 1;
|
|
112
114
|
}
|
|
113
115
|
|
|
@@ -194,7 +196,11 @@ function finalizeReviewSummaryUnlocked(
|
|
|
194
196
|
|
|
195
197
|
let rows;
|
|
196
198
|
try {
|
|
197
|
-
|
|
199
|
+
const ledger = parseLedgerDocument(taskContent);
|
|
200
|
+
if (!ledger.present) {
|
|
201
|
+
return failed(request, 'REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
|
|
202
|
+
}
|
|
203
|
+
rows = ledger.rows;
|
|
198
204
|
} catch (error) {
|
|
199
205
|
return failed(request, 'REVIEW_LEDGER_INVALID', String(error), resolved.taskId);
|
|
200
206
|
}
|
|
@@ -208,14 +214,34 @@ function finalizeReviewSummaryUnlocked(
|
|
|
208
214
|
);
|
|
209
215
|
}
|
|
210
216
|
const stageStatus = summarizeLedgerStage(rows, stage);
|
|
217
|
+
const repaired = repairDecisionDetailDuplicates(artifactContent);
|
|
218
|
+
if (!repaired.ok) {
|
|
219
|
+
return failed(
|
|
220
|
+
request,
|
|
221
|
+
'REVIEW_DECISION_DETAIL_INVALID',
|
|
222
|
+
`${repaired.code}: ${repaired.message}`,
|
|
223
|
+
resolved.taskId,
|
|
224
|
+
stageStatus
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const detailInspection = inspectDecisionDetailDuplicates(repaired.content);
|
|
228
|
+
if (!detailInspection.ok) {
|
|
229
|
+
return failed(
|
|
230
|
+
request,
|
|
231
|
+
'REVIEW_DECISION_DETAIL_INVALID',
|
|
232
|
+
`${detailInspection.code}: ${detailInspection.message}`,
|
|
233
|
+
resolved.taskId,
|
|
234
|
+
stageStatus
|
|
235
|
+
);
|
|
236
|
+
}
|
|
211
237
|
const transformed = finalizeReviewSummaryContent(
|
|
212
|
-
|
|
238
|
+
repaired.content,
|
|
213
239
|
stageStatus.unresolvedFindingCounts
|
|
214
240
|
);
|
|
215
241
|
if (!transformed.ok) {
|
|
216
242
|
return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
|
|
217
243
|
}
|
|
218
|
-
if (!transformed.changed) {
|
|
244
|
+
if (!repaired.changed && !transformed.changed) {
|
|
219
245
|
return {
|
|
220
246
|
...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus),
|
|
221
247
|
status: 'no-op',
|
|
@@ -22,9 +22,11 @@ import { resolveMaterializedReviewedHeadRelation } from "../platform/change-requ
|
|
|
22
22
|
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.ts";
|
|
23
23
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.ts";
|
|
24
24
|
import { parseTypedTaskFrontmatter } from "./frontmatter.ts";
|
|
25
|
-
import {
|
|
25
|
+
import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.ts";
|
|
26
26
|
import type { LedgerRow } from "./ledger.ts";
|
|
27
|
+
import { isValidAgentInfraVersion } from "../version.ts";
|
|
27
28
|
import { equalCounts, parseReviewSummary } from "./review-artifacts.ts";
|
|
29
|
+
import { inspectDecisionDetailDuplicates } from "./decision-details.ts";
|
|
28
30
|
import { loadVerificationConfig } from "./verification-config.ts";
|
|
29
31
|
import { snapshotReview } from "../git/review-snapshot.ts";
|
|
30
32
|
import { OrchestrationStateError, readRun } from "./orchestration.ts";
|
|
@@ -48,7 +50,6 @@ const DEFAULT_REQUIRED_FIELDS = [
|
|
|
48
50
|
];
|
|
49
51
|
|
|
50
52
|
const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?$/;
|
|
51
|
-
const AGENT_INFRA_VERSION_PATTERN = /^v\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
52
53
|
const ACTIVITY_LOG_PATTERN = /^- (\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?) — \*\*(.+?)\*\* by (.+?) — (.+)$/;
|
|
53
54
|
// Start markers (action suffixed with ` [started]`) are excluded from the
|
|
54
55
|
// "latest action" / freshness computation so a step's in-flight marker never
|
|
@@ -108,6 +109,8 @@ function runCheck(type: any, context: any, shared: any): any {
|
|
|
108
109
|
return checkTaskMeta(context);
|
|
109
110
|
case "artifact":
|
|
110
111
|
return checkArtifact(context);
|
|
112
|
+
case "decision-details":
|
|
113
|
+
return checkDecisionDetails(context);
|
|
111
114
|
case "implementation-input":
|
|
112
115
|
return checkImplementationInput(context);
|
|
113
116
|
case "activity-log":
|
|
@@ -392,23 +395,20 @@ function checkTaskMeta({ taskDir, config }: any): any {
|
|
|
392
395
|
const metadata = task.metadata;
|
|
393
396
|
const requiredFields = config.required_fields || DEFAULT_REQUIRED_FIELDS;
|
|
394
397
|
const missingFields = requiredFields.filter((field: any) => isBlank(metadata[field]));
|
|
395
|
-
const
|
|
398
|
+
const currentTask = metadata.status === 'active';
|
|
399
|
+
const blockingMissingFields = missingFields.filter((field: any) => field !== "agent_infra_version" || currentTask);
|
|
396
400
|
const warnings = [];
|
|
397
|
-
if (missingFields.includes("agent_infra_version")) {
|
|
401
|
+
if (missingFields.includes("agent_infra_version") && !currentTask) {
|
|
398
402
|
warnings.push("field 'agent_infra_version' missing — historical task or skipped version stamp");
|
|
399
403
|
}
|
|
400
404
|
if (blockingMissingFields.length > 0) {
|
|
401
405
|
return failResult("task-meta", `Missing required fields: ${blockingMissingFields.join(", ")}`);
|
|
402
406
|
}
|
|
403
407
|
|
|
404
|
-
if (
|
|
405
|
-
!isBlank(metadata.agent_infra_version) &&
|
|
406
|
-
metadata.agent_infra_version !== "unknown" &&
|
|
407
|
-
!AGENT_INFRA_VERSION_PATTERN.test(metadata.agent_infra_version)
|
|
408
|
-
) {
|
|
408
|
+
if (currentTask && !isValidAgentInfraVersion(metadata.agent_infra_version)) {
|
|
409
409
|
return failResult(
|
|
410
410
|
"task-meta",
|
|
411
|
-
`Invalid agent_infra_version: ${metadata.agent_infra_version}`
|
|
411
|
+
`Invalid agent_infra_version: ${String(metadata.agent_infra_version ?? 'missing')}`
|
|
412
412
|
);
|
|
413
413
|
}
|
|
414
414
|
|
|
@@ -622,6 +622,18 @@ function checkArtifact({ taskDir, config, artifactFile }: any): any {
|
|
|
622
622
|
);
|
|
623
623
|
}
|
|
624
624
|
|
|
625
|
+
function checkDecisionDetails({ taskDir, artifactFile }: any): any {
|
|
626
|
+
const resolvedArtifact = resolveArtifactPath(taskDir, "", artifactFile);
|
|
627
|
+
if (!resolvedArtifact.ok) return failResult("decision-details", resolvedArtifact.message);
|
|
628
|
+
const artifactPath = resolvedArtifact.path;
|
|
629
|
+
const stat = safeStat(artifactPath);
|
|
630
|
+
if (!stat?.isFile()) return failResult("decision-details", `Artifact not found: ${path.basename(artifactPath)}`);
|
|
631
|
+
if (stat.size === 0) return passResult("decision-details", `${path.basename(artifactPath)} contains no decision details`);
|
|
632
|
+
const inspection = inspectDecisionDetailDuplicates(fs.readFileSync(artifactPath, "utf8"));
|
|
633
|
+
if (!inspection.ok) return failResult("decision-details", `${path.basename(artifactPath)}: ${inspection.message}`);
|
|
634
|
+
return passResult("decision-details", `${path.basename(artifactPath)} has no duplicate decision detail ids`);
|
|
635
|
+
}
|
|
636
|
+
|
|
625
637
|
function checkReviewSummary({ taskDir, config, artifactFile }: any): any {
|
|
626
638
|
const stage = String(config.stage || "");
|
|
627
639
|
if (!["analysis", "plan", "code"].includes(stage)) {
|
|
@@ -640,7 +652,9 @@ function checkReviewSummary({ taskDir, config, artifactFile }: any): any {
|
|
|
640
652
|
if (!task.ok) return failResult("review-summary", task.message);
|
|
641
653
|
let rows;
|
|
642
654
|
try {
|
|
643
|
-
|
|
655
|
+
const ledger = parseLedgerDocument(task.content);
|
|
656
|
+
if (!ledger.present) return failResult("review-summary", `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
657
|
+
rows = ledger.rows;
|
|
644
658
|
} catch (error) {
|
|
645
659
|
return failResult("review-summary", `Invalid disagreement ledger: ${String(error)}`);
|
|
646
660
|
}
|
|
@@ -946,14 +960,11 @@ function checkReviewLedger({ taskDir, config }: any): any {
|
|
|
946
960
|
return failResult("review-ledger", task.message);
|
|
947
961
|
}
|
|
948
962
|
|
|
949
|
-
const section = getSectionContent(task.content, LEDGER_SECTION_NAMES);
|
|
950
|
-
if (!section.trim()) {
|
|
951
|
-
return passResult("review-ledger", "No disagreement ledger section; treated as no open disagreements");
|
|
952
|
-
}
|
|
953
|
-
|
|
954
963
|
let rows;
|
|
955
964
|
try {
|
|
956
|
-
|
|
965
|
+
const ledger = parseLedgerDocument(task.content);
|
|
966
|
+
if (!ledger.present) return failResult("review-ledger", `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
967
|
+
rows = ledger.rows.map((row) => [
|
|
957
968
|
row.id, row.stage, row.round, row.severity, row.status, row.evidence
|
|
958
969
|
]);
|
|
959
970
|
} catch (error) {
|
|
@@ -1248,7 +1259,11 @@ function resolvePostReviewExemption(content: string):
|
|
|
1248
1259
|
| { ok: true; exemptions: readonly LedgerRow[] }
|
|
1249
1260
|
| { ok: false; code: "POST_REVIEW_EXEMPTION_INVALID"; message: string } {
|
|
1250
1261
|
try {
|
|
1251
|
-
const
|
|
1262
|
+
const ledger = parseLedgerDocument(content);
|
|
1263
|
+
if (!ledger.present) {
|
|
1264
|
+
return { ok: false, code: "POST_REVIEW_EXEMPTION_INVALID", message: `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}` };
|
|
1265
|
+
}
|
|
1266
|
+
const rows = ledger.rows;
|
|
1252
1267
|
const invalid = validateLedgerRows(rows);
|
|
1253
1268
|
if (invalid) {
|
|
1254
1269
|
return {
|
|
@@ -8,4 +8,4 @@ export {
|
|
|
8
8
|
parseReviewVerdict,
|
|
9
9
|
resolvePostReviewGlobs
|
|
10
10
|
} from './review-fingerprint.ts';
|
|
11
|
-
export {
|
|
11
|
+
export { parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
|
|
@@ -53,13 +53,6 @@ function captureWorktreeTree(repoRoot: string, forcedPath: string | null): strin
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
function captureLegacyWorkspaceSnapshot(repoRoot: string, taskId: string | null): string {
|
|
57
|
-
const workspaceRelative = taskId
|
|
58
|
-
? `.agents/workspace/active/${taskId}`
|
|
59
|
-
: '.agents/workspace/active';
|
|
60
|
-
return captureWorktreeTree(repoRoot, workspaceRelative);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
56
|
function taskFileFingerprints(stateRoot: string, taskId: string | null): TaskFileFingerprint[] {
|
|
64
57
|
const activeRoot = path.join(stateRoot, '.agents', 'workspace', 'active');
|
|
65
58
|
const roots = taskId ? [path.join(activeRoot, taskId)] : [activeRoot];
|
|
@@ -73,6 +66,7 @@ function taskFileFingerprints(stateRoot: string, taskId: string | null): TaskFil
|
|
|
73
66
|
return;
|
|
74
67
|
}
|
|
75
68
|
if (!stat.isFile() && !stat.isSymbolicLink()) return;
|
|
69
|
+
if (relative.startsWith('.agents/workspace/active/') && path.posix.basename(relative) === 'orchestration.json') return;
|
|
76
70
|
const content = stat.isSymbolicLink()
|
|
77
71
|
? Buffer.from(fs.readlinkSync(absolute))
|
|
78
72
|
: fs.readFileSync(absolute);
|
|
@@ -108,14 +102,11 @@ function decodeWorkspaceFingerprint(value: string): OrchestrationWorkspaceFinger
|
|
|
108
102
|
}
|
|
109
103
|
}
|
|
110
104
|
|
|
111
|
-
function captureWorkspaceSnapshot(context: WorkspaceSnapshotContext): string
|
|
112
|
-
function captureWorkspaceSnapshot(repoRoot: string, taskId: string | null): string;
|
|
113
|
-
function captureWorkspaceSnapshot(contextOrRoot: WorkspaceSnapshotContext | string, taskId?: string | null): string {
|
|
114
|
-
if (typeof contextOrRoot === 'string') return captureLegacyWorkspaceSnapshot(contextOrRoot, taskId ?? null);
|
|
105
|
+
function captureWorkspaceSnapshot(context: WorkspaceSnapshotContext): string {
|
|
115
106
|
return encodeWorkspaceFingerprint({
|
|
116
107
|
version: 2,
|
|
117
|
-
gitTree: captureWorktreeTree(
|
|
118
|
-
taskFiles: taskFileFingerprints(
|
|
108
|
+
gitTree: captureWorktreeTree(context.gitRoot, null),
|
|
109
|
+
taskFiles: taskFileFingerprints(context.stateRoot, context.taskId)
|
|
119
110
|
});
|
|
120
111
|
}
|
|
121
112
|
|
package/lib/task/write.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
import { VERSION } from '../version.ts';
|
|
4
|
+
import { isValidAgentInfraVersion, VERSION } from '../version.ts';
|
|
5
5
|
import {
|
|
6
6
|
parseTypedTaskFrontmatter,
|
|
7
7
|
updateTaskFrontmatter
|
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
import { allowsManualOverride } from './guard-override.ts';
|
|
16
16
|
import type { ManualOverrideCapability } from './guard-override.ts';
|
|
17
17
|
import { mutateTableRow, upsertSection } from './sections.ts';
|
|
18
|
+
import { validateCurrentTaskContract } from './current-contract.ts';
|
|
18
19
|
import type {
|
|
19
20
|
TableRowDeleteMutation,
|
|
20
21
|
TableRowUpsertMutation
|
|
@@ -84,6 +85,7 @@ type TaskWriteMetadata = {
|
|
|
84
85
|
type TaskWriteErrorCode =
|
|
85
86
|
| ResolveTaskRefErrorCode
|
|
86
87
|
| 'TASK_STATE_MISMATCH'
|
|
88
|
+
| 'TASK_CURRENT_CONTRACT_INVALID'
|
|
87
89
|
| 'TASK_READ_FAILED'
|
|
88
90
|
| 'TASK_DOCUMENT_INVALID'
|
|
89
91
|
| 'MUTATION_INVALID'
|
|
@@ -268,6 +270,13 @@ function writeTask(request: TaskWriteRequest, options: TaskWriteOptions = {}): T
|
|
|
268
270
|
);
|
|
269
271
|
}
|
|
270
272
|
|
|
273
|
+
if (resolved.state === 'active') {
|
|
274
|
+
const contract = validateCurrentTaskContract(original);
|
|
275
|
+
if (!contract.ok) {
|
|
276
|
+
return failure(request, identity, contract.code, contract.message);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
271
280
|
let candidate = original;
|
|
272
281
|
const operations: TaskOperationSummary[] = [];
|
|
273
282
|
try {
|
|
@@ -342,6 +351,16 @@ function writeTask(request: TaskWriteRequest, options: TaskWriteOptions = {}): T
|
|
|
342
351
|
operations
|
|
343
352
|
);
|
|
344
353
|
}
|
|
354
|
+
if (resolved.state === 'active' && !isValidAgentInfraVersion(metadata.agentInfraVersion)) {
|
|
355
|
+
return failure(
|
|
356
|
+
request,
|
|
357
|
+
identity,
|
|
358
|
+
'TASK_CURRENT_CONTRACT_INVALID',
|
|
359
|
+
`metadata agentInfraVersion must be a valid v-prefixed semver (received ${metadata.agentInfraVersion})`,
|
|
360
|
+
operations,
|
|
361
|
+
metadata
|
|
362
|
+
);
|
|
363
|
+
}
|
|
345
364
|
|
|
346
365
|
if (candidate === original) {
|
|
347
366
|
return {
|