@fitlab-ai/agent-infra 0.11.3 → 0.11.4
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 +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +1 -1
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +41 -1
- package/dist/lib/internal/task-ledger.js +13 -5
- package/dist/lib/internal/task-operation-registry.js +1 -0
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/sandbox/control/server.js +26 -143
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +1 -26
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +1 -0
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +3 -2
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization.js +301 -143
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +10 -0
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/orchestration.js +17 -7
- package/dist/lib/task/review-finalization.js +3 -16
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/verification-engine.js +20 -30
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workflow-warning-intents.js +21 -6
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +40 -1
- package/lib/internal/task-ledger.ts +12 -5
- package/lib/internal/task-operation-registry.ts +1 -0
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/sandbox/control/server.ts +21 -146
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +1 -26
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +1 -0
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +3 -1
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization.ts +269 -111
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +13 -0
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/orchestration.ts +26 -8
- package/lib/task/review-finalization.ts +3 -16
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/verification-engine.ts +19 -30
- package/lib/task/verification.ts +2 -1
- package/lib/task/workflow-warning-intents.ts +18 -6
- package/lib/task/workspace-snapshot.ts +3 -2
- package/package.json +1 -1
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/sandbox/control/finalization-response.js +0 -33
- package/lib/sandbox/control/finalization-response.ts +0 -35
package/lib/task/events.ts
CHANGED
|
@@ -35,14 +35,15 @@ import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from
|
|
|
35
35
|
import { allowsManualOverride } from './guard-override.ts';
|
|
36
36
|
import { validateLocalArtifact } from './local-artifact-finalization.ts';
|
|
37
37
|
import type { LocalArtifactFamily } from './local-artifact-finalization.ts';
|
|
38
|
-
import { buildLifecycleFacts, canStart } from './capabilities.ts';
|
|
38
|
+
import { buildLifecycleFacts, canStart, effectiveReworkTarget } from './capabilities.ts';
|
|
39
39
|
import type { ExplicitTrigger, LifecycleAction, TriggerInitiator, TriggerReason } from './capabilities.ts';
|
|
40
40
|
import { createInvalidationOperation, invalidationMutation, parseInvalidationDocument, targetIdFor, upsertInvalidation } from './invalidation.ts';
|
|
41
41
|
import type { InvalidationTargetKind } from './invalidation.ts';
|
|
42
42
|
import { reconcileTaskInvalidation } from './invalidation-command.ts';
|
|
43
43
|
import { consumeReworkIntents, parseReworkIntentDocument, reworkIntentMutation, supersedeReworkIntents } from './rework-intent.ts';
|
|
44
|
-
import {
|
|
45
|
-
import
|
|
44
|
+
import type { ReworkTarget } from './rework-intent.ts';
|
|
45
|
+
import { ARTIFACT_FAMILIES, parseQualificationAudit, parseTaskQualification, upstreamArtifactDigest } from './qualification-audit.ts';
|
|
46
|
+
import type { QualificationAudit } from './qualification-audit.ts';
|
|
46
47
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
47
48
|
import { canonicalSemanticDigest, inspectArtifactContract } from './artifact-operations.ts';
|
|
48
49
|
import {
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
type LifecycleRecoveryAttestationV1
|
|
53
54
|
} from './control-authority.ts';
|
|
54
55
|
import { readManualValidationCompletion } from './manual-validation-completion.ts';
|
|
56
|
+
import { parseLifecyclePathDecision } from './lifecycle-path.ts';
|
|
55
57
|
|
|
56
58
|
const eventCatalog = [
|
|
57
59
|
'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
|
|
@@ -107,17 +109,39 @@ type TaskEventResult = {
|
|
|
107
109
|
operations: readonly TaskOperationSummary[]; error: TaskEventError | null;
|
|
108
110
|
};
|
|
109
111
|
|
|
110
|
-
function
|
|
112
|
+
function branchWorktree(repoRoot: string, branch: string): string | null {
|
|
113
|
+
if (!branch) return repoRoot;
|
|
114
|
+
try {
|
|
115
|
+
const records = execFileSync('git', ['worktree', 'list', '--porcelain'], {
|
|
116
|
+
cwd: repoRoot,
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
119
|
+
}).trim().split(/\r?\n\r?\n/);
|
|
120
|
+
const expectedRef = `refs/heads/${branch}`;
|
|
121
|
+
for (const record of records) {
|
|
122
|
+
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
123
|
+
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
124
|
+
if (worktree && branchRef === expectedRef) return worktree;
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// A missing worktree must not anchor a review to an unrelated repository.
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function approvedCleanReviewedCommit(reviewContent: string, verdict: Verdict | undefined, repoRoot: string, branch: string): string | null {
|
|
111
133
|
if (verdict !== 'approved') return null;
|
|
112
134
|
const reviewedHead = extractReviewedHead(reviewContent) || extractReviewBaseline(reviewContent);
|
|
113
135
|
const reviewedTree = extractReviewedSnapshotTree(reviewContent);
|
|
114
136
|
if (!/^[a-f0-9]{40}$/.test(reviewedHead) || !/^[a-f0-9]{40}$/.test(reviewedTree)) return null;
|
|
137
|
+
const worktree = branchWorktree(repoRoot, branch);
|
|
138
|
+
if (!worktree) return null;
|
|
115
139
|
try {
|
|
116
|
-
const currentHead = execFileSync('git', ['-C',
|
|
140
|
+
const currentHead = execFileSync('git', ['-C', worktree, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
117
141
|
if (currentHead !== reviewedHead) return null;
|
|
118
|
-
const currentTree = execFileSync('git', ['-C',
|
|
142
|
+
const currentTree = execFileSync('git', ['-C', worktree, 'rev-parse', 'HEAD^{tree}'], { encoding: 'utf8' }).trim();
|
|
119
143
|
if (currentTree !== reviewedTree) return null;
|
|
120
|
-
execFileSync('git', ['-C',
|
|
144
|
+
execFileSync('git', ['-C', worktree, 'diff-index', '--quiet', 'HEAD', '--']);
|
|
121
145
|
return reviewedHead;
|
|
122
146
|
} catch {
|
|
123
147
|
return null;
|
|
@@ -329,7 +353,13 @@ function failed(request: TaskEventRequest, error: TaskEventError, extra: Partial
|
|
|
329
353
|
|
|
330
354
|
function normalizeStarted(request: TaskEventRequest, repoRoot: string): { request: TaskEventRequest; context: ArtifactContextResult } | { error: TaskEventError; context: ArtifactContextResult } {
|
|
331
355
|
const family = eventParts(request.event).family;
|
|
332
|
-
const context = resolveArtifactContext(request.taskRef, FAMILY[family].artifact, {
|
|
356
|
+
const context = resolveArtifactContext(request.taskRef, FAMILY[family].artifact, {
|
|
357
|
+
repoRoot,
|
|
358
|
+
reasonCode: request.reasonCode,
|
|
359
|
+
sourceFinding: request.sourceFinding,
|
|
360
|
+
sourceArtifact: request.sourceArtifact,
|
|
361
|
+
sourceSha256: request.sourceSha256
|
|
362
|
+
});
|
|
333
363
|
if (context.status !== 'ready' || !context.next) {
|
|
334
364
|
return { error: { code: context.error?.code ?? 'EVENT_ARTIFACT_CONFLICT', message: context.error?.message ?? context.codeMode?.message ?? 'artifact context is not writable' }, context };
|
|
335
365
|
}
|
|
@@ -386,25 +416,6 @@ function reviewInputFamily(family: EventFamily): ArtifactFamily {
|
|
|
386
416
|
return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
|
|
387
417
|
}
|
|
388
418
|
|
|
389
|
-
function relationForStartedInput(family: EventFamily, input: ArtifactIdentity): UpstreamRelation['relation'] {
|
|
390
|
-
if (family.startsWith('review-')) {
|
|
391
|
-
return input.family === reviewInputFamily(family) ? 'reviewed-input' : 'approval-context';
|
|
392
|
-
}
|
|
393
|
-
const consumer = family === 'analyze' ? 'analysis' : family;
|
|
394
|
-
if ((consumer === 'plan' && input.family === 'analysis') || (consumer === 'code' && input.family === 'plan')) return 'required-input';
|
|
395
|
-
return 'review-context';
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function qualificationRelationsForStarted(family: EventFamily, inputs: readonly ArtifactIdentity[]): UpstreamRelation[] {
|
|
399
|
-
return inputs.map((input) => ({
|
|
400
|
-
upstreamFamily: input.family as UpstreamRelation['upstreamFamily'],
|
|
401
|
-
upstreamArtifact: input.name,
|
|
402
|
-
upstreamRound: input.round,
|
|
403
|
-
upstreamSha256: sha256File(input.path),
|
|
404
|
-
relation: relationForStartedInput(family, input)
|
|
405
|
-
}));
|
|
406
|
-
}
|
|
407
|
-
|
|
408
419
|
function lifecycleAction(family: EventFamily): LifecycleAction {
|
|
409
420
|
return family === 'analyze' ? 'analysis' : family;
|
|
410
421
|
}
|
|
@@ -586,7 +597,20 @@ function reworkIntentMutationForCompletion(
|
|
|
586
597
|
try { return [[name, sha256File(path.join(taskDir, name))]]; }
|
|
587
598
|
catch { return []; }
|
|
588
599
|
}));
|
|
589
|
-
|
|
600
|
+
const action = family === 'analyze' ? 'analysis' : family;
|
|
601
|
+
let target: ReworkTarget = action;
|
|
602
|
+
if (family === 'analyze') {
|
|
603
|
+
const previousAnalysis = fs.readdirSync(taskDir)
|
|
604
|
+
.map((name) => parseArtifactName(name))
|
|
605
|
+
.filter((identity) => identity?.family === 'analysis' && identity.name !== artifact.name)
|
|
606
|
+
.sort((left, right) => right!.round - left!.round || left!.name.localeCompare(right!.name))[0];
|
|
607
|
+
const pathState = previousAnalysis
|
|
608
|
+
? parseLifecyclePathDecision(fs.readFileSync(path.join(taskDir, previousAnalysis.name), 'utf8'))
|
|
609
|
+
: undefined;
|
|
610
|
+
const pending = next.find((intent) => intent.status === 'pending');
|
|
611
|
+
if (pending && effectiveReworkTarget(pending.target, pathState) === action) target = pending.target;
|
|
612
|
+
}
|
|
613
|
+
next = consumeReworkIntents(next, target, hashes, timestamp).intents;
|
|
590
614
|
}
|
|
591
615
|
if (family === 'review-analysis' || family === 'review-plan' || family === 'review-code') {
|
|
592
616
|
next = supersedeReworkIntents(next, artifact.name, hash, timestamp).intents;
|
|
@@ -614,13 +638,15 @@ function buildCompletionReceipt(
|
|
|
614
638
|
? frontmatter.code_input_artifact : existing?.input ?? '';
|
|
615
639
|
const startedSha256 = typeof frontmatter.code_input_sha256 === 'string'
|
|
616
640
|
? frontmatter.code_input_sha256 : existing?.inputSha256 ?? '';
|
|
617
|
-
if (!startedInput || !startedSha256) return { ok: false, message: 'code.started
|
|
618
|
-
const
|
|
619
|
-
if (
|
|
620
|
-
|
|
641
|
+
if (!startedInput || !startedSha256) return { ok: false, message: 'code.started lifecycle input context is missing' };
|
|
642
|
+
const inputIdentity = parseArtifactName(startedInput);
|
|
643
|
+
if (!inputIdentity || !['analysis', 'plan'].includes(inputIdentity.family)) return { ok: false, message: `code input '${startedInput}' has an invalid family` };
|
|
644
|
+
const lifecycleInput = inspectArtifactDirectory(taskDir, inputIdentity.family);
|
|
645
|
+
if (lifecycleInput.status !== 'ready' || !lifecycleInput.latest || lifecycleInput.latest.name !== startedInput) {
|
|
646
|
+
return { ok: false, message: `code input '${startedInput}' is not the latest ${inputIdentity.family} artifact` };
|
|
621
647
|
}
|
|
622
648
|
try {
|
|
623
|
-
const inputSha256 = sha256File(
|
|
649
|
+
const inputSha256 = sha256File(lifecycleInput.latest.path);
|
|
624
650
|
if (inputSha256 !== startedSha256) return { ok: false, message: `code input ${startedInput} changed after code.started` };
|
|
625
651
|
return {
|
|
626
652
|
ok: true,
|
|
@@ -630,7 +656,7 @@ function buildCompletionReceipt(
|
|
|
630
656
|
}
|
|
631
657
|
};
|
|
632
658
|
} catch (error) {
|
|
633
|
-
return { ok: false, message: `cannot hash code input
|
|
659
|
+
return { ok: false, message: `cannot hash code lifecycle input: ${error instanceof Error ? error.message : String(error)}` };
|
|
634
660
|
}
|
|
635
661
|
}
|
|
636
662
|
if (!family.startsWith('review-')) return null;
|
|
@@ -774,6 +800,12 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
774
800
|
normalized = { ...request, round: openIdentity.round, artifact: artifactName(FAMILY[initialParts.family].artifact, openIdentity.round), fixFor: openIdentity.fixFor, implementationInput: openIdentity.implementationInput };
|
|
775
801
|
return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '', identity(normalized), openIdentity.row.started, frontmatter, null);
|
|
776
802
|
}
|
|
803
|
+
if (rows.some((item) => item.started && !item.done)) {
|
|
804
|
+
return failed(request, {
|
|
805
|
+
code: 'EVENT_TRANSITION_INVALID',
|
|
806
|
+
message: 'EXECUTION_BUSY: another lifecycle execution is open'
|
|
807
|
+
}, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
|
|
808
|
+
}
|
|
777
809
|
const result = normalizeStarted(request, resolved.repoRoot);
|
|
778
810
|
artifactContext = result.context;
|
|
779
811
|
if ('error' in result) return failed(request, result.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, artifactContext });
|
|
@@ -836,25 +868,13 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
836
868
|
if (eventIdentity.family.startsWith('review-')) {
|
|
837
869
|
try { reviewContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
|
|
838
870
|
catch (error) {
|
|
839
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read
|
|
871
|
+
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
840
872
|
}
|
|
841
873
|
const schema = getArtifactSchema(eventIdentity.family);
|
|
842
874
|
const structure = schema ? inspectArtifactContract(reviewContent, schema) : null;
|
|
843
875
|
if (structure && !structure.ok) {
|
|
844
876
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `shared artifact structure invalid: ${structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
845
877
|
}
|
|
846
|
-
const expected = expectedQualificationRelations(content, eventIdentity.family as 'review-analysis' | 'review-plan' | 'review-code');
|
|
847
|
-
if (!expected.ok) {
|
|
848
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `${expected.code}: ${expected.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
849
|
-
}
|
|
850
|
-
const qualification = validateQualificationAudit(content, reviewContent, {
|
|
851
|
-
family: eventIdentity.family as 'review-analysis' | 'review-plan' | 'review-code',
|
|
852
|
-
artifact: completedArtifact.name,
|
|
853
|
-
expectedUpstreamRelations: expected.relations
|
|
854
|
-
});
|
|
855
|
-
if (!qualification.ok) {
|
|
856
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `${qualification.code}: ${qualification.message}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
857
|
-
}
|
|
858
878
|
}
|
|
859
879
|
if (eventIdentity.family === 'analyze' || eventIdentity.family === 'plan' || eventIdentity.family === 'code') {
|
|
860
880
|
const localFamily = eventIdentity.family === 'analyze'
|
|
@@ -866,9 +886,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
866
886
|
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot read ${completedArtifact.name}: ${String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
867
887
|
}
|
|
868
888
|
const local = validateLocalArtifact(artifactContent, {
|
|
869
|
-
family: localFamily
|
|
870
|
-
taskContent: content,
|
|
871
|
-
artifact: completedArtifact.name
|
|
889
|
+
family: localFamily
|
|
872
890
|
});
|
|
873
891
|
if (!local.ok) {
|
|
874
892
|
return failed(normalized, {
|
|
@@ -903,7 +921,9 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
903
921
|
const capabilityOverride = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
904
922
|
? stateOverride
|
|
905
923
|
: allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID');
|
|
906
|
-
|
|
924
|
+
const safetyFailure = capability.reasonCode === 'TASK_NOT_ACTIVE'
|
|
925
|
+
|| capability.reasonCode === 'LIFECYCLE_EXECUTION_OPEN';
|
|
926
|
+
if (!capability.allowed && (normalized.initiator === 'orchestrator' || safetyFailure) && !capabilityOverride) {
|
|
907
927
|
const code = capability.reasonCode === 'INVALIDATION_INCOMPLETE' ? 'TASK_INVALIDATION_BLOCKED' : 'EVENT_TRANSITION_INVALID';
|
|
908
928
|
return failed(normalized, { code, message: `${capability.reasonCode}: ${capability.evidence.join(', ')}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
909
929
|
}
|
|
@@ -916,10 +936,11 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
916
936
|
);
|
|
917
937
|
if (findingCountError) return failed(normalized, findingCountError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
|
|
918
938
|
let orchestrationCompletion: OrchestrationStageCompletion | null = null;
|
|
919
|
-
if (eventIdentity.phase === 'completed' &&
|
|
939
|
+
if (eventIdentity.phase === 'completed' && normalized.orchestrated
|
|
940
|
+
&& eventIdentity.family !== 'manual-validation' && eventIdentity.family !== 'validation-run') {
|
|
920
941
|
const orchestrationStage = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
|
|
921
942
|
const execution = validateLifecycleExecution(normalized.taskRef, {
|
|
922
|
-
mode:
|
|
943
|
+
mode: 'orchestrated',
|
|
923
944
|
identity: {
|
|
924
945
|
stage: orchestrationStage,
|
|
925
946
|
round: normalized.round!,
|
|
@@ -984,16 +1005,9 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
984
1005
|
const frontmatterSet: Record<string, string> = { current_step: step, assigned_to: normalized.agent };
|
|
985
1006
|
if (currentFact) frontmatterSet.completion_facts = JSON.stringify(replaceCompletionFact(completionFacts(frontmatter), currentFact));
|
|
986
1007
|
let frontmatterRemove: string[] | undefined;
|
|
987
|
-
if (eventIdentity.phase === 'started' && artifactContext) {
|
|
988
|
-
try {
|
|
989
|
-
frontmatterSet.qualification_input_relations = JSON.stringify(qualificationRelationsForStarted(eventIdentity.family, artifactContext.inputs));
|
|
990
|
-
} catch (error) {
|
|
991
|
-
return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot capture qualification input relations: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
1008
|
if (eventIdentity.phase === 'started' && eventIdentity.family === 'code') {
|
|
995
|
-
const planInput = artifactContext?.inputs.find((input) => input.family === 'plan');
|
|
996
|
-
if (!planInput) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'code.started
|
|
1009
|
+
const planInput = artifactContext?.inputs.find((input) => input.family === 'plan' || input.family === 'analysis');
|
|
1010
|
+
if (!planInput) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'code.started lifecycle input context is unavailable' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
|
|
997
1011
|
try {
|
|
998
1012
|
frontmatterSet.code_input_artifact = planInput.name;
|
|
999
1013
|
frontmatterSet.code_input_sha256 = sha256File(planInput.path);
|
|
@@ -1016,11 +1030,15 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
|
|
|
1016
1030
|
frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
|
|
1017
1031
|
}
|
|
1018
1032
|
if (eventIdentity.phase === 'completed' && eventIdentity.family === 'review-code' && reviewContent !== null) {
|
|
1019
|
-
const reviewedCommit = approvedCleanReviewedCommit(
|
|
1033
|
+
const reviewedCommit = approvedCleanReviewedCommit(
|
|
1034
|
+
reviewContent,
|
|
1035
|
+
normalized.verdict,
|
|
1036
|
+
resolved.repoRoot,
|
|
1037
|
+
typeof frontmatter.branch === 'string' ? frontmatter.branch : ''
|
|
1038
|
+
);
|
|
1020
1039
|
if (reviewedCommit) frontmatterSet.last_reviewed_commit = reviewedCommit;
|
|
1021
1040
|
else if (normalized.verdict === 'approved') frontmatterSet.last_reviewed_commit = '';
|
|
1022
1041
|
}
|
|
1023
|
-
if (eventIdentity.phase === 'completed') frontmatterRemove = [...(frontmatterRemove ?? []), 'qualification_input_relations'];
|
|
1024
1042
|
if (eventIdentity.phase === 'started' && normalized.implementationInput) frontmatterSet.last_reviewed_commit = '';
|
|
1025
1043
|
const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
|
|
1026
1044
|
{ kind: 'frontmatter', set: frontmatterSet, remove: frontmatterRemove }
|